PageRenderTime 77ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 2ms

/libs/pdf.js/1.0.1040/viewer.js

https://gitlab.com/mba811/static
JavaScript | 6952 lines | 5293 code | 862 blank | 797 comment | 863 complexity | 1824a6c2b1d7f8f9134f78b7d79ca9e1 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
  3. /* Copyright 2012 Mozilla Foundation
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar,
  18. DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL,
  19. PDFHistory, Preferences, SidebarView, ViewHistory, PageView,
  20. PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
  21. PasswordPrompt, PresentationMode, HandTool, Promise,
  22. DocumentProperties, DocumentOutlineView, DocumentAttachmentsView,
  23. OverlayManager, PDFFindController, PDFFindBar, getVisibleElements,
  24. watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState,
  25. RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE,
  26. IGNORE_CURRENT_POSITION_ON_ZOOM: true */
  27. 'use strict';
  28. var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
  29. var DEFAULT_SCALE_DELTA = 1.1;
  30. var MIN_SCALE = 0.25;
  31. var MAX_SCALE = 10.0;
  32. var VIEW_HISTORY_MEMORY = 20;
  33. var SCALE_SELECT_CONTAINER_PADDING = 8;
  34. var SCALE_SELECT_PADDING = 22;
  35. var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading';
  36. var DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000;
  37. PDFJS.imageResourcesPath = './images/';
  38. PDFJS.workerSrc = '../build/pdf.worker.js';
  39. PDFJS.cMapUrl = '../web/cmaps/';
  40. PDFJS.cMapPacked = true;
  41. var mozL10n = document.mozL10n || document.webL10n;
  42. var CSS_UNITS = 96.0 / 72.0;
  43. var DEFAULT_SCALE = 'auto';
  44. var UNKNOWN_SCALE = 0;
  45. var MAX_AUTO_SCALE = 1.25;
  46. var SCROLLBAR_PADDING = 40;
  47. var VERTICAL_PADDING = 5;
  48. var DEFAULT_CACHE_SIZE = 10;
  49. // optimised CSS custom property getter/setter
  50. var CustomStyle = (function CustomStyleClosure() {
  51. // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
  52. // animate-css-transforms-firefox-webkit.html
  53. // in some versions of IE9 it is critical that ms appear in this list
  54. // before Moz
  55. var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
  56. var _cache = {};
  57. function CustomStyle() {}
  58. CustomStyle.getProp = function get(propName, element) {
  59. // check cache only when no element is given
  60. if (arguments.length === 1 && typeof _cache[propName] === 'string') {
  61. return _cache[propName];
  62. }
  63. element = element || document.documentElement;
  64. var style = element.style, prefixed, uPropName;
  65. // test standard property first
  66. if (typeof style[propName] === 'string') {
  67. return (_cache[propName] = propName);
  68. }
  69. // capitalize
  70. uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
  71. // test vendor specific properties
  72. for (var i = 0, l = prefixes.length; i < l; i++) {
  73. prefixed = prefixes[i] + uPropName;
  74. if (typeof style[prefixed] === 'string') {
  75. return (_cache[propName] = prefixed);
  76. }
  77. }
  78. //if all fails then set to undefined
  79. return (_cache[propName] = 'undefined');
  80. };
  81. CustomStyle.setProp = function set(propName, element, str) {
  82. var prop = this.getProp(propName);
  83. if (prop !== 'undefined') {
  84. element.style[prop] = str;
  85. }
  86. };
  87. return CustomStyle;
  88. })();
  89. function getFileName(url) {
  90. var anchor = url.indexOf('#');
  91. var query = url.indexOf('?');
  92. var end = Math.min(
  93. anchor > 0 ? anchor : url.length,
  94. query > 0 ? query : url.length);
  95. return url.substring(url.lastIndexOf('/', end) + 1, end);
  96. }
  97. /**
  98. * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
  99. * @return {Object} The object with horizontal (sx) and vertical (sy)
  100. scales. The scaled property is set to false if scaling is
  101. not required, true otherwise.
  102. */
  103. function getOutputScale(ctx) {
  104. var devicePixelRatio = window.devicePixelRatio || 1;
  105. var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
  106. ctx.mozBackingStorePixelRatio ||
  107. ctx.msBackingStorePixelRatio ||
  108. ctx.oBackingStorePixelRatio ||
  109. ctx.backingStorePixelRatio || 1;
  110. var pixelRatio = devicePixelRatio / backingStoreRatio;
  111. return {
  112. sx: pixelRatio,
  113. sy: pixelRatio,
  114. scaled: pixelRatio !== 1
  115. };
  116. }
  117. /**
  118. * Scrolls specified element into view of its parent.
  119. * element {Object} The element to be visible.
  120. * spot {Object} An object with optional top and left properties,
  121. * specifying the offset from the top left edge.
  122. */
  123. function scrollIntoView(element, spot) {
  124. // Assuming offsetParent is available (it's not available when viewer is in
  125. // hidden iframe or object). We have to scroll: if the offsetParent is not set
  126. // producing the error. See also animationStartedClosure.
  127. var parent = element.offsetParent;
  128. var offsetY = element.offsetTop + element.clientTop;
  129. var offsetX = element.offsetLeft + element.clientLeft;
  130. if (!parent) {
  131. console.error('offsetParent is not set -- cannot scroll');
  132. return;
  133. }
  134. while (parent.clientHeight === parent.scrollHeight) {
  135. if (parent.dataset._scaleY) {
  136. offsetY /= parent.dataset._scaleY;
  137. offsetX /= parent.dataset._scaleX;
  138. }
  139. offsetY += parent.offsetTop;
  140. offsetX += parent.offsetLeft;
  141. parent = parent.offsetParent;
  142. if (!parent) {
  143. return; // no need to scroll
  144. }
  145. }
  146. if (spot) {
  147. if (spot.top !== undefined) {
  148. offsetY += spot.top;
  149. }
  150. if (spot.left !== undefined) {
  151. offsetX += spot.left;
  152. parent.scrollLeft = offsetX;
  153. }
  154. }
  155. parent.scrollTop = offsetY;
  156. }
  157. /**
  158. * Helper function to start monitoring the scroll event and converting them into
  159. * PDF.js friendly one: with scroll debounce and scroll direction.
  160. */
  161. function watchScroll(viewAreaElement, callback) {
  162. var debounceScroll = function debounceScroll(evt) {
  163. if (rAF) {
  164. return;
  165. }
  166. // schedule an invocation of scroll for next animation frame.
  167. rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
  168. rAF = null;
  169. var currentY = viewAreaElement.scrollTop;
  170. var lastY = state.lastY;
  171. if (currentY > lastY) {
  172. state.down = true;
  173. } else if (currentY < lastY) {
  174. state.down = false;
  175. }
  176. state.lastY = currentY;
  177. // else do nothing and use previous value
  178. callback(state);
  179. });
  180. };
  181. var state = {
  182. down: true,
  183. lastY: viewAreaElement.scrollTop,
  184. _eventHandler: debounceScroll
  185. };
  186. var rAF = null;
  187. viewAreaElement.addEventListener('scroll', debounceScroll, true);
  188. return state;
  189. }
  190. /**
  191. * Generic helper to find out what elements are visible within a scroll pane.
  192. */
  193. function getVisibleElements(scrollEl, views, sortByVisibility) {
  194. var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
  195. var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
  196. var visible = [], view;
  197. var currentHeight, viewHeight, hiddenHeight, percentHeight;
  198. var currentWidth, viewWidth;
  199. for (var i = 0, ii = views.length; i < ii; ++i) {
  200. view = views[i];
  201. currentHeight = view.el.offsetTop + view.el.clientTop;
  202. viewHeight = view.el.clientHeight;
  203. if ((currentHeight + viewHeight) < top) {
  204. continue;
  205. }
  206. if (currentHeight > bottom) {
  207. break;
  208. }
  209. currentWidth = view.el.offsetLeft + view.el.clientLeft;
  210. viewWidth = view.el.clientWidth;
  211. if ((currentWidth + viewWidth) < left || currentWidth > right) {
  212. continue;
  213. }
  214. hiddenHeight = Math.max(0, top - currentHeight) +
  215. Math.max(0, currentHeight + viewHeight - bottom);
  216. percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
  217. visible.push({ id: view.id, x: currentWidth, y: currentHeight,
  218. view: view, percent: percentHeight });
  219. }
  220. var first = visible[0];
  221. var last = visible[visible.length - 1];
  222. if (sortByVisibility) {
  223. visible.sort(function(a, b) {
  224. var pc = a.percent - b.percent;
  225. if (Math.abs(pc) > 0.001) {
  226. return -pc;
  227. }
  228. return a.id - b.id; // ensure stability
  229. });
  230. }
  231. return {first: first, last: last, views: visible};
  232. }
  233. /**
  234. * Event handler to suppress context menu.
  235. */
  236. function noContextMenuHandler(e) {
  237. e.preventDefault();
  238. }
  239. /**
  240. * Returns the filename or guessed filename from the url (see issue 3455).
  241. * url {String} The original PDF location.
  242. * @return {String} Guessed PDF file name.
  243. */
  244. function getPDFFileNameFromURL(url) {
  245. var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
  246. // SCHEME HOST 1.PATH 2.QUERY 3.REF
  247. // Pattern to get last matching NAME.pdf
  248. var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
  249. var splitURI = reURI.exec(url);
  250. var suggestedFilename = reFilename.exec(splitURI[1]) ||
  251. reFilename.exec(splitURI[2]) ||
  252. reFilename.exec(splitURI[3]);
  253. if (suggestedFilename) {
  254. suggestedFilename = suggestedFilename[0];
  255. if (suggestedFilename.indexOf('%') !== -1) {
  256. // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
  257. try {
  258. suggestedFilename =
  259. reFilename.exec(decodeURIComponent(suggestedFilename))[0];
  260. } catch(e) { // Possible (extremely rare) errors:
  261. // URIError "Malformed URI", e.g. for "%AA.pdf"
  262. // TypeError "null has no properties", e.g. for "%2F.pdf"
  263. }
  264. }
  265. }
  266. return suggestedFilename || 'document.pdf';
  267. }
  268. var ProgressBar = (function ProgressBarClosure() {
  269. function clamp(v, min, max) {
  270. return Math.min(Math.max(v, min), max);
  271. }
  272. function ProgressBar(id, opts) {
  273. this.visible = true;
  274. // Fetch the sub-elements for later.
  275. this.div = document.querySelector(id + ' .progress');
  276. // Get the loading bar element, so it can be resized to fit the viewer.
  277. this.bar = this.div.parentNode;
  278. // Get options, with sensible defaults.
  279. this.height = opts.height || 100;
  280. this.width = opts.width || 100;
  281. this.units = opts.units || '%';
  282. // Initialize heights.
  283. this.div.style.height = this.height + this.units;
  284. this.percent = 0;
  285. }
  286. ProgressBar.prototype = {
  287. updateBar: function ProgressBar_updateBar() {
  288. if (this._indeterminate) {
  289. this.div.classList.add('indeterminate');
  290. this.div.style.width = this.width + this.units;
  291. return;
  292. }
  293. this.div.classList.remove('indeterminate');
  294. var progressSize = this.width * this._percent / 100;
  295. this.div.style.width = progressSize + this.units;
  296. },
  297. get percent() {
  298. return this._percent;
  299. },
  300. set percent(val) {
  301. this._indeterminate = isNaN(val);
  302. this._percent = clamp(val, 0, 100);
  303. this.updateBar();
  304. },
  305. setWidth: function ProgressBar_setWidth(viewer) {
  306. if (viewer) {
  307. var container = viewer.parentNode;
  308. var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
  309. if (scrollbarWidth > 0) {
  310. this.bar.setAttribute('style', 'width: calc(100% - ' +
  311. scrollbarWidth + 'px);');
  312. }
  313. }
  314. },
  315. hide: function ProgressBar_hide() {
  316. if (!this.visible) {
  317. return;
  318. }
  319. this.visible = false;
  320. this.bar.classList.add('hidden');
  321. document.body.classList.remove('loadingInProgress');
  322. },
  323. show: function ProgressBar_show() {
  324. if (this.visible) {
  325. return;
  326. }
  327. this.visible = true;
  328. document.body.classList.add('loadingInProgress');
  329. this.bar.classList.remove('hidden');
  330. }
  331. };
  332. return ProgressBar;
  333. })();
  334. var Cache = function cacheCache(size) {
  335. var data = [];
  336. this.push = function cachePush(view) {
  337. var i = data.indexOf(view);
  338. if (i >= 0) {
  339. data.splice(i, 1);
  340. }
  341. data.push(view);
  342. if (data.length > size) {
  343. data.shift().destroy();
  344. }
  345. };
  346. this.resize = function (newSize) {
  347. size = newSize;
  348. while (data.length > size) {
  349. data.shift().destroy();
  350. }
  351. };
  352. };
  353. var DEFAULT_PREFERENCES = {
  354. showPreviousViewOnLoad: true,
  355. defaultZoomValue: '',
  356. sidebarViewOnLoad: 0,
  357. enableHandToolOnLoad: false,
  358. enableWebGL: false,
  359. pdfBugEnabled: false,
  360. disableRange: false,
  361. disableStream: false,
  362. disableAutoFetch: false,
  363. disableFontFace: false,
  364. disableTextLayer: false,
  365. useOnlyCssZoom: false
  366. };
  367. var SidebarView = {
  368. NONE: 0,
  369. THUMBS: 1,
  370. OUTLINE: 2,
  371. ATTACHMENTS: 3
  372. };
  373. /**
  374. * Preferences - Utility for storing persistent settings.
  375. * Used for settings that should be applied to all opened documents,
  376. * or every time the viewer is loaded.
  377. */
  378. var Preferences = {
  379. prefs: Object.create(DEFAULT_PREFERENCES),
  380. isInitializedPromiseResolved: false,
  381. initializedPromise: null,
  382. /**
  383. * Initialize and fetch the current preference values from storage.
  384. * @return {Promise} A promise that is resolved when the preferences
  385. * have been initialized.
  386. */
  387. initialize: function preferencesInitialize() {
  388. return this.initializedPromise =
  389. this._readFromStorage(DEFAULT_PREFERENCES).then(function(prefObj) {
  390. this.isInitializedPromiseResolved = true;
  391. if (prefObj) {
  392. this.prefs = prefObj;
  393. }
  394. }.bind(this));
  395. },
  396. /**
  397. * Stub function for writing preferences to storage.
  398. * NOTE: This should be overridden by a build-specific function defined below.
  399. * @param {Object} prefObj The preferences that should be written to storage.
  400. * @return {Promise} A promise that is resolved when the preference values
  401. * have been written.
  402. */
  403. _writeToStorage: function preferences_writeToStorage(prefObj) {
  404. return Promise.resolve();
  405. },
  406. /**
  407. * Stub function for reading preferences from storage.
  408. * NOTE: This should be overridden by a build-specific function defined below.
  409. * @param {Object} prefObj The preferences that should be read from storage.
  410. * @return {Promise} A promise that is resolved with an {Object} containing
  411. * the preferences that have been read.
  412. */
  413. _readFromStorage: function preferences_readFromStorage(prefObj) {
  414. return Promise.resolve();
  415. },
  416. /**
  417. * Reset the preferences to their default values and update storage.
  418. * @return {Promise} A promise that is resolved when the preference values
  419. * have been reset.
  420. */
  421. reset: function preferencesReset() {
  422. return this.initializedPromise.then(function() {
  423. this.prefs = Object.create(DEFAULT_PREFERENCES);
  424. return this._writeToStorage(DEFAULT_PREFERENCES);
  425. }.bind(this));
  426. },
  427. /**
  428. * Replace the current preference values with the ones from storage.
  429. * @return {Promise} A promise that is resolved when the preference values
  430. * have been updated.
  431. */
  432. reload: function preferencesReload() {
  433. return this.initializedPromise.then(function () {
  434. this._readFromStorage(DEFAULT_PREFERENCES).then(function(prefObj) {
  435. if (prefObj) {
  436. this.prefs = prefObj;
  437. }
  438. }.bind(this));
  439. }.bind(this));
  440. },
  441. /**
  442. * Set the value of a preference.
  443. * @param {string} name The name of the preference that should be changed.
  444. * @param {boolean|number|string} value The new value of the preference.
  445. * @return {Promise} A promise that is resolved when the value has been set,
  446. * provided that the preference exists and the types match.
  447. */
  448. set: function preferencesSet(name, value) {
  449. return this.initializedPromise.then(function () {
  450. if (DEFAULT_PREFERENCES[name] === undefined) {
  451. throw new Error('preferencesSet: \'' + name + '\' is undefined.');
  452. } else if (value === undefined) {
  453. throw new Error('preferencesSet: no value is specified.');
  454. }
  455. var valueType = typeof value;
  456. var defaultType = typeof DEFAULT_PREFERENCES[name];
  457. if (valueType !== defaultType) {
  458. if (valueType === 'number' && defaultType === 'string') {
  459. value = value.toString();
  460. } else {
  461. throw new Error('Preferences_set: \'' + value + '\' is a \"' +
  462. valueType + '\", expected \"' + defaultType + '\".');
  463. }
  464. } else {
  465. if (valueType === 'number' && (value | 0) !== value) {
  466. throw new Error('Preferences_set: \'' + value +
  467. '\' must be an \"integer\".');
  468. }
  469. }
  470. this.prefs[name] = value;
  471. return this._writeToStorage(this.prefs);
  472. }.bind(this));
  473. },
  474. /**
  475. * Get the value of a preference.
  476. * @param {string} name The name of the preference whose value is requested.
  477. * @return {Promise} A promise that is resolved with a {boolean|number|string}
  478. * containing the value of the preference.
  479. */
  480. get: function preferencesGet(name) {
  481. return this.initializedPromise.then(function () {
  482. var defaultValue = DEFAULT_PREFERENCES[name];
  483. if (defaultValue === undefined) {
  484. throw new Error('preferencesGet: \'' + name + '\' is undefined.');
  485. } else {
  486. var prefValue = this.prefs[name];
  487. if (prefValue !== undefined) {
  488. return prefValue;
  489. }
  490. }
  491. return defaultValue;
  492. }.bind(this));
  493. }
  494. };
  495. Preferences._writeToStorage = function (prefObj) {
  496. return new Promise(function (resolve) {
  497. localStorage.setItem('pdfjs.preferences', JSON.stringify(prefObj));
  498. resolve();
  499. });
  500. };
  501. Preferences._readFromStorage = function (prefObj) {
  502. return new Promise(function (resolve) {
  503. var readPrefs = JSON.parse(localStorage.getItem('pdfjs.preferences'));
  504. resolve(readPrefs);
  505. });
  506. };
  507. (function mozPrintCallbackPolyfillClosure() {
  508. if ('mozPrintCallback' in document.createElement('canvas')) {
  509. return;
  510. }
  511. // Cause positive result on feature-detection:
  512. HTMLCanvasElement.prototype.mozPrintCallback = undefined;
  513. var canvases; // During print task: non-live NodeList of <canvas> elements
  514. var index; // Index of <canvas> element that is being processed
  515. var print = window.print;
  516. window.print = function print() {
  517. if (canvases) {
  518. console.warn('Ignored window.print() because of a pending print job.');
  519. return;
  520. }
  521. try {
  522. dispatchEvent('beforeprint');
  523. } finally {
  524. canvases = document.querySelectorAll('canvas');
  525. index = -1;
  526. next();
  527. }
  528. };
  529. function dispatchEvent(eventType) {
  530. var event = document.createEvent('CustomEvent');
  531. event.initCustomEvent(eventType, false, false, 'custom');
  532. window.dispatchEvent(event);
  533. }
  534. function next() {
  535. if (!canvases) {
  536. return; // Print task cancelled by user (state reset in abort())
  537. }
  538. renderProgress();
  539. if (++index < canvases.length) {
  540. var canvas = canvases[index];
  541. if (typeof canvas.mozPrintCallback === 'function') {
  542. canvas.mozPrintCallback({
  543. context: canvas.getContext('2d'),
  544. abort: abort,
  545. done: next
  546. });
  547. } else {
  548. next();
  549. }
  550. } else {
  551. renderProgress();
  552. print.call(window);
  553. setTimeout(abort, 20); // Tidy-up
  554. }
  555. }
  556. function abort() {
  557. if (canvases) {
  558. canvases = null;
  559. renderProgress();
  560. dispatchEvent('afterprint');
  561. }
  562. }
  563. function renderProgress() {
  564. var progressContainer = document.getElementById('mozPrintCallback-shim');
  565. if (canvases) {
  566. var progress = Math.round(100 * index / canvases.length);
  567. var progressBar = progressContainer.querySelector('progress');
  568. var progressPerc = progressContainer.querySelector('.relative-progress');
  569. progressBar.value = progress;
  570. progressPerc.textContent = progress + '%';
  571. progressContainer.removeAttribute('hidden');
  572. progressContainer.onclick = abort;
  573. } else {
  574. progressContainer.setAttribute('hidden', '');
  575. }
  576. }
  577. var hasAttachEvent = !!document.attachEvent;
  578. window.addEventListener('keydown', function(event) {
  579. // Intercept Cmd/Ctrl + P in all browsers.
  580. // Also intercept Cmd/Ctrl + Shift + P in Chrome and Opera
  581. if (event.keyCode === 80/*P*/ && (event.ctrlKey || event.metaKey) &&
  582. !event.altKey && (!event.shiftKey || window.chrome || window.opera)) {
  583. window.print();
  584. if (hasAttachEvent) {
  585. // Only attachEvent can cancel Ctrl + P dialog in IE <=10
  586. // attachEvent is gone in IE11, so the dialog will re-appear in IE11.
  587. return;
  588. }
  589. event.preventDefault();
  590. if (event.stopImmediatePropagation) {
  591. event.stopImmediatePropagation();
  592. } else {
  593. event.stopPropagation();
  594. }
  595. return;
  596. }
  597. if (event.keyCode === 27 && canvases) { // Esc
  598. abort();
  599. }
  600. }, true);
  601. if (hasAttachEvent) {
  602. document.attachEvent('onkeydown', function(event) {
  603. event = event || window.event;
  604. if (event.keyCode === 80/*P*/ && event.ctrlKey) {
  605. event.keyCode = 0;
  606. return false;
  607. }
  608. });
  609. }
  610. if ('onbeforeprint' in window) {
  611. // Do not propagate before/afterprint events when they are not triggered
  612. // from within this polyfill. (FF/IE).
  613. var stopPropagationIfNeeded = function(event) {
  614. if (event.detail !== 'custom' && event.stopImmediatePropagation) {
  615. event.stopImmediatePropagation();
  616. }
  617. };
  618. window.addEventListener('beforeprint', stopPropagationIfNeeded, false);
  619. window.addEventListener('afterprint', stopPropagationIfNeeded, false);
  620. }
  621. })();
  622. var DownloadManager = (function DownloadManagerClosure() {
  623. function download(blobUrl, filename) {
  624. var a = document.createElement('a');
  625. if (a.click) {
  626. // Use a.click() if available. Otherwise, Chrome might show
  627. // "Unsafe JavaScript attempt to initiate a navigation change
  628. // for frame with URL" and not open the PDF at all.
  629. // Supported by (not mentioned = untested):
  630. // - Firefox 6 - 19 (4- does not support a.click, 5 ignores a.click)
  631. // - Chrome 19 - 26 (18- does not support a.click)
  632. // - Opera 9 - 12.15
  633. // - Internet Explorer 6 - 10
  634. // - Safari 6 (5.1- does not support a.click)
  635. a.href = blobUrl;
  636. a.target = '_parent';
  637. // Use a.download if available. This increases the likelihood that
  638. // the file is downloaded instead of opened by another PDF plugin.
  639. if ('download' in a) {
  640. a.download = filename;
  641. }
  642. // <a> must be in the document for IE and recent Firefox versions.
  643. // (otherwise .click() is ignored)
  644. (document.body || document.documentElement).appendChild(a);
  645. a.click();
  646. a.parentNode.removeChild(a);
  647. } else {
  648. if (window.top === window &&
  649. blobUrl.split('#')[0] === window.location.href.split('#')[0]) {
  650. // If _parent == self, then opening an identical URL with different
  651. // location hash will only cause a navigation, not a download.
  652. var padCharacter = blobUrl.indexOf('?') === -1 ? '?' : '&';
  653. blobUrl = blobUrl.replace(/#|$/, padCharacter + '$&');
  654. }
  655. window.open(blobUrl, '_parent');
  656. }
  657. }
  658. function DownloadManager() {}
  659. DownloadManager.prototype = {
  660. downloadUrl: function DownloadManager_downloadUrl(url, filename) {
  661. if (!PDFJS.isValidUrl(url, true)) {
  662. return; // restricted/invalid URL
  663. }
  664. download(url + '#pdfjs.action=download', filename);
  665. },
  666. downloadData: function DownloadManager_downloadData(data, filename,
  667. contentType) {
  668. if (navigator.msSaveBlob) { // IE10 and above
  669. return navigator.msSaveBlob(new Blob([data], { type: contentType }),
  670. filename);
  671. }
  672. var blobUrl = PDFJS.createObjectURL(data, contentType);
  673. download(blobUrl, filename);
  674. },
  675. download: function DownloadManager_download(blob, url, filename) {
  676. if (!URL) {
  677. // URL.createObjectURL is not supported
  678. this.downloadUrl(url, filename);
  679. return;
  680. }
  681. if (navigator.msSaveBlob) {
  682. // IE10 / IE11
  683. if (!navigator.msSaveBlob(blob, filename)) {
  684. this.downloadUrl(url, filename);
  685. }
  686. return;
  687. }
  688. var blobUrl = URL.createObjectURL(blob);
  689. download(blobUrl, filename);
  690. }
  691. };
  692. return DownloadManager;
  693. })();
  694. /**
  695. * View History - This is a utility for saving various view parameters for
  696. * recently opened files.
  697. *
  698. * The way that the view parameters are stored depends on how PDF.js is built,
  699. * for 'node make <flag>' the following cases exist:
  700. * - FIREFOX or MOZCENTRAL - uses sessionStorage.
  701. * - B2G - uses asyncStorage.
  702. * - GENERIC or CHROME - uses localStorage, if it is available.
  703. */
  704. var ViewHistory = (function ViewHistoryClosure() {
  705. function ViewHistory(fingerprint) {
  706. this.fingerprint = fingerprint;
  707. this.isInitializedPromiseResolved = false;
  708. this.initializedPromise =
  709. this._readFromStorage().then(function (databaseStr) {
  710. this.isInitializedPromiseResolved = true;
  711. var database = JSON.parse(databaseStr || '{}');
  712. if (!('files' in database)) {
  713. database.files = [];
  714. }
  715. if (database.files.length >= VIEW_HISTORY_MEMORY) {
  716. database.files.shift();
  717. }
  718. var index;
  719. for (var i = 0, length = database.files.length; i < length; i++) {
  720. var branch = database.files[i];
  721. if (branch.fingerprint === this.fingerprint) {
  722. index = i;
  723. break;
  724. }
  725. }
  726. if (typeof index !== 'number') {
  727. index = database.files.push({fingerprint: this.fingerprint}) - 1;
  728. }
  729. this.file = database.files[index];
  730. this.database = database;
  731. }.bind(this));
  732. }
  733. ViewHistory.prototype = {
  734. _writeToStorage: function ViewHistory_writeToStorage() {
  735. return new Promise(function (resolve) {
  736. var databaseStr = JSON.stringify(this.database);
  737. localStorage.setItem('database', databaseStr);
  738. resolve();
  739. }.bind(this));
  740. },
  741. _readFromStorage: function ViewHistory_readFromStorage() {
  742. return new Promise(function (resolve) {
  743. resolve(localStorage.getItem('database'));
  744. });
  745. },
  746. set: function ViewHistory_set(name, val) {
  747. if (!this.isInitializedPromiseResolved) {
  748. return;
  749. }
  750. this.file[name] = val;
  751. return this._writeToStorage();
  752. },
  753. setMultiple: function ViewHistory_setMultiple(properties) {
  754. if (!this.isInitializedPromiseResolved) {
  755. return;
  756. }
  757. for (var name in properties) {
  758. this.file[name] = properties[name];
  759. }
  760. return this._writeToStorage();
  761. },
  762. get: function ViewHistory_get(name, defaultValue) {
  763. if (!this.isInitializedPromiseResolved) {
  764. return defaultValue;
  765. }
  766. return this.file[name] || defaultValue;
  767. }
  768. };
  769. return ViewHistory;
  770. })();
  771. /**
  772. * Creates a "search bar" given a set of DOM elements that act as controls
  773. * for searching or for setting search preferences in the UI. This object
  774. * also sets up the appropriate events for the controls. Actual searching
  775. * is done by PDFFindController.
  776. */
  777. var PDFFindBar = (function PDFFindBarClosure() {
  778. function PDFFindBar(options) {
  779. this.opened = false;
  780. this.bar = options.bar || null;
  781. this.toggleButton = options.toggleButton || null;
  782. this.findField = options.findField || null;
  783. this.highlightAll = options.highlightAllCheckbox || null;
  784. this.caseSensitive = options.caseSensitiveCheckbox || null;
  785. this.findMsg = options.findMsg || null;
  786. this.findStatusIcon = options.findStatusIcon || null;
  787. this.findPreviousButton = options.findPreviousButton || null;
  788. this.findNextButton = options.findNextButton || null;
  789. this.findController = options.findController || null;
  790. if (this.findController === null) {
  791. throw new Error('PDFFindBar cannot be used without a ' +
  792. 'PDFFindController instance.');
  793. }
  794. // Add event listeners to the DOM elements.
  795. var self = this;
  796. this.toggleButton.addEventListener('click', function() {
  797. self.toggle();
  798. });
  799. this.findField.addEventListener('input', function() {
  800. self.dispatchEvent('');
  801. });
  802. this.bar.addEventListener('keydown', function(evt) {
  803. switch (evt.keyCode) {
  804. case 13: // Enter
  805. if (evt.target === self.findField) {
  806. self.dispatchEvent('again', evt.shiftKey);
  807. }
  808. break;
  809. case 27: // Escape
  810. self.close();
  811. break;
  812. }
  813. });
  814. this.findPreviousButton.addEventListener('click', function() {
  815. self.dispatchEvent('again', true);
  816. });
  817. this.findNextButton.addEventListener('click', function() {
  818. self.dispatchEvent('again', false);
  819. });
  820. this.highlightAll.addEventListener('click', function() {
  821. self.dispatchEvent('highlightallchange');
  822. });
  823. this.caseSensitive.addEventListener('click', function() {
  824. self.dispatchEvent('casesensitivitychange');
  825. });
  826. }
  827. PDFFindBar.prototype = {
  828. dispatchEvent: function PDFFindBar_dispatchEvent(type, findPrev) {
  829. var event = document.createEvent('CustomEvent');
  830. event.initCustomEvent('find' + type, true, true, {
  831. query: this.findField.value,
  832. caseSensitive: this.caseSensitive.checked,
  833. highlightAll: this.highlightAll.checked,
  834. findPrevious: findPrev
  835. });
  836. return window.dispatchEvent(event);
  837. },
  838. updateUIState: function PDFFindBar_updateUIState(state, previous) {
  839. var notFound = false;
  840. var findMsg = '';
  841. var status = '';
  842. switch (state) {
  843. case FindStates.FIND_FOUND:
  844. break;
  845. case FindStates.FIND_PENDING:
  846. status = 'pending';
  847. break;
  848. case FindStates.FIND_NOTFOUND:
  849. findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
  850. notFound = true;
  851. break;
  852. case FindStates.FIND_WRAPPED:
  853. if (previous) {
  854. findMsg = mozL10n.get('find_reached_top', null,
  855. 'Reached top of document, continued from bottom');
  856. } else {
  857. findMsg = mozL10n.get('find_reached_bottom', null,
  858. 'Reached end of document, continued from top');
  859. }
  860. break;
  861. }
  862. if (notFound) {
  863. this.findField.classList.add('notFound');
  864. } else {
  865. this.findField.classList.remove('notFound');
  866. }
  867. this.findField.setAttribute('data-status', status);
  868. this.findMsg.textContent = findMsg;
  869. },
  870. open: function PDFFindBar_open() {
  871. if (!this.opened) {
  872. this.opened = true;
  873. this.toggleButton.classList.add('toggled');
  874. this.bar.classList.remove('hidden');
  875. }
  876. this.findField.select();
  877. this.findField.focus();
  878. },
  879. close: function PDFFindBar_close() {
  880. if (!this.opened) {
  881. return;
  882. }
  883. this.opened = false;
  884. this.toggleButton.classList.remove('toggled');
  885. this.bar.classList.add('hidden');
  886. this.findController.active = false;
  887. },
  888. toggle: function PDFFindBar_toggle() {
  889. if (this.opened) {
  890. this.close();
  891. } else {
  892. this.open();
  893. }
  894. }
  895. };
  896. return PDFFindBar;
  897. })();
  898. var FindStates = {
  899. FIND_FOUND: 0,
  900. FIND_NOTFOUND: 1,
  901. FIND_WRAPPED: 2,
  902. FIND_PENDING: 3
  903. };
  904. /**
  905. * Provides "search" or "find" functionality for the PDF.
  906. * This object actually performs the search for a given string.
  907. */
  908. var PDFFindController = (function PDFFindControllerClosure() {
  909. function PDFFindController(options) {
  910. this.startedTextExtraction = false;
  911. this.extractTextPromises = [];
  912. this.pendingFindMatches = {};
  913. this.active = false; // If active, find results will be highlighted.
  914. this.pageContents = []; // Stores the text for each page.
  915. this.pageMatches = [];
  916. this.selected = { // Currently selected match.
  917. pageIdx: -1,
  918. matchIdx: -1
  919. };
  920. this.offset = { // Where the find algorithm currently is in the document.
  921. pageIdx: null,
  922. matchIdx: null
  923. };
  924. this.pagesToSearch = null;
  925. this.resumePageIdx = null;
  926. this.state = null;
  927. this.dirtyMatch = false;
  928. this.findTimeout = null;
  929. this.pdfViewer = options.pdfViewer || null;
  930. this.integratedFind = options.integratedFind || false;
  931. this.charactersToNormalize = {
  932. '\u2018': '\'', // Left single quotation mark
  933. '\u2019': '\'', // Right single quotation mark
  934. '\u201A': '\'', // Single low-9 quotation mark
  935. '\u201B': '\'', // Single high-reversed-9 quotation mark
  936. '\u201C': '"', // Left double quotation mark
  937. '\u201D': '"', // Right double quotation mark
  938. '\u201E': '"', // Double low-9 quotation mark
  939. '\u201F': '"', // Double high-reversed-9 quotation mark
  940. '\u00BC': '1/4', // Vulgar fraction one quarter
  941. '\u00BD': '1/2', // Vulgar fraction one half
  942. '\u00BE': '3/4' // Vulgar fraction three quarters
  943. };
  944. this.findBar = options.findBar || null;
  945. // Compile the regular expression for text normalization once
  946. var replace = Object.keys(this.charactersToNormalize).join('');
  947. this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
  948. var events = [
  949. 'find',
  950. 'findagain',
  951. 'findhighlightallchange',
  952. 'findcasesensitivitychange'
  953. ];
  954. this.firstPagePromise = new Promise(function (resolve) {
  955. this.resolveFirstPage = resolve;
  956. }.bind(this));
  957. this.handleEvent = this.handleEvent.bind(this);
  958. for (var i = 0, len = events.length; i < len; i++) {
  959. window.addEventListener(events[i], this.handleEvent);
  960. }
  961. }
  962. PDFFindController.prototype = {
  963. setFindBar: function PDFFindController_setFindBar(findBar) {
  964. this.findBar = findBar;
  965. },
  966. reset: function PDFFindController_reset() {
  967. this.startedTextExtraction = false;
  968. this.extractTextPromises = [];
  969. this.active = false;
  970. },
  971. normalize: function PDFFindController_normalize(text) {
  972. var self = this;
  973. return text.replace(this.normalizationRegex, function (ch) {
  974. return self.charactersToNormalize[ch];
  975. });
  976. },
  977. calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) {
  978. var pageContent = this.normalize(this.pageContents[pageIndex]);
  979. var query = this.normalize(this.state.query);
  980. var caseSensitive = this.state.caseSensitive;
  981. var queryLen = query.length;
  982. if (queryLen === 0) {
  983. return; // Do nothing: the matches should be wiped out already.
  984. }
  985. if (!caseSensitive) {
  986. pageContent = pageContent.toLowerCase();
  987. query = query.toLowerCase();
  988. }
  989. var matches = [];
  990. var matchIdx = -queryLen;
  991. while (true) {
  992. matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
  993. if (matchIdx === -1) {
  994. break;
  995. }
  996. matches.push(matchIdx);
  997. }
  998. this.pageMatches[pageIndex] = matches;
  999. this.updatePage(pageIndex);
  1000. if (this.resumePageIdx === pageIndex) {
  1001. this.resumePageIdx = null;
  1002. this.nextPageMatch();
  1003. }
  1004. },
  1005. extractText: function PDFFindController_extractText() {
  1006. if (this.startedTextExtraction) {
  1007. return;
  1008. }
  1009. this.startedTextExtraction = true;
  1010. this.pageContents = [];
  1011. var extractTextPromisesResolves = [];
  1012. var numPages = this.pdfViewer.pagesCount;
  1013. for (var i = 0; i < numPages; i++) {
  1014. this.extractTextPromises.push(new Promise(function (resolve) {
  1015. extractTextPromisesResolves.push(resolve);
  1016. }));
  1017. }
  1018. var self = this;
  1019. function extractPageText(pageIndex) {
  1020. self.pdfViewer.getPageTextContent(pageIndex).then(
  1021. function textContentResolved(textContent) {
  1022. var textItems = textContent.items;
  1023. var str = [];
  1024. for (var i = 0, len = textItems.length; i < len; i++) {
  1025. str.push(textItems[i].str);
  1026. }
  1027. // Store the pageContent as a string.
  1028. self.pageContents.push(str.join(''));
  1029. extractTextPromisesResolves[pageIndex](pageIndex);
  1030. if ((pageIndex + 1) < self.pdfViewer.pagesCount) {
  1031. extractPageText(pageIndex + 1);
  1032. }
  1033. }
  1034. );
  1035. }
  1036. extractPageText(0);
  1037. },
  1038. handleEvent: function PDFFindController_handleEvent(e) {
  1039. if (this.state === null || e.type !== 'findagain') {
  1040. this.dirtyMatch = true;
  1041. }
  1042. this.state = e.detail;
  1043. this.updateUIState(FindStates.FIND_PENDING);
  1044. this.firstPagePromise.then(function() {
  1045. this.extractText();
  1046. clearTimeout(this.findTimeout);
  1047. if (e.type === 'find') {
  1048. // Only trigger the find action after 250ms of silence.
  1049. this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
  1050. } else {
  1051. this.nextMatch();
  1052. }
  1053. }.bind(this));
  1054. },
  1055. updatePage: function PDFFindController_updatePage(index) {
  1056. var page = this.pdfViewer.getPageView(index);
  1057. if (this.selected.pageIdx === index) {
  1058. // If the page is selected, scroll the page into view, which triggers
  1059. // rendering the page, which adds the textLayer. Once the textLayer is
  1060. // build, it will scroll onto the selected match.
  1061. this.pdfViewer.scrollPageIntoView(index + 1);
  1062. }
  1063. if (page.textLayer) {
  1064. page.textLayer.updateMatches();
  1065. }
  1066. },
  1067. nextMatch: function PDFFindController_nextMatch() {
  1068. var previous = this.state.findPrevious;
  1069. var currentPageIndex = this.pdfViewer.currentPageNumber - 1;
  1070. var numPages = this.pdfViewer.pagesCount;
  1071. this.active = true;
  1072. if (this.dirtyMatch) {
  1073. // Need to recalculate the matches, reset everything.
  1074. this.dirtyMatch = false;
  1075. this.selected.pageIdx = this.selected.matchIdx = -1;
  1076. this.offset.pageIdx = currentPageIndex;
  1077. this.offset.matchIdx = null;
  1078. this.hadMatch = false;
  1079. this.resumePageIdx = null;
  1080. this.pageMatches = [];
  1081. var self = this;
  1082. for (var i = 0; i < numPages; i++) {
  1083. // Wipe out any previous highlighted matches.
  1084. this.updatePage(i);
  1085. // As soon as the text is extracted start finding the matches.
  1086. if (!(i in this.pendingFindMatches)) {
  1087. this.pendingFindMatches[i] = true;
  1088. this.extractTextPromises[i].then(function(pageIdx) {
  1089. delete self.pendingFindMatches[pageIdx];
  1090. self.calcFindMatch(pageIdx);
  1091. });
  1092. }
  1093. }
  1094. }
  1095. // If there's no query there's no point in searching.
  1096. if (this.state.query === '') {
  1097. this.updateUIState(FindStates.FIND_FOUND);
  1098. return;
  1099. }
  1100. // If we're waiting on a page, we return since we can't do anything else.
  1101. if (this.resumePageIdx) {
  1102. return;
  1103. }
  1104. var offset = this.offset;
  1105. // Keep track of how many pages we should maximally iterate through.
  1106. this.pagesToSearch = numPages;
  1107. // If there's already a matchIdx that means we are iterating through a
  1108. // page's matches.
  1109. if (offset.matchIdx !== null) {
  1110. var numPageMatches = this.pageMatches[offset.pageIdx].length;
  1111. if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
  1112. (previous && offset.matchIdx > 0)) {
  1113. // The simple case; we just have advance the matchIdx to select
  1114. // the next match on the page.
  1115. this.hadMatch = true;
  1116. offset.matchIdx = (previous ? offset.matchIdx - 1 :
  1117. offset.matchIdx + 1);
  1118. this.updateMatch(true);
  1119. return;
  1120. }
  1121. // We went beyond the current page's matches, so we advance to
  1122. // the next page.
  1123. this.advanceOffsetPage(previous);
  1124. }
  1125. // Start searching through the page.
  1126. this.nextPageMatch();
  1127. },
  1128. matchesReady: function PDFFindController_matchesReady(matches) {
  1129. var offset = this.offset;
  1130. var numMatches = matches.length;
  1131. var previous = this.state.findPrevious;
  1132. if (numMatches) {
  1133. // There were matches for the page, so initialize the matchIdx.
  1134. this.hadMatch = true;
  1135. offset.matchIdx = (previous ? numMatches - 1 : 0);
  1136. this.updateMatch(true);
  1137. return true;
  1138. } else {
  1139. // No matches, so attempt to search the next page.
  1140. this.advanceOffsetPage(previous);
  1141. if (offset.wrapped) {
  1142. offset.matchIdx = null;
  1143. if (this.pagesToSearch < 0) {
  1144. // No point in wrapping again, there were no matches.
  1145. this.updateMatch(false);
  1146. // while matches were not found, searching for a page
  1147. // with matches should nevertheless halt.
  1148. return true;
  1149. }
  1150. }
  1151. // Matches were not found (and searching is not done).
  1152. return false;
  1153. }
  1154. },
  1155. nextPageMatch: function PDFFindController_nextPageMatch() {
  1156. if (this.resumePageIdx !== null) {
  1157. console.error('There can only be one pending page.');
  1158. }
  1159. do {
  1160. var pageIdx = this.offset.pageIdx;
  1161. var matches = this.pageMatches[pageIdx];
  1162. if (!matches) {
  1163. // The matches don't exist yet for processing by "matchesReady",
  1164. // so set a resume point for when they do exist.
  1165. this.resumePageIdx = pageIdx;
  1166. break;
  1167. }
  1168. } while (!this.matchesReady(matches));
  1169. },
  1170. advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) {
  1171. var offset = this.offset;
  1172. var numPages = this.extractTextPromises.length;
  1173. offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1);
  1174. offset.matchIdx = null;
  1175. this.pagesToSearch--;
  1176. if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
  1177. offset.pageIdx = (previous ? numPages - 1 : 0);
  1178. offset.wrapped = true;
  1179. }
  1180. },
  1181. updateMatch: function PDFFindController_updateMatch(found) {
  1182. var state = FindStates.FIND_NOTFOUND;
  1183. var wrapped = this.offset.wrapped;
  1184. this.offset.wrapped = false;
  1185. if (found) {
  1186. var previousPage = this.selected.pageIdx;
  1187. this.selected.pageIdx = this.offset.pageIdx;
  1188. this.selected.matchIdx = this.offset.matchIdx;
  1189. state = (wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND);
  1190. // Update the currently selected page to wipe out any selected matches.
  1191. if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
  1192. this.updatePage(previousPage);
  1193. }
  1194. }
  1195. this.updateUIState(state, this.state.findPrevious);
  1196. if (this.selected.pageIdx !== -1) {
  1197. this.updatePage(this.selected.pageIdx);
  1198. }
  1199. },
  1200. updateUIState: function PDFFindController_updateUIState(state, previous) {
  1201. if (this.integratedFind) {
  1202. FirefoxCom.request('updateFindControlState',
  1203. { result: state, findPrevious: previous });
  1204. return;
  1205. }
  1206. if (this.findBar === null) {
  1207. throw new Error('PDFFindController is not initialized with a ' +
  1208. 'PDFFindBar instance.');
  1209. }
  1210. this.findBar.updateUIState(state, previous);
  1211. }
  1212. };
  1213. return PDFFindController;
  1214. })();
  1215. var PDFHistory = {
  1216. initialized: false,
  1217. initialDestination: null,
  1218. /**
  1219. * @param {string} fingerprint
  1220. * @param {IPDFLinkService} linkService
  1221. */
  1222. initialize: function pdfHistoryInitialize(fingerprint, linkService) {
  1223. this.initialized = true;
  1224. this.reInitialized = false;
  1225. this.allowHashChange = true;
  1226. this.historyUnlocked = true;
  1227. this.previousHash = window.location.hash.substring(1);
  1228. this.currentBookmark = '';
  1229. this.currentPage = 0;
  1230. this.updatePreviousBookmark = false;
  1231. this.previousBookmark = '';
  1232. this.previousPage = 0;
  1233. this.nextHashParam = '';
  1234. this.fingerprint = fingerprint;
  1235. this.linkService = linkService;
  1236. this.currentUid = this.uid = 0;
  1237. this.current = {};
  1238. var state = window.history.state;
  1239. if (this._isStateObjectDefined(state)) {
  1240. // This corresponds to navigating back to the document
  1241. // from another page in the browser history.
  1242. if (state.target.dest) {
  1243. this.initialDestination = state.target.dest;
  1244. } else {
  1245. linkService.setHash(state.target.hash);
  1246. }
  1247. this.currentUid = state.uid;
  1248. this.uid = state.uid + 1;
  1249. this.current = state.target;
  1250. } else {
  1251. // This corresponds to the loading of a new document.
  1252. if (state && state.fingerprint &&
  1253. this.fingerprint !== state.fingerprint) {
  1254. // Reinitialize the browsing history when a new document
  1255. // is opened in the web viewer.
  1256. this.reInitialized = true;
  1257. }
  1258. this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
  1259. }
  1260. var self = this;
  1261. window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
  1262. evt.preventDefault();
  1263. evt.stopPropagation();
  1264. if (!self.historyUnlocked) {
  1265. return;
  1266. }
  1267. if (evt.state) {
  1268. // Move back/forward in the history.
  1269. self._goTo(evt.state);
  1270. } else {
  1271. // Handle the user modifying the hash of a loaded document.
  1272. self.previousHash = window.location.hash.substring(1);
  1273. // If the history is empty when the hash changes,
  1274. // update the previous entry in the browser history.
  1275. if (self.uid === 0) {
  1276. var previousParams = (self.previousHash && self.currentBookmark &&
  1277. self.previousHash !== self.currentBookmark) ?
  1278. { hash: self.currentBookmark, page: self.currentPage } :
  1279. { page: 1 };
  1280. self.historyUnlocked = false;
  1281. self.allowHashChange = false;
  1282. window.history.back();
  1283. self._pushToHistory(previousParams, false, true);
  1284. window.history.forward();
  1285. self.historyUnlocked = true;
  1286. }
  1287. self._pushToHistory({ hash: self.previousHash }, false, true);
  1288. self._updatePreviousBookmark();
  1289. }
  1290. }, false);
  1291. function pdfHistoryBeforeUnload() {
  1292. var previousParams = self._getPreviousParams(null, true);
  1293. if (previousParams) {
  1294. var replacePrevious = (!self.current.dest &&
  1295. self.current.hash !== self.previousHash);
  1296. self._pushToHistory(previousParams, false, replacePrevious);
  1297. self._updatePreviousBookmark();
  1298. }
  1299. // Remove the event listener when navigating away from the document,
  1300. // since 'beforeunload' prevents Firefox from caching the document.
  1301. window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false);
  1302. }
  1303. window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
  1304. window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
  1305. // If the entire viewer (including the PDF file) is cached in the browser,
  1306. // we need to reattach the 'beforeunload' event listener since
  1307. // the 'DOMContentLoaded' event is not fired on 'pageshow'.
  1308. window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
  1309. }, false);
  1310. },
  1311. _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
  1312. return (state && state.uid >= 0 &&
  1313. state.fingerprint && this.fingerprint === state.fingerprint &&
  1314. state.target && state.target.hash) ? true : false;
  1315. },
  1316. _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
  1317. replace) {
  1318. if (replace) {
  1319. window.history.replaceState(stateObj, '', document.URL);
  1320. } else {
  1321. window.history.pushState(stateObj, '', document.URL);
  1322. }
  1323. },
  1324. get isHashChangeUnlocked() {
  1325. if (!this.initialized) {
  1326. return true;
  1327. }
  1328. // If the current hash changes when moving back/forward in the history,
  1329. // this will trigger a 'popstate' event *as well* as a 'hashchange' event.
  1330. // Since the hash generally won't correspond to the exact the position
  1331. // stored in the history's state object, triggering the 'hashchange' event
  1332. // can thus corrupt the browser history.
  1333. //
  1334. // When the hash changes during a 'popstate' event, we *only* prevent the
  1335. // first 'hashchange' event and immediately reset allowHashChange.
  1336. // If it is not reset, the user would not be able to change the hash.
  1337. var temp = this.allowHashChange;
  1338. this.allowHashChange = true;
  1339. return temp;
  1340. },
  1341. _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
  1342. if (this.updatePreviousBookmark &&
  1343. this.currentBookmark && this.currentPage) {
  1344. this.previousBookmark = this.currentBookmark;
  1345. this.previousPage = this.currentPage;
  1346. this.updatePreviousBookmark = false;
  1347. }
  1348. },
  1349. updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
  1350. pageNum) {
  1351. if (this.initialized) {
  1352. this.currentBookmark = bookmark.substring(1);
  1353. this.currentPage = pageNum | 0;
  1354. this._updatePreviousBookmark();
  1355. }
  1356. },
  1357. updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
  1358. if (this.initialized) {
  1359. this.nextHashParam = param;
  1360. }
  1361. },
  1362. push: function pdfHistoryPush(params, isInitialBookmark) {
  1363. if (!(this.initialized && this.historyUnlocked)) {
  1364. return;
  1365. }
  1366. if (params.dest && !params.

Large files files are truncated, but you can click here to view the full file