src/vs/workbench/api/common/extHostExtensionActivator.ts
454 LOC · 385 covered · 69 uncovered · 93 ranges · 63 concepts · 21 introducers · 42 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
/*---------------------------------------------------------------------------------------------
extHostExtensionActivator.ts ×33
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as vscode from 'vscode';
import * as errors from '../../../base/common/errors.js';
import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
import { ExtensionDescriptionRegistry } from '../../services/extensions/common/extensionDescriptionRegistry.js';
import { ExtensionIdentifier, ExtensionIdentifierMap } from '../../../platform/extensions/common/extensions.js';
import { ExtensionActivationReason, MissingExtensionDependency } from '../../services/extensions/common/extensions.js';
import { ILogService } from '../../../platform/log/common/log.js';
import { Barrier } from '../../../base/common/async.js';
/**
* Represents the source code (module) of an extension.
*/
export interface IExtensionModule {
activate?(ctx: vscode.ExtensionContext): Promise<IExtensionAPI>;
deactivate?(): void;
}
/**
* Represents the API of an extension (return value of `activate`).
*/
export interface IExtensionAPI {
// _extensionAPIBrand: any;
}
export type ExtensionActivationTimesFragment = {
startup?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Activation occurred during startup' };
codeLoadingTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time it took to load the extension\'s code' };
activateCallTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time it took to call activate' };
activateResolvedTime?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time it took for async-activation to finish' };
};
export class ExtensionActivationTimes {
public static readonly NONE = new ExtensionActivationTimes(false, -1, -1, -1);
public readonly startup: boolean;
public readonly codeLoadingTime: number;
public readonly activateCallTime: number;
public readonly activateResolvedTime: number;
constructor(startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number) {
this.startup = startup;
this.codeLoadingTime = codeLoadingTime;
this.activateCallTime = activateCallTime;
this.activateResolvedTime = activateResolvedTime;
}
}
export class ExtensionActivationTimesBuilder {
private readonly _startup: boolean;
private _codeLoadingStart: number;
private _codeLoadingStop: number;
private _activateCallStart: number;
private _activateCallStop: number;
private _activateResolveStart: number;
private _activateResolveStop: number;
constructor(startup: boolean) {
this._startup = startup;
this._codeLoadingStart = -1;
this._codeLoadingStop = -1;
this._activateCallStart = -1;
this._activateCallStop = -1;
this._activateResolveStart = -1;
this._activateResolveStop = -1;
}
private _delta(start: number, stop: number): number {
if (start === -1 || stop === -1) {
return -1;
}
return stop - start;
}
public build(): ExtensionActivationTimes {
return new ExtensionActivationTimes(
this._startup,
this._delta(this._codeLoadingStart, this._codeLoadingStop),
this._delta(this._activateCallStart, this._activateCallStop),
this._delta(this._activateResolveStart, this._activateResolveStop)
);
}
public codeLoadingStart(): void {
this._codeLoadingStart = Date.now();
}
public codeLoadingStop(): void {
this._codeLoadingStop = Date.now();
}
public activateCallStart(): void {
this._activateCallStart = Date.now();
}
public activateCallStop(): void {
this._activateCallStop = Date.now();
}
public activateResolveStart(): void {
this._activateResolveStart = Date.now();
}
public activateResolveStop(): void {
this._activateResolveStop = Date.now();
}
export class ActivatedExtension {
public readonly activationFailed: boolean;
public readonly activationFailedError: Error | null;
public readonly activationTimes: ExtensionActivationTimes;
public readonly module: IExtensionModule;
public readonly exports: IExtensionAPI | undefined;
public readonly disposable: IDisposable;
constructor(
activationFailedError: Error | null,
activationTimes: ExtensionActivationTimes,
module: IExtensionModule,
exports: IExtensionAPI | undefined,
disposable: IDisposable
) {
this.activationFailed = activationFailed;
this.activationFailedError = activationFailedError;
this.activationTimes = activationTimes;
this.module = module;
this.exports = exports;
this.disposable = disposable;
}
export class EmptyExtension extends ActivatedExtension {
constructor(activationTimes: ExtensionActivationTimes) {
super(false, null, activationTimes, { activate: undefined, deactivate: undefined }, undefined, Disposable.None);
extHostExtensionActivator.ts ×1
}
export class HostExtension extends ActivatedExtension {
constructor() {
super(false, null, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, Disposable.None);
}
class FailedExtension extends ActivatedExtension {
constructor(activationError: Error) {
super(true, activationError, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, Disposable.None);
extHostExtensionActivator.ts ×1
}
export interface IExtensionsActivatorHost {
onExtensionActivationError(extensionId: ExtensionIdentifier, error: Error | null, missingExtensionDependency: MissingExtensionDependency | null): void;
actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<ActivatedExtension>;
}
type ActivationIdAndReason = { id: ExtensionIdentifier; reason: ExtensionActivationReason };
export class ExtensionsActivator implements IDisposable {
private readonly _registry: ExtensionDescriptionRegistry;
private readonly _globalRegistry: ExtensionDescriptionRegistry;
private readonly _host: IExtensionsActivatorHost;
private readonly _operations: ExtensionIdentifierMap<ActivationOperation>;
/**
* A map of already activated events to speed things up if the same activation event is triggered multiple times.
*/
private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean };
constructor(
globalRegistry: ExtensionDescriptionRegistry,
host: IExtensionsActivatorHost,
@ILogService private readonly _logService: ILogService
) {
this._registry = registry;
this._globalRegistry = globalRegistry;
this._host = host;
this._operations = new ExtensionIdentifierMap<ActivationOperation>();
this._alreadyActivatedEvents = Object.create(null);
}
public dispose(): void {
}
public async waitForActivatingExtensions(): Promise<void> {
const res: Promise<boolean>[] = [];
for (const [_, op] of this._operations) {
res.push(op.wait());
}
await Promise.all(res);
}
public isActivated(extensionId: ExtensionIdentifier): boolean {
return Boolean(op && op.value);
}
public getActivatedExtension(extensionId: ExtensionIdentifier): ActivatedExtension {
const op = this._operations.get(extensionId);
if (!op || !op.value) {
throw new Error(`Extension '${extensionId.value}' is not known or not activated`);
}
return op.value;
}
public async activateByEvent(activationEvent: string, startup: boolean): Promise<void> {
}
const activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent);
await this._activateExtensions(activateExtensions.map(e => ({
id: e.identifier,
reason: { startup, extensionId: e.identifier, activationEvent }
})));
this._alreadyActivatedEvents[activationEvent] = true;
public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
const desc = this._registry.getExtensionDescription(extensionId);
extHostExtensionActivator.ts ×2
if (!desc) {
throw new Error(`Extension '${extensionId.value}' is not known`);
}
return this._activateExtensions([{ id: desc.identifier, reason }]);
private async _activateExtensions(extensions: ActivationIdAndReason[]): Promise<void> {
.filter((p) => !this.isActivated(p.id))
.map(ext => this._handleActivationRequest(ext));
await Promise.all(operations.map(op => op.wait()));
}
/**
* Handle semantics related to dependencies for `currentExtension`.
* We don't need to worry about dependency loops because they are handled by the registry.
*/
private _handleActivationRequest(currentActivation: ActivationIdAndReason): ActivationOperation {
}
if (this._isHostExtension(currentActivation.id)) {
return this._createAndSaveOperation(currentActivation, null, [], null);
extHostExtensionActivator.ts ×2
}
const currentExtension = this._registry.getExtensionDescription(currentActivation.id);
if (!currentExtension) {
// Error condition 0: unknown extension
const error = new Error(`Cannot activate unknown extension '${currentActivation.id.value}'`);
const result = this._createAndSaveOperation(currentActivation, null, [], new FailedExtension(error));
this._host.onExtensionActivationError(
currentActivation.id,
error,
new MissingExtensionDependency(currentActivation.id.value)
);
return result;
}
const deps: ActivationOperation[] = [];
const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies);
for (const depId of depIds) {
if (this._isResolvedExtension(depId)) {
continue;
}
const dep = this._operations.get(depId);
if (dep) {
deps.push(dep);
continue;
}
if (this._isHostExtension(depId)) {
deps.push(this._handleActivationRequest({
id: this._globalRegistry.getExtensionDescription(depId)!.identifier,
reason: currentActivation.reason
}));
continue;
}
const depDesc = this._registry.getExtensionDescription(depId);
if (depDesc) {
// this dependency does not need to activate because it is descriptive only
continue;
}
// must first wait for the dependency to activate
deps.push(this._handleActivationRequest({
id: depDesc.identifier,
reason: currentActivation.reason
}));
continue;
}
// Error condition 1: unknown dependency
const currentExtensionFriendlyName = currentExtension.displayName || currentExtension.identifier.value;
const error = new Error(`Cannot activate the '${currentExtensionFriendlyName}' extension because it depends on unknown extension '${depId}'`);
extHostExtensionActivator.ts ×4
const result = this._createAndSaveOperation(currentActivation, currentExtension.displayName, [], new FailedExtension(error));
this._host.onExtensionActivationError(
currentExtension.identifier,
error,
new MissingExtensionDependency(depId)
);
return result;
}
return this._createAndSaveOperation(currentActivation, currentExtension.displayName, deps, null);
private _createAndSaveOperation(activation: ActivationIdAndReason, displayName: string | null | undefined, deps: ActivationOperation[], value: ActivatedExtension | null): ActivationOperation {
const operation = new ActivationOperation(activation.id, displayName, activation.reason, deps, value, this._host, this._logService);
extHostExtensionActivator.ts ×20
this._operations.set(activation.id, operation);
return operation;
}
private _isHostExtension(extensionId: ExtensionIdentifier | string): boolean {
return ExtensionDescriptionRegistry.isHostExtension(extensionId, this._registry, this._globalRegistry);
extHostExtensionActivator.ts ×20
}
private _isResolvedExtension(extensionId: ExtensionIdentifier | string): boolean {
const extensionDescription = this._globalRegistry.getExtensionDescription(extensionId);
extHostExtensionActivator.ts ×4
if (!extensionDescription) {
return false;
}
return (!extensionDescription.main && !extensionDescription.browser);
extHostExtensionActivator.ts ×1
class ActivationOperation {
private readonly _barrier = new Barrier();
private _isDisposed = false;
public get value(): ActivatedExtension | null {
return this._value;
}
public get friendlyName(): string {
}
constructor(
private readonly _displayName: string | null | undefined,
private readonly _reason: ExtensionActivationReason,
private readonly _deps: ActivationOperation[],
private _value: ActivatedExtension | null,
private readonly _host: IExtensionsActivatorHost,
@ILogService private readonly _logService: ILogService
) {
this._initialize();
}
public dispose(): void {
}
public wait() {
}
private async _initialize(): Promise<void> {
this._barrier.open();
}
private async _waitForDepsThenActivate(): Promise<void> {
return;
}
while (this._deps.length > 0) {
for (let i = 0; i < this._deps.length; i++) {
const dep = this._deps[i];
if (dep.value && !dep.value.activationFailed) {
this._deps.splice(i, 1);
i--;
continue;
}
if (dep.value && dep.value.activationFailed) {
// Error condition 2: a dependency has already failed activation
extHostExtensionActivator.ts ×5
const error = new Error(`Cannot activate the '${this.friendlyName}' extension because its dependency '${dep.friendlyName}' failed to activate`);
// eslint-disable-next-line local/code-no-any-casts
(<any>error).detail = dep.value.activationFailedError;
this._value = new FailedExtension(error);
this._host.onExtensionActivationError(this._id, error, null);
return;
}
if (this._deps.length > 0) {
// wait for one dependency
await Promise.race(this._deps.map(dep => dep.wait()));
}
}
await this._activate();
private async _activate(): Promise<void> {
this._value = await this._host.actualActivateExtension(this._id, this._reason);
} catch (err) {
const error = new Error();
if (err && err.name) {
error.name = err.name;
}
if (err && err.message) {
error.message = `Activating extension '${this._id.value}' failed: ${err.message}.`;
} else {
error.message = `Activating extension '${this._id.value}' failed: ${err}.`;
}
error.stack = err.stack;
}
// Treat the extension as being empty
this._value = new FailedExtension(error);
if (this._isDisposed && errors.isCancellationError(err)) {
// It is expected for ongoing activations to fail if the extension host is going down
// So simply ignore and don't log canceled errors in this case
return;
}
this._host.onExtensionActivationError(this._id, error, null);
this._logService.error(`Activating extension ${this._id.value} failed due to an error:`);
this._logService.error(err);
}