Skip to content

Commit

Permalink
JS -- Add listener for sandbox events only if there are some actions
Browse files Browse the repository at this point in the history
* When no actions then set it to null instead of empty object
* Even if a field has no actions, it needs to listen to events from the sandbox in order to be updated if an action changes something in it.
  • Loading branch information
calixteman committed Nov 9, 2020
1 parent 55f55f5 commit de4b0d5
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 43 deletions.
15 changes: 15 additions & 0 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,21 @@ class PDFDocument {
);
}

get hasJSActions() {
return shadow(
this,
"hasJSActions",
this.fieldObjects.then(fieldObjects => {
return (
fieldObjects !== null &&
Object.values(fieldObjects).some(fieldObject =>
fieldObject.some(object => object.actions !== null)
)
);
})
);
}

get calculationOrderIds() {
const acroForm = this.catalog.acroForm;
if (!acroForm || !acroForm.has("CO")) {
Expand Down
4 changes: 4 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,10 @@ class WorkerMessageHandler {
return pdfManager.ensureDoc("fieldObjects");
});

handler.on("HasJSActions", function (data) {
return pdfManager.ensureDoc("hasJSActions");
});

handler.on("GetCalculationOrderIds", function (data) {
return pdfManager.ensureDoc("calculationOrderIds");
});
Expand Down
41 changes: 25 additions & 16 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ class AnnotationElement {
this.renderInteractiveForms = parameters.renderInteractiveForms;
this.svgFactory = parameters.svgFactory;
this.annotationStorage = parameters.annotationStorage;
this.enableScripting = parameters.enableScripting;
this.hasJSActions = parameters.hasJSActions;

if (isRenderable) {
this.container = this._createContainer(ignoreBorder);
Expand Down Expand Up @@ -507,7 +509,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
event.target.setSelectionRange(0, 0);
});

if (this.data.actions) {
if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updateFromSandbox", function (event) {
const data = event.detail;
if ("value" in data) {
Expand All @@ -517,21 +519,23 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
}
});

for (const eventType of Object.keys(this.data.actions)) {
switch (eventType) {
case "Format":
element.addEventListener("blur", function (event) {
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id,
name: "Format",
value: event.target.value,
},
})
);
});
break;
if (this.data.actions !== null) {
for (const eventType of Object.keys(this.data.actions)) {
switch (eventType) {
case "Format":
element.addEventListener("blur", function (event) {
window.dispatchEvent(
new CustomEvent("dispatchEventInSandbox", {
detail: {
id,
name: "Format",
value: event.target.value,
},
})
);
});
break;
}
}
}
}
Expand Down Expand Up @@ -1562,6 +1566,9 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
* @property {string} [imageResourcesPath] - Path for image resources, mainly
* for annotation icons. Include trailing slash.
* @property {boolean} renderInteractiveForms
* @property {boolean} [enableScripting] - Enable embedded script execution.
* @property {boolean} [hasJSActions] - Some fields have JS actions.
* The default value is `false`.
*/

class AnnotationLayer {
Expand Down Expand Up @@ -1608,6 +1615,8 @@ class AnnotationLayer {
svgFactory: new DOMSVGFactory(),
annotationStorage:
parameters.annotationStorage || new AnnotationStorage(),
enableScripting: parameters.enableScripting,
hasJSActions: parameters.hasJSActions,
});
if (element.isRenderable) {
const rendered = element.render();
Expand Down
12 changes: 12 additions & 0 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,14 @@ class PDFDocumentProxy {
return this._transport.getFieldObjects();
}

/**
* @returns {Promise<boolean>} A promise that is resolved with true
* if some fields have some JS actions.
*/
hasJSActions() {
return this._transport.hasJSActions();
}

/**
* @returns {Promise<Array<string> | null>} A promise that is resolved with an
* {Array<string>} containing IDs of annotations that have a calculation
Expand Down Expand Up @@ -2568,6 +2576,10 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise("GetFieldObjects", null);
}

hasJSActions() {
return this.messageHandler.sendWithPromise("HasJSActions", null);
}

getCalculationOrderIds() {
return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null);
}
Expand Down
14 changes: 8 additions & 6 deletions src/scripting_api/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@ class Field extends PDFObject {
// Private
this._actions = Object.create(null);
const doc = (this._document = data.doc);
for (const [eventType, actions] of Object.entries(data.actions)) {
// This code is running in a sandbox so it's safe to use Function
this._actions[eventType] = actions.map(action =>
// eslint-disable-next-line no-new-func
Function("event", `with (this) {${action}}`).bind(doc)
);
if (data.actions !== null) {
for (const [eventType, actions] of Object.entries(data.actions)) {
// This code is running in a sandbox so it's safe to use Function
this._actions[eventType] = actions.map(action =>
// eslint-disable-next-line no-new-func
Function("event", `with (this) {${action}}`).bind(doc)
);
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions test/unit/annotation_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ describe("annotation", function () {
ensureCatalog(prop, args) {
return this.ensure(this.pdfDocument.catalog, prop, args);
}

ensureDoc(prop, args) {
return this.ensure(this.pdfDocument, prop, args);
}
}

function HandlerMock() {
Expand Down
55 changes: 55 additions & 0 deletions test/unit/document_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,5 +239,60 @@ describe("document", function () {
expect(fields["parent.kid2"]).toEqual(["265R"]);
expect(fields.parent).toEqual(["358R"]);
});

it("should check if fields have any actions", async function () {
const acroForm = new Dict();

let pdfDocument = getDocument(acroForm);
let hasJSActions = await pdfDocument.hasJSActions;
expect(hasJSActions).toEqual(false);

acroForm.set("Fields", []);
pdfDocument = getDocument(acroForm);
hasJSActions = await pdfDocument.hasJSActions;
expect(hasJSActions).toEqual(false);

const kid1Ref = Ref.get(314, 0);
const kid11Ref = Ref.get(159, 0);
const kid2Ref = Ref.get(265, 0);
const parentRef = Ref.get(358, 0);

const allFields = Object.create(null);
for (const name of ["parent", "kid1", "kid2", "kid11"]) {
const buttonWidgetDict = new Dict();
buttonWidgetDict.set("Type", Name.get("Annot"));
buttonWidgetDict.set("Subtype", Name.get("Widget"));
buttonWidgetDict.set("FT", Name.get("Btn"));
buttonWidgetDict.set("T", name);
allFields[name] = buttonWidgetDict;
}

allFields.kid1.set("Kids", [kid11Ref]);
allFields.parent.set("Kids", [kid1Ref, kid2Ref]);

const xref = new XRefMock([
{ ref: parentRef, data: allFields.parent },
{ ref: kid1Ref, data: allFields.kid1 },
{ ref: kid11Ref, data: allFields.kid11 },
{ ref: kid2Ref, data: allFields.kid2 },
]);

acroForm.set("Fields", [parentRef]);
pdfDocument = getDocument(acroForm, xref);
hasJSActions = await pdfDocument.hasJSActions;
expect(hasJSActions).toEqual(false);

const JS = Name.get("JavaScript");
const additionalActionsDict = new Dict();
const eDict = new Dict();
eDict.set("JS", "hello()");
eDict.set("S", JS);
additionalActionsDict.set("E", eDict);
allFields.kid2.set("AA", additionalActionsDict);

pdfDocument = getDocument(acroForm, xref);
hasJSActions = await pdfDocument.hasJSActions;
expect(hasJSActions).toEqual(true);
});
});
});
15 changes: 14 additions & 1 deletion web/annotation_layer_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { SimpleLinkService } from "./pdf_link_service.js";
* @property {IPDFLinkService} linkService
* @property {DownloadManager} downloadManager
* @property {IL10n} l10n - Localization service.
* @property {boolean} enableScripting
*/

class AnnotationLayerBuilder {
Expand All @@ -43,6 +44,8 @@ class AnnotationLayerBuilder {
imageResourcesPath = "",
renderInteractiveForms = true,
l10n = NullL10n,
enableScripting = false,
hasJSActions = false,
}) {
this.pageDiv = pageDiv;
this.pdfPage = pdfPage;
Expand All @@ -52,6 +55,8 @@ class AnnotationLayerBuilder {
this.renderInteractiveForms = renderInteractiveForms;
this.l10n = l10n;
this.annotationStorage = annotationStorage;
this.enableScripting = enableScripting;
this.hasJSActions = hasJSActions;

this.div = null;
this._cancelled = false;
Expand Down Expand Up @@ -82,6 +87,8 @@ class AnnotationLayerBuilder {
linkService: this.linkService,
downloadManager: this.downloadManager,
annotationStorage: this.annotationStorage,
enableScripting: this.enableScripting,
hasJSActions: this.hasJSActions,
};

if (this.div) {
Expand Down Expand Up @@ -126,6 +133,8 @@ class DefaultAnnotationLayerFactory {
* for annotation icons. Include trailing slash.
* @param {boolean} renderInteractiveForms
* @param {IL10n} l10n
* @param {boolean} enableScripting
* @param {boolean} hasJSActions
* @returns {AnnotationLayerBuilder}
*/
createAnnotationLayerBuilder(
Expand All @@ -134,7 +143,9 @@ class DefaultAnnotationLayerFactory {
annotationStorage = null,
imageResourcesPath = "",
renderInteractiveForms = true,
l10n = NullL10n
l10n = NullL10n,
enableScripting = false,
hasJSActions = false
) {
return new AnnotationLayerBuilder({
pageDiv,
Expand All @@ -144,6 +155,8 @@ class DefaultAnnotationLayerFactory {
linkService: new SimpleLinkService(),
l10n,
annotationStorage,
enableScripting,
hasJSActions,
});
}
}
Expand Down
1 change: 1 addition & 0 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ const PDFViewerApplication = {
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
enableScripting: AppOptions.get("enableScripting"),
});
pdfRenderingQueue.setViewer(this.pdfViewer);
pdfLinkService.setViewer(this.pdfViewer);
Expand Down
14 changes: 13 additions & 1 deletion web/base_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ const DEFAULT_CACHE_SIZE = 10;
* total pixels, i.e. width * height. Use -1 for no limit. The default value
* is 4096 * 4096 (16 mega-pixels).
* @property {IL10n} l10n - Localization service.
* @property {boolean} [enableScripting] - Enable embedded script execution.
* The default value is `false`.
*/

function PDFPageViewBuffer(size) {
Expand Down Expand Up @@ -187,6 +189,7 @@ class BaseViewer {
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
this.maxCanvasPixels = options.maxCanvasPixels;
this.l10n = options.l10n || NullL10n;
this.enableScripting = options.enableScripting || false;

this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) {
Expand Down Expand Up @@ -465,6 +468,7 @@ class BaseViewer {

const annotationStorage = pdfDocument.annotationStorage;
const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
const hasJSActionsPromise = pdfDocument.hasJSActions();

this._pagesCapability.promise.then(() => {
this.eventBus.dispatch("pagesloaded", {
Expand Down Expand Up @@ -527,6 +531,8 @@ class BaseViewer {
useOnlyCssZoom: this.useOnlyCssZoom,
maxCanvasPixels: this.maxCanvasPixels,
l10n: this.l10n,
enableScripting: this.enableScripting,
hasJSActionsPromise,
});
this._pages.push(pageView);
}
Expand Down Expand Up @@ -1208,6 +1214,8 @@ class BaseViewer {
* for annotation icons. Include trailing slash.
* @param {boolean} renderInteractiveForms
* @param {IL10n} l10n
* @param {boolean} enableScripting
* @param {boolean} hasJSActions
* @returns {AnnotationLayerBuilder}
*/
createAnnotationLayerBuilder(
Expand All @@ -1216,7 +1224,9 @@ class BaseViewer {
annotationStorage = null,
imageResourcesPath = "",
renderInteractiveForms = false,
l10n = NullL10n
l10n = NullL10n,
enableScripting = false,
hasJSActions = false
) {
return new AnnotationLayerBuilder({
pageDiv,
Expand All @@ -1227,6 +1237,8 @@ class BaseViewer {
linkService: this.linkService,
downloadManager: this.downloadManager,
l10n,
enableScripting,
hasJSActions,
});
}

Expand Down
6 changes: 5 additions & 1 deletion web/interfaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ class IPDFAnnotationLayerFactory {
* for annotation icons. Include trailing slash.
* @param {boolean} renderInteractiveForms
* @param {IL10n} l10n
* @param {boolean} enableScripting
* @param {boolean} hasJSActions
* @returns {AnnotationLayerBuilder}
*/
createAnnotationLayerBuilder(
Expand All @@ -189,7 +191,9 @@ class IPDFAnnotationLayerFactory {
annotationStorage = null,
imageResourcesPath = "",
renderInteractiveForms = true,
l10n = undefined
l10n = undefined,
enableScripting = false,
hasJSActions = false
) {}
}

Expand Down
Loading

0 comments on commit de4b0d5

Please sign in to comment.