Merge pull request #12748 from Snuffleupagus/scripting-misc-fixes

Update the events, used with scripting, to use lower-case names and avoid using DOM events internally in the viewer + misc scripting-related tweaks
This commit is contained in:
Tim van der Meij 2020-12-19 20:58:53 +01:00 committed by GitHub
commit af52c5fd17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 230 additions and 224 deletions

View File

@ -97,7 +97,6 @@ const DEFINES = Object.freeze({
PRODUCTION: true, PRODUCTION: true,
SKIP_BABEL: true, SKIP_BABEL: true,
TESTING: false, TESTING: false,
ENABLE_SCRIPTING: false,
// The main build targets: // The main build targets:
GENERIC: false, GENERIC: false,
MOZCENTRAL: false, MOZCENTRAL: false,
@ -682,7 +681,6 @@ gulp.task("default_preferences-pre", function () {
LIB: true, LIB: true,
BUNDLE_VERSION: 0, // Dummy version BUNDLE_VERSION: 0, // Dummy version
BUNDLE_BUILD: 0, // Dummy build BUNDLE_BUILD: 0, // Dummy build
ENABLE_SCRIPTING: process.env.ENABLE_SCRIPTING === "true",
}), }),
map: { map: {
"pdfjs-lib": "../pdf", "pdfjs-lib": "../pdf",
@ -1551,46 +1549,29 @@ gulp.task("testing-pre", function (done) {
done(); done();
}); });
gulp.task("enable-scripting", function (done) {
process.env.ENABLE_SCRIPTING = "true";
done();
});
gulp.task( gulp.task(
"test", "test",
gulp.series( gulp.series("testing-pre", "generic", "components", function () {
"enable-scripting", return streamqueue(
"testing-pre", { objectMode: true },
"generic", createTestSource("unit"),
"components", createTestSource("browser"),
function () { createTestSource("integration")
return streamqueue( );
{ objectMode: true }, })
createTestSource("unit"),
createTestSource("browser"),
createTestSource("integration")
);
}
)
); );
gulp.task( gulp.task(
"bottest", "bottest",
gulp.series( gulp.series("testing-pre", "generic", "components", function () {
"enable-scripting", return streamqueue(
"testing-pre", { objectMode: true },
"generic", createTestSource("unit", true),
"components", createTestSource("font", true),
function () { createTestSource("browser (no reftest)", true),
return streamqueue( createTestSource("integration")
{ objectMode: true }, );
createTestSource("unit", true), })
createTestSource("font", true),
createTestSource("browser (no reftest)", true),
createTestSource("integration")
);
}
)
); );
gulp.task( gulp.task(
@ -1609,7 +1590,7 @@ gulp.task(
gulp.task( gulp.task(
"integrationtest", "integrationtest",
gulp.series("enable-scripting", "testing-pre", "generic", function () { gulp.series("testing-pre", "generic", function () {
return createTestSource("integration"); return createTestSource("integration");
}) })
); );

View File

@ -47,6 +47,7 @@ import { ColorConverters } from "../shared/scripting_utils.js";
* @property {Object} svgFactory * @property {Object} svgFactory
* @property {boolean} [enableScripting] * @property {boolean} [enableScripting]
* @property {boolean} [hasJSActions] * @property {boolean} [hasJSActions]
* @property {Object} [mouseState]
*/ */
class AnnotationElementFactory { class AnnotationElementFactory {
@ -155,6 +156,7 @@ class AnnotationElement {
this.annotationStorage = parameters.annotationStorage; this.annotationStorage = parameters.annotationStorage;
this.enableScripting = parameters.enableScripting; this.enableScripting = parameters.enableScripting;
this.hasJSActions = parameters.hasJSActions; this.hasJSActions = parameters.hasJSActions;
this._mouseState = parameters.mouseState;
if (isRenderable) { if (isRenderable) {
this.container = this._createContainer(ignoreBorder); this.container = this._createContainer(ignoreBorder);
@ -397,7 +399,7 @@ class LinkAnnotationElement extends AnnotationElement {
this.enableScripting && this.enableScripting &&
this.hasJSActions this.hasJSActions
) { ) {
this._bindJSAction(link); this._bindJSAction(link, data);
} else { } else {
this._bindLink(link, ""); this._bindLink(link, "");
} }
@ -463,9 +465,8 @@ class LinkAnnotationElement extends AnnotationElement {
* @param {Object} data * @param {Object} data
* @memberof LinkAnnotationElement * @memberof LinkAnnotationElement
*/ */
_bindJSAction(link) { _bindJSAction(link, data) {
link.href = this.linkService.getAnchorUrl("#"); link.href = this.linkService.getAnchorUrl("");
const { data } = this;
const map = new Map([ const map = new Map([
["Action", "onclick"], ["Action", "onclick"],
["MouseUp", "onmouseup"], ["MouseUp", "onmouseup"],
@ -477,14 +478,13 @@ class LinkAnnotationElement extends AnnotationElement {
continue; continue;
} }
link[jsName] = () => { link[jsName] = () => {
window.dispatchEvent( this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
new CustomEvent("dispatchEventInSandbox", { source: this,
detail: { detail: {
id: data.id, id: data.id,
name, name,
}, },
}) });
);
return false; return false;
}; };
} }
@ -544,40 +544,42 @@ class WidgetAnnotationElement extends AnnotationElement {
} }
_setEventListener(element, baseName, eventName, valueGetter) { _setEventListener(element, baseName, eventName, valueGetter) {
if (this.data.actions && eventName.replace(" ", "") in this.data.actions) { if (this.data.actions[eventName.replace(" ", "")] === undefined) {
if (baseName.includes("mouse")) { return;
// Mouse events }
element.addEventListener(baseName, event => { if (baseName.includes("mouse")) {
window.dispatchEvent( // Mouse events
new CustomEvent("dispatchEventInSandbox", { element.addEventListener(baseName, event => {
detail: { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
id: this.data.id, source: this,
name: eventName, detail: {
value: valueGetter(event), id: this.data.id,
shift: event.shiftKey, name: eventName,
modifier: this._getKeyModifier(event), value: valueGetter(event),
}, shift: event.shiftKey,
}) modifier: this._getKeyModifier(event),
); },
}); });
} else { });
// Non mouse event } else {
element.addEventListener(baseName, event => { // Non mouse event
window.dispatchEvent( element.addEventListener(baseName, event => {
new CustomEvent("dispatchEventInSandbox", { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
detail: { source: this,
id: this.data.id, detail: {
name: eventName, id: this.data.id,
value: event.target.checked, name: eventName,
}, value: event.target.checked,
}) },
);
}); });
} });
} }
} }
_setEventListeners(element, names, getter) { _setEventListeners(element, names, getter) {
if (!this.data.actions) {
return;
}
for (const [baseName, eventName] of names) { for (const [baseName, eventName] of names) {
this._setEventListener(element, baseName, eventName, getter); this._setEventListener(element, baseName, eventName, getter);
} }
@ -590,7 +592,6 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
parameters.renderInteractiveForms || parameters.renderInteractiveForms ||
(!parameters.data.hasAppearance && !!parameters.data.fieldValue); (!parameters.data.hasAppearance && !!parameters.data.fieldValue);
super(parameters, { isRenderable }); super(parameters, { isRenderable });
this.mouseState = parameters.mouseState;
} }
render() { render() {
@ -646,7 +647,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
} }
}); });
element.addEventListener("updateFromSandbox", function (event) { element.addEventListener("updatefromsandbox", function (event) {
const { detail } = event; const { detail } = event;
const actions = { const actions = {
value() { value() {
@ -717,39 +718,37 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
} }
// Save the entered value // Save the entered value
elementData.userValue = event.target.value; elementData.userValue = event.target.value;
window.dispatchEvent( this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
new CustomEvent("dispatchEventInSandbox", { source: this,
detail: {
id,
name: "Keystroke",
value: event.target.value,
willCommit: true,
commitKey,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd,
},
});
});
const _blurListener = blurListener;
blurListener = null;
element.addEventListener("blur", event => {
if (this._mouseState.isDown) {
// Focus out using the mouse: data are committed
elementData.userValue = event.target.value;
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: { detail: {
id, id,
name: "Keystroke", name: "Keystroke",
value: event.target.value, value: event.target.value,
willCommit: true, willCommit: true,
commitKey, commitKey: 1,
selStart: event.target.selectionStart, selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd, selEnd: event.target.selectionEnd,
}, },
}) });
);
});
const _blurListener = blurListener;
blurListener = null;
element.addEventListener("blur", event => {
if (this.mouseState.isDown) {
// Focus out using the mouse: data are committed
elementData.userValue = event.target.value;
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id,
name: "Keystroke",
value: event.target.value,
willCommit: true,
commitKey: 1,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd,
},
})
);
} }
_blurListener(event); _blurListener(event);
}); });
@ -779,19 +778,18 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
if (elementData.beforeInputSelectionRange) { if (elementData.beforeInputSelectionRange) {
[selStart, selEnd] = elementData.beforeInputSelectionRange; [selStart, selEnd] = elementData.beforeInputSelectionRange;
} }
window.dispatchEvent( this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
new CustomEvent("dispatchEventInSandbox", { source: this,
detail: { detail: {
id, id,
name: "Keystroke", name: "Keystroke",
value: elementData.beforeInputValue, value: elementData.beforeInputValue,
change: event.data, change: event.data,
willCommit: false, willCommit: false,
selStart, selStart,
selEnd, selEnd,
}, },
}) });
);
}); });
} }
@ -925,7 +923,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
}); });
if (this.enableScripting && this.hasJSActions) { if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updateFromSandbox", event => { element.addEventListener("updatefromsandbox", event => {
const { detail } = event; const { detail } = event;
const actions = { const actions = {
value() { value() {
@ -996,8 +994,8 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
element.setAttribute("id", id); element.setAttribute("id", id);
element.addEventListener("change", function (event) { element.addEventListener("change", function (event) {
const target = event.target; const { target } = event;
for (const radio of document.getElementsByName(event.target.name)) { for (const radio of document.getElementsByName(target.name)) {
if (radio !== target) { if (radio !== target) {
storage.setValue(radio.getAttribute("id"), { value: false }); storage.setValue(radio.getAttribute("id"), { value: false });
} }
@ -1006,7 +1004,7 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
}); });
if (this.enableScripting && this.hasJSActions) { if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updateFromSandbox", event => { element.addEventListener("updatefromsandbox", event => {
const { detail } = event; const { detail } = event;
const actions = { const actions = {
value() { value() {
@ -1128,7 +1126,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
} }
if (this.enableScripting && this.hasJSActions) { if (this.enableScripting && this.hasJSActions) {
selectElement.addEventListener("updateFromSandbox", event => { selectElement.addEventListener("updatefromsandbox", event => {
const { detail } = event; const { detail } = event;
const actions = { const actions = {
value() { value() {
@ -1158,22 +1156,21 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
.forEach(name => actions[name]()); .forEach(name => actions[name]());
}); });
selectElement.addEventListener("input", function (event) { selectElement.addEventListener("input", event => {
const value = getValue(event); const value = getValue(event);
storage.setValue(id, { value }); storage.setValue(id, { value });
window.dispatchEvent( this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
new CustomEvent("dispatchEventInSandbox", { source: this,
detail: { detail: {
id, id,
name: "Keystroke", name: "Keystroke",
changeEx: value, changeEx: value,
willCommit: true, willCommit: true,
commitKey: 1, commitKey: 1,
keyDown: false, keyDown: false,
}, },
}) });
);
}); });
this._setEventListeners( this._setEventListeners(
@ -1848,14 +1845,12 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
this.filename = getFilenameFromUrl(filename); this.filename = getFilenameFromUrl(filename);
this.content = content; this.content = content;
if (this.linkService.eventBus) { this.linkService.eventBus?.dispatch("fileattachmentannotation", {
this.linkService.eventBus.dispatch("fileattachmentannotation", { source: this,
source: this, id: stringToPDFString(filename),
id: stringToPDFString(filename), filename,
filename, content,
content, });
});
}
} }
render() { render() {
@ -1951,7 +1946,7 @@ class AnnotationLayer {
parameters.annotationStorage || new AnnotationStorage(), parameters.annotationStorage || new AnnotationStorage(),
enableScripting: parameters.enableScripting, enableScripting: parameters.enableScripting,
hasJSActions: parameters.hasJSActions, hasJSActions: parameters.hasJSActions,
mouseState: parameters.mouseState, mouseState: parameters.mouseState || { isDown: false },
}); });
if (element.isRenderable) { if (element.isRenderable) {
const rendered = element.render(); const rendered = element.render();

View File

@ -148,7 +148,7 @@ class SandboxSupportBase {
if (!data) { if (!data) {
return; return;
} }
const event = new this.win.CustomEvent("updateFromSandbox", { const event = new this.win.CustomEvent("updatefromsandbox", {
detail: this.importValueFromSandbox(data), detail: this.importValueFromSandbox(data),
}); });
this.win.dispatchEvent(event); this.win.dispatchEvent(event);

View File

@ -63,8 +63,6 @@ class Sandbox {
} }
const sandboxData = JSON.stringify(data); const sandboxData = JSON.stringify(data);
const code = [ const code = [
// Next line is replaced by code from initialization.js
// when we create the bundle for the sandbox.
PDFJSDev.eval("PDF_SCRIPTING_JS_SOURCE"), PDFJSDev.eval("PDF_SCRIPTING_JS_SOURCE"),
`pdfjsScripting.initSandbox({ data: ${sandboxData} })`, `pdfjsScripting.initSandbox({ data: ${sandboxData} })`,
]; ];

View File

@ -30,9 +30,12 @@ describe("Interaction", () => {
it("must check that first text field has focus", async () => { it("must check that first text field has focus", async () => {
await Promise.all( await Promise.all(
pages.map(async ([browserName, page]) => { pages.map(async ([browserName, page]) => {
await page.waitForFunction(
"window.PDFViewerApplication.scriptingReady === true"
);
// The document has an open action in order to give // The document has an open action in order to give
// the focus to 401R. // the focus to 401R.
await page.waitForTimeout(1000);
const id = await page.evaluate( const id = await page.evaluate(
() => window.document.activeElement.id () => window.document.activeElement.id
); );

View File

@ -30,6 +30,7 @@ import { SimpleLinkService } from "./pdf_link_service.js";
* @property {IL10n} l10n - Localization service. * @property {IL10n} l10n - Localization service.
* @property {boolean} [enableScripting] * @property {boolean} [enableScripting]
* @property {Promise<boolean>} [hasJSActionsPromise] * @property {Promise<boolean>} [hasJSActionsPromise]
* @property {Object} [mouseState]
*/ */
class AnnotationLayerBuilder { class AnnotationLayerBuilder {

View File

@ -784,16 +784,22 @@ const PDFViewerApplication = {
if (!this._scriptingInstance) { if (!this._scriptingInstance) {
return; return;
} }
const { scripting, events } = this._scriptingInstance; const { scripting, internalEvents, domEvents } = this._scriptingInstance;
try { try {
await scripting.destroySandbox(); await scripting.destroySandbox();
} catch (ex) {} } catch (ex) {}
for (const [name, listener] of events) { for (const [name, listener] of internalEvents) {
this.eventBus._off(name, listener);
}
internalEvents.clear();
for (const [name, listener] of domEvents) {
window.removeEventListener(name, listener); window.removeEventListener(name, listener);
} }
events.clear(); domEvents.clear();
delete this._mouseState.isDown;
this._scriptingInstance = null; this._scriptingInstance = null;
}, },
@ -1030,13 +1036,10 @@ const PDFViewerApplication = {
this.download({ sourceEventType }); this.download({ sourceEventType });
return; return;
} }
this._scriptingInstance?.scripting.dispatchEventInSandbox({
if (this._scriptingInstance) { id: "doc",
this._scriptingInstance.scripting.dispatchEventInSandbox({ name: "WillSave",
id: "doc", });
name: "WillSave",
});
}
this._saveInProgress = true; this._saveInProgress = true;
this.pdfDocument this.pdfDocument
@ -1045,12 +1048,10 @@ const PDFViewerApplication = {
const blob = new Blob([data], { type: "application/pdf" }); const blob = new Blob([data], { type: "application/pdf" });
downloadManager.download(blob, url, filename, sourceEventType); downloadManager.download(blob, url, filename, sourceEventType);
if (this._scriptingInstance) { this._scriptingInstance?.scripting.dispatchEventInSandbox({
this._scriptingInstance.scripting.dispatchEventInSandbox({ id: "doc",
id: "doc", name: "DidSave",
name: "DidSave", });
});
}
}) })
.catch(() => { .catch(() => {
this.download({ sourceEventType }); this.download({ sourceEventType });
@ -1467,16 +1468,24 @@ const PDFViewerApplication = {
pdfDocument.getJSActions(), pdfDocument.getJSActions(),
]); ]);
if ((!objects && !docActions) || pdfDocument !== this.pdfDocument) { if (!objects && !docActions) {
// No FieldObjects were found in the document, no JS Actions at doc level // No FieldObjects or JavaScript actions were found in the document.
// or the document was closed while the data resolved.
return; return;
} }
if (pdfDocument !== this.pdfDocument) {
return; // The document was closed while the data resolved.
}
const scripting = this.externalServices.createScripting(); const scripting = this.externalServices.createScripting();
// Store a reference to the current scripting-instance, to allow destruction // Store a reference to the current scripting-instance, to allow destruction
// of the sandbox and removal of the event listeners at document closing. // of the sandbox and removal of the event listeners at document closing.
this._scriptingInstance = { scripting, events: new Map() }; 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
@ -1493,8 +1502,7 @@ const PDFViewerApplication = {
} }
} }
const updateFromSandbox = event => { const updateFromSandbox = ({ detail }) => {
const { detail } = event;
const { id, command, value } = detail; const { id, command, value } = detail;
if (!id) { if (!id) {
switch (command) { switch (command) {
@ -1506,24 +1514,20 @@ const PDFViewerApplication = {
break; break;
case "layout": case "layout":
this.pdfViewer.spreadMode = apiPageLayoutToSpreadMode(value); this.pdfViewer.spreadMode = apiPageLayoutToSpreadMode(value);
return; break;
case "page-num": case "page-num":
this.pdfViewer.currentPageNumber = value + 1; this.pdfViewer.currentPageNumber = value + 1;
return; break;
case "print": case "print":
this.pdfViewer.pagesPromise.then(() => { this.pdfViewer.pagesPromise.then(() => {
this.triggerPrinting(); this.triggerPrinting();
}); });
return; break;
case "println": case "println":
console.log(value); console.log(value);
break; break;
case "zoom": case "zoom":
if (typeof value === "string") { this.pdfViewer.currentScaleValue = value;
this.pdfViewer.currentScaleValue = value;
} else {
this.pdfViewer.currentScale = value;
}
break; break;
} }
return; return;
@ -1531,7 +1535,7 @@ const PDFViewerApplication = {
const element = document.getElementById(id); const element = document.getElementById(id);
if (element) { if (element) {
element.dispatchEvent(new CustomEvent("updateFromSandbox", { detail })); element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
} else { } else {
if (value !== undefined && value !== null) { if (value !== undefined && value !== null) {
// The element hasn't been rendered yet, use the AnnotationStorage. // The element hasn't been rendered yet, use the AnnotationStorage.
@ -1539,30 +1543,29 @@ const PDFViewerApplication = {
} }
} }
}; };
window.addEventListener("updateFromSandbox", updateFromSandbox); internalEvents.set("updatefromsandbox", updateFromSandbox);
// Ensure that the event listener can be removed at document closing.
this._scriptingInstance.events.set("updateFromSandbox", updateFromSandbox);
const dispatchEventInSandbox = event => { const dispatchEventInSandbox = ({ detail }) => {
scripting.dispatchEventInSandbox(event.detail); scripting.dispatchEventInSandbox(detail);
}; };
window.addEventListener("dispatchEventInSandbox", dispatchEventInSandbox); internalEvents.set("dispatcheventinsandbox", dispatchEventInSandbox);
// Ensure that the event listener can be removed at document closing.
this._scriptingInstance.events.set(
"dispatchEventInSandbox",
dispatchEventInSandbox
);
const mouseDown = event => { const mouseDown = event => {
this._mouseState.isDown = true; this._mouseState.isDown = true;
}; };
domEvents.set("mousedown", mouseDown);
const mouseUp = event => { const mouseUp = event => {
this._mouseState.isDown = false; this._mouseState.isDown = false;
}; };
window.addEventListener("mousedown", mouseDown); domEvents.set("mouseup", mouseUp);
this._scriptingInstance.events.set("mousedown", mouseDown);
window.addEventListener("mouseup", mouseUp); for (const [name, listener] of internalEvents) {
this._scriptingInstance.events.set("mouseup", mouseUp); this.eventBus._on(name, listener);
}
for (const [name, listener] of domEvents) {
window.addEventListener(name, listener);
}
if (!this._contentLength) { if (!this._contentLength) {
// Always waiting for the entire PDF document to be loaded will, most // Always waiting for the entire PDF document to be loaded will, most
@ -1601,19 +1604,28 @@ const PDFViewerApplication = {
}); });
if (this.externalServices.isInAutomation) { if (this.externalServices.isInAutomation) {
this.eventBus.dispatch("sandboxcreated", { this.eventBus.dispatch("sandboxcreated", { source: this });
source: this,
});
} }
} catch (error) { } catch (error) {
console.error(error); console.error(`_initializeJavaScript: "${error?.message}".`);
this._destroyScriptingInstance(); this._destroyScriptingInstance();
return;
} }
scripting.dispatchEventInSandbox({ scripting.dispatchEventInSandbox({
id: "doc", id: "doc",
name: "Open", name: "Open",
}); });
// 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;
}
});
}, },
/** /**
@ -2032,21 +2044,17 @@ const PDFViewerApplication = {
if (!this.supportsPrinting) { if (!this.supportsPrinting) {
return; return;
} }
if (this._scriptingInstance) { this._scriptingInstance?.scripting.dispatchEventInSandbox({
this._scriptingInstance.scripting.dispatchEventInSandbox({ id: "doc",
id: "doc", name: "WillPrint",
name: "WillPrint", });
});
}
window.print(); window.print();
if (this._scriptingInstance) { this._scriptingInstance?.scripting.dispatchEventInSandbox({
this._scriptingInstance.scripting.dispatchEventInSandbox({ id: "doc",
id: "doc", name: "DidPrint",
name: "DidPrint", });
});
}
}, },
bindEvents() { bindEvents() {
@ -2124,6 +2132,12 @@ const PDFViewerApplication = {
_boundEvents.windowAfterPrint = () => { _boundEvents.windowAfterPrint = () => {
eventBus.dispatch("afterprint", { source: window }); eventBus.dispatch("afterprint", { source: window });
}; };
_boundEvents.windowUpdateFromSandbox = event => {
eventBus.dispatch("updatefromsandbox", {
source: window,
detail: event.detail,
});
};
window.addEventListener("visibilitychange", webViewerVisibilityChange); window.addEventListener("visibilitychange", webViewerVisibilityChange);
window.addEventListener("wheel", webViewerWheel, { passive: false }); window.addEventListener("wheel", webViewerWheel, { passive: false });
@ -2137,6 +2151,10 @@ const PDFViewerApplication = {
window.addEventListener("hashchange", _boundEvents.windowHashChange); window.addEventListener("hashchange", _boundEvents.windowHashChange);
window.addEventListener("beforeprint", _boundEvents.windowBeforePrint); window.addEventListener("beforeprint", _boundEvents.windowBeforePrint);
window.addEventListener("afterprint", _boundEvents.windowAfterPrint); window.addEventListener("afterprint", _boundEvents.windowAfterPrint);
window.addEventListener(
"updatefromsandbox",
_boundEvents.windowUpdateFromSandbox
);
}, },
unbindEvents() { unbindEvents() {
@ -2211,11 +2229,16 @@ const PDFViewerApplication = {
window.removeEventListener("hashchange", _boundEvents.windowHashChange); window.removeEventListener("hashchange", _boundEvents.windowHashChange);
window.removeEventListener("beforeprint", _boundEvents.windowBeforePrint); window.removeEventListener("beforeprint", _boundEvents.windowBeforePrint);
window.removeEventListener("afterprint", _boundEvents.windowAfterPrint); window.removeEventListener("afterprint", _boundEvents.windowAfterPrint);
window.removeEventListener(
"updatefromsandbox",
_boundEvents.windowUpdateFromSandbox
);
_boundEvents.windowResize = null; _boundEvents.windowResize = null;
_boundEvents.windowHashChange = null; _boundEvents.windowHashChange = null;
_boundEvents.windowBeforePrint = null; _boundEvents.windowBeforePrint = null;
_boundEvents.windowAfterPrint = null; _boundEvents.windowAfterPrint = null;
_boundEvents.windowUpdateFromSandbox = null;
}, },
accumulateWheelTicks(ticks) { accumulateWheelTicks(ticks) {
@ -2233,6 +2256,14 @@ const PDFViewerApplication = {
this._wheelUnusedTicks -= wholeTicks; this._wheelUnusedTicks -= wholeTicks;
return wholeTicks; return wholeTicks;
}, },
/**
* Used together with the integration-tests, to enable awaiting full
* initialization of the scripting/sandbox.
*/
get scriptingReady() {
return this._scriptingInstance?.ready || false;
},
}; };
let validateFileURL; let validateFileURL;

View File

@ -67,7 +67,7 @@ const defaultOptions = {
}, },
enableScripting: { enableScripting: {
/** @type {boolean} */ /** @type {boolean} */
value: typeof PDFJSDev !== "undefined" && PDFJSDev.test("ENABLE_SCRIPTING"), value: typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING"),
kind: OptionKind.VIEWER + OptionKind.PREFERENCE, kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
}, },
enableWebGL: { enableWebGL: {
@ -249,7 +249,7 @@ if (
) { ) {
defaultOptions.disablePreferences = { defaultOptions.disablePreferences = {
/** @type {boolean} */ /** @type {boolean} */
value: false, value: typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING"),
kind: OptionKind.VIEWER, kind: OptionKind.VIEWER,
}; };
defaultOptions.locale = { defaultOptions.locale = {
@ -260,8 +260,7 @@ if (
defaultOptions.sandboxBundleSrc = { defaultOptions.sandboxBundleSrc = {
/** @type {string} */ /** @type {string} */
value: value:
typeof PDFJSDev === "undefined" || typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")
PDFJSDev.test("!PRODUCTION && !ENABLE_SCRIPTING")
? "../build/dev-sandbox/pdf.sandbox.js" ? "../build/dev-sandbox/pdf.sandbox.js"
: "../build/pdf.sandbox.js", : "../build/pdf.sandbox.js",
kind: OptionKind.VIEWER, kind: OptionKind.VIEWER,

View File

@ -79,7 +79,8 @@ const DEFAULT_CACHE_SIZE = 10;
* @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`. * The default value is `false`.
* @property {Object} [mouseState] - The mouse button state. * @property {Object} [mouseState] - The mouse button state. The default value
* is `null`.
*/ */
function PDFPageViewBuffer(size) { function PDFPageViewBuffer(size) {
@ -195,7 +196,7 @@ class BaseViewer {
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 = options.enableScripting || false;
this.mouseState = options.mouseState || null; this._mouseState = options.mouseState || null;
this.defaultRenderingQueue = !options.renderingQueue; this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) { if (this.defaultRenderingQueue) {
@ -540,7 +541,6 @@ class BaseViewer {
maxCanvasPixels: this.maxCanvasPixels, maxCanvasPixels: this.maxCanvasPixels,
l10n: this.l10n, l10n: this.l10n,
enableScripting: this.enableScripting, enableScripting: this.enableScripting,
mouseState: this.mouseState,
}); });
this._pages.push(pageView); this._pages.push(pageView);
} }
@ -1301,7 +1301,7 @@ class BaseViewer {
enableScripting, enableScripting,
hasJSActionsPromise: hasJSActionsPromise:
hasJSActionsPromise || this.pdfDocument?.hasJSActions(), hasJSActionsPromise || this.pdfDocument?.hasJSActions(),
mouseState, mouseState: mouseState || this._mouseState,
}); });
} }

View File

@ -255,12 +255,12 @@ class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
} }
class FirefoxScripting { class FirefoxScripting {
static createSandbox(data) { static async createSandbox(data) {
return new Promise(resolve => { return new Promise(resolve => {
FirefoxCom.request("createSandbox", data, resolve); FirefoxCom.request("createSandbox", data, resolve);
}).then(success => { }).then(success => {
if (!success) { if (!success) {
throw new Error("Cannot start sandbox"); throw new Error("Cannot create sandbox.");
} }
}); });
} }

View File

@ -63,7 +63,6 @@ import { viewerCompatibilityParams } from "./viewer_compatibility.js";
* @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`. * The default value is `false`.
* @property {Object} [mouseState] - The mouse button state.
*/ */
const MAX_CANVAS_PIXELS = viewerCompatibilityParams.maxCanvasPixels || 16777216; const MAX_CANVAS_PIXELS = viewerCompatibilityParams.maxCanvasPixels || 16777216;
@ -110,7 +109,6 @@ class PDFPageView {
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 || false;
this.mouseState = options.mouseState || null;
this.paintTask = null; this.paintTask = null;
this.paintedViewportMap = new WeakMap(); this.paintedViewportMap = new WeakMap();
@ -554,7 +552,7 @@ class PDFPageView {
this.l10n, this.l10n,
this.enableScripting, this.enableScripting,
/* hasJSActionsPromise = */ null, /* hasJSActionsPromise = */ null,
this.mouseState /* mouseState = */ null
); );
} }
this._renderAnnotationLayer(); this._renderAnnotationLayer();