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 3, 2020
1 parent 3e52098 commit 10d1965
Show file tree
Hide file tree
Showing 14 changed files with 172 additions and 31 deletions.
22 changes: 19 additions & 3 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,39 @@ class AnnotationFactory {
* @param {Object} ref
* @param {PDFManager} pdfManager
* @param {Object} idFactory
* @param {boolean} checkJSActions
* @returns {Promise} A promise that is resolved with an {Annotation}
* instance.
*/
static create(xref, ref, pdfManager, idFactory) {
return pdfManager.ensureCatalog("acroForm").then(acroForm => {
static create(xref, ref, pdfManager, idFactory, checkJSActions = true) {
return Promise.all([
pdfManager.ensureCatalog("acroForm"),
checkJSActions
? pdfManager.ensureDoc("hasJSActions")
: Promise.resolve(false),
]).then(([acroForm, hasJSActions]) => {
return pdfManager.ensure(this, "_create", [
xref,
ref,
pdfManager,
idFactory,
acroForm,
hasJSActions,
]);
});
}

/**
* @private
*/
static _create(xref, ref, pdfManager, idFactory, acroForm) {
static _create(
xref,
ref,
pdfManager,
idFactory,
acroForm,
hasJSActions = false
) {
const dict = xref.fetchIfRef(ref);
if (!isDict(dict)) {
return undefined;
Expand All @@ -98,6 +112,7 @@ class AnnotationFactory {
id,
pdfManager,
acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
hasJSActions,
};

switch (subtype) {
Expand Down Expand Up @@ -282,6 +297,7 @@ class Annotation {
modificationDate: this.modificationDate,
rect: this.rectangle,
subtype: params.subtype,
documentHasJSActions: params.hasJSActions,
};

this._fallbackFontDict = null;
Expand Down
24 changes: 23 additions & 1 deletion src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -962,12 +962,14 @@ class PDFDocument {
if (!(name in promises)) {
promises.set(name, []);
}

promises.get(name).push(
AnnotationFactory.create(
this.xref,
fieldRef,
this.pdfManager,
this._localIdFactory
this._localIdFactory,
/* checkJSActions */ false
)
.then(annotation => annotation && annotation.getFieldObject())
.catch(function (reason) {
Expand Down Expand Up @@ -1014,6 +1016,26 @@ class PDFDocument {
);
}

get hasJSActions() {
return shadow(
this,
"hasJSActions",
this.fieldObjects.then(objects => {
if (objects === null) {
return false;
}
for (const object of Object.values(objects)) {
for (const obj of object) {
if (obj.actions !== null) {
return true;
}
}
}
return false;
})
);
}

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 @@ -524,6 +524,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
36 changes: 20 additions & 16 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ class AnnotationElement {
this.renderInteractiveForms = parameters.renderInteractiveForms;
this.svgFactory = parameters.svgFactory;
this.annotationStorage = parameters.annotationStorage;
this.enableScripting = parameters.enableScripting;

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

if (this.data.actions) {
if (this.enableScripting && this.data.documentHasJSActions) {
element.addEventListener("updateFromSandbox", function (event) {
const data = event.detail;
if ("value" in data) {
Expand All @@ -488,21 +489,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 @@ -1565,6 +1568,7 @@ class AnnotationLayer {
svgFactory: new DOMSVGFactory(),
annotationStorage:
parameters.annotationStorage || new AnnotationStorage(),
enableScripting: parameters.enableScripting,
});
if (element.isRenderable) {
parameters.div.appendChild(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
* there are some actions in JavaScript.
*/
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 @@ -2581,6 +2589,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);
});
});
});
9 changes: 8 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,7 @@ class AnnotationLayerBuilder {
imageResourcesPath = "",
renderInteractiveForms = true,
l10n = NullL10n,
enableScripting = false,
}) {
this.pageDiv = pageDiv;
this.pdfPage = pdfPage;
Expand All @@ -52,6 +54,7 @@ class AnnotationLayerBuilder {
this.renderInteractiveForms = renderInteractiveForms;
this.l10n = l10n;
this.annotationStorage = annotationStorage;
this.enableScripting = enableScripting;

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

if (this.div) {
Expand Down Expand Up @@ -126,6 +130,7 @@ class DefaultAnnotationLayerFactory {
* for annotation icons. Include trailing slash.
* @param {boolean} renderInteractiveForms
* @param {IL10n} l10n
* @param {boolean} enableScripting
* @returns {AnnotationLayerBuilder}
*/
createAnnotationLayerBuilder(
Expand All @@ -134,7 +139,8 @@ class DefaultAnnotationLayerFactory {
annotationStorage = null,
imageResourcesPath = "",
renderInteractiveForms = true,
l10n = NullL10n
l10n = NullL10n,
enableScripting = false
) {
return new AnnotationLayerBuilder({
pageDiv,
Expand All @@ -144,6 +150,7 @@ class DefaultAnnotationLayerFactory {
linkService: new SimpleLinkService(),
l10n,
annotationStorage,
enableScripting,
});
}
}
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
Loading

0 comments on commit 10d1965

Please sign in to comment.