app.js 94 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041
  1. /* Copyright 2012 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. import {
  16. animationStarted,
  17. apiPageLayoutToViewerModes,
  18. apiPageModeToSidebarView,
  19. AutoPrintRegExp,
  20. DEFAULT_SCALE_VALUE,
  21. getActiveOrFocusedElement,
  22. isValidRotation,
  23. isValidScrollMode,
  24. isValidSpreadMode,
  25. normalizeWheelEventDirection,
  26. parseQueryString,
  27. ProgressBar,
  28. RendererType,
  29. RenderingStates,
  30. ScrollMode,
  31. SidebarView,
  32. SpreadMode,
  33. TextLayerMode,
  34. } from "./ui_utils.js";
  35. import {
  36. AnnotationEditorType,
  37. build,
  38. createPromiseCapability,
  39. getDocument,
  40. getFilenameFromUrl,
  41. getPdfFilenameFromUrl,
  42. GlobalWorkerOptions,
  43. InvalidPDFException,
  44. isDataScheme,
  45. isPdfFile,
  46. loadScript,
  47. MissingPDFException,
  48. OPS,
  49. PDFWorker,
  50. shadow,
  51. UnexpectedResponseException,
  52. version,
  53. } from "pdfjs-lib";
  54. import { AppOptions, OptionKind } from "./app_options.js";
  55. import { AutomationEventBus, EventBus } from "./event_utils.js";
  56. import { CursorTool, PDFCursorTools } from "./pdf_cursor_tools.js";
  57. import { LinkTarget, PDFLinkService } from "./pdf_link_service.js";
  58. import { AnnotationEditorParams } from "./annotation_editor_params.js";
  59. import { OverlayManager } from "./overlay_manager.js";
  60. import { PasswordPrompt } from "./password_prompt.js";
  61. import { PDFAttachmentViewer } from "./pdf_attachment_viewer.js";
  62. import { PDFDocumentProperties } from "./pdf_document_properties.js";
  63. import { PDFFindBar } from "./pdf_find_bar.js";
  64. import { PDFFindController } from "./pdf_find_controller.js";
  65. import { PDFHistory } from "./pdf_history.js";
  66. import { PDFLayerViewer } from "./pdf_layer_viewer.js";
  67. import { PDFOutlineViewer } from "./pdf_outline_viewer.js";
  68. import { PDFPresentationMode } from "./pdf_presentation_mode.js";
  69. import { PDFRenderingQueue } from "./pdf_rendering_queue.js";
  70. import { PDFScriptingManager } from "./pdf_scripting_manager.js";
  71. import { PDFSidebar } from "./pdf_sidebar.js";
  72. import { PDFSidebarResizer } from "./pdf_sidebar_resizer.js";
  73. import { PDFThumbnailViewer } from "./pdf_thumbnail_viewer.js";
  74. import { PDFViewer } from "./pdf_viewer.js";
  75. import { SecondaryToolbar } from "./secondary_toolbar.js";
  76. import { Toolbar } from "./toolbar.js";
  77. import { ViewHistory } from "./view_history.js";
  78. const DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000; // ms
  79. const FORCE_PAGES_LOADED_TIMEOUT = 10000; // ms
  80. const WHEEL_ZOOM_DISABLED_TIMEOUT = 1000; // ms
  81. const ViewOnLoad = {
  82. UNKNOWN: -1,
  83. PREVIOUS: 0, // Default value.
  84. INITIAL: 1,
  85. };
  86. const ViewerCssTheme = {
  87. AUTOMATIC: 0, // Default value.
  88. LIGHT: 1,
  89. DARK: 2,
  90. };
  91. class DefaultExternalServices {
  92. constructor() {
  93. throw new Error("Cannot initialize DefaultExternalServices.");
  94. }
  95. static updateFindControlState(data) {}
  96. static updateFindMatchesCount(data) {}
  97. static initPassiveLoading(callbacks) {}
  98. static reportTelemetry(data) {}
  99. static createDownloadManager() {
  100. throw new Error("Not implemented: createDownloadManager");
  101. }
  102. static createPreferences() {
  103. throw new Error("Not implemented: createPreferences");
  104. }
  105. static createL10n(options) {
  106. throw new Error("Not implemented: createL10n");
  107. }
  108. static createScripting(options) {
  109. throw new Error("Not implemented: createScripting");
  110. }
  111. static get supportsIntegratedFind() {
  112. return shadow(this, "supportsIntegratedFind", false);
  113. }
  114. static get supportsDocumentFonts() {
  115. return shadow(this, "supportsDocumentFonts", true);
  116. }
  117. static get supportedMouseWheelZoomModifierKeys() {
  118. return shadow(this, "supportedMouseWheelZoomModifierKeys", {
  119. ctrlKey: true,
  120. metaKey: true,
  121. });
  122. }
  123. static get isInAutomation() {
  124. return shadow(this, "isInAutomation", false);
  125. }
  126. static updateEditorStates(data) {
  127. throw new Error("Not implemented: updateEditorStates");
  128. }
  129. }
  130. const PDFViewerApplication = {
  131. initialBookmark: document.location.hash.substring(1),
  132. _initializedCapability: createPromiseCapability(),
  133. appConfig: null,
  134. pdfDocument: null,
  135. pdfLoadingTask: null,
  136. printService: null,
  137. /** @type {PDFViewer} */
  138. pdfViewer: null,
  139. /** @type {PDFThumbnailViewer} */
  140. pdfThumbnailViewer: null,
  141. /** @type {PDFRenderingQueue} */
  142. pdfRenderingQueue: null,
  143. /** @type {PDFPresentationMode} */
  144. pdfPresentationMode: null,
  145. /** @type {PDFDocumentProperties} */
  146. pdfDocumentProperties: null,
  147. /** @type {PDFLinkService} */
  148. pdfLinkService: null,
  149. /** @type {PDFHistory} */
  150. pdfHistory: null,
  151. /** @type {PDFSidebar} */
  152. pdfSidebar: null,
  153. /** @type {PDFSidebarResizer} */
  154. pdfSidebarResizer: null,
  155. /** @type {PDFOutlineViewer} */
  156. pdfOutlineViewer: null,
  157. /** @type {PDFAttachmentViewer} */
  158. pdfAttachmentViewer: null,
  159. /** @type {PDFLayerViewer} */
  160. pdfLayerViewer: null,
  161. /** @type {PDFCursorTools} */
  162. pdfCursorTools: null,
  163. /** @type {PDFScriptingManager} */
  164. pdfScriptingManager: null,
  165. /** @type {ViewHistory} */
  166. store: null,
  167. /** @type {DownloadManager} */
  168. downloadManager: null,
  169. /** @type {OverlayManager} */
  170. overlayManager: null,
  171. /** @type {Preferences} */
  172. preferences: null,
  173. /** @type {Toolbar} */
  174. toolbar: null,
  175. /** @type {SecondaryToolbar} */
  176. secondaryToolbar: null,
  177. /** @type {EventBus} */
  178. eventBus: null,
  179. /** @type {IL10n} */
  180. l10n: null,
  181. /** @type {AnnotationEditorParams} */
  182. annotationEditorParams: null,
  183. isInitialViewSet: false,
  184. downloadComplete: false,
  185. isViewerEmbedded: window.parent !== window,
  186. url: "",
  187. baseUrl: "",
  188. _downloadUrl: "",
  189. externalServices: DefaultExternalServices,
  190. _boundEvents: Object.create(null),
  191. documentInfo: null,
  192. metadata: null,
  193. _contentDispositionFilename: null,
  194. _contentLength: null,
  195. _saveInProgress: false,
  196. _wheelUnusedTicks: 0,
  197. _PDFBug: null,
  198. _hasAnnotationEditors: false,
  199. _title: document.title,
  200. _printAnnotationStoragePromise: null,
  201. // Called once when the document is loaded.
  202. async initialize(appConfig) {
  203. this.preferences = this.externalServices.createPreferences();
  204. this.appConfig = appConfig;
  205. await this._readPreferences();
  206. await this._parseHashParameters();
  207. this._forceCssTheme();
  208. await this._initializeL10n();
  209. if (
  210. this.isViewerEmbedded &&
  211. AppOptions.get("externalLinkTarget") === LinkTarget.NONE
  212. ) {
  213. // Prevent external links from "replacing" the viewer,
  214. // when it's embedded in e.g. an <iframe> or an <object>.
  215. AppOptions.set("externalLinkTarget", LinkTarget.TOP);
  216. }
  217. await this._initializeViewerComponents();
  218. // Bind the various event handlers *after* the viewer has been
  219. // initialized, to prevent errors if an event arrives too soon.
  220. this.bindEvents();
  221. this.bindWindowEvents();
  222. // We can start UI localization now.
  223. const appContainer = appConfig.appContainer || document.documentElement;
  224. this.l10n.translate(appContainer).then(() => {
  225. // Dispatch the 'localized' event on the `eventBus` once the viewer
  226. // has been fully initialized and translated.
  227. this.eventBus.dispatch("localized", { source: this });
  228. });
  229. this._initializedCapability.resolve();
  230. },
  231. /**
  232. * @private
  233. */
  234. async _readPreferences() {
  235. if (
  236. typeof PDFJSDev === "undefined" ||
  237. PDFJSDev.test("!PRODUCTION || GENERIC")
  238. ) {
  239. if (AppOptions.get("disablePreferences")) {
  240. // Give custom implementations of the default viewer a simpler way to
  241. // opt-out of having the `Preferences` override existing `AppOptions`.
  242. return;
  243. }
  244. if (AppOptions._hasUserOptions()) {
  245. console.warn(
  246. "_readPreferences: The Preferences may override manually set AppOptions; " +
  247. 'please use the "disablePreferences"-option in order to prevent that.'
  248. );
  249. }
  250. }
  251. try {
  252. AppOptions.setAll(await this.preferences.getAll());
  253. } catch (reason) {
  254. console.error(`_readPreferences: "${reason?.message}".`);
  255. }
  256. },
  257. /**
  258. * Potentially parse special debugging flags in the hash section of the URL.
  259. * @private
  260. */
  261. async _parseHashParameters() {
  262. if (!AppOptions.get("pdfBugEnabled")) {
  263. return;
  264. }
  265. const hash = document.location.hash.substring(1);
  266. if (!hash) {
  267. return;
  268. }
  269. const { mainContainer, viewerContainer } = this.appConfig,
  270. params = parseQueryString(hash);
  271. if (params.get("disableworker") === "true") {
  272. try {
  273. await loadFakeWorker();
  274. } catch (ex) {
  275. console.error(`_parseHashParameters: "${ex.message}".`);
  276. }
  277. }
  278. if (params.has("disablerange")) {
  279. AppOptions.set("disableRange", params.get("disablerange") === "true");
  280. }
  281. if (params.has("disablestream")) {
  282. AppOptions.set("disableStream", params.get("disablestream") === "true");
  283. }
  284. if (params.has("disableautofetch")) {
  285. AppOptions.set(
  286. "disableAutoFetch",
  287. params.get("disableautofetch") === "true"
  288. );
  289. }
  290. if (params.has("disablefontface")) {
  291. AppOptions.set(
  292. "disableFontFace",
  293. params.get("disablefontface") === "true"
  294. );
  295. }
  296. if (params.has("disablehistory")) {
  297. AppOptions.set("disableHistory", params.get("disablehistory") === "true");
  298. }
  299. if (params.has("verbosity")) {
  300. AppOptions.set("verbosity", params.get("verbosity") | 0);
  301. }
  302. if (params.has("textlayer")) {
  303. switch (params.get("textlayer")) {
  304. case "off":
  305. AppOptions.set("textLayerMode", TextLayerMode.DISABLE);
  306. break;
  307. case "visible":
  308. case "shadow":
  309. case "hover":
  310. viewerContainer.classList.add(`textLayer-${params.get("textlayer")}`);
  311. try {
  312. await loadPDFBug(this);
  313. this._PDFBug.loadCSS();
  314. } catch (ex) {
  315. console.error(`_parseHashParameters: "${ex.message}".`);
  316. }
  317. break;
  318. }
  319. }
  320. if (params.has("pdfbug")) {
  321. AppOptions.set("pdfBug", true);
  322. AppOptions.set("fontExtraProperties", true);
  323. const enabled = params.get("pdfbug").split(",");
  324. try {
  325. await loadPDFBug(this);
  326. this._PDFBug.init({ OPS }, mainContainer, enabled);
  327. } catch (ex) {
  328. console.error(`_parseHashParameters: "${ex.message}".`);
  329. }
  330. }
  331. // It is not possible to change locale for the (various) extension builds.
  332. if (
  333. (typeof PDFJSDev === "undefined" ||
  334. PDFJSDev.test("!PRODUCTION || GENERIC")) &&
  335. params.has("locale")
  336. ) {
  337. AppOptions.set("locale", params.get("locale"));
  338. }
  339. },
  340. /**
  341. * @private
  342. */
  343. async _initializeL10n() {
  344. this.l10n = this.externalServices.createL10n(
  345. typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || GENERIC")
  346. ? { locale: AppOptions.get("locale") }
  347. : null
  348. );
  349. const dir = await this.l10n.getDirection();
  350. document.getElementsByTagName("html")[0].dir = dir;
  351. },
  352. /**
  353. * @private
  354. */
  355. _forceCssTheme() {
  356. const cssTheme = AppOptions.get("viewerCssTheme");
  357. if (
  358. cssTheme === ViewerCssTheme.AUTOMATIC ||
  359. !Object.values(ViewerCssTheme).includes(cssTheme)
  360. ) {
  361. return;
  362. }
  363. try {
  364. const styleSheet = document.styleSheets[0];
  365. const cssRules = styleSheet?.cssRules || [];
  366. for (let i = 0, ii = cssRules.length; i < ii; i++) {
  367. const rule = cssRules[i];
  368. if (
  369. rule instanceof CSSMediaRule &&
  370. rule.media?.[0] === "(prefers-color-scheme: dark)"
  371. ) {
  372. if (cssTheme === ViewerCssTheme.LIGHT) {
  373. styleSheet.deleteRule(i);
  374. return;
  375. }
  376. // cssTheme === ViewerCssTheme.DARK
  377. const darkRules =
  378. /^@media \(prefers-color-scheme: dark\) {\n\s*([\w\s-.,:;/\\{}()]+)\n}$/.exec(
  379. rule.cssText
  380. );
  381. if (darkRules?.[1]) {
  382. styleSheet.deleteRule(i);
  383. styleSheet.insertRule(darkRules[1], i);
  384. }
  385. return;
  386. }
  387. }
  388. } catch (reason) {
  389. console.error(`_forceCssTheme: "${reason?.message}".`);
  390. }
  391. },
  392. /**
  393. * @private
  394. */
  395. async _initializeViewerComponents() {
  396. const { appConfig, externalServices } = this;
  397. const eventBus = externalServices.isInAutomation
  398. ? new AutomationEventBus()
  399. : new EventBus();
  400. this.eventBus = eventBus;
  401. this.overlayManager = new OverlayManager();
  402. const pdfRenderingQueue = new PDFRenderingQueue();
  403. pdfRenderingQueue.onIdle = this._cleanup.bind(this);
  404. this.pdfRenderingQueue = pdfRenderingQueue;
  405. const pdfLinkService = new PDFLinkService({
  406. eventBus,
  407. externalLinkTarget: AppOptions.get("externalLinkTarget"),
  408. externalLinkRel: AppOptions.get("externalLinkRel"),
  409. ignoreDestinationZoom: AppOptions.get("ignoreDestinationZoom"),
  410. });
  411. this.pdfLinkService = pdfLinkService;
  412. const downloadManager = externalServices.createDownloadManager();
  413. this.downloadManager = downloadManager;
  414. const findController = new PDFFindController({
  415. linkService: pdfLinkService,
  416. eventBus,
  417. });
  418. this.findController = findController;
  419. const pdfScriptingManager = new PDFScriptingManager({
  420. eventBus,
  421. sandboxBundleSrc:
  422. typeof PDFJSDev === "undefined" ||
  423. PDFJSDev.test("!PRODUCTION || GENERIC || CHROME")
  424. ? AppOptions.get("sandboxBundleSrc")
  425. : null,
  426. scriptingFactory: externalServices,
  427. docPropertiesLookup: this._scriptingDocProperties.bind(this),
  428. });
  429. this.pdfScriptingManager = pdfScriptingManager;
  430. const container = appConfig.mainContainer,
  431. viewer = appConfig.viewerContainer;
  432. const annotationEditorMode = AppOptions.get("annotationEditorMode");
  433. const pageColors =
  434. AppOptions.get("forcePageColors") ||
  435. window.matchMedia("(forced-colors: active)").matches
  436. ? {
  437. background: AppOptions.get("pageColorsBackground"),
  438. foreground: AppOptions.get("pageColorsForeground"),
  439. }
  440. : null;
  441. this.pdfViewer = new PDFViewer({
  442. container,
  443. viewer,
  444. eventBus,
  445. renderingQueue: pdfRenderingQueue,
  446. linkService: pdfLinkService,
  447. downloadManager,
  448. findController,
  449. scriptingManager:
  450. AppOptions.get("enableScripting") && pdfScriptingManager,
  451. renderer:
  452. typeof PDFJSDev === "undefined" ||
  453. PDFJSDev.test("!PRODUCTION || GENERIC")
  454. ? AppOptions.get("renderer")
  455. : null,
  456. l10n: this.l10n,
  457. textLayerMode: AppOptions.get("textLayerMode"),
  458. annotationMode: AppOptions.get("annotationMode"),
  459. annotationEditorMode,
  460. imageResourcesPath: AppOptions.get("imageResourcesPath"),
  461. enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
  462. useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
  463. isOffscreenCanvasSupported: AppOptions.get("isOffscreenCanvasSupported"),
  464. maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
  465. enablePermissions: AppOptions.get("enablePermissions"),
  466. pageColors,
  467. });
  468. pdfRenderingQueue.setViewer(this.pdfViewer);
  469. pdfLinkService.setViewer(this.pdfViewer);
  470. pdfScriptingManager.setViewer(this.pdfViewer);
  471. if (appConfig.sidebar?.thumbnailView) {
  472. this.pdfThumbnailViewer = new PDFThumbnailViewer({
  473. container: appConfig.sidebar.thumbnailView,
  474. eventBus,
  475. renderingQueue: pdfRenderingQueue,
  476. linkService: pdfLinkService,
  477. l10n: this.l10n,
  478. pageColors,
  479. });
  480. pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
  481. }
  482. // The browsing history is only enabled when the viewer is standalone,
  483. // i.e. not when it is embedded in a web page.
  484. if (!this.isViewerEmbedded && !AppOptions.get("disableHistory")) {
  485. this.pdfHistory = new PDFHistory({
  486. linkService: pdfLinkService,
  487. eventBus,
  488. });
  489. pdfLinkService.setHistory(this.pdfHistory);
  490. }
  491. if (!this.supportsIntegratedFind && appConfig.findBar) {
  492. this.findBar = new PDFFindBar(appConfig.findBar, eventBus, this.l10n);
  493. }
  494. if (appConfig.annotationEditorParams) {
  495. if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
  496. this.annotationEditorParams = new AnnotationEditorParams(
  497. appConfig.annotationEditorParams,
  498. eventBus
  499. );
  500. } else {
  501. for (const id of ["editorModeButtons", "editorModeSeparator"]) {
  502. document.getElementById(id)?.classList.add("hidden");
  503. }
  504. }
  505. }
  506. if (appConfig.documentProperties) {
  507. this.pdfDocumentProperties = new PDFDocumentProperties(
  508. appConfig.documentProperties,
  509. this.overlayManager,
  510. eventBus,
  511. this.l10n,
  512. /* fileNameLookup = */ () => {
  513. return this._docFilename;
  514. }
  515. );
  516. }
  517. this.pdfCursorTools = new PDFCursorTools({
  518. container,
  519. eventBus,
  520. cursorToolOnLoad: AppOptions.get("cursorToolOnLoad"),
  521. });
  522. if (appConfig.toolbar) {
  523. this.toolbar = new Toolbar(appConfig.toolbar, eventBus, this.l10n);
  524. }
  525. if (appConfig.secondaryToolbar) {
  526. this.secondaryToolbar = new SecondaryToolbar(
  527. appConfig.secondaryToolbar,
  528. eventBus,
  529. this.externalServices
  530. );
  531. }
  532. if (this.supportsFullscreen) {
  533. this.pdfPresentationMode = new PDFPresentationMode({
  534. container,
  535. pdfViewer: this.pdfViewer,
  536. eventBus,
  537. });
  538. }
  539. if (appConfig.passwordOverlay) {
  540. this.passwordPrompt = new PasswordPrompt(
  541. appConfig.passwordOverlay,
  542. this.overlayManager,
  543. this.l10n,
  544. this.isViewerEmbedded
  545. );
  546. }
  547. if (appConfig.sidebar?.outlineView) {
  548. this.pdfOutlineViewer = new PDFOutlineViewer({
  549. container: appConfig.sidebar.outlineView,
  550. eventBus,
  551. linkService: pdfLinkService,
  552. downloadManager,
  553. });
  554. }
  555. if (appConfig.sidebar?.attachmentsView) {
  556. this.pdfAttachmentViewer = new PDFAttachmentViewer({
  557. container: appConfig.sidebar.attachmentsView,
  558. eventBus,
  559. downloadManager,
  560. });
  561. }
  562. if (appConfig.sidebar?.layersView) {
  563. this.pdfLayerViewer = new PDFLayerViewer({
  564. container: appConfig.sidebar.layersView,
  565. eventBus,
  566. l10n: this.l10n,
  567. });
  568. }
  569. if (appConfig.sidebar) {
  570. this.pdfSidebar = new PDFSidebar({
  571. elements: appConfig.sidebar,
  572. pdfViewer: this.pdfViewer,
  573. pdfThumbnailViewer: this.pdfThumbnailViewer,
  574. eventBus,
  575. l10n: this.l10n,
  576. });
  577. this.pdfSidebar.onToggled = this.forceRendering.bind(this);
  578. this.pdfSidebarResizer = new PDFSidebarResizer(
  579. appConfig.sidebarResizer,
  580. eventBus,
  581. this.l10n
  582. );
  583. }
  584. },
  585. run(config) {
  586. this.initialize(config).then(webViewerInitialized);
  587. },
  588. get initialized() {
  589. return this._initializedCapability.settled;
  590. },
  591. get initializedPromise() {
  592. return this._initializedCapability.promise;
  593. },
  594. zoomIn(steps) {
  595. if (this.pdfViewer.isInPresentationMode) {
  596. return;
  597. }
  598. this.pdfViewer.increaseScale(steps, {
  599. delay: AppOptions.get("defaultZoomDelay"),
  600. });
  601. },
  602. zoomOut(steps) {
  603. if (this.pdfViewer.isInPresentationMode) {
  604. return;
  605. }
  606. this.pdfViewer.decreaseScale(steps, {
  607. delay: AppOptions.get("defaultZoomDelay"),
  608. });
  609. },
  610. zoomReset() {
  611. if (this.pdfViewer.isInPresentationMode) {
  612. return;
  613. }
  614. this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
  615. },
  616. get pagesCount() {
  617. return this.pdfDocument ? this.pdfDocument.numPages : 0;
  618. },
  619. get page() {
  620. return this.pdfViewer.currentPageNumber;
  621. },
  622. set page(val) {
  623. this.pdfViewer.currentPageNumber = val;
  624. },
  625. get supportsPrinting() {
  626. return PDFPrintServiceFactory.instance.supportsPrinting;
  627. },
  628. get supportsFullscreen() {
  629. return shadow(this, "supportsFullscreen", document.fullscreenEnabled);
  630. },
  631. get supportsIntegratedFind() {
  632. return this.externalServices.supportsIntegratedFind;
  633. },
  634. get supportsDocumentFonts() {
  635. return this.externalServices.supportsDocumentFonts;
  636. },
  637. get loadingBar() {
  638. const barElement = document.getElementById("loadingBar");
  639. const bar = barElement ? new ProgressBar(barElement) : null;
  640. return shadow(this, "loadingBar", bar);
  641. },
  642. get supportedMouseWheelZoomModifierKeys() {
  643. return this.externalServices.supportedMouseWheelZoomModifierKeys;
  644. },
  645. initPassiveLoading() {
  646. if (
  647. typeof PDFJSDev === "undefined" ||
  648. !PDFJSDev.test("MOZCENTRAL || CHROME")
  649. ) {
  650. throw new Error("Not implemented: initPassiveLoading");
  651. }
  652. this.externalServices.initPassiveLoading({
  653. onOpenWithTransport: (url, length, transport) => {
  654. this.open(url, { length, range: transport });
  655. },
  656. onOpenWithData: (data, contentDispositionFilename) => {
  657. if (isPdfFile(contentDispositionFilename)) {
  658. this._contentDispositionFilename = contentDispositionFilename;
  659. }
  660. this.open(data);
  661. },
  662. onOpenWithURL: (url, length, originalUrl) => {
  663. const file = originalUrl !== undefined ? { url, originalUrl } : url;
  664. const args = length !== undefined ? { length } : null;
  665. this.open(file, args);
  666. },
  667. onError: err => {
  668. this.l10n.get("loading_error").then(msg => {
  669. this._documentError(msg, err);
  670. });
  671. },
  672. onProgress: (loaded, total) => {
  673. this.progress(loaded / total);
  674. },
  675. });
  676. },
  677. setTitleUsingUrl(url = "", downloadUrl = null) {
  678. this.url = url;
  679. this.baseUrl = url.split("#")[0];
  680. if (downloadUrl) {
  681. this._downloadUrl =
  682. downloadUrl === url ? this.baseUrl : downloadUrl.split("#")[0];
  683. }
  684. if (isDataScheme(url)) {
  685. this._hideViewBookmark();
  686. }
  687. let title = getPdfFilenameFromUrl(url, "");
  688. if (!title) {
  689. try {
  690. title = decodeURIComponent(getFilenameFromUrl(url)) || url;
  691. } catch (ex) {
  692. // decodeURIComponent may throw URIError,
  693. // fall back to using the unprocessed url in that case
  694. title = url;
  695. }
  696. }
  697. this.setTitle(title);
  698. },
  699. setTitle(title = this._title) {
  700. this._title = title;
  701. if (this.isViewerEmbedded) {
  702. // Embedded PDF viewers should not be changing their parent page's title.
  703. return;
  704. }
  705. const editorIndicator =
  706. this._hasAnnotationEditors && !this.pdfRenderingQueue.printing;
  707. document.title = `${editorIndicator ? "* " : ""}${title}`;
  708. },
  709. get _docFilename() {
  710. // Use `this.url` instead of `this.baseUrl` to perform filename detection
  711. // based on the reference fragment as ultimate fallback if needed.
  712. return this._contentDispositionFilename || getPdfFilenameFromUrl(this.url);
  713. },
  714. /**
  715. * @private
  716. */
  717. _hideViewBookmark() {
  718. const { secondaryToolbar } = this.appConfig;
  719. // URL does not reflect proper document location - hiding some buttons.
  720. secondaryToolbar?.viewBookmarkButton.classList.add("hidden");
  721. // Avoid displaying multiple consecutive separators in the secondaryToolbar.
  722. if (secondaryToolbar?.presentationModeButton.classList.contains("hidden")) {
  723. document.getElementById("viewBookmarkSeparator")?.classList.add("hidden");
  724. }
  725. },
  726. /**
  727. * Closes opened PDF document.
  728. * @returns {Promise} - Returns the promise, which is resolved when all
  729. * destruction is completed.
  730. */
  731. async close() {
  732. this._unblockDocumentLoadEvent();
  733. this._hideViewBookmark();
  734. if (!this.pdfLoadingTask) {
  735. return;
  736. }
  737. if (
  738. (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) &&
  739. this.pdfDocument?.annotationStorage.size > 0 &&
  740. this._annotationStorageModified
  741. ) {
  742. try {
  743. // Trigger saving, to prevent data loss in forms; see issue 12257.
  744. await this.save();
  745. } catch (reason) {
  746. // Ignoring errors, to ensure that document closing won't break.
  747. }
  748. }
  749. const promises = [];
  750. promises.push(this.pdfLoadingTask.destroy());
  751. this.pdfLoadingTask = null;
  752. if (this.pdfDocument) {
  753. this.pdfDocument = null;
  754. this.pdfThumbnailViewer?.setDocument(null);
  755. this.pdfViewer.setDocument(null);
  756. this.pdfLinkService.setDocument(null);
  757. this.pdfDocumentProperties?.setDocument(null);
  758. }
  759. this.pdfLinkService.externalLinkEnabled = true;
  760. this.store = null;
  761. this.isInitialViewSet = false;
  762. this.downloadComplete = false;
  763. this.url = "";
  764. this.baseUrl = "";
  765. this._downloadUrl = "";
  766. this.documentInfo = null;
  767. this.metadata = null;
  768. this._contentDispositionFilename = null;
  769. this._contentLength = null;
  770. this._saveInProgress = false;
  771. this._hasAnnotationEditors = false;
  772. promises.push(this.pdfScriptingManager.destroyPromise);
  773. this.setTitle();
  774. this.pdfSidebar?.reset();
  775. this.pdfOutlineViewer?.reset();
  776. this.pdfAttachmentViewer?.reset();
  777. this.pdfLayerViewer?.reset();
  778. this.pdfHistory?.reset();
  779. this.findBar?.reset();
  780. this.toolbar?.reset();
  781. this.secondaryToolbar?.reset();
  782. this._PDFBug?.cleanup();
  783. await Promise.all(promises);
  784. },
  785. /**
  786. * Opens PDF document specified by URL or array with additional arguments.
  787. * @param {string|TypedArray|ArrayBuffer} file - PDF location or binary data.
  788. * @param {Object} [args] - Additional arguments for the getDocument call,
  789. * e.g. HTTP headers ('httpHeaders') or alternative
  790. * data transport ('range').
  791. * @returns {Promise} - Returns the promise, which is resolved when document
  792. * is opened.
  793. */
  794. async open(file, args) {
  795. if (this.pdfLoadingTask) {
  796. // We need to destroy already opened document.
  797. await this.close();
  798. }
  799. // Set the necessary global worker parameters, using the available options.
  800. const workerParameters = AppOptions.getAll(OptionKind.WORKER);
  801. for (const key in workerParameters) {
  802. GlobalWorkerOptions[key] = workerParameters[key];
  803. }
  804. const parameters = Object.create(null);
  805. if (typeof file === "string") {
  806. // URL
  807. this.setTitleUsingUrl(file, /* downloadUrl = */ file);
  808. parameters.url = file;
  809. } else if (file && "byteLength" in file) {
  810. // ArrayBuffer
  811. parameters.data = file;
  812. } else if (file.url && file.originalUrl) {
  813. this.setTitleUsingUrl(file.originalUrl, /* downloadUrl = */ file.url);
  814. parameters.url = file.url;
  815. }
  816. // Set the necessary API parameters, using the available options.
  817. const apiParameters = AppOptions.getAll(OptionKind.API);
  818. for (const key in apiParameters) {
  819. let value = apiParameters[key];
  820. if (key === "docBaseUrl" && !value) {
  821. if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
  822. value = document.URL.split("#")[0];
  823. } else if (PDFJSDev.test("MOZCENTRAL || CHROME")) {
  824. value = this.baseUrl;
  825. }
  826. }
  827. parameters[key] = value;
  828. }
  829. // Finally, update the API parameters with the arguments (if they exist).
  830. if (args) {
  831. for (const key in args) {
  832. parameters[key] = args[key];
  833. }
  834. }
  835. const loadingTask = getDocument(parameters);
  836. this.pdfLoadingTask = loadingTask;
  837. loadingTask.onPassword = (updateCallback, reason) => {
  838. if (this.isViewerEmbedded) {
  839. // The load event can't be triggered until the password is entered, so
  840. // if the viewer is in an iframe and its visibility depends on the
  841. // onload callback then the viewer never shows (bug 1801341).
  842. this._unblockDocumentLoadEvent();
  843. }
  844. this.pdfLinkService.externalLinkEnabled = false;
  845. this.passwordPrompt.setUpdateCallback(updateCallback, reason);
  846. this.passwordPrompt.open();
  847. };
  848. loadingTask.onProgress = ({ loaded, total }) => {
  849. this.progress(loaded / total);
  850. };
  851. return loadingTask.promise.then(
  852. pdfDocument => {
  853. this.load(pdfDocument);
  854. },
  855. reason => {
  856. if (loadingTask !== this.pdfLoadingTask) {
  857. return undefined; // Ignore errors for previously opened PDF files.
  858. }
  859. let key = "loading_error";
  860. if (reason instanceof InvalidPDFException) {
  861. key = "invalid_file_error";
  862. } else if (reason instanceof MissingPDFException) {
  863. key = "missing_file_error";
  864. } else if (reason instanceof UnexpectedResponseException) {
  865. key = "unexpected_response_error";
  866. }
  867. return this.l10n.get(key).then(msg => {
  868. this._documentError(msg, { message: reason?.message });
  869. throw reason;
  870. });
  871. }
  872. );
  873. },
  874. /**
  875. * @private
  876. */
  877. _ensureDownloadComplete() {
  878. if (this.pdfDocument && this.downloadComplete) {
  879. return;
  880. }
  881. throw new Error("PDF document not downloaded.");
  882. },
  883. async download() {
  884. const url = this._downloadUrl,
  885. filename = this._docFilename;
  886. try {
  887. this._ensureDownloadComplete();
  888. const data = await this.pdfDocument.getData();
  889. const blob = new Blob([data], { type: "application/pdf" });
  890. await this.downloadManager.download(blob, url, filename);
  891. } catch (reason) {
  892. // When the PDF document isn't ready, or the PDF file is still
  893. // downloading, simply download using the URL.
  894. await this.downloadManager.downloadUrl(url, filename);
  895. }
  896. },
  897. async save() {
  898. if (this._saveInProgress) {
  899. return;
  900. }
  901. this._saveInProgress = true;
  902. await this.pdfScriptingManager.dispatchWillSave();
  903. const url = this._downloadUrl,
  904. filename = this._docFilename;
  905. try {
  906. this._ensureDownloadComplete();
  907. const data = await this.pdfDocument.saveDocument();
  908. const blob = new Blob([data], { type: "application/pdf" });
  909. await this.downloadManager.download(blob, url, filename);
  910. } catch (reason) {
  911. // When the PDF document isn't ready, or the PDF file is still
  912. // downloading, simply fallback to a "regular" download.
  913. console.error(`Error when saving the document: ${reason.message}`);
  914. await this.download();
  915. } finally {
  916. await this.pdfScriptingManager.dispatchDidSave();
  917. this._saveInProgress = false;
  918. }
  919. if (this._hasAnnotationEditors) {
  920. this.externalServices.reportTelemetry({
  921. type: "editing",
  922. data: { type: "save" },
  923. });
  924. }
  925. },
  926. downloadOrSave() {
  927. if (this.pdfDocument?.annotationStorage.size > 0) {
  928. this.save();
  929. } else {
  930. this.download();
  931. }
  932. },
  933. /**
  934. * Report the error; used for errors affecting loading and/or parsing of
  935. * the entire PDF document.
  936. */
  937. _documentError(message, moreInfo = null) {
  938. this._unblockDocumentLoadEvent();
  939. this._otherError(message, moreInfo);
  940. this.eventBus.dispatch("documenterror", {
  941. source: this,
  942. message,
  943. reason: moreInfo?.message ?? null,
  944. });
  945. },
  946. /**
  947. * Report the error; used for errors affecting e.g. only a single page.
  948. * @param {string} message - A message that is human readable.
  949. * @param {Object} [moreInfo] - Further information about the error that is
  950. * more technical. Should have a 'message' and
  951. * optionally a 'stack' property.
  952. */
  953. _otherError(message, moreInfo = null) {
  954. const moreInfoText = [`PDF.js v${version || "?"} (build: ${build || "?"})`];
  955. if (moreInfo) {
  956. moreInfoText.push(`Message: ${moreInfo.message}`);
  957. if (moreInfo.stack) {
  958. moreInfoText.push(`Stack: ${moreInfo.stack}`);
  959. } else {
  960. if (moreInfo.filename) {
  961. moreInfoText.push(`File: ${moreInfo.filename}`);
  962. }
  963. if (moreInfo.lineNumber) {
  964. moreInfoText.push(`Line: ${moreInfo.lineNumber}`);
  965. }
  966. }
  967. }
  968. console.error(`${message}\n\n${moreInfoText.join("\n")}`);
  969. },
  970. progress(level) {
  971. if (!this.loadingBar || this.downloadComplete) {
  972. // Don't accidentally show the loading bar again when the entire file has
  973. // already been fetched (only an issue when disableAutoFetch is enabled).
  974. return;
  975. }
  976. const percent = Math.round(level * 100);
  977. // When we transition from full request to range requests, it's possible
  978. // that we discard some of the loaded data. This can cause the loading
  979. // bar to move backwards. So prevent this by only updating the bar if it
  980. // increases.
  981. if (percent <= this.loadingBar.percent) {
  982. return;
  983. }
  984. this.loadingBar.percent = percent;
  985. // When disableAutoFetch is enabled, it's not uncommon for the entire file
  986. // to never be fetched (depends on e.g. the file structure). In this case
  987. // the loading bar will not be completely filled, nor will it be hidden.
  988. // To prevent displaying a partially filled loading bar permanently, we
  989. // hide it when no data has been loaded during a certain amount of time.
  990. const disableAutoFetch =
  991. this.pdfDocument?.loadingParams.disableAutoFetch ??
  992. AppOptions.get("disableAutoFetch");
  993. if (!disableAutoFetch || isNaN(percent)) {
  994. return;
  995. }
  996. if (this.disableAutoFetchLoadingBarTimeout) {
  997. clearTimeout(this.disableAutoFetchLoadingBarTimeout);
  998. this.disableAutoFetchLoadingBarTimeout = null;
  999. }
  1000. this.loadingBar.show();
  1001. this.disableAutoFetchLoadingBarTimeout = setTimeout(() => {
  1002. this.loadingBar.hide();
  1003. this.disableAutoFetchLoadingBarTimeout = null;
  1004. }, DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT);
  1005. },
  1006. load(pdfDocument) {
  1007. this.pdfDocument = pdfDocument;
  1008. pdfDocument.getDownloadInfo().then(({ length }) => {
  1009. this._contentLength = length; // Ensure that the correct length is used.
  1010. this.downloadComplete = true;
  1011. this.loadingBar?.hide();
  1012. firstPagePromise.then(() => {
  1013. this.eventBus.dispatch("documentloaded", { source: this });
  1014. });
  1015. });
  1016. // Since the `setInitialView` call below depends on this being resolved,
  1017. // fetch it early to avoid delaying initial rendering of the PDF document.
  1018. const pageLayoutPromise = pdfDocument.getPageLayout().catch(function () {
  1019. /* Avoid breaking initial rendering; ignoring errors. */
  1020. });
  1021. const pageModePromise = pdfDocument.getPageMode().catch(function () {
  1022. /* Avoid breaking initial rendering; ignoring errors. */
  1023. });
  1024. const openActionPromise = pdfDocument.getOpenAction().catch(function () {
  1025. /* Avoid breaking initial rendering; ignoring errors. */
  1026. });
  1027. this.toolbar?.setPagesCount(pdfDocument.numPages, false);
  1028. this.secondaryToolbar?.setPagesCount(pdfDocument.numPages);
  1029. let baseDocumentUrl;
  1030. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1031. baseDocumentUrl = null;
  1032. } else if (PDFJSDev.test("MOZCENTRAL")) {
  1033. baseDocumentUrl = this.baseUrl;
  1034. } else if (PDFJSDev.test("CHROME")) {
  1035. baseDocumentUrl = location.href.split("#")[0];
  1036. }
  1037. if (baseDocumentUrl && isDataScheme(baseDocumentUrl)) {
  1038. // Ignore "data:"-URLs for performance reasons, even though it may cause
  1039. // internal links to not work perfectly in all cases (see bug 1803050).
  1040. baseDocumentUrl = null;
  1041. }
  1042. this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
  1043. this.pdfDocumentProperties?.setDocument(pdfDocument);
  1044. const pdfViewer = this.pdfViewer;
  1045. pdfViewer.setDocument(pdfDocument);
  1046. const { firstPagePromise, onePageRendered, pagesPromise } = pdfViewer;
  1047. this.pdfThumbnailViewer?.setDocument(pdfDocument);
  1048. const storedPromise = (this.store = new ViewHistory(
  1049. pdfDocument.fingerprints[0]
  1050. ))
  1051. .getMultiple({
  1052. page: null,
  1053. zoom: DEFAULT_SCALE_VALUE,
  1054. scrollLeft: "0",
  1055. scrollTop: "0",
  1056. rotation: null,
  1057. sidebarView: SidebarView.UNKNOWN,
  1058. scrollMode: ScrollMode.UNKNOWN,
  1059. spreadMode: SpreadMode.UNKNOWN,
  1060. })
  1061. .catch(() => {
  1062. /* Unable to read from storage; ignoring errors. */
  1063. return Object.create(null);
  1064. });
  1065. firstPagePromise.then(pdfPage => {
  1066. this.loadingBar?.setWidth(this.appConfig.viewerContainer);
  1067. this._initializeAnnotationStorageCallbacks(pdfDocument);
  1068. Promise.all([
  1069. animationStarted,
  1070. storedPromise,
  1071. pageLayoutPromise,
  1072. pageModePromise,
  1073. openActionPromise,
  1074. ])
  1075. .then(async ([timeStamp, stored, pageLayout, pageMode, openAction]) => {
  1076. const viewOnLoad = AppOptions.get("viewOnLoad");
  1077. this._initializePdfHistory({
  1078. fingerprint: pdfDocument.fingerprints[0],
  1079. viewOnLoad,
  1080. initialDest: openAction?.dest,
  1081. });
  1082. const initialBookmark = this.initialBookmark;
  1083. // Initialize the default values, from user preferences.
  1084. const zoom = AppOptions.get("defaultZoomValue");
  1085. let hash = zoom ? `zoom=${zoom}` : null;
  1086. let rotation = null;
  1087. let sidebarView = AppOptions.get("sidebarViewOnLoad");
  1088. let scrollMode = AppOptions.get("scrollModeOnLoad");
  1089. let spreadMode = AppOptions.get("spreadModeOnLoad");
  1090. if (stored.page && viewOnLoad !== ViewOnLoad.INITIAL) {
  1091. hash =
  1092. `page=${stored.page}&zoom=${zoom || stored.zoom},` +
  1093. `${stored.scrollLeft},${stored.scrollTop}`;
  1094. rotation = parseInt(stored.rotation, 10);
  1095. // Always let user preference take precedence over the view history.
  1096. if (sidebarView === SidebarView.UNKNOWN) {
  1097. sidebarView = stored.sidebarView | 0;
  1098. }
  1099. if (scrollMode === ScrollMode.UNKNOWN) {
  1100. scrollMode = stored.scrollMode | 0;
  1101. }
  1102. if (spreadMode === SpreadMode.UNKNOWN) {
  1103. spreadMode = stored.spreadMode | 0;
  1104. }
  1105. }
  1106. // Always let the user preference/view history take precedence.
  1107. if (pageMode && sidebarView === SidebarView.UNKNOWN) {
  1108. sidebarView = apiPageModeToSidebarView(pageMode);
  1109. }
  1110. // NOTE: Always ignore the pageLayout in GeckoView since there's
  1111. // no UI available to change Scroll/Spread modes for the user.
  1112. if (
  1113. (typeof PDFJSDev === "undefined" || !PDFJSDev.test("GECKOVIEW")) &&
  1114. pageLayout &&
  1115. scrollMode === ScrollMode.UNKNOWN &&
  1116. spreadMode === SpreadMode.UNKNOWN
  1117. ) {
  1118. const modes = apiPageLayoutToViewerModes(pageLayout);
  1119. // TODO: Try to improve page-switching when using the mouse-wheel
  1120. // and/or arrow-keys before allowing the document to control this.
  1121. // scrollMode = modes.scrollMode;
  1122. spreadMode = modes.spreadMode;
  1123. }
  1124. this.setInitialView(hash, {
  1125. rotation,
  1126. sidebarView,
  1127. scrollMode,
  1128. spreadMode,
  1129. });
  1130. this.eventBus.dispatch("documentinit", { source: this });
  1131. // Make all navigation keys work on document load,
  1132. // unless the viewer is embedded in a web page.
  1133. if (!this.isViewerEmbedded) {
  1134. pdfViewer.focus();
  1135. }
  1136. // For documents with different page sizes, once all pages are
  1137. // resolved, ensure that the correct location becomes visible on load.
  1138. // (To reduce the risk, in very large and/or slow loading documents,
  1139. // that the location changes *after* the user has started interacting
  1140. // with the viewer, wait for either `pagesPromise` or a timeout.)
  1141. await Promise.race([
  1142. pagesPromise,
  1143. new Promise(resolve => {
  1144. setTimeout(resolve, FORCE_PAGES_LOADED_TIMEOUT);
  1145. }),
  1146. ]);
  1147. if (!initialBookmark && !hash) {
  1148. return;
  1149. }
  1150. if (pdfViewer.hasEqualPageSizes) {
  1151. return;
  1152. }
  1153. this.initialBookmark = initialBookmark;
  1154. // eslint-disable-next-line no-self-assign
  1155. pdfViewer.currentScaleValue = pdfViewer.currentScaleValue;
  1156. // Re-apply the initial document location.
  1157. this.setInitialView(hash);
  1158. })
  1159. .catch(() => {
  1160. // Ensure that the document is always completely initialized,
  1161. // even if there are any errors thrown above.
  1162. this.setInitialView();
  1163. })
  1164. .then(function () {
  1165. // At this point, rendering of the initial page(s) should always have
  1166. // started (and may even have completed).
  1167. // To prevent any future issues, e.g. the document being completely
  1168. // blank on load, always trigger rendering here.
  1169. pdfViewer.update();
  1170. });
  1171. });
  1172. pagesPromise.then(
  1173. () => {
  1174. this._unblockDocumentLoadEvent();
  1175. this._initializeAutoPrint(pdfDocument, openActionPromise);
  1176. },
  1177. reason => {
  1178. this.l10n.get("loading_error").then(msg => {
  1179. this._documentError(msg, { message: reason?.message });
  1180. });
  1181. }
  1182. );
  1183. onePageRendered.then(data => {
  1184. this.externalServices.reportTelemetry({
  1185. type: "pageInfo",
  1186. timestamp: data.timestamp,
  1187. });
  1188. pdfDocument.getOutline().then(outline => {
  1189. if (pdfDocument !== this.pdfDocument) {
  1190. return; // The document was closed while the outline resolved.
  1191. }
  1192. this.pdfOutlineViewer?.render({ outline, pdfDocument });
  1193. });
  1194. pdfDocument.getAttachments().then(attachments => {
  1195. if (pdfDocument !== this.pdfDocument) {
  1196. return; // The document was closed while the attachments resolved.
  1197. }
  1198. this.pdfAttachmentViewer?.render({ attachments });
  1199. });
  1200. // Ensure that the layers accurately reflects the current state in the
  1201. // viewer itself, rather than the default state provided by the API.
  1202. pdfViewer.optionalContentConfigPromise.then(optionalContentConfig => {
  1203. if (pdfDocument !== this.pdfDocument) {
  1204. return; // The document was closed while the layers resolved.
  1205. }
  1206. this.pdfLayerViewer?.render({ optionalContentConfig, pdfDocument });
  1207. });
  1208. });
  1209. this._initializePageLabels(pdfDocument);
  1210. this._initializeMetadata(pdfDocument);
  1211. },
  1212. /**
  1213. * @private
  1214. */
  1215. async _scriptingDocProperties(pdfDocument) {
  1216. if (!this.documentInfo) {
  1217. // It should be *extremely* rare for metadata to not have been resolved
  1218. // when this code runs, but ensure that we handle that case here.
  1219. await new Promise(resolve => {
  1220. this.eventBus._on("metadataloaded", resolve, { once: true });
  1221. });
  1222. if (pdfDocument !== this.pdfDocument) {
  1223. return null; // The document was closed while the metadata resolved.
  1224. }
  1225. }
  1226. if (!this._contentLength) {
  1227. // Always waiting for the entire PDF document to be loaded will, most
  1228. // likely, delay sandbox-creation too much in the general case for all
  1229. // PDF documents which are not provided as binary data to the API.
  1230. // Hence we'll simply have to trust that the `contentLength` (as provided
  1231. // by the server), when it exists, is accurate enough here.
  1232. await new Promise(resolve => {
  1233. this.eventBus._on("documentloaded", resolve, { once: true });
  1234. });
  1235. if (pdfDocument !== this.pdfDocument) {
  1236. return null; // The document was closed while the downloadInfo resolved.
  1237. }
  1238. }
  1239. return {
  1240. ...this.documentInfo,
  1241. baseURL: this.baseUrl,
  1242. filesize: this._contentLength,
  1243. filename: this._docFilename,
  1244. metadata: this.metadata?.getRaw(),
  1245. authors: this.metadata?.get("dc:creator"),
  1246. numPages: this.pagesCount,
  1247. URL: this.url,
  1248. };
  1249. },
  1250. /**
  1251. * @private
  1252. */
  1253. async _initializeAutoPrint(pdfDocument, openActionPromise) {
  1254. const [openAction, javaScript] = await Promise.all([
  1255. openActionPromise,
  1256. !this.pdfViewer.enableScripting ? pdfDocument.getJavaScript() : null,
  1257. ]);
  1258. if (pdfDocument !== this.pdfDocument) {
  1259. return; // The document was closed while the auto print data resolved.
  1260. }
  1261. let triggerAutoPrint = false;
  1262. if (openAction?.action === "Print") {
  1263. triggerAutoPrint = true;
  1264. }
  1265. if (javaScript) {
  1266. javaScript.some(js => {
  1267. if (!js) {
  1268. // Don't warn/fallback for empty JavaScript actions.
  1269. return false;
  1270. }
  1271. console.warn("Warning: JavaScript support is not enabled");
  1272. return true;
  1273. });
  1274. if (!triggerAutoPrint) {
  1275. // Hack to support auto printing.
  1276. for (const js of javaScript) {
  1277. if (js && AutoPrintRegExp.test(js)) {
  1278. triggerAutoPrint = true;
  1279. break;
  1280. }
  1281. }
  1282. }
  1283. }
  1284. if (triggerAutoPrint) {
  1285. this.triggerPrinting();
  1286. }
  1287. },
  1288. /**
  1289. * @private
  1290. */
  1291. async _initializeMetadata(pdfDocument) {
  1292. const { info, metadata, contentDispositionFilename, contentLength } =
  1293. await pdfDocument.getMetadata();
  1294. if (pdfDocument !== this.pdfDocument) {
  1295. return; // The document was closed while the metadata resolved.
  1296. }
  1297. this.documentInfo = info;
  1298. this.metadata = metadata;
  1299. this._contentDispositionFilename ??= contentDispositionFilename;
  1300. this._contentLength ??= contentLength; // See `getDownloadInfo`-call above.
  1301. // Provides some basic debug information
  1302. console.log(
  1303. `PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` +
  1304. `${(info.Producer || "-").trim()} / ${(info.Creator || "-").trim()}] ` +
  1305. `(PDF.js: ${version || "?"} [${build || "?"}])`
  1306. );
  1307. let pdfTitle = info.Title;
  1308. const metadataTitle = metadata?.get("dc:title");
  1309. if (metadataTitle) {
  1310. // Ghostscript can produce invalid 'dc:title' Metadata entries:
  1311. // - The title may be "Untitled" (fixes bug 1031612).
  1312. // - The title may contain incorrectly encoded characters, which thus
  1313. // looks broken, hence we ignore the Metadata entry when it contains
  1314. // characters from the Specials Unicode block (fixes bug 1605526).
  1315. if (
  1316. metadataTitle !== "Untitled" &&
  1317. !/[\uFFF0-\uFFFF]/g.test(metadataTitle)
  1318. ) {
  1319. pdfTitle = metadataTitle;
  1320. }
  1321. }
  1322. if (pdfTitle) {
  1323. this.setTitle(
  1324. `${pdfTitle} - ${this._contentDispositionFilename || this._title}`
  1325. );
  1326. } else if (this._contentDispositionFilename) {
  1327. this.setTitle(this._contentDispositionFilename);
  1328. }
  1329. if (
  1330. info.IsXFAPresent &&
  1331. !info.IsAcroFormPresent &&
  1332. !pdfDocument.isPureXfa
  1333. ) {
  1334. if (pdfDocument.loadingParams.enableXfa) {
  1335. console.warn("Warning: XFA Foreground documents are not supported");
  1336. } else {
  1337. console.warn("Warning: XFA support is not enabled");
  1338. }
  1339. } else if (
  1340. (info.IsAcroFormPresent || info.IsXFAPresent) &&
  1341. !this.pdfViewer.renderForms
  1342. ) {
  1343. console.warn("Warning: Interactive form support is not enabled");
  1344. }
  1345. if (info.IsSignaturesPresent) {
  1346. console.warn("Warning: Digital signatures validation is not supported");
  1347. }
  1348. this.eventBus.dispatch("metadataloaded", { source: this });
  1349. },
  1350. /**
  1351. * @private
  1352. */
  1353. async _initializePageLabels(pdfDocument) {
  1354. const labels = await pdfDocument.getPageLabels();
  1355. if (pdfDocument !== this.pdfDocument) {
  1356. return; // The document was closed while the page labels resolved.
  1357. }
  1358. if (!labels || AppOptions.get("disablePageLabels")) {
  1359. return;
  1360. }
  1361. const numLabels = labels.length;
  1362. // Ignore page labels that correspond to standard page numbering,
  1363. // or page labels that are all empty.
  1364. let standardLabels = 0,
  1365. emptyLabels = 0;
  1366. for (let i = 0; i < numLabels; i++) {
  1367. const label = labels[i];
  1368. if (label === (i + 1).toString()) {
  1369. standardLabels++;
  1370. } else if (label === "") {
  1371. emptyLabels++;
  1372. } else {
  1373. break;
  1374. }
  1375. }
  1376. if (standardLabels >= numLabels || emptyLabels >= numLabels) {
  1377. return;
  1378. }
  1379. const { pdfViewer, pdfThumbnailViewer, toolbar } = this;
  1380. pdfViewer.setPageLabels(labels);
  1381. pdfThumbnailViewer?.setPageLabels(labels);
  1382. // Changing toolbar page display to use labels and we need to set
  1383. // the label of the current page.
  1384. toolbar?.setPagesCount(numLabels, true);
  1385. toolbar?.setPageNumber(
  1386. pdfViewer.currentPageNumber,
  1387. pdfViewer.currentPageLabel
  1388. );
  1389. },
  1390. /**
  1391. * @private
  1392. */
  1393. _initializePdfHistory({ fingerprint, viewOnLoad, initialDest = null }) {
  1394. if (!this.pdfHistory) {
  1395. return;
  1396. }
  1397. this.pdfHistory.initialize({
  1398. fingerprint,
  1399. resetHistory: viewOnLoad === ViewOnLoad.INITIAL,
  1400. updateUrl: AppOptions.get("historyUpdateUrl"),
  1401. });
  1402. if (this.pdfHistory.initialBookmark) {
  1403. this.initialBookmark = this.pdfHistory.initialBookmark;
  1404. this.initialRotation = this.pdfHistory.initialRotation;
  1405. }
  1406. // Always let the browser history/document hash take precedence.
  1407. if (
  1408. initialDest &&
  1409. !this.initialBookmark &&
  1410. viewOnLoad === ViewOnLoad.UNKNOWN
  1411. ) {
  1412. this.initialBookmark = JSON.stringify(initialDest);
  1413. // TODO: Re-factor the `PDFHistory` initialization to remove this hack
  1414. // that's currently necessary to prevent weird initial history state.
  1415. this.pdfHistory.push({ explicitDest: initialDest, pageNumber: null });
  1416. }
  1417. },
  1418. /**
  1419. * @private
  1420. */
  1421. _initializeAnnotationStorageCallbacks(pdfDocument) {
  1422. if (pdfDocument !== this.pdfDocument) {
  1423. return;
  1424. }
  1425. const { annotationStorage } = pdfDocument;
  1426. annotationStorage.onSetModified = () => {
  1427. window.addEventListener("beforeunload", beforeUnload);
  1428. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1429. this._annotationStorageModified = true;
  1430. }
  1431. };
  1432. annotationStorage.onResetModified = () => {
  1433. window.removeEventListener("beforeunload", beforeUnload);
  1434. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1435. delete this._annotationStorageModified;
  1436. }
  1437. };
  1438. annotationStorage.onAnnotationEditor = typeStr => {
  1439. this._hasAnnotationEditors = !!typeStr;
  1440. this.setTitle();
  1441. if (typeStr) {
  1442. this.externalServices.reportTelemetry({
  1443. type: "editing",
  1444. data: { type: typeStr },
  1445. });
  1446. }
  1447. };
  1448. },
  1449. setInitialView(
  1450. storedHash,
  1451. { rotation, sidebarView, scrollMode, spreadMode } = {}
  1452. ) {
  1453. const setRotation = angle => {
  1454. if (isValidRotation(angle)) {
  1455. this.pdfViewer.pagesRotation = angle;
  1456. }
  1457. };
  1458. const setViewerModes = (scroll, spread) => {
  1459. if (isValidScrollMode(scroll)) {
  1460. this.pdfViewer.scrollMode = scroll;
  1461. }
  1462. if (isValidSpreadMode(spread)) {
  1463. this.pdfViewer.spreadMode = spread;
  1464. }
  1465. };
  1466. this.isInitialViewSet = true;
  1467. this.pdfSidebar?.setInitialView(sidebarView);
  1468. setViewerModes(scrollMode, spreadMode);
  1469. if (this.initialBookmark) {
  1470. setRotation(this.initialRotation);
  1471. delete this.initialRotation;
  1472. this.pdfLinkService.setHash(this.initialBookmark);
  1473. this.initialBookmark = null;
  1474. } else if (storedHash) {
  1475. setRotation(rotation);
  1476. this.pdfLinkService.setHash(storedHash);
  1477. }
  1478. // Ensure that the correct page number is displayed in the UI,
  1479. // even if the active page didn't change during document load.
  1480. this.toolbar?.setPageNumber(
  1481. this.pdfViewer.currentPageNumber,
  1482. this.pdfViewer.currentPageLabel
  1483. );
  1484. this.secondaryToolbar?.setPageNumber(this.pdfViewer.currentPageNumber);
  1485. if (!this.pdfViewer.currentScaleValue) {
  1486. // Scale was not initialized: invalid bookmark or scale was not specified.
  1487. // Setting the default one.
  1488. this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
  1489. }
  1490. },
  1491. /**
  1492. * @private
  1493. */
  1494. _cleanup() {
  1495. if (!this.pdfDocument) {
  1496. return; // run cleanup when document is loaded
  1497. }
  1498. this.pdfViewer.cleanup();
  1499. this.pdfThumbnailViewer?.cleanup();
  1500. if (
  1501. typeof PDFJSDev === "undefined" ||
  1502. PDFJSDev.test("!PRODUCTION || GENERIC")
  1503. ) {
  1504. // We don't want to remove fonts used by active page SVGs.
  1505. this.pdfDocument.cleanup(
  1506. /* keepLoadedFonts = */ this.pdfViewer.renderer === RendererType.SVG
  1507. );
  1508. } else {
  1509. this.pdfDocument.cleanup();
  1510. }
  1511. },
  1512. forceRendering() {
  1513. this.pdfRenderingQueue.printing = !!this.printService;
  1514. this.pdfRenderingQueue.isThumbnailViewEnabled =
  1515. this.pdfSidebar?.visibleView === SidebarView.THUMBS;
  1516. this.pdfRenderingQueue.renderHighestPriority();
  1517. },
  1518. beforePrint() {
  1519. this._printAnnotationStoragePromise = this.pdfScriptingManager
  1520. .dispatchWillPrint()
  1521. .catch(() => {
  1522. /* Avoid breaking printing; ignoring errors. */
  1523. })
  1524. .then(() => {
  1525. return this.pdfDocument?.annotationStorage.print;
  1526. });
  1527. if (this.printService) {
  1528. // There is no way to suppress beforePrint/afterPrint events,
  1529. // but PDFPrintService may generate double events -- this will ignore
  1530. // the second event that will be coming from native window.print().
  1531. return;
  1532. }
  1533. if (!this.supportsPrinting) {
  1534. this.l10n.get("printing_not_supported").then(msg => {
  1535. this._otherError(msg);
  1536. });
  1537. return;
  1538. }
  1539. // The beforePrint is a sync method and we need to know layout before
  1540. // returning from this method. Ensure that we can get sizes of the pages.
  1541. if (!this.pdfViewer.pageViewsReady) {
  1542. this.l10n.get("printing_not_ready").then(msg => {
  1543. // eslint-disable-next-line no-alert
  1544. window.alert(msg);
  1545. });
  1546. return;
  1547. }
  1548. const pagesOverview = this.pdfViewer.getPagesOverview();
  1549. const printContainer = this.appConfig.printContainer;
  1550. const printResolution = AppOptions.get("printResolution");
  1551. const optionalContentConfigPromise =
  1552. this.pdfViewer.optionalContentConfigPromise;
  1553. const printService = PDFPrintServiceFactory.instance.createPrintService(
  1554. this.pdfDocument,
  1555. pagesOverview,
  1556. printContainer,
  1557. printResolution,
  1558. optionalContentConfigPromise,
  1559. this._printAnnotationStoragePromise,
  1560. this.l10n
  1561. );
  1562. this.printService = printService;
  1563. this.forceRendering();
  1564. // Disable the editor-indicator during printing (fixes bug 1790552).
  1565. this.setTitle();
  1566. printService.layout();
  1567. if (this._hasAnnotationEditors) {
  1568. this.externalServices.reportTelemetry({
  1569. type: "editing",
  1570. data: { type: "print" },
  1571. });
  1572. }
  1573. },
  1574. afterPrint() {
  1575. if (this._printAnnotationStoragePromise) {
  1576. this._printAnnotationStoragePromise.then(() => {
  1577. this.pdfScriptingManager.dispatchDidPrint();
  1578. });
  1579. this._printAnnotationStoragePromise = null;
  1580. }
  1581. if (this.printService) {
  1582. this.printService.destroy();
  1583. this.printService = null;
  1584. this.pdfDocument?.annotationStorage.resetModified();
  1585. }
  1586. this.forceRendering();
  1587. // Re-enable the editor-indicator after printing (fixes bug 1790552).
  1588. this.setTitle();
  1589. },
  1590. rotatePages(delta) {
  1591. this.pdfViewer.pagesRotation += delta;
  1592. // Note that the thumbnail viewer is updated, and rendering is triggered,
  1593. // in the 'rotationchanging' event handler.
  1594. },
  1595. requestPresentationMode() {
  1596. this.pdfPresentationMode?.request();
  1597. },
  1598. triggerPrinting() {
  1599. if (!this.supportsPrinting) {
  1600. return;
  1601. }
  1602. window.print();
  1603. },
  1604. bindEvents() {
  1605. const { eventBus, _boundEvents } = this;
  1606. _boundEvents.beforePrint = this.beforePrint.bind(this);
  1607. _boundEvents.afterPrint = this.afterPrint.bind(this);
  1608. eventBus._on("resize", webViewerResize);
  1609. eventBus._on("hashchange", webViewerHashchange);
  1610. eventBus._on("beforeprint", _boundEvents.beforePrint);
  1611. eventBus._on("afterprint", _boundEvents.afterPrint);
  1612. eventBus._on("pagerender", webViewerPageRender);
  1613. eventBus._on("pagerendered", webViewerPageRendered);
  1614. eventBus._on("updateviewarea", webViewerUpdateViewarea);
  1615. eventBus._on("pagechanging", webViewerPageChanging);
  1616. eventBus._on("scalechanging", webViewerScaleChanging);
  1617. eventBus._on("rotationchanging", webViewerRotationChanging);
  1618. eventBus._on("sidebarviewchanged", webViewerSidebarViewChanged);
  1619. eventBus._on("pagemode", webViewerPageMode);
  1620. eventBus._on("namedaction", webViewerNamedAction);
  1621. eventBus._on("presentationmodechanged", webViewerPresentationModeChanged);
  1622. eventBus._on("presentationmode", webViewerPresentationMode);
  1623. eventBus._on(
  1624. "switchannotationeditormode",
  1625. webViewerSwitchAnnotationEditorMode
  1626. );
  1627. eventBus._on(
  1628. "switchannotationeditorparams",
  1629. webViewerSwitchAnnotationEditorParams
  1630. );
  1631. eventBus._on("print", webViewerPrint);
  1632. eventBus._on("download", webViewerDownload);
  1633. eventBus._on("firstpage", webViewerFirstPage);
  1634. eventBus._on("lastpage", webViewerLastPage);
  1635. eventBus._on("nextpage", webViewerNextPage);
  1636. eventBus._on("previouspage", webViewerPreviousPage);
  1637. eventBus._on("zoomin", webViewerZoomIn);
  1638. eventBus._on("zoomout", webViewerZoomOut);
  1639. eventBus._on("zoomreset", webViewerZoomReset);
  1640. eventBus._on("pagenumberchanged", webViewerPageNumberChanged);
  1641. eventBus._on("scalechanged", webViewerScaleChanged);
  1642. eventBus._on("rotatecw", webViewerRotateCw);
  1643. eventBus._on("rotateccw", webViewerRotateCcw);
  1644. eventBus._on("optionalcontentconfig", webViewerOptionalContentConfig);
  1645. eventBus._on("switchscrollmode", webViewerSwitchScrollMode);
  1646. eventBus._on("scrollmodechanged", webViewerScrollModeChanged);
  1647. eventBus._on("switchspreadmode", webViewerSwitchSpreadMode);
  1648. eventBus._on("spreadmodechanged", webViewerSpreadModeChanged);
  1649. eventBus._on("documentproperties", webViewerDocumentProperties);
  1650. eventBus._on("findfromurlhash", webViewerFindFromUrlHash);
  1651. eventBus._on("updatefindmatchescount", webViewerUpdateFindMatchesCount);
  1652. eventBus._on("updatefindcontrolstate", webViewerUpdateFindControlState);
  1653. if (AppOptions.get("pdfBug")) {
  1654. _boundEvents.reportPageStatsPDFBug = reportPageStatsPDFBug;
  1655. eventBus._on("pagerendered", _boundEvents.reportPageStatsPDFBug);
  1656. eventBus._on("pagechanging", _boundEvents.reportPageStatsPDFBug);
  1657. }
  1658. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1659. eventBus._on("fileinputchange", webViewerFileInputChange);
  1660. eventBus._on("openfile", webViewerOpenFile);
  1661. }
  1662. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  1663. eventBus._on(
  1664. "annotationeditorstateschanged",
  1665. webViewerAnnotationEditorStatesChanged
  1666. );
  1667. }
  1668. },
  1669. bindWindowEvents() {
  1670. const { eventBus, _boundEvents } = this;
  1671. function addWindowResolutionChange(evt = null) {
  1672. if (evt) {
  1673. webViewerResolutionChange(evt);
  1674. }
  1675. const mediaQueryList = window.matchMedia(
  1676. `(resolution: ${window.devicePixelRatio || 1}dppx)`
  1677. );
  1678. mediaQueryList.addEventListener("change", addWindowResolutionChange, {
  1679. once: true,
  1680. });
  1681. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  1682. return;
  1683. }
  1684. _boundEvents.removeWindowResolutionChange ||= function () {
  1685. mediaQueryList.removeEventListener("change", addWindowResolutionChange);
  1686. _boundEvents.removeWindowResolutionChange = null;
  1687. };
  1688. }
  1689. addWindowResolutionChange();
  1690. _boundEvents.windowResize = () => {
  1691. eventBus.dispatch("resize", { source: window });
  1692. };
  1693. _boundEvents.windowHashChange = () => {
  1694. eventBus.dispatch("hashchange", {
  1695. source: window,
  1696. hash: document.location.hash.substring(1),
  1697. });
  1698. };
  1699. _boundEvents.windowBeforePrint = () => {
  1700. eventBus.dispatch("beforeprint", { source: window });
  1701. };
  1702. _boundEvents.windowAfterPrint = () => {
  1703. eventBus.dispatch("afterprint", { source: window });
  1704. };
  1705. _boundEvents.windowUpdateFromSandbox = event => {
  1706. eventBus.dispatch("updatefromsandbox", {
  1707. source: window,
  1708. detail: event.detail,
  1709. });
  1710. };
  1711. window.addEventListener("visibilitychange", webViewerVisibilityChange);
  1712. window.addEventListener("wheel", webViewerWheel, { passive: false });
  1713. window.addEventListener("touchstart", webViewerTouchStart, {
  1714. passive: false,
  1715. });
  1716. window.addEventListener("click", webViewerClick);
  1717. window.addEventListener("keydown", webViewerKeyDown);
  1718. window.addEventListener("resize", _boundEvents.windowResize);
  1719. window.addEventListener("hashchange", _boundEvents.windowHashChange);
  1720. window.addEventListener("beforeprint", _boundEvents.windowBeforePrint);
  1721. window.addEventListener("afterprint", _boundEvents.windowAfterPrint);
  1722. window.addEventListener(
  1723. "updatefromsandbox",
  1724. _boundEvents.windowUpdateFromSandbox
  1725. );
  1726. },
  1727. unbindEvents() {
  1728. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  1729. throw new Error("Not implemented: unbindEvents");
  1730. }
  1731. const { eventBus, _boundEvents } = this;
  1732. eventBus._off("resize", webViewerResize);
  1733. eventBus._off("hashchange", webViewerHashchange);
  1734. eventBus._off("beforeprint", _boundEvents.beforePrint);
  1735. eventBus._off("afterprint", _boundEvents.afterPrint);
  1736. eventBus._off("pagerender", webViewerPageRender);
  1737. eventBus._off("pagerendered", webViewerPageRendered);
  1738. eventBus._off("updateviewarea", webViewerUpdateViewarea);
  1739. eventBus._off("pagechanging", webViewerPageChanging);
  1740. eventBus._off("scalechanging", webViewerScaleChanging);
  1741. eventBus._off("rotationchanging", webViewerRotationChanging);
  1742. eventBus._off("sidebarviewchanged", webViewerSidebarViewChanged);
  1743. eventBus._off("pagemode", webViewerPageMode);
  1744. eventBus._off("namedaction", webViewerNamedAction);
  1745. eventBus._off("presentationmodechanged", webViewerPresentationModeChanged);
  1746. eventBus._off("presentationmode", webViewerPresentationMode);
  1747. eventBus._off("print", webViewerPrint);
  1748. eventBus._off("download", webViewerDownload);
  1749. eventBus._off("firstpage", webViewerFirstPage);
  1750. eventBus._off("lastpage", webViewerLastPage);
  1751. eventBus._off("nextpage", webViewerNextPage);
  1752. eventBus._off("previouspage", webViewerPreviousPage);
  1753. eventBus._off("zoomin", webViewerZoomIn);
  1754. eventBus._off("zoomout", webViewerZoomOut);
  1755. eventBus._off("zoomreset", webViewerZoomReset);
  1756. eventBus._off("pagenumberchanged", webViewerPageNumberChanged);
  1757. eventBus._off("scalechanged", webViewerScaleChanged);
  1758. eventBus._off("rotatecw", webViewerRotateCw);
  1759. eventBus._off("rotateccw", webViewerRotateCcw);
  1760. eventBus._off("optionalcontentconfig", webViewerOptionalContentConfig);
  1761. eventBus._off("switchscrollmode", webViewerSwitchScrollMode);
  1762. eventBus._off("scrollmodechanged", webViewerScrollModeChanged);
  1763. eventBus._off("switchspreadmode", webViewerSwitchSpreadMode);
  1764. eventBus._off("spreadmodechanged", webViewerSpreadModeChanged);
  1765. eventBus._off("documentproperties", webViewerDocumentProperties);
  1766. eventBus._off("findfromurlhash", webViewerFindFromUrlHash);
  1767. eventBus._off("updatefindmatchescount", webViewerUpdateFindMatchesCount);
  1768. eventBus._off("updatefindcontrolstate", webViewerUpdateFindControlState);
  1769. if (_boundEvents.reportPageStatsPDFBug) {
  1770. eventBus._off("pagerendered", _boundEvents.reportPageStatsPDFBug);
  1771. eventBus._off("pagechanging", _boundEvents.reportPageStatsPDFBug);
  1772. _boundEvents.reportPageStatsPDFBug = null;
  1773. }
  1774. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1775. eventBus._off("fileinputchange", webViewerFileInputChange);
  1776. eventBus._off("openfile", webViewerOpenFile);
  1777. }
  1778. _boundEvents.beforePrint = null;
  1779. _boundEvents.afterPrint = null;
  1780. },
  1781. unbindWindowEvents() {
  1782. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  1783. throw new Error("Not implemented: unbindWindowEvents");
  1784. }
  1785. const { _boundEvents } = this;
  1786. window.removeEventListener("visibilitychange", webViewerVisibilityChange);
  1787. window.removeEventListener("wheel", webViewerWheel, { passive: false });
  1788. window.removeEventListener("touchstart", webViewerTouchStart, {
  1789. passive: false,
  1790. });
  1791. window.removeEventListener("click", webViewerClick);
  1792. window.removeEventListener("keydown", webViewerKeyDown);
  1793. window.removeEventListener("resize", _boundEvents.windowResize);
  1794. window.removeEventListener("hashchange", _boundEvents.windowHashChange);
  1795. window.removeEventListener("beforeprint", _boundEvents.windowBeforePrint);
  1796. window.removeEventListener("afterprint", _boundEvents.windowAfterPrint);
  1797. window.removeEventListener(
  1798. "updatefromsandbox",
  1799. _boundEvents.windowUpdateFromSandbox
  1800. );
  1801. _boundEvents.removeWindowResolutionChange?.();
  1802. _boundEvents.windowResize = null;
  1803. _boundEvents.windowHashChange = null;
  1804. _boundEvents.windowBeforePrint = null;
  1805. _boundEvents.windowAfterPrint = null;
  1806. _boundEvents.windowUpdateFromSandbox = null;
  1807. },
  1808. accumulateWheelTicks(ticks) {
  1809. // If the scroll direction changed, reset the accumulated wheel ticks.
  1810. if (
  1811. (this._wheelUnusedTicks > 0 && ticks < 0) ||
  1812. (this._wheelUnusedTicks < 0 && ticks > 0)
  1813. ) {
  1814. this._wheelUnusedTicks = 0;
  1815. }
  1816. this._wheelUnusedTicks += ticks;
  1817. const wholeTicks = Math.trunc(this._wheelUnusedTicks);
  1818. this._wheelUnusedTicks -= wholeTicks;
  1819. return wholeTicks;
  1820. },
  1821. /**
  1822. * Should be called *after* all pages have loaded, or if an error occurred,
  1823. * to unblock the "load" event; see https://bugzilla.mozilla.org/show_bug.cgi?id=1618553
  1824. * @private
  1825. */
  1826. _unblockDocumentLoadEvent() {
  1827. document.blockUnblockOnload?.(false);
  1828. // Ensure that this method is only ever run once.
  1829. this._unblockDocumentLoadEvent = () => {};
  1830. },
  1831. /**
  1832. * Used together with the integration-tests, to enable awaiting full
  1833. * initialization of the scripting/sandbox.
  1834. */
  1835. get scriptingReady() {
  1836. return this.pdfScriptingManager.ready;
  1837. },
  1838. };
  1839. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1840. const HOSTED_VIEWER_ORIGINS = [
  1841. "null",
  1842. "http://mozilla.github.io",
  1843. "https://mozilla.github.io",
  1844. ];
  1845. // eslint-disable-next-line no-var
  1846. var validateFileURL = function (file) {
  1847. if (!file) {
  1848. return;
  1849. }
  1850. try {
  1851. const viewerOrigin = new URL(window.location.href).origin || "null";
  1852. if (HOSTED_VIEWER_ORIGINS.includes(viewerOrigin)) {
  1853. // Hosted or local viewer, allow for any file locations
  1854. return;
  1855. }
  1856. const fileOrigin = new URL(file, window.location.href).origin;
  1857. // Removing of the following line will not guarantee that the viewer will
  1858. // start accepting URLs from foreign origin -- CORS headers on the remote
  1859. // server must be properly configured.
  1860. // if (fileOrigin !== viewerOrigin) {
  1861. // throw new Error("file origin does not match viewer's");
  1862. // }
  1863. } catch (ex) {
  1864. PDFViewerApplication.l10n.get("loading_error").then(msg => {
  1865. PDFViewerApplication._documentError(msg, { message: ex?.message });
  1866. });
  1867. throw ex;
  1868. }
  1869. };
  1870. }
  1871. async function loadFakeWorker() {
  1872. GlobalWorkerOptions.workerSrc ||= AppOptions.get("workerSrc");
  1873. if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
  1874. window.pdfjsWorker = await import("pdfjs/core/worker.js");
  1875. return;
  1876. }
  1877. await loadScript(PDFWorker.workerSrc);
  1878. }
  1879. async function loadPDFBug(self) {
  1880. const { debuggerScriptPath } = self.appConfig;
  1881. const { PDFBug } =
  1882. typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")
  1883. ? await import(debuggerScriptPath) // eslint-disable-line no-unsanitized/method
  1884. : await __non_webpack_import__(debuggerScriptPath); // eslint-disable-line no-undef
  1885. self._PDFBug = PDFBug;
  1886. }
  1887. function reportPageStatsPDFBug({ pageNumber }) {
  1888. if (!globalThis.Stats?.enabled) {
  1889. return;
  1890. }
  1891. const pageView = PDFViewerApplication.pdfViewer.getPageView(
  1892. /* index = */ pageNumber - 1
  1893. );
  1894. globalThis.Stats.add(pageNumber, pageView?.pdfPage?.stats);
  1895. }
  1896. function webViewerInitialized() {
  1897. const { appConfig, eventBus } = PDFViewerApplication;
  1898. let file;
  1899. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1900. const queryString = document.location.search.substring(1);
  1901. const params = parseQueryString(queryString);
  1902. file = params.get("file") ?? AppOptions.get("defaultUrl");
  1903. validateFileURL(file);
  1904. } else if (PDFJSDev.test("MOZCENTRAL")) {
  1905. file = window.location.href;
  1906. } else if (PDFJSDev.test("CHROME")) {
  1907. file = AppOptions.get("defaultUrl");
  1908. }
  1909. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1910. const fileInput = appConfig.openFileInput;
  1911. fileInput.value = null;
  1912. fileInput.addEventListener("change", function (evt) {
  1913. const { files } = evt.target;
  1914. if (!files || files.length === 0) {
  1915. return;
  1916. }
  1917. eventBus.dispatch("fileinputchange", {
  1918. source: this,
  1919. fileInput: evt.target,
  1920. });
  1921. });
  1922. // Enable dragging-and-dropping a new PDF file onto the viewerContainer.
  1923. appConfig.mainContainer.addEventListener("dragover", function (evt) {
  1924. evt.preventDefault();
  1925. evt.dataTransfer.dropEffect =
  1926. evt.dataTransfer.effectAllowed === "copy" ? "copy" : "move";
  1927. });
  1928. appConfig.mainContainer.addEventListener("drop", function (evt) {
  1929. evt.preventDefault();
  1930. const { files } = evt.dataTransfer;
  1931. if (!files || files.length === 0) {
  1932. return;
  1933. }
  1934. eventBus.dispatch("fileinputchange", {
  1935. source: this,
  1936. fileInput: evt.dataTransfer,
  1937. });
  1938. });
  1939. }
  1940. if (!PDFViewerApplication.supportsDocumentFonts) {
  1941. AppOptions.set("disableFontFace", true);
  1942. PDFViewerApplication.l10n.get("web_fonts_disabled").then(msg => {
  1943. console.warn(msg);
  1944. });
  1945. }
  1946. if (!PDFViewerApplication.supportsPrinting) {
  1947. appConfig.toolbar?.print.classList.add("hidden");
  1948. appConfig.secondaryToolbar?.printButton.classList.add("hidden");
  1949. }
  1950. if (!PDFViewerApplication.supportsFullscreen) {
  1951. appConfig.secondaryToolbar?.presentationModeButton.classList.add("hidden");
  1952. }
  1953. if (PDFViewerApplication.supportsIntegratedFind) {
  1954. appConfig.toolbar?.viewFind.classList.add("hidden");
  1955. }
  1956. appConfig.mainContainer.addEventListener(
  1957. "transitionend",
  1958. function (evt) {
  1959. if (evt.target === /* mainContainer */ this) {
  1960. eventBus.dispatch("resize", { source: this });
  1961. }
  1962. },
  1963. true
  1964. );
  1965. try {
  1966. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1967. if (file) {
  1968. PDFViewerApplication.open(file);
  1969. } else {
  1970. PDFViewerApplication._hideViewBookmark();
  1971. }
  1972. } else if (PDFJSDev.test("MOZCENTRAL || CHROME")) {
  1973. PDFViewerApplication.setTitleUsingUrl(file, /* downloadUrl = */ file);
  1974. PDFViewerApplication.initPassiveLoading();
  1975. } else {
  1976. throw new Error("Not implemented: webViewerInitialized");
  1977. }
  1978. } catch (reason) {
  1979. PDFViewerApplication.l10n.get("loading_error").then(msg => {
  1980. PDFViewerApplication._documentError(msg, reason);
  1981. });
  1982. }
  1983. }
  1984. function webViewerPageRender({ pageNumber }) {
  1985. // If the page is (the most) visible when it starts rendering,
  1986. // ensure that the page number input loading indicator is displayed.
  1987. if (pageNumber === PDFViewerApplication.page) {
  1988. PDFViewerApplication.toolbar?.updateLoadingIndicatorState(true);
  1989. }
  1990. }
  1991. function webViewerPageRendered({ pageNumber, error }) {
  1992. // If the page is still visible when it has finished rendering,
  1993. // ensure that the page number input loading indicator is hidden.
  1994. if (pageNumber === PDFViewerApplication.page) {
  1995. PDFViewerApplication.toolbar?.updateLoadingIndicatorState(false);
  1996. }
  1997. // Use the rendered page to set the corresponding thumbnail image.
  1998. if (PDFViewerApplication.pdfSidebar?.visibleView === SidebarView.THUMBS) {
  1999. const pageView = PDFViewerApplication.pdfViewer.getPageView(
  2000. /* index = */ pageNumber - 1
  2001. );
  2002. const thumbnailView = PDFViewerApplication.pdfThumbnailViewer?.getThumbnail(
  2003. /* index = */ pageNumber - 1
  2004. );
  2005. if (pageView && thumbnailView) {
  2006. thumbnailView.setImage(pageView);
  2007. }
  2008. }
  2009. if (error) {
  2010. PDFViewerApplication.l10n.get("rendering_error").then(msg => {
  2011. PDFViewerApplication._otherError(msg, error);
  2012. });
  2013. }
  2014. }
  2015. function webViewerPageMode({ mode }) {
  2016. // Handle the 'pagemode' hash parameter, see also `PDFLinkService_setHash`.
  2017. let view;
  2018. switch (mode) {
  2019. case "thumbs":
  2020. view = SidebarView.THUMBS;
  2021. break;
  2022. case "bookmarks":
  2023. case "outline": // non-standard
  2024. view = SidebarView.OUTLINE;
  2025. break;
  2026. case "attachments": // non-standard
  2027. view = SidebarView.ATTACHMENTS;
  2028. break;
  2029. case "layers": // non-standard
  2030. view = SidebarView.LAYERS;
  2031. break;
  2032. case "none":
  2033. view = SidebarView.NONE;
  2034. break;
  2035. default:
  2036. console.error('Invalid "pagemode" hash parameter: ' + mode);
  2037. return;
  2038. }
  2039. PDFViewerApplication.pdfSidebar?.switchView(view, /* forceOpen = */ true);
  2040. }
  2041. function webViewerNamedAction(evt) {
  2042. // Processing a couple of named actions that might be useful, see also
  2043. // `PDFLinkService.executeNamedAction`.
  2044. switch (evt.action) {
  2045. case "GoToPage":
  2046. PDFViewerApplication.appConfig.toolbar?.pageNumber.select();
  2047. break;
  2048. case "Find":
  2049. if (!PDFViewerApplication.supportsIntegratedFind) {
  2050. PDFViewerApplication?.findBar.toggle();
  2051. }
  2052. break;
  2053. case "Print":
  2054. PDFViewerApplication.triggerPrinting();
  2055. break;
  2056. case "SaveAs":
  2057. PDFViewerApplication.downloadOrSave();
  2058. break;
  2059. }
  2060. }
  2061. function webViewerPresentationModeChanged(evt) {
  2062. PDFViewerApplication.pdfViewer.presentationModeState = evt.state;
  2063. }
  2064. function webViewerSidebarViewChanged({ view }) {
  2065. PDFViewerApplication.pdfRenderingQueue.isThumbnailViewEnabled =
  2066. view === SidebarView.THUMBS;
  2067. if (PDFViewerApplication.isInitialViewSet) {
  2068. // Only update the storage when the document has been loaded *and* rendered.
  2069. PDFViewerApplication.store?.set("sidebarView", view).catch(() => {
  2070. // Unable to write to storage.
  2071. });
  2072. }
  2073. }
  2074. function webViewerUpdateViewarea({ location }) {
  2075. if (PDFViewerApplication.isInitialViewSet) {
  2076. // Only update the storage when the document has been loaded *and* rendered.
  2077. PDFViewerApplication.store
  2078. ?.setMultiple({
  2079. page: location.pageNumber,
  2080. zoom: location.scale,
  2081. scrollLeft: location.left,
  2082. scrollTop: location.top,
  2083. rotation: location.rotation,
  2084. })
  2085. .catch(() => {
  2086. // Unable to write to storage.
  2087. });
  2088. }
  2089. if (PDFViewerApplication.appConfig.secondaryToolbar) {
  2090. const href = PDFViewerApplication.pdfLinkService.getAnchorUrl(
  2091. location.pdfOpenParams
  2092. );
  2093. PDFViewerApplication.appConfig.secondaryToolbar.viewBookmarkButton.href =
  2094. href;
  2095. }
  2096. }
  2097. function webViewerScrollModeChanged(evt) {
  2098. if (
  2099. PDFViewerApplication.isInitialViewSet &&
  2100. !PDFViewerApplication.pdfViewer.isInPresentationMode
  2101. ) {
  2102. // Only update the storage when the document has been loaded *and* rendered.
  2103. PDFViewerApplication.store?.set("scrollMode", evt.mode).catch(() => {
  2104. // Unable to write to storage.
  2105. });
  2106. }
  2107. }
  2108. function webViewerSpreadModeChanged(evt) {
  2109. if (
  2110. PDFViewerApplication.isInitialViewSet &&
  2111. !PDFViewerApplication.pdfViewer.isInPresentationMode
  2112. ) {
  2113. // Only update the storage when the document has been loaded *and* rendered.
  2114. PDFViewerApplication.store?.set("spreadMode", evt.mode).catch(() => {
  2115. // Unable to write to storage.
  2116. });
  2117. }
  2118. }
  2119. function webViewerResize() {
  2120. const { pdfDocument, pdfViewer, pdfRenderingQueue } = PDFViewerApplication;
  2121. if (pdfRenderingQueue.printing && window.matchMedia("print").matches) {
  2122. // Work-around issue 15324 by ignoring "resize" events during printing.
  2123. return;
  2124. }
  2125. if (!pdfDocument) {
  2126. return;
  2127. }
  2128. const currentScaleValue = pdfViewer.currentScaleValue;
  2129. if (
  2130. currentScaleValue === "auto" ||
  2131. currentScaleValue === "page-fit" ||
  2132. currentScaleValue === "page-width"
  2133. ) {
  2134. // Note: the scale is constant for 'page-actual'.
  2135. pdfViewer.currentScaleValue = currentScaleValue;
  2136. }
  2137. pdfViewer.update();
  2138. }
  2139. function webViewerHashchange(evt) {
  2140. const hash = evt.hash;
  2141. if (!hash) {
  2142. return;
  2143. }
  2144. if (!PDFViewerApplication.isInitialViewSet) {
  2145. PDFViewerApplication.initialBookmark = hash;
  2146. } else if (!PDFViewerApplication.pdfHistory?.popStateInProgress) {
  2147. PDFViewerApplication.pdfLinkService.setHash(hash);
  2148. }
  2149. }
  2150. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  2151. // eslint-disable-next-line no-var
  2152. var webViewerFileInputChange = function (evt) {
  2153. if (PDFViewerApplication.pdfViewer?.isInPresentationMode) {
  2154. return; // Opening a new PDF file isn't supported in Presentation Mode.
  2155. }
  2156. const file = evt.fileInput.files[0];
  2157. let url = URL.createObjectURL(file);
  2158. if (file.name) {
  2159. url = { url, originalUrl: file.name };
  2160. }
  2161. PDFViewerApplication.open(url);
  2162. };
  2163. // eslint-disable-next-line no-var
  2164. var webViewerOpenFile = function (evt) {
  2165. const fileInput = PDFViewerApplication.appConfig.openFileInput;
  2166. fileInput.click();
  2167. };
  2168. }
  2169. function webViewerPresentationMode() {
  2170. PDFViewerApplication.requestPresentationMode();
  2171. }
  2172. function webViewerSwitchAnnotationEditorMode(evt) {
  2173. PDFViewerApplication.pdfViewer.annotationEditorMode = evt.mode;
  2174. }
  2175. function webViewerSwitchAnnotationEditorParams(evt) {
  2176. PDFViewerApplication.pdfViewer.annotationEditorParams = evt;
  2177. }
  2178. function webViewerPrint() {
  2179. PDFViewerApplication.triggerPrinting();
  2180. }
  2181. function webViewerDownload() {
  2182. PDFViewerApplication.downloadOrSave();
  2183. }
  2184. function webViewerFirstPage() {
  2185. if (PDFViewerApplication.pdfDocument) {
  2186. PDFViewerApplication.page = 1;
  2187. }
  2188. }
  2189. function webViewerLastPage() {
  2190. if (PDFViewerApplication.pdfDocument) {
  2191. PDFViewerApplication.page = PDFViewerApplication.pagesCount;
  2192. }
  2193. }
  2194. function webViewerNextPage() {
  2195. PDFViewerApplication.pdfViewer.nextPage();
  2196. }
  2197. function webViewerPreviousPage() {
  2198. PDFViewerApplication.pdfViewer.previousPage();
  2199. }
  2200. function webViewerZoomIn() {
  2201. PDFViewerApplication.zoomIn();
  2202. }
  2203. function webViewerZoomOut() {
  2204. PDFViewerApplication.zoomOut();
  2205. }
  2206. function webViewerZoomReset() {
  2207. PDFViewerApplication.zoomReset();
  2208. }
  2209. function webViewerPageNumberChanged(evt) {
  2210. const pdfViewer = PDFViewerApplication.pdfViewer;
  2211. // Note that for `<input type="number">` HTML elements, an empty string will
  2212. // be returned for non-number inputs; hence we simply do nothing in that case.
  2213. if (evt.value !== "") {
  2214. PDFViewerApplication.pdfLinkService.goToPage(evt.value);
  2215. }
  2216. // Ensure that the page number input displays the correct value, even if the
  2217. // value entered by the user was invalid (e.g. a floating point number).
  2218. if (
  2219. evt.value !== pdfViewer.currentPageNumber.toString() &&
  2220. evt.value !== pdfViewer.currentPageLabel
  2221. ) {
  2222. PDFViewerApplication.toolbar?.setPageNumber(
  2223. pdfViewer.currentPageNumber,
  2224. pdfViewer.currentPageLabel
  2225. );
  2226. }
  2227. }
  2228. function webViewerScaleChanged(evt) {
  2229. PDFViewerApplication.pdfViewer.currentScaleValue = evt.value;
  2230. }
  2231. function webViewerRotateCw() {
  2232. PDFViewerApplication.rotatePages(90);
  2233. }
  2234. function webViewerRotateCcw() {
  2235. PDFViewerApplication.rotatePages(-90);
  2236. }
  2237. function webViewerOptionalContentConfig(evt) {
  2238. PDFViewerApplication.pdfViewer.optionalContentConfigPromise = evt.promise;
  2239. }
  2240. function webViewerSwitchScrollMode(evt) {
  2241. PDFViewerApplication.pdfViewer.scrollMode = evt.mode;
  2242. }
  2243. function webViewerSwitchSpreadMode(evt) {
  2244. PDFViewerApplication.pdfViewer.spreadMode = evt.mode;
  2245. }
  2246. function webViewerDocumentProperties() {
  2247. PDFViewerApplication.pdfDocumentProperties?.open();
  2248. }
  2249. function webViewerFindFromUrlHash(evt) {
  2250. PDFViewerApplication.eventBus.dispatch("find", {
  2251. source: evt.source,
  2252. type: "",
  2253. query: evt.query,
  2254. phraseSearch: evt.phraseSearch,
  2255. caseSensitive: false,
  2256. entireWord: false,
  2257. highlightAll: true,
  2258. findPrevious: false,
  2259. matchDiacritics: true,
  2260. });
  2261. }
  2262. function webViewerUpdateFindMatchesCount({ matchesCount }) {
  2263. if (PDFViewerApplication.supportsIntegratedFind) {
  2264. PDFViewerApplication.externalServices.updateFindMatchesCount(matchesCount);
  2265. } else {
  2266. PDFViewerApplication.findBar.updateResultsCount(matchesCount);
  2267. }
  2268. }
  2269. function webViewerUpdateFindControlState({
  2270. state,
  2271. previous,
  2272. matchesCount,
  2273. rawQuery,
  2274. }) {
  2275. if (PDFViewerApplication.supportsIntegratedFind) {
  2276. PDFViewerApplication.externalServices.updateFindControlState({
  2277. result: state,
  2278. findPrevious: previous,
  2279. matchesCount,
  2280. rawQuery,
  2281. });
  2282. } else {
  2283. PDFViewerApplication.findBar?.updateUIState(state, previous, matchesCount);
  2284. }
  2285. }
  2286. function webViewerScaleChanging(evt) {
  2287. PDFViewerApplication.toolbar?.setPageScale(evt.presetValue, evt.scale);
  2288. PDFViewerApplication.pdfViewer.update();
  2289. }
  2290. function webViewerRotationChanging(evt) {
  2291. if (PDFViewerApplication.pdfThumbnailViewer) {
  2292. PDFViewerApplication.pdfThumbnailViewer.pagesRotation = evt.pagesRotation;
  2293. }
  2294. PDFViewerApplication.forceRendering();
  2295. // Ensure that the active page doesn't change during rotation.
  2296. PDFViewerApplication.pdfViewer.currentPageNumber = evt.pageNumber;
  2297. }
  2298. function webViewerPageChanging({ pageNumber, pageLabel }) {
  2299. PDFViewerApplication.toolbar?.setPageNumber(pageNumber, pageLabel);
  2300. PDFViewerApplication.secondaryToolbar?.setPageNumber(pageNumber);
  2301. if (PDFViewerApplication.pdfSidebar?.visibleView === SidebarView.THUMBS) {
  2302. PDFViewerApplication.pdfThumbnailViewer?.scrollThumbnailIntoView(
  2303. pageNumber
  2304. );
  2305. }
  2306. // Show/hide the loading indicator in the page number input element.
  2307. const currentPage = PDFViewerApplication.pdfViewer.getPageView(
  2308. /* index = */ pageNumber - 1
  2309. );
  2310. PDFViewerApplication.toolbar?.updateLoadingIndicatorState(
  2311. currentPage?.renderingState === RenderingStates.RUNNING
  2312. );
  2313. }
  2314. function webViewerResolutionChange(evt) {
  2315. PDFViewerApplication.pdfViewer.refresh();
  2316. }
  2317. function webViewerVisibilityChange(evt) {
  2318. if (document.visibilityState === "visible") {
  2319. // Ignore mouse wheel zooming during tab switches (bug 1503412).
  2320. setZoomDisabledTimeout();
  2321. }
  2322. }
  2323. let zoomDisabledTimeout = null;
  2324. function setZoomDisabledTimeout() {
  2325. if (zoomDisabledTimeout) {
  2326. clearTimeout(zoomDisabledTimeout);
  2327. }
  2328. zoomDisabledTimeout = setTimeout(function () {
  2329. zoomDisabledTimeout = null;
  2330. }, WHEEL_ZOOM_DISABLED_TIMEOUT);
  2331. }
  2332. function webViewerWheel(evt) {
  2333. const { pdfViewer, supportedMouseWheelZoomModifierKeys } =
  2334. PDFViewerApplication;
  2335. if (pdfViewer.isInPresentationMode) {
  2336. return;
  2337. }
  2338. if (
  2339. (evt.ctrlKey && supportedMouseWheelZoomModifierKeys.ctrlKey) ||
  2340. (evt.metaKey && supportedMouseWheelZoomModifierKeys.metaKey)
  2341. ) {
  2342. // Only zoom the pages, not the entire viewer.
  2343. evt.preventDefault();
  2344. // NOTE: this check must be placed *after* preventDefault.
  2345. if (zoomDisabledTimeout || document.visibilityState === "hidden") {
  2346. return;
  2347. }
  2348. // It is important that we query deltaMode before delta{X,Y}, so that
  2349. // Firefox doesn't switch to DOM_DELTA_PIXEL mode for compat with other
  2350. // browsers, see https://bugzilla.mozilla.org/show_bug.cgi?id=1392460.
  2351. const deltaMode = evt.deltaMode;
  2352. const delta = normalizeWheelEventDirection(evt);
  2353. const previousScale = pdfViewer.currentScale;
  2354. let ticks = 0;
  2355. if (
  2356. deltaMode === WheelEvent.DOM_DELTA_LINE ||
  2357. deltaMode === WheelEvent.DOM_DELTA_PAGE
  2358. ) {
  2359. // For line-based devices, use one tick per event, because different
  2360. // OSs have different defaults for the number lines. But we generally
  2361. // want one "clicky" roll of the wheel (which produces one event) to
  2362. // adjust the zoom by one step.
  2363. if (Math.abs(delta) >= 1) {
  2364. ticks = Math.sign(delta);
  2365. } else {
  2366. // If we're getting fractional lines (I can't think of a scenario
  2367. // this might actually happen), be safe and use the accumulator.
  2368. ticks = PDFViewerApplication.accumulateWheelTicks(delta);
  2369. }
  2370. } else {
  2371. // pixel-based devices
  2372. const PIXELS_PER_LINE_SCALE = 30;
  2373. ticks = PDFViewerApplication.accumulateWheelTicks(
  2374. delta / PIXELS_PER_LINE_SCALE
  2375. );
  2376. }
  2377. if (ticks < 0) {
  2378. PDFViewerApplication.zoomOut(-ticks);
  2379. } else if (ticks > 0) {
  2380. PDFViewerApplication.zoomIn(ticks);
  2381. }
  2382. const currentScale = pdfViewer.currentScale;
  2383. if (previousScale !== currentScale) {
  2384. // After scaling the page via zoomIn/zoomOut, the position of the upper-
  2385. // left corner is restored. When the mouse wheel is used, the position
  2386. // under the cursor should be restored instead.
  2387. const scaleCorrectionFactor = currentScale / previousScale - 1;
  2388. const [top, left] = pdfViewer.containerTopLeft;
  2389. const dx = evt.clientX - left;
  2390. const dy = evt.clientY - top;
  2391. pdfViewer.container.scrollLeft += dx * scaleCorrectionFactor;
  2392. pdfViewer.container.scrollTop += dy * scaleCorrectionFactor;
  2393. }
  2394. } else {
  2395. setZoomDisabledTimeout();
  2396. }
  2397. }
  2398. function webViewerTouchStart(evt) {
  2399. if (evt.touches.length > 1) {
  2400. // Disable touch-based zooming, because the entire UI bits gets zoomed and
  2401. // that doesn't look great. If we do want to have a good touch-based
  2402. // zooming experience, we need to implement smooth zoom capability (probably
  2403. // using a CSS transform for faster visual response, followed by async
  2404. // re-rendering at the final zoom level) and do gesture detection on the
  2405. // touchmove events to drive it. Or if we want to settle for a less good
  2406. // experience we can make the touchmove events drive the existing step-zoom
  2407. // behaviour that the ctrl+mousewheel path takes.
  2408. evt.preventDefault();
  2409. }
  2410. }
  2411. function webViewerClick(evt) {
  2412. if (!PDFViewerApplication.secondaryToolbar?.isOpen) {
  2413. return;
  2414. }
  2415. const appConfig = PDFViewerApplication.appConfig;
  2416. if (
  2417. PDFViewerApplication.pdfViewer.containsElement(evt.target) ||
  2418. (appConfig.toolbar?.container.contains(evt.target) &&
  2419. evt.target !== appConfig.secondaryToolbar?.toggleButton)
  2420. ) {
  2421. PDFViewerApplication.secondaryToolbar.close();
  2422. }
  2423. }
  2424. function webViewerKeyDown(evt) {
  2425. if (PDFViewerApplication.overlayManager.active) {
  2426. return;
  2427. }
  2428. const { eventBus, pdfViewer } = PDFViewerApplication;
  2429. const isViewerInPresentationMode = pdfViewer.isInPresentationMode;
  2430. let handled = false,
  2431. ensureViewerFocused = false;
  2432. const cmd =
  2433. (evt.ctrlKey ? 1 : 0) |
  2434. (evt.altKey ? 2 : 0) |
  2435. (evt.shiftKey ? 4 : 0) |
  2436. (evt.metaKey ? 8 : 0);
  2437. // First, handle the key bindings that are independent whether an input
  2438. // control is selected or not.
  2439. if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
  2440. // either CTRL or META key with optional SHIFT.
  2441. switch (evt.keyCode) {
  2442. case 70: // f
  2443. if (!PDFViewerApplication.supportsIntegratedFind && !evt.shiftKey) {
  2444. PDFViewerApplication.findBar?.open();
  2445. handled = true;
  2446. }
  2447. break;
  2448. case 71: // g
  2449. if (!PDFViewerApplication.supportsIntegratedFind) {
  2450. const { state } = PDFViewerApplication.findController;
  2451. if (state) {
  2452. const eventState = Object.assign(Object.create(null), state, {
  2453. source: window,
  2454. type: "again",
  2455. findPrevious: cmd === 5 || cmd === 12,
  2456. });
  2457. eventBus.dispatch("find", eventState);
  2458. }
  2459. handled = true;
  2460. }
  2461. break;
  2462. case 61: // FF/Mac '='
  2463. case 107: // FF '+' and '='
  2464. case 187: // Chrome '+'
  2465. case 171: // FF with German keyboard
  2466. if (!isViewerInPresentationMode) {
  2467. PDFViewerApplication.zoomIn();
  2468. }
  2469. handled = true;
  2470. break;
  2471. case 173: // FF/Mac '-'
  2472. case 109: // FF '-'
  2473. case 189: // Chrome '-'
  2474. if (!isViewerInPresentationMode) {
  2475. PDFViewerApplication.zoomOut();
  2476. }
  2477. handled = true;
  2478. break;
  2479. case 48: // '0'
  2480. case 96: // '0' on Numpad of Swedish keyboard
  2481. if (!isViewerInPresentationMode) {
  2482. // keeping it unhandled (to restore page zoom to 100%)
  2483. setTimeout(function () {
  2484. // ... and resetting the scale after browser adjusts its scale
  2485. PDFViewerApplication.zoomReset();
  2486. });
  2487. handled = false;
  2488. }
  2489. break;
  2490. case 38: // up arrow
  2491. if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
  2492. PDFViewerApplication.page = 1;
  2493. handled = true;
  2494. ensureViewerFocused = true;
  2495. }
  2496. break;
  2497. case 40: // down arrow
  2498. if (
  2499. isViewerInPresentationMode ||
  2500. PDFViewerApplication.page < PDFViewerApplication.pagesCount
  2501. ) {
  2502. PDFViewerApplication.page = PDFViewerApplication.pagesCount;
  2503. handled = true;
  2504. ensureViewerFocused = true;
  2505. }
  2506. break;
  2507. }
  2508. }
  2509. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC || CHROME")) {
  2510. // CTRL or META without shift
  2511. if (cmd === 1 || cmd === 8) {
  2512. switch (evt.keyCode) {
  2513. case 83: // s
  2514. eventBus.dispatch("download", { source: window });
  2515. handled = true;
  2516. break;
  2517. case 79: // o
  2518. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  2519. eventBus.dispatch("openfile", { source: window });
  2520. handled = true;
  2521. }
  2522. break;
  2523. }
  2524. }
  2525. }
  2526. // CTRL+ALT or Option+Command
  2527. if (cmd === 3 || cmd === 10) {
  2528. switch (evt.keyCode) {
  2529. case 80: // p
  2530. PDFViewerApplication.requestPresentationMode();
  2531. handled = true;
  2532. PDFViewerApplication.externalServices.reportTelemetry({
  2533. type: "buttons",
  2534. data: { id: "presentationModeKeyboard" },
  2535. });
  2536. break;
  2537. case 71: // g
  2538. // focuses input#pageNumber field
  2539. if (PDFViewerApplication.appConfig.toolbar) {
  2540. PDFViewerApplication.appConfig.toolbar.pageNumber.select();
  2541. handled = true;
  2542. }
  2543. break;
  2544. }
  2545. }
  2546. if (handled) {
  2547. if (ensureViewerFocused && !isViewerInPresentationMode) {
  2548. pdfViewer.focus();
  2549. }
  2550. evt.preventDefault();
  2551. return;
  2552. }
  2553. // Some shortcuts should not get handled if a control/input element
  2554. // is selected.
  2555. const curElement = getActiveOrFocusedElement();
  2556. const curElementTagName = curElement?.tagName.toUpperCase();
  2557. if (
  2558. curElementTagName === "INPUT" ||
  2559. curElementTagName === "TEXTAREA" ||
  2560. curElementTagName === "SELECT" ||
  2561. curElement?.isContentEditable
  2562. ) {
  2563. // Make sure that the secondary toolbar is closed when Escape is pressed.
  2564. if (evt.keyCode !== /* Esc = */ 27) {
  2565. return;
  2566. }
  2567. }
  2568. // No control key pressed at all.
  2569. if (cmd === 0) {
  2570. let turnPage = 0,
  2571. turnOnlyIfPageFit = false;
  2572. switch (evt.keyCode) {
  2573. case 38: // up arrow
  2574. case 33: // pg up
  2575. // vertical scrolling using arrow/pg keys
  2576. if (pdfViewer.isVerticalScrollbarEnabled) {
  2577. turnOnlyIfPageFit = true;
  2578. }
  2579. turnPage = -1;
  2580. break;
  2581. case 8: // backspace
  2582. if (!isViewerInPresentationMode) {
  2583. turnOnlyIfPageFit = true;
  2584. }
  2585. turnPage = -1;
  2586. break;
  2587. case 37: // left arrow
  2588. // horizontal scrolling using arrow keys
  2589. if (pdfViewer.isHorizontalScrollbarEnabled) {
  2590. turnOnlyIfPageFit = true;
  2591. }
  2592. /* falls through */
  2593. case 75: // 'k'
  2594. case 80: // 'p'
  2595. turnPage = -1;
  2596. break;
  2597. case 27: // esc key
  2598. if (PDFViewerApplication.secondaryToolbar?.isOpen) {
  2599. PDFViewerApplication.secondaryToolbar.close();
  2600. handled = true;
  2601. }
  2602. if (
  2603. !PDFViewerApplication.supportsIntegratedFind &&
  2604. PDFViewerApplication.findBar?.opened
  2605. ) {
  2606. PDFViewerApplication.findBar.close();
  2607. handled = true;
  2608. }
  2609. break;
  2610. case 40: // down arrow
  2611. case 34: // pg down
  2612. // vertical scrolling using arrow/pg keys
  2613. if (pdfViewer.isVerticalScrollbarEnabled) {
  2614. turnOnlyIfPageFit = true;
  2615. }
  2616. turnPage = 1;
  2617. break;
  2618. case 13: // enter key
  2619. case 32: // spacebar
  2620. if (!isViewerInPresentationMode) {
  2621. turnOnlyIfPageFit = true;
  2622. }
  2623. turnPage = 1;
  2624. break;
  2625. case 39: // right arrow
  2626. // horizontal scrolling using arrow keys
  2627. if (pdfViewer.isHorizontalScrollbarEnabled) {
  2628. turnOnlyIfPageFit = true;
  2629. }
  2630. /* falls through */
  2631. case 74: // 'j'
  2632. case 78: // 'n'
  2633. turnPage = 1;
  2634. break;
  2635. case 36: // home
  2636. if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
  2637. PDFViewerApplication.page = 1;
  2638. handled = true;
  2639. ensureViewerFocused = true;
  2640. }
  2641. break;
  2642. case 35: // end
  2643. if (
  2644. isViewerInPresentationMode ||
  2645. PDFViewerApplication.page < PDFViewerApplication.pagesCount
  2646. ) {
  2647. PDFViewerApplication.page = PDFViewerApplication.pagesCount;
  2648. handled = true;
  2649. ensureViewerFocused = true;
  2650. }
  2651. break;
  2652. case 83: // 's'
  2653. PDFViewerApplication.pdfCursorTools?.switchTool(CursorTool.SELECT);
  2654. break;
  2655. case 72: // 'h'
  2656. PDFViewerApplication.pdfCursorTools?.switchTool(CursorTool.HAND);
  2657. break;
  2658. case 82: // 'r'
  2659. PDFViewerApplication.rotatePages(90);
  2660. break;
  2661. case 115: // F4
  2662. PDFViewerApplication.pdfSidebar?.toggle();
  2663. break;
  2664. }
  2665. if (
  2666. turnPage !== 0 &&
  2667. (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === "page-fit")
  2668. ) {
  2669. if (turnPage > 0) {
  2670. pdfViewer.nextPage();
  2671. } else {
  2672. pdfViewer.previousPage();
  2673. }
  2674. handled = true;
  2675. }
  2676. }
  2677. // shift-key
  2678. if (cmd === 4) {
  2679. switch (evt.keyCode) {
  2680. case 13: // enter key
  2681. case 32: // spacebar
  2682. if (
  2683. !isViewerInPresentationMode &&
  2684. pdfViewer.currentScaleValue !== "page-fit"
  2685. ) {
  2686. break;
  2687. }
  2688. pdfViewer.previousPage();
  2689. handled = true;
  2690. break;
  2691. case 82: // 'r'
  2692. PDFViewerApplication.rotatePages(-90);
  2693. break;
  2694. }
  2695. }
  2696. if (!handled && !isViewerInPresentationMode) {
  2697. // 33=Page Up 34=Page Down 35=End 36=Home
  2698. // 37=Left 38=Up 39=Right 40=Down
  2699. // 32=Spacebar
  2700. if (
  2701. (evt.keyCode >= 33 && evt.keyCode <= 40) ||
  2702. (evt.keyCode === 32 && curElementTagName !== "BUTTON")
  2703. ) {
  2704. ensureViewerFocused = true;
  2705. }
  2706. }
  2707. if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) {
  2708. // The page container is not focused, but a page navigation key has been
  2709. // pressed. Change the focus to the viewer container to make sure that
  2710. // navigation by keyboard works as expected.
  2711. pdfViewer.focus();
  2712. }
  2713. if (handled) {
  2714. evt.preventDefault();
  2715. }
  2716. }
  2717. function beforeUnload(evt) {
  2718. evt.preventDefault();
  2719. evt.returnValue = "";
  2720. return false;
  2721. }
  2722. function webViewerAnnotationEditorStatesChanged(data) {
  2723. PDFViewerApplication.externalServices.updateEditorStates(data);
  2724. }
  2725. /* Abstract factory for the print service. */
  2726. const PDFPrintServiceFactory = {
  2727. instance: {
  2728. supportsPrinting: false,
  2729. createPrintService() {
  2730. throw new Error("Not implemented: createPrintService");
  2731. },
  2732. },
  2733. };
  2734. export {
  2735. DefaultExternalServices,
  2736. PDFPrintServiceFactory,
  2737. PDFViewerApplication,
  2738. };