1313 * limitations under the License.
1414 */
1515
16- import { getPDFFileNameFromURL , mozL10n } from './ui_utils' ;
16+ import { cloneObj , getPDFFileNameFromURL , mozL10n } from './ui_utils' ;
1717import { createPromiseCapability } from './pdfjs' ;
1818import { OverlayManager } from './overlay_manager' ;
1919
20+ const DEFAULT_FIELD_CONTENT = '-' ;
21+
2022/**
2123 * @typedef {Object } PDFDocumentPropertiesOptions
2224 * @property {string } overlayName - Name/identifier for the overlay.
@@ -29,21 +31,16 @@ class PDFDocumentProperties {
2931 /**
3032 * @param {PDFDocumentPropertiesOptions } options
3133 */
32- constructor ( options ) {
33- this . overlayName = options . overlayName ;
34- this . fields = options . fields ;
35- this . container = options . container ;
34+ constructor ( { overlayName , fields , container , closeButton , } ) {
35+ this . overlayName = overlayName ;
36+ this . fields = fields ;
37+ this . container = container ;
3638
37- this . rawFileSize = 0 ;
38- this . url = null ;
39- this . pdfDocument = null ;
39+ this . _reset ( ) ;
4040
41- // Bind the event listener for the Close button.
42- if ( options . closeButton ) {
43- options . closeButton . addEventListener ( 'click' , this . close . bind ( this ) ) ;
41+ if ( closeButton ) { // Bind the event listener for the Close button.
42+ closeButton . addEventListener ( 'click' , this . close . bind ( this ) ) ;
4443 }
45- this . _dataAvailableCapability = createPromiseCapability ( ) ;
46-
4744 OverlayManager . register ( this . overlayName , this . container ,
4845 this . close . bind ( this ) ) ;
4946 }
@@ -52,9 +49,51 @@ class PDFDocumentProperties {
5249 * Open the document properties overlay.
5350 */
5451 open ( ) {
52+ let freezeFieldData = ( data ) => {
53+ Object . defineProperty ( this , 'fieldData' , {
54+ value : Object . freeze ( data ) ,
55+ writable : false ,
56+ enumerable : true ,
57+ configurable : true ,
58+ } ) ;
59+ } ;
60+
5561 Promise . all ( [ OverlayManager . open ( this . overlayName ) ,
5662 this . _dataAvailableCapability . promise ] ) . then ( ( ) => {
57- this . _getProperties ( ) ;
63+ // If the document properties were previously fetched (for this PDF file),
64+ // just update the dialog immediately to avoid redundant lookups.
65+ if ( this . fieldData ) {
66+ this . _updateUI ( ) ;
67+ return ;
68+ }
69+ // Get the document properties.
70+ this . pdfDocument . getMetadata ( ) . then ( ( { info, metadata, } ) => {
71+ freezeFieldData ( {
72+ 'fileName' : getPDFFileNameFromURL ( this . url ) ,
73+ 'fileSize' : this . _parseFileSize ( this . maybeFileSize ) ,
74+ 'title' : info . Title ,
75+ 'author' : info . Author ,
76+ 'subject' : info . Subject ,
77+ 'keywords' : info . Keywords ,
78+ 'creationDate' : this . _parseDate ( info . CreationDate ) ,
79+ 'modificationDate' : this . _parseDate ( info . ModDate ) ,
80+ 'creator' : info . Creator ,
81+ 'producer' : info . Producer ,
82+ 'version' : info . PDFFormatVersion ,
83+ 'pageCount' : this . pdfDocument . numPages ,
84+ } ) ;
85+ this . _updateUI ( ) ;
86+
87+ // Get the correct fileSize, since it may not have been set (if
88+ // `this.setFileSize` wasn't called) or may be incorrectly set.
89+ return this . pdfDocument . getDownloadInfo ( ) ;
90+ } ) . then ( ( { length, } ) => {
91+ let data = cloneObj ( this . fieldData ) ;
92+ data [ 'fileSize' ] = this . _parseFileSize ( length ) ;
93+
94+ freezeFieldData ( data ) ;
95+ this . _updateUI ( ) ;
96+ } ) ;
5897 } ) ;
5998 }
6099
@@ -65,19 +104,6 @@ class PDFDocumentProperties {
65104 OverlayManager . close ( this . overlayName ) ;
66105 }
67106
68- /**
69- * Set the file size of the PDF document. This method is used to
70- * update the file size in the document properties overlay once it
71- * is known so we do not have to wait until the entire file is loaded.
72- *
73- * @param {number } fileSize - The file size of the PDF document.
74- */
75- setFileSize ( fileSize ) {
76- if ( fileSize > 0 ) {
77- this . rawFileSize = fileSize ;
78- }
79- }
80-
81107 /**
82108 * Set a reference to the PDF document and the URL in order
83109 * to populate the overlay fields with the document properties.
@@ -87,68 +113,75 @@ class PDFDocumentProperties {
87113 * @param {Object } pdfDocument - A reference to the PDF document.
88114 * @param {string } url - The URL of the document.
89115 */
90- setDocumentAndUrl ( pdfDocument , url ) {
116+ setDocument ( pdfDocument , url ) {
117+ if ( this . pdfDocument ) {
118+ this . _reset ( ) ;
119+ this . _updateUI ( true ) ;
120+ }
121+ if ( ! pdfDocument ) {
122+ return ;
123+ }
91124 this . pdfDocument = pdfDocument ;
92125 this . url = url ;
126+
93127 this . _dataAvailableCapability . resolve ( ) ;
94128 }
95129
96130 /**
97- * @private
131+ * Set the file size of the PDF document. This method is used to
132+ * update the file size in the document properties overlay once it
133+ * is known so we do not have to wait until the entire file is loaded.
134+ *
135+ * @param {number } fileSize - The file size of the PDF document.
98136 */
99- _getProperties ( ) {
100- if ( ! OverlayManager . active ) {
101- // If the dialog was closed before `_dataAvailableCapability` was
102- // resolved, don't bother updating the properties.
103- return ;
137+ setFileSize ( fileSize ) {
138+ if ( typeof fileSize === 'number' && fileSize > 0 ) {
139+ this . maybeFileSize = fileSize ;
104140 }
105- // Get the file size (if it hasn't already been set).
106- this . pdfDocument . getDownloadInfo ( ) . then ( ( data ) => {
107- if ( data . length === this . rawFileSize ) {
108- return ;
109- }
110- this . setFileSize ( data . length ) ;
111- this . _updateUI ( this . fields [ 'fileSize' ] , this . _parseFileSize ( ) ) ;
112- } ) ;
141+ }
113142
114- // Get the document properties.
115- this . pdfDocument . getMetadata ( ) . then ( ( data ) => {
116- var content = {
117- 'fileName' : getPDFFileNameFromURL ( this . url ) ,
118- 'fileSize' : this . _parseFileSize ( ) ,
119- 'title' : data . info . Title ,
120- 'author' : data . info . Author ,
121- 'subject' : data . info . Subject ,
122- 'keywords' : data . info . Keywords ,
123- 'creationDate' : this . _parseDate ( data . info . CreationDate ) ,
124- 'modificationDate' : this . _parseDate ( data . info . ModDate ) ,
125- 'creator' : data . info . Creator ,
126- 'producer' : data . info . Producer ,
127- 'version' : data . info . PDFFormatVersion ,
128- 'pageCount' : this . pdfDocument . numPages
129- } ;
130-
131- // Show the properties in the dialog.
132- for ( var identifier in content ) {
133- this . _updateUI ( this . fields [ identifier ] , content [ identifier ] ) ;
134- }
135- } ) ;
143+ /**
144+ * @private
145+ */
146+ _reset ( ) {
147+ this . pdfDocument = null ;
148+ this . url = null ;
149+
150+ this . maybeFileSize = 0 ;
151+ delete this . fieldData ;
152+ this . _dataAvailableCapability = createPromiseCapability ( ) ;
136153 }
137154
138155 /**
156+ * Always updates all of the dialog fields, to prevent inconsistent UI state.
157+ * NOTE: If the contents of a particular field is neither a non-empty string,
158+ * nor a number, it will fall back to `DEFAULT_FIELD_CONTENT`.
139159 * @private
140160 */
141- _updateUI ( field , content ) {
142- if ( field && content !== undefined && content !== '' ) {
143- field . textContent = content ;
161+ _updateUI ( reset = false ) {
162+ if ( reset || ! this . fieldData ) {
163+ for ( let id in this . fields ) {
164+ this . fields [ id ] . textContent = DEFAULT_FIELD_CONTENT ;
165+ }
166+ return ;
167+ }
168+ if ( OverlayManager . active !== this . overlayName ) {
169+ // Don't bother updating the dialog if has already been closed,
170+ // since it will be updated the next time `this.open` is called.
171+ return ;
172+ }
173+ for ( let id in this . fields ) {
174+ let content = this . fieldData [ id ] ;
175+ this . fields [ id ] . textContent = ( content || content === 0 ) ?
176+ content : DEFAULT_FIELD_CONTENT ;
144177 }
145178 }
146179
147180 /**
148181 * @private
149182 */
150- _parseFileSize ( ) {
151- var fileSize = this . rawFileSize , kb = fileSize / 1024 ;
183+ _parseFileSize ( fileSize = 0 ) {
184+ let kb = fileSize / 1024 ;
152185 if ( ! kb ) {
153186 return ;
154187 } else if ( kb < 1024 ) {
@@ -167,14 +200,14 @@ class PDFDocumentProperties {
167200 * @private
168201 */
169202 _parseDate ( inputDate ) {
203+ if ( ! inputDate ) {
204+ return ;
205+ }
170206 // This is implemented according to the PDF specification, but note that
171207 // Adobe Reader doesn't handle changing the date to universal time
172208 // and doesn't use the user's time zone (they're effectively ignoring
173209 // the HH' and mm' parts of the date string).
174- var dateToParse = inputDate ;
175- if ( dateToParse === undefined ) {
176- return '' ;
177- }
210+ let dateToParse = inputDate ;
178211
179212 // Remove the D: prefix if it is available.
180213 if ( dateToParse . substring ( 0 , 2 ) === 'D:' ) {
0 commit comments