Skip to content

Restore mobile PDF preview compatibility for browsers without Array.prototype.at#729

Merged
klboke merged 4 commits intomasterfrom
copilot/fix-pdf-preview-error
Apr 9, 2026
Merged

Restore mobile PDF preview compatibility for browsers without Array.prototype.at#729
klboke merged 4 commits intomasterfrom
copilot/fix-pdf-preview-error

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 9, 2026

Mobile PDF preview could fail during PDF.js initialization with pattern.at is not a function, leaving the viewer blank on older mobile browsers. The failure also broke worker startup, cascading into fake-worker initialization errors.

  • Problem scope

    • PDF.js 5.x assets in kkFileView rely on .at() in both the viewer/runtime path and the worker path.
    • Older mobile WebViews/Safari variants do not implement .at(), so PDF rendering fails before the document can load.
  • Compatibility shim

    • Added server/src/main/resources/static/pdfjs/web/compatibility.mjs
    • Polyfills .at() for:
      • Array
      • String
      • typed arrays
    • Keeps the shim narrowly scoped to the missing API instead of changing viewer behavior more broadly.
  • Viewer/worker wiring

    • Load the compatibility module before pdf.mjs in server/src/main/resources/static/pdfjs/web/viewer.html
    • Import the same module from server/src/main/resources/static/pdfjs/build/pdf.worker.mjs
    • This covers both:
      • normal viewer boot
      • worker / fake-worker fallback paths
  • Regression coverage

    • Added server/src/test/java/cn/keking/PdfViewerCompatibilityTests.java
    • Verifies the compatibility module remains wired into both the viewer entrypoint and the worker entrypoint.
  • Representative change

    <link rel="resource" type="application/l10n" href="locale/locale.json" />
    <script src="compatibility.mjs" type="module"></script>
    <script src="../build/pdf.mjs" type="module"></script>
  • Screenshot

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • repository.aspose.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --enable-native-access=ALL-UNNAMED -classpath /usr/share/apache-maven-3.9.14/boot/plexus-classworlds-2.9.0.jar -Dclassworlds.conf=/usr/share/apache-maven-3.9.14/bin/m2.conf -Dmaven.home=/usr/share/apache-maven-3.9.14 -Dlibrary.jansi.path=/usr/share/apache-maven-3.9.14/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/kkFileView/kkFileView org.codehaus.plexus.classworlds.launcher.Launcher -B clean test (dns block)
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --enable-native-access=ALL-UNNAMED -classpath /usr/share/apache-maven-3.9.14/boot/plexus-classworlds-2.9.0.jar -Dclassworlds.conf=/usr/share/apache-maven-3.9.14/bin/m2.conf -Dmaven.home=/usr/share/apache-maven-3.9.14 -Dlibrary.jansi.path=/usr/share/apache-maven-3.9.14/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/kkFileView/kkFileView org.codehaus.plexus.classworlds.launcher.Launcher -B clean package -Dmaven.test.skip=true (dns block)
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --enable-native-access=ALL-UNNAMED -classpath /usr/share/apache-maven-3.9.14/boot/plexus-classworlds-2.9.0.jar -Dclassworlds.conf=/usr/share/apache-maven-3.9.14/bin/m2.conf -Dmaven.home=/usr/share/apache-maven-3.9.14 -Dlibrary.jansi.path=/usr/share/apache-maven-3.9.14/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/kkFileView/kkFileView org.codehaus.plexus.classworlds.launcher.Launcher -f pom.xml -B -V -e -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -Dmaven.javadoc.skip (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI linked an issue Apr 9, 2026 that may be closed by this pull request
3 tasks
Copilot AI changed the title [WIP] Fix mobile PDF preview error when using pattern.at Restore mobile PDF preview compatibility for browsers without Array.prototype.at Apr 9, 2026
Copilot AI requested a review from klboke April 9, 2026 02:52
@klboke
Copy link
Copy Markdown
Contributor

klboke commented Apr 9, 2026

整体看下来没问题,这个修复方式比较克制,风险也相对可控。

我的 review 结论:

  • 修复点收得比较准,只补了 .at() 兼容层,没有去大范围改 pdf.js 主逻辑。
  • 同时覆盖了 viewer 和 worker 两边,这点是对的,能避免只修一半导致 fake worker 路径继续出问题。
  • 新增的测试虽然偏“接线验证”,但至少能防止后续升级 pdf.js 资源时把 compatibility 引用丢掉。

目前我没有看到 blocker,可以合。

一个后续可选增强建议(不阻塞这次 PR):

  • 可以考虑再补一个更贴近真实运行时的兼容性 smoke,比如模拟 Array.prototype.at 缺失的环境,验证 viewer 至少能正常启动。

总体来说,这个 PR 我这边是 LGTM 的。

@klboke
Copy link
Copy Markdown
Contributor

klboke commented Apr 9, 2026

补充一下我这边的本地验证结果(不是只看 diff):

我把这个 PR 拉到本地分支后,额外做了几轮验证:

  • mvn -q -pl server -Dtest=PdfViewerCompatibilityTests test:通过
  • mvn -q -pl server -DskipTests package:通过
  • 本地启动服务后,现有 PDF 预览主链路正常:sample.pdf文件转换中 -> sample.pdf图片预览
  • 直接访问 pdf.js viewer 页面并加载同源 PDF,控制台里没有再出现 pattern.at is not a function

另外我还补做了一个更贴近问题本身的本地模块级验证:

  1. 先临时删除 Array.prototype.atString.prototype.at 以及 typed arrays 的 .at()
  2. 再加载这次新增的 compatibility.mjs
  3. 验证 polyfill 是否真的恢复行为

本地结果是:

  • Array:OK
  • String:OK
  • TypedArray:OK

所以从我这边的本地验证看,这个 PR 不只是“把 compatibility 脚本接上了”,而是确实能补上 .at() 缺失场景,同时也没有把现有 PDF 预览链路搞坏。

Copy link
Copy Markdown
Contributor

@klboke klboke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本地已补充验证:兼容层可实际修复 .at() 缺失场景,且未破坏现有 PDF 预览链路。Approve。

@klboke klboke marked this pull request as ready for review April 9, 2026 04:00
Copilot AI review requested due to automatic review settings April 9, 2026 04:00
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Restores PDF.js initialization on older mobile browsers/WebViews that lack Array.prototype.at, preventing blank PDF previews and subsequent worker/fake-worker startup failures.

Changes:

  • Added a small at() compatibility shim and ensured it loads before pdf.mjs in the viewer.
  • Imported the same shim from pdf.worker.mjs so both worker and fake-worker paths are covered.
  • Added regression tests to ensure the shim remains wired into both entrypoints.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
server/src/main/resources/static/pdfjs/web/viewer.html Loads the compatibility shim before pdf.mjs.
server/src/main/resources/static/pdfjs/web/compatibility.mjs Adds an at() polyfill for arrays/strings/typed arrays.
server/src/main/resources/static/pdfjs/build/pdf.worker.mjs Imports the compatibility shim in the worker entrypoint.
server/src/test/java/cn/keking/PdfViewerCompatibilityTests.java Verifies shim wiring in viewer and worker assets.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +11 to +15
Float32Array,
Float64Array,
globalThis.BigInt64Array,
globalThis.BigUint64Array,
];
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compatibility.mjs references globalThis directly when building atTargets. In environments without native globalThis (which can overlap with browsers missing Array.prototype.at), this will throw before the polyfill installs and prevent PDF.js from booting. Use a safe global-object resolution (e.g., prefer globalThis when defined, otherwise self/window) before accessing BigInt typed array constructors.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +28
if (Number.isNaN(relativeIndex)) {
relativeIndex = 0;
}
relativeIndex = Math.trunc(relativeIndex);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The polyfill implementation depends on Number.isNaN and Math.trunc, but the compatibility module runs before PDF.js (and its bundled polyfills) load. On older engines these methods may be missing, causing the shim itself to crash. Consider avoiding these dependencies (e.g., NaN check via x !== x and integer conversion via Math.floor/Math.ceil) so the shim only requires the minimum baseline needed to run module scripts.

Suggested change
if (Number.isNaN(relativeIndex)) {
relativeIndex = 0;
}
relativeIndex = Math.trunc(relativeIndex);
if (relativeIndex !== relativeIndex) {
relativeIndex = 0;
}
relativeIndex =
relativeIndex < 0 ? Math.ceil(relativeIndex) : Math.floor(relativeIndex);

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +4
const atTargets = [
Array,
String,
Int8Array,
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The String polyfill uses this[resolvedIndex], which returns UTF-16 code units; native String.prototype.at is code-point aware (handles surrogate pairs). Since String.prototype.at is included in atTargets, either implement the code-point semantics or drop String from the targets to avoid introducing subtly incorrect behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +20
void shouldLoadCompatibilityModuleBeforePdfJs() throws IOException {
String viewerHtml = readResource("/static/pdfjs/web/viewer.html");

assertTrue(viewerHtml.contains("<script src=\"compatibility.mjs\" type=\"module\"></script>"));
assertTrue(viewerHtml.indexOf("compatibility.mjs") < viewerHtml.indexOf("../build/pdf.mjs"));
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These assertions are very brittle because they depend on exact HTML/script formatting (attribute order, whitespace, self-closing style). A small upstream PDF.js update could break the test without changing behavior. Consider making the check more resilient (e.g., regex for a module script whose src ends with compatibility.mjs, and order based on match positions) while still ensuring the shim loads before pdf.mjs.

Copilot uses AI. Check for mistakes.
void shouldLoadCompatibilityModuleInPdfWorker() throws IOException {
String workerScript = readResource("/static/pdfjs/build/pdf.worker.mjs");

assertTrue(workerScript.contains("import \"../web/compatibility.mjs\";"));
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion requires the worker import line to match exactly (including quote style and semicolon). To reduce false negatives on future PDF.js asset updates, consider loosening it to a substring/regex that tolerates whitespace/quotes while still verifying the compatibility module is imported from the worker entrypoint.

Suggested change
assertTrue(workerScript.contains("import \"../web/compatibility.mjs\";"));
assertTrue(workerScript.matches("(?s).*import\\s+[\"']\\.\\./web/compatibility\\.mjs[\"']\\s*;?.*"));

Copilot uses AI. Check for mistakes.
@klboke klboke merged commit c41c14b into master Apr 9, 2026
6 checks passed
@klboke klboke deleted the copilot/fix-pdf-preview-error branch April 9, 2026 04:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

移动端预览PDF报错 pattern.at is not a function

3 participants