2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 function SourceMapV3()
36 /** @type {number} */ this.version;
37 /** @type {string|undefined} */ this.file;
38 /** @type {!Array.<string>} */ this.sources;
39 /** @type {!Array.<!SourceMapV3.Section>|undefined} */ this.sections;
40 /** @type {string} */ this.mappings;
41 /** @type {string|undefined} */ this.sourceRoot;
47 SourceMapV3.Section = function()
49 /** @type {!SourceMapV3} */ this.map;
50 /** @type {!SourceMapV3.Offset} */ this.offset;
56 SourceMapV3.Offset = function()
58 /** @type {number} */ this.line;
59 /** @type {number} */ this.column;
63 * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
64 * for format description.
66 * @param {string} sourceMappingURL
67 * @param {!SourceMapV3} payload
69 WebInspector.SourceMap = function(sourceMappingURL, payload)
71 if (!WebInspector.SourceMap.prototype._base64Map) {
72 const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
73 WebInspector.SourceMap.prototype._base64Map = {};
74 for (var i = 0; i < base64Digits.length; ++i)
75 WebInspector.SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i;
78 this._sourceMappingURL = sourceMappingURL;
79 this._reverseMappingsBySourceURL = {};
82 this._sourceContentByURL = {};
83 this._parseMappingPayload(payload);
87 * @param {string} sourceMapURL
88 * @param {string} compiledURL
89 * @param {function(?WebInspector.SourceMap)} callback
90 * @this {WebInspector.SourceMap}
92 WebInspector.SourceMap.load = function(sourceMapURL, compiledURL, callback)
94 var resourceTreeModel = WebInspector.resourceTreeModel;
95 if (resourceTreeModel.cachedResourcesLoaded())
98 resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, cachedResourcesLoaded);
100 function cachedResourcesLoaded()
102 resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, cachedResourcesLoaded);
106 function loadResource()
109 NetworkAgent.loadResourceForFrontend(resourceTreeModel.mainFrame.id, sourceMapURL, headers, contentLoaded);
113 * @param {?Protocol.Error} error
114 * @param {number} statusCode
115 * @param {!NetworkAgent.Headers} headers
116 * @param {string} content
118 function contentLoaded(error, statusCode, headers, content)
120 if (error || !content || statusCode >= 400) {
125 if (content.slice(0, 3) === ")]}")
126 content = content.substring(content.indexOf('\n'));
128 var payload = /** @type {!SourceMapV3} */ (JSON.parse(content));
129 var baseURL = sourceMapURL.startsWith("data:") ? compiledURL : sourceMapURL;
130 callback(new WebInspector.SourceMap(baseURL, payload));
132 console.error(e.message);
138 WebInspector.SourceMap.prototype = {
144 return this._sourceMappingURL;
148 * @return {!Array.<string>}
152 return Object.keys(this._sources);
156 * @param {string} sourceURL
157 * @return {string|undefined}
159 sourceContent: function(sourceURL)
161 return this._sourceContentByURL[sourceURL];
165 * @param {string} sourceURL
166 * @param {!WebInspector.ResourceType} contentType
167 * @return {!WebInspector.ContentProvider}
169 sourceContentProvider: function(sourceURL, contentType)
171 var sourceContent = this.sourceContent(sourceURL);
173 return new WebInspector.StaticContentProvider(contentType, sourceContent);
174 return new WebInspector.CompilerSourceMappingContentProvider(sourceURL, contentType);
178 * @param {!SourceMapV3} mappingPayload
180 _parseMappingPayload: function(mappingPayload)
182 if (mappingPayload.sections)
183 this._parseSections(mappingPayload.sections);
185 this._parseMap(mappingPayload, 0, 0);
189 * @param {!Array.<!SourceMapV3.Section>} sections
191 _parseSections: function(sections)
193 for (var i = 0; i < sections.length; ++i) {
194 var section = sections[i];
195 this._parseMap(section.map, section.offset.line, section.offset.column);
200 * @param {number} lineNumber in compiled resource
201 * @param {number} columnNumber in compiled resource
202 * @return {?Array.<number|string>}
204 findEntry: function(lineNumber, columnNumber)
207 var count = this._mappings.length;
209 var step = count >> 1;
210 var middle = first + step;
211 var mapping = this._mappings[middle];
212 if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1]))
219 var entry = this._mappings[first];
220 if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1])))
226 * @param {string} sourceURL of the originating resource
227 * @param {number} lineNumber in the originating resource
228 * @param {number=} span
229 * @return {?Array.<*>}
231 findEntryReversed: function(sourceURL, lineNumber, span)
233 var mappings = this._reverseMappingsBySourceURL[sourceURL];
234 var maxLineNumber = typeof span === "number" ? Math.min(lineNumber + span + 1, mappings.length) : mappings.length;
235 for ( ; lineNumber < maxLineNumber; ++lineNumber) {
236 var mapping = mappings[lineNumber];
246 _parseMap: function(map, lineNumber, columnNumber)
249 var sourceLineNumber = 0;
250 var sourceColumnNumber = 0;
254 var originalToCanonicalURLMap = {};
255 for (var i = 0; i < map.sources.length; ++i) {
256 var originalSourceURL = map.sources[i];
257 var sourceRoot = map.sourceRoot || "";
258 if (sourceRoot && !sourceRoot.endsWith("/"))
260 var href = sourceRoot + originalSourceURL;
261 var url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href;
262 originalToCanonicalURLMap[originalSourceURL] = url;
264 this._sources[url] = true;
266 if (map.sourcesContent && map.sourcesContent[i])
267 this._sourceContentByURL[url] = map.sourcesContent[i];
270 var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings);
271 var sourceURL = sources[sourceIndex];
274 if (stringCharIterator.peek() === ",")
275 stringCharIterator.next();
277 while (stringCharIterator.peek() === ";") {
280 stringCharIterator.next();
282 if (!stringCharIterator.hasNext())
286 columnNumber += this._decodeVLQ(stringCharIterator);
287 if (!stringCharIterator.hasNext() || this._isSeparator(stringCharIterator.peek())) {
288 this._mappings.push([lineNumber, columnNumber]);
292 var sourceIndexDelta = this._decodeVLQ(stringCharIterator);
293 if (sourceIndexDelta) {
294 sourceIndex += sourceIndexDelta;
295 sourceURL = sources[sourceIndex];
297 sourceLineNumber += this._decodeVLQ(stringCharIterator);
298 sourceColumnNumber += this._decodeVLQ(stringCharIterator);
299 if (!this._isSeparator(stringCharIterator.peek()))
300 nameIndex += this._decodeVLQ(stringCharIterator);
302 this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]);
305 for (var i = 0; i < this._mappings.length; ++i) {
306 var mapping = this._mappings[i];
307 var url = mapping[2];
310 if (!this._reverseMappingsBySourceURL[url])
311 this._reverseMappingsBySourceURL[url] = [];
312 var reverseMappings = this._reverseMappingsBySourceURL[url];
313 var sourceLine = mapping[3];
314 if (!reverseMappings[sourceLine])
315 reverseMappings[sourceLine] = [mapping[0], mapping[1]];
320 * @param {string} char
323 _isSeparator: function(char)
325 return char === "," || char === ";";
329 * @param {!WebInspector.SourceMap.StringCharIterator} stringCharIterator
332 _decodeVLQ: function(stringCharIterator)
334 // Read unsigned value.
338 var digit = this._base64Map[stringCharIterator.next()];
339 result += (digit & this._VLQ_BASE_MASK) << shift;
340 shift += this._VLQ_BASE_SHIFT;
341 } while (digit & this._VLQ_CONTINUATION_MASK);
344 var negative = result & 1;
346 return negative ? -result : result;
350 _VLQ_BASE_MASK: (1 << 5) - 1,
351 _VLQ_CONTINUATION_MASK: 1 << 5
356 * @param {string} string
358 WebInspector.SourceMap.StringCharIterator = function(string)
360 this._string = string;
364 WebInspector.SourceMap.StringCharIterator.prototype = {
370 return this._string.charAt(this._position++);
378 return this._string.charAt(this._position);
386 return this._position < this._string.length;