UNPKG

15.8 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * Javascript code in this page
4 *
5 * Copyright 2017 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * Javascript code in this page
21 */
22'use strict';
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.isDestArraysEqual = exports.isDestHashesEqual = exports.PDFHistory = undefined;
28
29var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
30
31var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
32
33var _ui_utils = require('./ui_utils');
34
35var _dom_events = require('./dom_events');
36
37function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
38
39var HASH_CHANGE_TIMEOUT = 1000;
40var POSITION_UPDATED_THRESHOLD = 50;
41var UPDATE_VIEWAREA_TIMEOUT = 1000;
42function getCurrentHash() {
43 return document.location.hash;
44}
45function parseCurrentHash(linkService) {
46 var hash = unescape(getCurrentHash()).substring(1);
47 var params = (0, _ui_utils.parseQueryString)(hash);
48 var page = params.page | 0;
49 if (!(Number.isInteger(page) && page > 0 && page <= linkService.pagesCount)) {
50 page = null;
51 }
52 return {
53 hash: hash,
54 page: page,
55 rotation: linkService.rotation
56 };
57}
58
59var PDFHistory = function () {
60 function PDFHistory(_ref) {
61 var _this = this;
62
63 var linkService = _ref.linkService,
64 eventBus = _ref.eventBus;
65
66 _classCallCheck(this, PDFHistory);
67
68 this.linkService = linkService;
69 this.eventBus = eventBus || (0, _dom_events.getGlobalEventBus)();
70 this.initialized = false;
71 this.initialBookmark = null;
72 this.initialRotation = null;
73 this._boundEvents = Object.create(null);
74 this._isViewerInPresentationMode = false;
75 this._isPagesLoaded = false;
76 this.eventBus.on('presentationmodechanged', function (evt) {
77 _this._isViewerInPresentationMode = evt.active || evt.switchInProgress;
78 });
79 this.eventBus.on('pagesloaded', function (evt) {
80 _this._isPagesLoaded = !!evt.pagesCount;
81 });
82 }
83
84 _createClass(PDFHistory, [{
85 key: 'initialize',
86 value: function initialize(fingerprint) {
87 var resetHistory = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
88
89 if (!fingerprint || typeof fingerprint !== 'string') {
90 console.error('PDFHistory.initialize: The "fingerprint" must be a non-empty string.');
91 return;
92 }
93 var reInitialized = this.initialized && this.fingerprint !== fingerprint;
94 this.fingerprint = fingerprint;
95 if (!this.initialized) {
96 this._bindEvents();
97 }
98 var state = window.history.state;
99 this.initialized = true;
100 this.initialBookmark = null;
101 this.initialRotation = null;
102 this._popStateInProgress = false;
103 this._blockHashChange = 0;
104 this._currentHash = getCurrentHash();
105 this._numPositionUpdates = 0;
106 this._uid = this._maxUid = 0;
107 this._destination = null;
108 this._position = null;
109 if (!this._isValidState(state) || resetHistory) {
110 var _parseCurrentHash = parseCurrentHash(this.linkService),
111 hash = _parseCurrentHash.hash,
112 page = _parseCurrentHash.page,
113 rotation = _parseCurrentHash.rotation;
114
115 if (!hash || reInitialized || resetHistory) {
116 this._pushOrReplaceState(null, true);
117 return;
118 }
119 this._pushOrReplaceState({
120 hash: hash,
121 page: page,
122 rotation: rotation
123 }, true);
124 return;
125 }
126 var destination = state.destination;
127 this._updateInternalState(destination, state.uid, true);
128 if (this._uid > this._maxUid) {
129 this._maxUid = this._uid;
130 }
131 if (destination.rotation !== undefined) {
132 this.initialRotation = destination.rotation;
133 }
134 if (destination.dest) {
135 this.initialBookmark = JSON.stringify(destination.dest);
136 this._destination.page = null;
137 } else if (destination.hash) {
138 this.initialBookmark = destination.hash;
139 } else if (destination.page) {
140 this.initialBookmark = 'page=' + destination.page;
141 }
142 }
143 }, {
144 key: 'push',
145 value: function push(_ref2) {
146 var _this2 = this;
147
148 var namedDest = _ref2.namedDest,
149 explicitDest = _ref2.explicitDest,
150 pageNumber = _ref2.pageNumber;
151
152 if (!this.initialized) {
153 return;
154 }
155 if (namedDest && typeof namedDest !== 'string' || !(explicitDest instanceof Array) || !(Number.isInteger(pageNumber) && pageNumber > 0 && pageNumber <= this.linkService.pagesCount)) {
156 console.error('PDFHistory.push: Invalid parameters.');
157 return;
158 }
159 var hash = namedDest || JSON.stringify(explicitDest);
160 if (!hash) {
161 return;
162 }
163 var forceReplace = false;
164 if (this._destination && (isDestHashesEqual(this._destination.hash, hash) || isDestArraysEqual(this._destination.dest, explicitDest))) {
165 if (this._destination.page) {
166 return;
167 }
168 forceReplace = true;
169 }
170 if (this._popStateInProgress && !forceReplace) {
171 return;
172 }
173 this._pushOrReplaceState({
174 dest: explicitDest,
175 hash: hash,
176 page: pageNumber,
177 rotation: this.linkService.rotation
178 }, forceReplace);
179 if (!this._popStateInProgress) {
180 this._popStateInProgress = true;
181 Promise.resolve().then(function () {
182 _this2._popStateInProgress = false;
183 });
184 }
185 }
186 }, {
187 key: 'pushCurrentPosition',
188 value: function pushCurrentPosition() {
189 if (!this.initialized || this._popStateInProgress) {
190 return;
191 }
192 this._tryPushCurrentPosition();
193 }
194 }, {
195 key: 'back',
196 value: function back() {
197 if (!this.initialized || this._popStateInProgress) {
198 return;
199 }
200 var state = window.history.state;
201 if (this._isValidState(state) && state.uid > 0) {
202 window.history.back();
203 }
204 }
205 }, {
206 key: 'forward',
207 value: function forward() {
208 if (!this.initialized || this._popStateInProgress) {
209 return;
210 }
211 var state = window.history.state;
212 if (this._isValidState(state) && state.uid < this._maxUid) {
213 window.history.forward();
214 }
215 }
216 }, {
217 key: '_pushOrReplaceState',
218 value: function _pushOrReplaceState(destination) {
219 var forceReplace = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
220
221 var shouldReplace = forceReplace || !this._destination;
222 var newState = {
223 fingerprint: this.fingerprint,
224 uid: shouldReplace ? this._uid : this._uid + 1,
225 destination: destination
226 };
227 this._updateInternalState(destination, newState.uid);
228 if (shouldReplace) {
229 window.history.replaceState(newState, '');
230 } else {
231 this._maxUid = this._uid;
232 window.history.pushState(newState, '');
233 }
234 }
235 }, {
236 key: '_tryPushCurrentPosition',
237 value: function _tryPushCurrentPosition() {
238 var temporary = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
239
240 if (!this._position) {
241 return;
242 }
243 var position = this._position;
244 if (temporary) {
245 position = (0, _ui_utils.cloneObj)(this._position);
246 position.temporary = true;
247 }
248 if (!this._destination) {
249 this._pushOrReplaceState(position);
250 return;
251 }
252 if (this._destination.temporary) {
253 this._pushOrReplaceState(position, true);
254 return;
255 }
256 if (this._destination.hash === position.hash) {
257 return;
258 }
259 if (!this._destination.page && (POSITION_UPDATED_THRESHOLD <= 0 || this._numPositionUpdates <= POSITION_UPDATED_THRESHOLD)) {
260 return;
261 }
262 var forceReplace = false;
263 if (this._destination.page === position.first || this._destination.page === position.page) {
264 if (this._destination.dest || !this._destination.first) {
265 return;
266 }
267 forceReplace = true;
268 }
269 this._pushOrReplaceState(position, forceReplace);
270 }
271 }, {
272 key: '_isValidState',
273 value: function _isValidState(state) {
274 if (!state) {
275 return false;
276 }
277 if (state.fingerprint !== this.fingerprint) {
278 return false;
279 }
280 if (!Number.isInteger(state.uid) || state.uid < 0) {
281 return false;
282 }
283 if (state.destination === null || _typeof(state.destination) !== 'object') {
284 return false;
285 }
286 return true;
287 }
288 }, {
289 key: '_updateInternalState',
290 value: function _updateInternalState(destination, uid) {
291 var removeTemporary = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
292
293 if (this._updateViewareaTimeout) {
294 clearTimeout(this._updateViewareaTimeout);
295 this._updateViewareaTimeout = null;
296 }
297 if (removeTemporary && destination && destination.temporary) {
298 delete destination.temporary;
299 }
300 this._destination = destination;
301 this._uid = uid;
302 this._numPositionUpdates = 0;
303 }
304 }, {
305 key: '_updateViewarea',
306 value: function _updateViewarea(_ref3) {
307 var _this3 = this;
308
309 var location = _ref3.location;
310
311 if (this._updateViewareaTimeout) {
312 clearTimeout(this._updateViewareaTimeout);
313 this._updateViewareaTimeout = null;
314 }
315 this._position = {
316 hash: this._isViewerInPresentationMode ? 'page=' + location.pageNumber : location.pdfOpenParams.substring(1),
317 page: this.linkService.page,
318 first: location.pageNumber,
319 rotation: location.rotation
320 };
321 if (this._popStateInProgress) {
322 return;
323 }
324 if (POSITION_UPDATED_THRESHOLD > 0 && this._isPagesLoaded && this._destination && !this._destination.page) {
325 this._numPositionUpdates++;
326 }
327 if (UPDATE_VIEWAREA_TIMEOUT > 0) {
328 this._updateViewareaTimeout = setTimeout(function () {
329 if (!_this3._popStateInProgress) {
330 _this3._tryPushCurrentPosition(true);
331 }
332 _this3._updateViewareaTimeout = null;
333 }, UPDATE_VIEWAREA_TIMEOUT);
334 }
335 }
336 }, {
337 key: '_popState',
338 value: function _popState(_ref4) {
339 var _this4 = this;
340
341 var state = _ref4.state;
342
343 var newHash = getCurrentHash(),
344 hashChanged = this._currentHash !== newHash;
345 this._currentHash = newHash;
346 if (!state || false) {
347 this._uid++;
348
349 var _parseCurrentHash2 = parseCurrentHash(this.linkService),
350 hash = _parseCurrentHash2.hash,
351 page = _parseCurrentHash2.page,
352 rotation = _parseCurrentHash2.rotation;
353
354 this._pushOrReplaceState({
355 hash: hash,
356 page: page,
357 rotation: rotation
358 }, true);
359 return;
360 }
361 if (!this._isValidState(state)) {
362 return;
363 }
364 this._popStateInProgress = true;
365 if (hashChanged) {
366 this._blockHashChange++;
367 (0, _ui_utils.waitOnEventOrTimeout)({
368 target: window,
369 name: 'hashchange',
370 delay: HASH_CHANGE_TIMEOUT
371 }).then(function () {
372 _this4._blockHashChange--;
373 });
374 }
375 var destination = state.destination;
376 this._updateInternalState(destination, state.uid, true);
377 if (this._uid > this._maxUid) {
378 this._maxUid = this._uid;
379 }
380 if ((0, _ui_utils.isValidRotation)(destination.rotation)) {
381 this.linkService.rotation = destination.rotation;
382 }
383 if (destination.dest) {
384 this.linkService.navigateTo(destination.dest);
385 } else if (destination.hash) {
386 this.linkService.setHash(destination.hash);
387 } else if (destination.page) {
388 this.linkService.page = destination.page;
389 }
390 Promise.resolve().then(function () {
391 _this4._popStateInProgress = false;
392 });
393 }
394 }, {
395 key: '_bindEvents',
396 value: function _bindEvents() {
397 var _this5 = this;
398
399 var _boundEvents = this._boundEvents,
400 eventBus = this.eventBus;
401
402 _boundEvents.updateViewarea = this._updateViewarea.bind(this);
403 _boundEvents.popState = this._popState.bind(this);
404 _boundEvents.pageHide = function (evt) {
405 if (!_this5._destination || _this5._destination.temporary) {
406 _this5._tryPushCurrentPosition();
407 }
408 };
409 eventBus.on('updateviewarea', _boundEvents.updateViewarea);
410 window.addEventListener('popstate', _boundEvents.popState);
411 window.addEventListener('pagehide', _boundEvents.pageHide);
412 }
413 }, {
414 key: 'popStateInProgress',
415 get: function get() {
416 return this.initialized && (this._popStateInProgress || this._blockHashChange > 0);
417 }
418 }]);
419
420 return PDFHistory;
421}();
422
423function isDestHashesEqual(destHash, pushHash) {
424 if (typeof destHash !== 'string' || typeof pushHash !== 'string') {
425 return false;
426 }
427 if (destHash === pushHash) {
428 return true;
429 }
430
431 var _parseQueryString = (0, _ui_utils.parseQueryString)(destHash),
432 nameddest = _parseQueryString.nameddest;
433
434 if (nameddest === pushHash) {
435 return true;
436 }
437 return false;
438}
439function isDestArraysEqual(firstDest, secondDest) {
440 function isEntryEqual(first, second) {
441 if ((typeof first === 'undefined' ? 'undefined' : _typeof(first)) !== (typeof second === 'undefined' ? 'undefined' : _typeof(second))) {
442 return false;
443 }
444 if (first instanceof Array || second instanceof Array) {
445 return false;
446 }
447 if (first !== null && (typeof first === 'undefined' ? 'undefined' : _typeof(first)) === 'object' && second !== null) {
448 if (Object.keys(first).length !== Object.keys(second).length) {
449 return false;
450 }
451 for (var key in first) {
452 if (!isEntryEqual(first[key], second[key])) {
453 return false;
454 }
455 }
456 return true;
457 }
458 return first === second || Number.isNaN(first) && Number.isNaN(second);
459 }
460 if (!(firstDest instanceof Array && secondDest instanceof Array)) {
461 return false;
462 }
463 if (firstDest.length !== secondDest.length) {
464 return false;
465 }
466 for (var i = 0, ii = firstDest.length; i < ii; i++) {
467 if (!isEntryEqual(firstDest[i], secondDest[i])) {
468 return false;
469 }
470 }
471 return true;
472}
473exports.PDFHistory = PDFHistory;
474exports.isDestHashesEqual = isDestHashesEqual;
475exports.isDestArraysEqual = isDestArraysEqual;
\No newline at end of file