diff --git a/src/scripting_api/doc.js b/src/scripting_api/doc.js index 6797e693570df9..96cbfe41330619 100644 --- a/src/scripting_api/doc.js +++ b/src/scripting_api/doc.js @@ -828,8 +828,26 @@ class Doc extends PDFObject { return searchedField; } + const parts = cName.split("#"); + let childIndex = NaN; + if (parts.length === 2) { + childIndex = Math.floor(parseFloat(parts[1])); + cName = parts[0]; + } + for (const [name, field] of this._fields.entries()) { - if (name.includes(cName)) { + if (name.endsWith(cName)) { + if (!isNaN(childIndex)) { + const children = this._getChildren(name); + if (childIndex < 0 || childIndex >= children.length) { + childIndex = 0; + } + if (childIndex < children.length) { + this._fields.set(cName, children[childIndex]); + return children[childIndex]; + } + } + this._fields.set(cName, field); return field; } } @@ -837,6 +855,23 @@ class Doc extends PDFObject { return undefined; } + _getChildren(fieldName) { + // Children of foo.bar are foo.bar.oof, foo.bar.rab + // but not foo.bar.oof.FOO. + const len = fieldName.length; + const children = []; + const pattern = /^\.[^.]+$/; + for (const [name, field] of this._fields.entries()) { + if (name.startsWith(fieldName)) { + const finalPart = name.slice(len); + if (finalPart.match(pattern)) { + children.push(field); + } + } + } + return children; + } + getIcon() { /* Not implemented */ } diff --git a/src/scripting_api/field.js b/src/scripting_api/field.js index ff0e3b9e72dafb..6fa16adad7c13f 100644 --- a/src/scripting_api/field.js +++ b/src/scripting_api/field.js @@ -39,7 +39,7 @@ class Field extends PDFObject { this.doNotSpellCheck = data.doNotSpellCheck; this.delay = data.delay; this.display = data.display; - this.doc = data.doc; + this.doc = data.doc.wrapped; this.editable = data.editable; this.exportValues = data.exportValues; this.fileSelect = data.fileSelect; @@ -68,8 +68,13 @@ class Field extends PDFObject { // Private this._actions = createActionsMap(data.actions); + this._browseForFileToSubmit = data.browseForFileToSubmit || null; + this._buttonCaption = null; + this._buttonIcon = null; + this._children = null; this._currentValueIndices = data.currentValueIndices || 0; this._document = data.doc; + this._fieldPath = data.fieldPath; this._fillColor = data.fillColor || ["T"]; this._isChoice = Array.isArray(data.items); this._items = data.items || []; @@ -197,6 +202,48 @@ class Field extends PDFObject { this._valueAsString = val ? val.toString() : ""; } + browseForFileToSubmit() { + if (this._browseForFileToSubmit) { + // TODO: implement this function on Firefox side + // we can use nsIFilePicker but open method is async. + // Maybe it's possible to use a html input (type=file) too. + this._browseForFileToSubmit(); + } + } + + buttonGetCaption(nFace = 0) { + if (this._buttonCaption) { + return this._buttonCaption[nFace]; + } + return ""; + } + + buttonGetIcon(nFace = 0) { + if (this._buttonIcon) { + return this._buttonIcon[nFace]; + } + return null; + } + + buttonImportIcon(cPath = null, nPave = 0) { + /* Not implemented */ + } + + buttonSetCaption(cCaption, nFace = 0) { + if (!this._buttonCaption) { + this._buttonCaption = ["", "", ""]; + } + this._buttonCaption[nFace] = cCaption; + // TODO: send to the annotation layer + } + + buttonSetIcon(oIcon, nFace = 0) { + if (!this._buttonIcon) { + this._buttonIcon = [null, null, null]; + } + this._buttonIcon[nFace] = oIcon; + } + checkThisBox(nWidget, bCheckIt = true) {} clearItems() { @@ -260,6 +307,17 @@ class Field extends PDFObject { return bExportValue ? item.exportValue : item.displayValue; } + getArray() { + if (this._children === null) { + this._children = this._document.obj._getChildren(this._fieldPath); + } + return this._children; + } + + getLock() { + return undefined; + } + isBoxChecked(nWidget) { return false; } @@ -337,6 +395,20 @@ class Field extends PDFObject { this._send({ id: this._id, items: this._items }); } + setLock() {} + + signatureGetModifications() {} + + signatureGetSeedValue() {} + + signatureInfo() {} + + signatureSetSeedValue() {} + + signatureSign() {} + + signatureValidate() {} + _isButton() { return false; } diff --git a/src/scripting_api/initialization.js b/src/scripting_api/initialization.js index fb1aa86defbc64..0d7c077aa7abb7 100644 --- a/src/scripting_api/initialization.js +++ b/src/scripting_api/initialization.js @@ -73,7 +73,8 @@ function initSandbox(params) { const obj = objs[0]; obj.send = send; obj.globalEval = globalEval; - obj.doc = _document.wrapped; + obj.doc = _document; + obj.fieldPath = name; let field; if (obj.type === "radiobutton") { const otherButtons = objs.slice(1); diff --git a/test/unit/scripting_spec.js b/test/unit/scripting_spec.js index 9448cb67bf9466..7b285fb15f96cc 100644 --- a/test/unit/scripting_spec.js +++ b/test/unit/scripting_spec.js @@ -159,6 +159,64 @@ describe("Scripting", function () { done.fail(ex); } }); + + it("should get field using a path", async function (done) { + const base = value => { + return { + id: getId(), + value, + actions: {}, + type: "text", + }; + }; + const data = { + objects: { + A: [base(1)], + "A.B": [base(2)], + "A.B.C": [base(3)], + "A.B.C.D": [base(4)], + "A.B.C.D.E": [base(5)], + "A.B.C.D.E.F": [base(6)], + "A.B.C.D.G": [base(7)], + C: [base(8)], + }, + appInfo: { language: "en-US", platform: "Linux x86_64" }, + calculationOrder: [], + dispatchEventName: "_dispatchMe", + }; + sandbox.createSandbox(data); + + try { + await myeval(`this.getField("A").value`).then(value => { + expect(value).toEqual(1); + }); + await myeval(`this.getField("B.C").value`).then(value => { + expect(value).toEqual(3); + }); + // path has been cached so try again + await myeval(`this.getField("B.C").value`).then(value => { + expect(value).toEqual(3); + }); + await myeval(`this.getField("B.C.D#0").value`).then(value => { + expect(value).toEqual(5); + }); + await myeval(`this.getField("B.C.D#1").value`).then(value => { + expect(value).toEqual(7); + }); + await myeval(`this.getField("C").value`).then(value => { + expect(value).toEqual(8); + }); + + await myeval( + `this.getField("A.B.C.D").getArray().map((x) => x.value)` + ).then(value => { + expect(value).toEqual([5, 7]); + }); + done(); + } catch (ex) { + done.fail(ex); + } + }); }); describe("Util", function () {