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