Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / sdk / SourceMap.js
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  * @constructor
33  */
34 function SourceMapV3()
35 {
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;
42 }
43
44 /**
45  * @constructor
46  */
47 SourceMapV3.Section = function()
48 {
49     /** @type {!SourceMapV3} */ this.map;
50     /** @type {!SourceMapV3.Offset} */ this.offset;
51 }
52
53 /**
54  * @constructor
55  */
56 SourceMapV3.Offset = function()
57 {
58     /** @type {number} */ this.line;
59     /** @type {number} */ this.column;
60 }
61
62 /**
63  * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
64  * for format description.
65  * @constructor
66  * @param {string} sourceMappingURL
67  * @param {!SourceMapV3} payload
68  */
69 WebInspector.SourceMap = function(sourceMappingURL, payload)
70 {
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;
76     }
77
78     this._sourceMappingURL = sourceMappingURL;
79     this._reverseMappingsBySourceURL = {};
80     this._mappings = [];
81     this._sources = {};
82     this._sourceContentByURL = {};
83     this._parseMappingPayload(payload);
84 }
85
86 /**
87  * @param {string} sourceMapURL
88  * @param {string} compiledURL
89  * @param {function(?WebInspector.SourceMap)} callback
90  * @this {WebInspector.SourceMap}
91  */
92 WebInspector.SourceMap.load = function(sourceMapURL, compiledURL, callback)
93 {
94     var resourceTreeModel = WebInspector.resourceTreeModel;
95     if (resourceTreeModel.cachedResourcesLoaded())
96         loadResource();
97     else
98         resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, cachedResourcesLoaded);
99
100     function cachedResourcesLoaded()
101     {
102         resourceTreeModel.removeEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, cachedResourcesLoaded);
103         loadResource();
104     }
105
106     function loadResource()
107     {
108         var headers = {};
109         NetworkAgent.loadResourceForFrontend(resourceTreeModel.mainFrame.id, sourceMapURL, headers, contentLoaded);
110     }
111
112     /**
113      * @param {?Protocol.Error} error
114      * @param {number} statusCode
115      * @param {!NetworkAgent.Headers} headers
116      * @param {string} content
117      */
118     function contentLoaded(error, statusCode, headers, content)
119     {
120         if (error || !content || statusCode >= 400) {
121             callback(null);
122             return;
123         }
124
125         if (content.slice(0, 3) === ")]}")
126             content = content.substring(content.indexOf('\n'));
127         try {
128             var payload = /** @type {!SourceMapV3} */ (JSON.parse(content));
129             var baseURL = sourceMapURL.startsWith("data:") ? compiledURL : sourceMapURL;
130             callback(new WebInspector.SourceMap(baseURL, payload));
131         } catch(e) {
132             console.error(e.message);
133             callback(null);
134         }
135     }
136 }
137
138 WebInspector.SourceMap.prototype = {
139     /**
140      * @return {string}
141      */
142     url: function()
143     {
144         return this._sourceMappingURL;
145     },
146
147    /**
148      * @return {!Array.<string>}
149      */
150     sources: function()
151     {
152         return Object.keys(this._sources);
153     },
154
155     /**
156      * @param {string} sourceURL
157      * @return {string|undefined}
158      */
159     sourceContent: function(sourceURL)
160     {
161         return this._sourceContentByURL[sourceURL];
162     },
163
164     /**
165      * @param {string} sourceURL
166      * @param {!WebInspector.ResourceType} contentType
167      * @return {!WebInspector.ContentProvider}
168      */
169     sourceContentProvider: function(sourceURL, contentType)
170     {
171         var sourceContent = this.sourceContent(sourceURL);
172         if (sourceContent)
173             return new WebInspector.StaticContentProvider(contentType, sourceContent);
174         return new WebInspector.CompilerSourceMappingContentProvider(sourceURL, contentType);
175     },
176
177     /**
178      * @param {!SourceMapV3} mappingPayload
179      */
180     _parseMappingPayload: function(mappingPayload)
181     {
182         if (mappingPayload.sections)
183             this._parseSections(mappingPayload.sections);
184         else
185             this._parseMap(mappingPayload, 0, 0);
186     },
187
188     /**
189      * @param {!Array.<!SourceMapV3.Section>} sections
190      */
191     _parseSections: function(sections)
192     {
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);
196         }
197     },
198
199     /**
200      * @param {number} lineNumber in compiled resource
201      * @param {number} columnNumber in compiled resource
202      * @return {?Array.<number|string>}
203      */
204     findEntry: function(lineNumber, columnNumber)
205     {
206         var first = 0;
207         var count = this._mappings.length;
208         while (count > 1) {
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]))
213               count = step;
214           else {
215               first = middle;
216               count -= step;
217           }
218         }
219         var entry = this._mappings[first];
220         if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1])))
221             return null;
222         return entry;
223     },
224
225     /**
226      * @param {string} sourceURL of the originating resource
227      * @param {number} lineNumber in the originating resource
228      * @param {number=} span
229      * @return {?Array.<*>}
230      */
231     findEntryReversed: function(sourceURL, lineNumber, span)
232     {
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];
237             if (mapping)
238                 return mapping;
239         }
240         return null;
241     },
242
243     /**
244      * @override
245      */
246     _parseMap: function(map, lineNumber, columnNumber)
247     {
248         var sourceIndex = 0;
249         var sourceLineNumber = 0;
250         var sourceColumnNumber = 0;
251         var nameIndex = 0;
252
253         var sources = [];
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("/"))
259                 sourceRoot += "/";
260             var href = sourceRoot + originalSourceURL;
261             var url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href;
262             originalToCanonicalURLMap[originalSourceURL] = url;
263             sources.push(url);
264             this._sources[url] = true;
265
266             if (map.sourcesContent && map.sourcesContent[i])
267                 this._sourceContentByURL[url] = map.sourcesContent[i];
268         }
269
270         var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings);
271         var sourceURL = sources[sourceIndex];
272
273         while (true) {
274             if (stringCharIterator.peek() === ",")
275                 stringCharIterator.next();
276             else {
277                 while (stringCharIterator.peek() === ";") {
278                     lineNumber += 1;
279                     columnNumber = 0;
280                     stringCharIterator.next();
281                 }
282                 if (!stringCharIterator.hasNext())
283                     break;
284             }
285
286             columnNumber += this._decodeVLQ(stringCharIterator);
287             if (!stringCharIterator.hasNext() || this._isSeparator(stringCharIterator.peek())) {
288                 this._mappings.push([lineNumber, columnNumber]);
289                 continue;
290             }
291
292             var sourceIndexDelta = this._decodeVLQ(stringCharIterator);
293             if (sourceIndexDelta) {
294                 sourceIndex += sourceIndexDelta;
295                 sourceURL = sources[sourceIndex];
296             }
297             sourceLineNumber += this._decodeVLQ(stringCharIterator);
298             sourceColumnNumber += this._decodeVLQ(stringCharIterator);
299             if (!this._isSeparator(stringCharIterator.peek()))
300                 nameIndex += this._decodeVLQ(stringCharIterator);
301
302             this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]);
303         }
304
305         for (var i = 0; i < this._mappings.length; ++i) {
306             var mapping = this._mappings[i];
307             var url = mapping[2];
308             if (!url)
309                 continue;
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]];
316         }
317     },
318
319     /**
320      * @param {string} char
321      * @return {boolean}
322      */
323     _isSeparator: function(char)
324     {
325         return char === "," || char === ";";
326     },
327
328     /**
329      * @param {!WebInspector.SourceMap.StringCharIterator} stringCharIterator
330      * @return {number}
331      */
332     _decodeVLQ: function(stringCharIterator)
333     {
334         // Read unsigned value.
335         var result = 0;
336         var shift = 0;
337         do {
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);
342
343         // Fix the sign.
344         var negative = result & 1;
345         result >>= 1;
346         return negative ? -result : result;
347     },
348
349     _VLQ_BASE_SHIFT: 5,
350     _VLQ_BASE_MASK: (1 << 5) - 1,
351     _VLQ_CONTINUATION_MASK: 1 << 5
352 }
353
354 /**
355  * @constructor
356  * @param {string} string
357  */
358 WebInspector.SourceMap.StringCharIterator = function(string)
359 {
360     this._string = string;
361     this._position = 0;
362 }
363
364 WebInspector.SourceMap.StringCharIterator.prototype = {
365     /**
366      * @return {string}
367      */
368     next: function()
369     {
370         return this._string.charAt(this._position++);
371     },
372
373     /**
374      * @return {string}
375      */
376     peek: function()
377     {
378         return this._string.charAt(this._position);
379     },
380
381     /**
382      * @return {boolean}
383      */
384     hasNext: function()
385     {
386         return this._position < this._string.length;
387     }
388 }