@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/dist/index.js +268 -269
- package/dist/index.umd.cjs +14 -14
- package/package.json +3 -3
- package/src/epub/frame/FrameBlobBuilder.ts +25 -9
- package/src/epub/frame/FramePoolManager.ts +18 -23
- package/src/epub/fxl/FXLFramePoolManager.ts +11 -12
- package/src/injection/Injector.ts +5 -5
- package/types/src/epub/frame/FrameBlobBuilder.d.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@readium/navigator",
|
|
3
|
-
"version": "2.4.0-alpha.
|
|
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/
|
|
59
|
-
"@readium/
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
56
|
-
const
|
|
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.
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
139
|
-
if(
|
|
140
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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,
|
|
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 =>
|
|
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
|
-
|
|
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 =>
|
|
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
|
-
|
|
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; //
|
|
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
|
|
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;
|