/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; import { originalFSPath, joinPath } from 'vs/base/common/resources'; import { Barrier } from 'vs/base/common/async'; import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostExtensionServiceShape, IInitData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ActivatedExtension, EmptyExtension, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import type * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Schemas } from 'vs/base/common/network'; import { VSBuffer } from 'vs/base/common/buffer'; import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { RemoteAuthorityResolverError } from 'vs/workbench/api/common/extHostTypes'; import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; interface ITestRunner { /** Old test runner API, as exported from `vscode/lib/testrunner` */ run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; } interface INewTestRunner { /** New test runner API, as explained in the extension test doc */ run(): Promise; } export const IHostUtils = createDecorator('IHostUtils'); export interface IHostUtils { _serviceBrand: undefined; exit(code?: number): void; exists(path: string): Promise; realpath(path: string): Promise; } type TelemetryActivationEventFragment = { id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; name: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; extensionVersion: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; publisherDisplayName: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; activationEvents: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; reason: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; reasonId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; }; export abstract class AbstractExtHostExtensionService implements ExtHostExtensionServiceShape { readonly _serviceBrand: undefined; private static readonly WORKSPACE_CONTAINS_TIMEOUT = 7000; protected readonly _hostUtils: IHostUtils; protected readonly _initData: IInitData; protected readonly _extHostContext: IExtHostRpcService; protected readonly _instaService: IInstantiationService; protected readonly _extHostWorkspace: ExtHostWorkspace; protected readonly _extHostConfiguration: ExtHostConfiguration; protected readonly _logService: ILogService; protected readonly _extHostTunnelService: IExtHostTunnelService; protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; protected readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape; private readonly _almostReadyToRunExtensions: Barrier; private readonly _readyToStartExtensionHost: Barrier; private readonly _readyToRunExtensions: Barrier; protected readonly _registry: ExtensionDescriptionRegistry; private readonly _storage: ExtHostStorage; private readonly _storagePath: IExtensionStoragePaths; private readonly _activator: ExtensionsActivator; private _extensionPathIndex: Promise> | null; private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; private _started: boolean; private readonly _disposables: DisposableStore; constructor( @IInstantiationService instaService: IInstantiationService, @IHostUtils hostUtils: IHostUtils, @IExtHostRpcService extHostContext: IExtHostRpcService, @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace, @IExtHostConfiguration extHostConfiguration: IExtHostConfiguration, @ILogService logService: ILogService, @IExtHostInitDataService initData: IExtHostInitDataService, @IExtensionStoragePaths storagePath: IExtensionStoragePaths, @IExtHostTunnelService extHostTunnelService: IExtHostTunnelService ) { this._hostUtils = hostUtils; this._extHostContext = extHostContext; this._initData = initData; this._extHostWorkspace = extHostWorkspace; this._extHostConfiguration = extHostConfiguration; this._logService = logService; this._extHostTunnelService = extHostTunnelService; this._disposables = new DisposableStore(); this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry); this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService); this._almostReadyToRunExtensions = new Barrier(); this._readyToStartExtensionHost = new Barrier(); this._readyToRunExtensions = new Barrier(); this._registry = new ExtensionDescriptionRegistry(this._initData.extensions); this._storage = new ExtHostStorage(this._extHostContext); this._storagePath = storagePath; this._instaService = instaService.createChild(new ServiceCollection( [IExtHostStorage, this._storage] )); const hostExtensions = new Set(); this._initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); this._activator = new ExtensionsActivator( this._registry, this._initData.resolvedExtensions, this._initData.hostExtensions, { onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); }, actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason); return new HostExtension(); } const extensionDescription = this._registry.getExtensionDescription(extensionId)!; return this._activateExtension(extensionDescription, reason); } }, this._logService ); this._extensionPathIndex = null; this._resolvers = Object.create(null); this._started = false; } public async initialize(): Promise { try { await this._beforeAlmostReadyToRunExtensions(); this._almostReadyToRunExtensions.open(); await this._extHostWorkspace.waitForInitializeCall(); this._readyToStartExtensionHost.open(); if (this._initData.autoStart) { this._startExtensionHost(); } } catch (err) { errors.onUnexpectedError(err); } } protected abstract _beforeAlmostReadyToRunExtensions(): Promise; public async deactivateAll(): Promise { let allPromises: Promise[] = []; try { const allExtensions = this._registry.getAllExtensionDescriptions(); const allExtensionsIds = allExtensions.map(ext => ext.identifier); const activatedExtensions = allExtensionsIds.filter(id => this.isActivated(id)); allPromises = activatedExtensions.map((extensionId) => { return this._deactivate(extensionId); }); } catch (err) { // TODO: write to log once we have one } await Promise.all(allPromises); } public isActivated(extensionId: ExtensionIdentifier): boolean { if (this._readyToRunExtensions.isOpen()) { return this._activator.isActivated(extensionId); } return false; } private _activateByEvent(activationEvent: string, startup: boolean): Promise { return this._activator.activateByEvent(activationEvent, startup); } private _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { return this._activator.activateById(extensionId, reason); } public activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { return this._activateById(extensionId, reason).then(() => { const extension = this._activator.getActivatedExtension(extensionId); if (extension.activationFailed) { // activation failed => bubble up the error as the promise result return Promise.reject(extension.activationFailedError); } return undefined; }); } public getExtensionRegistry(): Promise { return this._readyToRunExtensions.wait().then(_ => this._registry); } public getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined { if (this._readyToRunExtensions.isOpen()) { return this._activator.getActivatedExtension(extensionId).exports; } else { return null; } } // create trie to enable fast 'filename -> extension id' look up public getExtensionPathIndex(): Promise> { if (!this._extensionPathIndex) { const tree = TernarySearchTree.forPaths(); const extensions = this._registry.getAllExtensionDescriptions().map(ext => { if (!ext.main) { return undefined; } return this._hostUtils.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext)); }); this._extensionPathIndex = Promise.all(extensions).then(() => tree); } return this._extensionPathIndex; } private _deactivate(extensionId: ExtensionIdentifier): Promise { let result = Promise.resolve(undefined); if (!this._readyToRunExtensions.isOpen()) { return result; } if (!this._activator.isActivated(extensionId)) { return result; } const extension = this._activator.getActivatedExtension(extensionId); if (!extension) { return result; } // call deactivate if available try { if (typeof extension.module.deactivate === 'function') { result = Promise.resolve(extension.module.deactivate()).then(undefined, (err) => { // TODO: Do something with err if this is not the shutdown case return Promise.resolve(undefined); }); } } catch (err) { // TODO: Do something with err if this is not the shutdown case } // clean up subscriptions try { dispose(extension.subscriptions); } catch (err) { // TODO: Do something with err if this is not the shutdown case } return result; } // --- impl private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier); return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => { const activationTimes = activatedExtension.activationTimes; this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, reason); this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes); return activatedExtension; }, (err) => { this._logExtensionActivationTimes(extensionDescription, reason, 'failure'); throw err; }); } private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) { const event = getTelemetryActivationEvent(extensionDescription, reason); type ExtensionActivationTimesClassification = { outcome: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; } & TelemetryActivationEventFragment & ExtensionActivationTimesFragment; type ExtensionActivationTimesEvent = { outcome: string } & ActivationTimesEvent & TelemetryActivationEvent; type ActivationTimesEvent = { startup?: boolean; codeLoadingTime?: number; activateCallTime?: number; activateResolvedTime?: number; }; this._mainThreadTelemetryProxy.$publicLog2('extensionActivationTimes', { ...event, ...(activationTimes || {}), outcome }); } private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { const event = getTelemetryActivationEvent(extensionDescription, reason); type ActivatePluginClassification = {} & TelemetryActivationEventFragment; this._mainThreadTelemetryProxy.$publicLog2('activatePlugin', event); if (!extensionDescription.main) { // Treat the extension as being empty => NOT AN ERROR CASE return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE)); } this._logService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value} ${JSON.stringify(reason)}`); this._logService.flush(); const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); return Promise.all([ this._loadCommonJSModule(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder), this._loadExtensionContext(extensionDescription) ]).then(values => { return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); }); } protected abstract _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`); return Promise.all([ globalState.whenReady, workspaceState.whenReady, this._storagePath.whenReady ]).then(() => { const that = this; return Object.freeze({ globalState, workspaceState, subscriptions: [], get extensionUri() { return extensionDescription.extensionLocation; }, get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, get storagePath() { return that._storagePath.workspaceValue(extensionDescription); }, get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); }, asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, asExtensionUri(relativePath: string) { return joinPath(extensionDescription.extensionLocation, relativePath); }, get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); } }); }); } private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: vscode.ExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { // Make sure the extension's surface is not undefined extensionModule = extensionModule || { activate: undefined, deactivate: undefined }; return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => { return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions); }); } private static _callActivateOptional(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: vscode.ExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { if (typeof extensionModule.activate === 'function') { try { activationTimesBuilder.activateCallStart(); logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`); const scope = typeof global === 'object' ? global : self; // `global` is nodejs while `self` is for workers const activateResult: Promise = extensionModule.activate.apply(scope, [context]); activationTimesBuilder.activateCallStop(); activationTimesBuilder.activateResolveStart(); return Promise.resolve(activateResult).then((value) => { activationTimesBuilder.activateResolveStop(); return value; }); } catch (err) { return Promise.reject(err); } } else { // No activate found => the module is the extension's exports return Promise.resolve(extensionModule); } } // -- eager activation // Handle "eager" activation extensions private _handleEagerExtensions(): Promise { this._activateByEvent('*', true).then(undefined, (err) => { this._logService.error(err); }); this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : []; return this._handleWorkspaceContainsEagerExtensions(folders); } private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray): Promise { if (folders.length === 0) { return Promise.resolve(undefined); } return Promise.all( this._registry.getAllExtensionDescriptions().map((desc) => { return this._handleWorkspaceContainsEagerExtension(folders, desc); }) ).then(() => { }); } private _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray, desc: IExtensionDescription): Promise { const activationEvents = desc.activationEvents; if (!activationEvents) { return Promise.resolve(undefined); } if (this.isActivated(desc.identifier)) { return Promise.resolve(undefined); } const fileNames: string[] = []; const globPatterns: string[] = []; for (const activationEvent of activationEvents) { if (/^workspaceContains:/.test(activationEvent)) { const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { globPatterns.push(fileNameOrGlob); } else { fileNames.push(fileNameOrGlob); } } } if (fileNames.length === 0 && globPatterns.length === 0) { return Promise.resolve(undefined); } const fileNamePromise = Promise.all(fileNames.map((fileName) => this._activateIfFileName(folders, desc.identifier, fileName))).then(() => { }); const globPatternPromise = this._activateIfGlobPatterns(folders, desc.identifier, globPatterns); return Promise.all([fileNamePromise, globPatternPromise]).then(() => { }); } private async _activateIfFileName(folders: ReadonlyArray, extensionId: ExtensionIdentifier, fileName: string): Promise { // find exact path for (const { uri } of folders) { if (await this._hostUtils.exists(path.join(URI.revive(uri).fsPath, fileName))) { // the file was found return ( this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${fileName}` }) .then(undefined, err => this._logService.error(err)) ); } } return undefined; } private async _activateIfGlobPatterns(folders: ReadonlyArray, extensionId: ExtensionIdentifier, globPatterns: string[]): Promise { this._logService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId.value}, entryPoint: workspaceContains`); if (globPatterns.length === 0) { return Promise.resolve(undefined); } const tokenSource = new CancellationTokenSource(); const searchP = this._mainThreadWorkspaceProxy.$checkExists(folders.map(folder => folder.uri), globPatterns, tokenSource.token); const timer = setTimeout(async () => { tokenSource.cancel(); this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContainsTimeout:${globPatterns.join(',')}` }) .then(undefined, err => this._logService.error(err)); }, AbstractExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); let exists: boolean = false; try { exists = await searchP; } catch (err) { if (!errors.isPromiseCanceledError(err)) { this._logService.error(err); } } tokenSource.dispose(); clearTimeout(timer); if (exists) { // a file was found matching one of the glob patterns return ( this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${globPatterns.join(',')}` }) .then(undefined, err => this._logService.error(err)) ); } return Promise.resolve(undefined); } private _handleExtensionTests(): Promise { return this._doHandleExtensionTests().then(undefined, error => { console.error(error); // ensure any error message makes it onto the console return Promise.reject(error); }); } private async _doHandleExtensionTests(): Promise { const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) { return Promise.resolve(undefined); } const extensionTestsPath = originalFSPath(extensionTestsLocationURI); // Require the test runner via node require from the provided path let testRunner: ITestRunner | INewTestRunner | undefined; let requireError: Error | undefined; try { testRunner = await this._loadCommonJSModule(URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false)); } catch (error) { requireError = error; } // Execute the runner if it follows the old `run` spec if (testRunner && typeof testRunner.run === 'function') { return new Promise((c, e) => { const oldTestRunnerCallback = (error: Error, failures: number | undefined) => { if (error) { e(error.toString()); } else { c(undefined); } // after tests have run, we shutdown the host this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); }; const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback); // Using the new API `run(): Promise` if (runResult && runResult.then) { runResult .then(() => { c(); this._gracefulExit(0); }) .catch((err: Error) => { e(err.toString()); this._gracefulExit(1); }); } }); } // Otherwise make sure to shutdown anyway even in case of an error else { this._gracefulExit(1 /* ERROR */); } return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath))); } private _gracefulExit(code: number): void { // to give the PH process a chance to flush any outstanding console // messages to the main process, we delay the exit() by some time setTimeout(() => { // If extension tests are running, give the exit code to the renderer if (this._initData.remote.isRemote && !!this._initData.environment.extensionTestsLocationURI) { this._mainThreadExtensionsProxy.$onExtensionHostExit(code); return; } this._hostUtils.exit(code); }, 500); } private _startExtensionHost(): Promise { if (this._started) { throw new Error(`Extension host is already started!`); } this._started = true; return this._readyToStartExtensionHost.wait() .then(() => this._readyToRunExtensions.open()) .then(() => this._handleEagerExtensions()) .then(() => this._handleExtensionTests()) .then(() => { this._logService.info(`eager extensions activated`); }); } // -- called by extensions public registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable { this._resolvers[authorityPrefix] = resolver; return toDisposable(() => { delete this._resolvers[authorityPrefix]; }); } // -- called by main thread public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { throw new Error(`Not an authority that can be resolved!`); } const authorityPrefix = remoteAuthority.substr(0, authorityPlusIndex); await this._almostReadyToRunExtensions.wait(); await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false); const resolver = this._resolvers[authorityPrefix]; if (!resolver) { return { type: 'error', error: { code: RemoteAuthorityResolverErrorCode.NoResolverFound, message: `No remote extension installed to resolve ${authorityPrefix}.`, detail: undefined } }; } try { const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver)); // Split merged API result into separate authority/options const authority: ResolvedAuthority = { authority: remoteAuthority, host: result.host, port: result.port }; const options: ResolvedOptions = { extensionHostEnv: result.extensionHostEnv }; return { type: 'ok', value: { authority, options, tunnelInformation: { environmentTunnels: result.environmentTunnels } } }; } catch (err) { if (err instanceof RemoteAuthorityResolverError) { return { type: 'error', error: { code: err._code, message: err._message, detail: err._detail } }; } throw err; } } public $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise { this._registry.keepOnly(enabledExtensionIds); return this._startExtensionHost(); } public $activateByEvent(activationEvent: string): Promise { return ( this._readyToRunExtensions.wait() .then(_ => this._activateByEvent(activationEvent, false)) ); } public async $activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { await this._readyToRunExtensions.wait(); if (!this._registry.getExtensionDescription(extensionId)) { // unknown extension => ignore return false; } await this._activateById(extensionId, reason); return true; } public async $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { toAdd.forEach((extension) => (extension).extensionLocation = URI.revive(extension.extensionLocation)); const trie = await this.getExtensionPathIndex(); await Promise.all(toRemove.map(async (extensionId) => { const extensionDescription = this._registry.getExtensionDescription(extensionId); if (!extensionDescription) { return; } const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); trie.delete(URI.file(realpathValue).fsPath); })); await Promise.all(toAdd.map(async (extensionDescription) => { const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); trie.set(URI.file(realpathValue).fsPath, extensionDescription); })); this._registry.deltaExtensions(toAdd, toRemove); return Promise.resolve(undefined); } public async $test_latency(n: number): Promise { return n; } public async $test_up(b: VSBuffer): Promise { return b.byteLength; } public async $test_down(size: number): Promise { let buff = VSBuffer.alloc(size); let value = Math.random() % 256; for (let i = 0; i < size; i++) { buff.writeUInt8(value, i); } return buff; } public abstract async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; } type TelemetryActivationEvent = { id: string; name: string; extensionVersion: string; publisherDisplayName: string; activationEvents: string | null; isBuiltin: boolean; reason: string; reasonId: string; }; function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TelemetryActivationEvent { const event = { id: extensionDescription.identifier.value, name: extensionDescription.name, extensionVersion: extensionDescription.version, publisherDisplayName: extensionDescription.publisher, activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null, isBuiltin: extensionDescription.isBuiltin, reason: reason.activationEvent, reasonId: reason.extensionId.value, }; return event; } export const IExtHostExtensionService = createDecorator('IExtHostExtensionService'); export interface IExtHostExtensionService extends AbstractExtHostExtensionService { _serviceBrand: undefined; initialize(): Promise; isActivated(extensionId: ExtensionIdentifier): boolean; activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; deactivateAll(): Promise; getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined; getExtensionRegistry(): Promise; getExtensionPathIndex(): Promise>; registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable; }