| /* |
| * Copyright (C) 2013 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps |
| * for format description. |
| * @constructor |
| * @param {string} sourceMappingURL |
| * @param {SourceMapV3} payload |
| * @param {WebInspector.Resource|WebInspector.Script} originalSourceCode |
| */ |
| WebInspector.SourceMap = function(sourceMappingURL, payload, originalSourceCode) |
| { |
| if (!WebInspector.SourceMap.prototype._base64Map) { |
| const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| WebInspector.SourceMap.prototype._base64Map = {}; |
| for (var i = 0; i < base64Digits.length; ++i) |
| WebInspector.SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i; |
| } |
| |
| this._originalSourceCode = originalSourceCode || null; |
| this._sourceMapResources = {}; |
| this._sourceMapResourcesList = []; |
| |
| this._sourceMappingURL = sourceMappingURL; |
| this._reverseMappingsBySourceURL = {}; |
| this._mappings = []; |
| this._sources = {}; |
| this._sourceRoot = null; |
| this._sourceContentByURL = {}; |
| this._parseMappingPayload(payload); |
| } |
| |
| WebInspector.SourceMap.prototype = { |
| |
| get originalSourceCode() |
| { |
| return this._originalSourceCode; |
| }, |
| |
| get sourceMappingBasePathURLComponents() |
| { |
| if (this._sourceMappingURLBasePathComponents) |
| return this._sourceMappingURLBasePathComponents; |
| |
| if (this._sourceRoot) { |
| var baseURLPath = absoluteURL(this._sourceRoot, this._sourceMappingURL); |
| console.assert(baseURLPath); |
| if (baseURLPath) { |
| var urlComponents = parseURL(baseURLPath); |
| if (!/\/$/.test(urlComponents.path)) |
| urlComponents.path += "/"; |
| this._sourceMappingURLBasePathComponents = urlComponents; |
| return this._sourceMappingURLBasePathComponents; |
| } |
| } |
| |
| var urlComponents = parseURL(this._sourceMappingURL); |
| urlComponents.path = urlComponents.path.substr(0, urlComponents.path.lastIndexOf(urlComponents.lastPathComponent)); |
| urlComponents.lastPathComponent = null; |
| this._sourceMappingURLBasePathComponents = urlComponents; |
| return this._sourceMappingURLBasePathComponents; |
| }, |
| |
| get resources() |
| { |
| return this._sourceMapResourcesList; |
| }, |
| |
| addResource: function(resource) |
| { |
| console.assert(!(resource.url in this._sourceMapResources)); |
| this._sourceMapResources[resource.url] = resource; |
| this._sourceMapResourcesList.push(resource); |
| }, |
| |
| resourceForURL: function(url) |
| { |
| return this._sourceMapResources[url]; |
| }, |
| |
| /** |
| * @return {Array.<string>} |
| */ |
| sources: function() |
| { |
| return Object.keys(this._sources); |
| }, |
| |
| /** |
| * @param {string} sourceURL |
| * @return {string|undefined} |
| */ |
| sourceContent: function(sourceURL) |
| { |
| return this._sourceContentByURL[sourceURL]; |
| }, |
| |
| /** |
| * @param {SourceMapV3} mappingPayload |
| */ |
| _parseMappingPayload: function(mappingPayload) |
| { |
| if (mappingPayload.sections) |
| this._parseSections(mappingPayload.sections); |
| else |
| this._parseMap(mappingPayload, 0, 0); |
| }, |
| |
| /** |
| * @param {Array.<SourceMapV3.Section>} sections |
| */ |
| _parseSections: function(sections) |
| { |
| for (var i = 0; i < sections.length; ++i) { |
| var section = sections[i]; |
| this._parseMap(section.map, section.offset.line, section.offset.column); |
| } |
| }, |
| |
| /** |
| * @param {number} lineNumber in compiled resource |
| * @param {number} columnNumber in compiled resource |
| * @return {?Array} |
| */ |
| findEntry: function(lineNumber, columnNumber) |
| { |
| var first = 0; |
| var count = this._mappings.length; |
| while (count > 1) { |
| var step = count >> 1; |
| var middle = first + step; |
| var mapping = this._mappings[middle]; |
| if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1])) |
| count = step; |
| else { |
| first = middle; |
| count -= step; |
| } |
| } |
| var entry = this._mappings[first]; |
| if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1]))) |
| return null; |
| return entry; |
| }, |
| |
| /** |
| * @param {string} sourceURL of the originating resource |
| * @param {number} lineNumber in the originating resource |
| * @return {Array} |
| */ |
| findEntryReversed: function(sourceURL, lineNumber) |
| { |
| var mappings = this._reverseMappingsBySourceURL[sourceURL]; |
| for ( ; lineNumber < mappings.length; ++lineNumber) { |
| var mapping = mappings[lineNumber]; |
| if (mapping) |
| return mapping; |
| } |
| return this._mappings[0]; |
| }, |
| |
| /** |
| * @param {SourceMapV3} map |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| */ |
| _parseMap: function(map, lineNumber, columnNumber) |
| { |
| var sourceIndex = 0; |
| var sourceLineNumber = 0; |
| var sourceColumnNumber = 0; |
| var nameIndex = 0; |
| |
| var sources = []; |
| var originalToCanonicalURLMap = {}; |
| for (var i = 0; i < map.sources.length; ++i) { |
| var originalSourceURL = map.sources[i]; |
| var href = originalSourceURL; |
| if (map.sourceRoot && href.charAt(0) !== "/") |
| href = map.sourceRoot.replace(/\/+$/, "") + "/" + href; |
| var url = absoluteURL(href, this._sourceMappingURL) || href; |
| originalToCanonicalURLMap[originalSourceURL] = url; |
| sources.push(url); |
| this._sources[url] = true; |
| |
| if (map.sourcesContent && map.sourcesContent[i]) |
| this._sourceContentByURL[url] = map.sourcesContent[i]; |
| } |
| |
| this._sourceRoot = map.sourceRoot || null; |
| |
| var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings); |
| var sourceURL = sources[sourceIndex]; |
| |
| while (true) { |
| if (stringCharIterator.peek() === ",") |
| stringCharIterator.next(); |
| else { |
| while (stringCharIterator.peek() === ";") { |
| lineNumber += 1; |
| columnNumber = 0; |
| stringCharIterator.next(); |
| } |
| if (!stringCharIterator.hasNext()) |
| break; |
| } |
| |
| columnNumber += this._decodeVLQ(stringCharIterator); |
| if (this._isSeparator(stringCharIterator.peek())) { |
| this._mappings.push([lineNumber, columnNumber]); |
| continue; |
| } |
| |
| var sourceIndexDelta = this._decodeVLQ(stringCharIterator); |
| if (sourceIndexDelta) { |
| sourceIndex += sourceIndexDelta; |
| sourceURL = sources[sourceIndex]; |
| } |
| sourceLineNumber += this._decodeVLQ(stringCharIterator); |
| sourceColumnNumber += this._decodeVLQ(stringCharIterator); |
| if (!this._isSeparator(stringCharIterator.peek())) |
| nameIndex += this._decodeVLQ(stringCharIterator); |
| |
| this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]); |
| } |
| |
| for (var i = 0; i < this._mappings.length; ++i) { |
| var mapping = this._mappings[i]; |
| var url = mapping[2]; |
| if (!url) |
| continue; |
| if (!this._reverseMappingsBySourceURL[url]) |
| this._reverseMappingsBySourceURL[url] = []; |
| var reverseMappings = this._reverseMappingsBySourceURL[url]; |
| var sourceLine = mapping[3]; |
| if (!reverseMappings[sourceLine]) |
| reverseMappings[sourceLine] = [mapping[0], mapping[1]]; |
| } |
| }, |
| |
| /** |
| * @param {string} char |
| * @return {boolean} |
| */ |
| _isSeparator: function(char) |
| { |
| return char === "," || char === ";"; |
| }, |
| |
| /** |
| * @param {WebInspector.SourceMap.StringCharIterator} stringCharIterator |
| * @return {number} |
| */ |
| _decodeVLQ: function(stringCharIterator) |
| { |
| // Read unsigned value. |
| var result = 0; |
| var shift = 0; |
| do { |
| var digit = this._base64Map[stringCharIterator.next()]; |
| result += (digit & this._VLQ_BASE_MASK) << shift; |
| shift += this._VLQ_BASE_SHIFT; |
| } while (digit & this._VLQ_CONTINUATION_MASK); |
| |
| // Fix the sign. |
| var negative = result & 1; |
| result >>= 1; |
| return negative ? -result : result; |
| }, |
| |
| _VLQ_BASE_SHIFT: 5, |
| _VLQ_BASE_MASK: (1 << 5) - 1, |
| _VLQ_CONTINUATION_MASK: 1 << 5 |
| } |
| |
| /** |
| * @constructor |
| * @param {string} string |
| */ |
| WebInspector.SourceMap.StringCharIterator = function(string) |
| { |
| this._string = string; |
| this._position = 0; |
| } |
| |
| WebInspector.SourceMap.StringCharIterator.prototype = { |
| /** |
| * @return {string} |
| */ |
| next: function() |
| { |
| return this._string.charAt(this._position++); |
| }, |
| |
| /** |
| * @return {string} |
| */ |
| peek: function() |
| { |
| return this._string.charAt(this._position); |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| hasNext: function() |
| { |
| return this._position < this._string.length; |
| } |
| } |