In order to simplify m-c code, move some in pdf.js

* move set/clear|Timeout/Interval and crackURL code in pdf.js
 * remove the "backdoor" in the proxy (used to dispatch event) and so return the dispatch function in the initializer
 * remove listeners if an error occured during sandbox initialization
 * add support for alert and prompt in the sandbox
 * add a function to eval in the global scope
This commit is contained in:
Calixte Denizet 2020-12-04 00:39:50 +01:00
parent 3447f7c703
commit 8bff4f1ea9
14 changed files with 472 additions and 265 deletions

File diff suppressed because one or more lines are too long

View File

@ -374,7 +374,28 @@ function createScriptingBundle(defines, extraOptions = undefined) {
.src("./src/pdf.scripting.js")
.pipe(webpack2Stream(scriptingFileConfig))
.pipe(replaceWebpackRequire())
.pipe(replaceJSRootName(scriptingAMDName, "pdfjsScripting"));
.pipe(
replace(
'root["' + scriptingAMDName + '"] = factory()',
"root.pdfjsScripting = factory()"
)
);
}
function createSandboxExternal(defines) {
const preprocessor2 = require("./external/builder/preprocessor2.js");
const licenseHeader = fs.readFileSync("./src/license_header.js").toString();
const ctx = {
saveComments: false,
defines,
};
return gulp.src("./src/pdf.sandbox.external.js").pipe(
transform("utf8", content => {
content = preprocessor2.preprocessPDFJSCode(ctx, content);
return `${licenseHeader}\n${content}`;
})
);
}
function createTemporaryScriptingBundle(defines, extraOptions = undefined) {
@ -1203,6 +1224,9 @@ gulp.task(
createScriptingBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createSandboxExternal(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
createWorkerBundle(defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
),
@ -1791,7 +1815,7 @@ gulp.task(
gulp.task("watch-dev-sandbox", function () {
gulp.watch(
[
"src/pdf.{sandbox,scripting}.js",
"src/pdf.{sandbox,sandbox.external,scripting}.js",
"src/scripting_api/*.js",
"src/shared/scripting_utils.js",
"external/quickjs/*.js",

175
src/pdf.sandbox.external.js Normal file
View File

@ -0,0 +1,175 @@
/* 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.
*/
// In mozilla-central, this file is loaded as non-module script,
// so it mustn't have any dependencies.
class SandboxSupportBase {
/**
* @param {DOMWindow} - win
*/
constructor(win) {
this.win = win;
this.timeoutIds = new Map();
// Will be assigned after the sandbox is initialized
this.commFun = null;
}
destroy() {
this.commFunc = null;
this.timeoutIds.forEach(([_, id]) => this.win.clearTimeout(id));
this.timeoutIds = null;
}
/**
* @param {Object} val - Export a value in the sandbox.
*/
exportValueToSandbox(val) {
throw new Error("Not implemented");
}
/**
* @param {Object} val - Import a value from the sandbox.
*/
importValueFromSandbox(val) {
throw new Error("Not implemented");
}
/**
* @param {String} errorMessage - Create an error in the sandbox.
*/
createErrorForSandbox(errorMessage) {
throw new Error("Not implemented");
}
/**
* @param {String} name - Name of the function to call in the sandbox
* @param {Array<Object>} args - Arguments of the function.
*/
callSandboxFunction(name, args) {
try {
args = this.exportValueToSandbox(args);
this.commFun(name, args);
} catch (e) {
this.win.console.error(e);
}
}
createSandboxExternals() {
// All the functions in externals object are called
// from the sandbox.
const externals = {
setTimeout: (callbackId, nMilliseconds) => {
if (
typeof callbackId !== "number" ||
typeof nMilliseconds !== "number"
) {
return;
}
const id = this.win.setTimeout(() => {
this.timeoutIds.delete(callbackId);
this.callSandboxFunction("timeoutCb", {
callbackId,
interval: false,
});
}, nMilliseconds);
this.timeoutIds.set(callbackId, id);
},
clearTimeout: id => {
this.win.clearTimeout(this.timeoutIds.get(id));
this.timeoutIds.delete(id);
},
setInterval: (callbackId, nMilliseconds) => {
if (
typeof callbackId !== "number" ||
typeof nMilliseconds !== "number"
) {
return;
}
const id = this.win.setInterval(() => {
this.callSandboxFunction("timeoutCb", {
callbackId,
interval: true,
});
}, nMilliseconds);
this.timeoutIds.set(callbackId, id);
},
clearInterval: id => {
this.win.clearInterval(this.timeoutIds.get(id));
this.timeoutIds.delete(id);
},
alert: cMsg => {
if (typeof cMsg !== "string") {
return;
}
this.win.alert(cMsg);
},
prompt: (cQuestion, cDefault) => {
if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
return null;
}
return this.win.prompt(cQuestion, cDefault);
},
parseURL: cUrl => {
const url = new this.win.URL(cUrl);
const props = [
"hash",
"host",
"hostname",
"href",
"origin",
"password",
"pathname",
"port",
"protocol",
"search",
"searchParams",
"username",
];
return Object.fromEntries(
props.map(name => [name, url[name].toString()])
);
},
send: data => {
if (!data) {
return;
}
const event = new this.win.CustomEvent("updateFromSandbox", {
detail: this.importValueFromSandbox(data),
});
this.win.dispatchEvent(event);
},
};
Object.setPrototypeOf(externals, null);
return (name, args) => {
try {
const result = externals[name](...args);
return this.exportValueToSandbox(result);
} catch (error) {
throw this.createErrorForSandbox(error?.toString() ?? "");
}
};
}
}
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
exports.SandboxSupportBase = SandboxSupportBase;
} else {
/* eslint-disable-next-line no-unused-vars, no-var */
var EXPORTED_SYMBOLS = ["SandboxSupportBase"];
}

View File

@ -14,6 +14,7 @@
*/
import ModuleLoader from "../external/quickjs/quickjs-eval.js";
import { SandboxSupportBase } from "./pdf.sandbox.external.js";
/* eslint-disable-next-line no-unused-vars */
const pdfjsVersion = PDFJSDev.eval("BUNDLE_VERSION");
@ -23,79 +24,111 @@ const pdfjsBuild = PDFJSDev.eval("BUNDLE_BUILD");
const TESTING =
typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING");
class SandboxSupport extends SandboxSupportBase {
exportValueToSandbox(val) {
// The communication with the Quickjs sandbox is based on strings
// So we use JSON.stringfy to serialize
return JSON.stringify(val);
}
importValueFromSandbox(val) {
return val;
}
createErrorForSandbox(errorMessage) {
return new Error(errorMessage);
}
}
class Sandbox {
constructor(module) {
this._evalInSandbox = module.cwrap("evalInSandbox", null, [
"string",
"int",
]);
this._dispatchEventName = null;
constructor(win, module) {
this.support = new SandboxSupport(win, this);
// The "external" functions created in pdf.sandbox.external.js
// are finally used here:
// https://github.com/mozilla/pdf.js.quickjs/blob/main/src/myjs.js
// They're called from the sandbox only.
module.externalCall = this.support.createSandboxExternals();
this._module = module;
this._alertOnError = 1;
// 0 to display error using console.error
// else display error using window.alert
this._alertOnError = 0;
}
create(data) {
const sandboxData = JSON.stringify(data);
const extra = [
"send",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"crackURL",
];
const extraStr = extra.join(",");
let code = [
"exports = Object.create(null);",
"module = Object.create(null);",
const code = [
// Next line is replaced by code from initialization.js
// when we create the bundle for the sandbox.
PDFJSDev.eval("PDF_SCRIPTING_JS_SOURCE"),
`data = ${sandboxData};`,
`module.exports.initSandbox({ data, extra: {${extraStr}}, out: this});`,
"delete exports;",
"delete module;",
"delete data;",
`pdfjsScripting.initSandbox({ data: ${sandboxData} })`,
];
if (!TESTING) {
code = code.concat(extra.map(name => `delete ${name};`));
code.push("delete debugMe;");
code.push("delete dump;");
} else {
code.unshift(
`globalThis.sendResultForTesting = callExternalFunction.bind(null, "send");`
);
}
let success = false;
try {
success = !!this._module.ccall(
"init",
"number",
["string", "number"],
[code.join("\n"), this._alertOnError]
);
} catch (error) {
console.error(error);
}
if (success) {
this.support.commFun = this._module.cwrap("commFun", null, [
"string",
"string",
]);
} else {
this.nukeSandbox();
throw new Error("Cannot start sandbox");
}
this._evalInSandbox(code.join("\n"), this._alertOnError);
this._dispatchEventName = data.dispatchEventName;
}
dispatchEvent(event) {
if (this._dispatchEventName === null) {
throw new Error("Sandbox must have been initialized");
}
event = JSON.stringify(event);
this._evalInSandbox(
`app["${this._dispatchEventName}"](${event});`,
this._alertOnError
);
this.support.callSandboxFunction("dispatchEvent", event);
}
dumpMemoryUse() {
this._module.ccall("dumpMemoryUse", null, []);
if (this._module) {
this._module.ccall("dumpMemoryUse", null, []);
}
}
nukeSandbox() {
this._dispatchEventName = null;
this._module.ccall("nukeSandbox", null, []);
this._module = null;
this._evalInSandbox = null;
if (this._module !== null) {
this.support.destroy();
this.support = null;
this._module.ccall("nukeSandbox", null, []);
this._module = null;
}
}
evalForTesting(code, key) {
if (TESTING) {
this._evalInSandbox(
`try {
send({ id: "${key}", result: ${code} });
} catch (error) {
send({ id: "${key}", result: error.message });
}`,
this._alertOnError
this._module.ccall(
"evalInSandbox",
null,
["string", "int"],
[
`try {
sendResultForTesting([{ id: "${key}", result: ${code} }]);
} catch (error) {
sendResultForTesting([{ id: "${key}", result: error.message }]);
}`,
this._alertOnError,
]
);
}
}
@ -103,7 +136,7 @@ class Sandbox {
function QuickJSSandbox() {
return ModuleLoader().then(module => {
return new Sandbox(module);
return new Sandbox(window, module);
});
}

View File

@ -49,15 +49,28 @@ class App extends PDFObject {
data.calculationOrder,
this._objects
);
this._setTimeout = data.setTimeout;
this._clearTimeout = data.clearTimeout;
this._setInterval = data.setInterval;
this._clearInterval = data.clearInterval;
this._timeoutIds = null;
this._timeoutIdsRegistry = null;
// used in proxy.js to check that this is the object with the backdoor
this._isApp = true;
this._timeoutIds = new WeakMap();
// eslint-disable-next-line no-undef
if (typeof FinalizationRegistry !== "undefined") {
// About setTimeOut/setInterval return values (specs):
// The return value of this method must be held in a
// JavaScript variable.
// Otherwise, the timeout object is subject to garbage-collection,
// which would cause the clock to stop.
// eslint-disable-next-line no-undef
this._timeoutIdsRegistry = new FinalizationRegistry(
this._cleanTimeout.bind(this)
);
} else {
this._timeoutIdsRegistry = null;
}
this._timeoutCallbackIds = new Map();
this._timeoutCallbackId = 0;
this._globalEval = data.globalEval;
this._externalCall = data.externalCall;
}
// This function is called thanks to the proxy
@ -66,50 +79,58 @@ class App extends PDFObject {
this._eventDispatcher.dispatch(pdfEvent);
}
_registerTimeout(timeout, id, interval) {
if (!this._timeoutIds) {
this._timeoutIds = new WeakMap();
// FinalizationRegistry isn't implemented in QuickJS
// eslint-disable-next-line no-undef
if (typeof FinalizationRegistry !== "undefined") {
// About setTimeOut/setInterval return values (specs):
// The return value of this method must be held in a
// JavaScript variable.
// Otherwise, the timeout object is subject to garbage-collection,
// which would cause the clock to stop.
_registerTimeoutCallback(cExpr) {
const id = this._timeoutCallbackId++;
this._timeoutCallbackIds.set(id, cExpr);
return id;
}
// eslint-disable-next-line no-undef
this._timeoutIdsRegistry = new FinalizationRegistry(
([timeoutId, isInterval]) => {
if (isInterval) {
this._clearInterval(timeoutId);
} else {
this._clearTimeout(timeoutId);
}
}
);
}
_unregisterTimeoutCallback(id) {
this._timeoutCallbackIds.delete(id);
}
_evalCallback({ callbackId, interval }) {
const expr = this._timeoutCallbackIds.get(callbackId);
if (!interval) {
this._unregisterTimeoutCallback(callbackId);
}
this._timeoutIds.set(timeout, [id, interval]);
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.register(timeout, [id, interval]);
if (expr) {
this._globalEval(expr);
}
}
_unregisterTimeout(timeout) {
if (!this._timeoutIds || !this._timeoutIds.has(timeout)) {
return;
_registerTimeout(callbackId, interval) {
const timeout = Object.create(null);
const id = { callbackId, interval };
this._timeoutIds.set(timeout, id);
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.register(timeout, id);
}
const [id, interval] = this._timeoutIds.get(timeout);
return timeout;
}
_unregisterTimeout(timeout) {
if (this._timeoutIdsRegistry) {
this._timeoutIdsRegistry.unregister(timeout);
}
const data = this._timeoutIds.get(timeout);
if (!data) {
return;
}
this._timeoutIds.delete(timeout);
this._cleanTimeout(data);
}
_cleanTimeout({ callbackId, interval }) {
this._unregisterTimeoutCallback(callbackId);
if (interval) {
this._clearInterval(id);
this._externalCall("clearInterval", [callbackId]);
} else {
this._clearTimeout(id);
this._externalCall("clearTimeout", [callbackId]);
}
}
@ -409,7 +430,7 @@ class App extends PDFObject {
oDoc = null,
oCheckbox = null
) {
this._send({ command: "alert", value: cMsg });
this._externalCall("alert", [cMsg]);
}
beep() {
@ -425,11 +446,11 @@ class App extends PDFObject {
}
clearInterval(oInterval) {
this.unregisterTimeout(oInterval);
this._unregisterTimeout(oInterval);
}
clearTimeOut(oTime) {
this.unregisterTimeout(oTime);
this._unregisterTimeout(oTime);
}
endPriv() {
@ -524,8 +545,8 @@ class App extends PDFObject {
/* Not implemented */
}
response() {
/* TODO or not */
response(cQuestion, cTitle = "", cDefault = "", bPassword = "", cLabel = "") {
return this._externalCall("prompt", [cQuestion, cDefault || ""]);
}
setInterval(cExpr, nMilliseconds) {
@ -537,11 +558,9 @@ class App extends PDFObject {
"Second argument of app.setInterval must be a number"
);
}
const id = this._setInterval(cExpr, nMilliseconds);
const timeout = Object.create(null);
this._registerTimeout(timeout, id, true);
return timeout;
const callbackId = this._registerTimeoutCallback(cExpr);
this._externalCall("setInterval", [callbackId, nMilliseconds]);
return this._registerTimeout(callbackId, true);
}
setTimeOut(cExpr, nMilliseconds) {
@ -551,11 +570,9 @@ class App extends PDFObject {
if (typeof nMilliseconds !== "number") {
throw new TypeError("Second argument of app.setTimeOut must be a number");
}
const id = this._setTimeout(cExpr, nMilliseconds);
const timeout = Object.create(null);
this._registerTimeout(timeout, id, false);
return timeout;
const callbackId = this._registerTimeoutCallback(cExpr);
this._externalCall("setTimeout", [callbackId, nMilliseconds]);
return this._registerTimeout(callbackId, false);
}
trustedFunction() {

View File

@ -30,7 +30,6 @@ class InfoProxyHandler {
class Doc extends PDFObject {
constructor(data) {
super(data);
this.calculate = true;
this.baseURL = data.baseURL || "";
this.calculate = true;

View File

@ -180,11 +180,7 @@ class Field extends PDFObject {
}
} catch (error) {
event.rc = false;
const value =
`"${error.toString()}" for event ` +
`"${eventName}" in object ${this._id}.` +
`\n${error.stack}`;
this._send({ command: "error", value });
throw error;
}
return true;

View File

@ -35,16 +35,20 @@ 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,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
} = extra;
function initSandbox(params) {
delete globalThis.pdfjsScripting;
// externalCall is a function to call a function defined
// outside the sandbox.
// (see src/pdf.sandbox.external.js).
const externalCall = globalThis.callExternalFunction;
delete globalThis.callExternalFunction;
// eslint-disable-next-line no-eval
const globalEval = code => globalThis.eval(code);
const send = data => externalCall("send", [data]);
const proxyHandler = new ProxyHandler();
const { data } = params;
const doc = new Doc({
send,
...data.docInfo,
@ -52,21 +56,21 @@ function initSandbox({ data, extra, out }) {
const _document = { obj: doc, wrapped: new Proxy(doc, proxyHandler) };
const app = new App({
send,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
globalEval,
externalCall,
_document,
calculationOrder: data.calculationOrder,
proxyHandler,
...data.appInfo,
});
const util = new Util({ crackURL });
const util = new Util({ externalCall });
const aform = new AForm(doc, app, util);
for (const [name, objs] of Object.entries(data.objects)) {
const obj = objs[0];
obj.send = send;
obj.globalEval = globalEval;
obj.doc = _document.wrapped;
const field = new Field(obj);
const wrapped = new Proxy(field, proxyHandler);
@ -74,28 +78,44 @@ function initSandbox({ data, extra, out }) {
app._objects[obj.id] = { obj: field, wrapped };
}
out.global = Object.create(null);
out.app = new Proxy(app, proxyHandler);
out.color = new Proxy(new Color(), proxyHandler);
out.console = new Proxy(new Console({ send }), proxyHandler);
out.util = new Proxy(util, proxyHandler);
out.border = Border;
out.cursor = Cursor;
out.display = Display;
out.font = Font;
out.highlight = Highlight;
out.position = Position;
out.scaleHow = ScaleHow;
out.scaleWhen = ScaleWhen;
out.style = Style;
out.trans = Trans;
out.zoomtype = ZoomType;
globalThis.event = null;
globalThis.global = Object.create(null);
globalThis.app = new Proxy(app, proxyHandler);
globalThis.doc = _document.wrapped;
globalThis.color = new Proxy(new Color(), proxyHandler);
globalThis.console = new Proxy(new Console({ send }), proxyHandler);
globalThis.util = new Proxy(util, proxyHandler);
globalThis.border = Border;
globalThis.cursor = Cursor;
globalThis.display = Display;
globalThis.font = Font;
globalThis.highlight = Highlight;
globalThis.position = Position;
globalThis.scaleHow = ScaleHow;
globalThis.scaleWhen = ScaleWhen;
globalThis.style = Style;
globalThis.trans = Trans;
globalThis.zoomtype = ZoomType;
for (const name of Object.getOwnPropertyNames(AForm.prototype)) {
if (name !== "constructor" && !name.startsWith("_")) {
out[name] = aform[name].bind(aform);
globalThis[name] = aform[name].bind(aform);
}
}
const functions = {
dispatchEvent: app._dispatchEvent.bind(app),
timeoutCb: app._evalCallback.bind(app),
};
return (name, args) => {
try {
functions[name](args);
} catch (error) {
const value = `${error.toString()}\n${error.stack}`;
send({ command: "error", value });
}
};
}
export { initSandbox };

View File

@ -14,18 +14,7 @@
*/
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];

View File

@ -19,7 +19,6 @@ class Util extends PDFObject {
constructor(data) {
super(data);
this._crackURL = data.crackURL;
this._scandCache = new Map();
this._months = [
"January",
@ -46,13 +45,9 @@ class Util extends PDFObject {
];
this.MILLISECONDS_IN_DAY = 86400000;
this.MILLISECONDS_IN_WEEK = 604800000;
}
crackURL(cURL) {
if (typeof cURL !== "string") {
throw new TypeError("First argument of util.crackURL must be a string");
}
return this._crackURL(cURL);
// used with crackURL
this._externalCall = data.externalCall;
}
printf(...args) {

View File

@ -48,6 +48,10 @@ describe("Scripting", function () {
send_queue.set(event.detail.id, event.detail);
}
};
window.alert = value => {
const command = "alert";
send_queue.set(command, { command, value });
};
const promise = loadScript(sandboxBundleSrc).then(() => {
return window.pdfjsSandbox.QuickJSSandbox();
});
@ -104,7 +108,6 @@ describe("Scripting", function () {
},
calculationOrder: [],
appInfo: { language: "en-US", platform: "Linux x86_64" },
dispatchEventName: "_dispatchMe",
};
sandbox.createSandbox(data);
sandbox
@ -132,7 +135,6 @@ describe("Scripting", function () {
appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {},
calculationOrder: [],
dispatchEventName: "_dispatchMe",
});
done();
});
@ -263,7 +265,6 @@ describe("Scripting", function () {
},
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [],
dispatchEventName: "_dispatchMe",
};
sandbox.createSandbox(data);
sandbox
@ -301,7 +302,6 @@ describe("Scripting", function () {
},
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [],
dispatchEventName: "_dispatchMe",
};
sandbox.createSandbox(data);
sandbox
@ -343,7 +343,6 @@ describe("Scripting", function () {
},
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [],
dispatchEventName: "_dispatchMe",
};
sandbox.createSandbox(data);
sandbox
@ -384,7 +383,6 @@ describe("Scripting", function () {
},
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [],
dispatchEventName: "_dispatchMe",
};
sandbox.createSandbox(data);
sandbox
@ -429,7 +427,6 @@ describe("Scripting", function () {
},
appInfo: { language: "en-US", platform: "Linux x86_64" },
calculationOrder: [refId2],
dispatchEventName: "_dispatchMe",
};
sandbox.createSandbox(data);
sandbox
@ -458,7 +455,6 @@ describe("Scripting", function () {
appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {},
calculationOrder: [],
dispatchEventName: "_dispatchMe",
});
done();
});
@ -553,7 +549,6 @@ describe("Scripting", function () {
appInfo: { language: "en-US", platform: "Linux x86_64" },
objects: {},
calculationOrder: [],
dispatchEventName: "_dispatchMe",
});
done();
});

View File

@ -19,7 +19,6 @@ import {
AutoPrintRegExp,
DEFAULT_SCALE_VALUE,
EventBus,
generateRandomStringForSandbox,
getActiveOrFocusedElement,
getPDFFileNameFromURL,
isValidRotation,
@ -1483,10 +1482,6 @@ const PDFViewerApplication = {
const { id, command, value } = detail;
if (!id) {
switch (command) {
case "alert":
// eslint-disable-next-line no-alert
window.alert(value);
break;
case "clear":
console.clear();
break;
@ -1501,7 +1496,7 @@ const PDFViewerApplication = {
return;
case "print":
this.triggerPrinting();
return;
break;
case "println":
console.log(value);
break;
@ -1511,7 +1506,7 @@ const PDFViewerApplication = {
} else {
this.pdfViewer.currentScale = value;
}
return;
break;
}
return;
}
@ -1551,8 +1546,6 @@ const PDFViewerApplication = {
window.addEventListener("mouseup", mouseUp);
this._scriptingInstance.events.set("mouseup", mouseUp);
const dispatchEventName = generateRandomStringForSandbox(objects);
if (!this._contentLength) {
// Always waiting for the entire PDF document to be loaded will, most
// likely, delay sandbox-creation too much in the general case for all
@ -1569,24 +1562,28 @@ const PDFViewerApplication = {
const filename =
this._contentDispositionFilename || getPDFFileNameFromURL(this.url);
scripting.createSandbox({
objects,
dispatchEventName,
calculationOrder,
appInfo: {
platform: navigator.platform,
language: navigator.language,
},
docInfo: {
...this.documentInfo,
baseURL: this.baseUrl,
filesize: this._contentLength,
filename,
metadata: this.metadata,
numPages: pdfDocument.numPages,
URL: this.url,
},
});
try {
await scripting.createSandbox({
objects,
calculationOrder,
appInfo: {
platform: navigator.platform,
language: navigator.language,
},
docInfo: {
...this.documentInfo,
baseURL: this.baseUrl,
filesize: this._contentLength,
filename,
metadata: this.metadata,
numPages: pdfDocument.numPages,
URL: this.url,
},
});
} catch (error) {
console.error(error);
this._destroyScriptingInstance();
}
},
/**

View File

@ -256,15 +256,21 @@ class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
class FirefoxScripting {
static createSandbox(data) {
FirefoxCom.requestSync("createSandbox", data);
return new Promise(resolve => {
FirefoxCom.request("createSandbox", data, resolve);
}).then(success => {
if (!success) {
throw new Error("Cannot start sandbox");
}
});
}
static dispatchEventInSandbox(event) {
FirefoxCom.requestSync("dispatchEventInSandbox", event);
static async dispatchEventInSandbox(event) {
FirefoxCom.request("dispatchEventInSandbox", event);
}
static destroySandbox() {
FirefoxCom.requestSync("destroySandbox", null);
static async destroySandbox() {
FirefoxCom.request("destroySandbox", null);
}
}

View File

@ -1015,42 +1015,6 @@ function getActiveOrFocusedElement() {
return curActiveOrFocused;
}
/**
* Generate a random string which is not define somewhere in actions.
*
* @param {Object} objects - The value returned by `getFieldObjects` in the API.
* @returns {string} A unique string.
*/
function generateRandomStringForSandbox(objects) {
const allObjects = Object.values(objects).flat(2);
const actions = allObjects
.filter(obj => !!obj.actions)
.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,
@ -1074,7 +1038,6 @@ export {
NullL10n,
EventBus,
ProgressBar,
generateRandomStringForSandbox,
getPDFFileNameFromURL,
noContextMenuHandler,
parseQueryString,