wasm-imagemagick
Version:
Webassembly compilation of ImageMagick
1,499 lines (1,372 loc) • 203 kB
JavaScript
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
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
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => {
options = Object.assign({
concurrency: Infinity
}, options);
if (typeof mapper !== 'function') {
throw new TypeError('Mapper function is required');
}
const {concurrency} = options;
if (!(typeof concurrency === 'number' && concurrency >= 1)) {
throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`);
}
const ret = [];
const iterator = iterable[Symbol.iterator]();
let isRejected = false;
let isIterableDone = false;
let resolvingCount = 0;
let currentIndex = 0;
const next = () => {
if (isRejected) {
return;
}
const nextItem = iterator.next();
const i = currentIndex;
currentIndex++;
if (nextItem.done) {
isIterableDone = true;
if (resolvingCount === 0) {
resolve(ret);
}
return;
}
resolvingCount++;
Promise.resolve(nextItem.value)
.then(element => mapper(element, i))
.then(
value => {
ret[i] = value;
resolvingCount--;
next();
},
error => {
isRejected = true;
reject(error);
}
);
};
for (let i = 0; i < concurrency; i++) {
next();
if (isIterableDone) {
break;
}
}
});
var pMap_1 = pMap;
var default_1 = pMap;
pMap_1.default = default_1;
// internal misc utilities
function values(object) {
return Object.keys(object).map(name => object[name]);
}
function flat(arr) {
return arr.reduce((a, b) => a.concat(b));
}
// export function trimNoNewLines(s: string): string {
// return s.replace(/^ +/, '').replace(/ +$/, '')
// }
// TODO: store variables from text file output and reuse them. example:
// `
// color=$(convert filename.png -format "%[pixel:p{0,0}]" info:foo.txt)
// convert filename.png -alpha off -bordercolor $color -border 1 \
// \( +clone -fuzz 30% -fill none -floodfill +0+0 $color \
// -alpha extract -geometry 200% -blur 0x0.5 \
// -morphology erode square:1 -geometry 50% \) \
// -compose CopyOpacity -composite -shave 1 outputfilename.png
// `
/**
* Generates a valid command line command from given `string[]` command. Works with a single command.
*/
function arrayToCliOne(command) {
return command
.map(c => c + '')
// if it contain spaces
.map(c => (c.trim().match(/\s/)) ? `'${c}'` : c)
// escape parenthesis
.map(c => c.trim() === '(' ? '\\(' : c.trim() === ')' ? '\\)' : c)
.join(' ');
}
/**
* Generates a valid command line string from given `string[]` that is compatible with {@link call}. Works with multiple
* commands by separating them with new lines and support comand splitting in new lines using `\`.
* See {@link ExecuteCommand} for more information.
*/
function arrayToCli(command) {
const cmd = typeof command[0] === 'string' ? [command] : command;
return cmd.map(arrayToCliOne).join('\n');
}
/**
* Generates a command in the form of array of strings, compatible with {@link call} from given command line string . The string must contain only one command (no newlines).
*/
function cliToArrayOne(cliCommand) {
let inString = false;
const spaceIndexes = [0];
for (let index = 0; index < cliCommand.length; index++) {
const c = cliCommand[index];
if (c.match(/[\s]/im) && !inString) {
spaceIndexes.push(index);
}
if (c === `'`) {
inString = !inString;
}
}
spaceIndexes.push(cliCommand.length);
const command = spaceIndexes
.map((spaceIndex, i) => cliCommand.substring(i === 0 ? 0 : spaceIndexes[i - 1], spaceIndexes[i]).trim())
.filter(s => !!s)
// remove quotes
.map(s => s.startsWith(`'`) ? s.substring(1, s.length) : s)
.map(s => s.endsWith(`'`) ? s.substring(0, s.length - 1) : s)
// unescape parenthesis
.map(s => s === `\\(` ? `(` : s === `\\)` ? `)` : s);
return command;
}
/**
* Generates a command in the form of `string[][]` that is compatible with {@link call} from given command line string.
* This works for strings containing multiple commands in different lines. and also respect `\` character for continue the same
* command in a new line. See {@link ExecuteCommand} for more information.
*/
function cliToArray(cliCommand) {
const lines = cliCommand.split('\n')
.map(s => s.trim()).map(cliToArrayOne)
.filter(a => a && a.length);
const result = [];
let currentCommand = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line[line.length - 1] !== '\\') {
currentCommand = currentCommand.concat(line);
result.push(currentCommand);
currentCommand = [];
}
else {
currentCommand = currentCommand.concat(line.slice(0, line.length - 1));
}
}
return result;
}
/**
* Makes sure that given {@link ExecuteCommand}, in whatever syntax, is transformed to the form `string[][]` that is compatible with {@link call}
*/
function asCommand(c) {
if (typeof c === 'string') {
return asCommand([c]);
}
if (!c[0]) {
return [];
}
if (typeof c[0] === 'string') {
return flat(c.map((subCommand) => cliToArray(subCommand)));
}
return c;
}
function blobToUint8Array(blob) {
return new Promise(resolve => {
const fileReader = new FileReader();
fileReader.onload = event => {
const result = event.target.result;
resolve(new Uint8Array(result));
};
fileReader.readAsArrayBuffer(blob);
});
}
function blobToString(blb) {
return new Promise(resolve => {
const reader = new FileReader();
reader.addEventListener('loadend', e => {
const text = e.srcElement.result;
resolve(text);
});
reader.readAsText(blb);
});
}
function isInputFile(file) {
return !!file.content;
}
function isOutputFile(file) {
return !!file.blob;
}
function uint8ArrayToString(arr, charset = 'utf-8') {
return new TextDecoder(charset).decode(arr);
}
/**
* Read files as string. Useful when files contains plain text like in the output file info.txt of `convert logo: -format '%[pixel:p{0,0}]' info:info.txt`
*/
function readFileAsText(file) {
return __awaiter(this, void 0, void 0, function* () {
if (isInputFile(file)) {
return uint8ArrayToString(file.content);
}
if (isOutputFile(file)) {
return yield blobToString(file.blob);
}
});
}
function isImage(file) {
return __awaiter(this, void 0, void 0, function* () {
const { exitCode } = yield execute$$1({ inputFiles: [yield asInputFile(file)], commands: `identify ${file.name}` });
return exitCode === 0;
});
}
/**
* Builds a new {@link MagickInputFile} by fetching the content of given url and optionally naming the file using given name
* or extracting the file name from the url otherwise.
*/
function buildInputFile(url, name = getFileName(url)) {
return __awaiter(this, void 0, void 0, function* () {
const fetchedSourceImage = yield fetch(url);
const arrayBuffer = yield fetchedSourceImage.arrayBuffer();
const content = new Uint8Array(arrayBuffer);
return { name, content };
});
}
function uint8ArrayToBlob(arr) {
return new Blob([arr]);
}
function outputFileToInputFile(file, name = file.name) {
return __awaiter(this, void 0, void 0, function* () {
return {
name,
content: yield blobToUint8Array(file.blob),
};
});
}
function inputFileToOutputFile(file, name = file.name) {
return {
name,
blob: uint8ArrayToBlob(file.content),
};
}
function asInputFile(f, name = f.name) {
return __awaiter(this, void 0, void 0, function* () {
let inputFile;
if (isOutputFile(f)) {
inputFile = yield outputFileToInputFile(f);
}
else {
inputFile = f;
}
inputFile.name = name;
return inputFile;
});
}
function asOutputFile(f, name = f.name) {
return __awaiter(this, void 0, void 0, function* () {
let outputFile;
if (isInputFile(f)) {
outputFile = inputFileToOutputFile(f);
}
else {
outputFile = f;
}
outputFile.name = name;
return outputFile;
});
}
function getFileName(url) {
try {
return decodeURIComponent(new URL(url).pathname.split('/').pop());
}
catch (error) {
const s = `http://foo.com/${url}`;
try {
return decodeURIComponent(new URL(s).pathname.split('/').pop());
}
catch (error) {
return url;
}
}
}
function getFileNameExtension(filePathOrUrl) {
const s = getFileName(filePathOrUrl);
return s.substring(s.lastIndexOf('.') + 1, s.length);
}
// utilities related to HTML (img) elements
/**
* Will load given html img element src with the inline image content.
* @param image the image to be loaded
* @param el the html image element in which to load the image
* @param forceBrowserSupport if true and the image extension is not supported by browsers, it will convert the image to png
* and return that src so it can be shown in browsers
*/
function loadImageElement$$1(image, el, forceBrowserSupport = false) {
return __awaiter(this, void 0, void 0, function* () {
el.src = yield buildImageSrc$$1(image, forceBrowserSupport);
});
}
/**
* Return a string with the inline image content, suitable to be used to assign to an html img src attribute. See {@link loadImageElement}.
* @param forceBrowserSupport if true and the image extension is not supported by browsers, it will convert the image to png
* and return that src so it can be shown in browsers
*/
function buildImageSrc$$1(image, forceBrowserSupport = false) {
return __awaiter(this, void 0, void 0, function* () {
let img = image;
const extension = getFileNameExtension(image.name);
if (!extension || forceBrowserSupport && browserSupportedImageExtensions.indexOf(extension) === -1) {
const { outputFiles } = yield execute$$1({ inputFiles: [yield asInputFile(image)], commands: `convert ${image.name} output.png` });
outputFiles[0].name = image.name;
img = outputFiles[0];
}
const outputFile = yield asOutputFile(img);
return URL.createObjectURL(outputFile.blob);
});
}
/**
* Build `MagickInputFile[]` from given HTMLInputElement of type=file that user may used to select several files
*/
function getInputFilesFromHtmlInputElement$$1(el) {
return __awaiter(this, void 0, void 0, function* () {
const files = yield inputFileToUint8Array(el);
return files.map(f => ({ name: f.file.name, content: f.content }));
});
}
const browserSupportedImageExtensions = ['gif', 'png', 'jpg', 'webp'];
function inputFileFiles(el) {
const files = [];
for (let i = 0; i < el.files.length; i++) {
const file = el.files.item(i);
files.push(file);
}
return files;
}
function inputFileToUint8Array(el) {
return __awaiter(this, void 0, void 0, function* () {
return Promise.all(inputFileFiles(el).map((file) => __awaiter(this, void 0, void 0, function* () {
const content = yield new Promise(resolve => {
const reader = new FileReader();
reader.addEventListener('loadend', e => {
resolve(new Uint8Array(reader.result));
});
reader.readAsArrayBuffer(file);
});
return { file, content };
})));
});
}
function getPixelColor$$1(img, x, y) {
return __awaiter(this, void 0, void 0, function* () {
const file = yield executeAndReturnOutputFile$$1({ inputFiles: [yield asInputFile(img)], commands: `convert ${img.name} -format '%[pixel:p{${x},${y}}]' info:info.txt` });
return yield readFileAsText(file);
});
}
let builtInImages;
const builtInImageNames$$1 = ['rose:', 'logo:', 'wizard:', 'granite:', 'netscape:'];
/**
* Gets ImageMagick built-in images like `rose:`, `logo:`, etc in the form of {@link MagickInputFile}s
*/
function getBuiltInImages$$1() {
return __awaiter(this, void 0, void 0, function* () {
if (!builtInImages) {
builtInImages = yield pMap_1(builtInImageNames$$1, (name) => __awaiter(this, void 0, void 0, function* () {
const info = yield extractInfo$$1(name);
const { outputFiles } = yield execute$$1({ commands: `convert ${name} ${`output1.${info[0].image.format.toLowerCase()}`}` });
outputFiles[0].name = name;
return yield asInputFile(outputFiles[0]);
}));
}
return builtInImages;
});
}
/**
* shortcut of {@link getBuiltInImages} to get a single image by name
*/
function getBuiltInImage$$1(name) {
return __awaiter(this, void 0, void 0, function* () {
const images = yield getBuiltInImages$$1();
return images.find(f => f.name === name);
});
}
/**
* Compare the two images and return true if they are equal visually. Optionally, a margin of error can be provided using `fuzz`
*/
function compare$$1(img1, img2, fuzz = 0.015) {
return __awaiter(this, void 0, void 0, function* () {
const identical = yield compareNumber$$1(img1, img2);
return identical <= fuzz;
});
}
function compareNumber$$1(img1, img2) {
return __awaiter(this, void 0, void 0, function* () {
const imgs = [];
let name1;
let name2;
if (typeof img1 !== 'string') {
const inputFile = yield asInputFile(img1);
imgs.push(inputFile);
name1 = inputFile.name;
}
else {
name1 = img1;
}
if (typeof img2 !== 'string') {
const inputFile = yield asInputFile(img2);
imgs.push(inputFile);
name2 = inputFile.name;
}
else {
name2 = img2;
}
const result = yield Call(imgs, ['convert', name1, name2, '-resize', '256x256^!', '-metric', 'RMSE', '-format', '%[distortion]', '-compare', 'info:info.txt']);
const n = yield blobToString(result[0].blob);
return parseFloat(n);
});
}
/**
* Execute `convert $IMG info.json` to extract image metadata. Returns the parsed info.json file contents
* @param img could be a string in case you want to extract information about built in images like `rose:`
*/
function extractInfo$$1(img) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: support several input images - we are already returning an array
let name;
let imgs;
if (typeof img !== 'string') {
imgs = [yield asInputFile(img)];
name = imgs[0].name;
}
else {
name = img;
imgs = [];
}
const processedFiles = yield Call(imgs, ['convert', name, 'info.json']);
try {
return JSON.parse(yield blobToString(processedFiles[0].blob));
}
catch (ex) {
return [{ error: ex }];
}
});
}
function getConfigureFolders$$1() {
return __awaiter(this, void 0, void 0, function* () {
const result = yield execute$$1(`convert -debug configure rose: info:`);
const contains = `Searching for configure file:`;
const folders = result.stderr
.filter(line => line.includes(contains))
.map(line => line.substring(line.indexOf(contains) + contains.length, line.length))
.map(s => s.replace(/\/\//g, '/'))
.map(s => s.substring(0, s.lastIndexOf('/')))
.map(s => s.replace(/"/g, '').trim());
return folders;
});
}
// has some heuristic information regarding features (not) supported by wasm-imagemagick, for example, image formats
// heads up - all images spec/assets/to_rotate.* where converted using gimp unless explicitly saying otherwise
/**
* list of image formats that are known to be supported by wasm-imagemagick. See `spec/formatSpec.ts`
*/
const knownSupportedReadWriteImageFormats$$1 = [
'jpg', 'png',
'psd',
'tiff', 'xcf', 'gif', 'bmp', 'tga', 'miff', 'ico', 'dcm', 'xpm', 'pcx',
// 'pix', // gives error
'fits',
// 'djvu', // read only support
'ppm',
'pgm',
'pfm',
'mng',
'hdr',
'dds',
'otb',
'txt',
];
/**
* Execute first command in given config.
*/
function executeOne$$1(configOrCommand) {
return __awaiter(this, void 0, void 0, function* () {
const config = asExecuteConfig$$1(configOrCommand);
let result = {
stderr: [],
stdout: [],
outputFiles: [],
exitCode: 1,
};
try {
config.inputFiles = config.inputFiles || [];
const command = asCommand(config.commands)[0];
const t0 = performance.now();
executeListeners.forEach(listener => listener.beforeExecute({ command, took: performance.now() - t0, id: t0 }));
result = yield call(config.inputFiles, command.map(c => c + ''));
executeListeners.forEach(listener => listener.afterExecute({ command, took: performance.now() - t0, id: t0 }));
if (result.exitCode) {
return Object.assign({}, result, { errors: ['exit code: ' + result.exitCode + ' stderr: ' + result.stderr.join('\n')] });
}
return Object.assign({}, result, { errors: [undefined] });
}
catch (error) {
return Object.assign({}, result, { errors: [error + ', exit code: ' + result.exitCode + ', stderr: ' + result.stderr.join('\n')] });
}
});
}
function isExecuteCommand$$1(arg) {
return !!arg.commands;
}
/**
* Transform `configOrCommand: ExecuteConfig | ExecuteCommand` to a valid ExecuteConfig object
*/
function asExecuteConfig$$1(arg) {
if (isExecuteCommand$$1(arg)) {
return arg;
}
return {
inputFiles: [],
commands: arg,
};
}
/**
* `execute()` shortcut that useful for commands that return only one output file or when only one particular output file is relevant.
* @param outputFileName optionally user can give the desired output file name
* @returns If `outputFileName` is given the file with that name, the first output file otherwise or undefined
* if no file match, or no output files where generated (like in an error).
*/
function executeAndReturnOutputFile$$1(configOrCommand, outputFileName) {
return __awaiter(this, void 0, void 0, function* () {
const config = asExecuteConfig$$1(configOrCommand);
const result = yield execute$$1(config);
return outputFileName ? result.outputFiles.find(f => f.name === outputFileName) : (result.outputFiles.length && result.outputFiles[0] || undefined);
});
}
const executeListeners = [];
function addExecuteListener$$1(l) {
executeListeners.push(l);
}
/**
* Execute all commands in given config serially in order. Output files from a command become available as
* input files in next commands. In the following example we execute two commands. Notice how the second one uses `image2.png` which was the output file of the first one:
*
* ```ts
* const { outputFiles, exitCode, stderr} = await execute({
* inputFiles: [await buildInputFile('fn.png', 'image1.png')],
* commands: `
* convert image1.png -bordercolor #ffee44 -background #eeff55 +polaroid image2.png
* convert image2.png -fill #997711 -tint 55 image3.jpg
* `
* })
* if (exitCode) {
* alert(`There was an error with the command: ${stderr.join('\n')}`)
* }
* else {
* await loadImageElement(outputFiles.find(f => f.name==='image3.jpg'), document.getElementById('outputImage'))
* }
* ```
*
* See {@link ExecuteCommand} for different command syntax supported.
*
* See {@link ExecuteResult} for details on the object returned
*/
function execute$$1(configOrCommand) {
return __awaiter(this, void 0, void 0, function* () {
const config = asExecuteConfig$$1(configOrCommand);
config.inputFiles = config.inputFiles || [];
const allOutputFiles = {};
const allInputFiles = {};
config.inputFiles.forEach(f => {
allInputFiles[f.name] = f;
});
let allErrors = [];
const results = [];
let allStdout = [];
let allStderr = [];
function mapper(c) {
return __awaiter(this, void 0, void 0, function* () {
const thisConfig = {
inputFiles: values(allInputFiles),
commands: [c],
};
const result = yield executeOne$$1(thisConfig);
results.push(result);
allErrors = allErrors.concat(result.errors || []);
allStdout = allStdout.concat(result.stdout || []);
allStderr = allStderr.concat(result.stderr || []);
yield pMap_1(result.outputFiles, (f) => __awaiter(this, void 0, void 0, function* () {
allOutputFiles[f.name] = f;
const inputFile = yield asInputFile(f);
allInputFiles[inputFile.name] = inputFile;
}));
});
}
const commands = asCommand(config.commands);
yield pMap_1(commands, mapper, { concurrency: 1 });
const resultWithError = results.find(r => r.exitCode !== 0);
return {
outputFiles: values(allOutputFiles),
errors: allErrors,
results,
stdout: allStdout,
stderr: allStderr,
exitCode: resultWithError ? resultWithError.exitCode : 0,
};
});
}
class ImageHomeImpl {
constructor() {
this.images = {};
this.builtInImagesAdded = false;
}
get(name) {
return this.images[name];
}
remove(names) {
const result = [];
Object.keys(this.images).forEach(name => {
if (names.indexOf(name) !== -1) {
result.push(this.images[name]);
delete this.images[name];
}
});
return result;
}
getAll() {
return __awaiter(this, void 0, void 0, function* () {
return yield Promise.all(values(this.images));
});
}
register(file, name = file.name) {
const promise = asInputFile(file);
this.images[name] = promise;
this.images[name].then(() => {
promise.resolved = true;
});
return promise;
}
isRegistered(name, andReady = true) {
return this.images[name] && (andReady && this.images[name].resolved);
}
addBuiltInImages() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.builtInImagesAdded) {
yield pMap_1(yield getBuiltInImages$$1(), img => this.register(img));
this.builtInImagesAdded = true;
}
});
}
}
function createImageHome$$1() { return new ImageHomeImpl(); }
class ExecutionContextImpl {
constructor(imageHome = createImageHome$$1()) {
this.imageHome = imageHome;
}
execute(configOrCommands) {
return __awaiter(this, void 0, void 0, function* () {
const config = asExecuteConfig$$1(configOrCommands);
config.inputFiles.forEach(f => {
this.imageHome.register(f);
});
const inputFiles = yield this.imageHome.getAll();
const result = yield execute$$1(Object.assign({}, config, { inputFiles }));
result.outputFiles.forEach(f => {
this.imageHome.register(f);
});
return result;
});
}
addFiles(files) {
files.forEach(f => this.imageHome.register(f));
}
getAllFiles() {
return __awaiter(this, void 0, void 0, function* () {
return yield this.imageHome.getAll();
});
}
getFile(name) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.imageHome.get(name);
});
}
addBuiltInImages() {
return __awaiter(this, void 0, void 0, function* () {
return this.imageHome.addBuiltInImages();
});
}
removeFiles(names) {
return this.imageHome.remove(names);
}
static create(inheritFrom) {
if (inheritFrom && !inheritFrom.imageHome) {
throw new Error('Dont know how to inherit from other ExecutionContext implementation than this one');
}
return new ExecutionContextImpl(inheritFrom && inheritFrom.imageHome);
}
}
function newExecutionContext$$1(inheritFrom) {
return ExecutionContextImpl.create(inheritFrom);
}
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var stackframe = createCommonjsModule(function (module, exports) {
(function(root, factory) {
// Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.
/* istanbul ignore next */
{
module.exports = factory();
}
}(commonjsGlobal, function() {
function _isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function _capitalize(str) {
return str.charAt(0).toUpperCase() + str.substring(1);
}
function _getter(p) {
return function() {
return this[p];
};
}
var booleanProps = ['isConstructor', 'isEval', 'isNative', 'isToplevel'];
var numericProps = ['columnNumber', 'lineNumber'];
var stringProps = ['fileName', 'functionName', 'source'];
var arrayProps = ['args'];
var props = booleanProps.concat(numericProps, stringProps, arrayProps);
function StackFrame(obj) {
if (obj instanceof Object) {
for (var i = 0; i < props.length; i++) {
if (obj.hasOwnProperty(props[i]) && obj[props[i]] !== undefined) {
this['set' + _capitalize(props[i])](obj[props[i]]);
}
}
}
}
StackFrame.prototype = {
getArgs: function() {
return this.args;
},
setArgs: function(v) {
if (Object.prototype.toString.call(v) !== '[object Array]') {
throw new TypeError('Args must be an Array');
}
this.args = v;
},
getEvalOrigin: function() {
return this.evalOrigin;
},
setEvalOrigin: function(v) {
if (v instanceof StackFrame) {
this.evalOrigin = v;
} else if (v instanceof Object) {
this.evalOrigin = new StackFrame(v);
} else {
throw new TypeError('Eval Origin must be an Object or StackFrame');
}
},
toString: function() {
var functionName = this.getFunctionName() || '{anonymous}';
var args = '(' + (this.getArgs() || []).join(',') + ')';
var fileName = this.getFileName() ? ('@' + this.getFileName()) : '';
var lineNumber = _isNumber(this.getLineNumber()) ? (':' + this.getLineNumber()) : '';
var columnNumber = _isNumber(this.getColumnNumber()) ? (':' + this.getColumnNumber()) : '';
return functionName + args + fileName + lineNumber + columnNumber;
}
};
for (var i = 0; i < booleanProps.length; i++) {
StackFrame.prototype['get' + _capitalize(booleanProps[i])] = _getter(booleanProps[i]);
StackFrame.prototype['set' + _capitalize(booleanProps[i])] = (function(p) {
return function(v) {
this[p] = Boolean(v);
};
})(booleanProps[i]);
}
for (var j = 0; j < numericProps.length; j++) {
StackFrame.prototype['get' + _capitalize(numericProps[j])] = _getter(numericProps[j]);
StackFrame.prototype['set' + _capitalize(numericProps[j])] = (function(p) {
return function(v) {
if (!_isNumber(v)) {
throw new TypeError(p + ' must be a Number');
}
this[p] = Number(v);
};
})(numericProps[j]);
}
for (var k = 0; k < stringProps.length; k++) {
StackFrame.prototype['get' + _capitalize(stringProps[k])] = _getter(stringProps[k]);
StackFrame.prototype['set' + _capitalize(stringProps[k])] = (function(p) {
return function(v) {
this[p] = String(v);
};
})(stringProps[k]);
}
return StackFrame;
}));
});
var errorStackParser = createCommonjsModule(function (module, exports) {
(function(root, factory) {
// Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.
/* istanbul ignore next */
{
module.exports = factory(stackframe);
}
}(commonjsGlobal, function ErrorStackParser(StackFrame) {
var FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+\:\d+/;
var CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+\:\d+|\(native\))/m;
var SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code\])?$/;
return {
/**
* Given an Error object, extract the most information from it.
*
* @param {Error} error object
* @return {Array} of StackFrames
*/
parse: function ErrorStackParser$$parse(error) {
if (typeof error.stacktrace !== 'undefined' || typeof error['opera#sourceloc'] !== 'undefined') {
return this.parseOpera(error);
} else if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) {
return this.parseV8OrIE(error);
} else if (error.stack) {
return this.parseFFOrSafari(error);
} else {
throw new Error('Cannot parse given Error object');
}
},
// Separate line and column numbers from a string of the form: (URI:Line:Column)
extractLocation: function ErrorStackParser$$extractLocation(urlLike) {
// Fail-fast but return locations like "(native)"
if (urlLike.indexOf(':') === -1) {
return [urlLike];
}
var regExp = /(.+?)(?:\:(\d+))?(?:\:(\d+))?$/;
var parts = regExp.exec(urlLike.replace(/[\(\)]/g, ''));
return [parts[1], parts[2] || undefined, parts[3] || undefined];
},
parseV8OrIE: function ErrorStackParser$$parseV8OrIE(error) {
var filtered = error.stack.split('\n').filter(function(line) {
return !!line.match(CHROME_IE_STACK_REGEXP);
}, this);
return filtered.map(function(line) {
if (line.indexOf('(eval ') > -1) {
// Throw away eval information until we implement stacktrace.js/stackframe#8
line = line.replace(/eval code/g, 'eval').replace(/(\(eval at [^\()]*)|(\)\,.*$)/g, '');
}
var tokens = line.replace(/^\s+/, '').replace(/\(eval code/g, '(').split(/\s+/).slice(1);
var locationParts = this.extractLocation(tokens.pop());
var functionName = tokens.join(' ') || undefined;
var fileName = ['eval', '<anonymous>'].indexOf(locationParts[0]) > -1 ? undefined : locationParts[0];
return new StackFrame({
functionName: functionName,
fileName: fileName,
lineNumber: locationParts[1],
columnNumber: locationParts[2],
source: line
});
}, this);
},
parseFFOrSafari: function ErrorStackParser$$parseFFOrSafari(error) {
var filtered = error.stack.split('\n').filter(function(line) {
return !line.match(SAFARI_NATIVE_CODE_REGEXP);
}, this);
return filtered.map(function(line) {
// Throw away eval information until we implement stacktrace.js/stackframe#8
if (line.indexOf(' > eval') > -1) {
line = line.replace(/ line (\d+)(?: > eval line \d+)* > eval\:\d+\:\d+/g, ':$1');
}
if (line.indexOf('@') === -1 && line.indexOf(':') === -1) {
// Safari eval frames only have function names and nothing else
return new StackFrame({
functionName: line
});
} else {
var functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/;
var matches = line.match(functionNameRegex);
var functionName = matches && matches[1] ? matches[1] : undefined;
var locationParts = this.extractLocation(line.replace(functionNameRegex, ''));
return new StackFrame({
functionName: functionName,
fileName: locationParts[0],
lineNumber: locationParts[1],
columnNumber: locationParts[2],
source: line
});
}
}, this);
},
parseOpera: function ErrorStackParser$$parseOpera(e) {
if (!e.stacktrace || (e.message.indexOf('\n') > -1 &&
e.message.split('\n').length > e.stacktrace.split('\n').length)) {
return this.parseOpera9(e);
} else if (!e.stack) {
return this.parseOpera10(e);
} else {
return this.parseOpera11(e);
}
},
parseOpera9: function ErrorStackParser$$parseOpera9(e) {
var lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
var lines = e.message.split('\n');
var result = [];
for (var i = 2, len = lines.length; i < len; i += 2) {
var match = lineRE.exec(lines[i]);
if (match) {
result.push(new StackFrame({
fileName: match[2],
lineNumber: match[1],
source: lines[i]
}));
}
}
return result;
},
parseOpera10: function ErrorStackParser$$parseOpera10(e) {
var lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
var lines = e.stacktrace.split('\n');
var result = [];
for (var i = 0, len = lines.length; i < len; i += 2) {
var match = lineRE.exec(lines[i]);
if (match) {
result.push(
new StackFrame({
functionName: match[3] || undefined,
fileName: match[2],
lineNumber: match[1],
source: lines[i]
})
);
}
}
return result;
},
// Opera 10.65+ Error.stack very similar to FF/Safari
parseOpera11: function ErrorStackParser$$parseOpera11(error) {
var filtered = error.stack.split('\n').filter(function(line) {
return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/);
}, this);
return filtered.map(function(line) {
var tokens = line.split('@');
var locationParts = this.extractLocation(tokens.pop());
var functionCall = (tokens.shift() || '');
var functionName = functionCall
.replace(/<anonymous function(: (\w+))?>/, '$2')
.replace(/\([^\)]*\)/g, '') || undefined;
var argsRaw;
if (functionCall.match(/\(([^\)]*)\)/)) {
argsRaw = functionCall.replace(/^[^\(]+\(([^\)]*)\)$/, '$1');
}
var args = (argsRaw === undefined || argsRaw === '[arguments not available]') ?
undefined : argsRaw.split(',');
return new StackFrame({
functionName: functionName,
args: args,
fileName: locationParts[0],
lineNumber: locationParts[1],
columnNumber: locationParts[2],
source: line
});
}, this);
}
};
}));
});
var stackGenerator = createCommonjsModule(function (module, exports) {
(function(root, factory) {
// Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.
/* istanbul ignore next */
{
module.exports = factory(stackframe);
}
}(commonjsGlobal, function(StackFrame) {
return {
backtrace: function StackGenerator$$backtrace(opts) {
var stack = [];
var maxStackSize = 10;
if (typeof opts === 'object' && typeof opts.maxStackSize === 'number') {
maxStackSize = opts.maxStackSize;
}
var curr = arguments.callee;
while (curr && stack.length < maxStackSize && curr['arguments']) {
// Allow V8 optimizations
var args = new Array(curr['arguments'].length);
for (var i = 0; i < args.length; ++i) {
args[i] = curr['arguments'][i];
}
if (/function(?:\s+([\w$]+))+\s*\(/.test(curr.toString())) {
stack.push(new StackFrame({functionName: RegExp.$1 || undefined, args: args}));
} else {
stack.push(new StackFrame({args: args}));
}
try {
curr = curr.caller;
} catch (e) {
break;
}
}
return stack;
}
};
}));
});
var util = createCommonjsModule(function (module, exports) {
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/**
* This is a helper function for getting values from parameter/options
* objects.
*
* @param args The object we are extracting values from
* @param name The name of the property we are getting.
* @param defaultValue An optional value to return if the property is missing
* from the object. If this is not specified and the property is missing, an
* error will be thrown.
*/
function getArg(aArgs, aName, aDefaultValue) {
if (aName in aArgs) {
return aArgs[aName];
} else if (arguments.length === 3) {
return aDefaultValue;
} else {
throw new Error('"' + aName + '" is a required argument.');
}
}
exports.getArg = getArg;
var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
var dataUrlRegexp = /^data:.+\,.+$/;
function urlParse(aUrl) {
var match = aUrl.match(urlRegexp);
if (!match) {
return null;
}
return {
scheme: match[1],
auth: match[2],
host: match[3],
port: match[4],
path: match[5]
};
}
exports.urlParse = urlParse;
function urlGenerate(aParsedUrl) {
var url = '';
if (aParsedUrl.scheme) {
url += aParsedUrl.scheme + ':';
}
url += '//';
if (aParsedUrl.auth) {
url += aParsedUrl.auth + '@';
}
if (aParsedUrl.host) {
url += aParsedUrl.host;
}
if (aParsedUrl.port) {
url += ":" + aParsedUrl.port;
}
if (aParsedUrl.path) {
url += aParsedUrl.path;
}
return url;
}
exports.urlGenerate = urlGenerate;
/**
* Normalizes a path, or the path portion of a URL:
*
* - Replaces consecutive slashes with one slash.
* - Removes unnecessary '.' parts.
* - Removes unnecessary '<dir>/..' parts.
*
* Based on code in the Node.js 'path' core module.
*
* @param aPath The path or url to normalize.
*/
function normalize(aPath) {
var path = aPath;
var url = urlParse(aPath);
if (url) {
if (!url.path) {
return aPath;
}
path = url.path;
}
var isAbsolute = exports.isAbsolute(path);
var parts = path.split(/\/+/);
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
part = parts[i];
if (part === '.') {
parts.splice(i, 1);
} else if (part === '..') {
up++;
} else if (up > 0) {
if (part === '') {
// The first part is blank if the path is absolute. Trying to go
// above the root is a no-op. Therefore we can remove all '..' parts
// directly after the root.
parts.splice(i + 1, up);
up = 0;
} else {
parts.splice(i, 2);
up--;
}
}
}
path = parts.join('/');
if (path === '') {
path = isAbsolute ? '/' : '.';
}
if (url) {
url.path = path;
return urlGenerate(url);
}
return path;
}
exports.normalize = normalize;
/**
* Joins two paths/URLs.
*
* @param aRoot The root path or URL.
* @param aPath The path or URL to be joined with the root.
*
* - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
* scheme-relative URL: Then the scheme of aRoot, if any, is prepended
* first.
* - Otherwise aPath is a path. If aRoot is a URL, then its path portion
* is updated with the result and aRoot is returned. Otherwise the result
* is returned.
* - If aPath is absolute, the result is aPath.
* - Otherwise the two paths are joined with a slash.
* - Joining for example 'http://' and 'www.example.com' is also supported.
*/
function join(aRoot, aPath) {
if (aRoot === "") {
aRoot = ".";
}
if (aPath === "") {
aPath = ".";
}
var aPathUrl = urlParse(aPath);
var aRootUrl = urlParse(aRoot);
if (aRootUrl) {
aRoot = aRootUrl.path || '/';
}
// `join(foo, '//www.example.org')`
if (aPathUrl && !aPathUrl.scheme) {
if (aRootUrl) {
aPathUrl.scheme = aRootUrl.scheme;
}
return urlGenerate(aPathUrl);
}
if (aPathUrl || aPath.match(dataUrlRegexp)) {
return aPath;
}
// `join('http://', 'www.example.com')`
if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
aRootUrl.host = aPath;
return urlGenerate(aRootUrl);
}
var joined = aPath.charAt(0) === '/'
? aPath
: normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
if (aRootUrl) {
aRootUrl.path = joined;
return urlGenerate(aRootUrl);
}
return joined;
}
exports.join = join;
exports.isAbsolute = function (aPath) {
return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
};
/**
* Make a path relative to a URL or another path.
*
* @param aRoot The root path or URL.
* @param aPath The path or URL to be made relative to aRoot.
*/
function relative(aRoot, aPath) {
if (aRoot === "") {
aRoot = ".";
}
aRoot = aRoot.replace(/\/$/, '');
// It is possible for the path to be above the root. In this case, simply
// checking whether the root is a prefix of the path won't work. Instead, we
// need to remove components from the root one by one, until either we find
// a prefix that fits, or we run out of components to remove.
var level = 0;
while (aPath.indexOf(aRoot + '/') !== 0) {
var index = aRoot.lastIndexOf("/");
if (index < 0) {
return aPath;
}
// If the only part of the root that is left is the scheme (i.e. http://,
// file:///, etc.), one or more slashes (/), or simply nothing at all, we
// have exhausted all components, so the path is not relative to the root.
aRoot = aRoot.slice(0, index);
if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
return aPath;
}
++level;
}
// Make sure we add a "../" for each component we removed from the root.
return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
}
exports.relative = relative;
var supportsNullProto = (function () {
var obj = Object.create(null);
return !('__proto__' in obj);
}());
function identity (s) {
return s;
}
/**
* Because behavior goes wacky when you set `__proto__` on objects, we
* have to prefix all the strings in our set with an arbitrary character.
*
* See https://github.com/mozilla/source-map/pull/31 and
* https://github.com/mozilla/source-map/issues/30
*
* @param String aStr
*/
function toSetString(aStr) {
if (isProtoString(aStr)) {
return '$' + aStr;
}
return aStr;
}
exports.toSetString = supportsNullProto ? identity : toSetString;
function fromSetString(aStr) {
if (isProtoString(aStr)) {
return aStr.slice(1);
}
return aStr;
}
exports.fromSetString = supportsNullProto ? identity : fromSetString;
function isProtoString(s) {
if (!s) {
return false;
}
var length = s.length;
if (length < 9 /* "__proto__".length */) {
return false;
}
if (s.charCodeAt(length - 1) !== 95 /* '_' */ ||
s.charCodeAt(length - 2) !== 95 /* '_' */ ||
s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
s.charCodeAt(length - 4) !== 116 /* 't' */ ||
s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
s.charCodeAt(length - 8) !== 95 /* '_' */ ||
s.charCodeAt(length - 9) !== 95 /* '_' */) {
return false;
}
for (var i = length - 10; i >= 0; i--) {
if (s.charCodeAt(i) !== 36 /* '$' */) {
return false;
}
}
return true;
}
/**
* Comparator between two mappings where the original positions are compared.
*
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
* mappings with the same original source/line/column, but different generated
* line and column the same. Useful when searching for a mapping with a
* stubbed out mapping.
*/
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
var cmp = mappingA.source - mappingB.source;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp !== 0 || onlyCompareOriginal) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp !== 0) {
return cmp;
}
return mappingA.name - mappingB.name;
}
exports.compareByOriginalPositions = compareByOriginalPositions;
/**
* Comparator between two mappings with deflated source and name indices where
* the generated positions are compared.
*
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
* mappings with the same generated line and column, but different
* source/name/original line and column the same. Useful when searching for a
* mapping with a stubbed out mapping.
*/
function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
var cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp !== 0 || onlyCompareGenerated) {
return cmp;
}
cmp = mappingA.source - mappingB.source;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp !== 0) {
return cmp;
}
return mappingA.name - mappingB.name;
}
exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
function strcmp(aStr1, aStr2) {
if (aStr1 === aStr2) {
return 0;
}
if (aStr1 > aStr2) {
return 1;
}
return -1;
}
/**
* Comparator between two mappings with inflated source and name strings where
* the generated positions are compared.
*/
function compareByGeneratedPositionsInflated(mappingA, mappingB) {
var cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp !== 0) {
return cmp;
}
cmp = strcmp(mappingA.source, mappingB.source);
if (cmp !== 0) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp !==