Add support for updating the document hash, off by default, when the browser history is updated (issue 5753)

This is *really* the best that we can do here, since other proposed solutions would interfere with (and break) the painstakingly implemented browsing history that's present in the default viewer.

I'm still not convinced that this is a good idea in general, but this patch implements it in a way where it is possible to toggle[1] for users that wish to have this feature. In particular, there's a couple of reasons why I'm not finding this feature necessary/great:
 - It's already possible to easily obtain the current hash, by simply clicking on the `viewBookmark` button at any time.
 - Hash changes requires a bit of special handling[2], i.e. extra code, to prevent issues when the browser history is traversed (see `PDFHistory._popState`). Currently this is only necessary when the user has manually changed the hash, with this patch it will always be the case (assuming the feature is active).
 - It's not always possible to change the URL when updating the browser history. For example: In the Firefox built-in viewer, the URL cannot be modified for local files (i.e. those using the `file://` protocol).
This leads to inconsistent behaviour, and may in some cases even result in errors being thrown and the history thus not updating, if the browser prevents changes to the URL during `pushState`/`replaceState` calls.

---
[1] Using the `historyUpdateUrl` viewer preference.

[2] This depends, to a great extent, on browsers always firing `popstate` events *before* `hashchange` events, which may or may not actually be guaranteed.
This commit is contained in:
Jonas Jenwald 2019-01-03 15:45:33 +01:00
parent af31b980b0
commit 4773bf6fcb
6 changed files with 37 additions and 10 deletions

View File

@ -147,6 +147,10 @@
"type": "boolean", "type": "boolean",
"default": false "default": false
}, },
"historyUpdateUrl": {
"type": "boolean",
"default": false
},
"enablePrintAutoRotate": { "enablePrintAutoRotate": {
"title": "Automatically rotate printed pages", "title": "Automatically rotate printed pages",
"description": "When enabled, pages whose orientation differ from the first page are rotated when printed.", "description": "When enabled, pages whose orientation differ from the first page are rotated when printed.",

View File

@ -903,8 +903,11 @@ let PDFViewerApplication = {
if (!AppOptions.get('disableHistory') && !this.isViewerEmbedded) { if (!AppOptions.get('disableHistory') && !this.isViewerEmbedded) {
// The browsing history is only enabled when the viewer is standalone, // The browsing history is only enabled when the viewer is standalone,
// i.e. not when it is embedded in a web page. // i.e. not when it is embedded in a web page.
let resetHistory = !AppOptions.get('showPreviousViewOnLoad'); this.pdfHistory.initialize({
this.pdfHistory.initialize(pdfDocument.fingerprint, resetHistory); fingerprint: pdfDocument.fingerprint,
resetHistory: !AppOptions.get('showPreviousViewOnLoad'),
updateUrl: AppOptions.get('historyUpdateUrl'),
});
if (this.pdfHistory.initialBookmark) { if (this.pdfHistory.initialBookmark) {
this.initialBookmark = this.pdfHistory.initialBookmark; this.initialBookmark = this.pdfHistory.initialBookmark;

View File

@ -91,6 +91,11 @@ const defaultOptions = {
value: 0, value: 0,
kind: OptionKind.VIEWER, kind: OptionKind.VIEWER,
}, },
historyUpdateUrl: {
/** @type {boolean} */
value: false,
kind: OptionKind.VIEWER,
},
imageResourcesPath: { imageResourcesPath: {
/** @type {string} */ /** @type {string} */
value: './images/', value: './images/',

View File

@ -19,6 +19,7 @@
"disableOpenActionDestination": true, "disableOpenActionDestination": true,
"disablePageMode": false, "disablePageMode": false,
"disablePageLabels": false, "disablePageLabels": false,
"historyUpdateUrl": false,
"scrollModeOnLoad": 0, "scrollModeOnLoad": 0,
"spreadModeOnLoad": 0 "spreadModeOnLoad": 0
} }

View File

@ -86,10 +86,9 @@ class IPDFLinkService {
*/ */
class IPDFHistory { class IPDFHistory {
/** /**
* @param {string} fingerprint - The PDF document's unique fingerprint. * @param {Object} params
* @param {boolean} resetHistory - (optional) Reset the browsing history.
*/ */
initialize(fingerprint, resetHistory = false) {} initialize({ fingerprint, resetHistory = false, updateUrl = false, }) {}
/** /**
* @param {Object} params * @param {Object} params

View File

@ -30,6 +30,14 @@ const UPDATE_VIEWAREA_TIMEOUT = 1000; // milliseconds
* @property {EventBus} eventBus - The application event bus. * @property {EventBus} eventBus - The application event bus.
*/ */
/**
* @typedef {Object} InitializeParameters
* @property {string} fingerprint - The PDF document's unique fingerprint.
* @property {boolean} resetHistory - (optional) Reset the browsing history.
* @property {boolean} updateUrl - (optional) Attempt to update the document
* URL, with the current hash, when pushing/replacing browser history entries.
*/
/** /**
* @typedef {Object} PushParameters * @typedef {Object} PushParameters
* @property {string} namedDest - (optional) The named destination. If absent, * @property {string} namedDest - (optional) The named destination. If absent,
@ -82,10 +90,9 @@ class PDFHistory {
/** /**
* Initialize the history for the PDF document, using either the current * Initialize the history for the PDF document, using either the current
* browser history entry or the document hash, whichever is present. * browser history entry or the document hash, whichever is present.
* @param {string} fingerprint - The PDF document's unique fingerprint. * @param {InitializeParameters} params
* @param {boolean} resetHistory - (optional) Reset the browsing history.
*/ */
initialize(fingerprint, resetHistory = false) { initialize({ fingerprint, resetHistory = false, updateUrl = false, }) {
if (!fingerprint || typeof fingerprint !== 'string') { if (!fingerprint || typeof fingerprint !== 'string') {
console.error( console.error(
'PDFHistory.initialize: The "fingerprint" must be a non-empty string.'); 'PDFHistory.initialize: The "fingerprint" must be a non-empty string.');
@ -93,6 +100,7 @@ class PDFHistory {
} }
let reInitialized = this.initialized && this.fingerprint !== fingerprint; let reInitialized = this.initialized && this.fingerprint !== fingerprint;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;
this._updateUrl = (updateUrl === true);
if (!this.initialized) { if (!this.initialized) {
this._bindEvents(); this._bindEvents();
@ -290,11 +298,18 @@ class PDFHistory {
} }
this._updateInternalState(destination, newState.uid); this._updateInternalState(destination, newState.uid);
let newUrl;
if (this._updateUrl && destination && destination.hash) {
const baseUrl = document.location.href.split('#')[0];
if (!baseUrl.startsWith('file://')) { // Prevent errors in Firefox.
newUrl = `${baseUrl}#${destination.hash}`;
}
}
if (shouldReplace) { if (shouldReplace) {
window.history.replaceState(newState, ''); window.history.replaceState(newState, '', newUrl);
} else { } else {
this._maxUid = this._uid; this._maxUid = this._uid;
window.history.pushState(newState, ''); window.history.pushState(newState, '', newUrl);
} }
if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME') && if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME') &&