Merge from vscode cfbd1999769f4f08dce29629fb92fdc0fac53829

This commit is contained in:
ADS Merger
2020-08-06 07:08:52 +00:00
parent 9c67832880
commit 540046ba00
362 changed files with 7588 additions and 6584 deletions

View File

@@ -17,19 +17,19 @@ import { AbstractExtensionService, parseScannedExtension } from 'vs/workbench/se
import { RemoteExtensionHost, IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
import { canExecuteOnWeb } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider';
import { Schemas } from 'vs/base/common/network';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { DeltaExtensionsResult } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
private _disposables = new DisposableStore();
private _remoteInitData: IRemoteExtensionHostInitData | null = null;
private _runningLocation: Map<string, ExtensionRunningLocation>;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@@ -54,6 +54,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
productService,
);
this._runningLocation = new Map<string, ExtensionRunningLocation>();
this._initialize();
this._initFetchFileSystem();
}
@@ -73,10 +75,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return {
getInitData: async () => {
const allExtensions = await this.getExtensions();
const webExtensions = allExtensions.filter(ext => canExecuteOnWeb(ext, this._productService, this._configService));
const localWebWorkerExtensions = filterByRunningLocation(allExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker);
return {
autoStart: true,
extensions: webExtensions
extensions: localWebWorkerExtensions
};
}
};
@@ -109,32 +111,26 @@ export class ExtensionService extends AbstractExtensionService implements IExten
protected async _scanAndHandleExtensions(): Promise<void> {
// fetch the remote environment
let [remoteEnv, localExtensions] = await Promise.all([
let [localExtensions, remoteEnv, remoteExtensions] = await Promise.all([
this._webExtensionsScannerService.scanAndTranslateExtensions().then(extensions => extensions.map(parseScannedExtension)),
this._remoteAgentService.getEnvironment(),
this._webExtensionsScannerService.scanExtensions().then(extensions => extensions.map(parseScannedExtension))
this._remoteAgentService.scanExtensions()
]);
localExtensions = this._checkEnabledAndProposedAPI(localExtensions);
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions);
const remoteAgentConnection = this._remoteAgentService.getConnection();
this._runningLocation = _determineRunningLocation(this._productService, this._configService, localExtensions, remoteExtensions, Boolean(remoteEnv && remoteAgentConnection));
let result: DeltaExtensionsResult;
localExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker);
remoteExtensions = filterByRunningLocation(remoteExtensions, this._runningLocation, ExtensionRunningLocation.Remote);
// local: only enabled and web'ish extension
localExtensions = localExtensions!.filter(ext => this._isEnabled(ext) && canExecuteOnWeb(ext, this._productService, this._configService));
this._checkEnableProposedApi(localExtensions);
if (!remoteEnv || !remoteAgentConnection) {
result = this._registry.deltaExtensions(localExtensions, []);
} else {
// remote: only enabled and none-web'ish extension
remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !canExecuteOnWeb(extension, this._productService, this._configService));
this._checkEnableProposedApi(remoteEnv.extensions);
// in case of overlap, the remote wins
const isRemoteExtension = new Set<string>();
remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier)));
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));
const result = this._registry.deltaExtensions(remoteExtensions.concat(localExtensions), []);
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
if (remoteEnv && remoteAgentConnection) {
// save for remote extension's init data
this._remoteInitData = {
connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAgentConnection.remoteAuthority),
@@ -143,16 +139,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten
extensionHostLogsPath: remoteEnv.extensionHostLogsPath,
globalStorageHome: remoteEnv.globalStorageHome,
workspaceStorageHome: remoteEnv.workspaceStorageHome,
extensions: remoteEnv.extensions,
allExtensions: remoteEnv.extensions.concat(localExtensions)
extensions: remoteExtensions,
allExtensions: this._registry.getAllExtensionDescriptions()
};
result = this._registry.deltaExtensions(remoteEnv.extensions.concat(localExtensions), []);
}
if (result.removedDueToLooping.length > 0) {
this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', ')));
}
this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions());
}
@@ -164,4 +155,55 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
}
const enum ExtensionRunningLocation {
None,
LocalWebWorker,
Remote
}
export function determineRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], allExtensionKinds: Map<string, ExtensionKind[]>, hasRemote: boolean): Map<string, ExtensionRunningLocation> {
const localExtensionsSet = new Set<string>();
localExtensions.forEach(ext => localExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
const remoteExtensionsSet = new Set<string>();
remoteExtensions.forEach(ext => remoteExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
const pickRunningLocation = (extension: IExtensionDescription): ExtensionRunningLocation => {
const isInstalledLocally = localExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
const isInstalledRemotely = remoteExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
const extensionKinds = allExtensionKinds.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
for (const extensionKind of extensionKinds) {
if (extensionKind === 'ui' && isInstalledRemotely) {
// ui extensions run remotely if possible
return ExtensionRunningLocation.Remote;
}
if (extensionKind === 'workspace' && isInstalledRemotely) {
// workspace extensions run remotely if possible
return ExtensionRunningLocation.Remote;
}
if (extensionKind === 'web' && isInstalledLocally) {
// web worker extensions run in the local web worker if possible
return ExtensionRunningLocation.LocalWebWorker;
}
}
return ExtensionRunningLocation.None;
};
const runningLocation = new Map<string, ExtensionRunningLocation>();
localExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext)));
remoteExtensions.forEach(ext => runningLocation.set(ExtensionIdentifier.toKey(ext.identifier), pickRunningLocation(ext)));
return runningLocation;
}
function _determineRunningLocation(productService: IProductService, configurationService: IConfigurationService, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], hasRemote: boolean): Map<string, ExtensionRunningLocation> {
const allExtensionKinds = new Map<string, ExtensionKind[]>();
localExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), getExtensionKind(ext, productService, configurationService)));
remoteExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), getExtensionKind(ext, productService, configurationService)));
return determineRunningLocation(localExtensions, remoteExtensions, allExtensionKinds, hasRemote);
}
function filterByRunningLocation(extensions: IExtensionDescription[], runningLocation: Map<string, ExtensionRunningLocation>, desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] {
return extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === desiredRunningLocation);
}
registerSingleton(IExtensionService, ExtensionService);

View File

@@ -5,7 +5,7 @@
import { getWorkerBootstrapUrl } from 'vs/base/worker/defaultWorkerFactory';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { VSBuffer } from 'vs/base/common/buffer';
import { createMessageOfType, MessageType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
@@ -16,6 +16,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as platform from 'vs/base/common/platform';
import * as dom from 'vs/base/browser/dom';
import { URI } from 'vs/base/common/uri';
import { IExtensionHost, ExtensionHostLogFileName, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -24,6 +25,11 @@ import { joinPath } from 'vs/base/common/resources';
import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { localize } from 'vs/nls';
import { generateUuid } from 'vs/base/common/uuid';
import { canceled, onUnexpectedError } from 'vs/base/common/errors';
import { WEB_WORKER_IFRAME } from 'vs/workbench/services/extensions/common/webWorkerIframe';
const WRAP_IN_IFRAME = true;
export interface IWebWorkerExtensionHostInitData {
readonly autoStart: boolean;
@@ -34,17 +40,17 @@ export interface IWebWorkerExtensionHostDataProvider {
getInitData(): Promise<IWebWorkerExtensionHostInitData>;
}
export class WebWorkerExtensionHost implements IExtensionHost {
export class WebWorkerExtensionHost extends Disposable implements IExtensionHost {
public readonly kind = ExtensionHostKind.LocalWebWorker;
public readonly remoteAuthority = null;
private _toDispose = new DisposableStore();
private _isTerminating: boolean = false;
private _protocol?: IMessagePassingProtocol;
private readonly _onDidExit = this._register(new Emitter<[number, string | null]>());
public readonly onExit: Event<[number, string | null]> = this._onDidExit.event;
private readonly _onDidExit = new Emitter<[number, string | null]>();
readonly onExit: Event<[number, string | null]> = this._onDidExit.event;
private _isTerminating: boolean;
private _protocolPromise: Promise<IMessagePassingProtocol> | null;
private _protocol: IMessagePassingProtocol | null;
private readonly _extensionHostLogsLocation: URI;
private readonly _extensionHostLogFile: URI;
@@ -58,76 +64,168 @@ export class WebWorkerExtensionHost implements IExtensionHost {
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@IProductService private readonly _productService: IProductService,
) {
super();
this._isTerminating = false;
this._protocolPromise = null;
this._protocol = null;
this._extensionHostLogsLocation = URI.file(this._environmentService.logsPath).with({ scheme: this._environmentService.logFile.scheme });
this._extensionHostLogFile = joinPath(this._extensionHostLogsLocation, `${ExtensionHostLogFileName}.log`);
}
async start(): Promise<IMessagePassingProtocol> {
if (!this._protocol) {
const emitter = new Emitter<VSBuffer>();
const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost');
const worker = new Worker(url, { name: 'WorkerExtensionHost' });
worker.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
return;
}
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
};
worker.onerror = (event) => {
console.error(event.message, event.error);
this._onDidExit.fire([81, event.message || event.error]);
};
// keep for cleanup
this._toDispose.add(emitter);
this._toDispose.add(toDisposable(() => worker.terminate()));
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
worker.postMessage(data, [data]);
}
};
// extension host handshake happens below
// (1) <== wait for: Ready
// (2) ==> send: init data
// (3) <== wait for: Initialized
await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Ready)));
protocol.send(VSBuffer.fromString(JSON.stringify(await this._createExtHostInitData())));
await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Initialized)));
// Register log channel for web worker exthost log
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id: 'webWorkerExtHostLog', label: localize('name', "Worker Extension Host"), file: this._extensionHostLogFile, log: true });
this._protocol = protocol;
public async start(): Promise<IMessagePassingProtocol> {
if (!this._protocolPromise) {
if (WRAP_IN_IFRAME && platform.isWeb) {
this._protocolPromise = this._startInsideIframe();
} else {
this._protocolPromise = this._startOutsideIframe();
}
this._protocolPromise.then(protocol => this._protocol = protocol);
}
return this._protocol;
return this._protocolPromise;
}
dispose(): void {
if (!this._protocol) {
this._toDispose.dispose();
return;
private _startInsideIframe(): Promise<IMessagePassingProtocol> {
const emitter = this._register(new Emitter<VSBuffer>());
const iframe = document.createElement('iframe');
iframe.setAttribute('class', 'web-worker-ext-host-iframe');
iframe.setAttribute('sandbox', 'allow-scripts');
iframe.style.display = 'none';
const vscodeWebWorkerExtHostId = generateUuid();
const workerUrl = require.toUrl('../worker/extensionHostWorkerMain.js');
const workerSrc = getWorkerBootstrapUrl(workerUrl, 'WorkerExtensionHost', true);
const escapeAttribute = (value: string): string => {
return value.replace(/"/g, '&quot;');
};
const html = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-eval' '${WEB_WORKER_IFRAME.sha}' *; worker-src data:; connect-src *" />
<meta id="vscode-worker-src" data-value="${escapeAttribute(workerSrc)}" />
<meta id="vscode-web-worker-ext-host-id" data-value="${escapeAttribute(vscodeWebWorkerExtHostId)}" />
</head>
<body>
<script>${WEB_WORKER_IFRAME.js}</script>
</body>
</html>`;
const iframeContent = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
iframe.setAttribute('src', iframeContent);
this._register(dom.addDisposableListener(window, 'message', (event) => {
if (event.source !== iframe.contentWindow) {
return;
}
if (event.data.vscodeWebWorkerExtHostId !== vscodeWebWorkerExtHostId) {
return;
}
if (event.data.error) {
const { name, message, stack } = event.data.error;
const err = new Error();
err.message = message;
err.name = name;
err.stack = stack;
onUnexpectedError(err);
this._onDidExit.fire([18, err.message]);
return;
}
const { data } = event.data;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
return;
}
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
}));
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
iframe.contentWindow!.postMessage({
vscodeWebWorkerExtHostId,
data: data
}, '*', [data]);
}
};
document.body.appendChild(iframe);
this._register(toDisposable(() => iframe.remove()));
return this._performHandshake(protocol);
}
private _startOutsideIframe(): Promise<IMessagePassingProtocol> {
const emitter = new Emitter<VSBuffer>();
const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost');
const worker = new Worker(url, { name: 'WorkerExtensionHost' });
worker.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
return;
}
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
};
worker.onerror = (event) => {
console.error(event.message, event.error);
this._onDidExit.fire([81, event.message || event.error]);
};
// keep for cleanup
this._register(emitter);
this._register(toDisposable(() => worker.terminate()));
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
worker.postMessage(data, [data]);
}
};
return this._performHandshake(protocol);
}
private async _performHandshake(protocol: IMessagePassingProtocol): Promise<IMessagePassingProtocol> {
// extension host handshake happens below
// (1) <== wait for: Ready
// (2) ==> send: init data
// (3) <== wait for: Initialized
await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Ready)));
if (this._isTerminating) {
throw canceled();
}
protocol.send(VSBuffer.fromString(JSON.stringify(await this._createExtHostInitData())));
if (this._isTerminating) {
throw canceled();
}
await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Initialized)));
if (this._isTerminating) {
throw canceled();
}
// Register log channel for web worker exthost log
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id: 'webWorkerExtHostLog', label: localize('name', "Worker Extension Host"), file: this._extensionHostLogFile, log: true });
return protocol;
}
public dispose(): void {
if (this._isTerminating) {
return;
}
this._isTerminating = true;
this._protocol.send(createMessageOfType(MessageType.Terminate));
setTimeout(() => this._toDispose.dispose(), 10 * 1000);
if (this._protocol) {
this._protocol.send(createMessageOfType(MessageType.Terminate));
}
super.dispose();
}
getInspectPort(): number | undefined {

View File

@@ -20,7 +20,7 @@ import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensi
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { ExtensionIdentifier, IExtensionDescription, IScannedExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -29,7 +29,7 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
export function parseScannedExtension(extension: IScannedExtension): IExtensionDescription {
export function parseScannedExtension(extension: ITranslatedScannedExtension): IExtensionDescription {
return {
identifier: new ExtensionIdentifier(`${extension.packageJSON.publisher}.${extension.packageJSON.name}`),
isBuiltin: extension.type === ExtensionType.System,

View File

@@ -27,7 +27,6 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens
// Enable to see detailed message communication between window and extension host
const LOG_EXTENSION_HOST_COMMUNICATION = false;
const LOG_USE_COLORS = true;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
export class ExtensionHostManager extends Disposable {
@@ -38,9 +37,9 @@ export class ExtensionHostManager extends Disposable {
public readonly onDidChangeResponsiveState: Event<ResponsiveState> = this._onDidChangeResponsiveState.event;
/**
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
* A map of already requested activation events to speed things up if the same activation event is triggered multiple times.
*/
private readonly _finishedActivateEvents: { [activationEvent: string]: boolean; };
private readonly _cachedActivationEvents: Map<string, Promise<void>>;
private _rpcProtocol: RPCProtocol | null;
private readonly _customers: IDisposable[];
private readonly _extensionHost: IExtensionHost;
@@ -57,7 +56,7 @@ export class ExtensionHostManager extends Disposable {
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
) {
super();
this._finishedActivateEvents = Object.create(null);
this._cachedActivationEvents = new Map<string, Promise<void>>();
this._rpcProtocol = null;
this._customers = [];
@@ -221,19 +220,23 @@ export class ExtensionHostManager extends Disposable {
}
public activateByEvent(activationEvent: string): Promise<void> {
if (this._finishedActivateEvents[activationEvent] || !this._proxy) {
return NO_OP_VOID_PROMISE;
if (!this._cachedActivationEvents.has(activationEvent)) {
this._cachedActivationEvents.set(activationEvent, this._activateByEvent(activationEvent));
}
return this._proxy.then((proxy) => {
if (!proxy) {
// this case is already covered above and logged.
// i.e. the extension host could not be started
return NO_OP_VOID_PROMISE;
}
return proxy.value.$activateByEvent(activationEvent);
}).then(() => {
this._finishedActivateEvents[activationEvent] = true;
});
return this._cachedActivationEvents.get(activationEvent)!;
}
private async _activateByEvent(activationEvent: string): Promise<void> {
if (!this._proxy) {
return;
}
const proxy = await this._proxy;
if (!proxy) {
// this case is already covered above and logged.
// i.e. the extension host could not be started
return;
}
return proxy.value.$activateByEvent(activationEvent);
}
public async getInspectPort(tryEnableInspector: boolean): Promise<number> {

View File

@@ -150,11 +150,13 @@ const extensionKindSchema: IJSONSchema = {
type: 'string',
enum: [
'ui',
'workspace'
'workspace',
'web'
],
enumDescriptions: [
nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."),
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.")
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote."),
nls.localize('web', "Web worker extension kind. Such an extension can execute in a web worker extension host.")
],
};

View File

@@ -59,18 +59,29 @@ export function getExtensionKind(manifest: IExtensionManifest, productService: I
return toArray(result);
}
return deduceExtensionKind(manifest);
}
export function deduceExtensionKind(manifest: IExtensionManifest): ExtensionKind[] {
// Not an UI extension if it has main
if (manifest.main) {
if (manifest.browser) {
return ['workspace', 'web'];
}
return ['workspace'];
}
// Not an UI extension if it has dependencies or an extension pack
if (manifest.browser) {
return ['web'];
}
// Not an UI nor web extension if it has dependencies or an extension pack
if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) {
return ['workspace'];
}
if (manifest.contributes) {
// Not an UI extension if it has no ui contributions
// Not an UI nor web extension if it has no ui contributions
for (const contribution of Object.keys(manifest.contributes)) {
if (!isUIExtensionPoint(contribution)) {
return ['workspace'];
@@ -78,7 +89,7 @@ export function getExtensionKind(manifest: IExtensionManifest, productService: I
}
}
return ['ui', 'workspace'];
return ['ui', 'workspace', 'web'];
}
let _uiExtensionPoints: Set<string> | null = null;

View File

@@ -96,7 +96,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
}
},
signService: this._signService,
logService: this._logService
logService: this._logService,
ipcLogger: null
};
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => {
@@ -204,8 +205,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
const [telemetryInfo, remoteInitData] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]);
// Collect all identifiers for extension ids which can be considered "resolved"
const resolvedExtensions = remoteInitData.allExtensions.filter(extension => !extension.main).map(extension => extension.identifier);
const hostExtensions = remoteInitData.allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
const resolvedExtensions = remoteInitData.allExtensions.filter(extension => !extension.main && !extension.browser).map(extension => extension.identifier);
const hostExtensions = remoteInitData.allExtensions.filter(extension => (extension.main || extension.browser) && extension.api === 'none').map(extension => extension.identifier);
const workspace = this._contextService.getWorkspace();
return {
commit: this._productService.commit,

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const WEB_WORKER_IFRAME = {
sha: 'sha256-rSINb5Ths99Zj4Ml59jEdHS4WbO+H5Iw+oyRmyi2MLw=',
js: `
(function() {
const workerSrc = document.getElementById('vscode-worker-src').getAttribute('data-value');
const worker = new Worker(workerSrc, { name: 'WorkerExtensionHost' });
const vscodeWebWorkerExtHostId = document.getElementById('vscode-web-worker-ext-host-id').getAttribute('data-value');
worker.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('Unknown data received', data);
window.parent.postMessage({
vscodeWebWorkerExtHostId,
error: {
name: 'Error',
message: 'Unknown data received',
stack: []
}
}, '*');
return;
}
window.parent.postMessage({
vscodeWebWorkerExtHostId,
data: data
}, '*', [data]);
};
worker.onerror = (event) => {
console.error(event.message, event.error);
window.parent.postMessage({
vscodeWebWorkerExtHostId,
error: {
name: event.error ? event.error.name : '',
message: event.error ? event.error.message : '',
stack: event.error ? event.error.stack : []
}
}, '*');
};
window.addEventListener('message', function(event) {
if (event.source !== window.parent) {
return;
}
if (event.data.vscodeWebWorkerExtHostId !== vscodeWebWorkerExtHostId) {
return;
}
worker.postMessage(event.data.data, [event.data.data]);
}, false);
})();
`
};

View File

@@ -24,7 +24,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IExtensionService, toExtension, ExtensionHostKind, IExtensionHost, webWorkerExtHostConfig } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription, ExtensionKind } from 'vs/platform/extensions/common/extensions';
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
@@ -377,7 +377,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
private async _scanAllLocalExtensions(): Promise<IExtensionDescription[]> {
return flatten(await Promise.all([
this._extensionScanner.scannedExtensions,
this._webExtensionsScannerService.scanExtensions().then(extensions => extensions.map(parseScannedExtension))
this._webExtensionsScannerService.scanAndTranslateExtensions().then(extensions => extensions.map(parseScannedExtension))
]));
}
@@ -386,7 +386,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
getInitData: async () => {
if (isInitialStart) {
const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions());
const runningLocation = determineRunningLocation(this._productService, this._configurationService, localExtensions, [], false, this._enableLocalWebWorker);
const runningLocation = _determineRunningLocation(this._productService, this._configurationService, localExtensions, [], false, this._enableLocalWebWorker);
const localProcessExtensions = filterByRunningLocation(localExtensions, runningLocation, desiredRunningLocation);
return {
autoStart: false,
@@ -503,8 +503,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess)!;
let localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions());
const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions());
let remoteEnv: IRemoteAgentEnvironment | null = null;
let remoteExtensions: IExtensionDescription[] = [];
if (remoteAuthority) {
let resolverResult: ResolverResult;
@@ -543,7 +544,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
// fetch the remote environment
remoteEnv = await this._remoteAgentService.getEnvironment();
[remoteEnv, remoteExtensions] = await Promise.all([
this._remoteAgentService.getEnvironment(),
this._remoteAgentService.scanExtensions()
]);
remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions);
if (!remoteEnv) {
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('getEnvironmentFailure', "Could not fetch remote environment") });
@@ -553,14 +558,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
}
await this._startLocalExtensionHost(localExtensions, remoteAuthority, remoteEnv);
await this._startLocalExtensionHost(localExtensions, remoteAuthority, remoteEnv, remoteExtensions);
}
private async _startLocalExtensionHost(localExtensions: IExtensionDescription[], remoteAuthority: string | undefined = undefined, remoteEnv: IRemoteAgentEnvironment | null = null): Promise<void> {
private async _startLocalExtensionHost(localExtensions: IExtensionDescription[], remoteAuthority: string | undefined = undefined, remoteEnv: IRemoteAgentEnvironment | null = null, remoteExtensions: IExtensionDescription[] = []): Promise<void> {
let remoteExtensions = remoteEnv ? this._checkEnabledAndProposedAPI(remoteEnv.extensions) : [];
this._runningLocation = determineRunningLocation(this._productService, this._configurationService, localExtensions, remoteExtensions, Boolean(remoteAuthority), this._enableLocalWebWorker);
this._runningLocation = _determineRunningLocation(this._productService, this._configurationService, localExtensions, remoteExtensions, Boolean(remoteAuthority), this._enableLocalWebWorker);
// remove non-UI extensions from the local extensions
const localProcessExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalProcess);
@@ -686,7 +689,7 @@ const enum ExtensionRunningLocation {
Remote
}
function determineRunningLocation(productService: IProductService, configurationService: IConfigurationService, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], hasRemote: boolean, hasLocalWebWorker: boolean): Map<string, ExtensionRunningLocation> {
export function determineRunningLocation(localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], allExtensionKinds: Map<string, ExtensionKind[]>, hasRemote: boolean, hasLocalWebWorker: boolean): Map<string, ExtensionRunningLocation> {
const localExtensionsSet = new Set<string>();
localExtensions.forEach(ext => localExtensionsSet.add(ExtensionIdentifier.toKey(ext.identifier)));
@@ -696,7 +699,8 @@ function determineRunningLocation(productService: IProductService, configuration
const pickRunningLocation = (extension: IExtensionDescription): ExtensionRunningLocation => {
const isInstalledLocally = localExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
const isInstalledRemotely = remoteExtensionsSet.has(ExtensionIdentifier.toKey(extension.identifier));
for (const extensionKind of getExtensionKind(extension, productService, configurationService)) {
const extensionKinds = allExtensionKinds.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
for (const extensionKind of extensionKinds) {
if (extensionKind === 'ui' && isInstalledLocally) {
// ui extensions run locally if possible
return ExtensionRunningLocation.LocalProcess;
@@ -711,10 +715,6 @@ function determineRunningLocation(productService: IProductService, configuration
}
if (extensionKind === 'web' && isInstalledLocally && hasLocalWebWorker) {
// web worker extensions run in the local web worker if possible
if (typeof extension.browser !== 'undefined') {
// The "browser" field determines the entry point
(<any>extension).main = extension.browser;
}
return ExtensionRunningLocation.LocalWebWorker;
}
}
@@ -727,6 +727,13 @@ function determineRunningLocation(productService: IProductService, configuration
return runningLocation;
}
function _determineRunningLocation(productService: IProductService, configurationService: IConfigurationService, localExtensions: IExtensionDescription[], remoteExtensions: IExtensionDescription[], hasRemote: boolean, hasLocalWebWorker: boolean): Map<string, ExtensionRunningLocation> {
const allExtensionKinds = new Map<string, ExtensionKind[]>();
localExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), getExtensionKind(ext, productService, configurationService)));
remoteExtensions.forEach(ext => allExtensionKinds.set(ExtensionIdentifier.toKey(ext.identifier), getExtensionKind(ext, productService, configurationService)));
return determineRunningLocation(localExtensions, remoteExtensions, allExtensionKinds, hasRemote, hasLocalWebWorker);
}
function filterByRunningLocation(extensions: IExtensionDescription[], runningLocation: Map<string, ExtensionRunningLocation>, desiredRunningLocation: ExtensionRunningLocation): IExtensionDescription[] {
return extensions.filter(ext => runningLocation.get(ExtensionIdentifier.toKey(ext.identifier)) === desiredRunningLocation);
}

View File

@@ -44,7 +44,6 @@ import { joinPath } from 'vs/base/common/resources';
import { Registry } from 'vs/platform/registry/common/platform';
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { isUUID } from 'vs/base/common/uuid';
export interface ILocalProcessExtensionHostInitData {
readonly autoStart: boolean;
@@ -183,23 +182,18 @@ export class LocalProcessExtensionHost implements IExtensionHost {
opts.execArgv = ['--inspect-port=0'];
}
// On linux crash reporter needs to be started on child node processes explicitly
if (platform.isLinux) {
const crashReporterStartOptions: CrashReporterStartOptions = {
// Enable the crash reporter depending on environment for local reporting
const crashesDirectory = this._environmentService.crashReporterDirectory;
if (crashesDirectory) {
const crashReporterOptions: CrashReporterStartOptions = {
companyName: this._productService.crashReporter?.companyName || 'Microsoft',
productName: this._productService.crashReporter?.productName || this._productService.nameShort,
submitURL: '',
uploadToServer: false
uploadToServer: false,
crashesDirectory
};
const crashReporterId = this._environmentService.crashReporterId; // crashReporterId is set by the main process only when crash reporting is enabled by the user.
const appcenter = this._productService.appCenter;
const uploadCrashesToServer = !this._environmentService.crashReporterDirectory; // only upload unless --crash-reporter-directory is provided
if (uploadCrashesToServer && appcenter && crashReporterId && isUUID(crashReporterId)) {
const submitURL = appcenter[`linux-x64`];
crashReporterStartOptions.submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId);
crashReporterStartOptions.uploadToServer = true;
}
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions);
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions);
}
// Run Extension Host as fork of current process

View File

@@ -393,9 +393,8 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
notices.push(nls.localize('extensionDescription.main1', "property `{0}` can be omitted or must be of type `string`", 'main'));
return false;
} else {
let normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.main);
if (normalizedAbsolutePath.indexOf(extensionFolderPath)) {
const normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.main);
if (!normalizedAbsolutePath.startsWith(extensionFolderPath)) {
notices.push(nls.localize('extensionDescription.main2', "Expected `main` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", normalizedAbsolutePath, extensionFolderPath));
// not a failure case
}
@@ -405,6 +404,22 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
return false;
}
}
if (typeof extensionDescription.browser !== 'undefined') {
if (typeof extensionDescription.browser !== 'string') {
notices.push(nls.localize('extensionDescription.browser1', "property `{0}` can be omitted or must be of type `string`", 'browser'));
return false;
} else {
const normalizedAbsolutePath = path.join(extensionFolderPath, extensionDescription.browser);
if (!normalizedAbsolutePath.startsWith(extensionFolderPath)) {
notices.push(nls.localize('extensionDescription.browser2', "Expected `browser` ({0}) to be included inside extension's folder ({1}). This might make the extension non-portable.", normalizedAbsolutePath, extensionFolderPath));
// not a failure case
}
}
if (typeof extensionDescription.activationEvents === 'undefined') {
notices.push(nls.localize('extensionDescription.browser3', "properties `{0}` and `{1}` must both be specified or must both be omitted", 'activationEvents', 'browser'));
return false;
}
}
return true;
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { deduceExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IExtensionManifest, ExtensionKind } from 'vs/platform/extensions/common/extensions';
suite('ExtensionKind', () => {
function check(manifest: Partial<IExtensionManifest>, expected: ExtensionKind[]): void {
assert.deepEqual(deduceExtensionKind(<IExtensionManifest>manifest), expected);
}
test('declarative with extension dependencies => workspace', () => {
check({ extensionDependencies: ['ext1'] }, ['workspace']);
});
test('declarative extension pack => workspace', () => {
check({ extensionPack: ['ext1', 'ext2'] }, ['workspace']);
});
test('declarative with unknown contribution point => workspace', () => {
check({ contributes: <any>{ 'unknownPoint': { something: true } } }, ['workspace']);
});
test('simple declarative => ui, workspace, web', () => {
check({}, ['ui', 'workspace', 'web']);
});
test('only browser => web', () => {
check({ browser: 'main.browser.js' }, ['web']);
});
test('only main => workspace', () => {
check({ main: 'main.js' }, ['workspace']);
});
test('main and browser => workspace, web', () => {
check({ main: 'main.js', browser: 'main.browser.js' }, ['workspace', 'web']);
});
});

View File

@@ -10,6 +10,7 @@ import { isMessageOfType, MessageType, createMessageOfType } from 'vs/workbench/
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { ExtensionHostMain } from 'vs/workbench/services/extensions/common/extensionHostMain';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import * as path from 'vs/base/common/path';
import 'vs/workbench/api/common/extHost.common.services';
import 'vs/workbench/api/worker/extHost.worker.services';
@@ -35,6 +36,17 @@ self.postMessage = () => console.trace(`'postMessage' has been blocked`);
const nativeAddEventLister = addEventListener.bind(self);
self.addEventLister = () => console.trace(`'addEventListener' has been blocked`);
if (location.protocol === 'data:') {
// make sure new Worker(...) always uses data:
const _Worker = Worker;
Worker = <any>function (stringUrl: string | URL, options?: WorkerOptions) {
const js = `importScripts('${stringUrl}');`;
options = options || {};
options.name = options.name || path.basename(stringUrl.toString());
return new _Worker(`data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`, options);
};
}
//#endregion ---
const hostUtil = new class implements IHostUtils {