/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol'; import { ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier'; import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol'; import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; // Enable to see detailed message communication between window and extension host const LOG_EXTENSION_HOST_COMMUNICATION = false; const LOG_USE_COLORS = true; const NO_OP_VOID_PROMISE = Promise.resolve(void 0); export class ExtensionHostProcessManager extends Disposable { public readonly onDidCrash: Event<[number, string]>; private readonly _onDidChangeResponsiveState: Emitter = this._register(new Emitter()); public readonly onDidChangeResponsiveState: Event = this._onDidChangeResponsiveState.event; /** * A map of already activated events to speed things up if the same activation event is triggered multiple times. */ private readonly _extensionHostProcessFinishedActivateEvents: { [activationEvent: string]: boolean; }; private _extensionHostProcessRPCProtocol: RPCProtocol; private readonly _extensionHostProcessCustomers: IDisposable[]; private readonly _extensionHostProcessWorker: IExtensionHostStarter; /** * winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object. */ private _extensionHostProcessProxy: Thenable<{ value: ExtHostExtensionServiceShape; }>; constructor( extensionHostProcessWorker: IExtensionHostStarter, private readonly _remoteAuthority: string, initialActivationEvents: string[], @IInstantiationService private readonly _instantiationService: IInstantiationService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, ) { super(); this._extensionHostProcessFinishedActivateEvents = Object.create(null); this._extensionHostProcessRPCProtocol = null; this._extensionHostProcessCustomers = []; this._extensionHostProcessWorker = extensionHostProcessWorker; this.onDidCrash = this._extensionHostProcessWorker.onCrashed; this._extensionHostProcessProxy = this._extensionHostProcessWorker.start().then( (protocol) => { return { value: this._createExtensionHostCustomers(protocol) }; }, (err) => { console.error('Error received from starting extension host'); console.error(err); return null; } ); this._extensionHostProcessProxy.then(() => { initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent)); }); } public dispose(): void { if (this._extensionHostProcessWorker) { this._extensionHostProcessWorker.dispose(); } if (this._extensionHostProcessRPCProtocol) { this._extensionHostProcessRPCProtocol.dispose(); } for (let i = 0, len = this._extensionHostProcessCustomers.length; i < len; i++) { const customer = this._extensionHostProcessCustomers[i]; try { customer.dispose(); } catch (err) { errors.onUnexpectedError(err); } } this._extensionHostProcessProxy = null; super.dispose(); } // {{SQL CARBON EDIT}} public getExtenstionHostProcessWorker(): IExtensionHostStarter { return this._extensionHostProcessWorker; } // {{SQL CARBON EDIT}} - End public canProfileExtensionHost(): boolean { return this._extensionHostProcessWorker && Boolean(this._extensionHostProcessWorker.getInspectPort()); } private _createExtensionHostCustomers(protocol: IMessagePassingProtocol): ExtHostExtensionServiceShape { let logger: IRPCProtocolLogger | null = null; if (LOG_EXTENSION_HOST_COMMUNICATION || this._environmentService.logExtensionHostCommunication) { logger = new RPCLogger(); } this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol, logger); this._register(this._extensionHostProcessRPCProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState))); const extHostContext: IExtHostContext = { remoteAuthority: this._remoteAuthority, getProxy: (identifier: ProxyIdentifier): T => this._extensionHostProcessRPCProtocol.getProxy(identifier), set: (identifier: ProxyIdentifier, instance: R): R => this._extensionHostProcessRPCProtocol.set(identifier, instance), assertRegistered: (identifiers: ProxyIdentifier[]): void => this._extensionHostProcessRPCProtocol.assertRegistered(identifiers), }; // Named customers const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers(); for (let i = 0, len = namedCustomers.length; i < len; i++) { const [id, ctor] = namedCustomers[i]; const instance = this._instantiationService.createInstance(ctor, extHostContext); this._extensionHostProcessCustomers.push(instance); this._extensionHostProcessRPCProtocol.set(id, instance); } // Customers const customers = ExtHostCustomersRegistry.getCustomers(); for (let i = 0, len = customers.length; i < len; i++) { const ctor = customers[i]; const instance = this._instantiationService.createInstance(ctor, extHostContext); this._extensionHostProcessCustomers.push(instance); } // Check that no named customers are missing const expected: ProxyIdentifier[] = Object.keys(MainContext).map((key) => (MainContext)[key]); this._extensionHostProcessRPCProtocol.assertRegistered(expected); return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService); } public activateByEvent(activationEvent: string): Thenable { if (this._extensionHostProcessFinishedActivateEvents[activationEvent] || !this._extensionHostProcessProxy) { return NO_OP_VOID_PROMISE; } return this._extensionHostProcessProxy.then((proxy) => { if (!proxy) { // this case is already covered above and logged. // i.e. the extension host could not be started return NO_OP_VOID_PROMISE; } return proxy.value.$activateByEvent(activationEvent); }).then(() => { this._extensionHostProcessFinishedActivateEvents[activationEvent] = true; }); } public startExtensionHostProfile(): Promise { if (this._extensionHostProcessWorker) { let port = this._extensionHostProcessWorker.getInspectPort(); if (port) { return this._instantiationService.createInstance(ExtensionHostProfiler, port).start(); } } throw new Error('Extension host not running or no inspect port available'); } public getInspectPort(): number { if (this._extensionHostProcessWorker) { let port = this._extensionHostProcessWorker.getInspectPort(); if (port) { return port; } } return 0; } public resolveAuthority(remoteAuthority: string): Thenable { return this._extensionHostProcessProxy.then(proxy => proxy.value.$resolveAuthority(remoteAuthority)); } public start(enabledExtensionIds: string[]): Thenable { return this._extensionHostProcessProxy.then(proxy => proxy.value.$startExtensionHost(enabledExtensionIds)); } } const colorTables = [ ['#2977B1', '#FC802D', '#34A13A', '#D3282F', '#9366BA'], ['#8B564C', '#E177C0', '#7F7F7F', '#BBBE3D', '#2EBECD'] ]; function prettyWithoutArrays(data: any): any { if (Array.isArray(data)) { return data; } if (data && typeof data === 'object' && typeof data.toString === 'function') { let result = data.toString(); if (result !== '[object Object]') { return result; } } return data; } function pretty(data: any): any { if (Array.isArray(data)) { return data.map(prettyWithoutArrays); } return prettyWithoutArrays(data); } class RPCLogger implements IRPCProtocolLogger { private _totalIncoming = 0; private _totalOutgoing = 0; private _log(direction: string, totalLength, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void { data = pretty(data); const colorTable = colorTables[initiator]; const color = LOG_USE_COLORS ? colorTable[req % colorTable.length] : '#000000'; let args = [`%c[${direction}]%c[${strings.pad(totalLength, 7, ' ')}]%c[len: ${strings.pad(msgLength, 5, ' ')}]%c${strings.pad(req, 5, ' ')} - ${str}`, 'color: darkgreen', 'color: grey', 'color: grey', `color: ${color}`]; if (/\($/.test(str)) { args = args.concat(data); args.push(')'); } else { args.push(data); } console.log.apply(console, args); } logIncoming(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void { this._totalIncoming += msgLength; this._log('Ext \u2192 Win', this._totalIncoming, msgLength, req, initiator, str, data); } logOutgoing(msgLength: number, req: number, initiator: RequestInitiator, str: string, data?: any): void { this._totalOutgoing += msgLength; this._log('Win \u2192 Ext', this._totalOutgoing, msgLength, req, initiator, str, data); } }