2323// eslint-disable-next-line max-len
2424/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue } PDFRenderingQueue */
2525
26- import { OutputScale , RenderingCancelledException } from "pdfjs-lib" ;
26+ import {
27+ FeatureTest ,
28+ OutputScale ,
29+ RenderingCancelledException ,
30+ } from "pdfjs-lib" ;
2731import { AppOptions } from "./app_options.js" ;
2832import { RenderingStates } from "./ui_utils.js" ;
2933
3034const DRAW_UPSCALE_FACTOR = 2 ; // See comment in `PDFThumbnailView.draw` below.
3135const MAX_NUM_SCALING_STEPS = 3 ;
3236const THUMBNAIL_WIDTH = 98 ; // px
3337
34- function zeroCanvas ( c ) {
35- // Zeroing the width and height causes Firefox to release graphics
36- // resources immediately, which can greatly reduce memory consumption.
37- c . width = 0 ;
38- c . height = 0 ;
39- }
40-
4138/**
4239 * @typedef {Object } PDFThumbnailViewOptions
4340 * @property {HTMLDivElement } container - The viewer element.
@@ -61,12 +58,15 @@ function zeroCanvas(c) {
6158 */
6259
6360class TempImageFactory {
64- static #tempCanvas = null ;
65-
6661 static getCanvas ( width , height ) {
67- const tempCanvas = ( this . #tempCanvas ||= document . createElement ( "canvas" ) ) ;
68- tempCanvas . width = width ;
69- tempCanvas . height = height ;
62+ let tempCanvas ;
63+ if ( FeatureTest . isOffscreenCanvasSupported ) {
64+ tempCanvas = new OffscreenCanvas ( width , height ) ;
65+ } else {
66+ tempCanvas = document . createElement ( "canvas" ) ;
67+ tempCanvas . width = width ;
68+ tempCanvas . height = height ;
69+ }
7070
7171 // Since this is a temporary canvas, we need to fill it with a white
7272 // background ourselves. `#getPageDrawContext` uses CSS rules for this.
@@ -75,14 +75,7 @@ class TempImageFactory {
7575 ctx . fillStyle = "rgb(255, 255, 255)" ;
7676 ctx . fillRect ( 0 , 0 , width , height ) ;
7777 ctx . restore ( ) ;
78- return [ tempCanvas , tempCanvas . getContext ( "2d" ) ] ;
79- }
80-
81- static destroyCanvas ( ) {
82- if ( this . #tempCanvas) {
83- zeroCanvas ( this . #tempCanvas) ;
84- }
85- this . #tempCanvas = null ;
78+ return [ tempCanvas , ctx ] ;
8679 }
8780}
8881
@@ -126,27 +119,24 @@ class PDFThumbnailView {
126119 this . renderingState = RenderingStates . INITIAL ;
127120 this . resume = null ;
128121
129- const anchor = document . createElement ( "a" ) ;
130- anchor . href = linkService . getAnchorUrl ( " #page=" + id ) ;
122+ const anchor = ( this . anchor = document . createElement ( "a" ) ) ;
123+ anchor . href = linkService . getAnchorUrl ( ` #page=${ id } ` ) ;
131124 anchor . setAttribute ( "data-l10n-id" , "pdfjs-thumb-page-title" ) ;
132125 anchor . setAttribute ( "data-l10n-args" , this . #pageL10nArgs) ;
133- anchor . onclick = function ( ) {
126+ anchor . onclick = ( ) => {
134127 linkService . goToPage ( id ) ;
135128 return false ;
136129 } ;
137- this . anchor = anchor ;
138130
139- const div = document . createElement ( "div" ) ;
140- div . className = "thumbnail" ;
131+ const div = ( this . div = document . createElement ( "div" ) ) ;
132+ div . classList . add ( "thumbnail" , "missingThumbnailImage" ) ;
141133 div . setAttribute ( "data-page-number" , this . id ) ;
142- this . div = div ;
143134 this . #updateDims( ) ;
144135
145- const img = document . createElement ( "div" ) ;
146- img . className = "thumbnailImage" ;
147- this . _placeholderImg = img ;
136+ const image = ( this . image = document . createElement ( "img" ) ) ;
137+ image . className = "thumbnailImage" ;
148138
149- div . append ( img ) ;
139+ div . append ( image ) ;
150140 anchor . append ( div ) ;
151141 container . append ( anchor ) ;
152142 }
@@ -155,13 +145,11 @@ class PDFThumbnailView {
155145 const { width, height } = this . viewport ;
156146 const ratio = width / height ;
157147
158- this . canvasWidth = THUMBNAIL_WIDTH ;
159- this . canvasHeight = ( this . canvasWidth / ratio ) | 0 ;
160- this . scale = this . canvasWidth / width ;
148+ const canvasWidth = ( this . canvasWidth = THUMBNAIL_WIDTH ) ;
149+ const canvasHeight = ( this . canvasHeight = ( canvasWidth / ratio ) | 0 ) ;
150+ this . scale = canvasWidth / width ;
161151
162- const { style } = this . div ;
163- style . setProperty ( "--thumbnail-width" , `${ this . canvasWidth } px` ) ;
164- style . setProperty ( "--thumbnail-height" , `${ this . canvasHeight } px` ) ;
152+ this . div . style . height = `${ canvasHeight } px` ;
165153 }
166154
167155 setPdfPage ( pdfPage ) {
@@ -175,14 +163,16 @@ class PDFThumbnailView {
175163 reset ( ) {
176164 this . cancelRendering ( ) ;
177165 this . renderingState = RenderingStates . INITIAL ;
178-
179- this . div . removeAttribute ( "data-loaded" ) ;
180- this . image ?. replaceWith ( this . _placeholderImg ) ;
181166 this . #updateDims( ) ;
182167
183- if ( this . image ) {
184- this . image . removeAttribute ( "src" ) ;
185- delete this . image ;
168+ const { image } = this ;
169+ const url = image . src ;
170+ if ( url ) {
171+ URL . revokeObjectURL ( url ) ;
172+ image . removeAttribute ( "data-l10n-id" ) ;
173+ image . removeAttribute ( "data-l10n-args" ) ;
174+ image . src = "" ;
175+ this . div . classList . add ( "missingThumbnailImage" ) ;
186176 }
187177 }
188178
@@ -213,7 +203,6 @@ class PDFThumbnailView {
213203 #getPageDrawContext( upscaleFactor = 1 ) {
214204 // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`,
215205 // until rendering/image conversion is complete, to avoid display issues.
216- const canvas = document . createElement ( "canvas" ) ;
217206 const outputScale = new OutputScale ( ) ;
218207 const width = upscaleFactor * this . canvasWidth ,
219208 height = upscaleFactor * this . canvasHeight ;
@@ -224,6 +213,9 @@ class PDFThumbnailView {
224213 this . maxCanvasPixels ,
225214 this . maxCanvasDim
226215 ) ;
216+ // Because of: https://bugzilla.mozilla.org/show_bug.cgi?id=2003060
217+ // we need use a HTMLCanvasElement here.
218+ const canvas = document . createElement ( "canvas" ) ;
227219 canvas . width = ( width * outputScale . sx ) | 0 ;
228220 canvas . height = ( height * outputScale . sy ) | 0 ;
229221
@@ -234,23 +226,23 @@ class PDFThumbnailView {
234226 return { canvas, transform } ;
235227 }
236228
237- #convertCanvasToImage( canvas ) {
229+ async #convertCanvasToImage( canvas ) {
238230 if ( this . renderingState !== RenderingStates . FINISHED ) {
239231 throw new Error ( "#convertCanvasToImage: Rendering has not finished." ) ;
240232 }
241233 const reducedCanvas = this . #reduceImage( canvas ) ;
242-
243- const image = document . createElement ( "img" ) ;
244- image . className = "thumbnailImage" ;
234+ const { image } = this ;
235+ const { promise, resolve } = Promise . withResolvers ( ) ;
236+ reducedCanvas . toBlob ( resolve ) ;
237+ const blob = await promise ;
238+ image . src = URL . createObjectURL ( blob ) ;
245239 image . setAttribute ( "data-l10n-id" , "pdfjs-thumb-page-canvas" ) ;
246240 image . setAttribute ( "data-l10n-args" , this . #pageL10nArgs) ;
247- image . src = reducedCanvas . toDataURL ( ) ;
248- this . image = image ;
249-
250- this . div . setAttribute ( "data-loaded" , true ) ;
251- this . _placeholderImg . replaceWith ( image ) ;
252-
253- zeroCanvas ( reducedCanvas ) ;
241+ this . div . classList . remove ( "missingThumbnailImage" ) ;
242+ if ( ! FeatureTest . isOffscreenCanvasSupported ) {
243+ // Clean up the canvas element since it is no longer needed.
244+ reducedCanvas . width = reducedCanvas . height = 0 ;
245+ }
254246 }
255247
256248 async draw ( ) {
@@ -303,7 +295,6 @@ class PDFThumbnailView {
303295 await renderTask . promise ;
304296 } catch ( e ) {
305297 if ( e instanceof RenderingCancelledException ) {
306- zeroCanvas ( canvas ) ;
307298 return ;
308299 }
309300 error = e ;
@@ -317,8 +308,7 @@ class PDFThumbnailView {
317308 }
318309 this . renderingState = RenderingStates . FINISHED ;
319310
320- this . #convertCanvasToImage( canvas ) ;
321- zeroCanvas ( canvas ) ;
311+ await this . #convertCanvasToImage( canvas ) ;
322312
323313 this . eventBus . dispatch ( "thumbnailrendered" , {
324314 source : this ,
@@ -449,14 +439,9 @@ class PDFThumbnailView {
449439 */
450440 setPageLabel ( label ) {
451441 this . pageLabel = typeof label === "string" ? label : null ;
452-
453442 this . anchor . setAttribute ( "data-l10n-args" , this . #pageL10nArgs) ;
454-
455- if ( this . renderingState !== RenderingStates . FINISHED ) {
456- return ;
457- }
458- this . image ?. setAttribute ( "data-l10n-args" , this . #pageL10nArgs) ;
443+ this . image . setAttribute ( "data-l10n-args" , this . #pageL10nArgs) ;
459444 }
460445}
461446
462- export { PDFThumbnailView , TempImageFactory } ;
447+ export { PDFThumbnailView } ;
0 commit comments