mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode cfbd1999769f4f08dce29629fb92fdc0fac53829
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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, '"');
|
||||
};
|
||||
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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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.")
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
})();
|
||||
`
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user