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.SourceCodeLocation = function(sourceCode, lineNumber, columnNumber) 27{ 28 WebInspector.Object.call(this); 29 30 console.assert(sourceCode === null || sourceCode instanceof WebInspector.SourceCode); 31 console.assert(!(sourceCode instanceof WebInspector.SourceMapResource)); 32 console.assert(typeof lineNumber === "number" && !isNaN(lineNumber) && lineNumber >= 0); 33 console.assert(typeof columnNumber === "number" && !isNaN(columnNumber) && columnNumber >= 0); 34 35 this._sourceCode = sourceCode || null; 36 this._lineNumber = lineNumber; 37 this._columnNumber = columnNumber; 38 this._resolveFormattedLocation(); 39 40 if (this._sourceCode) { 41 this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); 42 this._sourceCode.addEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); 43 } 44 45 this._resetMappedLocation(); 46}; 47 48WebInspector.SourceCodeLocation.DisplayLocationClassName = "display-location"; 49 50WebInspector.SourceCodeLocation.LargeColumnNumber = 80; 51 52WebInspector.SourceCodeLocation.NameStyle = { 53 None: "none", // File name not included. 54 Short: "short", // Only the file name. 55 Full: "full" // Full URL is used. 56}; 57 58WebInspector.SourceCodeLocation.ColumnStyle = { 59 Hidden: "hidden", // column numbers are not included. 60 OnlyIfLarge: "only-if-large", // column numbers greater than 80 are shown. 61 Shown: "shown" // non-zero column numbers are shown. 62}; 63 64WebInspector.SourceCodeLocation.Event = { 65 LocationChanged: "source-code-location-location-changed", 66 DisplayLocationChanged: "source-code-location-display-location-changed" 67}; 68 69WebInspector.SourceCodeLocation.prototype = { 70 constructor: WebInspector.SourceCodeLocation, 71 72 // Public 73 74 isEqual: function(other) 75 { 76 if (!other) 77 return false; 78 return this._sourceCode === other._sourceCode && this._lineNumber === other._lineNumber && this._columnNumber === other._columnNumber; 79 }, 80 81 get sourceCode() 82 { 83 return this._sourceCode; 84 }, 85 86 set sourceCode(sourceCode) 87 { 88 this.setSourceCode(sourceCode); 89 }, 90 91 // Raw line and column in the original source code. 92 93 get lineNumber() 94 { 95 return this._lineNumber; 96 }, 97 98 get columnNumber() 99 { 100 return this._columnNumber; 101 }, 102 103 position: function() 104 { 105 return new WebInspector.SourceCodePosition(this.lineNumber, this.columnNumber); 106 }, 107 108 // Formatted line and column if the original source code is pretty printed. 109 // This is the same as the raw location if there is no formatter. 110 111 get formattedLineNumber() 112 { 113 return this._formattedLineNumber; 114 }, 115 116 get formattedColumnNumber() 117 { 118 return this._formattedColumnNumber; 119 }, 120 121 formattedPosition: function() 122 { 123 return new WebInspector.SourceCodePosition(this.formattedLineNumber, this.formattedColumnNumber); 124 }, 125 126 // Display line and column: 127 // - Mapped line and column if the original source code has a source map. 128 // - Otherwise this is the formatted / raw line and column. 129 130 get displaySourceCode() 131 { 132 this.resolveMappedLocation(); 133 return this._mappedResource || this._sourceCode; 134 }, 135 136 get displayLineNumber() 137 { 138 this.resolveMappedLocation(); 139 return isNaN(this._mappedLineNumber) ? this._formattedLineNumber : this._mappedLineNumber; 140 }, 141 142 get displayColumnNumber() 143 { 144 this.resolveMappedLocation(); 145 return isNaN(this._mappedColumnNumber) ? this._formattedColumnNumber : this._mappedColumnNumber; 146 }, 147 148 displayPosition: function() 149 { 150 return new WebInspector.SourceCodePosition(this.displayLineNumber, this.displayColumnNumber); 151 }, 152 153 // User presentable location strings: "file:lineNumber:columnNumber". 154 155 originalLocationString: function(columnStyle, nameStyle, prefix) 156 { 157 return this._locationString(this.sourceCode, this.lineNumber, this.columnNumber, columnStyle, nameStyle, prefix); 158 }, 159 160 formattedLocationString: function(columnStyle, nameStyle, prefix) 161 { 162 return this._locationString(this.sourceCode, this.formattedLineNumber, this.formattedColumn, columnStyle, nameStyle, prefix); 163 }, 164 165 displayLocationString: function(columnStyle, nameStyle, prefix) 166 { 167 return this._locationString(this.displaySourceCode, this.displayLineNumber, this.displayColumnNumber, columnStyle, nameStyle, prefix); 168 }, 169 170 tooltipString: function() 171 { 172 if (!this.hasDifferentDisplayLocation()) 173 return this.originalLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, WebInspector.SourceCodeLocation.NameStyle.Full); 174 175 var tooltip = WebInspector.UIString("Located at %s").format(this.displayLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, WebInspector.SourceCodeLocation.NameStyle.Full)); 176 tooltip += "\n" + WebInspector.UIString("Originally %s").format(this.originalLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, WebInspector.SourceCodeLocation.NameStyle.Full)); 177 return tooltip; 178 }, 179 180 hasMappedLocation: function() 181 { 182 this.resolveMappedLocation(); 183 return this._mappedResource !== null; 184 }, 185 186 hasFormattedLocation: function() 187 { 188 return this._formattedLineNumber !== this._lineNumber || this._formattedColumnNumber !== this._columnNumber; 189 }, 190 191 hasDifferentDisplayLocation: function() 192 { 193 return this.hasMappedLocation() || this.hasFormattedLocation(); 194 }, 195 196 update: function(sourceCode, lineNumber, columnNumber) 197 { 198 console.assert(sourceCode === this._sourceCode || (this._mappedResource && sourceCode === this._mappedResource)); 199 console.assert(typeof lineNumber === "number" && !isNaN(lineNumber) && lineNumber >= 0); 200 console.assert(typeof columnNumber === "number" && !isNaN(columnNumber) && columnNumber >= 0); 201 202 if (sourceCode === this._sourceCode && lineNumber === this._lineNumber && columnNumber === this._columnNumber) 203 return; 204 else if (this._mappedResource && sourceCode === this._mappedResource && lineNumber === this._mappedLineNumber && columnNumber === this._mappedColumnNumber) 205 return; 206 207 var newSourceCodeLocation = sourceCode.createSourceCodeLocation(lineNumber, columnNumber); 208 console.assert(newSourceCodeLocation.sourceCode === this._sourceCode); 209 210 this._makeChangeAndDispatchChangeEventIfNeeded(function() { 211 this._lineNumber = newSourceCodeLocation._lineNumber; 212 this._columnNumber = newSourceCodeLocation._columnNumber; 213 if (newSourceCodeLocation._mappedLocationIsResolved) { 214 this._mappedLocationIsResolved = true; 215 this._mappedResource = newSourceCodeLocation._mappedResource; 216 this._mappedLineNumber = newSourceCodeLocation._mappedLineNumber; 217 this._mappedColumnNumber = newSourceCodeLocation._mappedColumnNumber; 218 } 219 }); 220 }, 221 222 populateLiveDisplayLocationTooltip: function(element, prefix) 223 { 224 prefix = prefix || ""; 225 226 element.title = prefix + this.tooltipString(); 227 228 this.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, function(event) { 229 element.title = prefix + this.tooltipString(); 230 }, this); 231 }, 232 233 populateLiveDisplayLocationString: function(element, propertyName, columnStyle, nameStyle, prefix) 234 { 235 var currentDisplay = undefined; 236 237 function updateDisplayString(showAlternativeLocation, forceUpdate) 238 { 239 if (!forceUpdate && currentDisplay === showAlternativeLocation) 240 return; 241 242 currentDisplay = showAlternativeLocation; 243 244 if (!showAlternativeLocation) { 245 element[propertyName] = this.displayLocationString(columnStyle, nameStyle, prefix); 246 element.classList.toggle(WebInspector.SourceCodeLocation.DisplayLocationClassName, this.hasDifferentDisplayLocation()); 247 } else if (this.hasDifferentDisplayLocation()) { 248 element[propertyName] = this.originalLocationString(columnStyle, nameStyle, prefix); 249 element.classList.remove(WebInspector.SourceCodeLocation.DisplayLocationClassName); 250 } 251 } 252 253 function mouseOverOrMove(event) 254 { 255 updateDisplayString.call(this, event.metaKey && !event.altKey && !event.shiftKey); 256 } 257 258 updateDisplayString.call(this, false); 259 260 this.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, function(event) { 261 updateDisplayString.call(this, currentDisplay, true); 262 }, this); 263 264 var boundMouseOverOrMove = mouseOverOrMove.bind(this); 265 element.addEventListener("mouseover", boundMouseOverOrMove); 266 element.addEventListener("mousemove", boundMouseOverOrMove); 267 268 element.addEventListener("mouseout", function(event) { 269 updateDisplayString.call(this, false); 270 }.bind(this)); 271 }, 272 273 // Protected 274 275 setSourceCode: function(sourceCode) 276 { 277 console.assert((this._sourceCode === null && sourceCode instanceof WebInspector.SourceCode) || (this._sourceCode instanceof WebInspector.SourceCode && sourceCode === null)); 278 279 if (sourceCode === this._sourceCode) 280 return; 281 282 this._makeChangeAndDispatchChangeEventIfNeeded(function() { 283 if (this._sourceCode) { 284 this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); 285 this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); 286 } 287 288 this._sourceCode = sourceCode; 289 290 if (this._sourceCode) { 291 this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); 292 this._sourceCode.addEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); 293 } 294 }); 295 }, 296 297 resolveMappedLocation: function() 298 { 299 if (this._mappedLocationIsResolved) 300 return; 301 302 console.assert(this._mappedResource === null); 303 console.assert(isNaN(this._mappedLineNumber)); 304 console.assert(isNaN(this._mappedColumnNumber)); 305 306 this._mappedLocationIsResolved = true; 307 308 if (!this._sourceCode) 309 return; 310 311 var sourceMaps = this._sourceCode.sourceMaps; 312 if (!sourceMaps.length) 313 return; 314 315 for (var i = 0; i < sourceMaps.length; ++i) { 316 var sourceMap = sourceMaps[i]; 317 var entry = sourceMap.findEntry(this._lineNumber, this._columnNumber); 318 if (!entry || entry.length === 2) 319 continue; 320 console.assert(entry.length === 5); 321 var url = entry[2]; 322 var sourceMapResource = sourceMap.resourceForURL(url); 323 if (!sourceMapResource) 324 return; 325 this._mappedResource = sourceMapResource; 326 this._mappedLineNumber = entry[3]; 327 this._mappedColumnNumber = entry[4]; 328 return; 329 } 330 }, 331 332 // Private 333 334 _locationString: function(sourceCode, lineNumber, columnNumber, columnStyle, nameStyle, prefix) 335 { 336 console.assert(sourceCode); 337 if (!sourceCode) 338 return ""; 339 340 columnStyle = columnStyle || WebInspector.SourceCodeLocation.ColumnStyle.OnlyIfLarge; 341 nameStyle = nameStyle || WebInspector.SourceCodeLocation.NameStyle.Short; 342 prefix = prefix || ""; 343 344 var lineString = lineNumber + 1; // The user visible line number is 1-based. 345 if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.Shown && columnNumber > 0) 346 lineString += ":" + (columnNumber + 1); // The user visible column number is 1-based. 347 else if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.OnlyIfLarge && columnNumber > WebInspector.SourceCodeLocation.LargeColumnNumber) 348 lineString += ":" + (columnNumber + 1); // The user visible column number is 1-based. 349 350 switch (nameStyle) { 351 case WebInspector.SourceCodeLocation.NameStyle.None: 352 return prefix + lineString; 353 354 case WebInspector.SourceCodeLocation.NameStyle.Short: 355 case WebInspector.SourceCodeLocation.NameStyle.Full: 356 var lineSuffix = sourceCode.url ? ":" + lineString : WebInspector.UIString(" (line %s)").format(lineString); 357 return prefix + (nameStyle === WebInspector.SourceCodeLocation.NameStyle.Full && sourceCode.url ? sourceCode.url : sourceCode.displayName) + lineSuffix; 358 359 default: 360 console.error("Unknown nameStyle: " + nameStyle); 361 return prefix + lineString; 362 } 363 }, 364 365 _resetMappedLocation: function() 366 { 367 this._mappedLocationIsResolved = false; 368 this._mappedResource = null; 369 this._mappedLineNumber = NaN; 370 this._mappedColumnNumber = NaN; 371 }, 372 373 _setMappedLocation: function(mappedResource, mappedLineNumber, mappedColumnNumber) 374 { 375 // Called by SourceMapResource when it creates a SourceCodeLocation and already knows the resolved location. 376 this._mappedLocationIsResolved = true; 377 this._mappedResource = mappedResource; 378 this._mappedLineNumber = mappedLineNumber; 379 this._mappedColumnNumber = mappedColumnNumber; 380 }, 381 382 _resolveFormattedLocation: function() 383 { 384 if (this._sourceCode && this._sourceCode.formatterSourceMap) { 385 var formattedLocation = this._sourceCode.formatterSourceMap.originalToFormatted(this._lineNumber, this._columnNumber); 386 this._formattedLineNumber = formattedLocation.lineNumber; 387 this._formattedColumnNumber = formattedLocation.columnNumber; 388 } else { 389 this._formattedLineNumber = this._lineNumber; 390 this._formattedColumnNumber = this._columnNumber; 391 } 392 }, 393 394 _makeChangeAndDispatchChangeEventIfNeeded: function(changeFunction) 395 { 396 var oldSourceCode = this._sourceCode; 397 var oldLineNumber = this._lineNumber; 398 var oldColumnNumber = this._columnNumber; 399 400 var oldFormattedLineNumber = this._formattedLineNumber; 401 var oldFormattedColumnNumber = this._formattedColumnNumber; 402 403 var oldDisplaySourceCode = this.displaySourceCode; 404 var oldDisplayLineNumber = this.displayLineNumber; 405 var oldDisplayColumnNumber = this.displayColumnNumber; 406 407 this._resetMappedLocation(); 408 409 if (changeFunction) 410 changeFunction.call(this); 411 412 this.resolveMappedLocation(); 413 this._resolveFormattedLocation(); 414 415 // If the display source code is non-null then the addresses are not NaN and can be compared. 416 var displayLocationChanged = false; 417 var newDisplaySourceCode = this.displaySourceCode; 418 if (oldDisplaySourceCode !== newDisplaySourceCode) 419 displayLocationChanged = true; 420 else if (newDisplaySourceCode && (oldDisplayLineNumber !== this.displayLineNumber || oldDisplayColumnNumber !== this.displayColumnNumber)) 421 displayLocationChanged = true; 422 423 var anyLocationChanged = false; 424 if (displayLocationChanged) 425 anyLocationChanged = true; 426 else if (oldSourceCode !== this._sourceCode) 427 anyLocationChanged = true; 428 else if (this._sourceCode && (oldLineNumber !== this._lineNumber || oldColumnNumber !== this._columnNumber)) 429 anyLocationChanged = true; 430 else if (this._sourceCode && (oldFormattedLineNumber !== this._formattedLineNumber || oldFormattedColumnNumber !== this._formattedColumnNumber)) 431 anyLocationChanged = true; 432 433 if (displayLocationChanged || anyLocationChanged) { 434 var oldData = { 435 oldSourceCode: oldSourceCode, 436 oldLineNumber: oldLineNumber, 437 oldColumnNumber: oldColumnNumber, 438 oldFormattedLineNumber: oldFormattedLineNumber, 439 oldFormattedColumnNumber: oldFormattedColumnNumber, 440 oldDisplaySourceCode: oldDisplaySourceCode, 441 oldDisplayLineNumber: oldDisplayLineNumber, 442 oldDisplayColumnNumber: oldDisplayColumnNumber 443 }; 444 if (displayLocationChanged) 445 this.dispatchEventToListeners(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, oldData); 446 if (anyLocationChanged) 447 this.dispatchEventToListeners(WebInspector.SourceCodeLocation.Event.LocationChanged, oldData); 448 } 449 }, 450 451 _sourceCodeSourceMapAdded: function() 452 { 453 this._makeChangeAndDispatchChangeEventIfNeeded(null); 454 }, 455 456 _sourceCodeFormatterDidChange: function() 457 { 458 this._makeChangeAndDispatchChangeEventIfNeeded(null); 459 } 460}; 461 462WebInspector.SourceCodeLocation.prototype.__proto__ = WebInspector.Object.prototype; 463