1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | "use strict";
|
23 |
|
24 | Object.defineProperty(exports, "__esModule", {
|
25 | value: true
|
26 | });
|
27 | exports.isDestHashesEqual = isDestHashesEqual;
|
28 | exports.isDestArraysEqual = isDestArraysEqual;
|
29 | exports.PDFHistory = void 0;
|
30 |
|
31 | var _ui_utils = require("./ui_utils.js");
|
32 |
|
33 | const HASH_CHANGE_TIMEOUT = 1000;
|
34 | const POSITION_UPDATED_THRESHOLD = 50;
|
35 | const UPDATE_VIEWAREA_TIMEOUT = 1000;
|
36 |
|
37 | function getCurrentHash() {
|
38 | return document.location.hash;
|
39 | }
|
40 |
|
41 | class PDFHistory {
|
42 | constructor({
|
43 | linkService,
|
44 | eventBus
|
45 | }) {
|
46 | this.linkService = linkService;
|
47 | this.eventBus = eventBus || (0, _ui_utils.getGlobalEventBus)();
|
48 | this._initialized = false;
|
49 | this._fingerprint = "";
|
50 | this.reset();
|
51 | this._boundEvents = null;
|
52 | this._isViewerInPresentationMode = false;
|
53 |
|
54 | this.eventBus._on("presentationmodechanged", evt => {
|
55 | this._isViewerInPresentationMode = evt.active || evt.switchInProgress;
|
56 | });
|
57 |
|
58 | this.eventBus._on("pagesinit", () => {
|
59 | this._isPagesLoaded = false;
|
60 |
|
61 | const onPagesLoaded = evt => {
|
62 | this.eventBus._off("pagesloaded", onPagesLoaded);
|
63 |
|
64 | this._isPagesLoaded = !!evt.pagesCount;
|
65 | };
|
66 |
|
67 | this.eventBus._on("pagesloaded", onPagesLoaded);
|
68 | });
|
69 | }
|
70 |
|
71 | initialize({
|
72 | fingerprint,
|
73 | resetHistory = false,
|
74 | updateUrl = false
|
75 | }) {
|
76 | if (!fingerprint || typeof fingerprint !== "string") {
|
77 | console.error('PDFHistory.initialize: The "fingerprint" must be a non-empty string.');
|
78 | return;
|
79 | }
|
80 |
|
81 | if (this._initialized) {
|
82 | this.reset();
|
83 | }
|
84 |
|
85 | const reInitialized = this._fingerprint !== "" && this._fingerprint !== fingerprint;
|
86 | this._fingerprint = fingerprint;
|
87 | this._updateUrl = updateUrl === true;
|
88 | this._initialized = true;
|
89 |
|
90 | this._bindEvents();
|
91 |
|
92 | const state = window.history.state;
|
93 | this._popStateInProgress = false;
|
94 | this._blockHashChange = 0;
|
95 | this._currentHash = getCurrentHash();
|
96 | this._numPositionUpdates = 0;
|
97 | this._uid = this._maxUid = 0;
|
98 | this._destination = null;
|
99 | this._position = null;
|
100 |
|
101 | if (!this._isValidState(state, true) || resetHistory) {
|
102 | const {
|
103 | hash,
|
104 | page,
|
105 | rotation
|
106 | } = this._parseCurrentHash();
|
107 |
|
108 | if (!hash || reInitialized || resetHistory) {
|
109 | this._pushOrReplaceState(null, true);
|
110 |
|
111 | return;
|
112 | }
|
113 |
|
114 | this._pushOrReplaceState({
|
115 | hash,
|
116 | page,
|
117 | rotation
|
118 | }, true);
|
119 |
|
120 | return;
|
121 | }
|
122 |
|
123 | const destination = state.destination;
|
124 |
|
125 | this._updateInternalState(destination, state.uid, true);
|
126 |
|
127 | if (this._uid > this._maxUid) {
|
128 | this._maxUid = this._uid;
|
129 | }
|
130 |
|
131 | if (destination.rotation !== undefined) {
|
132 | this._initialRotation = destination.rotation;
|
133 | }
|
134 |
|
135 | if (destination.dest) {
|
136 | this._initialBookmark = JSON.stringify(destination.dest);
|
137 | this._destination.page = null;
|
138 | } else if (destination.hash) {
|
139 | this._initialBookmark = destination.hash;
|
140 | } else if (destination.page) {
|
141 | this._initialBookmark = `page=${destination.page}`;
|
142 | }
|
143 | }
|
144 |
|
145 | reset() {
|
146 | if (this._initialized) {
|
147 | this._pageHide();
|
148 |
|
149 | this._initialized = false;
|
150 |
|
151 | this._unbindEvents();
|
152 | }
|
153 |
|
154 | if (this._updateViewareaTimeout) {
|
155 | clearTimeout(this._updateViewareaTimeout);
|
156 | this._updateViewareaTimeout = null;
|
157 | }
|
158 |
|
159 | this._initialBookmark = null;
|
160 | this._initialRotation = null;
|
161 | }
|
162 |
|
163 | push({
|
164 | namedDest = null,
|
165 | explicitDest,
|
166 | pageNumber
|
167 | }) {
|
168 | if (!this._initialized) {
|
169 | return;
|
170 | }
|
171 |
|
172 | if (namedDest && typeof namedDest !== "string") {
|
173 | console.error("PDFHistory.push: " + `"${namedDest}" is not a valid namedDest parameter.`);
|
174 | return;
|
175 | } else if (!Array.isArray(explicitDest)) {
|
176 | console.error("PDFHistory.push: " + `"${explicitDest}" is not a valid explicitDest parameter.`);
|
177 | return;
|
178 | } else if (!(Number.isInteger(pageNumber) && pageNumber > 0 && pageNumber <= this.linkService.pagesCount)) {
|
179 | if (pageNumber !== null || this._destination) {
|
180 | console.error("PDFHistory.push: " + `"${pageNumber}" is not a valid pageNumber parameter.`);
|
181 | return;
|
182 | }
|
183 | }
|
184 |
|
185 | const hash = namedDest || JSON.stringify(explicitDest);
|
186 |
|
187 | if (!hash) {
|
188 | return;
|
189 | }
|
190 |
|
191 | let forceReplace = false;
|
192 |
|
193 | if (this._destination && (isDestHashesEqual(this._destination.hash, hash) || isDestArraysEqual(this._destination.dest, explicitDest))) {
|
194 | if (this._destination.page) {
|
195 | return;
|
196 | }
|
197 |
|
198 | forceReplace = true;
|
199 | }
|
200 |
|
201 | if (this._popStateInProgress && !forceReplace) {
|
202 | return;
|
203 | }
|
204 |
|
205 | this._pushOrReplaceState({
|
206 | dest: explicitDest,
|
207 | hash,
|
208 | page: pageNumber,
|
209 | rotation: this.linkService.rotation
|
210 | }, forceReplace);
|
211 |
|
212 | if (!this._popStateInProgress) {
|
213 | this._popStateInProgress = true;
|
214 | Promise.resolve().then(() => {
|
215 | this._popStateInProgress = false;
|
216 | });
|
217 | }
|
218 | }
|
219 |
|
220 | pushCurrentPosition() {
|
221 | if (!this._initialized || this._popStateInProgress) {
|
222 | return;
|
223 | }
|
224 |
|
225 | this._tryPushCurrentPosition();
|
226 | }
|
227 |
|
228 | back() {
|
229 | if (!this._initialized || this._popStateInProgress) {
|
230 | return;
|
231 | }
|
232 |
|
233 | const state = window.history.state;
|
234 |
|
235 | if (this._isValidState(state) && state.uid > 0) {
|
236 | window.history.back();
|
237 | }
|
238 | }
|
239 |
|
240 | forward() {
|
241 | if (!this._initialized || this._popStateInProgress) {
|
242 | return;
|
243 | }
|
244 |
|
245 | const state = window.history.state;
|
246 |
|
247 | if (this._isValidState(state) && state.uid < this._maxUid) {
|
248 | window.history.forward();
|
249 | }
|
250 | }
|
251 |
|
252 | get popStateInProgress() {
|
253 | return this._initialized && (this._popStateInProgress || this._blockHashChange > 0);
|
254 | }
|
255 |
|
256 | get initialBookmark() {
|
257 | return this._initialized ? this._initialBookmark : null;
|
258 | }
|
259 |
|
260 | get initialRotation() {
|
261 | return this._initialized ? this._initialRotation : null;
|
262 | }
|
263 |
|
264 | _pushOrReplaceState(destination, forceReplace = false) {
|
265 | const shouldReplace = forceReplace || !this._destination;
|
266 | const newState = {
|
267 | fingerprint: this._fingerprint,
|
268 | uid: shouldReplace ? this._uid : this._uid + 1,
|
269 | destination
|
270 | };
|
271 |
|
272 | this._updateInternalState(destination, newState.uid);
|
273 |
|
274 | let newUrl;
|
275 |
|
276 | if (this._updateUrl && destination && destination.hash) {
|
277 | const baseUrl = document.location.href.split("#")[0];
|
278 |
|
279 | if (!baseUrl.startsWith("file://")) {
|
280 | newUrl = `${baseUrl}#${destination.hash}`;
|
281 | }
|
282 | }
|
283 |
|
284 | if (shouldReplace) {
|
285 | window.history.replaceState(newState, "", newUrl);
|
286 | } else {
|
287 | this._maxUid = this._uid;
|
288 | window.history.pushState(newState, "", newUrl);
|
289 | }
|
290 | }
|
291 |
|
292 | _tryPushCurrentPosition(temporary = false) {
|
293 | if (!this._position) {
|
294 | return;
|
295 | }
|
296 |
|
297 | let position = this._position;
|
298 |
|
299 | if (temporary) {
|
300 | position = Object.assign(Object.create(null), this._position);
|
301 | position.temporary = true;
|
302 | }
|
303 |
|
304 | if (!this._destination) {
|
305 | this._pushOrReplaceState(position);
|
306 |
|
307 | return;
|
308 | }
|
309 |
|
310 | if (this._destination.temporary) {
|
311 | this._pushOrReplaceState(position, true);
|
312 |
|
313 | return;
|
314 | }
|
315 |
|
316 | if (this._destination.hash === position.hash) {
|
317 | return;
|
318 | }
|
319 |
|
320 | if (!this._destination.page && (POSITION_UPDATED_THRESHOLD <= 0 || this._numPositionUpdates <= POSITION_UPDATED_THRESHOLD)) {
|
321 | return;
|
322 | }
|
323 |
|
324 | let forceReplace = false;
|
325 |
|
326 | if (this._destination.page >= position.first && this._destination.page <= position.page) {
|
327 | if (this._destination.dest || !this._destination.first) {
|
328 | return;
|
329 | }
|
330 |
|
331 | forceReplace = true;
|
332 | }
|
333 |
|
334 | this._pushOrReplaceState(position, forceReplace);
|
335 | }
|
336 |
|
337 | _isValidState(state, checkReload = false) {
|
338 | if (!state) {
|
339 | return false;
|
340 | }
|
341 |
|
342 | if (state.fingerprint !== this._fingerprint) {
|
343 | if (checkReload) {
|
344 | if (typeof state.fingerprint !== "string" || state.fingerprint.length !== this._fingerprint.length) {
|
345 | return false;
|
346 | }
|
347 |
|
348 | const [perfEntry] = performance.getEntriesByType("navigation");
|
349 |
|
350 | if (!perfEntry || perfEntry.type !== "reload") {
|
351 | return false;
|
352 | }
|
353 | } else {
|
354 | return false;
|
355 | }
|
356 | }
|
357 |
|
358 | if (!Number.isInteger(state.uid) || state.uid < 0) {
|
359 | return false;
|
360 | }
|
361 |
|
362 | if (state.destination === null || typeof state.destination !== "object") {
|
363 | return false;
|
364 | }
|
365 |
|
366 | return true;
|
367 | }
|
368 |
|
369 | _updateInternalState(destination, uid, removeTemporary = false) {
|
370 | if (this._updateViewareaTimeout) {
|
371 | clearTimeout(this._updateViewareaTimeout);
|
372 | this._updateViewareaTimeout = null;
|
373 | }
|
374 |
|
375 | if (removeTemporary && destination && destination.temporary) {
|
376 | delete destination.temporary;
|
377 | }
|
378 |
|
379 | this._destination = destination;
|
380 | this._uid = uid;
|
381 | this._numPositionUpdates = 0;
|
382 | }
|
383 |
|
384 | _parseCurrentHash() {
|
385 | const hash = unescape(getCurrentHash()).substring(1);
|
386 | let page = (0, _ui_utils.parseQueryString)(hash).page | 0;
|
387 |
|
388 | if (!(Number.isInteger(page) && page > 0 && page <= this.linkService.pagesCount)) {
|
389 | page = null;
|
390 | }
|
391 |
|
392 | return {
|
393 | hash,
|
394 | page,
|
395 | rotation: this.linkService.rotation
|
396 | };
|
397 | }
|
398 |
|
399 | _updateViewarea({
|
400 | location
|
401 | }) {
|
402 | if (this._updateViewareaTimeout) {
|
403 | clearTimeout(this._updateViewareaTimeout);
|
404 | this._updateViewareaTimeout = null;
|
405 | }
|
406 |
|
407 | this._position = {
|
408 | hash: this._isViewerInPresentationMode ? `page=${location.pageNumber}` : location.pdfOpenParams.substring(1),
|
409 | page: this.linkService.page,
|
410 | first: location.pageNumber,
|
411 | rotation: location.rotation
|
412 | };
|
413 |
|
414 | if (this._popStateInProgress) {
|
415 | return;
|
416 | }
|
417 |
|
418 | if (POSITION_UPDATED_THRESHOLD > 0 && this._isPagesLoaded && this._destination && !this._destination.page) {
|
419 | this._numPositionUpdates++;
|
420 | }
|
421 |
|
422 | if (UPDATE_VIEWAREA_TIMEOUT > 0) {
|
423 | this._updateViewareaTimeout = setTimeout(() => {
|
424 | if (!this._popStateInProgress) {
|
425 | this._tryPushCurrentPosition(true);
|
426 | }
|
427 |
|
428 | this._updateViewareaTimeout = null;
|
429 | }, UPDATE_VIEWAREA_TIMEOUT);
|
430 | }
|
431 | }
|
432 |
|
433 | _popState({
|
434 | state
|
435 | }) {
|
436 | const newHash = getCurrentHash(),
|
437 | hashChanged = this._currentHash !== newHash;
|
438 | this._currentHash = newHash;
|
439 |
|
440 | if (!state) {
|
441 | this._uid++;
|
442 |
|
443 | const {
|
444 | hash,
|
445 | page,
|
446 | rotation
|
447 | } = this._parseCurrentHash();
|
448 |
|
449 | this._pushOrReplaceState({
|
450 | hash,
|
451 | page,
|
452 | rotation
|
453 | }, true);
|
454 |
|
455 | return;
|
456 | }
|
457 |
|
458 | if (!this._isValidState(state)) {
|
459 | return;
|
460 | }
|
461 |
|
462 | this._popStateInProgress = true;
|
463 |
|
464 | if (hashChanged) {
|
465 | this._blockHashChange++;
|
466 | (0, _ui_utils.waitOnEventOrTimeout)({
|
467 | target: window,
|
468 | name: "hashchange",
|
469 | delay: HASH_CHANGE_TIMEOUT
|
470 | }).then(() => {
|
471 | this._blockHashChange--;
|
472 | });
|
473 | }
|
474 |
|
475 | const destination = state.destination;
|
476 |
|
477 | this._updateInternalState(destination, state.uid, true);
|
478 |
|
479 | if (this._uid > this._maxUid) {
|
480 | this._maxUid = this._uid;
|
481 | }
|
482 |
|
483 | if ((0, _ui_utils.isValidRotation)(destination.rotation)) {
|
484 | this.linkService.rotation = destination.rotation;
|
485 | }
|
486 |
|
487 | if (destination.dest) {
|
488 | this.linkService.navigateTo(destination.dest);
|
489 | } else if (destination.hash) {
|
490 | this.linkService.setHash(destination.hash);
|
491 | } else if (destination.page) {
|
492 | this.linkService.page = destination.page;
|
493 | }
|
494 |
|
495 | Promise.resolve().then(() => {
|
496 | this._popStateInProgress = false;
|
497 | });
|
498 | }
|
499 |
|
500 | _pageHide() {
|
501 | if (!this._destination || this._destination.temporary) {
|
502 | this._tryPushCurrentPosition();
|
503 | }
|
504 | }
|
505 |
|
506 | _bindEvents() {
|
507 | if (this._boundEvents) {
|
508 | return;
|
509 | }
|
510 |
|
511 | this._boundEvents = {
|
512 | updateViewarea: this._updateViewarea.bind(this),
|
513 | popState: this._popState.bind(this),
|
514 | pageHide: this._pageHide.bind(this)
|
515 | };
|
516 |
|
517 | this.eventBus._on("updateviewarea", this._boundEvents.updateViewarea);
|
518 |
|
519 | window.addEventListener("popstate", this._boundEvents.popState);
|
520 | window.addEventListener("pagehide", this._boundEvents.pageHide);
|
521 | }
|
522 |
|
523 | _unbindEvents() {
|
524 | if (!this._boundEvents) {
|
525 | return;
|
526 | }
|
527 |
|
528 | this.eventBus._off("updateviewarea", this._boundEvents.updateViewarea);
|
529 |
|
530 | window.removeEventListener("popstate", this._boundEvents.popState);
|
531 | window.removeEventListener("pagehide", this._boundEvents.pageHide);
|
532 | this._boundEvents = null;
|
533 | }
|
534 |
|
535 | }
|
536 |
|
537 | exports.PDFHistory = PDFHistory;
|
538 |
|
539 | function isDestHashesEqual(destHash, pushHash) {
|
540 | if (typeof destHash !== "string" || typeof pushHash !== "string") {
|
541 | return false;
|
542 | }
|
543 |
|
544 | if (destHash === pushHash) {
|
545 | return true;
|
546 | }
|
547 |
|
548 | const {
|
549 | nameddest
|
550 | } = (0, _ui_utils.parseQueryString)(destHash);
|
551 |
|
552 | if (nameddest === pushHash) {
|
553 | return true;
|
554 | }
|
555 |
|
556 | return false;
|
557 | }
|
558 |
|
559 | function isDestArraysEqual(firstDest, secondDest) {
|
560 | function isEntryEqual(first, second) {
|
561 | if (typeof first !== typeof second) {
|
562 | return false;
|
563 | }
|
564 |
|
565 | if (Array.isArray(first) || Array.isArray(second)) {
|
566 | return false;
|
567 | }
|
568 |
|
569 | if (first !== null && typeof first === "object" && second !== null) {
|
570 | if (Object.keys(first).length !== Object.keys(second).length) {
|
571 | return false;
|
572 | }
|
573 |
|
574 | for (const key in first) {
|
575 | if (!isEntryEqual(first[key], second[key])) {
|
576 | return false;
|
577 | }
|
578 | }
|
579 |
|
580 | return true;
|
581 | }
|
582 |
|
583 | return first === second || Number.isNaN(first) && Number.isNaN(second);
|
584 | }
|
585 |
|
586 | if (!(Array.isArray(firstDest) && Array.isArray(secondDest))) {
|
587 | return false;
|
588 | }
|
589 |
|
590 | if (firstDest.length !== secondDest.length) {
|
591 | return false;
|
592 | }
|
593 |
|
594 | for (let i = 0, ii = firstDest.length; i < ii; i++) {
|
595 | if (!isEntryEqual(firstDest[i], secondDest[i])) {
|
596 | return false;
|
597 | }
|
598 | }
|
599 |
|
600 | return true;
|
601 | } |
\ | No newline at end of file |