diff --git a/crates/engine/src/js_dom_bridge_tests/part18.rs b/crates/engine/src/js_dom_bridge_tests/part18.rs
index ca73959f50..302d21747d 100644
--- a/crates/engine/src/js_dom_bridge_tests/part18.rs
+++ b/crates/engine/src/js_dom_bridge_tests/part18.rs
@@ -2095,3 +2095,118 @@ fn test_synthetic_click_pre_activation_r108() {
"R108 合成 click pre-activation 核心三面(全六语义由 WPT Event-dispatch-click 31P 守)"
);
}
+
+// t8q(site-compat bilibili-20261002-r1 P7):DOMParser 解析快照 adoptNode 落地。
+// 真站链:tr.t = `document.adoptNode(new DOMParser().parseFromString(tpl, 'text/html').body.firstChild)`
+// ——快照产物(_zwParseEl)此前 adopt 恒 identity no-op:无 .style(widget 样式操作
+// `nodes.X.style.foo` 全崩)、append 后序列化为空 `
`、不进文档查询。
+// spec https://dom.spec.whatwg.org/#concept-node-adopt —— adopt 把节点完整搬入本文档。
+#[test]
+fn t8q_adopt_node_materializes_dom_parser_snapshot_element() {
+ use std::sync::{Arc, Mutex};
+ use zero_script_sandbox::{Sandbox, V8Sandbox};
+
+ let mut sandbox = V8Sandbox::with_config(zero_script_sandbox::SandboxConfig {
+ persistent_context: true,
+ ..Default::default()
+ })
+ .unwrap();
+ sandbox.execute(generate_js_dom_shim()).unwrap();
+ let mutations = Arc::new(Mutex::new(Vec::::new()));
+ let dom_html = Arc::new(Mutex::new("".to_string()));
+ let page_url = Arc::new(Mutex::new("https://zero.test/t8q-adopt".to_string()));
+ let canvas_registry = Arc::new(Mutex::new(crate::js_dom_bridge::CanvasRegistry::new()));
+ register_dom_callbacks(&mut sandbox, &mutations, &dom_html, &page_url, &canvas_registry, None);
+
+ let out = sandbox
+ .execute(
+ r#"(function(){
+ var tpl = '';
+ var doc = new DOMParser().parseFromString(tpl, 'text/html');
+ var snap = doc.body.firstChild;
+ if (!snap || snap.nodeType !== 1) return 'setup:no-first-child';
+ var adopted = document.adoptNode(snap);
+ var r = [];
+ // ① 物化:adopt 产物带 .style 接口(快照对象无 style——真站 7 处 TypeError 根面)。
+ r.push('style:' + typeof adopted.style);
+ r.push('styleSet:' + typeof (adopted.style && adopted.style.setProperty));
+ // ② 原父摘除(spec concept-node-adopt 第 1 步):物化返回副本后,原节点已从
+ // 快照文档树摘除——body.firstChild 不再返回它(PR #129 审查②:不摘则 adopt
+ // 退化为复制,双引用消费时内容重复)。
+ r.push('snapGone:' + (doc.body.firstChild ? 'bad' : 'ok'));
+ // ③ 结构保真:tagName/子树查询(真站 nodes 注册表同款相对查询)。
+ r.push('tag:' + adopted.tagName);
+ r.push('relQ:' + (adopted.querySelector('.bpx-player-video-wrap') ? 'hit' : 'miss'));
+ // ④ append 进文档:父相对查询 + 序列化可见(快照 append 后为空 )。
+ var host = document.getElementById('host');
+ host.appendChild(adopted);
+ r.push('parentQ:' + (host.querySelector('.bpx-player-video-wrap') ? 'hit' : 'miss'));
+ r.push('serial:' + (host.innerHTML.indexOf('bpx-player-video-wrap') >= 0 ? 'hit' : 'miss'));
+ return r.join('|');
+})()"#,
+ )
+ .unwrap()
+ .value;
+ assert_eq!(
+ out,
+ "style:object|styleSet:function|snapGone:ok|tag:DIV|relQ:hit|parentQ:hit|serial:hit",
+ "t8q:DOMParser 快照 adoptNode 物化落地(style 接口 + 原父摘除 + 相对查询 + append 后查询/序列化)"
+ );
+}
+
+// t8q 邻近变体:① 快照后代元素(querySelector 产物)adopt 同样物化;② 元素物化的
+// 子树保真——根元素重建时内部 Text/Comment 经 _wcRebuildAsHandle 递归物化为对应
+// 类型节点(顶层裸 Text/Comment 快照直接 adopt 的物化不在本修复域,见池记录);
+// ③ 同文档真元素 adoptNode 保持 identity 返回(R192 语义零回归)。
+#[test]
+fn t8q_adopt_node_snapshot_variants_and_identity_regression() {
+ use std::sync::{Arc, Mutex};
+ use zero_script_sandbox::{Sandbox, V8Sandbox};
+
+ let mut sandbox = V8Sandbox::with_config(zero_script_sandbox::SandboxConfig {
+ persistent_context: true,
+ ..Default::default()
+ })
+ .unwrap();
+ sandbox.execute(generate_js_dom_shim()).unwrap();
+ let mutations = Arc::new(Mutex::new(Vec::::new()));
+ let dom_html = Arc::new(Mutex::new("".to_string()));
+ let page_url = Arc::new(Mutex::new("https://zero.test/t8q-adopt-var".to_string()));
+ let canvas_registry = Arc::new(Mutex::new(crate::js_dom_bridge::CanvasRegistry::new()));
+ register_dom_callbacks(&mut sandbox, &mutations, &dom_html, &page_url, &canvas_registry, None);
+
+ let out = sandbox
+ .execute(
+ r#"(function(){
+ var doc = new DOMParser().parseFromString('', 'text/html');
+ var root = doc.body.firstChild;
+ var r = [];
+ // ① 元素物化的子树保真:li 内文本 + ul 内注释随根物化(createTextNode/createComment)。
+ // (先 adopt 根——adopt 有摘除副作用,后代臂用独立文档,避免序列互相污染。)
+ var ul = document.adoptNode(root);
+ var liText = ul.firstChild && ul.firstChild.firstChild;
+ var ulCmt = ul.childNodes[1];
+ r.push('text:' + (liText && liText.nodeType === 3 && liText.data === 't' ? 'ok' : 'bad'));
+ r.push('comment:' + (ulCmt && ulCmt.nodeType === 8 && ulCmt.data === ' lc ' ? 'ok' : 'bad'));
+ // ② 快照后代(querySelector 产物)adopt 物化 + 属性保真(独立文档)。
+ var doc2 = new DOMParser().parseFromString('', 'text/html');
+ var li = document.adoptNode(doc2.body.firstChild.querySelector('li'));
+ r.push('li:' + (li && li.tagName === 'LI' && typeof li.style === 'object' && li.className === 'i' ? 'ok' : 'bad'));
+ // ③ 同文档真元素 adopt 仍 identity(R192)。
+ var real = document.createElement('div');
+ r.push('identity:' + (document.adoptNode(real) === real ? 'ok' : 'bad'));
+ // ④ Document adopt 仍抛 NotSupportedError(R192;审查①:断言异常类型防退化)。
+ var threw = '';
+ try { document.adoptNode(new DOMParser().parseFromString('', 'text/html')); } catch (e) { threw = e.name; }
+ r.push('docThrow:' + (threw === 'NotSupportedError' ? 'ok' : 'bad:' + threw));
+ return r.join('|');
+})()"#,
+ )
+ .unwrap()
+ .value;
+ assert_eq!(
+ out,
+ "text:ok|comment:ok|li:ok|identity:ok|docThrow:ok",
+ "t8q:子树文本/注释保真 + 快照后代物化 + 真元素 identity 与 Document 抛错零回归"
+ );
+}
diff --git a/crates/engine/src/js_dom_shim/part03.js b/crates/engine/src/js_dom_shim/part03.js
index 7cf8bc00ef..3e13f6cd1c 100644
--- a/crates/engine/src/js_dom_shim/part03.js
+++ b/crates/engine/src/js_dom_shim/part03.js
@@ -9286,6 +9286,11 @@
childNodes: [],
parentNode: parent || null
};
+ // t8q(site-compat):本地产物标记(non-enumerable 防 for-in/序列化扩散)——
+ // _zwMEl 是工厂返回的 plain 对象(无共享 prototype 可 instanceof);DOMParser
+ // 文档 body.firstChild 经 _ensureMutTree 返回的就是本形态节点,adoptNode
+ // 落地分支(part06)以 `__zwIsMEl === true` 识别物化对象。
+ try { Object.defineProperty(node, '__zwIsMEl', { value: true, enumerable: false }); } catch (_eMElTag) {}
// R167(js-dom M1 L2-d3b):ownerDocument accessor——查询产物归一到 mutTree 真实
// 节点后,消费面(WPT Element-matches 的 runSpecialMatchesTests
// `element.ownerDocument.defaultView.TypeError`)经此读文档。归建树设
diff --git a/crates/engine/src/js_dom_shim/part06.js b/crates/engine/src/js_dom_shim/part06.js
index 14c1f11f7b..d45c5335d5 100644
--- a/crates/engine/src/js_dom_shim/part06.js
+++ b/crates/engine/src/js_dom_shim/part06.js
@@ -3324,12 +3324,53 @@
//(spec concept-node-adopt 前置——WPT "Explicitly adopting a DocumentType" 断言
// adopt 后 parentNode null);③ 子树 ownerDocument 重指本文档(handle 注册表 +
// plain defineProperty,与 R191 appendChild adopt 同款);④ 返回节点自身。
+ // t8q(site-compat bilibili-20261002-r1 P7):**DOMParser 快照元素落地**——
+ // 快照产物(_zwParseEl,R2790 查询工厂)无 __zwHandle/__zwSelector 身份,旧
+ // R192 第 3 分支只补 ownerDocument 后原样返回:append 进文档后 ① 无 `.style`
+ //(站点模板引擎 tr.t = parseFromString + adoptNode 链的样式操作全 TypeError
+ // ——bilibili nano 播放器 7 处崩溃实证);② 序列化为空壳 ``;
+ // ③ 不进文档查询。spec https://dom.spec.whatwg.org/#concept-node-adopt 要求
+ // adopt 把节点完整搬入本文档——此处经 R3019 lazy 可变子树桥(_ensureMutTree)
+ // + `_wcRebuildAsHandle`(WC-M2 既有重建器)物化为 handle 树(真 wrapper:
+ // 同回合完整接口,mutation 队列异步 apply 与页面 createElement 同时序)。
+ // 识别 _zwParseEl 实例(R2790 查询工厂)与本地可变树产物(_zwMEl 印章
+ // `__zwIsMEl`——工厂产物是 plain 对象无共享 prototype 可 instanceof,覆盖
+ // R3019 懒桥树与 detached 归建树两类解析域产物;主文档 handle 元素带
+ // `__zwHandle` 身份不进本分支)。物化只发生在 adopt 时刻,物化成功即从
+ // 原父摘除(concept-node-adopt 第 1 步);真 wrapper 走下方既有分支零变化。
adoptNode: function(node) {
if (!node || typeof node !== 'object') return node;
if ((node.nodeType | 0) === 9) {
throw new (globalThis.DOMException || Error)(
'Cannot adopt a Document node.', 'NotSupportedError');
}
+ if (node.nodeType === 1 && !node.__zwHandle && !node.__zwSelector
+ && ((typeof _zwParseEl === 'function' && node instanceof _zwParseEl)
+ || node.__zwIsMEl === true)) {
+ try {
+ // 查询产物(_zwParseEl)经 R3019 lazy 桥转 live 可变树(_zwMEl);本地
+ // 可变树产物(_zwMEl,如 doc.body.firstChild)直接物化。
+ var _t8qTree = (typeof node._ensureMutTree === 'function' && node instanceof _zwParseEl)
+ ? node._ensureMutTree() : node;
+ if (_t8qTree) {
+ var _t8qMat = _wcRebuildAsHandle(_t8qTree);
+ if (_t8qMat && _t8qMat !== node && _t8qMat.nodeType === 1) {
+ // t8q 返修(PR #129 双审查②#1/#2):spec concept-node-adopt 第 1 步
+ // ——adopt 先从原父摘除。物化返回的是重建副本,原节点若留在原树,
+ // adopt 退化为复制(站点双引用消费时内容重复;快照文档
+ // body.firstChild 仍返原节点)。经原父 removeChild 摘除(_zwParseEl
+ // R3019 桥与 _zwMEl 本地树同一语义面);失败不阻断物化(残留回旧
+ // 状态——detached 弃置文档域,无实际消费面)。
+ try {
+ var _t8qP = node.parentNode;
+ if (_t8qP && typeof _t8qP.removeChild === 'function') _t8qP.removeChild(node);
+ } catch (_eT8qCut) {}
+ return _t8qMat;
+ }
+ }
+ } catch (_eT8qMat) {}
+ // 物化不可行 → 落入下方既有分支(identity 返回兜底)。
+ }
try {
if (node.parentNode && typeof node.parentNode.removeChild === 'function') {
node.parentNode.removeChild(node);