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