8d56a69e74
With these changes SystemJS is now only used, during development, on the worker-thread and in the unit/font-tests, since Firefox is currently missing support for worker modules; please see https://bugzilla.mozilla.org/show_bug.cgi?id=1247687 Hence all the JavaScript files in the `web/` and `src/display/` folders are now loaded *natively* by the browser (during development) using standard `import` statements/calls, thanks to a nice `import-maps` polyfill. *Please note:* As soon as https://bugzilla.mozilla.org/show_bug.cgi?id=1247687 is fixed in Firefox, we should be able to remove all traces of SystemJS and thus finally be able to use every possible modern JavaScript feature.
342 lines
10 KiB
JavaScript
342 lines
10 KiB
JavaScript
"use strict";
|
|
|
|
var acorn = require("acorn");
|
|
var escodegen = require("escodegen");
|
|
var vm = require("vm");
|
|
var fs = require("fs");
|
|
var path = require("path");
|
|
|
|
var PDFJS_PREPROCESSOR_NAME = "PDFJSDev";
|
|
var ROOT_PREFIX = "$ROOT/";
|
|
|
|
function isLiteral(obj, value) {
|
|
return obj.type === "Literal" && obj.value === value;
|
|
}
|
|
|
|
function isPDFJSPreprocessor(obj) {
|
|
return obj.type === "Identifier" && obj.name === PDFJS_PREPROCESSOR_NAME;
|
|
}
|
|
|
|
function evalWithDefines(code, defines, loc) {
|
|
if (!code || !code.trim()) {
|
|
throw new Error("No JavaScript expression given");
|
|
}
|
|
return vm.runInNewContext(code, defines, { displayErrors: false });
|
|
}
|
|
|
|
function handlePreprocessorAction(ctx, actionName, args, loc) {
|
|
try {
|
|
var arg;
|
|
switch (actionName) {
|
|
case "test":
|
|
arg = args[0];
|
|
if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
|
|
throw new Error("No code for testing is given");
|
|
}
|
|
var isTrue = !!evalWithDefines(arg.value, ctx.defines);
|
|
return { type: "Literal", value: isTrue, loc: loc };
|
|
case "eval":
|
|
arg = args[0];
|
|
if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
|
|
throw new Error("No code for eval is given");
|
|
}
|
|
var result = evalWithDefines(arg.value, ctx.defines);
|
|
if (
|
|
typeof result === "boolean" ||
|
|
typeof result === "string" ||
|
|
typeof result === "number"
|
|
) {
|
|
return { type: "Literal", value: result, loc: loc };
|
|
}
|
|
if (typeof result === "object") {
|
|
var parsedObj = acorn.parse("(" + JSON.stringify(result) + ")");
|
|
parsedObj.body[0].expression.loc = loc;
|
|
return parsedObj.body[0].expression;
|
|
}
|
|
break;
|
|
case "json":
|
|
arg = args[0];
|
|
if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
|
|
throw new Error("Path to JSON is not provided");
|
|
}
|
|
var jsonPath = arg.value;
|
|
if (jsonPath.indexOf(ROOT_PREFIX) === 0) {
|
|
jsonPath = path.join(
|
|
ctx.rootPath,
|
|
jsonPath.substring(ROOT_PREFIX.length)
|
|
);
|
|
}
|
|
var jsonContent = fs.readFileSync(jsonPath).toString();
|
|
var parsedJSON = acorn.parse("(" + jsonContent + ")");
|
|
parsedJSON.body[0].expression.loc = loc;
|
|
return parsedJSON.body[0].expression;
|
|
}
|
|
throw new Error("Unsupported action");
|
|
} catch (e) {
|
|
throw new Error(
|
|
"Could not process " +
|
|
PDFJS_PREPROCESSOR_NAME +
|
|
"." +
|
|
actionName +
|
|
" at " +
|
|
JSON.stringify(loc) +
|
|
"\n" +
|
|
e.name +
|
|
": " +
|
|
e.message
|
|
);
|
|
}
|
|
}
|
|
|
|
function postprocessNode(ctx, node) {
|
|
switch (node.type) {
|
|
case "ExportNamedDeclaration":
|
|
case "ImportDeclaration":
|
|
if (
|
|
node.source &&
|
|
node.source.type === "Literal" &&
|
|
ctx.map &&
|
|
ctx.map[node.source.value]
|
|
) {
|
|
var newValue = ctx.map[node.source.value];
|
|
node.source.value = node.source.raw = newValue;
|
|
}
|
|
break;
|
|
case "IfStatement":
|
|
if (isLiteral(node.test, true)) {
|
|
// if (true) stmt1; => stmt1
|
|
return node.consequent;
|
|
} else if (isLiteral(node.test, false)) {
|
|
// if (false) stmt1; else stmt2; => stmt2
|
|
return node.alternate || { type: "EmptyStatement", loc: node.loc };
|
|
}
|
|
break;
|
|
case "ConditionalExpression":
|
|
if (isLiteral(node.test, true)) {
|
|
// true ? stmt1 : stmt2 => stmt1
|
|
return node.consequent;
|
|
} else if (isLiteral(node.test, false)) {
|
|
// false ? stmt1 : stmt2 => stmt2
|
|
return node.alternate;
|
|
}
|
|
break;
|
|
case "UnaryExpression":
|
|
if (node.operator === "typeof" && isPDFJSPreprocessor(node.argument)) {
|
|
// typeof PDFJSDev => 'object'
|
|
return { type: "Literal", value: "object", loc: node.loc };
|
|
}
|
|
if (
|
|
node.operator === "!" &&
|
|
node.argument.type === "Literal" &&
|
|
typeof node.argument.value === "boolean"
|
|
) {
|
|
// !true => false, !false => true
|
|
return { type: "Literal", value: !node.argument.value, loc: node.loc };
|
|
}
|
|
break;
|
|
case "LogicalExpression":
|
|
switch (node.operator) {
|
|
case "&&":
|
|
if (isLiteral(node.left, true)) {
|
|
return node.right;
|
|
}
|
|
if (isLiteral(node.left, false)) {
|
|
return node.left;
|
|
}
|
|
break;
|
|
case "||":
|
|
if (isLiteral(node.left, true)) {
|
|
return node.left;
|
|
}
|
|
if (isLiteral(node.left, false)) {
|
|
return node.right;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case "BinaryExpression":
|
|
switch (node.operator) {
|
|
case "==":
|
|
case "===":
|
|
case "!=":
|
|
case "!==":
|
|
if (
|
|
node.left.type === "Literal" &&
|
|
node.right.type === "Literal" &&
|
|
typeof node.left.value === typeof node.right.value
|
|
) {
|
|
// folding two literals == and != check
|
|
switch (typeof node.left.value) {
|
|
case "string":
|
|
case "boolean":
|
|
case "number":
|
|
var equal = node.left.value === node.right.value;
|
|
return {
|
|
type: "Literal",
|
|
value: (node.operator[0] === "=") === equal,
|
|
loc: node.loc,
|
|
};
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case "CallExpression":
|
|
if (
|
|
node.callee.type === "MemberExpression" &&
|
|
isPDFJSPreprocessor(node.callee.object) &&
|
|
node.callee.property.type === "Identifier"
|
|
) {
|
|
// PDFJSDev.xxxx(arg1, arg2, ...) => transform
|
|
var action = node.callee.property.name;
|
|
return handlePreprocessorAction(ctx, action, node.arguments, node.loc);
|
|
}
|
|
// require('string')
|
|
if (
|
|
node.callee.type === "Identifier" &&
|
|
node.callee.name === "require" &&
|
|
node.arguments.length === 1 &&
|
|
node.arguments[0].type === "Literal" &&
|
|
ctx.map &&
|
|
ctx.map[node.arguments[0].value]
|
|
) {
|
|
var requireName = node.arguments[0];
|
|
requireName.value = requireName.raw = ctx.map[requireName.value];
|
|
}
|
|
break;
|
|
case "BlockStatement":
|
|
var subExpressionIndex = 0;
|
|
while (subExpressionIndex < node.body.length) {
|
|
switch (node.body[subExpressionIndex].type) {
|
|
case "EmptyStatement":
|
|
// Removing empty statements from the blocks.
|
|
node.body.splice(subExpressionIndex, 1);
|
|
continue;
|
|
case "BlockStatement":
|
|
// Block statements inside a block are moved to the parent one.
|
|
var subChildren = node.body[subExpressionIndex].body;
|
|
Array.prototype.splice.apply(
|
|
node.body,
|
|
[subExpressionIndex, 1].concat(subChildren)
|
|
);
|
|
subExpressionIndex += Math.max(subChildren.length - 1, 0);
|
|
continue;
|
|
case "ReturnStatement":
|
|
case "ThrowStatement":
|
|
// Removing dead code after return or throw.
|
|
node.body.splice(
|
|
subExpressionIndex + 1,
|
|
node.body.length - subExpressionIndex - 1
|
|
);
|
|
break;
|
|
}
|
|
subExpressionIndex++;
|
|
}
|
|
break;
|
|
case "FunctionDeclaration":
|
|
case "FunctionExpression":
|
|
var block = node.body;
|
|
if (
|
|
block.body.length > 0 &&
|
|
block.body[block.body.length - 1].type === "ReturnStatement" &&
|
|
!block.body[block.body.length - 1].argument
|
|
) {
|
|
// Function body ends with return without arg -- removing it.
|
|
block.body.pop();
|
|
}
|
|
break;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
function fixComments(ctx, node) {
|
|
if (!ctx.saveComments) {
|
|
return;
|
|
}
|
|
// Fixes double comments in the escodegen output.
|
|
delete node.trailingComments;
|
|
// Removes ESLint and other service comments.
|
|
if (node.leadingComments) {
|
|
var CopyrightRegExp = /\bcopyright\b/i;
|
|
var BlockCommentRegExp = /^\s*(globals|eslint|falls through)\b/;
|
|
var LineCommentRegExp = /^\s*eslint\b/;
|
|
|
|
var i = 0;
|
|
while (i < node.leadingComments.length) {
|
|
var type = node.leadingComments[i].type;
|
|
var value = node.leadingComments[i].value;
|
|
|
|
if (ctx.saveComments === "copyright") {
|
|
// Remove all comments, except Copyright notices and License headers.
|
|
if (!(type === "Block" && CopyrightRegExp.test(value))) {
|
|
node.leadingComments.splice(i, 1);
|
|
continue;
|
|
}
|
|
} else if (
|
|
(type === "Block" && BlockCommentRegExp.test(value)) ||
|
|
(type === "Line" && LineCommentRegExp.test(value))
|
|
) {
|
|
node.leadingComments.splice(i, 1);
|
|
continue;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
function traverseTree(ctx, node) {
|
|
// generic node processing
|
|
for (var i in node) {
|
|
var child = node[i];
|
|
if (typeof child === "object" && child !== null && child.type) {
|
|
const result = traverseTree(ctx, child);
|
|
if (result !== child) {
|
|
node[i] = result;
|
|
}
|
|
} else if (Array.isArray(child)) {
|
|
child.forEach(function (childItem, index) {
|
|
if (
|
|
typeof childItem === "object" &&
|
|
childItem !== null &&
|
|
childItem.type
|
|
) {
|
|
const result = traverseTree(ctx, childItem);
|
|
if (result !== childItem) {
|
|
child[index] = result;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
node = postprocessNode(ctx, node) || node;
|
|
|
|
fixComments(ctx, node);
|
|
return node;
|
|
}
|
|
|
|
function preprocessPDFJSCode(ctx, code) {
|
|
var format = ctx.format || {
|
|
indent: {
|
|
style: " ",
|
|
},
|
|
};
|
|
var parseOptions = {
|
|
ecmaVersion: 2020,
|
|
locations: true,
|
|
sourceFile: ctx.sourceFile,
|
|
sourceType: "module",
|
|
};
|
|
var codegenOptions = {
|
|
format: format,
|
|
parse: acorn.parse,
|
|
sourceMap: ctx.sourceMap,
|
|
sourceMapWithCode: ctx.sourceMap,
|
|
};
|
|
var syntax = acorn.parse(code, parseOptions);
|
|
traverseTree(ctx, syntax);
|
|
return escodegen.generate(syntax, codegenOptions);
|
|
}
|
|
|
|
exports.preprocessPDFJSCode = preprocessPDFJSCode;
|