Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/common/extHostExtensionService.ts
5227 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
// eslint-disable-next-line local/code-no-any-casts
567
postMessage: messagePort.postMessage.bind(messagePort) as any
568
};
569
}
570
571
return messagePassingProtocol;
572
}
573
});
574
});
575
}
576
577
private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: vscode.ExtensionContext, extensionInternalStore: IDisposable, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<ActivatedExtension> {
578
// Make sure the extension's surface is not undefined
579
extensionModule = extensionModule || {
580
activate: undefined,
581
deactivate: undefined
582
};
583
584
return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => {
585
return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, toDisposable(() => {
586
extensionInternalStore.dispose();
587
dispose(context.subscriptions);
588
}));
589
});
590
}
591
592
private static _callActivateOptional(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: vscode.ExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<IExtensionAPI> {
593
if (typeof extensionModule.activate === 'function') {
594
try {
595
activationTimesBuilder.activateCallStart();
596
logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`);
597
const activateResult: Promise<IExtensionAPI> = extensionModule.activate.apply(globalThis, [context]);
598
activationTimesBuilder.activateCallStop();
599
600
activationTimesBuilder.activateResolveStart();
601
return Promise.resolve(activateResult).then((value) => {
602
activationTimesBuilder.activateResolveStop();
603
return value;
604
});
605
} catch (err) {
606
return Promise.reject(err);
607
}
608
} else {
609
// No activate found => the module is the extension's exports
610
return Promise.resolve<IExtensionAPI>(extensionModule);
611
}
612
}
613
614
// -- eager activation
615
616
private _activateOneStartupFinished(desc: IExtensionDescription, activationEvent: string): void {
617
this._activateById(desc.identifier, {
618
startup: false,
619
extensionId: desc.identifier,
620
activationEvent: activationEvent
621
}).then(undefined, (err) => {
622
this._logService.error(err);
623
});
624
}
625
626
private _activateAllStartupFinishedDeferred(extensions: IExtensionDescription[], start: number = 0): void {
627
const timeBudget = 50; // 50 milliseconds
628
const startTime = Date.now();
629
630
setTimeout0(() => {
631
for (let i = start; i < extensions.length; i += 1) {
632
const desc = extensions[i];
633
for (const activationEvent of (desc.activationEvents ?? [])) {
634
if (activationEvent === 'onStartupFinished') {
635
if (Date.now() - startTime > timeBudget) {
636
// time budget for current task has been exceeded
637
// set a new task to activate current and remaining extensions
638
this._activateAllStartupFinishedDeferred(extensions, i);
639
break;
640
} else {
641
this._activateOneStartupFinished(desc, activationEvent);
642
}
643
}
644
}
645
}
646
});
647
}
648
649
private _activateAllStartupFinished(): void {
650
// startup is considered finished
651
this._mainThreadExtensionsProxy.$setPerformanceMarks(performance.getMarks());
652
653
this._extHostConfiguration.getConfigProvider().then((configProvider) => {
654
const shouldDeferActivation = configProvider.getConfiguration('extensions.experimental').get<boolean>('deferredStartupFinishedActivation');
655
const allExtensionDescriptions = this._myRegistry.getAllExtensionDescriptions();
656
if (shouldDeferActivation) {
657
this._activateAllStartupFinishedDeferred(allExtensionDescriptions);
658
} else {
659
for (const desc of allExtensionDescriptions) {
660
if (desc.activationEvents) {
661
for (const activationEvent of desc.activationEvents) {
662
if (activationEvent === 'onStartupFinished') {
663
this._activateOneStartupFinished(desc, activationEvent);
664
}
665
}
666
}
667
}
668
}
669
});
670
}
671
672
// Handle "eager" activation extensions
673
private _handleEagerExtensions(): Promise<void> {
674
const starActivation = this._activateByEvent('*', true).then(undefined, (err) => {
675
this._logService.error(err);
676
});
677
678
this._register(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added)));
679
const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : [];
680
const workspaceContainsActivation = this._handleWorkspaceContainsEagerExtensions(folders);
681
const remoteResolverActivation = this._handleRemoteResolverEagerExtensions();
682
const eagerExtensionsActivation = Promise.all([remoteResolverActivation, starActivation, workspaceContainsActivation]).then(() => { });
683
684
Promise.race([eagerExtensionsActivation, timeout(10000)]).then(() => {
685
this._activateAllStartupFinished();
686
});
687
688
return eagerExtensionsActivation;
689
}
690
691
private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray<vscode.WorkspaceFolder>): Promise<void> {
692
if (folders.length === 0) {
693
return Promise.resolve(undefined);
694
}
695
696
return Promise.all(
697
this._myRegistry.getAllExtensionDescriptions().map((desc) => {
698
return this._handleWorkspaceContainsEagerExtension(folders, desc);
699
})
700
).then(() => { });
701
}
702
703
private async _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray<vscode.WorkspaceFolder>, desc: IExtensionDescription): Promise<void> {
704
if (this.isActivated(desc.identifier)) {
705
return;
706
}
707
708
const localWithRemote = !this._initData.remote.isRemote && !!this._initData.remote.authority;
709
const host: IExtensionActivationHost = {
710
logService: this._logService,
711
folders: folders.map(folder => folder.uri),
712
forceUsingSearch: localWithRemote || !this._hostUtils.fsExists,
713
exists: (uri) => this._hostUtils.fsExists!(uri.fsPath),
714
checkExists: (folders, includes, token) => this._mainThreadWorkspaceProxy.$checkExists(folders, includes, token)
715
};
716
717
const result = await checkActivateWorkspaceContainsExtension(host, desc);
718
if (!result) {
719
return;
720
}
721
722
return (
723
this._activateById(desc.identifier, { startup: true, extensionId: desc.identifier, activationEvent: result.activationEvent })
724
.then(undefined, err => this._logService.error(err))
725
);
726
}
727
728
private async _handleRemoteResolverEagerExtensions(): Promise<void> {
729
if (this._initData.remote.authority) {
730
return this._activateByEvent(`onResolveRemoteAuthority:${this._initData.remote.authority}`, false);
731
}
732
}
733
734
public async $extensionTestsExecute(): Promise<number> {
735
await this._eagerExtensionsActivated.wait();
736
try {
737
return await this._doHandleExtensionTests();
738
} catch (error) {
739
console.error(error); // ensure any error message makes it onto the console
740
throw error;
741
}
742
}
743
744
private async _doHandleExtensionTests(): Promise<number> {
745
const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment;
746
if (!extensionDevelopmentLocationURI || !extensionTestsLocationURI) {
747
throw new Error(nls.localize('extensionTestError1', "Cannot load test runner."));
748
}
749
750
const extensionDescription = (await this.getExtensionPathIndex()).findSubstr(extensionTestsLocationURI);
751
const isESM = this._isESM(extensionDescription, extensionTestsLocationURI.path);
752
753
// Require the test runner via node require from the provided path
754
const testRunner = await (isESM
755
? this._loadESMModule<ITestRunner | INewTestRunner | undefined>(null, extensionTestsLocationURI, new ExtensionActivationTimesBuilder(false))
756
: this._loadCommonJSModule<ITestRunner | INewTestRunner | undefined>(null, extensionTestsLocationURI, new ExtensionActivationTimesBuilder(false)));
757
758
if (!testRunner || typeof testRunner.run !== 'function') {
759
throw new Error(nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsLocationURI.toString()));
760
}
761
762
// Execute the runner if it follows the old `run` spec
763
return new Promise<number>((resolve, reject) => {
764
const oldTestRunnerCallback = (error: Error, failures: number | undefined) => {
765
if (error) {
766
if (isCI) {
767
this._logService.error(`Test runner called back with error`, error);
768
}
769
reject(error);
770
} else {
771
if (isCI) {
772
if (failures) {
773
this._logService.info(`Test runner called back with ${failures} failures.`);
774
} else {
775
this._logService.info(`Test runner called back with successful outcome.`);
776
}
777
}
778
resolve((typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
779
}
780
};
781
782
const extensionTestsPath = originalFSPath(extensionTestsLocationURI); // for the old test runner API
783
784
const runResult = testRunner.run(extensionTestsPath, oldTestRunnerCallback);
785
786
// Using the new API `run(): Promise<void>`
787
if (runResult && runResult.then) {
788
runResult
789
.then(() => {
790
if (isCI) {
791
this._logService.info(`Test runner finished successfully.`);
792
}
793
resolve(0);
794
})
795
.catch((err: unknown) => {
796
if (isCI) {
797
this._logService.error(`Test runner finished with error`, err);
798
}
799
reject(err instanceof Error && err.stack ? err.stack : String(err));
800
});
801
}
802
});
803
}
804
805
private _startExtensionHost(): Promise<void> {
806
if (this._started) {
807
throw new Error(`Extension host is already started!`);
808
}
809
this._started = true;
810
811
return this._readyToStartExtensionHost.wait()
812
.then(() => this._readyToRunExtensions.open())
813
.then(() => {
814
// wait for all activation events that came in during workbench startup, but at maximum 1s
815
return Promise.race([this._activator.waitForActivatingExtensions(), timeout(1000)]);
816
})
817
.then(() => this._handleEagerExtensions())
818
.then(() => {
819
this._eagerExtensionsActivated.open();
820
this._logService.info(`Eager extensions activated`);
821
});
822
}
823
824
// -- called by extensions
825
826
public registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable {
827
this._resolvers[authorityPrefix] = resolver;
828
return toDisposable(() => {
829
delete this._resolvers[authorityPrefix];
830
});
831
}
832
833
public async getRemoteExecServer(remoteAuthority: string): Promise<vscode.ExecServer | undefined> {
834
const { resolver } = await this._activateAndGetResolver(remoteAuthority);
835
return resolver?.resolveExecServer?.(remoteAuthority, { resolveAttempt: 0 });
836
}
837
838
// -- called by main thread
839
840
private async _activateAndGetResolver(remoteAuthority: string): Promise<{ authorityPrefix: string; resolver: vscode.RemoteAuthorityResolver | undefined }> {
841
const authorityPlusIndex = remoteAuthority.indexOf('+');
842
if (authorityPlusIndex === -1) {
843
throw new RemoteAuthorityResolverError(`Not an authority that can be resolved!`, RemoteAuthorityResolverErrorCode.InvalidAuthority);
844
}
845
const authorityPrefix = remoteAuthority.substr(0, authorityPlusIndex);
846
847
await this._almostReadyToRunExtensions.wait();
848
await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false);
849
850
return { authorityPrefix, resolver: this._resolvers[authorityPrefix] };
851
}
852
853
public async $resolveAuthority(remoteAuthorityChain: string, resolveAttempt: number): Promise<Dto<IResolveAuthorityResult>> {
854
const sw = StopWatch.create(false);
855
const prefix = () => `[resolveAuthority(${getRemoteAuthorityPrefix(remoteAuthorityChain)},${resolveAttempt})][${sw.elapsed()}ms] `;
856
const logInfo = (msg: string) => this._logService.info(`${prefix()}${msg}`);
857
const logWarning = (msg: string) => this._logService.warn(`${prefix()}${msg}`);
858
const logError = (msg: string, err: any = undefined) => this._logService.error(`${prefix()}${msg}`, err);
859
const normalizeError = (err: unknown) => {
860
if (err instanceof RemoteAuthorityResolverError) {
861
return {
862
type: 'error' as const,
863
error: {
864
code: err._code,
865
message: err._message,
866
detail: err._detail
867
}
868
};
869
}
870
throw err;
871
};
872
873
const getResolver = async (remoteAuthority: string) => {
874
logInfo(`activating resolver for ${remoteAuthority}...`);
875
const { resolver, authorityPrefix } = await this._activateAndGetResolver(remoteAuthority);
876
if (!resolver) {
877
logError(`no resolver for ${authorityPrefix}`);
878
throw new RemoteAuthorityResolverError(`No remote extension installed to resolve ${authorityPrefix}.`, RemoteAuthorityResolverErrorCode.NoResolverFound);
879
}
880
return { resolver, authorityPrefix, remoteAuthority };
881
};
882
883
const chain = remoteAuthorityChain.split(/@|%40/g).reverse();
884
logInfo(`activating remote resolvers ${chain.join(' -> ')}`);
885
886
let resolvers;
887
try {
888
resolvers = await Promise.all(chain.map(getResolver)).catch(async (e: Error) => {
889
if (!(e instanceof RemoteAuthorityResolverError) || e._code !== RemoteAuthorityResolverErrorCode.InvalidAuthority) { throw e; }
890
logWarning(`resolving nested authorities failed: ${e.message}`);
891
return [await getResolver(remoteAuthorityChain)];
892
});
893
} catch (e) {
894
return normalizeError(e);
895
}
896
897
const intervalLogger = new IntervalTimer();
898
intervalLogger.cancelAndSet(() => logInfo('waiting...'), 1000);
899
900
let result!: vscode.ResolverResult;
901
let execServer: vscode.ExecServer | undefined;
902
for (const [i, { authorityPrefix, resolver, remoteAuthority }] of resolvers.entries()) {
903
try {
904
if (i === resolvers.length - 1) {
905
logInfo(`invoking final resolve()...`);
906
performance.mark(`code/extHost/willResolveAuthority/${authorityPrefix}`);
907
result = await resolver.resolve(remoteAuthority, { resolveAttempt, execServer });
908
performance.mark(`code/extHost/didResolveAuthorityOK/${authorityPrefix}`);
909
logInfo(`setting tunnel factory...`);
910
this._register(await this._extHostTunnelService.setTunnelFactory(
911
resolver,
912
ExtHostManagedResolvedAuthority.isManagedResolvedAuthority(result) ? result : undefined
913
));
914
} else {
915
logInfo(`invoking resolveExecServer() for ${remoteAuthority}`);
916
performance.mark(`code/extHost/willResolveExecServer/${authorityPrefix}`);
917
execServer = await resolver.resolveExecServer?.(remoteAuthority, { resolveAttempt, execServer });
918
if (!execServer) {
919
throw new RemoteAuthorityResolverError(`Exec server was not available for ${remoteAuthority}`, RemoteAuthorityResolverErrorCode.NoResolverFound); // we did, in fact, break the chain :(
920
}
921
performance.mark(`code/extHost/didResolveExecServerOK/${authorityPrefix}`);
922
}
923
} catch (e) {
924
performance.mark(`code/extHost/didResolveAuthorityError/${authorityPrefix}`);
925
logError(`returned an error`, e);
926
intervalLogger.dispose();
927
return normalizeError(e);
928
}
929
}
930
931
intervalLogger.dispose();
932
933
const tunnelInformation: TunnelInformation = {
934
environmentTunnels: result.environmentTunnels,
935
features: result.tunnelFeatures ? {
936
elevation: result.tunnelFeatures.elevation,
937
privacyOptions: result.tunnelFeatures.privacyOptions,
938
protocol: result.tunnelFeatures.protocol === undefined ? true : result.tunnelFeatures.protocol,
939
} : undefined
940
};
941
942
// Split merged API result into separate authority/options
943
const options: ResolvedOptions = {
944
extensionHostEnv: result.extensionHostEnv,
945
isTrusted: result.isTrusted,
946
authenticationSession: result.authenticationSessionForInitializingExtensions ? { id: result.authenticationSessionForInitializingExtensions.id, providerId: result.authenticationSessionForInitializingExtensions.providerId } : undefined
947
};
948
949
// extension are not required to return an instance of ResolvedAuthority or ManagedResolvedAuthority, so don't use `instanceof`
950
logInfo(`returned ${ExtHostManagedResolvedAuthority.isManagedResolvedAuthority(result) ? 'managed authority' : `${result.host}:${result.port}`}`);
951
952
let authority: ResolvedAuthority;
953
if (ExtHostManagedResolvedAuthority.isManagedResolvedAuthority(result)) {
954
// The socket factory is identified by the `resolveAttempt`, since that is a number which
955
// always increments and is unique over all resolve() calls in a workbench session.
956
const socketFactoryId = resolveAttempt;
957
958
// There is only on managed socket factory at a time, so we can just overwrite the old one.
959
this._extHostManagedSockets.setFactory(socketFactoryId, result.makeConnection);
960
961
authority = {
962
authority: remoteAuthorityChain,
963
connectTo: new ManagedRemoteConnection(socketFactoryId),
964
connectionToken: result.connectionToken
965
};
966
} else {
967
authority = {
968
authority: remoteAuthorityChain,
969
connectTo: new WebSocketRemoteConnection(result.host, result.port),
970
connectionToken: result.connectionToken
971
};
972
}
973
974
return {
975
type: 'ok',
976
value: {
977
authority: authority as Dto<ResolvedAuthority>,
978
options,
979
tunnelInformation,
980
}
981
};
982
}
983
984
public async $getCanonicalURI(remoteAuthority: string, uriComponents: UriComponents): Promise<UriComponents | null> {
985
this._logService.info(`$getCanonicalURI invoked for authority (${getRemoteAuthorityPrefix(remoteAuthority)})`);
986
987
const { resolver } = await this._activateAndGetResolver(remoteAuthority);
988
if (!resolver) {
989
// Return `null` if no resolver for `remoteAuthority` is found.
990
return null;
991
}
992
993
const uri = URI.revive(uriComponents);
994
995
if (typeof resolver.getCanonicalURI === 'undefined') {
996
// resolver cannot compute canonical URI
997
return uri;
998
}
999
1000
const result = await asPromise(() => resolver.getCanonicalURI!(uri));
1001
if (!result) {
1002
return uri;
1003
}
1004
1005
return result;
1006
}
1007
1008
public async $startExtensionHost(extensionsDelta: IExtensionDescriptionDelta): Promise<void> {
1009
// eslint-disable-next-line local/code-no-any-casts
1010
extensionsDelta.toAdd.forEach((extension) => (<any>extension).extensionLocation = URI.revive(extension.extensionLocation));
1011
1012
const { globalRegistry, myExtensions } = applyExtensionsDelta(this._activationEventsReader, this._globalRegistry, this._myRegistry, extensionsDelta);
1013
const newSearchTree = await this._createExtensionPathIndex(myExtensions);
1014
const extensionsPaths = await this.getExtensionPathIndex();
1015
extensionsPaths.setSearchTree(newSearchTree);
1016
this._globalRegistry.set(globalRegistry.getAllExtensionDescriptions());
1017
this._myRegistry.set(myExtensions);
1018
1019
if (isCI) {
1020
this._logService.info(`$startExtensionHost: global extensions: ${printExtIds(this._globalRegistry)}`);
1021
this._logService.info(`$startExtensionHost: local extensions: ${printExtIds(this._myRegistry)}`);
1022
}
1023
1024
return this._startExtensionHost();
1025
}
1026
1027
public $activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
1028
if (activationKind === ActivationKind.Immediate) {
1029
return this._almostReadyToRunExtensions.wait()
1030
.then(_ => this._activateByEvent(activationEvent, false));
1031
}
1032
1033
return (
1034
this._readyToRunExtensions.wait()
1035
.then(_ => this._activateByEvent(activationEvent, false))
1036
);
1037
}
1038
1039
public async $activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean> {
1040
await this._readyToRunExtensions.wait();
1041
if (!this._myRegistry.getExtensionDescription(extensionId)) {
1042
// unknown extension => ignore
1043
return false;
1044
}
1045
await this._activateById(extensionId, reason);
1046
return true;
1047
}
1048
1049
public async $deltaExtensions(extensionsDelta: IExtensionDescriptionDelta): Promise<void> {
1050
// eslint-disable-next-line local/code-no-any-casts
1051
extensionsDelta.toAdd.forEach((extension) => (<any>extension).extensionLocation = URI.revive(extension.extensionLocation));
1052
1053
// First build up and update the trie and only afterwards apply the delta
1054
const { globalRegistry, myExtensions } = applyExtensionsDelta(this._activationEventsReader, this._globalRegistry, this._myRegistry, extensionsDelta);
1055
const newSearchTree = await this._createExtensionPathIndex(myExtensions);
1056
const extensionsPaths = await this.getExtensionPathIndex();
1057
extensionsPaths.setSearchTree(newSearchTree);
1058
this._globalRegistry.set(globalRegistry.getAllExtensionDescriptions());
1059
this._myRegistry.set(myExtensions);
1060
1061
if (isCI) {
1062
this._logService.info(`$deltaExtensions: global extensions: ${printExtIds(this._globalRegistry)}`);
1063
this._logService.info(`$deltaExtensions: local extensions: ${printExtIds(this._myRegistry)}`);
1064
}
1065
1066
return Promise.resolve(undefined);
1067
}
1068
1069
public async $test_latency(n: number): Promise<number> {
1070
return n;
1071
}
1072
1073
public async $test_up(b: VSBuffer): Promise<number> {
1074
return b.byteLength;
1075
}
1076
1077
public async $test_down(size: number): Promise<VSBuffer> {
1078
const buff = VSBuffer.alloc(size);
1079
const value = Math.random() % 256;
1080
for (let i = 0; i < size; i++) {
1081
buff.writeUInt8(value, i);
1082
}
1083
return buff;
1084
}
1085
1086
public async $updateRemoteConnectionData(connectionData: IRemoteConnectionData): Promise<void> {
1087
this._remoteConnectionData = connectionData;
1088
this._onDidChangeRemoteConnectionData.fire();
1089
}
1090
1091
protected _isESM(extensionDescription: IExtensionDescription | undefined, modulePath?: string): boolean {
1092
modulePath ??= extensionDescription ? this._getEntryPoint(extensionDescription) : modulePath;
1093
return modulePath?.endsWith('.mjs') || (extensionDescription?.type === 'module' && !modulePath?.endsWith('.cjs'));
1094
}
1095
1096
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
1097
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
1098
protected abstract _loadCommonJSModule<T extends object | undefined>(extensionId: IExtensionDescription | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
1099
protected abstract _loadESMModule<T>(extension: IExtensionDescription | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
1100
public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
1101
}
1102
1103
function applyExtensionsDelta(activationEventsReader: SyncedActivationEventsReader, oldGlobalRegistry: ExtensionDescriptionRegistry, oldMyRegistry: ExtensionDescriptionRegistry, extensionsDelta: IExtensionDescriptionDelta) {
1104
activationEventsReader.addActivationEvents(extensionsDelta.addActivationEvents);
1105
const globalRegistry = new ExtensionDescriptionRegistry(activationEventsReader, oldGlobalRegistry.getAllExtensionDescriptions());
1106
globalRegistry.deltaExtensions(extensionsDelta.toAdd, extensionsDelta.toRemove);
1107
1108
const myExtensionsSet = new ExtensionIdentifierSet(oldMyRegistry.getAllExtensionDescriptions().map(extension => extension.identifier));
1109
for (const extensionId of extensionsDelta.myToRemove) {
1110
myExtensionsSet.delete(extensionId);
1111
}
1112
for (const extensionId of extensionsDelta.myToAdd) {
1113
myExtensionsSet.add(extensionId);
1114
}
1115
const myExtensions = filterExtensions(globalRegistry, myExtensionsSet);
1116
1117
return { globalRegistry, myExtensions };
1118
}
1119
1120
type TelemetryActivationEvent = {
1121
id: string;
1122
name: string;
1123
extensionVersion: string;
1124
publisherDisplayName: string;
1125
activationEvents: string | null;
1126
isBuiltin: boolean;
1127
reason: string;
1128
reasonId: string;
1129
};
1130
1131
function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TelemetryActivationEvent {
1132
const event = {
1133
id: extensionDescription.identifier.value,
1134
name: extensionDescription.name,
1135
extensionVersion: extensionDescription.version,
1136
publisherDisplayName: extensionDescription.publisher,
1137
activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null,
1138
isBuiltin: extensionDescription.isBuiltin,
1139
reason: reason.activationEvent,
1140
reasonId: reason.extensionId.value,
1141
};
1142
1143
return event;
1144
}
1145
1146
function printExtIds(registry: ExtensionDescriptionRegistry) {
1147
return registry.getAllExtensionDescriptions().map(ext => ext.identifier.value).join(',');
1148
}
1149
1150
export const IExtHostExtensionService = createDecorator<IExtHostExtensionService>('IExtHostExtensionService');
1151
1152
export interface IExtHostExtensionService extends AbstractExtHostExtensionService {
1153
readonly _serviceBrand: undefined;
1154
initialize(): Promise<void>;
1155
terminate(reason: string): void;
1156
getExtension(extensionId: string): Promise<IExtensionDescription | undefined>;
1157
isActivated(extensionId: ExtensionIdentifier): boolean;
1158
activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
1159
getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined;
1160
getExtensionRegistry(): Promise<ExtensionDescriptionRegistry>;
1161
getExtensionPathIndex(): Promise<ExtensionPaths>;
1162
registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable;
1163
getRemoteExecServer(authority: string): Promise<vscode.ExecServer | undefined>;
1164
1165
readonly onDidChangeRemoteConnectionData: Event<void>;
1166
getRemoteConnectionData(): IRemoteConnectionData | null;
1167
}
1168
1169
export class Extension<T extends object | null | undefined> implements vscode.Extension<T> {
1170
1171
#extensionService: IExtHostExtensionService;
1172
#originExtensionId: ExtensionIdentifier;
1173
#identifier: ExtensionIdentifier;
1174
1175
readonly id: string;
1176
readonly extensionUri: URI;
1177
readonly extensionPath: string;
1178
readonly packageJSON: IExtensionDescription;
1179
readonly extensionKind: vscode.ExtensionKind;
1180
readonly isFromDifferentExtensionHost: boolean;
1181
1182
constructor(extensionService: IExtHostExtensionService, originExtensionId: ExtensionIdentifier, description: IExtensionDescription, kind: ExtensionKind, isFromDifferentExtensionHost: boolean) {
1183
this.#extensionService = extensionService;
1184
this.#originExtensionId = originExtensionId;
1185
this.#identifier = description.identifier;
1186
this.id = description.identifier.value;
1187
this.extensionUri = description.extensionLocation;
1188
this.extensionPath = path.normalize(originalFSPath(description.extensionLocation));
1189
this.packageJSON = description;
1190
this.extensionKind = kind;
1191
this.isFromDifferentExtensionHost = isFromDifferentExtensionHost;
1192
}
1193
1194
get isActive(): boolean {
1195
// TODO@alexdima support this
1196
return this.#extensionService.isActivated(this.#identifier);
1197
}
1198
1199
get exports(): T {
1200
if (this.packageJSON.api === 'none' || this.isFromDifferentExtensionHost) {
1201
return undefined!; // Strict nulloverride - Public api
1202
}
1203
return <T>this.#extensionService.getExtensionExports(this.#identifier);
1204
}
1205
1206
async activate(): Promise<T> {
1207
if (this.isFromDifferentExtensionHost) {
1208
throw new Error('Cannot activate foreign extension'); // TODO@alexdima support this
1209
}
1210
await this.#extensionService.activateByIdWithErrors(this.#identifier, { startup: false, extensionId: this.#originExtensionId, activationEvent: 'api' });
1211
return this.exports;
1212
}
1213
}
1214
1215
function filterExtensions(globalRegistry: ExtensionDescriptionRegistry, desiredExtensions: ExtensionIdentifierSet): IExtensionDescription[] {
1216
return globalRegistry.getAllExtensionDescriptions().filter(
1217
extension => desiredExtensions.has(extension.identifier)
1218
);
1219
}
1220
1221
export class ExtensionPaths {
1222
1223
constructor(
1224
private _searchTree: TernarySearchTree<URI, IExtensionDescription>
1225
) { }
1226
1227
setSearchTree(searchTree: TernarySearchTree<URI, IExtensionDescription>): void {
1228
this._searchTree = searchTree;
1229
}
1230
1231
findSubstr(key: URI): IExtensionDescription | undefined {
1232
return this._searchTree.findSubstr(key);
1233
}
1234
1235
forEach(callback: (value: IExtensionDescription, index: URI) => any): void {
1236
return this._searchTree.forEach(callback);
1237
}
1238
}
1239
1240
/**
1241
* This mirrors the activation events as seen by the renderer. The renderer
1242
* is the only one which can have a reliable view of activation events because
1243
* implicit activation events are generated via extension points, and they
1244
* are registered only on the renderer side.
1245
*/
1246
class SyncedActivationEventsReader implements IActivationEventsReader {
1247
1248
private readonly _map = new ExtensionIdentifierMap<string[]>();
1249
1250
constructor(activationEvents: { [extensionId: string]: string[] }) {
1251
this.addActivationEvents(activationEvents);
1252
}
1253
1254
public readActivationEvents(extensionDescription: IExtensionDescription): string[] {
1255
return this._map.get(extensionDescription.identifier) ?? [];
1256
}
1257
1258
public addActivationEvents(activationEvents: { [extensionId: string]: string[] }): void {
1259
for (const extensionId of Object.keys(activationEvents)) {
1260
this._map.set(extensionId, activationEvents[extensionId]);
1261
}
1262
}
1263
}
1264
1265