Skip to content

fix(engine-dom): adoptNode 物化 DOMParser 快照产物,修 bilibili nano 播放器容器空壳挂载 - #129

Merged
leizongmin merged 2 commits into
mainfrom
t8q-nano-widget-mount
Oct 10, 2026
Merged

leizongmin merged 2 commits into
mainfrom
t8q-nano-widget-mount

Conversation

@leizongmin

@leizongmin leizongmin commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

fix(engine-dom): adoptNode 物化 DOMParser 快照产物,修 nano 播放器容器空壳挂载

问题

bilibili 视频页(未登录公开面)nano 播放器初始化时 console 报 7 条同形态错误:TypeError: Cannot set properties of undefined (setting 'webkitTransform'/'display'/'right')、Cannot read properties of undefined (reading 'removeProperty'/'style')——元素已挂载但 .style 为 undefined;探针 census 显示播放器容器序列化为空壳 <div></div>,<video> 元素 0 个。

站点链路(bundle 静态解剖):nano 模板引擎以 tr.t = document.adoptNode(new DOMParser().parseFromString(tpl.trim(), 'text/html').body.firstChild) 构造容器模板树,随后 append 进主文档并对树内元素执行样式操作。

根因

ZeroWeb 的 DOMParser 是快照式只读实现(_zwParsedDoc/_zwParseEl),doc.body.firstChild 经 R3019 lazy 桥返回本地产物节点(_zwMEl 形态)。adoptNode 对该形态 identity no-op,产物 append 后:

  1. 无 .style 等 HTMLElement 接口 → 真站 7 条 TypeError 全部落在此面;
  2. 挂载经 mutation-apply 落地时子树不携带,探针 census 序列化显示空壳 <div></div>(快照树自身的 innerHTML 读不空,空壳产生在挂载消费路径);
  3. 不进文档查询索引。

违反 DOM Standard §concept-node-adopt「node 完整搬入本文档」语义。

修复(2 文件,最小通用)

  • js_dom_shim/part03.js:_zwMEl 构造时打 non-enumerable __zwIsMEl 标记(工厂产物是 plain 对象,无共享 prototype 可 instanceof;non-enumerable 防止 for-in/序列化扩散)。
  • js_dom_shim/part06.js:adoptNode 顶部新增物化分支——识别 _zwParseEl 实例或 __zwIsMEl === true 的元素节点(nodeType 1、无 __zwHandle/__zwSelector 身份),经既有 _ensureMutTree() lazy 桥 + 既有 _wcRebuildAsHandle(WC-M2 重建器)重建为 handle 树真 wrapper(同回合完整接口,mutation 异步 apply 与 createElement 同时序);物化不可行时回落既有分支,行为零变化。同文档真元素 adoptNode 仍保持 identity(原 R192 语义不变),Document 入参仍抛 NotSupportedError。

不引入新机制:识别标记与重建器均为既有设施的最小接线。

验证

  • 红→绿钉测(js_dom_bridge_tests/part18.rs 新增 2 测):主钉复刻真站链(DOMParser 模板 → adoptNode → 相对查询 → append → 序列化),断言 style:object|styleSet:function|tag:DIV|relQ:hit|parentQ:hit|serial:hit;变体钉覆盖快照后代物化+属性保真、物化子树 Text/Comment 保真、同文档 identity 保持、Document 抛错。红态实跑确认(style:undefined|parentQ:miss|li:bad|text:bad|comment:bad),修复后双绿。
  • zero-engine lib 全量:2959 passed / 0 failed(RUST_MIN_STACK=33554432 深嵌套既有要求)。
  • WPT 相邻套零回归:Document-adoptNode 4/4、Element-tagName 6/6、Node-normalize 4/4、Node-cloneNode-document-with-doctype 3/3、processing-instruction-attributes 全 P、MutationObserver-textContent 4P/0F、Document-createElement-namespace 51P/0F。
  • 无适用上游用例:上游 Document-adoptNode.html 不覆盖「快照产物 adopt」(标准引擎无快照实现域);本地钉测为等价回归,红→绿双向判别已证。
  • 真站复跑(bilibili 视频页,headless 探针):
    • r1 的 display×2/right/removeProperty 空壳簇全部消除;
    • census mountHTML 从空壳 <div></div> 推进为完整 bpx-player-container → primary-area → video-area → video-perch → video-wrap → <video> 树,videos 0→1;
    • players 计数仍为 0、未起播——剩余错误归因下一层缺陷(模板挂载走 innerHTML/相对查询链的产物无完整接口,与 adoptNode 主链不同路径),已记入问题池,不在本切片虚报起播;
    • 返修后 r3 复跑与 r2 逐项一致(console 11/err 6/pageErr 1、videos=1、mountHTML 同构)——摘除只影响 detached 弃置文档域,对真站零变化。

返修(双审查两独立首轮统一返修)

两份首轮均为 APPROVE_WITH_NITS(4+5 项非阻断)。采纳返修:

  1. 物化成功后摘除原节点父链(spec concept-node-adopt 第 1 步):物化返回重建副本,原节点若留在原树,adopt 退化为复制(站点双引用消费时内容重复;快照文档 body.firstChild 仍返原节点)。经原父 removeChild 摘除,失败不阻断(回落旧状态)。同时消除首轮②的发现 Bump actions/github-script from 7 to 9 #1(复制面)与 Bump actions/download-artifact from 4 to 8 #2(快照残留)主体。
  2. docThrow 断言收紧(首轮①Bump actions/github-script from 7 to 9 #1):变体钉断言 e.name === 'NotSupportedError',防异常类型退化逃逸。
  3. 主钉新增 snapGone 断言:adopt 后快照文档 body.firstChild 不再返回原节点(摘除语义钉住)。
  4. 测试序列修正:adopt 有摘除副作用,后代臂与根臂改用独立文档(原序列在真 DOM 语义下同样互扰,修复前 identity no-op 无副作用掩盖了假设错误)。
  5. PR 正文根因②措辞修正(首轮①Bump actions/checkout from 4 to 7 #4):空壳产生在 mutation-apply 挂载消费路径,非快照树序列化缺陷。

审查发现的记录项(不在本切片修):顶层裸 Text/Comment 快照直接 adopt 不物化(_zwMText/_zwMComment 无识别标记,扩域另立切片);XML 文档 adopt 丢 namespace/大小写(_wcRebuildAsHandle 既有限制);同节点二次 adopt 产生新副本(无物化映射表,记池)。

既有失败披露:dom/nodes/Node-mutation-adoptNode.html :: simple append of foreign div with text(createHTMLDocument 的 createElement 产物跨文档 append 后 ownerDocument identity 断言)在 main 基线(81a7014db,无本 PR 改动)同样 Fail——既有失败,非本 PR 引入,已记入问题池。

返修后复验:zero-engine lib 2959P/0F、fmt 零 diff、clippy 双腿零 warning、WPT Document-adoptNode 4/4(含摘除语义下的 "Adopting a Document should throw"/"Explicitly adopting a DocumentType")、r3 真站一致。

截图

无适用画面:本修复改的是 DOM 接口语义(元素接口完整性),非视觉呈现面;验证经 headless CDP 探针采集,无 GUI 窗口可截。挂载状态证据以探针 census 序列化文本为准(上节 r1→r2 对比)。

测试计划

  • cargo test -p zero-engine(lib 全量 2959P/0F)
  • WPT 相邻套定向(7 套全 P/0F)
  • cargo fmt --all -- --check / cargo clippy --workspace --all-targets -- -D warnings(默认 + quickjs 双腿)
  • make test 全量

🤖 Generated with Claude Code


Note

Medium Risk
Changes core document.adoptNode behavior for parser snapshot nodes; regression tests cover identity and Document errors, but DOM adoption paths are easy to break subtly.

Overview
Fixes bilibili-style flows where document.adoptNode is used on DOMParser snapshot nodes (parseFromString → body.firstChild). Previously those nodes were returned unchanged, so after append they lacked .style, serialized as empty shells, and did not participate in queries.

adoptNode now detects snapshot / local mutable-tree elements (_zwParseEl or non-enumerable __zwIsMEl) and materializes them into real handle-backed element wrappers via _ensureMutTree + _wcRebuildAsHandle, removing the original node from the snapshot tree per spec. Same-document handle elements still get identity return; adopting a Document still throws NotSupportedError.

Two Rust bridge tests (t8q_*) lock the site chain: style API, detach from parser doc, subtree query/serialize after append, plus variants (text/comment, descendant adopt, identity regression).

Reviewed by Cursor Bugbot for commit 93be92e. Bugbot is set up for automated code reviews on this repo. Configure here.

t8q(site-compat bilibili-20261002-r1 P7):DOMParser 快照式实现的本地产物
(_zwParseEl/_zwMEl 形态)经 adoptNode 后 identity no-op,append 进主文档后
无 .style 等 HTMLElement 接口、序列化空壳、不进查询索引——真站 nano 播放器
容器模板挂载后 7 条 TypeError(setting 'webkitTransform'/'display'/'right'、
reading 'removeProperty'/'style')、<video> 0 个。违反 DOM Standard
concept-node-adopt「node 完整搬入本文档」语义。

最小通用修复(2 文件,复用既有设施不引入新机制):
- part03: _zwMEl 构造打 non-enumerable __zwIsMEl 标记(工厂产物为 plain
  对象无共享 prototype 可 instanceof;non-enumerable 防 for-in/序列化扩散)
- part06: adoptNode 顶部物化分支——识别 _zwParseEl 实例或 __zwIsMEl===true
  的元素节点,经 _ensureMutTree lazy 桥 + 既有 _wcRebuildAsHandle 重建为
  handle 树真 wrapper;物化不可行回落既有分支零变化;同文档真元素 identity
  保持(R192 语义不变)、Document 入参仍抛 NotSupportedError

验证:
- 新增双钉(js_dom_bridge_tests/part18):主钉复刻真站链(parse→adopt→
  相对查询→append→序列化)style/relQ/parentQ/serial 六断言 + 变体钉
  (后代物化属性保真/子树 Text+Comment 保真/identity 保持/docThrow);
  红态实跑(style:undefined|parentQ:miss|li:bad|text:bad|comment:bad)→
  修复后双绿
- zero-engine lib 全量 2959P/0F(RUST_MIN_STACK=33554432)
- WPT 相邻套零回归:Document-adoptNode 4/4、Element-tagName 6/6、
  Node-normalize 4/4、Node-cloneNode-document-with-doctype 3/3、
  processing-instruction-attributes 全 P、MutationObserver-textContent
  4P/0F、Document-createElement-namespace 51P/0F
- 真站复跑(headless 探针):display/right/removeProperty 空壳簇消除,
  mountHTML 空壳 <div></div> → 完整 bpx-player-container→video-wrap→
  <video> 树,videos 0→1;players=0 未起播(剩余错误归因 innerHTML/
  相对查询模板链产物无接口的下一层缺陷,已记池不虚报)
- make test 全量 exit 0;fmt 零 diff;clippy -D warnings 双腿(默认 v8 +
  quickjs no-default)零 warning

Co-Authored-By: Claude Code <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 1a1e76e6-51c3-43f5-bbbf-6a77830b33a9)

PR #129 双审查两独立首轮(测试有效性 + 缺陷,均 APPROVE_WITH_NITS)统一返修:

- 物化成功返回副本前,经原父 removeChild 摘除原节点(spec concept-node-adopt
  第 1 步)——不摘则 adopt 退化为复制(站点双引用消费时内容重复;快照文档
  body.firstChild 仍返原节点)。try/catch 包裹,失败不阻断(回落旧状态)。
  同时消审查②#1 复制面与 #2 快照残留主体。ownerDocument 收窄方案经源码
  考古证伪弃用:R3019 懒桥根 _zwMEl(snap,null) 无 owner 章回落主 document,
  收窄会误杀真站主链。
- docThrow 断言收紧为 e.name==='NotSupportedError'(审查①#1,防异常类型
  退化逃逸;shim DOMException 第二参直设 name)。
- 主钉新增 snapGone 断言(adopt 后快照 body.firstChild 不再返原节点)。
- 测试序列修正:adopt 有摘除副作用,后代臂/根臂改用独立文档(真 DOM 同
  序列同样互扰,修复前 identity no-op 无副作用掩盖了测试假设错误)。
- PR 正文根因②措辞修正:空壳产生在 mutation-apply 挂载消费路径。

审查记录项转池:裸 Text/Comment 物化、XML ns 丢失、二次 adopt 副本;
Node-mutation-adoptNode "simple append of foreign div with text" Fail 经
main 基线对照(/tmp 备份+md5+cp 变异法)定谳既有失败非本 PR 引入。

复验:zero-engine lib 2959P/0F;fmt 零 diff;clippy -D warnings 双腿
(v8+quickjs)零 warning;WPT Document-adoptNode 4/4(含摘除语义下
Document 抛错/DocumentType 断言);r3 真站探针与 r2 逐项一致。

Co-Authored-By: Claude Code <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 93d54040-f0b6-44bb-b0e8-df4cfb3b369a)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant