Skip to content

Commit

Permalink
Merge pull request #12432 from calixteman/scripting_api
Browse files Browse the repository at this point in the history
JS - Add the basic architecture to be able to execute embedded js
  • Loading branch information
brendandahl authored Oct 23, 2020
2 parents 8cf2749 + e76a968 commit 1eaf9c9
Show file tree
Hide file tree
Showing 18 changed files with 823 additions and 0 deletions.
4 changes: 4 additions & 0 deletions extensions/chromium/preferences_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@
"type": "boolean",
"default": true
},
"enableScripting": {
"type": "boolean",
"default": false
},
"enablePermissions": {
"type": "boolean",
"default": false
Expand Down
20 changes: 20 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,23 @@ function createMainBundle(defines) {
.pipe(replaceJSRootName(mainAMDName, "pdfjsLib"));
}

function createScriptingBundle(defines) {
var mainAMDName = "pdfjs-dist/build/pdf.scripting";
var mainOutputName = "pdf.scripting.js";

var mainFileConfig = createWebpackConfig(defines, {
filename: mainOutputName,
library: mainAMDName,
libraryTarget: "umd",
umdNamedDefine: true,
});
return gulp
.src("./src/scripting_api/initialization.js")
.pipe(webpack2Stream(mainFileConfig))
.pipe(replaceWebpackRequire())
.pipe(replaceJSRootName(mainAMDName, "pdfjsScripting"));
}

function createWorkerBundle(defines) {
var workerAMDName = "pdfjs-dist/build/pdf.worker";
var workerOutputName = "pdf.worker.js";
Expand Down Expand Up @@ -1036,6 +1053,9 @@ gulp.task(
createMainBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createScriptingBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createWorkerBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
Expand Down
31 changes: 31 additions & 0 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
element.setAttribute("value", textContent);
}

element.setAttribute("id", id);

element.addEventListener("input", function (event) {
storage.setValue(id, event.target.value);
});
Expand All @@ -476,6 +478,35 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
event.target.setSelectionRange(0, 0);
});

if (this.data.actions) {
element.addEventListener("updateFromSandbox", function (event) {
const data = event.detail;
if ("value" in data) {
event.target.value = event.detail.value;
} else if ("focus" in data) {
event.target.focus({ preventScroll: false });
}
});

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;
}
}
}

element.disabled = this.data.readOnly;
element.name = this.data.fieldName;

Expand Down
46 changes: 46 additions & 0 deletions src/scripting_api/aform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

class AForm {
constructor(document, app, util) {
this._document = document;
this._app = app;
this._util = util;
}

AFNumber_Format(
nDec,
sepStyle,
negStyle,
currStyle,
strCurrency,
bCurrencyPrepend
) {
const event = this._document._event;
if (!event.value) {
return;
}

nDec = Math.abs(nDec);
const value = event.value.trim().replace(",", ".");
let number = Number.parseFloat(value);
if (isNaN(number) || !isFinite(number)) {
number = 0;
}
event.value = number.toFixed(nDec);
}
}

export { AForm };
61 changes: 61 additions & 0 deletions src/scripting_api/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { EventDispatcher } from "./event.js";
import { NotSupportedError } from "./error.js";
import { PDFObject } from "./pdf_object.js";

class App extends PDFObject {
constructor(data) {
super(data);
this._document = data._document;
this._objects = Object.create(null);
this._eventDispatcher = new EventDispatcher(
this._document,
data.calculationOrder,
this._objects
);

// used in proxy.js to check that this the object with the backdoor
this._isApp = true;
}

// This function is called thanks to the proxy
// when we call app['random_string'] to dispatch the event.
_dispatchEvent(pdfEvent) {
this._eventDispatcher.dispatch(pdfEvent);
}

get activeDocs() {
return [this._document.wrapped];
}

set activeDocs(_) {
throw new NotSupportedError("app.activeDocs");
}

alert(
cMsg,
nIcon = 0,
nType = 0,
cTitle = "PDF.js",
oDoc = null,
oCheckbox = null
) {
this._send({ command: "alert", value: cMsg });
}
}

export { App };
38 changes: 38 additions & 0 deletions src/scripting_api/console.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { PDFObject } from "./pdf_object.js";

class Console extends PDFObject {
clear() {
this._send({ id: "clear" });
}

hide() {
/* Not implemented */
}

println(msg) {
if (typeof msg === "string") {
this._send({ command: "println", value: "PDF.js Console:: " + msg });
}
}

show() {
/* Not implemented */
}
}

export { Console };
48 changes: 48 additions & 0 deletions src/scripting_api/doc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { PDFObject } from "./pdf_object.js";

class Doc extends PDFObject {
constructor(data) {
super(data);

this._printParams = null;
this._fields = Object.create(null);
this._event = null;
}

calculateNow() {
this._eventDispatcher.calculateNow();
}

getField(cName) {
if (typeof cName !== "string") {
throw new TypeError("Invalid field name: must be a string");
}
if (cName in this._fields) {
return this._fields[cName];
}
for (const [name, field] of Object.entries(this._fields)) {
if (name.includes(cName)) {
return field;
}
}

return undefined;
}
}

export { Doc };
23 changes: 23 additions & 0 deletions src/scripting_api/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

class NotSupportedError extends Error {
constructor(name) {
super(`${name} isn't supported in PDF.js`);
this.name = "NotSupportedError";
}
}

export { NotSupportedError };
79 changes: 79 additions & 0 deletions src/scripting_api/event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

class Event {
constructor(data) {
this.change = data.change || "";
this.changeEx = data.changeEx || null;
this.commitKey = data.commitKey || 0;
this.fieldFull = data.fieldFull || false;
this.keyDown = data.keyDown || false;
this.modifier = data.modifier || false;
this.name = data.name;
this.rc = true;
this.richChange = data.richChange || [];
this.richChangeEx = data.richChangeEx || [];
this.richValue = data.richValue || [];
this.selEnd = data.selEnd || 0;
this.selStart = data.selStart || 0;
this.shift = data.shift || false;
this.source = data.source || null;
this.target = data.target || null;
this.targetName = data.targetName || "";
this.type = "Field";
this.value = data.value || null;
this.willCommit = data.willCommit || false;
}
}

class EventDispatcher {
constructor(document, calculationOrder, objects) {
this._document = document;
this._calculationOrder = calculationOrder;
this._objects = objects;

this._document.obj._eventDispatcher = this;
}

dispatch(baseEvent) {
const id = baseEvent.id;
if (!(id in this._objects)) {
return;
}

const name = baseEvent.name.replace(" ", "");
const source = this._objects[id];
const event = (this._document.obj._event = new Event(baseEvent));
const oldValue = source.obj.value;

this.runActions(source, source, event, name);
if (event.rc && oldValue !== event.value) {
source.wrapped.value = event.value;
}
}

runActions(source, target, event, eventName) {
event.source = source.wrapped;
event.target = target.wrapped;
event.name = eventName;
event.rc = true;
if (!target.obj._runActions(event)) {
return true;
}
return event.rc;
}
}

export { Event, EventDispatcher };
Loading

0 comments on commit 1eaf9c9

Please sign in to comment.