JS - Add the basic architecture to be able to execute embedded js
This commit is contained in:
parent
a373137304
commit
e76a96892a
@ -151,6 +151,10 @@
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"enableScripting": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"enablePermissions": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
|
20
gulpfile.js
20
gulpfile.js
@ -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";
|
||||
@ -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")
|
||||
),
|
||||
|
@ -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);
|
||||
});
|
||||
@ -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;
|
||||
|
||||
|
46
src/scripting_api/aform.js
Normal file
46
src/scripting_api/aform.js
Normal file
@ -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
src/scripting_api/app.js
Normal file
61
src/scripting_api/app.js
Normal file
@ -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
src/scripting_api/console.js
Normal file
38
src/scripting_api/console.js
Normal file
@ -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
src/scripting_api/doc.js
Normal file
48
src/scripting_api/doc.js
Normal file
@ -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
src/scripting_api/error.js
Normal file
23
src/scripting_api/error.js
Normal file
@ -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
src/scripting_api/event.js
Normal file
79
src/scripting_api/event.js
Normal file
@ -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 };
|
123
src/scripting_api/field.js
Normal file
123
src/scripting_api/field.js
Normal file
@ -0,0 +1,123 @@
|
||||
/* 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 Field extends PDFObject {
|
||||
constructor(data) {
|
||||
super(data);
|
||||
this.alignment = data.alignment || "left";
|
||||
this.borderStyle = data.borderStyle || "";
|
||||
this.buttonAlignX = data.buttonAlignX || 50;
|
||||
this.buttonAlignY = data.buttonAlignY || 50;
|
||||
this.buttonFitBounds = data.buttonFitBounds;
|
||||
this.buttonPosition = data.buttonPosition;
|
||||
this.buttonScaleHow = data.buttonScaleHow;
|
||||
this.ButtonScaleWhen = data.buttonScaleWhen;
|
||||
this.calcOrderIndex = data.calcOrderIndex;
|
||||
this.charLimit = data.charLimit;
|
||||
this.comb = data.comb;
|
||||
this.commitOnSelChange = data.commitOnSelChange;
|
||||
this.currentValueIndices = data.currentValueIndices;
|
||||
this.defaultStyle = data.defaultStyle;
|
||||
this.defaultValue = data.defaultValue;
|
||||
this.doNotScroll = data.doNotScroll;
|
||||
this.doNotSpellCheck = data.doNotSpellCheck;
|
||||
this.delay = data.delay;
|
||||
this.display = data.display;
|
||||
this.doc = data.doc;
|
||||
this.editable = data.editable;
|
||||
this.exportValues = data.exportValues;
|
||||
this.fileSelect = data.fileSelect;
|
||||
this.fillColor = data.fillColor;
|
||||
this.hidden = data.hidden;
|
||||
this.highlight = data.highlight;
|
||||
this.lineWidth = data.lineWidth;
|
||||
this.multiline = data.multiline;
|
||||
this.multipleSelection = data.multipleSelection;
|
||||
this.name = data.name;
|
||||
this.numItems = data.numItems;
|
||||
this.page = data.page;
|
||||
this.password = data.password;
|
||||
this.print = data.print;
|
||||
this.radiosInUnison = data.radiosInUnison;
|
||||
this.readonly = data.readonly;
|
||||
this.rect = data.rect;
|
||||
this.required = data.required;
|
||||
this.richText = data.richText;
|
||||
this.richValue = data.richValue;
|
||||
this.rotation = data.rotation;
|
||||
this.strokeColor = data.strokeColor;
|
||||
this.style = data.style;
|
||||
this.submitName = data.submitName;
|
||||
this.textColor = data.textColor;
|
||||
this.textFont = data.textFont;
|
||||
this.textSize = data.textSize;
|
||||
this.type = data.type;
|
||||
this.userName = data.userName;
|
||||
this.value = data.value || "";
|
||||
this.valueAsString = data.valueAsString;
|
||||
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setAction(cTrigger, cScript) {
|
||||
if (typeof cTrigger !== "string" || typeof cScript !== "string") {
|
||||
return;
|
||||
}
|
||||
if (!(cTrigger in this._actions)) {
|
||||
this._actions[cTrigger] = [];
|
||||
}
|
||||
this._actions[cTrigger].push(cScript);
|
||||
}
|
||||
|
||||
setFocus() {
|
||||
this._send({ id: this._id, focus: true });
|
||||
}
|
||||
|
||||
_runActions(event) {
|
||||
const eventName = event.name;
|
||||
if (!(eventName in this._actions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const actions = this._actions[eventName];
|
||||
try {
|
||||
for (const action of actions) {
|
||||
action(event);
|
||||
}
|
||||
} catch (error) {
|
||||
event.rc = false;
|
||||
const value =
|
||||
`\"${error.toString()}\" for event ` +
|
||||
`\"${eventName}\" in object ${this._id}.` +
|
||||
`\n${error.stack}`;
|
||||
this._send({ command: "error", value });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export { Field };
|
57
src/scripting_api/initialization.js
Normal file
57
src/scripting_api/initialization.js
Normal file
@ -0,0 +1,57 @@
|
||||
/* 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 { AForm } from "./aform.js";
|
||||
import { App } from "./app.js";
|
||||
import { Console } from "./console.js";
|
||||
import { Doc } from "./doc.js";
|
||||
import { Field } from "./field.js";
|
||||
import { ProxyHandler } from "./proxy.js";
|
||||
import { Util } from "./util.js";
|
||||
|
||||
function initSandbox(data, extra, out) {
|
||||
const proxyHandler = new ProxyHandler(data.dispatchEventName);
|
||||
const { send, crackURL } = extra;
|
||||
const doc = new Doc({ send });
|
||||
const _document = { obj: doc, wrapped: new Proxy(doc, proxyHandler) };
|
||||
const app = new App({
|
||||
send,
|
||||
_document,
|
||||
calculationOrder: data.calculationOrder,
|
||||
});
|
||||
const util = new Util({ crackURL });
|
||||
const aform = new AForm(doc, app, util);
|
||||
|
||||
for (const [name, objs] of Object.entries(data.objects)) {
|
||||
const obj = objs[0];
|
||||
obj.send = send;
|
||||
obj.doc = _document.wrapped;
|
||||
const field = new Field(obj);
|
||||
const wrapped = (doc._fields[name] = new Proxy(field, proxyHandler));
|
||||
app._objects[obj.id] = { obj: field, wrapped };
|
||||
}
|
||||
|
||||
out.global = Object.create(null);
|
||||
out.app = new Proxy(app, proxyHandler);
|
||||
out.console = new Proxy(new Console({ send }), proxyHandler);
|
||||
out.util = new Proxy(util, proxyHandler);
|
||||
for (const name of Object.getOwnPropertyNames(AForm.prototype)) {
|
||||
if (name.startsWith("AF")) {
|
||||
out[name] = aform[name].bind(aform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { initSandbox };
|
24
src/scripting_api/pdf_object.js
Normal file
24
src/scripting_api/pdf_object.js
Normal file
@ -0,0 +1,24 @@
|
||||
/* 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 PDFObject {
|
||||
constructor(data) {
|
||||
this._expandos = Object.create(null);
|
||||
this._send = data.send || null;
|
||||
this._id = data.id || null;
|
||||
}
|
||||
}
|
||||
|
||||
export { PDFObject };
|
125
src/scripting_api/proxy.js
Normal file
125
src/scripting_api/proxy.js
Normal file
@ -0,0 +1,125 @@
|
||||
/* 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 ProxyHandler {
|
||||
constructor(dispatchEventName) {
|
||||
this.dispatchEventName = dispatchEventName;
|
||||
}
|
||||
|
||||
get(obj, prop) {
|
||||
if (obj._isApp && prop === this.dispatchEventName) {
|
||||
// a backdoor to be able to call _dispatchEvent method
|
||||
// the value of 'dispatchEvent' is generated randomly
|
||||
// and injected in the code
|
||||
return obj._dispatchEvent.bind(obj);
|
||||
}
|
||||
|
||||
// script may add some properties to the object
|
||||
if (prop in obj._expandos) {
|
||||
const val = obj._expandos[prop];
|
||||
if (typeof val === "function") {
|
||||
return val.bind(obj);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
|
||||
// return only public properties
|
||||
// i.e. the ones not starting with a '_'
|
||||
const val = obj[prop];
|
||||
if (typeof val === "function") {
|
||||
return val.bind(obj);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
set(obj, prop, value) {
|
||||
if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
|
||||
const old = obj[prop];
|
||||
obj[prop] = value;
|
||||
if (obj._send && obj._id !== null && typeof old !== "function") {
|
||||
const data = { id: obj._id };
|
||||
data[prop] = value;
|
||||
|
||||
// send the updated value to the other side
|
||||
obj._send(data);
|
||||
}
|
||||
} else {
|
||||
obj._expandos[prop] = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
has(obj, prop) {
|
||||
return (
|
||||
prop in obj._expandos ||
|
||||
(typeof prop === "string" && !prop.startsWith("_") && prop in obj)
|
||||
);
|
||||
}
|
||||
|
||||
getPrototypeOf(obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
setPrototypeOf(obj, proto) {
|
||||
return false;
|
||||
}
|
||||
|
||||
isExtensible(obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
preventExtensions(obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
getOwnPropertyDescriptor(obj, prop) {
|
||||
if (prop in obj._expandos) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: obj._expandos[prop],
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
|
||||
return { configurable: true, enumerable: true, value: obj[prop] };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
defineProperty(obj, key, descriptor) {
|
||||
Object.defineProperty(obj._expandos, key, descriptor);
|
||||
return true;
|
||||
}
|
||||
|
||||
deleteProperty(obj, prop) {
|
||||
if (prop in obj._expandos) {
|
||||
delete obj._expandos[prop];
|
||||
}
|
||||
}
|
||||
|
||||
ownKeys(obj) {
|
||||
const fromExpandos = Reflect.ownKeys(obj._expandos);
|
||||
const fromObj = Reflect.ownKeys(obj).filter(k => !k.startsWith("_"));
|
||||
return fromExpandos.concat(fromObj);
|
||||
}
|
||||
}
|
||||
|
||||
export { ProxyHandler };
|
30
src/scripting_api/util.js
Normal file
30
src/scripting_api/util.js
Normal file
@ -0,0 +1,30 @@
|
||||
/* 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 Util extends PDFObject {
|
||||
constructor(data) {
|
||||
super(data);
|
||||
|
||||
this._crackURL = data.crackURL;
|
||||
}
|
||||
|
||||
crackURL(cURL) {
|
||||
return this._crackURL(cURL);
|
||||
}
|
||||
}
|
||||
|
||||
export { Util };
|
59
web/app.js
59
web/app.js
@ -19,6 +19,7 @@ import {
|
||||
AutoPrintRegExp,
|
||||
DEFAULT_SCALE_VALUE,
|
||||
EventBus,
|
||||
generateRandomStringForSandbox,
|
||||
getActiveOrFocusedElement,
|
||||
getPDFFileNameFromURL,
|
||||
isValidRotation,
|
||||
@ -179,6 +180,10 @@ class DefaultExternalServices {
|
||||
static get isInAutomation() {
|
||||
return shadow(this, "isInAutomation", false);
|
||||
}
|
||||
|
||||
static get scripting() {
|
||||
throw new Error("Not implemented: scripting");
|
||||
}
|
||||
}
|
||||
|
||||
const PDFViewerApplication = {
|
||||
@ -1333,6 +1338,60 @@ const PDFViewerApplication = {
|
||||
|
||||
this._initializePageLabels(pdfDocument);
|
||||
this._initializeMetadata(pdfDocument);
|
||||
this._initializeJavaScript(pdfDocument);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async _initializeJavaScript(pdfDocument) {
|
||||
if (!AppOptions.get("enableScripting")) {
|
||||
return;
|
||||
}
|
||||
const objects = await pdfDocument.getFieldObjects();
|
||||
const scripting = this.externalServices.scripting;
|
||||
|
||||
window.addEventListener("updateFromSandbox", function (event) {
|
||||
const detail = event.detail;
|
||||
const id = detail.id;
|
||||
if (!id) {
|
||||
switch (detail.command) {
|
||||
case "println":
|
||||
console.log(detail.value);
|
||||
break;
|
||||
case "clear":
|
||||
console.clear();
|
||||
break;
|
||||
case "alert":
|
||||
// eslint-disable-next-line no-alert
|
||||
window.alert(detail.value);
|
||||
break;
|
||||
case "error":
|
||||
console.error(detail.value);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.dispatchEvent(new CustomEvent("updateFromSandbox", { detail }));
|
||||
} else {
|
||||
const value = detail.value;
|
||||
if (value !== undefined && value !== null) {
|
||||
// the element hasn't been rendered yet so use annotation storage
|
||||
pdfDocument.annotationStorage.setValue(id, detail.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("dispatchEventInSandbox", function (event) {
|
||||
scripting.dispatchEventInSandbox(event.detail);
|
||||
});
|
||||
|
||||
const dispatchEventName = generateRandomStringForSandbox(objects);
|
||||
const calculationOrder = [];
|
||||
scripting.createSandbox({ objects, dispatchEventName, calculationOrder });
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -65,6 +65,11 @@ const defaultOptions = {
|
||||
value: false,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
enableScripting: {
|
||||
/** @type {boolean} */
|
||||
value: false,
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
enableWebGL: {
|
||||
/** @type {boolean} */
|
||||
value: false,
|
||||
|
@ -254,6 +254,18 @@ class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
|
||||
}
|
||||
}
|
||||
|
||||
const FirefoxScripting = {
|
||||
createSandbox(data) {
|
||||
FirefoxCom.requestSync("createSandbox", data);
|
||||
},
|
||||
dispatchEventInSandbox(event, sandboxID) {
|
||||
FirefoxCom.requestSync("dispatchEventInSandbox", event);
|
||||
},
|
||||
destroySandbox() {
|
||||
FirefoxCom.requestSync("destroySandbox", null);
|
||||
},
|
||||
};
|
||||
|
||||
class FirefoxExternalServices extends DefaultExternalServices {
|
||||
static updateFindControlState(data) {
|
||||
FirefoxCom.request("updateFindControlState", data);
|
||||
@ -346,6 +358,10 @@ class FirefoxExternalServices extends DefaultExternalServices {
|
||||
return new MozL10n(mozL10n);
|
||||
}
|
||||
|
||||
static get scripting() {
|
||||
return FirefoxScripting;
|
||||
}
|
||||
|
||||
static get supportsIntegratedFind() {
|
||||
const support = FirefoxCom.requestSync("supportsIntegratedFind");
|
||||
return shadow(this, "supportsIntegratedFind", support);
|
||||
|
@ -1007,6 +1007,39 @@ function getActiveOrFocusedElement() {
|
||||
return curActiveOrFocused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random string which is not define somewhere in actions.
|
||||
*
|
||||
* @param {WaitOnEventOrTimeoutParameters}
|
||||
* @returns {Promise} A promise that is resolved with a {WaitOnType} value.
|
||||
*/
|
||||
function generateRandomStringForSandbox(objects) {
|
||||
const allObjects = Object.values(objects).flat(2);
|
||||
const actions = allObjects.map(obj => Object.values(obj.actions)).flat(2);
|
||||
|
||||
while (true) {
|
||||
const name = new Uint8Array(64);
|
||||
if (typeof crypto !== "undefined") {
|
||||
crypto.getRandomValues(name);
|
||||
} else {
|
||||
for (let i = 0, ii = name.length; i < ii; i++) {
|
||||
name[i] = Math.floor(256 * Math.random());
|
||||
}
|
||||
}
|
||||
|
||||
const nameString =
|
||||
"_" +
|
||||
btoa(
|
||||
Array.from(name)
|
||||
.map(x => String.fromCharCode(x))
|
||||
.join("")
|
||||
);
|
||||
if (actions.every(action => !action.includes(nameString))) {
|
||||
return nameString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
AutoPrintRegExp,
|
||||
CSS_UNITS,
|
||||
@ -1030,6 +1063,7 @@ export {
|
||||
NullL10n,
|
||||
EventBus,
|
||||
ProgressBar,
|
||||
generateRandomStringForSandbox,
|
||||
getPDFFileNameFromURL,
|
||||
noContextMenuHandler,
|
||||
parseQueryString,
|
||||
|
Loading…
Reference in New Issue
Block a user