Commit Graph
2 Commits
Author SHA1 Message Date
Daniele f6f94e579d fix(file-viewer): keep rendering a PDF after a file-watcher reload
Nightly Build / build (push) Successful in 8m13s
An open PDF went blank the moment the watcher reported the file had
changed, and stayed blank for the rest of the session — every later
version of the file too.

<pdf-view>._teardown() released the previous document with
PDFDocumentProxy.destroy(), a method pdf.js no longer has: a document is
torn down through its loading task. The absent method threw a TypeError,
and _teardown() is the *first* statement of _open(): the page list had
already been emptied, so nothing after the throw ran — no new document
was loaded, and the emptied .pdfv-pages had nothing to refill it. Since
_doc was never cleared either, every subsequent src hit the same throw,
which is why the viewer never recovered. _open() is async and its caller
(updated()) does not await it, so the TypeError surfaced only as an
unhandled rejection.

Tear the document down through doc.loadingTask.destroy() instead, and
swallow its failure: releasing the previous document must never be able
to stop the next one from loading. The same call in _open()'s
stale-document path had the identical bug.

Verified in headless Chromium against the real component: swapping the
blob URL the way FileViewerBase._load does now reloads the document
(5 pages -> 7 -> 5, correct text layer, no exceptions), including three
reloads fired back-to-back so the stale-document path is exercised.
2026-08-10 17:53:00 +01:00
Daniele 71e1a26b08 fix(ui): render PDFs with pdf.js instead of an iframe
Nightly Build / build (push) Successful in 7m59s
On iOS the file viewer showed only the first page of a PDF, with no way
to scroll to the rest — the document had to be downloaded and opened in
another app. The cause was not ours: WebKit refuses to mount its PDF
viewer inside an <iframe>/<object>/<embed> and paints a static first-page
thumbnail instead. That hits Safari on iOS and every WKWebView, so the
native shell too. The full viewer only exists for a top-level navigation.

The desktop browsers do mount a viewer, but each mounts its own — Chrome's
toolbar, Safari's page-index sidebar — so the same document also looked
different on every machine.

Both are answered by drawing the pages ourselves. New <pdf-view>
(web/components/shared/pdf-view.js) renders a continuous scroll of canvas
pages on the vendored pdf.js, with a zoom control and a page counter, and
replaces the iframe for both native .pdf files and server-compiled LaTeX.

Three properties are load-bearing:

- pdf.js is imported lazily (~450 KB + a 1.2 MB worker), so a session that
  never opens a PDF never pays for it.
- Canvases are created and destroyed as pages scroll. iOS caps the total
  canvas backing store a page may hold and silently blanks canvases past
  it, so an eager render would come out empty on exactly the platform this
  was written for. Off-screen pages keep only a correctly-sized box, which
  is also what keeps the scrollbar honest.
- The text layer (selection, in-page find) is best-effort: it is
  transparent DOM over the pixels, so its failures are swallowed rather
  than surfaced.

Vendored from pdfjs-dist 6.2.108: pdf.min.mjs, pdf.worker.min.mjs, the
standard-font data (needed by PDFs that reference Helvetica/Times without
embedding them) and the .textLayer block of pdf_viewer.css. CJK cmaps are
deliberately left out. pdf.js 6 needs Safari/iOS 17.4+.

Verified in headless Chromium against both a synthetic 12-page PDF using
non-embedded Helvetica and a real 14-page paper with embedded fonts and
figures: all pages present, last page renders after scrolling, page 1
released off-screen, text layer populated, zoom re-renders at the new
scale, no JS errors.
2026-08-08 17:16:15 +01:00