:::info ℹ️ 这里的主要类关系图我们使用 Electron-Browser 来作为示例。 :::
核心类和关系图
主要执行时序
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’;
值得一提的是:`workbench.web` 和 `workbench.desktop` 所加载的 `extensionService` 不同,其差异点主要在 desktop 使用的服务是基于 `electron-browser` 提供的,而 web 提供的服务自然是由 `browser` 提供的。<br />因此,这里主要讨论 `electron-browser` 的架构设计(Electron RenderProcess based)。<br />至于 `extension.contribution` 主要是完成对扩展协议的注册(一大堆内置的 contribution configurations 加载和注册) |
| `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
- `localExtHost` – A Node.js extension host running locally, on the same machine as the user interface.
- `webExtHost` – A web extension host running in the browser or locally, on the same machine as the user interface.
- `remoteExtHost` – A Node.js extension host running remotely in a container or a remote location.
:::
| ```typescript
[this._enableLocalWebWorker, this._lazyLocalWebWorker] = this._isLocalWebWorkerEnabled();
this._remoteInitData = new Map<string, IRemoteExtensionHostInitData>();
this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner);
// 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
this._lifecycleService.when(LifecyclePhase.Ready).then(() => {
// reschedule to ensure this runs after restoring viewlets, panels, and editors
runWhenIdle(() => {
this._initialize();
}, 50 /*max delay*/);
});
// 关键函数 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
// 在 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 又是如何注入的?