:::info ℹ️ 这里的主要类关系图我们使用 Electron-Browser 来作为示例。 :::

核心类和关系图

VSCode 插件扩展实现分析 - 图1

主要执行时序

EelectronBrwoser

特点 补充

- 在启动时序分析一节中,我们看到了 workbench.html 初始化之后会加载 ./static/out/vs/code/browser/workbench/workbench.js
- 在 workbench.js 中会加载 vs/workbench/workbench.desktop.main 中的服务,其中和插件相关的部分便是由下面这两个文件作为入口实现
- extensionService
- extensions.contribution
```typescript

// Extension Service import ‘vs/workbench/services/extensions/electron-browser/extensionService’; // Extensions Management import ‘vs/workbench/contrib/extensions/electron-browser/extensions.contribution’;

  1. 值得一提的是:`workbench.web` `workbench.desktop` 所加载的 `extensionService` 不同,其差异点主要在 desktop 使用的服务是基于 `electron-browser` 提供的,而 web 提供的服务自然是由 `browser` 提供的。<br />因此,这里主要讨论 `electron-browser` 的架构设计(Electron RenderProcess based)。<br />至于 `extension.contribution` 主要是完成对扩展协议的注册(一大堆内置的 contribution configurations 加载和注册) |
  2. | `workbench/services/extensions/electron-browser/extensionService.ts` <br /><br />- 在其抽象父类中的 `_initialize()` 函数中执行了如下操作:<br /> - `_startExtensionHosts`<br /> - `LocalWorker` 相关初始化<br /> - 监听 LifeCycle,当 LifeCyclePhase Ready 的时候,异步启动 `ExtensionHost`,创建 `extensionHosts` 实例数组包括:<br /> - 本地 Local ExtensionHost<br /> - 本地 Worker ExtensionHost<br /> - 远端 Remote ExtensionHost<br /> - 批量根据 `extensionHost` 创建 `ExtensionHostManager`<br /> - `ExtensionHostManager` 创建后返回 `ExtenionHost` 实例,用于创建扩展的运行时进程(process)<br /> - `_scanAndHandleExtensions` 扫描和初始化扩展<br /> - 处理 RemoteExtension 启用机制(验权等操作)<br /> - 调用 await `_startLocalExtensionHost` 启动本地 ExtensionHost<br /> - 调用 `extensionHost.start()` 方法启动本地 ExtensionHost<br />- 进入 `extensionHostManager.ts` 开始初始化 extensions 执行<br /><br /><br />P.S.:::info
  3. - `localExtHost` A Node.js extension host running locally, on the same machine as the user interface.
  4. - `webExtHost` A web extension host running in the browser or locally, on the same machine as the user interface.
  5. - `remoteExtHost` A Node.js extension host running remotely in a container or a remote location.
  6. :::
  7. | ```typescript
  8. [this._enableLocalWebWorker, this._lazyLocalWebWorker] = this._isLocalWebWorkerEnabled();
  9. this._remoteInitData = new Map<string, IRemoteExtensionHostInitData>();
  10. this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner);
  11. // delay extension host creation and extension scanning
  12. // until the workbench is running. we cannot defer the
  13. // extension host more (LifecyclePhase.Restored) because
  14. // some editors require the extension host to restore
  15. // and this would result in a deadlock
  16. // see https://github.com/microsoft/vscode/issues/41322
  17. this._lifecycleService.when(LifecyclePhase.Ready).then(() => {
  18. // reschedule to ensure this runs after restoring viewlets, panels, and editors
  19. runWhenIdle(() => {
  20. this._initialize();
  21. }, 50 /*max delay*/);
  22. });
// 关键函数 initialize
protected async _initialize(): Promise<void> {
  this._startExtensionHosts(true, []);
  // 提供锁和保护机制
  const lock = await this._registryLock.acquire('_initialize');
  try {
    await this._scanAndHandleExtensions();
  } finally {
    lock.dispose();
  }
  this._releaseBarrier();
}

// ExtensionHost Initializer
private _startExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): void {
  const extensionHosts = this._createExtensionHosts(isInitialStart);
  extensionHosts.forEach((extensionHost) => {
    const processManager: IExtensionHostManager = createExtensionHostManager(this._instantiationService, extensionHost, isInitialStart, initialActivationEvents);
    // ...
    this._extensionHostManagers.push(processManager);
  });
}


protected _createExtensionHosts(isInitialStart: boolean): IExtensionHost[] {
  const result: IExtensionHost[] = [];
  // 本地型
  const localProcessExtHost = this._instantiationService.createInstance(LocalProcessExtensionHost, this._createLocalExtensionHostDataProvider(isInitialStart, ExtensionRunningLocation.LocalProcess));
  result.push(localProcessExtHost);
    // Worker 型
  if (this._enableLocalWebWorker) {
    const webWorkerExtHost = this._instantiationService.createInstance(WebWorkerExtensionHost, this._lazyLocalWebWorker, this._createLocalExtensionHostDataProvider(isInitialStart, ExtensionRunningLocation.LocalWebWorker));
    result.push(webWorkerExtHost);
  }
  // 远程型
  const remoteAgentConnection = this._remoteAgentService.getConnection();
  if (remoteAgentConnection) {
    const remoteExtHost = this._instantiationService.createInstance(RemoteExtensionHost, this._createRemoteExtensionHostDataProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
    result.push(remoteExtHost);
  }
  return result;
}


private async _startLocalExtensionHost(remoteAuthority: string | undefined = undefined, remoteEnv: IRemoteAgentEnvironment | null = null, remoteExtensions: IExtensionDescription[] = []): Promise<void> {
  remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions, false);
  const localExtensions = this._checkEnabledAndProposedAPI(await this._scanAllLocalExtensions(), false);
  this._runningLocation = this._runningLocationClassifier.determineRunningLocation(localExtensions, remoteExtensions);

  // remove non-UI extensions from the local extensions
  const localProcessExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalProcess);
  const localWebWorkerExtensions = filterByRunningLocation(localExtensions, this._runningLocation, ExtensionRunningLocation.LocalWebWorker);
  remoteExtensions = filterByRunningLocation(remoteExtensions, this._runningLocation, ExtensionRunningLocation.Remote);

    // 处理扩展注册的 Registry
  this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions());

    // 关键启动入口 LocalThread
  const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess);
  if (localProcessExtensionHost) {
    localProcessExtensionHost.start(localProcessExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
  }
  // 关键启动入口 WebWorker
  const localWebWorkerExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalWebWorker);
  if (localWebWorkerExtensionHost) {
    localWebWorkerExtensionHost.start(localWebWorkerExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
  }
}

| | workbench/services/extensions/electron-browser/localProcessExtensionHost.ts

以启用本地进程的 LocalProcessExtensionHost 为例,其关键的 start 函数做了下面的事情:

- 创建一个和 ExtensionHostStarter 通信的管理对象
- 配置进程启动的环境变量(要加载的入口脚本)
- 配置 child_process.fork() 方法需要的参数,这里做了跨平台的调优和兼容
- 优化了部分 process 相关的 I/O 和日志操作
- 调用 HostHandshake 启动进程


继续解析 _tryExtHostHandshake 函数对进程创建的步程:

- 调用之前创建好的 _extensionHostProcess 执行其 start() 方法创建进程
| ```typescript // LocalProcessExtensionHost.prototype.start() if (!this._messageProtocol) { this._messageProtocol = Promise.all([ // ]).then(([extensionHostCreationResult, pipeName, portNumber, processEnv]) => { // 创建一个用于通信的 HostProcess 对象 this._extensionHostProcess = new ExtensionHostProcess( extensionHostCreationResult.id, this._extensionHostStarter );

// 配置环境变量
const env = objects.mixin(processEnv, {
  // 执行入口,最关键
  VSCODE_AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',
  VSCODE_PIPE_LOGGING: 'true',
  // 一堆其它环境变量
});

// 装配 child_process.fork 需要的参数
const opts = {
  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
};

// ... 一堆装配代码 ...
// Catch all output coming from the extension host process
type Output = { data: string, format: string[] };
const onStdout = this._handleProcessOutputStream(this._extensionHostProcess.onStdout);
const onStderr = this._handleProcessOutputStream(this._extensionHostProcess.onStderr);
// ... 一堆处理进程 I/O 和日志的操作 ...

// Lifecycle
this._extensionHostProcess.onError((e) => this._onExtHostProcessError(e.error));
this._extensionHostProcess.onExit(({ code, signal }) => this._onExtHostProcessExit(code, signal));

// Initialize extension host process with hand shakes
// 关键函数执行 VSCodeExtensionHost 协议通信握手
return this._tryExtHostHandshake(opts, timer).then((protocol) => {
  timer.markDidFinishHandhsake();
  return protocol;
});

}); } // 最终返回用于和 Render 进程通信的 messageProtocol return this._messageProtocol;

```typescript
// _tryExtHostHandshake() 关键实现
this._extensionHostProcess!.start(opts).then(() => { /* log */ }, (err) => {
  // Starting the ext host process resulted in an error
  reject(err);
});

| | platform/extensions/node/extensionHostStarter.ts> starter 是一个单例服务。

其关键的 start 方法去创建进程。值得一提的是 bootstrap-fork 是位于根目录下 bootstrap-fork.js,它是子进程运行时脚本的入口函数。 | ```typescript this._process = fork(FileAccess.asFileUri(‘bootstrap-fork’, require).fsPath, [‘—type=extensionHost’, ‘—skipWorkspaceStorageLock’], opts); const pid = this._process.pid; // … 输入输出的 encoder/ decoder 配置 this._process.on(‘message’, msg => { this._onMessage.fire(msg); }); // … process 事件监听 … return { pid };

 |
| `src/bootstrap-fork.js`<br /><br />- `./bootstrap.js` 和 `./bootstrap-node.js` 是整个初始化脚本,前者是 VSCode 整体的,后者是 Node 相关的<br />- `./bootstrap-amd.js` 是 VSCode amd-loader,最终加载了之前环境变量注入的 ENTRY 路径<br /> | ```typescript
const bootstrap = require('./bootstrap');
const bootstrapNode = require('./bootstrap-node');

// ... 一堆进程的初始化参数解析 ...

// Load AMD entry point
require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']);
// 执行入口,最关键
// VSCODE_AMD_ENTRYPOINT: 'vs/workbench/services/extensions/node/extensionHostProcess',

| | workbench/services/extensions/node/extensionHostProcess.ts
入口文件,最终加载 extensionHostProcessSetup.ts 开始运行。 | ```typescript // extensionHostProcess.ts import { startExtensionHostProcess } from ‘vs/workbench/services/extensions/node/extensionHostProcessSetup’; startExtensionHostProcess().catch((err) => console.log(err));

 |
| `workbench/services/extensions/node/extensionHostProcessSetup.ts`<br /><br />- 创建和父进程(Render 进程)通信的 Protocol<br />- 尝试连接 Protocol,拿到初始化数据 `initData`<br />- 创建 `ExtensionHostMain` 对象,并进行实例化<br /> | ```typescript
export async function startExtensionHostProcess(): Promise<void> {
    const protocol = await createExtHostProtocol();
    const renderer = await connectToRenderer(protocol);
    const { initData } = renderer;
    // ... init hostUtils and uriTransformer ...
    const extensionHostMain = new ExtensionHostMain(
        renderer.protocol,
        initData,
        hostUtils,
        uriTransformer
    );
    // rewrite onTerminate-function to be a proper shutdown
    onTerminate = (reason: string) => extensionHostMain.terminate(reason);
}

// IPC 会返回初始化结构数据(Render 相关)
// - resolvedExtensions
// - hostExtensions
// - extensions
export interface IInitData {
    version: string;
    commit?: string;
    parentPid: number;
    environment: IEnvironment;
    workspace?: IStaticWorkspaceData | null;
    resolvedExtensions: ExtensionIdentifier[];
    hostExtensions: ExtensionIdentifier[];
    extensions: IExtensionDescription[];
    telemetryInfo: ITelemetryInfo;
    logLevel: LogLevel;
    logsLocation: URI;
    logFile: URI;
    autoStart: boolean;
    remote: { isRemote: boolean; authority: string | undefined; connectionData: IRemoteConnectionData | null; };
    uiKind: UIKind;
}

| | workbench/services/extensions/common/extensionHostMain.ts
| ```typescript // constrcutor function this._rpcProtocol = new RPCProtocol(protocol, null, uriTransformer); // ensure URIs are transformed and revived initData = ExtensionHostMain._transform(initData, this._rpcProtocol); // bootstrap services const services = new ServiceCollection(…getSingletonServiceDescriptors()); // … init series of services …

// ugly self - inject // must call initialize after creating the extension service // because initialize itself creates instances that depend on it this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService)); // ℹ️ 关键入口 this._extensionService.initialize(); // … error log related …

 |
| `workbench/api/common/extHostExtensionService.ts`<br />`workbench/api/node/extHostExtensionService.ts`<br /><br />这里对 Extension 扩展插件的启动的封装是最花里胡哨的。细节不说明了,直接看代码里面的注释吧。<br />:::info
关于 `HostExtension` 的定义:<br />是 `ExtensionIdentifier.toKey(extension.identifier.value)` 为空的扩展,就是所谓的内置扩展?
:::
 | ```typescript
// ===============================================================
// initialize()
// ===============================================================
await this._beforeAlmostReadyToRunExtensions();
if (this._initData.autoStart) {
  this._startExtensionHost();
}

// ===============================================================
// _handleEagerExtensions
// ===============================================================
// Handle "eager" activation extensions
// '*' 通用型的扩展启动
const starActivation = this._activateByEvent('*', true);
// Workspace 中需要在启动时启动的扩展
const workspaceContainsActivation = this._handleWorkspaceContainsEagerExtensions(folders);
const eagerExtensionsActivation = Promise.all([starActivation, workspaceContainsActivation]).then(
  () => {}
);
Promise.race([eagerExtensionsActivation, timeout(10000)]).then(() => {
  this._activateAllStartupFinished();
});
return eagerExtensionsActivation;

// ===============================================================
// actualActivateExtension
// ===============================================================
// HostExtension 
if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) {
  // 从 MainThreadExtensionProxy 中启动扩展插件
  // 这个 _mainThreadExtensionsProxy 映射到具体的服务注入为:MainThreadExtensionService
  await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason);
  return new HostExtension();
}
const extensionDescription = this._registry.getExtensionDescription(extensionId)!;
return this._activateExtension(extensionDescription, reason);

// ===============================================================
// _activateExtension
// ===============================================================
return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => {
  // ... log related affairs ...
  return activatedExtension;
});

// ===============================================================
// _doActivateExtension()
// ===============================================================
// 从 description 里面获取入口文件
const entryPoint = this._getEntryPoint(extensionDescription);
return Promise.all([
  // 加载 commonJS Module 文件
  this._loadCommonJSModule<IExtensionModule>(
    extensionDescription.identifier,
    joinPath(extensionDescription.extensionLocation, entryPoint),
    activationTimesBuilder
  ),
  // 注入插件的上下文信息
  this._loadExtensionContext(extensionDescription),
])
  .then((values) => {
    // 加载完毕代码后进入最终运行函数
    return AbstractExtHostExtensionService._callActivate(
      this._logService,
      extensionDescription.identifier,
      values[0],
      values[1],
      activationTimesBuilder
    );
  })
  .then((activatedExtension) => {
    return activatedExtension;
  });

// ===============================================================
// _callActivate()
// ===============================================================
// Make sure the extension's surface is not undefined
extensionModule = extensionModule || {
  activate: undefined,
  deactivate: undefined,
};
return this._callActivateOptional(
  logService,
  extensionId,
  extensionModule,
  context,
  activationTimesBuilder
).then((extensionExports) => {
  // 返回创建完毕的实例
  return new ActivatedExtension(
    false,
    null,
    activationTimesBuilder.build(),
    extensionModule,
    extensionExports,
    context.subscriptions
  );
});

// ===============================================================
// _callActivateOptional()
// ===============================================================
if (typeof extensionModule.activate === 'function') {
  try {
    // `global` is nodejs while `self` is for workers
    const scope = typeof global === 'object' ? global : self;
    // 执行 Extension 规范里面的 activate 函数,可以操作相关的 API
    const activateResult: Promise<IExtensionAPI> = extensionModule.activate.apply(scope, [context]);
    return Promise.resolve(activateResult).then((value) => {
      return value;
    });
  } catch (err) {
    return Promise.reject(err);
  }
}

| | workbench/api/common/extHostExtensionActivator.ts

Activator 负责抽象对 Extension 的启用。 | ```typescript // 但是最终的启动函数依旧是上层的 extHostExtensionService 在创建它的时候传递给它的 const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason);

 |

<a name="N3cZ3"></a>
## WebBrowser 模式

[开发指南](https://code.visualstudio.com/api/extension-guides/web-extensions)

WIP:肝不动了 🙈 ,未来有时间再看。

<a name="hK4ZD"></a>
# 一些关键设计思考

- 实现上,使用 Promise 变量直接 await 做 pending 等待 Ready 是一种思路。
```typescript
// Ensure that the workspace trust state has been fully initialized so
// that the extension host can start with the correct set of extensions.
await this._workspaceTrustManagementService.workspaceTrustInitialized;
  • extHost.protocol.ts 里面展现了 Extension 相关的 Protocol 设计,比较精粹。VSCode 对协议的方法均以 $ 开头,便于区分:

    export interface ExtHostCommandsShape {
      $executeContributedCommand<T>(id: string, ...args: any[]): Promise<T>;
      $getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription; }>;
    }
    

    :::info ⚡️ 后续阅读发现,$ prefix 主要表示可以跨进程通信的方法抽象,满足 extension.host.protocol 的相关通信方法均需要实现 $ 前缀,妙啊 ~

  • 所以接 👇 这个问题 :::

  • 在看 this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason) 的时候,发现没有看懂 _mainThreadExtensionsProxy 是啥? ```typescript @extHostNamedCustomer(MainContext.MainThreadExtensionService) export class MainThreadExtensionService { // }

export function extHostNamedCustomer(id: ProxyIdentifier) { return function (ctor: { new(context: IExtHostContext, …services: Services): T }): void { ExtHostCustomersRegistryImpl.INSTANCE.registerNamedCustomer(id, ctor as IExtHostCustomerCtor); }; }

// 在 extensionHostManager 里面就有一个方法进行关联,建立 RPC 信道 // 最终实现通信在 renderProcess 中创建 extHostContext 并注册在 extensionHostManager const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers(); for (let i = 0, len = namedCustomers.length; i < len; i++) { const [id, ctor] = namedCustomers[i]; const instance = this._instantiationService.createInstance(ctor, extHostContext); this._customers.push(instance); this._rpcProtocol.set(id, instance); } `` 后续发现,这个@extHostNamedCustomer会将MainThreadExtensionService等类,挂载到ExtHostCustomersRegistryImpl中。使之在扩展的 RenderProcess 中的主线程上的扩展通信实现操作。 即在ExtensionHost所在的进程上,想要和RenderProcess通信,比如告诉 RenderProcess 去激活一个新的扩展时,RenderProcess 下的职能类在去通知ExtensionHost` 进行实例化和创建管理。

值得后续关注的点

  • WebBrowser 的插件隔离机制是怎样的?
  • WebBrowser 模式下 Main、Render、ExtensionHost 是如何隔离的,IPC 是如何被替换为 Web 协议工作的,ExtensionAPI 又是如何注入的?