@readium/navigator 2.4.0-alpha.8 → 2.4.0-beta.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.
Files changed (54) hide show
  1. package/dist/index.js +1809 -1055
  2. package/dist/index.umd.cjs +170 -23
  3. package/package.json +10 -10
  4. package/src/Navigator.ts +55 -1
  5. package/src/audio/AudioNavigator.ts +497 -0
  6. package/src/audio/AudioPoolManager.ts +120 -0
  7. package/src/audio/engine/AudioEngine.ts +26 -10
  8. package/src/audio/engine/PreservePitchProcessor.js +149 -0
  9. package/src/audio/engine/PreservePitchWorklet.ts +79 -0
  10. package/src/audio/engine/WebAudioEngine.ts +558 -259
  11. package/src/audio/index.ts +3 -1
  12. package/src/audio/preferences/AudioDefaults.ts +43 -0
  13. package/src/audio/preferences/AudioPreferences.ts +54 -0
  14. package/src/audio/preferences/AudioPreferencesEditor.ts +123 -0
  15. package/src/audio/preferences/AudioSettings.ts +36 -0
  16. package/src/audio/preferences/index.ts +4 -0
  17. package/src/epub/EpubNavigator.ts +2 -2
  18. package/src/epub/frame/FrameBlobBuilder.ts +33 -84
  19. package/src/epub/frame/FramePoolManager.ts +23 -18
  20. package/src/epub/fxl/FXLFrameManager.ts +4 -11
  21. package/src/epub/fxl/FXLFramePoolManager.ts +22 -26
  22. package/src/epub/preferences/EpubPreferences.ts +4 -4
  23. package/src/injection/Injector.ts +5 -5
  24. package/src/preferences/Configurable.ts +2 -3
  25. package/src/preferences/PreferencesEditor.ts +1 -1
  26. package/src/preferences/Types.ts +19 -0
  27. package/src/webpub/WebPubNavigator.ts +1 -2
  28. package/src/webpub/preferences/WebPubPreferences.ts +3 -3
  29. package/types/src/Navigator.d.ts +46 -0
  30. package/types/src/audio/AudioNavigator.d.ts +79 -0
  31. package/types/src/audio/AudioPoolManager.d.ts +52 -0
  32. package/types/src/audio/engine/AudioEngine.d.ts +21 -7
  33. package/types/src/audio/engine/PreservePitchWorklet.d.ts +18 -0
  34. package/types/src/audio/engine/WebAudioEngine.d.ts +52 -7
  35. package/types/src/audio/index.d.ts +2 -0
  36. package/types/src/audio/preferences/AudioDefaults.d.ts +21 -0
  37. package/types/src/audio/preferences/AudioPreferences.d.ts +23 -0
  38. package/types/src/audio/preferences/AudioPreferencesEditor.d.ts +19 -0
  39. package/types/src/audio/preferences/AudioSettings.d.ts +24 -0
  40. package/types/src/audio/preferences/index.d.ts +4 -0
  41. package/types/src/epub/EpubNavigator.d.ts +2 -2
  42. package/types/src/epub/frame/FrameBlobBuilder.d.ts +3 -6
  43. package/types/src/epub/fxl/FXLFrameManager.d.ts +0 -2
  44. package/types/src/epub/preferences/EpubPreferences.d.ts +2 -2
  45. package/types/src/preferences/Configurable.d.ts +2 -3
  46. package/types/src/preferences/PreferencesEditor.d.ts +1 -1
  47. package/types/src/preferences/Types.d.ts +3 -0
  48. package/types/src/webpub/WebPubNavigator.d.ts +2 -2
  49. package/types/src/webpub/preferences/WebPubPreferences.d.ts +2 -2
  50. package/LICENSE +0 -28
  51. package/src/divina/DivinaNavigator.ts +0 -0
  52. package/src/divina/index.ts +0 -0
  53. package/types/src/divina/DivinaNavigator.d.ts +0 -0
  54. package/types/src/divina/index.d.ts +0 -0
@@ -1 +1,3 @@
1
- export * from './engine';
1
+ export * from './engine';
2
+ export * from './preferences';
3
+ export * from './AudioNavigator';
@@ -0,0 +1,43 @@
1
+ import {
2
+ ensureBoolean,
3
+ ensureValueInRange,
4
+ ensureNonNegative
5
+ } from "../../preferences/guards";
6
+ import {
7
+ volumeRangeConfig,
8
+ playbackRateRangeConfig,
9
+ skipIntervalRangeConfig
10
+ } from "../../preferences/Types";
11
+
12
+ export interface IAudioDefaults {
13
+ volume?: number | null;
14
+ playbackRate?: number | null;
15
+ preservePitch?: boolean | null;
16
+ skipBackwardInterval?: number | null;
17
+ skipForwardInterval?: number | null;
18
+ pollInterval?: number | null;
19
+ autoPlay?: boolean | null;
20
+ enableMediaSession?: boolean | null;
21
+ }
22
+
23
+ export class AudioDefaults {
24
+ public readonly volume: number;
25
+ public readonly playbackRate: number;
26
+ public readonly preservePitch: boolean;
27
+ public readonly skipBackwardInterval: number;
28
+ public readonly skipForwardInterval: number;
29
+ public readonly pollInterval: number;
30
+ public readonly autoPlay: boolean;
31
+ public readonly enableMediaSession: boolean;
32
+
33
+ constructor(defaults: IAudioDefaults = {}) {
34
+ this.volume = ensureValueInRange(defaults.volume, volumeRangeConfig.range) ?? 1;
35
+ this.playbackRate = ensureValueInRange(defaults.playbackRate, playbackRateRangeConfig.range) ?? 1;
36
+ this.preservePitch = ensureBoolean(defaults.preservePitch) ?? true;
37
+ this.skipBackwardInterval = ensureValueInRange(defaults.skipBackwardInterval, skipIntervalRangeConfig.range) ?? 10;
38
+ this.skipForwardInterval = ensureValueInRange(defaults.skipForwardInterval, skipIntervalRangeConfig.range) ?? 10;
39
+ this.pollInterval = ensureNonNegative(defaults.pollInterval) ?? 1000;
40
+ this.autoPlay = ensureBoolean(defaults.autoPlay) ?? true;
41
+ this.enableMediaSession = ensureBoolean(defaults.enableMediaSession) ?? true;
42
+ }
43
+ }
@@ -0,0 +1,54 @@
1
+ import { ConfigurablePreferences } from "../../preferences/Configurable";
2
+ import {
3
+ ensureBoolean,
4
+ ensureValueInRange,
5
+ ensureNonNegative
6
+ } from "../../preferences/guards";
7
+ import {
8
+ volumeRangeConfig,
9
+ playbackRateRangeConfig,
10
+ skipIntervalRangeConfig
11
+ } from "../../preferences/Types";
12
+
13
+ export interface IAudioPreferences {
14
+ volume?: number | null;
15
+ playbackRate?: number | null;
16
+ preservePitch?: boolean | null;
17
+ skipBackwardInterval?: number | null;
18
+ skipForwardInterval?: number | null;
19
+ pollInterval?: number | null;
20
+ autoPlay?: boolean | null;
21
+ enableMediaSession?: boolean | null;
22
+ }
23
+
24
+ export class AudioPreferences implements ConfigurablePreferences<AudioPreferences> {
25
+ public readonly volume: number | null | undefined;
26
+ public readonly playbackRate: number | null | undefined;
27
+ public readonly preservePitch: boolean | null | undefined;
28
+ public readonly skipBackwardInterval: number | null | undefined;
29
+ public readonly skipForwardInterval: number | null | undefined;
30
+ public readonly pollInterval: number | null | undefined;
31
+ public readonly autoPlay: boolean | null | undefined;
32
+ public readonly enableMediaSession: boolean | null | undefined;
33
+
34
+ constructor(preferences: IAudioPreferences = {}) {
35
+ this.volume = ensureValueInRange(preferences.volume, volumeRangeConfig.range);
36
+ this.playbackRate = ensureValueInRange(preferences.playbackRate, playbackRateRangeConfig.range);
37
+ this.preservePitch = ensureBoolean(preferences.preservePitch);
38
+ this.skipBackwardInterval = ensureValueInRange(preferences.skipBackwardInterval, skipIntervalRangeConfig.range);
39
+ this.skipForwardInterval = ensureValueInRange(preferences.skipForwardInterval, skipIntervalRangeConfig.range);
40
+ this.pollInterval = ensureNonNegative(preferences.pollInterval);
41
+ this.autoPlay = ensureBoolean(preferences.autoPlay);
42
+ this.enableMediaSession = ensureBoolean(preferences.enableMediaSession);
43
+ }
44
+
45
+ merging(other: AudioPreferences): AudioPreferences {
46
+ const merged: IAudioPreferences = { ...this };
47
+ for (const key of Object.keys(other) as (keyof IAudioPreferences)[]) {
48
+ if (other[key] !== undefined) {
49
+ (merged as Record<string, unknown>)[key] = other[key];
50
+ }
51
+ }
52
+ return new AudioPreferences(merged);
53
+ }
54
+ }
@@ -0,0 +1,123 @@
1
+ import { IPreferencesEditor } from "../../preferences/PreferencesEditor";
2
+ import { AudioPreferences } from "./AudioPreferences";
3
+ import { AudioSettings } from "./AudioSettings";
4
+ import { Preference, BooleanPreference, RangePreference } from "../../preferences/Preference";
5
+ import {
6
+ volumeRangeConfig,
7
+ playbackRateRangeConfig,
8
+ skipIntervalRangeConfig
9
+ } from "../../preferences/Types";
10
+
11
+ export class AudioPreferencesEditor implements IPreferencesEditor {
12
+ preferences: AudioPreferences;
13
+ private settings: AudioSettings;
14
+
15
+ constructor(initialPreferences: AudioPreferences, settings: AudioSettings) {
16
+ this.preferences = initialPreferences;
17
+ this.settings = settings;
18
+ }
19
+
20
+ clear(): void {
21
+ this.preferences = new AudioPreferences();
22
+ }
23
+
24
+ private updatePreference<K extends keyof AudioPreferences>(key: K, value: AudioPreferences[K]) {
25
+ this.preferences[key] = value;
26
+ }
27
+
28
+ get volume(): RangePreference<number> {
29
+ return new RangePreference<number>({
30
+ initialValue: this.preferences.volume,
31
+ effectiveValue: this.settings.volume,
32
+ isEffective: this.preferences.volume !== null,
33
+ onChange: (newValue: number | null | undefined) => {
34
+ this.updatePreference("volume", newValue ?? 1);
35
+ },
36
+ supportedRange: volumeRangeConfig.range,
37
+ step: volumeRangeConfig.step
38
+ });
39
+ }
40
+
41
+ get playbackRate(): RangePreference<number> {
42
+ return new RangePreference<number>({
43
+ initialValue: this.preferences.playbackRate,
44
+ effectiveValue: this.settings.playbackRate,
45
+ isEffective: this.preferences.playbackRate !== null,
46
+ onChange: (newValue: number | null | undefined) => {
47
+ this.updatePreference("playbackRate", newValue ?? 1);
48
+ },
49
+ supportedRange: playbackRateRangeConfig.range,
50
+ step: playbackRateRangeConfig.step
51
+ });
52
+ }
53
+
54
+ get preservePitch(): BooleanPreference {
55
+ return new BooleanPreference({
56
+ initialValue: this.preferences.preservePitch,
57
+ effectiveValue: this.settings.preservePitch,
58
+ isEffective: this.preferences.preservePitch !== null,
59
+ onChange: (newValue: boolean | null | undefined) => {
60
+ this.updatePreference("preservePitch", newValue ?? true);
61
+ }
62
+ });
63
+ }
64
+
65
+ get skipBackwardInterval(): RangePreference<number> {
66
+ return new RangePreference<number>({
67
+ initialValue: this.preferences.skipBackwardInterval,
68
+ effectiveValue: this.settings.skipBackwardInterval,
69
+ isEffective: this.preferences.skipBackwardInterval !== null,
70
+ onChange: (newValue: number | null | undefined) => {
71
+ this.updatePreference("skipBackwardInterval", newValue ?? 10);
72
+ },
73
+ supportedRange: skipIntervalRangeConfig.range,
74
+ step: skipIntervalRangeConfig.step
75
+ });
76
+ }
77
+
78
+ get skipForwardInterval(): RangePreference<number> {
79
+ return new RangePreference<number>({
80
+ initialValue: this.preferences.skipForwardInterval,
81
+ effectiveValue: this.settings.skipForwardInterval,
82
+ isEffective: this.preferences.skipForwardInterval !== null,
83
+ onChange: (newValue: number | null | undefined) => {
84
+ this.updatePreference("skipForwardInterval", newValue ?? 10);
85
+ },
86
+ supportedRange: skipIntervalRangeConfig.range,
87
+ step: skipIntervalRangeConfig.step
88
+ });
89
+ }
90
+
91
+ get pollInterval(): Preference<number> {
92
+ return new Preference<number>({
93
+ initialValue: this.preferences.pollInterval,
94
+ effectiveValue: this.settings.pollInterval,
95
+ isEffective: this.preferences.pollInterval !== null,
96
+ onChange: (newValue: number | null | undefined) => {
97
+ this.updatePreference("pollInterval", newValue ?? 1000);
98
+ }
99
+ });
100
+ }
101
+
102
+ get autoPlay(): BooleanPreference {
103
+ return new BooleanPreference({
104
+ initialValue: this.preferences.autoPlay,
105
+ effectiveValue: this.settings.autoPlay,
106
+ isEffective: this.preferences.autoPlay !== null,
107
+ onChange: (newValue: boolean | null | undefined) => {
108
+ this.updatePreference("autoPlay", newValue ?? true);
109
+ }
110
+ });
111
+ }
112
+
113
+ get enableMediaSession(): BooleanPreference {
114
+ return new BooleanPreference({
115
+ initialValue: this.preferences.enableMediaSession,
116
+ effectiveValue: this.settings.enableMediaSession,
117
+ isEffective: this.preferences.enableMediaSession !== null,
118
+ onChange: (newValue: boolean | null | undefined) => {
119
+ this.updatePreference("enableMediaSession", newValue ?? true);
120
+ }
121
+ });
122
+ }
123
+ }
@@ -0,0 +1,36 @@
1
+ import { AudioPreferences } from "./AudioPreferences";
2
+ import { AudioDefaults } from "./AudioDefaults";
3
+ import { ConfigurableSettings } from "../../preferences/Configurable";
4
+
5
+ export interface IAudioSettings extends ConfigurableSettings {
6
+ volume: number;
7
+ playbackRate: number;
8
+ preservePitch: boolean;
9
+ skipBackwardInterval: number;
10
+ skipForwardInterval: number;
11
+ pollInterval: number;
12
+ autoPlay: boolean;
13
+ enableMediaSession: boolean;
14
+ }
15
+
16
+ export class AudioSettings implements IAudioSettings, ConfigurableSettings {
17
+ public readonly volume: number;
18
+ public readonly playbackRate: number;
19
+ public readonly preservePitch: boolean;
20
+ public readonly skipBackwardInterval: number;
21
+ public readonly skipForwardInterval: number;
22
+ public readonly pollInterval: number;
23
+ public readonly autoPlay: boolean;
24
+ public readonly enableMediaSession: boolean;
25
+
26
+ constructor(preferences: AudioPreferences, defaults: AudioDefaults) {
27
+ this.volume = preferences.volume ?? defaults.volume;
28
+ this.playbackRate = preferences.playbackRate ?? defaults.playbackRate;
29
+ this.preservePitch = preferences.preservePitch ?? defaults.preservePitch;
30
+ this.skipBackwardInterval = preferences.skipBackwardInterval ?? defaults.skipBackwardInterval;
31
+ this.skipForwardInterval = preferences.skipForwardInterval ?? defaults.skipForwardInterval;
32
+ this.pollInterval = preferences.pollInterval ?? defaults.pollInterval;
33
+ this.autoPlay = preferences.autoPlay ?? defaults.autoPlay;
34
+ this.enableMediaSession = preferences.enableMediaSession ?? defaults.enableMediaSession;
35
+ }
36
+ }
@@ -0,0 +1,4 @@
1
+ export * from './AudioPreferences';
2
+ export * from './AudioDefaults';
3
+ export * from './AudioSettings';
4
+ export * from './AudioPreferencesEditor';
@@ -1,5 +1,5 @@
1
1
  import { Layout, Link, Locator, Profile, Publication, ReadingProgression } from "@readium/shared";
2
- import { Configurable, ConfigurablePreferences, ConfigurableSettings, LineLengths, ProgressionRange, VisualNavigator, VisualNavigatorViewport } from "../";
2
+ import { Configurable, ConfigurableSettings, LineLengths, ProgressionRange, VisualNavigator, VisualNavigatorViewport } from "../";
3
3
  import { FramePoolManager } from "./frame/FramePoolManager";
4
4
  import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager";
5
5
  import { CommsEventKey, ContextMenuEvent, FXLModules, KeyboardEventData, ModuleLibrary, ModuleName, ReflowableModules } from "@readium/navigator-html-injectables";
@@ -64,7 +64,7 @@ const defaultListeners = (listeners: EpubNavigatorListeners): EpubNavigatorListe
64
64
  peripheral: listeners.peripheral || (() => {}),
65
65
  })
66
66
 
67
- export class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, ConfigurablePreferences> {
67
+ export class EpubNavigator extends VisualNavigator implements Configurable<ConfigurableSettings, EpubPreferences> {
68
68
  private readonly pub: Publication;
69
69
  private readonly container: HTMLElement;
70
70
  private readonly listeners: EpubNavigatorListeners;
@@ -1,4 +1,4 @@
1
- import { MediaType, Resource } from "@readium/shared";
1
+ import { MediaType } from "@readium/shared";
2
2
  import { Link, Publication } from "@readium/shared";
3
3
  import { Injector } from "../../injection/Injector";
4
4
 
@@ -20,120 +20,73 @@ const csp = (domains: string[]) => {
20
20
  ].join("; ");
21
21
  };
22
22
 
23
- export default class FrameBlobBuilder {
23
+ export default class FrameBlobBuider {
24
+ private readonly item: Link;
25
+ private readonly burl: string;
26
+ private readonly pub: Publication;
24
27
  private readonly cssProperties?: { [key: string]: string };
25
28
  private readonly injector: Injector | null = null;
26
29
 
27
- private currentUrl?: string;
28
- private currentResource?: Resource;
29
-
30
30
  constructor(
31
- private readonly pub: Publication,
32
- private readonly baseURL: string,
33
- private readonly item: Link,
31
+ pub: Publication,
32
+ baseURL: string,
33
+ item: Link,
34
34
  options: {
35
35
  cssProperties?: { [key: string]: string };
36
36
  injector?: Injector | null;
37
37
  }
38
38
  ) {
39
+ this.pub = pub;
39
40
  this.item = item;
41
+ this.burl = item.toURL(baseURL) || "";
40
42
  this.cssProperties = options.cssProperties;
41
43
  this.injector = options.injector ?? null;
42
44
  }
43
45
 
44
- public reset() {
45
- this.currentUrl && URL.revokeObjectURL(this.currentUrl);
46
- this.currentUrl = undefined;
47
- this.currentResource?.close();
48
- this.currentResource = undefined;
49
- }
50
-
51
46
  public async build(fxl = false): Promise<string> {
52
- if(this.currentUrl) return this.currentUrl;
53
-
54
- this.currentResource = this.pub.get(this.item);
55
- const link = await this.currentResource.link();
56
- if(!this.currentResource) {
57
- // Reset has occured in the meantime
58
- return "about:blank";
59
- }
60
- if(!link.mediaType.isHTML) {
61
- if(link.mediaType.isBitmap || link.mediaType.equals(MediaType.SVG)) {
62
- const blobUrl = await this.buildImageFrame();
63
- this.currentUrl = blobUrl;
64
- return blobUrl;
47
+ if(!this.item.mediaType.isHTML) {
48
+ if(this.item.mediaType.isBitmap || this.item.mediaType.equals(MediaType.SVG)) {
49
+ return this.buildImageFrame();
65
50
  } else
66
- throw Error("Unsupported frame mediatype " + link.mediaType.string);
51
+ throw Error("Unsupported frame mediatype " + this.item.mediaType.string);
67
52
  } else {
68
- const blobUrl = await this.buildHtmlFrame(fxl);
69
- this.currentUrl = blobUrl;
70
- return blobUrl;
53
+ return await this.buildHtmlFrame(fxl);
71
54
  }
72
55
  }
73
56
 
74
57
  private async buildHtmlFrame(fxl = false): Promise<string> {
75
- if(!this.currentResource) throw new Error("No resource loaded");
76
-
77
58
  // Load the HTML resource
78
- const link = await this.currentResource.link();
79
- const doc = await this.currentResource.readAsXML() as HTMLDocument;
80
- if(!doc) throw new Error(`Failed reading item ${link.href}`);
59
+ const txt = await this.pub.get(this.item).readAsString();
60
+ if(!txt) throw new Error(`Failed reading item ${this.item.href}`);
61
+
62
+ const doc = new DOMParser().parseFromString(
63
+ txt,
64
+ this.item.mediaType.string as DOMParserSupportedType
65
+ );
66
+
81
67
  const perror = doc.querySelector("parsererror");
82
68
  if (perror) {
83
69
  const details = perror.querySelector("div");
84
- throw new Error(`Failed parsing item ${link.href}: ${details?.textContent || perror.textContent}`);
70
+ throw new Error(`Failed parsing item ${this.item.href}: ${details?.textContent || perror.textContent}`);
85
71
  }
86
72
 
87
73
  // Apply resource injections if injection service is provided
88
74
  if (this.injector) {
89
- await this.injector.injectForDocument(doc, link);
75
+ await this.injector.injectForDocument(doc, this.item);
90
76
  }
91
77
 
92
- return this.finalizeDOM(doc, this.pub.baseURL, link.toURL(this.baseURL) || "", link.mediaType, fxl, this.cssProperties);
78
+ return this.finalizeDOM(doc, this.pub.baseURL, this.burl, this.item.mediaType, fxl, this.cssProperties);
93
79
  }
94
80
 
95
- private async buildImageFrame(): Promise<string> {
96
- if(!this.currentResource) throw new Error("No resource loaded");
97
- const link = await this.currentResource.link();
98
- const burl = link.toURL(this.baseURL) || ""
99
-
100
- // Rudimentary image display in an HTML doc
101
- const doc = document.implementation.createHTMLDocument(link.title || link.href);
102
-
103
- // Add viewport if available
104
- if((link?.height || 0) > 0 && (link?.width || 0) > 0) {
105
- const viewportMeta = doc.createElement("meta");
106
- viewportMeta.name = "viewport";
107
- viewportMeta.content = `width=${link.width}, height=${link.height}`;
108
- viewportMeta.dataset.readium = "true";
109
- doc.head.appendChild(viewportMeta);
110
- }
111
-
81
+ private buildImageFrame(): string {
82
+ // Rudimentary image display
83
+ const doc = document.implementation.createHTMLDocument(this.item.title || this.item.href);
112
84
  const simg = document.createElement("img");
113
- simg.src = burl || "";
114
- simg.alt = link.title || "";
85
+ simg.src = this.burl || "";
86
+ simg.alt = this.item.title || "";
115
87
  simg.decoding = "async";
116
88
  doc.body.appendChild(simg);
117
-
118
- // Apply resource injections if injection service is provided
119
- if (this.injector) {
120
- await this.injector.injectForDocument(doc, new Link({
121
- // Temporary solution to address injector only expecting (X)HTML
122
- // documents for injection, which we are technically providing
123
- href: "readium-image-frame.xhtml",
124
- type: MediaType.XHTML.string
125
- }));
126
- }
127
-
128
- // Add image style
129
- const sstyle = doc.createElement("style");
130
- sstyle.dataset.readium = "true";
131
- sstyle.textContent = `
132
- html, body { width: 100%; height: 100%; margin: 0; padding: 0; font-size: 0; }
133
- img { margin: 0; padding: 0; border: 0; }`;
134
- doc.head.appendChild(sstyle);
135
-
136
- return this.finalizeDOM(doc, this.pub.baseURL, burl, link.mediaType, true);
89
+ return this.finalizeDOM(doc, this.pub.baseURL, this.burl, this.item.mediaType, true);
137
90
  }
138
91
 
139
92
  private setProperties(cssProperties: { [key: string]: string }, doc: Document) {
@@ -148,10 +101,7 @@ export default class FrameBlobBuilder {
148
101
 
149
102
  // Get allowed domains from injector if it exists
150
103
  const allowedDomains = this.injector?.getAllowedDomains?.() || [];
151
-
152
- // Remove query from root if present, as CSP doesn't allow them
153
- root = root?.split("?")[0];
154
-
104
+
155
105
  // Always include the root domain if provided
156
106
  const domains = [...new Set([
157
107
  ...(root ? [root] : []),
@@ -174,7 +124,6 @@ export default class FrameBlobBuilder {
174
124
  // loaded in parallel, greatly increasing overall speed.
175
125
  doc.body.querySelectorAll("img").forEach((img) => {
176
126
  img.setAttribute("fetchpriority", "high");
177
- img.setAttribute("referrerpolicy", "origin");
178
127
  });
179
128
 
180
129
  // We need to ensure that lang is set on the root element
@@ -1,6 +1,6 @@
1
1
  import { ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { Locator, Publication } from "@readium/shared";
3
- import FrameBlobBuilder from "./FrameBlobBuilder";
3
+ import FrameBlobBuider 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, FrameBlobBuilder> = new Map();
17
+ private readonly blobs: Map<string, string> = 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,8 +62,10 @@ export class FramePoolManager {
62
62
  this.pool.clear();
63
63
 
64
64
  // Revoke all blobs
65
- this.blobs.forEach(v => v.reset());
66
- this.blobs.clear();
65
+ this.blobs.forEach(v => {
66
+ this.injector?.releaseBlobUrl?.(v);
67
+ URL.revokeObjectURL(v);
68
+ });
67
69
 
68
70
  // Clean up injector if it exists
69
71
  this.injector?.dispose();
@@ -104,18 +106,15 @@ export class FramePoolManager {
104
106
  this.pool.delete(href);
105
107
  if(this.pendingUpdates.has(href))
106
108
  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.
113
109
  });
114
110
 
115
111
  // Check if base URL of publication has changed
116
112
  if(this.currentBaseURL !== undefined && pub.baseURL !== this.currentBaseURL) {
117
113
  // Revoke all blobs
118
- this.blobs.forEach(v => v.reset());
114
+ this.blobs.forEach(v => {
115
+ this.injector?.releaseBlobUrl?.(v);
116
+ URL.revokeObjectURL(v);
117
+ });
119
118
  this.blobs.clear();
120
119
  }
121
120
  this.currentBaseURL = pub.baseURL;
@@ -128,14 +127,18 @@ export class FramePoolManager {
128
127
  // when navigating backwards, where paginated will go the
129
128
  // start of the resource instead of the end due to the
130
129
  // corrupted width ColumnSnapper (injectables) gets on init
131
- this.blobs.forEach(v => v.reset());
130
+ this.blobs.forEach(v => {
131
+ this.injector?.releaseBlobUrl?.(v);
132
+ URL.revokeObjectURL(v);
133
+ });
132
134
  this.blobs.clear();
133
135
  this.pendingUpdates.clear();
134
136
  }
135
137
  if(this.pendingUpdates.has(href) && this.pendingUpdates.get(href)?.inPool === false) {
136
- const v = this.blobs.get(href);
137
- if(v) {
138
- v.reset();
138
+ const url = this.blobs.get(href);
139
+ if(url) {
140
+ this.injector?.releaseBlobUrl?.(url);
141
+ URL.revokeObjectURL(url);
139
142
  this.blobs.delete(href);
140
143
  this.pendingUpdates.delete(href);
141
144
  }
@@ -154,7 +157,7 @@ export class FramePoolManager {
154
157
  const itm = pub.readingOrder.findWithHref(href);
155
158
  if(!itm) return; // TODO throw?
156
159
  if(!this.blobs.has(href)) {
157
- this.blobs.set(href, new FrameBlobBuilder(
160
+ const blobBuilder = new FrameBlobBuider(
158
161
  pub,
159
162
  this.currentBaseURL || "",
160
163
  itm,
@@ -162,11 +165,13 @@ export class FramePoolManager {
162
165
  cssProperties: this.currentCssProperties,
163
166
  injector: this.injector
164
167
  }
165
- ));
168
+ );
169
+ const blobURL = await blobBuilder.build();
170
+ this.blobs.set(href, blobURL);
166
171
  }
167
172
 
168
173
  // Create <iframe>
169
- const fm = new FrameManager(await this.blobs.get(href)!.build(), this.contentProtectionConfig, this.keyboardPeripheralsConfig);
174
+ const fm = new FrameManager(this.blobs.get(href)!, this.contentProtectionConfig, this.keyboardPeripheralsConfig);
170
175
  if(href !== newHref) await fm.hide(); // Avoid unecessary hide
171
176
  this.container.appendChild(fm.iframe);
172
177
  await fm.load(modules);
@@ -7,7 +7,6 @@ import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navi
7
7
 
8
8
  export class FXLFrameManager {
9
9
  private frame: HTMLIFrameElement;
10
- private frameIsAppended = false;
11
10
  private loader: Loader | undefined;
12
11
  public source: string;
13
12
  private comms: FrameComms | undefined;
@@ -21,7 +20,6 @@ export class FXLFrameManager {
21
20
  public debugHref: string;
22
21
  private loadPromise: Promise<Window> | undefined;
23
22
  private showPromise: Promise<void> | undefined;
24
- private viewportSize: { width: number, height: number } | undefined = undefined;
25
23
 
26
24
  constructor(
27
25
  peripherals: FXLPeripherals,
@@ -56,13 +54,11 @@ export class FXLFrameManager {
56
54
  this.wrapper = document.createElement("div");
57
55
  this.wrapper.style.position = "relative";
58
56
  this.wrapper.style.float = this.wrapper.style.cssFloat = direction === ReadingProgression.rtl ? "right" : "left";
57
+
58
+ this.wrapper.appendChild(this.frame);
59
59
  }
60
60
 
61
61
  async load(modules: ModuleName[], source: string): Promise<Window> {
62
- if(!this.frameIsAppended) {
63
- this.wrapper.appendChild(this.frame);
64
- this.frameIsAppended = true;
65
- }
66
62
  if(this.source === source && this.loadPromise/* && this.loaded*/) {
67
63
  if([...this.currModules].sort().join("|") === [...modules].sort().join("|")) {
68
64
  return this.loadPromise;
@@ -108,7 +104,6 @@ export class FXLFrameManager {
108
104
 
109
105
  // Parses the page size from the viewport meta tag of the loaded resource.
110
106
  loadPageSize(): { width: number, height: number } {
111
- if(this.viewportSize) return this.viewportSize;
112
107
  const wnd = this.frame.contentWindow!;
113
108
 
114
109
  // Try to get the page size from the viewport meta tag
@@ -125,10 +120,8 @@ export class FXLFrameManager {
125
120
  else if(match[1] === "height")
126
121
  height = Number.parseFloat(match[2]);
127
122
  }
128
- if(width > 0 && height > 0) {
129
- this.viewportSize = { width, height };
130
- return this.viewportSize;
131
- }
123
+ if(width > 0 && height > 0)
124
+ return { width, height };
132
125
  }
133
126
 
134
127
  // Otherwise get it from the size of the loaded content