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