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