dsh-mpkg-wallpaper 3.13.2 → 3.13.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.
- package/lib/client.js +95 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -80,6 +80,95 @@ function mpwTrace(ev, data) {
|
|
|
80
80
|
return Math.max(1, Math.min(120, Number.isFinite(n) && n > 0 ? n : 5));
|
|
81
81
|
}
|
|
82
82
|
try { window.__mpwClampRotMin = mpwClampRotMin; } catch {}
|
|
83
|
+
/* ═══ MPW-BLOBRETRY-BEGIN ═══ */
|
|
84
|
+
/** blob 媒体「有界重试看门狗」(**唯一定义处**;上游 Firefox Bug 2056444 的页内兜底)。
|
|
85
|
+
* 症状(已定性,有实测证据):把**包内字节**做成 `blob:` URL 再喂 `<video>`/`<audio>`,在受影响的
|
|
86
|
+
* Firefox 构建上会**静默挂住** —— `readyState` 恒 0、`networkState` 恒 1、**不触发 error**、
|
|
87
|
+
* `play()` 的 Promise 永不 settle。
|
|
88
|
+
* 上游回归:Bug 2056444(由 Bug 2005247 引入,Firefox 152 起;官方已修:155 nightly → beta 154 →
|
|
89
|
+
* release 153(153.0.3)/ ESR153)。机制:blob 数据超过 **1MiB 的 IPC 内联上限**后改走 IPC 异步流,
|
|
90
|
+
* `CloneableWithRangeMediaResource` 在异步 IPC 流上做同步读 ⇒ 首次元数据读(MP4 的 `moov` 常在
|
|
91
|
+
* **尾部** = 远端 seek)挂住;`decodeAudioData` 同族也会挂。
|
|
92
|
+
* 证据(**仓外**姐妹仓库「渲染器仓」的两条探针,只读引用、**勿改**;本仓门禁禁止在 lib/** 里写跨仓路径):
|
|
93
|
+
* · `tests/mpkg-video-decode-probe.mjs` —— 同一段 22MB h264 字节:HTTP 直供 `rs=4`(约 0.4s);
|
|
94
|
+
* blob 恒 `rs=0`;14KB blob `rs=4`(⇒ 与 1MiB 上限相关);`new Blob([buf])`、`new File([buf])`、
|
|
95
|
+
* `new Response(buf).blob()`、`data:` URL 四种写法全部复现。
|
|
96
|
+
* · `tests/mpkg-videobase-recovery-probe.mjs` —— 真页面里元素卡在 `rs=0` 后,**只补一次 `load()`**
|
|
97
|
+
* 就恢复(`rs=4 / 1636x1156 / dur=30`,并且画面真的出来了)。
|
|
98
|
+
* ⇒ 页内可用的兜底 = **有界重试 `load()`**:最多 2 次、间隔 1500ms。
|
|
99
|
+
* **健康浏览器上永不触发**:`rs≥1` 早在 1.5s 内到达 ⇒ 首次检查就返回,一次 `load()` 都不会多调。
|
|
100
|
+
* 契约(`tools/blob-media-retry-test.mjs` S2 逐条钉住):①有界(2 次 / 1500ms);②`rs≥1` 或
|
|
101
|
+
* `el.error` 非空 ⇒ 立刻停(不再重试、不再计时);③每次重试**恰好一行**日志(`🔁` + label + 第几次
|
|
102
|
+
* + 缺陷号 2056444);④2 次用尽仍 `rs=0` ⇒ **恰好一行** `⚠`;⑤绝不抛异常(元素已摘 / `load()` 抛错 /
|
|
103
|
+
* 无 `window` 一律吞掉);⑥只兜 `blob:` 源(HTTP 直供是健康路径 —— 探针里 `rs=4` 的就是它 —— 不打扰)。
|
|
104
|
+
* 计数钩子 `window.__mpwBlobMediaRetry`(**仅当 `window` 存在时**写;语义如下):
|
|
105
|
+
* `calls` = 本函数被调用的次数(每处接线各算一次,**不代表真的重试过**);
|
|
106
|
+
* `retries` = 真的调了 `el.load()` 的次数;
|
|
107
|
+
* `gaveUp` = 2 次用尽仍 `rs=0` 的收尾次数;
|
|
108
|
+
* `byLabel` = 同口径按 `label` 分桶:`{ [label]: { calls, retries, gaveUp } }`。
|
|
109
|
+
* 纯诊断、无用户可见提示:恢复是自动的,正常用户不该被打扰(只在控制台留痕)。 */
|
|
110
|
+
function mpwBlobMediaRetry(el, label) {
|
|
111
|
+
const RETRY_MAX = 2, RETRY_GAP_MS = 1500, BUG = "2056444";
|
|
112
|
+
const tag = String(label == null ? "" : label);
|
|
113
|
+
/** 计数(**唯一**碰 `window` 的地方;任何一步失败都只吞掉,绝不影响兜底主流程)。 */
|
|
114
|
+
const count = (k) => {
|
|
115
|
+
try {
|
|
116
|
+
if (typeof window === "undefined" || !window) return;
|
|
117
|
+
const st = window.__mpwBlobMediaRetry || (window.__mpwBlobMediaRetry = { calls: 0, retries: 0, gaveUp: 0, byLabel: {} });
|
|
118
|
+
const bk = st.byLabel || (st.byLabel = {});
|
|
119
|
+
const b = bk[tag] || (bk[tag] = { calls: 0, retries: 0, gaveUp: 0 });
|
|
120
|
+
st[k] = (st[k] || 0) + 1;
|
|
121
|
+
b[k] = (b[k] || 0) + 1;
|
|
122
|
+
} catch (e) {}
|
|
123
|
+
};
|
|
124
|
+
/** 停的条件:已出元数据(`rs≥1`)或已报错(`error` 非空)。读失败按"停"处理(不拿读不准的状态去戳元素)。 */
|
|
125
|
+
const done = () => { try { return !!(el && (el.readyState >= 1 || el.error)) } catch (e) { return true } };
|
|
126
|
+
/** 只兜 `blob:` 源:先读 `src` 属性(`showVideoEl` 判等用的就是它,避免 `currentSrc` 还没换过来的窗口),
|
|
127
|
+
* 读不到再退回属性值;读不出 / 为空 ⇒ 放行(fail-open:宁可多兜一次,也不漏掉真挂住的那个)。 */
|
|
128
|
+
const blobSrc = () => {
|
|
129
|
+
try {
|
|
130
|
+
let u = "";
|
|
131
|
+
try { if (el && typeof el.getAttribute === "function") u = String(el.getAttribute("src") || "") } catch (e) {}
|
|
132
|
+
if (!u) { try { u = String((el && el.src) || "") } catch (e) {} }
|
|
133
|
+
return !u || /^blob:/i.test(u);
|
|
134
|
+
} catch (e) { return true }
|
|
135
|
+
};
|
|
136
|
+
let tries = 0, timer = null;
|
|
137
|
+
const tick = () => {
|
|
138
|
+
try {
|
|
139
|
+
timer = null;
|
|
140
|
+
if (done()) return; // 已出元数据 / 已报错 ⇒ 停(不再重试、不再计时)
|
|
141
|
+
if (tries >= RETRY_MAX) { // 2 次用尽仍 rs=0 ⇒ 恰好一行 ⚠ 收尾
|
|
142
|
+
count("gaveUp");
|
|
143
|
+
try { console.warn("[dsh-mpkg-wallpaper] ⚠ " + tag + " 始终未出元数据 rs=0,已重试 " + RETRY_MAX + " 次(上游 Firefox Bug " + BUG + ":blob>1MiB 走 IPC 异步流、同步读挂住;健康浏览器上永不触发)"); } catch (e) {}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
tries++; // 只数"真的调了 load()"的次数
|
|
147
|
+
count("retries");
|
|
148
|
+
try { console.warn("[dsh-mpkg-wallpaper] 🔁 " + tag + " rs=0 无元数据 → 第 " + tries + "/" + RETRY_MAX + " 次补 load()(上游 Firefox Bug " + BUG + ";依据渲染器仓探针 tests/mpkg-videobase-recovery-probe.mjs:只补一次 load() 即恢复)"); } catch (e) {}
|
|
149
|
+
try { el.load() } catch (e) {}
|
|
150
|
+
try { const p = el.play(); if (p && typeof p.catch === "function") p.catch(() => {}) } catch (e) {}
|
|
151
|
+
timer = setTimeout(tick, RETRY_GAP_MS);
|
|
152
|
+
} catch (e) {}
|
|
153
|
+
};
|
|
154
|
+
try {
|
|
155
|
+
count("calls");
|
|
156
|
+
if (!el) return; // 元素已摘 / 还没建:什么都不做
|
|
157
|
+
if (!blobSrc()) return; // HTTP/宿主直供:不打扰(探针里 rs=4 的就是这条健康路径)
|
|
158
|
+
if (done()) return; // 健康浏览器:一次 load() 都不调、也不计时
|
|
159
|
+
/* "立刻停"的另一半:真出元数据 / 真报错时把**待发的那一发**撤掉(不产生多余的 load())。
|
|
160
|
+
`{once:true}` + 元素长期存活 ⇒ 不累积;拿不到 addEventListener 就算了(tick 里仍会判)。 */
|
|
161
|
+
try {
|
|
162
|
+
if (typeof el.addEventListener === "function") {
|
|
163
|
+
const stop = () => { try { if (timer != null) clearTimeout(timer) } catch (e) {} timer = null };
|
|
164
|
+
el.addEventListener("loadedmetadata", stop, { once: true });
|
|
165
|
+
el.addEventListener("error", stop, { once: true });
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {}
|
|
168
|
+
timer = setTimeout(tick, RETRY_GAP_MS);
|
|
169
|
+
} catch (e) {}
|
|
170
|
+
}
|
|
171
|
+
/* ═══ MPW-BLOBRETRY-END ═══ */
|
|
83
172
|
// ①(P-104 2026-09-17 用户发布纪律②) **插件侧 localStorage 也要有上限**:
|
|
84
173
|
// 本插件只用固定几个键(STORE_KEY / mpw_settings_backup / mpwdiag / mpwwatch),键数量天然有界;
|
|
85
174
|
// 真正的风险在**单值**——STORE_KEY 里含背景 data URL(本文件注释自述 1MB+)。这里给单值硬顶
|
|
@@ -6596,6 +6685,9 @@ function mpwThumbFail(el, why) {
|
|
|
6596
6685
|
// ⑤(新) Edge 兼容:画到 canvas
|
|
6597
6686
|
if (IS_EDGE) { try { showVideoEdge(url, bgElements().video); } catch { showVideoEl(url); } }
|
|
6598
6687
|
else showVideoEl(url);
|
|
6688
|
+
/* ①(2026-09-23 上游 Firefox Bug 2056444) blob: 喂 `<video>` 的**唯一落点**(`showVideoEl` 只在这里
|
|
6689
|
+
拿到新建的 ObjectURL)⇒ 挂有界重试看门狗。健康浏览器 `rs≥1` ⇒ 一次 `load()` 都不会多调。 */
|
|
6690
|
+
mpwBlobMediaRetry(bgElements().video, "视频壁纸");
|
|
6599
6691
|
}).catch(() => {});
|
|
6600
6692
|
} else if (image === "idb:img") {
|
|
6601
6693
|
// ③(新) 大图片 Blob 路径:ObjectURL 显示,不走 dataURL(防内存膨胀)
|
|
@@ -8458,6 +8550,9 @@ function mpwThumbFail(el, why) {
|
|
|
8458
8550
|
npAudio.src = obj;
|
|
8459
8551
|
npBlobUrlSet(obj);
|
|
8460
8552
|
const p = npAudio.play(); if (p && p.catch) p.catch(() => {});
|
|
8553
|
+
/* ①(2026-09-23 上游 Firefox Bug 2056444) 同一族的静默挂起(blob 音轨 rs=0 / 无 error、
|
|
8554
|
+
play() 的 Promise 永不 settle;`decodeAudioData` 亦然)⇒ 同一个有界重试兜底。 */
|
|
8555
|
+
mpwBlobMediaRetry(npAudio, "音轨");
|
|
8461
8556
|
}).catch(() => {});
|
|
8462
8557
|
} catch (e) {}
|
|
8463
8558
|
}
|
package/package.json
CHANGED