Path: blob/main/src/vs/workbench/api/browser/mainThreadExtensionService.ts
5236 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 { toAction } from '../../../base/common/actions.js';6import { VSBuffer } from '../../../base/common/buffer.js';7import { CancellationToken } from '../../../base/common/cancellation.js';8import { SerializedError, transformErrorFromSerialization } from '../../../base/common/errors.js';9import { FileAccess } from '../../../base/common/network.js';10import Severity from '../../../base/common/severity.js';11import { URI, UriComponents } from '../../../base/common/uri.js';12import { localize } from '../../../nls.js';13import { ICommandService } from '../../../platform/commands/common/commands.js';14import { ILocalExtension } from '../../../platform/extensionManagement/common/extensionManagement.js';15import { areSameExtensions } from '../../../platform/extensionManagement/common/extensionManagementUtil.js';16import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';17import { INotificationService } from '../../../platform/notification/common/notification.js';18import { IRemoteConnectionData, ManagedRemoteConnection, RemoteConnection, RemoteConnectionType, ResolvedAuthority, WebSocketRemoteConnection } from '../../../platform/remote/common/remoteAuthorityResolver.js';19import { ExtHostContext, ExtHostExtensionServiceShape, MainContext, MainThreadExtensionServiceShape } from '../common/extHost.protocol.js';20import { IExtension, IExtensionsWorkbenchService } from '../../contrib/extensions/common/extensions.js';21import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js';22import { EnablementState, IWorkbenchExtensionEnablementService } from '../../services/extensionManagement/common/extensionManagement.js';23import { ExtensionHostKind } from '../../services/extensions/common/extensionHostKind.js';24import { IExtensionDescriptionDelta } from '../../services/extensions/common/extensionHostProtocol.js';25import { IExtensionHostProxy, IResolveAuthorityResult } from '../../services/extensions/common/extensionHostProxy.js';26import { ActivationKind, ExtensionActivationReason, IExtensionService, IInternalExtensionService, MissingExtensionDependency } from '../../services/extensions/common/extensions.js';27import { extHostNamedCustomer, IExtHostContext, IInternalExtHostContext } from '../../services/extensions/common/extHostCustomers.js';28import { Dto } from '../../services/extensions/common/proxyIdentifier.js';29import { IHostService } from '../../services/host/browser/host.js';30import { ITimerService } from '../../services/timer/browser/timerService.js';3132@extHostNamedCustomer(MainContext.MainThreadExtensionService)33export class MainThreadExtensionService implements MainThreadExtensionServiceShape {3435private readonly _extensionHostKind: ExtensionHostKind;36private readonly _internalExtensionService: IInternalExtensionService;3738constructor(39extHostContext: IExtHostContext,40@IExtensionService private readonly _extensionService: IExtensionService,41@INotificationService private readonly _notificationService: INotificationService,42@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,43@IHostService private readonly _hostService: IHostService,44@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,45@ITimerService private readonly _timerService: ITimerService,46@ICommandService private readonly _commandService: ICommandService,47@IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService,48) {49this._extensionHostKind = extHostContext.extensionHostKind;5051const internalExtHostContext = (<IInternalExtHostContext>extHostContext);52this._internalExtensionService = internalExtHostContext.internalExtensionService;53internalExtHostContext._setExtensionHostProxy(54new ExtensionHostProxy(extHostContext.getProxy(ExtHostContext.ExtHostExtensionService))55);56// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any57internalExtHostContext._setAllMainProxyIdentifiers(Object.keys(MainContext).map((key) => (<any>MainContext)[key]));58}5960public dispose(): void {61}6263$getExtension(extensionId: string) {64return this._extensionService.getExtension(extensionId);65}66$activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {67return this._internalExtensionService._activateById(extensionId, reason);68}69async $onWillActivateExtension(extensionId: ExtensionIdentifier): Promise<void> {70this._internalExtensionService._onWillActivateExtension(extensionId);71}72$onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void {73this._internalExtensionService._onDidActivateExtension(extensionId, codeLoadingTime, activateCallTime, activateResolvedTime, activationReason);74}75$onExtensionRuntimeError(extensionId: ExtensionIdentifier, data: SerializedError): void {76const error = transformErrorFromSerialization(data);77this._internalExtensionService._onExtensionRuntimeError(extensionId, error);78console.error(`[${extensionId.value}]${error.message}`);79console.error(error.stack);80}81async $onExtensionActivationError(extensionId: ExtensionIdentifier, data: SerializedError, missingExtensionDependency: MissingExtensionDependency | null): Promise<void> {82const error = transformErrorFromSerialization(data);8384this._internalExtensionService._onDidActivateExtensionError(extensionId, error);8586if (missingExtensionDependency) {87const extension = await this._extensionService.getExtension(extensionId.value);88if (extension) {89const local = await this._extensionsWorkbenchService.queryLocal();90const installedDependency = local.find(i => areSameExtensions(i.identifier, { id: missingExtensionDependency.dependency }));91if (installedDependency?.local) {92await this._handleMissingInstalledDependency(extension, installedDependency.local);93return;94} else {95await this._handleMissingNotInstalledDependency(extension, missingExtensionDependency.dependency);96return;97}98}99}100101const isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;102if (isDev) {103this._notificationService.error(error);104return;105}106107console.error(error.message);108}109110private async _handleMissingInstalledDependency(extension: IExtensionDescription, missingInstalledDependency: ILocalExtension): Promise<void> {111const extName = extension.displayName || extension.name;112if (this._extensionEnablementService.isEnabled(missingInstalledDependency)) {113this._notificationService.notify({114severity: Severity.Error,115message: localize('reload window', "Cannot activate the '{0}' extension because it depends on the '{1}' extension, which is not loaded. Would you like to reload the window to load the extension?", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name),116actions: {117primary: [toAction({ id: 'reload', label: localize('reload', "Reload Window"), run: () => this._hostService.reload() })]118}119});120} else {121const enablementState = this._extensionEnablementService.getEnablementState(missingInstalledDependency);122if (enablementState === EnablementState.DisabledByVirtualWorkspace) {123this._notificationService.notify({124severity: Severity.Error,125message: localize('notSupportedInWorkspace', "Cannot activate the '{0}' extension because it depends on the '{1}' extension which is not supported in the current workspace", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name),126});127} else if (enablementState === EnablementState.DisabledByTrustRequirement) {128this._notificationService.notify({129severity: Severity.Error,130message: localize('restrictedMode', "Cannot activate the '{0}' extension because it depends on the '{1}' extension which is not supported in Restricted Mode", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name),131actions: {132primary: [toAction({ id: 'manageWorkspaceTrust', label: localize('manageWorkspaceTrust', "Manage Workspace Trust"), run: () => this._commandService.executeCommand('workbench.trust.manage') })]133}134});135} else if (this._extensionEnablementService.canChangeEnablement(missingInstalledDependency)) {136this._notificationService.notify({137severity: Severity.Error,138message: localize('disabledDep', "Cannot activate the '{0}' extension because it depends on the '{1}' extension which is disabled. Would you like to enable the extension and reload the window?", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name),139actions: {140primary: [toAction({141id: 'enable', label: localize('enable dep', "Enable and Reload"), enabled: true,142run: () => this._extensionEnablementService.setEnablement([missingInstalledDependency], enablementState === EnablementState.DisabledGlobally ? EnablementState.EnabledGlobally : EnablementState.EnabledWorkspace)143.then(() => this._hostService.reload(), e => this._notificationService.error(e))144})]145}146});147} else {148this._notificationService.notify({149severity: Severity.Error,150message: localize('disabledDepNoAction', "Cannot activate the '{0}' extension because it depends on the '{1}' extension which is disabled.", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name),151});152}153}154}155156private async _handleMissingNotInstalledDependency(extension: IExtensionDescription, missingDependency: string): Promise<void> {157const extName = extension.displayName || extension.name;158let dependencyExtension: IExtension | null = null;159try {160dependencyExtension = (await this._extensionsWorkbenchService.getExtensions([{ id: missingDependency }], CancellationToken.None))[0];161} catch (err) {162}163if (dependencyExtension) {164this._notificationService.notify({165severity: Severity.Error,166message: localize('uninstalledDep', "Cannot activate the '{0}' extension because it depends on the '{1}' extension from '{2}', which is not installed. Would you like to install the extension and reload the window?", extName, dependencyExtension.displayName, dependencyExtension.publisherDisplayName),167actions: {168primary: [toAction({169id: 'install',170label: localize('install missing dep', "Install and Reload"),171run: () => this._extensionsWorkbenchService.install(dependencyExtension)172.then(() => this._hostService.reload(), e => this._notificationService.error(e))173})]174}175});176} else {177this._notificationService.error(localize('unknownDep', "Cannot activate the '{0}' extension because it depends on an unknown '{1}' extension.", extName, missingDependency));178}179}180181async $setPerformanceMarks(marks: PerformanceMark[]): Promise<void> {182if (this._extensionHostKind === ExtensionHostKind.LocalProcess) {183this._timerService.setPerformanceMarks('localExtHost', marks);184} else if (this._extensionHostKind === ExtensionHostKind.LocalWebWorker) {185this._timerService.setPerformanceMarks('workerExtHost', marks);186} else {187this._timerService.setPerformanceMarks('remoteExtHost', marks);188}189}190191async $asBrowserUri(uri: UriComponents): Promise<UriComponents> {192return FileAccess.uriToBrowserUri(URI.revive(uri));193}194}195196class ExtensionHostProxy implements IExtensionHostProxy {197constructor(198private readonly _actual: ExtHostExtensionServiceShape199) { }200201async resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult> {202const resolved = reviveResolveAuthorityResult(await this._actual.$resolveAuthority(remoteAuthority, resolveAttempt));203return resolved;204}205async getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI | null> {206const uriComponents = await this._actual.$getCanonicalURI(remoteAuthority, uri);207return (uriComponents ? URI.revive(uriComponents) : uriComponents);208}209startExtensionHost(extensionsDelta: IExtensionDescriptionDelta): Promise<void> {210return this._actual.$startExtensionHost(extensionsDelta);211}212extensionTestsExecute(): Promise<number> {213return this._actual.$extensionTestsExecute();214}215activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {216return this._actual.$activateByEvent(activationEvent, activationKind);217}218activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean> {219return this._actual.$activate(extensionId, reason);220}221setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void> {222return this._actual.$setRemoteEnvironment(env);223}224updateRemoteConnectionData(connectionData: IRemoteConnectionData): Promise<void> {225return this._actual.$updateRemoteConnectionData(connectionData);226}227deltaExtensions(extensionsDelta: IExtensionDescriptionDelta): Promise<void> {228return this._actual.$deltaExtensions(extensionsDelta);229}230test_latency(n: number): Promise<number> {231return this._actual.$test_latency(n);232}233test_up(b: VSBuffer): Promise<number> {234return this._actual.$test_up(b);235}236test_down(size: number): Promise<VSBuffer> {237return this._actual.$test_down(size);238}239}240241function reviveResolveAuthorityResult(result: Dto<IResolveAuthorityResult>): IResolveAuthorityResult {242if (result.type === 'ok') {243return {244type: 'ok',245value: {246...result.value,247authority: reviveResolvedAuthority(result.value.authority),248}249};250} else {251return result;252}253}254255function reviveResolvedAuthority(resolvedAuthority: Dto<ResolvedAuthority>): ResolvedAuthority {256return {257...resolvedAuthority,258connectTo: reviveConnection(resolvedAuthority.connectTo),259};260}261262function reviveConnection(connection: Dto<RemoteConnection>): RemoteConnection {263if (connection.type === RemoteConnectionType.WebSocket) {264return new WebSocketRemoteConnection(connection.host, connection.port);265}266return new ManagedRemoteConnection(connection.id);267}268269270