UNPKG

11.8 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
4 *
5 * Copyright 2022 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
21 */
22"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.PDFScriptingManager = void 0;
28
29var _ui_utils = require("./ui_utils.js");
30
31var _pdf = require("../pdf");
32
33class PDFScriptingManager {
34 constructor({
35 eventBus,
36 sandboxBundleSrc = null,
37 scriptingFactory = null,
38 docPropertiesLookup = null
39 }) {
40 this._pdfDocument = null;
41 this._pdfViewer = null;
42 this._closeCapability = null;
43 this._destroyCapability = null;
44 this._scripting = null;
45 this._mouseState = Object.create(null);
46 this._ready = false;
47 this._eventBus = eventBus;
48 this._sandboxBundleSrc = sandboxBundleSrc;
49 this._scriptingFactory = scriptingFactory;
50 this._docPropertiesLookup = docPropertiesLookup;
51 }
52
53 setViewer(pdfViewer) {
54 this._pdfViewer = pdfViewer;
55 }
56
57 async setDocument(pdfDocument) {
58 if (this._pdfDocument) {
59 await this._destroyScripting();
60 }
61
62 this._pdfDocument = pdfDocument;
63
64 if (!pdfDocument) {
65 return;
66 }
67
68 const [objects, calculationOrder, docActions] = await Promise.all([pdfDocument.getFieldObjects(), pdfDocument.getCalculationOrderIds(), pdfDocument.getJSActions()]);
69
70 if (!objects && !docActions) {
71 await this._destroyScripting();
72 return;
73 }
74
75 if (pdfDocument !== this._pdfDocument) {
76 return;
77 }
78
79 try {
80 this._scripting = this._createScripting();
81 } catch (error) {
82 console.error(`PDFScriptingManager.setDocument: "${error?.message}".`);
83 await this._destroyScripting();
84 return;
85 }
86
87 this._internalEvents.set("updatefromsandbox", event => {
88 if (event?.source !== window) {
89 return;
90 }
91
92 this._updateFromSandbox(event.detail);
93 });
94
95 this._internalEvents.set("dispatcheventinsandbox", event => {
96 this._scripting?.dispatchEventInSandbox(event.detail);
97 });
98
99 this._internalEvents.set("pagechanging", ({
100 pageNumber,
101 previous
102 }) => {
103 if (pageNumber === previous) {
104 return;
105 }
106
107 this._dispatchPageClose(previous);
108
109 this._dispatchPageOpen(pageNumber);
110 });
111
112 this._internalEvents.set("pagerendered", ({
113 pageNumber
114 }) => {
115 if (!this._pageOpenPending.has(pageNumber)) {
116 return;
117 }
118
119 if (pageNumber !== this._pdfViewer.currentPageNumber) {
120 return;
121 }
122
123 this._dispatchPageOpen(pageNumber);
124 });
125
126 this._internalEvents.set("pagesdestroy", async event => {
127 await this._dispatchPageClose(this._pdfViewer.currentPageNumber);
128 await this._scripting?.dispatchEventInSandbox({
129 id: "doc",
130 name: "WillClose"
131 });
132 this._closeCapability?.resolve();
133 });
134
135 this._domEvents.set("mousedown", event => {
136 this._mouseState.isDown = true;
137 });
138
139 this._domEvents.set("mouseup", event => {
140 this._mouseState.isDown = false;
141 });
142
143 for (const [name, listener] of this._internalEvents) {
144 this._eventBus._on(name, listener);
145 }
146
147 for (const [name, listener] of this._domEvents) {
148 window.addEventListener(name, listener, true);
149 }
150
151 try {
152 const docProperties = await this._getDocProperties();
153
154 if (pdfDocument !== this._pdfDocument) {
155 return;
156 }
157
158 await this._scripting.createSandbox({
159 objects,
160 calculationOrder,
161 appInfo: {
162 platform: navigator.platform,
163 language: navigator.language
164 },
165 docInfo: { ...docProperties,
166 actions: docActions
167 }
168 });
169
170 this._eventBus.dispatch("sandboxcreated", {
171 source: this
172 });
173 } catch (error) {
174 console.error(`PDFScriptingManager.setDocument: "${error?.message}".`);
175 await this._destroyScripting();
176 return;
177 }
178
179 await this._scripting?.dispatchEventInSandbox({
180 id: "doc",
181 name: "Open"
182 });
183 await this._dispatchPageOpen(this._pdfViewer.currentPageNumber, true);
184 Promise.resolve().then(() => {
185 if (pdfDocument === this._pdfDocument) {
186 this._ready = true;
187 }
188 });
189 }
190
191 async dispatchWillSave(detail) {
192 return this._scripting?.dispatchEventInSandbox({
193 id: "doc",
194 name: "WillSave"
195 });
196 }
197
198 async dispatchDidSave(detail) {
199 return this._scripting?.dispatchEventInSandbox({
200 id: "doc",
201 name: "DidSave"
202 });
203 }
204
205 async dispatchWillPrint(detail) {
206 return this._scripting?.dispatchEventInSandbox({
207 id: "doc",
208 name: "WillPrint"
209 });
210 }
211
212 async dispatchDidPrint(detail) {
213 return this._scripting?.dispatchEventInSandbox({
214 id: "doc",
215 name: "DidPrint"
216 });
217 }
218
219 get mouseState() {
220 return this._mouseState;
221 }
222
223 get destroyPromise() {
224 return this._destroyCapability?.promise || null;
225 }
226
227 get ready() {
228 return this._ready;
229 }
230
231 get _internalEvents() {
232 return (0, _pdf.shadow)(this, "_internalEvents", new Map());
233 }
234
235 get _domEvents() {
236 return (0, _pdf.shadow)(this, "_domEvents", new Map());
237 }
238
239 get _pageOpenPending() {
240 return (0, _pdf.shadow)(this, "_pageOpenPending", new Set());
241 }
242
243 get _visitedPages() {
244 return (0, _pdf.shadow)(this, "_visitedPages", new Map());
245 }
246
247 async _updateFromSandbox(detail) {
248 const isInPresentationMode = this._pdfViewer.isInPresentationMode || this._pdfViewer.isChangingPresentationMode;
249 const {
250 id,
251 siblings,
252 command,
253 value
254 } = detail;
255
256 if (!id) {
257 switch (command) {
258 case "clear":
259 console.clear();
260 break;
261
262 case "error":
263 console.error(value);
264 break;
265
266 case "layout":
267 if (isInPresentationMode) {
268 return;
269 }
270
271 const modes = (0, _ui_utils.apiPageLayoutToViewerModes)(value);
272 this._pdfViewer.spreadMode = modes.spreadMode;
273 break;
274
275 case "page-num":
276 this._pdfViewer.currentPageNumber = value + 1;
277 break;
278
279 case "print":
280 await this._pdfViewer.pagesPromise;
281
282 this._eventBus.dispatch("print", {
283 source: this
284 });
285
286 break;
287
288 case "println":
289 console.log(value);
290 break;
291
292 case "zoom":
293 if (isInPresentationMode) {
294 return;
295 }
296
297 this._pdfViewer.currentScaleValue = value;
298 break;
299
300 case "SaveAs":
301 this._eventBus.dispatch("download", {
302 source: this
303 });
304
305 break;
306
307 case "FirstPage":
308 this._pdfViewer.currentPageNumber = 1;
309 break;
310
311 case "LastPage":
312 this._pdfViewer.currentPageNumber = this._pdfViewer.pagesCount;
313 break;
314
315 case "NextPage":
316 this._pdfViewer.nextPage();
317
318 break;
319
320 case "PrevPage":
321 this._pdfViewer.previousPage();
322
323 break;
324
325 case "ZoomViewIn":
326 if (isInPresentationMode) {
327 return;
328 }
329
330 this._pdfViewer.increaseScale();
331
332 break;
333
334 case "ZoomViewOut":
335 if (isInPresentationMode) {
336 return;
337 }
338
339 this._pdfViewer.decreaseScale();
340
341 break;
342 }
343
344 return;
345 }
346
347 if (isInPresentationMode) {
348 if (detail.focus) {
349 return;
350 }
351 }
352
353 delete detail.id;
354 delete detail.siblings;
355 const ids = siblings ? [id, ...siblings] : [id];
356
357 for (const elementId of ids) {
358 const element = document.querySelector(`[data-element-id="${elementId}"]`);
359
360 if (element) {
361 element.dispatchEvent(new CustomEvent("updatefromsandbox", {
362 detail
363 }));
364 } else {
365 this._pdfDocument?.annotationStorage.setValue(elementId, detail);
366 }
367 }
368 }
369
370 async _dispatchPageOpen(pageNumber, initialize = false) {
371 const pdfDocument = this._pdfDocument,
372 visitedPages = this._visitedPages;
373
374 if (initialize) {
375 this._closeCapability = (0, _pdf.createPromiseCapability)();
376 }
377
378 if (!this._closeCapability) {
379 return;
380 }
381
382 const pageView = this._pdfViewer.getPageView(pageNumber - 1);
383
384 if (pageView?.renderingState !== _ui_utils.RenderingStates.FINISHED) {
385 this._pageOpenPending.add(pageNumber);
386
387 return;
388 }
389
390 this._pageOpenPending.delete(pageNumber);
391
392 const actionsPromise = (async () => {
393 const actions = await (!visitedPages.has(pageNumber) ? pageView.pdfPage?.getJSActions() : null);
394
395 if (pdfDocument !== this._pdfDocument) {
396 return;
397 }
398
399 await this._scripting?.dispatchEventInSandbox({
400 id: "page",
401 name: "PageOpen",
402 pageNumber,
403 actions
404 });
405 })();
406
407 visitedPages.set(pageNumber, actionsPromise);
408 }
409
410 async _dispatchPageClose(pageNumber) {
411 const pdfDocument = this._pdfDocument,
412 visitedPages = this._visitedPages;
413
414 if (!this._closeCapability) {
415 return;
416 }
417
418 if (this._pageOpenPending.has(pageNumber)) {
419 return;
420 }
421
422 const actionsPromise = visitedPages.get(pageNumber);
423
424 if (!actionsPromise) {
425 return;
426 }
427
428 visitedPages.set(pageNumber, null);
429 await actionsPromise;
430
431 if (pdfDocument !== this._pdfDocument) {
432 return;
433 }
434
435 await this._scripting?.dispatchEventInSandbox({
436 id: "page",
437 name: "PageClose",
438 pageNumber
439 });
440 }
441
442 async _getDocProperties() {
443 if (this._docPropertiesLookup) {
444 return this._docPropertiesLookup(this._pdfDocument);
445 }
446
447 throw new Error("_getDocProperties: Unable to lookup properties.");
448 }
449
450 _createScripting() {
451 this._destroyCapability = (0, _pdf.createPromiseCapability)();
452
453 if (this._scripting) {
454 throw new Error("_createScripting: Scripting already exists.");
455 }
456
457 if (this._scriptingFactory) {
458 return this._scriptingFactory.createScripting({
459 sandboxBundleSrc: this._sandboxBundleSrc
460 });
461 }
462
463 throw new Error("_createScripting: Cannot create scripting.");
464 }
465
466 async _destroyScripting() {
467 if (!this._scripting) {
468 this._pdfDocument = null;
469 this._destroyCapability?.resolve();
470 return;
471 }
472
473 if (this._closeCapability) {
474 await Promise.race([this._closeCapability.promise, new Promise(resolve => {
475 setTimeout(resolve, 1000);
476 })]).catch(reason => {});
477 this._closeCapability = null;
478 }
479
480 this._pdfDocument = null;
481
482 try {
483 await this._scripting.destroySandbox();
484 } catch (ex) {}
485
486 for (const [name, listener] of this._internalEvents) {
487 this._eventBus._off(name, listener);
488 }
489
490 this._internalEvents.clear();
491
492 for (const [name, listener] of this._domEvents) {
493 window.removeEventListener(name, listener, true);
494 }
495
496 this._domEvents.clear();
497
498 this._pageOpenPending.clear();
499
500 this._visitedPages.clear();
501
502 this._scripting = null;
503 delete this._mouseState.isDown;
504 this._ready = false;
505 this._destroyCapability?.resolve();
506 }
507
508}
509
510exports.PDFScriptingManager = PDFScriptingManager;
\No newline at end of file