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
115 changes: 115 additions & 0 deletions crates/engine/src/js_dom_bridge_tests/part18.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 后序列化为空 `<div></div>`、不进文档查询。
// 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::<DomMutation>::new()));
let dom_html = Arc::new(Mutex::new("<html><body><div id='host'></div></body></html>".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 = '<div class="bpx-player-container"><div class="bpx-player-video-area"><div class="bpx-player-video-wrap"></div></div></div>';
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 后为空 <div></div>)。
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::<DomMutation>::new()));
let dom_html = Arc::new(Mutex::new("<html><body></body></html>".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('<ul class="u"><li class="i">t</li><!-- lc --></ul>', '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('<ul><li class="i">t</li></ul>', '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('<x/>', '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 抛错零回归"
);
}
5 changes: 5 additions & 0 deletions crates/engine/src/js_dom_shim/part03.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)经此读文档。归建树设
Expand Down
41 changes: 41 additions & 0 deletions crates/engine/src/js_dom_shim/part06.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 处崩溃实证);② 序列化为空壳 `<div></div>`;
// ③ 不进文档查询。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);
Expand Down
Loading