Web Inspector: implement reverse mapping for compiler source maps.
[WebKit.git] / Source / WebCore / inspector / front-end / CompilerSourceMapping.js
1 /*
2  * Copyright (C) 2011 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 WebInspector.CompilerSourceMapping = function()
35 {
36 }
37
38 WebInspector.CompilerSourceMapping.prototype = {
39     compiledLocationToSourceLocation: function(lineNumber, columnNumber)
40     {
41         // Should be implemented by subclasses.
42     },
43
44     sourceLocationToCompiledLocation: function(sourceURL, lineNumber, columnNumber)
45     {
46         // Should be implemented by subclasses.
47     },
48
49     sources: function()
50     {
51         // Should be implemented by subclasses.
52     }
53 }
54
55 /**
56  * Implements Source Map V3 consumer. See http://code.google.com/p/closure-compiler/wiki/SourceMaps
57  * for format description.
58  * @extends {WebInspector.CompilerSourceMapping}
59  * @constructor
60  */
61 WebInspector.ClosureCompilerSourceMapping = function(payload)
62 {
63     if (!WebInspector.ClosureCompilerSourceMapping.prototype._base64Map) {
64         base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
65         WebInspector.ClosureCompilerSourceMapping.prototype._base64Map = {};
66         for (var i = 0; i < base64Digits.length; ++i)
67             WebInspector.ClosureCompilerSourceMapping.prototype._base64Map[base64Digits.charAt(i)] = i;
68     }
69
70     this._sources = payload.sources;
71     this._mappings = [];
72     this._reverseMappingsBySourceURL = {};
73     for (var i = 0; i < this._sources.length; ++i)
74         this._reverseMappingsBySourceURL[this._sources[i]] = [];
75     this._parseMappings(payload.mappings);
76 }
77
78 WebInspector.ClosureCompilerSourceMapping.prototype = {
79     compiledLocationToSourceLocation: function(lineNumber, columnNumber)
80     {
81         var mapping = this._findMapping(lineNumber, columnNumber);
82         return { sourceURL: mapping[2], lineNumber: mapping[3], columnNumber: mapping[4] };
83     },
84
85     sourceLocationToCompiledLocation: function(sourceURL, lineNumber)
86     {
87         var mappings = this._reverseMappingsBySourceURL[sourceURL];
88         for ( ; lineNumber < mappings.length; ++lineNumber) {
89             var mapping = mappings[lineNumber];
90             if (mapping)
91                 return { lineNumber: mapping[0], columnNumber: mapping[1] };
92         }
93     },
94
95     sources: function()
96     {
97         return this._sources;
98     },
99
100     _findMapping: function(lineNumber, columnNumber)
101     {
102         var first = 0;
103         var count = this._mappings.length;
104         while (count > 1) {
105           var step = count >> 1;
106           var middle = first + step;
107           var mapping = this._mappings[middle];
108           if (lineNumber < mapping[0] || (lineNumber == mapping[0] && columnNumber < mapping[1]))
109               count = step;
110           else {
111               first = middle;
112               count -= step;
113           }
114         }
115         return this._mappings[first];
116     },
117
118     _parseMappings: function(mappingsPayload)
119     {
120         var stringCharIterator = new WebInspector.ClosureCompilerSourceMapping.StringCharIterator(mappingsPayload);
121
122         var lineNumber = 0;
123         var columnNumber = 0;
124         var sourceIndex = 0;
125         var sourceLineNumber = 0;
126         var sourceColumnNumber = 0;
127         var nameIndex = 0;
128
129         var sourceURL = this._sources[0];
130         var reverseMappings = this._reverseMappingsBySourceURL[sourceURL];
131
132         do {
133             columnNumber += this._decodeVLQ(stringCharIterator);
134             if (this._isSeparator(stringCharIterator.peek()))
135                 continue;
136             var sourceIndexDelta = this._decodeVLQ(stringCharIterator);
137             if (sourceIndexDelta) {
138                 sourceIndex += sourceIndexDelta;
139                 sourceURL = this._sources[sourceIndex];
140                 reverseMappings = this._reverseMappingsBySourceURL[sourceURL];
141             }
142             sourceLineNumber += this._decodeVLQ(stringCharIterator);
143             sourceColumnNumber += this._decodeVLQ(stringCharIterator);
144             if (!this._isSeparator(stringCharIterator.peek()))
145                 nameIndex += this._decodeVLQ(stringCharIterator);
146
147             this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]);
148             if (!reverseMappings[sourceLineNumber])
149                 reverseMappings[sourceLineNumber] = [lineNumber, columnNumber];
150
151             if (stringCharIterator.next() === ";") {
152                 lineNumber += 1;
153                 columnNumber = 0;
154             }
155         } while(stringCharIterator.hasNext());
156     },
157
158     _isSeparator: function(char)
159     {
160         return char === "," || char === ";";
161     },
162
163     _decodeVLQ: function(stringCharIterator)
164     {
165         // Read unsigned value.
166         var result = 0;
167         var shift = 0;
168         do {
169             var digit = this._base64Map[stringCharIterator.next()];
170             result += (digit & this._VLQ_BASE_MASK) << shift;
171             shift += this._VLQ_BASE_SHIFT;
172         } while (digit & this._VLQ_CONTINUATION_MASK);
173
174         // Fix the sign.
175         var negative = result & 1;
176         result >>= 1;
177         return negative ? -result : result;
178     },
179
180     _VLQ_BASE_SHIFT: 5,
181     _VLQ_BASE_MASK: (1 << 5) - 1,
182     _VLQ_CONTINUATION_MASK: 1 << 5
183 }
184
185 /**
186  * @constructor
187  */
188 WebInspector.ClosureCompilerSourceMapping.StringCharIterator = function(string)
189 {
190     this._string = string;
191     this._position = 0;
192 }
193
194 WebInspector.ClosureCompilerSourceMapping.StringCharIterator.prototype = {
195     next: function()
196     {
197         return this._string.charAt(this._position++);
198     },
199
200     peek: function()
201     {
202         return this._string.charAt(this._position);
203     },
204
205     hasNext: function()
206     {
207         return this._position < this._string.length;
208     }
209 }