From 3d16eaa1854a955aa5c68b3edfc8862e81d671e4 Mon Sep 17 00:00:00 2001 From: LEI Zongmin Date: Sat, 10 Oct 2026 20:08:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(engine-dom):=20sel=20=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=20innerHTML=20=E6=9F=A5=E8=AF=A2=E6=97=B6=E7=89=A9=E5=8C=96?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E4=BB=A3=E7=90=86=EF=BC=8C=E4=BF=AE=20nano?= =?UTF-8?q?=20widget=20=E6=A8=A1=E6=9D=BF=E6=B3=A8=E5=86=8C=E8=A1=A8=20nul?= =?UTF-8?q?l=20=E7=B0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 树内容器 innerHTML 后 host apply 异步,立即相对查询走 host 快照 miss → null(bilibili nano sendBar widget 注册表 this.template.info/online 等全 null,resize/hideDm/renderOnlineCount 消费崩,npd.314)。R322 host-miss 兜底只扫桶 added 的 handle 子树,sel 路径产物 _zwMEl 解析代理不可见。 修复为查询时物化:R322 兜底臂扩扫桶 added 解析顶层代理(R304 槽、无 handle 无 sel、parentSel 匹配),_matchCompoundOf 命中现场 _wcRebuildAsHandle 物化并原位替换桶成员/全局表/by-id 索引 + 写 sel 域父 反链 + _zwMatParsed 标记;K3 apply 代际清除加 _zwMatParsed wrapper 臂 (反链定位、同批清),消 apply 后 overlay 重复并入。setter 记账面 (mutation 队列/CE attach/_mo_notify)保持解析代理原样零变化。已 upgrade CE 代理按 prototype 印记判后返回代理,防二次 ctor。 第一版 setter 侧物化(_ihAdded 全量替换)引入 slice13 双计 + t7 host 序列化两簇回归,已否决;定案下三测试恢复绿。 验证:t8r-4 双钉红→绿(红态变异实证 info:null + styleWrite throw TypeError 精确命中 npd.314 消费崩形态);lib 全量 2964P/0F(+2 钉); WPT dom 套 55,085P/198F/16T vs 基线 55,084P/198F/17T,Fail 集合 diff 零差异(唯一差异 Event-dispatch-click 文件级 Timeout→Pass 正向翻转); make test exit 0;fmt CLEAN;clippy 双腿 -D warnings 全绿。 Co-Authored-By: Claude Code --- .../engine/src/js_dom_bridge_tests/part18.rs | 117 ++++++++++++++++++ crates/engine/src/js_dom_shim/part04.js | 59 ++++++++- crates/engine/src/js_dom_shim/part05.js | 23 +++- 3 files changed, 196 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/js_dom_bridge_tests/part18.rs b/crates/engine/src/js_dom_bridge_tests/part18.rs index b8fcca538..6ea2f5ca3 100644 --- a/crates/engine/src/js_dom_bridge_tests/part18.rs +++ b/crates/engine/src/js_dom_bridge_tests/part18.rs @@ -2406,3 +2406,120 @@ fn t8r_inner_html_materialized_custom_element_ctor_once() { "t8r N1:innerHTML 物化的自定义元素 ctor 恰 1 次,查询产物为真实升级实例,createElement 基线不受扰" ); } + +// t8r-4(site-compat bilibili-20261002-r1 续):sel 路径 innerHTML 同步查询可见性。 +// 真站链:bilibili nano sendBar(树内元素)`(0,v.W)(sendBar, renderFragment())`(HTML +// 串注入)→ `this.template = { info: sendBar.querySelector('.{i}-video-info'), online: +// sendBar.querySelector('.{i}-video-info-online'), … }` → `template.info.style` 消费。 +// ZW 现状:树内容器 innerHTML 后 host apply 异步,立即相对查询走 host 快照 miss → +// null(注册表成员全 null → resize/hideDm/renderOnlineCount 消费崩,npd.314 null 簇)。 +// 修复:R322 host-miss 兜底臂扩扫桶 added 的解析顶层代理(R304 槽、无 handle 无 +// sel)——compound 匹配命中现场 _wcRebuildAsHandle 物化为 detached 真 handle 树 +//(全接口),原位替换桶成员/全局表/by-id 索引 + 写 sel 域父反链 + `_zwMatParsed` +// 标记(apply 代际边界 K3 定点清除)。setter 记账面(mutation 队列/CE attach/ +// _mo_notify/K3)保持解析代理原样零变化。已 upgrade CE 代理按代理返回不物化 +//(防二次 ctor)。 +// spec https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml +#[test] +fn t8r4_sel_path_inner_html_immediate_query_materialized() { + 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/t8r4-sel".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 = []; + var sendbar = document.querySelector('#sendbar'); + sendbar.innerHTML = '
x
'; + // ① 立即相对查询命中且全接口(真站 TypeError 根面)。 + var info = sendbar.querySelector('.bpx-video-info'); + r.push('info:' + (info === null ? 'null' : (typeof info.style === 'object' ? 'wrapper' : 'proxy'))); + var online = info ? info.querySelector('.bpx-video-info-online') : null; + r.push('online:' + (online && typeof online.style === 'object' ? 'ok' : 'bad')); + // ② querySelectorAll 融合(R309 兜底面)。 + var all = sendbar.querySelectorAll('div'); + r.push('qsa:' + (all.length === 2 ? 'ok' : 'len:' + all.length)); + // ③ childNodes/firstChild 融合(R304/R56 既有面零回归)。 + r.push('fc:' + (sendbar.firstChild && sendbar.firstChild.nodeType === 1 ? 'ok' : 'bad')); + r.push('cc:' + (sendbar.childNodes.length === 2 ? 'ok' : 'len:' + sendbar.childNodes.length)); + // ④ parentNode 反链(sel 域 append 家族同款)。 + r.push('parent:' + (info && info.parentNode === sendbar ? 'ok' : 'bad')); + // ⑤ 接口写读回(真站消费形态:this.template.info.style.width = …)。 + var sw = 'ok'; + try { info.style.setProperty('opacity', '0.6'); sw = info.style.getPropertyValue('opacity') === '0.6' ? 'ok' : 'bad'; } + catch (e) { sw = 'throw:' + e.name; } + r.push('styleWrite:' + sw); + // ⑥ 结构保真(第二顶层子 + 深层文本)。 + var dm = sendbar.querySelector('.bpx-video-dm'); + r.push('sib:' + (dm && online && online.textContent === 'x' ? 'ok' : 'bad')); + return r.join('|'); +})()"#).unwrap().value; + assert_eq!( + out, + "info:wrapper|online:ok|qsa:ok|fc:ok|cc:ok|parent:ok|styleWrite:ok|sib:ok", + "t8r-4:sel 路径 innerHTML 后立即相对查询命中物化 wrapper(querySelector/querySelectorAll/childNodes/反链/接口写读回/结构保真)" + ); +} + +// t8r-4 变体:① 纯文本 sel 路径零回归(R34xx/R100 文本注册面);② 混合 markup+文本顶层; +// ③ 清空重注(二次 innerHTML 后新产物仍可查);④ sel 域 CE attach——setter 链已对解析 +// 代理 upgrade(prototype 印记),R322 扩扫臂判已 upgrade 按代理返回不物化,ctor 恒 1。 +#[test] +fn t8r4_sel_path_variants_and_ce_once() { + 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/t8r4-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 r = []; + // ① 纯文本 sel 路径零回归。 + var sb = document.querySelector('#sb2'); + sb.innerHTML = 'plain text'; + r.push('text:' + (sb.firstChild && sb.firstChild.nodeType === 3 && sb.firstChild.data === 'plain text' ? 'ok' : 'bad')); + // ② 混合 markup+文本顶层:元素子物化、尾文本子保持代理。 + sb.innerHTML = 'xtail'; + var hb = sb.querySelector('.h'); + var tl = sb.childNodes[1]; + r.push('mix:' + (hb && typeof hb.style === 'object' && tl && tl.nodeType === 3 && tl.data === 'tail' ? 'ok' : 'bad')); + // ③ 清空重注:二次 innerHTML 后新产物仍可查(旧物化树被替换)。 + var sb3 = document.querySelector('#sb3'); + sb3.innerHTML = '1'; + var first = sb3.querySelector('.a'); + sb3.innerHTML = '2'; + var second = sb3.querySelector('.b'); + r.push('reIh:' + (first && second && typeof second.style === 'object' ? 'ok' : 'bad')); + // ④ sel 域 CE attach ctor 恒 1(createElement 物化构造 + attach 不重复 upgrade)。 + var made = 0; + class T8r4Ce extends HTMLElement { + constructor() { super(); made++; } + } + customElements.define('t8r4-ce', T8r4Ce); + sb3.innerHTML = ''; + var q = sb3.querySelector('t8r4-ce'); + r.push('ctor:' + made); + r.push('isInst:' + (q instanceof T8r4Ce ? 'ok' : 'bad')); + return r.join('|'); +})()"#).unwrap().value; + assert_eq!( + out, + "text:ok|mix:ok|reIh:ok|ctor:1|isInst:ok", + "t8r-4:纯文本/混合顶层/清空重注零回归 + sel 域 CE attach ctor 恰 1 次" + ); +} diff --git a/crates/engine/src/js_dom_shim/part04.js b/crates/engine/src/js_dom_shim/part04.js index 4ee8f74cd..7cde2baa6 100644 --- a/crates/engine/src/js_dom_shim/part04.js +++ b/crates/engine/src/js_dom_shim/part04.js @@ -7985,7 +7985,57 @@ if (_r322bq && _r322bq.added.length) { for (var _r322qa = 0; _r322qa < _r322bq.added.length; _r322qa++) { var _r322qn = _r322bq.added[_r322qa]; - if (!_r322qn || !_r322qn.__zwHandle) continue; + if (!_r322qn || !_r322qn.__zwHandle) { + // t8r-4(site-compat bilibili-20261002-r1 续):sel 路径 innerHTML 的 + // 解析代理物化兜底——setter 落树的 host apply 异步,窗口内立即相对 + // 查询走 host 快照 miss(npd.314:bilibili nano sendBar widget 注册 + // 表 this.template.info/online 等全 null → resize/hideDm/ + // renderOnlineCount 消费崩)。桶 added 的解析顶层代理(R304 槽、 + // 无 handle 无 sel)compound 匹配命中即现场 _wcRebuildAsHandle + // 物化为 detached 真 handle 树(全接口),原位替换桶成员/全局表/ + // by-id 索引并写 sel 域父反链(overlay/childNodes 融合同一 + // wrapper,R309 identity 双源教训),`_zwMatParsed` 标记供 apply + // 代际边界 K3 定点清除(快照已含本 turn——与解析补偿 added 同批 + // 作废)。物化失败回退代理原样(可见性保持既有 R3254-K3 代理匹配 + // 语义)。已 upgrade 的 CE 代理不物化——createElement 会二次 ctor + // (t8r N1 查询面变体),原型已挂、instanceof 语义成立,按代理 + // 原样返回。 + // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml + if (!_r322qn || _r322qn.nodeType !== 1 || _r322qn.__zwSelector + || !_r322qn._zwSelPendingParent || _r322qn._zwSelPendingParent.parentSel !== sel) continue; + try { + if (!_matchCompoundOf(_r322qn, _r322compQ)) continue; + var _r322tag = ''; + try { _r322tag = String(_r322qn.localName || _r322qn.tagName || '').toLowerCase(); } catch (_e322tg) {} + var _r322isName = null; + try { _r322isName = _r322qn.getAttribute('is'); } catch (_e322is) {} + var _r322reg = null; + if (_r322isName && globalThis.__zwCERegistryLookup) { + try { _r322reg = globalThis.__zwCERegistryLookup(String(_r322isName), _r322tag); } catch (_e322lk) {} + } + if (!_r322reg && _r322tag && typeof _ce_registry !== 'undefined') _r322reg = _ce_registry[_r322tag] || null; + if (_r322reg && _r322reg.ctor) { + var _r322ceUp = false; + try { _r322ceUp = Object.getPrototypeOf(_r322qn) === _r322reg.ctor.prototype; } catch (_e322gp) {} + if (_r322ceUp) return _r322qn; + } + var _r322mw = (typeof _wcRebuildAsHandle === 'function') ? _wcRebuildAsHandle(_r322qn) : _r322qn; + if (_r322mw && _r322mw !== _r322qn && _r322mw.__zwHandle) { + _r322bq.added[_r322qa] = _r322mw; + if (_r322bq.addedSet) { _r322bq.addedSet.delete(_r322qn); _r322bq.addedSet.add(_r322mw); } + var _r322gi = _zwPendingAdded.indexOf(_r322qn); + if (_r322gi >= 0) _zwPendingAdded[_r322gi] = _r322mw; + if (_zwPendingAddedSet) { _zwPendingAddedSet.delete(_r322qn); _zwPendingAddedSet.add(_r322mw); } + _zwPAIdRemove(_r322qn); + _zwPAIdAdd(_r322mw); + _r322mw._zwMatParsed = true; + if (_zwNodeParent) { + _zwNodeParent[_r322mw.__zwHandle] = { parentSel: sel, parentHandle: null, nextSibling: _r322qn._zwSelPendingParent.nextSibling || null }; + } + } + return _r322mw || _r322qn; + } catch (_e322pm) { continue; } + } var _r322ql = (typeof _zwNodeParent !== 'undefined' && _zwNodeParent) ? _zwNodeParent[_r322qn.__zwHandle] : null; if (!_r322ql || _r322ql.parentSel !== sel) continue; var _r322qf = []; @@ -9624,6 +9674,13 @@ // pending-added 并入 = 同 turn firstChild/lastChild/childNodes 立即 // 反映新子;host apply(dom_html 换代)时 _zwChildBaseInvalidateAll // 全量失效换代(生命周期不变)。 + // t8r-4(site-compat bilibili-20261002-r1 续):sel 路径 setter **不做**本地物 + // 化——mutation 队列 addedNodes、CE attach、`_mo_notify` 桶展平与 K3 apply + // 代际清除全按解析代理原样记账(setter 物化会让 host apply 消费 wrapper + // 序列化出空壳、CE 双 ctor、apply 后桶残留双计)。查询面的立即可见性由 + // R322 兜底臂的解析代理物化承担(下方 querySelector host-miss 分支)—— + // spec innerHTML 同步替换子节点、后续查询立即可见。 + // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml if (!handle && typeof _zwChildBaseCache !== 'undefined') _zwChildBaseCache.set(sel, []); // R34xx:纯文本 innerHTML → 本地文本节点注册(selection-rects 的 // el.childNodes[0] 文本节点——created handle 元素无 sel,host 不可查)。 diff --git a/crates/engine/src/js_dom_shim/part05.js b/crates/engine/src/js_dom_shim/part05.js index 90ac92ae4..0c85115d0 100644 --- a/crates/engine/src/js_dom_shim/part05.js +++ b/crates/engine/src/js_dom_shim/part05.js @@ -10079,19 +10079,38 @@ _r3kStale.push(_r3knd); _zwPendingAdded.splice(_r3kpa, 1); } + // t8r-4:查询面物化的 wrapper(R322 兜底臂打 `_zwMatParsed`)同批作废—— + // 它带 handle,上方解析补偿条件豁免它;但 apply 后快照已含该子,桶内 + // wrapper + 父反链残留会让 overlay 重复并入(vue_mount_lands 双份 + // `

` 同型)。物化 wrapper 无槽,桶定位走父反链(下方清理 + // 循环 `_r3kMatLink` 臂),反链与标记同批清除。 + else if (_r3knd && _r3knd.__zwHandle && _r3knd._zwMatParsed) { + _r3kStale.push(_r3knd); + _zwPendingAdded.splice(_r3kpa, 1); + } } if (_r3kStale.length) { for (var _r3ks = 0; _r3ks < _r3kStale.length; _r3ks++) { _zwPendingAddedSet && _zwPendingAddedSet.delete(_r3kStale[_r3ks]); _zwPAIdRemove(_r3kStale[_r3ks]); var _r3kSlot = _r3kStale[_r3ks]._zwSelPendingParent; - var _r3kB = _zwPendingByParent.get(_r3kSlot && _r3kSlot.parentSel ? _r3kSlot.parentSel : '_h:' + String(_r3kSlot && _r3kSlot.parentHandle == null ? '' : _r3kSlot && _r3kSlot.parentHandle)); + // t8r-4:物化 wrapper 无槽——桶定位走父反链,并同批清反链与物化标记。 + var _r3kMatLink = (!_r3kSlot && _r3kStale[_r3ks]._zwMatParsed && _r3kStale[_r3ks].__zwHandle + && typeof _zwNodeParent !== 'undefined' && _zwNodeParent) + ? _zwNodeParent[_r3kStale[_r3ks].__zwHandle] : null; + var _r3kLink = _r3kMatLink || _r3kSlot; + var _r3kB = _zwPendingByParent.get(_r3kLink && _r3kLink.parentSel ? _r3kLink.parentSel : '_h:' + String(_r3kLink && _r3kLink.parentHandle == null ? '' : _r3kLink && _r3kLink.parentHandle)); if (_r3kB) { var _r3ki = _r3kB.added.indexOf(_r3kStale[_r3ks]); if (_r3ki >= 0) _r3kB.added.splice(_r3ki, 1); _r3kB.addedSet && _r3kB.addedSet.delete(_r3kStale[_r3ks]); } - delete _r3kStale[_r3ks]._zwSelPendingParent; + if (_r3kMatLink) { + try { delete _zwNodeParent[_r3kStale[_r3ks].__zwHandle]; } catch (_e3kmu) {} + try { delete _r3kStale[_r3ks]._zwMatParsed; } catch (_e3kmu2) {} + } else { + delete _r3kStale[_r3ks]._zwSelPendingParent; + } } } // R3254-E2 切片 13(editing goal,2026-09-08):**removed 补偿同批作废**—— From 9d4dfeda179f9dd1255cef737e26403f49a60810 Mon Sep 17 00:00:00 2001 From: LEI Zongmin Date: Sat, 10 Oct 2026 22:00:14 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(engine-dom):=20t8r-4=20=E8=BF=94?= =?UTF-8?q?=E4=BF=AE=E2=80=94=E2=80=94=E6=89=A9=E6=89=AB=E8=87=82=20noWire?= =?UTF-8?q?/CE=20=E5=AD=90=E6=A0=91=E5=8F=8C=E8=B1=81=E5=85=8D=20+=20K3=20?= =?UTF-8?q?=E4=BD=9C=E5=BA=9F=E8=87=82=E9=92=89=E6=B5=8B=EF=BC=8C=E5=8F=8C?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=20blocking=20=E5=85=A8=E9=97=AD=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 双审查统一返修(两独立首轮均 REQUEST_CHANGES): - noWire 豁免(审查② F1):R97/createContextualFragment 的 noWire 解析子 从未 wire 进 host 快照,物化打标被 K3 apply 代际作废后跨 turn 丢子 (审查实证 base post:1 → 修复初版 post:0)。匹配命中按代理返回(可见 性=代理匹配面一致),不物化不打标。 - CE 子树印记检测(审查② F2):setter 期 attach 已递归 upgrade 全子树, 物化根经 createElement 对已 upgrade CE 后代重复 ctor(实测 1→2)。物 化前展平子树逐元素反查 registry+prototype 印记,命中整条目回退代理。 - K3 作废臂钉测(审查① blocking):新钉 t8r4_k3_invalidate_nowire_and_ ce_subtree 三臂(host apply 序列作废/noWire 豁免/CE 后代单 ctor), 承重半区从零护栏到三变异红态判别(3≠2 / nwPost:lost / ctor:2,后两 者与审查探针读数逐字符一致)。 - 写前校验(审查② F6):物化(CE ctor 可执行页面代码)后校验桶槽未被 重入改动,防按物化前索引写腐化。 - 主钉负控制 `.nope`=null(审查① nit,防过匹配)。 验证:定向 6/6 绿;lib 全量 2965P/0F(+1 钉);WPT dom 套返修轮 55,085P/198F/16T 与首轮逐数一致(Fail/Pass diff 均空);make test exit 0(首跑超时为共用宿主兄弟会话构建负载挤压 900s 墙钟,负载窗口外 复跑零 60s 警告,环境超时非代码卡死);fmt/clippy 双腿全绿。 Co-Authored-By: Claude Code --- .../engine/src/js_dom_bridge_tests/part18.rs | 139 +++++++++++++++++- crates/engine/src/js_dom_shim/part04.js | 58 +++++--- 2 files changed, 178 insertions(+), 19 deletions(-) diff --git a/crates/engine/src/js_dom_bridge_tests/part18.rs b/crates/engine/src/js_dom_bridge_tests/part18.rs index 6ea2f5ca3..034ae52be 100644 --- a/crates/engine/src/js_dom_bridge_tests/part18.rs +++ b/crates/engine/src/js_dom_bridge_tests/part18.rs @@ -2460,12 +2460,14 @@ fn t8r4_sel_path_inner_html_immediate_query_materialized() { // ⑥ 结构保真(第二顶层子 + 深层文本)。 var dm = sendbar.querySelector('.bpx-video-dm'); r.push('sib:' + (dm && online && online.textContent === 'x' ? 'ok' : 'bad')); + // ⑦ 负控制:不匹配选择器必须仍为 null(防过匹配护栏)。 + r.push('nope:' + (sendbar.querySelector('.nope') === null ? 'null' : 'hit')); return r.join('|'); })()"#).unwrap().value; assert_eq!( out, - "info:wrapper|online:ok|qsa:ok|fc:ok|cc:ok|parent:ok|styleWrite:ok|sib:ok", - "t8r-4:sel 路径 innerHTML 后立即相对查询命中物化 wrapper(querySelector/querySelectorAll/childNodes/反链/接口写读回/结构保真)" + "info:wrapper|online:ok|qsa:ok|fc:ok|cc:ok|parent:ok|styleWrite:ok|sib:ok|nope:null", + "t8r-4:sel 路径 innerHTML 后立即相对查询命中物化 wrapper(querySelector/querySelectorAll/childNodes/反链/接口写读回/结构保真/负控制)" ); } @@ -2523,3 +2525,136 @@ fn t8r4_sel_path_variants_and_ce_once() { "t8r-4:纯文本/混合顶层/清空重注零回归 + sel 域 CE attach ctor 恰 1 次" ); } + +// t8r-4 返修(PR #131 双审查统一返修轮):扩扫臂豁免面 + K3 作废臂钉测—— +// ① K3 `_zwMatParsed` 作废(审查① blocking):innerHTML → 查询触发物化 → host apply +// (apply → bump 代际)→ re-register → 断言 childNodes 无重复并入(K3 新臂禁用 +// 变异下物化 wrapper 留桶 + 反链,overlay 重复并入 = 4); +// ② noWire 豁免(审查② F1):createContextualFragment + insertBefore 的 noWire 解析 +// 子匹配查询按代理返回(可见性=代理匹配面一致),apply 代际 bump 后不消失(豁免 +// 禁用变异下物化打标被 K3 作废 → post 丢子); +// ③ CE 后代单 ctor(审查② F2):setter 期 attach 已递归 upgrade 后代,匹配根的查询 +// 展平检测命中印记 → 整条目回退代理语义、ctor 恒 1(CE 检测禁用变异下 createElement +// 二次构造 → ctor:2)。 +// spec https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml +#[test] +fn t8r4_k3_invalidate_nowire_and_ce_subtree() { + 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/t8r4-k3nw".to_string())); + let canvas_registry = Arc::new(Mutex::new(crate::js_dom_bridge::CanvasRegistry::new())); + + // 宿主 apply 语义(镜像 part26.rs slice13 的 host_apply:apply → bump,代际边界; + // bump 生产调用点 crates/webview/src/webview.rs:2245/4085)。 + fn host_apply( + sandbox: &mut V8Sandbox, + mutations: &Arc>>, + dom_html: &Arc>, + ) { + let tail: Vec = mutations.lock().unwrap().clone(); + if tail.is_empty() { + return; + } + let new_html = crate::js_dom_bridge::apply_mutations_to_html( + &dom_html.lock().unwrap().clone(), + &tail, + ) + .unwrap(); + *dom_html.lock().unwrap() = new_html; + crate::js_dom_bridge::bump_dom_view_gen(); + mutations.lock().unwrap().clear(); + crate::js_dom_bridge::bump_mut_drain_gen(); + sandbox + .execute("if (typeof globalThis.__zw_apply_generation_bump === 'function') globalThis.__zw_apply_generation_bump();") + .unwrap(); + } + + register_dom_callbacks(&mut sandbox, &mutations, &dom_html, &page_url, &canvas_registry, None); + + // 臂 ③:CE 后代单 ctor(纯 JS;先跑避免后续 re-register/reset 清桶干扰)。 + let ce_out = sandbox.execute(r#"(function(){ + var r = []; + globalThis.__ceMade = 0; + class T8r4CeSub extends HTMLElement { + constructor() { super(); globalThis.__ceMade++; } + } + customElements.define('t8r4-ce-sub', T8r4CeSub); + var cec = document.getElementById('cec'); + cec.innerHTML = '
'; + r.push('ceAfterSet:' + globalThis.__ceMade); + var q = cec.querySelector('.ce-root'); + r.push('ceHit:' + (q ? 'ok' : 'null')); + r.push('ceAfterQ:' + globalThis.__ceMade); + return r.join('|'); +})()"#).unwrap().value; + assert_eq!( + ce_out, + "ceAfterSet:1|ceHit:ok|ceAfterQ:1", + "t8r-4 返修③:CE 后代已 upgrade 时匹配根查询回退代理语义,ctor 恒 1" + ); + + // 臂 ②:noWire 豁免(纯 JS;bump 直调为忠实模拟生产调用点 webview.rs:2245/4085)。 + let nw_out = sandbox.execute(r#"(function(){ + var r = []; + var cnw = document.getElementById('nw'); + var fw = document.createRange().createContextualFragment('x'); + cnw.insertBefore(fw, null); + var h1 = cnw.querySelector('.nwel'); + r.push('nwPre:' + (h1 ? 'ok' : 'null')); + r.push('nwMat:' + (h1 && typeof h1.style === 'object' ? 'wrapper' : 'proxy')); + if (typeof globalThis.__zw_apply_generation_bump === 'function') globalThis.__zw_apply_generation_bump(); + r.push('nwPost:' + (cnw.querySelector('.nwel') !== null ? 'ok' : 'lost')); + r.push('nwFc:' + (cnw.firstChild ? 'yes' : 'null')); + return r.join('|'); +})()"#).unwrap().value; + assert_eq!( + nw_out, + "nwPre:ok|nwMat:proxy|nwPost:ok|nwFc:yes", + "t8r-4 返修②:noWire 解析子匹配查询按代理返回,apply 代际 bump 后不消失" + ); + + // 臂 ①:K3 `_zwMatParsed` 作废(Rust host apply 序列,镜像 slice13)。 + sandbox + .execute( + "var c1 = document.getElementById('k3c');\n\ + c1.innerHTML = 'ab';\n\ + var m1 = c1.querySelector('.k1');\n\ + globalThis.__k3mat = (m1 && typeof m1.style === 'object') ? 'wrapper' : (m1 ? 'proxy' : 'null');", + ) + .unwrap(); + assert_eq!( + sandbox.execute("globalThis.__k3mat").unwrap().value, + "wrapper", + "t8r-4 返修①:查询触发物化为 wrapper" + ); + host_apply(&mut sandbox, &mutations, &dom_html); + register_dom_callbacks(&mut sandbox, &mutations, &dom_html, &page_url, &canvas_registry, None); + sandbox + .execute( + "globalThis.__k3post = c1.childNodes.length;\n\ + globalThis.__k3c0 = c1.childNodes[0] ? c1.childNodes[0].textContent : '';", + ) + .unwrap(); + assert_eq!( + sandbox.execute("globalThis.__k3post").unwrap().value, + "2", + "t8r-4 返修①:apply 代际后物化 wrapper 已被 K3 作废,fresh 基底无重复并入" + ); + assert_eq!( + sandbox.execute("globalThis.__k3c0").unwrap().value, + "a", + "t8r-4 返修①:fresh 基底内容正确" + ); +} diff --git a/crates/engine/src/js_dom_shim/part04.js b/crates/engine/src/js_dom_shim/part04.js index 7cde2baa6..da1550cca 100644 --- a/crates/engine/src/js_dom_shim/part04.js +++ b/crates/engine/src/js_dom_shim/part04.js @@ -7997,30 +7997,54 @@ // wrapper,R309 identity 双源教训),`_zwMatParsed` 标记供 apply // 代际边界 K3 定点清除(快照已含本 turn——与解析补偿 added 同批 // 作废)。物化失败回退代理原样(可见性保持既有 R3254-K3 代理匹配 - // 语义)。已 upgrade 的 CE 代理不物化——createElement 会二次 ctor - // (t8r N1 查询面变体),原型已挂、instanceof 语义成立,按代理 - // 原样返回。 + // 语义)。两类豁免(返修轮,双审查发现): + // ① noWire 槽(R97 fragment 视图/createContextualFragment 形态) + // ——子从未 wire 进 host 快照,物化打标会被 K3 在 apply 代际 + // 边界作废而整体消失(t8i 同源语义);匹配命中按代理返回 + // (可见性与代理匹配面一致),不物化不打标。 + // ② 子树含已 upgrade CE(含根)——物化经 createElement 会对已 + // upgrade 元素重复 ctor(setter 期 __zwCeAttachForAdded 已递归 + // upgrade 全子树,t8r N1 查询面变体);展平逐元素反查 registry + + // prototype 印记(_ceRunCtor 挂接语义),命中整条目回退代理。 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml if (!_r322qn || _r322qn.nodeType !== 1 || _r322qn.__zwSelector || !_r322qn._zwSelPendingParent || _r322qn._zwSelPendingParent.parentSel !== sel) continue; try { if (!_matchCompoundOf(_r322qn, _r322compQ)) continue; - var _r322tag = ''; - try { _r322tag = String(_r322qn.localName || _r322qn.tagName || '').toLowerCase(); } catch (_e322tg) {} - var _r322isName = null; - try { _r322isName = _r322qn.getAttribute('is'); } catch (_e322is) {} - var _r322reg = null; - if (_r322isName && globalThis.__zwCERegistryLookup) { - try { _r322reg = globalThis.__zwCERegistryLookup(String(_r322isName), _r322tag); } catch (_e322lk) {} - } - if (!_r322reg && _r322tag && typeof _ce_registry !== 'undefined') _r322reg = _ce_registry[_r322tag] || null; - if (_r322reg && _r322reg.ctor) { - var _r322ceUp = false; - try { _r322ceUp = Object.getPrototypeOf(_r322qn) === _r322reg.ctor.prototype; } catch (_e322gp) {} - if (_r322ceUp) return _r322qn; - } + // noWire 槽(R97 fragment 视图/createContextualFragment 形态): + // 子从未 wire 进 host 快照,K3 apply 代际清除对它不豁免就会在 + // bump 后从 overlay 整体消失(t8i 同源语义)——匹配命中按代理 + // 返回(可见性与代理匹配面一致),不物化不打标。 + if (_r322qn._zwSelPendingParent.noWire) return _r322qn; + var _r322ceHit = false; + try { + var _r322flat = []; + _zwHCCollectSubtree(_r322qn, _r322flat); + for (var _r322fi = 0; _r322fi < _r322flat.length; _r322fi++) { + var _r322fe = _r322flat[_r322fi]; + if (!_r322fe || _r322fe.nodeType !== 1) continue; + var _r322ftag = ''; + try { _r322ftag = String(_r322fe.localName || _r322fe.tagName || '').toLowerCase(); } catch (_e322ft) {} + var _r322fis = null; + try { _r322fis = _r322fe.getAttribute('is'); } catch (_e322fi2) {} + var _r322freg = null; + if (_r322fis && globalThis.__zwCERegistryLookup) { + try { _r322freg = globalThis.__zwCERegistryLookup(String(_r322fis), _r322ftag); } catch (_e322fl) {} + } + if (!_r322freg && _r322ftag && typeof _ce_registry !== 'undefined') _r322freg = _ce_registry[_r322ftag] || null; + if (_r322freg && _r322freg.ctor) { + var _r322fup = false; + try { _r322fup = Object.getPrototypeOf(_r322fe) === _r322freg.ctor.prototype; } catch (_e322fp) {} + if (_r322fup) { _r322ceHit = true; break; } + } + } + } catch (_e322cf) {} + if (_r322ceHit) return _r322qn; var _r322mw = (typeof _wcRebuildAsHandle === 'function') ? _wcRebuildAsHandle(_r322qn) : _r322qn; if (_r322mw && _r322mw !== _r322qn && _r322mw.__zwHandle) { + // 物化(CE ctor 可同步执行页面代码)后写前校验桶槽未被重入 + // 改动——重入 splice/清空时按物化前索引写会腐化无关条目。 + if (_r322bq.added[_r322qa] !== _r322qn) continue; _r322bq.added[_r322qa] = _r322mw; if (_r322bq.addedSet) { _r322bq.addedSet.delete(_r322qn); _r322bq.addedSet.add(_r322mw); } var _r322gi = _zwPendingAdded.indexOf(_r322qn); From af4b7a52b2187aee54603df9e94e06e1fe6f3f0c Mon Sep 17 00:00:00 2001 From: LEI Zongmin Date: Sat, 10 Oct 2026 22:20:47 +0800 Subject: [PATCH 3/3] =?UTF-8?q?docs(engine-dom):=20t8r-4=20=E5=A4=8D?= =?UTF-8?q?=E6=A0=B8=20N2=E2=80=94=E2=80=94=E8=BF=94=E4=BF=AE=E9=92=89?= =?UTF-8?q?=E6=B5=8B=E6=B3=A8=E9=87=8A=E7=BA=A2=E6=80=81=E8=AF=BB=E6=95=B0?= =?UTF-8?q?=204=20=E4=BF=AE=E6=AD=A3=E4=B8=BA=E5=AE=9E=E6=B5=8B=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 非作者复核 APPROVE_WITH_NITS N2:钉测内联注释写「K3 新臂禁用变异下 overlay 重复并入 = 4」,变异实测为 3(仅被查询物化的首子残留并入,未 物化的次子被既有解析补偿臂正常清除)。注释对齐实测读数。 Co-Authored-By: Claude Code --- crates/engine/src/js_dom_bridge_tests/part18.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/src/js_dom_bridge_tests/part18.rs b/crates/engine/src/js_dom_bridge_tests/part18.rs index 034ae52be..776cec74d 100644 --- a/crates/engine/src/js_dom_bridge_tests/part18.rs +++ b/crates/engine/src/js_dom_bridge_tests/part18.rs @@ -2529,7 +2529,7 @@ fn t8r4_sel_path_variants_and_ce_once() { // t8r-4 返修(PR #131 双审查统一返修轮):扩扫臂豁免面 + K3 作废臂钉测—— // ① K3 `_zwMatParsed` 作废(审查① blocking):innerHTML → 查询触发物化 → host apply // (apply → bump 代际)→ re-register → 断言 childNodes 无重复并入(K3 新臂禁用 -// 变异下物化 wrapper 留桶 + 反链,overlay 重复并入 = 4); +// 变异下物化 wrapper 留桶 + 反链,overlay 重复并入 = 3:仅被查询物化的首子); // ② noWire 豁免(审查② F1):createContextualFragment + insertBefore 的 noWire 解析 // 子匹配查询按代理返回(可见性=代理匹配面一致),apply 代际 bump 后不消失(豁免 // 禁用变异下物化打标被 K3 作废 → post 丢子);