Path: blob/main/src/vs/workbench/services/extensions/common/extensions.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 { Event } from '../../../../base/common/event.js';6import Severity from '../../../../base/common/severity.js';7import { URI } from '../../../../base/common/uri.js';8import { IMessagePassingProtocol } from '../../../../base/parts/ipc/common/ipc.js';9import { getExtensionId, getGalleryExtensionId } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';10import { ImplicitActivationEvents } from '../../../../platform/extensionManagement/common/implicitActivationEvents.js';11import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, ExtensionType, IExtension, IExtensionContributions, IExtensionDescription, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';12import { ApiProposalName } from '../../../../platform/extensions/common/extensionsApiProposals.js';13import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';14import { IV8Profile } from '../../../../platform/profiling/common/profiling.js';15import { ExtensionHostKind } from './extensionHostKind.js';16import { IExtensionDescriptionDelta, IExtensionDescriptionSnapshot } from './extensionHostProtocol.js';17import { ExtensionRunningLocation } from './extensionRunningLocation.js';18import { IExtensionPoint } from './extensionsRegistry.js';1920export const nullExtensionDescription = Object.freeze<IExtensionDescription>({21identifier: new ExtensionIdentifier('nullExtensionDescription'),22name: 'Null Extension Description',23version: '0.0.0',24publisher: 'vscode',25engines: { vscode: '' },26extensionLocation: URI.parse('void:location'),27isBuiltin: false,28targetPlatform: TargetPlatform.UNDEFINED,29isUserBuiltin: false,30isUnderDevelopment: false,31preRelease: false,32});3334export type WebWorkerExtHostConfigValue = boolean | 'auto';35export const webWorkerExtHostConfig = 'extensions.webWorker';3637export const IExtensionService = createDecorator<IExtensionService>('extensionService');3839export interface IMessage {40type: Severity;41message: string;42extensionId: ExtensionIdentifier;43extensionPointId: string;44}4546export interface IExtensionsStatus {47id: ExtensionIdentifier;48messages: IMessage[];49activationStarted: boolean;50activationTimes: ActivationTimes | undefined;51runtimeErrors: Error[];52runningLocation: ExtensionRunningLocation | null;53}5455export class MissingExtensionDependency {56constructor(readonly dependency: string) { }57}5859/**60* e.g.61* ```62* {63* startTime: 1511954813493000,64* endTime: 1511954835590000,65* deltas: [ 100, 1500, 123456, 1500, 100000 ],66* ids: [ 'idle', 'self', 'extension1', 'self', 'idle' ]67* }68* ```69*/70export interface IExtensionHostProfile {71/**72* Profiling start timestamp in microseconds.73*/74startTime: number;75/**76* Profiling end timestamp in microseconds.77*/78endTime: number;79/**80* Duration of segment in microseconds.81*/82deltas: number[];83/**84* Segment identifier: extension id or one of the four known strings.85*/86ids: ProfileSegmentId[];8788/**89* Get the information as a .cpuprofile.90*/91data: IV8Profile;9293/**94* Get the aggregated time per segmentId95*/96getAggregatedTimes(): Map<ProfileSegmentId, number>;97}9899export const enum ExtensionHostStartup {100/**101* The extension host should be launched immediately and doesn't require a `$startExtensionHost` call.102*/103EagerAutoStart = 1,104/**105* The extension host should be launched immediately and needs a `$startExtensionHost` call.106*/107EagerManualStart = 2,108/**109* The extension host should be launched lazily and only when it has extensions it needs to host. It doesn't require a `$startExtensionHost` call.110*/111LazyAutoStart = 3,112}113114export interface IExtensionInspectInfo {115readonly port: number;116readonly host: string;117readonly devtoolsUrl?: string;118readonly devtoolsLabel?: string;119}120121export interface IExtensionHost {122readonly pid: number | null;123readonly runningLocation: ExtensionRunningLocation;124readonly remoteAuthority: string | null;125readonly startup: ExtensionHostStartup;126/**127* A collection of extensions which includes information about which128* extension will execute or is executing on this extension host.129* **NOTE**: this will reflect extensions correctly only after `start()` resolves.130*/131readonly extensions: ExtensionHostExtensions | null;132readonly onExit: Event<[number, string | null]>;133134start(): Promise<IMessagePassingProtocol>;135getInspectPort(): IExtensionInspectInfo | undefined;136enableInspectPort(): Promise<boolean>;137disconnect?(): Promise<void>;138dispose(): void;139}140141export class ExtensionHostExtensions {142private _versionId: number;143private _allExtensions: IExtensionDescription[];144private _myExtensions: ExtensionIdentifier[];145private _myActivationEvents: Set<string> | null;146147public get versionId(): number {148return this._versionId;149}150151public get allExtensions(): IExtensionDescription[] {152return this._allExtensions;153}154155public get myExtensions(): ExtensionIdentifier[] {156return this._myExtensions;157}158159constructor(versionId: number, allExtensions: readonly IExtensionDescription[], myExtensions: ExtensionIdentifier[]) {160this._versionId = versionId;161this._allExtensions = allExtensions.slice(0);162this._myExtensions = myExtensions.slice(0);163this._myActivationEvents = null;164}165166toSnapshot(): IExtensionDescriptionSnapshot {167return {168versionId: this._versionId,169allExtensions: this._allExtensions,170myExtensions: this._myExtensions,171activationEvents: ImplicitActivationEvents.createActivationEventsMap(this._allExtensions)172};173}174175public set(versionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): IExtensionDescriptionDelta {176if (this._versionId > versionId) {177throw new Error(`ExtensionHostExtensions: invalid versionId ${versionId} (current: ${this._versionId})`);178}179const toRemove: ExtensionIdentifier[] = [];180const toAdd: IExtensionDescription[] = [];181const myToRemove: ExtensionIdentifier[] = [];182const myToAdd: ExtensionIdentifier[] = [];183184const oldExtensionsMap = extensionDescriptionArrayToMap(this._allExtensions);185const newExtensionsMap = extensionDescriptionArrayToMap(allExtensions);186const extensionsAreTheSame = (a: IExtensionDescription, b: IExtensionDescription) => {187return (188(a.extensionLocation.toString() === b.extensionLocation.toString())189|| (a.isBuiltin === b.isBuiltin)190|| (a.isUserBuiltin === b.isUserBuiltin)191|| (a.isUnderDevelopment === b.isUnderDevelopment)192);193};194195for (const oldExtension of this._allExtensions) {196const newExtension = newExtensionsMap.get(oldExtension.identifier);197if (!newExtension) {198toRemove.push(oldExtension.identifier);199oldExtensionsMap.delete(oldExtension.identifier);200continue;201}202if (!extensionsAreTheSame(oldExtension, newExtension)) {203// The new extension is different than the old one204// (e.g. maybe it executes in a different location)205toRemove.push(oldExtension.identifier);206oldExtensionsMap.delete(oldExtension.identifier);207continue;208}209}210for (const newExtension of allExtensions) {211const oldExtension = oldExtensionsMap.get(newExtension.identifier);212if (!oldExtension) {213toAdd.push(newExtension);214continue;215}216if (!extensionsAreTheSame(oldExtension, newExtension)) {217// The new extension is different than the old one218// (e.g. maybe it executes in a different location)219toRemove.push(oldExtension.identifier);220oldExtensionsMap.delete(oldExtension.identifier);221continue;222}223}224225const myOldExtensionsSet = new ExtensionIdentifierSet(this._myExtensions);226const myNewExtensionsSet = new ExtensionIdentifierSet(myExtensions);227for (const oldExtensionId of this._myExtensions) {228if (!myNewExtensionsSet.has(oldExtensionId)) {229myToRemove.push(oldExtensionId);230}231}232for (const newExtensionId of myExtensions) {233if (!myOldExtensionsSet.has(newExtensionId)) {234myToAdd.push(newExtensionId);235}236}237238const addActivationEvents = ImplicitActivationEvents.createActivationEventsMap(toAdd);239const delta = { versionId, toRemove, toAdd, addActivationEvents, myToRemove, myToAdd };240this.delta(delta);241return delta;242}243244public delta(extensionsDelta: IExtensionDescriptionDelta): IExtensionDescriptionDelta | null {245if (this._versionId >= extensionsDelta.versionId) {246// ignore older deltas247return null;248}249250const { toRemove, toAdd, myToRemove, myToAdd } = extensionsDelta;251// First handle removals252const toRemoveSet = new ExtensionIdentifierSet(toRemove);253const myToRemoveSet = new ExtensionIdentifierSet(myToRemove);254for (let i = 0; i < this._allExtensions.length; i++) {255if (toRemoveSet.has(this._allExtensions[i].identifier)) {256this._allExtensions.splice(i, 1);257i--;258}259}260for (let i = 0; i < this._myExtensions.length; i++) {261if (myToRemoveSet.has(this._myExtensions[i])) {262this._myExtensions.splice(i, 1);263i--;264}265}266// Then handle additions267for (const extension of toAdd) {268this._allExtensions.push(extension);269}270for (const extensionId of myToAdd) {271this._myExtensions.push(extensionId);272}273274// clear cached activation events275this._myActivationEvents = null;276277return extensionsDelta;278}279280public containsExtension(extensionId: ExtensionIdentifier): boolean {281for (const myExtensionId of this._myExtensions) {282if (ExtensionIdentifier.equals(myExtensionId, extensionId)) {283return true;284}285}286return false;287}288289public containsActivationEvent(activationEvent: string): boolean {290if (!this._myActivationEvents) {291this._myActivationEvents = this._readMyActivationEvents();292}293return this._myActivationEvents.has(activationEvent);294}295296private _readMyActivationEvents(): Set<string> {297const result = new Set<string>();298299for (const extensionDescription of this._allExtensions) {300if (!this.containsExtension(extensionDescription.identifier)) {301continue;302}303304const activationEvents = ImplicitActivationEvents.readActivationEvents(extensionDescription);305for (const activationEvent of activationEvents) {306result.add(activationEvent);307}308}309310return result;311}312}313314function extensionDescriptionArrayToMap(extensions: IExtensionDescription[]): ExtensionIdentifierMap<IExtensionDescription> {315const result = new ExtensionIdentifierMap<IExtensionDescription>();316for (const extension of extensions) {317result.set(extension.identifier, extension);318}319return result;320}321322export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {323if (!extension.enabledApiProposals) {324return false;325}326return extension.enabledApiProposals.includes(proposal);327}328329export function checkProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): void {330if (!isProposedApiEnabled(extension, proposal)) {331throw new Error(`Extension '${extension.identifier.value}' CANNOT use API proposal: ${proposal}.\nIts package.json#enabledApiProposals-property declares: ${extension.enabledApiProposals?.join(', ') ?? '[]'} but NOT ${proposal}.\n The missing proposal MUST be added and you must start in extension development mode or use the following command line switch: --enable-proposed-api ${extension.identifier.value}`);332}333}334335336/**337* Extension id or one of the four known program states.338*/339export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self';340341export interface ExtensionActivationReason {342readonly startup: boolean;343readonly extensionId: ExtensionIdentifier;344readonly activationEvent: string;345}346347export class ActivationTimes {348constructor(349public readonly codeLoadingTime: number,350public readonly activateCallTime: number,351public readonly activateResolvedTime: number,352public readonly activationReason: ExtensionActivationReason353) {354}355}356357export class ExtensionPointContribution<T> {358readonly description: IExtensionDescription;359readonly value: T;360361constructor(description: IExtensionDescription, value: T) {362this.description = description;363this.value = value;364}365}366367export interface IWillActivateEvent {368readonly event: string;369readonly activation: Promise<void>;370}371372export interface IResponsiveStateChangeEvent {373extensionHostKind: ExtensionHostKind;374isResponsive: boolean;375/**376* Return the inspect port or `0`. `0` means inspection is not possible.377*/378getInspectListener(tryEnableInspector: boolean): Promise<IExtensionInspectInfo | undefined>;379}380381export const enum ActivationKind {382Normal = 0,383Immediate = 1384}385386export interface WillStopExtensionHostsEvent {387388/**389* A human readable reason for stopping the extension hosts390* that e.g. can be shown in a confirmation dialog to the391* user.392*/393readonly reason: string;394395/**396* A flag to indicate if the operation was triggered automatically397*/398readonly auto: boolean;399400/**401* Allows to veto the stopping of extension hosts. The veto can be a long running402* operation.403*404* @param reason a human readable reason for vetoing the extension host stop in case405* where the resolved `value: true`.406*/407veto(value: boolean | Promise<boolean>, reason: string): void;408}409410export interface IExtensionService {411readonly _serviceBrand: undefined;412413/**414* An event emitted when extensions are registered after their extension points got handled.415*416* This event will also fire on startup to signal the installed extensions.417*418* @returns the extensions that got registered419*/420onDidRegisterExtensions: Event<void>;421422/**423* @event424* Fired when extensions status changes.425* The event contains the ids of the extensions that have changed.426*/427onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]>;428429/**430* Fired when the available extensions change (i.e. when extensions are added or removed).431*/432onDidChangeExtensions: Event<{ readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }>;433434/**435* All registered extensions.436* - List will be empty initially during workbench startup and will be filled with extensions as they are registered437* - Listen to `onDidChangeExtensions` event for any changes to the extensions list. It will change as extensions get registered or de-reigstered.438* - Listen to `onDidRegisterExtensions` event or wait for `whenInstalledExtensionsRegistered` promise to get the initial list of registered extensions.439*/440readonly extensions: readonly IExtensionDescription[];441442/**443* An event that is fired when activation happens.444*/445onWillActivateByEvent: Event<IWillActivateEvent>;446447/**448* An event that is fired when an extension host changes its449* responsive-state.450*/451onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent>;452453/**454* Fired before stop of extension hosts happens. Allows listeners to veto against the455* stop to prevent it from happening.456*/457onWillStop: Event<WillStopExtensionHostsEvent>;458459/**460* Send an activation event and activate interested extensions.461*462* This will wait for the normal startup of the extension host(s).463*464* In extraordinary circumstances, if the activation event needs to activate465* one or more extensions before the normal startup is finished, then you can use466* `ActivationKind.Immediate`. Please do not use this flag unless really necessary467* and you understand all consequences.468*/469activateByEvent(activationEvent: string, activationKind?: ActivationKind): Promise<void>;470471/**472* Send an activation ID and activate interested extensions.473*474*/475activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;476477/**478* Determine if `activateByEvent(activationEvent)` has resolved already.479*480* i.e. the activation event is finished and all interested extensions are already active.481*/482activationEventIsDone(activationEvent: string): boolean;483484/**485* An promise that resolves when the installed extensions are registered after486* their extension points got handled.487*/488whenInstalledExtensionsRegistered(): Promise<boolean>;489490/**491* Return a specific extension492* @param id An extension id493*/494getExtension(id: string): Promise<IExtensionDescription | undefined>;495496/**497* Returns `true` if the given extension can be added. Otherwise `false`.498* @param extension An extension499*/500canAddExtension(extension: IExtensionDescription): boolean;501502/**503* Returns `true` if the given extension can be removed. Otherwise `false`.504* @param extension An extension505*/506canRemoveExtension(extension: IExtensionDescription): boolean;507508/**509* Read all contributions to an extension point.510*/511readExtensionPointContributions<T extends IExtensionContributions[keyof IExtensionContributions]>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]>;512513/**514* Get information about extensions status.515*/516getExtensionsStatus(): { [id: string]: IExtensionsStatus };517518/**519* Return the inspect ports (if inspection is possible) for extension hosts of kind `extensionHostKind`.520*/521getInspectPorts(extensionHostKind: ExtensionHostKind, tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]>;522523/**524* Stops the extension hosts.525*526* @param reason a human readable reason for stopping the extension hosts. This maybe527* can be presented to the user when showing dialogs.528*529* @param auto indicates if the operation was triggered by an automatic action530*531* @returns a promise that resolves to `true` if the extension hosts were stopped, `false`532* if the operation was vetoed by listeners of the `onWillStop` event.533*/534stopExtensionHosts(reason: string, auto?: boolean): Promise<boolean>;535536/**537* Starts the extension hosts. If updates are provided, the extension hosts are started with the given updates.538*/539startExtensionHosts(updates?: { readonly toAdd: readonly IExtension[]; readonly toRemove: readonly string[] }): Promise<void>;540541/**542* Modify the environment of the remote extension host543* @param env New properties for the remote extension host544*/545setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;546}547548export interface IInternalExtensionService {549_activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;550_onWillActivateExtension(extensionId: ExtensionIdentifier): void;551_onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;552_onDidActivateExtensionError(extensionId: ExtensionIdentifier, error: Error): void;553_onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void;554}555556export interface ProfileSession {557stop(): Promise<IExtensionHostProfile>;558}559560export function toExtension(extensionDescription: IExtensionDescription): IExtension {561return {562type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User,563isBuiltin: extensionDescription.isBuiltin || extensionDescription.isUserBuiltin,564identifier: { id: getGalleryExtensionId(extensionDescription.publisher, extensionDescription.name), uuid: extensionDescription.uuid },565manifest: extensionDescription,566location: extensionDescription.extensionLocation,567targetPlatform: extensionDescription.targetPlatform,568validations: [],569isValid: true,570preRelease: extensionDescription.preRelease,571publisherDisplayName: extensionDescription.publisherDisplayName,572};573}574575export function toExtensionDescription(extension: IExtension, isUnderDevelopment?: boolean): IExtensionDescription {576const id = getExtensionId(extension.manifest.publisher, extension.manifest.name);577return {578id,579identifier: new ExtensionIdentifier(id),580isBuiltin: extension.type === ExtensionType.System,581isUserBuiltin: extension.type === ExtensionType.User && extension.isBuiltin,582isUnderDevelopment: !!isUnderDevelopment,583extensionLocation: extension.location,584uuid: extension.identifier.uuid,585targetPlatform: extension.targetPlatform,586publisherDisplayName: extension.publisherDisplayName,587preRelease: extension.preRelease,588...extension.manifest589};590}591592593export class NullExtensionService implements IExtensionService {594declare readonly _serviceBrand: undefined;595onDidRegisterExtensions: Event<void> = Event.None;596onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = Event.None;597onDidChangeExtensions = Event.None;598onWillActivateByEvent: Event<IWillActivateEvent> = Event.None;599onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = Event.None;600onWillStop: Event<WillStopExtensionHostsEvent> = Event.None;601readonly extensions = [];602activateByEvent(_activationEvent: string): Promise<void> { return Promise.resolve(undefined); }603activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> { return Promise.resolve(undefined); }604activationEventIsDone(_activationEvent: string): boolean { return false; }605whenInstalledExtensionsRegistered(): Promise<boolean> { return Promise.resolve(true); }606getExtension() { return Promise.resolve(undefined); }607readExtensionPointContributions<T>(_extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> { return Promise.resolve(Object.create(null)); }608getExtensionsStatus(): { [id: string]: IExtensionsStatus } { return Object.create(null); }609getInspectPorts(_extensionHostKind: ExtensionHostKind, _tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]> { return Promise.resolve([]); }610async stopExtensionHosts(): Promise<boolean> { return true; }611async startExtensionHosts(): Promise<void> { }612async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise<void> { }613canAddExtension(): boolean { return false; }614canRemoveExtension(): boolean { return false; }615}616617618