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._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this); 55 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this); 56 57 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessageAdded, this._consoleMessageAdded, this); 58 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, this._consoleMessageRemoved, this); 59 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this); 60 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this); 61 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 62 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 63 64 /** @type {!Map.<!WebInspector.Target, !WebInspector.ScriptFile>}*/ 65 this._scriptFileForTarget = new Map(); 66 this._registerShortcuts(); 67 var targets = WebInspector.targetManager.targets(); 68 for (var i = 0; i < targets.length; ++i) { 69 var scriptFile = uiSourceCode.scriptFileForTarget(targets[i]); 70 if (scriptFile) 71 this._updateScriptFile(targets[i]); 72 } 73 } 74 75 WebInspector.JavaScriptSourceFrame.prototype = { 76 /** 77 * @param {!Element} infobarElement 78 */ 79 _showInfobar: function(infobarElement) 80 { 81 if (this._infobarElement) 82 this._infobarElement.remove(); 83 this._infobarElement = infobarElement; 84 this._infobarElement.classList.add("java-script-source-frame-infobar"); 85 this.element.insertBefore(this._infobarElement, this.element.children[0]); 86 this.doResize(); 87 }, 88 89 /** 90 * @param {!Element} infobarElement 91 */ 92 _hideInfobar: function(infobarElement) 93 { 94 infobarElement.remove(); 95 this.doResize(); 96 }, 97 98 _showDivergedInfobar: function() 99 { 100 if (this._uiSourceCode.contentType() !== WebInspector.resourceTypes.Script) 101 return; 102 103 this._divergedInfobarElement = document.createElement("div"); 104 var infobarMainRow = this._divergedInfobarElement.createChild("div", "java-script-source-frame-infobar-main-row"); 105 var infobarDetailsContainer = this._divergedInfobarElement.createChild("span", "java-script-source-frame-infobar-details-container"); 106 107 infobarMainRow.createChild("span", "java-script-source-frame-infobar-warning-icon"); 108 var infobarMessage = infobarMainRow.createChild("span", "java-script-source-frame-infobar-row-message"); 109 infobarMessage.textContent = WebInspector.UIString("Workspace mapping mismatch"); 110 111 /** 112 * @this {WebInspector.JavaScriptSourceFrame} 113 */ 114 function updateDetailsVisibility() 115 { 116 detailsToggleElement.textContent = detailsToggleElement._toggled ? WebInspector.UIString("less") : WebInspector.UIString("more"); 117 infobarDetailsContainer.classList.toggle("hidden", !detailsToggleElement._toggled); 118 this.doResize(); 119 } 120 121 /** 122 * @this {WebInspector.JavaScriptSourceFrame} 123 */ 124 function toggleDetails() 125 { 126 detailsToggleElement._toggled = !detailsToggleElement._toggled; 127 updateDetailsVisibility.call(this); 128 } 129 130 infobarMainRow.appendChild(document.createTextNode("\u00a0")); 131 var detailsToggleElement = infobarMainRow.createChild("div", "java-script-source-frame-infobar-toggle"); 132 detailsToggleElement.addEventListener("click", toggleDetails.bind(this), false); 133 updateDetailsVisibility.call(this); 134 135 function createDetailsRowMessage() 136 { 137 var infobarDetailsRow = infobarDetailsContainer.createChild("div", "java-script-source-frame-infobar-details-row"); 138 return infobarDetailsRow.createChild("span", "java-script-source-frame-infobar-row-message"); 139 } 140 141 var infobarDetailsRowMessage; 142 143 infobarDetailsRowMessage = createDetailsRowMessage(); 144 infobarDetailsRowMessage.appendChild(document.createTextNode(WebInspector.UIString("The content of this file on the file system:\u00a0"))); 145 var fileURL = this._uiSourceCode.originURL(); 146 infobarDetailsRowMessage.appendChild(WebInspector.linkifyURLAsNode(fileURL, fileURL, "java-script-source-frame-infobar-details-url", true, fileURL)); 147 148 infobarDetailsRowMessage = createDetailsRowMessage(); 149 infobarDetailsRowMessage.appendChild(document.createTextNode(WebInspector.UIString("does not match the loaded script:\u00a0"))); 150 var scriptURL = this._uiSourceCode.url; 151 infobarDetailsRowMessage.appendChild(WebInspector.linkifyURLAsNode(scriptURL, scriptURL, "java-script-source-frame-infobar-details-url", true, scriptURL)); 152 153 // Add an empty row 154 createDetailsRowMessage(); 155 156 createDetailsRowMessage().textContent = WebInspector.UIString("Possible solutions are:");; 157 158 function createDetailsRowMessageAction(title) 159 { 160 infobarDetailsRowMessage = createDetailsRowMessage(); 161 infobarDetailsRowMessage.appendChild(document.createTextNode(" - ")); 162 infobarDetailsRowMessage.appendChild(document.createTextNode(title)); 163 } 164 165 if (WebInspector.settings.cacheDisabled.get()) 166 createDetailsRowMessageAction(WebInspector.UIString("Reload inspected page")); 167 else 168 createDetailsRowMessageAction(WebInspector.UIString("Check \"Disable cache\" in settings and reload inspected page (recommended setup for authoring and debugging)")); 169 createDetailsRowMessageAction(WebInspector.UIString("Check that your file and script are both loaded from the correct source and their contents match.")); 170 171 this._showInfobar(this._divergedInfobarElement); 172 }, 173 174 _hideDivergedInfobar: function() 175 { 176 if (!this._divergedInfobarElement) 177 return; 178 this._hideInfobar(this._divergedInfobarElement); 179 delete this._divergedInfobarElement; 180 }, 181 182 _registerShortcuts: function() 183 { 184 var shortcutKeys = WebInspector.ShortcutsScreen.SourcesPanelShortcuts; 185 for (var i = 0; i < shortcutKeys.EvaluateSelectionInConsole.length; ++i) { 186 var keyDescriptor = shortcutKeys.EvaluateSelectionInConsole[i]; 187 this.addShortcut(keyDescriptor.key, this._evaluateSelectionInConsole.bind(this)); 188 } 189 for (var i = 0; i < shortcutKeys.AddSelectionToWatch.length; ++i) { 190 var keyDescriptor = shortcutKeys.AddSelectionToWatch[i]; 191 this.addShortcut(keyDescriptor.key, this._addCurrentSelectionToWatch.bind(this)); 192 } 193 }, 194 195 _addCurrentSelectionToWatch: function() 196 { 197 var textSelection = this.textEditor.selection(); 198 if (textSelection && !textSelection.isEmpty()) 199 this._innerAddToWatch(this.textEditor.copyRange(textSelection)); 200 }, 201 202 /** 203 * @param {string} expression 204 */ 205 _innerAddToWatch: function(expression) 206 { 207 this._scriptsPanel.addToWatch(expression); 208 }, 209 210 /** 211 * @return {boolean} 212 */ 213 _evaluateSelectionInConsole: function() 214 { 215 var selection = this.textEditor.selection(); 216 if (!selection || selection.isEmpty()) 217 return false; 218 this._evaluateInConsole(this.textEditor.copyRange(selection)); 219 return true; 220 }, 221 222 /** 223 * @param {string} expression 224 */ 225 _evaluateInConsole: function(expression) 226 { 227 var currentExecutionContext = WebInspector.context.flavor(WebInspector.ExecutionContext); 228 if (currentExecutionContext) 229 WebInspector.ConsoleModel.evaluateCommandInConsole(currentExecutionContext, expression); 230 }, 231 232 // View events 233 wasShown: function() 234 { 235 WebInspector.UISourceCodeFrame.prototype.wasShown.call(this); 236 }, 237 238 willHide: function() 239 { 240 WebInspector.UISourceCodeFrame.prototype.willHide.call(this); 241 this._popoverHelper.hidePopover(); 242 }, 243 244 onUISourceCodeContentChanged: function() 245 { 246 this._removeAllBreakpoints(); 247 WebInspector.UISourceCodeFrame.prototype.onUISourceCodeContentChanged.call(this); 248 }, 249 250 populateLineGutterContextMenu: function(contextMenu, lineNumber) 251 { 252 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Continue to here" : "Continue to Here"), this._continueToLine.bind(this, lineNumber)); 253 var breakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, lineNumber); 254 if (!breakpoint) { 255 // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint. 256 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add breakpoint" : "Add Breakpoint"), this._setBreakpoint.bind(this, lineNumber, 0, "", true)); 257 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add conditional breakpoint" : "Add Conditional Breakpoint"), this._editBreakpointCondition.bind(this, lineNumber)); 258 } else { 259 // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable. 260 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), breakpoint.remove.bind(breakpoint)); 261 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit breakpoint" : "Edit Breakpoint"), this._editBreakpointCondition.bind(this, lineNumber, breakpoint)); 262 if (breakpoint.enabled()) 263 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Disable breakpoint" : "Disable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, false)); 264 else 265 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Enable breakpoint" : "Enable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, true)); 266 } 267 }, 268 269 populateTextAreaContextMenu: function(contextMenu, lineNumber) 270 { 271 var textSelection = this.textEditor.selection(); 272 if (textSelection && !textSelection.isEmpty()) { 273 var selection = this.textEditor.copyRange(textSelection); 274 var addToWatchLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add to watch" : "Add to Watch"); 275 contextMenu.appendItem(addToWatchLabel, this._innerAddToWatch.bind(this, selection)); 276 var evaluateLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Evaluate in console" : "Evaluate in Console"); 277 contextMenu.appendItem(evaluateLabel, this._evaluateInConsole.bind(this, selection)); 278 contextMenu.appendSeparator(); 279 } else if (this._uiSourceCode.project().type() === WebInspector.projectTypes.Debugger) { 280 // FIXME: Change condition above to explicitly check that current uiSourceCode is created by default debugger mapping 281 // and move the code adding this menu item to generic context menu provider for UISourceCode. 282 var liveEditLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Live edit" : "Live Edit"); 283 contextMenu.appendItem(liveEditLabel, liveEdit.bind(this)); 284 contextMenu.appendSeparator(); 285 } 286 287 /** 288 * @this {WebInspector.JavaScriptSourceFrame} 289 */ 290 function liveEdit() 291 { 292 var liveEditUISourceCode = WebInspector.liveEditSupport.uiSourceCodeForLiveEdit(this._uiSourceCode); 293 WebInspector.Revealer.reveal(liveEditUISourceCode.uiLocation(lineNumber)); 294 } 295 296 WebInspector.UISourceCodeFrame.prototype.populateTextAreaContextMenu.call(this, contextMenu, lineNumber); 297 }, 298 299 _workingCopyChanged: function(event) 300 { 301 if (this._supportsEnabledBreakpointsWhileEditing() || this._scriptFileForTarget.size()) 302 return; 303 304 if (this._uiSourceCode.isDirty()) 305 this._muteBreakpointsWhileEditing(); 306 else 307 this._restoreBreakpointsAfterEditing(); 308 }, 309 310 _workingCopyCommitted: function(event) 311 { 312 if (this._supportsEnabledBreakpointsWhileEditing()) 313 return; 314 if (this._scriptFileForTarget.size()) { 315 this._hasCommittedLiveEdit = true; 316 var scriptFiles = this._scriptFileForTarget.values(); 317 for (var i = 0; i < scriptFiles.length; ++i) 318 scriptFiles[i].commitLiveEdit(); 319 return; 320 } 321 this._restoreBreakpointsAfterEditing(); 322 }, 323 324 _didMergeToVM: function() 325 { 326 if (this._supportsEnabledBreakpointsWhileEditing()) 327 return; 328 this._updateDivergedInfobar(); 329 this._restoreBreakpointsIfConsistentScripts(); 330 }, 331 332 _didDivergeFromVM: function() 333 { 334 if (this._supportsEnabledBreakpointsWhileEditing()) 335 return; 336 this._updateDivergedInfobar(); 337 this._muteBreakpointsWhileEditing(); 338 }, 339 340 _muteBreakpointsWhileEditing: function() 341 { 342 if (this._muted) 343 return; 344 for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) { 345 var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint"); 346 if (!breakpointDecoration) 347 continue; 348 this._removeBreakpointDecoration(lineNumber); 349 this._addBreakpointDecoration(lineNumber, breakpointDecoration.columnNumber, breakpointDecoration.condition, breakpointDecoration.enabled, true); 350 } 351 this._muted = true; 352 }, 353 354 _updateDivergedInfobar: function() 355 { 356 if (this._uiSourceCode.project().type() !== WebInspector.projectTypes.FileSystem) { 357 this._hideDivergedInfobar(); 358 return; 359 } 360 361 var scriptFiles = this._scriptFileForTarget.values(); 362 var hasDivergedScript = false; 363 for (var i = 0; i < scriptFiles.length; ++i) 364 hasDivergedScript = hasDivergedScript || scriptFiles[i].hasDivergedFromVM(); 365 366 if (this._divergedInfobarElement) { 367 if (!hasDivergedScript || this._hasCommittedLiveEdit) 368 this._hideDivergedInfobar(); 369 } else { 370 if (hasDivergedScript && !this._uiSourceCode.isDirty() && !this._hasCommittedLiveEdit) 371 this._showDivergedInfobar(); 372 } 373 }, 374 375 _supportsEnabledBreakpointsWhileEditing: function() 376 { 377 return this._uiSourceCode.project().type() === WebInspector.projectTypes.Snippets; 378 }, 379 380 _restoreBreakpointsIfConsistentScripts: function() 381 { 382 var scriptFiles = this._scriptFileForTarget.values(); 383 for (var i = 0; i < scriptFiles.length; ++i) 384 if (scriptFiles[i].hasDivergedFromVM() || scriptFiles[i].isMergingToVM()) 385 return; 386 387 this._restoreBreakpointsAfterEditing(); 388 }, 389 390 _restoreBreakpointsAfterEditing: function() 391 { 392 delete this._muted; 393 var breakpoints = {}; 394 // Save and remove muted breakpoint decorations. 395 for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) { 396 var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint"); 397 if (breakpointDecoration) { 398 breakpoints[lineNumber] = breakpointDecoration; 399 this._removeBreakpointDecoration(lineNumber); 400 } 401 } 402 403 // Remove all breakpoints. 404 this._removeAllBreakpoints(); 405 406 // Restore all breakpoints from saved decorations. 407 for (var lineNumberString in breakpoints) { 408 var lineNumber = parseInt(lineNumberString, 10); 409 if (isNaN(lineNumber)) 410 continue; 411 var breakpointDecoration = breakpoints[lineNumberString]; 412 this._setBreakpoint(lineNumber, breakpointDecoration.columnNumber, breakpointDecoration.condition, breakpointDecoration.enabled); 413 } 414 }, 415 416 _removeAllBreakpoints: function() 417 { 418 var breakpoints = this._breakpointManager.breakpointsForUISourceCode(this._uiSourceCode); 419 for (var i = 0; i < breakpoints.length; ++i) 420 breakpoints[i].remove(); 421 }, 422 423 _getPopoverAnchor: function(element, event) 424 { 425 if (!WebInspector.debuggerModel.isPaused()) 426 return null; 427 428 var textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y); 429 if (!textPosition) 430 return null; 431 var mouseLine = textPosition.startLine; 432 var mouseColumn = textPosition.startColumn; 433 var textSelection = this.textEditor.selection().normalize(); 434 if (textSelection && !textSelection.isEmpty()) { 435 if (textSelection.startLine !== textSelection.endLine || textSelection.startLine !== mouseLine || mouseColumn < textSelection.startColumn || mouseColumn > textSelection.endColumn) 436 return null; 437 438 var leftCorner = this.textEditor.cursorPositionToCoordinates(textSelection.startLine, textSelection.startColumn); 439 var rightCorner = this.textEditor.cursorPositionToCoordinates(textSelection.endLine, textSelection.endColumn); 440 var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height); 441 anchorBox.highlight = { 442 lineNumber: textSelection.startLine, 443 startColumn: textSelection.startColumn, 444 endColumn: textSelection.endColumn - 1 445 }; 446 anchorBox.forSelection = true; 447 return anchorBox; 448 } 449 450 var token = this.textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn); 451 if (!token) 452 return null; 453 var lineNumber = textPosition.startLine; 454 var line = this.textEditor.line(lineNumber); 455 var tokenContent = line.substring(token.startColumn, token.endColumn + 1); 456 457 var isIdentifier = token.type.startsWith("js-variable") || token.type.startsWith("js-property") || token.type == "js-def"; 458 if (!isIdentifier && (token.type !== "js-keyword" || tokenContent !== "this")) 459 return null; 460 461 var leftCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.startColumn); 462 var rightCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.endColumn + 1); 463 var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height); 464 465 anchorBox.highlight = { 466 lineNumber: lineNumber, 467 startColumn: token.startColumn, 468 endColumn: token.endColumn 469 }; 470 471 return anchorBox; 472 }, 473 474 _resolveObjectForPopover: function(anchorBox, showCallback, objectGroupName) 475 { 476 if (!WebInspector.debuggerModel.isPaused()) { 477 this._popoverHelper.hidePopover(); 478 return; 479 } 480 var lineNumber = anchorBox.highlight.lineNumber; 481 var startHighlight = anchorBox.highlight.startColumn; 482 var endHighlight = anchorBox.highlight.endColumn; 483 var line = this.textEditor.line(lineNumber); 484 if (!anchorBox.forSelection) { 485 while (startHighlight > 1 && line.charAt(startHighlight - 1) === '.') { 486 var token = this.textEditor.tokenAtTextPosition(lineNumber, startHighlight - 2); 487 if (!token) { 488 this._popoverHelper.hidePopover(); 489 return; 490 } 491 startHighlight = token.startColumn; 492 } 493 } 494 var evaluationText = line.substring(startHighlight, endHighlight + 1); 495 var selectedCallFrame = WebInspector.debuggerModel.selectedCallFrame(); 496 selectedCallFrame.evaluate(evaluationText, objectGroupName, false, true, false, false, showObjectPopover.bind(this)); 497 498 /** 499 * @param {?RuntimeAgent.RemoteObject} result 500 * @param {boolean=} wasThrown 501 * @this {WebInspector.JavaScriptSourceFrame} 502 */ 503 function showObjectPopover(result, wasThrown) 504 { 505 if (!WebInspector.debuggerModel.isPaused() || !result) { 506 this._popoverHelper.hidePopover(); 507 return; 508 } 509 this._popoverAnchorBox = anchorBox; 510 showCallback(selectedCallFrame.target().runtimeModel.createRemoteObject(result), wasThrown, this._popoverAnchorBox); 511 // Popover may have been removed by showCallback(). 512 if (this._popoverAnchorBox) { 513 var highlightRange = new WebInspector.TextRange(lineNumber, startHighlight, lineNumber, endHighlight); 514 this._popoverAnchorBox._highlightDescriptor = this.textEditor.highlightRange(highlightRange, "source-frame-eval-expression"); 515 } 516 } 517 }, 518 519 _onHidePopover: function() 520 { 521 if (!this._popoverAnchorBox) 522 return; 523 if (this._popoverAnchorBox._highlightDescriptor) 524 this.textEditor.removeHighlight(this._popoverAnchorBox._highlightDescriptor); 525 delete this._popoverAnchorBox; 526 }, 527 528 /** 529 * @param {number} lineNumber 530 * @param {number} columnNumber 531 * @param {string} condition 532 * @param {boolean} enabled 533 * @param {boolean} mutedWhileEditing 534 */ 535 _addBreakpointDecoration: function(lineNumber, columnNumber, condition, enabled, mutedWhileEditing) 536 { 537 var breakpoint = { 538 condition: condition, 539 enabled: enabled, 540 columnNumber: columnNumber 541 }; 542 543 this.textEditor.setAttribute(lineNumber, "breakpoint", breakpoint); 544 545 var disabled = !enabled || mutedWhileEditing; 546 this.textEditor.addBreakpoint(lineNumber, disabled, !!condition); 547 }, 548 549 _removeBreakpointDecoration: function(lineNumber) 550 { 551 this.textEditor.removeAttribute(lineNumber, "breakpoint"); 552 this.textEditor.removeBreakpoint(lineNumber); 553 }, 554 555 _onKeyDown: function(event) 556 { 557 if (event.keyIdentifier === "U+001B") { // Escape key 558 if (this._popoverHelper.isPopoverVisible()) { 559 this._popoverHelper.hidePopover(); 560 event.consume(); 561 } 562 } 563 }, 564 565 /** 566 * @param {number} lineNumber 567 * @param {!WebInspector.BreakpointManager.Breakpoint=} breakpoint 568 */ 569 _editBreakpointCondition: function(lineNumber, breakpoint) 570 { 571 this._conditionElement = this._createConditionElement(lineNumber); 572 this.textEditor.addDecoration(lineNumber, this._conditionElement); 573 574 /** 575 * @this {WebInspector.JavaScriptSourceFrame} 576 */ 577 function finishEditing(committed, element, newText) 578 { 579 this.textEditor.removeDecoration(lineNumber, this._conditionElement); 580 delete this._conditionEditorElement; 581 delete this._conditionElement; 582 if (!committed) 583 return; 584 585 if (breakpoint) 586 breakpoint.setCondition(newText); 587 else 588 this._setBreakpoint(lineNumber, 0, newText, true); 589 } 590 591 var config = new WebInspector.InplaceEditor.Config(finishEditing.bind(this, true), finishEditing.bind(this, false)); 592 WebInspector.InplaceEditor.startEditing(this._conditionEditorElement, config); 593 this._conditionEditorElement.value = breakpoint ? breakpoint.condition() : ""; 594 this._conditionEditorElement.select(); 595 }, 596 597 _createConditionElement: function(lineNumber) 598 { 599 var conditionElement = document.createElement("div"); 600 conditionElement.className = "source-frame-breakpoint-condition"; 601 602 var labelElement = document.createElement("label"); 603 labelElement.className = "source-frame-breakpoint-message"; 604 labelElement.htmlFor = "source-frame-breakpoint-condition"; 605 labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber + 1))); 606 conditionElement.appendChild(labelElement); 607 608 var editorElement = document.createElement("input"); 609 editorElement.id = "source-frame-breakpoint-condition"; 610 editorElement.className = "monospace"; 611 editorElement.type = "text"; 612 conditionElement.appendChild(editorElement); 613 this._conditionEditorElement = editorElement; 614 615 return conditionElement; 616 }, 617 618 /** 619 * @param {number} lineNumber 620 */ 621 setExecutionLine: function(lineNumber) 622 { 623 this._executionLineNumber = lineNumber; 624 if (this.loaded) 625 this.textEditor.setExecutionLine(lineNumber); 626 }, 627 628 clearExecutionLine: function() 629 { 630 if (this.loaded && typeof this._executionLineNumber === "number") 631 this.textEditor.clearExecutionLine(); 632 delete this._executionLineNumber; 633 }, 634 635 /** 636 * @return {boolean} 637 */ 638 _shouldIgnoreExternalBreakpointEvents: function() 639 { 640 if (this._supportsEnabledBreakpointsWhileEditing()) 641 return false; 642 if (this._muted) 643 return true; 644 var scriptFiles = this._scriptFileForTarget.values(); 645 var hasDivergingOrMergingFile = false; 646 for (var i = 0; i < scriptFiles.length; ++i) 647 if (scriptFiles[i].isDivergingFromVM() || scriptFiles[i].isMergingToVM()) 648 return true; 649 return false; 650 }, 651 652 _breakpointAdded: function(event) 653 { 654 var uiLocation = /** @type {!WebInspector.UILocation} */ (event.data.uiLocation); 655 if (uiLocation.uiSourceCode !== this._uiSourceCode) 656 return; 657 if (this._shouldIgnoreExternalBreakpointEvents()) 658 return; 659 660 var breakpoint = /** @type {!WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint); 661 if (this.loaded) 662 this._addBreakpointDecoration(uiLocation.lineNumber, uiLocation.columnNumber, breakpoint.condition(), breakpoint.enabled(), false); 663 }, 664 665 _breakpointRemoved: function(event) 666 { 667 var uiLocation = /** @type {!WebInspector.UILocation} */ (event.data.uiLocation); 668 if (uiLocation.uiSourceCode !== this._uiSourceCode) 669 return; 670 if (this._shouldIgnoreExternalBreakpointEvents()) 671 return; 672 673 var breakpoint = /** @type {!WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint); 674 var remainingBreakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, uiLocation.lineNumber); 675 if (!remainingBreakpoint && this.loaded) 676 this._removeBreakpointDecoration(uiLocation.lineNumber); 677 }, 678 679 _consoleMessageAdded: function(event) 680 { 681 var message = /** @type {!WebInspector.PresentationConsoleMessage} */ (event.data); 682 if (this.loaded) 683 this.addMessageToSource(message.lineNumber, message.originalMessage); 684 }, 685 686 _consoleMessageRemoved: function(event) 687 { 688 var message = /** @type {!WebInspector.PresentationConsoleMessage} */ (event.data); 689 if (this.loaded) 690 this.removeMessageFromSource(message.lineNumber, message.originalMessage); 691 }, 692 693 _consoleMessagesCleared: function(event) 694 { 695 this.clearMessages(); 696 }, 697 698 /** 699 * @param {!WebInspector.Event} event 700 */ 701 _onSourceMappingChanged: function(event) 702 { 703 var data = /** @type {{target: !WebInspector.Target}} */ (event.data); 704 this._updateScriptFile(data.target); 705 }, 706 707 /** 708 * @param {!WebInspector.Target} target 709 */ 710 _updateScriptFile: function(target) 711 { 712 var oldScriptFile = this._scriptFileForTarget.get(target); 713 var newScriptFile = this._uiSourceCode.scriptFileForTarget(target); 714 this._scriptFileForTarget.remove(target); 715 if (oldScriptFile) { 716 oldScriptFile.removeEventListener(WebInspector.ScriptFile.Events.DidMergeToVM, this._didMergeToVM, this); 717 oldScriptFile.removeEventListener(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this); 718 if (this._muted && !this._uiSourceCode.isDirty()) 719 this._restoreBreakpointsIfConsistentScripts(); 720 } 721 if (newScriptFile) 722 this._scriptFileForTarget.put(target, newScriptFile); 723 724 delete this._hasCommittedLiveEdit; 725 this._updateDivergedInfobar(); 726 727 if (newScriptFile) { 728 newScriptFile.addEventListener(WebInspector.ScriptFile.Events.DidMergeToVM, this._didMergeToVM, this); 729 newScriptFile.addEventListener(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this); 730 if (this.loaded) 731 newScriptFile.checkMapping(); 732 } 733 }, 734 735 onTextEditorContentLoaded: function() 736 { 737 if (typeof this._executionLineNumber === "number") 738 this.setExecutionLine(this._executionLineNumber); 739 740 var breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._uiSourceCode); 741 for (var i = 0; i < breakpointLocations.length; ++i) 742 this._breakpointAdded({data:breakpointLocations[i]}); 743 744 var messages = this._uiSourceCode.consoleMessages(); 745 for (var i = 0; i < messages.length; ++i) { 746 var message = messages[i]; 747 this.addMessageToSource(message.lineNumber, message.originalMessage); 748 } 749 750 var scriptFiles = this._scriptFileForTarget.values(); 751 for (var i = 0; i < scriptFiles.length; ++i) 752 scriptFiles[i].checkMapping(); 753 }, 754 755 /** 756 * @param {!WebInspector.Event} event 757 */ 758 _handleGutterClick: function(event) 759 { 760 if (this._muted) 761 return; 762 763 var eventData = /** @type {!WebInspector.TextEditor.GutterClickEventData} */ (event.data); 764 var lineNumber = eventData.lineNumber; 765 var eventObject = /** @type {!Event} */ (eventData.event); 766 767 if (eventObject.button != 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey) 768 return; 769 770 this._toggleBreakpoint(lineNumber, eventObject.shiftKey); 771 eventObject.consume(true); 772 }, 773 774 /** 775 * @param {number} lineNumber 776 * @param {boolean} onlyDisable 777 */ 778 _toggleBreakpoint: function(lineNumber, onlyDisable) 779 { 780 var breakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, lineNumber); 781 if (breakpoint) { 782 if (onlyDisable) 783 breakpoint.setEnabled(!breakpoint.enabled()); 784 else 785 breakpoint.remove(); 786 } else 787 this._setBreakpoint(lineNumber, 0, "", true); 788 }, 789 790 toggleBreakpointOnCurrentLine: function() 791 { 792 if (this._muted) 793 return; 794 795 var selection = this.textEditor.selection(); 796 if (!selection) 797 return; 798 this._toggleBreakpoint(selection.startLine, false); 799 }, 800 801 /** 802 * @param {number} lineNumber 803 * @param {number} columnNumber 804 * @param {string} condition 805 * @param {boolean} enabled 806 */ 807 _setBreakpoint: function(lineNumber, columnNumber, condition, enabled) 808 { 809 this._breakpointManager.setBreakpoint(this._uiSourceCode, lineNumber, columnNumber, condition, enabled); 810 811 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 812 action: WebInspector.UserMetrics.UserActionNames.SetBreakpoint, 813 url: this._uiSourceCode.originURL(), 814 line: lineNumber, 815 enabled: enabled 816 }); 817 }, 818 819 /** 820 * @param {number} lineNumber 821 */ 822 _continueToLine: function(lineNumber) 823 { 824 var executionContext = WebInspector.context.flavor(WebInspector.ExecutionContext); 825 if (!executionContext) 826 return; 827 var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (this._uiSourceCode.uiLocationToRawLocation(executionContext.target(), lineNumber, 0)); 828 this._scriptsPanel.continueToLocation(rawLocation); 829 }, 830 831 dispose: function() 832 { 833 this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this); 834 this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this); 835 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessageAdded, this._consoleMessageAdded, this); 836 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, this._consoleMessageRemoved, this); 837 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this); 838 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this); 839 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); 840 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); 841 WebInspector.UISourceCodeFrame.prototype.dispose.call(this); 842 }, 843 844 __proto__: WebInspector.UISourceCodeFrame.prototype 845 } 846