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 33export const WebInspector = {}; 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 (let 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 const payload = /** @type {SourceMapV3} */ (JSON.parse(content)); 119 const 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() 133 { 134 return Object.keys(this._sources); 135 }, 136 137 /** 138 * @param {string} sourceURL 139 * @return {string|undefined} 140 */ 141 sourceContent(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(sourceURL, contentType) 152 { 153 const lastIndexOfDot = sourceURL.lastIndexOf("."); 154 const extension = lastIndexOfDot !== -1 ? sourceURL.substr(lastIndexOfDot + 1) : ""; 155 const mimeType = WebInspector.ResourceType.mimeTypesForExtensions[extension.toLowerCase()]; 156 const 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(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(sections) 177 { 178 for (let i = 0; i < sections.length; ++i) { 179 const 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(lineNumber, columnNumber) 190 { 191 let first = 0; 192 let count = this._mappings.length; 193 while (count > 1) { 194 const step = count >> 1; 195 const middle = first + step; 196 const 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 const 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(sourceURL, lineNumber) 216 { 217 const mappings = this._reverseMappingsBySourceURL[sourceURL]; 218 for ( ; lineNumber < mappings.length; ++lineNumber) { 219 const mapping = mappings[lineNumber]; 220 if (mapping) 221 return mapping; 222 } 223 return this._mappings[0]; 224 }, 225 226 /** 227 * @override 228 */ 229 _parseMap(map, lineNumber, columnNumber) 230 { 231 let sourceIndex = 0; 232 let sourceLineNumber = 0; 233 let sourceColumnNumber = 0; 234 let nameIndex = 0; 235 236 const sources = []; 237 const originalToCanonicalURLMap = {}; 238 for (let i = 0; i < map.sources.length; ++i) { 239 const originalSourceURL = map.sources[i]; 240 let sourceRoot = map.sourceRoot || ""; 241 if (sourceRoot && !sourceRoot.endsWith("/")) sourceRoot += "/"; 242 const href = sourceRoot + originalSourceURL; 243 const url = WebInspector.ParsedURL.completeURL(this._sourceMappingURL, href) || href; 244 originalToCanonicalURLMap[originalSourceURL] = url; 245 sources.push(url); 246 this._sources[url] = true; 247 248 if (map.sourcesContent && map.sourcesContent[i]) { 249 this._sourceContentByURL[url] = map.sourcesContent[i]; 250 } 251 } 252 253 const stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings); 254 let 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 const 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 (let i = 0; i < this._mappings.length; ++i) { 289 const mapping = this._mappings[i]; 290 const url = mapping[2]; 291 if (!url) continue; 292 if (!this._reverseMappingsBySourceURL[url]) { 293 this._reverseMappingsBySourceURL[url] = []; 294 } 295 const reverseMappings = this._reverseMappingsBySourceURL[url]; 296 const sourceLine = mapping[3]; 297 if (!reverseMappings[sourceLine]) { 298 reverseMappings[sourceLine] = [mapping[0], mapping[1]]; 299 } 300 } 301 }, 302 303 /** 304 * @param {string} char 305 * @return {boolean} 306 */ 307 _isSeparator(char) 308 { 309 return char === "," || char === ";"; 310 }, 311 312 /** 313 * @param {WebInspector.SourceMap.StringCharIterator} stringCharIterator 314 * @return {number} 315 */ 316 _decodeVLQ(stringCharIterator) 317 { 318 // Read unsigned value. 319 let result = 0; 320 let shift = 0; 321 let digit; 322 do { 323 digit = this._base64Map[stringCharIterator.next()]; 324 result += (digit & this._VLQ_BASE_MASK) << shift; 325 shift += this._VLQ_BASE_SHIFT; 326 } while (digit & this._VLQ_CONTINUATION_MASK); 327 328 // Fix the sign. 329 const negate = result & 1; 330 // Use unsigned right shift, so that the 32nd bit is properly shifted 331 // to the 31st, and the 32nd becomes unset. 332 result >>>= 1; 333 if (negate) { 334 // We need to OR 0x80000000 here to ensure the 32nd bit (the sign bit 335 // in a 32bit int) is always set for negative numbers. If `result` 336 // were 1, (meaning `negate` is true and all other bits were zeros), 337 // `result` would now be 0. But -0 doesn't flip the 32nd bit as 338 // intended. All other numbers will successfully set the 32nd bit 339 // without issue, so doing this is a noop for them. 340 return -result | 0x80000000; 341 } 342 return result; 343 }, 344 345 _VLQ_BASE_SHIFT: 5, 346 _VLQ_BASE_MASK: (1 << 5) - 1, 347 _VLQ_CONTINUATION_MASK: 1 << 5 348} 349 350/** 351 * @constructor 352 * @param {string} string 353 */ 354WebInspector.SourceMap.StringCharIterator = function(string) 355{ 356 this._string = string; 357 this._position = 0; 358} 359 360WebInspector.SourceMap.StringCharIterator.prototype = { 361 /** 362 * @return {string} 363 */ 364 next() 365 { 366 return this._string.charAt(this._position++); 367 }, 368 369 /** 370 * @return {string} 371 */ 372 peek() 373 { 374 return this._string.charAt(this._position); 375 }, 376 377 /** 378 * @return {boolean} 379 */ 380 hasNext() 381 { 382 return this._position < this._string.length; 383 } 384} 385