dsh-mpkg-wallpaper 3.16.0 → 3.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/client.js +272 -149
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -9390,7 +9390,21 @@ function mpwThumbFail(el, why) {
9390
9390
  宿主每帧都改 class/style,旧写法每帧同步跑一遍 applyFrostInline)。 */
9391
9391
  let fraf = 0;
9392
9392
  const frun = () => { fraf = 0; try { applyFrostInline(); } catch (e) {} };
9393
- frostObserver = new MutationObserver(() => {
9393
+ /* ②(2026-10-05 真机采样定案:"刷新/切子代理会话时标题栏的模糊会闪一下消失")
9394
+ 采样读数:`t=2028 frost=false attr=false`、下一帧又回来 —— 宿主切换视图时**重建了 header
9395
+ 元素**,我们插在 header 里的磨砂层随旧节点被卸载,而重建走的是 rAF 合并的慢路
9396
+ ⇒ 中间有 1~2 帧没有任何磨砂。修法:**新增节点**这一路同步重跑 `syncHeaderFrost()`
9397
+ (MutationObserver 回调是微任务,跑在绘制之前 ⇒ 那一帧就已经带上层),
9398
+ 其余(class/style 变更)仍走 rAF 合并,避免动画期间每帧跑。 */
9399
+ frostObserver = new MutationObserver((records) => {
9400
+ let addedEl = false;
9401
+ try {
9402
+ for (const rec of (records || [])) {
9403
+ for (const n2 of (rec.addedNodes || [])) { if (n2 && n2.nodeType === 1) { addedEl = true; break } }
9404
+ if (addedEl) break;
9405
+ }
9406
+ } catch (e) { addedEl = true; }
9407
+ if (addedEl) { try { frun(); } catch (e) {} return; }
9394
9408
  if (fraf) return;
9395
9409
  try { fraf = (typeof requestAnimationFrame === "function") ? requestAnimationFrame(frun) : setTimeout(frun, 16); }
9396
9410
  catch (e) { frun(); }
@@ -17991,24 +18005,24 @@ ${unifyTakeover ? `
17991
18005
  现在接管真的做出来:这些表面与左栏**读同一套 token**(同色 + 同厚度 + 同半径),
17992
18006
  于是"整屏虚化程度 + 左侧边栏/标题栏透明度"一改,右栏/dock 一起变。
17993
18007
  位置在本块最后(与前文选择器同特异性时后者胜)⇒ 不需要额外的权重技巧。 */
17994
- html body[data-mpw-unify] [data-sidebar-right-panel],
17995
- html body[data-mpw-unify] [data-sidebar-right-float-host],
17996
- html body[data-mpw-unify] [data-mpw-rs],
17997
- html body[data-mpw-unify] [data-dockkit-pane],
17998
- html body[data-mpw-unify] [data-dockkit-strip],
17999
- html body[data-mpw-unify] [data-dockkit-surface],
18000
- html body[data-mpw-unify] [data-dockkit-float] {
18008
+ html body[data-mpw-unify] [data-mpw-rs-surface],
18009
+ html body[data-mpw-unify] [data-mpw-rs-surface][data-sidebar-right-float-host],
18010
+ html body[data-mpw-unify] [data-mpw-rs-surface][data-mpw-rs],
18011
+ html body[data-mpw-unify] [data-mpw-rs-surface][data-dockkit-pane],
18012
+ html body[data-mpw-unify] [data-mpw-rs-surface][data-dockkit-strip],
18013
+ html body[data-mpw-unify] [data-mpw-rs-surface][data-dockkit-surface],
18014
+ html body[data-mpw-unify] [data-mpw-rs-surface][data-dockkit-float] {
18001
18015
  -webkit-backdrop-filter: ${unifyBlurCss} !important;
18002
18016
  backdrop-filter: ${unifyBlurCss} !important;
18003
18017
  background-color: var(--mpw-unify-surface) !important;
18004
18018
  }
18005
- html body[data-ds-dark-theme][data-mpw-unify] [data-sidebar-right-panel],
18006
- html body[data-ds-dark-theme][data-mpw-unify] [data-sidebar-right-float-host],
18007
- html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs],
18008
- html body[data-ds-dark-theme][data-mpw-unify] [data-dockkit-pane],
18009
- html body[data-ds-dark-theme][data-mpw-unify] [data-dockkit-strip],
18010
- html body[data-ds-dark-theme][data-mpw-unify] [data-dockkit-surface],
18011
- html body[data-ds-dark-theme][data-mpw-unify] [data-dockkit-float] {
18019
+ html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface],
18020
+ html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface][data-sidebar-right-float-host],
18021
+ html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface][data-mpw-rs],
18022
+ html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface][data-dockkit-pane],
18023
+ html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface][data-dockkit-strip],
18024
+ html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface][data-dockkit-surface],
18025
+ html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface][data-dockkit-float] {
18012
18026
  background-color: var(--mpw-unify-surface-dark) !important;
18013
18027
  }
18014
18028
  /* better-sidebar 的面板 / 底部工作台:**只跟雾色与厚度,不给 backdrop-filter**。
@@ -18077,6 +18091,9 @@ html body[data-mpw-sblur-off] [data-mpw-rs] {
18077
18091
  tok("--mpw-pop-surface", mpwSurfaceColor(surfacePlan.left.base, popFallbackAlpha).replace(/[\d.]+%/, `calc(var(--mpw-pop-surface-alpha, ${popFallbackAlpha.toFixed(2)}) * 100%)`));
18078
18092
  tok("--mpw-pop-surface-alpha", popFallbackAlpha.toFixed(2));
18079
18093
  tok("--mpw-pop-surface-dark", `rgba(18, 22, 30, var(--mpw-pop-surface-alpha, ${popFallbackAlpha.toFixed(2)}))`);
18094
+ /* ③(2026-10-05) C 类弹层(糊不到页面背后 ⇒ 做实在)专用的 88% 表面:**我们自己的 token**,
18095
+ 不再借宿主 --dsw-specific-sidebar-fill(真机在侧栏作用域里它算出来是透明的 ⇒ 面板变透明)。 */
18096
+ tok("--mpw-pop-trunc-surface", mpwSurfaceColor(surfacePlan.left.base, 0.88));
18080
18097
  tok("--mpw-surface-pop-dark", `rgba(18, 22, 30, var(--mpw-pop-alpha, ${(popSurfaceAlpha / 100).toFixed(2)}))`);
18081
18098
  // 弹层**表面**选择器(背景 + 模糊共用一份,避免两处写法漂移)。
18082
18099
  // ③(关键收窄 2026-09-13) 只用 [class*="_menu"] 会连**非表面**一起命中:
@@ -18085,13 +18102,17 @@ html body[data-mpw-sblur-off] [data-mpw-rs] {
18085
18102
  // `._G5b-a_menuStatus`(说明文字)都含 "_menu" ——
18086
18103
  // 给它们加 backdrop-filter 会在界面上糊出一块方块,加底色会糊出白块。
18087
18104
  // 这里显式排除 anchor/icon/status/open/item/row/trigger/card 等非表面后缀。
18088
- const POP_NOT = `:not([role="menuitem"]):not([role="option"]):not([class*="Anchor"]):not([class*="anchor"]):not([class*="Icon"]):not([class*="icon"]):not([class*="Status"]):not([class*="status"]):not([class*="Open"]):not([class*="open"]):not([class*="_item"]):not([class*="Item"]):not([class*="_row"]):not([class*="Row"]):not([class*="_trigger"]):not([class*="Trigger"]):not([class*="_card"]):not([class*="Card"]):not([class*="_groupTitle"]):not([class*="_viewport"])`;
18105
+ /* ③(2026-10-05) 末尾追加 `:not([data-mpw-pop-untrunc])`:被我们"去截断"的祖先
18106
+ (见 lib 里的 mpwPopUntruncApply)不再吃这套弹层表面规则 —— 否则我们自己的
18107
+ blur 规则会跟去截断规则互相压制(真机:加号菜单容器的 blur 是**我们自己**给的,
18108
+ 特异性还比注入的去截断规则高,摘不掉 ⇒ 内层弹层照旧被截断)。 */
18109
+ const POP_NOT = `:not([role="menuitem"]):not([role="option"]):not([class*="Anchor"]):not([class*="anchor"]):not([class*="Icon"]):not([class*="icon"]):not([class*="Status"]):not([class*="status"]):not([class*="Open"]):not([class*="open"]):not([class*="_item"]):not([class*="Item"]):not([class*="_row"]):not([class*="Row"]):not([class*="_trigger"]):not([class*="Trigger"]):not([class*="_card"]):not([class*="Card"]):not([class*="_groupTitle"]):not([class*="_viewport"]):not([data-mpw-pop-untrunc])`;
18089
18110
  /* ①(2026-10-02 用户第 3 条⑥:上下文占用圆圈点开的框是**纯白**、没有玻璃感)
18090
18111
  它的真实结构是 div.JObwrW_panel[role="dialog"][aria-label="上下文已用"] ⇒ 既不在
18091
18112
  下面这串弹层选择器里(所以没有我们的底),又被"真弹层不做玻璃"那条按 role=dialog
18092
18113
  清掉了模糊 ⇒ 纯白。把它(以及同类小面板)纳入弹层表面集合。 */
18093
18114
  const popSurfaceSel = `[role="menu"]${POP_NOT}, [role="listbox"]${POP_NOT}, [role="combobox"]${POP_NOT}, [data-dsh-surface]${POP_NOT}, [class*="_menu"]${POP_NOT}, [class*="_popover"]${POP_NOT}, [class*="_dropdown"]${POP_NOT}, [class*="_denseList"]${POP_NOT}, [class*="JObwrW_panel"]${POP_NOT}`;
18094
- const popTipSel = `[role="tooltip"], [role="alert"]`;
18115
+ const popTipSel = `[role="tooltip"]:not([data-mpw-pop-untrunc]), [role="alert"]:not([data-mpw-pop-untrunc])`;
18095
18116
  /* ①(2026-10-02 用户第 3 条:「把这个好用的模糊效果独立成 token,因为还有几个地方也要实现」)
18096
18117
  弹层表面 = **底色 + 模糊半径 + 圆角** 三件套,全部由这几枚 token 定义;任何弹层只要被
18097
18118
  下面的选择器命中,就自动同一套效果(不再各处写死 rgba/blur)。
@@ -18133,8 +18154,25 @@ ${popSurfaceSel} {
18133
18154
  html body [data-mpw-pop-bg] { background-color: var(--mpw-pop-surface) !important; }
18134
18155
  /* ②(2026-10-05) 弹层处在一个"自己有底的玻璃祖先"里面(backdrop root 被截断 ⇒ 我们糊不到页面背后):
18135
18156
  与其"半透明却没模糊"(真机被读成"只有白色滤镜"),不如**做实在**——表面不透明度抬到 88%,
18136
- 并把 backdrop-filter 撤掉(白算一遍)。注意:只动我们自己的标记属性,宿主元素一个都不碰。 */
18137
- html body [data-mpw-pop-trunc] { background-color: color-mix(in srgb, var(--dsw-specific-sidebar-fill) 88%, transparent) !important; backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
18157
+ 并把 backdrop-filter 撤掉(白算一遍)。注意:只动我们自己的标记属性,宿主元素一个都不碰。
18158
+ ③(2026-10-05) 底色改用**我们自己的** token(--mpw-pop-trunc-surface):原来借宿主
18159
+ --dsw-specific-sidebar-fill,真机在侧栏作用域里它被算成**透明**(color-mix 嵌套后 α=0)
18160
+ ⇒ 面板整个变透明、白字直接看不见(提示气泡就是这么"没有底"的)。 */
18161
+ html body [data-mpw-pop-trunc] { background-color: var(--mpw-pop-trunc-surface, var(--mpw-pop-surface)) !important; backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
18162
+ /* ②(2026-10-05 v3 定案)「去截断 + 补霜」:弹层自己的 backdrop-filter 采样不到页面,是因为
18163
+ 祖先链上有个创建 backdrop root 的元素(真机:输入框卡片 uV2eYG_card blur(14px))。
18164
+ B 类弹层开着期间给那个祖先打 data-mpw-pop-untrunc ⇒ 这条规则把它的 blur 摘掉(root 回到整页,
18165
+ 弹层自己的 blur 立刻采样得到页面);丢掉的磨砂由 JS 在它内部补一层对齐霜层(data-mpw-pop-frost)。
18166
+ 特异性 (0,2,2):压得过宿主按类名写的 !important 规则;而我们自己的弹层表面规则已经被
18167
+ POP_NOT 里的 :not([data-mpw-pop-untrunc]) 排除,不打架。 */
18168
+ html body [data-mpw-pop-untrunc][data-mpw-pop-untrunc] {
18169
+ backdrop-filter: none !important;
18170
+ -webkit-backdrop-filter: none !important;
18171
+ }
18172
+ /* ②(2026-10-05 v3) 旧「根级模糊层」机制已删除(真机结论:挂在卡片的层 root 被截断;
18173
+ 挂 body 的层又会压住弹层——弹层在卡片的层叠上下文里)。data-mpw-pop-glass 现在只是"B 类、
18174
+ 祖先截断已被我们摘掉"的**诊断标记**,没有任何 CSS 效果(弹层自己的 blur 照常工作)。 */
18175
+ html body [data-mpw-pop-frost] { background-color: transparent !important; }
18138
18176
  html body[data-ds-dark-theme] [data-mpw-pop-bg] { background-color: var(--mpw-pop-surface-dark) !important; }
18139
18177
  [role="menu"] [class*="card"],
18140
18178
  [role="tooltip"] [class*="card"] {
@@ -24824,6 +24862,9 @@ body[data-mpw-modal] [role="dialog"] {
24824
24862
  } catch { sblurObserver = null; }
24825
24863
  // 窗口尺寸变化会让"近全屏"阈值与面板 rect 都变(父对话的真机判据就带视口切换)⇒ 一并重算
24826
24864
  try { window.addEventListener("resize", recheck); } catch {}
24865
+ /* ③(2026-10-05 v3) 旧「根级模糊层」是 fixed + 按弹层 rect 对齐 ⇒ 那时才需要 scroll/resize 重贴;
24866
+ 现在是"摘祖先 blur + 容器内 inset:0 的霜层",尺寸/位置全交给宿主自己的布局 ⇒ 两个监听删掉
24867
+ (顺带省掉滚动期的一路回调)。 */
24827
24868
  check();
24828
24869
  }
24829
24870
 
@@ -24933,10 +24974,22 @@ body[data-mpw-modal] [role="dialog"] {
24933
24974
  } catch (e) { return 0 }
24934
24975
  }
24935
24976
 
24977
+ /** 我们自己注入的节点(根级模糊层等):它们的突变**绝不能再喂回自己的快路**(否则死循环)。 */
24978
+ function mpwIsOurs(node) {
24979
+ try {
24980
+ if (!node || node.nodeType !== 1) return false;
24981
+ if (node.getAttribute && node.getAttribute("data-plugin") === "dsh-mpkg-wallpaper") return true;
24982
+ for (let p = node.parentElement, k = 0; p && k < 3; p = p.parentElement, k++) {
24983
+ if (p.getAttribute && p.getAttribute("data-plugin") === "dsh-mpkg-wallpaper") return true;
24984
+ }
24985
+ } catch (e) {}
24986
+ return false;
24987
+ }
24936
24988
  function mpwTagPopoverFast(records) {
24937
24989
  try {
24938
24990
  let n = 0;
24939
24991
  for (const rec of (records || [])) {
24992
+ if (mpwIsOurs(rec.target)) continue;
24940
24993
  /* ②(2026-10-05 真机仍见白帧的根因) 弹层大多是**先插入(隐藏)→ 再靠 class/style 显形**:
24941
24994
  只认 addedNodes 时,元素插入那一刻还是 display:none(判定不出"浮层面积",也看不见),
24942
24995
  等它显形时走的是 **attributes 突变**,而旧观察器只监听 childList ⇒ 显形那一帧没人管,
@@ -24960,32 +25013,49 @@ body[data-mpw-modal] [role="dialog"] {
24960
25013
  for (const el of cands) { if (mpwTagOnePopover(el)) n++; }
24961
25014
  }
24962
25015
  }
24963
- try { mpwSideLiftSync(); } catch (e) {}
24964
- try { mpwPopUntruncSync(); } catch (e) {}
25016
+ /* ③(2026-10-05 会话里掉帧) 快路里**只留打标记**:mpwSideLiftSync 是全文档扫描
25017
+ (class* 子串匹配,代价随会话 DOM 线性涨),侧栏收起这类"宿主每帧改 DOM"的场景下
25018
+ 每个突变批次都跑一次 ⇒ 会话里卡、空会话流畅。它挪去节流的重活路径(见 prun)。 */
25019
+ if (n) { try { mpwPopUntruncSync(); } catch (e) {} }
24965
25020
  return n;
24966
25021
  } catch (e) { return 0 }
24967
25022
  }
24968
25023
  /** 单个元素:该不该有我们的兜底底;返回是否发生了变化。 */
24969
25024
  function mpwTagOnePopover(el) {
24970
25025
  let need = false;
25026
+ let why = "";
24971
25027
  try {
24972
25028
  const cls = (typeof el.className === "string") ? el.className : "";
24973
- if (!MPW_POP_TAG_NOT.test(cls)) {
25029
+ /* ③(2026-10-05 白字修复) **提示气泡(role=tooltip)不接管底色**:宿主的气泡是深底浅字
25030
+ (--dsw-alias-tooltip-bg = #2c2c2e 配 #fff 字),我们那层 58% 浅色表面压上去 ⇒
25031
+ 白字看不清(真机:右栏右上角图标的气泡)。它自己的 blur 由 CSS 那条规则照常给。 */
25032
+ let isTip = false;
25033
+ try { isTip = el.getAttribute("role") === "tooltip" } catch (e) {}
25034
+ if (!MPW_POP_TAG_NOT.test(cls) && !isTip) {
24974
25035
  const cs = getComputedStyle(el);
24975
25036
  const blurred = !!(cs && /blur\(/.test(String(cs.backdropFilter || cs.webkitBackdropFilter || "")));
24976
25037
  if (blurred) {
24977
- const a = mpwColorAlpha(cs.backgroundColor);
24978
- /* ②(2026-10-05) 两类分开判:
24979
- · 宿主**没有底**(α<0.05)的弹层:**不要求此刻可见** —— 隐藏时打标无害,
24980
- 等它显形时表面已经在位(这正是"先插入后显形"那条路的白帧来源);
24981
- · 宿主画了**不透明底**的小浮层:必须量得到面积(隐藏时 rect=0 ⇒ 留给下一次触发)。 */
24982
- if (a < 0.05) need = !mpwPopHasPaintedAncestor(el);
24983
- else if (a >= 0.999 && cs.display !== "none" && cs.visibility !== "hidden") need = mpwPopIsFloating(el);
25038
+ /* ③(2026-10-05 闪烁修复) 已经带标记的:**只按当初那条结构性判据复核**,绝不重读底色。
25039
+ 标记一生效,底色就变成我们自己画的 58%,重读会得到"宿主有半透明底"⇒ 撤标 ⇒
25040
+ 宿主底回来 ⇒ 再打标 …… 逐帧自激(真机:"底色时有时无")。
25041
+ 属性值就是当初的理由:empty = 宿主没画底;painted = 宿主画了不透明底。 */
25042
+ const reason = el.getAttribute("data-mpw-pop-bg");
25043
+ if (reason === "empty") { need = !mpwPopHasPaintedAncestor(el); why = "empty" }
25044
+ else if (reason === "painted") { need = mpwPopIsFloating(el); why = "painted" }
25045
+ else {
25046
+ const a = mpwColorAlpha(cs.backgroundColor);
25047
+ /* ②(2026-10-05) 两类分开判:
25048
+ · 宿主**没有底**(α<0.05)的弹层:**不要求此刻可见** —— 隐藏时打标无害,
25049
+ 等它显形时表面已经在位(这正是"先插入后显形"那条路的白帧来源);
25050
+ · 宿主画了**不透明底**的小浮层:必须量得到面积(隐藏时 rect=0 ⇒ 留给下一次触发)。 */
25051
+ if (a < 0.05) { need = !mpwPopHasPaintedAncestor(el); why = "empty" }
25052
+ else if (a >= 0.999 && cs.display !== "none" && cs.visibility !== "hidden") { need = mpwPopIsFloating(el); why = "painted" }
25053
+ }
24984
25054
  }
24985
25055
  }
24986
25056
  } catch (e) { need = false; }
24987
25057
  try {
24988
- if (need) { if (!el.hasAttribute("data-mpw-pop-bg")) { el.setAttribute("data-mpw-pop-bg", ""); return true } }
25058
+ if (need) { if (el.getAttribute("data-mpw-pop-bg") !== why) { el.setAttribute("data-mpw-pop-bg", why); return true } }
24989
25059
  else if (el.hasAttribute("data-mpw-pop-bg")) { el.removeAttribute("data-mpw-pop-bg"); return true }
24990
25060
  } catch (e) {}
24991
25061
  return false;
@@ -24997,7 +25067,8 @@ body[data-mpw-modal] [role="dialog"] {
24997
25067
  for (let i = 0; i < list.length; i++) mpwTagOnePopover(list[i]);
24998
25068
  }
24999
25069
 
25000
- /* ①(P3 修复 2026-10-03) 弹层玻璃的 backdrop root 被截断 ⇒ 有半透明、没模糊。
25070
+ /* ①(P3 修复 2026-10-03;历史:机制已被下面的 v3 取代,保留沿革与真机读数)
25071
+ 弹层玻璃的 backdrop root 被截断 ⇒ 有半透明、没模糊。
25001
25072
  读数与机制(docs/POPOVER-BLUR.md):挂在宿主输入框卡片(uV2eYG_card,backdrop-filter:
25002
25073
  blur(14px))子树里的弹层,其 backdrop root 被卡片截断 ⇒ 弹层自己的 blur(11px) 只能采样
25003
25074
  卡片内部 = 看不出模糊;挂在 body 下的模型选择器正常。实验定案:把表面移出祖先链即恢复
@@ -25008,158 +25079,210 @@ body[data-mpw-modal] [role="dialog"] {
25008
25079
  (`[data-mpw-pop-untrunc] { backdrop-filter: none !important; }`)——只在弹层开着期间生效,
25009
25080
  弹层关闭(无已标记表面)即摘属性、撤规则元素,宿主逐字还原。代价:属性表 + 一次
25010
25081
  querySelectorAll,挂在 prun(已 rAF 合并 + mpwHeavyGate 闸门)里,不开新观察器。 */
25011
- let popUntruncStyleEl = null;
25012
- function mpwPopUntruncStyle() {
25013
- if (!popUntruncStyleEl || popUntruncStyleEl.isConnected === false) {
25014
- popUntruncStyleEl = document.createElement("style");
25015
- popUntruncStyleEl.setAttribute("data-plugin", "dsh-mpkg-wallpaper");
25016
- popUntruncStyleEl.setAttribute("data-mpw-pop-untrunc-style", "");
25017
- popUntruncStyleEl.textContent = "[data-mpw-pop-untrunc] { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }";
25018
- (document.head || document.documentElement).appendChild(popUntruncStyleEl);
25019
- }
25020
- return popUntruncStyleEl;
25082
+ /* ②(2026-10-05 设计收口 v1) 三类弹层,三条路(宿主元素一个都不碰):
25083
+ A. 没有模糊祖先(典型:挂在 body 下的模型/推理选择器)⇒ 我们自己的 58% 表面 + 自己的 blur;
25084
+ B. 祖先里有模糊、且那个祖先是**已知包装容器**(输入框卡片 / 菜单容器一类);
25085
+ C. 祖先里有模糊、但那不是包装容器(用户正在看的面板,例如子代理展开框)⇒ 88% 实底。 */
25086
+ /* ③(2026-10-05 v3 定案:**去截断 + 补霜**,取代 v2 的「根级模糊层」)
25087
+ 为什么 v2 不行(真机读数 tools/host-popover-probe.mjs --groups glass-placement,结构性判据不用像素):
25088
+ · 层挂在**卡片子树里**(v2 最后那版紧挨弹层之前)⇒ 它的 backdrop root 被 uV2eYG_card
25089
+ (宿主自己的 backdrop-filter: blur(14px))截断,采样到的只是卡片内部已经糊过的底色
25090
+ ⇒ 观感正好是用户报的"只剩一层白色半透明,模糊没了";
25091
+ · 层挂到 **body**(任何 z)⇒ root 确实干净了,但 `elementsFromPoint` 命中栈显示它**压在整个
25092
+ 弹层之上**:弹层在卡片的层叠上下文里(卡片有 backdrop-filter ⇒ 自成层叠上下文),
25093
+ body 级定位元素整体画在它上面 ⇒ 弹层会被我们的层糊掉。两条路都堵死。
25094
+ 所以只剩一条路:**把截断源头摘掉**——弹层开着期间给那个祖先打 `data-mpw-pop-untrunc`
25095
+ (静态规则 backdrop-filter:none !important ⇒ backdrop root 回到整页,弹层**自己的** blur
25096
+ 立刻采样得到页面,连目标条一起糊),丢掉的磨砂由 `data-mpw-pop-frost` 补回来。
25097
+ 真机验证(--groups glass-fix):卡片摘 bf + z-index:0(它本来就是 position:relative)后,
25098
+ 弹层截断链**清空**、弹层矩形**一字不动**、补的霜层与卡片矩形完全重合、中心命中仍是弹层自己。
25099
+ 代价:一次属性表 + 一次 querySelectorAll(挂在 prun,已 rAF 合并);层是 inset:0 的绝对定位,
25100
+ **不需要**随滚动/缩放重贴(v2 那两个 scroll/resize 监听因此删掉)。 */
25101
+ const popUntruncs = new Map(); // 祖先元素 -> { bf, radius, frost, frostKey, zInline }
25102
+ /** 该祖先能不能给"z-index:-1 的霜层"当层叠上下文:定位元素即可(静态元素我们不碰 ——
25103
+ 给它 position:relative 会改包含块、把宿主弹层带跑,真机混账过)。 */
25104
+ function mpwPopCanCtx(el, cs) {
25105
+ try {
25106
+ if (cs && cs.position) return cs.position !== "static";
25107
+ return getComputedStyle(el).position !== "static";
25108
+ } catch (e) { return false }
25021
25109
  }
25022
- function mpwPopUntruncSync() {
25110
+ /** 去截断 + 补霜(幂等:need 里没有的一律还原,宿主逐字回到原样)。 */
25111
+ function mpwPopUntruncApply(need) {
25112
+ let changed = 0;
25113
+ try {
25114
+ for (const el of Array.from(popUntruncs.keys())) {
25115
+ if (need && need.has(el) && el.isConnected !== false) continue;
25116
+ const rec = popUntruncs.get(el);
25117
+ if (rec && rec.frost && rec.frost.parentElement) { try { rec.frost.remove() } catch (e) {} }
25118
+ try { el.removeAttribute("data-mpw-pop-untrunc") } catch (e) {}
25119
+ /* 只还原我们动过的那一项(z-index 是我们为"造层叠上下文"补的) */
25120
+ if (rec && rec.zInline !== null) { try { el.style.zIndex = rec.zInline } catch (e) {} }
25121
+ popUntruncs.delete(el);
25122
+ changed++;
25123
+ }
25124
+ if (!need) return changed;
25125
+ for (const el of need) {
25126
+ let rec = popUntruncs.get(el) || null;
25127
+ if (!rec) {
25128
+ let cs = null;
25129
+ try { cs = getComputedStyle(el) } catch (e) {}
25130
+ const bf = (cs && cs.backdropFilter && cs.backdropFilter !== "none") ? String(cs.backdropFilter) : "";
25131
+ rec = { bf: bf, radius: (cs && cs.borderTopLeftRadius) || "0px", frost: null, frostKey: "", zInline: null };
25132
+ popUntruncs.set(el, rec);
25133
+ try { el.setAttribute("data-mpw-pop-untrunc", "") } catch (e) {}
25134
+ /* 层叠上下文:定位元素且 z-index:auto 时补 z-index:0(auto 与 0 的绘制次序一致,
25135
+ 不会把它挪到别的东西上下);已经是数值的不动(它本来就有层叠上下文)。 */
25136
+ try {
25137
+ const pcs = cs || getComputedStyle(el);
25138
+ if (pcs.position !== "static" && pcs.zIndex === "auto") { rec.zInline = el.style.zIndex || ""; el.style.zIndex = "0" }
25139
+ } catch (e) {}
25140
+ changed++;
25141
+ }
25142
+ const key = rec.bf + "|" + rec.radius;
25143
+ /* 存活判据用 parentElement + isConnected === false:桩/异常环境拿不到 isConnected 时
25144
+ 也不会每轮重建霜层(重建 = 不可见的 DOM 抖动)。 */
25145
+ const frostAlive = !!(rec.frost && rec.frost.parentElement && rec.frost.isConnected !== false);
25146
+ if (rec.bf && rec.bf.indexOf("blur") >= 0 && (!frostAlive || rec.frostKey !== key)) {
25147
+ try { if (rec.frost && rec.frost.parentElement) rec.frost.remove() } catch (e) {}
25148
+ const frost = document.createElement("div");
25149
+ frost.setAttribute("data-plugin", "dsh-mpkg-wallpaper");
25150
+ frost.setAttribute("data-mpw-pop-frost", "");
25151
+ /* inset:0 + z-index:-1:按 CSS 绘制顺序画在**父元素背景之上、内容之下** ⇒
25152
+ 容器自己的磨砂观感、圆角、内容层次全都不变(真机实测矩形与容器逐像素重合)。 */
25153
+ frost.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;z-index:-1;pointer-events:none;"
25154
+ + "border-radius:" + rec.radius + ";backdrop-filter:" + rec.bf + ";";
25155
+ rec.frost = frost;
25156
+ rec.frostKey = key;
25157
+ try { el.insertBefore(frost, el.firstChild) } catch (e) { try { el.appendChild(frost) } catch (e2) {} }
25158
+ changed++;
25159
+ }
25160
+ }
25161
+ } catch (e) {}
25162
+ return changed;
25163
+ }
25164
+ /* 判据出口(tools/popover-glass-test.mjs 直接用) */
25165
+ try {
25166
+ globalThis.__mpwPopUntruncTest = {
25167
+ sync: () => mpwPopClassifySync(),
25168
+ /* 旧名保留(旧判据引用过"根级模糊层"):v3 已经没有那层了,恒为 0 */
25169
+ glassCount: () => 0,
25170
+ /* 去截断的祖先数(每个都该配一层霜层:无 blur 可补的祖先不计霜层) */
25171
+ untruncCount: () => { try { return document.querySelectorAll("[data-mpw-pop-untrunc]").length } catch (e) { return 0 } },
25172
+ frostCount: () => { try { return document.querySelectorAll("[data-mpw-pop-frost]").length } catch (e) { return 0 } },
25173
+ truncCount: () => { try { return document.querySelectorAll("[data-mpw-pop-trunc]").length } catch (e) { return 0 } },
25174
+ /* 单个元素的兜底底判定(判据要能直接驱动它:闪烁是"打标/撤标"自激,必须单元素可复现) */
25175
+ tag: (el) => mpwTagOnePopover(el),
25176
+ };
25177
+ } catch (e) {}
25178
+
25179
+ function mpwPopClassifySync() {
25023
25180
  try {
25024
25181
  const surfaces = document.querySelectorAll("[data-mpw-pop-bg]");
25025
- const targets = new Set();
25182
+ const glasses = new Set();
25026
25183
  const truncated = new Set();
25027
- const markedNow = Array.from(document.querySelectorAll("[data-mpw-pop-untrunc]"));
25184
+ const neutralize = new Set();
25028
25185
  for (let i = 0; i < surfaces.length; i++) {
25029
25186
  const el = surfaces[i];
25030
25187
  let cs = null;
25031
25188
  try { cs = getComputedStyle(el) } catch (e) {}
25032
- /* 硬前提(与 mpwTagPopoverBg 同口径):我们真的给了模糊才值得摘祖先的截断 */
25033
- if (!cs || !cs.backdropFilter || String(cs.backdropFilter).indexOf("blur") < 0) continue;
25034
- /* ②(2026-10-05 抖动修复) **已经摘过、且仍在这条祖先链上**的祖先,弹层没关就一直保持:
25035
- 摘掉之后它自己就没有 blur 了,若每轮都重新"找第一个带 blur 的祖先",下一轮就找不到它
25036
- ⇒ 属性被当陈旧标记摘掉 ⇒ blur 又回来 ⇒ 摘/还原来回抖(真机现象:同一个菜单时好时坏)。 */
25037
- let kept = false;
25038
- for (const mEl of markedNow) {
25039
- if (mEl === el) continue;
25040
- let onChain = false;
25041
- /* 用 parentElement 走链(不用 contains:桩 DOM/老引擎上不一定有) */
25042
- for (let q = el.parentElement, k = 0; q && k < 40; q = q.parentElement, k++) { if (q === mEl) { onChain = true; break } }
25043
- if (onChain) { targets.add(mEl); kept = true; break }
25044
- }
25045
- if (kept) continue;
25046
- let blockedByGlass = false;
25189
+ /* 硬前提:我们自己给了模糊才有"被截断"这回事。
25190
+ ③(2026-10-05 闪烁修复) **已带标记的表面必须放行**:C 类规则把表面的 backdrop-filter
25191
+ 撤成 none,只认 computed blur 的话下一轮就会把刚打的标记摘掉 ⇒ 打标/撤标逐帧自激
25192
+ (真机实测:按钮提示气泡与几个面板底色"时有时无")。 */
25193
+ const marked = el.hasAttribute("data-mpw-pop-glass") || el.hasAttribute("data-mpw-pop-trunc");
25194
+ if (!marked && (!cs || !cs.backdropFilter || String(cs.backdropFilter).indexOf("blur") < 0)) continue;
25195
+ /* ③(2026-10-05) 提示气泡(role=tooltip)一律不接管:宿主的气泡是**深底浅字**
25196
+ (--dsw-alias-tooltip-bg 配 #fff 字),我们的表面/实底压上去 ⇒ 白字看不见。
25197
+ 它自己的 blur 由 CSS 那条 [role="tooltip"] 规则照常给。 */
25198
+ try { if (el.getAttribute("role") === "tooltip") continue } catch (e) {}
25199
+ let path = "";
25200
+ const cands = []; // 这条链上"可去截断"的祖先(走完再决定用不用:C 路一个都不摘)
25047
25201
  for (let p = el.parentElement, n = 0; p && p !== document.body && p !== document.documentElement && n < 12; p = p.parentElement, n++) {
25048
25202
  let acs = null;
25049
25203
  try { acs = getComputedStyle(p) } catch (e) { break }
25050
- const ablur = acs && acs.backdropFilter && acs.backdropFilter !== "none" && String(acs.backdropFilter).indexOf("blur") >= 0;
25204
+ /* ③(2026-10-05 反自激) 已经被我们"去截断"的祖先照样算截断祖先 —— 否则下一轮
25205
+ 看不见它的 blur(被我们摘了)⇒ 判成 A ⇒ 撤标记 ⇒ 还原 blur ⇒ 再判 B … 逐帧自激。 */
25206
+ const declared = !!(p.hasAttribute && p.hasAttribute("data-mpw-pop-untrunc"));
25207
+ const ablur = declared || (acs && acs.backdropFilter && acs.backdropFilter !== "none" && String(acs.backdropFilter).indexOf("blur") >= 0);
25051
25208
  if (!ablur) continue;
25052
- /* ②(2026-10-05 真机回归) **祖先自己是"看得见的玻璃板"时绝不动它**:
25053
- 它是用户能看到的那层模糊(真机现场:子代理展开框/输入框卡片被我们摘掉 blur 之后
25054
- "模糊背景没有了"——正是之前夸过的那处效果)。判据 = 它有可见底色(α ≥ 0.05)。
25055
- 这种情况下改为把**弹层自己的表面做实在**(不能糊到页面背后,就别假装是玻璃)。 */
25056
- /* ②(2026-10-05 真机复核) 判据收紧到"**它自己也是我们管的弹层表面**":
25057
- 子代理展开框/后台任务面板这类"用户正在看的面板"本身就在我们的弹层表面集合里
25058
- ⇒ 摘它的 blur = 把用户看得见的那层玻璃删掉(真机回归)。
25059
- 而输入框卡片 `uV2eYG_card`(有底、有 blur,但**不是**弹层表面)只是"碰巧挡在
25060
- 弹层前面的包装" ⇒ 摘掉它的 blur 换来弹层真模糊,代价是"菜单打开期间它自己不糊"。
25061
- 真机读数支持这条:四个弹层里只有 body 下的模型选择器有模糊,另三个全被卡片截断。 */
25062
- /* ②(2026-10-05 复核) **不做几何相交判定**:backdrop root 的截断是 DOM/层叠性质,
25063
- 与两者矩形是否重叠无关 —— 祖先的模糊区不覆盖弹层时,弹层的 backdrop 是**空的**,
25064
- 照样"只有半透明没有模糊"(真机就是这么报的)。⇒ 只要是已知包装容器就摘。 */
25065
- let ancestorIsSurface = false;
25066
- try { ancestorIsSurface = p.matches(MPW_POP_TAG_SEL) } catch (e) {}
25067
- if (ancestorIsSurface) { blockedByGlass = true; break }
25068
- /* ②(2026-10-05 审查后收紧) **只摘"已知包装容器"的 blur**:真机定案的截断者只有一类 ——
25069
- 输入框卡片这类"碰巧挡在弹层前面的包装"(类名含 `_card` / `composer` / `overlayAnchor`)。
25070
- 其余任何带 blur 的祖先(标题栏里的子代理展开框、后台任务面板、别的宿主面板)**一律不碰**:
25071
- 它们是用户**正在看**的玻璃,摘掉就是"效果没了"(真机已因此报过一次)。
25072
- 代价:包在未知面板里的弹层保持"半透明无模糊"——这是**保守但不会破坏用户看得见的东西**的一侧。 */
25073
- let strippableWrap = false;
25074
- try { strippableWrap = /(_card|composer|overlayAnchor|_seat|_stack)/i.test(String(p.className || "")) } catch (e) {}
25075
- if (!strippableWrap) { blockedByGlass = true; break }
25076
- targets.add(p);
25209
+ let isOurDeco = false;
25210
+ try { isOurDeco = !declared && p.matches(MPW_POP_TAG_SEL) && !p.hasAttribute("data-mpw-pop-bg") } catch (e) {}
25211
+ let wrap = false;
25212
+ try { wrap = declared || isOurDeco || /(_card|composer|overlayAnchor|_seat|_stack)/i.test(String(p.className || "")) } catch (e) {}
25213
+ /* B:能摘(它得是定位元素:霜层的 z-index:-1 要有层叠上下文兜住;真机两个截断祖先
25214
+ 都是定位的)⇒ 记进待摘表;C:其余的(用户正在看的面板 / 未知祖先 / 静态元素)保守做实在。 */
25215
+ if (wrap && mpwPopCanCtx(p, acs)) { cands.push(p); continue }
25216
+ path = "C";
25077
25217
  break;
25078
25218
  }
25079
- if (blockedByGlass) truncated.add(el);
25080
- }
25081
- const stale = markedNow;
25082
- for (let i = 0; i < stale.length; i++) { const el = stale[i]; if (!targets.has(el)) { try { el.removeAttribute("data-mpw-pop-untrunc") } catch (e) {} } }
25083
- for (const el of targets) { if (!el.hasAttribute("data-mpw-pop-untrunc")) { try { el.setAttribute("data-mpw-pop-untrunc", "") } catch (e) {} } }
25084
- /* 被"有底的玻璃祖先"挡住的那批:标记在**弹层自己**身上(绝不碰宿主元素),由 CSS 给实底 */
25085
- const staleT = document.querySelectorAll("[data-mpw-pop-trunc]");
25086
- for (let i = 0; i < staleT.length; i++) { const el = staleT[i]; if (!truncated.has(el)) { try { el.removeAttribute("data-mpw-pop-trunc") } catch (e) {} } }
25087
- for (const el of truncated) { if (!el.hasAttribute("data-mpw-pop-trunc")) { try { el.setAttribute("data-mpw-pop-trunc", "") } catch (e) {} } }
25088
- if (targets.size) mpwPopUntruncStyle();
25089
- else if (popUntruncStyleEl && popUntruncStyleEl.parentElement) {
25090
- /* ②(2026-10-05 审查建议) 撤除也要**瞬时**:宿主若给这些元素写了 transition,
25091
- 直接撤规则会反向播一次"清晰→磨砂"的可感知跳变。先注入一条"禁过渡 + 保持 none",
25092
- 下一帧再真正撤掉。 */
25093
- try {
25094
- const kill = document.createElement("style");
25095
- kill.setAttribute("data-plugin", "dsh-mpkg-wallpaper");
25096
- kill.setAttribute("data-mpw-pop-untrunc-kill", "");
25097
- kill.textContent = "[data-mpw-pop-untrunc]{transition:none !important;backdrop-filter:none !important;-webkit-backdrop-filter:none !important}";
25098
- (document.head || document.documentElement).appendChild(kill);
25099
- setTimeout(() => {
25100
- try { kill.remove() } catch (e) {}
25101
- try { if (popUntruncStyleEl && popUntruncStyleEl.parentElement) popUntruncStyleEl.remove() } catch (e) {}
25102
- popUntruncStyleEl = null;
25103
- }, 0);
25104
- } catch (e) {
25105
- try { popUntruncStyleEl.remove() } catch (e2) {}
25106
- popUntruncStyleEl = null;
25107
- }
25219
+ if (path === "C") truncated.add(el);
25220
+ else if (cands.length) { for (const p of cands) neutralize.add(p); glasses.add(el) }
25108
25221
  }
25109
- return targets.size + truncated.size * 1000; // 千位段 = 被"有底玻璃祖先"挡住、改为实底的弹层数
25222
+ const sync = (attr, want) => {
25223
+ const stale = document.querySelectorAll("[" + attr + "]");
25224
+ for (let i = 0; i < stale.length; i++) { const el = stale[i]; if (!want.has(el)) { try { el.removeAttribute(attr) } catch (e) {} } }
25225
+ for (const el of want) { if (!el.hasAttribute(attr)) { try { el.setAttribute(attr, "") } catch (e) {} } }
25226
+ };
25227
+ sync("data-mpw-pop-glass", glasses);
25228
+ sync("data-mpw-pop-trunc", truncated);
25229
+ mpwPopUntruncApply(neutralize);
25230
+ return glasses.size + truncated.size;
25110
25231
  } catch (e) { return 0 }
25111
25232
  }
25112
- /* ①(P3) 判据出口(tools/popover-untrunc-test.mjs 直接调 sync 断言 打标/还原/硬前提/首截断者)。 */
25113
- try {
25114
- globalThis.__mpwPopUntruncTest = {
25115
- sync: () => mpwPopUntruncSync(),
25116
- stylePresent: () => !!(popUntruncStyleEl && popUntruncStyleEl.parentElement),
25117
- killPresent: () => !!document.querySelector("[data-mpw-pop-untrunc-kill]"),
25118
- };
25119
- } catch (e) {}
25120
-
25121
- /* ②(2026-10-05) 探针/判据出口:弹层兜底底标记与"摘截断"两件事可被**手动触发并读回**——
25122
- 真机排障要能区分"根本没跑"(调度被饿死、observer 没接上)与"跑了但条件不满足"(宿主
25123
- 自己画了不透明白底/面积超阈值…)。只读+幂等,不改变任何语义。 */
25124
- try {
25125
- globalThis.__mpwPopTagTest = {
25126
- run: () => { try { return mpwTagPopoverBg() } catch (e) { return -1 } },
25127
- untrunc: () => { try { return mpwPopUntruncSync() } catch (e) { return -1 } },
25128
- state: (sel) => {
25129
- try {
25130
- const el = document.querySelector(sel || "[data-mpw-pop-bg]");
25131
- if (!el) return null;
25132
- const cs = getComputedStyle(el);
25133
- return { cls: String(el.className).slice(0, 60), tag: el.hasAttribute("data-mpw-pop-bg"), bg: cs.backgroundColor, bf: cs.backdropFilter, display: cs.display, vis: cs.visibility };
25134
- } catch (e) { return null }
25135
- },
25136
- };
25137
- } catch (e) {}
25233
+ /* 旧名保留(多处调用/探针判据在用) */
25234
+ function mpwPopUntruncSync() { return mpwPopClassifySync() }
25138
25235
 
25139
25236
  function setupPopTagWatch() {
25140
25237
  if (popTagWatch !== null) return;
25141
25238
  let praf = 0;
25239
+ /* ③(2026-10-05 会话里掉帧的根因) 下面这三个同步**每个都是全文档扫描**,而且选择器带
25240
+ `[class*=…]` 子串匹配 ⇒ 代价随会话 DOM 线性涨(真机现象正好是"空会话流畅、会话里卡":
25241
+ 宿主在侧栏收起/展开动画期间每帧改 DOM ⇒ 每个突变批次都把这三趟全扫一遍)。
25242
+ 改成**节流 + 尾随**:距上次 ≥150ms 才立刻跑;否则跳过并挂一个 180ms 的尾随定时器
25243
+ (突发结束一定还会跑一次,状态不会被饿死)。动画在跑时要求 600ms 才放行(优先保动画帧)。
25244
+ 注意:标记本身(data-mpw-pop-bg 等)仍走快路,不受这里影响。 */
25245
+ let popHeavyLast = 0, popHeavyTimer = 0;
25246
+ const nowMs = () => { try { return (typeof performance !== "undefined" && performance.now) ? performance.now() : Date.now() } catch (e) { return Date.now() } };
25247
+ const popHeavySync = () => {
25248
+ popHeavyLast = nowMs();
25249
+ try { mpwTagSurfaceOuter(); } catch (e) {}
25250
+ try { mpwPopUntruncSync(); } catch (e) {}
25251
+ try { mpwSideLiftSync(); } catch (e) {}
25252
+ };
25253
+ const popHeavyTail = () => {
25254
+ if (popHeavyTimer) return;
25255
+ try {
25256
+ popHeavyTimer = setTimeout(() => { popHeavyTimer = 0; try { popHeavySync() } catch (e) {} }, 180);
25257
+ } catch (e) { popHeavyTimer = 0; }
25258
+ };
25142
25259
  const prun = () => {
25143
25260
  praf = 0;
25144
25261
  try {
25145
25262
  // 左栏收起/展开与拖拽期间宿主每帧改 DOM:跳过(收尾还会再来一次)
25146
25263
  if (document.body && document.body.hasAttribute
25147
25264
  && (document.body.hasAttribute("data-dsh-sidebar-dragging") || document.body.hasAttribute("data-dragging"))) return;
25148
- /* ①(P1B) 动画期间跳过全文档弹层扫描(结束补跑一次) */
25149
- if (!mpwHeavyGate("popTag", prun)) return;
25150
- mpwTagPopoverBg();
25265
+ /* ②(2026-10-05 卡死 + 卡顿修复) 这一路**不再做全文档扫描**(旧写法每帧
25266
+ `querySelectorAll` 全文档,会话里元素多 ⇒ 侧栏动画就掉帧;空会话元素少 ⇒ 流畅,
25267
+ 与真机观察完全一致)。全文档扫描只在 apply/初始那一次,突变一律走 fast path。 */
25151
25268
  /* ②(2026-10-04) 右栏/dock 的"只留最外层"标记必须**跟着 DOM 走**:右栏面板是
25152
25269
  用户打开后才挂载的,apply 那一刻它还不存在 ⇒ 只按一次会把 dock 错标成表面,
25153
25270
  之后面板出现就变成两层(真机读数:dockPane/dockStrip 仍是 0.55)。
25154
- 这里与弹层标记共用同一个 rAF 合并的入口(要素很少,代价可忽略)。 */
25155
- try { mpwTagSurfaceOuter(); } catch (e) {}
25156
- try { mpwSideLiftSync(); } catch (e) {}
25157
- /* ①(P3) 弹层开着 ⇒ 摘截断祖先的 blur;关了 ⇒ 还原(同一入口,随突变重算) */
25158
- try { mpwPopUntruncSync(); } catch (e) {}
25271
+ 这里与弹层标记共用同一个节流入口(要素很少时几乎立刻就跑)。 */
25272
+ const since = nowMs() - popHeavyLast;
25273
+ if (since >= 150 && (!mpwHeavyAnimRunning() || since >= 600)) popHeavySync();
25274
+ else popHeavyTail();
25159
25275
  } catch (e) {}
25160
25276
  };
25161
25277
  try {
25162
25278
  popTagWatch = new MutationObserver((records) => {
25279
+ /* ②(2026-10-05 卡死修复的第二道保险) 全是**我们自己**的写入(根级模糊层的 style 等)
25280
+ ⇒ 直接返回:连 rAF 都不排,杜绝"写→观察→再写"的自激。 */
25281
+ let anyForeign = false;
25282
+ try {
25283
+ for (const rec of (records || [])) { if (!mpwIsOurs(rec.target)) { anyForeign = true; break } }
25284
+ } catch (e) { anyForeign = true; }
25285
+ if (!anyForeign) return;
25163
25286
  /* 同步快路:新增的弹层当场判定打标(消灭"先白后变"的过渡态) */
25164
25287
  try { mpwTagPopoverFast(records); } catch (e) {}
25165
25288
  if (praf) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mpkg-wallpaper",
3
- "version": "3.16.0",
3
+ "version": "3.16.3",
4
4
  "description": "DSH Web 壁纸引擎 Wallpaper Engine mpkg 背景插件:浏览器内直接解析 .mpkg(preview.gif 动态背景/内嵌 mp4 视频/多时段切换),整屏统一虚化/对话框/弹层/遮罩独立虚化、镜头缩放平移、时钟、冲突检测。",
5
5
  "private": false,
6
6
  "type": "module",