Enable IPC API for web (#138054)

* wip: ipc api

* wip: send message ports upfront

* address both inside and outside iframe

* fix build

* relay MessagePort to worker

* address api discussion feedback

* check for proposed api

* fix layer breakage

Co-authored-by: Alex Dima <alexdima@microsoft.com>
This commit is contained in:
João Moreno 2022-01-05 10:11:12 +00:00 committed by GitHub
parent 898073bcdb
commit 5e630c145f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 97 additions and 20 deletions

View file

@ -52,7 +52,9 @@ const CORE_TYPES = [
'trimEnd',
'trimLeft',
'trimRight',
'queueMicrotask'
'queueMicrotask',
'MessageChannel',
'MessagePort'
];
// Types that are defined in a common layer but are known to be only
// available in native environments should not be allowed in browser

View file

@ -53,7 +53,9 @@ const CORE_TYPES = [
'trimEnd',
'trimLeft',
'trimRight',
'queueMicrotask'
'queueMicrotask',
'MessageChannel',
'MessagePort'
];
// Types that are defined in a common layer but are known to be only

View file

@ -98,6 +98,12 @@ export interface IWorkspaceData extends IStaticWorkspaceData {
folders: { uri: UriComponents, name: string, index: number; }[];
}
export interface MessagePortLike {
postMessage(message: any, transfer?: any[]): void;
addEventListener(type: 'message', listener: (e: any) => any): void;
removeEventListener(type: 'message', listener: (e: any) => any): void;
}
export interface IInitData {
version: string;
commit?: string;
@ -114,6 +120,7 @@ export interface IInitData {
autoStart: boolean;
remote: { isRemote: boolean; authority: string | undefined; connectionData: IRemoteConnectionData | null; };
uiKind: UIKind;
messagePorts?: ReadonlyMap<string, MessagePortLike>;
}
export interface IConfigurationInitData extends IConfigurationData {
@ -2260,7 +2267,7 @@ export const MainContext = {
MainThreadTheming: createMainId<MainThreadThemingShape>('MainThreadTheming'),
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
MainThreadTimeline: createMainId<MainThreadTimelineShape>('MainThreadTimeline'),
MainThreadTesting: createMainId<MainThreadTestingShape>('MainThreadTesting')
MainThreadTesting: createMainId<MainThreadTestingShape>('MainThreadTesting'),
};
export const ExtHostContext = {

View file

@ -17,7 +17,7 @@ import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/co
import { ActivatedExtension, EmptyExtension, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator';
import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { MissingExtensionDependency, ActivationKind, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { MissingExtensionDependency, ActivationKind, checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import * as errors from 'vs/base/common/errors';
import type * as vscode from 'vscode';
@ -424,6 +424,10 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
const that = this;
let extension: vscode.Extension<any> | undefined;
const messagePort = isProposedApiEnabled(extensionDescription, 'ipc')
? this._initData.messagePorts?.get(ExtensionIdentifier.toKey(extensionDescription.identifier))
: undefined;
return Object.freeze<vscode.ExtensionContext>({
globalState,
workspaceState,
@ -449,7 +453,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
checkProposedApiEnabled(extensionDescription, 'extensionRuntime');
return that.extensionRuntime;
},
get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); }
get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); },
messagePassingProtocol: messagePort && {
onDidReceiveMessage: Event.fromDOMEventEmitter(messagePort, 'message', e => e.data),
postMessage: messagePort.postMessage.bind(messagePort) as any
}
});
});
}

View file

@ -187,6 +187,10 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
throw barrierError;
}
// Send over message ports for extension API
const messagePorts = this._environmentService.options?.messagePorts ?? new Map();
iframe.contentWindow!.postMessage(messagePorts, '*', [...messagePorts.values()]);
port.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {

View file

@ -45,7 +45,8 @@ export class ExtensionHostMain {
protocol: IMessagePassingProtocol,
initData: IInitData,
hostUtils: IHostUtils,
uriTransformer: IURITransformer | null
uriTransformer: IURITransformer | null,
messagePorts?: ReadonlyMap<string, MessagePort>
) {
this._isTerminating = false;
this._hostUtils = hostUtils;
@ -56,7 +57,7 @@ export class ExtensionHostMain {
// bootstrap services
const services = new ServiceCollection(...getSingletonServiceDescriptors());
services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData });
services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData, messagePorts });
services.set(IExtHostRpcService, new ExtHostRpcService(this._rpcProtocol));
services.set(IURITransformerService, new URITransformerService(uriTransformer));
services.set(IHostUtils, hostUtils);

View file

@ -25,6 +25,7 @@ export const allApiProposals = Object.freeze({
fsChunks: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts',
inlayHints: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlayHints.d.ts',
inlineCompletions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletions.d.ts',
ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts',
languageIcon: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageIcon.d.ts',
languageStatus: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.languageStatus.d.ts',
notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts',

View file

@ -216,18 +216,24 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
let onTerminate = (reason: string) => nativeClose();
export function create(): void {
const res = new ExtensionWorker();
export function create(): { onmessage: (message: any) => void } {
performance.mark(`code/extHost/willConnectToRenderer`);
connectToRenderer(res.protocol).then(data => {
performance.mark(`code/extHost/didWaitForInitData`);
const extHostMain = new ExtensionHostMain(
data.protocol,
data.initData,
hostUtil,
null,
);
const res = new ExtensionWorker();
onTerminate = (reason: string) => extHostMain.terminate(reason);
});
return {
onmessage(messagePorts: ReadonlyMap<string, MessagePort>) {
connectToRenderer(res.protocol).then(data => {
performance.mark(`code/extHost/didWaitForInitData`);
const extHostMain = new ExtensionHostMain(
data.protocol,
data.initData,
hostUtil,
null,
messagePorts
);
onTerminate = (reason: string) => extHostMain.terminate(reason);
});
}
};
}

View file

@ -4,7 +4,7 @@
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
child-src 'self' data: blob:;
script-src 'self' 'unsafe-eval' 'sha256-cb2sg39EJV8ABaSNFfWu/ou8o1xVXYK7jp90oZ9vpcg=' https:;
script-src 'self' 'unsafe-eval' 'sha256-7r+WjLnkogQ49YJMiebuJrtdmXlsN8evaIGRDcHnFCo=' https:;
connect-src 'self' https: wss: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*;"/>
</head>
<body>
@ -59,6 +59,8 @@
console.error(event.message, event.error);
sendError(event.error);
};
self.onmessage = (event) => worker.postMessage(event.data, event.ports);
} catch(err) {
console.error(err);
sendError(err);

View file

@ -540,6 +540,13 @@ interface IWorkbenchConstructionOptions {
//#endregion
//#region IPC
readonly messagePorts?: ReadonlyMap<ExtensionId, MessagePort>;
//#endregion
//#region Development options
readonly developmentOptions?: IDevelopmentOptions;

37
src/vscode-dts/vscode.proposed.ipc.d.ts vendored Normal file
View file

@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
/**
* A message passing protocol, which enables sending and receiving messages
* between two parties.
*/
export interface MessagePassingProtocol {
/**
* Fired when a message is received from the other party.
*/
readonly onDidReceiveMessage: Event<any>;
/**
* Post a message to the other party.
*
* @param message Body of the message. This must be a JSON serializable object.
* @param transfer A collection of `ArrayBuffer` instances which can be transferred
* to the other party, saving costly memory copy operations.
*/
postMessage(message: any, transfer?: ArrayBuffer[]): void;
}
export interface ExtensionContext {
/**
* When not `undefined`, this is an instance of {@link MessagePassingProtocol} in
* which the other party is owned by the web embedder.
*/
readonly messagePassingProtocol: MessagePassingProtocol | undefined;
}
}