@0k/pdf-viewer 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -116,9 +116,17 @@ error reason are preserved.
116
116
  Idempotent, including before loading completes.
117
117
 
118
118
  Paging and zoom methods do nothing unless the document is ready.
119
- Mounting again on the same host replaces its viewer and waits for the
120
- previous owner's teardown before creating a new worker. An old owner
121
- cannot destroy the replacement.
119
+ Mounting again on the same host replaces its viewer. Across all hosts
120
+ using this module, every worker startup waits for all pending
121
+ loading-task teardowns to settle, whether resolved or rejected. A host
122
+ can call an old owner's `destroy()` without awaiting it and mount on a
123
+ new element: the new worker will not overlap that pending teardown. This
124
+ also applies to `reload()`. Only pending teardowns are retained; settled
125
+ ones are removed. A failed teardown is logged and does not block
126
+ startup, but may leave its worker running. A slow teardown delays
127
+ startup of every later viewer in the module (across all hosts); a
128
+ teardown that never settles holds them back indefinitely. An old owner
129
+ cannot destroy a replacement or a viewer on another host.
122
130
 
123
131
  ### Exported constants
124
132
 
@@ -212,6 +220,26 @@ To try an unreleased change in a host, link the checkout there:
212
220
  ## Changelog
213
221
 
214
222
 
223
+ ### 0.2.1 (2026-11-06)
224
+
225
+ #### Fix
226
+
227
+ * Start a viewer's ``PDF.js`` worker only after all pending teardowns on any host settle. [Valentin Lab]
228
+
229
+ Hosts that mount each document on a new element can switch documents
230
+ without awaiting the old owner: the next worker now waits for every
231
+ loading-task teardown already under way in the module, whatever its
232
+ host. Switching documents no longer runs the old and the new worker
233
+ side by side, which bounds worker memory on phones.
234
+
235
+ A failed teardown is logged and does not block startup, but ``PDF.js`` may
236
+ leave its worker running. A slow teardown delays every later viewer of
237
+ the module; one that never settles holds them back. Settled teardowns,
238
+ resolved or rejected, are dropped from the module-wide tracking, and
239
+ reload ordering, same-host replacement and owner isolation are
240
+ preserved.
241
+
242
+
215
243
  ### 0.2.0 (2026-10-28)
216
244
 
217
245
  #### New
package/dist/index.d.ts CHANGED
@@ -98,8 +98,12 @@ export interface PdfViewerOwner {
98
98
  /**
99
99
  * Mount a viewer into `host` and start loading `options.url`.
100
100
  *
101
- * Mounting twice on one host replaces the previous viewer, releasing its
102
- * loading task before the replacement starts its worker. The returned
103
- * owner acts only on the viewer it mounted.
101
+ * Mounting twice on one host replaces the previous viewer. On any host,
102
+ * worker startup waits for every pending loading-task teardown in this
103
+ * module to settle, including rejected teardowns. Hosts need not await
104
+ * an old owner's destroy() before mounting on a new element. The returned
105
+ * owner acts only on the viewer it mounted. A failed teardown is logged
106
+ * and allows startup, but may leave its worker running. A slow or never
107
+ * settling teardown delays or blocks startup across all hosts.
104
108
  */
105
109
  export declare function mountPdfViewer(host: HTMLElement, options: PdfViewerOptions): PdfViewerOwner;
package/dist/index.js CHANGED
@@ -12,10 +12,12 @@
12
12
  * - ⚠ The LOADING TASK is the sole owner of worker/transport teardown.
13
13
  * The runtime's `PdfDocument` has no `destroy`, so `doc.destroy()` is a
14
14
  * compile error rather than a second path to the same worker.
15
- * - ⚠ Teardown → startup is SERIALIZED. A reload awaits the previous
16
- * task's `destroy()` (settled, rejection tolerated) before importing
17
- * the runtime or creating a worker; two workers overlapping blanks the
18
- * canvas on WebView 91.
15
+ * - ⚠ Teardown → startup is SERIALIZED across hosts. A reload awaits its
16
+ * previous task's `destroy()` before importing the runtime; every load
17
+ * awaits all pending task teardowns (settled, rejection tolerated)
18
+ * before creating a worker. Failed teardown is logged and does not
19
+ * block startup, but may leave its worker running. Overlap can blank
20
+ * the canvas on WebView 91.
19
21
  * - ⚠ The DISPLAYED page is never `cleanup()`-ed. Releasing it right
20
22
  * after `render()` resolves can race the first compositor flush and
21
23
  * lose the paint. Only stale, cancelled or errored renders — whose
@@ -68,6 +70,8 @@ export const DEFAULT_PDF_VIEWER_LABELS = {
68
70
  loading: "Loading…",
69
71
  failed: "Failed to load PDF: %{reason}",
70
72
  };
73
+ /** Only unsettled task teardowns are retained, regardless of their host. */
74
+ const pendingTaskDestructions = new Set();
71
75
  /**
72
76
  * Internal renderer state, never returned to hosts. The public owner is a
73
77
  * frozen facade whose closures call this instance with its original receiver,
@@ -637,7 +641,17 @@ class Viewer {
637
641
  if (this.taskDestructions.has(task))
638
642
  return;
639
643
  const destruction = task.destroy()
640
- .catch((reason) => console.error("pdf-viewer: task.destroy failed", reason));
644
+ .catch((reason) => {
645
+ try {
646
+ console.error("pdf-viewer: task.destroy failed", reason);
647
+ }
648
+ catch {
649
+ // A throwing host logger must not reject the teardown/startup barrier.
650
+ }
651
+ });
652
+ pendingTaskDestructions.add(destruction);
653
+ const forget = () => { pendingTaskDestructions.delete(destruction); };
654
+ void destruction.then(forget, forget);
641
655
  this.taskDestructions.set(task, destruction);
642
656
  this.pendingDestroy = Promise.all([this.pendingDestroy, destruction]).then(() => undefined);
643
657
  }
@@ -666,6 +680,13 @@ class Viewer {
666
680
  const runtime = await loadPdfRuntime();
667
681
  if (superseded())
668
682
  return;
683
+ // Check after the import and after every wait: another host may
684
+ // begin teardown while either await is parked.
685
+ while (pendingTaskDestructions.size > 0) {
686
+ await Promise.all(pendingTaskDestructions);
687
+ if (superseded())
688
+ return;
689
+ }
669
690
  task = runtime.getDocument({
670
691
  url: this.options.url,
671
692
  disableAutoFetch: true,
@@ -972,9 +993,13 @@ const viewerOf = new WeakMap();
972
993
  /**
973
994
  * Mount a viewer into `host` and start loading `options.url`.
974
995
  *
975
- * Mounting twice on one host replaces the previous viewer, releasing its
976
- * loading task before the replacement starts its worker. The returned
977
- * owner acts only on the viewer it mounted.
996
+ * Mounting twice on one host replaces the previous viewer. On any host,
997
+ * worker startup waits for every pending loading-task teardown in this
998
+ * module to settle, including rejected teardowns. Hosts need not await
999
+ * an old owner's destroy() before mounting on a new element. The returned
1000
+ * owner acts only on the viewer it mounted. A failed teardown is logged
1001
+ * and allows startup, but may leave its worker running. A slow or never
1002
+ * settling teardown delays or blocks startup across all hosts.
978
1003
  */
979
1004
  export function mountPdfViewer(host, options) {
980
1005
  const previous = viewerOf.get(host);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0k/pdf-viewer",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Plain-DOM PDF page viewer over the shared PDF.js legacy runtime: paging, zoom, pinch/pan, error/retry and single-owner teardown.",
5
5
  "keywords": [
6
6
  "pdf",