Home | History | Annotate | Download | only in sources
      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