pdf_scripting_manager.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  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. case "SaveAs":
  243. this._eventBus.dispatch("save", {
  244. source: this
  245. });
  246. break;
  247. case "FirstPage":
  248. this._pdfViewer.currentPageNumber = 1;
  249. break;
  250. case "LastPage":
  251. this._pdfViewer.currentPageNumber = this._pdfViewer.pagesCount;
  252. break;
  253. case "NextPage":
  254. this._pdfViewer.nextPage();
  255. break;
  256. case "PrevPage":
  257. this._pdfViewer.previousPage();
  258. break;
  259. case "ZoomViewIn":
  260. if (isInPresentationMode) {
  261. return;
  262. }
  263. this._pdfViewer.increaseScale();
  264. break;
  265. case "ZoomViewOut":
  266. if (isInPresentationMode) {
  267. return;
  268. }
  269. this._pdfViewer.decreaseScale();
  270. break;
  271. }
  272. return;
  273. }
  274. if (isInPresentationMode) {
  275. if (detail.focus) {
  276. return;
  277. }
  278. }
  279. delete detail.id;
  280. delete detail.siblings;
  281. const ids = siblings ? [id, ...siblings] : [id];
  282. for (const elementId of ids) {
  283. const element = document.getElementById(elementId);
  284. if (element) {
  285. element.dispatchEvent(new CustomEvent("updatefromsandbox", {
  286. detail
  287. }));
  288. } else {
  289. this._pdfDocument?.annotationStorage.setValue(elementId, detail);
  290. }
  291. }
  292. }
  293. async _dispatchPageOpen(pageNumber, initialize = false) {
  294. const pdfDocument = this._pdfDocument,
  295. visitedPages = this._visitedPages;
  296. if (initialize) {
  297. this._closeCapability = (0, _pdf.createPromiseCapability)();
  298. }
  299. if (!this._closeCapability) {
  300. return;
  301. }
  302. const pageView = this._pdfViewer.getPageView(pageNumber - 1);
  303. if (pageView?.renderingState !== _pdf_rendering_queue.RenderingStates.FINISHED) {
  304. this._pageOpenPending.add(pageNumber);
  305. return;
  306. }
  307. this._pageOpenPending.delete(pageNumber);
  308. const actionsPromise = (async () => {
  309. const actions = await (!visitedPages.has(pageNumber) ? pageView.pdfPage?.getJSActions() : null);
  310. if (pdfDocument !== this._pdfDocument) {
  311. return;
  312. }
  313. await this._scripting?.dispatchEventInSandbox({
  314. id: "page",
  315. name: "PageOpen",
  316. pageNumber,
  317. actions
  318. });
  319. })();
  320. visitedPages.set(pageNumber, actionsPromise);
  321. }
  322. async _dispatchPageClose(pageNumber) {
  323. const pdfDocument = this._pdfDocument,
  324. visitedPages = this._visitedPages;
  325. if (!this._closeCapability) {
  326. return;
  327. }
  328. if (this._pageOpenPending.has(pageNumber)) {
  329. return;
  330. }
  331. const actionsPromise = visitedPages.get(pageNumber);
  332. if (!actionsPromise) {
  333. return;
  334. }
  335. visitedPages.set(pageNumber, null);
  336. await actionsPromise;
  337. if (pdfDocument !== this._pdfDocument) {
  338. return;
  339. }
  340. await this._scripting?.dispatchEventInSandbox({
  341. id: "page",
  342. name: "PageClose",
  343. pageNumber
  344. });
  345. }
  346. async _getDocProperties() {
  347. if (this._docPropertiesLookup) {
  348. return this._docPropertiesLookup(this._pdfDocument);
  349. }
  350. throw new Error("_getDocProperties: Unable to lookup properties.");
  351. }
  352. _createScripting() {
  353. this._destroyCapability = (0, _pdf.createPromiseCapability)();
  354. if (this._scripting) {
  355. throw new Error("_createScripting: Scripting already exists.");
  356. }
  357. if (this._scriptingFactory) {
  358. return this._scriptingFactory.createScripting({
  359. sandboxBundleSrc: this._sandboxBundleSrc
  360. });
  361. }
  362. throw new Error("_createScripting: Cannot create scripting.");
  363. }
  364. async _destroyScripting() {
  365. if (!this._scripting) {
  366. this._pdfDocument = null;
  367. this._destroyCapability?.resolve();
  368. return;
  369. }
  370. if (this._closeCapability) {
  371. await Promise.race([this._closeCapability.promise, new Promise(resolve => {
  372. setTimeout(resolve, 1000);
  373. })]).catch(reason => {});
  374. this._closeCapability = null;
  375. }
  376. this._pdfDocument = null;
  377. try {
  378. await this._scripting.destroySandbox();
  379. } catch (ex) {}
  380. for (const [name, listener] of this._internalEvents) {
  381. this._eventBus._off(name, listener);
  382. }
  383. this._internalEvents.clear();
  384. for (const [name, listener] of this._domEvents) {
  385. window.removeEventListener(name, listener);
  386. }
  387. this._domEvents.clear();
  388. this._pageOpenPending.clear();
  389. this._visitedPages.clear();
  390. this._scripting = null;
  391. delete this._mouseState.isDown;
  392. this._ready = false;
  393. this._destroyCapability?.resolve();
  394. }
  395. }
  396. exports.PDFScriptingManager = PDFScriptingManager;