Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/extensions/common/extensions.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Event } from '../../../../base/common/event.js';
7
import Severity from '../../../../base/common/severity.js';
8
import { URI } from '../../../../base/common/uri.js';
9
import { IMessagePassingProtocol } from '../../../../base/parts/ipc/common/ipc.js';
10
import { getExtensionId, getGalleryExtensionId } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
11
import { ImplicitActivationEvents } from '../../../../platform/extensionManagement/common/implicitActivationEvents.js';
12
import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, ExtensionType, IExtension, IExtensionContributions, IExtensionDescription, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';
13
import { ApiProposalName } from '../../../../platform/extensions/common/extensionsApiProposals.js';
14
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
15
import { IV8Profile } from '../../../../platform/profiling/common/profiling.js';
16
import { ExtensionHostKind } from './extensionHostKind.js';
17
import { IExtensionDescriptionDelta, IExtensionDescriptionSnapshot } from './extensionHostProtocol.js';
18
import { ExtensionRunningLocation } from './extensionRunningLocation.js';
19
import { IExtensionPoint } from './extensionsRegistry.js';
20
21
export const nullExtensionDescription = Object.freeze<IExtensionDescription>({
22
identifier: new ExtensionIdentifier('nullExtensionDescription'),
23
name: 'Null Extension Description',
24
version: '0.0.0',
25
publisher: 'vscode',
26
engines: { vscode: '' },
27
extensionLocation: URI.parse('void:location'),
28
isBuiltin: false,
29
targetPlatform: TargetPlatform.UNDEFINED,
30
isUserBuiltin: false,
31
isUnderDevelopment: false,
32
preRelease: false,
33
});
34
35
export type WebWorkerExtHostConfigValue = boolean | 'auto';
36
export const webWorkerExtHostConfig = 'extensions.webWorker';
37
38
export const IExtensionService = createDecorator<IExtensionService>('extensionService');
39
40
export interface IMessage {
41
type: Severity;
42
message: string;
43
extensionId: ExtensionIdentifier;
44
extensionPointId: string;
45
}
46
47
export interface IExtensionsStatus {
48
id: ExtensionIdentifier;
49
messages: IMessage[];
50
activationStarted: boolean;
51
activationTimes: ActivationTimes | undefined;
52
runtimeErrors: Error[];
53
runningLocation: ExtensionRunningLocation | null;
54
}
55
56
export class MissingExtensionDependency {
57
constructor(readonly dependency: string) { }
58
}
59
60
/**
61
* e.g.
62
* ```
63
* {
64
* startTime: 1511954813493000,
65
* endTime: 1511954835590000,
66
* deltas: [ 100, 1500, 123456, 1500, 100000 ],
67
* ids: [ 'idle', 'self', 'extension1', 'self', 'idle' ]
68
* }
69
* ```
70
*/
71
export interface IExtensionHostProfile {
72
/**
73
* Profiling start timestamp in microseconds.
74
*/
75
startTime: number;
76
/**
77
* Profiling end timestamp in microseconds.
78
*/
79
endTime: number;
80
/**
81
* Duration of segment in microseconds.
82
*/
83
deltas: number[];
84
/**
85
* Segment identifier: extension id or one of the four known strings.
86
*/
87
ids: ProfileSegmentId[];
88
89
/**
90
* Get the information as a .cpuprofile.
91
*/
92
data: IV8Profile;
93
94
/**
95
* Get the aggregated time per segmentId
96
*/
97
getAggregatedTimes(): Map<ProfileSegmentId, number>;
98
}
99
100
export const enum ExtensionHostStartup {
101
/**
102
* The extension host should be launched immediately and doesn't require a `$startExtensionHost` call.
103
*/
104
EagerAutoStart = 1,
105
/**
106
* The extension host should be launched immediately and needs a `$startExtensionHost` call.
107
*/
108
EagerManualStart = 2,
109
/**
110
* The extension host should be launched lazily and only when it has extensions it needs to host. It doesn't require a `$startExtensionHost` call.
111
*/
112
LazyAutoStart = 3,
113
}
114
115
export interface IExtensionInspectInfo {
116
readonly port: number;
117
readonly host: string;
118
readonly devtoolsUrl?: string;
119
readonly devtoolsLabel?: string;
120
}
121
122
export interface IExtensionHost {
123
readonly pid: number | null;
124
readonly runningLocation: ExtensionRunningLocation;
125
readonly remoteAuthority: string | null;
126
readonly startup: ExtensionHostStartup;
127
/**
128
* A collection of extensions which includes information about which
129
* extension will execute or is executing on this extension host.
130
* **NOTE**: this will reflect extensions correctly only after `start()` resolves.
131
*/
132
readonly extensions: ExtensionHostExtensions | null;
133
readonly onExit: Event<[number, string | null]>;
134
135
start(): Promise<IMessagePassingProtocol>;
136
getInspectPort(): IExtensionInspectInfo | undefined;
137
enableInspectPort(): Promise<boolean>;
138
disconnect?(): Promise<void>;
139
dispose(): void;
140
}
141
142
export class ExtensionHostExtensions {
143
private _versionId: number;
144
private _allExtensions: IExtensionDescription[];
145
private _myExtensions: ExtensionIdentifier[];
146
private _myActivationEvents: Set<string> | null;
147
148
public get versionId(): number {
149
return this._versionId;
150
}
151
152
public get allExtensions(): IExtensionDescription[] {
153
return this._allExtensions;
154
}
155
156
public get myExtensions(): ExtensionIdentifier[] {
157
return this._myExtensions;
158
}
159
160
constructor(versionId: number, allExtensions: readonly IExtensionDescription[], myExtensions: ExtensionIdentifier[]) {
161
this._versionId = versionId;
162
this._allExtensions = allExtensions.slice(0);
163
this._myExtensions = myExtensions.slice(0);
164
this._myActivationEvents = null;
165
}
166
167
toSnapshot(): IExtensionDescriptionSnapshot {
168
return {
169
versionId: this._versionId,
170
allExtensions: this._allExtensions,
171
myExtensions: this._myExtensions,
172
activationEvents: ImplicitActivationEvents.createActivationEventsMap(this._allExtensions)
173
};
174
}
175
176
public set(versionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): IExtensionDescriptionDelta {
177
if (this._versionId > versionId) {
178
throw new Error(`ExtensionHostExtensions: invalid versionId ${versionId} (current: ${this._versionId})`);
179
}
180
const toRemove: ExtensionIdentifier[] = [];
181
const toAdd: IExtensionDescription[] = [];
182
const myToRemove: ExtensionIdentifier[] = [];
183
const myToAdd: ExtensionIdentifier[] = [];
184
185
const oldExtensionsMap = extensionDescriptionArrayToMap(this._allExtensions);
186
const newExtensionsMap = extensionDescriptionArrayToMap(allExtensions);
187
const extensionsAreTheSame = (a: IExtensionDescription, b: IExtensionDescription) => {
188
return (
189
(a.extensionLocation.toString() === b.extensionLocation.toString())
190
|| (a.isBuiltin === b.isBuiltin)
191
|| (a.isUserBuiltin === b.isUserBuiltin)
192
|| (a.isUnderDevelopment === b.isUnderDevelopment)
193
);
194
};
195
196
for (const oldExtension of this._allExtensions) {
197
const newExtension = newExtensionsMap.get(oldExtension.identifier);
198
if (!newExtension) {
199
toRemove.push(oldExtension.identifier);
200
oldExtensionsMap.delete(oldExtension.identifier);
201
continue;
202
}
203
if (!extensionsAreTheSame(oldExtension, newExtension)) {
204
// The new extension is different than the old one
205
// (e.g. maybe it executes in a different location)
206
toRemove.push(oldExtension.identifier);
207
oldExtensionsMap.delete(oldExtension.identifier);
208
continue;
209
}
210
}
211
for (const newExtension of allExtensions) {
212
const oldExtension = oldExtensionsMap.get(newExtension.identifier);
213
if (!oldExtension) {
214
toAdd.push(newExtension);
215
continue;
216
}
217
if (!extensionsAreTheSame(oldExtension, newExtension)) {
218
// The new extension is different than the old one
219
// (e.g. maybe it executes in a different location)
220
toRemove.push(oldExtension.identifier);
221
oldExtensionsMap.delete(oldExtension.identifier);
222
continue;
223
}
224
}
225
226
const myOldExtensionsSet = new ExtensionIdentifierSet(this._myExtensions);
227
const myNewExtensionsSet = new ExtensionIdentifierSet(myExtensions);
228
for (const oldExtensionId of this._myExtensions) {
229
if (!myNewExtensionsSet.has(oldExtensionId)) {
230
myToRemove.push(oldExtensionId);
231
}
232
}
233
for (const newExtensionId of myExtensions) {
234
if (!myOldExtensionsSet.has(newExtensionId)) {
235
myToAdd.push(newExtensionId);
236
}
237
}
238
239
const addActivationEvents = ImplicitActivationEvents.createActivationEventsMap(toAdd);
240
const delta = { versionId, toRemove, toAdd, addActivationEvents, myToRemove, myToAdd };
241
this.delta(delta);
242
return delta;
243
}
244
245
public delta(extensionsDelta: IExtensionDescriptionDelta): IExtensionDescriptionDelta | null {
246
if (this._versionId >= extensionsDelta.versionId) {
247
// ignore older deltas
248
return null;
249
}
250
251
const { toRemove, toAdd, myToRemove, myToAdd } = extensionsDelta;
252
// First handle removals
253
const toRemoveSet = new ExtensionIdentifierSet(toRemove);
254
const myToRemoveSet = new ExtensionIdentifierSet(myToRemove);
255
for (let i = 0; i < this._allExtensions.length; i++) {
256
if (toRemoveSet.has(this._allExtensions[i].identifier)) {
257
this._allExtensions.splice(i, 1);
258
i--;
259
}
260
}
261
for (let i = 0; i < this._myExtensions.length; i++) {
262
if (myToRemoveSet.has(this._myExtensions[i])) {
263
this._myExtensions.splice(i, 1);
264
i--;
265
}
266
}
267
// Then handle additions
268
for (const extension of toAdd) {
269
this._allExtensions.push(extension);
270
}
271
for (const extensionId of myToAdd) {
272
this._myExtensions.push(extensionId);
273
}
274
275
// clear cached activation events
276
this._myActivationEvents = null;
277
278
return extensionsDelta;
279
}
280
281
public containsExtension(extensionId: ExtensionIdentifier): boolean {
282
for (const myExtensionId of this._myExtensions) {
283
if (ExtensionIdentifier.equals(myExtensionId, extensionId)) {
284
return true;
285
}
286
}
287
return false;
288
}
289
290
public containsActivationEvent(activationEvent: string): boolean {
291
if (!this._myActivationEvents) {
292
this._myActivationEvents = this._readMyActivationEvents();
293
}
294
return this._myActivationEvents.has(activationEvent);
295
}
296
297
private _readMyActivationEvents(): Set<string> {
298
const result = new Set<string>();
299
300
for (const extensionDescription of this._allExtensions) {
301
if (!this.containsExtension(extensionDescription.identifier)) {
302
continue;
303
}
304
305
const activationEvents = ImplicitActivationEvents.readActivationEvents(extensionDescription);
306
for (const activationEvent of activationEvents) {
307
result.add(activationEvent);
308
}
309
}
310
311
return result;
312
}
313
}
314
315
function extensionDescriptionArrayToMap(extensions: IExtensionDescription[]): ExtensionIdentifierMap<IExtensionDescription> {
316
const result = new ExtensionIdentifierMap<IExtensionDescription>();
317
for (const extension of extensions) {
318
result.set(extension.identifier, extension);
319
}
320
return result;
321
}
322
323
export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {
324
if (!extension.enabledApiProposals) {
325
return false;
326
}
327
return extension.enabledApiProposals.includes(proposal);
328
}
329
330
export function checkProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): void {
331
if (!isProposedApiEnabled(extension, proposal)) {
332
throw 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}`);
333
}
334
}
335
336
337
/**
338
* Extension id or one of the four known program states.
339
*/
340
export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self';
341
342
export interface ExtensionActivationReason {
343
readonly startup: boolean;
344
readonly extensionId: ExtensionIdentifier;
345
readonly activationEvent: string;
346
}
347
348
export class ActivationTimes {
349
constructor(
350
public readonly codeLoadingTime: number,
351
public readonly activateCallTime: number,
352
public readonly activateResolvedTime: number,
353
public readonly activationReason: ExtensionActivationReason
354
) {
355
}
356
}
357
358
export class ExtensionPointContribution<T> {
359
readonly description: IExtensionDescription;
360
readonly value: T;
361
362
constructor(description: IExtensionDescription, value: T) {
363
this.description = description;
364
this.value = value;
365
}
366
}
367
368
export interface IWillActivateEvent {
369
readonly event: string;
370
readonly activation: Promise<void>;
371
}
372
373
export interface IResponsiveStateChangeEvent {
374
extensionHostKind: ExtensionHostKind;
375
isResponsive: boolean;
376
/**
377
* Return the inspect port or `0`. `0` means inspection is not possible.
378
*/
379
getInspectListener(tryEnableInspector: boolean): Promise<IExtensionInspectInfo | undefined>;
380
}
381
382
export const enum ActivationKind {
383
Normal = 0,
384
Immediate = 1
385
}
386
387
export interface WillStopExtensionHostsEvent {
388
389
/**
390
* A human readable reason for stopping the extension hosts
391
* that e.g. can be shown in a confirmation dialog to the
392
* user.
393
*/
394
readonly reason: string;
395
396
/**
397
* A flag to indicate if the operation was triggered automatically
398
*/
399
readonly auto: boolean;
400
401
/**
402
* Allows to veto the stopping of extension hosts. The veto can be a long running
403
* operation.
404
*
405
* @param reason a human readable reason for vetoing the extension host stop in case
406
* where the resolved `value: true`.
407
*/
408
veto(value: boolean | Promise<boolean>, reason: string): void;
409
}
410
411
export interface IExtensionService {
412
readonly _serviceBrand: undefined;
413
414
/**
415
* An event emitted when extensions are registered after their extension points got handled.
416
*
417
* This event will also fire on startup to signal the installed extensions.
418
*
419
* @returns the extensions that got registered
420
*/
421
onDidRegisterExtensions: Event<void>;
422
423
/**
424
* @event
425
* Fired when extensions status changes.
426
* The event contains the ids of the extensions that have changed.
427
*/
428
onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]>;
429
430
/**
431
* Fired when the available extensions change (i.e. when extensions are added or removed).
432
*/
433
onDidChangeExtensions: Event<{ readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }>;
434
435
/**
436
* All registered extensions.
437
* - List will be empty initially during workbench startup and will be filled with extensions as they are registered
438
* - Listen to `onDidChangeExtensions` event for any changes to the extensions list. It will change as extensions get registered or de-reigstered.
439
* - Listen to `onDidRegisterExtensions` event or wait for `whenInstalledExtensionsRegistered` promise to get the initial list of registered extensions.
440
*/
441
readonly extensions: readonly IExtensionDescription[];
442
443
/**
444
* An event that is fired when activation happens.
445
*/
446
onWillActivateByEvent: Event<IWillActivateEvent>;
447
448
/**
449
* An event that is fired when an extension host changes its
450
* responsive-state.
451
*/
452
onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent>;
453
454
/**
455
* Fired before stop of extension hosts happens. Allows listeners to veto against the
456
* stop to prevent it from happening.
457
*/
458
onWillStop: Event<WillStopExtensionHostsEvent>;
459
460
/**
461
* Send an activation event and activate interested extensions.
462
*
463
* This will wait for the normal startup of the extension host(s).
464
*
465
* In extraordinary circumstances, if the activation event needs to activate
466
* one or more extensions before the normal startup is finished, then you can use
467
* `ActivationKind.Immediate`. Please do not use this flag unless really necessary
468
* and you understand all consequences.
469
*/
470
activateByEvent(activationEvent: string, activationKind?: ActivationKind): Promise<void>;
471
472
/**
473
* Send an activation ID and activate interested extensions.
474
*
475
*/
476
activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
477
478
/**
479
* Determine if `activateByEvent(activationEvent)` has resolved already.
480
*
481
* i.e. the activation event is finished and all interested extensions are already active.
482
*/
483
activationEventIsDone(activationEvent: string): boolean;
484
485
/**
486
* An promise that resolves when the installed extensions are registered after
487
* their extension points got handled.
488
*/
489
whenInstalledExtensionsRegistered(): Promise<boolean>;
490
491
/**
492
* Return a specific extension
493
* @param id An extension id
494
*/
495
getExtension(id: string): Promise<IExtensionDescription | undefined>;
496
497
/**
498
* Returns `true` if the given extension can be added. Otherwise `false`.
499
* @param extension An extension
500
*/
501
canAddExtension(extension: IExtensionDescription): boolean;
502
503
/**
504
* Returns `true` if the given extension can be removed. Otherwise `false`.
505
* @param extension An extension
506
*/
507
canRemoveExtension(extension: IExtensionDescription): boolean;
508
509
/**
510
* Read all contributions to an extension point.
511
*/
512
readExtensionPointContributions<T extends IExtensionContributions[keyof IExtensionContributions]>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]>;
513
514
/**
515
* Get information about extensions status.
516
*/
517
getExtensionsStatus(): { [id: string]: IExtensionsStatus };
518
519
/**
520
* Return the inspect ports (if inspection is possible) for extension hosts of kind `extensionHostKind`.
521
*/
522
getInspectPorts(extensionHostKind: ExtensionHostKind, tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]>;
523
524
/**
525
* Stops the extension hosts.
526
*
527
* @param reason a human readable reason for stopping the extension hosts. This maybe
528
* can be presented to the user when showing dialogs.
529
*
530
* @param auto indicates if the operation was triggered by an automatic action
531
*
532
* @returns a promise that resolves to `true` if the extension hosts were stopped, `false`
533
* if the operation was vetoed by listeners of the `onWillStop` event.
534
*/
535
stopExtensionHosts(reason: string, auto?: boolean): Promise<boolean>;
536
537
/**
538
* Starts the extension hosts. If updates are provided, the extension hosts are started with the given updates.
539
*/
540
startExtensionHosts(updates?: { readonly toAdd: readonly IExtension[]; readonly toRemove: readonly string[] }): Promise<void>;
541
542
/**
543
* Modify the environment of the remote extension host
544
* @param env New properties for the remote extension host
545
*/
546
setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
547
}
548
549
export interface IInternalExtensionService {
550
_activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
551
_onWillActivateExtension(extensionId: ExtensionIdentifier): void;
552
_onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
553
_onDidActivateExtensionError(extensionId: ExtensionIdentifier, error: Error): void;
554
_onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void;
555
}
556
557
export interface ProfileSession {
558
stop(): Promise<IExtensionHostProfile>;
559
}
560
561
export function toExtension(extensionDescription: IExtensionDescription): IExtension {
562
return {
563
type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User,
564
isBuiltin: extensionDescription.isBuiltin || extensionDescription.isUserBuiltin,
565
identifier: { id: getGalleryExtensionId(extensionDescription.publisher, extensionDescription.name), uuid: extensionDescription.uuid },
566
manifest: extensionDescription,
567
location: extensionDescription.extensionLocation,
568
targetPlatform: extensionDescription.targetPlatform,
569
validations: [],
570
isValid: true,
571
preRelease: extensionDescription.preRelease,
572
publisherDisplayName: extensionDescription.publisherDisplayName,
573
};
574
}
575
576
export function toExtensionDescription(extension: IExtension, isUnderDevelopment?: boolean): IExtensionDescription {
577
const id = getExtensionId(extension.manifest.publisher, extension.manifest.name);
578
return {
579
id,
580
identifier: new ExtensionIdentifier(id),
581
isBuiltin: extension.type === ExtensionType.System,
582
isUserBuiltin: extension.type === ExtensionType.User && extension.isBuiltin,
583
isUnderDevelopment: !!isUnderDevelopment,
584
extensionLocation: extension.location,
585
uuid: extension.identifier.uuid,
586
targetPlatform: extension.targetPlatform,
587
publisherDisplayName: extension.publisherDisplayName,
588
preRelease: extension.preRelease,
589
...extension.manifest
590
};
591
}
592
593
594
export class NullExtensionService implements IExtensionService {
595
declare readonly _serviceBrand: undefined;
596
onDidRegisterExtensions: Event<void> = Event.None;
597
onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = Event.None;
598
onDidChangeExtensions = Event.None;
599
onWillActivateByEvent: Event<IWillActivateEvent> = Event.None;
600
onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = Event.None;
601
onWillStop: Event<WillStopExtensionHostsEvent> = Event.None;
602
readonly extensions = [];
603
activateByEvent(_activationEvent: string): Promise<void> { return Promise.resolve(undefined); }
604
activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> { return Promise.resolve(undefined); }
605
activationEventIsDone(_activationEvent: string): boolean { return false; }
606
whenInstalledExtensionsRegistered(): Promise<boolean> { return Promise.resolve(true); }
607
getExtension() { return Promise.resolve(undefined); }
608
readExtensionPointContributions<T>(_extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> { return Promise.resolve(Object.create(null)); }
609
getExtensionsStatus(): { [id: string]: IExtensionsStatus } { return Object.create(null); }
610
getInspectPorts(_extensionHostKind: ExtensionHostKind, _tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]> { return Promise.resolve([]); }
611
async stopExtensionHosts(): Promise<boolean> { return true; }
612
async startExtensionHosts(): Promise<void> { }
613
async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise<void> { }
614
canAddExtension(): boolean { return false; }
615
canRemoveExtension(): boolean { return false; }
616
}
617
618