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