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 55/** 56 * @param {string} sourceMapURL 57 * @param {string} compiledURL 58 * @return {WebInspector.SourceMap} 59 */ 60WebInspector.SourceMap.load = function(sourceMapURL, compiledURL) 61{ 62 try { 63 // FIXME: make sendRequest async. 64 var response = InspectorFrontendHost.loadResourceSynchronously(sourceMapURL); 65 if (!response) 66 return null; 67 if (response.slice(0, 3) === ")]}") 68 response = response.substring(response.indexOf('\n')); 69 var payload = /** @type {SourceMapV3} */ (JSON.parse(response)); 70 var baseURL = sourceMapURL.startsWith("data:") ? compiledURL : sourceMapURL; 71 return new WebInspector.SourceMap(baseURL, payload); 72 } catch(e) { 73 console.error(e.message); 74 return null; 75 } 76} 77 78WebInspector.SourceMap.prototype = { 79 /** 80 * @return {Array.<string>} 81 */ 82 sources: function() 83 { 84 return Object.keys(this._sources); 85 }, 86 87 /** 88 * @param {string} sourceURL 89 * @return {string|undefined} 90 */ 91 sourceContent: function(sourceURL) 92 { 93 return this._sourceContentByURL[sourceURL]; 94 }, 95 96 /** 97 * @param {SourceMapV3} mappingPayload 98 */ 99 _parseMappingPayload: function(mappingPayload) 100 { 101 if (mappingPayload.sections) 102 this._parseSections(mappingPayload.sections); 103 else 104 this._parseMap(mappingPayload, 0, 0); 105 }, 106 107 /** 108 * @param {Array.<SourceMapV3.Section>} sections 109 */ 110 _parseSections: function(sections) 111 { 112 for (var i = 0; i < sections.length; ++i) { 113 var section = sections[i]; 114 this._parseMap(section.map, section.offset.line, section.offset.column); 115 } 116 }, 117 118 /** 119 * @param {number} lineNumber in compiled resource 120 * @param {number} columnNumber in compiled resource 121 * @return {?Array} 122 */ 123 findEntry: function(lineNumber, columnNumber) 124 { 125 var first = 0; 126 var count = this._mappings.length; 127 while (count > 1) { 128 var step = count >> 1; 129 var middle = first + step; 130 var mapping = this._mappings[middle]; 131 if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1])) 132 count = step; 133 else { 134 first = middle; 135 count -= step; 136 } 137 } 138 var entry = this._mappings[first]; 139 if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1]))) 140 return null; 141 return entry; 142 }, 143 144 /** 145 * @param {string} sourceURL of the originating resource 146 * @param {number} lineNumber in the originating resource 147 * @return {Array} 148 */ 149 findEntryReversed: function(sourceURL, lineNumber) 150 { 151 var mappings = this._reverseMappingsBySourceURL[sourceURL]; 152 for ( ; lineNumber < mappings.length; ++lineNumber) { 153 var mapping = mappings[lineNumber]; 154 if (mapping) 155 return mapping; 156 } 157 return this._mappings[0]; 158 }, 159 160 /** 161 * @override 162 */ 163 _parseMap: function(map, lineNumber, columnNumber) 164 { 165 var sourceIndex = 0; 166 var sourceLineNumber = 0; 167 var sourceColumnNumber = 0; 168 var nameIndex = 0; 169 170 var sources = []; 171 var originalToCanonicalURLMap = {}; 172 for (var i = 0; i < map.sources.length; ++i) { 173 var originalSourceURL = map.sources[i]; 174 var href = (map.sourceRoot ? map.sourceRoot + "/" : "") + originalSourceURL; 175 var url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href; 176 originalToCanonicalURLMap[originalSourceURL] = url; 177 sources.push(url); 178 this._sources[url] = true; 179 180 if (map.sourcesContent && map.sourcesContent[i]) 181 this._sourceContentByURL[url] = map.sourcesContent[i]; 182 } 183 184 var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings); 185 var sourceURL = sources[sourceIndex]; 186 187 while (true) { 188 if (stringCharIterator.peek() === ",") 189 stringCharIterator.next(); 190 else { 191 while (stringCharIterator.peek() === ";") { 192 lineNumber += 1; 193 columnNumber = 0; 194 stringCharIterator.next(); 195 } 196 if (!stringCharIterator.hasNext()) 197 break; 198 } 199 200 columnNumber += this._decodeVLQ(stringCharIterator); 201 if (this._isSeparator(stringCharIterator.peek())) { 202 this._mappings.push([lineNumber, columnNumber]); 203 continue; 204 } 205 206 var sourceIndexDelta = this._decodeVLQ(stringCharIterator); 207 if (sourceIndexDelta) { 208 sourceIndex += sourceIndexDelta; 209 sourceURL = sources[sourceIndex]; 210 } 211 sourceLineNumber += this._decodeVLQ(stringCharIterator); 212 sourceColumnNumber += this._decodeVLQ(stringCharIterator); 213 if (!this._isSeparator(stringCharIterator.peek())) 214 nameIndex += this._decodeVLQ(stringCharIterator); 215 216 this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]); 217 } 218 219 for (var i = 0; i < this._mappings.length; ++i) { 220 var mapping = this._mappings[i]; 221 var url = mapping[2]; 222 if (!url) 223 continue; 224 if (!this._reverseMappingsBySourceURL[url]) 225 this._reverseMappingsBySourceURL[url] = []; 226 var reverseMappings = this._reverseMappingsBySourceURL[url]; 227 var sourceLine = mapping[3]; 228 if (!reverseMappings[sourceLine]) 229 reverseMappings[sourceLine] = [mapping[0], mapping[1]]; 230 } 231 }, 232 233 /** 234 * @param {string} char 235 * @return {boolean} 236 */ 237 _isSeparator: function(char) 238 { 239 return char === "," || char === ";"; 240 }, 241 242 /** 243 * @param {WebInspector.SourceMap.StringCharIterator} stringCharIterator 244 * @return {number} 245 */ 246 _decodeVLQ: function(stringCharIterator) 247 { 248 // Read unsigned value. 249 var result = 0; 250 var shift = 0; 251 do { 252 var digit = this._base64Map[stringCharIterator.next()]; 253 result += (digit & this._VLQ_BASE_MASK) << shift; 254 shift += this._VLQ_BASE_SHIFT; 255 } while (digit & this._VLQ_CONTINUATION_MASK); 256 257 // Fix the sign. 258 var negative = result & 1; 259 result >>= 1; 260 return negative ? -result : result; 261 }, 262 263 _VLQ_BASE_SHIFT: 5, 264 _VLQ_BASE_MASK: (1 << 5) - 1, 265 _VLQ_CONTINUATION_MASK: 1 << 5 266} 267 268/** 269 * @constructor 270 * @param {string} string 271 */ 272WebInspector.SourceMap.StringCharIterator = function(string) 273{ 274 this._string = string; 275 this._position = 0; 276} 277 278WebInspector.SourceMap.StringCharIterator.prototype = { 279 /** 280 * @return {string} 281 */ 282 next: function() 283 { 284 return this._string.charAt(this._position++); 285 }, 286 287 /** 288 * @return {string} 289 */ 290 peek: function() 291 { 292 return this._string.charAt(this._position); 293 }, 294 295 /** 296 * @return {boolean} 297 */ 298 hasNext: function() 299 { 300 return this._position < this._string.length; 301 } 302} 303