pdf_history.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. 'use strict';
  16. var domEvents = require('./dom_events.js');
  17. function PDFHistory(options) {
  18. this.linkService = options.linkService;
  19. this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
  20. this.initialized = false;
  21. this.initialDestination = null;
  22. this.initialBookmark = null;
  23. }
  24. PDFHistory.prototype = {
  25. initialize: function pdfHistoryInitialize(fingerprint) {
  26. this.initialized = true;
  27. this.reInitialized = false;
  28. this.allowHashChange = true;
  29. this.historyUnlocked = true;
  30. this.isViewerInPresentationMode = false;
  31. this.previousHash = window.location.hash.substring(1);
  32. this.currentBookmark = '';
  33. this.currentPage = 0;
  34. this.updatePreviousBookmark = false;
  35. this.previousBookmark = '';
  36. this.previousPage = 0;
  37. this.nextHashParam = '';
  38. this.fingerprint = fingerprint;
  39. this.currentUid = this.uid = 0;
  40. this.current = {};
  41. var state = window.history.state;
  42. if (this._isStateObjectDefined(state)) {
  43. if (state.target.dest) {
  44. this.initialDestination = state.target.dest;
  45. } else {
  46. this.initialBookmark = state.target.hash;
  47. }
  48. this.currentUid = state.uid;
  49. this.uid = state.uid + 1;
  50. this.current = state.target;
  51. } else {
  52. if (state && state.fingerprint && this.fingerprint !== state.fingerprint) {
  53. this.reInitialized = true;
  54. }
  55. this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
  56. }
  57. var self = this;
  58. window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
  59. if (!self.historyUnlocked) {
  60. return;
  61. }
  62. if (evt.state) {
  63. self._goTo(evt.state);
  64. return;
  65. }
  66. if (self.uid === 0) {
  67. var previousParams = self.previousHash && self.currentBookmark && self.previousHash !== self.currentBookmark ? {
  68. hash: self.currentBookmark,
  69. page: self.currentPage
  70. } : { page: 1 };
  71. replacePreviousHistoryState(previousParams, function () {
  72. updateHistoryWithCurrentHash();
  73. });
  74. } else {
  75. updateHistoryWithCurrentHash();
  76. }
  77. });
  78. function updateHistoryWithCurrentHash() {
  79. self.previousHash = window.location.hash.slice(1);
  80. self._pushToHistory({ hash: self.previousHash }, false, true);
  81. self._updatePreviousBookmark();
  82. }
  83. function replacePreviousHistoryState(params, callback) {
  84. self.historyUnlocked = false;
  85. self.allowHashChange = false;
  86. window.addEventListener('popstate', rewriteHistoryAfterBack);
  87. history.back();
  88. function rewriteHistoryAfterBack() {
  89. window.removeEventListener('popstate', rewriteHistoryAfterBack);
  90. window.addEventListener('popstate', rewriteHistoryAfterForward);
  91. self._pushToHistory(params, false, true);
  92. history.forward();
  93. }
  94. function rewriteHistoryAfterForward() {
  95. window.removeEventListener('popstate', rewriteHistoryAfterForward);
  96. self.allowHashChange = true;
  97. self.historyUnlocked = true;
  98. callback();
  99. }
  100. }
  101. function pdfHistoryBeforeUnload() {
  102. var previousParams = self._getPreviousParams(null, true);
  103. if (previousParams) {
  104. var replacePrevious = !self.current.dest && self.current.hash !== self.previousHash;
  105. self._pushToHistory(previousParams, false, replacePrevious);
  106. self._updatePreviousBookmark();
  107. }
  108. window.removeEventListener('beforeunload', pdfHistoryBeforeUnload);
  109. }
  110. window.addEventListener('beforeunload', pdfHistoryBeforeUnload);
  111. window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
  112. window.addEventListener('beforeunload', pdfHistoryBeforeUnload);
  113. });
  114. self.eventBus.on('presentationmodechanged', function (e) {
  115. self.isViewerInPresentationMode = e.active;
  116. });
  117. },
  118. clearHistoryState: function pdfHistory_clearHistoryState() {
  119. this._pushOrReplaceState(null, true);
  120. },
  121. _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
  122. return state && state.uid >= 0 && state.fingerprint && this.fingerprint === state.fingerprint && state.target && state.target.hash ? true : false;
  123. },
  124. _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj, replace) {
  125. if (replace) {
  126. window.history.replaceState(stateObj, '', document.URL);
  127. } else {
  128. window.history.pushState(stateObj, '', document.URL);
  129. }
  130. },
  131. get isHashChangeUnlocked() {
  132. if (!this.initialized) {
  133. return true;
  134. }
  135. return this.allowHashChange;
  136. },
  137. _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
  138. if (this.updatePreviousBookmark && this.currentBookmark && this.currentPage) {
  139. this.previousBookmark = this.currentBookmark;
  140. this.previousPage = this.currentPage;
  141. this.updatePreviousBookmark = false;
  142. }
  143. },
  144. updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark, pageNum) {
  145. if (this.initialized) {
  146. this.currentBookmark = bookmark.substring(1);
  147. this.currentPage = pageNum | 0;
  148. this._updatePreviousBookmark();
  149. }
  150. },
  151. updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
  152. if (this.initialized) {
  153. this.nextHashParam = param;
  154. }
  155. },
  156. push: function pdfHistoryPush(params, isInitialBookmark) {
  157. if (!(this.initialized && this.historyUnlocked)) {
  158. return;
  159. }
  160. if (params.dest && !params.hash) {
  161. params.hash = this.current.hash && this.current.dest && this.current.dest === params.dest ? this.current.hash : this.linkService.getDestinationHash(params.dest).split('#')[1];
  162. }
  163. if (params.page) {
  164. params.page |= 0;
  165. }
  166. if (isInitialBookmark) {
  167. var target = window.history.state.target;
  168. if (!target) {
  169. this._pushToHistory(params, false);
  170. this.previousHash = window.location.hash.substring(1);
  171. }
  172. this.updatePreviousBookmark = this.nextHashParam ? false : true;
  173. if (target) {
  174. this._updatePreviousBookmark();
  175. }
  176. return;
  177. }
  178. if (this.nextHashParam) {
  179. if (this.nextHashParam === params.hash) {
  180. this.nextHashParam = null;
  181. this.updatePreviousBookmark = true;
  182. return;
  183. }
  184. this.nextHashParam = null;
  185. }
  186. if (params.hash) {
  187. if (this.current.hash) {
  188. if (this.current.hash !== params.hash) {
  189. this._pushToHistory(params, true);
  190. } else {
  191. if (!this.current.page && params.page) {
  192. this._pushToHistory(params, false, true);
  193. }
  194. this.updatePreviousBookmark = true;
  195. }
  196. } else {
  197. this._pushToHistory(params, true);
  198. }
  199. } else if (this.current.page && params.page && this.current.page !== params.page) {
  200. this._pushToHistory(params, true);
  201. }
  202. },
  203. _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage, beforeUnload) {
  204. if (!(this.currentBookmark && this.currentPage)) {
  205. return null;
  206. } else if (this.updatePreviousBookmark) {
  207. this.updatePreviousBookmark = false;
  208. }
  209. if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
  210. return null;
  211. }
  212. if (!this.current.dest && !onlyCheckPage || beforeUnload) {
  213. if (this.previousBookmark === this.currentBookmark) {
  214. return null;
  215. }
  216. } else if (this.current.page || onlyCheckPage) {
  217. if (this.previousPage === this.currentPage) {
  218. return null;
  219. }
  220. } else {
  221. return null;
  222. }
  223. var params = {
  224. hash: this.currentBookmark,
  225. page: this.currentPage
  226. };
  227. if (this.isViewerInPresentationMode) {
  228. params.hash = null;
  229. }
  230. return params;
  231. },
  232. _stateObj: function pdfHistory_stateObj(params) {
  233. return {
  234. fingerprint: this.fingerprint,
  235. uid: this.uid,
  236. target: params
  237. };
  238. },
  239. _pushToHistory: function pdfHistory_pushToHistory(params, addPrevious, overwrite) {
  240. if (!this.initialized) {
  241. return;
  242. }
  243. if (!params.hash && params.page) {
  244. params.hash = 'page=' + params.page;
  245. }
  246. if (addPrevious && !overwrite) {
  247. var previousParams = this._getPreviousParams();
  248. if (previousParams) {
  249. var replacePrevious = !this.current.dest && this.current.hash !== this.previousHash;
  250. this._pushToHistory(previousParams, false, replacePrevious);
  251. }
  252. }
  253. this._pushOrReplaceState(this._stateObj(params), overwrite || this.uid === 0);
  254. this.currentUid = this.uid++;
  255. this.current = params;
  256. this.updatePreviousBookmark = true;
  257. },
  258. _goTo: function pdfHistory_goTo(state) {
  259. if (!(this.initialized && this.historyUnlocked && this._isStateObjectDefined(state))) {
  260. return;
  261. }
  262. if (!this.reInitialized && state.uid < this.currentUid) {
  263. var previousParams = this._getPreviousParams(true);
  264. if (previousParams) {
  265. this._pushToHistory(this.current, false);
  266. this._pushToHistory(previousParams, false);
  267. this.currentUid = state.uid;
  268. window.history.back();
  269. return;
  270. }
  271. }
  272. this.historyUnlocked = false;
  273. if (state.target.dest) {
  274. this.linkService.navigateTo(state.target.dest);
  275. } else {
  276. this.linkService.setHash(state.target.hash);
  277. }
  278. this.currentUid = state.uid;
  279. if (state.uid > this.uid) {
  280. this.uid = state.uid;
  281. }
  282. this.current = state.target;
  283. this.updatePreviousBookmark = true;
  284. var currentHash = window.location.hash.substring(1);
  285. if (this.previousHash !== currentHash) {
  286. this.allowHashChange = false;
  287. }
  288. this.previousHash = currentHash;
  289. this.historyUnlocked = true;
  290. },
  291. back: function pdfHistoryBack() {
  292. this.go(-1);
  293. },
  294. forward: function pdfHistoryForward() {
  295. this.go(1);
  296. },
  297. go: function pdfHistoryGo(direction) {
  298. if (this.initialized && this.historyUnlocked) {
  299. var state = window.history.state;
  300. if (direction === -1 && state && state.uid > 0) {
  301. window.history.back();
  302. } else if (direction === 1 && state && state.uid < this.uid - 1) {
  303. window.history.forward();
  304. }
  305. }
  306. }
  307. };
  308. exports.PDFHistory = PDFHistory;