@readium/navigator 2.11.0 → 2.11.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.
@@ -1 +1 @@
1
- import{mid as n,COMMS_VERSION as a}from"@readium/navigator-html-injectables";const d=1e4;class f{constructor(t,e){this.registry=new Map,this._ready=!1,this.listenerBuffer=[],this.handler=this.handle.bind(this),this.wnd=t,this.origin=e;try{this.channelId=window.crypto.randomUUID()}catch{this.channelId=n()}this.gc=setInterval(()=>{this.registry.forEach((i,r)=>{performance.now()-i.time>d&&(console.warn(r,"event for",i.key,"was never handled!"),this.registry.delete(r))})},5e3),window.addEventListener("message",this.handler),this.send("_ping",void 0)}set listener(t){this.listenerBuffer.length>0&&this.listenerBuffer.forEach(e=>t(e[0],e[1])),this.listenerBuffer=[],this._listener=t}clearListener(){typeof this._listener=="function"&&(this._listener=void 0)}halt(){this._ready=!1,window.removeEventListener("message",this.handler),clearInterval(this.gc),this._listener=void 0,this.registry.clear()}resume(){window.addEventListener("message",this.handler),this._ready=!0}handle(t){const e=t.data;if(!e._readium){console.warn("Ignoring",e);return}if(e._channel===this.channelId)switch(e.key){case"_ack":{if(!e.id)return;const i=this.registry.get(e.id);if(!i)return;this.registry.delete(e.id),i.cb(!!e.data);return}case"_pong":this._ready=!0;default:{if(!this.ready)return;typeof this._listener=="function"?this._listener(e.key,e.data):this.listenerBuffer.push([e.key,e.data])}}}get ready(){return this._ready}send(t,e,i,r=!1,h=[]){const s=n();return i&&this.registry.set(s,{cb:i,time:performance.now(),key:t}),this.wnd.postMessage({_readium:a,_channel:this.channelId,id:s,data:e,key:t,strict:r},"/",h),s}}export{f as FrameComms};
1
+ import{mid as n,COMMS_VERSION as d}from"@readium/navigator-html-injectables";const a=1e4,l=5e3;class v{constructor(e,i){this.registry=new Map,this._ready=!1,this.listenerBuffer=[],this.visibleTime=0,this.visibleSince=document.hidden?void 0:performance.now(),this.visibilityHandler=this.handleVisibility.bind(this),this.handler=this.handle.bind(this),this.wnd=e,this.origin=i;try{this.channelId=window.crypto.randomUUID()}catch{this.channelId=n()}this.startGc(),window.addEventListener("message",this.handler),this.send("_ping",void 0)}set listener(e){this.listenerBuffer.length>0&&this.listenerBuffer.forEach(i=>e(i[0],i[1])),this.listenerBuffer=[],this._listener=e}clearListener(){typeof this._listener=="function"&&(this._listener=void 0)}startGc(){this.gc===void 0&&(this.handleVisibility(),document.addEventListener("visibilitychange",this.visibilityHandler),this.gc=setInterval(()=>{const e=this.visibleNow();this.registry.forEach((i,t)=>{e-i.time>a&&(console.warn(t,"event for",i.key,"was never handled!"),this.registry.delete(t))})},l))}stopGc(){this.visibleTime=this.visibleNow(),this.visibleSince=void 0,document.removeEventListener("visibilitychange",this.visibilityHandler),clearInterval(this.gc),this.gc=void 0}visibleNow(){return this.visibleTime+(this.visibleSince===void 0?0:performance.now()-this.visibleSince)}handleVisibility(){document.hidden?(this.visibleTime=this.visibleNow(),this.visibleSince=void 0):this.visibleSince===void 0&&(this.visibleSince=performance.now())}halt(){this._ready=!1,window.removeEventListener("message",this.handler),this.stopGc(),this._listener=void 0,this.registry.clear()}resume(){window.addEventListener("message",this.handler),this.startGc(),this._ready=!0}handle(e){const i=e.data;if(!i._readium){console.warn("Ignoring",i);return}if(i._channel===this.channelId)switch(i.key){case"_ack":{if(!i.id)return;const t=this.registry.get(i.id);if(!t)return;this.registry.delete(i.id),t.cb(!!i.data);return}case"_pong":this._ready=!0;default:{if(!this.ready)return;typeof this._listener=="function"?this._listener(i.key,i.data):this.listenerBuffer.push([i.key,i.data])}}}get ready(){return this._ready}send(e,i,t,r=!1,h=[]){const s=n();return t&&this.registry.set(s,{cb:t,time:this.visibleNow(),key:e}),this.wnd.postMessage({_readium:d,_channel:this.channelId,id:s,data:i,key:e,strict:r},"/",h),s}}export{v as FrameComms};
@@ -1 +1 @@
1
- import{Loader as o}from"@readium/navigator-html-injectables";import{FrameComms as n}from"./FrameComms.js";import{sML as h}from"@readium/helpers";import{KeyboardConditionBridge as a}from"../../peripherals/KeyboardConditionBridge.js";class y{constructor(t,i={},s=[],e=[],r=[]){this.timelineFragmentIds=e,this.hidden=!0,this.destroyed=!1,this.pendingUnwatchSelectors=[],this.currModules=[],this.frame=document.createElement("iframe"),this.frame.sandbox.value="allow-same-origin allow-scripts",this.frame.classList.add("readium-navigator-iframe"),this.frame.style.visibility="hidden",this.frame.ariaHidden="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",this.source=t,this.contentProtectionConfig={...i},this.keyboardPeripheralsConfig=[...s],this.resizeWatchSelectors=[...r]}async load(t){return new Promise((i,s)=>{if(this.loader){const e=this.frame.contentWindow;if([...this.currModules].sort().join("|")===[...t].sort().join("|")){try{i(e)}catch{}return}this.comms?.halt(),this.loader.destroy(),this.loader=new o(e,t),this.currModules=t,this.comms=void 0;try{i(e)}catch{}return}this.frame.onload=()=>{const e=this.frame.contentWindow;this.loader=new o(e,t),this.currModules=t;try{i(e)}catch{}},this.frame.onerror=e=>{try{s(e)}catch{}},this.frame.contentWindow.location.replace(this.source)})}applyContentProtection(){this.comms||this.comms.resume(),this.comms.send("peripherals_protection",this.contentProtectionConfig),this.keyboardPeripheralsConfig&&this.keyboardPeripheralsConfig.length>0&&(this.conditionBridge?.destroy(),this.conditionBridge=new a(this.keyboardPeripheralsConfig,t=>{t.length>0&&this.comms.send("keyboard_peripherals",t)}),this.conditionBridge.setup()),this.contentProtectionConfig.monitorScrollingExperimental&&this.comms.send("scroll_protection",{}),this.contentProtectionConfig.protectPrinting?.disable&&this.comms.send("print_protection",this.contentProtectionConfig.protectPrinting),this.resizeWatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"watch",selector:t})}),this.pendingUnwatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"unwatch",selector:t})}),this.pendingUnwatchSelectors=[]}addResizeTarget(t){this.resizeWatchSelectors.includes(t)||this.resizeWatchSelectors.push(t),this.pendingUnwatchSelectors=this.pendingUnwatchSelectors.filter(i=>i!==t),this.comms?.ready&&this.comms.send("decoration_resize",{action:"watch",selector:t})}removeResizeTarget(t){this.resizeWatchSelectors=this.resizeWatchSelectors.filter(i=>i!==t),this.comms?.ready?this.comms.send("decoration_resize",{action:"unwatch",selector:t}):this.pendingUnwatchSelectors.includes(t)||this.pendingUnwatchSelectors.push(t)}async destroy(){this.conditionBridge?.destroy(),await this.hide(),this.loader?.destroy(),this.frame.remove(),this.destroyed=!0}async hide(){if(!this.destroyed){if(this.frame.style.visibility="hidden",this.frame.ariaHidden="true",this.frame.style.opacity="0",this.frame.style.pointerEvents="none",this.hidden=!0,this.frame.blur(),this.frame.parentElement)return this.comms===void 0||!this.comms.ready?void 0:new Promise((t,i)=>{this.comms?.send("unfocus",void 0,s=>{this.comms?.halt(),t()})});this.comms?.halt()}}async show(t){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");return this.comms?this.comms.resume():this.comms=new n(this.frame.contentWindow,this.source),new Promise((i,s)=>{this.comms?.send("activate",void 0,()=>{this.comms?.send("focus",void 0,()=>{this.applyContentProtection(),this.timelineFragmentIds.length>0&&this.comms?.send("timeline_entries",this.timelineFragmentIds);const e=()=>{this.frame.style.removeProperty("visibility"),this.frame.style.removeProperty("opacity"),this.frame.style.removeProperty("pointer-events"),this.frame.ariaHidden=null,this.hidden=!1,h.UA.WebKit&&this.comms?.send("force_webkit_recalc",void 0),i()};t!==void 0?this.comms?.send("go_progression",t,e):e()})})})}setCSSProperties(t){this.destroyed||!this.frame.contentWindow||(this.hidden&&!this.comms&&(this.comms=new n(this.frame.contentWindow,this.source),this.comms.halt()),this.comms?.send("update_properties",t))}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 isDestroyed(){return this.destroyed}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 atLeft(){return this.window.scrollX<5}get atRight(){return this.window.scrollX>this.window.document.scrollingElement.scrollWidth-this.window.innerWidth-5}get msg(){return this.comms}get ldr(){return this.loader}}export{y as FrameManager};
1
+ import{Loader as o}from"@readium/navigator-html-injectables";import{FrameComms as n}from"./FrameComms.js";import{sML as h}from"@readium/helpers";import{KeyboardConditionBridge as a}from"../../peripherals/KeyboardConditionBridge.js";import{createSerializer as c}from"../../helpers/serialize.js";class u{constructor(t,i={},s=[],e=[],r=[]){this.timelineFragmentIds=e,this.hidden=!0,this.destroyed=!1,this.pendingUnwatchSelectors=[],this.currModules=[],this.serialize=c(),this.frame=document.createElement("iframe"),this.frame.sandbox.value="allow-same-origin allow-scripts",this.frame.classList.add("readium-navigator-iframe"),this.frame.style.visibility="hidden",this.frame.ariaHidden="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",this.source=t,this.contentProtectionConfig={...i},this.keyboardPeripheralsConfig=[...s],this.resizeWatchSelectors=[...r]}async load(t){return this.serialize(()=>new Promise((i,s)=>{if(this.loader){const e=this.frame.contentWindow;if([...this.currModules].sort().join("|")===[...t].sort().join("|")){try{i(e)}catch{}return}this.comms?.halt(),this.loader.destroy(),this.loader=new o(e,t),this.currModules=t,this.comms=void 0;try{i(e)}catch{}return}this.frame.onload=()=>{const e=this.frame.contentWindow;this.loader=new o(e,t),this.currModules=t;try{i(e)}catch{}},this.frame.onerror=e=>{try{s(e)}catch{}},this.frame.contentWindow.location.replace(this.source)}))}applyContentProtection(){this.comms||this.comms.resume(),this.comms.send("peripherals_protection",this.contentProtectionConfig),this.keyboardPeripheralsConfig&&this.keyboardPeripheralsConfig.length>0&&(this.conditionBridge?.destroy(),this.conditionBridge=new a(this.keyboardPeripheralsConfig,t=>{t.length>0&&this.comms.send("keyboard_peripherals",t)}),this.conditionBridge.setup()),this.contentProtectionConfig.monitorScrollingExperimental&&this.comms.send("scroll_protection",{}),this.contentProtectionConfig.protectPrinting?.disable&&this.comms.send("print_protection",this.contentProtectionConfig.protectPrinting),this.resizeWatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"watch",selector:t})}),this.pendingUnwatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"unwatch",selector:t})}),this.pendingUnwatchSelectors=[]}addResizeTarget(t){this.resizeWatchSelectors.includes(t)||this.resizeWatchSelectors.push(t),this.pendingUnwatchSelectors=this.pendingUnwatchSelectors.filter(i=>i!==t),this.comms?.ready&&this.comms.send("decoration_resize",{action:"watch",selector:t})}removeResizeTarget(t){this.resizeWatchSelectors=this.resizeWatchSelectors.filter(i=>i!==t),this.comms?.ready?this.comms.send("decoration_resize",{action:"unwatch",selector:t}):this.pendingUnwatchSelectors.includes(t)||this.pendingUnwatchSelectors.push(t)}async destroy(){return this.serialize(async()=>{this.conditionBridge?.destroy(),await this.hideNow(),this.loader?.destroy(),this.frame.remove(),this.destroyed=!0})}async hide(){return this.serialize(()=>this.hideNow())}async hideNow(){if(!this.destroyed){if(this.frame.style.visibility="hidden",this.frame.ariaHidden="true",this.frame.style.opacity="0",this.frame.style.pointerEvents="none",this.hidden=!0,this.frame.blur(),this.frame.parentElement)return this.comms===void 0||!this.comms.ready?void 0:new Promise((t,i)=>{this.comms?.send("unfocus",void 0,s=>{this.comms?.halt(),t()})});this.comms?.halt()}}async show(t){return this.serialize(()=>this.showNow(t))}async showNow(t){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");return this.comms?this.comms.resume():this.comms=new n(this.frame.contentWindow,this.source),new Promise((i,s)=>{this.comms?.send("activate",void 0,()=>{this.comms?.send("focus",void 0,()=>{this.applyContentProtection(),this.timelineFragmentIds.length>0&&this.comms?.send("timeline_entries",this.timelineFragmentIds);const e=()=>{this.frame.style.removeProperty("visibility"),this.frame.style.removeProperty("opacity"),this.frame.style.removeProperty("pointer-events"),this.frame.ariaHidden=null,this.hidden=!1,h.UA.WebKit&&this.comms?.send("force_webkit_recalc",void 0),i()};t!==void 0?this.comms?.send("go_progression",t,e):e()})})})}setCSSProperties(t){this.destroyed||!this.frame.contentWindow||(this.hidden&&!this.comms&&(this.comms=new n(this.frame.contentWindow,this.source),this.comms.halt()),this.comms?.send("update_properties",t))}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 isDestroyed(){return this.destroyed}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 atLeft(){return this.window.scrollX<5}get atRight(){return this.window.scrollX>this.window.document.scrollingElement.scrollWidth-this.window.innerWidth-5}get msg(){return this.comms}get ldr(){return this.loader}}export{u as FrameManager};
@@ -1 +1 @@
1
- import{Loader as a}from"@readium/navigator-html-injectables";import{ReadingProgression as d,Page as h}from"@readium/shared";import{FrameComms as c}from"../frame/FrameComms.js";import{isTypedOMSupported as n}from"./FXLPeripherals.js";import{KeyboardConditionBridge as l}from"../../peripherals/KeyboardConditionBridge.js";class g{constructor(t,e,s,r={},i=[],o=[]){this.frameIsAppended=!1,this.pendingUnwatchSelectors=[],this.currModules=[],this.viewportSize=void 0,this.cachedPage=void 0,this.peripherals=t,this.debugHref=s,this.contentProtectionConfig={...r},this.keyboardPeripheralsConfig=[...i],this.resizeWatchSelectors=[...o],this.frame=document.createElement("iframe"),this.frame.sandbox.value="allow-same-origin allow-scripts",this.frame.classList.add("readium-navigator-iframe"),this.frame.classList.add("blank"),this.frame.scrolling="no",this.frame.style.visibility="hidden",this.frame.ariaHidden="true",this.frame.style.display="none",this.frame.style.position="absolute",this.frame.style.pointerEvents="none",this.frame.style.transformOrigin="0 0",this.frame.style.transform="scale(1)",this.frame.style.background="#fff",this.frame.style.touchAction="none",this.frame.dataset.originalHref=s,this.source="about:blank",this.wrapper=document.createElement("div"),this.wrapper.style.position="relative",this.wrapper.style.contain="strict",this.wrapper.style.float=this.wrapper.style.cssFloat=e===d.rtl?"right":"left"}async load(t,e){return this.frameIsAppended||(this.wrapper.appendChild(this.frame),this.frameIsAppended=!0),this.source===e&&this.loadPromise&&[...this.currModules].sort().join("|")===[...t].sort().join("|")?this.loadPromise:(this.loaded&&this.source!==e&&this.window.stop(),this.source=e,this.loadPromise=new Promise((s,r)=>{if(this.loader&&this.loaded){const i=this.frame.contentWindow;if([...this.currModules].sort().join("|")===[...t].sort().join("|")){try{s(i),this.loadPromise=void 0}catch{}return}this.comms?.halt(),this.loader.destroy(),this.loader=new a(i,t),this.currModules=t,this.comms=void 0;try{s(i),this.loadPromise=void 0}catch{}return}this.frame.addEventListener("load",()=>{const i=this.frame.contentWindow;this.loader=new a(i,t),this.currModules=t,this.peripherals.observe(this.wrapper),this.peripherals.observe(i);try{s(i)}catch{}},{once:!0}),this.frame.addEventListener("error",i=>{try{r(i.error),this.loadPromise=void 0}catch{}},{once:!0}),this.frame.style.removeProperty("display"),this.frame.contentWindow.location.replace(this.source)}),this.loadPromise)}loadPageSize(){if(this.viewportSize)return this.viewportSize;const t=this.frame.contentWindow,e=t.document.head.querySelector("meta[name=viewport]");if(e){const s=/(\w+) *= *([^\s,]+)/g;let r,i=0,o=0;for(;r=s.exec(e.content);)r[1]==="width"?i=Number.parseFloat(r[2]):r[1]==="height"&&(o=Number.parseFloat(r[2]));if(i>0&&o>0)return this.viewportSize={width:i,height:o},this.viewportSize}return{width:t.document.body.scrollWidth,height:t.document.body.scrollHeight}}update(t){if(!this.loaded)return;const e=this.loadPageSize(),s=Math.min(this.wrapper.clientWidth/e.width,this.wrapper.clientHeight/e.height);n()?(this.frame.attributeStyleMap.set("width",CSS.px(e.width)),this.frame.attributeStyleMap.set("height",CSS.px(e.height)),this.frame.attributeStyleMap.set("transform",new CSSTransformValue([new CSSScale(s,s)]))):(this.frame.style.height=`${e.height}px`,this.frame.style.width=`${e.width}px`,this.frame.style.transform=`scale(${s})`);const r=this.frame.getBoundingClientRect(),i=this.wrapper.clientHeight-r.height;if(n())if(this.frame.attributeStyleMap.set("top",CSS.px(i/2)),t===h.left){const o=this.wrapper.clientWidth-r.width;this.frame.attributeStyleMap.set("left",CSS.px(o))}else if(t===h.center){const o=this.wrapper.clientWidth-r.width;this.frame.attributeStyleMap.set("left",CSS.px(o/2))}else this.frame.attributeStyleMap.set("left",CSS.px(0));else if(this.frame.style.top=`${i/2}px`,t===h.left){const o=this.wrapper.clientWidth-r.width;this.frame.style.left=`${o}px`}else if(t===h.center){const o=this.wrapper.clientWidth-r.width;this.frame.style.left=`${o/2}px`}else this.frame.style.left="0px";this.frame.style.removeProperty("visibility"),this.frame.ariaHidden=null,this.frame.style.removeProperty("pointer-events"),this.frame.classList.remove("blank"),this.frame.classList.add("loaded")}async destroy(){this.conditionBridge?.destroy(),await this.unfocus(),this.loader?.destroy(),this.wrapper.remove()}async unload(){if(this.loaded)return this.deselect(),this.frame.style.visibility="hidden",this.frame.ariaHidden="true",this.frame.style.pointerEvents="none",this.frame.classList.add("blank"),this.frame.classList.remove("loaded"),this.comms?.halt(),this.loader?.destroy(),this.comms=void 0,this.frame.blur(),new Promise((t,e)=>{this.frame.addEventListener("load",()=>{try{this.showPromise=void 0,t()}catch{}},{once:!0}),this.frame.addEventListener("error",s=>{try{this.showPromise=void 0,e(s.error)}catch{}},{once:!0}),this.source="about:blank",this.frame.contentWindow.location.replace("about:blank"),this.frame.style.display="none",this.frame.style.width="0px",this.frame.style.height="0px"})}deselect(){this.frame.contentWindow?.getSelection()?.removeAllRanges()}async unfocus(){if(this.frame.parentElement)return this.comms===void 0?void 0:(this.frame.blur(),new Promise((t,e)=>{this.comms?.send("unfocus",void 0,s=>{this.comms?.halt(),this.showPromise=void 0,t()})}));this.comms?.halt()}applyContentProtection(){this.comms||this.comms.resume(),this.comms.send("peripherals_protection",this.contentProtectionConfig),this.keyboardPeripheralsConfig&&this.keyboardPeripheralsConfig.length>0&&(this.conditionBridge?.destroy(),this.conditionBridge=new l(this.keyboardPeripheralsConfig,t=>{t.length>0&&this.comms.send("keyboard_peripherals",t)}),this.conditionBridge.setup()),this.contentProtectionConfig.protectPrinting?.disable&&this.comms.send("print_protection",this.contentProtectionConfig.protectPrinting),this.resizeWatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"watch",selector:t})}),this.pendingUnwatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"unwatch",selector:t})}),this.pendingUnwatchSelectors=[]}addResizeTarget(t){this.resizeWatchSelectors.includes(t)||this.resizeWatchSelectors.push(t),this.pendingUnwatchSelectors=this.pendingUnwatchSelectors.filter(e=>e!==t),this.comms?.ready&&this.comms.send("decoration_resize",{action:"watch",selector:t})}removeResizeTarget(t){this.resizeWatchSelectors=this.resizeWatchSelectors.filter(e=>e!==t),this.comms?.ready?this.comms.send("decoration_resize",{action:"unwatch",selector:t}):this.pendingUnwatchSelectors.includes(t)||this.pendingUnwatchSelectors.push(t)}async show(t){if(!this.frame.parentElement){console.warn("Trying to show frame that is not attached to the DOM");return}if(!this.loaded){this.showPromise=void 0;return}return this.showPromise?(this.cachedPage!==t&&(this.update(t),this.cachedPage=t),this.showPromise):(this.cachedPage=t,this.comms?this.comms.resume():this.comms=new c(this.frame.contentWindow,this.source),this.showPromise=new Promise((e,s)=>{this.comms.send("focus",void 0,r=>{this.update(this.cachedPage),this.applyContentProtection(),e()})}),this.showPromise)}async activate(){return new Promise((t,e)=>{if(!this.comms)return t();this.comms?.send("activate",void 0,()=>{t()})})}get element(){return this.wrapper}get iframe(){return this.frame}get realSize(){return this.frame.getBoundingClientRect()}get loaded(){return this.frame.contentWindow&&this.frame.contentWindow.location.href!=="about:blank"}set width(t){if(n()){const e=this.wrapper.attributeStyleMap.get("width");if(e instanceof CSSUnitValue&&e.unit==="percent"&&e.value===t)return;this.wrapper.attributeStyleMap.set("width",CSS.percent(t))}else{const e=`${t}%`;if(this.wrapper.style.width===e)return;this.wrapper.style.width=e}}set height(t){if(n()){const e=this.wrapper.attributeStyleMap.get("height");if(e instanceof CSSUnitValue&&e.unit==="px"&&e.value===t)return;this.wrapper.attributeStyleMap.set("height",CSS.px(t))}else{const e=`${t}px`;if(this.wrapper.style.height===e)return;this.wrapper.style.height=e}}get window(){if(!this.frame.contentWindow)throw Error("Trying to use frame window when it doesn't exist");return this.frame.contentWindow}get atLeft(){return this.window.scrollX<5}get atRight(){return this.window.scrollX>this.window.document.scrollingElement.scrollWidth-this.window.innerWidth-5}get msg(){return this.comms}get ldr(){return this.loader}}export{g as FXLFrameManager};
1
+ import{Loader as n}from"@readium/navigator-html-injectables";import{ReadingProgression as c,Page as h}from"@readium/shared";import{FrameComms as d}from"../frame/FrameComms.js";import{isTypedOMSupported as a}from"./FXLPeripherals.js";import{KeyboardConditionBridge as l}from"../../peripherals/KeyboardConditionBridge.js";import{createSerializer as m}from"../../helpers/serialize.js";class v{constructor(t,e,s,r={},i=[],o=[]){this.frameIsAppended=!1,this.pendingUnwatchSelectors=[],this.currModules=[],this.viewportSize=void 0,this.serialize=m(),this.cachedPage=void 0,this.peripherals=t,this.debugHref=s,this.contentProtectionConfig={...r},this.keyboardPeripheralsConfig=[...i],this.resizeWatchSelectors=[...o],this.frame=document.createElement("iframe"),this.frame.sandbox.value="allow-same-origin allow-scripts",this.frame.classList.add("readium-navigator-iframe"),this.frame.classList.add("blank"),this.frame.scrolling="no",this.frame.style.visibility="hidden",this.frame.ariaHidden="true",this.frame.style.display="none",this.frame.style.position="absolute",this.frame.style.pointerEvents="none",this.frame.style.transformOrigin="0 0",this.frame.style.transform="scale(1)",this.frame.style.background="#fff",this.frame.style.touchAction="none",this.frame.dataset.originalHref=s,this.source="about:blank",this.wrapper=document.createElement("div"),this.wrapper.style.position="relative",this.wrapper.style.contain="strict",this.wrapper.style.float=this.wrapper.style.cssFloat=e===c.rtl?"right":"left"}async load(t,e){return this.serialize(()=>this.loadNow(t,e))}async loadNow(t,e){return this.frameIsAppended||(this.wrapper.appendChild(this.frame),this.frameIsAppended=!0),this.source===e&&this.loadPromise&&[...this.currModules].sort().join("|")===[...t].sort().join("|")?this.loadPromise:(this.loaded&&this.source!==e&&this.window.stop(),this.source=e,this.loadPromise=new Promise((s,r)=>{if(this.loader&&this.loaded){const i=this.frame.contentWindow;if([...this.currModules].sort().join("|")===[...t].sort().join("|")){try{s(i),this.loadPromise=void 0}catch{}return}this.comms?.halt(),this.loader.destroy(),this.loader=new n(i,t),this.currModules=t,this.comms=void 0;try{s(i),this.loadPromise=void 0}catch{}return}this.frame.addEventListener("load",()=>{const i=this.frame.contentWindow;this.loader=new n(i,t),this.currModules=t,this.peripherals.observe(this.wrapper),this.peripherals.observe(i);try{s(i)}catch{}},{once:!0}),this.frame.addEventListener("error",i=>{try{r(i.error),this.loadPromise=void 0}catch{}},{once:!0}),this.frame.style.removeProperty("display"),this.frame.contentWindow.location.replace(this.source)}),this.loadPromise)}loadPageSize(){if(this.viewportSize)return this.viewportSize;const t=this.frame.contentWindow,e=t.document.head.querySelector("meta[name=viewport]");if(e){const s=/(\w+) *= *([^\s,]+)/g;let r,i=0,o=0;for(;r=s.exec(e.content);)r[1]==="width"?i=Number.parseFloat(r[2]):r[1]==="height"&&(o=Number.parseFloat(r[2]));if(i>0&&o>0)return this.viewportSize={width:i,height:o},this.viewportSize}return{width:t.document.body.scrollWidth,height:t.document.body.scrollHeight}}update(t){if(!this.loaded)return;const e=this.loadPageSize(),s=Math.min(this.wrapper.clientWidth/e.width,this.wrapper.clientHeight/e.height);a()?(this.frame.attributeStyleMap.set("width",CSS.px(e.width)),this.frame.attributeStyleMap.set("height",CSS.px(e.height)),this.frame.attributeStyleMap.set("transform",new CSSTransformValue([new CSSScale(s,s)]))):(this.frame.style.height=`${e.height}px`,this.frame.style.width=`${e.width}px`,this.frame.style.transform=`scale(${s})`);const r=this.frame.getBoundingClientRect(),i=this.wrapper.clientHeight-r.height;if(a())if(this.frame.attributeStyleMap.set("top",CSS.px(i/2)),t===h.left){const o=this.wrapper.clientWidth-r.width;this.frame.attributeStyleMap.set("left",CSS.px(o))}else if(t===h.center){const o=this.wrapper.clientWidth-r.width;this.frame.attributeStyleMap.set("left",CSS.px(o/2))}else this.frame.attributeStyleMap.set("left",CSS.px(0));else if(this.frame.style.top=`${i/2}px`,t===h.left){const o=this.wrapper.clientWidth-r.width;this.frame.style.left=`${o}px`}else if(t===h.center){const o=this.wrapper.clientWidth-r.width;this.frame.style.left=`${o/2}px`}else this.frame.style.left="0px";this.frame.style.removeProperty("visibility"),this.frame.ariaHidden=null,this.frame.style.removeProperty("pointer-events"),this.frame.classList.remove("blank"),this.frame.classList.add("loaded")}async destroy(){return this.serialize(async()=>{this.conditionBridge?.destroy(),await this.unfocusNow(),this.loader?.destroy(),this.wrapper.remove()})}async unload(){return this.serialize(()=>this.unloadNow())}async unloadNow(){if(this.loaded)return this.deselect(),this.frame.style.visibility="hidden",this.frame.ariaHidden="true",this.frame.style.pointerEvents="none",this.frame.classList.add("blank"),this.frame.classList.remove("loaded"),this.comms?.halt(),this.loader?.destroy(),this.comms=void 0,this.frame.blur(),new Promise((t,e)=>{this.frame.addEventListener("load",()=>{try{this.showPromise=void 0,t()}catch{}},{once:!0}),this.frame.addEventListener("error",s=>{try{this.showPromise=void 0,e(s.error)}catch{}},{once:!0}),this.source="about:blank",this.frame.contentWindow.location.replace("about:blank"),this.frame.style.display="none",this.frame.style.width="0px",this.frame.style.height="0px"})}deselect(){this.frame.contentWindow?.getSelection()?.removeAllRanges()}async unfocus(){return this.serialize(()=>this.unfocusNow())}async unfocusNow(){if(this.frame.parentElement)return this.comms===void 0||!this.comms.ready?void 0:(this.frame.blur(),new Promise((t,e)=>{this.comms?.send("unfocus",void 0,s=>{this.comms?.halt(),this.showPromise=void 0,t()})}));this.comms?.halt()}applyContentProtection(){this.comms||this.comms.resume(),this.comms.send("peripherals_protection",this.contentProtectionConfig),this.keyboardPeripheralsConfig&&this.keyboardPeripheralsConfig.length>0&&(this.conditionBridge?.destroy(),this.conditionBridge=new l(this.keyboardPeripheralsConfig,t=>{t.length>0&&this.comms.send("keyboard_peripherals",t)}),this.conditionBridge.setup()),this.contentProtectionConfig.protectPrinting?.disable&&this.comms.send("print_protection",this.contentProtectionConfig.protectPrinting),this.resizeWatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"watch",selector:t})}),this.pendingUnwatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"unwatch",selector:t})}),this.pendingUnwatchSelectors=[]}addResizeTarget(t){this.resizeWatchSelectors.includes(t)||this.resizeWatchSelectors.push(t),this.pendingUnwatchSelectors=this.pendingUnwatchSelectors.filter(e=>e!==t),this.comms?.ready&&this.comms.send("decoration_resize",{action:"watch",selector:t})}removeResizeTarget(t){this.resizeWatchSelectors=this.resizeWatchSelectors.filter(e=>e!==t),this.comms?.ready?this.comms.send("decoration_resize",{action:"unwatch",selector:t}):this.pendingUnwatchSelectors.includes(t)||this.pendingUnwatchSelectors.push(t)}async show(t){return this.serialize(()=>this.showNow(t))}async showNow(t){if(!this.frame.parentElement){console.warn("Trying to show frame that is not attached to the DOM");return}if(!this.loaded){this.showPromise=void 0;return}return this.showPromise?(this.cachedPage!==t&&(this.update(t),this.cachedPage=t),this.showPromise):(this.cachedPage=t,this.comms?this.comms.resume():this.comms=new d(this.frame.contentWindow,this.source),this.showPromise=new Promise((e,s)=>{this.comms.send("focus",void 0,r=>{this.update(this.cachedPage),this.applyContentProtection(),e()})}),this.showPromise)}async activate(){return this.serialize(()=>new Promise((t,e)=>{if(!this.comms?.ready)return t();this.comms?.send("activate",void 0,()=>{t()})}))}get element(){return this.wrapper}get iframe(){return this.frame}get realSize(){return this.frame.getBoundingClientRect()}get loaded(){return this.frame.contentWindow&&this.frame.contentWindow.location.href!=="about:blank"}set width(t){if(a()){const e=this.wrapper.attributeStyleMap.get("width");if(e instanceof CSSUnitValue&&e.unit==="percent"&&e.value===t)return;this.wrapper.attributeStyleMap.set("width",CSS.percent(t))}else{const e=`${t}%`;if(this.wrapper.style.width===e)return;this.wrapper.style.width=e}}set height(t){if(a()){const e=this.wrapper.attributeStyleMap.get("height");if(e instanceof CSSUnitValue&&e.unit==="px"&&e.value===t)return;this.wrapper.attributeStyleMap.set("height",CSS.px(t))}else{const e=`${t}px`;if(this.wrapper.style.height===e)return;this.wrapper.style.height=e}}get window(){if(!this.frame.contentWindow)throw Error("Trying to use frame window when it doesn't exist");return this.frame.contentWindow}get atLeft(){return this.window.scrollX<5}get atRight(){return this.window.scrollX>this.window.document.scrollingElement.scrollWidth-this.window.innerWidth-5}get msg(){return this.comms}get ldr(){return this.loader}}export{v as FXLFrameManager};
@@ -0,0 +1 @@
1
+ function n(){let e=Promise.resolve();return t=>{const r=e.then(t);return e=r.catch(()=>{}),r}}export{n as createSerializer};
@@ -1 +1 @@
1
- import{Loader as o}from"@readium/navigator-html-injectables";import{FrameComms as n}from"../epub/frame/FrameComms.js";import{sML as h}from"@readium/helpers";import{KeyboardConditionBridge as a}from"../peripherals/KeyboardConditionBridge.js";class y{constructor(t,i={},s=[],e=[],r=[]){this.timelineFragmentIds=e,this.hidden=!0,this.destroyed=!1,this.pendingUnwatchSelectors=[],this.currModules=[],this.frame=document.createElement("iframe"),this.frame.classList.add("readium-navigator-iframe"),this.frame.style.visibility="hidden",this.frame.ariaHidden="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",this.frame.style.backgroundColor="#FFFFFF",this.source=t,this.contentProtectionConfig={...i},this.keyboardPeripheralsConfig=[...s],this.resizeWatchSelectors=[...r]}async load(t=[]){return new Promise((i,s)=>{if(this.loader){const e=this.frame.contentWindow;if([...this.currModules].sort().join("|")===[...t].sort().join("|")){try{i(e)}catch{}return}this.comms?.halt(),this.loader.destroy(),this.loader=new o(e,t),this.currModules=t,this.comms=void 0;try{i(e)}catch{}return}this.frame.onload=()=>{const e=this.frame.contentWindow;this.loader=new o(e,t),this.currModules=t;try{i(e)}catch{}},this.frame.onerror=e=>{try{s(e)}catch{}},this.frame.contentWindow.location.replace(this.source)})}applyContentProtection(){this.comms||this.comms.resume(),this.comms.send("peripherals_protection",this.contentProtectionConfig),this.keyboardPeripheralsConfig&&this.keyboardPeripheralsConfig.length>0&&(this.conditionBridge?.destroy(),this.conditionBridge=new a(this.keyboardPeripheralsConfig,t=>{t.length>0&&this.comms.send("keyboard_peripherals",t)}),this.conditionBridge.setup()),this.contentProtectionConfig.monitorScrollingExperimental&&this.comms.send("scroll_protection",{}),this.contentProtectionConfig.protectPrinting?.disable&&this.comms.send("print_protection",this.contentProtectionConfig.protectPrinting),this.resizeWatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"watch",selector:t})}),this.pendingUnwatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"unwatch",selector:t})}),this.pendingUnwatchSelectors=[]}addResizeTarget(t){this.resizeWatchSelectors.includes(t)||this.resizeWatchSelectors.push(t),this.pendingUnwatchSelectors=this.pendingUnwatchSelectors.filter(i=>i!==t),this.comms?.ready&&this.comms.send("decoration_resize",{action:"watch",selector:t})}removeResizeTarget(t){this.resizeWatchSelectors=this.resizeWatchSelectors.filter(i=>i!==t),this.comms?.ready?this.comms.send("decoration_resize",{action:"unwatch",selector:t}):this.pendingUnwatchSelectors.includes(t)||this.pendingUnwatchSelectors.push(t)}async destroy(){this.conditionBridge?.destroy(),await this.hide(),this.loader?.destroy(),this.frame.remove(),this.destroyed=!0}async hide(){if(!this.destroyed){if(this.frame.style.visibility="hidden",this.frame.ariaHidden="true",this.frame.style.opacity="0",this.frame.style.pointerEvents="none",this.hidden=!0,this.frame.parentElement)return this.comms===void 0||!this.comms.ready?void 0:new Promise((t,i)=>{this.comms?.send("unfocus",void 0,s=>{this.comms?.halt(),t()})});this.comms?.halt()}}async show(t){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");return this.comms?this.comms.resume():this.comms=new n(this.frame.contentWindow,this.source),new Promise((i,s)=>{this.comms?.send("activate",void 0,()=>{this.comms?.send("focus",void 0,()=>{this.applyContentProtection(),this.timelineFragmentIds.length>0&&this.comms?.send("timeline_entries",this.timelineFragmentIds);const e=()=>{this.frame.style.removeProperty("visibility"),this.frame.ariaHidden=null,this.frame.style.removeProperty("opacity"),this.frame.style.removeProperty("pointer-events"),this.hidden=!1,h.UA.WebKit&&this.comms?.send("force_webkit_recalc",void 0),i()};t!==void 0?this.comms?.send("go_progression",t,e):e()})})})}setCSSProperties(t){this.destroyed||!this.frame.contentWindow||(this.hidden&&!this.comms&&(this.comms=new n(this.frame.contentWindow,this.source),this.comms.halt()),this.comms?.send("update_properties",t))}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}}export{y as WebPubFrameManager};
1
+ import{Loader as o}from"@readium/navigator-html-injectables";import{FrameComms as n}from"../epub/frame/FrameComms.js";import{sML as h}from"@readium/helpers";import{KeyboardConditionBridge as a}from"../peripherals/KeyboardConditionBridge.js";import{createSerializer as c}from"../helpers/serialize.js";class w{constructor(t,i={},s=[],e=[],r=[]){this.timelineFragmentIds=e,this.hidden=!0,this.destroyed=!1,this.pendingUnwatchSelectors=[],this.currModules=[],this.serialize=c(),this.frame=document.createElement("iframe"),this.frame.classList.add("readium-navigator-iframe"),this.frame.style.visibility="hidden",this.frame.ariaHidden="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",this.frame.style.backgroundColor="#FFFFFF",this.source=t,this.contentProtectionConfig={...i},this.keyboardPeripheralsConfig=[...s],this.resizeWatchSelectors=[...r]}async load(t=[]){return this.serialize(()=>new Promise((i,s)=>{if(this.loader){const e=this.frame.contentWindow;if([...this.currModules].sort().join("|")===[...t].sort().join("|")){try{i(e)}catch{}return}this.comms?.halt(),this.loader.destroy(),this.loader=new o(e,t),this.currModules=t,this.comms=void 0;try{i(e)}catch{}return}this.frame.onload=()=>{const e=this.frame.contentWindow;this.loader=new o(e,t),this.currModules=t;try{i(e)}catch{}},this.frame.onerror=e=>{try{s(e)}catch{}},this.frame.contentWindow.location.replace(this.source)}))}applyContentProtection(){this.comms||this.comms.resume(),this.comms.send("peripherals_protection",this.contentProtectionConfig),this.keyboardPeripheralsConfig&&this.keyboardPeripheralsConfig.length>0&&(this.conditionBridge?.destroy(),this.conditionBridge=new a(this.keyboardPeripheralsConfig,t=>{t.length>0&&this.comms.send("keyboard_peripherals",t)}),this.conditionBridge.setup()),this.contentProtectionConfig.monitorScrollingExperimental&&this.comms.send("scroll_protection",{}),this.contentProtectionConfig.protectPrinting?.disable&&this.comms.send("print_protection",this.contentProtectionConfig.protectPrinting),this.resizeWatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"watch",selector:t})}),this.pendingUnwatchSelectors.forEach(t=>{this.comms.send("decoration_resize",{action:"unwatch",selector:t})}),this.pendingUnwatchSelectors=[]}addResizeTarget(t){this.resizeWatchSelectors.includes(t)||this.resizeWatchSelectors.push(t),this.pendingUnwatchSelectors=this.pendingUnwatchSelectors.filter(i=>i!==t),this.comms?.ready&&this.comms.send("decoration_resize",{action:"watch",selector:t})}removeResizeTarget(t){this.resizeWatchSelectors=this.resizeWatchSelectors.filter(i=>i!==t),this.comms?.ready?this.comms.send("decoration_resize",{action:"unwatch",selector:t}):this.pendingUnwatchSelectors.includes(t)||this.pendingUnwatchSelectors.push(t)}async destroy(){return this.serialize(async()=>{this.conditionBridge?.destroy(),await this.hideNow(),this.loader?.destroy(),this.frame.remove(),this.destroyed=!0})}async hide(){return this.serialize(()=>this.hideNow())}async hideNow(){if(!this.destroyed){if(this.frame.style.visibility="hidden",this.frame.ariaHidden="true",this.frame.style.opacity="0",this.frame.style.pointerEvents="none",this.hidden=!0,this.frame.parentElement)return this.comms===void 0||!this.comms.ready?void 0:new Promise((t,i)=>{this.comms?.send("unfocus",void 0,s=>{this.comms?.halt(),t()})});this.comms?.halt()}}async show(t){return this.serialize(()=>this.showNow(t))}async showNow(t){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");return this.comms?this.comms.resume():this.comms=new n(this.frame.contentWindow,this.source),new Promise((i,s)=>{this.comms?.send("activate",void 0,()=>{this.comms?.send("focus",void 0,()=>{this.applyContentProtection(),this.timelineFragmentIds.length>0&&this.comms?.send("timeline_entries",this.timelineFragmentIds);const e=()=>{this.frame.style.removeProperty("visibility"),this.frame.ariaHidden=null,this.frame.style.removeProperty("opacity"),this.frame.style.removeProperty("pointer-events"),this.hidden=!1,h.UA.WebKit&&this.comms?.send("force_webkit_recalc",void 0),i()};t!==void 0?this.comms?.send("go_progression",t,e):e()})})})}setCSSProperties(t){this.destroyed||!this.frame.contentWindow||(this.hidden&&!this.comms&&(this.comms=new n(this.frame.contentWindow,this.source),this.comms.halt()),this.comms?.send("update_properties",t))}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}}export{w as WebPubFrameManager};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/navigator",
3
- "version": "2.11.0",
3
+ "version": "2.11.1",
4
4
  "type": "module",
5
5
  "description": "Next generation SDK for publications in Web Apps",
6
6
  "author": "readium",
@@ -45,9 +45,9 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@readium/decorator": "1.2.4",
48
- "@readium/shared": "2.6.0",
49
48
  "@readium/helpers": "1.1.1",
50
- "@readium/navigator-html-injectables": "2.8.4"
49
+ "@readium/navigator-html-injectables": "2.8.4",
50
+ "@readium/shared": "2.6.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@readium/css": "^2.0.5",
@@ -14,19 +14,22 @@ interface RegistryValue {
14
14
  cb: CommsAck;
15
15
  }
16
16
  const REGISTRY_EXPIRY = 10000; // 10 seconds max
17
+ const GC_INTERVAL = 5000;
17
18
 
18
19
  export type FrameCommsListener = (key: CommsEventKey | ManagerEventKey, value: unknown) => void;
19
20
 
20
21
  export class FrameComms {
21
22
  private readonly wnd: Window;
22
23
  private readonly registry = new Map<string, RegistryValue>();
23
- private readonly gc: ReturnType<typeof setInterval>;
24
+ private gc: ReturnType<typeof setInterval> | undefined;
24
25
  // @ts-ignore
25
26
  private readonly origin: string;
26
27
  public readonly channelId: string;
27
28
  private _ready = false;
28
29
  private _listener: FrameCommsListener | undefined;
29
30
  private listenerBuffer: [key: CommsEventKey, value: unknown][] = [];
31
+ private visibleTime = 0;
32
+ private visibleSince: number | undefined = document.hidden ? undefined : performance.now();
30
33
 
31
34
  public set listener(listener: FrameCommsListener) {
32
35
  if(this.listenerBuffer.length > 0)
@@ -46,28 +49,60 @@ export class FrameComms {
46
49
  } catch (error) {
47
50
  this.channelId = mid();
48
51
  }
52
+ this.startGc();
53
+ window.addEventListener("message", this.handler);
54
+ this.send("_ping", undefined);
55
+ }
56
+
57
+ private startGc() {
58
+ if (this.gc !== undefined) return;
59
+ this.handleVisibility();
60
+ document.addEventListener("visibilitychange", this.visibilityHandler);
49
61
  this.gc = setInterval(() => {
62
+ const now = this.visibleNow();
50
63
  this.registry.forEach((v, k) => {
51
- if (performance.now() - v.time > REGISTRY_EXPIRY) {
64
+ if (now - v.time > REGISTRY_EXPIRY) {
52
65
  console.warn(k, "event for", v.key, "was never handled!");
53
66
  this.registry.delete(k);
54
67
  }
55
68
  });
56
- }, 5000);
57
- window.addEventListener("message", this.handler);
58
- this.send("_ping", undefined);
69
+ }, GC_INTERVAL);
59
70
  }
60
71
 
72
+ private stopGc() {
73
+ this.visibleTime = this.visibleNow();
74
+ this.visibleSince = undefined;
75
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
76
+ clearInterval(this.gc);
77
+ this.gc = undefined;
78
+ }
79
+
80
+ // Frames ack some commands from requestAnimationFrame, which doesn't run while the page is hidden
81
+ private visibleNow() {
82
+ return this.visibleTime + (this.visibleSince === undefined ? 0 : performance.now() - this.visibleSince);
83
+ }
84
+
85
+ private handleVisibility() {
86
+ if (document.hidden) {
87
+ this.visibleTime = this.visibleNow();
88
+ this.visibleSince = undefined;
89
+ } else if (this.visibleSince === undefined) {
90
+ this.visibleSince = performance.now();
91
+ }
92
+ }
93
+ private visibilityHandler = this.handleVisibility.bind(this);
94
+
61
95
  public halt() {
62
96
  this._ready = false;
63
97
  window.removeEventListener("message", this.handler);
64
- clearInterval(this.gc);
98
+ this.stopGc();
65
99
  this._listener = undefined;
66
100
  this.registry.clear();
67
101
  }
68
102
 
69
103
  public resume() {
70
104
  window.addEventListener("message", this.handler);
105
+ this.startGc();
71
106
  this._ready = true;
72
107
  }
73
108
 
@@ -122,7 +157,7 @@ export class FrameComms {
122
157
  this.registry.set(id, {
123
158
  // Add callback to the registry
124
159
  cb: callback,
125
- time: performance.now(),
160
+ time: this.visibleNow(),
126
161
  key,
127
162
  });
128
163
  this.wnd.postMessage(
@@ -4,6 +4,7 @@ import type { ReadiumWindow } from "@readium/navigator-html-injectables";
4
4
  import { sML } from "@readium/helpers";
5
5
  import type { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../../Navigator.ts";
6
6
  import { KeyboardConditionBridge } from "../../peripherals/KeyboardConditionBridge.ts";
7
+ import { createSerializer } from "../../helpers/serialize.ts";
7
8
 
8
9
 
9
10
  export class FrameManager {
@@ -19,6 +20,8 @@ export class FrameManager {
19
20
  private pendingUnwatchSelectors: string[] = [];
20
21
  private conditionBridge?: KeyboardConditionBridge;
21
22
  private currModules: ModuleName[] = [];
23
+ // Halting comms drops pending acks, so an operation must not start while another awaits one
24
+ private readonly serialize = createSerializer();
22
25
 
23
26
  constructor(
24
27
  source: string,
@@ -46,7 +49,7 @@ export class FrameManager {
46
49
  }
47
50
 
48
51
  async load(modules: ModuleName[]): Promise<Window> {
49
- return new Promise((res, rej) => {
52
+ return this.serialize(() => new Promise((res, rej) => {
50
53
  if(this.loader) {
51
54
  const wnd = this.frame.contentWindow!;
52
55
  // Check if currently loaded modules are equal
@@ -72,7 +75,7 @@ export class FrameManager {
72
75
  try { rej(err); } catch (error) {}
73
76
  }
74
77
  this.frame.contentWindow!.location.replace(this.source);
75
- });
78
+ }));
76
79
  }
77
80
 
78
81
  private applyContentProtection() {
@@ -132,14 +135,20 @@ export class FrameManager {
132
135
  }
133
136
 
134
137
  async destroy() {
135
- this.conditionBridge?.destroy();
136
- await this.hide();
137
- this.loader?.destroy();
138
- this.frame.remove();
139
- this.destroyed = true;
138
+ return this.serialize(async () => {
139
+ this.conditionBridge?.destroy();
140
+ await this.hideNow();
141
+ this.loader?.destroy();
142
+ this.frame.remove();
143
+ this.destroyed = true;
144
+ });
140
145
  }
141
146
 
142
147
  async hide(): Promise<void> {
148
+ return this.serialize(() => this.hideNow());
149
+ }
150
+
151
+ private async hideNow(): Promise<void> {
143
152
  if(this.destroyed) return;
144
153
  this.frame.style.visibility = "hidden";
145
154
  this.frame.ariaHidden = "true";
@@ -162,6 +171,10 @@ export class FrameManager {
162
171
  }
163
172
 
164
173
  async show(atProgress?: number): Promise<void> {
174
+ return this.serialize(() => this.showNow(atProgress));
175
+ }
176
+
177
+ private async showNow(atProgress?: number): Promise<void> {
165
178
  if(this.destroyed) throw Error("Trying to show frame when it doesn't exist");
166
179
  if(!this.frame.parentElement) throw Error("Trying to show frame that is not attached to the DOM");
167
180
  if(this.comms) this.comms.resume();
@@ -5,6 +5,7 @@ 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
+ import { createSerializer } from "../../helpers/serialize.ts";
8
9
 
9
10
  export class FXLFrameManager {
10
11
  private frame: HTMLIFrameElement;
@@ -26,6 +27,8 @@ export class FXLFrameManager {
26
27
  private loadPromise: Promise<Window> | undefined;
27
28
  private showPromise: Promise<void> | undefined;
28
29
  private viewportSize: { width: number, height: number } | undefined = undefined;
30
+ // Halting comms drops pending acks, so an operation must not start while another awaits one
31
+ private readonly serialize = createSerializer();
29
32
 
30
33
  constructor(
31
34
  peripherals: FXLPeripherals,
@@ -65,6 +68,10 @@ export class FXLFrameManager {
65
68
  }
66
69
 
67
70
  async load(modules: ModuleName[], source: string): Promise<Window> {
71
+ return this.serialize(() => this.loadNow(modules, source));
72
+ }
73
+
74
+ private async loadNow(modules: ModuleName[], source: string): Promise<Window> {
68
75
  if(!this.frameIsAppended) {
69
76
  this.wrapper.appendChild(this.frame);
70
77
  this.frameIsAppended = true;
@@ -194,13 +201,19 @@ export class FXLFrameManager {
194
201
  }
195
202
 
196
203
  async destroy() {
197
- this.conditionBridge?.destroy();
198
- await this.unfocus();
199
- this.loader?.destroy();
200
- this.wrapper.remove();
204
+ return this.serialize(async () => {
205
+ this.conditionBridge?.destroy();
206
+ await this.unfocusNow();
207
+ this.loader?.destroy();
208
+ this.wrapper.remove();
209
+ });
201
210
  }
202
211
 
203
212
  async unload() {
213
+ return this.serialize(() => this.unloadNow());
214
+ }
215
+
216
+ private async unloadNow() {
204
217
  if(!this.loaded) return;
205
218
  this.deselect();
206
219
  this.frame.style.visibility = "hidden";
@@ -232,8 +245,13 @@ export class FXLFrameManager {
232
245
  }
233
246
 
234
247
  async unfocus(): Promise<void> {
248
+ return this.serialize(() => this.unfocusNow());
249
+ }
250
+
251
+ private async unfocusNow(): Promise<void> {
235
252
  if(this.frame.parentElement) {
236
- if(this.comms === undefined) return;
253
+ // A halted channel no longer receives acks
254
+ if(this.comms === undefined || !this.comms.ready) return;
237
255
  // Return focus to the parent document so keyboard events aren't silently
238
256
  // swallowed by an off-screen iframe whose comms channel has been halted.
239
257
  this.frame.blur();
@@ -301,6 +319,10 @@ export class FXLFrameManager {
301
319
 
302
320
  private cachedPage: Page | undefined = undefined;
303
321
  async show(page: Page): Promise<void> {
322
+ return this.serialize(() => this.showNow(page));
323
+ }
324
+
325
+ private async showNow(page: Page): Promise<void> {
304
326
  if(!this.frame.parentElement) {
305
327
  console.warn("Trying to show frame that is not attached to the DOM");
306
328
  return;
@@ -332,12 +354,12 @@ export class FXLFrameManager {
332
354
  }
333
355
 
334
356
  async activate(): Promise<void> {
335
- return new Promise<void>((res, _) => {
336
- if(!this.comms) return res(); // TODO: investigate when this is the case
357
+ return this.serialize(() => new Promise<void>((res, _) => {
358
+ if(!this.comms?.ready) return res(); // TODO: investigate when this is the case
337
359
  this.comms?.send("activate", undefined, () => {
338
360
  res();
339
361
  });
340
- });
362
+ }));
341
363
  }
342
364
 
343
365
  get element() {
@@ -0,0 +1,9 @@
1
+ // Returns a function that runs operations one after another, even when one rejects
2
+ export function createSerializer() {
3
+ let queue: Promise<unknown> = Promise.resolve();
4
+ return <T>(operation: () => Promise<T>): Promise<T> => {
5
+ const result = queue.then(operation);
6
+ queue = result.catch(() => {});
7
+ return result;
8
+ };
9
+ }
@@ -4,6 +4,7 @@ import type { ReadiumWindow } from "@readium/navigator-html-injectables";
4
4
  import { sML } from "@readium/helpers";
5
5
  import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator.ts";
6
6
  import { KeyboardConditionBridge } from "../peripherals/KeyboardConditionBridge.ts";
7
+ import { createSerializer } from "../helpers/serialize.ts";
7
8
 
8
9
  export class WebPubFrameManager {
9
10
  private frame: HTMLIFrameElement;
@@ -18,6 +19,8 @@ export class WebPubFrameManager {
18
19
  private pendingUnwatchSelectors: string[] = [];
19
20
  private conditionBridge?: KeyboardConditionBridge;
20
21
  private currModules: ModuleName[] = [];
22
+ // Halting comms drops pending acks, so an operation must not start while another awaits one
23
+ private readonly serialize = createSerializer();
21
24
 
22
25
  constructor(
23
26
  source: string,
@@ -45,7 +48,7 @@ export class WebPubFrameManager {
45
48
  }
46
49
 
47
50
  async load(modules: ModuleName[] = []): Promise<Window> {
48
- return new Promise((res, rej) => {
51
+ return this.serialize(() => new Promise((res, rej) => {
49
52
  if (this.loader) {
50
53
  const wnd = this.frame.contentWindow!;
51
54
  if ([...this.currModules].sort().join("|") === [...modules].sort().join("|")) {
@@ -70,7 +73,7 @@ export class WebPubFrameManager {
70
73
  try { rej(err); } catch (error) {}
71
74
  }
72
75
  this.frame.contentWindow!.location.replace(this.source);
73
- });
76
+ }));
74
77
  }
75
78
 
76
79
  private applyContentProtection() {
@@ -130,14 +133,20 @@ export class WebPubFrameManager {
130
133
  }
131
134
 
132
135
  async destroy() {
133
- this.conditionBridge?.destroy();
134
- await this.hide();
135
- this.loader?.destroy();
136
- this.frame.remove();
137
- this.destroyed = true;
136
+ return this.serialize(async () => {
137
+ this.conditionBridge?.destroy();
138
+ await this.hideNow();
139
+ this.loader?.destroy();
140
+ this.frame.remove();
141
+ this.destroyed = true;
142
+ });
138
143
  }
139
144
 
140
145
  async hide(): Promise<void> {
146
+ return this.serialize(() => this.hideNow());
147
+ }
148
+
149
+ private async hideNow(): Promise<void> {
141
150
  if(this.destroyed) return;
142
151
  this.frame.style.visibility = "hidden";
143
152
  this.frame.ariaHidden = "true";
@@ -159,6 +168,10 @@ export class WebPubFrameManager {
159
168
  }
160
169
 
161
170
  async show(atProgress?: number): Promise<void> {
171
+ return this.serialize(() => this.showNow(atProgress));
172
+ }
173
+
174
+ private async showNow(atProgress?: number): Promise<void> {
162
175
  if (this.destroyed) throw Error("Trying to show frame when it doesn't exist");
163
176
  if (!this.frame.parentElement) throw Error("Trying to show frame that is not attached to the DOM");
164
177
  if (this.comms) this.comms.resume();
@@ -4,15 +4,22 @@ export type FrameCommsListener = (key: CommsEventKey | ManagerEventKey, value: u
4
4
  export declare class FrameComms {
5
5
  private readonly wnd;
6
6
  private readonly registry;
7
- private readonly gc;
7
+ private gc;
8
8
  private readonly origin;
9
9
  readonly channelId: string;
10
10
  private _ready;
11
11
  private _listener;
12
12
  private listenerBuffer;
13
+ private visibleTime;
14
+ private visibleSince;
13
15
  set listener(listener: FrameCommsListener);
14
16
  clearListener(): void;
15
17
  constructor(wnd: Window, origin: string);
18
+ private startGc;
19
+ private stopGc;
20
+ private visibleNow;
21
+ private handleVisibility;
22
+ private visibilityHandler;
16
23
  halt(): void;
17
24
  resume(): void;
18
25
  private handle;
@@ -15,6 +15,7 @@ export declare class FrameManager {
15
15
  private pendingUnwatchSelectors;
16
16
  private conditionBridge?;
17
17
  private currModules;
18
+ private readonly serialize;
18
19
  constructor(source: string, contentProtectionConfig?: IContentProtectionConfig, keyboardPeripheralsConfig?: IKeyboardPeripheralsConfig, timelineFragmentIds?: string[], resizeWatchSelectors?: string[]);
19
20
  load(modules: ModuleName[]): Promise<Window>;
20
21
  private applyContentProtection;
@@ -22,7 +23,9 @@ export declare class FrameManager {
22
23
  removeResizeTarget(selector: string): void;
23
24
  destroy(): Promise<void>;
24
25
  hide(): Promise<void>;
26
+ private hideNow;
25
27
  show(atProgress?: number): Promise<void>;
28
+ private showNow;
26
29
  setCSSProperties(properties: {
27
30
  [key: string]: string;
28
31
  }): void;
@@ -21,8 +21,10 @@ export declare class FXLFrameManager {
21
21
  private loadPromise;
22
22
  private showPromise;
23
23
  private viewportSize;
24
+ private readonly serialize;
24
25
  constructor(peripherals: FXLPeripherals, direction: ReadingProgression, debugHref: string, contentProtectionConfig?: IContentProtectionConfig, keyboardPeripheralsConfig?: IKeyboardPeripheralsConfig, resizeWatchSelectors?: string[]);
25
26
  load(modules: ModuleName[], source: string): Promise<Window>;
27
+ private loadNow;
26
28
  loadPageSize(): {
27
29
  width: number;
28
30
  height: number;
@@ -30,13 +32,16 @@ export declare class FXLFrameManager {
30
32
  update(page?: Page): void;
31
33
  destroy(): Promise<void>;
32
34
  unload(): Promise<void>;
35
+ private unloadNow;
33
36
  deselect(): void;
34
37
  unfocus(): Promise<void>;
38
+ private unfocusNow;
35
39
  private applyContentProtection;
36
40
  addResizeTarget(selector: string): void;
37
41
  removeResizeTarget(selector: string): void;
38
42
  private cachedPage;
39
43
  show(page: Page): Promise<void>;
44
+ private showNow;
40
45
  activate(): Promise<void>;
41
46
  get element(): HTMLDivElement;
42
47
  get iframe(): HTMLIFrameElement;
@@ -0,0 +1 @@
1
+ export declare function createSerializer(): <T>(operation: () => Promise<T>) => Promise<T>;
@@ -15,6 +15,7 @@ export declare class WebPubFrameManager {
15
15
  private pendingUnwatchSelectors;
16
16
  private conditionBridge?;
17
17
  private currModules;
18
+ private readonly serialize;
18
19
  constructor(source: string, contentProtectionConfig?: IContentProtectionConfig, keyboardPeripheralsConfig?: IKeyboardPeripheralsConfig, timelineFragmentIds?: string[], resizeWatchSelectors?: string[]);
19
20
  load(modules?: ModuleName[]): Promise<Window>;
20
21
  private applyContentProtection;
@@ -22,7 +23,9 @@ export declare class WebPubFrameManager {
22
23
  removeResizeTarget(selector: string): void;
23
24
  destroy(): Promise<void>;
24
25
  hide(): Promise<void>;
26
+ private hideNow;
25
27
  show(atProgress?: number): Promise<void>;
28
+ private showNow;
26
29
  setCSSProperties(properties: {
27
30
  [key: string]: string;
28
31
  }): void;