From 9dcd16661f1178dac6fdf729c273f8fdadcbd477 Mon Sep 17 00:00:00 2001 From: LEI Zongmin Date: Sat, 10 Oct 2026 16:46:38 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(engine-dom):=20innerHTML=20=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E8=A7=86=E5=9B=BE=E4=BA=A7=E7=89=A9=E7=89=A9=E5=8C=96?= =?UTF-8?q?=E7=9C=9F=20handle=20=E6=A0=91=EF=BC=8C=E4=BF=AE=20nano=20?= =?UTF-8?q?=E5=B0=8F=E9=83=A8=E4=BB=B6=E8=84=9A=E6=9C=AC=E4=B8=AD=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit innerHTML setter 的 handle 路径(detached createElement 容器)此前把 _zwMEl 解析代理存入 _handleChildren——相对查询返回 truthy 节点但无 .style/.classList 接口,bilibili nano 播放器 volume(npd.586 webkitTransform×2)/progress (npd.911 transform×3)小部件构造即中断,players=0。 改为逐顶层元素子经既有 _wcRebuildAsHandle 物化为真 handle 树(appendChild 路径同构成员类型),顶层子补 _zwNodeParent 反链保持 R136 parentNode 语义; 物化失败回退代理原样。shadow root 复用 fragment 容器同被物化后,focus 从 解析节点域切到 handle 域,暴露 handle 域焦点迁移无 shadow retargeting 缺口—— 沿父反链上行判 shadow 归属,命中 _shadowHandleMeta 以 host 为 relatedTarget (与 R148 解析节点臂同源),非 shadow 上行 miss 回落既有语义。 验证:红→绿钉测×2(红态读数与真站崩型一致);lib 2961P/0F;WPT dom 套 55,083P/199F 与基线 Fail 集合精确 diff 零实质差异;真站 r5 探针五条 TypeError 全消、mount 推进到 bpx-docker 层(新暴露 npd.314 查询 null 簇 记池);fmt/clippy 双腿/make test 全绿。 Co-Authored-By: Claude Code --- .../engine/src/js_dom_bridge_tests/part18.rs | 126 +++++++++++++++++- crates/engine/src/js_dom_shim/part04.js | 43 +++++- 2 files changed, 167 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/js_dom_bridge_tests/part18.rs b/crates/engine/src/js_dom_bridge_tests/part18.rs index 302d21747..025108894 100644 --- a/crates/engine/src/js_dom_bridge_tests/part18.rs +++ b/crates/engine/src/js_dom_bridge_tests/part18.rs @@ -2204,9 +2204,133 @@ fn t8q_adopt_node_snapshot_variants_and_identity_regression() { ) .unwrap() .value; - assert_eq!( + assert_eq!( out, "text:ok|comment:ok|li:ok|identity:ok|docThrow:ok", "t8q:子树文本/注释保真 + 快照后代物化 + 真元素 identity 与 Document 抛错零回归" ); } + +// 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 同型)—— +// innerHTML setter 的 handle 路径(detached createElement 容器)把 _zwFragmentAdded 的 +// _zwMEl 解析代理存入 _handleChildren,相对查询返回 truthy 节点但无 .style 接口 → TypeError +// 脚本中断。spec https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml +// ——innerHTML 产物应与 createElement 产物同体系(完整元素接口)。 +#[test] +fn t8r_inner_html_local_view_materializes_query_artifacts() { + 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/t8r-innerhtml".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 area = document.createElement('div'); + area.innerHTML = '
'; + var bar = area.querySelector('.bar-normal'); + if (!bar) return 'setup:no-bar'; + var r = []; + // ① 元素接口:查询产物带 .style(真站 TypeError 根面)。 + r.push('style:' + typeof bar.style); + r.push('styleSet:' + typeof (bar.style && bar.style.setProperty)); + var sw = 'ok'; + try { bar.style.setProperty('opacity', '0.5'); } catch (e) { sw = 'throw:' + e.name; } + r.push('styleWrite:' + sw); + r.push('styleRead:' + (bar.style ? bar.style.getPropertyValue('opacity') : 'nostyle')); + // ② 结构与属性:tagName/className(物化重建保真)。 + r.push('tag:' + bar.tagName); + r.push('class:' + (bar.className === 'bar-normal' ? 'ok' : 'bad')); + // ③ 树内深查:物化子树的相对查询产物同为完整元素(递归物化)。 + var fill = bar.querySelector('.bar-fill'); + r.push('nested:' + (fill && typeof fill.style === 'object' ? 'ok' : 'bad')); + // ④ 多顶层子:第二个顶层子同样物化。 + var tip = area.querySelector('.tip'); + r.push('tip:' + (tip && typeof tip.style === 'object' ? 'ok' : 'bad')); + // ⑤ parentNode 反链(append 家族同款):查询产物上行到宿主容器。 + r.push('parent:' + (bar.parentNode === area ? 'ok' : 'bad')); + return r.join('|'); +})()"#, + ) + .unwrap() + .value; + assert_eq!( + out, + "style:object|styleSet:function|styleWrite:ok|styleRead:0.5|tag:DIV|class:ok|nested:ok|tip:ok|parent:ok", + "t8r:innerHTML 本地视图产物物化(style 接口 + 写读回 + 深查 + 多顶层子 + parentNode 反链)" + ); +} + +// t8r 邻近变体:① 纯文本 innerHTML 分支零回归(R34xx 文本注册面);② 清空语义; +// ③ 二次 innerHTML 重物化(旧物化树被替换、新查询产物仍全接口);④ 混合容器—— +// innerHTML 物化成员与 appendChild wrapper 成员共存(_handleChildren 混合类型契约); +// ⑤ 物化树 introspection 护栏(属性/结构保真——_zwMEl 代理时代已有的消费面不变坏)。 +#[test] +fn t8r_inner_html_local_view_variants_and_text_path_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/t8r-innerhtml-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 = []; + // ① 纯文本 innerHTML:firstChild 文本节点路径(不物化分支)零回归。 + var t = document.createElement('div'); + t.innerHTML = 'plain text'; + r.push('text:' + (t.firstChild && t.firstChild.nodeType === 3 && t.firstChild.data === 'plain text' ? 'ok' : 'bad')); + // ② 清空:innerHTML='' 后无子。 + t.innerHTML = ''; + r.push('clear:' + (t.firstChild ? 'bad' : 'ok')); + // ③ 二次 innerHTML:重物化替换,新查询产物仍全接口。 + var d2 = document.createElement('div'); + d2.innerHTML = 'x'; + var first = d2.querySelector('.a'); + d2.innerHTML = 'y'; + var second = d2.querySelector('.b'); + r.push('reIh:' + (first && second && typeof second.style === 'object' && second.tagName === 'I' ? 'ok' : 'bad')); + // ④ 混合容器:物化成员 + appendChild wrapper 成员共存(结构计数 + append 成员接口)。 + var d3 = document.createElement('div'); + d3.innerHTML = 'z'; + var app = document.createElement('strong'); + d3.appendChild(app); + r.push('mix:' + (d3.querySelectorAll('*').length === 2 && d3.querySelector('strong') === app ? 'ok' : 'bad')); + // ⑤ introspection 护栏:物化树属性保真 + 结构计数(_zwMEl 代理时代已有消费面)。 + var d4 = document.createElement('div'); + d4.innerHTML = '

1

2

'; + var w = d4.querySelector('.w'); + r.push('deep:' + (w && w.querySelectorAll('p').length === 2 && w.getAttribute('data-k') === 'v' ? 'ok' : 'bad')); + return r.join('|'); +})()"#, + ) + .unwrap() + .value; + assert_eq!( + out, + "text:ok|clear:ok|reIh:ok|mix:ok|deep:ok", + "t8r:纯文本/清空分支零回归 + 重物化 + 混合容器共存 + introspection 保真" + ); +} diff --git a/crates/engine/src/js_dom_shim/part04.js b/crates/engine/src/js_dom_shim/part04.js index e14559e31..be1130fdb 100644 --- a/crates/engine/src/js_dom_shim/part04.js +++ b/crates/engine/src/js_dom_shim/part04.js @@ -3507,6 +3507,21 @@ } else { r148Related = r148OldMEl; } + } else if (oldProxy && oldProxy.__zwHandle) { + // t8r:旧焦点为 handle/proxy 域——innerHTML 产物物化后 shadow 子是真 + // handle(解析节点臂不再命中),retarget 语义须同源:沿父反链上行判 + // shadow 归属,命中 _shadowHandleMeta → 以 host 为 relatedTarget(spec + // UI Events shadow DOM retargeting,同上)。非 shadow 的 handle 焦点 + // 上行 miss → r148Related 保持 null,rtForNew 回落 oldProxy(既有语义)。 + // 焦点迁移是交互级频率(非热路径),guard 32 足够浅。 + var _r148Up = oldProxy.__zwHandle; + var _r148Guard = 0; + while (_r148Up != null && _r148Guard++ < 32) { + var _r148Meta = (typeof _shadowHandleMeta !== 'undefined') ? _shadowHandleMeta[_r148Up] : null; + if (_r148Meta) { r148Related = _makeProxy(_r148Meta.hostSel, _r148Meta.hostHandle); break; } + var _r148Link = (typeof _zwNodeParent !== 'undefined' && _zwNodeParent) ? _zwNodeParent[_r148Up] : null; + _r148Up = (_r148Link && _r148Link.parentHandle != null) ? _r148Link.parentHandle : null; + } } // uievents-compat M3 尾簇 6b(2026-10-04):失焦相位**先于**获焦相位且 // blur 先于 focusout(WPT focus-events expected「blur@a → focusout@a → @@ -9594,7 +9609,33 @@ // R100:纯文本 innerHTML 且 handle 容器——下方 _zwRegisterTextEl 已建本地文本 // 视图,此处不再同时入 registry(R81 textContent getter 融合两源会双计文本, // e2e 'Hello WorldHello World' 实证)。含 markup 时保持 registry(结构子树)。 - if (handle && !(_ihVal.indexOf('<') < 0)) _handleChildren[handle] = _ihAdded; + if (handle && !(_ihVal.indexOf('<') < 0)) { + // t8r:注入产物物化为真 handle 树——_zwMEl 解析代理无 .style/classList + // 等元素接口,相对查询(_handleChildren 扫描)返回代理后站点脚本读 .style + // 抛 TypeError(bilibili nano 播放器 volume/progress 两簇:area.innerHTML + // 注入 → elements.bar 相对查询产物无接口)。复用 t8q 重建设施 + // [_wcRebuildAsHandle]:顶层元素子重建为 detached 真 handle 树(属性/ + // 深层子递归),与 createElement+appendChild 路径成员类型同构(appendChild + // push wrapper、R380 存 wrapper——_handleChildren 混合容器两种皆合法)。 + // 文本/注释子保持原样(cloneNode 复制分支读 .data 兼容)。物化失败回退 + // 代理原样(_wcRebuildAsHandle catch 返原 node)。R136 的宿主 parentNode + // 语义经 _zwNodeParent 反链保持(append 家族同款——_parentNodeFor handle + // 分支消费)。R3031 MO addedNodes 仍持 setter 时快照的代理原件(既有钉测 + // 纯 introspect;identity 双表示 documented)。已知限制:物化 wrapper 是 + // detached 新建 handle,与宿主树 innerHTML 产物 handle 不同源——对查询 + // 产物的样式/属性写不穿透渲染(本切片消「.style 抛 TypeError 脚本中断」面)。 + // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml + var _ihMat = []; + for (var _ihMi = 0; _ihMi < _ihAdded.length; _ihMi++) { + var _ihMk = _ihAdded[_ihMi]; + var _ihMw = (_ihMk && _ihMk.nodeType === 1) ? _wcRebuildAsHandle(_ihMk) : _ihMk; + if (_ihMw && _ihMw !== _ihMk && _ihMw.__zwHandle && _zwNodeParent) { + _zwNodeParent[_ihMw.__zwHandle] = { parentSel: null, parentHandle: handle, nextSibling: null }; + } + _ihMat.push(_ihMw); + } + _handleChildren[handle] = _ihMat; + } else if (handle && _ihVal.indexOf('<') < 0) _handleChildren[handle] = []; // WC-M3 切片 8 第九增量:slotchange 队列钩子**下移**到本地文本视图重注册 // (下方 _zwRegisterTextEl/_zwUnregisterTextEl)之后——queue 时 diff 读的 From b61384f2691d3fa443caa7366659f9b743c19ae4 Mon Sep 17 00:00:00 2001 From: LEI Zongmin Date: Sat, 10 Oct 2026 18:40:00 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(engine-dom):=20innerHTML=20=E7=89=A9?= =?UTF-8?q?=E5=8C=96=E7=9A=84=E8=87=AA=E5=AE=9A=E4=B9=89=E5=85=83=E7=B4=A0?= =?UTF-8?q?=E6=9E=84=E9=80=A0=E5=8E=BB=E9=87=8D=EF=BC=8Cattach=20=E5=88=86?= =?UTF-8?q?=E6=B5=81=E9=98=B2=E5=8F=8C=20ctor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 审查② N1:物化经 createElement 已对 registry 命中的 CE 跑过 ctor, __zwCeAttachForAdded 再对原解析代理整表 upgrade(_ceUpgradeNode 无幂等门) → ctor ×2、connectedCallback 落孤儿代理。修法:物化循环收集回退代理 (_wcRebuildAsHandle catch 返原 node),attach 点按物化是否生效分流—— 生效时回退代理仍走 upgrade,成功 wrapper 只补连接态派发(pc 双源判定 + _ceApplyConn 递归派发,无 ctor);未物化路径原调用逐字不变。 附带双审查采纳项:变体钉补混合 markup+文本顶层注入臂与物化成员跨查询 identity 断言(①#1/#2)、_zwNodeParent 反链注释按实际承重面校正(①#3)、 t8q 主钉缩进 6→4(①#4)、N1 新钉断言 ctor 恰 1 次(红态变异实证 ctor:2 与审查探针读数一致)。 验证:t8r 三钉绿 + N1 红态 ctor:2 → 修复后 ctor:1;lib 全量 2962P/0F; make test exit 0;WPT dom 套 55,084P/198F 与基线唯一差异为 GamepadEvent 时间戳 flaky 正向翻转(Fail→Pass,同族已记录时序 flaky,零实质退化); fmt CLEAN + clippy 双腿(默认 v8 + quickjs no-default)-D warnings 全绿。 Co-Authored-By: Claude Code --- .../engine/src/js_dom_bridge_tests/part18.rs | 80 ++++++++++++++++++- crates/engine/src/js_dom_shim/part04.js | 41 ++++++++-- 2 files changed, 112 insertions(+), 9 deletions(-) diff --git a/crates/engine/src/js_dom_bridge_tests/part18.rs b/crates/engine/src/js_dom_bridge_tests/part18.rs index 025108894..b8fcca538 100644 --- a/crates/engine/src/js_dom_bridge_tests/part18.rs +++ b/crates/engine/src/js_dom_bridge_tests/part18.rs @@ -2204,7 +2204,7 @@ fn t8q_adopt_node_snapshot_variants_and_identity_regression() { ) .unwrap() .value; - assert_eq!( + assert_eq!( out, "text:ok|comment:ok|li:ok|identity:ok|docThrow:ok", "t8q:子树文本/注释保真 + 快照后代物化 + 真元素 identity 与 Document 抛错零回归" @@ -2276,7 +2276,9 @@ fn t8r_inner_html_local_view_materializes_query_artifacts() { // t8r 邻近变体:① 纯文本 innerHTML 分支零回归(R34xx 文本注册面);② 清空语义; // ③ 二次 innerHTML 重物化(旧物化树被替换、新查询产物仍全接口);④ 混合容器—— // innerHTML 物化成员与 appendChild wrapper 成员共存(_handleChildren 混合类型契约); -// ⑤ 物化树 introspection 护栏(属性/结构保真——_zwMEl 代理时代已有的消费面不变坏)。 +// ⑤ 物化树 introspection 护栏(属性/结构保真——_zwMEl 代理时代已有的消费面不变坏); +// ⑥ 混合 markup+文本顶层注入(审查①#1:顶层文本子保持代理、元素子物化);⑦ 物化 +// 成员跨查询 identity 稳定(审查①#2,对照 ④ 的 append 成员断言)。 #[test] fn t8r_inner_html_local_view_variants_and_text_path_regression() { use std::sync::{Arc, Mutex}; @@ -2323,6 +2325,76 @@ fn t8r_inner_html_local_view_variants_and_text_path_regression() { d4.innerHTML = '

1

2

'; var w = d4.querySelector('.w'); r.push('deep:' + (w && w.querySelectorAll('p').length === 2 && w.getAttribute('data-k') === 'v' ? 'ok' : 'bad')); + // ⑥ 混合 markup+文本顶层注入:顶层元素子物化,尾文本子保持解析代理(.data 可读), + // 元素子接口完整(审查①#1 缺口护栏)。 + var d5 = document.createElement('div'); + d5.innerHTML = 'xtail'; + var hb = d5.querySelector('.h'); + var tl = d5.childNodes[1]; + r.push('mixText:' + (hb && typeof hb.style === 'object' && hb.firstChild.data === 'x' && + tl && tl.nodeType === 3 && tl.data === 'tail' && d5.childNodes.length === 2 ? 'ok' : 'bad')); + // ⑦ 物化成员跨查询 identity 稳定(对照 appendChild 成员 ④ 的 === app 断言;审查①#2)。 + var q1 = d5.querySelector('.h'); + var q2 = d5.querySelector('.h'); + r.push('ident:' + (q1 && q1 === q2 && q1 === d5.firstElementChild ? 'ok' : 'bad')); + return r.join('|'); +})()"#, + ) + .unwrap() + .value; + assert_eq!( + out, + "text:ok|clear:ok|reIh:ok|mix:ok|deep:ok|mixText:ok|ident:ok", + "t8r:纯文本/清空分支零回归 + 重物化 + 混合容器共存 + introspection 保真 + 混合 markup/文本顶层注入 + 物化成员 identity" + ); +} + +// t8r 返修(审查② N1):innerHTML 物化与 `__zwCeAttachForAdded` 双轨——物化经 +// createElement 已对 registry 命中的 CE 跑过 ctor,attach 再对原解析代理整表 upgrade +//(_ceUpgradeNode 无幂等门)→ ctor ×2、connectedCallback 落孤儿代理。修复后回退代理 +// 才走 upgrade、成功 wrapper 只补连接态派发:ctor 恒 1 次,查询产物是真实升级实例。 +#[test] +fn t8r_inner_html_materialized_custom_element_ctor_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/t8r-ce-once".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 made = 0; + class T8rCe extends HTMLElement { + constructor() { super(); made++; } + } + customElements.define('t8r-ce', T8rCe); + var c = document.createElement('div'); + c.innerHTML = ''; + var q = c.querySelector('t8r-ce'); + var r = []; + // ctor 恰 1 次(createElement 物化时构造;attach 不再对丢弃代理重复 upgrade)。 + r.push('ctor:' + made); + // 查询产物仍是真实升级实例(接口完整 + instanceof 命中)。 + r.push('iface:' + (q && typeof q.style === 'object' ? 'ok' : 'bad')); + r.push('isInst:' + (q instanceof T8rCe ? 'ok' : 'bad')); + // createElement 基线路径不受扰(ctor 恒 1)。 + var made2 = 0; + class T8rCe2 extends HTMLElement { + constructor() { super(); made2++; } + } + customElements.define('t8r-ce2', T8rCe2); + var solo = document.createElement('t8r-ce2'); + r.push('solo:' + (made2 === 1 && solo instanceof T8rCe2 ? 'ok' : 'bad')); return r.join('|'); })()"#, ) @@ -2330,7 +2402,7 @@ fn t8r_inner_html_local_view_variants_and_text_path_regression() { .value; assert_eq!( out, - "text:ok|clear:ok|reIh:ok|mix:ok|deep:ok", - "t8r:纯文本/清空分支零回归 + 重物化 + 混合容器共存 + introspection 保真" + "ctor:1|iface:ok|isInst:ok|solo:ok", + "t8r N1:innerHTML 物化的自定义元素 ctor 恰 1 次,查询产物为真实升级实例,createElement 基线不受扰" ); } diff --git a/crates/engine/src/js_dom_shim/part04.js b/crates/engine/src/js_dom_shim/part04.js index be1130fdb..63c1224e1 100644 --- a/crates/engine/src/js_dom_shim/part04.js +++ b/crates/engine/src/js_dom_shim/part04.js @@ -9618,20 +9618,28 @@ // 深层子递归),与 createElement+appendChild 路径成员类型同构(appendChild // push wrapper、R380 存 wrapper——_handleChildren 混合容器两种皆合法)。 // 文本/注释子保持原样(cloneNode 复制分支读 .data 兼容)。物化失败回退 - // 代理原样(_wcRebuildAsHandle catch 返原 node)。R136 的宿主 parentNode - // 语义经 _zwNodeParent 反链保持(append 家族同款——_parentNodeFor handle - // 分支消费)。R3031 MO addedNodes 仍持 setter 时快照的代理原件(既有钉测 - // 纯 introspect;identity 双表示 documented)。已知限制:物化 wrapper 是 + // 代理原样(_wcRebuildAsHandle catch 返原 node)。_zwNodeParent 反链的 + // 承重面是焦点注册/retargeting 上行链(R148 handle 域臂沿它上行判 shadow + // 归属)与事件冒泡;parentNode 读另有 _parentNodeFor 的 R155 + // _handleChildren 反查 fallback 兜底。R3031 MO addedNodes 仍持 setter 时 + // 快照的代理原件(既有钉测纯 introspect;identity 双表示 documented)。 + // 已知限制:物化 wrapper 是 // detached 新建 handle,与宿主树 innerHTML 产物 handle 不同源——对查询 // 产物的样式/属性写不穿透渲染(本切片消「.style 抛 TypeError 脚本中断」面)。 // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml var _ihMat = []; + var _ihFallback = []; for (var _ihMi = 0; _ihMi < _ihAdded.length; _ihMi++) { var _ihMk = _ihAdded[_ihMi]; var _ihMw = (_ihMk && _ihMk.nodeType === 1) ? _wcRebuildAsHandle(_ihMk) : _ihMk; if (_ihMw && _ihMw !== _ihMk && _ihMw.__zwHandle && _zwNodeParent) { _zwNodeParent[_ihMw.__zwHandle] = { parentSel: null, parentHandle: handle, nextSibling: null }; } + // t8r 返修(N1):物化失败的顶层元素子(_wcRebuildAsHandle catch 返原 + // node,_ihMw === _ihMk)未经 createElement 构造,仍需 CE upgrade—— + // 下方 attach 分流时交给 __zwCeAttachForAdded;成功 wrapper 已由 + // createElement ctor(registry 命中即构造),不得再 upgrade。 + if (_ihMk && _ihMk.nodeType === 1 && _ihMw === _ihMk) _ihFallback.push(_ihMk); _ihMat.push(_ihMw); } _handleChildren[handle] = _ihMat; @@ -9711,7 +9719,30 @@ // 'innerHTML on ...' inserting-markup 簇 + builtin-coverage 解析实例化面)。 if (typeof globalThis.__zwCeAttachForAdded === 'function') { try { - globalThis.__zwCeAttachForAdded(_ihAdded, _ceParentConnected(sel, handle)); + if (_ihMat) { + // t8r 返修(N1):物化生效时顶层元素子已经 createElement 构造 + //(registry 命中的 CE ctor 在 createElement 内跑过一次),不能再对 + // 原解析代理整表 upgrade——_ceUpgradeNode 无幂等门(_ceRunCtor 无条件 + // 执行),重复 ctor 副作用翻倍,且 connectedCallback 会派发到被丢弃 + // 的孤儿代理上。改为:物化失败回退的代理(未构造)仍走 upgrade; + // 成功 wrapper 只补连接态派发(同 __zwCeAttachForAdded 的 pc 双源 + // 判定 + _ceApplyConn 递归派发,无 ctor)。 + // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-innerhtml + var _ihPc = _ceParentConnected(sel, handle); + if (_ihFallback.length) globalThis.__zwCeAttachForAdded(_ihFallback, _ihPc); + for (var _ihCi = 0; _ihCi < _ihMat.length; _ihCi++) { + var _ihCw = _ihMat[_ihCi]; + if (_ihCw && _ihCw.__zwHandle) { + var _ihCpc = _ihPc === true; + if (!_ihCpc) { try { _ihCpc = _elConnected(_ihCw); } catch (_eIhCp) { _ihCpc = _ihCpc; } } + if (_ihCpc && typeof _ceApplyConn === 'function') { + try { _ceApplyConn(_ihCw, true); } catch (_eIhCc) {} + } + } + } + } else { + globalThis.__zwCeAttachForAdded(_ihAdded, _ceParentConnected(sel, handle)); + } } catch (_eCeIh) {} } // R304(js-dom M4):innerHTML 解析 wrapper 打挂父槽(sel 容器的同 turn