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