Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/common/extHostExtensionService.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
/* eslint-disable local/code-no-native-private */
7
8
import * as nls from '../../../nls.js';
9
import * as path from '../../../base/common/path.js';
10
import * as performance from '../../../base/common/performance.js';
11
import { originalFSPath, joinPath, extUriBiasedIgnorePathCase } from '../../../base/common/resources.js';
12
import { asPromise, Barrier, IntervalTimer, timeout } from '../../../base/common/async.js';
13
import { dispose, toDisposable, Disposable, DisposableStore, IDisposable } from '../../../base/common/lifecycle.js';
14
import { TernarySearchTree } from '../../../base/common/ternarySearchTree.js';
15
import { URI, UriComponents } from '../../../base/common/uri.js';
16
import { ILogService } from '../../../platform/log/common/log.js';
17
import { ExtHostExtensionServiceShape, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape } from './extHost.protocol.js';
18
import { IExtensionDescriptionDelta, IExtensionHostInitData } from '../../services/extensions/common/extensionHostProtocol.js';
19
import { ExtHostConfiguration, IExtHostConfiguration } from './extHostConfiguration.js';
20
import { ActivatedExtension, EmptyExtension, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from './extHostExtensionActivator.js';
21
import { ExtHostStorage, IExtHostStorage } from './extHostStorage.js';
22
import { ExtHostWorkspace, IExtHostWorkspace } from './extHostWorkspace.js';
23
import { MissingExtensionDependency, ActivationKind, checkProposedApiEnabled, isProposedApiEnabled, ExtensionActivationReason } from '../../services/extensions/common/extensions.js';
24
import { ExtensionDescriptionRegistry, IActivationEventsReader } from '../../services/extensions/common/extensionDescriptionRegistry.js';
25
import * as errors from '../../../base/common/errors.js';
26
import type * as vscode from 'vscode';
27
import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
28
import { VSBuffer } from '../../../base/common/buffer.js';
29
import { ExtensionGlobalMemento, ExtensionMemento } from './extHostMemento.js';
30
import { RemoteAuthorityResolverError, ExtensionKind, ExtensionMode, ExtensionRuntime, ManagedResolvedAuthority as ExtHostManagedResolvedAuthority } from './extHostTypes.js';
31
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode, IRemoteConnectionData, getRemoteAuthorityPrefix, TunnelInformation, ManagedRemoteConnection, WebSocketRemoteConnection } from '../../../platform/remote/common/remoteAuthorityResolver.js';
32
import { IInstantiationService, createDecorator } from '../../../platform/instantiation/common/instantiation.js';
33
import { IExtHostInitDataService } from './extHostInitDataService.js';
34
import { IExtensionStoragePaths } from './extHostStoragePaths.js';
35
import { IExtHostRpcService } from './extHostRpcService.js';
36
import { ServiceCollection } from '../../../platform/instantiation/common/serviceCollection.js';
37
import { IExtHostTunnelService } from './extHostTunnelService.js';
38
import { IExtHostTerminalService } from './extHostTerminalService.js';
39
import { IExtHostLanguageModels } from './extHostLanguageModels.js';
40
import { Emitter, Event } from '../../../base/common/event.js';
41
import { IExtensionActivationHost, checkActivateWorkspaceContainsExtension } from '../../services/extensions/common/workspaceContains.js';
42
import { ExtHostSecretState, IExtHostSecretState } from './extHostSecretState.js';
43
import { ExtensionSecrets } from './extHostSecrets.js';
44
import { Schemas } from '../../../base/common/network.js';
45
import { IResolveAuthorityResult } from '../../services/extensions/common/extensionHostProxy.js';
46
import { IExtHostLocalizationService } from './extHostLocalizationService.js';
47
import { StopWatch } from '../../../base/common/stopwatch.js';
48
import { isCI, setTimeout0 } from '../../../base/common/platform.js';
49
import { IExtHostManagedSockets } from './extHostManagedSockets.js';
50
import { Dto } from '../../services/extensions/common/proxyIdentifier.js';
51
52
interface ITestRunner {
53
/** Old test runner API, as exported from `vscode/lib/testrunner` */
54
run(testsRoot: string, clb: (error: Error, failures?: number) => void): void;
55
}
56
57
interface INewTestRunner {
58
/** New test runner API, as explained in the extension test doc */
59
run(): Promise<void>;
60
}
61
62
export const IHostUtils = createDecorator<IHostUtils>('IHostUtils');
63
64
export interface IHostUtils {
65
readonly _serviceBrand: undefined;
66
readonly pid: number | undefined;
67
exit(code: number): void;
68
fsExists?(path: string): Promise<boolean>;
69
fsRealpath?(path: string): Promise<string>;
70
}
71
72
type TelemetryActivationEventFragment = {
73
id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of an extension' };
74
name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The name of the extension' };
75
extensionVersion: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The version of the extension' };
76
publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The publisher of the extension' };
77
activationEvents: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'All activation events of the extension' };
78
isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If the extension is builtin or git installed' };
79
reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The activation event' };
80
reasonId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The identifier of the activation event' };
81
};
82
83
export abstract class AbstractExtHostExtensionService extends Disposable implements ExtHostExtensionServiceShape {
84
85
readonly _serviceBrand: undefined;
86
87
abstract readonly extensionRuntime: ExtensionRuntime;
88
89
private readonly _onDidChangeRemoteConnectionData = this._register(new Emitter<void>());
90
public readonly onDidChangeRemoteConnectionData = this._onDidChangeRemoteConnectionData.event;
91
92
protected readonly _hostUtils: IHostUtils;
93
protected readonly _initData: IExtensionHostInitData;
94
protected readonly _extHostContext: IExtHostRpcService;
95
protected readonly _instaService: IInstantiationService;
96
protected readonly _extHostWorkspace: ExtHostWorkspace;
97
protected readonly _extHostConfiguration: ExtHostConfiguration;
98
protected readonly _logService: ILogService;
99
protected readonly _extHostTunnelService: IExtHostTunnelService;
100
protected readonly _extHostTerminalService: IExtHostTerminalService;
101
protected readonly _extHostLocalizationService: IExtHostLocalizationService;
102
103
protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape;
104
protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape;
105
protected readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape;
106
107
private readonly _almostReadyToRunExtensions: Barrier;
108
private readonly _readyToStartExtensionHost: Barrier;
109
private readonly _readyToRunExtensions: Barrier;
110
private readonly _eagerExtensionsActivated: Barrier;
111
112
private readonly _activationEventsReader: SyncedActivationEventsReader;
113
protected readonly _myRegistry: ExtensionDescriptionRegistry;
114
protected readonly _globalRegistry: ExtensionDescriptionRegistry;
115
private readonly _storage: ExtHostStorage;
116
private readonly _secretState: ExtHostSecretState;
117
private readonly _storagePath: IExtensionStoragePaths;
118
private readonly _activator: ExtensionsActivator;
119
private _extensionPathIndex: Promise<ExtensionPaths> | null;
120
private _realPathCache = new Map<string, Promise<string>>();
121
122
private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver };
123
124
private _started: boolean;
125
private _isTerminating: boolean = false;
126
private _remoteConnectionData: IRemoteConnectionData | null;
127
128
constructor(
129
@IInstantiationService instaService: IInstantiationService,
130
@IHostUtils hostUtils: IHostUtils,
131
@IExtHostRpcService extHostContext: IExtHostRpcService,
132
@IExtHostWorkspace extHostWorkspace: IExtHostWorkspace,
133
@IExtHostConfiguration extHostConfiguration: IExtHostConfiguration,
134
@ILogService logService: ILogService,
135
@IExtHostInitDataService initData: IExtHostInitDataService,
136
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
137
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService,
138
@IExtHostTerminalService extHostTerminalService: IExtHostTerminalService,
139
@IExtHostLocalizationService extHostLocalizationService: IExtHostLocalizationService,
140
@IExtHostManagedSockets private readonly _extHostManagedSockets: IExtHostManagedSockets,
141
@IExtHostLanguageModels private readonly _extHostLanguageModels: IExtHostLanguageModels,
142
) {
143
super();
144
this._hostUtils = hostUtils;
145
this._extHostContext = extHostContext;
146
this._initData = initData;
147
148
this._extHostWorkspace = extHostWorkspace;
149
this._extHostConfiguration = extHostConfiguration;
150
this._logService = logService;
151
this._extHostTunnelService = extHostTunnelService;
152
this._extHostTerminalService = extHostTerminalService;
153
this._extHostLocalizationService = extHostLocalizationService;
154
155
this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace);
156
this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry);
157
this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService);
158
159
this._almostReadyToRunExtensions = new Barrier();
160
this._readyToStartExtensionHost = new Barrier();
161
this._readyToRunExtensions = new Barrier();
162
this._eagerExtensionsActivated = new Barrier();
163
this._activationEventsReader = new SyncedActivationEventsReader(this._initData.extensions.activationEvents);
164
this._globalRegistry = new ExtensionDescriptionRegistry(this._activationEventsReader, this._initData.extensions.allExtensions);
165
const myExtensionsSet = new ExtensionIdentifierSet(this._initData.extensions.myExtensions);
166
this._myRegistry = new ExtensionDescriptionRegistry(
167
this._activationEventsReader,
168
filterExtensions(this._globalRegistry, myExtensionsSet)
169
);
170
171
if (isCI) {
172
this._logService.info(`Creating extension host with the following global extensions: ${printExtIds(this._globalRegistry)}`);
173
this._logService.info(`Creating extension host with the following local extensions: ${printExtIds(this._myRegistry)}`);
174
}
175
176
this._storage = new ExtHostStorage(this._extHostContext, this._logService);
177
this._secretState = new ExtHostSecretState(this._extHostContext);
178
this._storagePath = storagePath;
179
180
this._instaService = this._store.add(instaService.createChild(new ServiceCollection(
181
[IExtHostStorage, this._storage],
182
[IExtHostSecretState, this._secretState]
183
)));
184
185
this._activator = this._register(new ExtensionsActivator(
186
this._myRegistry,
187
this._globalRegistry,
188
{
189
onExtensionActivationError: (extensionId: ExtensionIdentifier, error: Error, missingExtensionDependency: MissingExtensionDependency | null): void => {
190
this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, errors.transformErrorForSerialization(error), missingExtensionDependency);
191
},
192
193
actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension> => {
194
if (ExtensionDescriptionRegistry.isHostExtension(extensionId, this._myRegistry, this._globalRegistry)) {
195
await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason);
196
return new HostExtension();
197
}
198
const extensionDescription = this._myRegistry.getExtensionDescription(extensionId)!;
199
return this._activateExtension(extensionDescription, reason);
200
}
201
},
202
this._logService
203
));
204
this._extensionPathIndex = null;
205
this._resolvers = Object.create(null);
206
this._started = false;
207
this._remoteConnectionData = this._initData.remote.connectionData;
208
}
209
210
public getRemoteConnectionData(): IRemoteConnectionData | null {
211
return this._remoteConnectionData;
212
}
213
214
public async initialize(): Promise<void> {
215
try {
216
217
await this._beforeAlmostReadyToRunExtensions();
218
this._almostReadyToRunExtensions.open();
219
220
await this._extHostWorkspace.waitForInitializeCall();
221
performance.mark('code/extHost/ready');
222
this._readyToStartExtensionHost.open();
223
224
if (this._initData.autoStart) {
225
this._startExtensionHost();
226
}
227
} catch (err) {
228
errors.onUnexpectedError(err);
229
}
230
}
231
232
private async _deactivateAll(): Promise<void> {
233
this._storagePath.onWillDeactivateAll();
234
235
let allPromises: Promise<void>[] = [];
236
try {
237
const allExtensions = this._myRegistry.getAllExtensionDescriptions();
238
const allExtensionsIds = allExtensions.map(ext => ext.identifier);
239
const activatedExtensions = allExtensionsIds.filter(id => this.isActivated(id));
240
241
allPromises = activatedExtensions.map((extensionId) => {
242
return this._deactivate(extensionId);
243
});
244
} catch (err) {
245
// TODO: write to log once we have one
246
}
247
await Promise.all(allPromises);
248
}
249
250
public terminate(reason: string, code: number = 0): void {
251
if (this._isTerminating) {
252
// we are already shutting down...
253
return;
254
}
255
this._isTerminating = true;
256
this._logService.info(`Extension host terminating: ${reason}`);
257
this._logService.flush();
258
259
this._extHostTerminalService.dispose();
260
this._activator.dispose();
261
262
errors.setUnexpectedErrorHandler((err) => {
263
this._logService.error(err);
264
});
265
266
// Invalidate all proxies
267
this._extHostContext.dispose();
268
269
const extensionsDeactivated = this._deactivateAll();
270
271
// Give extensions at most 5 seconds to wrap up any async deactivate, then exit
272
Promise.race([timeout(5000), extensionsDeactivated]).finally(() => {
273
if (this._hostUtils.pid) {
274
this._logService.info(`Extension host with pid ${this._hostUtils.pid} exiting with code ${code}`);
275
} else {
276
this._logService.info(`Extension host exiting with code ${code}`);
277
}
278
this._logService.flush();
279
this._logService.dispose();
280
this._hostUtils.exit(code);
281
});
282
}
283
284
public isActivated(extensionId: ExtensionIdentifier): boolean {
285
if (this._readyToRunExtensions.isOpen()) {
286
return this._activator.isActivated(extensionId);
287
}
288
return false;
289
}
290
291
public async getExtension(extensionId: string): Promise<IExtensionDescription | undefined> {
292
const ext = await this._mainThreadExtensionsProxy.$getExtension(extensionId);
293
return ext && {
294
...ext,
295
identifier: new ExtensionIdentifier(ext.identifier.value),
296
extensionLocation: URI.revive(ext.extensionLocation)
297
};
298
}
299
300
private _activateByEvent(activationEvent: string, startup: boolean): Promise<void> {
301
return this._activator.activateByEvent(activationEvent, startup);
302
}
303
304
private _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
305
return this._activator.activateById(extensionId, reason);
306
}
307
308
public activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
309
return this._activateById(extensionId, reason).then(() => {
310
const extension = this._activator.getActivatedExtension(extensionId);
311
if (extension.activationFailed) {
312
// activation failed => bubble up the error as the promise result
313
return Promise.reject(extension.activationFailedError);
314
}
315
return undefined;
316
});
317
}
318
319
public getExtensionRegistry(): Promise<ExtensionDescriptionRegistry> {
320
return this._readyToRunExtensions.wait().then(_ => this._myRegistry);
321
}
322
323
public getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined {
324
if (this._readyToRunExtensions.isOpen()) {
325
return this._activator.getActivatedExtension(extensionId).exports;
326
} else {
327
try {
328
return this._activator.getActivatedExtension(extensionId).exports;
329
} catch (err) {
330
return null;
331
}
332
}
333
}
334
335
/**
336
* Applies realpath to file-uris and returns all others uris unmodified.
337
* The real path is cached for the lifetime of the extension host.
338
*/
339
private async _realPathExtensionUri(uri: URI): Promise<URI> {
340
if (uri.scheme === Schemas.file && this._hostUtils.fsRealpath) {
341
const fsPath = uri.fsPath;
342
if (!this._realPathCache.has(fsPath)) {
343
this._realPathCache.set(fsPath, this._hostUtils.fsRealpath(fsPath));
344
}
345
const realpathValue = await this._realPathCache.get(fsPath)!;
346
return URI.file(realpathValue);
347
}
348
return uri;
349
}
350
351
// create trie to enable fast 'filename -> extension id' look up
352
public async getExtensionPathIndex(): Promise<ExtensionPaths> {
353
if (!this._extensionPathIndex) {
354
this._extensionPathIndex = this._createExtensionPathIndex(this._myRegistry.getAllExtensionDescriptions()).then((searchTree) => {
355
return new ExtensionPaths(searchTree);
356
});
357
}
358
return this._extensionPathIndex;
359
}
360
361
/**
362
* create trie to enable fast 'filename -> extension id' look up
363
*/
364
private async _createExtensionPathIndex(extensions: IExtensionDescription[]): Promise<TernarySearchTree<URI, IExtensionDescription>> {
365
const tst = TernarySearchTree.forUris<IExtensionDescription>(key => {
366
// using the default/biased extUri-util because the IExtHostFileSystemInfo-service
367
// isn't ready to be used yet, e.g the knowledge about `file` protocol and others
368
// comes in while this code runs
369
return extUriBiasedIgnorePathCase.ignorePathCasing(key);
370
});
371
// const tst = TernarySearchTree.forUris<IExtensionDescription>(key => true);
372
await Promise.all(extensions.map(async (ext) => {
373
if (this._getEntryPoint(ext)) {
374
const uri = await this._realPathExtensionUri(ext.extensionLocation);
375
tst.set(uri, ext);
376
}
377
}));
378
return tst;
379
}
380
381
private _deactivate(extensionId: ExtensionIdentifier): Promise<void> {
382
let result = Promise.resolve(undefined);
383
384
if (!this._readyToRunExtensions.isOpen()) {
385
return result;
386
}
387
388
if (!this._activator.isActivated(extensionId)) {
389
return result;
390
}
391
392
const extension = this._activator.getActivatedExtension(extensionId);
393
if (!extension) {
394
return result;
395
}
396
397
// call deactivate if available
398
try {
399
if (typeof extension.module.deactivate === 'function') {
400
result = Promise.resolve(extension.module.deactivate()).then(undefined, (err) => {
401
this._logService.error(err);
402
return Promise.resolve(undefined);
403
});
404
}
405
} catch (err) {
406
this._logService.error(`An error occurred when deactivating the extension '${extensionId.value}':`);
407
this._logService.error(err);
408
}
409
410
// clean up subscriptions
411
try {
412
extension.disposable.dispose();
413
} catch (err) {
414
this._logService.error(`An error occurred when disposing the subscriptions for extension '${extensionId.value}':`);
415
this._logService.error(err);
416
}
417
418
return result;
419
}
420
421
// --- impl
422
423
private async _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> {
424
if (!this._initData.remote.isRemote) {
425
// local extension host process
426
await this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier);
427
} else {
428
// remote extension host process
429
// do not wait for renderer confirmation
430
this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier);
431
}
432
return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => {
433
const activationTimes = activatedExtension.activationTimes;
434
this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, reason);
435
this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes);
436
return activatedExtension;
437
}, (err) => {
438
this._logExtensionActivationTimes(extensionDescription, reason, 'failure');
439
throw err;
440
});
441
}
442
443
private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) {
444
const event = getTelemetryActivationEvent(extensionDescription, reason);
445
type ExtensionActivationTimesClassification = {
446
owner: 'jrieken';
447
comment: 'Timestamps for extension activation';
448
outcome: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Did extension activation succeed or fail' };
449
} & TelemetryActivationEventFragment & ExtensionActivationTimesFragment;
450
451
type ExtensionActivationTimesEvent = {
452
outcome: string;
453
} & ActivationTimesEvent & TelemetryActivationEvent;
454
455
type ActivationTimesEvent = {
456
startup?: boolean;
457
codeLoadingTime?: number;
458
activateCallTime?: number;
459
activateResolvedTime?: number;
460
};
461
462
this._mainThreadTelemetryProxy.$publicLog2<ExtensionActivationTimesEvent, ExtensionActivationTimesClassification>('extensionActivationTimes', {
463
...event,
464
...(activationTimes || {}),
465
outcome
466
});
467
}
468
469
private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> {
470
const event = getTelemetryActivationEvent(extensionDescription, reason);
471
type ActivatePluginClassification = {
472
owner: 'jrieken';
473
comment: 'Data about how/why an extension was activated';
474
} & TelemetryActivationEventFragment;
475
this._mainThreadTelemetryProxy.$publicLog2<TelemetryActivationEvent, ActivatePluginClassification>('activatePlugin', event);
476
const entryPoint = this._getEntryPoint(extensionDescription);
477
if (!entryPoint) {
478
// Treat the extension as being empty => NOT AN ERROR CASE
479
return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE));
480
}
481
482
this._logService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value}, startup: ${reason.startup}, activationEvent: '${reason.activationEvent}'${extensionDescription.identifier.value !== reason.extensionId.value ? `, root cause: ${reason.extensionId.value}` : ``}`);
483
this._logService.flush();
484
485
const isESM = this._isESM(extensionDescription);
486
487
const extensionInternalStore = new DisposableStore(); // disposables that follow the extension lifecycle
488
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
489
return Promise.all([
490
isESM
491
? this._loadESMModule<IExtensionModule>(extensionDescription, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder)
492
: this._loadCommonJSModule<IExtensionModule>(extensionDescription, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),
493
this._loadExtensionContext(extensionDescription, extensionInternalStore)
494
]).then(values => {
495
performance.mark(`code/extHost/willActivateExtension/${extensionDescription.identifier.value}`);
496
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], extensionInternalStore, activationTimesBuilder);
497
}).then((activatedExtension) => {
498
performance.mark(`code/extHost/didActivateExtension/${extensionDescription.identifier.value}`);
499
return activatedExtension;
500
});
501
}
502
503
private _loadExtensionContext(extensionDescription: IExtensionDescription, extensionInternalStore: DisposableStore): Promise<vscode.ExtensionContext> {
504
505
const languageModelAccessInformation = this._extHostLanguageModels.createLanguageModelAccessInformation(extensionDescription);
506
const globalState = extensionInternalStore.add(new ExtensionGlobalMemento(extensionDescription, this._storage));
507
const workspaceState = extensionInternalStore.add(new ExtensionMemento(extensionDescription.identifier.value, false, this._storage));
508
const secrets = extensionInternalStore.add(new ExtensionSecrets(extensionDescription, this._secretState));
509
const extensionMode = extensionDescription.isUnderDevelopment
510
? (this._initData.environment.extensionTestsLocationURI ? ExtensionMode.Test : ExtensionMode.Development)
511
: ExtensionMode.Production;
512
const extensionKind = this._initData.remote.isRemote ? ExtensionKind.Workspace : ExtensionKind.UI;
513
514
this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`);
515
516
return Promise.all([
517
globalState.whenReady,
518
workspaceState.whenReady,
519
this._storagePath.whenReady
520
]).then(() => {
521
const that = this;
522
let extension: vscode.Extension<any> | undefined;
523
524
let messagePassingProtocol: vscode.MessagePassingProtocol | undefined;
525
const messagePort = isProposedApiEnabled(extensionDescription, 'ipc')
526
? this._initData.messagePorts?.get(ExtensionIdentifier.toKey(extensionDescription.identifier))
527
: undefined;
528
529
return Object.freeze<vscode.ExtensionContext>({
530
globalState,
531
workspaceState,
532
secrets,
533
subscriptions: [],
534
get languageModelAccessInformation() { return languageModelAccessInformation; },
535
get extensionUri() { return extensionDescription.extensionLocation; },
536
get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
537
asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); },
538
get storagePath() { return that._storagePath.workspaceValue(extensionDescription)?.fsPath; },
539
get globalStoragePath() { return that._storagePath.globalValue(extensionDescription).fsPath; },
540
get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); },
541
get logUri() { return URI.joinPath(that._initData.logsLocation, extensionDescription.identifier.value); },
542
get storageUri() { return that._storagePath.workspaceValue(extensionDescription); },
543
get globalStorageUri() { return that._storagePath.globalValue(extensionDescription); },
544
get extensionMode() { return extensionMode; },
545
get extension() {
546
if (extension === undefined) {
547
extension = new Extension(that, extensionDescription.identifier, extensionDescription, extensionKind, false);
548
}
549
return extension;
550
},
551
get extensionRuntime() {
552
checkProposedApiEnabled(extensionDescription, 'extensionRuntime');
553
return that.extensionRuntime;
554
},
555
get environmentVariableCollection() { return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); },
556
get messagePassingProtocol() {
557
if (!messagePassingProtocol) {
558
if (!messagePort) {
559
return undefined;
560
}
561
562
const onDidReceiveMessage = Event.buffer(Event.fromDOMEventEmitter(messagePort, 'message', e => e.data));
563
messagePort.start();
564
messagePassingProtocol = {
565
onDidReceiveMessage,
566
postMessage: messagePort.postMessage.bind(messagePort) as any
567
};
568
}
569
570
return messagePassingProtocol;
571
}
572
});
573
});
574
}
575
576
private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: vscode.ExtensionContext, extensionInternalStore: IDisposable, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<ActivatedExtension> {
577
// Make sure the extension's surface is not undefined
578
extensionModule = extensionModule || {
579
activate: undefined,
580
deactivate: undefined
581
};
582
583
return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => {
584
return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, toDisposable(() => {
585
extensionInternalStore.dispose();
586
dispose(context.subscriptions);
587
}));
588
});
589
}
590
591
private static _callActivateOptional(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: vscode.ExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<IExtensionAPI> {
592
if (typeof extensionModule.activate === 'function') {
593
try {
594
activationTimesBuilder.activateCallStart();
595
logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`);
596
const activateResult: Promise<IExtensionAPI> = extensionModule.activate.apply(globalThis, [context]);
597
activationTimesBuilder.activateCallStop();
598
599
activationTimesBuilder.activateResolveStart();
600
return Promise.resolve(activateResult).then((value) => {
601
activationTimesBuilder.activateResolveStop();
602
return value;
603
});
604
} catch (err) {
605
return Promise.reject(err);
606
}
607
} else {
608
// No activate found => the module is the extension's exports
609
return Promise.resolve<IExtensionAPI>(extensionModule);
610
}
611
}
612
613
// -- eager activation
614
615
private _activateOneStartupFinished(desc: IExtensionDescription, activationEvent: string): void {
616
this._activateById(desc.identifier, {
617
startup: false,
618
extensionId: desc.identifier,
619
activationEvent: activationEvent
620
}).then(undefined, (err) => {
621
this._logService.error(err);
622
});
623
}
624
625
private _activateAllStartupFinishedDeferred(extensions: IExtensionDescription[], start: number = 0): void {
626
const timeBudget = 50; // 50 milliseconds
627
const startTime = Date.now();
628
629
setTimeout0(() => {
630
for (let i = start; i < extensions.length; i += 1) {
631
const desc = extensions[i];
632
for (const activationEvent of (desc.activationEvents ?? [])) {
633
if (activationEvent === 'onStartupFinished') {
634
if (Date.now() - startTime > timeBudget) {
635
// time budget for current task has been exceeded
636
// set a new task to activate current and remaining extensions
637
this._activateAllStartupFinishedDeferred(extensions, i);
638
break;
639
} else {
640
this._activateOneStartupFinished(desc, activationEvent);
641
}
642
}
643
}
644
}
645
});
646
}
647
648
private _activateAllStartupFinished(): void {
649
// startup is considered finished
650
this._mainThreadExtensionsProxy.$setPerformanceMarks(performance.getMarks());
651
652
this._extHostConfiguration.getConfigProvider().then((configProvider) => {
653
const shouldDeferActivation = configProvider.getConfiguration('extensions.experimental').get<boolean>('deferredStartupFinishedActivation');
654
const allExtensionDescriptions = this._myRegistry.getAllExtensionDescriptions();
655
if (shouldDeferActivation) {
656
this._activateAllStartupFinishedDeferred(allExtensionDescriptions);
657
} else {
658
for (const desc of allExtensionDescriptions) {
659
if (desc.activationEvents) {
660
for (const activationEvent of desc.activationEvents) {
661
if (activationEvent === 'onStartupFinished') {
662
this._activateOneStartupFinished(desc, activationEvent);
663
}
664
}
665
}
666
}
667
}
668
});
669
}
670
671
// Handle "eager" activation extensions
672
private _handleEagerExtensions(): Promise<void> {
673
const starActivation = this._activateByEvent('*', true).then(undefined, (err) => {
674
this._logService.error(err);
675
});
676
677
this._register(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added)));
678
const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : [];
679
const workspaceContainsActivation = this._handleWorkspaceContainsEagerExtensions(folders);
680
const remoteResolverActivation = this._handleRemoteResolverEagerExtensions();
681
const eagerExtensionsActivation = Promise.all([remoteResolverActivation, starActivation, workspaceContainsActivation]).then(() => { });
682
683
Promise.race([eagerExtensionsActivation, timeout(10000)]).then(() => {
684
this._activateAllStartupFinished();
685
});
686
687
return eagerExtensionsActivation;
688
}
689
690
private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray<vscode.WorkspaceFolder>): Promise<void> {
691
if (folders.length === 0) {
692
return Promise.resolve(undefined);
693
}
694
695
return Promise.all(
696
this._myRegistry.getAllExtensionDescriptions().map((desc) => {
697
return this._handleWorkspaceContainsEagerExtension(folders, desc);
698
})
699
).then(() => { });
700
}
701
702
private async _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray<vscode.WorkspaceFolder>, desc: IExtensionDescription): Promise<void> {
703
if (this.isActivated(desc.identifier)) {
704
return;
705
}
706
707
const localWithRemote = !this._initData.remote.isRemote && !!this._initData.remote.authority;
708
const host: IExtensionActivationHost = {
709
logService: this._logService,
710
folders: folders.map(folder => folder.uri),
711
forceUsingSearch: localWithRemote || !this._hostUtils.fsExists,
712
exists: (uri) => this._hostUtils.fsExists!(uri.fsPath),
713
checkExists: (folders, includes, token) => this._mainThreadWorkspaceProxy.$checkExists(folders, includes, token)
714
};
715
716
const result = await checkActivateWorkspaceContainsExtension(host, desc);
717
if (!result) {
718
return;
719
}
720
721
return (
722
this._activateById(desc.identifier, { startup: true, extensionId: desc.identifier, activationEvent: result.activationEvent })
723
.then(undefined, err => this._logService.error(err))
724
);
725
}
726
727
private async _handleRemoteResolverEagerExtensions(): Promise<void> {
728
if (this._initData.remote.authority) {
729
return this._activateByEvent(`onResolveRemoteAuthority:${this._initData.remote.authority}`, false);
730
}
731
}
732
733
public async $extensionTestsExecute(): Promise<number> {
734
await this._eagerExtensionsActivated.wait();
735
try {
736
return await this._doHandleExtensionTests();
737
} catch (error) {
738
console.error(error); // ensure any error message makes it onto the console
739
throw error;
740
}
741
}
742
743
private async _doHandleExtensionTests(): Promise<number> {
744
const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment;
745
if (!extensionDevelopmentLocationURI || !extensionTestsLocationURI) {
746
throw new Error(nls.localize('extensionTestError1', "Cannot load test runner."));
747
}
748
749
const extensionDescription = (await this.getExtensionPathIndex()).findSubstr(extensionTestsLocationURI);
750
const isESM = this._isESM(extensionDescription, extensionTestsLocationURI.path);
751
752
// Require the test runner via node require from the provided path
753
const testRunner = await (isESM
754
? this._loadESMModule<ITestRunner | INewTestRunner | undefined>(null, extensionTestsLocationURI, new ExtensionActivationTimesBuilder(false))
755
: this._loadCommonJSModule<ITestRunner | INewTestRunner | undefined>(null, extensionTestsLocationURI, new ExtensionActivationTimesBuilder(false)));
756
757
if (!testRunner || typeof testRunner.run !== 'function') {
758
throw new Error(nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsLocationURI.toString()));
759
}
760
761
// Execute the runner if it follows the old `run` spec
762
return new Promise<number>((resolve, reject) => {
763
const oldTestRunnerCallback = (error: Error, failures: number | undefined) => {
764
if (error) {
765
if (isCI) {
766
this._logService.error(`Test runner called back with error`, error);
767
}
768
reject(error);
769
} else {
770
if (isCI) {
771
if (failures) {
772
this._logService.info(`Test runner called back with ${failures} failures.`);
773
} else {
774
this._logService.info(`Test runner called back with successful outcome.`);
775
}
776
}
777
resolve((typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
778
}
779
};
780
781
const extensionTestsPath = originalFSPath(extensionTestsLocationURI); // for the old test runner API
782
783
const runResult = testRunner.run(extensionTestsPath, oldTestRunnerCallback);
784
785
// Using the new API `run(): Promise<void>`
786
if (runResult && runResult.then) {
787
runResult
788
.then(() => {
789
if (isCI) {
790
this._logService.info(`Test runner finished successfully.`);
791
}
792
resolve(0);
793
})
794
.catch((err: unknown) => {
795
if (isCI) {
796
this._logService.error(`Test runner finished with error`, err);
797
}
798
reject(err instanceof Error && err.stack ? err.stack : String(err));
799
});
800
}
801
});
802
}
803
804
private _startExtensionHost(): Promise<void> {
805
if (this._started) {
806
throw new Error(`Extension host is already started!`);
807
}
808
this._started = true;
809
810
return this._readyToStartExtensionHost.wait()
811
.then(() => this._readyToRunExtensions.open())
812
.then(() => {
813
// wait for all activation events that came in during workbench startup, but at maximum 1s
814
return Promise.race([this._activator.waitForActivatingExtensions(), timeout(1000)]);
815
})
816
.then(() => this._handleEagerExtensions())
817
.then(() => {
818
this._eagerExtensionsActivated.open();
819
this._logService.info(`Eager extensions activated`);
820
});
821
}
822
823
// -- called by extensions
824
825
public registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable {
826
this._resolvers[authorityPrefix] = resolver;
827
return toDisposable(() => {
828
delete this._resolvers[authorityPrefix];
829
});
830
}
831
832
public async getRemoteExecServer(remoteAuthority: string): Promise<vscode.ExecServer | undefined> {
833
const { resolver } = await this._activateAndGetResolver(remoteAuthority);
834
return resolver?.resolveExecServer?.(remoteAuthority, { resolveAttempt: 0 });
835
}
836
837
// -- called by main thread
838
839
private async _activateAndGetResolver(remoteAuthority: string): Promise<{ authorityPrefix: string; resolver: vscode.RemoteAuthorityResolver | undefined }> {
840
const authorityPlusIndex = remoteAuthority.indexOf('+');
841
if (authorityPlusIndex === -1) {
842
throw new RemoteAuthorityResolverError(`Not an authority that can be resolved!`, RemoteAuthorityResolverErrorCode.InvalidAuthority);
843
}
844
const authorityPrefix = remoteAuthority.substr(0, authorityPlusIndex);
845
846
await this._almostReadyToRunExtensions.wait();
847
await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false);
848
849
return { authorityPrefix, resolver: this._resolvers[authorityPrefix] };
850
}
851
852
public async $resolveAuthority(remoteAuthorityChain: string, resolveAttempt: number): Promise<Dto<IResolveAuthorityResult>> {
853
const sw = StopWatch.create(false);
854
const prefix = () => `[resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthorityChain)},${resolveAttempt})][${sw.elapsed()}ms] `;
855
const logInfo = (msg: string) => this._logService.info(`${prefix()}${msg}`);
856
const logWarning = (msg: string) => this._logService.warn(`${prefix()}${msg}`);
857
const logError = (msg: string, err: any = undefined) => this._logService.error(`${prefix()}${msg}`, err);
858
const normalizeError = (err: unknown) => {
859
if (err instanceof RemoteAuthorityResolverError) {
860
return {
861
type: 'error' as const,
862
error: {
863
code: err._code,
864
message: err._message,
865
detail: err._detail
866
}
867
};
868
}
869
throw err;
870
};
871
872
const getResolver = async (remoteAuthority: string) => {
873
logInfo(`activating resolver for ${remoteAuthority}...`);
874
const { resolver, authorityPrefix } = await this._activateAndGetResolver(remoteAuthority);
875
if (!resolver) {
876
logError(`no resolver for ${authorityPrefix}`);
877
throw new RemoteAuthorityResolverError(`No remote extension installed to resolve ${authorityPrefix}.`, RemoteAuthorityResolverErrorCode.NoResolverFound);
878
}
879
return { resolver, authorityPrefix, remoteAuthority };
880
};
881
882
const chain = remoteAuthorityChain.split(/@|%40/g).reverse();
883
logInfo(`activating remote resolvers ${chain.join(' -> ')}`);
884
885
let resolvers;
886
try {
887
resolvers = await Promise.all(chain.map(getResolver)).catch(async (e: Error) => {
888
if (!(e instanceof RemoteAuthorityResolverError) || e._code !== RemoteAuthorityResolverErrorCode.InvalidAuthority) { throw e; }
889
logWarning(`resolving nested authorities failed: ${e.message}`);
890
return [await getResolver(remoteAuthorityChain)];
891
});
892
} catch (e) {
893
return normalizeError(e);
894
}
895
896
const intervalLogger = new IntervalTimer();
897
intervalLogger.cancelAndSet(() => logInfo('waiting...'), 1000);
898
899
let result!: vscode.ResolverResult;
900
let execServer: vscode.ExecServer | undefined;
901
for (const [i, { authorityPrefix, resolver, remoteAuthority }] of resolvers.entries()) {
902
try {
903
if (i === resolvers.length - 1) {
904
logInfo(`invoking final resolve()...`);
905
performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`);
906
result = await resolver.resolve(remoteAuthority, { resolveAttempt, execServer });
907
performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`);
908
logInfo(`setting tunnel factory...`);
909
this._register(await this._extHostTunnelService.setTunnelFactory(
910
resolver,
911
ExtHostManagedResolvedAuthority.isManagedResolvedAuthority(result) ? result : undefined
912
));
913
} else {
914
logInfo(`invoking resolveExecServer() for ${remoteAuthority}`);
915
performance.mark(`code/extHost/willResolveExecServer/${authorityPrefix}`);
916
execServer = await resolver.resolveExecServer?.(remoteAuthority, { resolveAttempt, execServer });
917
if (!execServer) {
918
throw new RemoteAuthorityResolverError(`Exec server was not available for ${remoteAuthority}`, RemoteAuthorityResolverErrorCode.NoResolverFound); // we did, in fact, break the chain :(
919
}
920
performance.mark(`code/extHost/didResolveExecServerOK/${authorityPrefix}`);
921
}
922
} catch (e) {
923
performance.mark(`code/extHost/didResolveAuthorityError/${authorityPrefix}`);
924
logError(`returned an error`, e);
925
intervalLogger.dispose();
926
return normalizeError(e);
927
}
928
}
929
930
intervalLogger.dispose();
931
932
const tunnelInformation: TunnelInformation = {
933
environmentTunnels: result.environmentTunnels,
934
features: result.tunnelFeatures ? {
935
elevation: result.tunnelFeatures.elevation,
936
privacyOptions: result.tunnelFeatures.privacyOptions,
937
protocol: result.tunnelFeatures.protocol === undefined ? true : result.tunnelFeatures.protocol,
938
} : undefined
939
};
940
941
// Split merged API result into separate authority/options
942
const options: ResolvedOptions = {
943
extensionHostEnv: result.extensionHostEnv,
944
isTrusted: result.isTrusted,
945
authenticationSession: result.authenticationSessionForInitializingExtensions ? { id: result.authenticationSessionForInitializingExtensions.id, providerId: result.authenticationSessionForInitializingExtensions.providerId } : undefined
946
};
947
948
// extension are not required to return an instance of ResolvedAuthority or ManagedResolvedAuthority, so don't use `instanceof`
949
logInfo(`returned ${ExtHostManagedResolvedAuthority.isManagedResolvedAuthority(result) ? 'managed authority' : `${result.host}:${result.port}`}`);
950
951
let authority: ResolvedAuthority;
952
if (ExtHostManagedResolvedAuthority.isManagedResolvedAuthority(result)) {
953
// The socket factory is identified by the `resolveAttempt`, since that is a number which
954
// always increments and is unique over all resolve() calls in a workbench session.
955
const socketFactoryId = resolveAttempt;
956
957
// There is only on managed socket factory at a time, so we can just overwrite the old one.
958
this._extHostManagedSockets.setFactory(socketFactoryId, result.makeConnection);
959
960
authority = {
961
authority: remoteAuthorityChain,
962
connectTo: new ManagedRemoteConnection(socketFactoryId),
963
connectionToken: result.connectionToken
964
};
965
} else {
966
authority = {
967
authority: remoteAuthorityChain,
968
connectTo: new WebSocketRemoteConnection(result.host, result.port),
969
connectionToken: result.connectionToken
970
};
971
}
972
973
return {
974
type: 'ok',
975
value: {
976
authority: authority as Dto<ResolvedAuthority>,
977
options,
978
tunnelInformation,
979
}
980
};
981
}
982
983
public async $getCanonicalURI(remoteAuthority: string, uriComponents: UriComponents): Promise<UriComponents | null> {
984
this._logService.info(`$getCanonicalURI invoked for authority (${getRemoteAuthorityPrefix(remoteAuthority)})`);
985
986
const { resolver } = await this._activateAndGetResolver(remoteAuthority);
987
if (!resolver) {
988
// Return `null` if no resolver for `remoteAuthority` is found.
989
return null;
990
}
991
992
const uri = URI.revive(uriComponents);
993
994
if (typeof resolver.getCanonicalURI === 'undefined') {
995
// resolver cannot compute canonical URI
996
return uri;
997
}
998
999
const result = await asPromise(() => resolver.getCanonicalURI!(uri));
1000
if (!result) {
1001
return uri;
1002
}
1003
1004
return result;
1005
}
1006
1007
public async $startExtensionHost(extensionsDelta: IExtensionDescriptionDelta): Promise<void> {
1008
extensionsDelta.toAdd.forEach((extension) => (<any>extension).extensionLocation = URI.revive(extension.extensionLocation));
1009
1010
const { globalRegistry, myExtensions } = applyExtensionsDelta(this._activationEventsReader, this._globalRegistry, this._myRegistry, extensionsDelta);
1011
const newSearchTree = await this._createExtensionPathIndex(myExtensions);
1012
const extensionsPaths = await this.getExtensionPathIndex();
1013
extensionsPaths.setSearchTree(newSearchTree);
1014
this._globalRegistry.set(globalRegistry.getAllExtensionDescriptions());
1015
this._myRegistry.set(myExtensions);
1016
1017
if (isCI) {
1018
this._logService.info(`$startExtensionHost: global extensions: ${printExtIds(this._globalRegistry)}`);
1019
this._logService.info(`$startExtensionHost: local extensions: ${printExtIds(this._myRegistry)}`);
1020
}
1021
1022
return this._startExtensionHost();
1023
}
1024
1025
public $activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
1026
if (activationKind === ActivationKind.Immediate) {
1027
return this._almostReadyToRunExtensions.wait()
1028
.then(_ => this._activateByEvent(activationEvent, false));
1029
}
1030
1031
return (
1032
this._readyToRunExtensions.wait()
1033
.then(_ => this._activateByEvent(activationEvent, false))
1034
);
1035
}
1036
1037
public async $activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean> {
1038
await this._readyToRunExtensions.wait();
1039
if (!this._myRegistry.getExtensionDescription(extensionId)) {
1040
// unknown extension => ignore
1041
return false;
1042
}
1043
await this._activateById(extensionId, reason);
1044
return true;
1045
}
1046
1047
public async $deltaExtensions(extensionsDelta: IExtensionDescriptionDelta): Promise<void> {
1048
extensionsDelta.toAdd.forEach((extension) => (<any>extension).extensionLocation = URI.revive(extension.extensionLocation));
1049
1050
// First build up and update the trie and only afterwards apply the delta
1051
const { globalRegistry, myExtensions } = applyExtensionsDelta(this._activationEventsReader, this._globalRegistry, this._myRegistry, extensionsDelta);
1052
const newSearchTree = await this._createExtensionPathIndex(myExtensions);
1053
const extensionsPaths = await this.getExtensionPathIndex();
1054
extensionsPaths.setSearchTree(newSearchTree);
1055
this._globalRegistry.set(globalRegistry.getAllExtensionDescriptions());
1056
this._myRegistry.set(myExtensions);
1057
1058
if (isCI) {
1059
this._logService.info(`$deltaExtensions: global extensions: ${printExtIds(this._globalRegistry)}`);
1060
this._logService.info(`$deltaExtensions: local extensions: ${printExtIds(this._myRegistry)}`);
1061
}
1062
1063
return Promise.resolve(undefined);
1064
}
1065
1066
public async $test_latency(n: number): Promise<number> {
1067
return n;
1068
}
1069
1070
public async $test_up(b: VSBuffer): Promise<number> {
1071
return b.byteLength;
1072
}
1073
1074
public async $test_down(size: number): Promise<VSBuffer> {
1075
const buff = VSBuffer.alloc(size);
1076
const value = Math.random() % 256;
1077
for (let i = 0; i < size; i++) {
1078
buff.writeUInt8(value, i);
1079
}
1080
return buff;
1081
}
1082
1083
public async $updateRemoteConnectionData(connectionData: IRemoteConnectionData): Promise<void> {
1084
this._remoteConnectionData = connectionData;
1085
this._onDidChangeRemoteConnectionData.fire();
1086
}
1087
1088
protected _isESM(extensionDescription: IExtensionDescription | undefined, modulePath?: string): boolean {
1089
modulePath ??= extensionDescription ? this._getEntryPoint(extensionDescription) : modulePath;
1090
return modulePath?.endsWith('.mjs') || (extensionDescription?.type === 'module' && !modulePath?.endsWith('.cjs'));
1091
}
1092
1093
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
1094
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
1095
protected abstract _loadCommonJSModule<T extends object | undefined>(extensionId: IExtensionDescription | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
1096
protected abstract _loadESMModule<T>(extension: IExtensionDescription | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
1097
public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
1098
}
1099
1100
function applyExtensionsDelta(activationEventsReader: SyncedActivationEventsReader, oldGlobalRegistry: ExtensionDescriptionRegistry, oldMyRegistry: ExtensionDescriptionRegistry, extensionsDelta: IExtensionDescriptionDelta) {
1101
activationEventsReader.addActivationEvents(extensionsDelta.addActivationEvents);
1102
const globalRegistry = new ExtensionDescriptionRegistry(activationEventsReader, oldGlobalRegistry.getAllExtensionDescriptions());
1103
globalRegistry.deltaExtensions(extensionsDelta.toAdd, extensionsDelta.toRemove);
1104
1105
const myExtensionsSet = new ExtensionIdentifierSet(oldMyRegistry.getAllExtensionDescriptions().map(extension => extension.identifier));
1106
for (const extensionId of extensionsDelta.myToRemove) {
1107
myExtensionsSet.delete(extensionId);
1108
}
1109
for (const extensionId of extensionsDelta.myToAdd) {
1110
myExtensionsSet.add(extensionId);
1111
}
1112
const myExtensions = filterExtensions(globalRegistry, myExtensionsSet);
1113
1114
return { globalRegistry, myExtensions };
1115
}
1116
1117
type TelemetryActivationEvent = {
1118
id: string;
1119
name: string;
1120
extensionVersion: string;
1121
publisherDisplayName: string;
1122
activationEvents: string | null;
1123
isBuiltin: boolean;
1124
reason: string;
1125
reasonId: string;
1126
};
1127
1128
function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TelemetryActivationEvent {
1129
const event = {
1130
id: extensionDescription.identifier.value,
1131
name: extensionDescription.name,
1132
extensionVersion: extensionDescription.version,
1133
publisherDisplayName: extensionDescription.publisher,
1134
activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
1135
isBuiltin: extensionDescription.isBuiltin,
1136
reason: reason.activationEvent,
1137
reasonId: reason.extensionId.value,
1138
};
1139
1140
return event;
1141
}
1142
1143
function printExtIds(registry: ExtensionDescriptionRegistry) {
1144
return registry.getAllExtensionDescriptions().map(ext => ext.identifier.value).join(',');
1145
}
1146
1147
export const IExtHostExtensionService = createDecorator<IExtHostExtensionService>('IExtHostExtensionService');
1148
1149
export interface IExtHostExtensionService extends AbstractExtHostExtensionService {
1150
readonly _serviceBrand: undefined;
1151
initialize(): Promise<void>;
1152
terminate(reason: string): void;
1153
getExtension(extensionId: string): Promise<IExtensionDescription | undefined>;
1154
isActivated(extensionId: ExtensionIdentifier): boolean;
1155
activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
1156
getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined;
1157
getExtensionRegistry(): Promise<ExtensionDescriptionRegistry>;
1158
getExtensionPathIndex(): Promise<ExtensionPaths>;
1159
registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable;
1160
getRemoteExecServer(authority: string): Promise<vscode.ExecServer | undefined>;
1161
1162
onDidChangeRemoteConnectionData: Event<void>;
1163
getRemoteConnectionData(): IRemoteConnectionData | null;
1164
}
1165
1166
export class Extension<T extends object | null | undefined> implements vscode.Extension<T> {
1167
1168
#extensionService: IExtHostExtensionService;
1169
#originExtensionId: ExtensionIdentifier;
1170
#identifier: ExtensionIdentifier;
1171
1172
readonly id: string;
1173
readonly extensionUri: URI;
1174
readonly extensionPath: string;
1175
readonly packageJSON: IExtensionDescription;
1176
readonly extensionKind: vscode.ExtensionKind;
1177
readonly isFromDifferentExtensionHost: boolean;
1178
1179
constructor(extensionService: IExtHostExtensionService, originExtensionId: ExtensionIdentifier, description: IExtensionDescription, kind: ExtensionKind, isFromDifferentExtensionHost: boolean) {
1180
this.#extensionService = extensionService;
1181
this.#originExtensionId = originExtensionId;
1182
this.#identifier = description.identifier;
1183
this.id = description.identifier.value;
1184
this.extensionUri = description.extensionLocation;
1185
this.extensionPath = path.normalize(originalFSPath(description.extensionLocation));
1186
this.packageJSON = description;
1187
this.extensionKind = kind;
1188
this.isFromDifferentExtensionHost = isFromDifferentExtensionHost;
1189
}
1190
1191
get isActive(): boolean {
1192
// TODO@alexdima support this
1193
return this.#extensionService.isActivated(this.#identifier);
1194
}
1195
1196
get exports(): T {
1197
if (this.packageJSON.api === 'none' || this.isFromDifferentExtensionHost) {
1198
return undefined!; // Strict nulloverride - Public api
1199
}
1200
return <T>this.#extensionService.getExtensionExports(this.#identifier);
1201
}
1202
1203
async activate(): Promise<T> {
1204
if (this.isFromDifferentExtensionHost) {
1205
throw new Error('Cannot activate foreign extension'); // TODO@alexdima support this
1206
}
1207
await this.#extensionService.activateByIdWithErrors(this.#identifier, { startup: false, extensionId: this.#originExtensionId, activationEvent: 'api' });
1208
return this.exports;
1209
}
1210
}
1211
1212
function filterExtensions(globalRegistry: ExtensionDescriptionRegistry, desiredExtensions: ExtensionIdentifierSet): IExtensionDescription[] {
1213
return globalRegistry.getAllExtensionDescriptions().filter(
1214
extension => desiredExtensions.has(extension.identifier)
1215
);
1216
}
1217
1218
export class ExtensionPaths {
1219
1220
constructor(
1221
private _searchTree: TernarySearchTree<URI, IExtensionDescription>
1222
) { }
1223
1224
setSearchTree(searchTree: TernarySearchTree<URI, IExtensionDescription>): void {
1225
this._searchTree = searchTree;
1226
}
1227
1228
findSubstr(key: URI): IExtensionDescription | undefined {
1229
return this._searchTree.findSubstr(key);
1230
}
1231
1232
forEach(callback: (value: IExtensionDescription, index: URI) => any): void {
1233
return this._searchTree.forEach(callback);
1234
}
1235
}
1236
1237
/**
1238
* This mirrors the activation events as seen by the renderer. The renderer
1239
* is the only one which can have a reliable view of activation events because
1240
* implicit activation events are generated via extension points, and they
1241
* are registered only on the renderer side.
1242
*/
1243
class SyncedActivationEventsReader implements IActivationEventsReader {
1244
1245
private readonly _map = new ExtensionIdentifierMap<string[]>();
1246
1247
constructor(activationEvents: { [extensionId: string]: string[] }) {
1248
this.addActivationEvents(activationEvents);
1249
}
1250
1251
public readActivationEvents(extensionDescription: IExtensionDescription): string[] {
1252
return this._map.get(extensionDescription.identifier) ?? [];
1253
}
1254
1255
public addActivationEvents(activationEvents: { [extensionId: string]: string[] }): void {
1256
for (const extensionId of Object.keys(activationEvents)) {
1257
this._map.set(extensionId, activationEvents[extensionId]);
1258
}
1259
}
1260
}
1261
1262