/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. 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 * as performance from 'vs/base/common/performance'; import { originalFSPath, joinPath } from 'vs/base/common/resources'; import { Barrier, timeout } from 'vs/base/common/async'; import { dispose, toDisposable, DisposableStore, Disposable } 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, checkProposedApiEnabled, ActivationKind } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; 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 { ExtensionGlobalMemento, ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { RemoteAuthorityResolverError, ExtensionMode, ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes'; import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData } 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'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { Emitter, Event } from 'vs/base/common/event'; import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains'; import { ExtHostSecretState, IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState'; import { ExtensionSecrets } from 'vs/workbench/api/common/extHostSecrets'; 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 { readonly _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 extends Disposable implements ExtHostExtensionServiceShape { readonly _serviceBrand: undefined; abstract readonly extensionRuntime: ExtensionRuntime; private readonly _onDidChangeRemoteConnectionData = this._register(new Emitter()); public readonly onDidChangeRemoteConnectionData = this._onDidChangeRemoteConnectionData.event; 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 _extHostTerminalService: IExtHostTerminalService; 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 _secretState: ExtHostSecretState; private readonly _storagePath: IExtensionStoragePaths; private readonly _activator: ExtensionsActivator; private _extensionPathIndex: Promise> | null; private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; private _started: boolean; private _remoteConnectionData: IRemoteConnectionData | null; 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, @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService, ) { super(); this._hostUtils = hostUtils; this._extHostContext = extHostContext; this._initData = initData; this._extHostWorkspace = extHostWorkspace; this._extHostConfiguration = extHostConfiguration; this._logService = logService; this._extHostTunnelService = extHostTunnelService; this._extHostTerminalService = extHostTerminalService; 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._secretState = new ExtHostSecretState(this._extHostContext); this._storagePath = storagePath; this._instaService = instaService.createChild(new ServiceCollection( [IExtHostStorage, this._storage], [IExtHostSecretState, this._secretState] )); 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; this._remoteConnectionData = this._initData.remote.connectionData; } public getRemoteConnectionData(): IRemoteConnectionData | null { return this._remoteConnectionData; } public async initialize(): Promise { try { await this._beforeAlmostReadyToRunExtensions(); this._almostReadyToRunExtensions.open(); await this._extHostWorkspace.waitForInitializeCall(); performance.mark('code/extHost/ready'); this._readyToStartExtensionHost.open(); if (this._initData.autoStart) { this._startExtensionHost(); } } catch (err) { errors.onUnexpectedError(err); } } 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 (!this._getEntryPoint(ext)) { 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 async _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { if (!this._initData.remote.isRemote) { // local extension host process await this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier); } else { // remote extension host process // do not wait for renderer confirmation 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); const entryPoint = this._getEntryPoint(extensionDescription); if (!entryPoint) { // 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(extensionDescription.identifier, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder), this._loadExtensionContext(extensionDescription) ]).then(values => { performance.mark(`code/extHost/willActivateExtension/${extensionDescription.identifier.value}`); return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); }).then((activatedExtension) => { performance.mark(`code/extHost/didActivateExtension/${extensionDescription.identifier.value}`); return activatedExtension; }); } private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { const globalState = new ExtensionGlobalMemento(extensionDescription, this._storage); const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); const secrets = new ExtensionSecrets(extensionDescription, this._secretState); const extensionMode = extensionDescription.isUnderDevelopment ? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development) : ExtensionMode.Production; 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, secrets, subscriptions: [], get extensionUri() { return extensionDescription.extensionLocation; }, get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, get storagePath() { return that._storagePath.workspaceValue(extensionDescription)?.fsPath; }, get globalStoragePath() { return that._storagePath.globalValue(extensionDescription).fsPath; }, get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); }, get logUri() { return URI.joinPath(that._initData.logsLocation, extensionDescription.identifier.value); }, get storageUri() { return that._storagePath.workspaceValue(extensionDescription); }, get globalStorageUri() { return that._storagePath.globalValue(extensionDescription); }, get extensionMode() { return extensionMode; }, get extensionRuntime() { checkProposedApiEnabled(extensionDescription); return that.extensionRuntime; }, get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); } }); }); } 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 private _activateOneStartupFinished(desc: IExtensionDescription, activationEvent: string): void { this._activateById(desc.identifier, { startup: false, extensionId: desc.identifier, activationEvent: activationEvent }).then(undefined, (err) => { this._logService.error(err); }); } private _activateAllStartupFinished(): void { // startup is considered finished this._mainThreadExtensionsProxy.$setPerformanceMarks(performance.getMarks()); for (const desc of this._registry.getAllExtensionDescriptions()) { if (desc.activationEvents) { for (const activationEvent of desc.activationEvents) { if (activationEvent === 'onStartupFinished') { this._activateOneStartupFinished(desc, activationEvent); } } } } } // Handle "eager" activation extensions private _handleEagerExtensions(): Promise { const starActivation = 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 : []; const workspaceContainsActivation = this._handleWorkspaceContainsEagerExtensions(folders); const eagerExtensionsActivation = Promise.all([starActivation, workspaceContainsActivation]).then(() => { }); Promise.race([eagerExtensionsActivation, timeout(10000)]).then(() => { this._activateAllStartupFinished(); }); return eagerExtensionsActivation; } 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 async _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray, desc: IExtensionDescription): Promise { if (this.isActivated(desc.identifier)) { return; } const localWithRemote = !this._initData.remote.isRemote && !!this._initData.remote.authority; const host: IExtensionActivationHost = { folders: folders.map(folder => folder.uri), forceUsingSearch: localWithRemote, exists: (uri) => this._hostUtils.exists(uri.fsPath), checkExists: (folders, includes, token) => this._mainThreadWorkspaceProxy.$checkExists(folders, includes, token) }; const result = await checkActivateWorkspaceContainsExtension(host, desc); if (!result) { return; } return ( this._activateById(desc.identifier, { startup: true, extensionId: desc.identifier, activationEvent: result.activationEvent }) .then(undefined, err => this._logService.error(err)) ); } 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(null, 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._testRunnerExit(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._testRunnerExit(0); }) .catch((err: Error) => { e(err.toString()); this._testRunnerExit(1); }); } }); } // Otherwise make sure to shutdown anyway even in case of an error else { this._testRunnerExit(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 _testRunnerExit(code: number): void { this._logService.info(`extension host terminating: test runner requested exit with code ${code}`); this._logService.flush(); // wait at most 5000ms for the renderer to confirm our exit request and for the renderer socket to drain // (this is to ensure all outstanding messages reach the renderer) const exitPromise = this._mainThreadExtensionsProxy.$onExtensionHostExit(code); const drainPromise = this._extHostContext.drain(); Promise.race([Promise.all([exitPromise, drainPromise]), timeout(5000)]).then(() => { this._logService.info(`exiting with code ${code}`); this._logService.flush(); this._hostUtils.exit(code); }); } 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 { performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`); const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`); 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, connectionToken: result.connectionToken }; const options: ResolvedOptions = { extensionHostEnv: result.extensionHostEnv }; return { type: 'ok', value: { authority, options, tunnelInformation: { environmentTunnels: result.environmentTunnels } } }; } catch (err) { performance.mark(`code/extHost/didResolveAuthorityError/${authorityPrefix}`); 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, activationKind: ActivationKind): Promise { if (activationKind === ActivationKind.Immediate) { return this._activateByEvent(activationEvent, false); } 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 async $updateRemoteConnectionData(connectionData: IRemoteConnectionData): Promise { this._remoteConnectionData = connectionData; this._onDidChangeRemoteConnectionData.fire(); } protected abstract _beforeAlmostReadyToRunExtensions(): Promise; protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined; protected abstract _loadCommonJSModule(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; public abstract $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 { readonly _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; onDidChangeRemoteConnectionData: Event; getRemoteConnectionData(): IRemoteConnectionData | null; }