From 0e94ef9e6ff0e2045cdbd858008ca61578c84576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eddy=20Bru=C3=ABl?= Date: Wed, 11 Mar 2015 15:12:26 +0100 Subject: [PATCH] Bug 1136146 - Merge version 0.4.0 of the source-map library with fx-team;r=fitzgen --- .../test/browser_dbg_source-maps-01.js | 4 +- .../test/browser_dbg_source-maps-03.js | 4 +- toolkit/devtools/server/actors/script.js | 5 +- toolkit/devtools/sourcemap/SourceMap.jsm | 1450 +++++++++++------ toolkit/devtools/sourcemap/source-map.js | 1450 +++++++++++------ .../devtools/sourcemap/tests/unit/Utils.jsm | 119 +- .../sourcemap/tests/unit/test_base64_vlq.js | 5 +- .../tests/unit/test_binary_search.js | 46 +- .../sourcemap/tests/unit/test_dog_fooding.js | 69 +- .../tests/unit/test_source_map_consumer.js | 238 ++- .../tests/unit/test_source_map_generator.js | 63 + 11 files changed, 2436 insertions(+), 1017 deletions(-) diff --git a/browser/devtools/debugger/test/browser_dbg_source-maps-01.js b/browser/devtools/debugger/test/browser_dbg_source-maps-01.js index 4bbbb14be1a..1b3ba4d8620 100644 --- a/browser/devtools/debugger/test/browser_dbg_source-maps-01.js +++ b/browser/devtools/debugger/test/browser_dbg_source-maps-01.js @@ -79,7 +79,7 @@ function testSetBreakpointBlankLine() { let sourceForm = getSourceForm(gSources, COFFEE_URL); let source = gDebugger.gThreadClient.source(sourceForm); - source.setBreakpoint({ line: 3 }, aResponse => { + source.setBreakpoint({ line: 1 }, aResponse => { ok(!aResponse.error, "Should be able to set a breakpoint in a coffee source file on a blank line."); ok(aResponse.actualLocation, @@ -147,7 +147,7 @@ function testStepping() { is(aPacket.frame.environment.bindings.variables.start.value, 0, "'start' is 0."); is(aPacket.frame.environment.bindings.variables.stop.value, 5, - "'stop' hasn't been assigned to yet."); + "'stop' is 5."); is(aPacket.frame.environment.bindings.variables.pivot.value.type, "undefined", "'pivot' hasn't been assigned to yet."); diff --git a/browser/devtools/debugger/test/browser_dbg_source-maps-03.js b/browser/devtools/debugger/test/browser_dbg_source-maps-03.js index a7a48f361d4..fbaf2c0b1b2 100644 --- a/browser/devtools/debugger/test/browser_dbg_source-maps-03.js +++ b/browser/devtools/debugger/test/browser_dbg_source-maps-03.js @@ -45,11 +45,11 @@ function testSetBreakpoint() { let sourceForm = getSourceForm(gSources, JS_URL); let source = gDebugger.gThreadClient.source(sourceForm); - source.setBreakpoint({ line: 30, column: 21 }, aResponse => { + source.setBreakpoint({ line: 30 }, aResponse => { ok(!aResponse.error, "Should be able to set a breakpoint in a js file."); ok(!aResponse.actualLocation, - "Should be able to set a breakpoint on line 30 and column 10."); + "Should be able to set a breakpoint on line 30."); gDebugger.gClient.addOneTimeListener("resumed", () => { waitForCaretAndScopes(gPanel, 30).then(() => { diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index 3769f6b1f16..99c144a021c 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -2584,7 +2584,7 @@ SourceActor.prototype = { */ _invertSourceMap: function ({ code, mappings }) { const generator = new SourceMapGenerator({ file: this.url }); - return DevToolsUtils.yieldingEach(mappings, m => { + return DevToolsUtils.yieldingEach(mappings._array, m => { let mapping = { generated: { line: m.generatedLine, @@ -5698,7 +5698,8 @@ ThreadSources.prototype = { } = map.generatedPositionFor({ source: originalSourceActor.url, line: originalLine, - column: originalColumn == null ? Infinity : originalColumn + column: originalColumn == null ? 0 : originalColumn, + bias: SourceMapConsumer.LEAST_UPPER_BOUND }); return new GeneratedLocation( diff --git a/toolkit/devtools/sourcemap/SourceMap.jsm b/toolkit/devtools/sourcemap/SourceMap.jsm index c9709a19318..8189d3e049e 100644 --- a/toolkit/devtools/sourcemap/SourceMap.jsm +++ b/toolkit/devtools/sourcemap/SourceMap.jsm @@ -31,118 +31,26 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou var ArraySet = require('source-map/array-set').ArraySet; var base64VLQ = require('source-map/base64-vlq'); - /** - * A SourceMapConsumer instance represents a parsed source map which we can - * query for information about the original file positions by giving it a file - * position in the generated source. - * - * The only parameter is the raw source map (either as a JSON string, or - * already parsed to an object). According to the spec, source maps have the - * following attributes: - * - * - version: Which version of the source map spec this map is following. - * - sources: An array of URLs to the original source files. - * - names: An array of identifiers which can be referrenced by individual mappings. - * - sourceRoot: Optional. The URL root from which all sources are relative. - * - sourcesContent: Optional. An array of contents of the original source files. - * - mappings: A string of base64 VLQs which contain the actual mappings. - * - file: Optional. The generated file this source map is associated with. - * - * Here is an example source map, taken from the source map spec[0]: - * - * { - * version : 3, - * file: "out.js", - * sourceRoot : "", - * sources: ["foo.js", "bar.js"], - * names: ["src", "maps", "are", "fun"], - * mappings: "AA,AB;;ABCDE;" - * } - * - * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# - */ function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; if (typeof aSourceMap === 'string') { sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); } - var version = util.getArg(sourceMap, 'version'); - var sources = util.getArg(sourceMap, 'sources'); - // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which - // requires the array) to play nice here. - var names = util.getArg(sourceMap, 'names', []); - var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); - var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); - var mappings = util.getArg(sourceMap, 'mappings'); - var file = util.getArg(sourceMap, 'file', null); - - // Once again, Sass deviates from the spec and supplies the version as a - // string rather than a number, so we use loose equality checking here. - if (version != this._version) { - throw new Error('Unsupported version: ' + version); - } - - // Some source maps produce relative source paths like "./foo.js" instead of - // "foo.js". Normalize these first so that future comparisons will succeed. - // See bugzil.la/1090768. - sources = sources.map(util.normalize); - - // Pass `true` below to allow duplicate names and sources. While source maps - // are intended to be compressed and deduplicated, the TypeScript compiler - // sometimes generates source maps with duplicates in them. See Github issue - // #72 and bugzil.la/889492. - this._names = ArraySet.fromArray(names, true); - this._sources = ArraySet.fromArray(sources, true); - - this.sourceRoot = sourceRoot; - this.sourcesContent = sourcesContent; - this._mappings = mappings; - this.file = file; + return sourceMap.sections != null + ? new IndexedSourceMapConsumer(sourceMap) + : new BasicSourceMapConsumer(sourceMap); } - /** - * Create a SourceMapConsumer from a SourceMapGenerator. - * - * @param SourceMapGenerator aSourceMap - * The source map that will be consumed. - * @returns SourceMapConsumer - */ - SourceMapConsumer.fromSourceMap = - function SourceMapConsumer_fromSourceMap(aSourceMap) { - var smc = Object.create(SourceMapConsumer.prototype); - - smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); - smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); - smc.sourceRoot = aSourceMap._sourceRoot; - smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), - smc.sourceRoot); - smc.file = aSourceMap._file; - - smc.__generatedMappings = aSourceMap._mappings.slice() - .sort(util.compareByGeneratedPositions); - smc.__originalMappings = aSourceMap._mappings.slice() - .sort(util.compareByOriginalPositions); - - return smc; - }; + SourceMapConsumer.fromSourceMap = function(aSourceMap) { + return BasicSourceMapConsumer.fromSourceMap(aSourceMap); + } /** * The version of the source mapping spec that we are consuming. */ SourceMapConsumer.prototype._version = 3; - /** - * The list of original sources. - */ - Object.defineProperty(SourceMapConsumer.prototype, 'sources', { - get: function () { - return this._sources.toArray().map(function (s) { - return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s; - }, this); - } - }); - // `__generatedMappings` and `__originalMappings` are arrays that hold the // parsed mapping coordinates from the source map's "mappings" attribute. They // are lazily instantiated, accessed via the `_generatedMappings` and @@ -200,8 +108,8 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou }); SourceMapConsumer.prototype._nextCharIsMappingSeparator = - function SourceMapConsumer_nextCharIsMappingSeparator(aStr) { - var c = aStr.charAt(0); + function SourceMapConsumer_nextCharIsMappingSeparator(aStr, index) { + var c = aStr.charAt(index); return c === ";" || c === ","; }; @@ -212,332 +120,15 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou */ SourceMapConsumer.prototype._parseMappings = function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { - var generatedLine = 1; - var previousGeneratedColumn = 0; - var previousOriginalLine = 0; - var previousOriginalColumn = 0; - var previousSource = 0; - var previousName = 0; - var str = aStr; - var temp = {}; - var mapping; - - while (str.length > 0) { - if (str.charAt(0) === ';') { - generatedLine++; - str = str.slice(1); - previousGeneratedColumn = 0; - } - else if (str.charAt(0) === ',') { - str = str.slice(1); - } - else { - mapping = {}; - mapping.generatedLine = generatedLine; - - // Generated column. - base64VLQ.decode(str, temp); - mapping.generatedColumn = previousGeneratedColumn + temp.value; - previousGeneratedColumn = mapping.generatedColumn; - str = temp.rest; - - if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) { - // Original source. - base64VLQ.decode(str, temp); - mapping.source = this._sources.at(previousSource + temp.value); - previousSource += temp.value; - str = temp.rest; - if (str.length === 0 || this._nextCharIsMappingSeparator(str)) { - throw new Error('Found a source, but no line and column'); - } - - // Original line. - base64VLQ.decode(str, temp); - mapping.originalLine = previousOriginalLine + temp.value; - previousOriginalLine = mapping.originalLine; - // Lines are stored 0-based - mapping.originalLine += 1; - str = temp.rest; - if (str.length === 0 || this._nextCharIsMappingSeparator(str)) { - throw new Error('Found a source and line, but no column'); - } - - // Original column. - base64VLQ.decode(str, temp); - mapping.originalColumn = previousOriginalColumn + temp.value; - previousOriginalColumn = mapping.originalColumn; - str = temp.rest; - - if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) { - // Original name. - base64VLQ.decode(str, temp); - mapping.name = this._names.at(previousName + temp.value); - previousName += temp.value; - str = temp.rest; - } - } - - this.__generatedMappings.push(mapping); - if (typeof mapping.originalLine === 'number') { - this.__originalMappings.push(mapping); - } - } - } - - this.__generatedMappings.sort(util.compareByGeneratedPositions); - this.__originalMappings.sort(util.compareByOriginalPositions); - }; - - /** - * Find the mapping that best matches the hypothetical "needle" mapping that - * we are searching for in the given "haystack" of mappings. - */ - SourceMapConsumer.prototype._findMapping = - function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, - aColumnName, aComparator) { - // To return the position we are searching for, we must first find the - // mapping for the given position and then return the opposite position it - // points to. Because the mappings are sorted, we can use binary search to - // find the best mapping. - - if (aNeedle[aLineName] <= 0) { - throw new TypeError('Line must be greater than or equal to 1, got ' - + aNeedle[aLineName]); - } - if (aNeedle[aColumnName] < 0) { - throw new TypeError('Column must be greater than or equal to 0, got ' - + aNeedle[aColumnName]); - } - - return binarySearch.search(aNeedle, aMappings, aComparator); - }; - - /** - * Compute the last column for each generated mapping. The last column is - * inclusive. - */ - SourceMapConsumer.prototype.computeColumnSpans = - function SourceMapConsumer_computeColumnSpans() { - for (var index = 0; index < this._generatedMappings.length; ++index) { - var mapping = this._generatedMappings[index]; - - // Mappings do not contain a field for the last generated columnt. We - // can come up with an optimistic estimate, however, by assuming that - // mappings are contiguous (i.e. given two consecutive mappings, the - // first mapping ends where the second one starts). - if (index + 1 < this._generatedMappings.length) { - var nextMapping = this._generatedMappings[index + 1]; - - if (mapping.generatedLine === nextMapping.generatedLine) { - mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; - continue; - } - } - - // The last mapping for each line spans the entire line. - mapping.lastGeneratedColumn = Infinity; - } - }; - - /** - * Returns the original source, line, and column information for the generated - * source's line and column positions provided. The only argument is an object - * with the following properties: - * - * - line: The line number in the generated source. - * - column: The column number in the generated source. - * - * and an object is returned with the following properties: - * - * - source: The original source file, or null. - * - line: The line number in the original source, or null. - * - column: The column number in the original source, or null. - * - name: The original identifier, or null. - */ - SourceMapConsumer.prototype.originalPositionFor = - function SourceMapConsumer_originalPositionFor(aArgs) { - var needle = { - generatedLine: util.getArg(aArgs, 'line'), - generatedColumn: util.getArg(aArgs, 'column') - }; - - var index = this._findMapping(needle, - this._generatedMappings, - "generatedLine", - "generatedColumn", - util.compareByGeneratedPositions); - - if (index >= 0) { - var mapping = this._generatedMappings[index]; - - if (mapping.generatedLine === needle.generatedLine) { - var source = util.getArg(mapping, 'source', null); - if (source != null && this.sourceRoot != null) { - source = util.join(this.sourceRoot, source); - } - return { - source: source, - line: util.getArg(mapping, 'originalLine', null), - column: util.getArg(mapping, 'originalColumn', null), - name: util.getArg(mapping, 'name', null) - }; - } - } - - return { - source: null, - line: null, - column: null, - name: null - }; - }; - - /** - * Returns the original source content. The only argument is the url of the - * original source file. Returns null if no original source content is - * availible. - */ - SourceMapConsumer.prototype.sourceContentFor = - function SourceMapConsumer_sourceContentFor(aSource) { - if (!this.sourcesContent) { - return null; - } - - if (this.sourceRoot != null) { - aSource = util.relative(this.sourceRoot, aSource); - } - - if (this._sources.has(aSource)) { - return this.sourcesContent[this._sources.indexOf(aSource)]; - } - - var url; - if (this.sourceRoot != null - && (url = util.urlParse(this.sourceRoot))) { - // XXX: file:// URIs and absolute paths lead to unexpected behavior for - // many users. We can help them out when they expect file:// URIs to - // behave like it would if they were running a local HTTP server. See - // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. - var fileUriAbsPath = aSource.replace(/^file:\/\//, ""); - if (url.scheme == "file" - && this._sources.has(fileUriAbsPath)) { - return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] - } - - if ((!url.path || url.path == "/") - && this._sources.has("/" + aSource)) { - return this.sourcesContent[this._sources.indexOf("/" + aSource)]; - } - } - - throw new Error('"' + aSource + '" is not in the SourceMap.'); - }; - - /** - * Returns the generated line and column information for the original source, - * line, and column positions provided. The only argument is an object with - * the following properties: - * - * - source: The filename of the original source. - * - line: The line number in the original source. - * - column: The column number in the original source. - * - * and an object is returned with the following properties: - * - * - line: The line number in the generated source, or null. - * - column: The column number in the generated source, or null. - */ - SourceMapConsumer.prototype.generatedPositionFor = - function SourceMapConsumer_generatedPositionFor(aArgs) { - var needle = { - source: util.getArg(aArgs, 'source'), - originalLine: util.getArg(aArgs, 'line'), - originalColumn: util.getArg(aArgs, 'column') - }; - - if (this.sourceRoot != null) { - needle.source = util.relative(this.sourceRoot, needle.source); - } - - var index = this._findMapping(needle, - this._originalMappings, - "originalLine", - "originalColumn", - util.compareByOriginalPositions); - - if (index >= 0) { - var mapping = this._originalMappings[index]; - - return { - line: util.getArg(mapping, 'generatedLine', null), - column: util.getArg(mapping, 'generatedColumn', null), - lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) - }; - } - - return { - line: null, - column: null, - lastColumn: null - }; - }; - - /** - * Returns all generated line and column information for the original source - * and line provided. The only argument is an object with the following - * properties: - * - * - source: The filename of the original source. - * - line: The line number in the original source. - * - * and an array of objects is returned, each with the following properties: - * - * - line: The line number in the generated source, or null. - * - column: The column number in the generated source, or null. - */ - SourceMapConsumer.prototype.allGeneratedPositionsFor = - function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { - // When there is no exact match, SourceMapConsumer.prototype._findMapping - // returns the index of the closest mapping less than the needle. By - // setting needle.originalColumn to Infinity, we thus find the last - // mapping for the given line, provided such a mapping exists. - var needle = { - source: util.getArg(aArgs, 'source'), - originalLine: util.getArg(aArgs, 'line'), - originalColumn: Infinity - }; - - if (this.sourceRoot != null) { - needle.source = util.relative(this.sourceRoot, needle.source); - } - - var mappings = []; - - var index = this._findMapping(needle, - this._originalMappings, - "originalLine", - "originalColumn", - util.compareByOriginalPositions); - if (index >= 0) { - var mapping = this._originalMappings[index]; - - while (mapping && mapping.originalLine === needle.originalLine) { - mappings.push({ - line: util.getArg(mapping, 'generatedLine', null), - column: util.getArg(mapping, 'generatedColumn', null), - lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) - }); - - mapping = this._originalMappings[--index]; - } - } - - return mappings.reverse(); + throw new Error("Subclasses must implement _parseMappings"); }; SourceMapConsumer.GENERATED_ORDER = 1; SourceMapConsumer.ORIGINAL_ORDER = 2; + SourceMapConsumer.GREATEST_LOWER_BOUND = 1; + SourceMapConsumer.LEAST_UPPER_BOUND = 2; + /** * Iterate over each mapping between an original source/line/column and a * generated line/column in this source map. @@ -588,8 +179,789 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou }).forEach(aCallback, context); }; + /** + * Returns all generated line and column information for the original source + * and line provided. The only argument is an object with the following + * properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * + * and an array of objects is returned, each with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ + SourceMapConsumer.prototype.allGeneratedPositionsFor = + function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { + // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping + // returns the index of the closest mapping less than the needle. By + // setting needle.originalColumn to Infinity, we thus find the last + // mapping for the given line, provided such a mapping exists. + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: util.getArg(aArgs, 'line'), + originalColumn: 0 + }; + + if (this.sourceRoot != null) { + needle.source = util.relative(this.sourceRoot, needle.source); + } + + var mappings = []; + + var index = this._findMapping(needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + binarySearch.LEAST_UPPER_BOUND); + if (index >= 0) { + var mapping = this._originalMappings[index]; + + // Iterate until either we run out of mappings, or we run into + // a mapping for a different line. Since mappings are sorted, this is + // guaranteed to find all mappings for the line we are searching for. + while (mapping && mapping.originalLine === needle.originalLine) { + mappings.push({ + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }); + + mapping = this._originalMappings[++index]; + } + } + + return mappings; + }; + exports.SourceMapConsumer = SourceMapConsumer; + /** + * A BasicSourceMapConsumer instance represents a parsed source map which we can + * query for information about the original file positions by giving it a file + * position in the generated source. + * + * The only parameter is the raw source map (either as a JSON string, or + * already parsed to an object). According to the spec, source maps have the + * following attributes: + * + * - version: Which version of the source map spec this map is following. + * - sources: An array of URLs to the original source files. + * - names: An array of identifiers which can be referrenced by individual mappings. + * - sourceRoot: Optional. The URL root from which all sources are relative. + * - sourcesContent: Optional. An array of contents of the original source files. + * - mappings: A string of base64 VLQs which contain the actual mappings. + * - file: Optional. The generated file this source map is associated with. + * + * Here is an example source map, taken from the source map spec[0]: + * + * { + * version : 3, + * file: "out.js", + * sourceRoot : "", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AA,AB;;ABCDE;" + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# + */ + function BasicSourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sources = util.getArg(sourceMap, 'sources'); + // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which + // requires the array) to play nice here. + var names = util.getArg(sourceMap, 'names', []); + var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); + var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); + var mappings = util.getArg(sourceMap, 'mappings'); + var file = util.getArg(sourceMap, 'file', null); + + // Once again, Sass deviates from the spec and supplies the version as a + // string rather than a number, so we use loose equality checking here. + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + // Some source maps produce relative source paths like "./foo.js" instead of + // "foo.js". Normalize these first so that future comparisons will succeed. + // See bugzil.la/1090768. + sources = sources.map(util.normalize); + + // Pass `true` below to allow duplicate names and sources. While source maps + // are intended to be compressed and deduplicated, the TypeScript compiler + // sometimes generates source maps with duplicates in them. See Github issue + // #72 and bugzil.la/889492. + this._names = ArraySet.fromArray(names, true); + this._sources = ArraySet.fromArray(sources, true); + + this.sourceRoot = sourceRoot; + this.sourcesContent = sourcesContent; + this._mappings = mappings; + this.file = file; + } + + BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); + BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer; + + /** + * Create a BasicSourceMapConsumer from a SourceMapGenerator. + * + * @param SourceMapGenerator aSourceMap + * The source map that will be consumed. + * @returns BasicSourceMapConsumer + */ + BasicSourceMapConsumer.fromSourceMap = + function SourceMapConsumer_fromSourceMap(aSourceMap) { + var smc = Object.create(BasicSourceMapConsumer.prototype); + + smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); + smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); + smc.sourceRoot = aSourceMap._sourceRoot; + smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), + smc.sourceRoot); + smc.file = aSourceMap._file; + + smc.__generatedMappings = aSourceMap._mappings.toArray().slice(); + smc.__originalMappings = aSourceMap._mappings.toArray().slice() + .sort(util.compareByOriginalPositions); + + return smc; + }; + + /** + * The version of the source mapping spec that we are consuming. + */ + BasicSourceMapConsumer.prototype._version = 3; + + /** + * The list of original sources. + */ + Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', { + get: function () { + return this._sources.toArray().map(function (s) { + return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s; + }, this); + } + }); + + /** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ + BasicSourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + var generatedLine = 1; + var previousGeneratedColumn = 0; + var previousOriginalLine = 0; + var previousOriginalColumn = 0; + var previousSource = 0; + var previousName = 0; + var length = aStr.length; + var index = 0; + var cachedValues = {}; + var temp = {}; + var mapping, str, values, end, value; + + while (index < length) { + if (aStr.charAt(index) === ';') { + generatedLine++; + ++index; + previousGeneratedColumn = 0; + } + else if (aStr.charAt(index) === ',') { + ++index; + } + else { + mapping = {}; + mapping.generatedLine = generatedLine; + + // Because each offset is encoded relative to the previous one, + // many segments often have the same encoding. We can exploit this + // fact by caching the parsed variable length fields of each segment, + // allowing us to avoid a second parse if we encounter the same + // segment again. + for (end = index; end < length; ++end) { + if (this._nextCharIsMappingSeparator(aStr, end)) { + break; + } + } + str = aStr.slice(index, end); + + values = cachedValues[str]; + if (values) { + index += str.length; + } else { + values = []; + while (index < end) { + base64VLQ.decode(aStr, index, temp); + value = temp.value; + index = temp.rest; + values.push(value); + } + cachedValues[str] = values; + } + + // Generated column. + mapping.generatedColumn = previousGeneratedColumn + values[0]; + previousGeneratedColumn = mapping.generatedColumn; + + if (values.length > 1) { + // Original source. + mapping.source = this._sources.at(previousSource + values[1]); + previousSource += values[1]; + if (values.length === 2) { + throw new Error('Found a source, but no line and column'); + } + + // Original line. + mapping.originalLine = previousOriginalLine + values[2]; + previousOriginalLine = mapping.originalLine; + // Lines are stored 0-based + mapping.originalLine += 1; + if (values.length === 3) { + throw new Error('Found a source and line, but no column'); + } + + // Original column. + mapping.originalColumn = previousOriginalColumn + values[3]; + previousOriginalColumn = mapping.originalColumn; + + if (values.length > 4) { + // Original name. + mapping.name = this._names.at(previousName + values[4]); + previousName += values[4]; + } + } + + this.__generatedMappings.push(mapping); + if (typeof mapping.originalLine === 'number') { + this.__originalMappings.push(mapping); + } + } + } + + this.__generatedMappings.sort(util.compareByGeneratedPositions); + this.__originalMappings.sort(util.compareByOriginalPositions); + }; + + /** + * Find the mapping that best matches the hypothetical "needle" mapping that + * we are searching for in the given "haystack" of mappings. + */ + BasicSourceMapConsumer.prototype._findMapping = + function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, + aColumnName, aComparator, aBias) { + // To return the position we are searching for, we must first find the + // mapping for the given position and then return the opposite position it + // points to. Because the mappings are sorted, we can use binary search to + // find the best mapping. + + if (aNeedle[aLineName] <= 0) { + throw new TypeError('Line must be greater than or equal to 1, got ' + + aNeedle[aLineName]); + } + if (aNeedle[aColumnName] < 0) { + throw new TypeError('Column must be greater than or equal to 0, got ' + + aNeedle[aColumnName]); + } + + return binarySearch.search(aNeedle, aMappings, aComparator, aBias); + }; + + /** + * Compute the last column for each generated mapping. The last column is + * inclusive. + */ + BasicSourceMapConsumer.prototype.computeColumnSpans = + function SourceMapConsumer_computeColumnSpans() { + for (var index = 0; index < this._generatedMappings.length; ++index) { + var mapping = this._generatedMappings[index]; + + // Mappings do not contain a field for the last generated columnt. We + // can come up with an optimistic estimate, however, by assuming that + // mappings are contiguous (i.e. given two consecutive mappings, the + // first mapping ends where the second one starts). + if (index + 1 < this._generatedMappings.length) { + var nextMapping = this._generatedMappings[index + 1]; + + if (mapping.generatedLine === nextMapping.generatedLine) { + mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; + continue; + } + } + + // The last mapping for each line spans the entire line. + mapping.lastGeneratedColumn = Infinity; + } + }; + + /** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ + BasicSourceMapConsumer.prototype.originalPositionFor = + function SourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + var index = this._findMapping( + needle, + this._generatedMappings, + "generatedLine", + "generatedColumn", + util.compareByGeneratedPositions, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._generatedMappings[index]; + + if (mapping.generatedLine === needle.generatedLine) { + var source = util.getArg(mapping, 'source', null); + if (source != null && this.sourceRoot != null) { + source = util.join(this.sourceRoot, source); + } + return { + source: source, + line: util.getArg(mapping, 'originalLine', null), + column: util.getArg(mapping, 'originalColumn', null), + name: util.getArg(mapping, 'name', null) + }; + } + } + + return { + source: null, + line: null, + column: null, + name: null + }; + }; + + /** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * availible. + */ + BasicSourceMapConsumer.prototype.sourceContentFor = + function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + if (!this.sourcesContent) { + return null; + } + + if (this.sourceRoot != null) { + aSource = util.relative(this.sourceRoot, aSource); + } + + if (this._sources.has(aSource)) { + return this.sourcesContent[this._sources.indexOf(aSource)]; + } + + var url; + if (this.sourceRoot != null + && (url = util.urlParse(this.sourceRoot))) { + // XXX: file:// URIs and absolute paths lead to unexpected behavior for + // many users. We can help them out when they expect file:// URIs to + // behave like it would if they were running a local HTTP server. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. + var fileUriAbsPath = aSource.replace(/^file:\/\//, ""); + if (url.scheme == "file" + && this._sources.has(fileUriAbsPath)) { + return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] + } + + if ((!url.path || url.path == "/") + && this._sources.has("/" + aSource)) { + return this.sourcesContent[this._sources.indexOf("/" + aSource)]; + } + } + + // This function is used recursively from + // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we + // don't want to throw if we can't find the source - we just want to + // return null, so we provide a flag to exit gracefully. + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + + /** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ + BasicSourceMapConsumer.prototype.generatedPositionFor = + function SourceMapConsumer_generatedPositionFor(aArgs) { + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: util.getArg(aArgs, 'line'), + originalColumn: util.getArg(aArgs, 'column') + }; + + if (this.sourceRoot != null) { + needle.source = util.relative(this.sourceRoot, needle.source); + } + + var index = this._findMapping( + needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._originalMappings[index]; + + return { + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }; + } + + return { + line: null, + column: null, + lastColumn: null + }; + }; + + exports.BasicSourceMapConsumer = BasicSourceMapConsumer; + + /** + * An IndexedSourceMapConsumer instance represents a parsed source map which + * we can query for information. It differs from BasicSourceMapConsumer in + * that it takes "indexed" source maps (i.e. ones with a "sections" field) as + * input. + * + * The only parameter is a raw source map (either as a JSON string, or already + * parsed to an object). According to the spec for indexed source maps, they + * have the following attributes: + * + * - version: Which version of the source map spec this map is following. + * - file: Optional. The generated file this source map is associated with. + * - sections: A list of section definitions. + * + * Each value under the "sections" field has two fields: + * - offset: The offset into the original specified at which this section + * begins to apply, defined as an object with a "line" and "column" + * field. + * - map: A source map definition. This source map could also be indexed, + * but doesn't have to be. + * + * Instead of the "map" field, it's also possible to have a "url" field + * specifying a URL to retrieve a source map from, but that's currently + * unsupported. + * + * Here's an example source map, taken from the source map spec[0], but + * modified to omit a section which uses the "url" field. + * + * { + * version : 3, + * file: "app.js", + * sections: [{ + * offset: {line:100, column:10}, + * map: { + * version : 3, + * file: "section.js", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AAAA,E;;ABCDE;" + * } + * }], + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt + */ + function IndexedSourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sections = util.getArg(sourceMap, 'sections'); + + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + var lastOffset = { + line: -1, + column: 0 + }; + this._sections = sections.map(function (s) { + if (s.url) { + // The url field will require support for asynchronicity. + // See https://github.com/mozilla/source-map/issues/16 + throw new Error('Support for url field in sections not implemented.'); + } + var offset = util.getArg(s, 'offset'); + var offsetLine = util.getArg(offset, 'line'); + var offsetColumn = util.getArg(offset, 'column'); + + if (offsetLine < lastOffset.line || + (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { + throw new Error('Section offsets must be ordered and non-overlapping.'); + } + lastOffset = offset; + + return { + generatedOffset: { + // The offset fields are 0-based, but we use 1-based indices when + // encoding/decoding from VLQ. + generatedLine: offsetLine + 1, + generatedColumn: offsetColumn + 1 + }, + consumer: new SourceMapConsumer(util.getArg(s, 'map')) + } + }); + } + + IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); + IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; + + /** + * The version of the source mapping spec that we are consuming. + */ + IndexedSourceMapConsumer.prototype._version = 3; + + /** + * The list of original sources. + */ + Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { + get: function () { + var sources = []; + for (var i = 0; i < this._sections.length; i++) { + for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { + sources.push(this._sections[i].consumer.sources[j]); + } + }; + return sources; + } + }); + + /** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ + IndexedSourceMapConsumer.prototype.originalPositionFor = + function IndexedSourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + // Find the section containing the generated position we're trying to map + // to an original position. + var sectionIndex = binarySearch.search(needle, this._sections, + function(needle, section) { + var cmp = needle.generatedLine - section.generatedOffset.generatedLine; + if (cmp) { + return cmp; + } + + return (needle.generatedColumn - + section.generatedOffset.generatedColumn); + }); + var section = this._sections[sectionIndex]; + + if (!section) { + return { + source: null, + line: null, + column: null, + name: null + }; + } + + return section.consumer.originalPositionFor({ + line: needle.generatedLine - + (section.generatedOffset.generatedLine - 1), + column: needle.generatedColumn - + (section.generatedOffset.generatedLine === needle.generatedLine + ? section.generatedOffset.generatedColumn - 1 + : 0), + bias: aArgs.bias + }); + }; + + /** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * available. + */ + IndexedSourceMapConsumer.prototype.sourceContentFor = + function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + var content = section.consumer.sourceContentFor(aSource, true); + if (content) { + return content; + } + } + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + + /** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ + IndexedSourceMapConsumer.prototype.generatedPositionFor = + function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + // Only consider this section if the requested source is in the list of + // sources of the consumer. + if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) { + continue; + } + var generatedPosition = section.consumer.generatedPositionFor(aArgs); + if (generatedPosition) { + var ret = { + line: generatedPosition.line + + (section.generatedOffset.generatedLine - 1), + column: generatedPosition.column + + (section.generatedOffset.generatedLine === generatedPosition.line + ? section.generatedOffset.generatedColumn - 1 + : 0) + }; + return ret; + } + } + + return { + line: null, + column: null + }; + }; + + /** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ + IndexedSourceMapConsumer.prototype._parseMappings = + function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { + this.__generatedMappings = []; + this.__originalMappings = []; + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + var sectionMappings = section.consumer._generatedMappings; + for (var j = 0; j < sectionMappings.length; j++) { + var mapping = sectionMappings[i]; + + var source = mapping.source; + var sourceRoot = section.consumer.sourceRoot; + + if (source != null && sourceRoot != null) { + source = util.join(sourceRoot, source); + } + + // The mappings coming from the consumer for the section have + // generated positions relative to the start of the section, so we + // need to offset them to be relative to the start of the concatenated + // generated file. + var adjustedMapping = { + source: source, + generatedLine: mapping.generatedLine + + (section.generatedOffset.generatedLine - 1), + generatedColumn: mapping.column + + (section.generatedOffset.generatedLine === mapping.generatedLine) + ? section.generatedOffset.generatedColumn - 1 + : 0, + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: mapping.name + }; + + this.__generatedMappings.push(adjustedMapping); + if (typeof adjustedMapping.originalLine === 'number') { + this.__originalMappings.push(adjustedMapping); + } + }; + }; + + this.__generatedMappings.sort(util.compareByGeneratedPositions); + this.__originalMappings.sort(util.compareByOriginalPositions); + }; + + exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; + }); /* -*- Mode: js; js-indent-level: 2; -*- */ /* @@ -851,7 +1223,7 @@ define('source-map/util', ['require', 'exports', 'module' , ], function(require, return cmp; } - cmp = strcmp(mappingA.name, mappingB.name); + cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp) { return cmp; } @@ -861,7 +1233,7 @@ define('source-map/util', ['require', 'exports', 'module' , ], function(require, return cmp; } - return mappingA.generatedColumn - mappingB.generatedColumn; + return strcmp(mappingA.name, mappingB.name); }; exports.compareByOriginalPositions = compareByOriginalPositions; @@ -915,6 +1287,9 @@ define('source-map/util', ['require', 'exports', 'module' , ], function(require, */ define('source-map/binary-search', ['require', 'exports', 'module' , ], function(require, exports, module) { + exports.GREATEST_LOWER_BOUND = 1; + exports.LEAST_UPPER_BOUND = 2; + /** * Recursive implementation of binary search. * @@ -923,18 +1298,21 @@ define('source-map/binary-search', ['require', 'exports', 'module' , ], function * @param aNeedle The element being searched for. * @param aHaystack The non-empty array being searched. * @param aCompare Function which takes two elements and returns -1, 0, or 1. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. */ - function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare) { + function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { // This function terminates when one of the following is true: // // 1. We find the exact element we are looking for. // // 2. We did not find the exact element, but we can return the index of - // the next closest element that is less than that element. + // the next-closest element. // // 3. We did not find the exact element, and there is no next-closest - // element which is less than the one we are searching for, so we - // return -1. + // element than the one we are searching for, so we return -1. var mid = Math.floor((aHigh - aLow) / 2) + aLow; var cmp = aCompare(aNeedle, aHaystack[mid], true); if (cmp === 0) { @@ -942,45 +1320,76 @@ define('source-map/binary-search', ['require', 'exports', 'module' , ], function return mid; } else if (cmp > 0) { - // aHaystack[mid] is greater than our needle. + // Our needle is greater than aHaystack[mid]. if (aHigh - mid > 1) { // The element is in the upper half. - return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare); + return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); + } + + // The exact needle element was not found in this haystack. Determine if + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == exports.LEAST_UPPER_BOUND) { + return aHigh < aHaystack.length ? aHigh : -1; + } else { + return mid; } - // We did not find an exact match, return the next closest one - // (termination case 2). - return mid; } else { - // aHaystack[mid] is less than our needle. + // Our needle is less than aHaystack[mid]. if (mid - aLow > 1) { // The element is in the lower half. - return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare); + return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); + } + + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == exports.LEAST_UPPER_BOUND) { + return mid; + } else { + return aLow < 0 ? -1 : aLow; } - // The exact needle element was not found in this haystack. Determine if - // we are in termination case (2) or (3) and return the appropriate thing. - return aLow < 0 ? -1 : aLow; } } /** * This is an implementation of binary search which will always try and return - * the index of next lowest value checked if there is no exact hit. This is - * because mappings between original and generated line/col pairs are single - * points, and there is an implicit region between each of them, so a miss - * just means that you aren't on the very start of a region. + * the index of the closest element if there is no exact hit. This is because + * mappings between original and generated line/col pairs are single points, + * and there is an implicit region between each of them, so a miss just means + * that you aren't on the very start of a region. * * @param aNeedle The element you are looking for. * @param aHaystack The array that is being searched. * @param aCompare A function which takes the needle and an element in the * array and returns -1, 0, or 1 depending on whether the needle is less * than, equal to, or greater than the element, respectively. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. */ - exports.search = function search(aNeedle, aHaystack, aCompare) { + exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { if (aHaystack.length === 0) { return -1; } - return recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, aCompare) + + var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, + aCompare, aBias || exports.GREATEST_LOWER_BOUND); + if (index < 0) { + return -1; + } + + // We have found either the exact element, or the next-closest element than + // the one we are searching for. However, there may be more than one such + // element. Make sure we always return the smallest of these. + while (index - 1 > 0) { + if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { + break; + } + --index; + } + + return index; }; }); @@ -1143,7 +1552,7 @@ define('source-map/base64-vlq', ['require', 'exports', 'module' , 'source-map/b /** * Converts from a two-complement value to a value where the sign bit is - * is placed in the least significant bit. For example, as decimals: + * placed in the least significant bit. For example, as decimals: * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) */ @@ -1155,7 +1564,7 @@ define('source-map/base64-vlq', ['require', 'exports', 'module' , 'source-map/b /** * Converts to a two-complement value from a value where the sign bit is - * is placed in the least significant bit. For example, as decimals: + * placed in the least significant bit. For example, as decimals: * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 */ @@ -1194,18 +1603,17 @@ define('source-map/base64-vlq', ['require', 'exports', 'module' , 'source-map/b * Decodes the next base 64 VLQ value from the given string and returns the * value and the rest of the string via the out parameter. */ - exports.decode = function base64VLQ_decode(aStr, aOutParam) { - var i = 0; + exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { var strLen = aStr.length; var result = 0; var shift = 0; var continuation, digit; do { - if (i >= strLen) { + if (aIndex >= strLen) { throw new Error("Expected more digits in base 64 VLQ value."); } - digit = base64.decode(aStr.charAt(i++)); + digit = base64.decode(aStr.charAt(aIndex++)); continuation = !!(digit & VLQ_CONTINUATION_BIT); digit &= VLQ_BASE_MASK; result = result + (digit << shift); @@ -1213,7 +1621,7 @@ define('source-map/base64-vlq', ['require', 'exports', 'module' , 'source-map/b } while (continuation); aOutParam.value = fromVLQSigned(result); - aOutParam.rest = aStr.slice(i); + aOutParam.rest = aIndex; }; }); @@ -1262,11 +1670,12 @@ define('source-map/base64', ['require', 'exports', 'module' , ], function(requir * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause */ -define('source-map/source-map-generator', ['require', 'exports', 'module' , 'source-map/base64-vlq', 'source-map/util', 'source-map/array-set'], function(require, exports, module) { +define('source-map/source-map-generator', ['require', 'exports', 'module' , 'source-map/base64-vlq', 'source-map/util', 'source-map/array-set', 'source-map/mapping-list'], function(require, exports, module) { var base64VLQ = require('source-map/base64-vlq'); var util = require('source-map/util'); var ArraySet = require('source-map/array-set').ArraySet; + var MappingList = require('source-map/mapping-list').MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -1282,9 +1691,10 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so } this._file = util.getArg(aArgs, 'file', null); this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); + this._skipValidation = util.getArg(aArgs, 'skipValidation', false); this._sources = new ArraySet(); this._names = new ArraySet(); - this._mappings = []; + this._mappings = new MappingList(); this._sourcesContents = null; } @@ -1354,7 +1764,9 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so var source = util.getArg(aArgs, 'source', null); var name = util.getArg(aArgs, 'name', null); - this._validateMapping(generated, original, source, name); + if (!this._skipValidation) { + this._validateMapping(generated, original, source, name); + } if (source != null && !this._sources.has(source)) { this._sources.add(source); @@ -1364,7 +1776,7 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so this._names.add(name); } - this._mappings.push({ + this._mappings.add({ generatedLine: generated.line, generatedColumn: generated.column, originalLine: original != null && original.line, @@ -1441,7 +1853,7 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so var newNames = new ArraySet(); // Find mappings for the "sourceFile" - this._mappings.forEach(function (mapping) { + this._mappings.unsortedForEach(function (mapping) { if (mapping.source === sourceFile && mapping.originalLine != null) { // Check if it can be mapped by the source map, then update the mapping. var original = aSourceMapConsumer.originalPositionFor({ @@ -1547,15 +1959,10 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so var result = ''; var mapping; - // The mappings must be guaranteed to be in sorted order before we start - // serializing them or else the generated line numbers (which are defined - // via the ';' separators) will be all messed up. Note: it might be more - // performant to maintain the sorting as we insert them, rather than as we - // serialize them, but the big O is the same either way. - this._mappings.sort(util.compareByGeneratedPositions); + var mappings = this._mappings.toArray(); - for (var i = 0, len = this._mappings.length; i < len; i++) { - mapping = this._mappings[i]; + for (var i = 0, len = mappings.length; i < len; i++) { + mapping = mappings[i]; if (mapping.generatedLine !== previousGeneratedLine) { previousGeneratedColumn = 0; @@ -1566,7 +1973,7 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so } else { if (i > 0) { - if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) { + if (!util.compareByGeneratedPositions(mapping, mappings[i - 1])) { continue; } result += ','; @@ -1648,11 +2055,94 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so */ SourceMapGenerator.prototype.toString = function SourceMapGenerator_toString() { - return JSON.stringify(this); + return JSON.stringify(this.toJSON()); }; exports.SourceMapGenerator = SourceMapGenerator; +}); +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2014 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ +define('source-map/mapping-list', ['require', 'exports', 'module' , 'source-map/util'], function(require, exports, module) { + + var util = require('source-map/util'); + + /** + * Determine whether mappingB is after mappingA with respect to generated + * position. + */ + function generatedPositionAfter(mappingA, mappingB) { + // Optimized for most common case + var lineA = mappingA.generatedLine; + var lineB = mappingB.generatedLine; + var columnA = mappingA.generatedColumn; + var columnB = mappingB.generatedColumn; + return lineB > lineA || lineB == lineA && columnB >= columnA || + util.compareByGeneratedPositions(mappingA, mappingB) <= 0; + } + + /** + * A data structure to provide a sorted view of accumulated mappings in a + * performance conscious manner. It trades a neglibable overhead in general + * case for a large speedup in case of mappings being added in order. + */ + function MappingList() { + this._array = []; + this._sorted = true; + // Serves as infimum + this._last = {generatedLine: -1, generatedColumn: 0}; + } + + /** + * Iterate through internal items. This method takes the same arguments that + * `Array.prototype.forEach` takes. + * + * NOTE: The order of the mappings is NOT guaranteed. + */ + MappingList.prototype.unsortedForEach = + function MappingList_forEach(aCallback, aThisArg) { + this._array.forEach(aCallback, aThisArg); + }; + + /** + * Add the given source mapping. + * + * @param Object aMapping + */ + MappingList.prototype.add = function MappingList_add(aMapping) { + var mapping; + if (generatedPositionAfter(this._last, aMapping)) { + this._last = aMapping; + this._array.push(aMapping); + } else { + this._sorted = false; + this._array.push(aMapping); + } + }; + + /** + * Returns the flat, sorted array of mappings. The mappings are sorted by + * generated position. + * + * WARNING: This method returns internal data without copying, for + * performance. The return value must NOT be mutated, and should be treated as + * an immutable borrow. If you want to take ownership, you must make your own + * copy. + */ + MappingList.prototype.toArray = function MappingList_toArray() { + if (!this._sorted) { + this._array.sort(util.compareByGeneratedPositions); + this._sorted = true; + } + return this._array; + }; + + exports.MappingList = MappingList; + }); /* -*- Mode: js; js-indent-level: 2; -*- */ /* @@ -1669,8 +2159,13 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ // operating systems these days (capturing the result). var REGEX_NEWLINE = /(\r?\n)/; - // Matches a Windows-style newline, or any character. - var REGEX_CHARACTER = /\r\n|[\s\S]/g; + // Newline character code for charCodeAt() comparisons + var NEWLINE_CODE = 10; + + // Private symbol for identifying `SourceNode`s when multiple versions of + // the source-map library are loaded. This MUST NOT CHANGE across + // versions! + var isSourceNode = "$$$isSourceNode$$$"; /** * SourceNodes provide a way to abstract over interpolating/concatenating @@ -1691,6 +2186,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ this.column = aColumn == null ? null : aColumn; this.source = aSource == null ? null : aSource; this.name = aName == null ? null : aName; + this[isSourceNode] = true; if (aChunks != null) this.add(aChunks); } @@ -1821,7 +2317,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ this.add(chunk); }, this); } - else if (aChunk instanceof SourceNode || typeof aChunk === "string") { + else if (aChunk[isSourceNode] || typeof aChunk === "string") { if (aChunk) { this.children.push(aChunk); } @@ -1846,7 +2342,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ this.prepend(aChunk[i]); } } - else if (aChunk instanceof SourceNode || typeof aChunk === "string") { + else if (aChunk[isSourceNode] || typeof aChunk === "string") { this.children.unshift(aChunk); } else { @@ -1868,7 +2364,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ var chunk; for (var i = 0, len = this.children.length; i < len; i++) { chunk = this.children[i]; - if (chunk instanceof SourceNode) { + if (chunk[isSourceNode]) { chunk.walk(aFn); } else { @@ -1913,7 +2409,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ */ SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) { var lastChild = this.children[this.children.length - 1]; - if (lastChild instanceof SourceNode) { + if (lastChild[isSourceNode]) { lastChild.replaceRight(aPattern, aReplacement); } else if (typeof lastChild === 'string') { @@ -1946,7 +2442,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ SourceNode.prototype.walkSourceContents = function SourceNode_walkSourceContents(aFn) { for (var i = 0, len = this.children.length; i < len; i++) { - if (this.children[i] instanceof SourceNode) { + if (this.children[i][isSourceNode]) { this.children[i].walkSourceContents(aFn); } } @@ -2022,12 +2518,12 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ lastOriginalSource = null; sourceMappingActive = false; } - chunk.match(REGEX_CHARACTER).forEach(function (ch, idx, array) { - if (REGEX_NEWLINE.test(ch)) { + for (var idx = 0, length = chunk.length; idx < length; idx++) { + if (chunk.charCodeAt(idx) === NEWLINE_CODE) { generated.line++; generated.column = 0; // Mappings end at eol - if (idx + 1 === array.length) { + if (idx + 1 === length) { lastOriginalSource = null; sourceMappingActive = false; } else if (sourceMappingActive) { @@ -2045,9 +2541,9 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ }); } } else { - generated.column += ch.length; + generated.column++; } - }); + } }); this.walkSourceContents(function (sourceFile, sourceContent) { map.setSourceContent(sourceFile, sourceContent); diff --git a/toolkit/devtools/sourcemap/source-map.js b/toolkit/devtools/sourcemap/source-map.js index 3c0b1abce34..a605e6a3d6f 100644 --- a/toolkit/devtools/sourcemap/source-map.js +++ b/toolkit/devtools/sourcemap/source-map.js @@ -156,11 +156,12 @@ var require = define.globalDomain.require.bind(define.globalDomain); * Licensed under the New BSD license. See LICENSE or: * http://opensource.org/licenses/BSD-3-Clause */ -define('source-map/source-map-generator', ['require', 'exports', 'module' , 'source-map/base64-vlq', 'source-map/util', 'source-map/array-set'], function(require, exports, module) { +define('source-map/source-map-generator', ['require', 'exports', 'module' , 'source-map/base64-vlq', 'source-map/util', 'source-map/array-set', 'source-map/mapping-list'], function(require, exports, module) { var base64VLQ = require('./base64-vlq'); var util = require('./util'); var ArraySet = require('./array-set').ArraySet; + var MappingList = require('./mapping-list').MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -176,9 +177,10 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so } this._file = util.getArg(aArgs, 'file', null); this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); + this._skipValidation = util.getArg(aArgs, 'skipValidation', false); this._sources = new ArraySet(); this._names = new ArraySet(); - this._mappings = []; + this._mappings = new MappingList(); this._sourcesContents = null; } @@ -248,7 +250,9 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so var source = util.getArg(aArgs, 'source', null); var name = util.getArg(aArgs, 'name', null); - this._validateMapping(generated, original, source, name); + if (!this._skipValidation) { + this._validateMapping(generated, original, source, name); + } if (source != null && !this._sources.has(source)) { this._sources.add(source); @@ -258,7 +262,7 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so this._names.add(name); } - this._mappings.push({ + this._mappings.add({ generatedLine: generated.line, generatedColumn: generated.column, originalLine: original != null && original.line, @@ -335,7 +339,7 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so var newNames = new ArraySet(); // Find mappings for the "sourceFile" - this._mappings.forEach(function (mapping) { + this._mappings.unsortedForEach(function (mapping) { if (mapping.source === sourceFile && mapping.originalLine != null) { // Check if it can be mapped by the source map, then update the mapping. var original = aSourceMapConsumer.originalPositionFor({ @@ -441,15 +445,10 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so var result = ''; var mapping; - // The mappings must be guaranteed to be in sorted order before we start - // serializing them or else the generated line numbers (which are defined - // via the ';' separators) will be all messed up. Note: it might be more - // performant to maintain the sorting as we insert them, rather than as we - // serialize them, but the big O is the same either way. - this._mappings.sort(util.compareByGeneratedPositions); + var mappings = this._mappings.toArray(); - for (var i = 0, len = this._mappings.length; i < len; i++) { - mapping = this._mappings[i]; + for (var i = 0, len = mappings.length; i < len; i++) { + mapping = mappings[i]; if (mapping.generatedLine !== previousGeneratedLine) { previousGeneratedColumn = 0; @@ -460,7 +459,7 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so } else { if (i > 0) { - if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) { + if (!util.compareByGeneratedPositions(mapping, mappings[i - 1])) { continue; } result += ','; @@ -542,7 +541,7 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so */ SourceMapGenerator.prototype.toString = function SourceMapGenerator_toString() { - return JSON.stringify(this); + return JSON.stringify(this.toJSON()); }; exports.SourceMapGenerator = SourceMapGenerator; @@ -613,7 +612,7 @@ define('source-map/base64-vlq', ['require', 'exports', 'module' , 'source-map/b /** * Converts from a two-complement value to a value where the sign bit is - * is placed in the least significant bit. For example, as decimals: + * placed in the least significant bit. For example, as decimals: * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) */ @@ -625,7 +624,7 @@ define('source-map/base64-vlq', ['require', 'exports', 'module' , 'source-map/b /** * Converts to a two-complement value from a value where the sign bit is - * is placed in the least significant bit. For example, as decimals: + * placed in the least significant bit. For example, as decimals: * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 */ @@ -664,18 +663,17 @@ define('source-map/base64-vlq', ['require', 'exports', 'module' , 'source-map/b * Decodes the next base 64 VLQ value from the given string and returns the * value and the rest of the string via the out parameter. */ - exports.decode = function base64VLQ_decode(aStr, aOutParam) { - var i = 0; + exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { var strLen = aStr.length; var result = 0; var shift = 0; var continuation, digit; do { - if (i >= strLen) { + if (aIndex >= strLen) { throw new Error("Expected more digits in base 64 VLQ value."); } - digit = base64.decode(aStr.charAt(i++)); + digit = base64.decode(aStr.charAt(aIndex++)); continuation = !!(digit & VLQ_CONTINUATION_BIT); digit &= VLQ_BASE_MASK; result = result + (digit << shift); @@ -683,7 +681,7 @@ define('source-map/base64-vlq', ['require', 'exports', 'module' , 'source-map/b } while (continuation); aOutParam.value = fromVLQSigned(result); - aOutParam.rest = aStr.slice(i); + aOutParam.rest = aIndex; }; }); @@ -986,7 +984,7 @@ define('source-map/util', ['require', 'exports', 'module' , ], function(require, return cmp; } - cmp = strcmp(mappingA.name, mappingB.name); + cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp) { return cmp; } @@ -996,7 +994,7 @@ define('source-map/util', ['require', 'exports', 'module' , ], function(require, return cmp; } - return mappingA.generatedColumn - mappingB.generatedColumn; + return strcmp(mappingA.name, mappingB.name); }; exports.compareByOriginalPositions = compareByOriginalPositions; @@ -1135,6 +1133,89 @@ define('source-map/array-set', ['require', 'exports', 'module' , 'source-map/ut exports.ArraySet = ArraySet; +}); +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2014 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ +define('source-map/mapping-list', ['require', 'exports', 'module' , 'source-map/util'], function(require, exports, module) { + + var util = require('./util'); + + /** + * Determine whether mappingB is after mappingA with respect to generated + * position. + */ + function generatedPositionAfter(mappingA, mappingB) { + // Optimized for most common case + var lineA = mappingA.generatedLine; + var lineB = mappingB.generatedLine; + var columnA = mappingA.generatedColumn; + var columnB = mappingB.generatedColumn; + return lineB > lineA || lineB == lineA && columnB >= columnA || + util.compareByGeneratedPositions(mappingA, mappingB) <= 0; + } + + /** + * A data structure to provide a sorted view of accumulated mappings in a + * performance conscious manner. It trades a neglibable overhead in general + * case for a large speedup in case of mappings being added in order. + */ + function MappingList() { + this._array = []; + this._sorted = true; + // Serves as infimum + this._last = {generatedLine: -1, generatedColumn: 0}; + } + + /** + * Iterate through internal items. This method takes the same arguments that + * `Array.prototype.forEach` takes. + * + * NOTE: The order of the mappings is NOT guaranteed. + */ + MappingList.prototype.unsortedForEach = + function MappingList_forEach(aCallback, aThisArg) { + this._array.forEach(aCallback, aThisArg); + }; + + /** + * Add the given source mapping. + * + * @param Object aMapping + */ + MappingList.prototype.add = function MappingList_add(aMapping) { + var mapping; + if (generatedPositionAfter(this._last, aMapping)) { + this._last = aMapping; + this._array.push(aMapping); + } else { + this._sorted = false; + this._array.push(aMapping); + } + }; + + /** + * Returns the flat, sorted array of mappings. The mappings are sorted by + * generated position. + * + * WARNING: This method returns internal data without copying, for + * performance. The return value must NOT be mutated, and should be treated as + * an immutable borrow. If you want to take ownership, you must make your own + * copy. + */ + MappingList.prototype.toArray = function MappingList_toArray() { + if (!this._sorted) { + this._array.sort(util.compareByGeneratedPositions); + this._sorted = true; + } + return this._array; + }; + + exports.MappingList = MappingList; + }); /* -*- Mode: js; js-indent-level: 2; -*- */ /* @@ -1149,118 +1230,26 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou var ArraySet = require('./array-set').ArraySet; var base64VLQ = require('./base64-vlq'); - /** - * A SourceMapConsumer instance represents a parsed source map which we can - * query for information about the original file positions by giving it a file - * position in the generated source. - * - * The only parameter is the raw source map (either as a JSON string, or - * already parsed to an object). According to the spec, source maps have the - * following attributes: - * - * - version: Which version of the source map spec this map is following. - * - sources: An array of URLs to the original source files. - * - names: An array of identifiers which can be referrenced by individual mappings. - * - sourceRoot: Optional. The URL root from which all sources are relative. - * - sourcesContent: Optional. An array of contents of the original source files. - * - mappings: A string of base64 VLQs which contain the actual mappings. - * - file: Optional. The generated file this source map is associated with. - * - * Here is an example source map, taken from the source map spec[0]: - * - * { - * version : 3, - * file: "out.js", - * sourceRoot : "", - * sources: ["foo.js", "bar.js"], - * names: ["src", "maps", "are", "fun"], - * mappings: "AA,AB;;ABCDE;" - * } - * - * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# - */ function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; if (typeof aSourceMap === 'string') { sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); } - var version = util.getArg(sourceMap, 'version'); - var sources = util.getArg(sourceMap, 'sources'); - // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which - // requires the array) to play nice here. - var names = util.getArg(sourceMap, 'names', []); - var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); - var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); - var mappings = util.getArg(sourceMap, 'mappings'); - var file = util.getArg(sourceMap, 'file', null); - - // Once again, Sass deviates from the spec and supplies the version as a - // string rather than a number, so we use loose equality checking here. - if (version != this._version) { - throw new Error('Unsupported version: ' + version); - } - - // Some source maps produce relative source paths like "./foo.js" instead of - // "foo.js". Normalize these first so that future comparisons will succeed. - // See bugzil.la/1090768. - sources = sources.map(util.normalize); - - // Pass `true` below to allow duplicate names and sources. While source maps - // are intended to be compressed and deduplicated, the TypeScript compiler - // sometimes generates source maps with duplicates in them. See Github issue - // #72 and bugzil.la/889492. - this._names = ArraySet.fromArray(names, true); - this._sources = ArraySet.fromArray(sources, true); - - this.sourceRoot = sourceRoot; - this.sourcesContent = sourcesContent; - this._mappings = mappings; - this.file = file; + return sourceMap.sections != null + ? new IndexedSourceMapConsumer(sourceMap) + : new BasicSourceMapConsumer(sourceMap); } - /** - * Create a SourceMapConsumer from a SourceMapGenerator. - * - * @param SourceMapGenerator aSourceMap - * The source map that will be consumed. - * @returns SourceMapConsumer - */ - SourceMapConsumer.fromSourceMap = - function SourceMapConsumer_fromSourceMap(aSourceMap) { - var smc = Object.create(SourceMapConsumer.prototype); - - smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); - smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); - smc.sourceRoot = aSourceMap._sourceRoot; - smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), - smc.sourceRoot); - smc.file = aSourceMap._file; - - smc.__generatedMappings = aSourceMap._mappings.slice() - .sort(util.compareByGeneratedPositions); - smc.__originalMappings = aSourceMap._mappings.slice() - .sort(util.compareByOriginalPositions); - - return smc; - }; + SourceMapConsumer.fromSourceMap = function(aSourceMap) { + return BasicSourceMapConsumer.fromSourceMap(aSourceMap); + } /** * The version of the source mapping spec that we are consuming. */ SourceMapConsumer.prototype._version = 3; - /** - * The list of original sources. - */ - Object.defineProperty(SourceMapConsumer.prototype, 'sources', { - get: function () { - return this._sources.toArray().map(function (s) { - return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s; - }, this); - } - }); - // `__generatedMappings` and `__originalMappings` are arrays that hold the // parsed mapping coordinates from the source map's "mappings" attribute. They // are lazily instantiated, accessed via the `_generatedMappings` and @@ -1318,8 +1307,8 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou }); SourceMapConsumer.prototype._nextCharIsMappingSeparator = - function SourceMapConsumer_nextCharIsMappingSeparator(aStr) { - var c = aStr.charAt(0); + function SourceMapConsumer_nextCharIsMappingSeparator(aStr, index) { + var c = aStr.charAt(index); return c === ";" || c === ","; }; @@ -1330,332 +1319,15 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou */ SourceMapConsumer.prototype._parseMappings = function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { - var generatedLine = 1; - var previousGeneratedColumn = 0; - var previousOriginalLine = 0; - var previousOriginalColumn = 0; - var previousSource = 0; - var previousName = 0; - var str = aStr; - var temp = {}; - var mapping; - - while (str.length > 0) { - if (str.charAt(0) === ';') { - generatedLine++; - str = str.slice(1); - previousGeneratedColumn = 0; - } - else if (str.charAt(0) === ',') { - str = str.slice(1); - } - else { - mapping = {}; - mapping.generatedLine = generatedLine; - - // Generated column. - base64VLQ.decode(str, temp); - mapping.generatedColumn = previousGeneratedColumn + temp.value; - previousGeneratedColumn = mapping.generatedColumn; - str = temp.rest; - - if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) { - // Original source. - base64VLQ.decode(str, temp); - mapping.source = this._sources.at(previousSource + temp.value); - previousSource += temp.value; - str = temp.rest; - if (str.length === 0 || this._nextCharIsMappingSeparator(str)) { - throw new Error('Found a source, but no line and column'); - } - - // Original line. - base64VLQ.decode(str, temp); - mapping.originalLine = previousOriginalLine + temp.value; - previousOriginalLine = mapping.originalLine; - // Lines are stored 0-based - mapping.originalLine += 1; - str = temp.rest; - if (str.length === 0 || this._nextCharIsMappingSeparator(str)) { - throw new Error('Found a source and line, but no column'); - } - - // Original column. - base64VLQ.decode(str, temp); - mapping.originalColumn = previousOriginalColumn + temp.value; - previousOriginalColumn = mapping.originalColumn; - str = temp.rest; - - if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) { - // Original name. - base64VLQ.decode(str, temp); - mapping.name = this._names.at(previousName + temp.value); - previousName += temp.value; - str = temp.rest; - } - } - - this.__generatedMappings.push(mapping); - if (typeof mapping.originalLine === 'number') { - this.__originalMappings.push(mapping); - } - } - } - - this.__generatedMappings.sort(util.compareByGeneratedPositions); - this.__originalMappings.sort(util.compareByOriginalPositions); - }; - - /** - * Find the mapping that best matches the hypothetical "needle" mapping that - * we are searching for in the given "haystack" of mappings. - */ - SourceMapConsumer.prototype._findMapping = - function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, - aColumnName, aComparator) { - // To return the position we are searching for, we must first find the - // mapping for the given position and then return the opposite position it - // points to. Because the mappings are sorted, we can use binary search to - // find the best mapping. - - if (aNeedle[aLineName] <= 0) { - throw new TypeError('Line must be greater than or equal to 1, got ' - + aNeedle[aLineName]); - } - if (aNeedle[aColumnName] < 0) { - throw new TypeError('Column must be greater than or equal to 0, got ' - + aNeedle[aColumnName]); - } - - return binarySearch.search(aNeedle, aMappings, aComparator); - }; - - /** - * Compute the last column for each generated mapping. The last column is - * inclusive. - */ - SourceMapConsumer.prototype.computeColumnSpans = - function SourceMapConsumer_computeColumnSpans() { - for (var index = 0; index < this._generatedMappings.length; ++index) { - var mapping = this._generatedMappings[index]; - - // Mappings do not contain a field for the last generated columnt. We - // can come up with an optimistic estimate, however, by assuming that - // mappings are contiguous (i.e. given two consecutive mappings, the - // first mapping ends where the second one starts). - if (index + 1 < this._generatedMappings.length) { - var nextMapping = this._generatedMappings[index + 1]; - - if (mapping.generatedLine === nextMapping.generatedLine) { - mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; - continue; - } - } - - // The last mapping for each line spans the entire line. - mapping.lastGeneratedColumn = Infinity; - } - }; - - /** - * Returns the original source, line, and column information for the generated - * source's line and column positions provided. The only argument is an object - * with the following properties: - * - * - line: The line number in the generated source. - * - column: The column number in the generated source. - * - * and an object is returned with the following properties: - * - * - source: The original source file, or null. - * - line: The line number in the original source, or null. - * - column: The column number in the original source, or null. - * - name: The original identifier, or null. - */ - SourceMapConsumer.prototype.originalPositionFor = - function SourceMapConsumer_originalPositionFor(aArgs) { - var needle = { - generatedLine: util.getArg(aArgs, 'line'), - generatedColumn: util.getArg(aArgs, 'column') - }; - - var index = this._findMapping(needle, - this._generatedMappings, - "generatedLine", - "generatedColumn", - util.compareByGeneratedPositions); - - if (index >= 0) { - var mapping = this._generatedMappings[index]; - - if (mapping.generatedLine === needle.generatedLine) { - var source = util.getArg(mapping, 'source', null); - if (source != null && this.sourceRoot != null) { - source = util.join(this.sourceRoot, source); - } - return { - source: source, - line: util.getArg(mapping, 'originalLine', null), - column: util.getArg(mapping, 'originalColumn', null), - name: util.getArg(mapping, 'name', null) - }; - } - } - - return { - source: null, - line: null, - column: null, - name: null - }; - }; - - /** - * Returns the original source content. The only argument is the url of the - * original source file. Returns null if no original source content is - * availible. - */ - SourceMapConsumer.prototype.sourceContentFor = - function SourceMapConsumer_sourceContentFor(aSource) { - if (!this.sourcesContent) { - return null; - } - - if (this.sourceRoot != null) { - aSource = util.relative(this.sourceRoot, aSource); - } - - if (this._sources.has(aSource)) { - return this.sourcesContent[this._sources.indexOf(aSource)]; - } - - var url; - if (this.sourceRoot != null - && (url = util.urlParse(this.sourceRoot))) { - // XXX: file:// URIs and absolute paths lead to unexpected behavior for - // many users. We can help them out when they expect file:// URIs to - // behave like it would if they were running a local HTTP server. See - // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. - var fileUriAbsPath = aSource.replace(/^file:\/\//, ""); - if (url.scheme == "file" - && this._sources.has(fileUriAbsPath)) { - return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] - } - - if ((!url.path || url.path == "/") - && this._sources.has("/" + aSource)) { - return this.sourcesContent[this._sources.indexOf("/" + aSource)]; - } - } - - throw new Error('"' + aSource + '" is not in the SourceMap.'); - }; - - /** - * Returns the generated line and column information for the original source, - * line, and column positions provided. The only argument is an object with - * the following properties: - * - * - source: The filename of the original source. - * - line: The line number in the original source. - * - column: The column number in the original source. - * - * and an object is returned with the following properties: - * - * - line: The line number in the generated source, or null. - * - column: The column number in the generated source, or null. - */ - SourceMapConsumer.prototype.generatedPositionFor = - function SourceMapConsumer_generatedPositionFor(aArgs) { - var needle = { - source: util.getArg(aArgs, 'source'), - originalLine: util.getArg(aArgs, 'line'), - originalColumn: util.getArg(aArgs, 'column') - }; - - if (this.sourceRoot != null) { - needle.source = util.relative(this.sourceRoot, needle.source); - } - - var index = this._findMapping(needle, - this._originalMappings, - "originalLine", - "originalColumn", - util.compareByOriginalPositions); - - if (index >= 0) { - var mapping = this._originalMappings[index]; - - return { - line: util.getArg(mapping, 'generatedLine', null), - column: util.getArg(mapping, 'generatedColumn', null), - lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) - }; - } - - return { - line: null, - column: null, - lastColumn: null - }; - }; - - /** - * Returns all generated line and column information for the original source - * and line provided. The only argument is an object with the following - * properties: - * - * - source: The filename of the original source. - * - line: The line number in the original source. - * - * and an array of objects is returned, each with the following properties: - * - * - line: The line number in the generated source, or null. - * - column: The column number in the generated source, or null. - */ - SourceMapConsumer.prototype.allGeneratedPositionsFor = - function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { - // When there is no exact match, SourceMapConsumer.prototype._findMapping - // returns the index of the closest mapping less than the needle. By - // setting needle.originalColumn to Infinity, we thus find the last - // mapping for the given line, provided such a mapping exists. - var needle = { - source: util.getArg(aArgs, 'source'), - originalLine: util.getArg(aArgs, 'line'), - originalColumn: Infinity - }; - - if (this.sourceRoot != null) { - needle.source = util.relative(this.sourceRoot, needle.source); - } - - var mappings = []; - - var index = this._findMapping(needle, - this._originalMappings, - "originalLine", - "originalColumn", - util.compareByOriginalPositions); - if (index >= 0) { - var mapping = this._originalMappings[index]; - - while (mapping && mapping.originalLine === needle.originalLine) { - mappings.push({ - line: util.getArg(mapping, 'generatedLine', null), - column: util.getArg(mapping, 'generatedColumn', null), - lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) - }); - - mapping = this._originalMappings[--index]; - } - } - - return mappings.reverse(); + throw new Error("Subclasses must implement _parseMappings"); }; SourceMapConsumer.GENERATED_ORDER = 1; SourceMapConsumer.ORIGINAL_ORDER = 2; + SourceMapConsumer.GREATEST_LOWER_BOUND = 1; + SourceMapConsumer.LEAST_UPPER_BOUND = 2; + /** * Iterate over each mapping between an original source/line/column and a * generated line/column in this source map. @@ -1706,8 +1378,789 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou }).forEach(aCallback, context); }; + /** + * Returns all generated line and column information for the original source + * and line provided. The only argument is an object with the following + * properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * + * and an array of objects is returned, each with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ + SourceMapConsumer.prototype.allGeneratedPositionsFor = + function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { + // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping + // returns the index of the closest mapping less than the needle. By + // setting needle.originalColumn to Infinity, we thus find the last + // mapping for the given line, provided such a mapping exists. + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: util.getArg(aArgs, 'line'), + originalColumn: 0 + }; + + if (this.sourceRoot != null) { + needle.source = util.relative(this.sourceRoot, needle.source); + } + + var mappings = []; + + var index = this._findMapping(needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + binarySearch.LEAST_UPPER_BOUND); + if (index >= 0) { + var mapping = this._originalMappings[index]; + + // Iterate until either we run out of mappings, or we run into + // a mapping for a different line. Since mappings are sorted, this is + // guaranteed to find all mappings for the line we are searching for. + while (mapping && mapping.originalLine === needle.originalLine) { + mappings.push({ + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }); + + mapping = this._originalMappings[++index]; + } + } + + return mappings; + }; + exports.SourceMapConsumer = SourceMapConsumer; + /** + * A BasicSourceMapConsumer instance represents a parsed source map which we can + * query for information about the original file positions by giving it a file + * position in the generated source. + * + * The only parameter is the raw source map (either as a JSON string, or + * already parsed to an object). According to the spec, source maps have the + * following attributes: + * + * - version: Which version of the source map spec this map is following. + * - sources: An array of URLs to the original source files. + * - names: An array of identifiers which can be referrenced by individual mappings. + * - sourceRoot: Optional. The URL root from which all sources are relative. + * - sourcesContent: Optional. An array of contents of the original source files. + * - mappings: A string of base64 VLQs which contain the actual mappings. + * - file: Optional. The generated file this source map is associated with. + * + * Here is an example source map, taken from the source map spec[0]: + * + * { + * version : 3, + * file: "out.js", + * sourceRoot : "", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AA,AB;;ABCDE;" + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# + */ + function BasicSourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sources = util.getArg(sourceMap, 'sources'); + // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which + // requires the array) to play nice here. + var names = util.getArg(sourceMap, 'names', []); + var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); + var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); + var mappings = util.getArg(sourceMap, 'mappings'); + var file = util.getArg(sourceMap, 'file', null); + + // Once again, Sass deviates from the spec and supplies the version as a + // string rather than a number, so we use loose equality checking here. + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + // Some source maps produce relative source paths like "./foo.js" instead of + // "foo.js". Normalize these first so that future comparisons will succeed. + // See bugzil.la/1090768. + sources = sources.map(util.normalize); + + // Pass `true` below to allow duplicate names and sources. While source maps + // are intended to be compressed and deduplicated, the TypeScript compiler + // sometimes generates source maps with duplicates in them. See Github issue + // #72 and bugzil.la/889492. + this._names = ArraySet.fromArray(names, true); + this._sources = ArraySet.fromArray(sources, true); + + this.sourceRoot = sourceRoot; + this.sourcesContent = sourcesContent; + this._mappings = mappings; + this.file = file; + } + + BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); + BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer; + + /** + * Create a BasicSourceMapConsumer from a SourceMapGenerator. + * + * @param SourceMapGenerator aSourceMap + * The source map that will be consumed. + * @returns BasicSourceMapConsumer + */ + BasicSourceMapConsumer.fromSourceMap = + function SourceMapConsumer_fromSourceMap(aSourceMap) { + var smc = Object.create(BasicSourceMapConsumer.prototype); + + smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); + smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); + smc.sourceRoot = aSourceMap._sourceRoot; + smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), + smc.sourceRoot); + smc.file = aSourceMap._file; + + smc.__generatedMappings = aSourceMap._mappings.toArray().slice(); + smc.__originalMappings = aSourceMap._mappings.toArray().slice() + .sort(util.compareByOriginalPositions); + + return smc; + }; + + /** + * The version of the source mapping spec that we are consuming. + */ + BasicSourceMapConsumer.prototype._version = 3; + + /** + * The list of original sources. + */ + Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', { + get: function () { + return this._sources.toArray().map(function (s) { + return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s; + }, this); + } + }); + + /** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ + BasicSourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + var generatedLine = 1; + var previousGeneratedColumn = 0; + var previousOriginalLine = 0; + var previousOriginalColumn = 0; + var previousSource = 0; + var previousName = 0; + var length = aStr.length; + var index = 0; + var cachedValues = {}; + var temp = {}; + var mapping, str, values, end; + + while (index < length) { + if (aStr.charAt(index) === ';') { + generatedLine++; + ++index; + previousGeneratedColumn = 0; + } + else if (aStr.charAt(index) === ',') { + ++index; + } + else { + mapping = {}; + mapping.generatedLine = generatedLine; + + // Because each offset is encoded relative to the previous one, + // many segments often have the same encoding. We can exploit this + // fact by caching the parsed variable length fields of each segment, + // allowing us to avoid a second parse if we encounter the same + // segment again. + for (end = index; end < length; ++end) { + if (this._nextCharIsMappingSeparator(aStr, end)) { + break; + } + } + str = aStr.slice(index, end); + + values = cachedValues[str]; + if (values) { + index += str.length; + } else { + values = []; + while (index < end) { + base64VLQ.decode(aStr, index, temp); + value = temp.value; + index = temp.rest; + values.push(value); + } + cachedValues[str] = values; + } + + // Generated column. + mapping.generatedColumn = previousGeneratedColumn + values[0]; + previousGeneratedColumn = mapping.generatedColumn; + + if (values.length > 1) { + // Original source. + mapping.source = this._sources.at(previousSource + values[1]); + previousSource += values[1]; + if (values.length === 2) { + throw new Error('Found a source, but no line and column'); + } + + // Original line. + mapping.originalLine = previousOriginalLine + values[2]; + previousOriginalLine = mapping.originalLine; + // Lines are stored 0-based + mapping.originalLine += 1; + if (values.length === 3) { + throw new Error('Found a source and line, but no column'); + } + + // Original column. + mapping.originalColumn = previousOriginalColumn + values[3]; + previousOriginalColumn = mapping.originalColumn; + + if (values.length > 4) { + // Original name. + mapping.name = this._names.at(previousName + values[4]); + previousName += values[4]; + } + } + + this.__generatedMappings.push(mapping); + if (typeof mapping.originalLine === 'number') { + this.__originalMappings.push(mapping); + } + } + } + + this.__generatedMappings.sort(util.compareByGeneratedPositions); + this.__originalMappings.sort(util.compareByOriginalPositions); + }; + + /** + * Find the mapping that best matches the hypothetical "needle" mapping that + * we are searching for in the given "haystack" of mappings. + */ + BasicSourceMapConsumer.prototype._findMapping = + function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, + aColumnName, aComparator, aBias) { + // To return the position we are searching for, we must first find the + // mapping for the given position and then return the opposite position it + // points to. Because the mappings are sorted, we can use binary search to + // find the best mapping. + + if (aNeedle[aLineName] <= 0) { + throw new TypeError('Line must be greater than or equal to 1, got ' + + aNeedle[aLineName]); + } + if (aNeedle[aColumnName] < 0) { + throw new TypeError('Column must be greater than or equal to 0, got ' + + aNeedle[aColumnName]); + } + + return binarySearch.search(aNeedle, aMappings, aComparator, aBias); + }; + + /** + * Compute the last column for each generated mapping. The last column is + * inclusive. + */ + BasicSourceMapConsumer.prototype.computeColumnSpans = + function SourceMapConsumer_computeColumnSpans() { + for (var index = 0; index < this._generatedMappings.length; ++index) { + var mapping = this._generatedMappings[index]; + + // Mappings do not contain a field for the last generated columnt. We + // can come up with an optimistic estimate, however, by assuming that + // mappings are contiguous (i.e. given two consecutive mappings, the + // first mapping ends where the second one starts). + if (index + 1 < this._generatedMappings.length) { + var nextMapping = this._generatedMappings[index + 1]; + + if (mapping.generatedLine === nextMapping.generatedLine) { + mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; + continue; + } + } + + // The last mapping for each line spans the entire line. + mapping.lastGeneratedColumn = Infinity; + } + }; + + /** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ + BasicSourceMapConsumer.prototype.originalPositionFor = + function SourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + var index = this._findMapping( + needle, + this._generatedMappings, + "generatedLine", + "generatedColumn", + util.compareByGeneratedPositions, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._generatedMappings[index]; + + if (mapping.generatedLine === needle.generatedLine) { + var source = util.getArg(mapping, 'source', null); + if (source != null && this.sourceRoot != null) { + source = util.join(this.sourceRoot, source); + } + return { + source: source, + line: util.getArg(mapping, 'originalLine', null), + column: util.getArg(mapping, 'originalColumn', null), + name: util.getArg(mapping, 'name', null) + }; + } + } + + return { + source: null, + line: null, + column: null, + name: null + }; + }; + + /** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * availible. + */ + BasicSourceMapConsumer.prototype.sourceContentFor = + function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + if (!this.sourcesContent) { + return null; + } + + if (this.sourceRoot != null) { + aSource = util.relative(this.sourceRoot, aSource); + } + + if (this._sources.has(aSource)) { + return this.sourcesContent[this._sources.indexOf(aSource)]; + } + + var url; + if (this.sourceRoot != null + && (url = util.urlParse(this.sourceRoot))) { + // XXX: file:// URIs and absolute paths lead to unexpected behavior for + // many users. We can help them out when they expect file:// URIs to + // behave like it would if they were running a local HTTP server. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. + var fileUriAbsPath = aSource.replace(/^file:\/\//, ""); + if (url.scheme == "file" + && this._sources.has(fileUriAbsPath)) { + return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] + } + + if ((!url.path || url.path == "/") + && this._sources.has("/" + aSource)) { + return this.sourcesContent[this._sources.indexOf("/" + aSource)]; + } + } + + // This function is used recursively from + // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we + // don't want to throw if we can't find the source - we just want to + // return null, so we provide a flag to exit gracefully. + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + + /** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ + BasicSourceMapConsumer.prototype.generatedPositionFor = + function SourceMapConsumer_generatedPositionFor(aArgs) { + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: util.getArg(aArgs, 'line'), + originalColumn: util.getArg(aArgs, 'column') + }; + + if (this.sourceRoot != null) { + needle.source = util.relative(this.sourceRoot, needle.source); + } + + var index = this._findMapping( + needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._originalMappings[index]; + + return { + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }; + } + + return { + line: null, + column: null, + lastColumn: null + }; + }; + + exports.BasicSourceMapConsumer = BasicSourceMapConsumer; + + /** + * An IndexedSourceMapConsumer instance represents a parsed source map which + * we can query for information. It differs from BasicSourceMapConsumer in + * that it takes "indexed" source maps (i.e. ones with a "sections" field) as + * input. + * + * The only parameter is a raw source map (either as a JSON string, or already + * parsed to an object). According to the spec for indexed source maps, they + * have the following attributes: + * + * - version: Which version of the source map spec this map is following. + * - file: Optional. The generated file this source map is associated with. + * - sections: A list of section definitions. + * + * Each value under the "sections" field has two fields: + * - offset: The offset into the original specified at which this section + * begins to apply, defined as an object with a "line" and "column" + * field. + * - map: A source map definition. This source map could also be indexed, + * but doesn't have to be. + * + * Instead of the "map" field, it's also possible to have a "url" field + * specifying a URL to retrieve a source map from, but that's currently + * unsupported. + * + * Here's an example source map, taken from the source map spec[0], but + * modified to omit a section which uses the "url" field. + * + * { + * version : 3, + * file: "app.js", + * sections: [{ + * offset: {line:100, column:10}, + * map: { + * version : 3, + * file: "section.js", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AAAA,E;;ABCDE;" + * } + * }], + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt + */ + function IndexedSourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sections = util.getArg(sourceMap, 'sections'); + + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + var lastOffset = { + line: -1, + column: 0 + }; + this._sections = sections.map(function (s) { + if (s.url) { + // The url field will require support for asynchronicity. + // See https://github.com/mozilla/source-map/issues/16 + throw new Error('Support for url field in sections not implemented.'); + } + var offset = util.getArg(s, 'offset'); + var offsetLine = util.getArg(offset, 'line'); + var offsetColumn = util.getArg(offset, 'column'); + + if (offsetLine < lastOffset.line || + (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { + throw new Error('Section offsets must be ordered and non-overlapping.'); + } + lastOffset = offset; + + return { + generatedOffset: { + // The offset fields are 0-based, but we use 1-based indices when + // encoding/decoding from VLQ. + generatedLine: offsetLine + 1, + generatedColumn: offsetColumn + 1 + }, + consumer: new SourceMapConsumer(util.getArg(s, 'map')) + } + }); + } + + IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); + IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; + + /** + * The version of the source mapping spec that we are consuming. + */ + IndexedSourceMapConsumer.prototype._version = 3; + + /** + * The list of original sources. + */ + Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { + get: function () { + var sources = []; + for (var i = 0; i < this._sections.length; i++) { + for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { + sources.push(this._sections[i].consumer.sources[j]); + } + }; + return sources; + } + }); + + /** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ + IndexedSourceMapConsumer.prototype.originalPositionFor = + function IndexedSourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + // Find the section containing the generated position we're trying to map + // to an original position. + var sectionIndex = binarySearch.search(needle, this._sections, + function(needle, section) { + var cmp = needle.generatedLine - section.generatedOffset.generatedLine; + if (cmp) { + return cmp; + } + + return (needle.generatedColumn - + section.generatedOffset.generatedColumn); + }); + var section = this._sections[sectionIndex]; + + if (!section) { + return { + source: null, + line: null, + column: null, + name: null + }; + } + + return section.consumer.originalPositionFor({ + line: needle.generatedLine - + (section.generatedOffset.generatedLine - 1), + column: needle.generatedColumn - + (section.generatedOffset.generatedLine === needle.generatedLine + ? section.generatedOffset.generatedColumn - 1 + : 0), + bias: aArgs.bias + }); + }; + + /** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * available. + */ + IndexedSourceMapConsumer.prototype.sourceContentFor = + function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + var content = section.consumer.sourceContentFor(aSource, true); + if (content) { + return content; + } + } + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + + /** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ + IndexedSourceMapConsumer.prototype.generatedPositionFor = + function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + // Only consider this section if the requested source is in the list of + // sources of the consumer. + if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) { + continue; + } + var generatedPosition = section.consumer.generatedPositionFor(aArgs); + if (generatedPosition) { + var ret = { + line: generatedPosition.line + + (section.generatedOffset.generatedLine - 1), + column: generatedPosition.column + + (section.generatedOffset.generatedLine === generatedPosition.line + ? section.generatedOffset.generatedColumn - 1 + : 0) + }; + return ret; + } + } + + return { + line: null, + column: null + }; + }; + + /** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ + IndexedSourceMapConsumer.prototype._parseMappings = + function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { + this.__generatedMappings = []; + this.__originalMappings = []; + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + var sectionMappings = section.consumer._generatedMappings; + for (var j = 0; j < sectionMappings.length; j++) { + var mapping = sectionMappings[i]; + + var source = mapping.source; + var sourceRoot = section.consumer.sourceRoot; + + if (source != null && sourceRoot != null) { + source = util.join(sourceRoot, source); + } + + // The mappings coming from the consumer for the section have + // generated positions relative to the start of the section, so we + // need to offset them to be relative to the start of the concatenated + // generated file. + var adjustedMapping = { + source: source, + generatedLine: mapping.generatedLine + + (section.generatedOffset.generatedLine - 1), + generatedColumn: mapping.column + + (section.generatedOffset.generatedLine === mapping.generatedLine) + ? section.generatedOffset.generatedColumn - 1 + : 0, + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: mapping.name + }; + + this.__generatedMappings.push(adjustedMapping); + if (typeof adjustedMapping.originalLine === 'number') { + this.__originalMappings.push(adjustedMapping); + } + }; + }; + + this.__generatedMappings.sort(util.compareByGeneratedPositions); + this.__originalMappings.sort(util.compareByOriginalPositions); + }; + + exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; + }); /* -*- Mode: js; js-indent-level: 2; -*- */ /* @@ -1717,6 +2170,9 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou */ define('source-map/binary-search', ['require', 'exports', 'module' , ], function(require, exports, module) { + exports.GREATEST_LOWER_BOUND = 1; + exports.LEAST_UPPER_BOUND = 2; + /** * Recursive implementation of binary search. * @@ -1725,18 +2181,21 @@ define('source-map/binary-search', ['require', 'exports', 'module' , ], function * @param aNeedle The element being searched for. * @param aHaystack The non-empty array being searched. * @param aCompare Function which takes two elements and returns -1, 0, or 1. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. */ - function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare) { + function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { // This function terminates when one of the following is true: // // 1. We find the exact element we are looking for. // // 2. We did not find the exact element, but we can return the index of - // the next closest element that is less than that element. + // the next-closest element. // // 3. We did not find the exact element, and there is no next-closest - // element which is less than the one we are searching for, so we - // return -1. + // element than the one we are searching for, so we return -1. var mid = Math.floor((aHigh - aLow) / 2) + aLow; var cmp = aCompare(aNeedle, aHaystack[mid], true); if (cmp === 0) { @@ -1744,45 +2203,76 @@ define('source-map/binary-search', ['require', 'exports', 'module' , ], function return mid; } else if (cmp > 0) { - // aHaystack[mid] is greater than our needle. + // Our needle is greater than aHaystack[mid]. if (aHigh - mid > 1) { // The element is in the upper half. - return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare); + return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); + } + + // The exact needle element was not found in this haystack. Determine if + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == exports.LEAST_UPPER_BOUND) { + return aHigh < aHaystack.length ? aHigh : -1; + } else { + return mid; } - // We did not find an exact match, return the next closest one - // (termination case 2). - return mid; } else { - // aHaystack[mid] is less than our needle. + // Our needle is less than aHaystack[mid]. if (mid - aLow > 1) { // The element is in the lower half. - return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare); + return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); + } + + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == exports.LEAST_UPPER_BOUND) { + return mid; + } else { + return aLow < 0 ? -1 : aLow; } - // The exact needle element was not found in this haystack. Determine if - // we are in termination case (2) or (3) and return the appropriate thing. - return aLow < 0 ? -1 : aLow; } } /** * This is an implementation of binary search which will always try and return - * the index of next lowest value checked if there is no exact hit. This is - * because mappings between original and generated line/col pairs are single - * points, and there is an implicit region between each of them, so a miss - * just means that you aren't on the very start of a region. + * the index of the closest element if there is no exact hit. This is because + * mappings between original and generated line/col pairs are single points, + * and there is an implicit region between each of them, so a miss just means + * that you aren't on the very start of a region. * * @param aNeedle The element you are looking for. * @param aHaystack The array that is being searched. * @param aCompare A function which takes the needle and an element in the * array and returns -1, 0, or 1 depending on whether the needle is less * than, equal to, or greater than the element, respectively. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. */ - exports.search = function search(aNeedle, aHaystack, aCompare) { + exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { if (aHaystack.length === 0) { return -1; } - return recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, aCompare) + + var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, + aCompare, aBias || exports.GREATEST_LOWER_BOUND); + if (index < 0) { + return -1; + } + + // We have found either the exact element, or the next-closest element than + // the one we are searching for. However, there may be more than one such + // element. Make sure we always return the smallest of these. + while (index - 1 > 0) { + if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { + break; + } + --index; + } + + return index; }; }); @@ -1801,8 +2291,13 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ // operating systems these days (capturing the result). var REGEX_NEWLINE = /(\r?\n)/; - // Matches a Windows-style newline, or any character. - var REGEX_CHARACTER = /\r\n|[\s\S]/g; + // Newline character code for charCodeAt() comparisons + var NEWLINE_CODE = 10; + + // Private symbol for identifying `SourceNode`s when multiple versions of + // the source-map library are loaded. This MUST NOT CHANGE across + // versions! + var isSourceNode = "$$$isSourceNode$$$"; /** * SourceNodes provide a way to abstract over interpolating/concatenating @@ -1823,6 +2318,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ this.column = aColumn == null ? null : aColumn; this.source = aSource == null ? null : aSource; this.name = aName == null ? null : aName; + this[isSourceNode] = true; if (aChunks != null) this.add(aChunks); } @@ -1953,7 +2449,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ this.add(chunk); }, this); } - else if (aChunk instanceof SourceNode || typeof aChunk === "string") { + else if (aChunk[isSourceNode] || typeof aChunk === "string") { if (aChunk) { this.children.push(aChunk); } @@ -1978,7 +2474,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ this.prepend(aChunk[i]); } } - else if (aChunk instanceof SourceNode || typeof aChunk === "string") { + else if (aChunk[isSourceNode] || typeof aChunk === "string") { this.children.unshift(aChunk); } else { @@ -2000,7 +2496,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ var chunk; for (var i = 0, len = this.children.length; i < len; i++) { chunk = this.children[i]; - if (chunk instanceof SourceNode) { + if (chunk[isSourceNode]) { chunk.walk(aFn); } else { @@ -2045,7 +2541,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ */ SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) { var lastChild = this.children[this.children.length - 1]; - if (lastChild instanceof SourceNode) { + if (lastChild[isSourceNode]) { lastChild.replaceRight(aPattern, aReplacement); } else if (typeof lastChild === 'string') { @@ -2078,7 +2574,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ SourceNode.prototype.walkSourceContents = function SourceNode_walkSourceContents(aFn) { for (var i = 0, len = this.children.length; i < len; i++) { - if (this.children[i] instanceof SourceNode) { + if (this.children[i][isSourceNode]) { this.children[i].walkSourceContents(aFn); } } @@ -2154,12 +2650,12 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ lastOriginalSource = null; sourceMappingActive = false; } - chunk.match(REGEX_CHARACTER).forEach(function (ch, idx, array) { - if (REGEX_NEWLINE.test(ch)) { + for (var idx = 0, length = chunk.length; idx < length; idx++) { + if (chunk.charCodeAt(idx) === NEWLINE_CODE) { generated.line++; generated.column = 0; // Mappings end at eol - if (idx + 1 === array.length) { + if (idx + 1 === length) { lastOriginalSource = null; sourceMappingActive = false; } else if (sourceMappingActive) { @@ -2177,9 +2673,9 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/ }); } } else { - generated.column += ch.length; + generated.column++; } - }); + } }); this.walkSourceContents(function (sourceFile, sourceContent) { map.setSourceContent(sourceFile, sourceContent); diff --git a/toolkit/devtools/sourcemap/tests/unit/Utils.jsm b/toolkit/devtools/sourcemap/tests/unit/Utils.jsm index 17319182066..73cb33f5a95 100644 --- a/toolkit/devtools/sourcemap/tests/unit/Utils.jsm +++ b/toolkit/devtools/sourcemap/tests/unit/Utils.jsm @@ -126,6 +126,113 @@ define('test/source-map/util', ['require', 'exports', 'module' , 'lib/source-ma sourceRoot: '', mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA' }; + // This mapping is identical to above, but uses the indexed format instead. + exports.indexedTestMap = { + version: 3, + file: 'min.js', + sections: [ + { + offset: { + line: 0, + column: 0 + }, + map: { + version: 3, + sources: [ + "one.js" + ], + sourcesContent: [ + ' ONE.foo = function (bar) {\n' + + ' return baz(bar);\n' + + ' };', + ], + names: [ + "bar", + "baz" + ], + mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID", + file: "min.js", + sourceRoot: "/the/root" + } + }, + { + offset: { + line: 1, + column: 0 + }, + map: { + version: 3, + sources: [ + "two.js" + ], + sourcesContent: [ + ' TWO.inc = function (n) {\n' + + ' return n + 1;\n' + + ' };' + ], + names: [ + "n" + ], + mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA", + file: "min.js", + sourceRoot: "/the/root" + } + } + ] + }; + exports.indexedTestMapDifferentSourceRoots = { + version: 3, + file: 'min.js', + sections: [ + { + offset: { + line: 0, + column: 0 + }, + map: { + version: 3, + sources: [ + "one.js" + ], + sourcesContent: [ + ' ONE.foo = function (bar) {\n' + + ' return baz(bar);\n' + + ' };', + ], + names: [ + "bar", + "baz" + ], + mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID", + file: "min.js", + sourceRoot: "/the/root" + } + }, + { + offset: { + line: 1, + column: 0 + }, + map: { + version: 3, + sources: [ + "two.js" + ], + sourcesContent: [ + ' TWO.inc = function (n) {\n' + + ' return n + 1;\n' + + ' };' + ], + names: [ + "n" + ], + mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA", + file: "min.js", + sourceRoot: "/different/root" + } + } + ] + }; exports.testMapWithSourcesContent = { version: 3, file: 'min.js', @@ -168,12 +275,13 @@ define('test/source-map/util', ['require', 'exports', 'module' , 'lib/source-ma function assertMapping(generatedLine, generatedColumn, originalSource, - originalLine, originalColumn, name, map, assert, + originalLine, originalColumn, name, bias, map, assert, dontTestGenerated, dontTestOriginal) { if (!dontTestOriginal) { var origMapping = map.originalPositionFor({ line: generatedLine, - column: generatedColumn + column: generatedColumn, + bias: bias }); assert.equal(origMapping.name, name, 'Incorrect name, expected ' + JSON.stringify(name) @@ -206,7 +314,8 @@ define('test/source-map/util', ['require', 'exports', 'module' , 'lib/source-ma var genMapping = map.generatedPositionFor({ source: originalSource, line: originalLine, - column: originalColumn + column: originalColumn, + bias: bias }); assert.equal(genMapping.line, generatedLine, 'Incorrect line, expected ' + JSON.stringify(generatedLine) @@ -521,7 +630,7 @@ define('lib/source-map/util', ['require', 'exports', 'module' , ], function(requ return cmp; } - cmp = strcmp(mappingA.name, mappingB.name); + cmp = mappingA.generatedColumn - mappingB.generatedColumn; if (cmp) { return cmp; } @@ -531,7 +640,7 @@ define('lib/source-map/util', ['require', 'exports', 'module' , ], function(requ return cmp; } - return mappingA.generatedColumn - mappingB.generatedColumn; + return strcmp(mappingA.name, mappingB.name); }; exports.compareByOriginalPositions = compareByOriginalPositions; diff --git a/toolkit/devtools/sourcemap/tests/unit/test_base64_vlq.js b/toolkit/devtools/sourcemap/tests/unit/test_base64_vlq.js index 4b8c52efafe..4f5a597a283 100644 --- a/toolkit/devtools/sourcemap/tests/unit/test_base64_vlq.js +++ b/toolkit/devtools/sourcemap/tests/unit/test_base64_vlq.js @@ -19,9 +19,10 @@ define("test/source-map/test-base64-vlq", ["require", "exports", "module"], func exports['test normal encoding and decoding'] = function (assert, util) { var result = {}; for (var i = -255; i < 256; i++) { - base64VLQ.decode(base64VLQ.encode(i), result); + var str = base64VLQ.encode(i); + base64VLQ.decode(str, 0, result); assert.equal(result.value, i); - assert.equal(result.rest, ""); + assert.equal(result.rest, str.length); } }; diff --git a/toolkit/devtools/sourcemap/tests/unit/test_binary_search.js b/toolkit/devtools/sourcemap/tests/unit/test_binary_search.js index 896e6f4fd84..270e16288e7 100644 --- a/toolkit/devtools/sourcemap/tests/unit/test_binary_search.js +++ b/toolkit/devtools/sourcemap/tests/unit/test_binary_search.js @@ -20,7 +20,7 @@ define("test/source-map/test-binary-search", ["require", "exports", "module"], f return a - b; } - exports['test too high'] = function (assert, util) { + exports['test too high with default (glb) bias'] = function (assert, util) { var needle = 30; var haystack = [2,4,6,8,10,12,14,16,18,20]; @@ -31,7 +31,7 @@ define("test/source-map/test-binary-search", ["require", "exports", "module"], f assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 20); }; - exports['test too low'] = function (assert, util) { + exports['test too low with default (glb) bias'] = function (assert, util) { var needle = 1; var haystack = [2,4,6,8,10,12,14,16,18,20]; @@ -42,6 +42,30 @@ define("test/source-map/test-binary-search", ["require", "exports", "module"], f assert.equal(binarySearch.search(needle, haystack, numberCompare), -1); }; + exports['test too high with lub bias'] = function (assert, util) { + var needle = 30; + var haystack = [2,4,6,8,10,12,14,16,18,20]; + + assert.doesNotThrow(function () { + binarySearch.search(needle, haystack, numberCompare); + }); + + assert.equal(binarySearch.search(needle, haystack, numberCompare, + binarySearch.LEAST_UPPER_BOUND), -1); + }; + + exports['test too low with lub bias'] = function (assert, util) { + var needle = 1; + var haystack = [2,4,6,8,10,12,14,16,18,20]; + + assert.doesNotThrow(function () { + binarySearch.search(needle, haystack, numberCompare); + }); + + assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare, + binarySearch.LEAST_UPPER_BOUND)], 2); + }; + exports['test exact search'] = function (assert, util) { var needle = 4; var haystack = [2,4,6,8,10,12,14,16,18,20]; @@ -49,13 +73,29 @@ define("test/source-map/test-binary-search", ["require", "exports", "module"], f assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 4); }; - exports['test fuzzy search'] = function (assert, util) { + exports['test fuzzy search with default (glb) bias'] = function (assert, util) { var needle = 19; var haystack = [2,4,6,8,10,12,14,16,18,20]; assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 18); }; + exports['test fuzzy search with lub bias'] = function (assert, util) { + var needle = 19; + var haystack = [2,4,6,8,10,12,14,16,18,20]; + + assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare, + binarySearch.LEAST_UPPER_BOUND)], 20); + }; + + exports['test multiple matches'] = function (assert, util) { + var needle = 5; + var haystack = [1, 1, 2, 5, 5, 5, 13, 21]; + + assert.equal(binarySearch.search(needle, haystack, numberCompare, + binarySearch.LEAST_UPPER_BOUND), 3); + }; + }); function run_test() { runSourceMapTests('test/source-map/test-binary-search', do_throw); diff --git a/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js b/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js index 77017063b40..46c2d8d453e 100644 --- a/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js +++ b/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js @@ -56,34 +56,55 @@ define("test/source-map/test-dog-fooding", ["require", "exports", "module"], fun var smc = new SourceMapConsumer(smg.toString()); // Exact - util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 0, null, smc, assert); - util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 0, null, smc, assert); - util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 0, null, smc, assert); - util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, smc, assert); - util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 10, null, smc, assert); + util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 0, null, null, smc, assert); + util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 0, null, null, smc, assert); + util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 0, null, null, smc, assert); + util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, null, smc, assert); + util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 10, null, null, smc, assert); // Fuzzy - // Generated to original - util.assertMapping(2, 0, null, null, null, null, smc, assert, true); - util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true); - util.assertMapping(3, 0, null, null, null, null, smc, assert, true); - util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true); - util.assertMapping(4, 0, null, null, null, null, smc, assert, true); - util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true); - util.assertMapping(5, 0, null, null, null, null, smc, assert, true); - util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert, true); - util.assertMapping(6, 0, null, null, null, null, smc, assert, true); - util.assertMapping(6, 9, null, null, null, null, smc, assert, true); - util.assertMapping(6, 13, '/wu/tang/gza.coffee', 5, 10, null, smc, assert, true); + // Generated to original with default (glb) bias. + util.assertMapping(2, 0, null, null, null, null, null, smc, assert, true); + util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, null, smc, assert, true); + util.assertMapping(3, 0, null, null, null, null, null, smc, assert, true); + util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, null, smc, assert, true); + util.assertMapping(4, 0, null, null, null, null, null, smc, assert, true); + util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, null, smc, assert, true); + util.assertMapping(5, 0, null, null, null, null, null, smc, assert, true); + util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, null, smc, assert, true); + util.assertMapping(6, 0, null, null, null, null, null, smc, assert, true); + util.assertMapping(6, 9, null, null, null, null, null, smc, assert, true); + util.assertMapping(6, 13, '/wu/tang/gza.coffee', 5, 10, null, null, smc, assert, true); - // Original to generated - util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, smc, assert, null, true); - util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, smc, assert, null, true); - util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, smc, assert, null, true); - util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, smc, assert, null, true); - util.assertMapping(5, 2, '/wu/tang/gza.coffee', 5, 9, null, smc, assert, null, true); - util.assertMapping(6, 12, '/wu/tang/gza.coffee', 6, 19, null, smc, assert, null, true); + // Generated to original with lub bias. + util.assertMapping(2, 0, '/wu/tang/gza.coffee', 1, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(2, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(3, 0, '/wu/tang/gza.coffee', 2, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(3, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(4, 0, '/wu/tang/gza.coffee', 3, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(4, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(5, 0, '/wu/tang/gza.coffee', 4, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(5, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(6, 0, '/wu/tang/gza.coffee', 5, 10, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(6, 9, '/wu/tang/gza.coffee', 5, 10, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + util.assertMapping(6, 13, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true); + + // Original to generated with default (glb) bias + util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, null, smc, assert, null, true); + util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, null, smc, assert, null, true); + util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, null, smc, assert, null, true); + util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, null, smc, assert, null, true); + util.assertMapping(5, 2, '/wu/tang/gza.coffee', 5, 9, null, null, smc, assert, null, true); + util.assertMapping(6, 12, '/wu/tang/gza.coffee', 6, 19, null, null, smc, assert, null, true); + + // Original to generated with lub bias. + util.assertMapping(3, 2, '/wu/tang/gza.coffee', 1, 1, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true); + util.assertMapping(4, 2, '/wu/tang/gza.coffee', 2, 3, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true); + util.assertMapping(5, 2, '/wu/tang/gza.coffee', 3, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true); + util.assertMapping(6, 12, '/wu/tang/gza.coffee', 4, 9, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true); + util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 9, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true); + util.assertMapping(null, null, '/wu/tang/gza.coffee', 6, 19, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true); }; }); diff --git a/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js b/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js index 367646c1820..9d83aac13e8 100644 --- a/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js +++ b/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js @@ -15,6 +15,8 @@ Components.utils.import('resource://test/Utils.jsm'); define("test/source-map/test-source-map-consumer", ["require", "exports", "module"], function (require, exports, module) { var SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer; + var IndexedSourceMapConsumer = require('source-map/source-map-consumer').IndexedSourceMapConsumer; + var BasicSourceMapConsumer = require('source-map/source-map-consumer').BasicSourceMapConsumer; var SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator; exports['test that we can instantiate with a string or an object'] = function (assert, util) { @@ -26,6 +28,18 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul }); }; + exports['test that the object returned from new SourceMapConsumer inherits from SourceMapConsumer'] = function (assert, util) { + assert.ok(new SourceMapConsumer(util.testMap) instanceof SourceMapConsumer); + } + + exports['test that a BasicSourceMapConsumer is returned for sourcemaps without sections'] = function(assert, util) { + assert.ok(new SourceMapConsumer(util.testMap) instanceof BasicSourceMapConsumer); + }; + + exports['test that an IndexedSourceMapConsumer is returned for sourcemaps with sections'] = function(assert, util) { + assert.ok(new SourceMapConsumer(util.indexedTestMap) instanceof IndexedSourceMapConsumer); + }; + exports['test that the `sources` field has the original sources'] = function (assert, util) { var map; var sources; @@ -36,6 +50,18 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul assert.equal(sources[1], '/the/root/two.js'); assert.equal(sources.length, 2); + map = new SourceMapConsumer(util.indexedTestMap); + sources = map.sources; + assert.equal(sources[0], '/the/root/one.js'); + assert.equal(sources[1], '/the/root/two.js'); + assert.equal(sources.length, 2); + + map = new SourceMapConsumer(util.indexedTestMapDifferentSourceRoots); + sources = map.sources; + assert.equal(sources[0], '/the/root/one.js'); + assert.equal(sources[1], '/different/root/two.js'); + assert.equal(sources.length, 2); + map = new SourceMapConsumer(util.testMapNoSourceRoot); sources = map.sources; assert.equal(sources[0], 'one.js'); @@ -101,34 +127,107 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul exports['test mapping tokens back exactly'] = function (assert, util) { var map = new SourceMapConsumer(util.testMap); - util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, map, assert); - util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, map, assert); - util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, map, assert); - util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', map, assert); - util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, map, assert); - util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', map, assert); - util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', map, assert); + util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert); + util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert); + util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert); + util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert); + util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert); + util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert); + util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert); - util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, map, assert); - util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, map, assert); - util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, map, assert); - util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', map, assert); - util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, map, assert); - util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', map, assert); + util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert); + util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert); + util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert); + util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert); + util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert); + util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert); + }; + + exports['test mapping tokens back exactly in indexed source map'] = function (assert, util) { + var map = new SourceMapConsumer(util.indexedTestMap); + + util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert); + util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert); + util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert); + util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert); + util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert); + util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert); + util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert); + + util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert); + util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert); + util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert); + util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert); + util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert); + util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert); + }; + + + exports['test mapping tokens back exactly'] = function (assert, util) { + var map = new SourceMapConsumer(util.testMap); + + util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert); + util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert); + util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert); + util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert); + util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert); + util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert); + util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert); + + util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert); + util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert); + util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert); + util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert); + util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert); + util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert); }; exports['test mapping tokens fuzzy'] = function (assert, util) { var map = new SourceMapConsumer(util.testMap); - // Finding original positions - util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', map, assert, true); - util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', map, assert, true); - util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert, true); + // Finding original positions with default (glb) bias. + util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', null, map, assert, true); + util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', null, map, assert, true); + util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, null, map, assert, true); - // Finding generated positions - util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', map, assert, null, true); - util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', map, assert, null, true); - util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, map, assert, null, true); + // Finding original positions with lub bias. + util.assertMapping(1, 16, '/the/root/one.js', 1, 21, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true); + util.assertMapping(1, 26, '/the/root/one.js', 2, 10, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true); + util.assertMapping(2, 6, '/the/root/two.js', 1, 11, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true); + + // Finding generated positions with default (glb) bias. + util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', null, map, assert, null, true); + util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', null, map, assert, null, true); + util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, null, map, assert, null, true); + + // Finding generated positions with lub bias. + util.assertMapping(1, 18, '/the/root/one.js', 1, 20, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true); + util.assertMapping(1, 28, '/the/root/one.js', 2, 7, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true); + util.assertMapping(2, 9, '/the/root/two.js', 1, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true); + }; + + exports['test mapping tokens fuzzy in indexed source map'] = function (assert, util) { + var map = new SourceMapConsumer(util.indexedTestMap); + + // Finding original positions with default (glb) bias. + util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', null, map, assert, true); + util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', null, map, assert, true); + util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, null, map, assert, true); + + // Finding original positions with lub bias. + util.assertMapping(1, 16, '/the/root/one.js', 1, 21, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true); + util.assertMapping(1, 26, '/the/root/one.js', 2, 10, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true); + util.assertMapping(2, 6, '/the/root/two.js', 1, 11, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true); + + // Finding generated positions with default (glb) bias. + util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', null, map, assert, null, true); + util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', null, map, assert, null, true); + util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, null, map, assert, null, true); + + // Finding generated positions with lub bias. + util.assertMapping(1, 18, '/the/root/one.js', 1, 20, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true); + util.assertMapping(1, 28, '/the/root/one.js', 2, 7, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true); + util.assertMapping(2, 9, '/the/root/two.js', 1, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true); }; exports['test mappings and end of lines'] = function (assert, util) { @@ -149,10 +248,10 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul var map = SourceMapConsumer.fromSourceMap(smg); // When finding original positions, mappings end at the end of the line. - util.assertMapping(2, 1, null, null, null, null, map, assert, true) + util.assertMapping(2, 1, null, null, null, null, null, map, assert, true) // When finding generated positions, mappings do not end at the end of the line. - util.assertMapping(1, 1, 'bar.js', 2, 1, null, map, assert, null, true); + util.assertMapping(1, 1, 'bar.js', 2, 1, null, null, map, assert, null, true); }; exports['test creating source map consumers with )]}\' prefix'] = function (assert, util) { @@ -193,6 +292,29 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul }); }; + exports['test eachMapping for indexed source maps'] = function(assert, util) { + var map = new SourceMapConsumer(util.indexedTestMap); + var previousLine = -Infinity; + var previousColumn = -Infinity; + map.eachMapping(function (mapping) { + assert.ok(mapping.generatedLine >= previousLine); + + if (mapping.source) { + assert.equal(mapping.source.indexOf(util.testMap.sourceRoot), 0); + } + + if (mapping.generatedLine === previousLine) { + assert.ok(mapping.generatedColumn >= previousColumn); + previousColumn = mapping.generatedColumn; + } + else { + previousLine = mapping.generatedLine; + previousColumn = -Infinity; + } + }); + }; + + exports['test iterating over mappings in a different order'] = function (assert, util) { var map = new SourceMapConsumer(util.testMap); var previousLine = -Infinity; @@ -221,6 +343,34 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul }, null, SourceMapConsumer.ORIGINAL_ORDER); }; + exports['test iterating over mappings in a different order in indexed source maps'] = function (assert, util) { + var map = new SourceMapConsumer(util.indexedTestMap); + var previousLine = -Infinity; + var previousColumn = -Infinity; + var previousSource = ""; + map.eachMapping(function (mapping) { + assert.ok(mapping.source >= previousSource); + + if (mapping.source === previousSource) { + assert.ok(mapping.originalLine >= previousLine); + + if (mapping.originalLine === previousLine) { + assert.ok(mapping.originalColumn >= previousColumn); + previousColumn = mapping.originalColumn; + } + else { + previousLine = mapping.originalLine; + previousColumn = -Infinity; + } + } + else { + previousSource = mapping.source; + previousLine = -Infinity; + previousColumn = -Infinity; + } + }, null, SourceMapConsumer.ORIGINAL_ORDER); + }; + exports['test that we can set the context for `this` in eachMapping'] = function (assert, util) { var map = new SourceMapConsumer(util.testMap); var context = {}; @@ -229,6 +379,14 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul }, context); }; + exports['test that we can set the context for `this` in eachMapping in indexed source maps'] = function (assert, util) { + var map = new SourceMapConsumer(util.indexedTestMap); + var context = {}; + map.eachMapping(function () { + assert.equal(this, context); + }, context); + }; + exports['test that the `sourcesContent` field has the original sources'] = function (assert, util) { var map = new SourceMapConsumer(util.testMapWithSourcesContent); var sourcesContent = map.sourcesContent; @@ -276,6 +434,26 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul }, Error); }; + exports['test that we can get the original source content for the sources on an indexed source map'] = function (assert, util) { + var map = new SourceMapConsumer(util.indexedTestMap); + var sources = map.sources; + + assert.equal(map.sourceContentFor(sources[0]), ' ONE.foo = function (bar) {\n return baz(bar);\n };'); + assert.equal(map.sourceContentFor(sources[1]), ' TWO.inc = function (n) {\n return n + 1;\n };'); + assert.equal(map.sourceContentFor("one.js"), ' ONE.foo = function (bar) {\n return baz(bar);\n };'); + assert.equal(map.sourceContentFor("two.js"), ' TWO.inc = function (n) {\n return n + 1;\n };'); + assert.throws(function () { + map.sourceContentFor(""); + }, Error); + assert.throws(function () { + map.sourceContentFor("/the/root/three.js"); + }, Error); + assert.throws(function () { + map.sourceContentFor("three.js"); + }, Error); + }; + + exports['test sourceRoot + generatedPositionFor'] = function (assert, util) { var map = new SourceMapGenerator({ sourceRoot: 'foo/bar', @@ -544,6 +722,20 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul 'Source should be relative the host of the source root.'); }; + exports['test indexed source map errors when sections are out of order by line'] = function(assert, util) { + // Make a deep copy of the indexedTestMap + var misorderedIndexedTestMap = JSON.parse(JSON.stringify(util.indexedTestMap)); + + misorderedIndexedTestMap.sections[0].offset = { + line: 2, + column: 0 + }; + + assert.throws(function() { + new SourceMapConsumer(misorderedIndexedTestMap); + }, Error); + }; + exports['test github issue #64'] = function (assert, util) { var map = new SourceMapConsumer({ "version": 3, diff --git a/toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js b/toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js index 58582d9966e..a95e4aa8014 100644 --- a/toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js +++ b/toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js @@ -103,6 +103,27 @@ define("test/source-map/test-source-map-generator", ["require", "exports", "modu }); }; + exports['test adding mappings with skipValidation'] = function (assert, util) { + var map = new SourceMapGenerator({ + file: 'generated-foo.js', + sourceRoot: '.', + skipValidation: true + }); + + // Not enough info, caught by `util.getArgs` + assert.throws(function () { + map.addMapping({}); + }); + + // Original file position, but no source. Not checked. + assert.doesNotThrow(function () { + map.addMapping({ + generated: { line: 1, column: 1 }, + original: { line: 1, column: 1 } + }); + }); + }; + exports['test that the correct mappings are being generated'] = function (assert, util) { var map = new SourceMapGenerator({ file: 'min.js', @@ -660,6 +681,48 @@ define("test/source-map/test-source-map-generator", ["require", "exports", "modu }); }; + exports['test applySourceMap with unexact match'] = function (assert, util) { + var map1 = new SourceMapGenerator({ + file: 'bundled-source' + }); + map1.addMapping({ + generated: { line: 1, column: 4 }, + original: { line: 1, column: 4 }, + source: 'transformed-source' + }); + map1.addMapping({ + generated: { line: 2, column: 4 }, + original: { line: 2, column: 4 }, + source: 'transformed-source' + }); + + var map2 = new SourceMapGenerator({ + file: 'transformed-source' + }); + map2.addMapping({ + generated: { line: 2, column: 0 }, + original: { line: 1, column: 0 }, + source: 'original-source' + }); + + var expectedMap = new SourceMapGenerator({ + file: 'bundled-source' + }); + expectedMap.addMapping({ + generated: { line: 1, column: 4 }, + original: { line: 1, column: 4 }, + source: 'transformed-source' + }); + expectedMap.addMapping({ + generated: { line: 2, column: 4 }, + original: { line: 1, column: 0 }, + source: 'original-source' + }); + + map1.applySourceMap(new SourceMapConsumer(map2.toJSON())); + + util.assertEqualMaps(assert, map1.toJSON(), expectedMap.toJSON()); + }; }); function run_test() { runSourceMapTests('test/source-map/test-source-map-generator', do_throw);