1/* 2 * Copyright (C) 2013 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.SourceMap = function(sourceMappingURL, payload, originalSourceCode) 27{ 28 if (!WebInspector.SourceMap.prototype._base64Map) { 29 const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 30 WebInspector.SourceMap.prototype._base64Map = {}; 31 for (var i = 0; i < base64Digits.length; ++i) 32 WebInspector.SourceMap.prototype._base64Map[base64Digits.charAt(i)] = i; 33 } 34 35 this._originalSourceCode = originalSourceCode || null; 36 this._sourceMapResources = {}; 37 this._sourceMapResourcesList = []; 38 39 this._sourceMappingURL = sourceMappingURL; 40 this._reverseMappingsBySourceURL = {}; 41 this._mappings = []; 42 this._sources = {}; 43 this._sourceRoot = null; 44 this._sourceContentByURL = {}; 45 this._parseMappingPayload(payload); 46} 47 48WebInspector.SourceMap.prototype = { 49 50 get originalSourceCode() 51 { 52 return this._originalSourceCode; 53 }, 54 55 get sourceMappingBasePathURLComponents() 56 { 57 if (this._sourceMappingURLBasePathComponents) 58 return this._sourceMappingURLBasePathComponents; 59 60 if (this._sourceRoot) { 61 var baseURLPath = absoluteURL(this._sourceRoot, this._sourceMappingURL); 62 console.assert(baseURLPath); 63 if (baseURLPath) { 64 var urlComponents = parseURL(baseURLPath); 65 if (!/\/$/.test(urlComponents.path)) 66 urlComponents.path += "/"; 67 this._sourceMappingURLBasePathComponents = urlComponents; 68 return this._sourceMappingURLBasePathComponents; 69 } 70 } 71 72 var urlComponents = parseURL(this._sourceMappingURL); 73 urlComponents.path = urlComponents.path.substr(0, urlComponents.path.lastIndexOf(urlComponents.lastPathComponent)); 74 urlComponents.lastPathComponent = null; 75 this._sourceMappingURLBasePathComponents = urlComponents; 76 return this._sourceMappingURLBasePathComponents; 77 }, 78 79 get resources() 80 { 81 return this._sourceMapResourcesList; 82 }, 83 84 addResource: function(resource) 85 { 86 console.assert(!(resource.url in this._sourceMapResources)); 87 this._sourceMapResources[resource.url] = resource; 88 this._sourceMapResourcesList.push(resource); 89 }, 90 91 resourceForURL: function(url) 92 { 93 return this._sourceMapResources[url]; 94 }, 95 96 sources: function() 97 { 98 return Object.keys(this._sources); 99 }, 100 101 sourceContent: function(sourceURL) 102 { 103 return this._sourceContentByURL[sourceURL]; 104 }, 105 106 _parseMappingPayload: function(mappingPayload) 107 { 108 if (mappingPayload.sections) 109 this._parseSections(mappingPayload.sections); 110 else 111 this._parseMap(mappingPayload, 0, 0); 112 }, 113 114 _parseSections: function(sections) 115 { 116 for (var i = 0; i < sections.length; ++i) { 117 var section = sections[i]; 118 this._parseMap(section.map, section.offset.line, section.offset.column); 119 } 120 }, 121 122 findEntry: function(lineNumber, columnNumber) 123 { 124 var first = 0; 125 var count = this._mappings.length; 126 while (count > 1) { 127 var step = count >> 1; 128 var middle = first + step; 129 var mapping = this._mappings[middle]; 130 if (lineNumber < mapping[0] || (lineNumber === mapping[0] && columnNumber < mapping[1])) 131 count = step; 132 else { 133 first = middle; 134 count -= step; 135 } 136 } 137 var entry = this._mappings[first]; 138 if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1]))) 139 return null; 140 return entry; 141 }, 142 143 findEntryReversed: function(sourceURL, lineNumber) 144 { 145 var mappings = this._reverseMappingsBySourceURL[sourceURL]; 146 for ( ; lineNumber < mappings.length; ++lineNumber) { 147 var mapping = mappings[lineNumber]; 148 if (mapping) 149 return mapping; 150 } 151 return this._mappings[0]; 152 }, 153 154 _parseMap: function(map, lineNumber, columnNumber) 155 { 156 var sourceIndex = 0; 157 var sourceLineNumber = 0; 158 var sourceColumnNumber = 0; 159 var nameIndex = 0; 160 161 var sources = []; 162 var originalToCanonicalURLMap = {}; 163 for (var i = 0; i < map.sources.length; ++i) { 164 var originalSourceURL = map.sources[i]; 165 var href = originalSourceURL; 166 if (map.sourceRoot && href.charAt(0) !== "/") 167 href = map.sourceRoot.replace(/\/+$/, "") + "/" + href; 168 var url = absoluteURL(href, this._sourceMappingURL) || href; 169 originalToCanonicalURLMap[originalSourceURL] = url; 170 sources.push(url); 171 this._sources[url] = true; 172 173 if (map.sourcesContent && map.sourcesContent[i]) 174 this._sourceContentByURL[url] = map.sourcesContent[i]; 175 } 176 177 this._sourceRoot = map.sourceRoot || null; 178 179 var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings); 180 var sourceURL = sources[sourceIndex]; 181 182 while (true) { 183 if (stringCharIterator.peek() === ",") 184 stringCharIterator.next(); 185 else { 186 while (stringCharIterator.peek() === ";") { 187 lineNumber += 1; 188 columnNumber = 0; 189 stringCharIterator.next(); 190 } 191 if (!stringCharIterator.hasNext()) 192 break; 193 } 194 195 columnNumber += this._decodeVLQ(stringCharIterator); 196 if (this._isSeparator(stringCharIterator.peek())) { 197 this._mappings.push([lineNumber, columnNumber]); 198 continue; 199 } 200 201 var sourceIndexDelta = this._decodeVLQ(stringCharIterator); 202 if (sourceIndexDelta) { 203 sourceIndex += sourceIndexDelta; 204 sourceURL = sources[sourceIndex]; 205 } 206 sourceLineNumber += this._decodeVLQ(stringCharIterator); 207 sourceColumnNumber += this._decodeVLQ(stringCharIterator); 208 if (!this._isSeparator(stringCharIterator.peek())) 209 nameIndex += this._decodeVLQ(stringCharIterator); 210 211 this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]); 212 } 213 214 for (var i = 0; i < this._mappings.length; ++i) { 215 var mapping = this._mappings[i]; 216 var url = mapping[2]; 217 if (!url) 218 continue; 219 if (!this._reverseMappingsBySourceURL[url]) 220 this._reverseMappingsBySourceURL[url] = []; 221 var reverseMappings = this._reverseMappingsBySourceURL[url]; 222 var sourceLine = mapping[3]; 223 if (!reverseMappings[sourceLine]) 224 reverseMappings[sourceLine] = [mapping[0], mapping[1]]; 225 } 226 }, 227 228 _isSeparator: function(char) 229 { 230 return char === "," || char === ";"; 231 }, 232 233 _decodeVLQ: function(stringCharIterator) 234 { 235 // Read unsigned value. 236 var result = 0; 237 var shift = 0; 238 do { 239 var digit = this._base64Map[stringCharIterator.next()]; 240 result += (digit & this._VLQ_BASE_MASK) << shift; 241 shift += this._VLQ_BASE_SHIFT; 242 } while (digit & this._VLQ_CONTINUATION_MASK); 243 244 // Fix the sign. 245 var negative = result & 1; 246 result >>= 1; 247 return negative ? -result : result; 248 }, 249 250 _VLQ_BASE_SHIFT: 5, 251 _VLQ_BASE_MASK: (1 << 5) - 1, 252 _VLQ_CONTINUATION_MASK: 1 << 5 253} 254 255WebInspector.SourceMap.StringCharIterator = function(string) 256{ 257 this._string = string; 258 this._position = 0; 259} 260 261WebInspector.SourceMap.StringCharIterator.prototype = { 262 next: function() 263 { 264 return this._string.charAt(this._position++); 265 }, 266 267 peek: function() 268 { 269 return this._string.charAt(this._position); 270 }, 271 272 hasNext: function() 273 { 274 return this._position < this._string.length; 275 } 276} 277