From 77f37d7ff1ae1dacb8e9cfb3a725cc77a5fa699f Mon Sep 17 00:00:00 2001 From: LEI Zongmin Date: Sun, 11 Oct 2026 01:17:14 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(engine-dom):=20t8s1=20DOMParser=20?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=20Text/Comment=20adopt=20=E7=89=A9=E5=8C=96?= =?UTF-8?q?=E2=80=94=E2=80=94insertBefore=20ref=20=E6=97=A0=20handle=20?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E4=B8=A2=E5=85=83=E7=B4=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真站 bilibili 播放器模板引擎 T.d(npd.314 bundle 模块 86806)用 `while(body.lastChild) frag.insertBefore(adoptNode(body.lastChild), frag.firstChild||null)` 倒序排空 DOMParser 快照 body:renderFragment HTML 带尾随空白 → 末子是文本。 t8q(PR #129)adoptNode 物化只覆盖 nodeType 1——快照文本 identity 返回无 host handle;其插入 fragment 成功但作 ref 时 host 桥按 ref handle 定位失败 → 第二轮的元素静默丢失 → `.bpx-video-info` 注册表查询全 miss → npd.314 四处 `template.info/online` null 崩(resize/hideDm/renderParts/classList)。 修复:_zwMText/_zwMComment 盖 __zwIsMEl 家族印章(与 _zwMEl 同款 non-enumerable,唯一消费者是 adoptNode 落地分支守卫);守卫与物化返回 判定扩至 nodeType 1|3|8。spec concept-node-adopt「完整搬入本文档」。 验证: - 钉测 ×2(js_dom_bridge_tests/part18.rs):T.d 倒序排空真站同构 + Comment ref/数据保真/原父摘除/identity 零回归;红态验证复现崩读数 (fragTypes:3|barInfo:null)后恢复修复复跑绿 - zero-engine lib 2967 passed;make test MAKE_EXIT=0 - clippy v8 全 workspace + quickjs 腿 -D warnings 零告警;fmt 干净 - 真站验证:docker=true 完整态两轮 errCount=0(修复前同态 9-10 条同簇), npd.314 chunk 确认加载 Co-Authored-By: Claude Code --- .../engine/src/js_dom_bridge_tests/part18.rs | 120 +++++++++++++++++- crates/engine/src/js_dom_shim/part03.js | 9 +- crates/engine/src/js_dom_shim/part06.js | 15 ++- 3 files changed, 140 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/js_dom_bridge_tests/part18.rs b/crates/engine/src/js_dom_bridge_tests/part18.rs index 776cec74d..c41488697 100644 --- a/crates/engine/src/js_dom_bridge_tests/part18.rs +++ b/crates/engine/src/js_dom_bridge_tests/part18.rs @@ -2156,7 +2156,8 @@ fn t8q_adopt_node_materializes_dom_parser_snapshot_element() { // t8q 邻近变体:① 快照后代元素(querySelector 产物)adopt 同样物化;② 元素物化的 // 子树保真——根元素重建时内部 Text/Comment 经 _wcRebuildAsHandle 递归物化为对应 -// 类型节点(顶层裸 Text/Comment 快照直接 adopt 的物化不在本修复域,见池记录); +// 类型节点(顶层裸 Text/Comment 快照直接 adopt 的物化已由 t8s1 扩入修复域—— +// npd.314 播放器崩簇根因,见下方 t8s1 钉测); // ③ 同文档真元素 adoptNode 保持 identity 返回(R192 语义零回归)。 #[test] fn t8q_adopt_node_snapshot_variants_and_identity_regression() { @@ -2211,6 +2212,123 @@ fn t8q_adopt_node_snapshot_variants_and_identity_regression() { ); } +// t8s1(site-compat bilibili-20261002-r1):快照顶层裸 Text/Comment adopt 物化—— +// 真站 bilibili 播放器模板引擎 T.d(模块 86806)用 +// `while(body.lastChild) frag.insertBefore(adoptNode(body.lastChild), frag.firstChild||null)` +// 倒序排空 DOMParser 快照 body(renderFragment HTML 带尾随空白 → body 末子是文本): +// 首轮 adopt 的快照文本(t8q 物化域外 → 无 host handle)作 ref 时,host 桥按 ref +// handle 定位失败 → 第二轮的元素静默丢失 → `.bpx-video-info` 注册表查询全 miss → +// npd.314 四处 `template.info/online` null 崩(resize/hideDm/renderParts/classList)。 +// spec https://dom.spec.whatwg.org/#concept-node-adopt(adopt 完整搬入本文档)。 +#[test] +fn t8s1_adopted_snapshot_text_ref_does_not_silently_drop_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/t8s1-adopt-text".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 r = []; + // T.d 倒序排空真站同构形态:div(含嵌套 online)+ 尾空白 → body [1,3]。 + var doc = new DOMParser().parseFromString('
5
\n ', 'text/html'); + var frag = document.createDocumentFragment(); + var guard = 0; + while (doc.body.lastChild && guard++ < 20) { + frag.insertBefore(document.adoptNode(doc.body.lastChild), frag.firstChild || null); + } + // ① 排空完成且元素在 frag(修复前:ref=无 handle 快照文本 → 元素静默丢失)。 + var types = []; + for (var i = 0; i < frag.childNodes.length; i++) types.push(frag.childNodes[i].nodeType); + r.push('fragTypes:' + types.join(',')); + r.push('fragInfo:' + (frag.querySelector('.bpx-video-info') ? 'hit' : 'miss')); + // ② v.W 式消费:清空容器 + append fragment → 相对查询 + style 写(npd.314 四崩点消费面)。 + var bar = document.createElement('div'); + bar.appendChild(document.createElement('span')); + while (bar.lastChild) bar.removeChild(bar.lastChild); + bar.appendChild(frag); + var info = bar.querySelector('.bpx-video-info'); + r.push('barInfo:' + (info ? 'hit' : 'null')); + var sw = 'nostyle'; + try { if (info && info.style) { info.style.width = '100px'; sw = String(info.style.width); } } catch (e) { sw = 'throw:' + e.name; } + r.push('styleW:' + sw); + r.push('online:' + (bar.querySelector('.bpx-video-info-online') ? 'hit' : 'null')); + return r.join('|'); +})()"#, + ) + .unwrap() + .value; + assert_eq!( + out, + "fragTypes:1,3|fragInfo:hit|barInfo:hit|styleW:100px|online:hit", + "t8s1:快照文本 adopt 物化后 T.d 倒序排空元素不再静默丢失(frag 保元素 + 注册表查询/style 消费全通)" + ); +} + +// t8s1 邻近变体:① adopted Comment 作 ref 同域;② 快照文本物化数据保真 + 原父摘除 +//(spec concept-node-adopt 第 1 步,t8q 返修同款);③ 快照文本不 adopt 的 identity +// 消费零回归(data/textContent 读照旧);④ handle 真文本 adopt 保持 identity(R192)。 +#[test] +fn t8s1_adopted_snapshot_comment_ref_and_identity_regressions() { + 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/t8s1-adopt-comment".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 r = []; + // ① adopted Comment 作 ref:后续 insert 元素不丢。 + var d1 = new DOMParser().parseFromString('px', 'text/html'); + var cf = document.createDocumentFragment(); + while (d1.body.lastChild) cf.insertBefore(document.adoptNode(d1.body.lastChild), cf.firstChild || null); + r.push('cFrag:' + (cf.querySelector('i') ? 'hit' : 'miss') + '/' + (cf.childNodes.length >= 2 ? 'both' : 'part:' + cf.childNodes.length)); + // ② 快照文本物化保真:data 内容逐字保留 + 原父摘除。 + var d2 = new DOMParser().parseFromString('x\n tail ', 'text/html'); + var at = document.adoptNode(d2.body.lastChild); + r.push('matType:' + at.nodeType + '|data:' + (at.data === '\n tail ' ? 'exact' : 'bad:' + JSON.stringify(at.data))); + r.push('cut:' + (d2.body.lastChild && d2.body.lastChild.nodeType === 1 ? 'ok' : 'bad')); + // ③ 快照文本不 adopt 的 identity 消费零回归。 + var d3 = new DOMParser().parseFromString('plain\nraw', 'text/html'); + var raw = d3.body.lastChild; + r.push('rawRead:' + (raw && raw.nodeType === 3 && raw.data === '\nraw' ? 'ok' : 'bad')); + // ④ handle 真文本 adopt identity(R192 语义)。 + var lt = document.createTextNode('live'); + r.push('liveIdent:' + (document.adoptNode(lt) === lt ? 'ok' : 'bad')); + return r.join('|'); +})()"#, + ) + .unwrap() + .value; + assert_eq!( + out, + "cFrag:hit/both|matType:3|data:exact|cut:ok|rawRead:ok|liveIdent:ok", + "t8s1:adopted Comment ref 同域 + 物化数据保真/原父摘除 + 快照与 handle 文本 identity 零回归" + ); +} + // t8r(site-compat bilibili-20261002-r1,t8q-1 本地产物接口完整化):innerHTML 注入 // 本地视图产物物化。真站链:`area.innerHTML = tpl()` → `area.querySelector(".bar-normal")` // → `bar.style.webkitTransform = …`(volume npd.586 @425567、progress npd.911 @72303 同型)—— diff --git a/crates/engine/src/js_dom_shim/part03.js b/crates/engine/src/js_dom_shim/part03.js index 457a62549..88e30b842 100644 --- a/crates/engine/src/js_dom_shim/part03.js +++ b/crates/engine/src/js_dom_shim/part03.js @@ -11941,9 +11941,16 @@ } catch (_eR202d) {} } function _zwMText(v, parent) { var t = String(v); var n = { nodeType: 3, nodeName: '#text', nodeValue: t, textContent: t, data: t, childNodes: [], children: [], hasChildNodes: function () { return false; }, contains: function (other) { return _zwNodeContains(n, other); }, compareDocumentPosition: function (other) { return _zwCompareDocumentPosition(n, other); }, parentNode: parent || null }; _zwMDefineSiblings(n); Object.defineProperty(n, 'firstChild', { get: function () { return null; }, configurable: true }); Object.defineProperty(n, 'lastChild', { get: function () { return null; }, configurable: true }); Object.defineProperty(n, 'parentElement', { get: function () { var p = n.parentNode; return p && p.nodeType === 1 ? p : null; }, configurable: true }); Object.defineProperty(n, 'length', { get: function () { return n.data.length; }, configurable: true }); Object.defineProperty(n, 'wholeText', { get: function () { var p = n.parentNode; if (!p || !p.childNodes) return n.data; var kids = p.childNodes, idx = -1, i2 = 0; for (i2 = 0; i2 < kids.length; i2++) { if (kids[i2] === n) { idx = i2; break; } } if (idx < 0) return n.data; var lo = idx, hi = idx; for (var l2 = idx - 1; l2 >= 0; l2--) { if (!kids[l2] || kids[l2].nodeType !== 3) break; lo = l2; } for (var h2 = idx + 1; h2 < kids.length; h2++) { if (!kids[h2] || kids[h2].nodeType !== 3) break; hi = h2; } var t2 = ''; for (var o2 = lo; o2 <= hi; o2++) { var c = kids[o2]; if (c && c.nodeType === 3) t2 += String(c.data != null ? c.data : ''); } return t2 || n.data; }, configurable: true }); /* R179:原型链 Text.prototype(spec Text : CharacterData : Node——`x instanceof Text` 断言;WPT node-creation-realm 的 innerHTML 解析子) */ try { Object.setPrototypeOf(n, (globalThis.Text && globalThis.Text.prototype) || (globalThis.Node ? globalThis.Node.prototype : Object.prototype)); } catch (_eR117t) {} + // t8s1(site-compat bilibili-20261002-r1):M 节点家族印章扩展到 Text——DOMParser + // 快照域顶层裸文本(_ensureMutTree 的 _zwMText 产物)无本印时 adoptNode 落地分支 + // 不识别 → identity 返回无 host handle 的快照文本;其作 insertBefore ref 时 host + // 桥按 ref handle 定位失败静默丢节点(bilibili npd.314 播放器崩簇根因——模板 + // renderFragment HTML 前导空白经 T.d `while(body.lastChild)` 倒序排空首子为文本)。 + // spec https://dom.spec.whatwg.org/#concept-node-adopt「adopt 完整搬入本文档」。 + try { Object.defineProperty(n, '__zwIsMEl', { value: true, enumerable: false }); } catch (_eMElTagT) {} _zwAttachCharacterDataMethods(n); return n; } - function _zwMComment(v, parent) { var t = String(v); var n = { nodeType: 8, nodeName: '#comment', nodeValue: t, textContent: t, data: t, childNodes: [], children: [], hasChildNodes: function () { return false; }, contains: function (other) { return _zwNodeContains(n, other); }, compareDocumentPosition: function (other) { return _zwCompareDocumentPosition(n, other); }, parentNode: parent || null }; _zwMDefineSiblings(n); Object.defineProperty(n, 'firstChild', { get: function () { return null; }, configurable: true }); Object.defineProperty(n, 'lastChild', { get: function () { return null; }, configurable: true }); Object.defineProperty(n, 'parentElement', { get: function () { var p = n.parentNode; return p && p.nodeType === 1 ? p : null; }, configurable: true }); Object.defineProperty(n, 'length', { get: function () { return n.data.length; }, configurable: true }); /* R179:原型链 Comment.prototype(spec Comment : CharacterData : Node——`x instanceof Comment` 断言;WPT node-creation-realm 的 cloneNode/importNode) */ try { if (globalThis.Comment && globalThis.Comment.prototype) Object.setPrototypeOf(n, globalThis.Comment.prototype); } catch (_e179n) {} _zwAttachCharacterDataMethods(n); return n; } + function _zwMComment(v, parent) { var t = String(v); var n = { nodeType: 8, nodeName: '#comment', nodeValue: t, textContent: t, data: t, childNodes: [], children: [], hasChildNodes: function () { return false; }, contains: function (other) { return _zwNodeContains(n, other); }, compareDocumentPosition: function (other) { return _zwCompareDocumentPosition(n, other); }, parentNode: parent || null }; _zwMDefineSiblings(n); Object.defineProperty(n, 'firstChild', { get: function () { return null; }, configurable: true }); Object.defineProperty(n, 'lastChild', { get: function () { return null; }, configurable: true }); Object.defineProperty(n, 'parentElement', { get: function () { var p = n.parentNode; return p && p.nodeType === 1 ? p : null; }, configurable: true }); Object.defineProperty(n, 'length', { get: function () { return n.data.length; }, configurable: true }); /* R179:原型链 Comment.prototype(spec Comment : CharacterData : Node——`x instanceof Comment` 断言;WPT node-creation-realm 的 cloneNode/importNode) */ try { if (globalThis.Comment && globalThis.Comment.prototype) Object.setPrototypeOf(n, globalThis.Comment.prototype); } catch (_e179n) {} /* t8s1:M 家族印章同 _zwMText——快照顶层裸 Comment adopt 物化同域(concept-node-adopt)。 */ try { Object.defineProperty(n, '__zwIsMEl', { value: true, enumerable: false }); } catch (_eMElTagC) {} _zwAttachCharacterDataMethods(n); return n; } // 递归建子树:entry = {k:'E',s:sel}/{k:'T',v}/{k:'C',v}(__zw_parse_html_child_nodes)。元素取快照 + 递归子。 // R123:`` 的 bogus comment(data '?…?'——tokenizer 在首个 '>' 结束并保留 '?' 前缀) // 转换为 PI 视图节点(nodeType 7)——Chrome「Parse processing instructions in HTML」后 diff --git a/crates/engine/src/js_dom_shim/part06.js b/crates/engine/src/js_dom_shim/part06.js index d00a2697b..888887bc3 100644 --- a/crates/engine/src/js_dom_shim/part06.js +++ b/crates/engine/src/js_dom_shim/part06.js @@ -3364,7 +3364,17 @@ throw new (globalThis.DOMException || Error)( 'Cannot adopt a Document node.', 'NotSupportedError'); } - if (node.nodeType === 1 && !node.__zwHandle && !node.__zwSelector + // t8s1(site-compat bilibili-20261002-r1):物化域扩展到快照顶层裸 Text/Comment + // ——t8q 只覆盖元素(nodeType 1);真站 bilibili 播放器模板引擎 T.d 用 + // `while(body.lastChild) frag.insertBefore(adoptNode(body.lastChild), frag.firstChild||null)` + // 倒序排空(renderFragment HTML 带前导空白 → 首子是文本):快照文本 adopt 后 + // 无 host handle,插入 fragment 成功但其作 ref 时 host 桥按 ref handle 定位 + // 失败 → 后续元素静默丢失 → `.bpx-video-info` 注册表查询全 miss → npd.314 + // 四处 `template.info/online` null 崩。spec concept-node-adopt 要求完整搬入 + // 本文档——快照 Text/Comment(_zwMText/_zwMComment 印章识别)同走物化。 + // https://dom.spec.whatwg.org/#concept-node-adopt + if ((node.nodeType === 1 || node.nodeType === 3 || node.nodeType === 8) + && !node.__zwHandle && !node.__zwSelector && ((typeof _zwParseEl === 'function' && node instanceof _zwParseEl) || node.__zwIsMEl === true)) { try { @@ -3374,7 +3384,8 @@ ? node._ensureMutTree() : node; if (_t8qTree) { var _t8qMat = _wcRebuildAsHandle(_t8qTree); - if (_t8qMat && _t8qMat !== node && _t8qMat.nodeType === 1) { + if (_t8qMat && _t8qMat !== node + && (_t8qMat.nodeType === 1 || _t8qMat.nodeType === 3 || _t8qMat.nodeType === 8)) { // t8q 返修(PR #129 双审查②#1/#2):spec concept-node-adopt 第 1 步 // ——adopt 先从原父摘除。物化返回的是重建副本,原节点若留在原树, // adopt 退化为复制(站点双引用消费时内容重复;快照文档 From 153b129352b00f9cafd25404df0de718e1e3c134 Mon Sep 17 00:00:00 2001 From: LEI Zongmin Date: Sun, 11 Oct 2026 01:52:46 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(engine-dom):=20t8s1=20=E8=BF=94?= =?UTF-8?q?=E4=BF=AE=E2=80=94=E2=80=94=E5=8F=8C=E5=AE=A1=E6=9F=A5=E9=A6=96?= =?UTF-8?q?=E8=BD=AE=E7=BB=9F=E4=B8=80=20nit=20=E9=97=AD=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ①前导/尾随空白表述统一(首轮 A nit4 / B nit2);②_zwMText 印章注释 如实记录盖章面拓宽的同文档 adopt identity 偏差面(B nit1);③变体钉测 补 Comment data 逐字断言(A nit1);④补物化产物二次 adopt identity 直接断言(A nit2)。返修后 t8s1 钉测 2 passed + engine lib 2967 passed + fmt 干净。 Co-Authored-By: Claude Code --- crates/engine/src/js_dom_bridge_tests/part18.rs | 14 ++++++++++---- crates/engine/src/js_dom_shim/part03.js | 5 ++++- crates/engine/src/js_dom_shim/part06.js | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/js_dom_bridge_tests/part18.rs b/crates/engine/src/js_dom_bridge_tests/part18.rs index c41488697..c3d56b5df 100644 --- a/crates/engine/src/js_dom_bridge_tests/part18.rs +++ b/crates/engine/src/js_dom_bridge_tests/part18.rs @@ -2300,9 +2300,12 @@ fn t8s1_adopted_snapshot_comment_ref_and_identity_regressions() { .execute( r#"(function(){ var r = []; - // ① adopted Comment 作 ref:后续 insert 元素不丢。 + // ① adopted Comment 作 ref:后续 insert 元素不丢;物化 Comment data 逐字保真。 var d1 = new DOMParser().parseFromString('px', 'text/html'); var cf = document.createDocumentFragment(); + var ac = document.adoptNode(d1.body.lastChild); + r.push('cData:' + (ac.nodeType === 8 && ac.data === 'anchor' ? 'ok' : 'bad:' + ac.nodeType + '/' + JSON.stringify(ac.data))); + cf.insertBefore(ac, cf.firstChild || null); while (d1.body.lastChild) cf.insertBefore(document.adoptNode(d1.body.lastChild), cf.firstChild || null); r.push('cFrag:' + (cf.querySelector('i') ? 'hit' : 'miss') + '/' + (cf.childNodes.length >= 2 ? 'both' : 'part:' + cf.childNodes.length)); // ② 快照文本物化保真:data 内容逐字保留 + 原父摘除。 @@ -2314,9 +2317,12 @@ fn t8s1_adopted_snapshot_comment_ref_and_identity_regressions() { var d3 = new DOMParser().parseFromString('plain\nraw', 'text/html'); var raw = d3.body.lastChild; r.push('rawRead:' + (raw && raw.nodeType === 3 && raw.data === '\nraw' ? 'ok' : 'bad')); - // ④ handle 真文本 adopt identity(R192 语义)。 + // ④ handle 真文本 adopt identity(R192 语义);物化产物(带 handle)二次 adopt 同样 identity。 var lt = document.createTextNode('live'); r.push('liveIdent:' + (document.adoptNode(lt) === lt ? 'ok' : 'bad')); + var d4 = new DOMParser().parseFromString('q\nagain', 'text/html'); + var mat = document.adoptNode(d4.body.lastChild); + r.push('readopt:' + (document.adoptNode(mat) === mat ? 'ok' : 'bad')); return r.join('|'); })()"#, ) @@ -2324,8 +2330,8 @@ fn t8s1_adopted_snapshot_comment_ref_and_identity_regressions() { .value; assert_eq!( out, - "cFrag:hit/both|matType:3|data:exact|cut:ok|rawRead:ok|liveIdent:ok", - "t8s1:adopted Comment ref 同域 + 物化数据保真/原父摘除 + 快照与 handle 文本 identity 零回归" + "cData:ok|cFrag:hit/both|matType:3|data:exact|cut:ok|rawRead:ok|liveIdent:ok|readopt:ok", + "t8s1:adopted Comment ref 同域 + 物化数据保真/原父摘除 + 快照与 handle/物化产物 identity 零回归" ); } diff --git a/crates/engine/src/js_dom_shim/part03.js b/crates/engine/src/js_dom_shim/part03.js index 88e30b842..6db06a334 100644 --- a/crates/engine/src/js_dom_shim/part03.js +++ b/crates/engine/src/js_dom_shim/part03.js @@ -11945,8 +11945,11 @@ // 快照域顶层裸文本(_ensureMutTree 的 _zwMText 产物)无本印时 adoptNode 落地分支 // 不识别 → identity 返回无 host handle 的快照文本;其作 insertBefore ref 时 host // 桥按 ref handle 定位失败静默丢节点(bilibili npd.314 播放器崩簇根因——模板 - // renderFragment HTML 前导空白经 T.d `while(body.lastChild)` 倒序排空首子为文本)。 + // renderFragment HTML 尾随空白经 T.d `while(body.lastChild)` 倒序排空末子为文本)。 // spec https://dom.spec.whatwg.org/#concept-node-adopt「adopt 完整搬入本文档」。 + // 已知偏差面(PR #132 审查 B):盖章面含 new Text()/cloneNode 等全部 M 家族产物—— + // 其同文档 adopt 由 R192 identity 变为物化副本(spec adopt-node 要求返回自身); + // 当前无内部 adoptNode 调用方、无测试/站点消费面受害;如需 identity 保真再拆独立印章。 try { Object.defineProperty(n, '__zwIsMEl', { value: true, enumerable: false }); } catch (_eMElTagT) {} _zwAttachCharacterDataMethods(n); return n; } diff --git a/crates/engine/src/js_dom_shim/part06.js b/crates/engine/src/js_dom_shim/part06.js index 888887bc3..84d95d04c 100644 --- a/crates/engine/src/js_dom_shim/part06.js +++ b/crates/engine/src/js_dom_shim/part06.js @@ -3367,7 +3367,7 @@ // t8s1(site-compat bilibili-20261002-r1):物化域扩展到快照顶层裸 Text/Comment // ——t8q 只覆盖元素(nodeType 1);真站 bilibili 播放器模板引擎 T.d 用 // `while(body.lastChild) frag.insertBefore(adoptNode(body.lastChild), frag.firstChild||null)` - // 倒序排空(renderFragment HTML 带前导空白 → 首子是文本):快照文本 adopt 后 + // 倒序排空(renderFragment HTML 尾随空白 → body 末子是文本):快照文本 adopt 后 // 无 host handle,插入 fragment 成功但其作 ref 时 host 桥按 ref handle 定位 // 失败 → 后续元素静默丢失 → `.bpx-video-info` 注册表查询全 miss → npd.314 // 四处 `template.info/online` null 崩。spec concept-node-adopt 要求完整搬入