Merge pull request #13042 from Snuffleupagus/PDFScriptingManager
[api-minor] Move the viewer scripting initialization/handling into a new `PDFScriptingManager` class
This commit is contained in:
commit
fb87704b38
@ -31,8 +31,14 @@ var CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
|
|||||||
var CMAP_PACKED = true;
|
var CMAP_PACKED = true;
|
||||||
|
|
||||||
var DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
var DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||||
|
// To test the AcroForm and/or scripting functionality, try e.g. this file:
|
||||||
|
// var DEFAULT_URL = "../../test/pdfs/160F-2019.pdf";
|
||||||
|
|
||||||
var SEARCH_FOR = ""; // try 'Mozilla';
|
var SEARCH_FOR = ""; // try 'Mozilla';
|
||||||
|
|
||||||
|
// For scripting support, note also `enableScripting` below.
|
||||||
|
var SANDBOX_BUNDLE_SRC = "../../node_modules/pdfjs-dist/build/pdf.sandbox.js";
|
||||||
|
|
||||||
var container = document.getElementById("viewerContainer");
|
var container = document.getElementById("viewerContainer");
|
||||||
|
|
||||||
var eventBus = new pdfjsViewer.EventBus();
|
var eventBus = new pdfjsViewer.EventBus();
|
||||||
@ -48,13 +54,22 @@ var pdfFindController = new pdfjsViewer.PDFFindController({
|
|||||||
linkService: pdfLinkService,
|
linkService: pdfLinkService,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// (Optionally) enable scripting support.
|
||||||
|
var pdfScriptingManager = new pdfjsViewer.PDFScriptingManager({
|
||||||
|
eventBus,
|
||||||
|
sandboxBundleSrc: SANDBOX_BUNDLE_SRC,
|
||||||
|
});
|
||||||
|
|
||||||
var pdfViewer = new pdfjsViewer.PDFViewer({
|
var pdfViewer = new pdfjsViewer.PDFViewer({
|
||||||
container,
|
container,
|
||||||
eventBus,
|
eventBus,
|
||||||
linkService: pdfLinkService,
|
linkService: pdfLinkService,
|
||||||
findController: pdfFindController,
|
findController: pdfFindController,
|
||||||
|
scriptingManager: pdfScriptingManager,
|
||||||
|
enableScripting: true,
|
||||||
});
|
});
|
||||||
pdfLinkService.setViewer(pdfViewer);
|
pdfLinkService.setViewer(pdfViewer);
|
||||||
|
pdfScriptingManager.setViewer(pdfViewer);
|
||||||
|
|
||||||
eventBus.on("pagesinit", function () {
|
eventBus.on("pagesinit", function () {
|
||||||
// We can use pdfViewer now, e.g. let's change default scale.
|
// We can use pdfViewer now, e.g. let's change default scale.
|
||||||
|
@ -31,8 +31,14 @@ var CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
|
|||||||
var CMAP_PACKED = true;
|
var CMAP_PACKED = true;
|
||||||
|
|
||||||
var DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
var DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||||
|
// To test the AcroForm and/or scripting functionality, try e.g. this file:
|
||||||
|
// var DEFAULT_URL = "../../test/pdfs/160F-2019.pdf";
|
||||||
|
|
||||||
var SEARCH_FOR = ""; // try 'Mozilla';
|
var SEARCH_FOR = ""; // try 'Mozilla';
|
||||||
|
|
||||||
|
// For scripting support, note also `enableScripting` below.
|
||||||
|
var SANDBOX_BUNDLE_SRC = "../../node_modules/pdfjs-dist/build/pdf.sandbox.js";
|
||||||
|
|
||||||
var container = document.getElementById("viewerContainer");
|
var container = document.getElementById("viewerContainer");
|
||||||
|
|
||||||
var eventBus = new pdfjsViewer.EventBus();
|
var eventBus = new pdfjsViewer.EventBus();
|
||||||
@ -48,13 +54,22 @@ var pdfFindController = new pdfjsViewer.PDFFindController({
|
|||||||
linkService: pdfLinkService,
|
linkService: pdfLinkService,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// (Optionally) enable scripting support.
|
||||||
|
var pdfScriptingManager = new pdfjsViewer.PDFScriptingManager({
|
||||||
|
eventBus,
|
||||||
|
sandboxBundleSrc: SANDBOX_BUNDLE_SRC,
|
||||||
|
});
|
||||||
|
|
||||||
var pdfSinglePageViewer = new pdfjsViewer.PDFSinglePageViewer({
|
var pdfSinglePageViewer = new pdfjsViewer.PDFSinglePageViewer({
|
||||||
container,
|
container,
|
||||||
eventBus,
|
eventBus,
|
||||||
linkService: pdfLinkService,
|
linkService: pdfLinkService,
|
||||||
findController: pdfFindController,
|
findController: pdfFindController,
|
||||||
|
scriptingManager: pdfScriptingManager,
|
||||||
|
enableScripting: true,
|
||||||
});
|
});
|
||||||
pdfLinkService.setViewer(pdfSinglePageViewer);
|
pdfLinkService.setViewer(pdfSinglePageViewer);
|
||||||
|
pdfScriptingManager.setViewer(pdfSinglePageViewer);
|
||||||
|
|
||||||
eventBus.on("pagesinit", function () {
|
eventBus.on("pagesinit", function () {
|
||||||
// We can use pdfSinglePageViewer now, e.g. let's change default scale.
|
// We can use pdfSinglePageViewer now, e.g. let's change default scale.
|
||||||
|
331
web/app.js
331
web/app.js
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
animationStarted,
|
animationStarted,
|
||||||
|
apiPageLayoutToSpreadMode,
|
||||||
|
apiPageModeToSidebarView,
|
||||||
AutoPrintRegExp,
|
AutoPrintRegExp,
|
||||||
DEFAULT_SCALE_VALUE,
|
DEFAULT_SCALE_VALUE,
|
||||||
EventBus,
|
EventBus,
|
||||||
@ -69,6 +71,7 @@ import { PDFLayerViewer } from "./pdf_layer_viewer.js";
|
|||||||
import { PDFLinkService } from "./pdf_link_service.js";
|
import { PDFLinkService } from "./pdf_link_service.js";
|
||||||
import { PDFOutlineViewer } from "./pdf_outline_viewer.js";
|
import { PDFOutlineViewer } from "./pdf_outline_viewer.js";
|
||||||
import { PDFPresentationMode } from "./pdf_presentation_mode.js";
|
import { PDFPresentationMode } from "./pdf_presentation_mode.js";
|
||||||
|
import { PDFScriptingManager } from "./pdf_scripting_manager.js";
|
||||||
import { PDFSidebar } from "./pdf_sidebar.js";
|
import { PDFSidebar } from "./pdf_sidebar.js";
|
||||||
import { PDFSidebarResizer } from "./pdf_sidebar_resizer.js";
|
import { PDFSidebarResizer } from "./pdf_sidebar_resizer.js";
|
||||||
import { PDFThumbnailViewer } from "./pdf_thumbnail_viewer.js";
|
import { PDFThumbnailViewer } from "./pdf_thumbnail_viewer.js";
|
||||||
@ -226,6 +229,8 @@ const PDFViewerApplication = {
|
|||||||
pdfLayerViewer: null,
|
pdfLayerViewer: null,
|
||||||
/** @type {PDFCursorTools} */
|
/** @type {PDFCursorTools} */
|
||||||
pdfCursorTools: null,
|
pdfCursorTools: null,
|
||||||
|
/** @type {PDFScriptingManager} */
|
||||||
|
pdfScriptingManager: null,
|
||||||
/** @type {ViewHistory} */
|
/** @type {ViewHistory} */
|
||||||
store: null,
|
store: null,
|
||||||
/** @type {DownloadManager} */
|
/** @type {DownloadManager} */
|
||||||
@ -257,8 +262,6 @@ const PDFViewerApplication = {
|
|||||||
_saveInProgress: false,
|
_saveInProgress: false,
|
||||||
_wheelUnusedTicks: 0,
|
_wheelUnusedTicks: 0,
|
||||||
_idleCallbacks: new Set(),
|
_idleCallbacks: new Set(),
|
||||||
_scriptingInstance: null,
|
|
||||||
_mouseState: Object.create(null),
|
|
||||||
|
|
||||||
// Called once when the document is loaded.
|
// Called once when the document is loaded.
|
||||||
async initialize(appConfig) {
|
async initialize(appConfig) {
|
||||||
@ -482,6 +485,18 @@ const PDFViewerApplication = {
|
|||||||
});
|
});
|
||||||
this.findController = findController;
|
this.findController = findController;
|
||||||
|
|
||||||
|
const pdfScriptingManager = new PDFScriptingManager({
|
||||||
|
eventBus,
|
||||||
|
sandboxBundleSrc:
|
||||||
|
typeof PDFJSDev === "undefined" ||
|
||||||
|
PDFJSDev.test("!PRODUCTION || GENERIC || CHROME")
|
||||||
|
? AppOptions.get("sandboxBundleSrc")
|
||||||
|
: null,
|
||||||
|
scriptingFactory: this.externalServices,
|
||||||
|
docPropertiesLookup: this._scriptingDocProperties.bind(this),
|
||||||
|
});
|
||||||
|
this.pdfScriptingManager = pdfScriptingManager;
|
||||||
|
|
||||||
const container = appConfig.mainContainer;
|
const container = appConfig.mainContainer;
|
||||||
const viewer = appConfig.viewerContainer;
|
const viewer = appConfig.viewerContainer;
|
||||||
this.pdfViewer = new PDFViewer({
|
this.pdfViewer = new PDFViewer({
|
||||||
@ -492,6 +507,7 @@ const PDFViewerApplication = {
|
|||||||
linkService: pdfLinkService,
|
linkService: pdfLinkService,
|
||||||
downloadManager,
|
downloadManager,
|
||||||
findController,
|
findController,
|
||||||
|
scriptingManager: pdfScriptingManager,
|
||||||
renderer: AppOptions.get("renderer"),
|
renderer: AppOptions.get("renderer"),
|
||||||
enableWebGL: AppOptions.get("enableWebGL"),
|
enableWebGL: AppOptions.get("enableWebGL"),
|
||||||
l10n: this.l10n,
|
l10n: this.l10n,
|
||||||
@ -502,10 +518,10 @@ const PDFViewerApplication = {
|
|||||||
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
|
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
|
||||||
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
||||||
enableScripting: AppOptions.get("enableScripting"),
|
enableScripting: AppOptions.get("enableScripting"),
|
||||||
mouseState: this._mouseState,
|
|
||||||
});
|
});
|
||||||
pdfRenderingQueue.setViewer(this.pdfViewer);
|
pdfRenderingQueue.setViewer(this.pdfViewer);
|
||||||
pdfLinkService.setViewer(this.pdfViewer);
|
pdfLinkService.setViewer(this.pdfViewer);
|
||||||
|
pdfScriptingManager.setViewer(this.pdfViewer);
|
||||||
|
|
||||||
this.pdfThumbnailViewer = new PDFThumbnailViewer({
|
this.pdfThumbnailViewer = new PDFThumbnailViewer({
|
||||||
container: appConfig.sidebar.thumbnailView,
|
container: appConfig.sidebar.thumbnailView,
|
||||||
@ -772,32 +788,6 @@ const PDFViewerApplication = {
|
|||||||
this._idleCallbacks.clear();
|
this._idleCallbacks.clear();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _destroyScriptingInstance() {
|
|
||||||
if (!this._scriptingInstance) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { scripting, internalEvents, domEvents } = this._scriptingInstance;
|
|
||||||
try {
|
|
||||||
await scripting.destroySandbox();
|
|
||||||
} catch (ex) {}
|
|
||||||
|
|
||||||
for (const [name, listener] of internalEvents) {
|
|
||||||
this.eventBus._off(name, listener);
|
|
||||||
}
|
|
||||||
internalEvents.clear();
|
|
||||||
|
|
||||||
for (const [name, listener] of domEvents) {
|
|
||||||
window.removeEventListener(name, listener);
|
|
||||||
}
|
|
||||||
domEvents.clear();
|
|
||||||
|
|
||||||
delete this._mouseState.isDown;
|
|
||||||
this._scriptingInstance = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes opened PDF document.
|
* Closes opened PDF document.
|
||||||
* @returns {Promise} - Returns the promise, which is resolved when all
|
* @returns {Promise} - Returns the promise, which is resolved when all
|
||||||
@ -841,7 +831,7 @@ const PDFViewerApplication = {
|
|||||||
this._saveInProgress = false;
|
this._saveInProgress = false;
|
||||||
|
|
||||||
this._cancelIdleCallbacks();
|
this._cancelIdleCallbacks();
|
||||||
promises.push(this._destroyScriptingInstance());
|
promises.push(this.pdfScriptingManager.destroyPromise);
|
||||||
|
|
||||||
this.pdfSidebar.reset();
|
this.pdfSidebar.reset();
|
||||||
this.pdfOutlineViewer.reset();
|
this.pdfOutlineViewer.reset();
|
||||||
@ -1000,11 +990,7 @@ const PDFViewerApplication = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._saveInProgress = true;
|
this._saveInProgress = true;
|
||||||
|
await this.pdfScriptingManager.dispatchWillSave();
|
||||||
await this._scriptingInstance?.scripting.dispatchEventInSandbox({
|
|
||||||
id: "doc",
|
|
||||||
name: "WillSave",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pdfDocument
|
this.pdfDocument
|
||||||
.saveDocument(this.pdfDocument.annotationStorage)
|
.saveDocument(this.pdfDocument.annotationStorage)
|
||||||
@ -1016,11 +1002,7 @@ const PDFViewerApplication = {
|
|||||||
this.download({ sourceEventType });
|
this.download({ sourceEventType });
|
||||||
})
|
})
|
||||||
.finally(async () => {
|
.finally(async () => {
|
||||||
await this._scriptingInstance?.scripting.dispatchEventInSandbox({
|
await this.pdfScriptingManager.dispatchDidSave();
|
||||||
id: "doc",
|
|
||||||
name: "DidSave",
|
|
||||||
});
|
|
||||||
|
|
||||||
this._saveInProgress = false;
|
this._saveInProgress = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -1419,7 +1401,6 @@ const PDFViewerApplication = {
|
|||||||
);
|
);
|
||||||
this._idleCallbacks.add(callback);
|
this._idleCallbacks.add(callback);
|
||||||
}
|
}
|
||||||
this._initializeJavaScript(pdfDocument);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this._initializePageLabels(pdfDocument);
|
this._initializePageLabels(pdfDocument);
|
||||||
@ -1429,40 +1410,7 @@ const PDFViewerApplication = {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _initializeJavaScript(pdfDocument) {
|
async _scriptingDocProperties(pdfDocument) {
|
||||||
if (!AppOptions.get("enableScripting")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const [objects, calculationOrder, docActions] = await Promise.all([
|
|
||||||
pdfDocument.getFieldObjects(),
|
|
||||||
pdfDocument.getCalculationOrderIds(),
|
|
||||||
pdfDocument.getJSActions(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!objects && !docActions) {
|
|
||||||
// No FieldObjects or JavaScript actions were found in the document.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pdfDocument !== this.pdfDocument) {
|
|
||||||
return; // The document was closed while the data resolved.
|
|
||||||
}
|
|
||||||
const scripting = this.externalServices.createScripting(
|
|
||||||
typeof PDFJSDev === "undefined" ||
|
|
||||||
PDFJSDev.test("!PRODUCTION || GENERIC || CHROME")
|
|
||||||
? { sandboxBundleSrc: AppOptions.get("sandboxBundleSrc") }
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
// Store a reference to the current scripting-instance, to allow destruction
|
|
||||||
// of the sandbox and removal of the event listeners at document closing.
|
|
||||||
const internalEvents = new Map(),
|
|
||||||
domEvents = new Map();
|
|
||||||
this._scriptingInstance = {
|
|
||||||
scripting,
|
|
||||||
ready: false,
|
|
||||||
internalEvents,
|
|
||||||
domEvents,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!this.documentInfo) {
|
if (!this.documentInfo) {
|
||||||
// It should be *extremely* rare for metadata to not have been resolved
|
// It should be *extremely* rare for metadata to not have been resolved
|
||||||
// when this code runs, but ensure that we handle that case here.
|
// when this code runs, but ensure that we handle that case here.
|
||||||
@ -1470,7 +1418,7 @@ const PDFViewerApplication = {
|
|||||||
this.eventBus._on("metadataloaded", resolve, { once: true });
|
this.eventBus._on("metadataloaded", resolve, { once: true });
|
||||||
});
|
});
|
||||||
if (pdfDocument !== this.pdfDocument) {
|
if (pdfDocument !== this.pdfDocument) {
|
||||||
return; // The document was closed while the metadata resolved.
|
return null; // The document was closed while the metadata resolved.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this._contentLength) {
|
if (!this._contentLength) {
|
||||||
@ -1483,170 +1431,20 @@ const PDFViewerApplication = {
|
|||||||
this.eventBus._on("documentloaded", resolve, { once: true });
|
this.eventBus._on("documentloaded", resolve, { once: true });
|
||||||
});
|
});
|
||||||
if (pdfDocument !== this.pdfDocument) {
|
if (pdfDocument !== this.pdfDocument) {
|
||||||
return; // The document was closed while the downloadInfo resolved.
|
return null; // The document was closed while the downloadInfo resolved.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateFromSandbox = ({ detail }) => {
|
return {
|
||||||
const { id, command, value } = detail;
|
...this.documentInfo,
|
||||||
if (!id) {
|
baseURL: this.baseUrl,
|
||||||
switch (command) {
|
filesize: this._contentLength,
|
||||||
case "clear":
|
filename: this._docFilename,
|
||||||
console.clear();
|
metadata: this.metadata?.getRaw(),
|
||||||
break;
|
authors: this.metadata?.get("dc:creator"),
|
||||||
case "error":
|
numPages: this.pagesCount,
|
||||||
console.error(value);
|
URL: this.url,
|
||||||
break;
|
|
||||||
case "layout":
|
|
||||||
this.pdfViewer.spreadMode = apiPageLayoutToSpreadMode(value);
|
|
||||||
break;
|
|
||||||
case "page-num":
|
|
||||||
this.pdfViewer.currentPageNumber = value + 1;
|
|
||||||
break;
|
|
||||||
case "print":
|
|
||||||
this.pdfViewer.pagesPromise.then(() => {
|
|
||||||
this.triggerPrinting();
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "println":
|
|
||||||
console.log(value);
|
|
||||||
break;
|
|
||||||
case "zoom":
|
|
||||||
this.pdfViewer.currentScaleValue = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const element = document.getElementById(id);
|
|
||||||
if (element) {
|
|
||||||
element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
|
|
||||||
} else {
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
// The element hasn't been rendered yet, use the AnnotationStorage.
|
|
||||||
pdfDocument.annotationStorage.setValue(id, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
internalEvents.set("updatefromsandbox", updateFromSandbox);
|
|
||||||
|
|
||||||
const visitedPages = new Map();
|
|
||||||
const pageOpen = ({ pageNumber, actionsPromise }) => {
|
|
||||||
visitedPages.set(
|
|
||||||
pageNumber,
|
|
||||||
(async () => {
|
|
||||||
// Avoid sending, and thus serializing, the `actions` data
|
|
||||||
// when the same page is opened several times.
|
|
||||||
let actions = null;
|
|
||||||
if (!visitedPages.has(pageNumber)) {
|
|
||||||
actions = await actionsPromise;
|
|
||||||
|
|
||||||
if (pdfDocument !== this.pdfDocument) {
|
|
||||||
return; // The document was closed while the actions resolved.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this._scriptingInstance?.scripting.dispatchEventInSandbox({
|
|
||||||
id: "page",
|
|
||||||
name: "PageOpen",
|
|
||||||
pageNumber,
|
|
||||||
actions,
|
|
||||||
});
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const pageClose = async ({ pageNumber }) => {
|
|
||||||
const actionsPromise = visitedPages.get(pageNumber);
|
|
||||||
if (!actionsPromise) {
|
|
||||||
// Ensure that the "pageclose" event was preceded by a "pageopen" event.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
visitedPages.set(pageNumber, null);
|
|
||||||
|
|
||||||
// Ensure that the "pageopen" event is handled first.
|
|
||||||
await actionsPromise;
|
|
||||||
|
|
||||||
if (pdfDocument !== this.pdfDocument) {
|
|
||||||
return; // The document was closed while the actions resolved.
|
|
||||||
}
|
|
||||||
|
|
||||||
await this._scriptingInstance?.scripting.dispatchEventInSandbox({
|
|
||||||
id: "page",
|
|
||||||
name: "PageClose",
|
|
||||||
pageNumber,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
internalEvents.set("pageopen", pageOpen);
|
|
||||||
internalEvents.set("pageclose", pageClose);
|
|
||||||
|
|
||||||
const dispatchEventInSandbox = ({ detail }) => {
|
|
||||||
scripting.dispatchEventInSandbox(detail);
|
|
||||||
};
|
|
||||||
internalEvents.set("dispatcheventinsandbox", dispatchEventInSandbox);
|
|
||||||
|
|
||||||
const mouseDown = event => {
|
|
||||||
this._mouseState.isDown = true;
|
|
||||||
};
|
|
||||||
domEvents.set("mousedown", mouseDown);
|
|
||||||
|
|
||||||
const mouseUp = event => {
|
|
||||||
this._mouseState.isDown = false;
|
|
||||||
};
|
|
||||||
domEvents.set("mouseup", mouseUp);
|
|
||||||
|
|
||||||
for (const [name, listener] of internalEvents) {
|
|
||||||
this.eventBus._on(name, listener);
|
|
||||||
}
|
|
||||||
for (const [name, listener] of domEvents) {
|
|
||||||
window.addEventListener(name, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await scripting.createSandbox({
|
|
||||||
objects,
|
|
||||||
calculationOrder,
|
|
||||||
appInfo: {
|
|
||||||
platform: navigator.platform,
|
|
||||||
language: navigator.language,
|
|
||||||
},
|
|
||||||
docInfo: {
|
|
||||||
...this.documentInfo,
|
|
||||||
baseURL: this.baseUrl,
|
|
||||||
filesize: this._contentLength,
|
|
||||||
filename: this._docFilename,
|
|
||||||
metadata: this.metadata?.getRaw(),
|
|
||||||
authors: this.metadata?.get("dc:creator"),
|
|
||||||
numPages: pdfDocument.numPages,
|
|
||||||
URL: this.url,
|
|
||||||
actions: docActions,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.externalServices.isInAutomation) {
|
|
||||||
this.eventBus.dispatch("sandboxcreated", { source: this });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`_initializeJavaScript: "${error?.message}".`);
|
|
||||||
|
|
||||||
this._destroyScriptingInstance();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await scripting.dispatchEventInSandbox({
|
|
||||||
id: "doc",
|
|
||||||
name: "Open",
|
|
||||||
});
|
|
||||||
await this.pdfViewer.initializeScriptingEvents();
|
|
||||||
|
|
||||||
// Used together with the integration-tests, see the `scriptingReady`
|
|
||||||
// getter, to enable awaiting full initialization of the scripting/sandbox.
|
|
||||||
// (Defer this slightly, to make absolutely sure that everything is done.)
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
if (this._scriptingInstance) {
|
|
||||||
this._scriptingInstance.ready = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1672,7 +1470,7 @@ const PDFViewerApplication = {
|
|||||||
async _initializeAutoPrint(pdfDocument, openActionPromise) {
|
async _initializeAutoPrint(pdfDocument, openActionPromise) {
|
||||||
const [openAction, javaScript] = await Promise.all([
|
const [openAction, javaScript] = await Promise.all([
|
||||||
openActionPromise,
|
openActionPromise,
|
||||||
!AppOptions.get("enableScripting") ? pdfDocument.getJavaScript() : null,
|
!this.pdfViewer.enableScripting ? pdfDocument.getJavaScript() : null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (pdfDocument !== this.pdfDocument) {
|
if (pdfDocument !== this.pdfDocument) {
|
||||||
@ -1986,10 +1784,7 @@ const PDFViewerApplication = {
|
|||||||
beforePrint() {
|
beforePrint() {
|
||||||
// Given that the "beforeprint" browser event is synchronous, we
|
// Given that the "beforeprint" browser event is synchronous, we
|
||||||
// unfortunately cannot await the scripting event dispatching here.
|
// unfortunately cannot await the scripting event dispatching here.
|
||||||
this._scriptingInstance?.scripting.dispatchEventInSandbox({
|
this.pdfScriptingManager.dispatchWillPrint();
|
||||||
id: "doc",
|
|
||||||
name: "WillPrint",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.printService) {
|
if (this.printService) {
|
||||||
// There is no way to suppress beforePrint/afterPrint events,
|
// There is no way to suppress beforePrint/afterPrint events,
|
||||||
@ -2042,10 +1837,7 @@ const PDFViewerApplication = {
|
|||||||
afterPrint() {
|
afterPrint() {
|
||||||
// Given that the "afterprint" browser event is synchronous, we
|
// Given that the "afterprint" browser event is synchronous, we
|
||||||
// unfortunately cannot await the scripting event dispatching here.
|
// unfortunately cannot await the scripting event dispatching here.
|
||||||
this._scriptingInstance?.scripting.dispatchEventInSandbox({
|
this.pdfScriptingManager.dispatchDidPrint();
|
||||||
id: "doc",
|
|
||||||
name: "DidPrint",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.printService) {
|
if (this.printService) {
|
||||||
this.printService.destroy();
|
this.printService.destroy();
|
||||||
@ -2300,7 +2092,7 @@ const PDFViewerApplication = {
|
|||||||
* initialization of the scripting/sandbox.
|
* initialization of the scripting/sandbox.
|
||||||
*/
|
*/
|
||||||
get scriptingReady() {
|
get scriptingReady() {
|
||||||
return this._scriptingInstance?.ready || false;
|
return this.pdfScriptingManager.ready;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3350,53 +3142,6 @@ function beforeUnload(evt) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts API PageLayout values to the format used by `PDFViewer`.
|
|
||||||
* NOTE: This is supported to the extent that the viewer implements the
|
|
||||||
* necessary Scroll/Spread modes (since SinglePage, TwoPageLeft,
|
|
||||||
* and TwoPageRight all suggests using non-continuous scrolling).
|
|
||||||
* @param {string} mode - The API PageLayout value.
|
|
||||||
* @returns {number} A value from {SpreadMode}.
|
|
||||||
*/
|
|
||||||
function apiPageLayoutToSpreadMode(layout) {
|
|
||||||
switch (layout) {
|
|
||||||
case "SinglePage":
|
|
||||||
case "OneColumn":
|
|
||||||
return SpreadMode.NONE;
|
|
||||||
case "TwoColumnLeft":
|
|
||||||
case "TwoPageLeft":
|
|
||||||
return SpreadMode.ODD;
|
|
||||||
case "TwoColumnRight":
|
|
||||||
case "TwoPageRight":
|
|
||||||
return SpreadMode.EVEN;
|
|
||||||
}
|
|
||||||
return SpreadMode.NONE; // Default value.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts API PageMode values to the format used by `PDFSidebar`.
|
|
||||||
* NOTE: There's also a "FullScreen" parameter which is not possible to support,
|
|
||||||
* since the Fullscreen API used in browsers requires that entering
|
|
||||||
* fullscreen mode only occurs as a result of a user-initiated event.
|
|
||||||
* @param {string} mode - The API PageMode value.
|
|
||||||
* @returns {number} A value from {SidebarView}.
|
|
||||||
*/
|
|
||||||
function apiPageModeToSidebarView(mode) {
|
|
||||||
switch (mode) {
|
|
||||||
case "UseNone":
|
|
||||||
return SidebarView.NONE;
|
|
||||||
case "UseThumbs":
|
|
||||||
return SidebarView.THUMBS;
|
|
||||||
case "UseOutlines":
|
|
||||||
return SidebarView.OUTLINE;
|
|
||||||
case "UseAttachments":
|
|
||||||
return SidebarView.ATTACHMENTS;
|
|
||||||
case "UseOC":
|
|
||||||
return SidebarView.LAYERS;
|
|
||||||
}
|
|
||||||
return SidebarView.NONE; // Default value.
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Abstract factory for the print service. */
|
/* Abstract factory for the print service. */
|
||||||
const PDFPrintServiceFactory = {
|
const PDFPrintServiceFactory = {
|
||||||
instance: {
|
instance: {
|
||||||
|
@ -55,6 +55,8 @@ const DEFAULT_CACHE_SIZE = 10;
|
|||||||
* component.
|
* component.
|
||||||
* @property {PDFFindController} [findController] - The find controller
|
* @property {PDFFindController} [findController] - The find controller
|
||||||
* component.
|
* component.
|
||||||
|
* @property {PDFScriptingManager} [scriptingManager] - The scripting manager
|
||||||
|
* component.
|
||||||
* @property {PDFRenderingQueue} [renderingQueue] - The rendering queue object.
|
* @property {PDFRenderingQueue} [renderingQueue] - The rendering queue object.
|
||||||
* @property {boolean} [removePageBorders] - Removes the border shadow around
|
* @property {boolean} [removePageBorders] - Removes the border shadow around
|
||||||
* the pages. The default value is `false`.
|
* the pages. The default value is `false`.
|
||||||
@ -77,10 +79,8 @@ const DEFAULT_CACHE_SIZE = 10;
|
|||||||
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
||||||
* is 4096 * 4096 (16 mega-pixels).
|
* is 4096 * 4096 (16 mega-pixels).
|
||||||
* @property {IL10n} l10n - Localization service.
|
* @property {IL10n} l10n - Localization service.
|
||||||
* @property {boolean} [enableScripting] - Enable embedded script execution.
|
* @property {boolean} [enableScripting] - Enable embedded script execution
|
||||||
* The default value is `false`.
|
* (also requires {scriptingManager} being set). The default value is `false`.
|
||||||
* @property {Object} [mouseState] - The mouse button state. The default value
|
|
||||||
* is `null`.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function PDFPageViewBuffer(size) {
|
function PDFPageViewBuffer(size) {
|
||||||
@ -183,6 +183,7 @@ class BaseViewer {
|
|||||||
this.linkService = options.linkService || new SimpleLinkService();
|
this.linkService = options.linkService || new SimpleLinkService();
|
||||||
this.downloadManager = options.downloadManager || null;
|
this.downloadManager = options.downloadManager || null;
|
||||||
this.findController = options.findController || null;
|
this.findController = options.findController || null;
|
||||||
|
this._scriptingManager = options.scriptingManager || null;
|
||||||
this.removePageBorders = options.removePageBorders || false;
|
this.removePageBorders = options.removePageBorders || false;
|
||||||
this.textLayerMode = Number.isInteger(options.textLayerMode)
|
this.textLayerMode = Number.isInteger(options.textLayerMode)
|
||||||
? options.textLayerMode
|
? options.textLayerMode
|
||||||
@ -195,8 +196,8 @@ class BaseViewer {
|
|||||||
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
||||||
this.maxCanvasPixels = options.maxCanvasPixels;
|
this.maxCanvasPixels = options.maxCanvasPixels;
|
||||||
this.l10n = options.l10n || NullL10n;
|
this.l10n = options.l10n || NullL10n;
|
||||||
this.enableScripting = options.enableScripting || false;
|
this.enableScripting =
|
||||||
this._mouseState = options.mouseState || null;
|
options.enableScripting === true && !!this._scriptingManager;
|
||||||
|
|
||||||
this.defaultRenderingQueue = !options.renderingQueue;
|
this.defaultRenderingQueue = !options.renderingQueue;
|
||||||
if (this.defaultRenderingQueue) {
|
if (this.defaultRenderingQueue) {
|
||||||
@ -468,6 +469,12 @@ class BaseViewer {
|
|||||||
if (this.findController) {
|
if (this.findController) {
|
||||||
this.findController.setDocument(null);
|
this.findController.setDocument(null);
|
||||||
}
|
}
|
||||||
|
if (this._scriptingManager) {
|
||||||
|
// Defer this slightly, to allow the "pageclose" event to be handled.
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
this._scriptingManager.setDocument(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pdfDocument = pdfDocument;
|
this.pdfDocument = pdfDocument;
|
||||||
@ -562,6 +569,9 @@ class BaseViewer {
|
|||||||
if (this.findController) {
|
if (this.findController) {
|
||||||
this.findController.setDocument(pdfDocument); // Enable searching.
|
this.findController.setDocument(pdfDocument); // Enable searching.
|
||||||
}
|
}
|
||||||
|
if (this.enableScripting) {
|
||||||
|
this._scriptingManager.setDocument(pdfDocument);
|
||||||
|
}
|
||||||
|
|
||||||
// In addition to 'disableAutoFetch' being set, also attempt to reduce
|
// In addition to 'disableAutoFetch' being set, also attempt to reduce
|
||||||
// resource usage when loading *very* long/large documents.
|
// resource usage when loading *very* long/large documents.
|
||||||
@ -1299,7 +1309,7 @@ class BaseViewer {
|
|||||||
enableScripting,
|
enableScripting,
|
||||||
hasJSActionsPromise:
|
hasJSActionsPromise:
|
||||||
hasJSActionsPromise || this.pdfDocument?.hasJSActions(),
|
hasJSActionsPromise || this.pdfDocument?.hasJSActions(),
|
||||||
mouseState: mouseState || this._mouseState,
|
mouseState: mouseState || this._scriptingManager?.mouseState,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1645,7 +1655,7 @@ class BaseViewer {
|
|||||||
}
|
}
|
||||||
const eventBus = this.eventBus,
|
const eventBus = this.eventBus,
|
||||||
pageOpenPendingSet = (this._pageOpenPendingSet = new Set()),
|
pageOpenPendingSet = (this._pageOpenPendingSet = new Set()),
|
||||||
scriptingEvents = (this._scriptingEvents ||= Object.create(null));
|
scriptingEvents = (this._scriptingEvents = Object.create(null));
|
||||||
|
|
||||||
const dispatchPageClose = pageNumber => {
|
const dispatchPageClose = pageNumber => {
|
||||||
if (pageOpenPendingSet.has(pageNumber)) {
|
if (pageOpenPendingSet.has(pageNumber)) {
|
||||||
@ -1709,15 +1719,11 @@ class BaseViewer {
|
|||||||
|
|
||||||
// Remove the event listeners.
|
// Remove the event listeners.
|
||||||
eventBus._off("pagechanging", scriptingEvents.onPageChanging);
|
eventBus._off("pagechanging", scriptingEvents.onPageChanging);
|
||||||
scriptingEvents.onPageChanging = null;
|
|
||||||
|
|
||||||
eventBus._off("pagerendered", scriptingEvents.onPageRendered);
|
eventBus._off("pagerendered", scriptingEvents.onPageRendered);
|
||||||
scriptingEvents.onPageRendered = null;
|
|
||||||
|
|
||||||
eventBus._off("pagesdestroy", scriptingEvents.onPagesDestroy);
|
eventBus._off("pagesdestroy", scriptingEvents.onPagesDestroy);
|
||||||
scriptingEvents.onPagesDestroy = null;
|
|
||||||
|
|
||||||
this._pageOpenPendingSet = null;
|
this._pageOpenPendingSet = null;
|
||||||
|
this._scriptingEvents = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,38 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { getPDFFileNameFromURL } from "./ui_utils.js";
|
||||||
import { loadScript } from "pdfjs-lib";
|
import { loadScript } from "pdfjs-lib";
|
||||||
|
|
||||||
|
async function docPropertiesLookup(pdfDocument) {
|
||||||
|
const url = "",
|
||||||
|
baseUrl = url.split("#")[0];
|
||||||
|
/* eslint-disable prefer-const */
|
||||||
|
let {
|
||||||
|
info,
|
||||||
|
metadata,
|
||||||
|
contentDispositionFilename,
|
||||||
|
contentLength,
|
||||||
|
} = await pdfDocument.getMetadata();
|
||||||
|
/* eslint-enable prefer-const */
|
||||||
|
|
||||||
|
if (!contentLength) {
|
||||||
|
const { length } = await pdfDocument.getDownloadInfo();
|
||||||
|
contentLength = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...info,
|
||||||
|
baseURL: baseUrl,
|
||||||
|
filesize: contentLength,
|
||||||
|
filename: contentDispositionFilename || getPDFFileNameFromURL(url),
|
||||||
|
metadata: metadata?.getRaw(),
|
||||||
|
authors: metadata?.get("dc:creator"),
|
||||||
|
numPages: pdfDocument.numPages,
|
||||||
|
URL: url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class GenericScripting {
|
class GenericScripting {
|
||||||
constructor(sandboxBundleSrc) {
|
constructor(sandboxBundleSrc) {
|
||||||
this._ready = loadScript(
|
this._ready = loadScript(
|
||||||
@ -41,4 +71,4 @@ class GenericScripting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { GenericScripting };
|
export { docPropertiesLookup, GenericScripting };
|
||||||
|
@ -105,7 +105,7 @@ class PDFPageView {
|
|||||||
this.renderer = options.renderer || RendererType.CANVAS;
|
this.renderer = options.renderer || RendererType.CANVAS;
|
||||||
this.enableWebGL = options.enableWebGL || false;
|
this.enableWebGL = options.enableWebGL || false;
|
||||||
this.l10n = options.l10n || NullL10n;
|
this.l10n = options.l10n || NullL10n;
|
||||||
this.enableScripting = options.enableScripting || false;
|
this.enableScripting = options.enableScripting === true;
|
||||||
|
|
||||||
this.paintTask = null;
|
this.paintTask = null;
|
||||||
this.paintedViewportMap = new WeakMap();
|
this.paintedViewportMap = new WeakMap();
|
||||||
|
406
web/pdf_scripting_manager.js
Normal file
406
web/pdf_scripting_manager.js
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
/* Copyright 2021 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createPromiseCapability, shadow } from "pdfjs-lib";
|
||||||
|
import { apiPageLayoutToSpreadMode } from "./ui_utils.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PDFScriptingManagerOptions
|
||||||
|
* @property {EventBus} eventBus - The application event bus.
|
||||||
|
* @property {string} sandboxBundleSrc - The path and filename of the scripting
|
||||||
|
* bundle.
|
||||||
|
* @property {Object} [scriptingFactory] - The factory that is used when
|
||||||
|
* initializing scripting; must contain a `createScripting` method.
|
||||||
|
* PLEASE NOTE: Primarily intended for the default viewer use-case.
|
||||||
|
* @property {function} [docPropertiesLookup] - The function that is used to
|
||||||
|
* lookup the necessary document properties.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PDFScriptingManager {
|
||||||
|
/**
|
||||||
|
* @param {PDFScriptingManager} options
|
||||||
|
*/
|
||||||
|
constructor({
|
||||||
|
eventBus,
|
||||||
|
sandboxBundleSrc = null,
|
||||||
|
scriptingFactory = null,
|
||||||
|
docPropertiesLookup = null,
|
||||||
|
}) {
|
||||||
|
this._pdfDocument = null;
|
||||||
|
this._pdfViewer = null;
|
||||||
|
this._destroyCapability = null;
|
||||||
|
|
||||||
|
this._scripting = null;
|
||||||
|
this._mouseState = Object.create(null);
|
||||||
|
this._ready = false;
|
||||||
|
|
||||||
|
this._eventBus = eventBus;
|
||||||
|
this._sandboxBundleSrc = sandboxBundleSrc;
|
||||||
|
this._scriptingFactory = scriptingFactory;
|
||||||
|
this._docPropertiesLookup = docPropertiesLookup;
|
||||||
|
|
||||||
|
// The default viewer already handles adding/removing of DOM events,
|
||||||
|
// hence limit this to only the viewer components.
|
||||||
|
if (
|
||||||
|
typeof PDFJSDev !== "undefined" &&
|
||||||
|
PDFJSDev.test("COMPONENTS") &&
|
||||||
|
!this._scriptingFactory
|
||||||
|
) {
|
||||||
|
window.addEventListener("updatefromsandbox", event => {
|
||||||
|
this._eventBus.dispatch("updatefromsandbox", {
|
||||||
|
source: window,
|
||||||
|
detail: event.detail,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setViewer(pdfViewer) {
|
||||||
|
this._pdfViewer = pdfViewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDocument(pdfDocument) {
|
||||||
|
if (this._pdfDocument) {
|
||||||
|
await this._destroyScripting();
|
||||||
|
}
|
||||||
|
this._pdfDocument = pdfDocument;
|
||||||
|
|
||||||
|
if (!pdfDocument) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [objects, calculationOrder, docActions] = await Promise.all([
|
||||||
|
pdfDocument.getFieldObjects(),
|
||||||
|
pdfDocument.getCalculationOrderIds(),
|
||||||
|
pdfDocument.getJSActions(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!objects && !docActions) {
|
||||||
|
// No FieldObjects or JavaScript actions were found in the document.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pdfDocument !== this._pdfDocument) {
|
||||||
|
return; // The document was closed while the data resolved.
|
||||||
|
}
|
||||||
|
this._scripting = this._createScripting();
|
||||||
|
|
||||||
|
this._internalEvents.set("updatefromsandbox", event => {
|
||||||
|
this._updateFromSandbox(event.detail);
|
||||||
|
});
|
||||||
|
this._internalEvents.set("dispatcheventinsandbox", event => {
|
||||||
|
this._scripting?.dispatchEventInSandbox(event.detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._internalEvents.set("pageopen", event => {
|
||||||
|
this._pageOpen(event.pageNumber, event.actionsPromise);
|
||||||
|
});
|
||||||
|
this._internalEvents.set("pageclose", event => {
|
||||||
|
this._pageClose(event.pageNumber);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._domEvents.set("mousedown", event => {
|
||||||
|
this._mouseState.isDown = true;
|
||||||
|
});
|
||||||
|
this._domEvents.set("mouseup", event => {
|
||||||
|
this._mouseState.isDown = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [name, listener] of this._internalEvents) {
|
||||||
|
this._eventBus._on(name, listener);
|
||||||
|
}
|
||||||
|
for (const [name, listener] of this._domEvents) {
|
||||||
|
window.addEventListener(name, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const docProperties = await this._getDocProperties();
|
||||||
|
if (pdfDocument !== this._pdfDocument) {
|
||||||
|
return; // The document was closed while the properties resolved.
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._scripting.createSandbox({
|
||||||
|
objects,
|
||||||
|
calculationOrder,
|
||||||
|
appInfo: {
|
||||||
|
platform: navigator.platform,
|
||||||
|
language: navigator.language,
|
||||||
|
},
|
||||||
|
docInfo: {
|
||||||
|
...docProperties,
|
||||||
|
actions: docActions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this._eventBus.dispatch("sandboxcreated", { source: this });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`PDFScriptingManager.setDocument: "${error?.message}".`);
|
||||||
|
|
||||||
|
await this._destroyScripting();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._scripting?.dispatchEventInSandbox({
|
||||||
|
id: "doc",
|
||||||
|
name: "Open",
|
||||||
|
});
|
||||||
|
await this._pdfViewer.initializeScriptingEvents();
|
||||||
|
|
||||||
|
// Defer this slightly, to ensure that scripting is *fully* initialized.
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
if (pdfDocument === this._pdfDocument) {
|
||||||
|
this._ready = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispatchWillSave(detail) {
|
||||||
|
return this._scripting?.dispatchEventInSandbox({
|
||||||
|
id: "doc",
|
||||||
|
name: "WillSave",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispatchDidSave(detail) {
|
||||||
|
return this._scripting?.dispatchEventInSandbox({
|
||||||
|
id: "doc",
|
||||||
|
name: "DidSave",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispatchWillPrint(detail) {
|
||||||
|
return this._scripting?.dispatchEventInSandbox({
|
||||||
|
id: "doc",
|
||||||
|
name: "WillPrint",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispatchDidPrint(detail) {
|
||||||
|
return this._scripting?.dispatchEventInSandbox({
|
||||||
|
id: "doc",
|
||||||
|
name: "DidPrint",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get mouseState() {
|
||||||
|
return this._mouseState;
|
||||||
|
}
|
||||||
|
|
||||||
|
get destroyPromise() {
|
||||||
|
return this._destroyCapability?.promise || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get ready() {
|
||||||
|
return this._ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
get _internalEvents() {
|
||||||
|
return shadow(this, "_internalEvents", new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
get _domEvents() {
|
||||||
|
return shadow(this, "_domEvents", new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
get _visitedPages() {
|
||||||
|
return shadow(this, "_visitedPages", new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_updateFromSandbox(detail) {
|
||||||
|
const { id, command, value } = detail;
|
||||||
|
if (!id) {
|
||||||
|
switch (command) {
|
||||||
|
case "clear":
|
||||||
|
console.clear();
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
console.error(value);
|
||||||
|
break;
|
||||||
|
case "layout":
|
||||||
|
this._pdfViewer.spreadMode = apiPageLayoutToSpreadMode(value);
|
||||||
|
break;
|
||||||
|
case "page-num":
|
||||||
|
this._pdfViewer.currentPageNumber = value + 1;
|
||||||
|
break;
|
||||||
|
case "print":
|
||||||
|
this._pdfViewer.pagesPromise.then(() => {
|
||||||
|
this._eventBus.dispatch("print", { source: this });
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "println":
|
||||||
|
console.log(value);
|
||||||
|
break;
|
||||||
|
case "zoom":
|
||||||
|
this._pdfViewer.currentScaleValue = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
|
||||||
|
} else {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
// The element hasn't been rendered yet, use the AnnotationStorage.
|
||||||
|
this._pdfDocument?.annotationStorage.setValue(id, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _pageOpen(pageNumber, actionsPromise) {
|
||||||
|
const pdfDocument = this._pdfDocument,
|
||||||
|
visitedPages = this._visitedPages;
|
||||||
|
|
||||||
|
visitedPages.set(
|
||||||
|
pageNumber,
|
||||||
|
(async () => {
|
||||||
|
// Avoid sending, and thus serializing, the `actions` data when the
|
||||||
|
// *same* page is opened several times.
|
||||||
|
let actions = null;
|
||||||
|
if (!visitedPages.has(pageNumber)) {
|
||||||
|
actions = await actionsPromise;
|
||||||
|
if (pdfDocument !== this._pdfDocument) {
|
||||||
|
return; // The document was closed while the actions resolved.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._scripting?.dispatchEventInSandbox({
|
||||||
|
id: "page",
|
||||||
|
name: "PageOpen",
|
||||||
|
pageNumber,
|
||||||
|
actions,
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _pageClose(pageNumber) {
|
||||||
|
const pdfDocument = this._pdfDocument,
|
||||||
|
visitedPages = this._visitedPages;
|
||||||
|
|
||||||
|
const actionsPromise = visitedPages.get(pageNumber);
|
||||||
|
if (!actionsPromise) {
|
||||||
|
// Ensure that the "pageclose" event was preceded by a "pageopen" event.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visitedPages.set(pageNumber, null);
|
||||||
|
|
||||||
|
// Ensure that the "pageopen" event is handled first.
|
||||||
|
await actionsPromise;
|
||||||
|
if (pdfDocument !== this._pdfDocument) {
|
||||||
|
return; // The document was closed while the actions resolved.
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._scripting?.dispatchEventInSandbox({
|
||||||
|
id: "page",
|
||||||
|
name: "PageClose",
|
||||||
|
pageNumber,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<Object>} A promise that is resolved with an {Object}
|
||||||
|
* containing the necessary document properties; please find the expected
|
||||||
|
* format in `PDFViewerApplication._scriptingDocProperties`.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _getDocProperties() {
|
||||||
|
// The default viewer use-case.
|
||||||
|
if (this._docPropertiesLookup) {
|
||||||
|
return this._docPropertiesLookup(this._pdfDocument);
|
||||||
|
}
|
||||||
|
// Fallback, to support the viewer components use-case.
|
||||||
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("COMPONENTS")) {
|
||||||
|
const { docPropertiesLookup } = require("./generic_scripting.js");
|
||||||
|
|
||||||
|
return docPropertiesLookup(this._pdfDocument);
|
||||||
|
}
|
||||||
|
throw new Error("_getDocProperties: Unable to lookup properties.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_createScripting() {
|
||||||
|
this._destroyCapability = createPromiseCapability();
|
||||||
|
|
||||||
|
if (this._scripting) {
|
||||||
|
throw new Error("_createScripting: Scripting already exists.");
|
||||||
|
}
|
||||||
|
if (this._scriptingFactory) {
|
||||||
|
return this._scriptingFactory.createScripting({
|
||||||
|
sandboxBundleSrc: this._sandboxBundleSrc,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("COMPONENTS")) {
|
||||||
|
const { GenericScripting } = require("./generic_scripting.js");
|
||||||
|
|
||||||
|
return new GenericScripting(this._sandboxBundleSrc);
|
||||||
|
}
|
||||||
|
throw new Error("_createScripting: Cannot create scripting.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _destroyScripting() {
|
||||||
|
this._pdfDocument = null; // Ensure that it's *always* reset synchronously.
|
||||||
|
|
||||||
|
if (!this._scripting) {
|
||||||
|
this._destroyCapability?.resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._scripting.destroySandbox();
|
||||||
|
} catch (ex) {}
|
||||||
|
|
||||||
|
for (const [name, listener] of this._internalEvents) {
|
||||||
|
this._eventBus._off(name, listener);
|
||||||
|
}
|
||||||
|
this._internalEvents.clear();
|
||||||
|
|
||||||
|
for (const [name, listener] of this._domEvents) {
|
||||||
|
window.removeEventListener(name, listener);
|
||||||
|
}
|
||||||
|
this._domEvents.clear();
|
||||||
|
|
||||||
|
this._visitedPages.clear();
|
||||||
|
|
||||||
|
this._scripting = null;
|
||||||
|
delete this._mouseState.isDown;
|
||||||
|
this._ready = false;
|
||||||
|
|
||||||
|
this._destroyCapability?.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PDFScriptingManager };
|
@ -29,6 +29,7 @@ import { NullL10n } from "./l10n_utils.js";
|
|||||||
import { PDFFindController } from "./pdf_find_controller.js";
|
import { PDFFindController } from "./pdf_find_controller.js";
|
||||||
import { PDFHistory } from "./pdf_history.js";
|
import { PDFHistory } from "./pdf_history.js";
|
||||||
import { PDFPageView } from "./pdf_page_view.js";
|
import { PDFPageView } from "./pdf_page_view.js";
|
||||||
|
import { PDFScriptingManager } from "./pdf_scripting_manager.js";
|
||||||
import { PDFSinglePageViewer } from "./pdf_single_page_viewer.js";
|
import { PDFSinglePageViewer } from "./pdf_single_page_viewer.js";
|
||||||
import { PDFViewer } from "./pdf_viewer.js";
|
import { PDFViewer } from "./pdf_viewer.js";
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ export {
|
|||||||
PDFHistory,
|
PDFHistory,
|
||||||
PDFLinkService,
|
PDFLinkService,
|
||||||
PDFPageView,
|
PDFPageView,
|
||||||
|
PDFScriptingManager,
|
||||||
PDFSinglePageViewer,
|
PDFSinglePageViewer,
|
||||||
PDFViewer,
|
PDFViewer,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
|
@ -1001,8 +1001,57 @@ function getActiveOrFocusedElement() {
|
|||||||
return curActiveOrFocused;
|
return curActiveOrFocused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts API PageLayout values to the format used by `BaseViewer`.
|
||||||
|
* NOTE: This is supported to the extent that the viewer implements the
|
||||||
|
* necessary Scroll/Spread modes (since SinglePage, TwoPageLeft,
|
||||||
|
* and TwoPageRight all suggests using non-continuous scrolling).
|
||||||
|
* @param {string} mode - The API PageLayout value.
|
||||||
|
* @returns {number} A value from {SpreadMode}.
|
||||||
|
*/
|
||||||
|
function apiPageLayoutToSpreadMode(layout) {
|
||||||
|
switch (layout) {
|
||||||
|
case "SinglePage":
|
||||||
|
case "OneColumn":
|
||||||
|
return SpreadMode.NONE;
|
||||||
|
case "TwoColumnLeft":
|
||||||
|
case "TwoPageLeft":
|
||||||
|
return SpreadMode.ODD;
|
||||||
|
case "TwoColumnRight":
|
||||||
|
case "TwoPageRight":
|
||||||
|
return SpreadMode.EVEN;
|
||||||
|
}
|
||||||
|
return SpreadMode.NONE; // Default value.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts API PageMode values to the format used by `PDFSidebar`.
|
||||||
|
* NOTE: There's also a "FullScreen" parameter which is not possible to support,
|
||||||
|
* since the Fullscreen API used in browsers requires that entering
|
||||||
|
* fullscreen mode only occurs as a result of a user-initiated event.
|
||||||
|
* @param {string} mode - The API PageMode value.
|
||||||
|
* @returns {number} A value from {SidebarView}.
|
||||||
|
*/
|
||||||
|
function apiPageModeToSidebarView(mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case "UseNone":
|
||||||
|
return SidebarView.NONE;
|
||||||
|
case "UseThumbs":
|
||||||
|
return SidebarView.THUMBS;
|
||||||
|
case "UseOutlines":
|
||||||
|
return SidebarView.OUTLINE;
|
||||||
|
case "UseAttachments":
|
||||||
|
return SidebarView.ATTACHMENTS;
|
||||||
|
case "UseOC":
|
||||||
|
return SidebarView.LAYERS;
|
||||||
|
}
|
||||||
|
return SidebarView.NONE; // Default value.
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
animationStarted,
|
animationStarted,
|
||||||
|
apiPageLayoutToSpreadMode,
|
||||||
|
apiPageModeToSidebarView,
|
||||||
approximateFraction,
|
approximateFraction,
|
||||||
AutoPrintRegExp,
|
AutoPrintRegExp,
|
||||||
backtrackBeforeAllVisibleElements, // only exported for testing
|
backtrackBeforeAllVisibleElements, // only exported for testing
|
||||||
|
Loading…
x
Reference in New Issue
Block a user