mirror of
https://github.com/zen-browser/pdf.js.git
synced 2025-07-10 02:05:37 +02:00
This patch is another step towards enabling Babel. Since we're moving towards ES6 modules, we will not be using UMD headers anymore, so we can remove the validation.
371 lines
13 KiB
JavaScript
371 lines
13 KiB
JavaScript
'use strict';
|
|
|
|
var esprima = require('esprima');
|
|
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 = esprima.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 = esprima.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 '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, ...) => tranform
|
|
var action = node.callee.property.name;
|
|
return handlePreprocessorAction(ctx, action,
|
|
node.arguments, node.loc);
|
|
}
|
|
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;
|
|
case 'Program':
|
|
// Checking for a function closure that looks like UMD header.
|
|
node.body.some(function (item, index) {
|
|
// Is it `(function (root, factory) { ? }(this, function (?) {?}));` ?
|
|
if (item.type !== 'ExpressionStatement' ||
|
|
item.expression.type !== 'CallExpression' ||
|
|
item.expression.callee.type !== 'FunctionExpression' ||
|
|
item.expression.callee.params.length !== 2 ||
|
|
item.expression.arguments.length !== 2 ||
|
|
item.expression.arguments[0].type !== 'ThisExpression' ||
|
|
item.expression.arguments[1].type !== 'FunctionExpression') {
|
|
return false;
|
|
}
|
|
var init = item.expression.callee;
|
|
// Is init body looks like
|
|
// `if (?) { ? } else if (typeof exports !== 'undefined') { ? } ...`?
|
|
if (init.body.type !== 'BlockStatement' ||
|
|
init.body.body.length !== 1 ||
|
|
init.body.body[0].type !== 'IfStatement') {
|
|
return false;
|
|
}
|
|
var initIf = init.body.body[0];
|
|
if (initIf.alternate.type !== 'IfStatement' ||
|
|
initIf.alternate.test.type !== 'BinaryExpression' ||
|
|
initIf.alternate.test.operator !== '!==' ||
|
|
initIf.alternate.test.left.type !== 'UnaryExpression' ||
|
|
initIf.alternate.test.left.operator !== 'typeof' ||
|
|
initIf.alternate.test.left.argument.type !== 'Identifier' ||
|
|
initIf.alternate.test.left.argument.name !== 'exports' ||
|
|
initIf.alternate.test.right.type !== 'Literal' ||
|
|
initIf.alternate.test.right.value !== 'undefined' ||
|
|
initIf.alternate.consequent.type !== 'BlockStatement') {
|
|
return false;
|
|
}
|
|
var commonJsInit = initIf.alternate.consequent;
|
|
// Is commonJsInit `factory(exports, ...)` ?
|
|
if (commonJsInit.body.length !== 1 ||
|
|
commonJsInit.body[0].type !== 'ExpressionStatement' ||
|
|
commonJsInit.body[0].expression.type !== 'CallExpression' ||
|
|
commonJsInit.body[0].expression.callee.type !== 'Identifier') {
|
|
return false;
|
|
}
|
|
var commonJsInitArgs = commonJsInit.body[0].expression.arguments;
|
|
if (commonJsInitArgs.length === 0 ||
|
|
commonJsInitArgs[0].type !== 'Identifier' ||
|
|
commonJsInitArgs[0].name !== 'exports') {
|
|
return false;
|
|
}
|
|
var factory = item.expression.arguments[1];
|
|
// Is factory `function (exports, ....) { ? }` ?
|
|
if (factory.params.length === 0 ||
|
|
factory.params[0].type !== 'Identifier' ||
|
|
factory.params[0].name !== 'exports' ||
|
|
factory.body.type !== 'BlockStatement') {
|
|
return true;
|
|
}
|
|
var factoryParams = factory.params;
|
|
var factoryBody = factory.body;
|
|
|
|
// Remove closure and function and replacing parameters with vars.
|
|
node.body.splice(index, 1);
|
|
for (var i = 1, ii = factoryParams.length; i < ii; i++) {
|
|
var varNode = {
|
|
type: 'VariableDeclaration',
|
|
'declarations': [{
|
|
type: 'VariableDeclarator',
|
|
id: factoryParams[i],
|
|
init: commonJsInitArgs[i] || null,
|
|
loc: factoryParams[i].loc
|
|
}],
|
|
kind: 'var'
|
|
};
|
|
node.body.splice(index++, 0, varNode);
|
|
}
|
|
factoryBody.body.forEach(function (item) {
|
|
node.body.splice(index++, 0, item);
|
|
});
|
|
return true;
|
|
});
|
|
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) {
|
|
var 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) {
|
|
var 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 saveComments = !!ctx.saveComments;
|
|
var format = ctx.format || {
|
|
indent: {
|
|
style: ' ',
|
|
adjustMultilineComment: saveComments,
|
|
}
|
|
};
|
|
var parseComment = {
|
|
loc: true,
|
|
attachComment: saveComments
|
|
};
|
|
var codegenOptions = {
|
|
format: format,
|
|
comment: saveComments,
|
|
parse: esprima.parse
|
|
};
|
|
var syntax = esprima.parse(code, parseComment);
|
|
traverseTree(ctx, syntax);
|
|
return escodegen.generate(syntax, codegenOptions);
|
|
}
|
|
|
|
exports.preprocessPDFJSCode = preprocessPDFJSCode;
|