1 /* 2 * Copyright (C) 2011 Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.UISourceCodeFrame} 34 * @param {!WebInspector.SourcesPanel} scriptsPanel 35 * @param {!WebInspector.UISourceCode} uiSourceCode 36 */ 37 WebInspector.JavaScriptSourceFrame = function(scriptsPanel, uiSourceCode) 38 { 39 this._scriptsPanel = scriptsPanel; 40 this._breakpointManager = WebInspector.breakpointManager; 41 this._uiSourceCode = uiSourceCode; 42 43 WebInspector.UISourceCodeFrame.call(this, uiSourceCode); 44 if (uiSourceCode.project().type() === WebInspector.projectTypes.Debugger) 45 this.element.classList.add("source-frame-debugger-script"); 46 47 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.textEditor.element, 48 this._getPopoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true); 49 50 this.textEditor.element.addEventListener("keydown", this._onKeyDown.bind(this), true); 51 52 this.textEditor.addEventListener(WebInspector.TextEditor.Events.GutterClick, this._handleGutterClick.bind(this), this); 53 54 this.textEditor.element.addEventListener("mousedown", this._onMouseDownAndClick.bind(this, true), true); 55 this.textEditor.element.addEventListener("click", this._onMouseDownAndClick.bind(this, false), true); 56 57 58 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this); 59 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this); 60 61 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessageAdded, this._consoleMessageAdded, this); 62 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, this._consoleMessageRemoved, this); 63 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this); 64 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this); 65 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 66 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 67 68 this._registerShortcuts(); 69 this._updateScriptFile(); 70 } 71 72 WebInspector.JavaScriptSourceFrame.prototype = { 73 _registerShortcuts: function() 74 { 75 var shortcutKeys = WebInspector.SourcesPanelDescriptor.ShortcutKeys; 76 for (var i = 0; i < shortcutKeys.EvaluateSelectionInConsole.length; ++i) { 77 var keyDescriptor = shortcutKeys.EvaluateSelectionInConsole[i]; 78 this.addShortcut(keyDescriptor.key, this._evaluateSelectionInConsole.bind(this)); 79 } 80 for (var i = 0; i < shortcutKeys.AddSelectionToWatch.length; ++i) { 81 var keyDescriptor = shortcutKeys.AddSelectionToWatch[i]; 82 this.addShortcut(keyDescriptor.key, this._addCurrentSelectionToWatch.bind(this)); 83 } 84 }, 85 86 _addCurrentSelectionToWatch: function() 87 { 88 var textSelection = this.textEditor.selection(); 89 if (textSelection && !textSelection.isEmpty()) 90 this._innerAddToWatch(this.textEditor.copyRange(textSelection)); 91 }, 92 93 /** 94 * @param {string} expression 95 */ 96 _innerAddToWatch: function(expression) 97 { 98 this._scriptsPanel.addToWatch(expression); 99 }, 100 101 /** 102 * @return {boolean} 103 */ 104 _evaluateSelectionInConsole: function() 105 { 106 var selection = this.textEditor.selection(); 107 if (!selection || selection.isEmpty()) 108 return false; 109 WebInspector.evaluateInConsole(this.textEditor.copyRange(selection)); 110 return true; 111 }, 112 113 // View events 114 wasShown: function() 115 { 116 WebInspector.UISourceCodeFrame.prototype.wasShown.call(this); 117 }, 118 119 willHide: function() 120 { 121 WebInspector.UISourceCodeFrame.prototype.willHide.call(this); 122 this._popoverHelper.hidePopover(); 123 }, 124 125 onUISourceCodeContentChanged: function() 126 { 127 this._removeAllBreakpoints(); 128 WebInspector.UISourceCodeFrame.prototype.onUISourceCodeContentChanged.call(this); 129 }, 130 131 populateLineGutterContextMenu: function(contextMenu, lineNumber) 132 { 133 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Continue to here" : "Continue to Here"), this._continueToLine.bind(this, lineNumber)); 134 135 var breakpoint = this._breakpointManager.findBreakpoint(this._uiSourceCode, lineNumber); 136 if (!breakpoint) { 137 // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint. 138 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add breakpoint" : "Add Breakpoint"), this._setBreakpoint.bind(this, lineNumber, "", true)); 139 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add conditional breakpoint" : "Add Conditional Breakpoint"), this._editBreakpointCondition.bind(this, lineNumber)); 140 } else { 141 // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable. 142 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), breakpoint.remove.bind(breakpoint)); 143 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit breakpoint" : "Edit Breakpoint"), this._editBreakpointCondition.bind(this, lineNumber, breakpoint)); 144 if (breakpoint.enabled()) 145 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Disable breakpoint" : "Disable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, false)); 146 else 147 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Enable breakpoint" : "Enable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, true)); 148 } 149 }, 150 151 populateTextAreaContextMenu: function(contextMenu, lineNumber) 152 { 153 var textSelection = this.textEditor.selection(); 154 if (textSelection && !textSelection.isEmpty()) { 155 var selection = this.textEditor.copyRange(textSelection); 156 var addToWatchLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add to watch" : "Add to Watch"); 157 contextMenu.appendItem(addToWatchLabel, this._innerAddToWatch.bind(this, selection)); 158 var evaluateLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Evaluate in console" : "Evaluate in Console"); 159 contextMenu.appendItem(evaluateLabel, WebInspector.evaluateInConsole.bind(WebInspector, selection)); 160 contextMenu.appendSeparator(); 161 } else if (!this._uiSourceCode.isEditable() && this._uiSourceCode.contentType() === WebInspector.resourceTypes.Script) { 162 163 // FIXME: Change condition above to explicitly check that current uiSourceCode is created by default debugger mapping 164 // and move the code adding this menu item to generic context menu provider for UISourceCode. 165 var liveEditLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Live edit" : "Live Edit"); 166 contextMenu.appendItem(liveEditLabel, liveEdit.bind(this)); 167 contextMenu.appendSeparator(); 168 } 169 170 /** 171 * @this {WebInspector.JavaScriptSourceFrame} 172 */ 173 function liveEdit() 174 { 175 var liveEditUISourceCode = WebInspector.liveEditSupport.uiSourceCodeForLiveEdit(this._uiSourceCode); 176 this._scriptsPanel.showUISourceCode(liveEditUISourceCode, lineNumber) 177 } 178 179 WebInspector.UISourceCodeFrame.prototype.populateTextAreaContextMenu.call(this, contextMenu, lineNumber); 180 }, 181 182 _workingCopyChanged: function(event) 183 { 184 if (this._supportsEnabledBreakpointsWhileEditing() || this._scriptFile) 185 return; 186 187 if (this._uiSourceCode.isDirty()) 188 this._muteBreakpointsWhileEditing(); 189 else 190 this._restoreBreakpointsAfterEditing(); 191 }, 192 193 _workingCopyCommitted: function(event) 194 { 195 if (this._supportsEnabledBreakpointsWhileEditing() || this._scriptFile) 196 return; 197 this._restoreBreakpointsAfterEditing(); 198 }, 199 200 _didMergeToVM: function() 201 { 202 if (this._supportsEnabledBreakpointsWhileEditing()) 203 return; 204 this._restoreBreakpointsAfterEditing(); 205 }, 206 207 _didDivergeFromVM: function() 208 { 209 if (this._supportsEnabledBreakpointsWhileEditing()) 210 return; 211 this._muteBreakpointsWhileEditing(); 212 }, 213 214 _muteBreakpointsWhileEditing: function() 215 { 216 if (this._muted) 217 return; 218 for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) { 219 var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint"); 220 if (!breakpointDecoration) 221 continue; 222 this._removeBreakpointDecoration(lineNumber); 223 this._addBreakpointDecoration(lineNumber, breakpointDecoration.condition, breakpointDecoration.enabled, true); 224 } 225 this._muted = true; 226 }, 227 228 _supportsEnabledBreakpointsWhileEditing: function() 229 { 230 return this._uiSourceCode.project().type() === WebInspector.projectTypes.Snippets; 231 }, 232 233 _restoreBreakpointsAfterEditing: function() 234 { 235 delete this._muted; 236 var breakpoints = {}; 237 // Save and remove muted breakpoint decorations. 238 for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) { 239 var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint"); 240 if (breakpointDecoration) { 241 breakpoints[lineNumber] = breakpointDecoration; 242 this._removeBreakpointDecoration(lineNumber); 243 } 244 } 245 246 // Remove all breakpoints. 247 this._removeAllBreakpoints(); 248 249 // Restore all breakpoints from saved decorations. 250 for (var lineNumberString in breakpoints) { 251 var lineNumber = parseInt(lineNumberString, 10); 252 if (isNaN(lineNumber)) 253 continue; 254 var breakpointDecoration = breakpoints[lineNumberString]; 255 this._setBreakpoint(lineNumber, breakpointDecoration.condition, breakpointDecoration.enabled); 256 } 257 }, 258 259 _removeAllBreakpoints: function() 260 { 261 var breakpoints = this._breakpointManager.breakpointsForUISourceCode(this._uiSourceCode); 262 for (var i = 0; i < breakpoints.length; ++i) 263 breakpoints[i].remove(); 264 }, 265 266 _getPopoverAnchor: function(element, event) 267 { 268 if (!WebInspector.debuggerModel.isPaused()) 269 return null; 270 271 var textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y); 272 if (!textPosition) 273 return null; 274 var mouseLine = textPosition.startLine; 275 var mouseColumn = textPosition.startColumn; 276 var textSelection = this.textEditor.selection().normalize(); 277 if (textSelection && !textSelection.isEmpty()) { 278 if (textSelection.startLine !== textSelection.endLine || textSelection.startLine !== mouseLine || mouseColumn < textSelection.startColumn || mouseColumn > textSelection.endColumn) 279 return null; 280 281 var leftCorner = this.textEditor.cursorPositionToCoordinates(textSelection.startLine, textSelection.startColumn); 282 var rightCorner = this.textEditor.cursorPositionToCoordinates(textSelection.endLine, textSelection.endColumn); 283 var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height); 284 anchorBox.highlight = { 285 lineNumber: textSelection.startLine, 286 startColumn: textSelection.startColumn, 287 endColumn: textSelection.endColumn - 1 288 }; 289 anchorBox.forSelection = true; 290 return anchorBox; 291 } 292 293 var token = this.textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn); 294 if (!token) 295 return null; 296 var lineNumber = textPosition.startLine; 297 var line = this.textEditor.line(lineNumber); 298 var tokenContent = line.substring(token.startColumn, token.endColumn + 1); 299 if (token.type !== "javascript-ident" && (token.type !== "javascript-keyword" || tokenContent !== "this")) 300 return null; 301 302 var leftCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.startColumn); 303 var rightCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.endColumn + 1); 304 var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height); 305 306 anchorBox.highlight = { 307 lineNumber: lineNumber, 308 startColumn: token.startColumn, 309 endColumn: token.endColumn 310 }; 311 312 return anchorBox; 313 }, 314 315 _resolveObjectForPopover: function(anchorBox, showCallback, objectGroupName) 316 { 317 /** 318 * @param {?RuntimeAgent.RemoteObject} result 319 * @param {boolean=} wasThrown 320 * @this {WebInspector.JavaScriptSourceFrame} 321 */ 322 function showObjectPopover(result, wasThrown) 323 { 324 if (!WebInspector.debuggerModel.isPaused() || !result) { 325 this._popoverHelper.hidePopover(); 326 return; 327 } 328 this._popoverAnchorBox = anchorBox; 329 showCallback(WebInspector.RemoteObject.fromPayload(result), wasThrown, this._popoverAnchorBox); 330 // Popover may have been removed by showCallback(). 331 if (this._popoverAnchorBox) { 332 var highlightRange = new WebInspector.TextRange(lineNumber, startHighlight, lineNumber, endHighlight); 333 this._popoverAnchorBox._highlightDescriptor = this.textEditor.highlightRange(highlightRange, "source-frame-eval-expression"); 334 } 335 } 336 337 if (!WebInspector.debuggerModel.isPaused()) { 338 this._popoverHelper.hidePopover(); 339 return; 340 } 341 var lineNumber = anchorBox.highlight.lineNumber; 342 var startHighlight = anchorBox.highlight.startColumn; 343 var endHighlight = anchorBox.highlight.endColumn; 344 var line = this.textEditor.line(lineNumber); 345 if (!anchorBox.forSelection) { 346 while (startHighlight > 1 && line.charAt(startHighlight - 1) === '.') { 347 var token = this.textEditor.tokenAtTextPosition(lineNumber, startHighlight - 2); 348 if (!token) { 349 this._popoverHelper.hidePopover(); 350 return; 351 } 352 startHighlight = token.startColumn; 353 } 354 } 355 var evaluationText = line.substring(startHighlight, endHighlight + 1); 356 var selectedCallFrame = WebInspector.debuggerModel.selectedCallFrame(); 357 selectedCallFrame.evaluate(evaluationText, objectGroupName, false, true, false, false, showObjectPopover.bind(this)); 358 }, 359 360 _onHidePopover: function() 361 { 362 if (!this._popoverAnchorBox) 363 return; 364 if (this._popoverAnchorBox._highlightDescriptor) 365 this.textEditor.removeHighlight(this._popoverAnchorBox._highlightDescriptor); 366 delete this._popoverAnchorBox; 367 }, 368 369 /** 370 * @param {number} lineNumber 371 * @param {string} condition 372 * @param {boolean} enabled 373 * @param {boolean} mutedWhileEditing 374 */ 375 _addBreakpointDecoration: function(lineNumber, condition, enabled, mutedWhileEditing) 376 { 377 var breakpoint = { 378 condition: condition, 379 enabled: enabled 380 }; 381 382 this.textEditor.setAttribute(lineNumber, "breakpoint", breakpoint); 383 384 var disabled = !enabled || mutedWhileEditing; 385 this.textEditor.addBreakpoint(lineNumber, disabled, !!condition); 386 }, 387 388 _removeBreakpointDecoration: function(lineNumber) 389 { 390 this.textEditor.removeAttribute(lineNumber, "breakpoint"); 391 this.textEditor.removeBreakpoint(lineNumber); 392 }, 393 394 _onKeyDown: function(event) 395 { 396 if (event.keyIdentifier === "U+001B") { // Escape key 397 if (this._popoverHelper.isPopoverVisible()) { 398 this._popoverHelper.hidePopover(); 399 event.consume(); 400 return; 401 } 402 if (this._stepIntoMarkup && WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event)) { 403 this._stepIntoMarkup.stoptIteratingSelection(); 404 event.consume(); 405 return; 406 } 407 } 408 }, 409 410 /** 411 * @param {number} lineNumber 412 * @param {!WebInspector.BreakpointManager.Breakpoint=} breakpoint 413 */ 414 _editBreakpointCondition: function(lineNumber, breakpoint) 415 { 416 this._conditionElement = this._createConditionElement(lineNumber); 417 this.textEditor.addDecoration(lineNumber, this._conditionElement); 418 419 /** 420 * @this {WebInspector.JavaScriptSourceFrame} 421 */ 422 function finishEditing(committed, element, newText) 423 { 424 this.textEditor.removeDecoration(lineNumber, this._conditionElement); 425 delete this._conditionEditorElement; 426 delete this._conditionElement; 427 if (!committed) 428 return; 429 430 if (breakpoint) 431 breakpoint.setCondition(newText); 432 else 433 this._setBreakpoint(lineNumber, newText, true); 434 } 435 436 var config = new WebInspector.EditingConfig(finishEditing.bind(this, true), finishEditing.bind(this, false)); 437 WebInspector.startEditing(this._conditionEditorElement, config); 438 this._conditionEditorElement.value = breakpoint ? breakpoint.condition() : ""; 439 this._conditionEditorElement.select(); 440 }, 441 442 _createConditionElement: function(lineNumber) 443 { 444 var conditionElement = document.createElement("div"); 445 conditionElement.className = "source-frame-breakpoint-condition"; 446 447 var labelElement = document.createElement("label"); 448 labelElement.className = "source-frame-breakpoint-message"; 449 labelElement.htmlFor = "source-frame-breakpoint-condition"; 450 labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber))); 451 conditionElement.appendChild(labelElement); 452 453 var editorElement = document.createElement("input"); 454 editorElement.id = "source-frame-breakpoint-condition"; 455 editorElement.className = "monospace"; 456 editorElement.type = "text"; 457 conditionElement.appendChild(editorElement); 458 this._conditionEditorElement = editorElement; 459 460 return conditionElement; 461 }, 462 463 /** 464 * @param {number} lineNumber 465 * @param {!WebInspector.DebuggerModel.CallFrame} callFrame 466 */ 467 setExecutionLine: function(lineNumber, callFrame) 468 { 469 this._executionLineNumber = lineNumber; 470 this._executionCallFrame = callFrame; 471 if (this.loaded) { 472 this.textEditor.setExecutionLine(lineNumber); 473 474 if (WebInspector.experimentsSettings.stepIntoSelection.isEnabled()) 475 callFrame.getStepIntoLocations(locationsCallback.bind(this)); 476 } 477 478 /** 479 * @param {!Array.<!DebuggerAgent.Location>} locations 480 * @this {WebInspector.JavaScriptSourceFrame} 481 */ 482 function locationsCallback(locations) 483 { 484 if (this._executionCallFrame !== callFrame || this._stepIntoMarkup) 485 return; 486 this._stepIntoMarkup = WebInspector.JavaScriptSourceFrame.StepIntoMarkup.create(this, locations); 487 if (this._stepIntoMarkup) 488 this._stepIntoMarkup.show(); 489 } 490 }, 491 492 clearExecutionLine: function() 493 { 494 if (this._stepIntoMarkup) { 495 this._stepIntoMarkup.dispose(); 496 delete this._stepIntoMarkup; 497 } 498 499 if (this.loaded && typeof this._executionLineNumber === "number") 500 this.textEditor.clearExecutionLine(); 501 delete this._executionLineNumber; 502 delete this._executionCallFrame; 503 }, 504 505 _lineNumberAfterEditing: function(lineNumber, oldRange, newRange) 506 { 507 var shiftOffset = lineNumber <= oldRange.startLine ? 0 : newRange.linesCount - oldRange.linesCount; 508 509 // Special case of editing the line itself. We should decide whether the line number should move below or not. 510 if (lineNumber === oldRange.startLine) { 511 var whiteSpacesRegex = /^[\s\xA0]*$/; 512 for (var i = 0; lineNumber + i <= newRange.endLine; ++i) { 513 if (!whiteSpacesRegex.test(this.textEditor.line(lineNumber + i))) { 514 shiftOffset = i; 515 break; 516 } 517 } 518 } 519 520 var newLineNumber = Math.max(0, lineNumber + shiftOffset); 521 if (oldRange.startLine < lineNumber && lineNumber < oldRange.endLine) 522 newLineNumber = oldRange.startLine; 523 return newLineNumber; 524 }, 525 526 _onMouseDownAndClick: function(isMouseDown, event) 527 { 528 var markup = this._stepIntoMarkup; 529 if (!markup) 530 return; 531 var index = markup.findItemByCoordinates(event.x, event.y); 532 if (typeof index === "undefined") 533 return; 534 535 if (isMouseDown) { 536 // Do not let text editor to spoil 'click' event that is coming for us. 537 event.consume(); 538 } else { 539 var rawLocation = markup.getRawPosition(index); 540 this._scriptsPanel.doStepIntoSelection(rawLocation); 541 } 542 }, 543 544 /** 545 * @return {boolean} 546 */ 547 _shouldIgnoreExternalBreakpointEvents: function() 548 { 549 if (this._supportsEnabledBreakpointsWhileEditing()) 550 return false; 551 if (this._muted) 552 return true; 553 return this._scriptFile && (this._scriptFile.isDivergingFromVM() || this._scriptFile.isMergingToVM()); 554 }, 555 556 _breakpointAdded: function(event) 557 { 558 var uiLocation = /** @type {!WebInspector.UILocation} */ (event.data.uiLocation); 559 if (uiLocation.uiSourceCode !== this._uiSourceCode) 560 return; 561 if (this._shouldIgnoreExternalBreakpointEvents()) 562 return; 563 564 var breakpoint = /** @type {!WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint); 565 if (this.loaded) 566 this._addBreakpointDecoration(uiLocation.lineNumber, breakpoint.condition(), breakpoint.enabled(), false); 567 }, 568 569 _breakpointRemoved: function(event) 570 { 571 var uiLocation = /** @type {!WebInspector.UILocation} */ (event.data.uiLocation); 572 if (uiLocation.uiSourceCode !== this._uiSourceCode) 573 return; 574 if (this._shouldIgnoreExternalBreakpointEvents()) 575 return; 576 577 var breakpoint = /** @type {!WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint); 578 var remainingBreakpoint = this._breakpointManager.findBreakpoint(this._uiSourceCode, uiLocation.lineNumber); 579 if (!remainingBreakpoint && this.loaded) 580 this._removeBreakpointDecoration(uiLocation.lineNumber); 581 }, 582 583 _consoleMessageAdded: function(event) 584 { 585 var message = /** @type {!WebInspector.PresentationConsoleMessage} */ (event.data); 586 if (this.loaded) 587 this.addMessageToSource(message.lineNumber, message.originalMessage); 588 }, 589 590 _consoleMessageRemoved: function(event) 591 { 592 var message = /** @type {!WebInspector.PresentationConsoleMessage} */ (event.data); 593 if (this.loaded) 594 this.removeMessageFromSource(message.lineNumber, message.originalMessage); 595 }, 596 597 _consoleMessagesCleared: function(event) 598 { 599 this.clearMessages(); 600 }, 601 602 /** 603 * @param {!WebInspector.Event} event 604 */ 605 _onSourceMappingChanged: function(event) 606 { 607 this._updateScriptFile(); 608 }, 609 610 _updateScriptFile: function() 611 { 612 if (this._scriptFile) { 613 this._scriptFile.removeEventListener(WebInspector.ScriptFile.Events.DidMergeToVM, this._didMergeToVM, this); 614 this._scriptFile.removeEventListener(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this); 615 if (this._muted && !this._uiSourceCode.isDirty()) 616 this._restoreBreakpointsAfterEditing(); 617 } 618 this._scriptFile = this._uiSourceCode.scriptFile(); 619 if (this._scriptFile) { 620 this._scriptFile.addEventListener(WebInspector.ScriptFile.Events.DidMergeToVM, this._didMergeToVM, this); 621 this._scriptFile.addEventListener(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this); 622 623 if (this.loaded) 624 this._scriptFile.checkMapping(); 625 } 626 }, 627 628 onTextEditorContentLoaded: function() 629 { 630 if (typeof this._executionLineNumber === "number") 631 this.setExecutionLine(this._executionLineNumber, this._executionCallFrame); 632 633 var breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._uiSourceCode); 634 for (var i = 0; i < breakpointLocations.length; ++i) 635 this._breakpointAdded({data:breakpointLocations[i]}); 636 637 var messages = this._uiSourceCode.consoleMessages(); 638 for (var i = 0; i < messages.length; ++i) { 639 var message = messages[i]; 640 this.addMessageToSource(message.lineNumber, message.originalMessage); 641 } 642 643 if (this._scriptFile) 644 this._scriptFile.checkMapping(); 645 }, 646 647 /** 648 * @param {!WebInspector.Event} event 649 */ 650 _handleGutterClick: function(event) 651 { 652 if (this._muted) 653 return; 654 655 var eventData = /** @type {!WebInspector.TextEditor.GutterClickEventData} */ (event.data); 656 var lineNumber = eventData.lineNumber; 657 var eventObject = /** @type {!Event} */ (eventData.event); 658 659 if (eventObject.button != 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey) 660 return; 661 662 this._toggleBreakpoint(lineNumber, eventObject.shiftKey); 663 eventObject.consume(true); 664 }, 665 666 /** 667 * @param {number} lineNumber 668 * @param {boolean} onlyDisable 669 */ 670 _toggleBreakpoint: function(lineNumber, onlyDisable) 671 { 672 var breakpoint = this._breakpointManager.findBreakpoint(this._uiSourceCode, lineNumber); 673 if (breakpoint) { 674 if (onlyDisable) 675 breakpoint.setEnabled(!breakpoint.enabled()); 676 else 677 breakpoint.remove(); 678 } else 679 this._setBreakpoint(lineNumber, "", true); 680 }, 681 682 toggleBreakpointOnCurrentLine: function() 683 { 684 if (this._muted) 685 return; 686 687 var selection = this.textEditor.selection(); 688 if (!selection) 689 return; 690 this._toggleBreakpoint(selection.startLine, false); 691 }, 692 693 /** 694 * @param {number} lineNumber 695 * @param {string} condition 696 * @param {boolean} enabled 697 */ 698 _setBreakpoint: function(lineNumber, condition, enabled) 699 { 700 this._breakpointManager.setBreakpoint(this._uiSourceCode, lineNumber, condition, enabled); 701 702 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 703 action: WebInspector.UserMetrics.UserActionNames.SetBreakpoint, 704 url: this._uiSourceCode.originURL(), 705 line: lineNumber, 706 enabled: enabled 707 }); 708 }, 709 710 /** 711 * @param {number} lineNumber 712 */ 713 _continueToLine: function(lineNumber) 714 { 715 var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (this._uiSourceCode.uiLocationToRawLocation(lineNumber, 0)); 716 this._scriptsPanel.continueToLocation(rawLocation); 717 }, 718 719 /** 720 * @return {!WebInspector.JavaScriptSourceFrame.StepIntoMarkup|undefined} 721 */ 722 stepIntoMarkup: function() 723 { 724 return this._stepIntoMarkup; 725 }, 726 727 dispose: function() 728 { 729 this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this); 730 this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this); 731 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessageAdded, this._consoleMessageAdded, this); 732 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, this._consoleMessageRemoved, this); 733 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this); 734 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this); 735 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 736 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 737 WebInspector.UISourceCodeFrame.prototype.dispose.call(this); 738 }, 739 740 __proto__: WebInspector.UISourceCodeFrame.prototype 741 } 742 743 /** 744 * @constructor 745 * @param {!Array.<!DebuggerAgent.Location>} rawPositions 746 * @param {!Array.<!WebInspector.TextRange>} editorRanges 747 * @param {number} firstToExecute 748 * @param {!WebInspector.JavaScriptSourceFrame} sourceFrame 749 */ 750 WebInspector.JavaScriptSourceFrame.StepIntoMarkup = function(rawPositions, editorRanges, firstToExecute, sourceFrame) 751 { 752 this._positions = rawPositions; 753 this._editorRanges = editorRanges; 754 this._highlightDescriptors = new Array(rawPositions.length); 755 this._currentHighlight = undefined; 756 this._firstToExecute = firstToExecute; 757 this._currentSelection = undefined; 758 this._sourceFrame = sourceFrame; 759 }; 760 761 WebInspector.JavaScriptSourceFrame.StepIntoMarkup.prototype = { 762 show: function() 763 { 764 var highlight = this._getVisibleHighlight(); 765 for (var i = 0; i < this._positions.length; ++i) 766 this._highlightItem(i, i === highlight); 767 this._shownVisibleHighlight = highlight; 768 }, 769 770 startIteratingSelection: function() 771 { 772 this._currentSelection = this._positions.length 773 this._redrawHighlight(); 774 }, 775 776 stopIteratingSelection: function() 777 { 778 this._currentSelection = undefined; 779 this._redrawHighlight(); 780 }, 781 782 /** 783 * @param {boolean} backward 784 */ 785 iterateSelection: function(backward) 786 { 787 if (typeof this._currentSelection === "undefined") 788 return; 789 var nextSelection = backward ? this._currentSelection - 1 : this._currentSelection + 1; 790 var modulo = this._positions.length + 1; 791 nextSelection = (nextSelection + modulo) % modulo; 792 this._currentSelection = nextSelection; 793 this._redrawHighlight(); 794 }, 795 796 _redrawHighlight: function() 797 { 798 var visibleHighlight = this._getVisibleHighlight(); 799 if (this._shownVisibleHighlight === visibleHighlight) 800 return; 801 this._hideItemHighlight(this._shownVisibleHighlight); 802 this._hideItemHighlight(visibleHighlight); 803 this._highlightItem(this._shownVisibleHighlight, false); 804 this._highlightItem(visibleHighlight, true); 805 this._shownVisibleHighlight = visibleHighlight; 806 }, 807 808 /** 809 * @return {number} 810 */ 811 _getVisibleHighlight: function() 812 { 813 return typeof this._currentSelection === "undefined" ? this._firstToExecute : this._currentSelection; 814 }, 815 816 /** 817 * @param {number} position 818 * @param {boolean} selected 819 */ 820 _highlightItem: function(position, selected) 821 { 822 if (position === this._positions.length) 823 return; 824 var styleName = selected ? "source-frame-stepin-mark-highlighted" : "source-frame-stepin-mark"; 825 var textEditor = this._sourceFrame.textEditor; 826 var highlightDescriptor = textEditor.highlightRange(this._editorRanges[position], styleName); 827 this._highlightDescriptors[position] = highlightDescriptor; 828 }, 829 830 /** 831 * @param {number} position 832 */ 833 _hideItemHighlight: function(position) 834 { 835 if (position === this._positions.length) 836 return; 837 var highlightDescriptor = this._highlightDescriptors[position]; 838 console.assert(highlightDescriptor); 839 var textEditor = this._sourceFrame.textEditor; 840 textEditor.removeHighlight(highlightDescriptor); 841 this._highlightDescriptors[position] = undefined; 842 }, 843 844 dispose: function() 845 { 846 for (var i = 0; i < this._positions.length; ++i) 847 this._hideItemHighlight(i); 848 }, 849 850 /** 851 * @param {number} x 852 * @param {number} y 853 * @return {number|undefined} 854 */ 855 findItemByCoordinates: function(x, y) 856 { 857 var textPosition = this._sourceFrame.textEditor.coordinatesToCursorPosition(x, y); 858 if (!textPosition) 859 return; 860 861 var ranges = this._editorRanges; 862 863 for (var i = 0; i < ranges.length; ++i) { 864 var nextRange = ranges[i]; 865 if (nextRange.startLine == textPosition.startLine && nextRange.startColumn <= textPosition.startColumn && nextRange.endColumn >= textPosition.startColumn) 866 return i; 867 } 868 }, 869 870 /** 871 * @return {number|undefined} 872 */ 873 getSelectedItemIndex: function() 874 { 875 if (this._currentSelection === this._positions.length) 876 return undefined; 877 return this._currentSelection; 878 }, 879 880 /** 881 * @return {!WebInspector.DebuggerModel.Location} 882 */ 883 getRawPosition: function(position) 884 { 885 return /** @type {!WebInspector.DebuggerModel.Location} */ (this._positions[position]); 886 } 887 888 }; 889 890 /** 891 * @param {!WebInspector.JavaScriptSourceFrame} sourceFrame 892 * @param {!Array.<!DebuggerAgent.Location>} stepIntoRawLocations 893 * @return {?WebInspector.JavaScriptSourceFrame.StepIntoMarkup} 894 */ 895 WebInspector.JavaScriptSourceFrame.StepIntoMarkup.create = function(sourceFrame, stepIntoRawLocations) 896 { 897 if (!stepIntoRawLocations.length) 898 return null; 899 900 var firstToExecute = stepIntoRawLocations[0]; 901 stepIntoRawLocations.sort(WebInspector.JavaScriptSourceFrame.StepIntoMarkup._Comparator); 902 var firstToExecuteIndex = stepIntoRawLocations.indexOf(firstToExecute); 903 904 var textEditor = sourceFrame.textEditor; 905 var uiRanges = []; 906 for (var i = 0; i < stepIntoRawLocations.length; ++i) { 907 var uiLocation = WebInspector.debuggerModel.rawLocationToUILocation(/** @type {!WebInspector.DebuggerModel.Location} */ (stepIntoRawLocations[i])); 908 909 var token = textEditor.tokenAtTextPosition(uiLocation.lineNumber, uiLocation.columnNumber); 910 var startColumn; 911 var endColumn; 912 if (token) { 913 startColumn = token.startColumn; 914 endColumn = token.endColumn; 915 } else { 916 startColumn = uiLocation.columnNumber; 917 endColumn = uiLocation.columnNumber; 918 } 919 var range = new WebInspector.TextRange(uiLocation.lineNumber, startColumn, uiLocation.lineNumber, endColumn); 920 uiRanges.push(range); 921 } 922 923 return new WebInspector.JavaScriptSourceFrame.StepIntoMarkup(stepIntoRawLocations, uiRanges, firstToExecuteIndex, sourceFrame); 924 }; 925 926 /** 927 * @param {!DebuggerAgent.Location} locationA 928 * @param {!DebuggerAgent.Location} locationB 929 * @return {number} 930 */ 931 WebInspector.JavaScriptSourceFrame.StepIntoMarkup._Comparator = function(locationA, locationB) 932 { 933 if (locationA.lineNumber === locationB.lineNumber) 934 return locationA.columnNumber - locationB.columnNumber; 935 else 936 return locationA.lineNumber - locationB.lineNumber; 937 }; 938