pdf_scripting_manager.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * Javascript code in this page
  4. *
  5. * Copyright 2021 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. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.PDFScriptingManager = void 0;
  27. var _pdf = require("../pdf");
  28. var _ui_utils = require("./ui_utils.js");
  29. var _pdf_rendering_queue = require("./pdf_rendering_queue.js");
  30. class PDFScriptingManager {
  31. constructor({
  32. eventBus,
  33. sandboxBundleSrc = null,
  34. scriptingFactory = null,
  35. docPropertiesLookup = null
  36. }) {
  37. this._pdfDocument = null;
  38. this._pdfViewer = null;
  39. this._closeCapability = null;
  40. this._destroyCapability = null;
  41. this._scripting = null;
  42. this._mouseState = Object.create(null);
  43. this._ready = false;
  44. this._eventBus = eventBus;
  45. this._sandboxBundleSrc = sandboxBundleSrc;
  46. this._scriptingFactory = scriptingFactory;
  47. this._docPropertiesLookup = docPropertiesLookup;
  48. }
  49. setViewer(pdfViewer) {
  50. this._pdfViewer = pdfViewer;
  51. }
  52. async setDocument(pdfDocument) {
  53. if (this._pdfDocument) {
  54. await this._destroyScripting();
  55. }
  56. this._pdfDocument = pdfDocument;
  57. if (!pdfDocument) {
  58. return;
  59. }
  60. const [objects, calculationOrder, docActions] = await Promise.all([pdfDocument.getFieldObjects(), pdfDocument.getCalculationOrderIds(), pdfDocument.getJSActions()]);
  61. if (!objects && !docActions) {
  62. await this._destroyScripting();
  63. return;
  64. }
  65. if (pdfDocument !== this._pdfDocument) {
  66. return;
  67. }
  68. try {
  69. this._scripting = this._createScripting();
  70. } catch (error) {
  71. console.error(`PDFScriptingManager.setDocument: "${error?.message}".`);
  72. await this._destroyScripting();
  73. return;
  74. }
  75. this._internalEvents.set("updatefromsandbox", event => {
  76. if (event?.source !== window) {
  77. return;
  78. }
  79. this._updateFromSandbox(event.detail);
  80. });
  81. this._internalEvents.set("dispatcheventinsandbox", event => {
  82. this._scripting?.dispatchEventInSandbox(event.detail);
  83. });
  84. this._internalEvents.set("pagechanging", ({
  85. pageNumber,
  86. previous
  87. }) => {
  88. if (pageNumber === previous) {
  89. return;
  90. }
  91. this._dispatchPageClose(previous);
  92. this._dispatchPageOpen(pageNumber);
  93. });
  94. this._internalEvents.set("pagerendered", ({
  95. pageNumber
  96. }) => {
  97. if (!this._pageOpenPending.has(pageNumber)) {
  98. return;
  99. }
  100. if (pageNumber !== this._pdfViewer.currentPageNumber) {
  101. return;
  102. }
  103. this._dispatchPageOpen(pageNumber);
  104. });
  105. this._internalEvents.set("pagesdestroy", async event => {
  106. await this._dispatchPageClose(this._pdfViewer.currentPageNumber);
  107. await this._scripting?.dispatchEventInSandbox({
  108. id: "doc",
  109. name: "WillClose"
  110. });
  111. this._closeCapability?.resolve();
  112. });
  113. this._domEvents.set("mousedown", event => {
  114. this._mouseState.isDown = true;
  115. });
  116. this._domEvents.set("mouseup", event => {
  117. this._mouseState.isDown = false;
  118. });
  119. for (const [name, listener] of this._internalEvents) {
  120. this._eventBus._on(name, listener);
  121. }
  122. for (const [name, listener] of this._domEvents) {
  123. window.addEventListener(name, listener);
  124. }
  125. try {
  126. const docProperties = await this._getDocProperties();
  127. if (pdfDocument !== this._pdfDocument) {
  128. return;
  129. }
  130. await this._scripting.createSandbox({
  131. objects,
  132. calculationOrder,
  133. appInfo: {
  134. platform: navigator.platform,
  135. language: navigator.language
  136. },
  137. docInfo: { ...docProperties,
  138. actions: docActions
  139. }
  140. });
  141. this._eventBus.dispatch("sandboxcreated", {
  142. source: this
  143. });
  144. } catch (error) {
  145. console.error(`PDFScriptingManager.setDocument: "${error?.message}".`);
  146. await this._destroyScripting();
  147. return;
  148. }
  149. await this._scripting?.dispatchEventInSandbox({
  150. id: "doc",
  151. name: "Open"
  152. });
  153. await this._dispatchPageOpen(this._pdfViewer.currentPageNumber, true);
  154. Promise.resolve().then(() => {
  155. if (pdfDocument === this._pdfDocument) {
  156. this._ready = true;
  157. }
  158. });
  159. }
  160. async dispatchWillSave(detail) {
  161. return this._scripting?.dispatchEventInSandbox({
  162. id: "doc",
  163. name: "WillSave"
  164. });
  165. }
  166. async dispatchDidSave(detail) {
  167. return this._scripting?.dispatchEventInSandbox({
  168. id: "doc",
  169. name: "DidSave"
  170. });
  171. }
  172. async dispatchWillPrint(detail) {
  173. return this._scripting?.dispatchEventInSandbox({
  174. id: "doc",
  175. name: "WillPrint"
  176. });
  177. }
  178. async dispatchDidPrint(detail) {
  179. return this._scripting?.dispatchEventInSandbox({
  180. id: "doc",
  181. name: "DidPrint"
  182. });
  183. }
  184. get mouseState() {
  185. return this._mouseState;
  186. }
  187. get destroyPromise() {
  188. return this._destroyCapability?.promise || null;
  189. }
  190. get ready() {
  191. return this._ready;
  192. }
  193. get _internalEvents() {
  194. return (0, _pdf.shadow)(this, "_internalEvents", new Map());
  195. }
  196. get _domEvents() {
  197. return (0, _pdf.shadow)(this, "_domEvents", new Map());
  198. }
  199. get _pageOpenPending() {
  200. return (0, _pdf.shadow)(this, "_pageOpenPending", new Set());
  201. }
  202. get _visitedPages() {
  203. return (0, _pdf.shadow)(this, "_visitedPages", new Map());
  204. }
  205. async _updateFromSandbox(detail) {
  206. const isInPresentationMode = this._pdfViewer.isInPresentationMode || this._pdfViewer.isChangingPresentationMode;
  207. const {
  208. id,
  209. siblings,
  210. command,
  211. value
  212. } = detail;
  213. if (!id) {
  214. switch (command) {
  215. case "clear":
  216. console.clear();
  217. break;
  218. case "error":
  219. console.error(value);
  220. break;
  221. case "layout":
  222. this._pdfViewer.spreadMode = (0, _ui_utils.apiPageLayoutToSpreadMode)(value);
  223. break;
  224. case "page-num":
  225. this._pdfViewer.currentPageNumber = value + 1;
  226. break;
  227. case "print":
  228. await this._pdfViewer.pagesPromise;
  229. this._eventBus.dispatch("print", {
  230. source: this
  231. });
  232. break;
  233. case "println":
  234. console.log(value);
  235. break;
  236. case "zoom":
  237. if (isInPresentationMode) {
  238. return;
  239. }
  240. this._pdfViewer.currentScaleValue = value;
  241. break;
  242. }
  243. return;
  244. }
  245. if (isInPresentationMode) {
  246. if (detail.focus) {
  247. return;
  248. }
  249. }
  250. delete detail.id;
  251. delete detail.siblings;
  252. const ids = siblings ? [id, ...siblings] : [id];
  253. for (const elementId of ids) {
  254. const element = document.getElementById(elementId);
  255. if (element) {
  256. element.dispatchEvent(new CustomEvent("updatefromsandbox", {
  257. detail
  258. }));
  259. } else {
  260. this._pdfDocument?.annotationStorage.setValue(elementId, detail);
  261. }
  262. }
  263. }
  264. async _dispatchPageOpen(pageNumber, initialize = false) {
  265. const pdfDocument = this._pdfDocument,
  266. visitedPages = this._visitedPages;
  267. if (initialize) {
  268. this._closeCapability = (0, _pdf.createPromiseCapability)();
  269. }
  270. if (!this._closeCapability) {
  271. return;
  272. }
  273. const pageView = this._pdfViewer.getPageView(pageNumber - 1);
  274. if (pageView?.renderingState !== _pdf_rendering_queue.RenderingStates.FINISHED) {
  275. this._pageOpenPending.add(pageNumber);
  276. return;
  277. }
  278. this._pageOpenPending.delete(pageNumber);
  279. const actionsPromise = (async () => {
  280. const actions = await (!visitedPages.has(pageNumber) ? pageView.pdfPage?.getJSActions() : null);
  281. if (pdfDocument !== this._pdfDocument) {
  282. return;
  283. }
  284. await this._scripting?.dispatchEventInSandbox({
  285. id: "page",
  286. name: "PageOpen",
  287. pageNumber,
  288. actions
  289. });
  290. })();
  291. visitedPages.set(pageNumber, actionsPromise);
  292. }
  293. async _dispatchPageClose(pageNumber) {
  294. const pdfDocument = this._pdfDocument,
  295. visitedPages = this._visitedPages;
  296. if (!this._closeCapability) {
  297. return;
  298. }
  299. if (this._pageOpenPending.has(pageNumber)) {
  300. return;
  301. }
  302. const actionsPromise = visitedPages.get(pageNumber);
  303. if (!actionsPromise) {
  304. return;
  305. }
  306. visitedPages.set(pageNumber, null);
  307. await actionsPromise;
  308. if (pdfDocument !== this._pdfDocument) {
  309. return;
  310. }
  311. await this._scripting?.dispatchEventInSandbox({
  312. id: "page",
  313. name: "PageClose",
  314. pageNumber
  315. });
  316. }
  317. async _getDocProperties() {
  318. if (this._docPropertiesLookup) {
  319. return this._docPropertiesLookup(this._pdfDocument);
  320. }
  321. throw new Error("_getDocProperties: Unable to lookup properties.");
  322. }
  323. _createScripting() {
  324. this._destroyCapability = (0, _pdf.createPromiseCapability)();
  325. if (this._scripting) {
  326. throw new Error("_createScripting: Scripting already exists.");
  327. }
  328. if (this._scriptingFactory) {
  329. return this._scriptingFactory.createScripting({
  330. sandboxBundleSrc: this._sandboxBundleSrc
  331. });
  332. }
  333. throw new Error("_createScripting: Cannot create scripting.");
  334. }
  335. async _destroyScripting() {
  336. if (!this._scripting) {
  337. this._pdfDocument = null;
  338. this._destroyCapability?.resolve();
  339. return;
  340. }
  341. if (this._closeCapability) {
  342. await Promise.race([this._closeCapability.promise, new Promise(resolve => {
  343. setTimeout(resolve, 1000);
  344. })]).catch(reason => {});
  345. this._closeCapability = null;
  346. }
  347. this._pdfDocument = null;
  348. try {
  349. await this._scripting.destroySandbox();
  350. } catch (ex) {}
  351. for (const [name, listener] of this._internalEvents) {
  352. this._eventBus._off(name, listener);
  353. }
  354. this._internalEvents.clear();
  355. for (const [name, listener] of this._domEvents) {
  356. window.removeEventListener(name, listener);
  357. }
  358. this._domEvents.clear();
  359. this._pageOpenPending.clear();
  360. this._visitedPages.clear();
  361. this._scripting = null;
  362. delete this._mouseState.isDown;
  363. this._ready = false;
  364. this._destroyCapability?.resolve();
  365. }
  366. }
  367. exports.PDFScriptingManager = PDFScriptingManager;