From 605a48adb044946710c360682daf56c42b5ec5db Mon Sep 17 00:00:00 2001 From: m1saka Date: Sun, 18 Oct 2026 14:22:40 +0800 Subject: [PATCH] Revert "fix(runtime): polyfill PDF.js Map APIs for older WebViews" This reverts commit e84906652ea36f3568cc37e6b4d677b426dcefc3. --- README.md | 17 ------- image/Dockerfile | 2 - image/install-webview-compat.mjs | 67 -------------------------- image/webview-compat.test.mjs | 83 -------------------------------- 4 files changed, 169 deletions(-) delete mode 100644 image/install-webview-compat.mjs delete mode 100644 image/webview-compat.test.mjs diff --git a/README.md b/README.md index 70496e0..df6b7bc 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,5 @@ # DSH Runtime Release -## 未发布:Android WebView 文档预览兼容补丁 - -针对 Chrome/WebView 143 缺少 Map.getOrInsertComputed 的错误,构建时为 DSH 0.2.1-rc.2 文档预览组件的 PDF.js 6.3.289 主线程和内嵌 Worker 同时加入 Map.prototype.getOrInsertComputed / getOrInsert 兼容实现。仅在方法缺失时安装,不替换已有原生实现。补丁检查宿主、插件版本及 Worker 特征;版本不匹配或重复应用时构建失败。无需在 Flutter 加载完成回调中注入,也不修改用户 profile。 - -验证:本地 node --test image/*.test.mjs 为 50 通过、1 跳过;跳过的真实资源检查在现有 dsh-web:runtime-7.9-amd64 镜像中单独运行,专项 6 项全部通过。覆盖缺失 API 的错误复现、Promise 缓存、空值、异常、重入、独立 Worker 及真实 bundle/worker 语法检查,并在禁网临时容器内执行安装脚本成功。未完成 Android 143 真机预览、完整镜像重建或线上部署,不代表所有新版 PDF.js API 都已兼容。 - -重新构建并部署含补丁的 runtime 后才会生效;部署后重新加载 App 内页面,若仍命中旧脚本需清理 WebView 缓存。发布前按下文流程分配新的 release_id;本次未修改发布清单或推送发布。 - -真实资源专项测试(仓库根目录): - -~~~sh -docker run --rm --network none \ - --mount type=bind,src="$PWD/image",dst=/test,readonly \ - -e DSH_PDF_CLIENT=/usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-ui-sidebar-documentpreview/lib/client.pdf.js \ - --entrypoint node dsh-web:runtime-7.9-amd64 --test /test/webview-compat.test.mjs -~~~ - ## runtime-7.9:DSH next 与启动配置迁移 锁文件同步修复:内置依赖迁移或补建 workspace 配置前,先持久化 `.dsh-lockfile-pending` 标记。仅带标记的启动安装显式传入 `--no-frozen-lockfile`,同步 `pnpm-lock.yaml`;成功后清除标记,后续启动恢复 CI 默认冻结检查。安装失败或超时保留标记并阻止应用启动,下次启动继续同步,不自动恢复旧 manifest。已有 manifest 备份机制保持不变。若旧版失败迁移已经留下“新 manifest+旧锁文件”且没有标记,应先恢复匹配的旧 profile 备份再升级;不要直接删除锁文件。 diff --git a/image/Dockerfile b/image/Dockerfile index f9aeec4..c0f9a9f 100644 --- a/image/Dockerfile +++ b/image/Dockerfile @@ -37,8 +37,6 @@ RUN node /usr/local/lib/dsh-mgr/install-office-preview.mjs COPY preview-download /usr/local/lib/dsh-mgr/preview-download COPY install-preview-download.mjs /usr/local/lib/dsh-mgr/install-preview-download.mjs RUN node /usr/local/lib/dsh-mgr/install-preview-download.mjs -COPY install-webview-compat.mjs /usr/local/lib/dsh-mgr/install-webview-compat.mjs -RUN node /usr/local/lib/dsh-mgr/install-webview-compat.mjs ARG RECIPE_REV=7 LABEL dsh-mgr.role=runtime dsh-mgr.version=$DSH_VERSION dsh-mgr.recipe=$RECIPE_REV ENV DSH_HOME=/workspace HOME=/workspace XDG_DATA_HOME=/workspace/.local/share XDG_CONFIG_HOME=/workspace/.config diff --git a/image/install-webview-compat.mjs b/image/install-webview-compat.mjs deleted file mode 100644 index 08b7b1b..0000000 --- a/image/install-webview-compat.mjs +++ /dev/null @@ -1,67 +0,0 @@ -import { readFileSync, writeFileSync } from 'node:fs'; -import { join, resolve } from 'node:path'; -import { pathToFileURL } from 'node:url'; - -// Executed independently in the page and the embedded PDF worker. -export function installMapCompat() { - 'use strict'; - const { has, get, set } = Map.prototype; - if (typeof Map.prototype.getOrInsertComputed !== 'function') { - Object.defineProperty(Map.prototype, 'getOrInsertComputed', { - configurable: true, writable: true, - value: function getOrInsertComputed(key, callback) { - const exists = has.call(this, key); - if (typeof callback !== 'function') throw new TypeError('callback must be callable'); - if (exists) return get.call(this, key); - if (key === 0) key = 0; // Canonicalize -0 before invoking callback. - const value = callback(key); - set.call(this, key, value); - return value; - }, - }); - } - if (typeof Map.prototype.getOrInsert !== 'function') { - Object.defineProperty(Map.prototype, 'getOrInsert', { - configurable: true, writable: true, - value: function getOrInsert(key, value) { - if (has.call(this, key)) return get.call(this, key); - set.call(this, key, value); - return value; - }, - }); - } -} - -const newline = String.fromCharCode(10); -export const prelude = '/* dsh-mgr: webview-map-compat */' + newline + '(' + installMapCompat.toString() + ')();' + newline; -export const workerPrefix = 'var _dsh_pdf_worker_default = '; - -export function patchPdfClient(source, version, pluginVersion) { - if (version !== '0.2.1-rc.2' || pluginVersion !== version) { - throw new Error('unsupported DSH/documentpreview version for WebView compatibility patch'); - } - if (source.includes('dsh-mgr: webview-map-compat')) throw new Error('WebView compatibility patch already applied'); - const lines = source.split(newline); - const targets = lines.filter(line => line.trimStart().startsWith(workerPrefix)); - if (targets.length !== 1 || !source.includes('this.#methodPromises.getOrInsertComputed(')) { - throw new Error('expected PDF.js method cache and exactly one embedded worker'); - } - const target = targets[0]; - // Preserve the JS string literal verbatim (it can contain non-JSON escapes). - const worker = target.trim().slice(workerPrefix.length, -1); - if (!worker.includes('getOrInsertComputed') || !worker.includes('pdfjsVersion = 6.3.289')) { - throw new Error('unexpected embedded PDF.js worker'); - } - // A page-level polyfill cannot cross into the worker realm. - return prelude + source.replace(target, () => workerPrefix + JSON.stringify(prelude) + ' + ' + worker + ';'); -} - -export function install(root = '/usr/local/lib/node_modules/@deepseek-ai/dsh') { - const plugin = join(root, 'node_modules/@deepseek-ai/dsh-client-ui-sidebar-documentpreview'); - const version = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version; - const pluginVersion = JSON.parse(readFileSync(join(plugin, 'package.json'), 'utf8')).version; - const file = join(plugin, 'lib/client.pdf.js'); - writeFileSync(file, patchPdfClient(readFileSync(file, 'utf8'), version, pluginVersion)); -} - -if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) install(); diff --git a/image/webview-compat.test.mjs b/image/webview-compat.test.mjs deleted file mode 100644 index 79cdb5f..0000000 --- a/image/webview-compat.test.mjs +++ /dev/null @@ -1,83 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import vm from 'node:vm'; -import { readFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; -import { patchPdfClient, prelude, workerPrefix } from './install-webview-compat.mjs'; - -function realm() { - const ctx = vm.createContext({ assert }); - vm.runInContext('delete Map.prototype.getOrInsert; delete Map.prototype.getOrInsertComputed;', ctx); - return ctx; -} - -test('reproduces missing API, then caches one Promise per key', async () => { - const ctx = realm(); - assert.throws(() => vm.runInContext('new Map().getOrInsertComputed("x", () => 1)', ctx), /not a function/); - vm.runInContext(prelude, ctx); - await vm.runInContext( - '(async () => { const map = new Map(); let calls = 0; const create = key => { assert.equal(key, "metadata"); calls++; return Promise.resolve(42); }; const a = map.getOrInsertComputed("metadata", create); const b = map.getOrInsertComputed("metadata", create); assert.equal(a, b); assert.equal(await a, 42); assert.equal(calls, 1); })()', ctx); -}); - -test('preserves falsy values, callback semantics, key identity and exceptions', () => { - const ctx = realm(); - vm.runInContext(prelude, ctx); - vm.runInContext( - 'const map = new Map(); for (const v of [undefined, null, false, 0, ""]) { map.set("x", v); assert.equal(map.getOrInsertComputed("x", () => { throw Error("unexpected"); }), v); assert.equal(map.getOrInsert("x", 99), v); }' + - 'assert.throws(() => map.getOrInsertComputed("x", null), /callable/); assert.throws(() => Map.prototype.getOrInsert.call({}, "x", 1));' + - 'const key = {}; assert.equal(map.getOrInsertComputed(key, k => k), key); assert.equal(map.getOrInsertComputed(NaN, () => 3), 3); assert.equal(map.getOrInsertComputed(NaN, () => 4), 3);' + - 'map.getOrInsertComputed(-0, k => { assert.equal(Object.is(k, -0), false); return 0; });' + - 'assert.throws(() => map.getOrInsertComputed("error", () => { throw Error("boom"); }), /boom/); assert.equal(map.has("error"), false);' + - 'assert.equal(map.getOrInsertComputed("reentrant", () => { map.set("reentrant", 1); return 2; }), 2); assert.equal(map.get("reentrant"), 2);' + - 'map.getOrInsertComputed("this", function () { "use strict"; assert.equal(this, undefined); });' + - 'assert.equal(Object.getOwnPropertyDescriptor(Map.prototype, "getOrInsertComputed").enumerable, false);', ctx); -}); - -test('does not replace existing implementations and is safe to run twice', () => { - const ctx = realm(); - vm.runInContext(prelude, ctx); - vm.runInContext('const original = Map.prototype.getOrInsertComputed; const direct = Map.prototype.getOrInsert;', ctx); - vm.runInContext(prelude, ctx); - vm.runInContext('assert.equal(Map.prototype.getOrInsertComputed, original); assert.equal(Map.prototype.getOrInsert, direct);', ctx); -}); - -const nl = String.fromCharCode(10); -const worker = '/* pdfjsVersion = 6.3.289 */' + nl + 'globalThis.result = new Map().getOrInsertComputed("worker", () => 7);'; -const fixture = '// this.#methodPromises.getOrInsertComputed(' + nl + workerPrefix + JSON.stringify(worker) + ';'; -const patch = source => patchPdfClient(source, '0.2.1-rc.2', '0.2.1-rc.2'); - -test('patch initializes both page and isolated embedded worker before use', () => { - const output = patch(fixture); - const page = realm(); - vm.runInContext(output, page); - const isolated = realm(); - vm.runInContext(page._dsh_pdf_worker_default, isolated); - assert.equal(isolated.result, 7); - assert.ok(output.startsWith(prelude)); -}); - -test('rejects version drift, missing/duplicate worker and repeated patch', () => { - assert.throws(() => patchPdfClient(fixture, 'other', 'other'), /unsupported/); - assert.throws(() => patchPdfClient(fixture, '0.2.1-rc.2', 'other'), /unsupported/); - assert.throws(() => patch(''), /expected/); - assert.throws(() => patch(fixture + nl + fixture), /exactly one/); - assert.throws(() => patch(fixture.replace('6.3.289', '0.0.0')), /unexpected/); - assert.throws(() => patch(patch(fixture)), /already applied/); -}); - -test('actual installed PDF bundle and embedded worker patch and parse', { skip: !process.env.DSH_PDF_CLIENT }, () => { - const source = readFileSync(process.env.DSH_PDF_CLIENT, 'utf8'); - const output = patch(source); - const line = output.split(nl).find(line => line.startsWith(workerPrefix)); - const embedded = vm.runInNewContext(line.slice(workerPrefix.length, -1), {}, { timeout: 1000 }); - assert.ok(embedded.startsWith(prelude)); - for (const code of [output, embedded]) { - const result = spawnSync(process.execPath, ['--input-type=module', '--check'], { input: code, encoding: 'utf8' }); - assert.equal(result.status, 0, result.stderr); - } - // Exercise the exact upstream cache method body with the API removed. - const body = source.split('#cacheSimpleMethod(name, data = null) {')[1].split(nl)[1].trim(); - const ctx = realm(); - vm.runInContext(prelude, ctx); - vm.runInContext('class Transport { #methodPromises = new Map(); calls = 0; messageHandler = { sendWithPromise: () => { this.calls++; return Promise.resolve(1); } }; cached(name, data = null) { ' + body + ' } } const t = new Transport(); assert.equal(t.cached("GetMetadata"), t.cached("GetMetadata")); assert.equal(t.calls, 1);', ctx); -});