Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions packages/devtools/docs/devtools-cdp-routing.mdx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
title: 嵌入式 DevTools 的 CDP 路由架构
description: 一个 Chrome DevTools 前端,如何同时服务小程序运行时的三个进程后端
description: 一个 Chrome DevTools 前端,如何汇集小程序运行时的跨进程调试数据
---

# 嵌入式 DevTools 的 CDP 路由架构

> **一句话**:devtools 右侧那个「开发者工具」是一整套**嵌入式 Chrome DevTools 前端**,但小程序运行时是**三个进程**。本文讲清楚——怎么让**一个**前端,把 Console / Network / Elements 三个面板分别接到**三个不同的后端**上
> **一句话**:devtools 右侧那个「开发者工具」是一整套**嵌入式 Chrome DevTools 前端**,但小程序运行时有**三个 WebContents 后端和主进程原生网络出口**。本文讲清楚——怎么让**一个**前端,把 Console / Network / Elements 三个面板接到各自的数据来源上
>
> Console / Network / Elements 三条路径分别由 `console-forward`、`network-forward`、`elements-forward` 三个独立服务承载(见 §7)。

Expand All @@ -20,7 +20,7 @@ description: 一个 Chrome DevTools 前端,如何同时服务小程序运行
</div>
<div style={{ flex: '1 1 200px', background: 'hsl(28 40% 12%)', border: '1px solid hsl(28 70% 50%)', borderRadius: '8px', padding: '12px' }}>
<strong style={{ color: 'hsl(28 80% 68%)' }}>Network</strong><br/>
<span style={{ opacity: .8 }}>← simulator WCV</span><br/>
<span style={{ opacity: .8 }}>← 主进程 + simulator / render</span><br/>
<code style={{ fontSize: '12px' }}>wx.request / fetch / XHR</code>
</div>
<div style={{ flex: '1 1 200px', background: 'hsl(210 40% 13%)', border: '1px solid hsl(210 70% 55%)', borderRadius: '8px', padding: '12px' }}>
Expand All @@ -32,20 +32,22 @@ description: 一个 Chrome DevTools 前端,如何同时服务小程序运行

---

## 1. 根本张力:一个前端 ↔ 三个后端
## 1. 一个前端与多个数据来源

native-host 把一个小程序拆成三个进程(详见 [native-host-abstractions](./native-host-abstractions.md)),三个面板想看的数据各在其中一个进程里
native-host 的三个 WebContents 后端与主进程共同提供调试数据(详见 [native-host-abstractions](./native-host-abstractions.md)):

- **service-host**(隐藏 `BrowserWindow`,`service.html`)跑**逻辑层**——`console.log`、断点、`wx.*` 业务 JS。**Console** 看的是它。
- **simulator WCV**(顶层 `WebContentsView`,`simulator.html`)真正**发起网络请求**——`wx.request`/`downloadFile`/`uploadFile` 都走它的 `fetch`/XHR。**Network** 看的是它。
- **主进程**通过 Node http/https 执行 `wx.request`,通过 trace 合成 Network 事件与正文缓存。
- **simulator WCV**(顶层 `WebContentsView`,`simulator.html`)执行 `downloadFile`/`uploadFile` 等 fetch/XHR;其原生 CDP Network 事件也进入同一面板。
- **render guest**(每页一个嵌套 `<webview>`,`pageFrame.html`)承载**页面 DOM**。**Elements** 看的是它。

而一个 Chrome DevTools 前端**原生只能 inspect 一个 WebContents**(被 Electron `setDevToolsWebContents` 绑定的那个;本架构里固定绑 service-host)。一个前端、三个后端——这就是 **1 ↔ N 的阻抗失配**。下面这张图是全局骨架,后续每节都在补全它的一条边。
而一个 Chrome DevTools 前端**原生只能 inspect 一个 WebContents**(被 Electron `setDevToolsWebContents` 绑定的那个;本架构里固定绑 service-host)。其余 WebContents 的 CDP 数据和主进程 trace 经转发器注入同一个前端。下面这张图是全局骨架,后续每节都在补全它的一条边。

```d2
direction: right

后端: 后端 · 三进程 {
后端: 数据来源 {
MAIN: "主进程\n原生 HTTP / WebSocket"
RG: "render guest\n页面 DOM"
SH: "service-host\n逻辑层"
SW: "simulator WCV\n网络栈"
Expand All @@ -72,6 +74,7 @@ direction: right
后端.SH -> 分流.R2: console·响应
分流.R2 -> 前端.CO: 推回前端

后端.MAIN -> 分流.R3: trace 合成
后端.SW -> 分流.R3: Network.* 事件
分流.R3 -> 前端.NW: 推送到面板
```
Expand Down Expand Up @@ -144,7 +147,7 @@ function routeByDomain(method): 'render' | 'service' {
}`}</pre>
</div>

- **出站**:前端的 `InspectorFrontendHost.sendMessageToBackend` 被包了一层——**唯一**的出站 CDP 闸口(`routeOutboundCommand` 是唯一判据函数)。命中 render 前缀的命令被拦下,交给主进程转发到 active render guest;`Network.getResponseBody` / `Network.getRequestPostData` 且 requestId 带 `dimina:sim:` 虚拟前缀的,由主进程从 network-forward 的预取缓存回答(service-host 根本不认识这些 id);**其余一切(含 Emulation / Page / Target)原样透传 = 原生 service-host**。Network 面板的**事件**侧由 network-forward 反向注入——它监听 simulator 的 Network.\* 事件并 dispatch 进前端。
- **出站**:前端的 `InspectorFrontendHost.sendMessageToBackend` 被包了一层——**唯一**的出站 CDP 闸口(`routeOutboundCommand` 是唯一判据函数)。命中 render 前缀的命令被拦下,交给主进程转发到 active render guest;`Network.getResponseBody` / `Network.getRequestPostData` 且 requestId 带 `dimina:sim:` 或 `dimina:http:` 虚拟前缀的,由主进程从 network-forward 的预取缓存回答(service-host 根本不认识这些 id);**其余一切(含 Emulation / Page / Target)原样透传 = 原生 service-host**。Network 面板的**事件**侧由 network-forward 反向注入——它监听 simulator 的 Network.\* 事件,并将主进程原生 HTTP trace 合成为 Network 事件,一起 dispatch 进前端。
- **入站**:render 的响应/事件、simulator 的 Network 事件,都经 `window.DevToolsAPI.dispatchMessage` 推送回前端(大 payload 走 `dispatchMessageChunk` 分片)。
- **三个后端**:`service-host` 是**隐式默认**(不拦、透传即得);`render`(elements-forward)认领 DOM / CSS / Overlay / DOMSnapshot / DOMDebugger;`simulator`(network-forward)认领 Network。

Expand All @@ -160,7 +163,7 @@ function routeByDomain(method): 'render' | 'service' {
</tr></thead>
<tbody>
<tr><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>Runtime / Console / Debugger / Profiler / Log / Sources</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)', color: 'hsl(160 70% 70%)' }}>service-host</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>原生透传(零开销)</td></tr>
<tr><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>Network(事件 + body/postData 回查)</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)', color: 'hsl(28 80% 70%)' }}>simulator WCV + 每个 active render guest</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>抓 simulator(`wx.request` 等)与 render guest(页面本体图片/字体等资源加载)各自的 Network.* 事件 → requestId 命名空间化 → 推送到面板前端;loadingFinished 时预取 body/postData 存有界缓存(forwarder 级并发上限 + 按 `encodedDataLength` 预筛跳过明显超限的),前端对 `dimina:sim:` id 的 `getResponseBody`/`getRequestPostData` 经出站闸口从缓存回答</td></tr>
<tr><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>Network(事件 + body/postData 回查)</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)', color: 'hsl(28 80% 70%)' }}>主进程 + simulator WCV + render guests</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>合成主进程 `wx.request` trace,抓取 simulator 与 render guest(图片/字体等资源)的 Network.* 事件 → requestId 命名空间化 → 推送到面板前端;loadingFinished 时预取 body/postData 存有界缓存(forwarder 级并发上限 + 按 `encodedDataLength` 预筛跳过明显超限的),主进程 HTTP 的 Response/Payload 直接写入同一有界缓存,前端对 `dimina:sim:` / `dimina:http:` id 的 `getResponseBody`/`getRequestPostData` 经出站闸口从缓存回答</td></tr>
<tr><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>DOM / CSS / Overlay / DOMSnapshot / DOMDebugger</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)', color: 'hsl(210 80% 72%)' }}>active render guest</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>转发 render debugger + 推回前端</td></tr>
<tr style={{ background: 'hsl(0 50% 12%)' }}><td style={{ padding: '7px', border: '1px solid hsl(0 40% 35%)' }}><b>Emulation</b> 🚫</td><td style={{ padding: '7px', border: '1px solid hsl(0 40% 35%)', color: 'hsl(160 70% 70%)' }}>service-host(红线)</td><td style={{ padding: '7px', border: '1px solid hsl(0 40% 35%)' }}>safe-area 主进程直发,前端 Emulation 绝不路由 render</td></tr>
<tr><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>Page / Target / Input</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>service-host</td><td style={{ padding: '7px', border: '1px solid hsl(0 0% 24%)' }}>透传(结构性默认,同 Emulation)</td></tr>
Expand Down
Loading
Loading