UNPKG

wasm-imagemagick

Version:
1,499 lines (1,372 loc) 203 kB
/*! ***************************************************************************** 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 !==