blob: 9cbd37a73553245c90c05b39ea0c2100971f165f [file] [log] [blame]
Ben Murdochb8a8cc12014-11-26 15:28:44 +00001// Copyright 2013 the V8 project authors. All rights reserved.
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions are
4// met:
5//
6// * Redistributions of source code must retain the above copyright
7// notice, this list of conditions and the following disclaimer.
8// * Redistributions in binary form must reproduce the above
9// copyright notice, this list of conditions and the following
10// disclaimer in the documentation and/or other materials provided
11// with the distribution.
12// * Neither the name of Google Inc. nor the names of its
13// contributors may be used to endorse or promote products derived
14// from this software without specific prior written permission.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28// This is a copy from blink dev tools, see:
29// http://src.chromium.org/viewvc/blink/trunk/Source/devtools/front_end/SourceMap.js
30// revision: 153407
31
32// Added to make the file work without dev tools
33WebInspector = {};
34WebInspector.ParsedURL = {};
35WebInspector.ParsedURL.completeURL = function(){};
36// start of original file content
37
38/*
39 * Copyright (C) 2012 Google Inc. All rights reserved.
40 *
41 * Redistribution and use in source and binary forms, with or without
42 * modification, are permitted provided that the following conditions are
43 * met:
44 *
45 * * Redistributions of source code must retain the above copyright
46 * notice, this list of conditions and the following disclaimer.
47 * * Redistributions in binary form must reproduce the above
48 * copyright notice, this list of conditions and the following disclaimer
49 * in the documentation and/or other materials provided with the
50 * distribution.
51 * * Neither the name of Google Inc. nor the names of its
52 * contributors may be used to endorse or promote products derived from
53 * this software without specific prior written permission.
54 *
55 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
56 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
57 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
58 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
59 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
60 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
61 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
62 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
63 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
64 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
65 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
66 */
67
68/**
69 * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
70 * for format description.
71 * @constructor
72 * @param {string} sourceMappingURL
73 * @param {SourceMapV3} payload
74 */
75WebInspector.SourceMap = function(sourceMappingURL, payload)
76{
77 if (!WebInspector.SourceMap.prototype._base64Map) {
78 const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
79 WebInspector.SourceMap.prototype._base64Map = {};
80 for (var i = 0; i < base64Digits.length; ++i)
81 WebInspector.SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i;
82 }
83
84 this._sourceMappingURL = sourceMappingURL;
85 this._reverseMappingsBySourceURL = {};
86 this._mappings = [];
87 this._sources = {};
88 this._sourceContentByURL = {};
89 this._parseMappingPayload(payload);
90}
91
92/**
93 * @param {string} sourceMapURL
94 * @param {string} compiledURL
95 * @param {function(WebInspector.SourceMap)} callback
96 */
97WebInspector.SourceMap.load = function(sourceMapURL, compiledURL, callback)
98{
99 NetworkAgent.loadResourceForFrontend(WebInspector.resourceTreeModel.mainFrame.id, sourceMapURL, undefined, contentLoaded.bind(this));
100
101 /**
102 * @param {?Protocol.Error} error
103 * @param {number} statusCode
104 * @param {NetworkAgent.Headers} headers
105 * @param {string} content
106 */
107 function contentLoaded(error, statusCode, headers, content)
108 {
109 if (error || !content || statusCode >= 400) {
110 console.error("Could not load content for " + sourceMapURL + " : " + (error || ("HTTP status code: " + statusCode)));
111 callback(null);
112 return;
113 }
114
115 if (content.slice(0, 3) === ")]}")
116 content = content.substring(content.indexOf('\n'));
117 try {
118 var payload = /** @type {SourceMapV3} */ (JSON.parse(content));
119 var baseURL = sourceMapURL.startsWith("data:") ? compiledURL : sourceMapURL;
120 callback(new WebInspector.SourceMap(baseURL, payload));
121 } catch(e) {
122 console.error(e.message);
123 callback(null);
124 }
125 }
126}
127
128WebInspector.SourceMap.prototype = {
129 /**
130 * @return {Array.<string>}
131 */
132 sources: function()
133 {
134 return Object.keys(this._sources);
135 },
136
137 /**
138 * @param {string} sourceURL
139 * @return {string|undefined}
140 */
141 sourceContent: function(sourceURL)
142 {
143 return this._sourceContentByURL[sourceURL];
144 },
145
146 /**
147 * @param {string} sourceURL
148 * @param {WebInspector.ResourceType} contentType
149 * @return {WebInspector.ContentProvider}
150 */
151 sourceContentProvider: function(sourceURL, contentType)
152 {
153 var lastIndexOfDot = sourceURL.lastIndexOf(".");
154 var extension = lastIndexOfDot !== -1 ? sourceURL.substr(lastIndexOfDot + 1) : "";
155 var mimeType = WebInspector.ResourceType.mimeTypesForExtensions[extension.toLowerCase()];
156 var sourceContent = this.sourceContent(sourceURL);
157 if (sourceContent)
158 return new WebInspector.StaticContentProvider(contentType, sourceContent, mimeType);
159 return new WebInspector.CompilerSourceMappingContentProvider(sourceURL, contentType, mimeType);
160 },
161
162 /**
163 * @param {SourceMapV3} mappingPayload
164 */
165 _parseMappingPayload: function(mappingPayload)
166 {
167 if (mappingPayload.sections)
168 this._parseSections(mappingPayload.sections);
169 else
170 this._parseMap(mappingPayload, 0, 0);
171 },
172
173 /**
174 * @param {Array.<SourceMapV3.Section>} sections
175 */
176 _parseSections: function(sections)
177 {
178 for (var i = 0; i < sections.length; ++i) {
179 var section = sections[i];
180 this._parseMap(section.map, section.offset.line, section.offset.column);
181 }
182 },
183
184 /**
185 * @param {number} lineNumber in compiled resource
186 * @param {number} columnNumber in compiled resource
187 * @return {?Array}
188 */
189 findEntry: function(lineNumber, columnNumber)
190 {
191 var first = 0;
192 var count = this._mappings.length;
193 while (count > 1) {
194 var step = count >> 1;
195 var middle = first + step;
196 var mapping = this._mappings[middle];
197 if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1]))
198 count = step;
199 else {
200 first = middle;
201 count -= step;
202 }
203 }
204 var entry = this._mappings[first];
205 if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1])))
206 return null;
207 return entry;
208 },
209
210 /**
211 * @param {string} sourceURL of the originating resource
212 * @param {number} lineNumber in the originating resource
213 * @return {Array}
214 */
215 findEntryReversed: function(sourceURL, lineNumber)
216 {
217 var mappings = this._reverseMappingsBySourceURL[sourceURL];
218 for ( ; lineNumber < mappings.length; ++lineNumber) {
219 var mapping = mappings[lineNumber];
220 if (mapping)
221 return mapping;
222 }
223 return this._mappings[0];
224 },
225
226 /**
227 * @override
228 */
229 _parseMap: function(map, lineNumber, columnNumber)
230 {
231 var sourceIndex = 0;
232 var sourceLineNumber = 0;
233 var sourceColumnNumber = 0;
234 var nameIndex = 0;
235
236 var sources = [];
237 var originalToCanonicalURLMap = {};
238 for (var i = 0; i < map.sources.length; ++i) {
239 var originalSourceURL = map.sources[i];
240 var sourceRoot = map.sourceRoot || "";
241 if (sourceRoot && !sourceRoot.endsWith("/"))
242 sourceRoot += "/";
243 var href = sourceRoot + originalSourceURL;
244 var url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href;
245 originalToCanonicalURLMap[originalSourceURL] = url;
246 sources.push(url);
247 this._sources[url] = true;
248
249 if (map.sourcesContent && map.sourcesContent[i])
250 this._sourceContentByURL[url] = map.sourcesContent[i];
251 }
252
253 var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings);
254 var sourceURL = sources[sourceIndex];
255
256 while (true) {
257 if (stringCharIterator.peek() === ",")
258 stringCharIterator.next();
259 else {
260 while (stringCharIterator.peek() === ";") {
261 lineNumber += 1;
262 columnNumber = 0;
263 stringCharIterator.next();
264 }
265 if (!stringCharIterator.hasNext())
266 break;
267 }
268
269 columnNumber += this._decodeVLQ(stringCharIterator);
270 if (this._isSeparator(stringCharIterator.peek())) {
271 this._mappings.push([lineNumber, columnNumber]);
272 continue;
273 }
274
275 var sourceIndexDelta = this._decodeVLQ(stringCharIterator);
276 if (sourceIndexDelta) {
277 sourceIndex += sourceIndexDelta;
278 sourceURL = sources[sourceIndex];
279 }
280 sourceLineNumber += this._decodeVLQ(stringCharIterator);
281 sourceColumnNumber += this._decodeVLQ(stringCharIterator);
282 if (!this._isSeparator(stringCharIterator.peek()))
283 nameIndex += this._decodeVLQ(stringCharIterator);
284
285 this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]);
286 }
287
288 for (var i = 0; i < this._mappings.length; ++i) {
289 var mapping = this._mappings[i];
290 var url = mapping[2];
291 if (!url)
292 continue;
293 if (!this._reverseMappingsBySourceURL[url])
294 this._reverseMappingsBySourceURL[url] = [];
295 var reverseMappings = this._reverseMappingsBySourceURL[url];
296 var sourceLine = mapping[3];
297 if (!reverseMappings[sourceLine])
298 reverseMappings[sourceLine] = [mapping[0], mapping[1]];
299 }
300 },
301
302 /**
303 * @param {string} char
304 * @return {boolean}
305 */
306 _isSeparator: function(char)
307 {
308 return char === "," || char === ";";
309 },
310
311 /**
312 * @param {WebInspector.SourceMap.StringCharIterator} stringCharIterator
313 * @return {number}
314 */
315 _decodeVLQ: function(stringCharIterator)
316 {
317 // Read unsigned value.
318 var result = 0;
319 var shift = 0;
320 do {
321 var digit = this._base64Map[stringCharIterator.next()];
322 result += (digit & this._VLQ_BASE_MASK) << shift;
323 shift += this._VLQ_BASE_SHIFT;
324 } while (digit & this._VLQ_CONTINUATION_MASK);
325
326 // Fix the sign.
327 var negative = result & 1;
328 result >>= 1;
329 return negative ? -result : result;
330 },
331
332 _VLQ_BASE_SHIFT: 5,
333 _VLQ_BASE_MASK: (1 << 5) - 1,
334 _VLQ_CONTINUATION_MASK: 1 << 5
335}
336
337/**
338 * @constructor
339 * @param {string} string
340 */
341WebInspector.SourceMap.StringCharIterator = function(string)
342{
343 this._string = string;
344 this._position = 0;
345}
346
347WebInspector.SourceMap.StringCharIterator.prototype = {
348 /**
349 * @return {string}
350 */
351 next: function()
352 {
353 return this._string.charAt(this._position++);
354 },
355
356 /**
357 * @return {string}
358 */
359 peek: function()
360 {
361 return this._string.charAt(this._position);
362 },
363
364 /**
365 * @return {boolean}
366 */
367 hasNext: function()
368 {
369 return this._position < this._string.length;
370 }
371}