@readium/navigator 2.4.0-alpha.2 → 2.4.0-alpha.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/navigator",
3
- "version": "2.4.0-alpha.2",
3
+ "version": "2.4.0-alpha.4",
4
4
  "type": "module",
5
5
  "description": "Next generation SDK for publications in Web Apps",
6
6
  "author": "readium",
@@ -55,8 +55,8 @@
55
55
  "typescript-plugin-css-modules": "^5.2.0",
56
56
  "user-agent-data-types": "^0.4.2",
57
57
  "vite": "^7.3.1",
58
- "@readium/shared": "2.1.5",
59
- "@readium/navigator-html-injectables": "2.3.0"
58
+ "@readium/navigator-html-injectables": "2.3.0",
59
+ "@readium/shared": "2.1.5"
60
60
  },
61
61
  "scripts": {
62
62
  "clean": "rimraf types dist",
@@ -1,4 +1,4 @@
1
- import { MediaType } from "@readium/shared";
1
+ import { MediaType, Resource } from "@readium/shared";
2
2
  import { Link, Publication } from "@readium/shared";
3
3
  import { Injector } from "../../injection/Injector";
4
4
 
@@ -20,10 +20,13 @@ const csp = (domains: string[]) => {
20
20
  ].join("; ");
21
21
  };
22
22
 
23
- export default class FrameBlobBuider {
23
+ export default class FrameBlobBuilder {
24
24
  private readonly cssProperties?: { [key: string]: string };
25
25
  private readonly injector: Injector | null = null;
26
26
 
27
+ private currentUrl?: string;
28
+ private currentResource?: Resource;
29
+
27
30
  constructor(
28
31
  private readonly pub: Publication,
29
32
  private readonly baseURL: string,
@@ -38,23 +41,36 @@ export default class FrameBlobBuider {
38
41
  this.injector = options.injector ?? null;
39
42
  }
40
43
 
44
+ public reset() {
45
+ this.currentUrl && URL.revokeObjectURL(this.currentUrl);
46
+ this.currentUrl = undefined;
47
+ this.currentResource?.close();
48
+ this.currentResource = undefined;
49
+ }
50
+
41
51
  public async build(fxl = false): Promise<string> {
42
- const link = await this.pub.get(this.item).link();
52
+ if(this.currentUrl) return this.currentUrl;
53
+
54
+ this.currentResource = this.pub.get(this.item);
55
+ const link = await this.currentResource.link();
43
56
  if(!link.mediaType.isHTML) {
44
57
  if(link.mediaType.isBitmap || link.mediaType.equals(MediaType.SVG)) {
45
- return await this.buildImageFrame();
58
+ const blobUrl = await this.buildImageFrame();
59
+ this.currentUrl = blobUrl;
60
+ return blobUrl;
46
61
  } else
47
62
  throw Error("Unsupported frame mediatype " + link.mediaType.string);
48
63
  } else {
49
- return await this.buildHtmlFrame(fxl);
64
+ const blobUrl = await this.buildHtmlFrame(fxl);
65
+ this.currentUrl = blobUrl;
66
+ return blobUrl;
50
67
  }
51
68
  }
52
69
 
53
70
  private async buildHtmlFrame(fxl = false): Promise<string> {
54
71
  // Load the HTML resource
55
- const resource = this.pub.get(this.item);
56
- const link = await resource.link();
57
- const txt = await resource.readAsString();
72
+ const link = await this.currentResource!.link();
73
+ const txt = await this.currentResource!.readAsString();
58
74
  if(!txt) throw new Error(`Failed reading item ${link.href}`);
59
75
 
60
76
  const doc = new DOMParser().parseFromString(
@@ -77,7 +93,7 @@ export default class FrameBlobBuider {
77
93
  }
78
94
 
79
95
  private async buildImageFrame(): Promise<string> {
80
- const link = await this.pub.get(this.item).link();
96
+ const link = await this.currentResource!.link();
81
97
  const burl = link.toURL(this.baseURL) || ""
82
98
 
83
99
  // Rudimentary image display in an HTML doc
@@ -1,6 +1,6 @@
1
1
  import { ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { Locator, Publication } from "@readium/shared";
3
- import FrameBlobBuider from "./FrameBlobBuilder";
3
+ import FrameBlobBuilder from "./FrameBlobBuilder";
4
4
  import { FrameManager } from "./FrameManager";
5
5
  import { Injector } from "../../injection/Injector";
6
6
  import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navigator";
@@ -14,7 +14,7 @@ export class FramePoolManager {
14
14
  private _currentFrame: FrameManager | undefined;
15
15
  private currentCssProperties: { [key: string]: string } | undefined;
16
16
  private readonly pool: Map<string, FrameManager> = new Map();
17
- private readonly blobs: Map<string, string> = new Map();
17
+ private readonly blobs: Map<string, FrameBlobBuilder> = new Map();
18
18
  private readonly inprogress: Map<string, Promise<void>> = new Map();
19
19
  private pendingUpdates: Map<string, { inPool: boolean }> = new Map();
20
20
  private currentBaseURL: string | undefined;
@@ -62,10 +62,8 @@ export class FramePoolManager {
62
62
  this.pool.clear();
63
63
 
64
64
  // Revoke all blobs
65
- this.blobs.forEach(v => {
66
- this.injector?.releaseBlobUrl?.(v);
67
- URL.revokeObjectURL(v);
68
- });
65
+ this.blobs.forEach(v => v.reset());
66
+ this.blobs.clear();
69
67
 
70
68
  // Clean up injector if it exists
71
69
  this.injector?.dispose();
@@ -106,15 +104,18 @@ export class FramePoolManager {
106
104
  this.pool.delete(href);
107
105
  if(this.pendingUpdates.has(href))
108
106
  this.pendingUpdates.set(href, { inPool: false });
107
+ // Note that we don't reset the blob here, unlike in the FXL pool.
108
+ // This is because FXL tends to have a ton more blobs. Maybe we'll adjust
109
+ // this at a later point with a much larger boundary for resets to deal
110
+ // with extremely long/large reflowable publications.
111
+ // Reflowable publication resources also tend to be much larger documents,
112
+ // so they're more expensive to preprocess with the FrameBlobBuilder.
109
113
  });
110
114
 
111
115
  // Check if base URL of publication has changed
112
116
  if(this.currentBaseURL !== undefined && pub.baseURL !== this.currentBaseURL) {
113
117
  // Revoke all blobs
114
- this.blobs.forEach(v => {
115
- this.injector?.releaseBlobUrl?.(v);
116
- URL.revokeObjectURL(v);
117
- });
118
+ this.blobs.forEach(v => v.reset());
118
119
  this.blobs.clear();
119
120
  }
120
121
  this.currentBaseURL = pub.baseURL;
@@ -127,18 +128,14 @@ export class FramePoolManager {
127
128
  // when navigating backwards, where paginated will go the
128
129
  // start of the resource instead of the end due to the
129
130
  // corrupted width ColumnSnapper (injectables) gets on init
130
- this.blobs.forEach(v => {
131
- this.injector?.releaseBlobUrl?.(v);
132
- URL.revokeObjectURL(v);
133
- });
131
+ this.blobs.forEach(v => v.reset());
134
132
  this.blobs.clear();
135
133
  this.pendingUpdates.clear();
136
134
  }
137
135
  if(this.pendingUpdates.has(href) && this.pendingUpdates.get(href)?.inPool === false) {
138
- const url = this.blobs.get(href);
139
- if(url) {
140
- this.injector?.releaseBlobUrl?.(url);
141
- URL.revokeObjectURL(url);
136
+ const v = this.blobs.get(href);
137
+ if(v) {
138
+ v.reset();
142
139
  this.blobs.delete(href);
143
140
  this.pendingUpdates.delete(href);
144
141
  }
@@ -157,7 +154,7 @@ export class FramePoolManager {
157
154
  const itm = pub.readingOrder.findWithHref(href);
158
155
  if(!itm) return; // TODO throw?
159
156
  if(!this.blobs.has(href)) {
160
- const blobBuilder = new FrameBlobBuider(
157
+ this.blobs.set(href, new FrameBlobBuilder(
161
158
  pub,
162
159
  this.currentBaseURL || "",
163
160
  itm,
@@ -165,13 +162,11 @@ export class FramePoolManager {
165
162
  cssProperties: this.currentCssProperties,
166
163
  injector: this.injector
167
164
  }
168
- );
169
- const blobURL = await blobBuilder.build();
170
- this.blobs.set(href, blobURL);
165
+ ));
171
166
  }
172
167
 
173
168
  // Create <iframe>
174
- const fm = new FrameManager(this.blobs.get(href)!, this.contentProtectionConfig, this.keyboardPeripheralsConfig);
169
+ const fm = new FrameManager(await this.blobs.get(href)!.build(), this.contentProtectionConfig, this.keyboardPeripheralsConfig);
175
170
  if(href !== newHref) await fm.hide(); // Avoid unecessary hide
176
171
  this.container.appendChild(fm.iframe);
177
172
  await fm.load(modules);
@@ -1,7 +1,7 @@
1
1
  import { ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { Locator, Publication, ReadingProgression, Page, Link } from "@readium/shared";
3
3
  import { FrameCommsListener } from "../frame";
4
- import FrameBlobBuider from "../frame/FrameBlobBuilder";
4
+ import FrameBlobBuilder from "../frame/FrameBlobBuilder";
5
5
  import { FXLFrameManager } from "./FXLFrameManager";
6
6
  import { FXLPeripherals } from "./FXLPeripherals";
7
7
  import { FXLSpreader, Orientation, Spread } from "./FXLSpreader";
@@ -21,7 +21,7 @@ export class FXLFramePoolManager {
21
21
  private readonly container: HTMLElement;
22
22
  private readonly positions: Locator[];
23
23
  private readonly pool: Map<string, FXLFrameManager> = new Map();
24
- private readonly blobs: Map<string, string> = new Map();
24
+ private readonly blobs: Map<string, FrameBlobBuilder> = new Map();
25
25
  private readonly inprogress: Map<string, Promise<void>> = new Map();
26
26
  private readonly delayedShow: Map<string, Promise<void>> = new Map();
27
27
  private readonly delayedTimeout: Map<string, number> = new Map();
@@ -405,7 +405,8 @@ export class FXLFramePoolManager {
405
405
  this.pool.clear();
406
406
 
407
407
  // Revoke all blobs
408
- this.blobs.forEach(v => URL.revokeObjectURL(v));
408
+ this.blobs.forEach(v => v.reset());
409
+ this.blobs.clear();
409
410
 
410
411
  // Clean up injector if it exists
411
412
  this.injector?.dispose();
@@ -496,13 +497,13 @@ export class FXLFramePoolManager {
496
497
  if(!this.pool.has(href)) return;
497
498
  this.cancelShowing(href);
498
499
  await this.pool.get(href)?.unload();
499
- // this.pool.delete(href);
500
+ this.blobs.get(href)?.reset();
500
501
  });
501
502
 
502
503
  // Check if base URL of publication has changed
503
504
  if(this.currentBaseURL !== undefined && pub.baseURL !== this.currentBaseURL) {
504
505
  // Revoke all blobs
505
- this.blobs.forEach(v => URL.revokeObjectURL(v));
506
+ this.blobs.forEach(v => v.reset());
506
507
  this.blobs.clear();
507
508
  }
508
509
  this.currentBaseURL = pub.baseURL;
@@ -512,16 +513,14 @@ export class FXLFramePoolManager {
512
513
  const itm = pub.readingOrder.items[index];
513
514
  if(!itm) return; // TODO throw?
514
515
  if(!this.blobs.has(href)) {
515
- const blobBuilder = new FrameBlobBuider(
516
+ this.blobs.set(href, new FrameBlobBuilder(
516
517
  pub,
517
518
  this.currentBaseURL || "",
518
519
  itm,
519
520
  {
520
521
  injector: this.injector
521
522
  }
522
- );
523
- const blobURL = await blobBuilder.build(true);
524
- this.blobs.set(href, blobURL);
523
+ ));
525
524
  }
526
525
 
527
526
  // Show future offscreen frame in advance after a delay
@@ -535,7 +534,7 @@ export class FXLFramePoolManager {
535
534
  const spread = this.makeSpread(this.reAlign(index));
536
535
  const page = this.spreadPosition(spread, itm);
537
536
  const fm = this.pool.get(href)!;
538
- await fm.load(modules, this.blobs.get(href)!);
537
+ await fm.load(modules, await this.blobs.get(href)!.build(true));
539
538
  if(!this.peripherals.isScaled) // When scaled, positioning is screwed up, so wait to show
540
539
  await fm.show(page); // Show/activate new frame
541
540
  this.delayedShow.delete(href);
@@ -559,10 +558,10 @@ export class FXLFramePoolManager {
559
558
  for (const s of spread) {
560
559
  const newFrame = this.pool.get(s.href)!;
561
560
  const source = this.blobs.get(s.href);
562
- if(!source) continue; // This can get destroyed
561
+ if(!source) continue; // Thfis can get destroyed
563
562
 
564
563
  this.cancelShowing(s.href);
565
- await newFrame.load(modules, source); // In order to ensure modules match the latest configuration
564
+ await newFrame.load(modules, await source.build(true)); // In order to ensure modules match the latest configuration
566
565
  await newFrame.show(this.spreadPosition(spread, s)); // Show/activate new frame
567
566
  this.previousFrames.push(newFrame);
568
567
  await newFrame.activate();
@@ -155,7 +155,7 @@ export class Injector implements IInjector {
155
155
  return [...this.allowedDomains]; // Return a copy to prevent external modification
156
156
  }
157
157
 
158
- public async injectForDocument(doc: Document, link: Link): Promise<void> {
158
+ public async injectForDocument(doc: Document, link: Link): Promise<void> {
159
159
  for (const rule of this.rules) {
160
160
  if (this.matchesRule(rule, link)) {
161
161
  await this.applyRule(doc, rule);
@@ -178,7 +178,7 @@ export class Injector implements IInjector {
178
178
  private async getOrCreateBlobUrl(resource: IInjectable): Promise<string> {
179
179
  // Use the injectable ID as the cache key
180
180
  const cacheKey = resource.id!; // ID is guaranteed to exist after constructor
181
-
181
+
182
182
  if (this.blobStore.has(cacheKey)) {
183
183
  const entry = this.blobStore.get(cacheKey)!;
184
184
  entry.refCount++;
@@ -191,7 +191,7 @@ export class Injector implements IInjector {
191
191
  this.createdBlobUrls.add(url);
192
192
  return url;
193
193
  }
194
-
194
+
195
195
  throw new Error("Resource must have a blob property");
196
196
  }
197
197
 
@@ -304,13 +304,13 @@ export class Injector implements IInjector {
304
304
  let url: string | null = null;
305
305
  try {
306
306
  url = await this.getResourceUrl(resource, doc);
307
-
307
+
308
308
  if (resource.rel === "preload" && "url" in resource) {
309
309
  this.createPreloadLink(doc, resource, url);
310
310
  } else {
311
311
  const element = this.createElement(doc, resource, url);
312
312
  createdElements.push({ element, url });
313
-
313
+
314
314
  if (position === "prepend") {
315
315
  target.prepend(element);
316
316
  } else {
@@ -1,17 +1,20 @@
1
1
  import { Link, Publication } from "@readium/shared";
2
2
  import { Injector } from "../../injection/Injector";
3
- export default class FrameBlobBuider {
3
+ export default class FrameBlobBuilder {
4
4
  private readonly pub;
5
5
  private readonly baseURL;
6
6
  private readonly item;
7
7
  private readonly cssProperties?;
8
8
  private readonly injector;
9
+ private currentUrl?;
10
+ private currentResource?;
9
11
  constructor(pub: Publication, baseURL: string, item: Link, options: {
10
12
  cssProperties?: {
11
13
  [key: string]: string;
12
14
  };
13
15
  injector?: Injector | null;
14
16
  });
17
+ reset(): void;
15
18
  build(fxl?: boolean): Promise<string>;
16
19
  private buildHtmlFrame;
17
20
  private buildImageFrame;