@readium/navigator 2.10.2 → 2.11.0

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 (86) hide show
  1. package/dist/audio/AudioNavigator.js +1 -1
  2. package/dist/audio/AudioPoolManager.js +1 -1
  3. package/dist/audio/engine/WebAudioEngine.js +1 -1
  4. package/dist/divina/DivinaNavigator.js +1 -0
  5. package/dist/divina/DivinaPageManager.js +1 -0
  6. package/dist/divina/DivinaPagedPresenter.js +1 -0
  7. package/dist/divina/DivinaPeripherals.js +1 -0
  8. package/dist/divina/DivinaScrolledPresenter.js +1 -0
  9. package/dist/divina/DivinaSpreader.js +1 -0
  10. package/dist/divina/DivinaVariantSelector.js +1 -0
  11. package/dist/divina/preferences/DivinaDefaults.js +1 -0
  12. package/dist/divina/preferences/DivinaPreferences.js +1 -0
  13. package/dist/divina/preferences/DivinaPreferencesEditor.js +1 -0
  14. package/dist/divina/preferences/DivinaSettings.js +1 -0
  15. package/dist/divina/protection/DivinaNavigatorProtector.js +1 -0
  16. package/dist/epub/EpubNavigator.js +1 -1
  17. package/dist/epub/frame/FrameBlobBuilder.js +3 -1
  18. package/dist/epub/frame/FrameManager.js +1 -1
  19. package/dist/epub/frame/FramePoolManager.js +1 -1
  20. package/dist/epub/fxl/FXLFrameManager.js +1 -1
  21. package/dist/epub/fxl/FXLFramePoolManager.js +1 -1
  22. package/dist/epub/fxl/FXLPeripherals.js +2 -2
  23. package/dist/index.js +1 -1
  24. package/dist/peripherals/KeyboardConditionBridge.js +1 -1
  25. package/dist/peripherals/KeyboardPeripherals.js +1 -1
  26. package/dist/webpub/WebPubFrameManager.js +1 -1
  27. package/dist/webpub/WebPubFramePoolManager.js +1 -1
  28. package/dist/webpub/WebPubNavigator.js +1 -1
  29. package/package.json +5 -5
  30. package/src/audio/AudioNavigator.ts +107 -5
  31. package/src/audio/AudioPoolManager.ts +14 -8
  32. package/src/audio/engine/AudioEngine.ts +34 -1
  33. package/src/audio/engine/WebAudioEngine.ts +48 -3
  34. package/src/divina/DivinaNavigator.ts +704 -0
  35. package/src/divina/DivinaPageManager.ts +322 -0
  36. package/src/divina/DivinaPagedPresenter.ts +508 -0
  37. package/src/divina/DivinaPeripherals.ts +630 -0
  38. package/src/divina/DivinaScrolledPresenter.ts +516 -0
  39. package/src/divina/DivinaSpreader.ts +202 -0
  40. package/src/divina/DivinaVariantSelector.ts +77 -0
  41. package/src/divina/index.ts +8 -0
  42. package/src/divina/preferences/DivinaDefaults.ts +37 -0
  43. package/src/divina/preferences/DivinaPreferences.ts +77 -0
  44. package/src/divina/preferences/DivinaPreferencesEditor.ts +96 -0
  45. package/src/divina/preferences/DivinaSettings.ts +38 -0
  46. package/src/divina/preferences/index.ts +4 -0
  47. package/src/divina/protection/DivinaNavigatorProtector.ts +44 -0
  48. package/src/epub/EpubNavigator.ts +73 -7
  49. package/src/epub/frame/FrameBlobBuilder.ts +144 -39
  50. package/src/epub/frame/FrameManager.ts +7 -9
  51. package/src/epub/frame/FramePoolManager.ts +106 -48
  52. package/src/epub/fxl/FXLFrameManager.ts +69 -26
  53. package/src/epub/fxl/FXLFramePoolManager.ts +98 -53
  54. package/src/epub/fxl/FXLPeripherals.ts +22 -1
  55. package/src/index.ts +1 -0
  56. package/src/peripherals/KeyboardConditionBridge.ts +15 -5
  57. package/src/peripherals/KeyboardPeripherals.ts +13 -7
  58. package/src/webpub/WebPubFrameManager.ts +7 -9
  59. package/src/webpub/WebPubFramePoolManager.ts +5 -2
  60. package/src/webpub/WebPubNavigator.ts +44 -0
  61. package/types/src/audio/AudioNavigator.d.ts +29 -0
  62. package/types/src/audio/engine/AudioEngine.d.ts +27 -1
  63. package/types/src/audio/engine/WebAudioEngine.d.ts +14 -2
  64. package/types/src/divina/DivinaNavigator.d.ts +119 -0
  65. package/types/src/divina/DivinaPageManager.d.ts +66 -0
  66. package/types/src/divina/DivinaPagedPresenter.d.ts +122 -0
  67. package/types/src/divina/DivinaPeripherals.d.ts +121 -0
  68. package/types/src/divina/DivinaScrolledPresenter.d.ts +125 -0
  69. package/types/src/divina/DivinaSpreader.d.ts +65 -0
  70. package/types/src/divina/DivinaVariantSelector.d.ts +30 -0
  71. package/types/src/divina/index.d.ts +8 -0
  72. package/types/src/divina/preferences/DivinaDefaults.d.ts +18 -0
  73. package/types/src/divina/preferences/DivinaPreferences.d.ts +33 -0
  74. package/types/src/divina/preferences/DivinaPreferencesEditor.d.ts +20 -0
  75. package/types/src/divina/preferences/DivinaSettings.d.ts +26 -0
  76. package/types/src/divina/preferences/index.d.ts +4 -0
  77. package/types/src/divina/protection/DivinaNavigatorProtector.d.ts +14 -0
  78. package/types/src/epub/EpubNavigator.d.ts +7 -1
  79. package/types/src/epub/frame/FrameBlobBuilder.d.ts +19 -3
  80. package/types/src/epub/frame/FramePoolManager.d.ts +5 -1
  81. package/types/src/epub/fxl/FXLFrameManager.d.ts +2 -0
  82. package/types/src/epub/fxl/FXLFramePoolManager.d.ts +8 -2
  83. package/types/src/epub/fxl/FXLPeripherals.d.ts +3 -0
  84. package/types/src/index.d.ts +1 -0
  85. package/types/src/peripherals/KeyboardPeripherals.d.ts +1 -0
  86. package/types/src/webpub/WebPubNavigator.d.ts +2 -0
@@ -1,12 +1,12 @@
1
1
  import { ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { Locator, Publication } from "@readium/shared";
3
- import FrameBlobBuider from "./FrameBlobBuilder.ts";
3
+ import FrameBlobBuilder, { UpgradeInsecureRequests } from "./FrameBlobBuilder.ts";
4
4
  import { FrameManager } from "./FrameManager.ts";
5
5
  import { Injector } from "../../injection/Injector.ts";
6
6
  import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navigator.ts";
7
7
 
8
- const UPPER_BOUNDARY = 5;
9
- const LOWER_BOUNDARY = 3;
8
+ const UPPER_BOUNDARY = 10;
9
+ const LOWER_BOUNDARY = 5;
10
10
 
11
11
  export class FramePoolManager {
12
12
  private readonly container: HTMLElement;
@@ -14,15 +14,18 @@ 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();
18
- private readonly inprogress: Map<string, Promise<void>> = new Map();
17
+ private readonly blobs: Map<string, FrameBlobBuilder> = new Map();
18
+ private readonly inprogress: Map<string, Promise<unknown>> = new Map();
19
19
  private pendingUpdates: Map<string, { inPool: boolean }> = new Map();
20
20
  private currentBaseURL: string | undefined;
21
21
  private readonly injector: Injector | null = null;
22
22
  private readonly contentProtectionConfig: IContentProtectionConfig;
23
23
  private readonly keyboardPeripheralsConfig: IKeyboardPeripheralsConfig;
24
+ private updateSequence = 0;
25
+ private forcedUpdate: Promise<void> | undefined;
24
26
  private resizeWatchSelectors: string[];
25
27
  private readonly getFragmentIds: (href: string) => string[];
28
+ private readonly upgradeInsecureRequests: UpgradeInsecureRequests;
26
29
 
27
30
  constructor(
28
31
  container: HTMLElement,
@@ -32,7 +35,8 @@ export class FramePoolManager {
32
35
  contentProtectionConfig?: IContentProtectionConfig,
33
36
  keyboardPeripheralsConfig?: IKeyboardPeripheralsConfig,
34
37
  getFragmentIds?: (href: string) => string[],
35
- resizeWatchSelectors: string[] = []
38
+ resizeWatchSelectors: string[] = [],
39
+ upgradeInsecureRequests?: UpgradeInsecureRequests
36
40
  ) {
37
41
  this.container = container;
38
42
  this.positions = positions;
@@ -42,6 +46,7 @@ export class FramePoolManager {
42
46
  this.keyboardPeripheralsConfig = keyboardPeripheralsConfig || [];
43
47
  this.getFragmentIds = getFragmentIds ?? (() => []);
44
48
  this.resizeWatchSelectors = [...resizeWatchSelectors];
49
+ this.upgradeInsecureRequests = upgradeInsecureRequests ?? 'always';
45
50
  }
46
51
 
47
52
  addResizeTarget(selector: string): void {
@@ -61,7 +66,7 @@ export class FramePoolManager {
61
66
  // Wait for all in-progress loads to complete
62
67
  let iit = this.inprogress.values();
63
68
  let inp = iit.next();
64
- const inprogressPromises: Promise<void>[] = [];
69
+ const inprogressPromises: Promise<unknown>[] = [];
65
70
  while(inp.value) {
66
71
  inprogressPromises.push(inp.value);
67
72
  inp = iit.next();
@@ -81,10 +86,8 @@ export class FramePoolManager {
81
86
  this.pool.clear();
82
87
 
83
88
  // Revoke all blobs
84
- this.blobs.forEach(v => {
85
- this.injector?.releaseBlobUrl?.(v);
86
- URL.revokeObjectURL(v);
87
- });
89
+ this.blobs.forEach(v => v.reset());
90
+ this.blobs.clear();
88
91
 
89
92
  // Clean up injector if it exists
90
93
  this.injector?.dispose();
@@ -96,6 +99,9 @@ export class FramePoolManager {
96
99
  }
97
100
 
98
101
  async update(pub: Publication, locator: Locator, modules: ModuleName[], force=false) {
102
+ // Updates arriving mid-rebuild must not cancel it, or the old frame is gone with no replacement
103
+ while (this.forcedUpdate) await this.forcedUpdate;
104
+ const updateSequence = ++this.updateSequence;
99
105
  let i = this.positions.findIndex(l => l.locations.position === locator.locations.position);
100
106
  if(i < 0) throw Error(`Locator not found in position list: ${locator.locations.position} > ${this.positions.reduce<number>((acc, l) => l.locations.position || 0 > acc ? l.locations.position || 0 : acc, 0) }`);
101
107
  const newHref = this.positions[i].href;
@@ -125,39 +131,33 @@ export class FramePoolManager {
125
131
  this.pool.delete(href);
126
132
  if(this.pendingUpdates.has(href))
127
133
  this.pendingUpdates.set(href, { inPool: false });
134
+ // Note that we don't reset the blob here, unlike in the FXL pool.
135
+ // This is because FXL tends to have a ton more blobs. Maybe we'll adjust
136
+ // this at a later point with a much larger boundary for resets to deal
137
+ // with extremely long/large reflowable publications.
138
+ // Reflowable publication resources also tend to be much larger documents,
139
+ // so they're more expensive to preprocess with the FrameBlobBuilder.
128
140
  });
129
141
 
130
142
  // Check if base URL of publication has changed
131
143
  if(this.currentBaseURL !== undefined && pub.baseURL !== this.currentBaseURL) {
132
144
  // Revoke all blobs
133
- this.blobs.forEach(v => {
134
- this.injector?.releaseBlobUrl?.(v);
135
- URL.revokeObjectURL(v);
136
- });
145
+ this.blobs.forEach(v => v.reset());
137
146
  this.blobs.clear();
138
147
  }
139
148
  this.currentBaseURL = pub.baseURL;
140
149
 
150
+ if(force) {
151
+ this.blobs.forEach(v => v.reset());
152
+ this.blobs.clear();
153
+ this.pendingUpdates.clear();
154
+ }
155
+
141
156
  const creator = async (href: string) => {
142
- if(force) {
143
- // Revoke all blobs so that CSSProperties are not stale
144
- // When using force, we switch scroll/paginated
145
- // If this property is not up to date, it creates issues
146
- // when navigating backwards, where paginated will go the
147
- // start of the resource instead of the end due to the
148
- // corrupted width ColumnSnapper (injectables) gets on init
149
- this.blobs.forEach(v => {
150
- this.injector?.releaseBlobUrl?.(v);
151
- URL.revokeObjectURL(v);
152
- });
153
- this.blobs.clear();
154
- this.pendingUpdates.clear();
155
- }
156
157
  if(this.pendingUpdates.has(href) && this.pendingUpdates.get(href)?.inPool === false) {
157
- const url = this.blobs.get(href);
158
- if(url) {
159
- this.injector?.releaseBlobUrl?.(url);
160
- URL.revokeObjectURL(url);
158
+ const v = this.blobs.get(href);
159
+ if(v) {
160
+ v.reset();
161
161
  this.blobs.delete(href);
162
162
  this.pendingUpdates.delete(href);
163
163
  }
@@ -176,44 +176,92 @@ export class FramePoolManager {
176
176
  const itm = pub.readingOrder.findWithHref(href);
177
177
  if(!itm) return; // TODO throw?
178
178
  if(!this.blobs.has(href)) {
179
- const blobBuilder = new FrameBlobBuider(
179
+ this.blobs.set(href, new FrameBlobBuilder(
180
180
  pub,
181
181
  this.currentBaseURL || "",
182
182
  itm,
183
183
  {
184
184
  cssProperties: this.currentCssProperties,
185
- injector: this.injector
185
+ injector: this.injector,
186
+ upgradeInsecureRequests: this.upgradeInsecureRequests
186
187
  }
187
- );
188
- const blobURL = await blobBuilder.build();
189
- this.blobs.set(href, blobURL);
188
+ ));
190
189
  }
191
190
 
192
191
  // Create <iframe>
193
- const fm = new FrameManager(this.blobs.get(href)!, this.contentProtectionConfig, this.keyboardPeripheralsConfig, this.getFragmentIds(href), this.resizeWatchSelectors);
192
+ const fm = new FrameManager(await this.blobs.get(href)!.build(), this.contentProtectionConfig, this.keyboardPeripheralsConfig, this.getFragmentIds(href), this.resizeWatchSelectors);
194
193
  if(href !== newHref) await fm.hide(); // Avoid unecessary hide
195
194
  this.container.appendChild(fm.iframe);
196
195
  await fm.load(modules);
197
196
  this.pool.set(href, fm);
197
+ return fm;
198
198
  }
199
+
200
+ // Target frame is awaited
199
201
  try {
200
- await Promise.all(creation.map(href => creator(href)));
202
+ await Promise.all(creation.filter(href => href === newHref).map(href => creator(href)));
201
203
  } catch (error) {
202
204
  reject(error);
205
+ return;
203
206
  }
204
207
 
208
+ if (updateSequence !== this.updateSequence) {
209
+ resolve();
210
+ return;
211
+ }
212
+
213
+ Promise.all(creation.filter(href => href !== newHref && !this.inprogress.has(href)).map(async href => {
214
+ const c = creator(href);
215
+ this.inprogress.set(href, c);
216
+ try {
217
+ await c;
218
+ } finally {
219
+ if (this.inprogress.get(href) === c) this.inprogress.delete(href);
220
+ }
221
+ })).catch(error => console.warn("Failed preloading frame", error));
222
+
205
223
  // Update current frame
206
- const newFrame = this.pool.get(newHref)!;
224
+ const newFrame = this.pool.get(newHref);
225
+ if (!newFrame) {
226
+ resolve();
227
+ return;
228
+ }
229
+
207
230
  if(newFrame?.source !== this._currentFrame?.source || force) {
208
231
  await this._currentFrame?.hide(); // Hide current frame. It's possible it no longer even exists in the DOM at this point
209
- if(newFrame) // If user is speeding through the publication, this can get destroyed
210
- await newFrame.load(modules); // In order to ensure modules match the latest configuration
232
+
233
+ // Resolve if there is a concurrent update that has started since this one began
234
+ if (updateSequence !== this.updateSequence) {
235
+ resolve();
236
+ return;
237
+ }
238
+
239
+ // If user is speeding through the publication, this can get destroyed
240
+ const currentFrame = this.pool.get(newHref);
241
+ if (!currentFrame) {
242
+ resolve();
243
+ return;
244
+ }
245
+
246
+ await currentFrame.load(modules); // In order to ensure modules match the latest configuration
247
+
248
+ // Resolve if there is a concurrent update that has started since this one began
249
+ if (updateSequence !== this.updateSequence) {
250
+ resolve();
251
+ return;
252
+ }
253
+
254
+ // Check if it was destroyed, again
255
+ const latestFrame = this.pool.get(newHref);
256
+ if (!latestFrame) {
257
+ resolve();
258
+ return;
259
+ }
211
260
 
212
261
  // Update progression if necessary and show the new frame
213
- if(newFrame) // If user is speeding through the publication, this can get destroyed
214
- await newFrame.show(locator.locations.progression); // Show/activate new frame
262
+ await latestFrame.show(locator.locations.progression); // Show/activate new frame
215
263
 
216
- this._currentFrame = newFrame;
264
+ this._currentFrame = latestFrame;
217
265
 
218
266
  // Safari retains focus on the hidden iframe; transfer it to the new
219
267
  // frame only if no meaningful element in the parent document owns focus.
@@ -227,8 +275,18 @@ export class FramePoolManager {
227
275
  });
228
276
 
229
277
  this.inprogress.set(newHref, progressPromise); // Add the job to the in progress map
230
- await progressPromise; // Wait on the job to finish...
231
- this.inprogress.delete(newHref); // Delete it from the in progress map!
278
+ if (force) {
279
+ const forced = progressPromise.catch(() => {});
280
+ this.forcedUpdate = forced;
281
+ forced.then(() => {
282
+ if (this.forcedUpdate === forced) this.forcedUpdate = undefined;
283
+ });
284
+ }
285
+ try {
286
+ await progressPromise; // Wait on the job to finish...
287
+ } finally {
288
+ this.inprogress.delete(newHref); // Delete it from the in progress map!
289
+ }
232
290
  }
233
291
 
234
292
  setCSSProperties(properties: { [key: string]: string }) {
@@ -1,13 +1,14 @@
1
1
  import { Loader, ModuleName } from "@readium/navigator-html-injectables";
2
2
  import { Page, ReadingProgression } from "@readium/shared";
3
3
  import { FrameComms } from "../frame/FrameComms.ts";
4
- import { FXLPeripherals } from "./FXLPeripherals.ts";
4
+ import { FXLPeripherals, isTypedOMSupported } from "./FXLPeripherals.ts";
5
5
  import type { ReadiumWindow } from "@readium/navigator-html-injectables";
6
6
  import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navigator.ts";
7
7
  import { KeyboardConditionBridge } from "../../peripherals/KeyboardConditionBridge.ts";
8
8
 
9
9
  export class FXLFrameManager {
10
10
  private frame: HTMLIFrameElement;
11
+ private frameIsAppended = false;
11
12
  private loader: Loader | undefined;
12
13
  public source: string;
13
14
  private comms: FrameComms | undefined;
@@ -24,6 +25,7 @@ export class FXLFrameManager {
24
25
  public debugHref: string;
25
26
  private loadPromise: Promise<Window> | undefined;
26
27
  private showPromise: Promise<void> | undefined;
28
+ private viewportSize: { width: number, height: number } | undefined = undefined;
27
29
 
28
30
  constructor(
29
31
  peripherals: FXLPeripherals,
@@ -45,7 +47,7 @@ export class FXLFrameManager {
45
47
  this.frame.classList.add("blank");
46
48
  this.frame.scrolling = "no";
47
49
  this.frame.style.visibility = "hidden";
48
- this.frame.style.setProperty("aria-hidden", "true");
50
+ this.frame.ariaHidden = "true";
49
51
  this.frame.style.display = "none";
50
52
  this.frame.style.position = "absolute";
51
53
  this.frame.style.pointerEvents = "none";
@@ -56,15 +58,17 @@ export class FXLFrameManager {
56
58
  this.frame.dataset.originalHref = debugHref;
57
59
  this.source = "about:blank";
58
60
 
59
- // NEW
60
61
  this.wrapper = document.createElement("div");
61
62
  this.wrapper.style.position = "relative";
63
+ this.wrapper.style.contain = "strict";
62
64
  this.wrapper.style.float = this.wrapper.style.cssFloat = direction === ReadingProgression.rtl ? "right" : "left";
63
-
64
- this.wrapper.appendChild(this.frame);
65
65
  }
66
66
 
67
67
  async load(modules: ModuleName[], source: string): Promise<Window> {
68
+ if(!this.frameIsAppended) {
69
+ this.wrapper.appendChild(this.frame);
70
+ this.frameIsAppended = true;
71
+ }
68
72
  if(this.source === source && this.loadPromise/* && this.loaded*/) {
69
73
  if([...this.currModules].sort().join("|") === [...modules].sort().join("|")) {
70
74
  return this.loadPromise;
@@ -110,6 +114,7 @@ export class FXLFrameManager {
110
114
 
111
115
  // Parses the page size from the viewport meta tag of the loaded resource.
112
116
  loadPageSize(): { width: number, height: number } {
117
+ if(this.viewportSize) return this.viewportSize;
113
118
  const wnd = this.frame.contentWindow!;
114
119
 
115
120
  // Try to get the page size from the viewport meta tag
@@ -126,8 +131,10 @@ export class FXLFrameManager {
126
131
  else if(match[1] === "height")
127
132
  height = Number.parseFloat(match[2]);
128
133
  }
129
- if(width > 0 && height > 0)
130
- return { width, height };
134
+ if(width > 0 && height > 0) {
135
+ this.viewportSize = { width, height };
136
+ return this.viewportSize;
137
+ }
131
138
  }
132
139
 
133
140
  // Otherwise get it from the size of the loaded content
@@ -140,25 +147,47 @@ export class FXLFrameManager {
140
147
  update(page?: Page) {
141
148
  if(!this.loaded) return;
142
149
  const dimensions = this.loadPageSize();
143
- this.frame.style.height = `${dimensions.height}px`;
144
- this.frame.style.width = `${dimensions.width}px`;
145
150
  const ratio = Math.min(this.wrapper.clientWidth / dimensions.width, this.wrapper.clientHeight / dimensions.height);
146
- this.frame.style.transform = `scale(${ratio})`;
151
+ if(isTypedOMSupported()) {
152
+ this.frame.attributeStyleMap.set("width", CSS.px(dimensions.width));
153
+ this.frame.attributeStyleMap.set("height", CSS.px(dimensions.height));
154
+ this.frame.attributeStyleMap.set("transform", new CSSTransformValue([
155
+ new CSSScale(ratio, ratio),
156
+ ]));
157
+ } else {
158
+ this.frame.style.height = `${dimensions.height}px`;
159
+ this.frame.style.width = `${dimensions.width}px`;
160
+ this.frame.style.transform = `scale(${ratio})`;
161
+ }
147
162
  const bcr = this.frame.getBoundingClientRect();
148
163
  const hdiff = this.wrapper.clientHeight - bcr.height;
149
- this.frame.style.top = `${hdiff / 2}px`;
150
- if(page === Page.left) {
151
- const wdiff = this.wrapper.clientWidth - bcr.width;
152
- this.frame.style.left = `${wdiff}px`;
153
- } else if(page === Page.center) {
154
- const wdiff = this.wrapper.clientWidth - bcr.width;
155
- this.frame.style.left = `${wdiff / 2}px`;
164
+
165
+ if(isTypedOMSupported()) {
166
+ this.frame.attributeStyleMap.set("top", CSS.px(hdiff / 2));
167
+ if(page === Page.left) {
168
+ const wdiff = this.wrapper.clientWidth - bcr.width;
169
+ this.frame.attributeStyleMap.set("left", CSS.px(wdiff));
170
+ } else if(page === Page.center) {
171
+ const wdiff = this.wrapper.clientWidth - bcr.width;
172
+ this.frame.attributeStyleMap.set("left", CSS.px(wdiff / 2));
173
+ } else {
174
+ this.frame.attributeStyleMap.set("left", CSS.px(0));
175
+ }
156
176
  } else {
157
- this.frame.style.left = "0px";
177
+ this.frame.style.top = `${hdiff / 2}px`;
178
+ if(page === Page.left) {
179
+ const wdiff = this.wrapper.clientWidth - bcr.width;
180
+ this.frame.style.left = `${wdiff}px`;
181
+ } else if(page === Page.center) {
182
+ const wdiff = this.wrapper.clientWidth - bcr.width;
183
+ this.frame.style.left = `${wdiff / 2}px`;
184
+ } else {
185
+ this.frame.style.left = "0px";
186
+ }
158
187
  }
159
188
 
160
189
  this.frame.style.removeProperty("visibility");
161
- this.frame.style.removeProperty("aria-hidden");
190
+ this.frame.ariaHidden = null;
162
191
  this.frame.style.removeProperty("pointer-events");
163
192
  this.frame.classList.remove("blank");
164
193
  this.frame.classList.add("loaded");
@@ -175,7 +204,7 @@ export class FXLFrameManager {
175
204
  if(!this.loaded) return;
176
205
  this.deselect();
177
206
  this.frame.style.visibility = "hidden";
178
- this.frame.style.setProperty("aria-hidden", "true");
207
+ this.frame.ariaHidden = "true";
179
208
  this.frame.style.pointerEvents = "none";
180
209
  this.frame.classList.add("blank");
181
210
  this.frame.classList.remove("loaded");
@@ -193,6 +222,8 @@ export class FXLFrameManager {
193
222
  this.source = "about:blank";
194
223
  this.frame.contentWindow!.location.replace("about:blank");
195
224
  this.frame.style.display = "none";
225
+ this.frame.style.width = "0px";
226
+ this.frame.style.height = "0px";
196
227
  });
197
228
  }
198
229
 
@@ -326,15 +357,27 @@ export class FXLFrameManager {
326
357
  }
327
358
 
328
359
  set width(width: number) {
329
- const newWidth = `${width}%`;
330
- if(this.wrapper.style.width === newWidth) return;
331
- this.wrapper.style.width = newWidth;
360
+ if(isTypedOMSupported()) {
361
+ const current = this.wrapper.attributeStyleMap.get("width");
362
+ if(current instanceof CSSUnitValue && current.unit === "percent" && current.value === width) return;
363
+ this.wrapper.attributeStyleMap.set("width", CSS.percent(width));
364
+ } else {
365
+ const newWidth = `${width}%`;
366
+ if(this.wrapper.style.width === newWidth) return;
367
+ this.wrapper.style.width = newWidth;
368
+ }
332
369
  }
333
370
 
334
371
  set height(height: number) {
335
- const newHeight = `${height}px`;
336
- if(this.wrapper.style.height === newHeight) return;
337
- this.wrapper.style.height = newHeight;
372
+ if(isTypedOMSupported()) {
373
+ const current = this.wrapper.attributeStyleMap.get("height");
374
+ if(current instanceof CSSUnitValue && current.unit === "px" && current.value === height) return;
375
+ this.wrapper.attributeStyleMap.set("height", CSS.px(height));
376
+ } else {
377
+ const newHeight = `${height}px`;
378
+ if(this.wrapper.style.height === newHeight) return;
379
+ this.wrapper.style.height = newHeight;
380
+ }
338
381
  }
339
382
 
340
383
  get window() {