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
5243 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
readonly activationKind: ActivationKind;
372
}
373
374
export interface IResponsiveStateChangeEvent {
375
extensionHostKind: ExtensionHostKind;
376
isResponsive: boolean;
377
/**
378
* Return the inspect port or `0`. `0` means inspection is not possible.
379
*/
380
getInspectListener(tryEnableInspector: boolean): Promise<IExtensionInspectInfo | undefined>;
381
}
382
383
export const enum ActivationKind {
384
Normal = 0,
385
Immediate = 1
386
}
387
388
export interface WillStopExtensionHostsEvent {
389
390
/**
391
* A human readable reason for stopping the extension hosts
392
* that e.g. can be shown in a confirmation dialog to the
393
* user.
394
*/
395
readonly reason: string;
396
397
/**
398
* A flag to indicate if the operation was triggered automatically
399
*/
400
readonly auto: boolean;
401
402
/**
403
* Allows to veto the stopping of extension hosts. The veto can be a long running
404
* operation.
405
*
406
* @param reason a human readable reason for vetoing the extension host stop in case
407
* where the resolved `value: true`.
408
*/
409
veto(value: boolean | Promise<boolean>, reason: string): void;
410
}
411
412
export interface IExtensionService {
413
readonly _serviceBrand: undefined;
414
415
/**
416
* An event emitted when extensions are registered after their extension points got handled.
417
*
418
* This event will also fire on startup to signal the installed extensions.
419
*
420
* @returns the extensions that got registered
421
*/
422
readonly onDidRegisterExtensions: Event<void>;
423
424
/**
425
* @event
426
* Fired when extensions status changes.
427
* The event contains the ids of the extensions that have changed.
428
*/
429
readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]>;
430
431
/**
432
* Fired when the available extensions change (i.e. when extensions are added or removed).
433
*/
434
readonly onDidChangeExtensions: Event<{ readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }>;
435
436
/**
437
* All registered extensions.
438
* - List will be empty initially during workbench startup and will be filled with extensions as they are registered
439
* - Listen to `onDidChangeExtensions` event for any changes to the extensions list. It will change as extensions get registered or de-reigstered.
440
* - Listen to `onDidRegisterExtensions` event or wait for `whenInstalledExtensionsRegistered` promise to get the initial list of registered extensions.
441
*/
442
readonly extensions: readonly IExtensionDescription[];
443
444
/**
445
* An event that is fired when activation happens.
446
*/
447
readonly onWillActivateByEvent: Event<IWillActivateEvent>;
448
449
/**
450
* An event that is fired when an extension host changes its
451
* responsive-state.
452
*/
453
readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent>;
454
455
/**
456
* Fired before stop of extension hosts happens. Allows listeners to veto against the
457
* stop to prevent it from happening.
458
*/
459
readonly onWillStop: Event<WillStopExtensionHostsEvent>;
460
461
/**
462
* Send an activation event and activate interested extensions.
463
*
464
* This will wait for the normal startup of the extension host(s).
465
*
466
* In extraordinary circumstances, if the activation event needs to activate
467
* one or more extensions before the normal startup is finished, then you can use
468
* `ActivationKind.Immediate`. Please do not use this flag unless really necessary
469
* and you understand all consequences.
470
*/
471
activateByEvent(activationEvent: string, activationKind?: ActivationKind): Promise<void>;
472
473
/**
474
* Send an activation ID and activate interested extensions.
475
*
476
*/
477
activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
478
479
/**
480
* Determine if `activateByEvent(activationEvent)` has resolved already.
481
*
482
* i.e. the activation event is finished and all interested extensions are already active.
483
*/
484
activationEventIsDone(activationEvent: string): boolean;
485
486
/**
487
* An promise that resolves when the installed extensions are registered after
488
* their extension points got handled.
489
*/
490
whenInstalledExtensionsRegistered(): Promise<boolean>;
491
492
/**
493
* Return a specific extension
494
* @param id An extension id
495
*/
496
getExtension(id: string): Promise<IExtensionDescription | undefined>;
497
498
/**
499
* Returns `true` if the given extension can be added. Otherwise `false`.
500
* @param extension An extension
501
*/
502
canAddExtension(extension: IExtensionDescription): boolean;
503
504
/**
505
* Returns `true` if the given extension can be removed. Otherwise `false`.
506
* @param extension An extension
507
*/
508
canRemoveExtension(extension: IExtensionDescription): boolean;
509
510
/**
511
* Read all contributions to an extension point.
512
*/
513
readExtensionPointContributions<T extends IExtensionContributions[keyof IExtensionContributions]>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]>;
514
515
/**
516
* Get information about extensions status.
517
*/
518
getExtensionsStatus(): { [id: string]: IExtensionsStatus };
519
520
/**
521
* Return the inspect ports (if inspection is possible) for extension hosts of kind `extensionHostKind`.
522
*/
523
getInspectPorts(extensionHostKind: ExtensionHostKind, tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]>;
524
525
/**
526
* Stops the extension hosts.
527
*
528
* @param reason a human readable reason for stopping the extension hosts. This maybe
529
* can be presented to the user when showing dialogs.
530
*
531
* @param auto indicates if the operation was triggered by an automatic action
532
*
533
* @returns a promise that resolves to `true` if the extension hosts were stopped, `false`
534
* if the operation was vetoed by listeners of the `onWillStop` event.
535
*/
536
stopExtensionHosts(reason: string, auto?: boolean): Promise<boolean>;
537
538
/**
539
* Starts the extension hosts. If updates are provided, the extension hosts are started with the given updates.
540
*/
541
startExtensionHosts(updates?: { readonly toAdd: readonly IExtension[]; readonly toRemove: readonly string[] }): Promise<void>;
542
543
/**
544
* Modify the environment of the remote extension host
545
* @param env New properties for the remote extension host
546
*/
547
setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
548
}
549
550
export interface IInternalExtensionService {
551
_activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
552
_onWillActivateExtension(extensionId: ExtensionIdentifier): void;
553
_onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
554
_onDidActivateExtensionError(extensionId: ExtensionIdentifier, error: Error): void;
555
_onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void;
556
}
557
558
export interface ProfileSession {
559
stop(): Promise<IExtensionHostProfile>;
560
}
561
562
export function toExtension(extensionDescription: IExtensionDescription): IExtension {
563
return {
564
type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User,
565
isBuiltin: extensionDescription.isBuiltin || extensionDescription.isUserBuiltin,
566
identifier: { id: getGalleryExtensionId(extensionDescription.publisher, extensionDescription.name), uuid: extensionDescription.uuid },
567
manifest: extensionDescription,
568
location: extensionDescription.extensionLocation,
569
targetPlatform: extensionDescription.targetPlatform,
570
validations: [],
571
isValid: true,
572
preRelease: extensionDescription.preRelease,
573
publisherDisplayName: extensionDescription.publisherDisplayName,
574
};
575
}
576
577
export function toExtensionDescription(extension: IExtension, isUnderDevelopment?: boolean): IExtensionDescription {
578
const id = getExtensionId(extension.manifest.publisher, extension.manifest.name);
579
return {
580
id,
581
identifier: new ExtensionIdentifier(id),
582
isBuiltin: extension.type === ExtensionType.System,
583
isUserBuiltin: extension.type === ExtensionType.User && extension.isBuiltin,
584
isUnderDevelopment: !!isUnderDevelopment,
585
extensionLocation: extension.location,
586
uuid: extension.identifier.uuid,
587
targetPlatform: extension.targetPlatform,
588
publisherDisplayName: extension.publisherDisplayName,
589
preRelease: extension.preRelease,
590
...extension.manifest
591
};
592
}
593
594
595
export class NullExtensionService implements IExtensionService {
596
declare readonly _serviceBrand: undefined;
597
readonly onDidRegisterExtensions: Event<void> = Event.None;
598
readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]> = Event.None;
599
onDidChangeExtensions = Event.None;
600
readonly onWillActivateByEvent: Event<IWillActivateEvent> = Event.None;
601
readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = Event.None;
602
readonly onWillStop: Event<WillStopExtensionHostsEvent> = Event.None;
603
readonly extensions = [];
604
activateByEvent(_activationEvent: string): Promise<void> { return Promise.resolve(undefined); }
605
activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> { return Promise.resolve(undefined); }
606
activationEventIsDone(_activationEvent: string): boolean { return false; }
607
whenInstalledExtensionsRegistered(): Promise<boolean> { return Promise.resolve(true); }
608
getExtension() { return Promise.resolve(undefined); }
609
readExtensionPointContributions<T>(_extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> { return Promise.resolve(Object.create(null)); }
610
getExtensionsStatus(): { [id: string]: IExtensionsStatus } { return Object.create(null); }
611
getInspectPorts(_extensionHostKind: ExtensionHostKind, _tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]> { return Promise.resolve([]); }
612
async stopExtensionHosts(): Promise<boolean> { return true; }
613
async startExtensionHosts(): Promise<void> { }
614
async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise<void> { }
615
canAddExtension(): boolean { return false; }
616
canRemoveExtension(): boolean { return false; }
617
}
618
619