Merge pull request #8381 from Snuffleupagus/document-properties-reset

Re-factor `PDFDocumentProperties` such that it's properly reset when a new PDF file is opened (issue 8371)
This commit is contained in:
Tim van der Meij 2017-05-07 17:43:23 +02:00 committed by GitHub
commit b3e4361d7b
4 changed files with 125 additions and 89 deletions

View File

@ -561,6 +561,7 @@ var PDFViewerApplication = {
this.pdfThumbnailViewer.setDocument(null); this.pdfThumbnailViewer.setDocument(null);
this.pdfViewer.setDocument(null); this.pdfViewer.setDocument(null);
this.pdfLinkService.setDocument(null, null); this.pdfLinkService.setDocument(null, null);
this.pdfDocumentProperties.setDocument(null, null);
} }
this.store = null; this.store = null;
this.isInitialViewSet = false; this.isInitialViewSet = false;
@ -847,8 +848,6 @@ var PDFViewerApplication = {
this.pdfDocument = pdfDocument; this.pdfDocument = pdfDocument;
this.pdfDocumentProperties.setDocumentAndUrl(pdfDocument, this.url);
var downloadedPromise = pdfDocument.getDownloadInfo().then(function() { var downloadedPromise = pdfDocument.getDownloadInfo().then(function() {
self.downloadComplete = true; self.downloadComplete = true;
self.loadingBar.hide(); self.loadingBar.hide();
@ -869,6 +868,7 @@ var PDFViewerApplication = {
baseDocumentUrl = location.href.split('#')[0]; baseDocumentUrl = location.href.split('#')[0];
} }
this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl); this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
this.pdfDocumentProperties.setDocument(pdfDocument, this.url);
var pdfViewer = this.pdfViewer; var pdfViewer = this.pdfViewer;
pdfViewer.currentScale = scale; pdfViewer.currentScale = scale;

View File

@ -13,10 +13,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { getPDFFileNameFromURL, mozL10n } from './ui_utils'; import { cloneObj, getPDFFileNameFromURL, mozL10n } from './ui_utils';
import { createPromiseCapability } from './pdfjs'; import { createPromiseCapability } from './pdfjs';
import { OverlayManager } from './overlay_manager'; import { OverlayManager } from './overlay_manager';
const DEFAULT_FIELD_CONTENT = '-';
/** /**
* @typedef {Object} PDFDocumentPropertiesOptions * @typedef {Object} PDFDocumentPropertiesOptions
* @property {string} overlayName - Name/identifier for the overlay. * @property {string} overlayName - Name/identifier for the overlay.
@ -29,21 +31,16 @@ class PDFDocumentProperties {
/** /**
* @param {PDFDocumentPropertiesOptions} options * @param {PDFDocumentPropertiesOptions} options
*/ */
constructor(options) { constructor({ overlayName, fields, container, closeButton, }) {
this.overlayName = options.overlayName; this.overlayName = overlayName;
this.fields = options.fields; this.fields = fields;
this.container = options.container; this.container = container;
this.rawFileSize = 0; this._reset();
this.url = null;
this.pdfDocument = null;
// Bind the event listener for the Close button. if (closeButton) { // Bind the event listener for the Close button.
if (options.closeButton) { closeButton.addEventListener('click', this.close.bind(this));
options.closeButton.addEventListener('click', this.close.bind(this));
} }
this._dataAvailableCapability = createPromiseCapability();
OverlayManager.register(this.overlayName, this.container, OverlayManager.register(this.overlayName, this.container,
this.close.bind(this)); this.close.bind(this));
} }
@ -52,9 +49,51 @@ class PDFDocumentProperties {
* Open the document properties overlay. * Open the document properties overlay.
*/ */
open() { open() {
let freezeFieldData = (data) => {
Object.defineProperty(this, 'fieldData', {
value: Object.freeze(data),
writable: false,
enumerable: true,
configurable: true,
});
};
Promise.all([OverlayManager.open(this.overlayName), Promise.all([OverlayManager.open(this.overlayName),
this._dataAvailableCapability.promise]).then(() => { this._dataAvailableCapability.promise]).then(() => {
this._getProperties(); // If the document properties were previously fetched (for this PDF file),
// just update the dialog immediately to avoid redundant lookups.
if (this.fieldData) {
this._updateUI();
return;
}
// Get the document properties.
this.pdfDocument.getMetadata().then(({ info, metadata, }) => {
freezeFieldData({
'fileName': getPDFFileNameFromURL(this.url),
'fileSize': this._parseFileSize(this.maybeFileSize),
'title': info.Title,
'author': info.Author,
'subject': info.Subject,
'keywords': info.Keywords,
'creationDate': this._parseDate(info.CreationDate),
'modificationDate': this._parseDate(info.ModDate),
'creator': info.Creator,
'producer': info.Producer,
'version': info.PDFFormatVersion,
'pageCount': this.pdfDocument.numPages,
});
this._updateUI();
// Get the correct fileSize, since it may not have been set (if
// `this.setFileSize` wasn't called) or may be incorrectly set.
return this.pdfDocument.getDownloadInfo();
}).then(({ length, }) => {
let data = cloneObj(this.fieldData);
data['fileSize'] = this._parseFileSize(length);
freezeFieldData(data);
this._updateUI();
});
}); });
} }
@ -65,19 +104,6 @@ class PDFDocumentProperties {
OverlayManager.close(this.overlayName); OverlayManager.close(this.overlayName);
} }
/**
* Set the file size of the PDF document. This method is used to
* update the file size in the document properties overlay once it
* is known so we do not have to wait until the entire file is loaded.
*
* @param {number} fileSize - The file size of the PDF document.
*/
setFileSize(fileSize) {
if (fileSize > 0) {
this.rawFileSize = fileSize;
}
}
/** /**
* Set a reference to the PDF document and the URL in order * Set a reference to the PDF document and the URL in order
* to populate the overlay fields with the document properties. * to populate the overlay fields with the document properties.
@ -87,68 +113,75 @@ class PDFDocumentProperties {
* @param {Object} pdfDocument - A reference to the PDF document. * @param {Object} pdfDocument - A reference to the PDF document.
* @param {string} url - The URL of the document. * @param {string} url - The URL of the document.
*/ */
setDocumentAndUrl(pdfDocument, url) { setDocument(pdfDocument, url) {
if (this.pdfDocument) {
this._reset();
this._updateUI(true);
}
if (!pdfDocument) {
return;
}
this.pdfDocument = pdfDocument; this.pdfDocument = pdfDocument;
this.url = url; this.url = url;
this._dataAvailableCapability.resolve(); this._dataAvailableCapability.resolve();
} }
/**
* Set the file size of the PDF document. This method is used to
* update the file size in the document properties overlay once it
* is known so we do not have to wait until the entire file is loaded.
*
* @param {number} fileSize - The file size of the PDF document.
*/
setFileSize(fileSize) {
if (typeof fileSize === 'number' && fileSize > 0) {
this.maybeFileSize = fileSize;
}
}
/** /**
* @private * @private
*/ */
_getProperties() { _reset() {
if (!OverlayManager.active) { this.pdfDocument = null;
// If the dialog was closed before `_dataAvailableCapability` was this.url = null;
// resolved, don't bother updating the properties.
this.maybeFileSize = 0;
delete this.fieldData;
this._dataAvailableCapability = createPromiseCapability();
}
/**
* Always updates all of the dialog fields, to prevent inconsistent UI state.
* NOTE: If the contents of a particular field is neither a non-empty string,
* nor a number, it will fall back to `DEFAULT_FIELD_CONTENT`.
* @private
*/
_updateUI(reset = false) {
if (reset || !this.fieldData) {
for (let id in this.fields) {
this.fields[id].textContent = DEFAULT_FIELD_CONTENT;
}
return; return;
} }
// Get the file size (if it hasn't already been set). if (OverlayManager.active !== this.overlayName) {
this.pdfDocument.getDownloadInfo().then((data) => { // Don't bother updating the dialog if has already been closed,
if (data.length === this.rawFileSize) { // since it will be updated the next time `this.open` is called.
return; return;
} }
this.setFileSize(data.length); for (let id in this.fields) {
this._updateUI(this.fields['fileSize'], this._parseFileSize()); let content = this.fieldData[id];
}); this.fields[id].textContent = (content || content === 0) ?
content : DEFAULT_FIELD_CONTENT;
// Get the document properties.
this.pdfDocument.getMetadata().then((data) => {
var content = {
'fileName': getPDFFileNameFromURL(this.url),
'fileSize': this._parseFileSize(),
'title': data.info.Title,
'author': data.info.Author,
'subject': data.info.Subject,
'keywords': data.info.Keywords,
'creationDate': this._parseDate(data.info.CreationDate),
'modificationDate': this._parseDate(data.info.ModDate),
'creator': data.info.Creator,
'producer': data.info.Producer,
'version': data.info.PDFFormatVersion,
'pageCount': this.pdfDocument.numPages
};
// Show the properties in the dialog.
for (var identifier in content) {
this._updateUI(this.fields[identifier], content[identifier]);
}
});
}
/**
* @private
*/
_updateUI(field, content) {
if (field && content !== undefined && content !== '') {
field.textContent = content;
} }
} }
/** /**
* @private * @private
*/ */
_parseFileSize() { _parseFileSize(fileSize = 0) {
var fileSize = this.rawFileSize, kb = fileSize / 1024; let kb = fileSize / 1024;
if (!kb) { if (!kb) {
return; return;
} else if (kb < 1024) { } else if (kb < 1024) {
@ -167,14 +200,14 @@ class PDFDocumentProperties {
* @private * @private
*/ */
_parseDate(inputDate) { _parseDate(inputDate) {
if (!inputDate) {
return;
}
// This is implemented according to the PDF specification, but note that // This is implemented according to the PDF specification, but note that
// Adobe Reader doesn't handle changing the date to universal time // Adobe Reader doesn't handle changing the date to universal time
// and doesn't use the user's time zone (they're effectively ignoring // and doesn't use the user's time zone (they're effectively ignoring
// the HH' and mm' parts of the date string). // the HH' and mm' parts of the date string).
var dateToParse = inputDate; let dateToParse = inputDate;
if (dateToParse === undefined) {
return '';
}
// Remove the D: prefix if it is available. // Remove the D: prefix if it is available.
if (dateToParse.substring(0, 2) === 'D:') { if (dateToParse.substring(0, 2) === 'D:') {

View File

@ -13,6 +13,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { cloneObj } from './ui_utils';
var defaultPreferences = null; var defaultPreferences = null;
function getDefaultPreferences() { function getDefaultPreferences() {
if (!defaultPreferences) { if (!defaultPreferences) {
@ -38,16 +40,6 @@ function getDefaultPreferences() {
return defaultPreferences; return defaultPreferences;
} }
function cloneObj(obj) {
var result = {};
for (var i in obj) {
if (Object.prototype.hasOwnProperty.call(obj, i)) {
result[i] = obj[i];
}
}
return result;
}
/** /**
* BasePreferences - Abstract base class for storing persistent settings. * BasePreferences - Abstract base class for storing persistent settings.
* Used for settings that should be applied to all opened documents, * Used for settings that should be applied to all opened documents,

View File

@ -422,6 +422,16 @@ function normalizeWheelEventDelta(evt) {
return delta; return delta;
} }
function cloneObj(obj) {
var result = {};
for (var i in obj) {
if (Object.prototype.hasOwnProperty.call(obj, i)) {
result[i] = obj[i];
}
}
return result;
}
/** /**
* Promise that is resolved when DOM window becomes visible. * Promise that is resolved when DOM window becomes visible.
*/ */
@ -582,6 +592,7 @@ export {
MAX_AUTO_SCALE, MAX_AUTO_SCALE,
SCROLLBAR_PADDING, SCROLLBAR_PADDING,
VERTICAL_PADDING, VERTICAL_PADDING,
cloneObj,
RendererType, RendererType,
mozL10n, mozL10n,
EventBus, EventBus,