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.SourceCodeTextEditor = function(sourceCode) 27{ 28 console.assert(sourceCode instanceof WebInspector.SourceCode); 29 30 this._sourceCode = sourceCode; 31 this._breakpointMap = {}; 32 this._issuesLineNumberMap = {}; 33 this._contentPopulated = false; 34 this._invalidLineNumbers = {0: true}; 35 this._ignoreContentDidChange = 0; 36 37 WebInspector.TextEditor.call(this, null, null, this); 38 39 // FIXME: Currently this just jumps between resources and related source map resources. It doesn't "jump to symbol" yet. 40 this._updateTokenTrackingControllerState(); 41 42 this.element.classList.add(WebInspector.SourceCodeTextEditor.StyleClassName); 43 44 if (this._supportsDebugging) { 45 WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._updateBreakpointStatus, this); 46 WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._updateBreakpointStatus, this); 47 WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._updateBreakpointStatus, this); 48 WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.LocationDidChange, this._updateBreakpointLocation, this); 49 50 WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this); 51 WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this); 52 WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this); 53 54 WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this); 55 WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this); 56 if (WebInspector.debuggerManager.activeCallFrame) 57 this._debuggerDidPause(); 58 59 this._activeCallFrameDidChange(); 60 } 61 62 WebInspector.issueManager.addEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this); 63 64 if (this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length > 0) 65 WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this); 66 else 67 this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); 68 69 sourceCode.requestContent(this._contentAvailable.bind(this)); 70 71 // FIXME: Cmd+L shorcut doesn't actually work. 72 new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command, "L", this.showGoToLineDialog.bind(this), this.element); 73 new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "G", this.showGoToLineDialog.bind(this), this.element); 74}; 75 76WebInspector.Object.addConstructorFunctions(WebInspector.SourceCodeTextEditor); 77 78WebInspector.SourceCodeTextEditor.StyleClassName = "source-code"; 79WebInspector.SourceCodeTextEditor.LineErrorStyleClassName = "error"; 80WebInspector.SourceCodeTextEditor.LineWarningStyleClassName = "warning"; 81WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName = "debugger-popover-content"; 82WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName = "hovered-expression-highlight"; 83WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken = 500; 84WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease = 1000; 85 86WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength = 500; 87 88WebInspector.SourceCodeTextEditor.Event = { 89 ContentWillPopulate: "source-code-text-editor-content-will-populate", 90 ContentDidPopulate: "source-code-text-editor-content-did-populate" 91}; 92 93WebInspector.SourceCodeTextEditor.prototype = { 94 constructor: WebInspector.SourceCodeTextEditor, 95 96 // Public 97 98 get sourceCode() 99 { 100 return this._sourceCode; 101 }, 102 103 hidden: function() 104 { 105 WebInspector.TextEditor.prototype.hidden.call(this); 106 107 this.tokenTrackingController.removeHighlightedRange(); 108 109 this._dismissPopover(); 110 111 this._dismissEditingController(true); 112 }, 113 114 close: function() 115 { 116 if (this._supportsDebugging) { 117 WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._updateBreakpointStatus, this); 118 WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._updateBreakpointStatus, this); 119 WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._updateBreakpointStatus, this); 120 WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.LocationDidChange, this._updateBreakpointLocation, this); 121 122 WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this); 123 WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this); 124 WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this); 125 126 if (this._activeCallFrameSourceCodeLocation) { 127 this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this); 128 delete this._activeCallFrameSourceCodeLocation; 129 } 130 } 131 132 WebInspector.issueManager.removeEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this); 133 134 WebInspector.notifications.removeEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this); 135 this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); 136 }, 137 138 canBeFormatted: function() 139 { 140 // Currently we assume that source map resources are formatted how the author wants it. 141 // We could allow source map resources to be formatted, we would then need to make 142 // SourceCodeLocation watch listen for mappedResource's formatting changes, and keep 143 // a formatted location alongside the regular mapped location. 144 if (this._sourceCode instanceof WebInspector.SourceMapResource) 145 return false; 146 147 return WebInspector.TextEditor.prototype.canBeFormatted.call(this); 148 }, 149 150 customPerformSearch: function(query) 151 { 152 function searchResultCallback(error, matches) 153 { 154 // Bail if the query changed since we started. 155 if (this.currentSearchQuery !== query) 156 return; 157 158 if (error || !matches || !matches.length) { 159 // Report zero matches. 160 this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange); 161 return; 162 } 163 164 var queryRegex = new RegExp(query.escapeForRegExp(), "gi"); 165 var searchResults = []; 166 167 for (var i = 0; i < matches.length; ++i) { 168 var matchLineNumber = matches[i].lineNumber; 169 var line = this.line(matchLineNumber); 170 171 // Reset the last index to reuse the regex on a new line. 172 queryRegex.lastIndex = 0; 173 174 // Search the line and mark the ranges. 175 var lineMatch = null; 176 while (queryRegex.lastIndex + query.length <= line.length && (lineMatch = queryRegex.exec(line))) { 177 var resultTextRange = new WebInspector.TextRange(matchLineNumber, lineMatch.index, matchLineNumber, queryRegex.lastIndex); 178 searchResults.push(resultTextRange); 179 } 180 } 181 182 this.addSearchResults(searchResults); 183 184 this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange); 185 } 186 187 if (this._sourceCode instanceof WebInspector.SourceMapResource) 188 return false; 189 190 if (this._sourceCode instanceof WebInspector.Resource) 191 PageAgent.searchInResource(this._sourceCode.parentFrame.id, this._sourceCode.url, query, false, false, searchResultCallback.bind(this)); 192 else if (this._sourceCode instanceof WebInspector.Script) 193 DebuggerAgent.searchInContent(this._sourceCode.id, query, false, false, searchResultCallback.bind(this)); 194 return true; 195 }, 196 197 showGoToLineDialog: function() 198 { 199 if (!this._goToLineDialog) { 200 this._goToLineDialog = new WebInspector.GoToLineDialog; 201 this._goToLineDialog.delegate = this; 202 } 203 204 this._goToLineDialog.present(this.element); 205 }, 206 207 isGoToLineDialogValueValid: function(goToLineDialog, lineNumber) 208 { 209 return !isNaN(lineNumber) && lineNumber > 0 && lineNumber <= this.lineCount; 210 }, 211 212 goToLineDialogValueWasValidated: function(goToLineDialog, lineNumber) 213 { 214 var position = new WebInspector.SourceCodePosition(lineNumber - 1, 0); 215 var range = new WebInspector.TextRange(lineNumber - 1, 0, lineNumber, 0); 216 this.revealPosition(position, range, false, true); 217 }, 218 219 goToLineDialogWasDismissed: function() 220 { 221 this.focus(); 222 }, 223 224 contentDidChange: function(replacedRanges, newRanges) 225 { 226 WebInspector.TextEditor.prototype.contentDidChange.call(this, replacedRanges, newRanges); 227 228 if (this._ignoreContentDidChange > 0) 229 return; 230 231 for (var range of newRanges) 232 this._updateEditableMarkers(range); 233 }, 234 235 // Private 236 237 _unformattedLineInfoForEditorLineInfo: function(lineInfo) 238 { 239 if (this.formatterSourceMap) 240 return this.formatterSourceMap.formattedToOriginal(lineInfo.lineNumber, lineInfo.columnNumber); 241 return lineInfo; 242 }, 243 244 _sourceCodeLocationForEditorPosition: function(position) 245 { 246 var lineInfo = {lineNumber: position.line, columnNumber: position.ch}; 247 var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(lineInfo); 248 return this.sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber); 249 }, 250 251 _editorLineInfoForSourceCodeLocation: function(sourceCodeLocation) 252 { 253 if (this._sourceCode instanceof WebInspector.SourceMapResource) 254 return {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber}; 255 return {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber}; 256 }, 257 258 _breakpointForEditorLineInfo: function(lineInfo) 259 { 260 if (!this._breakpointMap[lineInfo.lineNumber]) 261 return null; 262 return this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber]; 263 }, 264 265 _addBreakpointWithEditorLineInfo: function(breakpoint, lineInfo) 266 { 267 if (!this._breakpointMap[lineInfo.lineNumber]) 268 this._breakpointMap[lineInfo.lineNumber] = {}; 269 270 this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber] = breakpoint; 271 }, 272 273 _removeBreakpointWithEditorLineInfo: function(breakpoint, lineInfo) 274 { 275 console.assert(breakpoint === this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber]); 276 277 delete this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber]; 278 279 if (isEmptyObject(this._breakpointMap[lineInfo.lineNumber])) 280 delete this._breakpointMap[lineInfo.lineNumber]; 281 }, 282 283 _contentWillPopulate: function(content) 284 { 285 this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentWillPopulate); 286 287 // We only do the rest of this work before the first populate. 288 if (this._contentPopulated) 289 return; 290 291 if (this._supportsDebugging) { 292 this._breakpointMap = {}; 293 294 var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode); 295 for (var i = 0; i < breakpoints.length; ++i) { 296 var breakpoint = breakpoints[i]; 297 console.assert(this._matchesBreakpoint(breakpoint)); 298 var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation); 299 this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo); 300 this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint)); 301 } 302 } 303 304 if (this._sourceCode instanceof WebInspector.Resource) 305 this.mimeType = this._sourceCode.syntheticMIMEType; 306 else if (this._sourceCode instanceof WebInspector.Script) 307 this.mimeType = "text/javascript"; 308 309 // Automatically format the content if it looks minified and it can be formatted. 310 console.assert(!this.formatted); 311 if (this.canBeFormatted()) { 312 var lastNewlineIndex = 0; 313 while (true) { 314 var nextNewlineIndex = content.indexOf("\n", lastNewlineIndex); 315 if (nextNewlineIndex === -1) { 316 if (content.length - lastNewlineIndex > WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength) 317 this.autoFormat = true; 318 break; 319 } 320 321 if (nextNewlineIndex - lastNewlineIndex > WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength) { 322 this.autoFormat = true; 323 break; 324 } 325 326 lastNewlineIndex = nextNewlineIndex + 1; 327 } 328 } 329 }, 330 331 _contentDidPopulate: function() 332 { 333 this._contentPopulated = true; 334 335 this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentDidPopulate); 336 337 // We add the issues each time content is populated. This is needed because lines might not exist 338 // if we tried added them before when the full content wasn't avaiable. (When populating with 339 // partial script content this can be called multiple times.) 340 341 this._issuesLineNumberMap = {}; 342 343 var issues = WebInspector.issueManager.issuesForSourceCode(this._sourceCode); 344 for (var i = 0; i < issues.length; ++i) { 345 var issue = issues[i]; 346 console.assert(this._matchesIssue(issue)); 347 this._addIssue(issue); 348 } 349 350 this._updateEditableMarkers(); 351 }, 352 353 _populateWithContent: function(content) 354 { 355 content = content || ""; 356 357 this._contentWillPopulate(content); 358 this.string = content; 359 this._contentDidPopulate(); 360 }, 361 362 _contentAvailable: function(sourceCode, content, base64Encoded) 363 { 364 console.assert(sourceCode === this._sourceCode); 365 console.assert(!base64Encoded); 366 367 // Abort if the full content populated while waiting for this async callback. 368 if (this._fullContentPopulated) 369 return; 370 371 this._fullContentPopulated = true; 372 this._invalidLineNumbers = {}; 373 374 this._populateWithContent(content); 375 }, 376 377 _updateBreakpointStatus: function(event) 378 { 379 console.assert(this._supportsDebugging); 380 381 if (!this._contentPopulated) 382 return; 383 384 var breakpoint = event.target; 385 if (!this._matchesBreakpoint(breakpoint)) 386 return; 387 388 var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation); 389 this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint)); 390 }, 391 392 _updateBreakpointLocation: function(event) 393 { 394 console.assert(this._supportsDebugging); 395 396 if (!this._contentPopulated) 397 return; 398 399 var breakpoint = event.target; 400 if (!this._matchesBreakpoint(breakpoint)) 401 return; 402 403 if (this._ignoreAllBreakpointLocationUpdates) 404 return; 405 406 if (breakpoint === this._ignoreLocationUpdateBreakpoint) 407 return; 408 409 var sourceCodeLocation = breakpoint.sourceCodeLocation; 410 411 if (this._sourceCode instanceof WebInspector.SourceMapResource) { 412 // Update our breakpoint location if the display location changed. 413 if (sourceCodeLocation.displaySourceCode !== this._sourceCode) 414 return; 415 var oldLineInfo = {lineNumber: event.data.oldDisplayLineNumber, columnNumber: event.data.oldDisplayColumnNumber}; 416 var newLineInfo = {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber}; 417 } else { 418 // Update our breakpoint location if the original location changed. 419 if (sourceCodeLocation.sourceCode !== this._sourceCode) 420 return; 421 var oldLineInfo = {lineNumber: event.data.oldFormattedLineNumber, columnNumber: event.data.oldFormattedColumnNumber}; 422 var newLineInfo = {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber}; 423 } 424 425 var existingBreakpoint = this._breakpointForEditorLineInfo(oldLineInfo); 426 if (!existingBreakpoint) 427 return; 428 429 console.assert(breakpoint === existingBreakpoint); 430 431 this.setBreakpointInfoForLineAndColumn(oldLineInfo.lineNumber, oldLineInfo.columnNumber, null); 432 this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint)); 433 434 this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo); 435 this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo); 436 }, 437 438 _breakpointAdded: function(event) 439 { 440 console.assert(this._supportsDebugging); 441 442 if (!this._contentPopulated) 443 return; 444 445 var breakpoint = event.data.breakpoint; 446 if (!this._matchesBreakpoint(breakpoint)) 447 return; 448 449 if (breakpoint === this._ignoreBreakpointAddedBreakpoint) 450 return; 451 452 var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation); 453 this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo); 454 this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint)); 455 }, 456 457 _breakpointRemoved: function(event) 458 { 459 console.assert(this._supportsDebugging); 460 461 if (!this._contentPopulated) 462 return; 463 464 var breakpoint = event.data.breakpoint; 465 if (!this._matchesBreakpoint(breakpoint)) 466 return; 467 468 if (breakpoint === this._ignoreBreakpointRemovedBreakpoint) 469 return; 470 471 var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation); 472 this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo); 473 this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, null); 474 }, 475 476 _activeCallFrameDidChange: function() 477 { 478 console.assert(this._supportsDebugging); 479 480 if (this._activeCallFrameSourceCodeLocation) { 481 this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this); 482 delete this._activeCallFrameSourceCodeLocation; 483 } 484 485 var activeCallFrame = WebInspector.debuggerManager.activeCallFrame; 486 if (!activeCallFrame || !this._matchesSourceCodeLocation(activeCallFrame.sourceCodeLocation)) { 487 this.executionLineNumber = NaN; 488 this.executionColumnNumber = NaN; 489 return; 490 } 491 492 this._dismissPopover(); 493 494 this._activeCallFrameSourceCodeLocation = activeCallFrame.sourceCodeLocation; 495 this._activeCallFrameSourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this); 496 497 // Don't return early if the line number didn't change. The execution state still 498 // could have changed (e.g. continuing in a loop with a breakpoint inside). 499 500 var lineInfo = this._editorLineInfoForSourceCodeLocation(activeCallFrame.sourceCodeLocation); 501 this.executionLineNumber = lineInfo.lineNumber; 502 this.executionColumnNumber = lineInfo.columnNumber; 503 504 // If we have full content or this source code isn't a Resource we can return early. 505 // Script source code populates from the request started in the constructor. 506 if (this._fullContentPopulated || !(this._sourceCode instanceof WebInspector.Resource) || this._requestingScriptContent) 507 return; 508 509 // Since we are paused in the debugger we need to show some content, and since the Resource 510 // content hasn't populated yet we need to populate with content from the Scripts by URL. 511 // Document resources will attempt to populate the scripts as inline (in <script> tags.) 512 // Other resources are assumed to be full scripts (JavaScript resources). 513 if (this._sourceCode.type === WebInspector.Resource.Type.Document) 514 this._populateWithInlineScriptContent(); 515 else 516 this._populateWithScriptContent(); 517 }, 518 519 _activeCallFrameSourceCodeLocationChanged: function(event) 520 { 521 console.assert(!isNaN(this.executionLineNumber)); 522 if (isNaN(this.executionLineNumber)) 523 return; 524 525 console.assert(WebInspector.debuggerManager.activeCallFrame); 526 console.assert(this._activeCallFrameSourceCodeLocation === WebInspector.debuggerManager.activeCallFrame.sourceCodeLocation); 527 528 var lineInfo = this._editorLineInfoForSourceCodeLocation(this._activeCallFrameSourceCodeLocation); 529 this.executionLineNumber = lineInfo.lineNumber; 530 this.executionColumnNumber = lineInfo.columnNumber; 531 }, 532 533 _populateWithInlineScriptContent: function() 534 { 535 console.assert(this._sourceCode instanceof WebInspector.Resource); 536 console.assert(!this._fullContentPopulated); 537 console.assert(!this._requestingScriptContent); 538 539 var scripts = this._sourceCode.scripts; 540 console.assert(scripts.length); 541 if (!scripts.length) 542 return; 543 544 var pendingRequestCount = scripts.length; 545 546 // If the number of scripts hasn't change since the last populate, then there is nothing to do. 547 if (this._inlineScriptContentPopulated === pendingRequestCount) 548 return; 549 550 this._inlineScriptContentPopulated = pendingRequestCount; 551 552 function scriptContentAvailable(error, content) 553 { 554 // Return early if we are still waiting for content from other scripts. 555 if (--pendingRequestCount) 556 return; 557 558 delete this._requestingScriptContent; 559 560 // Abort if the full content populated while waiting for these async callbacks. 561 if (this._fullContentPopulated) 562 return; 563 564 const scriptOpenTag = "<script>"; 565 const scriptCloseTag = "</script>"; 566 567 var content = ""; 568 var lineNumber = 0; 569 var columnNumber = 0; 570 571 this._invalidLineNumbers = {}; 572 573 for (var i = 0; i < scripts.length; ++i) { 574 // Fill the line gap with newline characters. 575 for (var newLinesCount = scripts[i].range.startLine - lineNumber; newLinesCount > 0; --newLinesCount) { 576 if (!columnNumber) 577 this._invalidLineNumbers[scripts[i].range.startLine - newLinesCount] = true; 578 columnNumber = 0; 579 content += "\n"; 580 } 581 582 // Fill the column gap with space characters. 583 for (var spacesCount = scripts[i].range.startColumn - columnNumber - scriptOpenTag.length; spacesCount > 0; --spacesCount) 584 content += " "; 585 586 // Add script tags and content. 587 content += scriptOpenTag; 588 content += scripts[i].content; 589 content += scriptCloseTag; 590 591 lineNumber = scripts[i].range.endLine; 592 columnNumber = scripts[i].range.endColumn + scriptCloseTag.length; 593 } 594 595 this._populateWithContent(content); 596 } 597 598 this._requestingScriptContent = true; 599 600 var boundScriptContentAvailable = scriptContentAvailable.bind(this); 601 for (var i = 0; i < scripts.length; ++i) 602 scripts[i].requestContent(boundScriptContentAvailable); 603 }, 604 605 _populateWithScriptContent: function() 606 { 607 console.assert(this._sourceCode instanceof WebInspector.Resource); 608 console.assert(!this._fullContentPopulated); 609 console.assert(!this._requestingScriptContent); 610 611 // We can assume this resource only has one script that starts at line/column 0. 612 var scripts = this._sourceCode.scripts; 613 console.assert(scripts.length === 1); 614 if (!scripts.length) 615 return; 616 617 console.assert(scripts[0].range.startLine === 0); 618 console.assert(scripts[0].range.startColumn === 0); 619 620 function scriptContentAvailable(error, content) 621 { 622 delete this._requestingScriptContent; 623 624 // Abort if the full content populated while waiting for this async callback. 625 if (this._fullContentPopulated) 626 return; 627 628 // This is the full content. 629 this._fullContentPopulated = true; 630 631 this._populateWithContent(content); 632 } 633 634 this._requestingScriptContent = true; 635 636 scripts[0].requestContent(scriptContentAvailable.bind(this)); 637 }, 638 639 _matchesSourceCodeLocation: function(sourceCodeLocation) 640 { 641 if (this._sourceCode instanceof WebInspector.SourceMapResource) 642 return sourceCodeLocation.displaySourceCode === this._sourceCode; 643 if (this._sourceCode instanceof WebInspector.Resource) 644 return sourceCodeLocation.sourceCode.url === this._sourceCode.url; 645 if (this._sourceCode instanceof WebInspector.Script) 646 return sourceCodeLocation.sourceCode === this._sourceCode; 647 return false; 648 }, 649 650 _matchesBreakpoint: function(breakpoint) 651 { 652 console.assert(this._supportsDebugging); 653 if (this._sourceCode instanceof WebInspector.SourceMapResource) 654 return breakpoint.sourceCodeLocation.displaySourceCode === this._sourceCode; 655 if (this._sourceCode instanceof WebInspector.Resource) 656 return breakpoint.url === this._sourceCode.url; 657 if (this._sourceCode instanceof WebInspector.Script) 658 return breakpoint.url === this._sourceCode.url || breakpoint.scriptIdentifier === this._sourceCode.id; 659 return false; 660 }, 661 662 _matchesIssue: function(issue) 663 { 664 if (this._sourceCode instanceof WebInspector.Resource) 665 return issue.url === this._sourceCode.url; 666 // FIXME: Support issues for Scripts based on id, not only by URL. 667 if (this._sourceCode instanceof WebInspector.Script) 668 return issue.url === this._sourceCode.url; 669 return false; 670 }, 671 672 _issueWasAdded: function(event) 673 { 674 var issue = event.data.issue; 675 if (!this._matchesIssue(issue)) 676 return; 677 678 this._addIssue(issue); 679 }, 680 681 _addIssue: function(issue) 682 { 683 var lineNumberIssues = this._issuesLineNumberMap[issue.lineNumber]; 684 if (!lineNumberIssues) 685 lineNumberIssues = this._issuesLineNumberMap[issue.lineNumber] = []; 686 687 lineNumberIssues.push(issue); 688 689 if (issue.level === WebInspector.IssueMessage.Level.Error) 690 this.addStyleClassToLine(issue.lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName); 691 else if (issue.level === WebInspector.IssueMessage.Level.Warning) 692 this.addStyleClassToLine(issue.lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName); 693 else 694 console.error("Unknown issue level"); 695 696 // FIXME <rdar://problem/10854857>: Show the issue message on the line as a bubble. 697 }, 698 699 _breakpointInfoForBreakpoint: function(breakpoint) 700 { 701 return {resolved: breakpoint.resolved, disabled: breakpoint.disabled, autoContinue: breakpoint.autoContinue}; 702 }, 703 704 get _supportsDebugging() 705 { 706 if (this._sourceCode instanceof WebInspector.Resource) 707 return this._sourceCode.type === WebInspector.Resource.Type.Document || this._sourceCode.type === WebInspector.Resource.Type.Script; 708 if (this._sourceCode instanceof WebInspector.Script) 709 return true; 710 return false; 711 }, 712 713 // TextEditor Delegate 714 715 textEditorBaseURL: function(textEditor) 716 { 717 return this._sourceCode.url; 718 }, 719 720 textEditorShouldHideLineNumber: function(textEditor, lineNumber) 721 { 722 return lineNumber in this._invalidLineNumbers; 723 }, 724 725 textEditorGutterContextMenu: function(textEditor, lineNumber, columnNumber, editorBreakpoints, event) 726 { 727 if (!this._supportsDebugging) 728 return; 729 730 event.preventDefault(); 731 732 var contextMenu = new WebInspector.ContextMenu(event); 733 734 // Paused. Add Continue to Here option only if we have a script identifier for the location. 735 if (WebInspector.debuggerManager.paused) { 736 var editorLineInfo = {lineNumber:lineNumber, columnNumber:columnNumber}; 737 var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo); 738 var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber); 739 740 if (sourceCodeLocation.sourceCode instanceof WebInspector.Script) 741 var script = sourceCodeLocation.sourceCode; 742 else if (sourceCodeLocation.sourceCode instanceof WebInspector.Resource) 743 var script = sourceCodeLocation.sourceCode.scriptForLocation(sourceCodeLocation); 744 745 if (script) { 746 function continueToLocation() 747 { 748 WebInspector.debuggerManager.continueToLocation(script.id, sourceCodeLocation.lineNumber, sourceCodeLocation.columnNumber); 749 } 750 751 contextMenu.appendItem(WebInspector.UIString("Continue to Here"), continueToLocation); 752 contextMenu.appendSeparator(); 753 } 754 } 755 756 var breakpoints = []; 757 for (var i = 0; i < editorBreakpoints.length; ++i) { 758 var lineInfo = editorBreakpoints[i]; 759 var breakpoint = this._breakpointForEditorLineInfo(lineInfo); 760 console.assert(breakpoint); 761 if (breakpoint) 762 breakpoints.push(breakpoint); 763 } 764 765 // No breakpoints. 766 if (!breakpoints.length) { 767 function addBreakpoint() 768 { 769 var data = this.textEditorBreakpointAdded(this, lineNumber, columnNumber); 770 this.setBreakpointInfoForLineAndColumn(data.lineNumber, data.columnNumber, data.breakpointInfo); 771 } 772 773 contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), addBreakpoint.bind(this)); 774 contextMenu.show(); 775 return; 776 } 777 778 // Single breakpoint. 779 if (breakpoints.length === 1) { 780 var breakpoint = breakpoints[0]; 781 function revealInSidebar() 782 { 783 WebInspector.debuggerSidebarPanel.show(); 784 var treeElement = WebInspector.debuggerSidebarPanel.treeElementForRepresentedObject(breakpoint); 785 if (treeElement) 786 treeElement.revealAndSelect(); 787 } 788 789 breakpoint.appendContextMenuItems(contextMenu, event.target); 790 contextMenu.appendSeparator(); 791 contextMenu.appendItem(WebInspector.UIString("Reveal in Debugger Navigation Sidebar"), revealInSidebar); 792 contextMenu.show(); 793 return; 794 } 795 796 // Multiple breakpoints. 797 var shouldDisable = false; 798 for (var i = 0; i < breakpoints.length; ++i) { 799 if (!breakpoints[i].disabled) { 800 shouldDisable = true; 801 break; 802 } 803 } 804 805 function removeBreakpoints() 806 { 807 for (var i = 0; i < breakpoints.length; ++i) { 808 var breakpoint = breakpoints[i]; 809 if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint)) 810 WebInspector.debuggerManager.removeBreakpoint(breakpoint); 811 } 812 } 813 814 function toggleBreakpoints() 815 { 816 for (var i = 0; i < breakpoints.length; ++i) 817 breakpoints[i].disabled = shouldDisable; 818 } 819 820 if (shouldDisable) 821 contextMenu.appendItem(WebInspector.UIString("Disable Breakpoints"), toggleBreakpoints.bind(this)); 822 else 823 contextMenu.appendItem(WebInspector.UIString("Enable Breakpoints"), toggleBreakpoints.bind(this)); 824 contextMenu.appendItem(WebInspector.UIString("Delete Breakpoints"), removeBreakpoints.bind(this)); 825 contextMenu.show(); 826 }, 827 828 textEditorBreakpointAdded: function(textEditor, lineNumber, columnNumber) 829 { 830 if (!this._supportsDebugging) 831 return null; 832 833 var editorLineInfo = {lineNumber:lineNumber, columnNumber:columnNumber}; 834 var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo); 835 var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber); 836 var breakpoint = new WebInspector.Breakpoint(sourceCodeLocation); 837 838 var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation); 839 this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo); 840 841 this._ignoreBreakpointAddedBreakpoint = breakpoint; 842 WebInspector.debuggerManager.addBreakpoint(breakpoint); 843 delete this._ignoreBreakpointAddedBreakpoint; 844 845 // Return the more accurate location and breakpoint info. 846 return { 847 breakpointInfo: this._breakpointInfoForBreakpoint(breakpoint), 848 lineNumber: lineInfo.lineNumber, 849 columnNumber: lineInfo.columnNumber 850 }; 851 }, 852 853 textEditorBreakpointRemoved: function(textEditor, lineNumber, columnNumber) 854 { 855 console.assert(this._supportsDebugging); 856 if (!this._supportsDebugging) 857 return; 858 859 var lineInfo = {lineNumber: lineNumber, columnNumber: columnNumber}; 860 var breakpoint = this._breakpointForEditorLineInfo(lineInfo); 861 console.assert(breakpoint); 862 if (!breakpoint) 863 return; 864 865 this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo); 866 867 this._ignoreBreakpointRemovedBreakpoint = breakpoint; 868 WebInspector.debuggerManager.removeBreakpoint(breakpoint); 869 delete this._ignoreBreakpointAddedBreakpoint; 870 }, 871 872 textEditorBreakpointMoved: function(textEditor, oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber) 873 { 874 console.assert(this._supportsDebugging); 875 if (!this._supportsDebugging) 876 return; 877 878 var oldLineInfo = {lineNumber: oldLineNumber, columnNumber: oldColumnNumber}; 879 var breakpoint = this._breakpointForEditorLineInfo(oldLineInfo); 880 console.assert(breakpoint); 881 if (!breakpoint) 882 return; 883 884 this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo); 885 886 var newLineInfo = {lineNumber: newLineNumber, columnNumber: newColumnNumber}; 887 var unformattedNewLineInfo = this._unformattedLineInfoForEditorLineInfo(newLineInfo); 888 this._ignoreLocationUpdateBreakpoint = breakpoint; 889 breakpoint.sourceCodeLocation.update(this._sourceCode, unformattedNewLineInfo.lineNumber, unformattedNewLineInfo.columnNumber); 890 delete this._ignoreLocationUpdateBreakpoint; 891 892 var accurateNewLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation); 893 this._addBreakpointWithEditorLineInfo(breakpoint, accurateNewLineInfo); 894 895 if (accurateNewLineInfo.lineNumber !== newLineInfo.lineNumber || accurateNewLineInfo.columnNumber !== newLineInfo.columnNumber) 896 this.updateBreakpointLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, accurateNewLineInfo.lineNumber, accurateNewLineInfo.columnNumber); 897 }, 898 899 textEditorBreakpointClicked: function(textEditor, lineNumber, columnNumber) 900 { 901 console.assert(this._supportsDebugging); 902 if (!this._supportsDebugging) 903 return; 904 905 var breakpoint = this._breakpointForEditorLineInfo({lineNumber: lineNumber, columnNumber: columnNumber}); 906 console.assert(breakpoint); 907 if (!breakpoint) 908 return; 909 910 breakpoint.cycleToNextMode(); 911 }, 912 913 textEditorUpdatedFormatting: function(textEditor) 914 { 915 this._ignoreAllBreakpointLocationUpdates = true; 916 this._sourceCode.formatterSourceMap = this.formatterSourceMap; 917 delete this._ignoreAllBreakpointLocationUpdates; 918 919 // Always put the source map on both the Script and Resource if both exist. For example, 920 // if this SourceCode is a Resource, then there might also be a Script. In the debugger, 921 // the backend identifies call frames with Script line and column information, and the 922 // Script needs the formatter source map to produce the proper display line and column. 923 if (this._sourceCode instanceof WebInspector.Resource && !(this._sourceCode instanceof WebInspector.SourceMapResource)) { 924 var scripts = this._sourceCode.scripts; 925 for (var i = 0; i < scripts.length; ++i) 926 scripts[i].formatterSourceMap = this.formatterSourceMap; 927 } else if (this._sourceCode instanceof WebInspector.Script) { 928 if (this._sourceCode.resource) 929 this._sourceCode.resource.formatterSourceMap = this.formatterSourceMap; 930 } 931 932 // Some breakpoints may have moved, some might not have. Just go through 933 // and remove and reinsert all the breakpoints. 934 935 var oldBreakpointMap = this._breakpointMap; 936 this._breakpointMap = {}; 937 938 for (var lineNumber in oldBreakpointMap) { 939 for (var columnNumber in oldBreakpointMap[lineNumber]) { 940 var breakpoint = oldBreakpointMap[lineNumber][columnNumber]; 941 var newLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation); 942 this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo); 943 this.setBreakpointInfoForLineAndColumn(lineNumber, columnNumber, null); 944 this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint)); 945 } 946 } 947 }, 948 949 _debuggerDidPause: function(event) 950 { 951 this._updateTokenTrackingControllerState(); 952 }, 953 954 _debuggerDidResume: function(event) 955 { 956 this._updateTokenTrackingControllerState(); 957 this._dismissPopover(); 958 }, 959 960 _sourceCodeSourceMapAdded: function(event) 961 { 962 WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this); 963 this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); 964 965 this._updateTokenTrackingControllerState(); 966 }, 967 968 _updateTokenTrackingControllerState: function() 969 { 970 var mode = WebInspector.CodeMirrorTokenTrackingController.Mode.None; 971 if (WebInspector.debuggerManager.paused) 972 mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression; 973 else if (this._hasColorMarkers()) 974 mode = WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens; 975 else if ((this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length !== 0) && WebInspector.modifierKeys.metaKey && !WebInspector.modifierKeys.altKey && !WebInspector.modifierKeys.shiftKey) 976 mode = WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens; 977 978 this.tokenTrackingController.enabled = mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.None; 979 980 if (mode === this.tokenTrackingController.mode) 981 return; 982 983 switch (mode) { 984 case WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens: 985 this.tokenTrackingController.mouseOverDelayDuration = 0; 986 this.tokenTrackingController.mouseOutReleaseDelayDuration = 0; 987 break; 988 case WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens: 989 this.tokenTrackingController.mouseOverDelayDuration = 0; 990 this.tokenTrackingController.mouseOutReleaseDelayDuration = 0; 991 this.tokenTrackingController.classNameForHighlightedRange = WebInspector.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName; 992 this._dismissPopover(); 993 break; 994 case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression: 995 this.tokenTrackingController.mouseOverDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken; 996 this.tokenTrackingController.mouseOutReleaseDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease; 997 this.tokenTrackingController.classNameForHighlightedRange = WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName; 998 break; 999 } 1000 1001 this.tokenTrackingController.mode = mode; 1002 }, 1003 1004 _hasColorMarkers: function() 1005 { 1006 for (var marker of this.markers) { 1007 if (marker.type === WebInspector.TextMarker.Type.Color) 1008 return true; 1009 } 1010 return false; 1011 }, 1012 1013 // CodeMirrorTokenTrackingController Delegate 1014 1015 tokenTrackingControllerCanReleaseHighlightedRange: function(tokenTrackingController, element) 1016 { 1017 if (!this._popover) 1018 return true; 1019 1020 if (!window.getSelection().isCollapsed && this._popover.element.contains(window.getSelection().anchorNode)) 1021 return false; 1022 1023 return true; 1024 }, 1025 1026 tokenTrackingControllerHighlightedRangeReleased: function(tokenTrackingController) 1027 { 1028 if (!this._mouseIsOverPopover) 1029 this._dismissPopover(); 1030 }, 1031 1032 tokenTrackingControllerHighlightedRangeWasClicked: function(tokenTrackingController) 1033 { 1034 if (this.tokenTrackingController.mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens) 1035 return; 1036 1037 // Links are handled by TextEditor. 1038 if (/\blink\b/.test(this.tokenTrackingController.candidate.hoveredToken.type)) 1039 return; 1040 1041 var sourceCodeLocation = this._sourceCodeLocationForEditorPosition(this.tokenTrackingController.candidate.hoveredTokenRange.start); 1042 if (this.sourceCode instanceof WebInspector.SourceMapResource) 1043 WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation); 1044 else 1045 WebInspector.resourceSidebarPanel.showSourceCodeLocation(sourceCodeLocation); 1046 }, 1047 1048 tokenTrackingControllerNewHighlightCandidate: function(tokenTrackingController, candidate) 1049 { 1050 if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens) { 1051 this.tokenTrackingController.highlightRange(candidate.hoveredTokenRange); 1052 return; 1053 } 1054 1055 if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression) { 1056 this._tokenTrackingControllerHighlightedJavaScriptExpression(candidate); 1057 return; 1058 } 1059 1060 if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens) { 1061 var markers = this.markersAtPosition(candidate.hoveredTokenRange.start); 1062 if (markers.length > 0) 1063 this._tokenTrackingControllerHighlightedMarkedExpression(candidate, markers); 1064 else 1065 this._dismissEditingController(); 1066 } 1067 }, 1068 1069 tokenTrackingControllerMouseOutOfHoveredMarker: function(tokenTrackingController, hoveredMarker) 1070 { 1071 this._dismissEditingController(); 1072 }, 1073 1074 _tokenTrackingControllerHighlightedJavaScriptExpression: function(candidate) 1075 { 1076 console.assert(candidate.expression); 1077 1078 function populate(error, result, wasThrown) 1079 { 1080 if (error || wasThrown) 1081 return; 1082 1083 if (candidate !== this.tokenTrackingController.candidate) 1084 return; 1085 1086 var data = WebInspector.RemoteObject.fromPayload(result); 1087 switch (data.type) { 1088 case "function": 1089 this._showPopoverForFunction(data); 1090 break; 1091 case "object": 1092 if (data.subtype === "regexp") 1093 this._showPopoverForRegExp(data); 1094 else 1095 this._showPopoverForObject(data); 1096 break; 1097 case "string": 1098 this._showPopoverForString(data); 1099 break; 1100 case "number": 1101 this._showPopoverForNumber(data); 1102 break; 1103 case "boolean": 1104 this._showPopoverForBoolean(data); 1105 break; 1106 case "undefined": 1107 this._showPopoverForUndefined(data); 1108 break; 1109 } 1110 } 1111 1112 DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WebInspector.debuggerManager.activeCallFrame.id, expression: candidate.expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this)); 1113 }, 1114 1115 _showPopover: function(content) 1116 { 1117 console.assert(this.tokenTrackingController.candidate); 1118 1119 var candidate = this.tokenTrackingController.candidate; 1120 if (!candidate) 1121 return; 1122 1123 content.classList.add(WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName); 1124 1125 var rects = this.rectsForRange(candidate.hoveredTokenRange); 1126 var bounds = WebInspector.Rect.unionOfRects(rects); 1127 1128 this._popover = this._popover || new WebInspector.Popover(this); 1129 this._popover.content = content; 1130 this._popover.present(bounds.pad(5), [WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X]); 1131 1132 this._trackPopoverEvents(); 1133 1134 this.tokenTrackingController.highlightRange(candidate.expressionRange); 1135 }, 1136 1137 _showPopoverForFunction: function(data) 1138 { 1139 var candidate = this.tokenTrackingController.candidate; 1140 1141 function didGetDetails(error, response) 1142 { 1143 if (error) { 1144 console.error(error); 1145 this._dismissPopover(); 1146 return; 1147 } 1148 1149 // Nothing to do if the token has changed since the time we 1150 // asked for the function details from the backend. 1151 if (candidate !== this.tokenTrackingController.candidate) 1152 return; 1153 1154 var wrapper = document.createElement("div"); 1155 wrapper.className = "body console-formatted-function"; 1156 wrapper.textContent = data.description; 1157 1158 var content = document.createElement("div"); 1159 content.className = "function"; 1160 1161 var title = content.appendChild(document.createElement("div")); 1162 title.className = "title"; 1163 title.textContent = response.name || response.inferredName || response.displayName || WebInspector.UIString("(anonymous function)"); 1164 1165 content.appendChild(wrapper); 1166 1167 this._showPopover(content); 1168 } 1169 DebuggerAgent.getFunctionDetails(data.objectId, didGetDetails.bind(this)); 1170 }, 1171 1172 _showPopoverForObject: function(data) 1173 { 1174 if (data.subtype === "null") { 1175 this._showPopoverForNull(data); 1176 return; 1177 } 1178 1179 var content = document.createElement("div"); 1180 content.className = "object expandable"; 1181 1182 var titleElement = document.createElement("div"); 1183 titleElement.className = "title"; 1184 titleElement.textContent = data.description; 1185 content.appendChild(titleElement); 1186 1187 var section = new WebInspector.ObjectPropertiesSection(data); 1188 section.expanded = true; 1189 section.element.classList.add("body"); 1190 content.appendChild(section.element); 1191 1192 this._showPopover(content); 1193 }, 1194 1195 _showPopoverForString: function(data) 1196 { 1197 var content = document.createElement("div"); 1198 content.className = "string console-formatted-string"; 1199 content.textContent = "\"" + data.description + "\""; 1200 1201 this._showPopover(content); 1202 }, 1203 1204 _showPopoverForRegExp: function(data) 1205 { 1206 var content = document.createElement("div"); 1207 content.className = "regexp console-formatted-regexp"; 1208 content.textContent = data.description; 1209 1210 this._showPopover(content); 1211 }, 1212 1213 _showPopoverForNumber: function(data) 1214 { 1215 var content = document.createElement("span"); 1216 content.className = "number console-formatted-number"; 1217 content.textContent = data.description; 1218 1219 this._showPopover(content); 1220 }, 1221 1222 _showPopoverForBoolean: function(data) 1223 { 1224 var content = document.createElement("span"); 1225 content.className = "boolean console-formatted-boolean"; 1226 content.textContent = data.description; 1227 1228 this._showPopover(content); 1229 }, 1230 1231 _showPopoverForNull: function(data) 1232 { 1233 var content = document.createElement("span"); 1234 content.className = "boolean console-formatted-null"; 1235 content.textContent = data.description; 1236 1237 this._showPopover(content); 1238 }, 1239 1240 _showPopoverForUndefined: function(data) 1241 { 1242 var content = document.createElement("span"); 1243 content.className = "boolean console-formatted-undefined"; 1244 content.textContent = data.description; 1245 1246 this._showPopover(content); 1247 }, 1248 1249 willDismissPopover: function(popover) 1250 { 1251 this.tokenTrackingController.removeHighlightedRange(); 1252 1253 RuntimeAgent.releaseObjectGroup("popover"); 1254 }, 1255 1256 _dismissPopover: function() 1257 { 1258 if (!this._popover) 1259 return; 1260 1261 this._popover.dismiss(); 1262 1263 if (this._popoverEventListeners) 1264 this._popoverEventListeners.unregister(); 1265 }, 1266 1267 _trackPopoverEvents: function() 1268 { 1269 if (!this._popoverEventListeners) 1270 this._popoverEventListeners = new WebInspector.EventListenerSet(this, "Popover listeners"); 1271 this._popoverEventListeners.register(this._popover.element, "mouseover", this._popoverMouseover); 1272 this._popoverEventListeners.register(this._popover.element, "mouseout", this._popoverMouseout); 1273 this._popoverEventListeners.install(); 1274 }, 1275 1276 _popoverMouseover: function(event) 1277 { 1278 this._mouseIsOverPopover = true; 1279 }, 1280 1281 _popoverMouseout: function(event) 1282 { 1283 this._mouseIsOverPopover = this._popover.element.contains(event.relatedTarget); 1284 }, 1285 1286 _updateEditableMarkers: function(range) 1287 { 1288 this.createColorMarkers(range); 1289 this.createGradientMarkers(range); 1290 1291 this._updateTokenTrackingControllerState(); 1292 }, 1293 1294 _tokenTrackingControllerHighlightedMarkedExpression: function(candidate, markers) 1295 { 1296 // Look for the outermost editable marker. 1297 var editableMarker; 1298 for (var marker of markers) { 1299 if (!marker.range || (marker.type !== WebInspector.TextMarker.Type.Color && marker.type !== WebInspector.TextMarker.Type.Gradient)) 1300 continue; 1301 1302 if (!editableMarker || (marker.range.startLine < editableMarker.range.startLine || (marker.range.startLine === editableMarker.range.startLine && marker.range.startColumn < editableMarker.range.startColumn))) 1303 editableMarker = marker; 1304 } 1305 1306 if (!editableMarker) { 1307 this.tokenTrackingController.hoveredMarker = null; 1308 return; 1309 } 1310 1311 if (this.tokenTrackingController.hoveredMarker === editableMarker) 1312 return; 1313 1314 this._dismissEditingController(); 1315 1316 this.tokenTrackingController.hoveredMarker = editableMarker; 1317 1318 this._editingController = this.editingControllerForMarker(editableMarker); 1319 1320 if (marker.type === WebInspector.TextMarker.Type.Color) { 1321 var color = this._editingController.value; 1322 if (!color || !color.valid) { 1323 editableMarker.clear(); 1324 delete this._editingController; 1325 return; 1326 } 1327 } 1328 1329 this._editingController.delegate = this; 1330 this._editingController.presentHoverMenu(); 1331 }, 1332 1333 _dismissEditingController: function(discrete) 1334 { 1335 if (this._editingController) 1336 this._editingController.dismissHoverMenu(discrete); 1337 1338 this.tokenTrackingController.hoveredMarker = null; 1339 delete this._editingController; 1340 }, 1341 1342 // CodeMirrorEditingController Delegate 1343 1344 editingControllerDidStartEditing: function(editingController) 1345 { 1346 // We can pause the token tracking controller during editing, it will be reset 1347 // to the expected state by calling _updateEditableMarkers() in the 1348 // editingControllerDidFinishEditing delegate. 1349 this.tokenTrackingController.enabled = false; 1350 1351 // We clear the marker since we'll reset it after editing. 1352 editingController.marker.clear(); 1353 1354 // We ignore content changes made as a result of color editing. 1355 this._ignoreContentDidChange++; 1356 }, 1357 1358 editingControllerDidFinishEditing: function(editingController) 1359 { 1360 this._updateEditableMarkers(editingController.range); 1361 1362 this._ignoreContentDidChange--; 1363 1364 delete this._editingController; 1365 } 1366}; 1367 1368WebInspector.SourceCodeTextEditor.prototype.__proto__ = WebInspector.TextEditor.prototype; 1369