mirror of
https://github.com/Caho1/open-claude-ui.git
synced 2026-11-07 10:26:02 -10:00
docs(v3): resolve the last two unknowns via the main process debugger
The node CLI inspector fuse is disabled, so --inspect does nothing:
npx @electron/fuses read --app /Applications/Claude.app
EnableNodeCliInspectArguments is Disabled
But the app ships its own escape hatch. The "Enable Main Process
Debugger" menu item calls node:inspector open() with no port, so it
listens on 9229 bound to 127.0.0.1. Source extracted from
index.chunk-jPKPzV-P.js:
function aX(){ return ce.url() !== undefined } // ce = node:inspector
async function y7r(){ ... ce.open(); await S7r() }
Attaching over CDP settles both remaining items.
1. The preload entry points were never missing, only unnamed.
webContents._getPreloadScript() returns:
main_window -> .vite/build/mainWindow.js
about:blank -> .vite/build/mainView.js
So the preload is the window script itself. Chapter 2 already analysed
both files without knowing they also serve as preload. This corrects
the earlier reading of REA's preload_entrypoints = 0: it means "no
file named preload", not "no preload mechanism". Chapter 2 now says so
at the point where it made the wrong claim.
The registration path is also clearer than the static picture:
app.registerPreloadScript is undefined, session.registerPreloadScript
exists but session.getPreloadScripts() returns [], and
getLastWebPreferences() omits preload because sandbox is true. The
windows pass preload through webPreferences at construction instead.
2. The app layer forks no utility process.
app.getAppMetrics() reports three Utility processes, all Chromium
built-ins: network.mojom.NetworkService, audio.mojom.AudioService,
video_capture.mojom.VideoCaptureService. A runtime hook on
utilityProcess.fork recorded zero calls.
Combined with the static finding that index.pre.js only wraps fork in
a Proxy for Sentry's MessageChannelMain bridge, this rules out reading
REA's utility_processes = 1 as a count of app-level processes. Recorded
as the third and last answer to that question; whether the bundled
pty-host, file-index-worker or heavy-work-worker scripts use
worker_threads rather than utilityProcess is left open because this
session never triggered them.
Runtime environment recorded alongside: Electron 44.9.3, Node v24.22.0,
app path pointing straight at the asar, sandbox and contextIsolation both
true with nodeIntegration false.
Chapter 6 gains section 6.7 for the runtime evidence, 6.8 and 6.9 shift
down, chapter 2 gets a correction note, chapter 7's unknown tables are
rewritten, and the merged edition is regenerated. Verified: 7 chapters,
0 broken links.
No runtime code, tokens or components are changed.
This commit is contained in:
1 parent
84c7f53930
commit
03a8aca6e0
4 files changed
+388
-28
No files matched your search
@@ -248,7 +248,20 @@ function ty(e,t){
|
||||
|
||||
唯一的磁盘文件在 `ion-dist/`(Claude 的另一个分发目录),不在 ASAR 内,且名为 `preload-helper`,是 Vite 的 preload 辅助函数而非应用 preload 入口。
|
||||
|
||||
**结论**:`preload_entrypoints = 0` **成立**,且原因是结构性的——preload 路径在运行时从 `registerPreloadScript` 的参数得到,打包后不做静态保留。
|
||||
**结论**:`preload_entrypoints = 0` 在**静态层面成立**(asar 内无 preload 命名文件),但**不代表没有 preload**。
|
||||
|
||||
> **⚠️ 本节已被 [6.7.2](06-全量CSS扫描与官方规范比对.md) 的运行时证据修正。**
|
||||
>
|
||||
> 运行时实测(`webContents._getPreloadScript()`):
|
||||
>
|
||||
> | 窗口 | preload 路径 |
|
||||
> | :--- | :--- |
|
||||
> | `main_window` | **`.vite/build/mainWindow.js`** |
|
||||
> | `about:blank` | **`.vite/build/mainView.js`** |
|
||||
>
|
||||
> **preload 就是 `.vite/build/` 下的窗口脚本**。这些文件本章 2.1 / 2.2 已经分析过,只是当时不知道它们同时承担 preload 角色。
|
||||
>
|
||||
> 因此正确表述是:**preload 存在,只是不叫 preload 这个名字**。REA 的 `preload_entrypoints = 0` 反映的是「没有文件名为 preload 的入口」,不是「没有 preload 机制」。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -794,10 +794,177 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
---
|
||||
|
||||
## 6.7 对前序章节结论的修正
|
||||
## 6.7 运行时实证(Main Process Debugger)
|
||||
|
||||
本章前六节均基于字节静态分析。补充项(preload 路径、utility process)无法静态解析,改用 Claude 自带的 **Enable Main Process Debugger** 菜单项取得运行时事实。
|
||||
|
||||
### 6.7.1 取证通道
|
||||
|
||||
Claude 的 `EnableNodeCliInspectArguments` fuse 为 **Disabled**,因此 `--inspect` 启动参数无效:
|
||||
|
||||
```bash
|
||||
npx @electron/fuses read --app /Applications/Claude.app
|
||||
# EnableNodeCliInspectArguments is Disabled
|
||||
```
|
||||
|
||||
但应用层提供了绕过该 fuse 的菜单项。从 `index.chunk-jPKPzV-P.js` 提取的实现:
|
||||
|
||||
```js
|
||||
var v7r = () => ({
|
||||
label: "Enable Main Process Debugger",
|
||||
type: "checkbox",
|
||||
checked: aX(),
|
||||
click: e => { aX() ? b7r() : y7r(), e.checked = aX() }
|
||||
});
|
||||
|
||||
function aX() { return ce.url() !== undefined } // ce = node:inspector
|
||||
|
||||
async function y7r() {
|
||||
if (LK(() => true)) try {
|
||||
aX() && b7r();
|
||||
ce.open(); // 无参 → 默认端口 9229
|
||||
await S7r();
|
||||
} catch (e) { N.debug("Debugger: Failed to enable main process debugger (node inspector)", e) }
|
||||
}
|
||||
|
||||
async function S7r() {
|
||||
process.platform === "darwin"
|
||||
? await xp("/usr/bin/open", ["-a", "Google Chrome", "chrome://inspect"])
|
||||
: ...
|
||||
}
|
||||
```
|
||||
|
||||
**关键**:`ce.open()` 不传端口,Node inspector 默认监听 **9229**,且只绑 `127.0.0.1`。
|
||||
|
||||
打开后:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:9229/json/list
|
||||
# [ { "description": "node.js instance", "webSocketDebuggerUrl": "ws://127.0.0.1:9229/…", "url": "file://" } ]
|
||||
```
|
||||
|
||||
只有一个 target(类型 `node`),即 Electron **主进程**。
|
||||
|
||||
### 6.7.2 preload 的实际路径(已解决)
|
||||
|
||||
静态分析只能找到 `registerPreloadScript({type:"frame", filePath: t})` 这个调用形态,`t` 是压缩后的变量。运行时通过 Electron 内部方法 `webContents._getPreloadScript()` 取得真实值:
|
||||
|
||||
```js
|
||||
BrowserWindow.getAllWindows().map(w => ({
|
||||
id: w.id,
|
||||
url: w.webContents.getURL(),
|
||||
preload: w.webContents._getPreloadScript()
|
||||
}))
|
||||
```
|
||||
|
||||
**实测结果**:
|
||||
|
||||
| 窗口 | URL | preload 路径 |
|
||||
| :--- | :--- | :--- |
|
||||
| id=1 | `.vite/renderer/main_window/index.html` | **`.vite/build/mainWindow.js`** |
|
||||
| id=2 | `about:blank` | **`.vite/build/mainView.js`** |
|
||||
|
||||
`_getPreloadScript()` 的完整返回:
|
||||
|
||||
```json
|
||||
{ "filePath": "/Applications/Claude.app/Contents/Resources/app.asar/.vite/build/mainWindow.js",
|
||||
"id": "", "type": "frame" }
|
||||
```
|
||||
|
||||
**这解决了一个此前的重大误解**:
|
||||
|
||||
- 第 2 章曾说「preload 入口 0 个,路径由 `registerPreloadScript` 运行时拼接,静态不可解」
|
||||
- 事实是:**preload 就是 `.vite/build/` 下的窗口脚本**(`mainWindow.js` / `mainView.js` / 以及同目录的 `aboutWindow.js`、`quickWindow.js`、`buddy.js`、`findInPage.js`)
|
||||
- 这些文件在第 2 章已经分析过(`mainWindow.js` 88,843 字节、`ipcRenderer` 出现 22 次等),只是当时不知道它们同时是 preload
|
||||
|
||||
**为什么静态看不出**:`session.registerPreloadScript` 的 `filePath` 是运行时变量,且 asar 内**没有任何 preload 命名文件**(运行时确认:`app.getAppPath()` 下递归搜索 `preload` 命中 0 个)。
|
||||
|
||||
### 6.7.3 preload 注册的实际 API 是 session 级
|
||||
|
||||
运行时确认:
|
||||
|
||||
```
|
||||
app.registerPreloadScript → undefined
|
||||
session.defaultSession.registerPreloadScript → function
|
||||
session.defaultSession.getPreloadScripts() → []
|
||||
session.defaultSession.getPreloads() → []
|
||||
```
|
||||
|
||||
**关键**:`getPreloadScripts()` 返回空数组,但窗口确实有 preload。说明这些窗口的 preload **不是通过 `session.registerPreloadScript` 注册的**,而是走 `webPreferences.preload` 直接传给 BrowserWindow 构造器。
|
||||
|
||||
交叉验证:`webContents.getLastWebPreferences()` 的返回里**没有 `preload` 键**:
|
||||
|
||||
```json
|
||||
{"allowRunningInsecureContent":false,"contextIsolation":true,"javascript":true,
|
||||
"nodeIntegration":false,"sandbox":true,"webSecurity":true,...}
|
||||
```
|
||||
|
||||
Electron 在 `sandbox:true` 时不把 `preload` 回显到 `getLastWebPreferences`,但 `_getPreloadScript()` 仍能取到。
|
||||
|
||||
**安全配置实测**:`sandbox: true` + `contextIsolation: true` + `nodeIntegration: false`。这与「渲染进程只能通过 12 个 `exposeInMainWorld` 桥接 API 访问主进程」的设计一致。
|
||||
|
||||
### 6.7.4 utility process:实测 3 个,全部是 Chromium 内置
|
||||
|
||||
`app.getAppMetrics()` 实测:
|
||||
|
||||
```
|
||||
{"Browser":1,"GPU":1,"Utility":3,"Tab":2}
|
||||
|
||||
utility serviceNames:
|
||||
network.mojom.NetworkService
|
||||
audio.mojom.AudioService
|
||||
video_capture.mojom.VideoCaptureService
|
||||
```
|
||||
|
||||
对照 `ps` 的进程命令行:
|
||||
|
||||
```
|
||||
--type=utility --utility-sub-type=network.mojom.NetworkService
|
||||
--type=utility --utility-sub-type=audio.mojom.AudioService
|
||||
--type=utility --utility-sub-type=video_capture.mojom.VideoCaptureService
|
||||
```
|
||||
|
||||
**三个 Utility 全部是 Chromium 自带的,没有一个是 Claude 应用层 fork 的。**
|
||||
|
||||
对 `utilityProcess.fork` 挂运行时钩子后,未捕获到任何调用:
|
||||
|
||||
```js
|
||||
utilityProcess.fork = new Proxy(...) // 钩子已安装
|
||||
globalThis.__FORK_HITS__ // → null(本次会话内零调用)
|
||||
```
|
||||
|
||||
**结合静态证据**:`index.pre.js` 中的 `utilityProcess` 3 处出现全部是对 `fork` 的 Proxy 包装(注入 Sentry 的 `MessageChannelMain` 桥),无实际调用点。
|
||||
|
||||
**因此对 REA 的 `utility_processes = 1` 给出裁定**:
|
||||
|
||||
| 来源 | 值 | 口径 |
|
||||
| :--- | ---: | :--- |
|
||||
| REA `summary.utility_processes` | **1** | 静态推断(无法对应到实际进程) |
|
||||
| REA `resolved_utility_entrypoints` | 0 | 入口未知 |
|
||||
| 本文静态 | 0 个 fork 调用点 | 只有 Proxy 包装 |
|
||||
| **本文运行时** | **3 个 Utility** | 全部是 Chromium 内置,非应用层 |
|
||||
|
||||
**结论**:REA 的 `1` 与实测的 `3` 都不是「Claude 应用层 utility process 的数量」。运行时证据表明**应用层当前没有 fork 任何 utility process**;`3` 是 Chromium 的内部服务。
|
||||
|
||||
> **不确定部分**:本次会话未触发应用层 utility process。若某个功能(如 `heavy-work-worker`、`pty-host`、`file-index-worker`)被使用时是否会 fork,本文**未验证**。这三个 worker 脚本在 asar 中存在(见 1.1.3 的 `pty-host`、`file-index-worker`、`heavy-work-worker` 目录),但属于 `node:worker_threads` 还是 `utilityProcess` 未确定。
|
||||
|
||||
### 6.7.5 运行时环境事实
|
||||
|
||||
```
|
||||
electron 版本 : 44.9.3
|
||||
node 版本 : v24.22.0
|
||||
app.getAppPath() : /Applications/Claude.app/Contents/Resources/app.asar
|
||||
userData 目录 : /Users/jiahao/Library/Application Support/Claude
|
||||
```
|
||||
|
||||
`app.getAppPath()` 直接指向 asar,说明 `OnlyLoadAppFromAsar` fuse 为 Enabled(与 6.7.1 的 fuse 清单一致)。
|
||||
|
||||
---
|
||||
|
||||
## 6.8 对前序章节结论的修正
|
||||
|
||||
|
||||
### 6.6.1 修正:`--cds-h-control-nested` 24px 是错的
|
||||
### 6.8.1 修正:`--cds-h-control-nested` 24px 是错的
|
||||
|
||||
**[第五章 5.2](05-open-claude-ui差异审计.md) 未列出此项。** 本次确认:
|
||||
|
||||
@@ -809,7 +976,7 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
**v2 文档在这个值上是正确的**,我们的实现错了。这修正了 [第三章](03-架构重构与对比分析.md) 中对 v2 的整体否定——v2 的取值并非全错。
|
||||
|
||||
### 6.6.2 修正:`--cds-gap-xl` 应为 40px
|
||||
### 6.8.2 修正:`--cds-gap-xl` 应为 40px
|
||||
|
||||
| 来源 | 值 |
|
||||
| :--- | :--- |
|
||||
@@ -818,7 +985,7 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
[第五章 5.2.1](05-open-claude-ui差异审计.md) 曾记录此项(v2 说 40px,我们 24px),本次确认**官方是 40px**。
|
||||
|
||||
### 6.6.3 新增:`--radius-composer` 应为 14px 且带缩放
|
||||
### 6.8.3 新增:`--radius-composer` 应为 14px 且带缩放
|
||||
|
||||
| 来源 | 值 |
|
||||
| :--- | :--- |
|
||||
@@ -828,13 +995,13 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
**我们的 20px 是 v1 时代的遗留值**,v2 文档说降为 12px/14px 是对的。
|
||||
|
||||
### 6.6.4 新增:alpha / text / accent / shadow 四类系统性问题
|
||||
### 6.8.4 新增:alpha / text / accent / shadow 四类系统性问题
|
||||
|
||||
这四类在 [第五章](05-open-claude-ui差异审计.md) 中均未涉及,因为当时只对比了主样式表的 `--cds-*`,没有官方 vanilla 作为参照。
|
||||
|
||||
---
|
||||
|
||||
## 6.8 更新后的差异优先级
|
||||
## 6.9 更新后的差异优先级
|
||||
|
||||
在 [5.5 修复优先级总表](05-open-claude-ui差异审计.md) 基础上,新增以下项(按优先级排序):
|
||||
|
||||
|
||||
@@ -101,12 +101,9 @@ evidence_root: "b2dfbbb40325d978dcb7561ad12a6f9bc199f8f46b7d3d2899af74ce8965d68a
|
||||
|
||||
以下各项本文**无法确定**,列出原因与可用的进一步手段。
|
||||
|
||||
### 7.3.1 需要运行时观测
|
||||
### 7.3.1 已全部解决
|
||||
|
||||
| 未知项 | 原因 | 可行手段 |
|
||||
| :--- | :--- | :--- |
|
||||
| `registerPreloadScript` 的 `filePath` 实际值 | 参数为运行时变量 | Electron 主进程 `--inspect` 断点 |
|
||||
| `utilityProcess.fork` 的实际调用点与参数 | REA 语义图被截断;Proxy 包装内不可静态定位 | 同上 |
|
||||
原列的两项「需运行时观测」已通过 Claude 自带的 **Enable Main Process Debugger** 取得运行时事实(见 [6.7](06-全量CSS扫描与官方规范比对.md))。
|
||||
|
||||
### 7.3.1.1 已在第六章解决
|
||||
|
||||
@@ -117,11 +114,14 @@ evidence_root: "b2dfbbb40325d978dcb7561ad12a6f9bc199f8f46b7d3d2899af74ce8965d68a
|
||||
| 44 个缺失 keyframes 是否在其它 CSS 文件 | **不在**。全部 44 项仅存在于主样式表;其余 16 个文件只多出 4 个非 `cds-*` 关键帧 | [6.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-*` 与官方 token 的关系 | `--cds-X` ≡ 官方 `--X`,131 个同名 | [6.5](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-radius-composer` 官方值 | **14px**(`calc(.875rem * scale)`),我们的 20px 是 v1 遗留;切换条件为 `[data-density=comfortable]` | [6.6.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-h-control-nested` 官方值 | **22px**(`calc(1.375rem * scale)`),v2 文档正确,我们错 | [6.7.1](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-gap-xl` 官方值 | **40px**(`calc(2.5rem * scale)`) | [6.7.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-h-control-nested` 官方值 | **22px**(`calc(1.375rem * scale)`),v2 文档正确,我们错 | [6.8.1](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-gap-xl` 官方值 | **40px**(`calc(2.5rem * scale)`) | [6.8.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-skel-elapsed` / `skel-settled` 由谁写入 | **没有代码写入**。551 个 JS 文件中 `SkeletonGroup` 零出现,恒为 `0s`;`reveal-end` 恒为 `0.65s` | [6.6](06-全量CSS扫描与官方规范比对.md) |
|
||||
| 「7 停非线性 mask」是否存在 | **不存在**。全文 6 停以上 gradient 为 0 | [6.6.3](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-rem-scale` 的实际取值 | **从未被赋值**,306 处引用全部走 fallback `1` | [6.6.1](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `registerPreloadScript` 的 `filePath` 实际值 | **`.vite/build/mainWindow.js`**(主窗口)与 **`.vite/build/mainView.js`**(about:blank)。preload 就是窗口脚本本身 | [6.7.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `utilityProcess.fork` 的实际调用点 | 运行时钩子零命中。实测 3 个 Utility 全为 Chromium 内置(Network / Audio / VideoCapture),**应用层未 fork 任何 utility process** | [6.7.4](06-全量CSS扫描与官方规范比对.md) |
|
||||
| preload 注册的实际 API | session 级 `registerPreloadScript`,但 `getPreloadScripts()` 返回 `[]`;实际走 `webPreferences.preload` | [6.7.3](06-全量CSS扫描与官方规范比对.md) |
|
||||
|
||||
### 7.3.2 需要历史版本
|
||||
|
||||
|
||||
@@ -524,7 +524,20 @@ function ty(e,t){
|
||||
|
||||
唯一的磁盘文件在 `ion-dist/`(Claude 的另一个分发目录),不在 ASAR 内,且名为 `preload-helper`,是 Vite 的 preload 辅助函数而非应用 preload 入口。
|
||||
|
||||
**结论**:`preload_entrypoints = 0` **成立**,且原因是结构性的——preload 路径在运行时从 `registerPreloadScript` 的参数得到,打包后不做静态保留。
|
||||
**结论**:`preload_entrypoints = 0` 在**静态层面成立**(asar 内无 preload 命名文件),但**不代表没有 preload**。
|
||||
|
||||
> **⚠️ 本节已被 [6.7.2](06-全量CSS扫描与官方规范比对.md) 的运行时证据修正。**
|
||||
>
|
||||
> 运行时实测(`webContents._getPreloadScript()`):
|
||||
>
|
||||
> | 窗口 | preload 路径 |
|
||||
> | :--- | :--- |
|
||||
> | `main_window` | **`.vite/build/mainWindow.js`** |
|
||||
> | `about:blank` | **`.vite/build/mainView.js`** |
|
||||
>
|
||||
> **preload 就是 `.vite/build/` 下的窗口脚本**。这些文件本章 2.1 / 2.2 已经分析过,只是当时不知道它们同时承担 preload 角色。
|
||||
>
|
||||
> 因此正确表述是:**preload 存在,只是不叫 preload 这个名字**。REA 的 `preload_entrypoints = 0` 反映的是「没有文件名为 preload 的入口」,不是「没有 preload 机制」。
|
||||
|
||||
---
|
||||
|
||||
@@ -3239,10 +3252,177 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
---
|
||||
|
||||
## 6.7 对前序章节结论的修正
|
||||
## 6.7 运行时实证(Main Process Debugger)
|
||||
|
||||
本章前六节均基于字节静态分析。补充项(preload 路径、utility process)无法静态解析,改用 Claude 自带的 **Enable Main Process Debugger** 菜单项取得运行时事实。
|
||||
|
||||
### 6.7.1 取证通道
|
||||
|
||||
Claude 的 `EnableNodeCliInspectArguments` fuse 为 **Disabled**,因此 `--inspect` 启动参数无效:
|
||||
|
||||
```bash
|
||||
npx @electron/fuses read --app /Applications/Claude.app
|
||||
# EnableNodeCliInspectArguments is Disabled
|
||||
```
|
||||
|
||||
但应用层提供了绕过该 fuse 的菜单项。从 `index.chunk-jPKPzV-P.js` 提取的实现:
|
||||
|
||||
```js
|
||||
var v7r = () => ({
|
||||
label: "Enable Main Process Debugger",
|
||||
type: "checkbox",
|
||||
checked: aX(),
|
||||
click: e => { aX() ? b7r() : y7r(), e.checked = aX() }
|
||||
});
|
||||
|
||||
function aX() { return ce.url() !== undefined } // ce = node:inspector
|
||||
|
||||
async function y7r() {
|
||||
if (LK(() => true)) try {
|
||||
aX() && b7r();
|
||||
ce.open(); // 无参 → 默认端口 9229
|
||||
await S7r();
|
||||
} catch (e) { N.debug("Debugger: Failed to enable main process debugger (node inspector)", e) }
|
||||
}
|
||||
|
||||
async function S7r() {
|
||||
process.platform === "darwin"
|
||||
? await xp("/usr/bin/open", ["-a", "Google Chrome", "chrome://inspect"])
|
||||
: ...
|
||||
}
|
||||
```
|
||||
|
||||
**关键**:`ce.open()` 不传端口,Node inspector 默认监听 **9229**,且只绑 `127.0.0.1`。
|
||||
|
||||
打开后:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:9229/json/list
|
||||
# [ { "description": "node.js instance", "webSocketDebuggerUrl": "ws://127.0.0.1:9229/…", "url": "file://" } ]
|
||||
```
|
||||
|
||||
只有一个 target(类型 `node`),即 Electron **主进程**。
|
||||
|
||||
### 6.7.2 preload 的实际路径(已解决)
|
||||
|
||||
静态分析只能找到 `registerPreloadScript({type:"frame", filePath: t})` 这个调用形态,`t` 是压缩后的变量。运行时通过 Electron 内部方法 `webContents._getPreloadScript()` 取得真实值:
|
||||
|
||||
```js
|
||||
BrowserWindow.getAllWindows().map(w => ({
|
||||
id: w.id,
|
||||
url: w.webContents.getURL(),
|
||||
preload: w.webContents._getPreloadScript()
|
||||
}))
|
||||
```
|
||||
|
||||
**实测结果**:
|
||||
|
||||
| 窗口 | URL | preload 路径 |
|
||||
| :--- | :--- | :--- |
|
||||
| id=1 | `.vite/renderer/main_window/index.html` | **`.vite/build/mainWindow.js`** |
|
||||
| id=2 | `about:blank` | **`.vite/build/mainView.js`** |
|
||||
|
||||
`_getPreloadScript()` 的完整返回:
|
||||
|
||||
```json
|
||||
{ "filePath": "/Applications/Claude.app/Contents/Resources/app.asar/.vite/build/mainWindow.js",
|
||||
"id": "", "type": "frame" }
|
||||
```
|
||||
|
||||
**这解决了一个此前的重大误解**:
|
||||
|
||||
- 第 2 章曾说「preload 入口 0 个,路径由 `registerPreloadScript` 运行时拼接,静态不可解」
|
||||
- 事实是:**preload 就是 `.vite/build/` 下的窗口脚本**(`mainWindow.js` / `mainView.js` / 以及同目录的 `aboutWindow.js`、`quickWindow.js`、`buddy.js`、`findInPage.js`)
|
||||
- 这些文件在第 2 章已经分析过(`mainWindow.js` 88,843 字节、`ipcRenderer` 出现 22 次等),只是当时不知道它们同时是 preload
|
||||
|
||||
**为什么静态看不出**:`session.registerPreloadScript` 的 `filePath` 是运行时变量,且 asar 内**没有任何 preload 命名文件**(运行时确认:`app.getAppPath()` 下递归搜索 `preload` 命中 0 个)。
|
||||
|
||||
### 6.7.3 preload 注册的实际 API 是 session 级
|
||||
|
||||
运行时确认:
|
||||
|
||||
```
|
||||
app.registerPreloadScript → undefined
|
||||
session.defaultSession.registerPreloadScript → function
|
||||
session.defaultSession.getPreloadScripts() → []
|
||||
session.defaultSession.getPreloads() → []
|
||||
```
|
||||
|
||||
**关键**:`getPreloadScripts()` 返回空数组,但窗口确实有 preload。说明这些窗口的 preload **不是通过 `session.registerPreloadScript` 注册的**,而是走 `webPreferences.preload` 直接传给 BrowserWindow 构造器。
|
||||
|
||||
交叉验证:`webContents.getLastWebPreferences()` 的返回里**没有 `preload` 键**:
|
||||
|
||||
```json
|
||||
{"allowRunningInsecureContent":false,"contextIsolation":true,"javascript":true,
|
||||
"nodeIntegration":false,"sandbox":true,"webSecurity":true,...}
|
||||
```
|
||||
|
||||
Electron 在 `sandbox:true` 时不把 `preload` 回显到 `getLastWebPreferences`,但 `_getPreloadScript()` 仍能取到。
|
||||
|
||||
**安全配置实测**:`sandbox: true` + `contextIsolation: true` + `nodeIntegration: false`。这与「渲染进程只能通过 12 个 `exposeInMainWorld` 桥接 API 访问主进程」的设计一致。
|
||||
|
||||
### 6.7.4 utility process:实测 3 个,全部是 Chromium 内置
|
||||
|
||||
`app.getAppMetrics()` 实测:
|
||||
|
||||
```
|
||||
{"Browser":1,"GPU":1,"Utility":3,"Tab":2}
|
||||
|
||||
utility serviceNames:
|
||||
network.mojom.NetworkService
|
||||
audio.mojom.AudioService
|
||||
video_capture.mojom.VideoCaptureService
|
||||
```
|
||||
|
||||
对照 `ps` 的进程命令行:
|
||||
|
||||
```
|
||||
--type=utility --utility-sub-type=network.mojom.NetworkService
|
||||
--type=utility --utility-sub-type=audio.mojom.AudioService
|
||||
--type=utility --utility-sub-type=video_capture.mojom.VideoCaptureService
|
||||
```
|
||||
|
||||
**三个 Utility 全部是 Chromium 自带的,没有一个是 Claude 应用层 fork 的。**
|
||||
|
||||
对 `utilityProcess.fork` 挂运行时钩子后,未捕获到任何调用:
|
||||
|
||||
```js
|
||||
utilityProcess.fork = new Proxy(...) // 钩子已安装
|
||||
globalThis.__FORK_HITS__ // → null(本次会话内零调用)
|
||||
```
|
||||
|
||||
**结合静态证据**:`index.pre.js` 中的 `utilityProcess` 3 处出现全部是对 `fork` 的 Proxy 包装(注入 Sentry 的 `MessageChannelMain` 桥),无实际调用点。
|
||||
|
||||
**因此对 REA 的 `utility_processes = 1` 给出裁定**:
|
||||
|
||||
| 来源 | 值 | 口径 |
|
||||
| :--- | ---: | :--- |
|
||||
| REA `summary.utility_processes` | **1** | 静态推断(无法对应到实际进程) |
|
||||
| REA `resolved_utility_entrypoints` | 0 | 入口未知 |
|
||||
| 本文静态 | 0 个 fork 调用点 | 只有 Proxy 包装 |
|
||||
| **本文运行时** | **3 个 Utility** | 全部是 Chromium 内置,非应用层 |
|
||||
|
||||
**结论**:REA 的 `1` 与实测的 `3` 都不是「Claude 应用层 utility process 的数量」。运行时证据表明**应用层当前没有 fork 任何 utility process**;`3` 是 Chromium 的内部服务。
|
||||
|
||||
> **不确定部分**:本次会话未触发应用层 utility process。若某个功能(如 `heavy-work-worker`、`pty-host`、`file-index-worker`)被使用时是否会 fork,本文**未验证**。这三个 worker 脚本在 asar 中存在(见 1.1.3 的 `pty-host`、`file-index-worker`、`heavy-work-worker` 目录),但属于 `node:worker_threads` 还是 `utilityProcess` 未确定。
|
||||
|
||||
### 6.7.5 运行时环境事实
|
||||
|
||||
```
|
||||
electron 版本 : 44.9.3
|
||||
node 版本 : v24.22.0
|
||||
app.getAppPath() : /Applications/Claude.app/Contents/Resources/app.asar
|
||||
userData 目录 : /Users/jiahao/Library/Application Support/Claude
|
||||
```
|
||||
|
||||
`app.getAppPath()` 直接指向 asar,说明 `OnlyLoadAppFromAsar` fuse 为 Enabled(与 6.7.1 的 fuse 清单一致)。
|
||||
|
||||
---
|
||||
|
||||
## 6.8 对前序章节结论的修正
|
||||
|
||||
|
||||
### 6.6.1 修正:`--cds-h-control-nested` 24px 是错的
|
||||
### 6.8.1 修正:`--cds-h-control-nested` 24px 是错的
|
||||
|
||||
**[第五章 5.2](05-open-claude-ui差异审计.md) 未列出此项。** 本次确认:
|
||||
|
||||
@@ -3254,7 +3434,7 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
**v2 文档在这个值上是正确的**,我们的实现错了。这修正了 [第三章](03-架构重构与对比分析.md) 中对 v2 的整体否定——v2 的取值并非全错。
|
||||
|
||||
### 6.6.2 修正:`--cds-gap-xl` 应为 40px
|
||||
### 6.8.2 修正:`--cds-gap-xl` 应为 40px
|
||||
|
||||
| 来源 | 值 |
|
||||
| :--- | :--- |
|
||||
@@ -3263,7 +3443,7 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
[第五章 5.2.1](05-open-claude-ui差异审计.md) 曾记录此项(v2 说 40px,我们 24px),本次确认**官方是 40px**。
|
||||
|
||||
### 6.6.3 新增:`--radius-composer` 应为 14px 且带缩放
|
||||
### 6.8.3 新增:`--radius-composer` 应为 14px 且带缩放
|
||||
|
||||
| 来源 | 值 |
|
||||
| :--- | :--- |
|
||||
@@ -3273,13 +3453,13 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
**我们的 20px 是 v1 时代的遗留值**,v2 文档说降为 12px/14px 是对的。
|
||||
|
||||
### 6.6.4 新增:alpha / text / accent / shadow 四类系统性问题
|
||||
### 6.8.4 新增:alpha / text / accent / shadow 四类系统性问题
|
||||
|
||||
这四类在 [第五章](05-open-claude-ui差异审计.md) 中均未涉及,因为当时只对比了主样式表的 `--cds-*`,没有官方 vanilla 作为参照。
|
||||
|
||||
---
|
||||
|
||||
## 6.8 更新后的差异优先级
|
||||
## 6.9 更新后的差异优先级
|
||||
|
||||
在 [5.5 修复优先级总表](05-open-claude-ui差异审计.md) 基础上,新增以下项(按优先级排序):
|
||||
|
||||
@@ -3407,12 +3587,9 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
|
||||
以下各项本文**无法确定**,列出原因与可用的进一步手段。
|
||||
|
||||
### 7.3.1 需要运行时观测
|
||||
### 7.3.1 已全部解决
|
||||
|
||||
| 未知项 | 原因 | 可行手段 |
|
||||
| :--- | :--- | :--- |
|
||||
| `registerPreloadScript` 的 `filePath` 实际值 | 参数为运行时变量 | Electron 主进程 `--inspect` 断点 |
|
||||
| `utilityProcess.fork` 的实际调用点与参数 | REA 语义图被截断;Proxy 包装内不可静态定位 | 同上 |
|
||||
原列的两项「需运行时观测」已通过 Claude 自带的 **Enable Main Process Debugger** 取得运行时事实(见 [6.7](06-全量CSS扫描与官方规范比对.md))。
|
||||
|
||||
### 7.3.1.1 已在第六章解决
|
||||
|
||||
@@ -3423,11 +3600,14 @@ grep -c "@custom-variant" MainWindowPage-BjVtjJFx.css # 0
|
||||
| 44 个缺失 keyframes 是否在其它 CSS 文件 | **不在**。全部 44 项仅存在于主样式表;其余 16 个文件只多出 4 个非 `cds-*` 关键帧 | [6.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-*` 与官方 token 的关系 | `--cds-X` ≡ 官方 `--X`,131 个同名 | [6.5](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-radius-composer` 官方值 | **14px**(`calc(.875rem * scale)`),我们的 20px 是 v1 遗留;切换条件为 `[data-density=comfortable]` | [6.6.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-h-control-nested` 官方值 | **22px**(`calc(1.375rem * scale)`),v2 文档正确,我们错 | [6.7.1](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-gap-xl` 官方值 | **40px**(`calc(2.5rem * scale)`) | [6.7.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-h-control-nested` 官方值 | **22px**(`calc(1.375rem * scale)`),v2 文档正确,我们错 | [6.8.1](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-gap-xl` 官方值 | **40px**(`calc(2.5rem * scale)`) | [6.8.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-skel-elapsed` / `skel-settled` 由谁写入 | **没有代码写入**。551 个 JS 文件中 `SkeletonGroup` 零出现,恒为 `0s`;`reveal-end` 恒为 `0.65s` | [6.6](06-全量CSS扫描与官方规范比对.md) |
|
||||
| 「7 停非线性 mask」是否存在 | **不存在**。全文 6 停以上 gradient 为 0 | [6.6.3](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `--cds-rem-scale` 的实际取值 | **从未被赋值**,306 处引用全部走 fallback `1` | [6.6.1](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `registerPreloadScript` 的 `filePath` 实际值 | **`.vite/build/mainWindow.js`**(主窗口)与 **`.vite/build/mainView.js`**(about:blank)。preload 就是窗口脚本本身 | [6.7.2](06-全量CSS扫描与官方规范比对.md) |
|
||||
| `utilityProcess.fork` 的实际调用点 | 运行时钩子零命中。实测 3 个 Utility 全为 Chromium 内置(Network / Audio / VideoCapture),**应用层未 fork 任何 utility process** | [6.7.4](06-全量CSS扫描与官方规范比对.md) |
|
||||
| preload 注册的实际 API | session 级 `registerPreloadScript`,但 `getPreloadScripts()` 返回 `[]`;实际走 `webPreferences.preload` | [6.7.3](06-全量CSS扫描与官方规范比对.md) |
|
||||
|
||||
### 7.3.2 需要历史版本
|
||||
|
||||
|
||||
Reference in new issue
Block a user