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:
Karl Burtram
2021-04-27 14:01:59 -07:00
committed by GitHub
parent 7e1c0076ba
commit 867a963882
1817 changed files with 81812 additions and 50843 deletions

View File

@@ -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.

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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
}
});

View File

@@ -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 {

View File

@@ -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.'),

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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];
}
};

View File

@@ -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);

View File

@@ -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);

View File

@@ -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');
}
}
}

View File

@@ -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);
}

View File

@@ -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) => {

View File

@@ -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 {

View File

@@ -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);
});
});

View File

@@ -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', () => {

View File

@@ -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);
});
});

View File

@@ -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);
});
})();

View File

@@ -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) => {

View File

@@ -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) => {

View File

@@ -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();
}
}