UNPKG

@readium/navigator

Version:

Next generation SDK for publications in Web Apps

156 lines (137 loc) 5.82 kB
import { Loader, ModuleName } from "@readium/navigator-html-injectables"; import { FrameComms } from "../epub/frame/FrameComms"; import { ReadiumWindow } from "../../../navigator-html-injectables/types/src/helpers/dom"; import { sML } from "../helpers"; export class WebPubFrameManager { private frame: HTMLIFrameElement; private loader: Loader | undefined; public readonly source: string; private comms: FrameComms | undefined; private hidden: boolean = true; private destroyed: boolean = false; private currModules: ModuleName[] = []; constructor(source: string) { this.frame = document.createElement("iframe"); this.frame.classList.add("readium-navigator-iframe"); this.frame.style.visibility = "hidden"; this.frame.style.setProperty("aria-hidden", "true"); this.frame.style.opacity = "0"; this.frame.style.position = "absolute"; this.frame.style.pointerEvents = "none"; this.frame.style.transition = "visibility 0s, opacity 0.1s linear"; // Protect against background color bleeding this.frame.style.backgroundColor = "#FFFFFF"; this.source = source; } async load(modules: ModuleName[] = []): Promise<Window> { return new Promise((res, rej) => { if(this.loader) { const wnd = this.frame.contentWindow!; // Check if currently loaded modules are equal if([...this.currModules].sort().join("|") === [...modules].sort().join("|")) { try { res(wnd); } catch (error) {}; return; } this.comms?.halt(); this.loader.destroy(); this.loader = new Loader(wnd as ReadiumWindow, modules); this.currModules = modules; this.comms = undefined; try { res(wnd); } catch (error) {} return; } this.frame.onload = () => { const wnd = this.frame.contentWindow!; this.loader = new Loader(wnd as ReadiumWindow, modules); this.currModules = modules; try { res(wnd); } catch (error) {} }; this.frame.onerror = (err) => { try { rej(err); } catch (error) {} } this.frame.contentWindow!.location.replace(this.source); }); } async destroy() { await this.hide(); this.loader?.destroy(); this.frame.remove(); this.destroyed = true; } async hide(): Promise<void> { if(this.destroyed) return; this.frame.style.visibility = "hidden"; this.frame.style.setProperty("aria-hidden", "true"); this.frame.style.opacity = "0"; this.frame.style.pointerEvents = "none"; this.hidden = true; if(this.frame.parentElement) { if(this.comms === undefined || !this.comms.ready) return; return new Promise((res, _) => { this.comms?.send("unfocus", undefined, (_: boolean) => { this.comms?.halt(); res(); }); }); } else { this.comms?.halt(); } } async show(atProgress?: number): Promise<void> { if (this.destroyed) throw Error("Trying to show frame when it doesn't exist"); if (!this.frame.parentElement) throw Error("Trying to show frame that is not attached to the DOM"); if (this.comms) this.comms.resume(); else this.comms = new FrameComms(this.frame.contentWindow!, this.source); return new Promise((res, _) => { this.comms?.send("activate", undefined, () => { this.comms?.send("focus", undefined, () => { const remove = () => { this.frame.style.removeProperty("visibility"); this.frame.style.removeProperty("aria-hidden"); this.frame.style.removeProperty("opacity"); this.frame.style.removeProperty("pointer-events"); this.hidden = false; if (sML.UA.WebKit) { this.comms?.send("force_webkit_recalc", undefined); } res(); } if (atProgress !== undefined) { this.comms?.send("go_progression", atProgress, remove); } else { remove(); } }); }); }); } setCSSProperties(properties: { [key: string]: string }) { if(this.destroyed || !this.frame.contentWindow) return; // We need to resume and halt postMessage to update the properties // if the frame is hidden since it's been halted in hide() if (this.hidden) { if (this.comms) this.comms?.resume(); else this.comms = new FrameComms(this.frame.contentWindow!, this.source); } this.comms?.send("update_properties", properties); if (this.hidden) this.comms?.halt(); } get iframe() { if(this.destroyed) throw Error("Trying to use frame when it doesn't exist"); return this.frame; } get realSize() { if(this.destroyed) throw Error("Trying to use frame client rect when it doesn't exist"); return this.frame.getBoundingClientRect(); } get window() { if(this.destroyed || !this.frame.contentWindow) throw Error("Trying to use frame window when it doesn't exist"); return this.frame.contentWindow; } get msg() { return this.comms; } get ldr() { return this.loader; } }