Skip to content

Commit

Permalink
Add the possibility to collect Javascript actions
Browse files Browse the repository at this point in the history
  • Loading branch information
calixteman committed Sep 30, 2020
1 parent d49b2f6 commit b247073
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 0 deletions.
132 changes: 132 additions & 0 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
AnnotationType,
assert,
escapeString,
EVENT_MAP,
getModificationDate,
isString,
OPS,
Expand Down Expand Up @@ -570,6 +571,10 @@ class Annotation {
return null;
}

async getAnnotationObject() {
return null;
}

/**
* Reset the annotation.
*
Expand Down Expand Up @@ -904,6 +909,7 @@ class WidgetAnnotation extends Annotation {

data.annotationType = AnnotationType.WIDGET;
data.fieldName = this._constructFieldName(dict);
data.actions = this._collectActions(params.xref, dict);

const fieldValue = getInheritableProperty({
dict,
Expand Down Expand Up @@ -938,13 +944,15 @@ class WidgetAnnotation extends Annotation {
}

data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
data.hidden = this.hasFieldFlag(AnnotationFieldFlag.HIDDEN);

// Hide signatures because we cannot validate them, and unset the fieldValue
// since it's (most likely) a `Dict` which is non-serializable and will thus
// cause errors when sending annotations to the main-thread (issue 10347).
if (data.fieldType === "Sig") {
data.fieldValue = null;
this.setFlags(AnnotationFlag.HIDDEN);
data.hidden = true;
}
}

Expand Down Expand Up @@ -1367,6 +1375,71 @@ class WidgetAnnotation extends Annotation {
}
return localResources || Dict.empty;
}

_collectJS(entry, xref, list, parents) {
if (!entry) {
return;
}

let parent = null;
if (isRef(entry)) {
if (parents.has(entry)) {
// If we've already met entry then we've a cycle
return;
}
parent = entry;
parents.add(parent);
entry = xref.fetch(entry, true);
}
if (Array.isArray(entry)) {
for (const e of entry) {
this._collectJS(e, xref, list, parents);
}
} else if (entry instanceof Dict) {
if (entry.get("S") === Name.get("JavaScript") && entry.has("JS")) {
const code = stringToPDFString(entry.get("JS"));
if (code) {
list.push(code);
}
}
this._collectJS(entry.getRaw("Next"), xref, list, parents);
}

if (parent) {
parents.delete(parent);
}
}

_collectActions(xref, dict) {
const actions = Object.create({});
if (dict.has("AA")) {
const additionalAction = dict.get("AA");
for (const key of additionalAction.getKeys()) {
if (key in EVENT_MAP) {
const actionDict = additionalAction.getRaw(key);
const parents = new Set();
const list = [];
this._collectJS(actionDict, xref, list, parents);
if (list.length > 0) {
actions[EVENT_MAP[key]] = list;
}
}
}
}
return actions;
}

async getAnnotationObject() {
if (this.data.fieldType === "Sig") {
return {
id: this.data.id,
value: null,
valueAsString: "null",
type: "signature",
};
}
return null;
}
}

class TextWidgetAnnotation extends WidgetAnnotation {
Expand Down Expand Up @@ -1517,6 +1590,24 @@ class TextWidgetAnnotation extends WidgetAnnotation {

return chunks;
}

async getAnnotationObject() {
return {
id: this.data.id,
value: this.data.fieldValue,
valueAsString: this.data.fieldValue.toString(),
multiline: this.data.multiline,
password: this.hasFieldFlag(AnnotationFieldFlag.PASSWORD),
charLimit: this.data.maxLen,
comb: this.data.comb,
editable: !this.data.readOnly,
hidden: this.data.hidden,
name: this.data.fieldName,
rect: this.data.rect,
actions: this.data.actions,
type: "text",
};
}
}

class ButtonWidgetAnnotation extends WidgetAnnotation {
Expand Down Expand Up @@ -1791,6 +1882,29 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
docBaseUrl: params.pdfManager.docBaseUrl,
});
}

async getAnnotationObject() {
let type = "button";
let value = null;
if (this.data.checkBox) {
type = "checkbox";
value = this.data.fieldValue && this.data.fieldValue !== "Off";
} else if (this.data.radioButton) {
type = "radiobutton";
value = this.data.fieldValue === this.data.buttonValue;
}
return {
id: this.data.id,
value,
valueAsString: value.toString(),
editable: !this.data.readOnly,
name: this.data.fieldName,
rect: this.data.rect,
hidden: this.data.hidden,
actions: this.data.actions,
type,
};
}
}

class ChoiceWidgetAnnotation extends WidgetAnnotation {
Expand Down Expand Up @@ -1841,6 +1955,24 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation {
this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
this._hasText = true;
}

async getAnnotationObject() {
const type = this.data.combo ? "combobox" : "listbox";
const value =
this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : null;
return {
id: this.data.id,
value,
valueAsString: value.toString(),
editable: !this.data.readOnly,
name: this.data.fieldName,
rect: this.data.rect,
multipleSelection: this.data.multiSelect,
hidden: this.data.hidden,
actions: this.data.actions,
type,
};
}
}

class TextAnnotation extends MarkupAnnotation {
Expand Down
21 changes: 21 additions & 0 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,27 @@ class Page {
return stream;
}

getAnnotationObjects() {
// Fetch the page's annotations and get annotation data
// to be used in JS sandbox
return this._parsedAnnotations.then(function (annotations) {
const promises = [];
for (const annotation of annotations) {
if (!isAnnotationRenderable(annotation, "print")) {
continue;
}
promises.push(
annotation.getAnnotationObject().catch(function (reason) {
warn(`getAnnotationObjects - ignoring annotation data: ${reason}.`);
return null;
})
);
}

return Promise.all(promises);
});
}

save(handler, task, annotationStorage) {
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
Expand Down
12 changes: 12 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,18 @@ class WorkerMessageHandler {
});
});

handler.on("GetAnnotationObjects", function ({ numPages }) {
const promises = [];
for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
promises.push(
pdfManager.getPage(pageIndex).then(function (page) {
return page.getAnnotationObjects();
})
);
}
return Promise.all(promises);
});

handler.on("SaveDocument", function ({
numPages,
annotationStorage,
Expand Down
20 changes: 20 additions & 0 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,14 @@ class PDFDocumentProxy {
saveDocument(annotationStorage) {
return this._transport.saveDocument(annotationStorage);
}

/**
* @returns {Promise<Array<Object>>} A promise that is resolved with an
* {Array<Object>} containing annotation data.
*/
getAnnotationObjects() {
return this._transport.getAnnotationObjects();
}
}

/**
Expand Down Expand Up @@ -2550,6 +2558,18 @@ class WorkerTransport {
});
}

getAnnotationObjects() {
return this.messageHandler
.sendWithPromise("GetAnnotationObjects", {
numPages: this._numPages,
})
.then(data => {
return data
.reduce((acc, x) => acc.concat(x), [])
.filter(x => x !== null);
});
}

getDestinations() {
return this.messageHandler.sendWithPromise("GetDestinations", null);
}
Expand Down
23 changes: 23 additions & 0 deletions src/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,28 @@ const PasswordResponses = {
INCORRECT_PASSWORD: 2,
};

const EVENT_MAP = {
E: "MouseEnter",
X: "MouseExit",
D: "MouseDown",
U: "MouseUp",
Fo: "Focus",
Bl: "Blur",
PO: "PageOpen",
PC: "PageClosed",
PV: "PageVisible",
PI: "PageInvisible",
K: "Keystroke",
F: "Format",
V: "Validate",
C: "Calculate",
WC: "WillClose",
WS: "WillSave",
DS: "DidSave",
WP: "WillPrint",
DP: "DidPrint",
};

let verbosity = VerbosityLevel.WARNINGS;

function setVerbosityLevel(level) {
Expand Down Expand Up @@ -967,6 +989,7 @@ function encodeToXmlString(str) {

export {
BaseException,
EVENT_MAP,
FONT_IDENTITY_MATRIX,
IDENTITY_MATRIX,
OPS,
Expand Down
Loading

0 comments on commit b247073

Please sign in to comment.