Path: blob/main/src/vs/workbench/services/extensions/browser/extensionService.ts
5251 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { mainWindow } from '../../../../base/browser/window.js';6import { Schemas } from '../../../../base/common/network.js';7import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';8import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';9import { ExtensionKind } from '../../../../platform/environment/common/environment.js';10import { ExtensionIdentifier, IExtensionDescription } from '../../../../platform/extensions/common/extensions.js';11import { IFileService } from '../../../../platform/files/common/files.js';12import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';13import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';14import { IAutomatedWindow, getLogs } from '../../../../platform/log/browser/log.js';15import { ILogService } from '../../../../platform/log/common/log.js';16import { INotificationService } from '../../../../platform/notification/common/notification.js';17import { IProductService } from '../../../../platform/product/common/productService.js';18import { PersistentConnectionEventType } from '../../../../platform/remote/common/remoteAgentConnection.js';19import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from '../../../../platform/remote/common/remoteAuthorityResolver.js';20import { IRemoteExtensionsScannerService } from '../../../../platform/remote/common/remoteExtensionsScanner.js';21import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';22import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';23import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js';24import { IBrowserWorkbenchEnvironmentService } from '../../environment/browser/environmentService.js';25import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../extensionManagement/common/extensionManagement.js';26import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, WebWorkerExtensionHost } from './webWorkerExtensionHost.js';27import { FetchFileSystemProvider } from './webWorkerFileSystemProvider.js';28import { AbstractExtensionService, IExtensionHostFactory, LocalExtensions, RemoteExtensions, ResolvedExtensions, ResolverExtensions, checkEnabledAndProposedAPI, isResolverExtension } from '../common/abstractExtensionService.js';29import { ExtensionDescriptionRegistrySnapshot } from '../common/extensionDescriptionRegistry.js';30import { ExtensionHostKind, ExtensionRunningPreference, IExtensionHostKindPicker, extensionHostKindToString, extensionRunningPreferenceToString } from '../common/extensionHostKind.js';31import { IExtensionManifestPropertiesService } from '../common/extensionManifestPropertiesService.js';32import { ExtensionRunningLocation } from '../common/extensionRunningLocation.js';33import { ExtensionRunningLocationTracker, filterExtensionDescriptions } from '../common/extensionRunningLocationTracker.js';34import { ExtensionHostExtensions, ExtensionHostStartup, IExtensionHost, IExtensionService, toExtensionDescription } from '../common/extensions.js';35import { ExtensionsProposedApi } from '../common/extensionsProposedApi.js';36import { dedupExtensions } from '../common/extensionsUtil.js';37import { IRemoteExtensionHostDataProvider, IRemoteExtensionHostInitData, RemoteExtensionHost } from '../common/remoteExtensionHost.js';38import { ILifecycleService, LifecyclePhase } from '../../lifecycle/common/lifecycle.js';39import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';40import { IRemoteExplorerService } from '../../remote/common/remoteExplorerService.js';41import { IUserDataInitializationService } from '../../userData/browser/userDataInit.js';42import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js';43import { AsyncIterableEmitter, AsyncIterableProducer } from '../../../../base/common/async.js';4445export class ExtensionService extends AbstractExtensionService implements IExtensionService {4647constructor(48@IInstantiationService instantiationService: IInstantiationService,49@INotificationService notificationService: INotificationService,50@IBrowserWorkbenchEnvironmentService private readonly _browserEnvironmentService: IBrowserWorkbenchEnvironmentService,51@ITelemetryService telemetryService: ITelemetryService,52@IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService,53@IFileService fileService: IFileService,54@IProductService productService: IProductService,55@IWorkbenchExtensionManagementService extensionManagementService: IWorkbenchExtensionManagementService,56@IWorkspaceContextService contextService: IWorkspaceContextService,57@IConfigurationService configurationService: IConfigurationService,58@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,59@IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService,60@ILogService logService: ILogService,61@IRemoteAgentService remoteAgentService: IRemoteAgentService,62@IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService,63@ILifecycleService lifecycleService: ILifecycleService,64@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,65@IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService,66@IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService,67@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,68@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,69@IDialogService dialogService: IDialogService,70) {71const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi);72const extensionHostFactory = new BrowserExtensionHostFactory(73extensionsProposedApi,74() => this._scanWebExtensions(),75() => this._getExtensionRegistrySnapshotWhenReady(),76instantiationService,77remoteAgentService,78remoteAuthorityResolverService,79extensionEnablementService,80logService81);82super(83{ hasLocalProcess: false, allowRemoteExtensionsInLocalWebWorker: true },84extensionsProposedApi,85extensionHostFactory,86new BrowserExtensionHostKindPicker(logService),87instantiationService,88notificationService,89_browserEnvironmentService,90telemetryService,91extensionEnablementService,92fileService,93productService,94extensionManagementService,95contextService,96configurationService,97extensionManifestPropertiesService,98logService,99remoteAgentService,100remoteExtensionsScannerService,101lifecycleService,102remoteAuthorityResolverService,103dialogService104);105106// Initialize installed extensions first and do it only after workbench is ready107lifecycleService.when(LifecyclePhase.Ready).then(async () => {108await this._initializeIfNeeded();109});110111this._initFetchFileSystem();112}113114private _initFetchFileSystem(): void {115const provider = new FetchFileSystemProvider();116this._register(this._fileService.registerProvider(Schemas.http, provider));117this._register(this._fileService.registerProvider(Schemas.https, provider));118}119120protected override async _initialize(): Promise<void> {121await this._userDataInitializationService.initializeInstalledExtensions(this._instantiationService);122await super._initialize();123}124125private _scanWebExtensionsPromise: Promise<IExtensionDescription[]> | undefined;126private async _scanWebExtensions(): Promise<IExtensionDescription[]> {127if (!this._scanWebExtensionsPromise) {128this._scanWebExtensionsPromise = (async () => {129const system: IExtensionDescription[] = [], user: IExtensionDescription[] = [], development: IExtensionDescription[] = [];130try {131await Promise.all([132this._webExtensionsScannerService.scanSystemExtensions().then(extensions => system.push(...extensions.map(e => toExtensionDescription(e)))),133this._webExtensionsScannerService.scanUserExtensions(this._userDataProfileService.currentProfile.extensionsResource, { skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))),134this._webExtensionsScannerService.scanExtensionsUnderDevelopment().then(extensions => development.push(...extensions.map(e => toExtensionDescription(e, true))))135]);136} catch (error) {137this._logService.error(error);138}139return dedupExtensions(system, user, [], development, this._logService);140})();141}142return this._scanWebExtensionsPromise;143}144145private async _resolveExtensionsDefault(emitter: AsyncIterableEmitter<ResolvedExtensions>) {146const [localExtensions, remoteExtensions] = await Promise.all([147this._scanWebExtensions(),148this._remoteExtensionsScannerService.scanExtensions()149]);150151if (remoteExtensions.length) {152emitter.emitOne(new RemoteExtensions(remoteExtensions));153}154emitter.emitOne(new LocalExtensions(localExtensions));155}156157protected _resolveExtensions(): AsyncIterable<ResolvedExtensions> {158return new AsyncIterableProducer(emitter => this._doResolveExtensions(emitter));159}160161private async _doResolveExtensions(emitter: AsyncIterableEmitter<ResolvedExtensions>): Promise<void> {162if (!this._browserEnvironmentService.expectsResolverExtension) {163return this._resolveExtensionsDefault(emitter);164}165166const remoteAuthority = this._environmentService.remoteAuthority!;167168// Now that the canonical URI provider has been registered, we need to wait for the trust state to be169// calculated. The trust state will be used while resolving the authority, however the resolver can170// override the trust state through the resolver result.171await this._workspaceTrustManagementService.workspaceResolved;172173const localExtensions = await this._scanWebExtensions();174const resolverExtensions = localExtensions.filter(extension => isResolverExtension(extension));175if (resolverExtensions.length) {176emitter.emitOne(new ResolverExtensions(resolverExtensions));177}178179let resolverResult: ResolverResult;180try {181resolverResult = await this._resolveAuthorityInitial(remoteAuthority);182} catch (err) {183if (RemoteAuthorityResolverError.isHandled(err)) {184console.log(`Error handled: Not showing a notification for the error`);185}186this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);187188// Proceed with the local extension host189return this._resolveExtensionsDefault(emitter);190}191192// set the resolved authority193this._remoteAuthorityResolverService._setResolvedAuthority(resolverResult.authority, resolverResult.options);194this._remoteExplorerService.setTunnelInformation(resolverResult.tunnelInformation);195196// monitor for breakage197const connection = this._remoteAgentService.getConnection();198if (connection) {199this._register(connection.onDidStateChange(async (e) => {200if (e.type === PersistentConnectionEventType.ConnectionLost) {201this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);202}203}));204this._register(connection.onReconnecting(() => this._resolveAuthorityAgain()));205}206207return this._resolveExtensionsDefault(emitter);208}209210protected async _onExtensionHostExit(code: number): Promise<void> {211// Dispose everything associated with the extension host212await this._doStopExtensionHosts();213214// If we are running extension tests, forward logs and exit code215const automatedWindow = mainWindow as unknown as IAutomatedWindow;216if (typeof automatedWindow.codeAutomationExit === 'function') {217automatedWindow.codeAutomationExit(code, await getLogs(this._fileService, this._environmentService));218}219}220221protected async _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {222return this._resolveAuthorityOnExtensionHosts(ExtensionHostKind.LocalWebWorker, remoteAuthority);223}224}225226class BrowserExtensionHostFactory implements IExtensionHostFactory {227228constructor(229private readonly _extensionsProposedApi: ExtensionsProposedApi,230private readonly _scanWebExtensions: () => Promise<IExtensionDescription[]>,231private readonly _getExtensionRegistrySnapshotWhenReady: () => Promise<ExtensionDescriptionRegistrySnapshot>,232@IInstantiationService private readonly _instantiationService: IInstantiationService,233@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,234@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,235@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,236@ILogService private readonly _logService: ILogService,237) { }238239createExtensionHost(runningLocations: ExtensionRunningLocationTracker, runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null {240switch (runningLocation.kind) {241case ExtensionHostKind.LocalProcess: {242return null;243}244case ExtensionHostKind.LocalWebWorker: {245const startup = (246isInitialStart247? ExtensionHostStartup.EagerManualStart248: ExtensionHostStartup.EagerAutoStart249);250return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, startup, this._createLocalExtensionHostDataProvider(runningLocations, runningLocation, isInitialStart));251}252case ExtensionHostKind.Remote: {253const remoteAgentConnection = this._remoteAgentService.getConnection();254if (remoteAgentConnection) {255return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority));256}257return null;258}259}260}261262private _createLocalExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: ExtensionRunningLocation, isInitialStart: boolean): IWebWorkerExtensionHostDataProvider {263return {264getInitData: async (): Promise<IWebWorkerExtensionHostInitData> => {265if (isInitialStart) {266// Here we load even extensions that would be disabled by workspace trust267const localExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, await this._scanWebExtensions(), /* ignore workspace trust */true);268const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false);269const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation));270const extensions = new ExtensionHostExtensions(0, localExtensions, myExtensions.map(extension => extension.identifier));271return { extensions };272} else {273// restart case274const snapshot = await this._getExtensionRegistrySnapshotWhenReady();275const myExtensions = runningLocations.filterByRunningLocation(snapshot.extensions, desiredRunningLocation);276const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier));277return { extensions };278}279}280};281}282283private _createRemoteExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, remoteAuthority: string): IRemoteExtensionHostDataProvider {284return {285remoteAuthority: remoteAuthority,286getInitData: async (): Promise<IRemoteExtensionHostInitData> => {287const snapshot = await this._getExtensionRegistrySnapshotWhenReady();288289const remoteEnv = await this._remoteAgentService.getEnvironment();290if (!remoteEnv) {291throw new Error('Cannot provide init data for remote extension host!');292}293294const myExtensions = runningLocations.filterByExtensionHostKind(snapshot.extensions, ExtensionHostKind.Remote);295const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier));296297return {298connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAuthority),299pid: remoteEnv.pid,300appRoot: remoteEnv.appRoot,301extensionHostLogsPath: remoteEnv.extensionHostLogsPath,302globalStorageHome: remoteEnv.globalStorageHome,303workspaceStorageHome: remoteEnv.workspaceStorageHome,304extensions,305};306}307};308}309}310311export class BrowserExtensionHostKindPicker implements IExtensionHostKindPicker {312313constructor(314@ILogService private readonly _logService: ILogService,315) { }316317pickExtensionHostKind(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null {318const result = BrowserExtensionHostKindPicker.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference);319this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionHostKindToString(result)}`);320return result;321}322323public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null {324const result: ExtensionHostKind[] = [];325let canRunRemotely = false;326for (const extensionKind of extensionKinds) {327if (extensionKind === 'ui' && isInstalledRemotely) {328// ui extensions run remotely if possible (but only as a last resort)329if (preference === ExtensionRunningPreference.Remote) {330return ExtensionHostKind.Remote;331} else {332canRunRemotely = true;333}334}335if (extensionKind === 'workspace' && isInstalledRemotely) {336// workspace extensions run remotely if possible337if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Remote) {338return ExtensionHostKind.Remote;339} else {340result.push(ExtensionHostKind.Remote);341}342}343if (extensionKind === 'web' && (isInstalledLocally || isInstalledRemotely)) {344// web worker extensions run in the local web worker if possible345if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) {346return ExtensionHostKind.LocalWebWorker;347} else {348result.push(ExtensionHostKind.LocalWebWorker);349}350}351}352if (canRunRemotely) {353result.push(ExtensionHostKind.Remote);354}355return (result.length > 0 ? result[0] : null);356}357}358359registerSingleton(IExtensionService, ExtensionService, InstantiationType.Eager);360361362