mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 (#14883)
* Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 * Bump distro * Upgrade GCC to 4.9 due to yarn install errors * Update build image * Fix bootstrap base url * Bump distro * Fix build errors * Update source map file * Disable checkbox for blocking migration issues (#15131) * disable checkbox for blocking issues * wip * disable checkbox fixes * fix strings * Remove duplicate tsec command * Default to off for tab color if settings not present * re-skip failing tests * Fix mocha error * Bump sqlite version & fix notebooks search view * Turn off esbuild warnings * Update esbuild log level * Fix overflowactionbar tests * Fix ts-ignore in dropdown tests * cleanup/fixes * Fix hygiene * Bundle in entire zone.js module * Remove extra constructor param * bump distro for web compile break * bump distro for web compile break v2 * Undo log level change * New distro * Fix integration test scripts * remove the "no yarn.lock changes" workflow * fix scripts v2 * Update unit test scripts * Ensure ads-kerberos2 updates in .vscodeignore * Try fix unit tests * Upload crash reports * remove nogpu * always upload crashes * Use bash script * Consolidate data/ext dir names * Create in tmp directory Co-authored-by: chlafreniere <hichise@gmail.com> Co-authored-by: Christopher Suh <chsuh@microsoft.com> Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
@@ -84,10 +84,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
protected _onExtensionHostCrashed(extensionHost: ExtensionHostManager, code: number, signal: string | null): void {
|
||||
super._onExtensionHostCrashed(extensionHost, code, signal);
|
||||
if (extensionHost.kind === ExtensionHostKind.LocalWebWorker) {
|
||||
if (code === ExtensionHostExitCode.StartTimeout10s) {
|
||||
if (code === ExtensionHostExitCode.StartTimeout60s) {
|
||||
this._notificationService.prompt(
|
||||
Severity.Error,
|
||||
nls.localize('extensionService.startTimeout', "The Web Worker Extension Host did not start in 10s."),
|
||||
nls.localize('extensionService.startTimeout', "The Web Worker Extension Host did not start in 60s."),
|
||||
[]
|
||||
);
|
||||
return;
|
||||
@@ -210,6 +210,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
}
|
||||
|
||||
public _onExtensionHostExit(code: number): void {
|
||||
// Dispose everything associated with the extension host
|
||||
this._stopExtensionHosts();
|
||||
|
||||
// We log the exit code to the console. Do NOT remove this
|
||||
// code as the automated integration tests in browser rely
|
||||
// on this message to exit properly.
|
||||
|
||||
@@ -30,6 +30,7 @@ import { canceled, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { NewWorkerMessage, TerminateWorkerMessage } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
|
||||
|
||||
export interface IWebWorkerExtensionHostInitData {
|
||||
readonly autoStart: boolean;
|
||||
@@ -40,6 +41,17 @@ export interface IWebWorkerExtensionHostDataProvider {
|
||||
getInitData(): Promise<IWebWorkerExtensionHostInitData>;
|
||||
}
|
||||
|
||||
const ttPolicy = window.trustedTypes?.createPolicy('webWorkerExtensionHost', { createScriptURL: value => value });
|
||||
|
||||
const ttPolicyNestedWorker = window.trustedTypes?.createPolicy('webNestedWorkerExtensionHost', {
|
||||
createScriptURL(value) {
|
||||
if (value.startsWith('blob:')) {
|
||||
return value;
|
||||
}
|
||||
throw new Error(value + ' is NOT allowed');
|
||||
}
|
||||
});
|
||||
|
||||
export class WebWorkerExtensionHost extends Disposable implements IExtensionHost {
|
||||
|
||||
public readonly kind = ExtensionHostKind.LocalWebWorker;
|
||||
@@ -154,8 +166,8 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
|
||||
};
|
||||
|
||||
startTimeout = setTimeout(() => {
|
||||
rejectBarrier(ExtensionHostExitCode.StartTimeout10s, new Error('The Web Worker Extension Host did not start in 10s'));
|
||||
}, 10000);
|
||||
rejectBarrier(ExtensionHostExitCode.StartTimeout60s, new Error('The Web Worker Extension Host did not start in 60s'));
|
||||
}, 60000);
|
||||
|
||||
this._register(dom.addDisposableListener(window, 'message', (event) => {
|
||||
if (event.source !== iframe.contentWindow) {
|
||||
@@ -217,20 +229,48 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
|
||||
const emitter = new Emitter<VSBuffer>();
|
||||
|
||||
const url = getWorkerBootstrapUrl(FileAccess.asBrowserUri('../worker/extensionHostWorkerMain.js', require).toString(true), 'WorkerExtensionHost');
|
||||
const worker = new Worker(url, { name: 'WorkerExtensionHost' });
|
||||
const worker = new Worker(ttPolicy?.createScriptURL(url) as unknown as string ?? url, { name: 'WorkerExtensionHost' });
|
||||
|
||||
const barrier = new Barrier();
|
||||
let port!: MessagePort;
|
||||
|
||||
const nestedWorker = new Map<string, Worker>();
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
const { data } = event;
|
||||
if (barrier.isOpen() || !(data instanceof MessagePort)) {
|
||||
|
||||
const data: MessagePort | NewWorkerMessage | TerminateWorkerMessage = event.data;
|
||||
|
||||
if (data instanceof MessagePort) {
|
||||
// receiving a message port which is used to communicate
|
||||
// with the web worker extension host
|
||||
if (barrier.isOpen()) {
|
||||
console.warn('UNEXPECTED message', event);
|
||||
this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, 'received a message port AFTER opening the barrier']);
|
||||
return;
|
||||
}
|
||||
port = data;
|
||||
barrier.open();
|
||||
|
||||
|
||||
} else if (data?.type === '_newWorker') {
|
||||
// receiving a message to create a new nested/child worker
|
||||
const worker = new Worker((ttPolicyNestedWorker?.createScriptURL(data.url) ?? data.url) as string, data.options);
|
||||
worker.postMessage(data.port, [data.port]);
|
||||
worker.onerror = console.error.bind(console);
|
||||
nestedWorker.set(data.id, worker);
|
||||
|
||||
} else if (data?.type === '_terminateWorker') {
|
||||
// receiving a message to terminate nested/child worker
|
||||
if (nestedWorker.has(data.id)) {
|
||||
nestedWorker.get(data.id)!.terminate();
|
||||
nestedWorker.delete(data.id);
|
||||
}
|
||||
|
||||
} else {
|
||||
// all other messages are an error
|
||||
console.warn('UNEXPECTED message', event);
|
||||
this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, 'UNEXPECTED message']);
|
||||
return;
|
||||
}
|
||||
port = data;
|
||||
barrier.open();
|
||||
};
|
||||
|
||||
// await MessagePort and use it to directly communicate
|
||||
@@ -326,7 +366,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
|
||||
appUriScheme: this._productService.urlProtocol,
|
||||
appLanguage: platform.language,
|
||||
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
|
||||
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
|
||||
extensionTestsLocationURI: undefined, // never run extension tests in web worker extension host
|
||||
globalStorageHome: this._environmentService.globalStorageHome,
|
||||
workspaceStorageHome: this._environmentService.workspaceStorageHome,
|
||||
webviewResourceRoot: this._environmentService.webviewResourceRoot,
|
||||
|
||||
@@ -407,21 +407,20 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
//#endregion
|
||||
|
||||
protected async _initialize(): Promise<void> {
|
||||
perf.mark('willLoadExtensions');
|
||||
perf.mark('code/willLoadExtensions');
|
||||
this._startExtensionHosts(true, []);
|
||||
this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions'));
|
||||
await this._scanAndHandleExtensions();
|
||||
this._releaseBarrier();
|
||||
perf.mark('code/didLoadExtensions');
|
||||
}
|
||||
|
||||
private _releaseBarrier(): void {
|
||||
perf.mark('extensionHostReady');
|
||||
this._installedExtensionsReady.open();
|
||||
this._onDidRegisterExtensions.fire(undefined);
|
||||
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
|
||||
}
|
||||
|
||||
private _stopExtensionHosts(): void {
|
||||
protected _stopExtensionHosts(): void {
|
||||
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
|
||||
this._extensionHostActiveExtensions.forEach((value) => {
|
||||
previouslyActivatedExtensionIds.push(value);
|
||||
@@ -465,7 +464,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
|
||||
protected _onExtensionHostCrashed(extensionHost: ExtensionHostManager, code: number, signal: string | null): void {
|
||||
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
|
||||
this._stopExtensionHosts();
|
||||
if (extensionHost.kind === ExtensionHostKind.LocalProcess) {
|
||||
this._stopExtensionHosts();
|
||||
}
|
||||
}
|
||||
|
||||
//#region IExtensionService
|
||||
@@ -644,11 +645,13 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg);
|
||||
const availableExtensions = this._registry.getAllExtensionDescriptions();
|
||||
const extensionPoints = ExtensionsRegistry.getExtensionPoints();
|
||||
perf.mark('code/willHandleExtensionPoints');
|
||||
for (const extensionPoint of extensionPoints) {
|
||||
if (affectedExtensionPoints[extensionPoint.name]) {
|
||||
AbstractExtensionService._handleExtensionPoint(extensionPoint, availableExtensions, messageHandler);
|
||||
}
|
||||
}
|
||||
perf.mark('code/didHandleExtensionPoints');
|
||||
}
|
||||
|
||||
private _handleExtensionPointMessage(msg: IMessage) {
|
||||
@@ -699,9 +702,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
||||
});
|
||||
}
|
||||
}
|
||||
perf.mark(`willHandleExtensionPoint/${extensionPoint.name}`);
|
||||
extensionPoint.acceptUsers(users);
|
||||
perf.mark(`didHandleExtensionPoint/${extensionPoint.name}`);
|
||||
}
|
||||
|
||||
private _showMessageToUser(severity: Severity, msg: string): void {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
@@ -36,6 +37,7 @@ export class ExtensionHostMain {
|
||||
private _isTerminating: boolean;
|
||||
private readonly _hostUtils: IHostUtils;
|
||||
private readonly _extensionService: IExtHostExtensionService;
|
||||
private readonly _logService: ILogService;
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
@@ -64,11 +66,11 @@ export class ExtensionHostMain {
|
||||
const terminalService = instaService.invokeFunction(accessor => accessor.get(IExtHostTerminalService));
|
||||
this._disposables.add(terminalService);
|
||||
|
||||
const logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
|
||||
this._disposables.add(logService);
|
||||
this._logService = instaService.invokeFunction(accessor => accessor.get(ILogService));
|
||||
|
||||
logService.info('extension host started');
|
||||
logService.trace('initData', initData);
|
||||
performance.mark(`code/extHost/didCreateServices`);
|
||||
this._logService.info('extension host started');
|
||||
this._logService.trace('initData', initData);
|
||||
|
||||
// ugly self - inject
|
||||
// must call initialize *after* creating the extension service
|
||||
@@ -110,12 +112,14 @@ export class ExtensionHostMain {
|
||||
});
|
||||
}
|
||||
|
||||
terminate(): void {
|
||||
terminate(reason: string): void {
|
||||
if (this._isTerminating) {
|
||||
// we are already shutting down...
|
||||
return;
|
||||
}
|
||||
this._isTerminating = true;
|
||||
this._logService.info(`extension host terminating: ${reason}`);
|
||||
this._logService.flush();
|
||||
|
||||
this._disposables.dispose();
|
||||
|
||||
@@ -127,7 +131,12 @@ export class ExtensionHostMain {
|
||||
|
||||
// Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds
|
||||
setTimeout(() => {
|
||||
Promise.race([timeout(4000), extensionsDeactivated]).finally(() => this._hostUtils.exit());
|
||||
Promise.race([timeout(4000), extensionsDeactivated]).finally(() => {
|
||||
this._logService.info(`exiting with code 0`);
|
||||
this._logService.flush();
|
||||
this._logService.dispose();
|
||||
this._hostUtils.exit(0);
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ export class ExtensionHostManager extends Disposable {
|
||||
this._register(this._rpcProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState)));
|
||||
const extHostContext: IExtHostContext = {
|
||||
remoteAuthority: this._extensionHost.remoteAuthority,
|
||||
extensionHostKind: this.kind,
|
||||
getProxy: <T>(identifier: ProxyIdentifier<T>): T => this._rpcProtocol!.getProxy(identifier),
|
||||
set: <T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R => this._rpcProtocol!.set(identifier, instance),
|
||||
assertRegistered: (identifiers: ProxyIdentifier<any>[]): void => this._rpcProtocol!.assertRegistered(identifiers),
|
||||
@@ -262,12 +263,12 @@ export class ExtensionHostManager extends Disposable {
|
||||
const authorityPlusIndex = remoteAuthority.indexOf('+');
|
||||
if (authorityPlusIndex === -1) {
|
||||
// This authority does not need to be resolved, simply parse the port number
|
||||
const pieces = remoteAuthority.split(':');
|
||||
const lastColon = remoteAuthority.lastIndexOf(':');
|
||||
return Promise.resolve({
|
||||
authority: {
|
||||
authority: remoteAuthority,
|
||||
host: pieces[0],
|
||||
port: parseInt(pieces[1], 10),
|
||||
host: remoteAuthority.substring(0, lastColon),
|
||||
port: parseInt(remoteAuthority.substring(lastColon + 1), 10),
|
||||
connectionToken: undefined
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
||||
export const enum ExtensionHostExitCode {
|
||||
// nodejs uses codes 1-13 and exit codes >128 are signal exits
|
||||
VersionMismatch = 55,
|
||||
StartTimeout10s = 56,
|
||||
StartTimeout60s = 56,
|
||||
UnexpectedError = 81,
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface IExtHostSocketMessage {
|
||||
type: 'VSCODE_EXTHOST_IPC_SOCKET';
|
||||
initialDataChunk: string;
|
||||
skipWebSocketFrames: boolean;
|
||||
permessageDeflate: boolean;
|
||||
inflateBytes: string;
|
||||
}
|
||||
|
||||
export interface IExtHostReduceGraceTimeMessage {
|
||||
|
||||
@@ -300,11 +300,21 @@ export const schema: IJSONSchema = {
|
||||
body: 'onUri',
|
||||
description: nls.localize('vscode.extension.activationEvents.onUri', 'An activation event emitted whenever a system-wide Uri directed towards this extension is open.'),
|
||||
},
|
||||
{
|
||||
label: 'onOpenExternalUri',
|
||||
body: 'onOpenExternalUri',
|
||||
description: nls.localize('vscode.extension.activationEvents.onOpenExternalUri', 'An activation event emitted whenever a external uri (such as an http or https link) is being opened.'),
|
||||
},
|
||||
{
|
||||
label: 'onCustomEditor',
|
||||
body: 'onCustomEditor:${9:viewType}',
|
||||
description: nls.localize('vscode.extension.activationEvents.onCustomEditor', 'An activation event emitted whenever the specified custom editor becomes visible.'),
|
||||
},
|
||||
{
|
||||
label: 'onNotebook',
|
||||
body: 'onNotebook:${10:viewType}',
|
||||
description: nls.localize('vscode.extension.activationEvents.onNotebook', 'An activation event emitted whenever the specified notebook document is opened.'),
|
||||
},
|
||||
{
|
||||
label: '*',
|
||||
description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'),
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
export interface NewWorkerMessage {
|
||||
type: '_newWorker';
|
||||
id: string;
|
||||
port: any /* MessagePort */;
|
||||
url: string;
|
||||
options: any /* WorkerOptions */ | undefined;
|
||||
}
|
||||
|
||||
export interface TerminateWorkerMessage {
|
||||
type: '_terminateWorker';
|
||||
id: string;
|
||||
}
|
||||
@@ -57,6 +57,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
|
||||
public readonly onExit: Event<[number, string | null]> = this._onExit.event;
|
||||
|
||||
private _protocol: PersistentProtocol | null;
|
||||
private _hasLostConnection: boolean;
|
||||
private _terminating: boolean;
|
||||
private readonly _isExtensionDevHost: boolean;
|
||||
|
||||
@@ -77,6 +78,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
|
||||
super();
|
||||
this.remoteAuthority = this._initDataProvider.remoteAuthority;
|
||||
this._protocol = null;
|
||||
this._hasLostConnection = false;
|
||||
this._terminating = false;
|
||||
|
||||
this._register(this._lifecycleService.onShutdown(reason => this.dispose()));
|
||||
@@ -130,7 +132,7 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
|
||||
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);
|
||||
}
|
||||
|
||||
protocol.onClose(() => {
|
||||
protocol.onDidDispose(() => {
|
||||
this._onExtHostConnectionLost();
|
||||
});
|
||||
|
||||
@@ -188,6 +190,11 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
|
||||
}
|
||||
|
||||
private _onExtHostConnectionLost(): void {
|
||||
if (this._hasLostConnection) {
|
||||
// avoid re-entering this method
|
||||
return;
|
||||
}
|
||||
this._hasLostConnection = true;
|
||||
|
||||
if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId) {
|
||||
this._extensionHostDebugService.close(this._environmentService.debugExtensionHost.debugId);
|
||||
|
||||
@@ -60,8 +60,13 @@ export interface IRPCProtocolLogger {
|
||||
|
||||
const noop = () => { };
|
||||
|
||||
const _RPCProtocolSymbol = Symbol.for('rpcProtocol');
|
||||
const _RPCProxySymbol = Symbol.for('rpcProxy');
|
||||
|
||||
export class RPCProtocol extends Disposable implements IRPCProtocol {
|
||||
|
||||
[_RPCProtocolSymbol] = true;
|
||||
|
||||
private static readonly UNRESPONSIVE_TIME = 3 * 1000; // 3s
|
||||
|
||||
private readonly _onDidChangeResponsiveState: Emitter<ResponsiveState> = this._register(new Emitter<ResponsiveState>());
|
||||
@@ -182,14 +187,14 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
|
||||
}
|
||||
|
||||
public getProxy<T>(identifier: ProxyIdentifier<T>): T {
|
||||
const rpcId = identifier.nid;
|
||||
const { nid: rpcId, sid } = identifier;
|
||||
if (!this._proxies[rpcId]) {
|
||||
this._proxies[rpcId] = this._createProxy(rpcId);
|
||||
this._proxies[rpcId] = this._createProxy(rpcId, sid);
|
||||
}
|
||||
return this._proxies[rpcId];
|
||||
}
|
||||
|
||||
private _createProxy<T>(rpcId: number): T {
|
||||
private _createProxy<T>(rpcId: number, debugName: string): T {
|
||||
let handler = {
|
||||
get: (target: any, name: PropertyKey) => {
|
||||
if (typeof name === 'string' && !target[name] && name.charCodeAt(0) === CharCode.DollarSign) {
|
||||
@@ -197,6 +202,9 @@ export class RPCProtocol extends Disposable implements IRPCProtocol {
|
||||
return this._remoteCall(rpcId, name, myArgs);
|
||||
};
|
||||
}
|
||||
if (name === _RPCProxySymbol) {
|
||||
return debugName;
|
||||
}
|
||||
return target[name];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
@@ -150,7 +151,7 @@ export class CachedExtensionScanner {
|
||||
const cacheFile = path.join(cacheFolder, cacheKey);
|
||||
|
||||
try {
|
||||
const cacheRawContents = await pfs.readFile(cacheFile, 'utf8');
|
||||
const cacheRawContents = await fs.promises.readFile(cacheFile, 'utf8');
|
||||
return JSON.parse(cacheRawContents);
|
||||
} catch (err) {
|
||||
// That's ok...
|
||||
@@ -164,7 +165,7 @@ export class CachedExtensionScanner {
|
||||
const cacheFile = path.join(cacheFolder, cacheKey);
|
||||
|
||||
try {
|
||||
await pfs.mkdirp(cacheFolder);
|
||||
await fs.promises.mkdir(cacheFolder, { recursive: true });
|
||||
} catch (err) {
|
||||
// That's ok...
|
||||
}
|
||||
@@ -183,7 +184,7 @@ export class CachedExtensionScanner {
|
||||
}
|
||||
|
||||
try {
|
||||
const folderStat = await pfs.stat(input.absoluteFolderPath);
|
||||
const folderStat = await fs.promises.stat(input.absoluteFolderPath);
|
||||
input.mtime = folderStat.mtime.getTime();
|
||||
} catch (err) {
|
||||
// That's ok...
|
||||
@@ -223,7 +224,7 @@ export class CachedExtensionScanner {
|
||||
private static async _readTranslationConfig(): Promise<Translations> {
|
||||
if (platform.translationsConfigFile) {
|
||||
try {
|
||||
const content = await pfs.readFile(platform.translationsConfigFile, 'utf8');
|
||||
const content = await fs.promises.readFile(platform.translationsConfigFile, 'utf8');
|
||||
return JSON.parse(content) as Translations;
|
||||
} catch (err) {
|
||||
// no problemo
|
||||
@@ -262,7 +263,7 @@ export class CachedExtensionScanner {
|
||||
const builtInExtensions = Promise.resolve<IBuiltInExtension[]>(productService.builtInExtensions || []);
|
||||
|
||||
const controlFilePath = joinPath(environmentService.userHome, '.vscode-oss-dev', 'extensions', 'control.json').fsPath;
|
||||
const controlFile = pfs.readFile(controlFilePath, 'utf8')
|
||||
const controlFile = fs.promises.readFile(controlFilePath, 'utf8')
|
||||
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
|
||||
|
||||
const input = new ExtensionScannerInput(version, commit, locale, devMode, getExtraDevSystemExtensionsRoot(), true, false, translations);
|
||||
|
||||
@@ -248,6 +248,24 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
signal,
|
||||
extensionIds: activatedExtensions.map(e => e.value)
|
||||
});
|
||||
|
||||
for (const extensionId of activatedExtensions) {
|
||||
type ExtensionHostCrashExtensionClassification = {
|
||||
code: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
|
||||
signal: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
|
||||
extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
|
||||
};
|
||||
type ExtensionHostCrashExtensionEvent = {
|
||||
code: number;
|
||||
signal: string | null;
|
||||
extensionId: string;
|
||||
};
|
||||
this._telemetryService.publicLog2<ExtensionHostCrashExtensionEvent, ExtensionHostCrashExtensionClassification>('extensionHostCrashExtension', {
|
||||
code,
|
||||
signal,
|
||||
extensionId: extensionId.value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,6 +412,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
||||
}
|
||||
|
||||
public _onExtensionHostExit(code: number): void {
|
||||
// Dispose everything associated with the extension host
|
||||
this._stopExtensionHosts();
|
||||
|
||||
if (this._isExtensionDevTestFromCli) {
|
||||
// When CLI testing make sure to exit with proper exit code
|
||||
this._nativeHostService.exit(code);
|
||||
|
||||
@@ -45,6 +45,8 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
|
||||
import { isUUID } from 'vs/base/common/uuid';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { Readable, Writable } from 'stream';
|
||||
import { StringDecoder } from 'string_decoder';
|
||||
|
||||
export interface ILocalProcessExtensionHostInitData {
|
||||
readonly autoStart: boolean;
|
||||
@@ -55,6 +57,11 @@ export interface ILocalProcessExtensionHostDataProvider {
|
||||
getInitData(): Promise<ILocalProcessExtensionHostInitData>;
|
||||
}
|
||||
|
||||
const enum NativeLogMarkers {
|
||||
Start = 'START_NATIVE_LOG',
|
||||
End = 'END_NATIVE_LOG',
|
||||
}
|
||||
|
||||
export class LocalProcessExtensionHost implements IExtensionHost {
|
||||
|
||||
public readonly kind = ExtensionHostKind.LocalProcess;
|
||||
@@ -151,13 +158,12 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
||||
this._messageProtocol = Promise.all([
|
||||
this._tryListenOnPipe(),
|
||||
this._tryFindDebugPort()
|
||||
]).then(data => {
|
||||
const pipeName = data[0];
|
||||
const portNumber = data[1];
|
||||
]).then(([pipeName, portNumber]) => {
|
||||
const env = objects.mixin(objects.deepClone(process.env), {
|
||||
AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
|
||||
PIPE_LOGGING: 'true',
|
||||
VERBOSE_LOGGING: true,
|
||||
VSCODE_AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
|
||||
VSCODE_PIPE_LOGGING: 'true',
|
||||
VSCODE_VERBOSE_LOGGING: true,
|
||||
VSCODE_LOG_NATIVE: this._isExtensionDevHost,
|
||||
VSCODE_IPC_HOOK_EXTHOST: pipeName,
|
||||
VSCODE_HANDLES_UNCAUGHT_ERRORS: true,
|
||||
VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || this._productService.quality !== 'stable' || this._environmentService.verbose),
|
||||
@@ -196,6 +202,10 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
||||
opts.execArgv = ['--inspect-port=0'];
|
||||
}
|
||||
|
||||
if (this._environmentService.args['prof-v8-extensions']) {
|
||||
opts.execArgv.unshift('--prof');
|
||||
}
|
||||
|
||||
// On linux crash reporter needs to be started on child node processes explicitly
|
||||
if (platform.isLinux) {
|
||||
const crashReporterStartOptions: CrashReporterStartOptions = {
|
||||
@@ -217,7 +227,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
||||
// For https://github.com/microsoft/vscode/issues/105743
|
||||
const extHostCrashDirectory = this._environmentService.crashReporterDirectory || this._environmentService.userDataPath;
|
||||
opts.env.BREAKPAD_DUMP_LOCATION = join(extHostCrashDirectory, `${ExtensionHostLogFileName} Crash Reports`);
|
||||
opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions);
|
||||
opts.env.VSCODE_CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterStartOptions);
|
||||
}
|
||||
|
||||
// Run Extension Host as fork of current process
|
||||
@@ -225,13 +235,11 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
||||
|
||||
// Catch all output coming from the extension host process
|
||||
type Output = { data: string, format: string[] };
|
||||
this._extensionHostProcess.stdout!.setEncoding('utf8');
|
||||
this._extensionHostProcess.stderr!.setEncoding('utf8');
|
||||
const onStdout = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stdout!, 'data');
|
||||
const onStderr = Event.fromNodeEventEmitter<string>(this._extensionHostProcess.stderr!, 'data');
|
||||
const onStdout = this._handleProcessOutputStream(this._extensionHostProcess.stdout!);
|
||||
const onStderr = this._handleProcessOutputStream(this._extensionHostProcess.stderr!);
|
||||
const onOutput = Event.any(
|
||||
Event.map(onStdout, o => ({ data: `%c${o}`, format: [''] })),
|
||||
Event.map(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
|
||||
Event.map(onStdout.event, o => ({ data: `%c${o}`, format: [''] })),
|
||||
Event.map(onStderr.event, o => ({ data: `%c${o}`, format: ['color: red'] }))
|
||||
);
|
||||
|
||||
// Debounce all output, so we can render it in the Chrome console as a group
|
||||
@@ -498,11 +506,6 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
||||
|
||||
// Send to local console
|
||||
log(entry, 'Extension Host');
|
||||
|
||||
// Broadcast to other windows if we are in development mode
|
||||
if (this._environmentService.debugExtensionHost.debugId && (!this._environmentService.isBuilt || this._isExtensionDevHost)) {
|
||||
this._extensionHostDebugService.logToSession(this._environmentService.debugExtensionHost.debugId, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,6 +529,44 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
||||
this._onExit.fire([code, signal]);
|
||||
}
|
||||
|
||||
private _handleProcessOutputStream(stream: Readable) {
|
||||
let last = '';
|
||||
let isOmitting = false;
|
||||
const event = new Emitter<string>();
|
||||
const decoder = new StringDecoder('utf-8');
|
||||
stream.pipe(new Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
// not a fancy approach, but this is the same approach used by the split2
|
||||
// module which is well-optimized (https://github.com/mcollina/split2)
|
||||
last += typeof chunk === 'string' ? chunk : decoder.write(chunk);
|
||||
let lines = last.split(/\r?\n/g);
|
||||
last = lines.pop()!;
|
||||
|
||||
// protected against an extension spamming and leaking memory if no new line is written.
|
||||
if (last.length > 10_000) {
|
||||
lines.push(last);
|
||||
last = '';
|
||||
}
|
||||
|
||||
for (const line of lines) {
|
||||
if (isOmitting) {
|
||||
if (line === NativeLogMarkers.End) {
|
||||
isOmitting = false;
|
||||
}
|
||||
} else if (line === NativeLogMarkers.Start) {
|
||||
isOmitting = true;
|
||||
} else if (line.length) {
|
||||
event.fire(line + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
}));
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
public async enableInspectPort(): Promise<boolean> {
|
||||
if (typeof this._inspectPort === 'number') {
|
||||
return true;
|
||||
@@ -615,7 +656,7 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
||||
// to communicate this back to the main side to terminate the debug session
|
||||
if (this._isExtensionDevHost && !this._isExtensionDevTestFromCli && !this._isExtensionDevDebug && this._environmentService.debugExtensionHost.debugId) {
|
||||
this._extensionHostDebugService.terminateSession(this._environmentService.debugExtensionHost.debugId);
|
||||
event.join(timeout(100 /* wait a bit for IPC to get delivered */));
|
||||
event.join(timeout(100 /* wait a bit for IPC to get delivered */), 'join.extensionDevelopment');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import * as nativeWatchdog from 'native-watchdog';
|
||||
import * as net from 'net';
|
||||
import * as minimist from 'minimist';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
@@ -92,7 +93,7 @@ interface IRendererConnection {
|
||||
|
||||
// This calls exit directly in case the initialization is not finished and we need to exit
|
||||
// Otherwise, if initialization completed we go to extensionHostMain.terminate()
|
||||
let onTerminate = function () {
|
||||
let onTerminate = function (reason: string) {
|
||||
nativeExit();
|
||||
};
|
||||
|
||||
@@ -109,8 +110,8 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
|
||||
|
||||
const reconnectionGraceTime = ProtocolConstants.ReconnectionGraceTime;
|
||||
const reconnectionShortGraceTime = ProtocolConstants.ReconnectionShortGraceTime;
|
||||
const disconnectRunner1 = new RunOnceScheduler(() => onTerminate(), reconnectionGraceTime);
|
||||
const disconnectRunner2 = new RunOnceScheduler(() => onTerminate(), reconnectionShortGraceTime);
|
||||
const disconnectRunner1 = new RunOnceScheduler(() => onTerminate('renderer disconnected for too long (1)'), reconnectionGraceTime);
|
||||
const disconnectRunner2 = new RunOnceScheduler(() => onTerminate('renderer disconnected for too long (2)'), reconnectionShortGraceTime);
|
||||
|
||||
process.on('message', (msg: IExtHostSocketMessage | IExtHostReduceGraceTimeMessage, handle: net.Socket) => {
|
||||
if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') {
|
||||
@@ -119,7 +120,8 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
|
||||
if (msg.skipWebSocketFrames) {
|
||||
socket = new NodeSocket(handle);
|
||||
} else {
|
||||
socket = new WebSocketNodeSocket(new NodeSocket(handle));
|
||||
const inflateBytes = VSBuffer.wrap(Buffer.from(msg.inflateBytes, 'base64'));
|
||||
socket = new WebSocketNodeSocket(new NodeSocket(handle), msg.permessageDeflate, inflateBytes, false);
|
||||
}
|
||||
if (protocol) {
|
||||
// reconnection case
|
||||
@@ -130,7 +132,7 @@ function _createExtHostProtocol(): Promise<PersistentProtocol> {
|
||||
} else {
|
||||
clearTimeout(timer);
|
||||
protocol = new PersistentProtocol(socket, initialDataChunk);
|
||||
protocol.onClose(() => onTerminate());
|
||||
protocol.onDidDispose(() => onTerminate('renderer disconnected'));
|
||||
resolve(protocol);
|
||||
|
||||
// Wait for rich client to reconnect
|
||||
@@ -191,7 +193,7 @@ async function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
|
||||
protocol.onMessage((msg) => {
|
||||
if (isMessageOfType(msg, MessageType.Terminate)) {
|
||||
this._terminating = true;
|
||||
onTerminate();
|
||||
onTerminate('received terminate message from renderer');
|
||||
} else {
|
||||
this._onMessage.fire(msg);
|
||||
}
|
||||
@@ -263,11 +265,23 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
|
||||
});
|
||||
|
||||
// Kill oneself if one's parent dies. Much drama.
|
||||
let epermErrors = 0;
|
||||
setInterval(function () {
|
||||
try {
|
||||
process.kill(initData.parentPid, 0); // throws an exception if the main process doesn't exist anymore.
|
||||
epermErrors = 0;
|
||||
} catch (e) {
|
||||
onTerminate();
|
||||
if (e && e.code === 'EPERM') {
|
||||
// Even if the parent process is still alive,
|
||||
// some antivirus software can lead to an EPERM error to be thrown here.
|
||||
// Let's terminate only if we get 3 consecutive EPERM errors.
|
||||
epermErrors++;
|
||||
if (epermErrors >= 3) {
|
||||
onTerminate(`parent process ${initData.parentPid} does not exist anymore (3 x EPERM): ${e.message} (code: ${e.code}) (errno: ${e.errno})`);
|
||||
}
|
||||
} else {
|
||||
onTerminate(`parent process ${initData.parentPid} does not exist anymore: ${e.message} (code: ${e.code}) (errno: ${e.errno})`);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
@@ -295,9 +309,11 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
|
||||
}
|
||||
|
||||
export async function startExtensionHostProcess(): Promise<void> {
|
||||
|
||||
performance.mark(`code/extHost/willConnectToRenderer`);
|
||||
const protocol = await createExtHostProtocol();
|
||||
performance.mark(`code/extHost/didConnectToRenderer`);
|
||||
const renderer = await connectToRenderer(protocol);
|
||||
performance.mark(`code/extHost/didWaitForInitData`);
|
||||
const { initData } = renderer;
|
||||
// setup things
|
||||
patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/microsoft/vscode/issues/37708)
|
||||
@@ -331,5 +347,5 @@ export async function startExtensionHostProcess(): Promise<void> {
|
||||
);
|
||||
|
||||
// rewrite onTerminate-function to be a proper shutdown
|
||||
onTerminate = () => extensionHostMain.terminate();
|
||||
onTerminate = (reason: string) => extensionHostMain.terminate(reason);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
@@ -12,7 +13,7 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { getGalleryExtensionId, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { getGalleryExtensionId, groupByExtension, ExtensionIdentifierWithVersion, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { isValidExtensionVersion } from 'vs/platform/extensions/common/extensionValidator';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints';
|
||||
@@ -47,10 +48,19 @@ abstract class ExtensionManifestHandler {
|
||||
|
||||
class ExtensionManifestParser extends ExtensionManifestHandler {
|
||||
|
||||
private static _fastParseJSON(text: string, errors: json.ParseError[]): any {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (err) {
|
||||
// invalid JSON, let's get good errors
|
||||
return json.parse(text, errors);
|
||||
}
|
||||
}
|
||||
|
||||
public parse(): Promise<IExtensionDescription> {
|
||||
return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => {
|
||||
return fs.promises.readFile(this._absoluteManifestPath).then((manifestContents) => {
|
||||
const errors: json.ParseError[] = [];
|
||||
const manifest = json.parse(manifestContents.toString(), errors);
|
||||
const manifest = ExtensionManifestParser._fastParseJSON(manifestContents.toString(), errors);
|
||||
if (json.getNodeType(manifest) !== 'object') {
|
||||
this._log.error(this._absoluteFolderPath, nls.localize('jsonParseInvalidType', "Invalid manifest file {0}: Not an JSON object.", this._absoluteManifestPath));
|
||||
} else if (errors.length === 0) {
|
||||
@@ -121,7 +131,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
let translationPath = this._nlsConfig.translations[translationId];
|
||||
let localizedMessages: Promise<LocalizedMessages | undefined>;
|
||||
if (translationPath) {
|
||||
localizedMessages = pfs.readFile(translationPath, 'utf8').then<LocalizedMessages, LocalizedMessages>((content) => {
|
||||
localizedMessages = fs.promises.readFile(translationPath, 'utf8').then<LocalizedMessages, LocalizedMessages>((content) => {
|
||||
let errors: json.ParseError[] = [];
|
||||
let translationBundle: TranslationBundle = json.parse(content, errors);
|
||||
if (errors.length > 0) {
|
||||
@@ -138,7 +148,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
return { values: undefined, default: `${basename}.nls.json` };
|
||||
});
|
||||
} else {
|
||||
localizedMessages = pfs.fileExists(basename + '.nls' + extension).then<LocalizedMessages | undefined, LocalizedMessages | undefined>(exists => {
|
||||
localizedMessages = pfs.SymlinkSupport.existsFile(basename + '.nls' + extension).then<LocalizedMessages | undefined, LocalizedMessages | undefined>(exists => {
|
||||
if (!exists) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -146,7 +156,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
if (!messageBundle.localized) {
|
||||
return { values: undefined, default: messageBundle.original };
|
||||
}
|
||||
return pfs.readFile(messageBundle.localized, 'utf8').then(messageBundleContent => {
|
||||
return fs.promises.readFile(messageBundle.localized, 'utf8').then(messageBundleContent => {
|
||||
let errors: json.ParseError[] = [];
|
||||
let messages: MessageBag = json.parse(messageBundleContent, errors);
|
||||
if (errors.length > 0) {
|
||||
@@ -195,7 +205,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
private static resolveOriginalMessageBundle(originalMessageBundle: string | null, errors: json.ParseError[]) {
|
||||
return new Promise<{ [key: string]: string; } | null>((c, e) => {
|
||||
if (originalMessageBundle) {
|
||||
pfs.readFile(originalMessageBundle).then(originalBundleContent => {
|
||||
fs.promises.readFile(originalMessageBundle).then(originalBundleContent => {
|
||||
c(json.parse(originalBundleContent.toString(), errors));
|
||||
}, (err) => {
|
||||
c(null);
|
||||
@@ -214,7 +224,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler {
|
||||
return new Promise<{ localized: string; original: string | null; }>((c, e) => {
|
||||
function loop(basename: string, locale: string): void {
|
||||
let toCheck = `${basename}.nls.${locale}.json`;
|
||||
pfs.fileExists(toCheck).then(exists => {
|
||||
pfs.SymlinkSupport.existsFile(toCheck).then(exists => {
|
||||
if (exists) {
|
||||
c({ localized: toCheck, original: `${basename}.nls.json` });
|
||||
}
|
||||
@@ -329,7 +339,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler {
|
||||
}
|
||||
|
||||
// id := `publisher.name`
|
||||
extensionDescription.id = `${extensionDescription.publisher}.${extensionDescription.name}`;
|
||||
extensionDescription.id = getExtensionId(extensionDescription.publisher, extensionDescription.name);
|
||||
extensionDescription.identifier = new ExtensionIdentifier(extensionDescription.id);
|
||||
|
||||
extensionDescription.extensionLocation = URI.file(this._absoluteFolderPath);
|
||||
@@ -542,7 +552,7 @@ export class ExtensionScanner {
|
||||
let obsolete: { [folderName: string]: boolean; } = {};
|
||||
if (!isBuiltin) {
|
||||
try {
|
||||
const obsoleteFileContents = await pfs.readFile(path.join(absoluteFolderPath, '.obsolete'), 'utf8');
|
||||
const obsoleteFileContents = await fs.promises.readFile(path.join(absoluteFolderPath, '.obsolete'), 'utf8');
|
||||
obsolete = JSON.parse(obsoleteFileContents);
|
||||
} catch (err) {
|
||||
// Don't care
|
||||
@@ -591,7 +601,7 @@ export class ExtensionScanner {
|
||||
const isBuiltin = input.isBuiltin;
|
||||
const isUnderDevelopment = input.isUnderDevelopment;
|
||||
|
||||
return pfs.fileExists(path.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => {
|
||||
return pfs.SymlinkSupport.existsFile(path.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => {
|
||||
if (exists) {
|
||||
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
|
||||
return this.scanExtension(input.ourVersion, log, absoluteFolderPath, isBuiltin, isUnderDevelopment, nlsConfig).then((extensionDescription) => {
|
||||
|
||||
@@ -18,7 +18,6 @@ import { MainThreadTelemetryShape, IInitData } from 'vs/workbench/api/common/ext
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { promisify } from 'util';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
@@ -397,7 +396,7 @@ function tlsPatches(originals: typeof tls) {
|
||||
};
|
||||
|
||||
function patch(original: typeof tls.createSecureContext): typeof tls.createSecureContext {
|
||||
return function (details: tls.SecureContextOptions): ReturnType<typeof tls.createSecureContext> {
|
||||
return function (details?: tls.SecureContextOptions): ReturnType<typeof tls.createSecureContext> {
|
||||
const context = original.apply(null, arguments as any);
|
||||
const certs = (details as any)._vscodeAdditionalCaCerts;
|
||||
if (certs) {
|
||||
@@ -498,7 +497,7 @@ async function readWindowsCaCertificates() {
|
||||
const winCA = await import('vscode-windows-ca-certs');
|
||||
|
||||
let ders: any[] = [];
|
||||
const store = winCA();
|
||||
const store = new winCA.Crypt32();
|
||||
try {
|
||||
let der: any;
|
||||
while (der = store.next()) {
|
||||
@@ -540,7 +539,7 @@ const linuxCaCertificatePaths = [
|
||||
async function readLinuxCaCertificates() {
|
||||
for (const certPath of linuxCaCertificatePaths) {
|
||||
try {
|
||||
const content = await promisify(fs.readFile)(certPath, { encoding: 'utf8' });
|
||||
const content = await fs.promises.readFile(certPath, { encoding: 'utf8' });
|
||||
const certs = new Set(content.split(/(?=-----BEGIN CERTIFICATE-----)/g)
|
||||
.filter(pem => !!pem.length));
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ExtensionService as BrowserExtensionService } from 'vs/workbench/services/extensions/browser/extensionService';
|
||||
import { ExtensionRunningLocation } from 'vs/workbench/services/extensions/common/abstractExtensionService';
|
||||
|
||||
suite('BrowserExtensionService', () => {
|
||||
test('pickRunningLocation', () => {
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], false, true), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation([], true, true), ExtensionRunningLocation.None);
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui'], true, true), ExtensionRunningLocation.Remote);
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace'], true, true), ExtensionRunningLocation.Remote);
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], false, true), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web'], true, true), ExtensionRunningLocation.LocalWebWorker);
|
||||
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace'], true, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui'], true, true), ExtensionRunningLocation.Remote);
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web'], true, true), ExtensionRunningLocation.Remote);
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web'], true, true), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker);
|
||||
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'web', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['ui', 'workspace', 'web'], true, true), ExtensionRunningLocation.Remote);
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'ui', 'workspace'], true, true), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['web', 'workspace', 'ui'], true, true), ExtensionRunningLocation.LocalWebWorker);
|
||||
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'ui', 'web'], true, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, false), ExtensionRunningLocation.None);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], false, true), ExtensionRunningLocation.Remote);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, false), ExtensionRunningLocation.LocalWebWorker);
|
||||
assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true), ExtensionRunningLocation.Remote);
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@ import { IExtensionManifest, ExtensionKind } from 'vs/platform/extensions/common
|
||||
suite('ExtensionKind', () => {
|
||||
|
||||
function check(manifest: Partial<IExtensionManifest>, expected: ExtensionKind[]): void {
|
||||
assert.deepEqual(deduceExtensionKind(<IExtensionManifest>manifest), expected);
|
||||
assert.deepStrictEqual(deduceExtensionKind(<IExtensionManifest>manifest), expected);
|
||||
}
|
||||
|
||||
test('declarative with extension dependencies => workspace', () => {
|
||||
|
||||
@@ -56,7 +56,7 @@ suite('RPCProtocol', () => {
|
||||
test('simple call', function (done) {
|
||||
delegate = (a1: number, a2: number) => a1 + a2;
|
||||
bProxy.$m(4, 1).then((res: number) => {
|
||||
assert.equal(res, 5);
|
||||
assert.strictEqual(res, 5);
|
||||
done(null);
|
||||
}, done);
|
||||
});
|
||||
@@ -64,7 +64,7 @@ suite('RPCProtocol', () => {
|
||||
test('simple call without result', function (done) {
|
||||
delegate = (a1: number, a2: number) => { };
|
||||
bProxy.$m(4, 1).then((res: number) => {
|
||||
assert.equal(res, undefined);
|
||||
assert.strictEqual(res, undefined);
|
||||
done(null);
|
||||
}, done);
|
||||
});
|
||||
@@ -80,7 +80,7 @@ suite('RPCProtocol', () => {
|
||||
b.buffer[2] = 3;
|
||||
b.buffer[3] = 4;
|
||||
bProxy.$m(b, 2).then((res: number) => {
|
||||
assert.equal(res, 3);
|
||||
assert.strictEqual(res, 3);
|
||||
done(null);
|
||||
}, done);
|
||||
});
|
||||
@@ -96,10 +96,10 @@ suite('RPCProtocol', () => {
|
||||
};
|
||||
bProxy.$m(4, 1).then((res: VSBuffer) => {
|
||||
assert.ok(res instanceof VSBuffer);
|
||||
assert.equal(res.buffer[0], 1);
|
||||
assert.equal(res.buffer[1], 2);
|
||||
assert.equal(res.buffer[2], 3);
|
||||
assert.equal(res.buffer[3], 4);
|
||||
assert.strictEqual(res.buffer[0], 1);
|
||||
assert.strictEqual(res.buffer[1], 2);
|
||||
assert.strictEqual(res.buffer[2], 3);
|
||||
assert.strictEqual(res.buffer[3], 4);
|
||||
done(null);
|
||||
}, done);
|
||||
});
|
||||
@@ -121,7 +121,7 @@ suite('RPCProtocol', () => {
|
||||
return a1 + 1;
|
||||
};
|
||||
bProxy.$m(4, CancellationToken.None).then((res: number) => {
|
||||
assert.equal(res, 5);
|
||||
assert.strictEqual(res, 5);
|
||||
done(null);
|
||||
}, done);
|
||||
});
|
||||
@@ -138,7 +138,7 @@ suite('RPCProtocol', () => {
|
||||
let tokenSource = new CancellationTokenSource();
|
||||
let p = bProxy.$m(4, tokenSource.token);
|
||||
p.then((res: number) => {
|
||||
assert.equal(res, 7);
|
||||
assert.strictEqual(res, 7);
|
||||
}, (err) => {
|
||||
assert.fail('should not receive error');
|
||||
}).finally(done);
|
||||
@@ -152,7 +152,7 @@ suite('RPCProtocol', () => {
|
||||
bProxy.$m(4, 1).then((res) => {
|
||||
assert.fail('unexpected');
|
||||
}, (err) => {
|
||||
assert.equal(err.message, 'nope');
|
||||
assert.strictEqual(err.message, 'nope');
|
||||
}).finally(done);
|
||||
});
|
||||
|
||||
@@ -163,7 +163,7 @@ suite('RPCProtocol', () => {
|
||||
bProxy.$m(4, 1).then((res) => {
|
||||
assert.fail('unexpected');
|
||||
}, (err) => {
|
||||
assert.equal(err, undefined);
|
||||
assert.strictEqual(err, undefined);
|
||||
}).finally(done);
|
||||
});
|
||||
|
||||
@@ -174,7 +174,7 @@ suite('RPCProtocol', () => {
|
||||
return circular;
|
||||
};
|
||||
bProxy.$m(4, 1).then((res) => {
|
||||
assert.equal(res, null);
|
||||
assert.strictEqual(res, null);
|
||||
}, (err) => {
|
||||
assert.fail('unexpected');
|
||||
}).finally(done);
|
||||
@@ -188,18 +188,18 @@ suite('RPCProtocol', () => {
|
||||
bProxy.$m(4, 1).then((res) => {
|
||||
assert.fail('unexpected');
|
||||
}, (err) => {
|
||||
assert.equal(err.what, 'what');
|
||||
assert.strictEqual(err.what, 'what');
|
||||
}).finally(done);
|
||||
});
|
||||
|
||||
test('undefined arguments arrive as null', function () {
|
||||
delegate = (a1: any, a2: any) => {
|
||||
assert.equal(typeof a1, 'undefined');
|
||||
assert.equal(a2, null);
|
||||
assert.strictEqual(typeof a1, 'undefined');
|
||||
assert.strictEqual(a2, null);
|
||||
return 7;
|
||||
};
|
||||
return bProxy.$m(undefined, null).then((res) => {
|
||||
assert.equal(res, 7);
|
||||
assert.strictEqual(res, 7);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ 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 { NestedWorker } from 'vs/workbench/services/extensions/worker/polyfillNestedWorker';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as performance from 'vs/base/common/performance';
|
||||
|
||||
import 'vs/workbench/api/common/extHost.common.services';
|
||||
import 'vs/workbench/api/worker/extHost.worker.services';
|
||||
@@ -23,6 +25,8 @@ declare namespace self {
|
||||
let close: any;
|
||||
let postMessage: any;
|
||||
let addEventListener: any;
|
||||
let removeEventListener: any;
|
||||
let dispatchEvent: any;
|
||||
let indexedDB: { open: any, [k: string]: any };
|
||||
let caches: { open: any, [k: string]: any };
|
||||
}
|
||||
@@ -46,13 +50,24 @@ self.addEventListener = () => console.trace(`'addEventListener' has been blocked
|
||||
(<any>self)['webkitResolveLocalFileSystemURL'] = undefined;
|
||||
|
||||
if ((<any>self).Worker) {
|
||||
// make sure new Worker(...) always uses data:
|
||||
const ttPolicy = (<any>self).trustedTypes?.createPolicy('extensionHostWorker', { createScriptURL: (value: string) => value });
|
||||
|
||||
// make sure new Worker(...) always uses blob: (to maintain current origin)
|
||||
const _Worker = (<any>self).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);
|
||||
const blob = new Blob([js], { type: 'application/javascript' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
return new _Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) : blobUrl, options);
|
||||
};
|
||||
|
||||
} else {
|
||||
(<any>self).Worker = class extends NestedWorker {
|
||||
constructor(stringOrUrl: string | URL, options?: WorkerOptions) {
|
||||
super(nativePostMessage, stringOrUrl, { name: path.basename(stringOrUrl.toString()), ...options });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,7 +112,7 @@ class ExtensionWorker {
|
||||
if (isMessageOfType(msg, MessageType.Terminate)) {
|
||||
// handle terminate-message right here
|
||||
terminating = true;
|
||||
onTerminate();
|
||||
onTerminate('received terminate message from renderer');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,13 +148,13 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
|
||||
});
|
||||
}
|
||||
|
||||
let onTerminate = nativeClose;
|
||||
let onTerminate = (reason: string) => nativeClose();
|
||||
|
||||
(function create(): void {
|
||||
const res = new ExtensionWorker();
|
||||
|
||||
performance.mark(`code/extHost/willConnectToRenderer`);
|
||||
connectToRenderer(res.protocol).then(data => {
|
||||
|
||||
performance.mark(`code/extHost/didWaitForInitData`);
|
||||
const extHostMain = new ExtensionHostMain(
|
||||
data.protocol,
|
||||
data.initData,
|
||||
@@ -147,6 +162,6 @@ let onTerminate = nativeClose;
|
||||
null,
|
||||
);
|
||||
|
||||
onTerminate = () => extHostMain.terminate();
|
||||
onTerminate = (reason: string) => extHostMain.terminate(reason);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data:; script-src 'unsafe-eval' 'sha256-DhNBVT9y4y9LG937ZrEbN5CwALd+WSpQnG3z5u1MOFk=' http: https:; connect-src http: https:" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data: blob:; script-src 'unsafe-eval' 'sha256-LU+tuagpyx5mKuYgHsSvz9593ZGS6yeLPRvzq1lKXlY=' http: https:; connect-src http: https: ws: wss:" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
@@ -22,13 +22,31 @@
|
||||
|
||||
try {
|
||||
const worker = new Worker('extensionHostWorkerMain.js', { name: 'WorkerExtensionHost' });
|
||||
const nestedWorkers = new Map();
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
const { data } = event;
|
||||
window.parent.postMessage({
|
||||
vscodeWebWorkerExtHostId,
|
||||
data
|
||||
}, '*', [data]);
|
||||
|
||||
if (data?.type === '_newWorker') {
|
||||
const { id, port, url, options } = data;
|
||||
const newWorker = new Worker(url, options);
|
||||
newWorker.postMessage(port, [port]);
|
||||
worker.onerror = console.error.bind(console);
|
||||
nestedWorkers.set(id, newWorker);
|
||||
|
||||
} else if (data?.type === '_terminateWorker') {
|
||||
const { id } = data;
|
||||
if(nestedWorkers.has(id)) {
|
||||
nestedWorkers.get(id).terminate();
|
||||
nestedWorkers.delete(id);
|
||||
}
|
||||
} else {
|
||||
worker.onerror = console.error.bind(console);
|
||||
window.parent.postMessage({
|
||||
vscodeWebWorkerExtHostId,
|
||||
data
|
||||
}, '*', [data]);
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (event) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data:; script-src 'unsafe-eval' 'sha256-DhNBVT9y4y9LG937ZrEbN5CwALd+WSpQnG3z5u1MOFk=' https:; connect-src https:" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data: blob:; script-src 'unsafe-eval' 'sha256-LU+tuagpyx5mKuYgHsSvz9593ZGS6yeLPRvzq1lKXlY=' https:; connect-src https: wss:" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
@@ -22,13 +22,31 @@
|
||||
|
||||
try {
|
||||
const worker = new Worker('extensionHostWorkerMain.js', { name: 'WorkerExtensionHost' });
|
||||
const nestedWorkers = new Map();
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
const { data } = event;
|
||||
window.parent.postMessage({
|
||||
vscodeWebWorkerExtHostId,
|
||||
data
|
||||
}, '*', [data]);
|
||||
|
||||
if (data?.type === '_newWorker') {
|
||||
const { id, port, url, options } = data;
|
||||
const newWorker = new Worker(url, options);
|
||||
newWorker.postMessage(port, [port]);
|
||||
worker.onerror = console.error.bind(console);
|
||||
nestedWorkers.set(id, newWorker);
|
||||
|
||||
} else if (data?.type === '_terminateWorker') {
|
||||
const { id } = data;
|
||||
if(nestedWorkers.has(id)) {
|
||||
nestedWorkers.get(id).terminate();
|
||||
nestedWorkers.delete(id);
|
||||
}
|
||||
} else {
|
||||
worker.onerror = console.error.bind(console);
|
||||
window.parent.postMessage({
|
||||
vscodeWebWorkerExtHostId,
|
||||
data
|
||||
}, '*', [data]);
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (event) => {
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NewWorkerMessage, TerminateWorkerMessage } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
|
||||
|
||||
declare function postMessage(data: any, transferables?: Transferable[]): void;
|
||||
|
||||
declare type MessageEventHandler = ((ev: MessageEvent<any>) => any) | null;
|
||||
|
||||
const _bootstrapFnSource = (function _bootstrapFn(workerUrl: string) {
|
||||
|
||||
const listener: EventListener = (event: Event): void => {
|
||||
// uninstall handler
|
||||
self.removeEventListener('message', listener);
|
||||
|
||||
// get data
|
||||
const port = <MessagePort>(<MessageEvent>event).data;
|
||||
|
||||
// postMessage
|
||||
// onmessage
|
||||
Object.defineProperties(self, {
|
||||
'postMessage': {
|
||||
value(data: any, transferOrOptions?: any) {
|
||||
port.postMessage(data, transferOrOptions);
|
||||
}
|
||||
},
|
||||
'onmessage': {
|
||||
get() {
|
||||
return port.onmessage;
|
||||
},
|
||||
set(value: MessageEventHandler) {
|
||||
port.onmessage = value;
|
||||
}
|
||||
}
|
||||
// todo onerror
|
||||
});
|
||||
|
||||
port.addEventListener('message', msg => {
|
||||
self.dispatchEvent(new MessageEvent('message', { data: msg.data }));
|
||||
});
|
||||
|
||||
port.start();
|
||||
|
||||
// fake recursively nested worker
|
||||
self.Worker = <any>class { constructor() { throw new TypeError('Nested workers from within nested worker are NOT supported.'); } };
|
||||
|
||||
// load module
|
||||
importScripts(workerUrl);
|
||||
};
|
||||
|
||||
self.addEventListener('message', listener);
|
||||
}).toString();
|
||||
|
||||
|
||||
export class NestedWorker extends EventTarget implements Worker {
|
||||
|
||||
onmessage: ((this: Worker, ev: MessageEvent<any>) => any) | null = null;
|
||||
onmessageerror: ((this: Worker, ev: MessageEvent<any>) => any) | null = null;
|
||||
onerror: ((this: AbstractWorker, ev: ErrorEvent) => any) | null = null;
|
||||
|
||||
readonly terminate: () => void;
|
||||
readonly postMessage: (message: any, options?: any) => void;
|
||||
|
||||
constructor(nativePostMessage: typeof postMessage, stringOrUrl: string | URL, options?: WorkerOptions) {
|
||||
super();
|
||||
|
||||
// create bootstrap script
|
||||
const bootstrap = `((${_bootstrapFnSource})('${stringOrUrl}'))`;
|
||||
const blob = new Blob([bootstrap], { type: 'application/javascript' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
const channel = new MessageChannel();
|
||||
const id = blobUrl; // works because blob url is unique, needs ID pool otherwise
|
||||
|
||||
const msg: NewWorkerMessage = {
|
||||
type: '_newWorker',
|
||||
id,
|
||||
port: channel.port2,
|
||||
url: blobUrl,
|
||||
options,
|
||||
};
|
||||
nativePostMessage(msg, [channel.port2]);
|
||||
|
||||
// worker-impl: functions
|
||||
this.postMessage = channel.port1.postMessage.bind(channel.port1);
|
||||
this.terminate = () => {
|
||||
const msg: TerminateWorkerMessage = {
|
||||
type: '_terminateWorker',
|
||||
id
|
||||
};
|
||||
channel.port1.postMessage(msg);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
|
||||
channel.port1.close();
|
||||
channel.port2.close();
|
||||
};
|
||||
|
||||
// worker-impl: events
|
||||
Object.defineProperties(this, {
|
||||
'onmessage': {
|
||||
get() {
|
||||
return channel.port1.onmessage;
|
||||
},
|
||||
set(value: MessageEventHandler) {
|
||||
channel.port1.onmessage = value;
|
||||
}
|
||||
},
|
||||
'onmessageerror': {
|
||||
get() {
|
||||
return channel.port1.onmessageerror;
|
||||
},
|
||||
set(value: MessageEventHandler) {
|
||||
channel.port1.onmessageerror = value;
|
||||
}
|
||||
},
|
||||
// todo onerror
|
||||
});
|
||||
|
||||
channel.port1.addEventListener('messageerror', evt => {
|
||||
const msgEvent = new MessageEvent('messageerror', { data: evt.data });
|
||||
this.dispatchEvent(msgEvent);
|
||||
});
|
||||
|
||||
channel.port1.addEventListener('message', evt => {
|
||||
const msgEvent = new MessageEvent('message', { data: evt.data });
|
||||
this.dispatchEvent(msgEvent);
|
||||
});
|
||||
|
||||
channel.port1.start();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user