Path: blob/main/src/vs/workbench/services/extensions/browser/extensionService.ts
3296 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, AsyncIterableObject } 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._userDataInitializationService.initializeInstalledExtensions(this._instantiationService);109this._initialize();110});111112this._initFetchFileSystem();113}114115private _initFetchFileSystem(): void {116const provider = new FetchFileSystemProvider();117this._register(this._fileService.registerProvider(Schemas.http, provider));118this._register(this._fileService.registerProvider(Schemas.https, provider));119}120121private _scanWebExtensionsPromise: Promise<IExtensionDescription[]> | undefined;122private async _scanWebExtensions(): Promise<IExtensionDescription[]> {123if (!this._scanWebExtensionsPromise) {124this._scanWebExtensionsPromise = (async () => {125const system: IExtensionDescription[] = [], user: IExtensionDescription[] = [], development: IExtensionDescription[] = [];126try {127await Promise.all([128this._webExtensionsScannerService.scanSystemExtensions().then(extensions => system.push(...extensions.map(e => toExtensionDescription(e)))),129this._webExtensionsScannerService.scanUserExtensions(this._userDataProfileService.currentProfile.extensionsResource, { skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))),130this._webExtensionsScannerService.scanExtensionsUnderDevelopment().then(extensions => development.push(...extensions.map(e => toExtensionDescription(e, true))))131]);132} catch (error) {133this._logService.error(error);134}135return dedupExtensions(system, user, [], development, this._logService);136})();137}138return this._scanWebExtensionsPromise;139}140141private async _resolveExtensionsDefault(emitter: AsyncIterableEmitter<ResolvedExtensions>) {142const [localExtensions, remoteExtensions] = await Promise.all([143this._scanWebExtensions(),144this._remoteExtensionsScannerService.scanExtensions()145]);146147if (remoteExtensions.length) {148emitter.emitOne(new RemoteExtensions(remoteExtensions));149}150emitter.emitOne(new LocalExtensions(localExtensions));151}152153protected _resolveExtensions(): AsyncIterable<ResolvedExtensions> {154return new AsyncIterableObject(emitter => this._doResolveExtensions(emitter));155}156157private async _doResolveExtensions(emitter: AsyncIterableEmitter<ResolvedExtensions>): Promise<void> {158if (!this._browserEnvironmentService.expectsResolverExtension) {159return this._resolveExtensionsDefault(emitter);160}161162const remoteAuthority = this._environmentService.remoteAuthority!;163164// Now that the canonical URI provider has been registered, we need to wait for the trust state to be165// calculated. The trust state will be used while resolving the authority, however the resolver can166// override the trust state through the resolver result.167await this._workspaceTrustManagementService.workspaceResolved;168169const localExtensions = await this._scanWebExtensions();170const resolverExtensions = localExtensions.filter(extension => isResolverExtension(extension));171if (resolverExtensions.length) {172emitter.emitOne(new ResolverExtensions(resolverExtensions));173}174175let resolverResult: ResolverResult;176try {177resolverResult = await this._resolveAuthorityInitial(remoteAuthority);178} catch (err) {179if (RemoteAuthorityResolverError.isHandled(err)) {180console.log(`Error handled: Not showing a notification for the error`);181}182this._remoteAuthorityResolverService._setResolvedAuthorityError(remoteAuthority, err);183184// Proceed with the local extension host185return this._resolveExtensionsDefault(emitter);186}187188// set the resolved authority189this._remoteAuthorityResolverService._setResolvedAuthority(resolverResult.authority, resolverResult.options);190this._remoteExplorerService.setTunnelInformation(resolverResult.tunnelInformation);191192// monitor for breakage193const connection = this._remoteAgentService.getConnection();194if (connection) {195connection.onDidStateChange(async (e) => {196if (e.type === PersistentConnectionEventType.ConnectionLost) {197this._remoteAuthorityResolverService._clearResolvedAuthority(remoteAuthority);198}199});200connection.onReconnecting(() => this._resolveAuthorityAgain());201}202203return this._resolveExtensionsDefault(emitter);204}205206protected async _onExtensionHostExit(code: number): Promise<void> {207// Dispose everything associated with the extension host208await this._doStopExtensionHosts();209210// If we are running extension tests, forward logs and exit code211const automatedWindow = mainWindow as unknown as IAutomatedWindow;212if (typeof automatedWindow.codeAutomationExit === 'function') {213automatedWindow.codeAutomationExit(code, await getLogs(this._fileService, this._environmentService));214}215}216217protected async _resolveAuthority(remoteAuthority: string): Promise<ResolverResult> {218return this._resolveAuthorityOnExtensionHosts(ExtensionHostKind.LocalWebWorker, remoteAuthority);219}220}221222class BrowserExtensionHostFactory implements IExtensionHostFactory {223224constructor(225private readonly _extensionsProposedApi: ExtensionsProposedApi,226private readonly _scanWebExtensions: () => Promise<IExtensionDescription[]>,227private readonly _getExtensionRegistrySnapshotWhenReady: () => Promise<ExtensionDescriptionRegistrySnapshot>,228@IInstantiationService private readonly _instantiationService: IInstantiationService,229@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,230@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,231@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,232@ILogService private readonly _logService: ILogService,233) { }234235createExtensionHost(runningLocations: ExtensionRunningLocationTracker, runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null {236switch (runningLocation.kind) {237case ExtensionHostKind.LocalProcess: {238return null;239}240case ExtensionHostKind.LocalWebWorker: {241const startup = (242isInitialStart243? ExtensionHostStartup.EagerManualStart244: ExtensionHostStartup.EagerAutoStart245);246return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, startup, this._createLocalExtensionHostDataProvider(runningLocations, runningLocation, isInitialStart));247}248case ExtensionHostKind.Remote: {249const remoteAgentConnection = this._remoteAgentService.getConnection();250if (remoteAgentConnection) {251return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority));252}253return null;254}255}256}257258private _createLocalExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, desiredRunningLocation: ExtensionRunningLocation, isInitialStart: boolean): IWebWorkerExtensionHostDataProvider {259return {260getInitData: async (): Promise<IWebWorkerExtensionHostInitData> => {261if (isInitialStart) {262// Here we load even extensions that would be disabled by workspace trust263const localExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, await this._scanWebExtensions(), /* ignore workspace trust */true);264const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false);265const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation));266const extensions = new ExtensionHostExtensions(0, localExtensions, myExtensions.map(extension => extension.identifier));267return { extensions };268} else {269// restart case270const snapshot = await this._getExtensionRegistrySnapshotWhenReady();271const myExtensions = runningLocations.filterByRunningLocation(snapshot.extensions, desiredRunningLocation);272const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier));273return { extensions };274}275}276};277}278279private _createRemoteExtensionHostDataProvider(runningLocations: ExtensionRunningLocationTracker, remoteAuthority: string): IRemoteExtensionHostDataProvider {280return {281remoteAuthority: remoteAuthority,282getInitData: async (): Promise<IRemoteExtensionHostInitData> => {283const snapshot = await this._getExtensionRegistrySnapshotWhenReady();284285const remoteEnv = await this._remoteAgentService.getEnvironment();286if (!remoteEnv) {287throw new Error('Cannot provide init data for remote extension host!');288}289290const myExtensions = runningLocations.filterByExtensionHostKind(snapshot.extensions, ExtensionHostKind.Remote);291const extensions = new ExtensionHostExtensions(snapshot.versionId, snapshot.extensions, myExtensions.map(extension => extension.identifier));292293return {294connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAuthority),295pid: remoteEnv.pid,296appRoot: remoteEnv.appRoot,297extensionHostLogsPath: remoteEnv.extensionHostLogsPath,298globalStorageHome: remoteEnv.globalStorageHome,299workspaceStorageHome: remoteEnv.workspaceStorageHome,300extensions,301};302}303};304}305}306307export class BrowserExtensionHostKindPicker implements IExtensionHostKindPicker {308309constructor(310@ILogService private readonly _logService: ILogService,311) { }312313pickExtensionHostKind(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null {314const result = BrowserExtensionHostKindPicker.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely, preference);315this._logService.trace(`pickRunningLocation for ${extensionId.value}, extension kinds: [${extensionKinds.join(', ')}], isInstalledLocally: ${isInstalledLocally}, isInstalledRemotely: ${isInstalledRemotely}, preference: ${extensionRunningPreferenceToString(preference)} => ${extensionHostKindToString(result)}`);316return result;317}318319public static pickRunningLocation(extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null {320const result: ExtensionHostKind[] = [];321let canRunRemotely = false;322for (const extensionKind of extensionKinds) {323if (extensionKind === 'ui' && isInstalledRemotely) {324// ui extensions run remotely if possible (but only as a last resort)325if (preference === ExtensionRunningPreference.Remote) {326return ExtensionHostKind.Remote;327} else {328canRunRemotely = true;329}330}331if (extensionKind === 'workspace' && isInstalledRemotely) {332// workspace extensions run remotely if possible333if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Remote) {334return ExtensionHostKind.Remote;335} else {336result.push(ExtensionHostKind.Remote);337}338}339if (extensionKind === 'web' && (isInstalledLocally || isInstalledRemotely)) {340// web worker extensions run in the local web worker if possible341if (preference === ExtensionRunningPreference.None || preference === ExtensionRunningPreference.Local) {342return ExtensionHostKind.LocalWebWorker;343} else {344result.push(ExtensionHostKind.LocalWebWorker);345}346}347}348if (canRunRemotely) {349result.push(ExtensionHostKind.Remote);350}351return (result.length > 0 ? result[0] : null);352}353}354355registerSingleton(IExtensionService, ExtensionService, InstantiationType.Eager);356357358