You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
436 lines
15 KiB
436 lines
15 KiB
/* Copyright 2012 Mozilla Foundation |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
'use strict'; |
|
|
|
(function (root, factory) { |
|
if (typeof define === 'function' && define.amd) { |
|
define('pdfjs-web/pdf_history', ['exports'], factory); |
|
} else if (typeof exports !== 'undefined') { |
|
factory(exports); |
|
} else { |
|
factory((root.pdfjsWebPDFHistory = {})); |
|
} |
|
}(this, function (exports) { |
|
|
|
function PDFHistory(options) { |
|
this.linkService = options.linkService; |
|
|
|
this.initialized = false; |
|
this.initialDestination = null; |
|
this.initialBookmark = null; |
|
} |
|
|
|
PDFHistory.prototype = { |
|
/** |
|
* @param {string} fingerprint |
|
* @param {IPDFLinkService} linkService |
|
*/ |
|
initialize: function pdfHistoryInitialize(fingerprint) { |
|
this.initialized = true; |
|
this.reInitialized = false; |
|
this.allowHashChange = true; |
|
this.historyUnlocked = true; |
|
this.isViewerInPresentationMode = false; |
|
|
|
this.previousHash = window.location.hash.substring(1); |
|
this.currentBookmark = ''; |
|
this.currentPage = 0; |
|
this.updatePreviousBookmark = false; |
|
this.previousBookmark = ''; |
|
this.previousPage = 0; |
|
this.nextHashParam = ''; |
|
|
|
this.fingerprint = fingerprint; |
|
this.currentUid = this.uid = 0; |
|
this.current = {}; |
|
|
|
var state = window.history.state; |
|
if (this._isStateObjectDefined(state)) { |
|
// This corresponds to navigating back to the document |
|
// from another page in the browser history. |
|
if (state.target.dest) { |
|
this.initialDestination = state.target.dest; |
|
} else { |
|
this.initialBookmark = state.target.hash; |
|
} |
|
this.currentUid = state.uid; |
|
this.uid = state.uid + 1; |
|
this.current = state.target; |
|
} else { |
|
// This corresponds to the loading of a new document. |
|
if (state && state.fingerprint && |
|
this.fingerprint !== state.fingerprint) { |
|
// Reinitialize the browsing history when a new document |
|
// is opened in the web viewer. |
|
this.reInitialized = true; |
|
} |
|
this._pushOrReplaceState({fingerprint: this.fingerprint}, true); |
|
} |
|
|
|
var self = this; |
|
window.addEventListener('popstate', function pdfHistoryPopstate(evt) { |
|
if (!self.historyUnlocked) { |
|
return; |
|
} |
|
if (evt.state) { |
|
// Move back/forward in the history. |
|
self._goTo(evt.state); |
|
return; |
|
} |
|
|
|
// If the state is not set, then the user tried to navigate to a |
|
// different hash by manually editing the URL and pressing Enter, or by |
|
// clicking on an in-page link (e.g. the "current view" link). |
|
// Save the current view state to the browser history. |
|
|
|
// Note: In Firefox, history.null could also be null after an in-page |
|
// navigation to the same URL, and without dispatching the popstate |
|
// event: https://bugzilla.mozilla.org/show_bug.cgi?id=1183881 |
|
|
|
if (self.uid === 0) { |
|
// Replace the previous state if it was not explicitly set. |
|
var previousParams = (self.previousHash && self.currentBookmark && |
|
self.previousHash !== self.currentBookmark) ? |
|
{hash: self.currentBookmark, page: self.currentPage} : |
|
{page: 1}; |
|
replacePreviousHistoryState(previousParams, function() { |
|
updateHistoryWithCurrentHash(); |
|
}); |
|
} else { |
|
updateHistoryWithCurrentHash(); |
|
} |
|
}, false); |
|
|
|
|
|
function updateHistoryWithCurrentHash() { |
|
self.previousHash = window.location.hash.slice(1); |
|
self._pushToHistory({hash: self.previousHash}, false, true); |
|
self._updatePreviousBookmark(); |
|
} |
|
|
|
function replacePreviousHistoryState(params, callback) { |
|
// To modify the previous history entry, the following happens: |
|
// 1. history.back() |
|
// 2. _pushToHistory, which calls history.replaceState( ... ) |
|
// 3. history.forward() |
|
// Because a navigation via the history API does not immediately update |
|
// the history state, the popstate event is used for synchronization. |
|
self.historyUnlocked = false; |
|
|
|
// Suppress the hashchange event to avoid side effects caused by |
|
// navigating back and forward. |
|
self.allowHashChange = false; |
|
window.addEventListener('popstate', rewriteHistoryAfterBack); |
|
history.back(); |
|
|
|
function rewriteHistoryAfterBack() { |
|
window.removeEventListener('popstate', rewriteHistoryAfterBack); |
|
window.addEventListener('popstate', rewriteHistoryAfterForward); |
|
self._pushToHistory(params, false, true); |
|
history.forward(); |
|
} |
|
function rewriteHistoryAfterForward() { |
|
window.removeEventListener('popstate', rewriteHistoryAfterForward); |
|
self.allowHashChange = true; |
|
self.historyUnlocked = true; |
|
callback(); |
|
} |
|
} |
|
|
|
function pdfHistoryBeforeUnload() { |
|
var previousParams = self._getPreviousParams(null, true); |
|
if (previousParams) { |
|
var replacePrevious = (!self.current.dest && |
|
self.current.hash !== self.previousHash); |
|
self._pushToHistory(previousParams, false, replacePrevious); |
|
self._updatePreviousBookmark(); |
|
} |
|
// Remove the event listener when navigating away from the document, |
|
// since 'beforeunload' prevents Firefox from caching the document. |
|
window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, |
|
false); |
|
} |
|
|
|
window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false); |
|
|
|
window.addEventListener('pageshow', function pdfHistoryPageShow(evt) { |
|
// If the entire viewer (including the PDF file) is cached in |
|
// the browser, we need to reattach the 'beforeunload' event listener |
|
// since the 'DOMContentLoaded' event is not fired on 'pageshow'. |
|
window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false); |
|
}, false); |
|
|
|
window.addEventListener('presentationmodechanged', function(e) { |
|
self.isViewerInPresentationMode = !!e.detail.active; |
|
}); |
|
}, |
|
|
|
clearHistoryState: function pdfHistory_clearHistoryState() { |
|
this._pushOrReplaceState(null, true); |
|
}, |
|
|
|
_isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) { |
|
return (state && state.uid >= 0 && |
|
state.fingerprint && this.fingerprint === state.fingerprint && |
|
state.target && state.target.hash) ? true : false; |
|
}, |
|
|
|
_pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj, |
|
replace) { |
|
//#if CHROME |
|
// history.state.chromecomState is managed by chromecom.js. |
|
if (window.history.state && 'chromecomState' in window.history.state) { |
|
stateObj = stateObj || {}; |
|
stateObj.chromecomState = window.history.state.chromecomState; |
|
} |
|
//#endif |
|
if (replace) { |
|
//#if (GENERIC || CHROME) |
|
window.history.replaceState(stateObj, '', document.URL); |
|
//#else |
|
// window.history.replaceState(stateObj, ''); |
|
//#endif |
|
} else { |
|
//#if (GENERIC || CHROME) |
|
window.history.pushState(stateObj, '', document.URL); |
|
//#else |
|
// window.history.pushState(stateObj, ''); |
|
//#endif |
|
//#if CHROME |
|
// if (top === window) { |
|
// chrome.runtime.sendMessage('showPageAction'); |
|
// } |
|
//#endif |
|
} |
|
}, |
|
|
|
get isHashChangeUnlocked() { |
|
if (!this.initialized) { |
|
return true; |
|
} |
|
return this.allowHashChange; |
|
}, |
|
|
|
_updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() { |
|
if (this.updatePreviousBookmark && |
|
this.currentBookmark && this.currentPage) { |
|
this.previousBookmark = this.currentBookmark; |
|
this.previousPage = this.currentPage; |
|
this.updatePreviousBookmark = false; |
|
} |
|
}, |
|
|
|
updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark, |
|
pageNum) { |
|
if (this.initialized) { |
|
this.currentBookmark = bookmark.substring(1); |
|
this.currentPage = pageNum | 0; |
|
this._updatePreviousBookmark(); |
|
} |
|
}, |
|
|
|
updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) { |
|
if (this.initialized) { |
|
this.nextHashParam = param; |
|
} |
|
}, |
|
|
|
push: function pdfHistoryPush(params, isInitialBookmark) { |
|
if (!(this.initialized && this.historyUnlocked)) { |
|
return; |
|
} |
|
if (params.dest && !params.hash) { |
|
params.hash = (this.current.hash && this.current.dest && |
|
this.current.dest === params.dest) ? |
|
this.current.hash : |
|
this.linkService.getDestinationHash(params.dest).split('#')[1]; |
|
} |
|
if (params.page) { |
|
params.page |= 0; |
|
} |
|
if (isInitialBookmark) { |
|
var target = window.history.state.target; |
|
if (!target) { |
|
// Invoked when the user specifies an initial bookmark, |
|
// thus setting initialBookmark, when the document is loaded. |
|
this._pushToHistory(params, false); |
|
this.previousHash = window.location.hash.substring(1); |
|
} |
|
this.updatePreviousBookmark = this.nextHashParam ? false : true; |
|
if (target) { |
|
// If the current document is reloaded, |
|
// avoid creating duplicate entries in the history. |
|
this._updatePreviousBookmark(); |
|
} |
|
return; |
|
} |
|
if (this.nextHashParam) { |
|
if (this.nextHashParam === params.hash) { |
|
this.nextHashParam = null; |
|
this.updatePreviousBookmark = true; |
|
return; |
|
} else { |
|
this.nextHashParam = null; |
|
} |
|
} |
|
|
|
if (params.hash) { |
|
if (this.current.hash) { |
|
if (this.current.hash !== params.hash) { |
|
this._pushToHistory(params, true); |
|
} else { |
|
if (!this.current.page && params.page) { |
|
this._pushToHistory(params, false, true); |
|
} |
|
this.updatePreviousBookmark = true; |
|
} |
|
} else { |
|
this._pushToHistory(params, true); |
|
} |
|
} else if (this.current.page && params.page && |
|
this.current.page !== params.page) { |
|
this._pushToHistory(params, true); |
|
} |
|
}, |
|
|
|
_getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage, |
|
beforeUnload) { |
|
if (!(this.currentBookmark && this.currentPage)) { |
|
return null; |
|
} else if (this.updatePreviousBookmark) { |
|
this.updatePreviousBookmark = false; |
|
} |
|
if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) { |
|
// Prevent the history from getting stuck in the current state, |
|
// effectively preventing the user from going back/forward in |
|
// the history. |
|
// |
|
// This happens if the current position in the document didn't change |
|
// when the history was previously updated. The reasons for this are |
|
// either: |
|
// 1. The current zoom value is such that the document does not need to, |
|
// or cannot, be scrolled to display the destination. |
|
// 2. The previous destination is broken, and doesn't actally point to a |
|
// position within the document. |
|
// (This is either due to a bad PDF generator, or the user making a |
|
// mistake when entering a destination in the hash parameters.) |
|
return null; |
|
} |
|
if ((!this.current.dest && !onlyCheckPage) || beforeUnload) { |
|
if (this.previousBookmark === this.currentBookmark) { |
|
return null; |
|
} |
|
} else if (this.current.page || onlyCheckPage) { |
|
if (this.previousPage === this.currentPage) { |
|
return null; |
|
} |
|
} else { |
|
return null; |
|
} |
|
var params = {hash: this.currentBookmark, page: this.currentPage}; |
|
if (this.isViewerInPresentationMode) { |
|
params.hash = null; |
|
} |
|
return params; |
|
}, |
|
|
|
_stateObj: function pdfHistory_stateObj(params) { |
|
return {fingerprint: this.fingerprint, uid: this.uid, target: params}; |
|
}, |
|
|
|
_pushToHistory: function pdfHistory_pushToHistory(params, |
|
addPrevious, overwrite) { |
|
if (!this.initialized) { |
|
return; |
|
} |
|
if (!params.hash && params.page) { |
|
params.hash = ('page=' + params.page); |
|
} |
|
if (addPrevious && !overwrite) { |
|
var previousParams = this._getPreviousParams(); |
|
if (previousParams) { |
|
var replacePrevious = (!this.current.dest && |
|
this.current.hash !== this.previousHash); |
|
this._pushToHistory(previousParams, false, replacePrevious); |
|
} |
|
} |
|
this._pushOrReplaceState(this._stateObj(params), |
|
(overwrite || this.uid === 0)); |
|
this.currentUid = this.uid++; |
|
this.current = params; |
|
this.updatePreviousBookmark = true; |
|
}, |
|
|
|
_goTo: function pdfHistory_goTo(state) { |
|
if (!(this.initialized && this.historyUnlocked && |
|
this._isStateObjectDefined(state))) { |
|
return; |
|
} |
|
if (!this.reInitialized && state.uid < this.currentUid) { |
|
var previousParams = this._getPreviousParams(true); |
|
if (previousParams) { |
|
this._pushToHistory(this.current, false); |
|
this._pushToHistory(previousParams, false); |
|
this.currentUid = state.uid; |
|
window.history.back(); |
|
return; |
|
} |
|
} |
|
this.historyUnlocked = false; |
|
|
|
if (state.target.dest) { |
|
this.linkService.navigateTo(state.target.dest); |
|
} else { |
|
this.linkService.setHash(state.target.hash); |
|
} |
|
this.currentUid = state.uid; |
|
if (state.uid > this.uid) { |
|
this.uid = state.uid; |
|
} |
|
this.current = state.target; |
|
this.updatePreviousBookmark = true; |
|
|
|
var currentHash = window.location.hash.substring(1); |
|
if (this.previousHash !== currentHash) { |
|
this.allowHashChange = false; |
|
} |
|
this.previousHash = currentHash; |
|
|
|
this.historyUnlocked = true; |
|
}, |
|
|
|
back: function pdfHistoryBack() { |
|
this.go(-1); |
|
}, |
|
|
|
forward: function pdfHistoryForward() { |
|
this.go(1); |
|
}, |
|
|
|
go: function pdfHistoryGo(direction) { |
|
if (this.initialized && this.historyUnlocked) { |
|
var state = window.history.state; |
|
if (direction === -1 && state && state.uid > 0) { |
|
window.history.back(); |
|
} else if (direction === 1 && state && state.uid < (this.uid - 1)) { |
|
window.history.forward(); |
|
} |
|
} |
|
} |
|
}; |
|
|
|
exports.PDFHistory = PDFHistory; |
|
}));
|
|
|