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