Merge pull request #12546 from calixteman/hasjs

[api-minor] JS -- Add listener for sandbox events only if there are some actions
This commit is contained in:
Tim van der Meij 2020-11-10 00:26:54 +01:00 committed by GitHub
commit 1c17e078ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 170 additions and 28 deletions

View File

@ -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() { get calculationOrderIds() {
const acroForm = this.catalog.acroForm; const acroForm = this.catalog.acroForm;
if (!acroForm || !acroForm.has("CO")) { if (!acroForm || !acroForm.has("CO")) {

View File

@ -525,6 +525,10 @@ class WorkerMessageHandler {
return pdfManager.ensureDoc("fieldObjects"); return pdfManager.ensureDoc("fieldObjects");
}); });
handler.on("HasJSActions", function (data) {
return pdfManager.ensureDoc("hasJSActions");
});
handler.on("GetCalculationOrderIds", function (data) { handler.on("GetCalculationOrderIds", function (data) {
return pdfManager.ensureDoc("calculationOrderIds"); return pdfManager.ensureDoc("calculationOrderIds");
}); });

View File

@ -43,6 +43,8 @@ import { AnnotationStorage } from "./annotation_storage.js";
* for annotation icons. Include trailing slash. * for annotation icons. Include trailing slash.
* @property {boolean} renderInteractiveForms * @property {boolean} renderInteractiveForms
* @property {Object} svgFactory * @property {Object} svgFactory
* @property {boolean} [enableScripting]
* @property {boolean} [hasJSActions]
*/ */
class AnnotationElementFactory { class AnnotationElementFactory {
@ -142,6 +144,8 @@ class AnnotationElement {
this.renderInteractiveForms = parameters.renderInteractiveForms; this.renderInteractiveForms = parameters.renderInteractiveForms;
this.svgFactory = parameters.svgFactory; this.svgFactory = parameters.svgFactory;
this.annotationStorage = parameters.annotationStorage; this.annotationStorage = parameters.annotationStorage;
this.enableScripting = parameters.enableScripting;
this.hasJSActions = parameters.hasJSActions;
if (isRenderable) { if (isRenderable) {
this.container = this._createContainer(ignoreBorder); this.container = this._createContainer(ignoreBorder);
@ -507,7 +511,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
event.target.setSelectionRange(0, 0); event.target.setSelectionRange(0, 0);
}); });
if (this.data.actions) { if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updateFromSandbox", function (event) { element.addEventListener("updateFromSandbox", function (event) {
const data = event.detail; const data = event.detail;
if ("value" in data) { if ("value" in data) {
@ -517,6 +521,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
} }
}); });
if (this.data.actions !== null) {
for (const eventType of Object.keys(this.data.actions)) { for (const eventType of Object.keys(this.data.actions)) {
switch (eventType) { switch (eventType) {
case "Format": case "Format":
@ -535,6 +540,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
} }
} }
} }
}
element.disabled = this.data.readOnly; element.disabled = this.data.readOnly;
element.name = this.data.fieldName; element.name = this.data.fieldName;
@ -1562,6 +1568,9 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
* @property {string} [imageResourcesPath] - Path for image resources, mainly * @property {string} [imageResourcesPath] - Path for image resources, mainly
* for annotation icons. Include trailing slash. * for annotation icons. Include trailing slash.
* @property {boolean} renderInteractiveForms * @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 { class AnnotationLayer {
@ -1608,6 +1617,8 @@ class AnnotationLayer {
svgFactory: new DOMSVGFactory(), svgFactory: new DOMSVGFactory(),
annotationStorage: annotationStorage:
parameters.annotationStorage || new AnnotationStorage(), parameters.annotationStorage || new AnnotationStorage(),
enableScripting: parameters.enableScripting,
hasJSActions: parameters.hasJSActions,
}); });
if (element.isRenderable) { if (element.isRenderable) {
const rendered = element.render(); const rendered = element.render();

View File

@ -903,6 +903,14 @@ class PDFDocumentProxy {
return this._transport.getFieldObjects(); return this._transport.getFieldObjects();
} }
/**
* @returns {Promise<boolean>} A promise that is resolved with `true`
* if some /AcroForm fields have JavaScript actions.
*/
hasJSActions() {
return this._transport.hasJSActions();
}
/** /**
* @returns {Promise<Array<string> | null>} A promise that is resolved with an * @returns {Promise<Array<string> | null>} A promise that is resolved with an
* {Array<string>} containing IDs of annotations that have a calculation * {Array<string>} containing IDs of annotations that have a calculation
@ -2568,6 +2576,10 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise("GetFieldObjects", null); return this.messageHandler.sendWithPromise("GetFieldObjects", null);
} }
hasJSActions() {
return this.messageHandler.sendWithPromise("HasJSActions", null);
}
getCalculationOrderIds() { getCalculationOrderIds() {
return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null); return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null);
} }

View File

@ -73,6 +73,7 @@ class Field extends PDFObject {
// Private // Private
this._actions = Object.create(null); this._actions = Object.create(null);
const doc = (this._document = data.doc); const doc = (this._document = data.doc);
if (data.actions !== null) {
for (const [eventType, actions] of Object.entries(data.actions)) { for (const [eventType, actions] of Object.entries(data.actions)) {
// This code is running in a sandbox so it's safe to use Function // This code is running in a sandbox so it's safe to use Function
this._actions[eventType] = actions.map(action => this._actions[eventType] = actions.map(action =>
@ -81,6 +82,7 @@ class Field extends PDFObject {
); );
} }
} }
}
setAction(cTrigger, cScript) { setAction(cTrigger, cScript) {
if (typeof cTrigger !== "string" || typeof cScript !== "string") { if (typeof cTrigger !== "string" || typeof cScript !== "string") {

View File

@ -61,6 +61,10 @@ describe("annotation", function () {
ensureCatalog(prop, args) { ensureCatalog(prop, args) {
return this.ensure(this.pdfDocument.catalog, prop, args); return this.ensure(this.pdfDocument.catalog, prop, args);
} }
ensureDoc(prop, args) {
return this.ensure(this.pdfDocument, prop, args);
}
} }
function HandlerMock() { function HandlerMock() {

View File

@ -239,5 +239,60 @@ describe("document", function () {
expect(fields["parent.kid2"]).toEqual(["265R"]); expect(fields["parent.kid2"]).toEqual(["265R"]);
expect(fields.parent).toEqual(["358R"]); 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);
});
}); });
}); });

View File

@ -28,6 +28,8 @@ import { SimpleLinkService } from "./pdf_link_service.js";
* @property {IPDFLinkService} linkService * @property {IPDFLinkService} linkService
* @property {DownloadManager} downloadManager * @property {DownloadManager} downloadManager
* @property {IL10n} l10n - Localization service. * @property {IL10n} l10n - Localization service.
* @property {boolean} [enableScripting]
* @property {Promise<boolean>} [hasJSActionsPromise]
*/ */
class AnnotationLayerBuilder { class AnnotationLayerBuilder {
@ -43,6 +45,8 @@ class AnnotationLayerBuilder {
imageResourcesPath = "", imageResourcesPath = "",
renderInteractiveForms = true, renderInteractiveForms = true,
l10n = NullL10n, l10n = NullL10n,
enableScripting = false,
hasJSActionsPromise = null,
}) { }) {
this.pageDiv = pageDiv; this.pageDiv = pageDiv;
this.pdfPage = pdfPage; this.pdfPage = pdfPage;
@ -52,6 +56,8 @@ class AnnotationLayerBuilder {
this.renderInteractiveForms = renderInteractiveForms; this.renderInteractiveForms = renderInteractiveForms;
this.l10n = l10n; this.l10n = l10n;
this.annotationStorage = annotationStorage; this.annotationStorage = annotationStorage;
this.enableScripting = enableScripting;
this._hasJSActionsPromise = hasJSActionsPromise;
this.div = null; this.div = null;
this._cancelled = false; this._cancelled = false;
@ -64,7 +70,10 @@ class AnnotationLayerBuilder {
* annotations is complete. * annotations is complete.
*/ */
render(viewport, intent = "display") { render(viewport, intent = "display") {
return this.pdfPage.getAnnotations({ intent }).then(annotations => { return Promise.all([
this.pdfPage.getAnnotations({ intent }),
this._hasJSActionsPromise,
]).then(([annotations, hasJSActions = false]) => {
if (this._cancelled) { if (this._cancelled) {
return; return;
} }
@ -82,6 +91,8 @@ class AnnotationLayerBuilder {
linkService: this.linkService, linkService: this.linkService,
downloadManager: this.downloadManager, downloadManager: this.downloadManager,
annotationStorage: this.annotationStorage, annotationStorage: this.annotationStorage,
enableScripting: this.enableScripting,
hasJSActions,
}; };
if (this.div) { if (this.div) {
@ -126,6 +137,8 @@ class DefaultAnnotationLayerFactory {
* for annotation icons. Include trailing slash. * for annotation icons. Include trailing slash.
* @param {boolean} renderInteractiveForms * @param {boolean} renderInteractiveForms
* @param {IL10n} l10n * @param {IL10n} l10n
* @param {boolean} enableScripting
* @param {Promise<boolean>} hasJSActionsPromise
* @returns {AnnotationLayerBuilder} * @returns {AnnotationLayerBuilder}
*/ */
createAnnotationLayerBuilder( createAnnotationLayerBuilder(
@ -134,7 +147,9 @@ class DefaultAnnotationLayerFactory {
annotationStorage = null, annotationStorage = null,
imageResourcesPath = "", imageResourcesPath = "",
renderInteractiveForms = true, renderInteractiveForms = true,
l10n = NullL10n l10n = NullL10n,
enableScripting = false,
hasJSActionsPromise = null
) { ) {
return new AnnotationLayerBuilder({ return new AnnotationLayerBuilder({
pageDiv, pageDiv,
@ -144,6 +159,8 @@ class DefaultAnnotationLayerFactory {
linkService: new SimpleLinkService(), linkService: new SimpleLinkService(),
l10n, l10n,
annotationStorage, annotationStorage,
enableScripting,
hasJSActionsPromise,
}); });
} }
} }

View File

@ -449,6 +449,7 @@ const PDFViewerApplication = {
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"), useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
maxCanvasPixels: AppOptions.get("maxCanvasPixels"), maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
enableScripting: AppOptions.get("enableScripting"),
}); });
pdfRenderingQueue.setViewer(this.pdfViewer); pdfRenderingQueue.setViewer(this.pdfViewer);
pdfLinkService.setViewer(this.pdfViewer); pdfLinkService.setViewer(this.pdfViewer);

View File

@ -77,6 +77,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.
* The default value is `false`.
*/ */
function PDFPageViewBuffer(size) { function PDFPageViewBuffer(size) {
@ -187,6 +189,7 @@ 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.defaultRenderingQueue = !options.renderingQueue; this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) { if (this.defaultRenderingQueue) {
@ -527,6 +530,7 @@ class BaseViewer {
useOnlyCssZoom: this.useOnlyCssZoom, useOnlyCssZoom: this.useOnlyCssZoom,
maxCanvasPixels: this.maxCanvasPixels, maxCanvasPixels: this.maxCanvasPixels,
l10n: this.l10n, l10n: this.l10n,
enableScripting: this.enableScripting,
}); });
this._pages.push(pageView); this._pages.push(pageView);
} }
@ -1208,6 +1212,7 @@ class BaseViewer {
* for annotation icons. Include trailing slash. * for annotation icons. Include trailing slash.
* @param {boolean} renderInteractiveForms * @param {boolean} renderInteractiveForms
* @param {IL10n} l10n * @param {IL10n} l10n
* @param {boolean} [enableScripting]
* @returns {AnnotationLayerBuilder} * @returns {AnnotationLayerBuilder}
*/ */
createAnnotationLayerBuilder( createAnnotationLayerBuilder(
@ -1216,7 +1221,8 @@ class BaseViewer {
annotationStorage = null, annotationStorage = null,
imageResourcesPath = "", imageResourcesPath = "",
renderInteractiveForms = false, renderInteractiveForms = false,
l10n = NullL10n l10n = NullL10n,
enableScripting = false
) { ) {
return new AnnotationLayerBuilder({ return new AnnotationLayerBuilder({
pageDiv, pageDiv,
@ -1227,6 +1233,10 @@ class BaseViewer {
linkService: this.linkService, linkService: this.linkService,
downloadManager: this.downloadManager, downloadManager: this.downloadManager,
l10n, l10n,
enableScripting,
hasJSActionsPromise: enableScripting
? this.pdfDocument.hasJSActions()
: Promise.resolve(false),
}); });
} }

View File

@ -181,6 +181,8 @@ class IPDFAnnotationLayerFactory {
* for annotation icons. Include trailing slash. * for annotation icons. Include trailing slash.
* @param {boolean} renderInteractiveForms * @param {boolean} renderInteractiveForms
* @param {IL10n} l10n * @param {IL10n} l10n
* @param {boolean} [enableScripting]
* @param {Promise<boolean>} [hasJSActionsPromise]
* @returns {AnnotationLayerBuilder} * @returns {AnnotationLayerBuilder}
*/ */
createAnnotationLayerBuilder( createAnnotationLayerBuilder(
@ -189,7 +191,9 @@ class IPDFAnnotationLayerFactory {
annotationStorage = null, annotationStorage = null,
imageResourcesPath = "", imageResourcesPath = "",
renderInteractiveForms = true, renderInteractiveForms = true,
l10n = undefined l10n = undefined,
enableScripting = false,
hasJSActionsPromise = null
) {} ) {}
} }

View File

@ -63,6 +63,8 @@ import { viewerCompatibilityParams } from "./viewer_compatibility.js";
* 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.
* The default value is `false`.
*/ */
const MAX_CANVAS_PIXELS = viewerCompatibilityParams.maxCanvasPixels || 16777216; const MAX_CANVAS_PIXELS = viewerCompatibilityParams.maxCanvasPixels || 16777216;
@ -109,6 +111,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.paintTask = null; this.paintTask = null;
this.paintedViewportMap = new WeakMap(); this.paintedViewportMap = new WeakMap();
@ -549,7 +552,8 @@ class PDFPageView {
this._annotationStorage, this._annotationStorage,
this.imageResourcesPath, this.imageResourcesPath,
this.renderInteractiveForms, this.renderInteractiveForms,
this.l10n this.l10n,
this.enableScripting
); );
} }
this._renderAnnotationLayer(); this._renderAnnotationLayer();

View File

@ -1023,7 +1023,10 @@ function getActiveOrFocusedElement() {
*/ */
function generateRandomStringForSandbox(objects) { function generateRandomStringForSandbox(objects) {
const allObjects = Object.values(objects).flat(2); const allObjects = Object.values(objects).flat(2);
const actions = allObjects.map(obj => Object.values(obj.actions)).flat(2); const actions = allObjects
.filter(obj => !!obj.actions)
.map(obj => Object.values(obj.actions))
.flat(2);
while (true) { while (true) {
const name = new Uint8Array(64); const name = new Uint8Array(64);