vscode 插件加载原理
多进程架构详解
1、主进程(Main),一个 Electron 应用只有一个主进程。创建 GUI 相关的接口只由主进程来调用。
2、渲染进程(Renderer),每一个工作区(workbench)对应一个进程,同时是BrowserWindow实例。一个Electron项目可以有多个渲染进程。
3、插件进程(Extension),fork了渲染进程,每个插件都运行在一个NodeJS宿主环境中,即插件间共享进程。VSCode规定,插件不允许直接访问UI,这和Atom不同。
4、Debug进程,一个特殊的插件进程。
5、Search进程,搜索是密集型任务,单独占用一个进程。
6、进程之间通过IPC、RPC进行通信,这个后面会介绍。
7、LSP和DAP像两座桥梁,连接起语言和调试服务,它们都运行在插件进程中。
因为VSCode基于Electron,Electron基于chromium,所以进程和浏览器架构十分相似。
进程间通信
IPC
electron.ipcRenderer.send(
"sendMessageFromRendererProcesses",
"渲染进程向主进程发送异步消息"
);
electron.ipcMain.on("sendMessageFromRendererProcesses", (event, message) => {
event.sender.send("sendMessageFromMainProcesses", "回应异步消息:" + message);
});
VSCode的IPC通信是基于Electron,进程间可以双向通信,并且支持同步异步通信。
RPC
const { BrowserWindow } = require("electron").remote;
let win = new BrowserWindow({ width: 800, height: 600 });
win.loadURL("https://github.com");
这里是渲染进程直接调用Electron的远程模块,重新初始化一个界面BrowserWindow,并且打开一个页面,地址为https://github.com。RPC一般用于单向调用,如渲染进程调用主进程。
小结
1、多进程架构,实现了视图与逻辑分离。
2、基于接口编程(LSP、DAP),规范了扩展功能。
3、插件进程,单独开启一个进程。不影响启动速度,不影响主进程和渲染进程,不能直接改变UI样式。缺点,UI可扩展性差,优点,带来了统一的视觉效果和交互风格。
最简单的插件
插件目录
helloworld-sample
├─.vscode
│ ├─launch.json
│ └─tasks.json
├─src
│ └─extension.ts
├─.eslintrc.js
├─package-lock.json
├─package.json
├─README.md
└─tsconfig.json
package.json
有两个关键字,engines
指VSCode兼容版本,activationEvents
表示触发事件。onLanguage
为语言为 java
时,输入命令 onCommand:java.show.references
(通过 cmd + p
可进入输入命令界面),或者工作区中包含 pom.xml
文件,这些都会加载插件。插件的加载机制是懒加载,只有触发了指定事件才会加载。
{
"engines": {
"vscode": "^1.47.0"
},
"contributes": {
"commands": [
{
"command": "extension.helloWorld",
"title": "Hello World"
}
]
},
"activationEvents": [
"onLanguage:java",
"onCommand:java.show.references",
"onCommand:java.show.implementations",
"onCommand:java.open.output",
"onCommand:java.open.serverLog",
"onCommand:java.execute.workspaceCommand",
"onCommand:java.projectConfiguration.update",
"workspaceContains:pom.xml",
"workspaceContains:build.gradle"
]
}
extension.ts
extension.ts
里导出一个 activate
函数,表示当插件被激活时执行函数内的内容。Demo里注册了一个命令到VSCode的context上下文中,且当执行hellworld这个命令时,会弹出一个提示语,我们将提示语由Hello World 改为了 Hello VS Code。
VSCode能自动实现插件项目,我们按 F5
即可进入调试模式,下面是一个输出提示语的视频。
// src/extension.ts
const vscode = require('vscode');
/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
console.log('Congratulations, your extension "helloworld-minimal-sample" is now active!');
let disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
vscode.window.showInformationMessage('Hello World!');
});
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
function deactivate() {}
// eslint-disable-next-line no-undef
module.exports = {
activate,
deactivate
}
extension load steps
源码组织
workbench.desktop.main.ts before
- main.ts -> ... -> workbench.desktop.main.ts 详见
workbench.desktop.main.ts
src/vs/workbench/workbench.desktop.main.ts
import './services/extensions/electron-sandbox/nativeExtensionService.js'
nativeExtensionService.ts
vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts
- NativeExtensionService -> NativeExtensionService -> extensionHostFactory -> super()
- NativeExtensionService -> this._initialize()
abstractExtensionService.ts
vs/workbench/services/extensions/common/abstractExtensionService.ts
- AbstractExtensionService -> _initialize() -> this._startExtensionHostsIfNecessary() -> extHostManager.start() -> ExtensionHostManager/LazyCreateExtensionHostManager
- this._startExtensionHostsIfNecessary() ->this._createExtensionHostManager()
- this._extensionHostFactory.createExtensionHost()
- this._doCreateExtensionHostManager()
- createInstance() -> ExtensionHostManager
- createInstance() -> LazyCreateExtensionHostManager
- this._startExtensionHostsIfNecessary() ->this._createExtensionHostManager()
- AbstractExtensionService -> constructor()
- 1
- AbstractExtensionService -> _initialize() -> this._startExtensionHostsIfNecessary() -> extHostManager.start() -> ExtensionHostManager/LazyCreateExtensionHostManager
nativeExtensionService.ts
vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts
- this._extensionHostFactory.createExtensionHost()
- createInstance() -> NativeLocalProcessExtensionHost
- createInstance() -> WebWorkerExtensionHost
- createInstance() -> RemoteExtensionHost
- this._extensionHostFactory.createExtensionHost()
extensionHostManager.ts
vs/workbench/services/extensions/common/extensionHostManager.ts
- ExtensionHostManager
- this._proxy = this._extensionHost.start().then(this._createExtensionHostCustomers())
- start() -> this._proxy.startExtensionHost()
- ExtensionHostManager
lazyCreateExtensionHostManager.ts
vs/workbench/services/extensions/common/lazyCreateExtensionHostManager.ts
- LazyCreateExtensionHostManager
- start() -> _start() -> _establishProtocol()
- LazyCreateExtensionHostManager
localProcessExtensionHost.ts
vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts
- NativeLocalProcessExtensionHost -> start() -> _start() -> _establishProtocol()
webWorkerExtensionHost.ts
vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts
- WebWorkerExtensionHost -> start() -> _startInsideIframe() -> workerUrl ->
vs/workbench/api/worker/extensionHostWorkerMain.js
-> create() ->vs/workbench/api/worker/extensionHostWorker.ts
-> ExtensionHostMain ->vs/workbench/api/common/extensionHostMain.ts
- WebWorkerExtensionHost -> start() -> _startInsideIframe() -> workerUrl ->
remoteExtensionHost.ts
vs/workbench/services/extensions/common/remoteExtensionHost.ts
- RemoteExtensionHost -> start() -> connectRemoteAgentExtensionHost()
源码链路(精简)
workbench.desktop.main.ts before
- main.ts -> ... -> workbench.desktop.main.ts 详见
workbench.desktop.main.ts
src/vs/workbench/workbench.desktop.main.ts
//#region --- workbench services
import './services/extensionManagement/electron-sandbox/extensionManagementService.js';
import './services/extensions/electron-sandbox/extensionHostStarter.js';
import '../platform/extensionResourceLoader/common/extensionResourceLoaderService.js';
import './services/extensions/electron-sandbox/extensionsScannerService.js';
import './services/extensionManagement/electron-sandbox/extensionManagementServerService.js';
import './services/extensionManagement/electron-sandbox/extensionTipsService.js';
import './services/extensions/electron-sandbox/nativeExtensionService.js';
import '../platform/extensionManagement/electron-sandbox/extensionsProfileScannerService.js';
//#endregion
//#region --- workbench contributions
// Debug
import './contrib/debug/electron-sandbox/extensionHostDebugService.js';
// Extensions Management
import './contrib/extensions/electron-sandbox/extensions.contribution.js';
//#endregion
nativeExtensionService.ts
// vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts
export class NativeExtensionService extends AbstractExtensionService implements IExtensionService {
private readonly _extensionScanner: CachedExtensionScanner;
private readonly _localCrashTracker = new ExtensionHostCrashTracker();
constructor(
// ...
) {
const extensionsProposedApi = instantiationService.createInstance(ExtensionsProposedApi);
const extensionScanner = instantiationService.createInstance(CachedExtensionScanner);
const extensionHostFactory = new NativeExtensionHostFactory(
extensionsProposedApi,
extensionScanner,
// ...
);
super(
extensionsProposedApi,
extensionHostFactory,
new NativeExtensionHostKindPicker(environmentService, configurationService, logService),
instantiationService,
// ...
);
this._extensionScanner = extensionScanner;
// delay extension host creation and extension scanning
// until the workbench is running. we cannot defer the
// extension host more (LifecyclePhase.Restored) because
// some editors require the extension host to restore
// and this would result in a deadlock
// see https://github.com/microsoft/vscode/issues/41322
lifecycleService.when(LifecyclePhase.Ready).then(() => {
// reschedule to ensure this runs after restoring viewlets, panels, and editors
runWhenWindowIdle(mainWindow, () => {
this._initialize();
}, 50 /*max delay*/);
});
}
}
class NativeExtensionHostFactory implements IExtensionHostFactory {
constructor(
private readonly _extensionsProposedApi: ExtensionsProposedApi,
private readonly _extensionScanner: CachedExtensionScanner,
// ...
) {
this._webWorkerExtHostEnablement = determineLocalWebWorkerExtHostEnablement(environmentService, configurationService);
}
public createExtensionHost(runningLocations: ExtensionRunningLocationTracker, runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null {
switch (runningLocation.kind) {
case ExtensionHostKind.LocalProcess: {
const startup = (
isInitialStart
? ExtensionHostStartup.EagerManualStart
: ExtensionHostStartup.EagerAutoStart
);
return this._instantiationService.createInstance(NativeLocalProcessExtensionHost, runningLocation, startup, this._createLocalProcessExtensionHostDataProvider(runningLocations, isInitialStart, runningLocation));
}
case ExtensionHostKind.LocalWebWorker: {
if (this._webWorkerExtHostEnablement !== LocalWebWorkerExtHostEnablement.Disabled) {
const startup = (
isInitialStart
? (this._webWorkerExtHostEnablement === LocalWebWorkerExtHostEnablement.Lazy ? ExtensionHostStartup.Lazy : ExtensionHostStartup.EagerManualStart)
: ExtensionHostStartup.EagerAutoStart
);
return this._instantiationService.createInstance(WebWorkerExtensionHost, runningLocation, startup, this._createWebWorkerExtensionHostDataProvider(runningLocations, runningLocation));
}
return null;
}
case ExtensionHostKind.Remote: {
const remoteAgentConnection = this._remoteAgentService.getConnection();
if (remoteAgentConnection) {
return this._instantiationService.createInstance(RemoteExtensionHost, runningLocation, this._createRemoteExtensionHostDataProvider(runningLocations, remoteAgentConnection.remoteAuthority));
}
return null;
}
}
}
}
abstractExtensionService.ts
// vs/workbench/services/extensions\common\abstractExtensionService.ts
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
public _serviceBrand: undefined;
private readonly _onDidRegisterExtensions = this._register(new Emitter<void>());
public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event;
private readonly _onDidChangeExtensionsStatus = this._register(new Emitter<ExtensionIdentifier[]>());
public readonly onDidChangeExtensionsStatus = this._onDidChangeExtensionsStatus.event;
private readonly _onDidChangeExtensions = this._register(new Emitter<{ readonly added: ReadonlyArray<IExtensionDescription>; readonly removed: ReadonlyArray<IExtensionDescription> }>({ leakWarningThreshold: 400 }));
public readonly onDidChangeExtensions = this._onDidChangeExtensions.event;
private readonly _onWillActivateByEvent = this._register(new Emitter<IWillActivateEvent>());
public readonly onWillActivateByEvent = this._onWillActivateByEvent.event;
private readonly _onDidChangeResponsiveChange = this._register(new Emitter<IResponsiveStateChangeEvent>());
public readonly onDidChangeResponsiveChange = this._onDidChangeResponsiveChange.event;
private readonly _onWillStop = this._register(new Emitter<WillStopExtensionHostsEvent>());
public readonly onWillStop = this._onWillStop.event;
private readonly _activationEventReader = new ImplicitActivationAwareReader();
private readonly _registry = new LockableExtensionDescriptionRegistry(this._activationEventReader);
private readonly _installedExtensionsReady = new Barrier();
private readonly _extensionStatus = new ExtensionIdentifierMap<ExtensionStatus>();
private readonly _allRequestedActivateEvents = new Set<string>();
private readonly _runningLocations: ExtensionRunningLocationTracker;
private readonly _remoteCrashTracker = new ExtensionHostCrashTracker();
private _deltaExtensionsQueue: DeltaExtensionsQueueItem[] = [];
private _inHandleDeltaExtensions = false;
private readonly _extensionHostManagers = this._register(new ExtensionHostCollection());
private readonly _extensionHostManagers = this._register(new ExtensionHostCollection());
private _resolveAuthorityAttempt: number = 0;
constructor(
private readonly _extensionsProposedApi: ExtensionsProposedApi,
private readonly _extensionHostFactory: IExtensionHostFactory,
private readonly _extensionHostKindPicker: IExtensionHostKindPicker,
// ...
) {
super();
// help the file service to activate providers by activating extensions by file system event
this._register(this._fileService.onWillActivateFileSystemProvider(e => {
if (e.scheme !== Schemas.vscodeRemote) {
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
}
}));
this._runningLocations = new ExtensionRunningLocationTracker(
this._registry,
this._extensionHostKindPicker,
this._environmentService,
this._configurationService,
this._logService,
this._extensionManifestPropertiesService
);
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
// ...
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove));
}));
this._register(this._extensionManagementService.onDidChangeProfile(({ added, removed }) => {
// ...
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(added, removed));
}));
this._register(this._extensionManagementService.onDidEnableExtensions(extensions => {
// ...
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(extensions, []));
}));
this._register(this._extensionManagementService.onDidInstallExtensions((result) => {
// ...
this._handleDeltaExtensions(new DeltaExtensionsQueueItem(extensions, []));
}));
this._register(this._extensionManagementService.onDidUninstallExtension((event) => {
// ...
this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id]));
}));
this._register(this._lifecycleService.onWillShutdown(event => {
// ...
}));
}
protected async _initialize(): Promise<void> {
this._startExtensionHostsIfNecessary(true, []);
for (const extHostManager of this._extensionHostManagers) {
if (extHostManager.startup !== ExtensionHostStartup.EagerAutoStart) {
const extensions = this._runningLocations.filterByExtensionHostManager(snapshot.extensions, extHostManager);
extHostManager.start(snapshot.versionId, snapshot.extensions, extensions.map(extension => extension.identifier));
}
}
}
private _startExtensionHostsIfNecessary(isInitialStart: boolean, initialActivationEvents: string[]): void {
const locations: ExtensionRunningLocation[] = [];
for (let affinity = 0; affinity <= this._runningLocations.maxLocalProcessAffinity; affinity++) {
locations.push(new LocalProcessRunningLocation(affinity));
}
for (let affinity = 0; affinity <= this._runningLocations.maxLocalWebWorkerAffinity; affinity++) {
locations.push(new LocalWebWorkerRunningLocation(affinity));
}
locations.push(new RemoteRunningLocation());
for (const location of locations) {
if (this._extensionHostManagers.getByRunningLocation(location)) {
// already running
continue;
}
const res = this._createExtensionHostManager(location, isInitialStart, initialActivationEvents);
if (res) {
const [extHostManager, disposableStore] = res;
this._extensionHostManagers.add(extHostManager, disposableStore);
}
}
}
private _createExtensionHostManager(runningLocation: ExtensionRunningLocation, isInitialStart: boolean, initialActivationEvents: string[]): null | [IExtensionHostManager, DisposableStore] {
const extensionHost = this._extensionHostFactory.createExtensionHost(this._runningLocations, runningLocation, isInitialStart);
if (!extensionHost) {
return null;
}
const processManager: IExtensionHostManager = this._doCreateExtensionHostManager(extensionHost, initialActivationEvents);
const disposableStore = new DisposableStore();
// ...
return [processManager, disposableStore];
}
protected _doCreateExtensionHostManager(extensionHost: IExtensionHost, initialActivationEvents: string[]): IExtensionHostManager {
const internalExtensionService = this._acquireInternalAPI(extensionHost);
if (extensionHost.startup === ExtensionHostStartup.Lazy && initialActivationEvents.length === 0) {
return this._instantiationService.createInstance(LazyCreateExtensionHostManager, extensionHost, internalExtensionService);
}
return this._instantiationService.createInstance(ExtensionHostManager, extensionHost, initialActivationEvents, internalExtensionService);
}
//#region IExtensionService
public activateByEvent(activationEvent: string, activationKind: ActivationKind = ActivationKind.Normal): Promise<void> {
if (this._installedExtensionsReady.isOpen()) {
// Extensions have been scanned and interpreted
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents.add(activationEvent);
if (!this._registry.containsActivationEvent(activationEvent)) {
// There is no extension that is interested in this activation event
return NO_OP_VOID_PROMISE;
}
return this._activateByEvent(activationEvent, activationKind);
} else {
// Extensions have not been scanned yet.
// Record the fact that this activationEvent was requested (in case of a restart)
this._allRequestedActivateEvents.add(activationEvent);
if (activationKind === ActivationKind.Immediate) {
// Do not wait for the normal start-up of the extension host(s)
return this._activateByEvent(activationEvent, activationKind);
}
return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent, activationKind));
}
}
private _activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
const result = Promise.all(
this._extensionHostManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent, activationKind))
).then(() => { });
this._onWillActivateByEvent.fire({
event: activationEvent,
activation: result
});
return result;
}
public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
return this._activateById(extensionId, reason);
}
public activationEventIsDone(activationEvent: string): boolean {
if (!this._installedExtensionsReady.isOpen()) {
return false;
}
if (!this._registry.containsActivationEvent(activationEvent)) {
// There is no extension that is interested in this activation event
return true;
}
return this._extensionHostManagers.every(manager => manager.activationEventIsDone(activationEvent));
}
public whenInstalledExtensionsRegistered(): Promise<boolean> {
return this._installedExtensionsReady.wait();
}
get extensions(): IExtensionDescription[] {
return this._registry.getAllExtensionDescriptions();
}
protected _getExtensionRegistrySnapshotWhenReady(): Promise<ExtensionDescriptionRegistrySnapshot> {
return this._installedExtensionsReady.wait().then(() => this._registry.getSnapshot());
}
public getExtension(id: string): Promise<IExtensionDescription | undefined> {
return this._installedExtensionsReady.wait().then(() => {
return this._registry.getExtensionDescription(id);
});
}
public readExtensionPointContributions<T extends IExtensionContributions[keyof IExtensionContributions]>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> {
return this._installedExtensionsReady.wait().then(() => {
const availableExtensions = this._registry.getAllExtensionDescriptions();
const result: ExtensionPointContribution<T>[] = [];
for (const desc of availableExtensions) {
if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) {
result.push(new ExtensionPointContribution<T>(desc, desc.contributes[extPoint.name as keyof typeof desc.contributes] as T));
}
}
return result;
});
}
public getExtensionsStatus(): { [id: string]: IExtensionsStatus } {
const result: { [id: string]: IExtensionsStatus } = Object.create(null);
if (this._registry) {
const extensions = this._registry.getAllExtensionDescriptions();
for (const extension of extensions) {
const extensionStatus = this._extensionStatus.get(extension.identifier);
result[extension.identifier.value] = {
id: extension.identifier,
messages: extensionStatus?.messages ?? [],
activationStarted: extensionStatus?.activationStarted ?? false,
activationTimes: extensionStatus?.activationTimes ?? undefined,
runtimeErrors: extensionStatus?.runtimeErrors ?? [],
runningLocation: this._runningLocations.getRunningLocation(extension.identifier),
};
}
}
return result;
}
public async getInspectPorts(extensionHostKind: ExtensionHostKind, tryEnableInspector: boolean): Promise<{ port: number; host: string }[]> {
const result = await Promise.all(
this._getExtensionHostManagers(extensionHostKind).map(extHost => extHost.getInspectPort(tryEnableInspector))
);
// remove 0s:
return result.filter(isDefined);
}
public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void> {
await this._extensionHostManagers
.map(manager => manager.setRemoteEnvironment(env));
}
//#endregion
}
extensionHostManager.ts
// vs\workbench\services\extensions\common\extensionHostManager.ts
export class ExtensionHostManager extends Disposable implements IExtensionHostManager {
constructor(
extensionHost: IExtensionHost,
// ...
) {
super();
this._extensionHost = extensionHost;
// ...
this._proxy = this._extensionHost.start().then(
// ...
return this._createExtensionHostCustomers(this.kind, protocol);
);
this._proxy.then(() => {
initialActivationEvents.forEach((activationEvent) => this.activateByEvent(activationEvent, ActivationKind.Normal));
this._register(registerLatencyTestProvider({
measure: () => this.measure()
}));
});
}
public get startup(): ExtensionHostStartup {
return this._extensionHost.startup;
}
public async start(extensionRegistryVersionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): Promise<void> {
const proxy = await this._proxy;
if (!proxy) {
return;
}
const deltaExtensions = this._extensionHost.extensions!.set(extensionRegistryVersionId, allExtensions, myExtensions);
return proxy.startExtensionHost(deltaExtensions);
}
public async activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean> {
const proxy = await this._proxy;
if (!proxy) {
return false;
}
return proxy.activate(extension, reason);
}
public activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
if (activationKind === ActivationKind.Immediate && !this._hasStarted) {
return Promise.resolve();
}
if (!this._cachedActivationEvents.has(activationEvent)) {
this._cachedActivationEvents.set(activationEvent, this._activateByEvent(activationEvent, activationKind));
}
return this._cachedActivationEvents.get(activationEvent)!;
}
public activationEventIsDone(activationEvent: string): boolean {
return this._resolvedActivationEvents.has(activationEvent);
}
private async _activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
if (!this._proxy) {
return;
}
const proxy = await this._proxy;
if (!proxy) {
// this case is already covered above and logged.
// i.e. the extension host could not be started
return;
}
if (!this._extensionHost.extensions!.containsActivationEvent(activationEvent)) {
this._resolvedActivationEvents.add(activationEvent);
return;
}
await proxy.activateByEvent(activationEvent, activationKind);
this._resolvedActivationEvents.add(activationEvent);
}
}
lazyCreateExtensionHostManager.ts
// LazyCreateExtensionHostManager
export class LazyCreateExtensionHostManager extends Disposable implements IExtensionHostManager {
constructor(
extensionHost: IExtensionHost,
// ...
) {
super();
this._extensionHost = extensionHost;
// ...
}
public get startup(): ExtensionHostStartup {
return this._extensionHost.startup;
}
public start(): Promise<IMessagePassingProtocol> {
if (this._terminating) {
// .terminate() was called
throw new CancellationError();
}
if (!this._messageProtocol) {
this._messageProtocol = this._start();
}
return this._messageProtocol;
}
private async _start(): Promise<IMessagePassingProtocol> {
const [extensionHostCreationResult, portNumber, processEnv] = await Promise.all([
this._extensionHostStarter.createExtensionHost(),
this._tryFindDebugPort(),
this._shellEnvironmentService.getShellEnv(),
]);
this._extensionHostProcess = new ExtensionHostProcess(extensionHostCreationResult.id, this._extensionHostStarter);
const env = objects.mixin(processEnv, {
VSCODE_ESM_ENTRYPOINT: 'vs/workbench/api/node/extensionHostProcess',
VSCODE_HANDLES_UNCAUGHT_ERRORS: true
});
if (this._environmentService.debugExtensionHost.env) {
objects.mixin(env, this._environmentService.debugExtensionHost.env);
}
const opts: IExtensionHostProcessOptions = {
responseWindowId: this._nativeHostService.windowId,
responseChannel: 'vscode:startExtensionHostMessagePortResult',
responseNonce: generateUuid(),
env,
// We only detach the extension host on windows. Linux and Mac orphan by default
// and detach under Linux and Mac create another process group.
// We detach because we have noticed that when the renderer exits, its child processes
// (i.e. extension host) are taken down in a brutal fashion by the OS
detached: !!platform.isWindows,
execArgv: undefined as string[] | undefined,
silent: true
};
// ...
// Initialize extension host process with hand shakes
const protocol = await this._establishProtocol(this._extensionHostProcess, opts);
await this._performHandshake(protocol);
clearTimeout(startupTimeoutHandle);
return protocol;
}
private _establishProtocol(extensionHostProcess: ExtensionHostProcess, opts: IExtensionHostProcessOptions): Promise<IMessagePassingProtocol> {
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
const handle = setTimeout(() => {
reject('The local extension host took longer than 60s to connect.');
}, 60 * 1000);
// Now that the message port listener is installed, start the ext host process
const sw = StopWatch.create(false);
extensionHostProcess.start(opts).then(({ pid }) => {
// ...
}, (err) => {
// ...
});
});
}
}
localProcessExtensionHost.ts
// vs\workbench\services\extensions\electron-sandbox\localProcessExtensionHost.ts
export class NativeLocalProcessExtensionHost implements IExtensionHost {
constructor(
public readonly runningLocation: LocalProcessRunningLocation,
public readonly startup: ExtensionHostStartup.EagerAutoStart | ExtensionHostStartup.EagerManualStart,
private readonly _initDataProvider: ILocalProcessExtensionHostDataProvider,
// ...
) {
// ...
}
public start(): Promise<IMessagePassingProtocol> {
if (this._terminating) {
// .terminate() was called
throw new CancellationError();
}
if (!this._messageProtocol) {
this._messageProtocol = this._start();
}
return this._messageProtocol;
}
private async _start(): Promise<IMessagePassingProtocol> {
const [extensionHostCreationResult, portNumber, processEnv] = await Promise.all([
this._extensionHostStarter.createExtensionHost(),
this._tryFindDebugPort(),
this._shellEnvironmentService.getShellEnv(),
]);
this._extensionHostProcess = new ExtensionHostProcess(extensionHostCreationResult.id, this._extensionHostStarter);
const env = objects.mixin(processEnv, {
VSCODE_ESM_ENTRYPOINT: 'vs/workbench/api/node/extensionHostProcess',
VSCODE_HANDLES_UNCAUGHT_ERRORS: true
});
const opts: IExtensionHostProcessOptions = {
env,
// ...
};
// Initialize extension host process with hand shakes
const protocol = await this._establishProtocol(this._extensionHostProcess, opts);
return protocol;
}
}
export class ExtensionHostProcess {
constructor(
id: string,
private readonly _extensionHostStarter: IExtensionHostStarter,
) {
this._id = id;
}
public start(opts: IExtensionHostProcessOptions): Promise<{ pid: number | undefined }> {
return this._extensionHostStarter.start(this._id, opts);
}
}
webWorkerExtensionHost.ts
// vs\workbench\services\extensions\browser\webWorkerExtensionHost.ts
export class WebWorkerExtensionHost extends Disposable implements IExtensionHost {
constructor(
public readonly runningLocation: LocalWebWorkerRunningLocation,
public readonly startup: ExtensionHostStartup,
private readonly _initDataProvider: IWebWorkerExtensionHostDataProvider,
// ...
) {
// ...
}
public async start(): Promise<IMessagePassingProtocol> {
if (!this._protocolPromise) {
this._protocolPromise = this._startInsideIframe();
this._protocolPromise.then(protocol => this._protocol = protocol);
}
return this._protocolPromise;
}
private async _startInsideIframe(): Promise<IMessagePassingProtocol> {
const webWorkerExtensionHostIframeSrc = await this._getWebWorkerExtensionHostIframeSrc();
const emitter = this._register(new Emitter<VSBuffer>());
const iframe = document.createElement('iframe');
iframe.setAttribute('class', 'web-worker-ext-host-iframe');
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
iframe.setAttribute('allow', 'usb; serial; hid; cross-origin-isolated;');
iframe.setAttribute('aria-hidden', 'true');
iframe.style.display = 'none';
const vscodeWebWorkerExtHostId = generateUuid();
iframe.setAttribute('src', `${webWorkerExtensionHostIframeSrc}&vscodeWebWorkerExtHostId=${vscodeWebWorkerExtHostId}`);
this._register(dom.addDisposableListener(mainWindow, 'message', (event) => {
if (event.data.type === 'vscode.bootstrap.nls') {
iframe.contentWindow!.postMessage({
type: event.data.type,
data: {
workerUrl: FileAccess.asBrowserUri('vs/workbench/api/worker/extensionHostWorkerMain.js').toString(true),
fileRoot: globalThis._VSCODE_FILE_ROOT,
nls: {
messages: getNLSMessages(),
language: getNLSLanguage()
}
}
}, '*');
return;
}
}));
this._layoutService.mainContainer.appendChild(iframe);
this._register(toDisposable(() => iframe.remove()));
// Send over message ports for extension API
const messagePorts = this._environmentService.options?.messagePorts ?? new Map();
iframe.contentWindow!.postMessage({ type: 'vscode.init', data: messagePorts }, '*', [...messagePorts.values()]);
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
port.postMessage(data, [data]);
}
};
return this._performHandshake(protocol);
}
}
remoteExtensionHost.ts
// vs\workbench\services\extensions\common\remoteExtensionHost.ts
export class RemoteExtensionHost extends Disposable implements IExtensionHost {
constructor(
public readonly runningLocation: RemoteRunningLocation,
private readonly _initDataProvider: IRemoteExtensionHostDataProvider,
// ...
) {
// ...
}
public start(): Promise<IMessagePassingProtocol> {
const options: IConnectionOptions = {
remoteSocketFactoryService: this.remoteSocketFactoryService,
// ...
};
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolverResult) => {
// ...
return connectRemoteAgentExtensionHost(options, startParams).then(result => {
// ...
});
});
}
}