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