1// Copyright 2013 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28// This is a copy from blink dev tools, see: 29// http://src.chromium.org/viewvc/blink/trunk/Source/devtools/front_end/SourceMap.js 30// revision: 153407 31 32// Added to make the file work without dev tools 33WebInspector = {}; 34WebInspector.ParsedURL = {}; 35WebInspector.ParsedURL.completeURL = function(){}; 36// start of original file content 37 38/* 39 * Copyright (C) 2012 Google Inc. All rights reserved. 40 * 41 * Redistribution and use in source and binary forms, with or without 42 * modification, are permitted provided that the following conditions are 43 * met: 44 * 45 * * Redistributions of source code must retain the above copyright 46 * notice, this list of conditions and the following disclaimer. 47 * * Redistributions in binary form must reproduce the above 48 * copyright notice, this list of conditions and the following disclaimer 49 * in the documentation and/or other materials provided with the 50 * distribution. 51 * * Neither the name of Google Inc. nor the names of its 52 * contributors may be used to endorse or promote products derived from 53 * this software without specific prior written permission. 54 * 55 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 56 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 57 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 58 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 59 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 60 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 61 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 62 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 63 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 64 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 65 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 66 */ 67 68/** 69 * Implements Source Map V3 model. See http://code.google.com/p/closure-compiler/wiki/SourceMaps 70 * for format description. 71 * @constructor 72 * @param {string} sourceMappingURL 73 * @param {SourceMapV3} payload 74 */ 75WebInspector.SourceMap = function(sourceMappingURL, payload) 76{ 77 if (!WebInspector.SourceMap.prototype._base64Map) { 78 const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 79 WebInspector.SourceMap.prototype._base64Map = {}; 80 for (var i = 0; i < base64Digits.length; ++i) 81 WebInspector.SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i; 82 } 83 84 this._sourceMappingURL = sourceMappingURL; 85 this._reverseMappingsBySourceURL = {}; 86 this._mappings = []; 87 this._sources = {}; 88 this._sourceContentByURL = {}; 89 this._parseMappingPayload(payload); 90} 91 92/** 93 * @param {string} sourceMapURL 94 * @param {string} compiledURL 95 * @param {function(WebInspector.SourceMap)} callback 96 */ 97WebInspector.SourceMap.load = function(sourceMapURL, compiledURL, callback) 98{ 99 NetworkAgent.loadResourceForFrontend(WebInspector.resourceTreeModel.mainFrame.id, sourceMapURL, undefined, contentLoaded.bind(this)); 100 101 /** 102 * @param {?Protocol.Error} error 103 * @param {number} statusCode 104 * @param {NetworkAgent.Headers} headers 105 * @param {string} content 106 */ 107 function contentLoaded(error, statusCode, headers, content) 108 { 109 if (error || !content || statusCode >= 400) { 110 console.error("Could not load content for " + sourceMapURL + " : " + (error || ("HTTP status code: " + statusCode))); 111 callback(null); 112 return; 113 } 114 115 if (content.slice(0, 3) === ")]}") 116 content = content.substring(content.indexOf('\n')); 117 try { 118 var payload = /** @type {SourceMapV3} */ (JSON.parse(content)); 119 var baseURL = sourceMapURL.startsWith("data:") ? compiledURL : sourceMapURL; 120 callback(new WebInspector.SourceMap(baseURL, payload)); 121 } catch(e) { 122 console.error(e.message); 123 callback(null); 124 } 125 } 126} 127 128WebInspector.SourceMap.prototype = { 129 /** 130 * @return {Array.<string>} 131 */ 132 sources: function() 133 { 134 return Object.keys(this._sources); 135 }, 136 137 /** 138 * @param {string} sourceURL 139 * @return {string|undefined} 140 */ 141 sourceContent: function(sourceURL) 142 { 143 return this._sourceContentByURL[sourceURL]; 144 }, 145 146 /** 147 * @param {string} sourceURL 148 * @param {WebInspector.ResourceType} contentType 149 * @return {WebInspector.ContentProvider} 150 */ 151 sourceContentProvider: function(sourceURL, contentType) 152 { 153 var lastIndexOfDot = sourceURL.lastIndexOf("."); 154 var extension = lastIndexOfDot !== -1 ? sourceURL.substr(lastIndexOfDot + 1) : ""; 155 var mimeType = WebInspector.ResourceType.mimeTypesForExtensions[extension.toLowerCase()]; 156 var sourceContent = this.sourceContent(sourceURL); 157 if (sourceContent) 158 return new WebInspector.StaticContentProvider(contentType, sourceContent, mimeType); 159 return new WebInspector.CompilerSourceMappingContentProvider(sourceURL, contentType, mimeType); 160 }, 161 162 /** 163 * @param {SourceMapV3} mappingPayload 164 */ 165 _parseMappingPayload: function(mappingPayload) 166 { 167 if (mappingPayload.sections) 168 this._parseSections(mappingPayload.sections); 169 else 170 this._parseMap(mappingPayload, 0, 0); 171 }, 172 173 /** 174 * @param {Array.<SourceMapV3.Section>} sections 175 */ 176 _parseSections: function(sections) 177 { 178 for (var i = 0; i < sections.length; ++i) { 179 var section = sections[i]; 180 this._parseMap(section.map, section.offset.line, section.offset.column); 181 } 182 }, 183 184 /** 185 * @param {number} lineNumber in compiled resource 186 * @param {number} columnNumber in compiled resource 187 * @return {?Array} 188 */ 189 findEntry: function(lineNumber, columnNumber) 190 { 191 var first = 0; 192 var count = this._mappings.length; 193 while (count > 1) { 194 var step = count >> 1; 195 var middle = first + step; 196 var mapping = this._mappings[middle]; 197 if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1])) 198 count = step; 199 else { 200 first = middle; 201 count -= step; 202 } 203 } 204 var entry = this._mappings[first]; 205 if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1]))) 206 return null; 207 return entry; 208 }, 209 210 /** 211 * @param {string} sourceURL of the originating resource 212 * @param {number} lineNumber in the originating resource 213 * @return {Array} 214 */ 215 findEntryReversed: function(sourceURL, lineNumber) 216 { 217 var mappings = this._reverseMappingsBySourceURL[sourceURL]; 218 for ( ; lineNumber < mappings.length; ++lineNumber) { 219 var mapping = mappings[lineNumber]; 220 if (mapping) 221 return mapping; 222 } 223 return this._mappings[0]; 224 }, 225 226 /** 227 * @override 228 */ 229 _parseMap: function(map, lineNumber, columnNumber) 230 { 231 var sourceIndex = 0; 232 var sourceLineNumber = 0; 233 var sourceColumnNumber = 0; 234 var nameIndex = 0; 235 236 var sources = []; 237 var originalToCanonicalURLMap = {}; 238 for (var i = 0; i < map.sources.length; ++i) { 239 var originalSourceURL = map.sources[i]; 240 var sourceRoot = map.sourceRoot || ""; 241 if (sourceRoot && !sourceRoot.endsWith("/")) 242 sourceRoot += "/"; 243 var href = sourceRoot + originalSourceURL; 244 var url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href; 245 originalToCanonicalURLMap[originalSourceURL] = url; 246 sources.push(url); 247 this._sources[url] = true; 248 249 if (map.sourcesContent && map.sourcesContent[i]) 250 this._sourceContentByURL[url] = map.sourcesContent[i]; 251 } 252 253 var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings); 254 var sourceURL = sources[sourceIndex]; 255 256 while (true) { 257 if (stringCharIterator.peek() === ",") 258 stringCharIterator.next(); 259 else { 260 while (stringCharIterator.peek() === ";") { 261 lineNumber += 1; 262 columnNumber = 0; 263 stringCharIterator.next(); 264 } 265 if (!stringCharIterator.hasNext()) 266 break; 267 } 268 269 columnNumber += this._decodeVLQ(stringCharIterator); 270 if (this._isSeparator(stringCharIterator.peek())) { 271 this._mappings.push([lineNumber, columnNumber]); 272 continue; 273 } 274 275 var sourceIndexDelta = this._decodeVLQ(stringCharIterator); 276 if (sourceIndexDelta) { 277 sourceIndex += sourceIndexDelta; 278 sourceURL = sources[sourceIndex]; 279 } 280 sourceLineNumber += this._decodeVLQ(stringCharIterator); 281 sourceColumnNumber += this._decodeVLQ(stringCharIterator); 282 if (!this._isSeparator(stringCharIterator.peek())) 283 nameIndex += this._decodeVLQ(stringCharIterator); 284 285 this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]); 286 } 287 288 for (var i = 0; i < this._mappings.length; ++i) { 289 var mapping = this._mappings[i]; 290 var url = mapping[2]; 291 if (!url) 292 continue; 293 if (!this._reverseMappingsBySourceURL[url]) 294 this._reverseMappingsBySourceURL[url] = []; 295 var reverseMappings = this._reverseMappingsBySourceURL[url]; 296 var sourceLine = mapping[3]; 297 if (!reverseMappings[sourceLine]) 298 reverseMappings[sourceLine] = [mapping[0], mapping[1]]; 299 } 300 }, 301 302 /** 303 * @param {string} char 304 * @return {boolean} 305 */ 306 _isSeparator: function(char) 307 { 308 return char === "," || char === ";"; 309 }, 310 311 /** 312 * @param {WebInspector.SourceMap.StringCharIterator} stringCharIterator 313 * @return {number} 314 */ 315 _decodeVLQ: function(stringCharIterator) 316 { 317 // Read unsigned value. 318 var result = 0; 319 var shift = 0; 320 do { 321 var digit = this._base64Map[stringCharIterator.next()]; 322 result += (digit & this._VLQ_BASE_MASK) << shift; 323 shift += this._VLQ_BASE_SHIFT; 324 } while (digit & this._VLQ_CONTINUATION_MASK); 325 326 // Fix the sign. 327 var negative = result & 1; 328 result >>= 1; 329 return negative ? -result : result; 330 }, 331 332 _VLQ_BASE_SHIFT: 5, 333 _VLQ_BASE_MASK: (1 << 5) - 1, 334 _VLQ_CONTINUATION_MASK: 1 << 5 335} 336 337/** 338 * @constructor 339 * @param {string} string 340 */ 341WebInspector.SourceMap.StringCharIterator = function(string) 342{ 343 this._string = string; 344 this._position = 0; 345} 346 347WebInspector.SourceMap.StringCharIterator.prototype = { 348 /** 349 * @return {string} 350 */ 351 next: function() 352 { 353 return this._string.charAt(this._position++); 354 }, 355 356 /** 357 * @return {string} 358 */ 359 peek: function() 360 { 361 return this._string.charAt(this._position); 362 }, 363 364 /** 365 * @return {boolean} 366 */ 367 hasNext: function() 368 { 369 return this._position < this._string.length; 370 } 371} 372