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     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