d8a149cf7adf949838234ad2d24d29ba243e60a9
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / DebuggerSidebarPanel.js
1 /*
2  * Copyright (C) 2013 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.DebuggerSidebarPanel = function()
27 {
28     WebInspector.NavigationSidebarPanel.call(this, "debugger", WebInspector.UIString("Debugger"), "Images/NavigationItemBug.svg", "3", true);
29
30     WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceChanged, this);
31     WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceAdded, this);
32
33     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
34     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, this._debuggerCallFramesDidChange, this);
35     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
36     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
37     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptAdded, this._scriptAdded, this);
38     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptsCleared, this._scriptsCleared, this);
39     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
40     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
41     WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this);
42
43     this._toggleBreakpointsKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "Y", this._breakpointsToggleButtonClicked.bind(this));
44     this.pauseOrResumeKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control | WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "Y", this._debuggerPauseResumeButtonClicked.bind(this));
45     this._stepOverKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.F6, this._debuggerStepOverButtonClicked.bind(this));
46     this._stepIntoKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.F7, this._debuggerStepIntoButtonClicked.bind(this));
47     this._stepOutKeyboardShortcut = new WebInspector.KeyboardShortcut(null, WebInspector.KeyboardShortcut.Key.F8, this._debuggerStepOutButtonClicked.bind(this));
48
49     this.pauseOrResumeAlternateKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Backslash, this._debuggerPauseResumeButtonClicked.bind(this));
50     this._stepOverAlternateKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.SingleQuote, this._debuggerStepOverButtonClicked.bind(this));
51     this._stepIntoAlternateKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Semicolon, this._debuggerStepIntoButtonClicked.bind(this));
52     this._stepOutAlternateKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Shift | WebInspector.KeyboardShortcut.Modifier.CommandOrControl, WebInspector.KeyboardShortcut.Key.Semicolon, this._debuggerStepOutButtonClicked.bind(this));
53
54     this._navigationBar = new WebInspector.NavigationBar;
55     this.element.appendChild(this._navigationBar.element);
56
57     var breakpointsImage, pauseImage, resumeImage, stepOverImage, stepIntoImage, stepOutImage;
58     if (WebInspector.Platform.isLegacyMacOS) {
59         breakpointsImage = {src: "Images/Legacy/Breakpoints.svg", width: 16, height: 16};
60         pauseImage = {src: "Images/Legacy/Pause.svg", width: 16, height: 16};
61         resumeImage = {src: "Images/Legacy/Resume.svg", width: 16, height: 16};
62         stepOverImage = {src: "Images/Legacy/StepOver.svg", width: 16, height: 16};
63         stepIntoImage = {src: "Images/Legacy/StepInto.svg", width: 16, height: 16};
64         stepOutImage = {src: "Images/Legacy/StepOut.svg", width: 16, height: 16};
65     } else {
66         breakpointsImage = {src: "Images/Breakpoints.svg", width: 15, height: 15};
67         pauseImage = {src: "Images/Pause.svg", width: 15, height: 15};
68         resumeImage = {src: "Images/Resume.svg", width: 15, height: 15};
69         stepOverImage = {src: "Images/StepOver.svg", width: 15, height: 15};
70         stepIntoImage = {src: "Images/StepInto.svg", width: 15, height: 15};
71         stepOutImage = {src: "Images/StepOut.svg", width: 15, height: 15};
72     }
73
74     var toolTip = WebInspector.UIString("Enable all breakpoints (%s)").format(this._toggleBreakpointsKeyboardShortcut.displayName);
75     var altToolTip = WebInspector.UIString("Disable all breakpoints (%s)").format(this._toggleBreakpointsKeyboardShortcut.displayName);
76
77     this._debuggerBreakpointsButtonItem = new WebInspector.ActivateButtonNavigationItem("debugger-breakpoints", toolTip, altToolTip, breakpointsImage.src, breakpointsImage.width, breakpointsImage.height);
78     this._debuggerBreakpointsButtonItem.activated = WebInspector.debuggerManager.breakpointsEnabled;
79     this._debuggerBreakpointsButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._breakpointsToggleButtonClicked, this);
80     this._navigationBar.addNavigationItem(this._debuggerBreakpointsButtonItem);
81
82     toolTip = WebInspector.UIString("Pause script execution (%s or %s)").format(this.pauseOrResumeKeyboardShortcut.displayName, this.pauseOrResumeAlternateKeyboardShortcut.displayName);
83     altToolTip = WebInspector.UIString("Continue script execution (%s or %s)").format(this.pauseOrResumeKeyboardShortcut.displayName, this.pauseOrResumeAlternateKeyboardShortcut.displayName);
84
85     this._debuggerPauseResumeButtonItem = new WebInspector.ToggleButtonNavigationItem("debugger-pause-resume", toolTip, altToolTip, pauseImage.src, resumeImage.src, pauseImage.width, pauseImage.height);
86     this._debuggerPauseResumeButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._debuggerPauseResumeButtonClicked, this);
87     this._navigationBar.addNavigationItem(this._debuggerPauseResumeButtonItem);
88
89     this._debuggerStepOverButtonItem = new WebInspector.ButtonNavigationItem("debugger-step-over", WebInspector.UIString("Step over (%s or %s)").format(this._stepOverKeyboardShortcut.displayName, this._stepOverAlternateKeyboardShortcut.displayName), stepOverImage.src, stepOverImage.width, stepOverImage.height);
90     this._debuggerStepOverButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._debuggerStepOverButtonClicked, this);
91     this._debuggerStepOverButtonItem.enabled = false;
92     this._navigationBar.addNavigationItem(this._debuggerStepOverButtonItem);
93
94     this._debuggerStepIntoButtonItem = new WebInspector.ButtonNavigationItem("debugger-step-into", WebInspector.UIString("Step into (%s or %s)").format(this._stepIntoKeyboardShortcut.displayName, this._stepIntoAlternateKeyboardShortcut.displayName), stepIntoImage.src, stepIntoImage.width, stepIntoImage.height);
95     this._debuggerStepIntoButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._debuggerStepIntoButtonClicked, this);
96     this._debuggerStepIntoButtonItem.enabled = false;
97     this._navigationBar.addNavigationItem(this._debuggerStepIntoButtonItem);
98
99     this._debuggerStepOutButtonItem = new WebInspector.ButtonNavigationItem("debugger-step-out", WebInspector.UIString("Step out (%s or %s)").format(this._stepOutKeyboardShortcut.displayName, this._stepOutAlternateKeyboardShortcut.displayName), stepOutImage.src, stepOutImage.width, stepOutImage.height);
100     this._debuggerStepOutButtonItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._debuggerStepOutButtonClicked, this);
101     this._debuggerStepOutButtonItem.enabled = false;
102     this._navigationBar.addNavigationItem(this._debuggerStepOutButtonItem);
103
104     // Add this offset-sections class name so the sticky headers don't overlap the navigation bar.
105     this.element.classList.add(WebInspector.DebuggerSidebarPanel.OffsetSectionsStyleClassName);
106
107     this._globalBreakpointsFolderTreeElement = new WebInspector.FolderTreeElement(WebInspector.UIString("Global Breakpoints"), null, WebInspector.DebuggerSidebarPanel.GlobalIconStyleClassName);
108     this._allExceptionsBreakpointTreeElement = new WebInspector.BreakpointTreeElement(WebInspector.debuggerManager.allExceptionsBreakpoint, WebInspector.DebuggerSidebarPanel.ExceptionIconStyleClassName, WebInspector.UIString("All Exceptions"));
109     this._allUncaughtExceptionsBreakpointTreeElement = new WebInspector.BreakpointTreeElement(WebInspector.debuggerManager.allUncaughtExceptionsBreakpoint, WebInspector.DebuggerSidebarPanel.ExceptionIconStyleClassName, WebInspector.UIString("All Uncaught Exceptions"));
110
111     this.filterBar.placeholder = WebInspector.UIString("Filter Breakpoint List");
112
113     this._breakpointsContentTreeOutline = this.contentTreeOutline;
114     this._breakpointsContentTreeOutline.onselect = this._treeElementSelected.bind(this);
115     this._breakpointsContentTreeOutline.ondelete = this._breakpointTreeOutlineDeleteTreeElement.bind(this);
116     this._breakpointsContentTreeOutline.oncontextmenu = this._breakpointTreeOutlineContextMenuTreeElement.bind(this);
117
118     this._breakpointsContentTreeOutline.appendChild(this._globalBreakpointsFolderTreeElement);
119     this._globalBreakpointsFolderTreeElement.appendChild(this._allExceptionsBreakpointTreeElement);
120     this._globalBreakpointsFolderTreeElement.appendChild(this._allUncaughtExceptionsBreakpointTreeElement);
121     this._globalBreakpointsFolderTreeElement.expand();
122
123     var breakpointsRow = new WebInspector.DetailsSectionRow;
124     breakpointsRow.element.appendChild(this._breakpointsContentTreeOutline.element);
125
126     var breakpointsGroup = new WebInspector.DetailsSectionGroup([breakpointsRow]);
127     var breakpointsSection = new WebInspector.DetailsSection("scripts", WebInspector.UIString("Scripts"), [breakpointsGroup]);
128     this.contentElement.appendChild(breakpointsSection.element);
129
130     this._callStackContentTreeOutline = this.createContentTreeOutline(true);
131     this._callStackContentTreeOutline.onselect = this._treeElementSelected.bind(this);
132
133     this._callStackRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Call Frames"));
134     this._callStackRow.showEmptyMessage();
135
136     var callStackGroup = new WebInspector.DetailsSectionGroup([this._callStackRow]);
137     this._callStackSection = new WebInspector.DetailsSection("call-stack", WebInspector.UIString("Call Stack"), [callStackGroup]);
138
139     this._pauseReasonTreeOutline = null;
140
141     this._pauseReasonLinkContainerElement = document.createElement("span");
142     this._pauseReasonTextRow = new WebInspector.DetailsSectionTextRow;
143     this._pauseReasonGroup = new WebInspector.DetailsSectionGroup([this._pauseReasonTextRow]);
144     this._pauseReasonSection = new WebInspector.DetailsSection("paused-reason", null, [this._pauseReasonGroup], this._pauseReasonLinkContainerElement);
145     this._pauseReasonSection.title = WebInspector.UIString("Pause Reason");
146
147     WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisplayLocationDidChange, this._breakpointDisplayLocationDidChange, this);
148 };
149
150 WebInspector.DebuggerSidebarPanel.OffsetSectionsStyleClassName = "offset-sections";
151 WebInspector.DebuggerSidebarPanel.DebuggerPausedStyleClassName = "paused";
152 WebInspector.DebuggerSidebarPanel.ExceptionIconStyleClassName = "breakpoint-exception-icon";
153 WebInspector.DebuggerSidebarPanel.PausedBreakpointIconStyleClassName = "breakpoint-paused-icon";
154 WebInspector.DebuggerSidebarPanel.GlobalIconStyleClassName = "global-breakpoints-icon";
155
156 WebInspector.DebuggerSidebarPanel.SelectedAllExceptionsCookieKey = "debugger-sidebar-panel-all-exceptions-breakpoint";
157 WebInspector.DebuggerSidebarPanel.SelectedAllUncaughtExceptionsCookieKey = "debugger-sidebar-panel-all-uncaught-exceptions-breakpoint";
158
159 WebInspector.DebuggerSidebarPanel.prototype = {
160     constructor: WebInspector.DebuggerSidebarPanel,
161     __proto__: WebInspector.NavigationSidebarPanel.prototype,
162
163     // Public
164
165     get hasSelectedElement()
166     {
167         return !!this._breakpointsContentTreeOutline.selectedTreeElement
168             || !!this._callStackContentTreeOutline.selectedTreeElement
169             || (this._pauseReasonTreeOutline && !!this._pauseReasonTreeOutline.selectedTreeElement);
170     },
171
172     showDefaultContentView: function()
173     {
174         WebInspector.resourceSidebarPanel.showDefaultContentView();
175     },
176
177     treeElementForRepresentedObject: function(representedObject)
178     {
179         // The main resource is used as the representedObject instead of Frame in our tree.
180         if (representedObject instanceof WebInspector.Frame)
181             representedObject = representedObject.mainResource;
182
183         return this.contentTreeOutline.getCachedTreeElement(representedObject);
184     },
185
186     // Protected
187
188     saveStateToCookie: function(cookie)
189     {
190         console.assert(cookie);
191
192         var selectedTreeElement = this._breakpointsContentTreeOutline.selectedTreeElement;
193         if (!selectedTreeElement)
194             return;
195
196         var representedObject = selectedTreeElement.representedObject;
197
198         if (representedObject === WebInspector.debuggerManager.allExceptionsBreakpoint)
199             cookie[WebInspector.DebuggerSidebarPanel.SelectedAllExceptionsCookieKey] = true;
200
201         if (representedObject === WebInspector.debuggerManager.allUncaughtExceptionsBreakpoint)
202             cookie[WebInspector.DebuggerSidebarPanel.SelectedAllUncaughtExceptionsCookieKey] = true;
203
204         WebInspector.NavigationSidebarPanel.prototype.saveStateToCookie.call(this, cookie);
205     },
206
207     restoreStateFromCookie: function(cookie, relaxedMatchDelay)
208     {
209         console.assert(cookie);
210
211         // Eagerly resolve the special breakpoints; otherwise, use the default behavior.
212         if (cookie[WebInspector.DebuggerSidebarPanel.SelectedAllExceptionsCookieKey])
213             this._allExceptionsBreakpointTreeElement.revealAndSelect();
214         else if (cookie[WebInspector.DebuggerSidebarPanel.SelectedAllUncaughtExceptionsCookieKey])
215             this._allUncaughtExceptionsBreakpointTreeElement.revealAndSelect();
216         else
217             WebInspector.NavigationSidebarPanel.prototype.restoreStateFromCookie.call(this, cookie, relaxedMatchDelay);
218     },
219
220     // Private
221
222     _debuggerPauseResumeButtonClicked: function(event)
223     {
224         if (WebInspector.debuggerManager.paused)
225             WebInspector.debuggerManager.resume();
226         else {
227             this._debuggerPauseResumeButtonItem.enabled = false;
228             WebInspector.debuggerManager.pause();
229         }
230     },
231
232     _debuggerStepOverButtonClicked: function(event)
233     {
234         WebInspector.debuggerManager.stepOver();
235     },
236
237     _debuggerStepIntoButtonClicked: function(event)
238     {
239         WebInspector.debuggerManager.stepInto();
240     },
241
242     _debuggerStepOutButtonClicked: function(event)
243     {
244         WebInspector.debuggerManager.stepOut();
245     },
246
247     _debuggerDidPause: function(event)
248     {
249         this.contentElement.insertBefore(this._callStackSection.element, this.contentElement.firstChild);
250         if (this._updatePauseReason())
251             this.contentElement.insertBefore(this._pauseReasonSection.element, this.contentElement.firstChild);
252
253         this._debuggerPauseResumeButtonItem.enabled = true;
254         this._debuggerPauseResumeButtonItem.toggled = true;
255         this._debuggerStepOverButtonItem.enabled = true;
256         this._debuggerStepIntoButtonItem.enabled = true;
257
258         this.element.classList.add(WebInspector.DebuggerSidebarPanel.DebuggerPausedStyleClassName);
259     },
260
261     _debuggerDidResume: function(event)
262     {
263         this._callStackSection.element.remove();
264         this._pauseReasonSection.element.remove();
265
266         this._debuggerPauseResumeButtonItem.enabled = true;
267         this._debuggerPauseResumeButtonItem.toggled = false;
268         this._debuggerStepOverButtonItem.enabled = false;
269         this._debuggerStepIntoButtonItem.enabled = false;
270         this._debuggerStepOutButtonItem.enabled = false;
271
272         this.element.classList.remove(WebInspector.DebuggerSidebarPanel.DebuggerPausedStyleClassName);
273     },
274
275     _breakpointsEnabledDidChange: function(event)
276     {
277         this._debuggerBreakpointsButtonItem.activated = WebInspector.debuggerManager.breakpointsEnabled;
278     },
279
280     _breakpointsToggleButtonClicked: function(event)
281     {
282         WebInspector.debuggerManager.breakpointsEnabled = !this._debuggerBreakpointsButtonItem.activated;
283     },
284
285     _addBreakpoint: function(breakpoint, sourceCode)
286     {
287         var sourceCode = breakpoint.sourceCodeLocation.displaySourceCode;
288         if (!sourceCode)
289             return null;
290
291         var parentTreeElement = this._addTreeElementForSourceCodeToContentTreeOutline(sourceCode);
292
293         // Mark disabled breakpoints as resolved if there is source code loaded with that URL.
294         // This gives the illusion the breakpoint was resolved, but since we don't send disabled
295         // breakpoints to the backend we don't know for sure. If the user enables the breakpoint
296         // it will be resolved properly.
297         if (breakpoint.disabled)
298             breakpoint.resolved = true;
299
300         var breakpointTreeElement = new WebInspector.BreakpointTreeElement(breakpoint);
301         parentTreeElement.insertChild(breakpointTreeElement, insertionIndexForObjectInListSortedByFunction(breakpointTreeElement, parentTreeElement.children, this._compareBreakpointTreeElements));
302         if (parentTreeElement.children.length === 1)
303             parentTreeElement.expand();
304         return breakpointTreeElement;
305     },
306
307     _addBreakpointsForSourceCode: function(sourceCode)
308     {
309         var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(sourceCode);
310         for (var i = 0; i < breakpoints.length; ++i)
311             this._addBreakpoint(breakpoints[i], sourceCode);
312     },
313
314     _addTreeElementForSourceCodeToContentTreeOutline: function(sourceCode)
315     {
316         var treeElement = this._breakpointsContentTreeOutline.getCachedTreeElement(sourceCode);
317         if (!treeElement) {
318             if (sourceCode instanceof WebInspector.SourceMapResource)
319                 treeElement = new WebInspector.SourceMapResourceTreeElement(sourceCode);
320             else if (sourceCode instanceof WebInspector.Resource)
321                 treeElement = new WebInspector.ResourceTreeElement(sourceCode);
322             else if (sourceCode instanceof WebInspector.Script)
323                 treeElement = new WebInspector.ScriptTreeElement(sourceCode);
324         }
325
326         if (!treeElement.parent) {
327             treeElement.hasChildren = false;
328             treeElement.expand();
329
330             this._breakpointsContentTreeOutline.insertChild(treeElement, insertionIndexForObjectInListSortedByFunction(treeElement, this._breakpointsContentTreeOutline.children, this._compareTopLevelTreeElements.bind(this)));
331         }
332
333         return treeElement;
334     },
335
336     _resourceAdded: function(event)
337     {
338         var resource = event.data.resource;
339
340         if (![WebInspector.Resource.Type.Document, WebInspector.Resource.Type.Script].contains(resource.type))
341             return;
342
343         this._addTreeElementForSourceCodeToContentTreeOutline(resource);
344         this._addBreakpointsForSourceCode(resource);
345     },
346
347     _mainResourceChanged: function(event)
348     {
349         var resource = event.target.mainResource;
350         this._addTreeElementForSourceCodeToContentTreeOutline(resource);
351         this._addBreakpointsForSourceCode(resource);
352     },
353
354     _scriptAdded: function(event)
355     {
356         var script = event.data.script;
357
358         // FIXME: Allow for scripts generated by eval statements to appear, but filter out JSC internals
359         // and other WebInspector internals lacking __WebInspector in the url attribute.
360         if (!script.url)
361             return;
362
363         // Exclude inspector scripts.
364         if (script.url && script.url.indexOf("__WebInspector") === 0)
365             return;
366
367         // Don't add breakpoints if the script is represented by a Resource. They were
368         // already added by _resourceAdded.
369         if (script.resource)
370             return;
371
372         this._addTreeElementForSourceCodeToContentTreeOutline(script);
373         this._addBreakpointsForSourceCode(script);
374     },
375
376     _scriptsCleared: function(event)
377     {
378         for (var i = this._breakpointsContentTreeOutline.children.length - 1; i >= 0; --i) {
379             var treeElement = this._breakpointsContentTreeOutline.children[i];
380             if (!(treeElement instanceof WebInspector.ScriptTreeElement))
381                 continue;
382
383             this._breakpointsContentTreeOutline.removeChildAtIndex(i, true, true);
384         }
385     },
386
387     _breakpointAdded: function(event)
388     {
389         var breakpoint = event.data.breakpoint;
390         this._addBreakpoint(breakpoint);
391     },
392
393     _breakpointRemoved: function(event)
394     {
395         var breakpoint = event.data.breakpoint;
396
397         if (this._pauseReasonTreeOutline) {
398             var pauseReasonBreakpointTreeElement = this._pauseReasonTreeOutline.getCachedTreeElement(breakpoint);
399             if (pauseReasonBreakpointTreeElement)
400                 pauseReasonBreakpointTreeElement.removeStatusImage();
401         }
402
403         var breakpointTreeElement = this._breakpointsContentTreeOutline.getCachedTreeElement(breakpoint);
404         console.assert(breakpointTreeElement);
405         if (!breakpointTreeElement)
406             return;
407
408         this._removeBreakpointTreeElement(breakpointTreeElement);
409     },
410
411     _breakpointDisplayLocationDidChange: function(event)
412     {
413         var breakpoint = event.target;
414         if (event.data.oldDisplaySourceCode === breakpoint.displaySourceCode)
415             return;
416
417         var breakpointTreeElement = this._breakpointsContentTreeOutline.getCachedTreeElement(breakpoint);
418         if (!breakpointTreeElement)
419             return;
420
421         // A known breakpoint moved between resources, remove the old tree element
422         // and create a new tree element with the updated file.
423
424         var wasSelected = breakpointTreeElement.selected;
425
426         this._removeBreakpointTreeElement(breakpointTreeElement);
427         var newBreakpointTreeElement = this._addBreakpoint(breakpoint);
428
429         if (newBreakpointTreeElement && wasSelected)
430             newBreakpointTreeElement.revealAndSelect(true, false, true, true);
431     },
432
433     _removeBreakpointTreeElement: function(breakpointTreeElement)
434     {
435         var parentTreeElement = breakpointTreeElement.parent;
436         parentTreeElement.removeChild(breakpointTreeElement);
437
438         console.assert(parentTreeElement.parent === this._breakpointsContentTreeOutline);
439     },
440
441     _debuggerCallFramesDidChange: function()
442     {
443         this._callStackContentTreeOutline.removeChildren();
444
445         var callFrames = WebInspector.debuggerManager.callFrames;
446         if (!callFrames || !callFrames.length) {
447             this._callStackRow.showEmptyMessage();
448             return;
449         }
450
451         this._callStackRow.hideEmptyMessage();
452         this._callStackRow.element.appendChild(this._callStackContentTreeOutline.element);
453
454         var treeElementToSelect = null;
455
456         var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
457         for (var i = 0; i < callFrames.length; ++i) {
458             var callFrameTreeElement = new WebInspector.CallFrameTreeElement(callFrames[i]);
459             if (callFrames[i] === activeCallFrame)
460                 treeElementToSelect = callFrameTreeElement;
461             this._callStackContentTreeOutline.appendChild(callFrameTreeElement);
462         }
463
464         if (treeElementToSelect)
465             treeElementToSelect.select(true, true);
466     },
467
468     _debuggerActiveCallFrameDidChange: function()
469     {
470         var callFrames = WebInspector.debuggerManager.callFrames;
471         if (!callFrames)
472             return;
473
474         var indexOfActiveCallFrame = callFrames.indexOf(WebInspector.debuggerManager.activeCallFrame);
475         // It is useful to turn off the step out button when there is no call frame to go through
476         // since there might be call frames in the backend that were removed when processing the call
477         // frame payload.
478         this._debuggerStepOutButtonItem.enabled = indexOfActiveCallFrame < callFrames.length - 1;
479     },
480
481     _breakpointsBeneathTreeElement: function(treeElement)
482     {
483         console.assert(treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement);
484         if (!(treeElement instanceof WebInspector.ResourceTreeElement) && !(treeElement instanceof WebInspector.ScriptTreeElement))
485             return [];
486
487         var breakpoints = [];
488         var breakpointTreeElements = treeElement.children;
489         for (var i = 0; i < breakpointTreeElements.length; ++i) {
490             console.assert(breakpointTreeElements[i] instanceof WebInspector.BreakpointTreeElement);
491             console.assert(breakpointTreeElements[i].breakpoint);
492             var breakpoint = breakpointTreeElements[i].breakpoint;
493             if (breakpoint)
494                 breakpoints.push(breakpoint);
495         }
496
497         return breakpoints;
498     },
499
500     _removeAllBreakpoints: function(breakpoints)
501     {
502         for (var i = 0; i < breakpoints.length; ++i) {
503             var breakpoint = breakpoints[i];
504             if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint))
505                 WebInspector.debuggerManager.removeBreakpoint(breakpoint);
506         }
507     },
508
509     _toggleAllBreakpoints: function(breakpoints, disabled)
510     {
511         for (var i = 0; i < breakpoints.length; ++i)
512             breakpoints[i].disabled = disabled;
513     },
514
515     _breakpointTreeOutlineDeleteTreeElement: function(treeElement)
516     {
517         console.assert(treeElement.selected);
518         console.assert(treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement);
519         if (!(treeElement instanceof WebInspector.ResourceTreeElement) && !(treeElement instanceof WebInspector.ScriptTreeElement))
520             return false;
521
522         var wasTopResourceTreeElement = treeElement.previousSibling === this._allUncaughtExceptionsBreakpointTreeElement;
523         var nextSibling = treeElement.nextSibling;
524
525         var breakpoints = this._breakpointsBeneathTreeElement(treeElement);
526         this._removeAllBreakpoints(breakpoints);
527
528         if (wasTopResourceTreeElement && nextSibling)
529             nextSibling.select(true, true);
530
531         return true;
532     },
533
534     _breakpointTreeOutlineContextMenuTreeElement: function(event, treeElement)
535     {
536         console.assert(treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement || treeElement.constructor === WebInspector.FolderTreeElement);
537         if (!(treeElement instanceof WebInspector.ResourceTreeElement) && !(treeElement instanceof WebInspector.ScriptTreeElement))
538             return;
539
540         var breakpoints = this._breakpointsBeneathTreeElement(treeElement);
541         var shouldDisable = false;
542         for (var i = 0; i < breakpoints.length; ++i) {
543             if (!breakpoints[i].disabled) {
544                 shouldDisable = true;
545                 break;
546             }
547         }
548
549         function removeAllResourceBreakpoints()
550         {
551             this._removeAllBreakpoints(breakpoints);
552         }
553
554         function toggleAllResourceBreakpoints()
555         {
556             this._toggleAllBreakpoints(breakpoints, shouldDisable);
557         }
558
559         var contextMenu = new WebInspector.ContextMenu(event);
560         if (shouldDisable)
561             contextMenu.appendItem(WebInspector.UIString("Disable Breakpoints"), toggleAllResourceBreakpoints.bind(this));
562         else
563             contextMenu.appendItem(WebInspector.UIString("Enable Breakpoints"), toggleAllResourceBreakpoints.bind(this));
564         contextMenu.appendItem(WebInspector.UIString("Delete Breakpoints"), removeAllResourceBreakpoints.bind(this));
565         contextMenu.show();
566     },
567
568     _treeElementSelected: function(treeElement, selectedByUser)
569     {
570         function deselectCallStackContentTreeElements()
571         {
572             var selectedTreeElement = this._callStackContentTreeOutline.selectedTreeElement;
573             if (selectedTreeElement)
574                 selectedTreeElement.deselect();
575         }
576
577         function deselectBreakpointContentTreeElements()
578         {
579             var selectedTreeElement = this._breakpointsContentTreeOutline.selectedTreeElement;
580             if (selectedTreeElement)
581                 selectedTreeElement.deselect();
582         }
583
584         function deselectPauseReasonContentTreeElements()
585         {
586             if (!this._pauseReasonTreeOutline)
587                 return;
588
589             var selectedTreeElement = this._pauseReasonTreeOutline.selectedTreeElement;
590             if (selectedTreeElement)
591                 selectedTreeElement.deselect();
592         }
593
594         if (treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement) {
595             deselectCallStackContentTreeElements.call(this);
596             deselectPauseReasonContentTreeElements.call(this);
597             WebInspector.resourceSidebarPanel.showSourceCode(treeElement.representedObject);
598             return;
599         }
600
601         if (treeElement instanceof WebInspector.CallFrameTreeElement) {
602             // Deselect any tree element in the breakpoint / pause reason content tree outlines to prevent two selections in the sidebar.
603             deselectBreakpointContentTreeElements.call(this);
604             deselectPauseReasonContentTreeElements.call(this);
605
606             var callFrame = treeElement.callFrame;
607             WebInspector.debuggerManager.activeCallFrame = callFrame;
608             WebInspector.resourceSidebarPanel.showSourceCodeLocation(callFrame.sourceCodeLocation);
609             return;
610         }
611
612         if (!(treeElement instanceof WebInspector.BreakpointTreeElement) || treeElement.parent.constructor === WebInspector.FolderTreeElement)
613             return;
614
615         // Deselect any other tree elements to prevent two selections in the sidebar.
616         deselectCallStackContentTreeElements.call(this);
617
618         if (treeElement.treeOutline === this._pauseReasonTreeOutline)
619             deselectBreakpointContentTreeElements.call(this);
620         else
621             deselectPauseReasonContentTreeElements.call(this);
622
623         var breakpoint = treeElement.breakpoint;
624         if (treeElement.treeOutline === this._pauseReasonTreeOutline) {
625             WebInspector.resourceSidebarPanel.showSourceCodeLocation(breakpoint.sourceCodeLocation);
626             return;
627         }
628
629         if (!treeElement.parent.representedObject)
630             return;
631
632         console.assert(treeElement.parent.representedObject instanceof WebInspector.SourceCode);
633         if (!(treeElement.parent.representedObject instanceof WebInspector.SourceCode))
634             return;
635
636         WebInspector.resourceSidebarPanel.showSourceCodeLocation(breakpoint.sourceCodeLocation);
637     },
638
639     _compareTopLevelTreeElements: function(a, b)
640     {
641         if (a === this._globalBreakpointsFolderTreeElement)
642             return -1;
643         if (b === this._globalBreakpointsFolderTreeElement)
644             return 1;
645
646         return a.mainTitle.localeCompare(b.mainTitle);
647     },
648
649     _compareBreakpointTreeElements: function(a, b)
650     {
651         var aLocation = a.breakpoint.sourceCodeLocation;
652         var bLocation = b.breakpoint.sourceCodeLocation;
653
654         var comparisonResult = aLocation.displayLineNumber - bLocation.displayLineNumber;
655         if (comparisonResult !== 0)
656             return comparisonResult;
657
658         return aLocation.displayColumnNumber - bLocation.displayColumnNumber;
659     },
660
661     _updatePauseReason: function()
662     {
663         this._pauseReasonTreeOutline = null;
664
665         this._updatePauseReasonGotoArrow();
666         return this._updatePauseReasonSection();
667     },
668
669     _updatePauseReasonSection: function()
670     {
671         var pauseData = WebInspector.debuggerManager.pauseData;
672
673         switch (WebInspector.debuggerManager.pauseReason) {
674         case WebInspector.DebuggerManager.PauseReason.Assertion:
675             // FIXME: We should include the assertion condition string.
676             console.assert(pauseData, "Expected data with an assertion, but found none.");
677             if (pauseData && pauseData.message) {
678                 this._pauseReasonTextRow.text = WebInspector.UIString("Assertion with message: %s").format(pauseData.message);
679                 return true;
680             }
681
682             this._pauseReasonTextRow.text = WebInspector.UIString("Assertion Failed");
683             this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
684             return true;
685
686         case WebInspector.DebuggerManager.PauseReason.Breakpoint:
687             console.assert(pauseData, "Expected breakpoint identifier, but found none.");
688             if (pauseData && pauseData.breakpointId) {
689                 var breakpoint = WebInspector.debuggerManager.breakpointForIdentifier(pauseData.breakpointId);
690                 var breakpointTreeOutline = this.createContentTreeOutline(true, true);
691                 breakpointTreeOutline.onselect = this._treeElementSelected.bind(this);
692                 var breakpointTreeElement = new WebInspector.BreakpointTreeElement(breakpoint, WebInspector.DebuggerSidebarPanel.PausedBreakpointIconStyleClassName, WebInspector.UIString("Triggered Breakpoint"));
693                 var breakpointDetailsSection = new WebInspector.DetailsSectionRow;
694                 breakpointTreeOutline.appendChild(breakpointTreeElement);
695                 breakpointDetailsSection.element.appendChild(breakpointTreeOutline.element);
696
697                 this._pauseReasonGroup.rows = [breakpointDetailsSection];
698                 this._pauseReasonTreeOutline = breakpointTreeOutline;
699                 return true;
700             }
701             break;
702
703         case WebInspector.DebuggerManager.PauseReason.CSPViolation:
704             console.assert(pauseData, "Expected data with a CSP Violation, but found none.");
705             if (pauseData) {
706                 // COMPATIBILITY (iOS 8): 'directive' was 'directiveText'.
707                 this._pauseReasonTextRow.text = WebInspector.UIString("Content Security Policy violation of directive: %s").format(pauseData.directive || pauseData.directiveText);
708                 this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
709                 return true;
710             }
711             break;
712
713         case WebInspector.DebuggerManager.PauseReason.DebuggerStatement:
714             this._pauseReasonTextRow.text = WebInspector.UIString("Debugger Statement");
715             this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
716             return true;
717
718         case WebInspector.DebuggerManager.PauseReason.Exception:
719             console.assert(pauseData, "Expected data with an exception, but found none.");
720             if (pauseData) {
721                 // FIXME: We should improve the appearance of thrown objects. This works well for exception strings.
722                 var data = WebInspector.RemoteObject.fromPayload(pauseData);
723                 this._pauseReasonTextRow.text = WebInspector.UIString("Exception with thrown value: %s").format(data.description);
724                 this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
725                 return true;
726             }
727             break;
728
729         case WebInspector.DebuggerManager.PauseReason.PauseOnNextStatement:
730             this._pauseReasonTextRow.text = WebInspector.UIString("Immediate Pause Requested");
731             this._pauseReasonGroup.rows = [this._pauseReasonTextRow];
732             return true;
733
734         case WebInspector.DebuggerManager.PauseReason.Other:
735             console.error("Paused for unknown reason. We should always have a reason.");
736             break;
737         }
738
739         return false;
740     },
741
742     _updatePauseReasonGotoArrow: function()
743     {
744         this._pauseReasonLinkContainerElement.removeChildren();
745
746         var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
747         if (!activeCallFrame)
748             return;
749
750         var sourceCodeLocation = activeCallFrame.sourceCodeLocation;
751         if (!sourceCodeLocation)
752             return;
753
754         var linkElement = WebInspector.createSourceCodeLocationLink(sourceCodeLocation, false, true);
755         this._pauseReasonLinkContainerElement.appendChild(linkElement);
756     }
757 };