• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
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
13 * distribution.
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.
17 *
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.
29 */
30
31/**
32 * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
33 * for format description.
34 * @constructor
35 * @param {string} sourceMappingURL
36 * @param {!SourceMapV3} payload
37 */
38WebInspector.SourceMap = function(sourceMappingURL, payload)
39{
40    if (!WebInspector.SourceMap.prototype._base64Map) {
41        const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
42        WebInspector.SourceMap.prototype._base64Map = {};
43        for (var i = 0; i < base64Digits.length; ++i)
44            WebInspector.SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i;
45    }
46
47    this._sourceMappingURL = sourceMappingURL;
48    this._reverseMappingsBySourceURL = {};
49    this._mappings = [];
50    this._sources = {};
51    this._sourceContentByURL = {};
52    this._parseMappingPayload(payload);
53}
54
55WebInspector.SourceMap._sourceMapRequestHeaderName = "X-Source-Map-Request-From";
56WebInspector.SourceMap._sourceMapRequestHeaderValue = "inspector";
57
58WebInspector.SourceMap.hasSourceMapRequestHeader = function(request)
59{
60    return request && request.requestHeaderValue(WebInspector.SourceMap._sourceMapRequestHeaderName) === WebInspector.SourceMap._sourceMapRequestHeaderValue;
61}
62
63/**
64 * @param {string} sourceMapURL
65 * @param {string} compiledURL
66 * @param {function(?WebInspector.SourceMap)} callback
67 * @this {WebInspector.SourceMap}
68 */
69WebInspector.SourceMap.load = function(sourceMapURL, compiledURL, callback)
70{
71    var headers = {};
72    headers[WebInspector.SourceMap._sourceMapRequestHeaderName] = WebInspector.SourceMap._sourceMapRequestHeaderValue;
73    NetworkAgent.loadResourceForFrontend(WebInspector.resourceTreeModel.mainFrame.id, sourceMapURL, headers, contentLoaded.bind(this));
74
75    /**
76     * @param {?Protocol.Error} error
77     * @param {number} statusCode
78     * @param {!NetworkAgent.Headers} headers
79     * @param {string} content
80     */
81    function contentLoaded(error, statusCode, headers, content)
82    {
83        if (error || !content || statusCode >= 400) {
84            callback(null);
85            return;
86        }
87
88        if (content.slice(0, 3) === ")]}")
89            content = content.substring(content.indexOf('\n'));
90        try {
91            var payload = /** @type {!SourceMapV3} */ (JSON.parse(content));
92            var baseURL = sourceMapURL.startsWith("data:") ? compiledURL : sourceMapURL;
93            callback(new WebInspector.SourceMap(baseURL, payload));
94        } catch(e) {
95            console.error(e.message);
96            callback(null);
97        }
98    }
99}
100
101WebInspector.SourceMap.prototype = {
102    /**
103     * @return {string}
104     */
105    url: function()
106    {
107        return this._sourceMappingURL;
108    },
109
110   /**
111     * @return {!Array.<string>}
112     */
113    sources: function()
114    {
115        return Object.keys(this._sources);
116    },
117
118    /**
119     * @param {string} sourceURL
120     * @return {string|undefined}
121     */
122    sourceContent: function(sourceURL)
123    {
124        return this._sourceContentByURL[sourceURL];
125    },
126
127    /**
128     * @param {string} sourceURL
129     * @param {!WebInspector.ResourceType} contentType
130     * @return {!WebInspector.ContentProvider}
131     */
132    sourceContentProvider: function(sourceURL, contentType)
133    {
134        var sourceContent = this.sourceContent(sourceURL);
135        if (sourceContent)
136            return new WebInspector.StaticContentProvider(contentType, sourceContent);
137        return new WebInspector.CompilerSourceMappingContentProvider(sourceURL, contentType);
138    },
139
140    /**
141     * @param {!SourceMapV3} mappingPayload
142     */
143    _parseMappingPayload: function(mappingPayload)
144    {
145        if (mappingPayload.sections)
146            this._parseSections(mappingPayload.sections);
147        else
148            this._parseMap(mappingPayload, 0, 0);
149    },
150
151    /**
152     * @param {!Array.<!SourceMapV3.Section>} sections
153     */
154    _parseSections: function(sections)
155    {
156        for (var i = 0; i < sections.length; ++i) {
157            var section = sections[i];
158            this._parseMap(section.map, section.offset.line, section.offset.column);
159        }
160    },
161
162    /**
163     * @param {number} lineNumber in compiled resource
164     * @param {number} columnNumber in compiled resource
165     * @return {?Array.<*>}
166     */
167    findEntry: function(lineNumber, columnNumber)
168    {
169        var first = 0;
170        var count = this._mappings.length;
171        while (count > 1) {
172          var step = count >> 1;
173          var middle = first + step;
174          var mapping = this._mappings[middle];
175          if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1]))
176              count = step;
177          else {
178              first = middle;
179              count -= step;
180          }
181        }
182        var entry = this._mappings[first];
183        if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1])))
184            return null;
185        return entry;
186    },
187
188    /**
189     * @param {string} sourceURL of the originating resource
190     * @param {number} lineNumber in the originating resource
191     * @return {!Array.<*>}
192     */
193    findEntryReversed: function(sourceURL, lineNumber)
194    {
195        var mappings = this._reverseMappingsBySourceURL[sourceURL];
196        for ( ; lineNumber < mappings.length; ++lineNumber) {
197            var mapping = mappings[lineNumber];
198            if (mapping)
199                return mapping;
200        }
201        return this._mappings[0];
202    },
203
204    /**
205     * @override
206     */
207    _parseMap: function(map, lineNumber, columnNumber)
208    {
209        var sourceIndex = 0;
210        var sourceLineNumber = 0;
211        var sourceColumnNumber = 0;
212        var nameIndex = 0;
213
214        var sources = [];
215        var originalToCanonicalURLMap = {};
216        for (var i = 0; i < map.sources.length; ++i) {
217            var originalSourceURL = map.sources[i];
218            var sourceRoot = map.sourceRoot || "";
219            if (sourceRoot && !sourceRoot.endsWith("/"))
220                sourceRoot += "/";
221            var href = sourceRoot + originalSourceURL;
222            var url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href;
223            originalToCanonicalURLMap[originalSourceURL] = url;
224            sources.push(url);
225            this._sources[url] = true;
226
227            if (map.sourcesContent && map.sourcesContent[i])
228                this._sourceContentByURL[url] = map.sourcesContent[i];
229        }
230
231        var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings);
232        var sourceURL = sources[sourceIndex];
233
234        while (true) {
235            if (stringCharIterator.peek() === ",")
236                stringCharIterator.next();
237            else {
238                while (stringCharIterator.peek() === ";") {
239                    lineNumber += 1;
240                    columnNumber = 0;
241                    stringCharIterator.next();
242                }
243                if (!stringCharIterator.hasNext())
244                    break;
245            }
246
247            columnNumber += this._decodeVLQ(stringCharIterator);
248            if (this._isSeparator(stringCharIterator.peek())) {
249                this._mappings.push([lineNumber, columnNumber]);
250                continue;
251            }
252
253            var sourceIndexDelta = this._decodeVLQ(stringCharIterator);
254            if (sourceIndexDelta) {
255                sourceIndex += sourceIndexDelta;
256                sourceURL = sources[sourceIndex];
257            }
258            sourceLineNumber += this._decodeVLQ(stringCharIterator);
259            sourceColumnNumber += this._decodeVLQ(stringCharIterator);
260            if (!this._isSeparator(stringCharIterator.peek()))
261                nameIndex += this._decodeVLQ(stringCharIterator);
262
263            this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]);
264        }
265
266        for (var i = 0; i < this._mappings.length; ++i) {
267            var mapping = this._mappings[i];
268            var url = mapping[2];
269            if (!url)
270                continue;
271            if (!this._reverseMappingsBySourceURL[url])
272                this._reverseMappingsBySourceURL[url] = [];
273            var reverseMappings = this._reverseMappingsBySourceURL[url];
274            var sourceLine = mapping[3];
275            if (!reverseMappings[sourceLine])
276                reverseMappings[sourceLine] = [mapping[0], mapping[1]];
277        }
278    },
279
280    /**
281     * @param {string} char
282     * @return {boolean}
283     */
284    _isSeparator: function(char)
285    {
286        return char === "," || char === ";";
287    },
288
289    /**
290     * @param {!WebInspector.SourceMap.StringCharIterator} stringCharIterator
291     * @return {number}
292     */
293    _decodeVLQ: function(stringCharIterator)
294    {
295        // Read unsigned value.
296        var result = 0;
297        var shift = 0;
298        do {
299            var digit = this._base64Map[stringCharIterator.next()];
300            result += (digit & this._VLQ_BASE_MASK) << shift;
301            shift += this._VLQ_BASE_SHIFT;
302        } while (digit & this._VLQ_CONTINUATION_MASK);
303
304        // Fix the sign.
305        var negative = result & 1;
306        result >>= 1;
307        return negative ? -result : result;
308    },
309
310    _VLQ_BASE_SHIFT: 5,
311    _VLQ_BASE_MASK: (1 << 5) - 1,
312    _VLQ_CONTINUATION_MASK: 1 << 5
313}
314
315/**
316 * @constructor
317 * @param {string} string
318 */
319WebInspector.SourceMap.StringCharIterator = function(string)
320{
321    this._string = string;
322    this._position = 0;
323}
324
325WebInspector.SourceMap.StringCharIterator.prototype = {
326    /**
327     * @return {string}
328     */
329    next: function()
330    {
331        return this._string.charAt(this._position++);
332    },
333
334    /**
335     * @return {string}
336     */
337    peek: function()
338    {
339        return this._string.charAt(this._position);
340    },
341
342    /**
343     * @return {boolean}
344     */
345    hasNext: function()
346    {
347        return this._position < this._string.length;
348    }
349}
350