1- import { MediaType } from "@readium/shared" ;
1+ import { MediaType , Resource } from "@readium/shared" ;
22import { Link , Publication } from "@readium/shared" ;
33import { Injector } from "../../injection/Injector" ;
44
@@ -20,73 +20,126 @@ const csp = (domains: string[]) => {
2020 ] . join ( "; " ) ;
2121} ;
2222
23- export default class FrameBlobBuider {
24- private readonly item : Link ;
25- private readonly burl : string ;
26- private readonly pub : Publication ;
23+ export default class FrameBlobBuilder {
2724 private readonly cssProperties ?: { [ key : string ] : string } ;
2825 private readonly injector : Injector | null = null ;
2926
27+ private currentUrl ?: string ;
28+ private currentResource ?: Resource ;
29+
3030 constructor (
31- pub : Publication ,
32- baseURL : string ,
33- item : Link ,
31+ private readonly pub : Publication ,
32+ private readonly baseURL : string ,
33+ private readonly item : Link ,
3434 options : {
3535 cssProperties ?: { [ key : string ] : string } ;
3636 injector ?: Injector | null ;
3737 }
3838 ) {
39- this . pub = pub ;
4039 this . item = item ;
41- this . burl = item . toURL ( baseURL ) || "" ;
4240 this . cssProperties = options . cssProperties ;
4341 this . injector = options . injector ?? null ;
4442 }
4543
44+ public reset ( ) {
45+ this . currentUrl && URL . revokeObjectURL ( this . currentUrl ) ;
46+ this . currentUrl = undefined ;
47+ this . currentResource ?. close ( ) ;
48+ this . currentResource = undefined ;
49+ }
50+
4651 public async build ( fxl = false ) : Promise < string > {
47- if ( ! this . item . mediaType . isHTML ) {
48- if ( this . item . mediaType . isBitmap || this . item . mediaType . equals ( MediaType . SVG ) ) {
49- return this . buildImageFrame ( ) ;
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 ;
5065 } else
51- throw Error ( "Unsupported frame mediatype " + this . item . mediaType . string ) ;
66+ throw Error ( "Unsupported frame mediatype " + link . mediaType . string ) ;
5267 } else {
53- return await this . buildHtmlFrame ( fxl ) ;
68+ const blobUrl = await this . buildHtmlFrame ( fxl ) ;
69+ this . currentUrl = blobUrl ;
70+ return blobUrl ;
5471 }
5572 }
5673
5774 private async buildHtmlFrame ( fxl = false ) : Promise < string > {
75+ if ( ! this . currentResource ) throw new Error ( "No resource loaded" ) ;
76+
5877 // Load the HTML resource
59- const txt = await this . pub . get ( this . item ) . readAsString ( ) ;
60- if ( ! txt ) throw new Error ( `Failed reading item ${ this . item . href } ` ) ;
78+ const link = await this . currentResource . link ( ) ;
79+ const txt = await this . currentResource . readAsString ( ) ;
80+ if ( ! txt ) throw new Error ( `Failed reading item ${ link . href } ` ) ;
6181
6282 const doc = new DOMParser ( ) . parseFromString (
6383 txt ,
64- this . item . mediaType . string as DOMParserSupportedType
84+ link . mediaType . string as DOMParserSupportedType
6585 ) ;
66-
86+
6787 const perror = doc . querySelector ( "parsererror" ) ;
6888 if ( perror ) {
6989 const details = perror . querySelector ( "div" ) ;
70- throw new Error ( `Failed parsing item ${ this . item . href } : ${ details ?. textContent || perror . textContent } ` ) ;
90+ throw new Error ( `Failed parsing item ${ link . href } : ${ details ?. textContent || perror . textContent } ` ) ;
7191 }
7292
7393 // Apply resource injections if injection service is provided
7494 if ( this . injector ) {
75- await this . injector . injectForDocument ( doc , this . item ) ;
95+ await this . injector . injectForDocument ( doc , link ) ;
7696 }
7797
78- return this . finalizeDOM ( doc , this . pub . baseURL , this . burl , this . item . mediaType , fxl , this . cssProperties ) ;
98+ return this . finalizeDOM ( doc , this . pub . baseURL , link . toURL ( this . baseURL ) || "" , link . mediaType , fxl , this . cssProperties ) ;
7999 }
80100
81- private buildImageFrame ( ) : string {
82- // Rudimentary image display
83- const doc = document . implementation . createHTMLDocument ( this . item . title || this . item . href ) ;
101+ private async buildImageFrame ( ) : Promise < string > {
102+ if ( ! this . currentResource ) throw new Error ( "No resource loaded" ) ;
103+ const link = await this . currentResource . link ( ) ;
104+ const burl = link . toURL ( this . baseURL ) || ""
105+
106+ // Rudimentary image display in an HTML doc
107+ const doc = document . implementation . createHTMLDocument ( link . title || link . href ) ;
108+
109+ // Add viewport if available
110+ if ( ( link ?. height || 0 ) > 0 && ( link ?. width || 0 ) > 0 ) {
111+ const viewportMeta = doc . createElement ( "meta" ) ;
112+ viewportMeta . name = "viewport" ;
113+ viewportMeta . content = `width=${ link . width } , height=${ link . height } ` ;
114+ viewportMeta . dataset . readium = "true" ;
115+ doc . head . appendChild ( viewportMeta ) ;
116+ }
117+
84118 const simg = document . createElement ( "img" ) ;
85- simg . src = this . burl || "" ;
86- simg . alt = this . item . title || "" ;
119+ simg . src = burl || "" ;
120+ simg . alt = link . title || "" ;
87121 simg . decoding = "async" ;
88122 doc . body . appendChild ( simg ) ;
89- return this . finalizeDOM ( doc , this . pub . baseURL , this . burl , this . item . mediaType , true ) ;
123+
124+ // Apply resource injections if injection service is provided
125+ if ( this . injector ) {
126+ await this . injector . injectForDocument ( doc , new Link ( {
127+ // Temporary solution to address injector only expecting (X)HTML
128+ // documents for injection, which we are technically providing
129+ href : "readium-image-frame.xhtml" ,
130+ type : MediaType . XHTML . string
131+ } ) ) ;
132+ }
133+
134+ // Add image style
135+ const sstyle = doc . createElement ( "style" ) ;
136+ sstyle . dataset . readium = "true" ;
137+ sstyle . textContent = `
138+ html, body { width: 100%; height: 100%; margin: 0; padding: 0; font-size: 0; }
139+ img { margin: 0; padding: 0; border: 0; }` ;
140+ doc . head . appendChild ( sstyle ) ;
141+
142+ return this . finalizeDOM ( doc , this . pub . baseURL , burl , link . mediaType , true ) ;
90143 }
91144
92145 private setProperties ( cssProperties : { [ key : string ] : string } , doc : Document ) {
@@ -101,7 +154,10 @@ export default class FrameBlobBuider {
101154
102155 // Get allowed domains from injector if it exists
103156 const allowedDomains = this . injector ?. getAllowedDomains ?.( ) || [ ] ;
104-
157+
158+ // Remove query from root if present, as CSP doesn't allow them
159+ root = root ?. split ( "?" ) [ 0 ] ;
160+
105161 // Always include the root domain if provided
106162 const domains = [ ...new Set ( [
107163 ...( root ? [ root ] : [ ] ) ,
@@ -124,6 +180,7 @@ export default class FrameBlobBuider {
124180 // loaded in parallel, greatly increasing overall speed.
125181 doc . body . querySelectorAll ( "img" ) . forEach ( ( img ) => {
126182 img . setAttribute ( "fetchpriority" , "high" ) ;
183+ img . setAttribute ( "referrerpolicy" , "origin" ) ;
127184 } ) ;
128185
129186 // We need to ensure that lang is set on the root element
0 commit comments