Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / JavaScriptSourceFrame.js
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.ShortcutsScreen.SourcesPanelShortcuts;
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         var breakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, lineNumber);
135         if (!breakpoint) {
136             // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint.
137             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add breakpoint" : "Add Breakpoint"), this._setBreakpoint.bind(this, lineNumber, 0, "", true));
138             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add conditional breakpoint…" : "Add Conditional Breakpoint…"), this._editBreakpointCondition.bind(this, lineNumber));
139         } else {
140             // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable.
141             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), breakpoint.remove.bind(breakpoint));
142             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit breakpoint…" : "Edit Breakpoint…"), this._editBreakpointCondition.bind(this, lineNumber, breakpoint));
143             if (breakpoint.enabled())
144                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Disable breakpoint" : "Disable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, false));
145             else
146                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Enable breakpoint" : "Enable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, true));
147         }
148     },
149
150     populateTextAreaContextMenu: function(contextMenu, lineNumber)
151     {
152         var textSelection = this.textEditor.selection();
153         if (textSelection && !textSelection.isEmpty()) {
154             var selection = this.textEditor.copyRange(textSelection);
155             var addToWatchLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add to watch" : "Add to Watch");
156             contextMenu.appendItem(addToWatchLabel, this._innerAddToWatch.bind(this, selection));
157             var evaluateLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Evaluate in console" : "Evaluate in Console");
158             contextMenu.appendItem(evaluateLabel, WebInspector.evaluateInConsole.bind(WebInspector, selection));
159             contextMenu.appendSeparator();
160         } else if (!this._uiSourceCode.isEditable() && this._uiSourceCode.contentType() === WebInspector.resourceTypes.Script) {
161
162             // FIXME: Change condition above to explicitly check that current uiSourceCode is created by default debugger mapping
163             // and move the code adding this menu item to generic context menu provider for UISourceCode.
164             var liveEditLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Live edit" : "Live Edit");
165             contextMenu.appendItem(liveEditLabel, liveEdit.bind(this));
166             contextMenu.appendSeparator();
167         }
168
169         /**
170          * @this {WebInspector.JavaScriptSourceFrame}
171          */
172         function liveEdit()
173         {
174             var liveEditUISourceCode = WebInspector.liveEditSupport.uiSourceCodeForLiveEdit(this._uiSourceCode);
175             this._scriptsPanel.showUISourceCode(liveEditUISourceCode, lineNumber)
176         }
177
178         WebInspector.UISourceCodeFrame.prototype.populateTextAreaContextMenu.call(this, contextMenu, lineNumber);
179     },
180
181     _workingCopyChanged: function(event)
182     {
183         if (this._supportsEnabledBreakpointsWhileEditing() || this._scriptFile)
184             return;
185
186         if (this._uiSourceCode.isDirty())
187             this._muteBreakpointsWhileEditing();
188         else
189             this._restoreBreakpointsAfterEditing();
190     },
191
192     _workingCopyCommitted: function(event)
193     {
194         if (this._supportsEnabledBreakpointsWhileEditing() || this._scriptFile)
195             return;
196         this._restoreBreakpointsAfterEditing();
197     },
198
199     _didMergeToVM: function()
200     {
201         if (this._supportsEnabledBreakpointsWhileEditing())
202             return;
203         this._restoreBreakpointsAfterEditing();
204     },
205
206     _didDivergeFromVM: function()
207     {
208         if (this._supportsEnabledBreakpointsWhileEditing())
209             return;
210         this._muteBreakpointsWhileEditing();
211     },
212
213     _muteBreakpointsWhileEditing: function()
214     {
215         if (this._muted)
216             return;
217         for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) {
218             var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint");
219             if (!breakpointDecoration)
220                 continue;
221             this._removeBreakpointDecoration(lineNumber);
222             this._addBreakpointDecoration(lineNumber, breakpointDecoration.columnNumber, breakpointDecoration.condition, breakpointDecoration.enabled, true);
223         }
224         this._muted = true;
225     },
226
227     _supportsEnabledBreakpointsWhileEditing: function()
228     {
229         return this._uiSourceCode.project().type() === WebInspector.projectTypes.Snippets;
230     },
231
232     _restoreBreakpointsAfterEditing: function()
233     {
234         delete this._muted;
235         var breakpoints = {};
236         // Save and remove muted breakpoint decorations.
237         for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) {
238             var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint");
239             if (breakpointDecoration) {
240                 breakpoints[lineNumber] = breakpointDecoration;
241                 this._removeBreakpointDecoration(lineNumber);
242             }
243         }
244
245         // Remove all breakpoints.
246         this._removeAllBreakpoints();
247
248         // Restore all breakpoints from saved decorations.
249         for (var lineNumberString in breakpoints) {
250             var lineNumber = parseInt(lineNumberString, 10);
251             if (isNaN(lineNumber))
252                 continue;
253             var breakpointDecoration = breakpoints[lineNumberString];
254             this._setBreakpoint(lineNumber, breakpointDecoration.columnNumber, breakpointDecoration.condition, breakpointDecoration.enabled);
255         }
256     },
257
258     _removeAllBreakpoints: function()
259     {
260         var breakpoints = this._breakpointManager.breakpointsForUISourceCode(this._uiSourceCode);
261         for (var i = 0; i < breakpoints.length; ++i)
262             breakpoints[i].remove();
263     },
264
265     _getPopoverAnchor: function(element, event)
266     {
267         if (!WebInspector.debuggerModel.isPaused())
268             return null;
269
270         var textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y);
271         if (!textPosition)
272             return null;
273         var mouseLine = textPosition.startLine;
274         var mouseColumn = textPosition.startColumn;
275         var textSelection = this.textEditor.selection().normalize();
276         if (textSelection && !textSelection.isEmpty()) {
277             if (textSelection.startLine !== textSelection.endLine || textSelection.startLine !== mouseLine || mouseColumn < textSelection.startColumn || mouseColumn > textSelection.endColumn)
278                 return null;
279
280             var leftCorner = this.textEditor.cursorPositionToCoordinates(textSelection.startLine, textSelection.startColumn);
281             var rightCorner = this.textEditor.cursorPositionToCoordinates(textSelection.endLine, textSelection.endColumn);
282             var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
283             anchorBox.highlight = {
284                 lineNumber: textSelection.startLine,
285                 startColumn: textSelection.startColumn,
286                 endColumn: textSelection.endColumn - 1
287             };
288             anchorBox.forSelection = true;
289             return anchorBox;
290         }
291
292         var token = this.textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn);
293         if (!token)
294             return null;
295         var lineNumber = textPosition.startLine;
296         var line = this.textEditor.line(lineNumber);
297         var tokenContent = line.substring(token.startColumn, token.endColumn + 1);
298
299         var isIdentifier = token.type.startsWith("js-variable") || token.type.startsWith("js-property") || token.type == "js-def";
300         if (!isIdentifier && (token.type !== "js-keyword" || tokenContent !== "this"))
301             return null;
302
303         var leftCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.startColumn);
304         var rightCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.endColumn + 1);
305         var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
306
307         anchorBox.highlight = {
308             lineNumber: lineNumber,
309             startColumn: token.startColumn,
310             endColumn: token.endColumn
311         };
312
313         return anchorBox;
314     },
315
316     _resolveObjectForPopover: function(anchorBox, showCallback, objectGroupName)
317     {
318         /**
319          * @param {?RuntimeAgent.RemoteObject} result
320          * @param {boolean=} wasThrown
321          * @this {WebInspector.JavaScriptSourceFrame}
322          */
323         function showObjectPopover(result, wasThrown)
324         {
325             if (!WebInspector.debuggerModel.isPaused() || !result) {
326                 this._popoverHelper.hidePopover();
327                 return;
328             }
329             this._popoverAnchorBox = anchorBox;
330             showCallback(WebInspector.RemoteObject.fromPayload(result), wasThrown, this._popoverAnchorBox);
331             // Popover may have been removed by showCallback().
332             if (this._popoverAnchorBox) {
333                 var highlightRange = new WebInspector.TextRange(lineNumber, startHighlight, lineNumber, endHighlight);
334                 this._popoverAnchorBox._highlightDescriptor = this.textEditor.highlightRange(highlightRange, "source-frame-eval-expression");
335             }
336         }
337
338         if (!WebInspector.debuggerModel.isPaused()) {
339             this._popoverHelper.hidePopover();
340             return;
341         }
342         var lineNumber = anchorBox.highlight.lineNumber;
343         var startHighlight = anchorBox.highlight.startColumn;
344         var endHighlight = anchorBox.highlight.endColumn;
345         var line = this.textEditor.line(lineNumber);
346         if (!anchorBox.forSelection) {
347             while (startHighlight > 1 && line.charAt(startHighlight - 1) === '.') {
348                 var token = this.textEditor.tokenAtTextPosition(lineNumber, startHighlight - 2);
349                 if (!token) {
350                     this._popoverHelper.hidePopover();
351                     return;
352                 }
353                 startHighlight = token.startColumn;
354             }
355         }
356         var evaluationText = line.substring(startHighlight, endHighlight + 1);
357         var selectedCallFrame = WebInspector.debuggerModel.selectedCallFrame();
358         selectedCallFrame.evaluate(evaluationText, objectGroupName, false, true, false, false, showObjectPopover.bind(this));
359     },
360
361     _onHidePopover: function()
362     {
363         if (!this._popoverAnchorBox)
364             return;
365         if (this._popoverAnchorBox._highlightDescriptor)
366             this.textEditor.removeHighlight(this._popoverAnchorBox._highlightDescriptor);
367         delete this._popoverAnchorBox;
368     },
369
370     /**
371      * @param {number} lineNumber
372      * @param {number} columnNumber
373      * @param {string} condition
374      * @param {boolean} enabled
375      * @param {boolean} mutedWhileEditing
376      */
377     _addBreakpointDecoration: function(lineNumber, columnNumber, condition, enabled, mutedWhileEditing)
378     {
379         var breakpoint = {
380             condition: condition,
381             enabled: enabled,
382             columnNumber: columnNumber
383         };
384
385         this.textEditor.setAttribute(lineNumber, "breakpoint", breakpoint);
386
387         var disabled = !enabled || mutedWhileEditing;
388         this.textEditor.addBreakpoint(lineNumber, disabled, !!condition);
389     },
390
391     _removeBreakpointDecoration: function(lineNumber)
392     {
393         this.textEditor.removeAttribute(lineNumber, "breakpoint");
394         this.textEditor.removeBreakpoint(lineNumber);
395     },
396
397     _onKeyDown: function(event)
398     {
399         if (event.keyIdentifier === "U+001B") { // Escape key
400             if (this._popoverHelper.isPopoverVisible()) {
401                 this._popoverHelper.hidePopover();
402                 event.consume();
403                 return;
404             }
405             if (this._stepIntoMarkup && WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
406                 this._stepIntoMarkup.stoptIteratingSelection();
407                 event.consume();
408                 return;
409             }
410         }
411     },
412
413     /**
414      * @param {number} lineNumber
415      * @param {!WebInspector.BreakpointManager.Breakpoint=} breakpoint
416      */
417     _editBreakpointCondition: function(lineNumber, breakpoint)
418     {
419         this._conditionElement = this._createConditionElement(lineNumber);
420         this.textEditor.addDecoration(lineNumber, this._conditionElement);
421
422         /**
423          * @this {WebInspector.JavaScriptSourceFrame}
424          */
425         function finishEditing(committed, element, newText)
426         {
427             this.textEditor.removeDecoration(lineNumber, this._conditionElement);
428             delete this._conditionEditorElement;
429             delete this._conditionElement;
430             if (!committed)
431                 return;
432
433             if (breakpoint)
434                 breakpoint.setCondition(newText);
435             else
436                 this._setBreakpoint(lineNumber, 0, newText, true);
437         }
438
439         var config = new WebInspector.InplaceEditor.Config(finishEditing.bind(this, true), finishEditing.bind(this, false));
440         WebInspector.InplaceEditor.startEditing(this._conditionEditorElement, config);
441         this._conditionEditorElement.value = breakpoint ? breakpoint.condition() : "";
442         this._conditionEditorElement.select();
443     },
444
445     _createConditionElement: function(lineNumber)
446     {
447         var conditionElement = document.createElement("div");
448         conditionElement.className = "source-frame-breakpoint-condition";
449
450         var labelElement = document.createElement("label");
451         labelElement.className = "source-frame-breakpoint-message";
452         labelElement.htmlFor = "source-frame-breakpoint-condition";
453         labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber)));
454         conditionElement.appendChild(labelElement);
455
456         var editorElement = document.createElement("input");
457         editorElement.id = "source-frame-breakpoint-condition";
458         editorElement.className = "monospace";
459         editorElement.type = "text";
460         conditionElement.appendChild(editorElement);
461         this._conditionEditorElement = editorElement;
462
463         return conditionElement;
464     },
465
466     /**
467      * @param {number} lineNumber
468      * @param {!WebInspector.DebuggerModel.CallFrame} callFrame
469      */
470     setExecutionLine: function(lineNumber, callFrame)
471     {
472         this._executionLineNumber = lineNumber;
473         this._executionCallFrame = callFrame;
474         if (this.loaded) {
475             this.textEditor.setExecutionLine(lineNumber);
476
477             if (WebInspector.experimentsSettings.stepIntoSelection.isEnabled())
478                 callFrame.getStepIntoLocations(locationsCallback.bind(this));
479         }
480
481         /**
482          * @param {!Array.<!DebuggerAgent.Location>} locations
483          * @this {WebInspector.JavaScriptSourceFrame}
484          */
485         function locationsCallback(locations)
486         {
487             if (this._executionCallFrame !== callFrame || this._stepIntoMarkup)
488                 return;
489             this._stepIntoMarkup = WebInspector.JavaScriptSourceFrame.StepIntoMarkup.create(this, locations);
490             if (this._stepIntoMarkup)
491                 this._stepIntoMarkup.show();
492         }
493     },
494
495     clearExecutionLine: function()
496     {
497         if (this._stepIntoMarkup) {
498             this._stepIntoMarkup.dispose();
499             delete this._stepIntoMarkup;
500         }
501
502         if (this.loaded && typeof this._executionLineNumber === "number")
503             this.textEditor.clearExecutionLine();
504         delete this._executionLineNumber;
505         delete this._executionCallFrame;
506     },
507
508     _lineNumberAfterEditing: function(lineNumber, oldRange, newRange)
509     {
510         var shiftOffset = lineNumber <= oldRange.startLine ? 0 : newRange.linesCount - oldRange.linesCount;
511
512         // Special case of editing the line itself. We should decide whether the line number should move below or not.
513         if (lineNumber === oldRange.startLine) {
514             var whiteSpacesRegex = /^[\s\xA0]*$/;
515             for (var i = 0; lineNumber + i <= newRange.endLine; ++i) {
516                 if (!whiteSpacesRegex.test(this.textEditor.line(lineNumber + i))) {
517                     shiftOffset = i;
518                     break;
519                 }
520             }
521         }
522
523         var newLineNumber = Math.max(0, lineNumber + shiftOffset);
524         if (oldRange.startLine < lineNumber && lineNumber < oldRange.endLine)
525             newLineNumber = oldRange.startLine;
526         return newLineNumber;
527     },
528
529     _onMouseDownAndClick: function(isMouseDown, event)
530     {
531         var markup = this._stepIntoMarkup;
532         if (!markup)
533             return;
534         var index = markup.findItemByCoordinates(event.x, event.y);
535         if (typeof index === "undefined")
536             return;
537
538         if (isMouseDown) {
539             // Do not let text editor to spoil 'click' event that is coming for us.
540             event.consume();
541         } else {
542             var rawLocation = markup.getRawPosition(index);
543             this._scriptsPanel.doStepIntoSelection(rawLocation);
544         }
545     },
546
547     /**
548      * @return {boolean}
549      */
550     _shouldIgnoreExternalBreakpointEvents: function()
551     {
552         if (this._supportsEnabledBreakpointsWhileEditing())
553             return false;
554         if (this._muted)
555             return true;
556         return this._scriptFile && (this._scriptFile.isDivergingFromVM() || this._scriptFile.isMergingToVM());
557     },
558
559     _breakpointAdded: function(event)
560     {
561         var uiLocation = /** @type {!WebInspector.UILocation} */ (event.data.uiLocation);
562         if (uiLocation.uiSourceCode !== this._uiSourceCode)
563             return;
564         if (this._shouldIgnoreExternalBreakpointEvents())
565             return;
566
567         var breakpoint = /** @type {!WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint);
568         if (this.loaded)
569             this._addBreakpointDecoration(uiLocation.lineNumber, uiLocation.columnNumber, breakpoint.condition(), breakpoint.enabled(), false);
570     },
571
572     _breakpointRemoved: function(event)
573     {
574         var uiLocation = /** @type {!WebInspector.UILocation} */ (event.data.uiLocation);
575         if (uiLocation.uiSourceCode !== this._uiSourceCode)
576             return;
577         if (this._shouldIgnoreExternalBreakpointEvents())
578             return;
579
580         var breakpoint = /** @type {!WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint);
581         var remainingBreakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, uiLocation.lineNumber);
582         if (!remainingBreakpoint && this.loaded)
583             this._removeBreakpointDecoration(uiLocation.lineNumber);
584     },
585
586     _consoleMessageAdded: function(event)
587     {
588         var message = /** @type {!WebInspector.PresentationConsoleMessage} */ (event.data);
589         if (this.loaded)
590             this.addMessageToSource(message.lineNumber, message.originalMessage);
591     },
592
593     _consoleMessageRemoved: function(event)
594     {
595         var message = /** @type {!WebInspector.PresentationConsoleMessage} */ (event.data);
596         if (this.loaded)
597             this.removeMessageFromSource(message.lineNumber, message.originalMessage);
598     },
599
600     _consoleMessagesCleared: function(event)
601     {
602         this.clearMessages();
603     },
604
605     /**
606      * @param {!WebInspector.Event} event
607      */
608     _onSourceMappingChanged: function(event)
609     {
610         this._updateScriptFile();
611     },
612
613     _updateScriptFile: function()
614     {
615         if (this._scriptFile) {
616             this._scriptFile.removeEventListener(WebInspector.ScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
617             this._scriptFile.removeEventListener(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
618             if (this._muted && !this._uiSourceCode.isDirty())
619                 this._restoreBreakpointsAfterEditing();
620         }
621         this._scriptFile = this._uiSourceCode.scriptFile();
622         if (this._scriptFile) {
623             this._scriptFile.addEventListener(WebInspector.ScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
624             this._scriptFile.addEventListener(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
625
626             if (this.loaded)
627                 this._scriptFile.checkMapping();
628         }
629     },
630
631     beforeFormattedChange: function()
632     {
633         this.clearExecutionLine();
634     },
635
636     onTextEditorContentLoaded: function()
637     {
638         if (typeof this._executionLineNumber === "number")
639             this.setExecutionLine(this._executionLineNumber, this._executionCallFrame);
640
641         var breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._uiSourceCode);
642         for (var i = 0; i < breakpointLocations.length; ++i)
643             this._breakpointAdded({data:breakpointLocations[i]});
644
645         var messages = this._uiSourceCode.consoleMessages();
646         for (var i = 0; i < messages.length; ++i) {
647             var message = messages[i];
648             this.addMessageToSource(message.lineNumber, message.originalMessage);
649         }
650
651         if (this._scriptFile)
652             this._scriptFile.checkMapping();
653     },
654
655     /**
656      * @param {!WebInspector.Event} event
657      */
658     _handleGutterClick: function(event)
659     {
660         if (this._muted)
661             return;
662
663         var eventData = /** @type {!WebInspector.TextEditor.GutterClickEventData} */ (event.data);
664         var lineNumber = eventData.lineNumber;
665         var eventObject = /** @type {!Event} */ (eventData.event);
666
667         if (eventObject.button != 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey)
668             return;
669
670         this._toggleBreakpoint(lineNumber, eventObject.shiftKey);
671         eventObject.consume(true);
672     },
673
674     /**
675      * @param {number} lineNumber
676      * @param {boolean} onlyDisable
677      */
678     _toggleBreakpoint: function(lineNumber, onlyDisable)
679     {
680         var breakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, lineNumber);
681         if (breakpoint) {
682             if (onlyDisable)
683                 breakpoint.setEnabled(!breakpoint.enabled());
684             else
685                 breakpoint.remove();
686         } else
687             this._setBreakpoint(lineNumber, 0, "", true);
688     },
689
690     toggleBreakpointOnCurrentLine: function()
691     {
692         if (this._muted)
693             return;
694
695         var selection = this.textEditor.selection();
696         if (!selection)
697             return;
698         this._toggleBreakpoint(selection.startLine, false);
699     },
700
701     /**
702      * @param {number} lineNumber
703      * @param {number} columnNumber
704      * @param {string} condition
705      * @param {boolean} enabled
706      */
707     _setBreakpoint: function(lineNumber, columnNumber, condition, enabled)
708     {
709         this._breakpointManager.setBreakpoint(this._uiSourceCode, lineNumber, columnNumber, condition, enabled);
710
711         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
712             action: WebInspector.UserMetrics.UserActionNames.SetBreakpoint,
713             url: this._uiSourceCode.originURL(),
714             line: lineNumber,
715             enabled: enabled
716         });
717     },
718
719     /**
720      * @param {number} lineNumber
721      */
722     _continueToLine: function(lineNumber)
723     {
724         var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (this._uiSourceCode.uiLocationToRawLocation(lineNumber, 0));
725         this._scriptsPanel.continueToLocation(rawLocation);
726     },
727
728     /**
729      * @return {!WebInspector.JavaScriptSourceFrame.StepIntoMarkup|undefined}
730      */
731     stepIntoMarkup: function()
732     {
733         return this._stepIntoMarkup;
734     },
735
736     dispose: function()
737     {
738         this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this);
739         this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this);
740         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessageAdded, this._consoleMessageAdded, this);
741         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, this._consoleMessageRemoved, this);
742         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this);
743         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this);
744         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
745         this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
746         WebInspector.UISourceCodeFrame.prototype.dispose.call(this);
747     },
748
749     __proto__: WebInspector.UISourceCodeFrame.prototype
750 }
751
752 /**
753  * @constructor
754  * @param {!Array.<!DebuggerAgent.Location>} rawPositions
755  * @param {!Array.<!WebInspector.TextRange>} editorRanges
756  * @param {number} firstToExecute
757  * @param {!WebInspector.JavaScriptSourceFrame} sourceFrame
758  */
759 WebInspector.JavaScriptSourceFrame.StepIntoMarkup = function(rawPositions, editorRanges, firstToExecute, sourceFrame)
760 {
761     this._positions = rawPositions;
762     this._editorRanges = editorRanges;
763     this._highlightDescriptors = new Array(rawPositions.length);
764     this._currentHighlight = undefined;
765     this._firstToExecute = firstToExecute;
766     this._currentSelection = undefined;
767     this._sourceFrame = sourceFrame;
768 };
769
770 WebInspector.JavaScriptSourceFrame.StepIntoMarkup.prototype = {
771     show: function()
772     {
773         var highlight = this._getVisibleHighlight();
774         for (var i = 0; i < this._positions.length; ++i)
775             this._highlightItem(i, i === highlight);
776         this._shownVisibleHighlight = highlight;
777     },
778
779     startIteratingSelection: function()
780     {
781         this._currentSelection = this._positions.length
782         this._redrawHighlight();
783     },
784
785     stopIteratingSelection: function()
786     {
787         this._currentSelection = undefined;
788         this._redrawHighlight();
789     },
790
791     /**
792      * @param {boolean} backward
793      */
794     iterateSelection: function(backward)
795     {
796         if (typeof this._currentSelection === "undefined")
797             return;
798         var nextSelection = backward ? this._currentSelection - 1 : this._currentSelection + 1;
799         var modulo = this._positions.length + 1;
800         nextSelection = (nextSelection + modulo) % modulo;
801         this._currentSelection = nextSelection;
802         this._redrawHighlight();
803     },
804
805     _redrawHighlight: function()
806     {
807         var visibleHighlight = this._getVisibleHighlight();
808         if (this._shownVisibleHighlight === visibleHighlight)
809             return;
810         this._hideItemHighlight(this._shownVisibleHighlight);
811         this._hideItemHighlight(visibleHighlight);
812         this._highlightItem(this._shownVisibleHighlight, false);
813         this._highlightItem(visibleHighlight, true);
814         this._shownVisibleHighlight = visibleHighlight;
815     },
816
817     /**
818      * @return {number}
819      */
820     _getVisibleHighlight: function()
821     {
822         return typeof this._currentSelection === "undefined" ? this._firstToExecute : this._currentSelection;
823     },
824
825     /**
826      * @param {number} position
827      * @param {boolean} selected
828      */
829     _highlightItem: function(position, selected)
830     {
831         if (position === this._positions.length)
832             return;
833         var styleName = selected ? "source-frame-stepin-mark-highlighted" : "source-frame-stepin-mark";
834         var textEditor = this._sourceFrame.textEditor;
835         var highlightDescriptor = textEditor.highlightRange(this._editorRanges[position], styleName);
836         this._highlightDescriptors[position] = highlightDescriptor;
837     },
838
839     /**
840      * @param {number} position
841      */
842     _hideItemHighlight: function(position)
843     {
844         if (position === this._positions.length)
845             return;
846         var highlightDescriptor = this._highlightDescriptors[position];
847         console.assert(highlightDescriptor);
848         var textEditor = this._sourceFrame.textEditor;
849         textEditor.removeHighlight(highlightDescriptor);
850         this._highlightDescriptors[position] = undefined;
851     },
852
853     dispose: function()
854     {
855         for (var i = 0; i < this._positions.length; ++i)
856             this._hideItemHighlight(i);
857     },
858
859     /**
860      * @param {number} x
861      * @param {number} y
862      * @return {number|undefined}
863      */
864     findItemByCoordinates: function(x, y)
865     {
866         var textPosition = this._sourceFrame.textEditor.coordinatesToCursorPosition(x, y);
867         if (!textPosition)
868             return;
869
870         var ranges = this._editorRanges;
871
872         for (var i = 0; i < ranges.length; ++i) {
873           var nextRange = ranges[i];
874           if (nextRange.startLine == textPosition.startLine && nextRange.startColumn <= textPosition.startColumn && nextRange.endColumn >= textPosition.startColumn)
875               return i;
876         }
877     },
878
879     /**
880      * @return {number|undefined}
881      */
882     getSelectedItemIndex: function()
883     {
884         if (this._currentSelection === this._positions.length)
885             return undefined;
886         return this._currentSelection;
887     },
888
889     /**
890      * @return {!WebInspector.DebuggerModel.Location}
891      */
892     getRawPosition: function(position)
893     {
894         return /** @type {!WebInspector.DebuggerModel.Location} */ (this._positions[position]);
895     }
896
897 };
898
899 /**
900  * @param {!WebInspector.JavaScriptSourceFrame} sourceFrame
901  * @param {!Array.<!DebuggerAgent.Location>} stepIntoRawLocations
902  * @return {?WebInspector.JavaScriptSourceFrame.StepIntoMarkup}
903  */
904 WebInspector.JavaScriptSourceFrame.StepIntoMarkup.create = function(sourceFrame, stepIntoRawLocations)
905 {
906     if (!stepIntoRawLocations.length)
907         return null;
908
909     var firstToExecute = stepIntoRawLocations[0];
910     stepIntoRawLocations.sort(WebInspector.JavaScriptSourceFrame.StepIntoMarkup._Comparator);
911     var firstToExecuteIndex = stepIntoRawLocations.indexOf(firstToExecute);
912
913     var textEditor = sourceFrame.textEditor;
914     var uiRanges = [];
915     for (var i = 0; i < stepIntoRawLocations.length; ++i) {
916         var uiLocation = WebInspector.debuggerModel.rawLocationToUILocation(/** @type {!WebInspector.DebuggerModel.Location} */ (stepIntoRawLocations[i]));
917
918         var token = textEditor.tokenAtTextPosition(uiLocation.lineNumber, uiLocation.columnNumber);
919         var startColumn;
920         var endColumn;
921         if (token) {
922             startColumn = token.startColumn;
923             endColumn = token.endColumn;
924         } else {
925             startColumn = uiLocation.columnNumber;
926             endColumn = uiLocation.columnNumber;
927         }
928         var range = new WebInspector.TextRange(uiLocation.lineNumber, startColumn, uiLocation.lineNumber, endColumn);
929         uiRanges.push(range);
930     }
931
932     return new WebInspector.JavaScriptSourceFrame.StepIntoMarkup(stepIntoRawLocations, uiRanges, firstToExecuteIndex, sourceFrame);
933 };
934
935 /**
936  * @param {!DebuggerAgent.Location} locationA
937  * @param {!DebuggerAgent.Location} locationB
938  * @return {number}
939  */
940 WebInspector.JavaScriptSourceFrame.StepIntoMarkup._Comparator = function(locationA, locationB)
941 {
942     if (locationA.lineNumber === locationB.lineNumber)
943         return locationA.columnNumber - locationB.columnNumber;
944     else
945         return locationA.lineNumber - locationB.lineNumber;
946 };