Inspector does not restore breakpoints after a page reload
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Controllers / DebuggerManager.js
1 /*
2  * Copyright (C) 2013, 2014 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.DebuggerManager = function()
27 {
28     WebInspector.Object.call(this);
29
30     if (window.DebuggerAgent)
31         DebuggerAgent.enable();
32
33     WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisplayLocationDidChange, this._breakpointDisplayLocationDidChange, this);
34     WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._breakpointDisabledStateDidChange, this);
35     WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ConditionDidChange, this._breakpointEditablePropertyDidChange, this);
36     WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._breakpointEditablePropertyDidChange, this);
37     WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ActionsDidChange, this._breakpointEditablePropertyDidChange, this);
38
39     window.addEventListener("pagehide", this._inspectorClosing.bind(this));
40
41     this._allExceptionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-all-exceptions", false);
42     this._allUncaughtExceptionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-all-uncaught-exceptions", false);
43
44     var specialBreakpointLocation = new WebInspector.SourceCodeLocation(null, Infinity, Infinity);
45
46     this._allExceptionsBreakpoint = new WebInspector.Breakpoint(specialBreakpointLocation, !this._allExceptionsBreakpointEnabledSetting.value);
47     this._allExceptionsBreakpoint.resolved = true;
48
49     this._allUncaughtExceptionsBreakpoint = new WebInspector.Breakpoint(specialBreakpointLocation, !this._allUncaughtExceptionsBreakpointEnabledSetting.value);
50
51     this._breakpoints = [];
52     this._breakpointURLMap = {};
53     this._breakpointScriptIdentifierMap = {};
54     this._breakpointIdMap = {};
55
56     this._nextBreakpointActionIdentifier = 1;
57
58     this._scriptIdMap = {};
59     this._scriptURLMap = {};
60
61     this._breakpointsSetting = new WebInspector.Setting("breakpoints", []);
62     this._breakpointsEnabledSetting = new WebInspector.Setting("breakpoints-enabled", true);
63
64     if (window.DebuggerAgent)
65         DebuggerAgent.setBreakpointsActive(this._breakpointsEnabledSetting.value);
66
67     this._updateBreakOnExceptionsState();
68
69     function restoreBreakpointsSoon() {
70         for (cookie of this._breakpointsSetting.value)
71             this.addBreakpoint(new WebInspector.Breakpoint(cookie));
72     }
73
74     // Ensure that all managers learn about restored breakpoints,
75     // regardless of their initialization order.
76     setTimeout(restoreBreakpointsSoon.bind(this), 0);
77 };
78
79 WebInspector.DebuggerManager.Event = {
80     BreakpointAdded: "debugger-manager-breakpoint-added",
81     BreakpointRemoved: "debugger-manager-breakpoint-removed",
82     BreakpointMoved: "debugger-manager-breakpoint-moved",
83     Paused: "debugger-manager-paused",
84     Resumed: "debugger-manager-resumed",
85     CallFramesDidChange: "debugger-manager-call-frames-did-change",
86     ActiveCallFrameDidChange: "debugger-manager-active-call-frame-did-change",
87     ScriptAdded: "debugger-manager-script-added",
88     ScriptsCleared: "debugger-manager-scripts-cleared",
89     BreakpointsEnabledDidChange: "debugger-manager-breakpoints-enabled-did-change"
90 };
91
92 WebInspector.DebuggerManager.prototype = {
93     constructor: WebInspector.DebuggerManager,
94
95     // Public
96
97     get breakpointsEnabled()
98     {
99         return this._breakpointsEnabledSetting.value;
100     },
101
102     set breakpointsEnabled(enabled)
103     {
104         if (this._breakpointsEnabled === enabled)
105             return;
106
107         this._breakpointsEnabledSetting.value = enabled;
108
109         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange);
110
111         this._allExceptionsBreakpoint.dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange);
112         this._allUncaughtExceptionsBreakpoint.dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange);
113
114         for (var i = 0; i < this._breakpoints.length; ++i)
115             this._breakpoints[i].dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange);
116
117         DebuggerAgent.setBreakpointsActive(enabled);
118
119         this._updateBreakOnExceptionsState();
120     },
121
122     get paused()
123     {
124         return this._paused;
125     },
126
127     get callFrames()
128     {
129         return this._callFrames;
130     },
131
132     get activeCallFrame()
133     {
134         return this._activeCallFrame;
135     },
136
137     set activeCallFrame(callFrame)
138     {
139         if (callFrame === this._activeCallFrame)
140             return;
141
142         this._activeCallFrame = callFrame || null;
143
144         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
145     },
146
147     pause: function()
148     {
149         DebuggerAgent.pause();
150     },
151
152     resume: function()
153     {
154         DebuggerAgent.resume();
155     },
156
157     stepOver: function()
158     {
159         DebuggerAgent.stepOver();
160     },
161
162     stepInto: function()
163     {
164         DebuggerAgent.stepInto();
165     },
166
167     stepOut: function()
168     {
169         DebuggerAgent.stepOut();
170     },
171
172     get allExceptionsBreakpoint()
173     {
174         return this._allExceptionsBreakpoint;
175     },
176
177     get allUncaughtExceptionsBreakpoint()
178     {
179         return this._allUncaughtExceptionsBreakpoint;
180     },
181
182     get breakpoints()
183     {
184         return this._breakpoints;
185     },
186
187     breakpointsForSourceCode: function(sourceCode)
188     {
189         console.assert(sourceCode instanceof WebInspector.Resource || sourceCode instanceof WebInspector.Script);
190
191         if (sourceCode instanceof WebInspector.SourceMapResource) {
192             var mappedResourceBreakpoints = [];
193             var originalSourceCodeBreakpoints = this.breakpointsForSourceCode(sourceCode.sourceMap.originalSourceCode);
194             return originalSourceCodeBreakpoints.filter(function(breakpoint) {
195                 return breakpoint.sourceCodeLocation.displaySourceCode === sourceCode;
196             });
197         }
198
199         if (sourceCode.url in this._breakpointURLMap) {
200             var urlBreakpoint = this._breakpointURLMap[sourceCode.url] || [];
201             this._associateBreakpointsWithSourceCode(urlBreakpoint, sourceCode);
202             return urlBreakpoint;
203         }
204
205         if (sourceCode instanceof WebInspector.Script && sourceCode.id in this._breakpointScriptIdentifierMap) {
206             var scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap[sourceCode.id] || [];
207             this._associateBreakpointsWithSourceCode(scriptIdentifierBreakpoints, sourceCode);
208             return scriptIdentifierBreakpoints;
209         }
210
211         return [];
212     },
213
214     scriptForIdentifier: function(id)
215     {
216         return this._scriptIdMap[id] || null;
217     },
218
219     scriptsForURL: function(url)
220     {
221         // FIXME: This may not be safe. A Resource's URL may differ from a Script's URL.
222         return this._scriptURLMap[url] || [];
223     },
224
225     continueToLocation: function(scriptIdentifier, lineNumber, columnNumber)
226     {
227         DebuggerAgent.continueToLocation({scriptId: scriptIdentifier, lineNumber: lineNumber, columnNumber: columnNumber});
228     },
229
230     addBreakpoint: function(breakpoint, skipEventDispatch)
231     {
232         console.assert(breakpoint instanceof WebInspector.Breakpoint, "Bad argument to DebuggerManger.addBreakpoint: ", breakpoint);
233         if (!breakpoint)
234             return;
235
236         if (breakpoint.url) {
237             var urlBreakpoints = this._breakpointURLMap[breakpoint.url];
238             if (!urlBreakpoints)
239                 urlBreakpoints = this._breakpointURLMap[breakpoint.url] = [];
240             urlBreakpoints.push(breakpoint);
241         }
242
243         if (breakpoint.scriptIdentifier) {
244             var scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap[breakpoint.scriptIdentifier];
245             if (!scriptIdentifierBreakpoints)
246                 scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap[breakpoint.scriptIdentifier] = [];
247             scriptIdentifierBreakpoints.push(breakpoint);
248         }
249
250         this._breakpoints.push(breakpoint);
251
252         if (!breakpoint.disabled)
253             this._setBreakpoint(breakpoint);
254
255         if (!skipEventDispatch)
256             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointAdded, {breakpoint: breakpoint});
257     },
258
259     removeBreakpoint: function(breakpoint)
260     {
261         console.assert(breakpoint);
262         if (!breakpoint)
263             return;
264
265         console.assert(this.isBreakpointRemovable(breakpoint));
266         if (!this.isBreakpointRemovable(breakpoint))
267             return;
268
269         this._breakpoints.remove(breakpoint);
270
271         if (breakpoint.identifier)
272             this._removeBreakpoint(breakpoint);
273
274         if (breakpoint.url) {
275             var urlBreakpoints = this._breakpointURLMap[breakpoint.url];
276             if (urlBreakpoints) {
277                 urlBreakpoints.remove(breakpoint);
278                 if (!urlBreakpoints.length)
279                     delete this._breakpointURLMap[breakpoint.url];
280             }
281         }
282
283         if (breakpoint.scriptIdentifier) {
284             var scriptIdentifierBreakpoints = this._breakpointScriptIdentifierMap[breakpoint.scriptIdentifier];
285             if (scriptIdentifierBreakpoints) {
286                 scriptIdentifierBreakpoints.remove(breakpoint);
287                 if (!scriptIdentifierBreakpoints.length)
288                     delete this._breakpointScriptIdentifierMap[breakpoint.scriptIdentifier];
289             }
290         }
291
292         // Disable the breakpoint first, so removing actions doesn't re-add the breakpoint.
293         breakpoint.disabled = true;
294         breakpoint.clearActions();
295
296         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointRemoved, {breakpoint: breakpoint});
297     },
298
299     breakpointResolved: function(breakpointIdentifier, location)
300     {
301         // Called from WebInspector.DebuggerObserver.
302
303         var breakpoint = this._breakpointIdMap[breakpointIdentifier];
304         console.assert(breakpoint);
305         if (!breakpoint)
306             return;
307
308         console.assert(breakpoint.identifier === breakpointIdentifier);
309
310         breakpoint.resolved = true;
311     },
312
313     reset: function()
314     {
315         // Called from WebInspector.DebuggerObserver.
316
317         var wasPaused = this._paused;
318
319         WebInspector.Script.resetUniqueDisplayNameNumbers();
320
321         this._paused = false;
322         this._scriptIdMap = {};
323         this._scriptURLMap = {};
324
325         this._ignoreBreakpointDisplayLocationDidChangeEvent = true;
326
327         // Mark all the breakpoints as unresolved. They will be reported as resolved when
328         // breakpointResolved is called as the page loads.
329         for (var i = 0; i < this._breakpoints.length; ++i) {
330             var breakpoint = this._breakpoints[i];
331             breakpoint.resolved = false;
332             if (breakpoint.sourceCodeLocation.sourceCode)
333                 breakpoint.sourceCodeLocation.sourceCode = null;
334         }
335
336         delete this._ignoreBreakpointDisplayLocationDidChangeEvent;
337
338         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ScriptsCleared);
339
340         if (wasPaused)
341             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
342     },
343
344     debuggerDidPause: function(callFramesPayload)
345     {
346         // Called from WebInspector.DebuggerObserver.
347
348         if (this._delayedResumeTimeout) {
349             clearTimeout(this._delayedResumeTimeout);
350             delete this._delayedResumeTimeout;
351         }
352
353         var wasStillPaused = this._paused;
354
355         this._paused = true;
356         this._callFrames = [];
357
358         for (var i = 0; i < callFramesPayload.length; ++i) {
359             var callFramePayload = callFramesPayload[i];
360             var sourceCodeLocation = this._sourceCodeLocationFromPayload(callFramePayload.location);
361             // Exclude the case where the call frame is in the inspector code.
362             if (!sourceCodeLocation || !sourceCodeLocation._sourceCode || !sourceCodeLocation._sourceCode._url || sourceCodeLocation._sourceCode._url.indexOf("__WebInspector") === 0)
363                 continue;
364             var thisObject = WebInspector.RemoteObject.fromPayload(callFramePayload.this);
365             var scopeChain = this._scopeChainFromPayload(callFramePayload.scopeChain);
366             var callFrame = new WebInspector.CallFrame(callFramePayload.callFrameId, sourceCodeLocation, callFramePayload.functionName, thisObject, scopeChain);
367             this._callFrames.push(callFrame);
368         }
369
370         if (!this._callFrames.length) {
371             this.resume();
372             return;
373         }
374
375         this._activeCallFrame = this._callFrames[0];
376
377         if (!wasStillPaused)
378             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Paused);
379         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange);
380         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
381     },
382
383     debuggerDidResume: function()
384     {
385         // Called from WebInspector.DebuggerObserver.
386
387         function delayedWork()
388         {
389             delete this._delayedResumeTimeout;
390
391             this._paused = false;
392             this._callFrames = null;
393             this._activeCallFrame = null;
394
395             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
396             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange);
397             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
398         }
399
400         // We delay clearing the state and firing events so the user interface does not flash
401         // between brief steps or successive breakpoints.
402         this._delayedResumeTimeout = setTimeout(delayedWork.bind(this), 50);
403     },
404
405     playBreakpointActionSound: function(breakpointActionIdentifier)
406     {
407         InspectorFrontendHost.beep();
408     },
409
410     scriptDidParse: function(scriptIdentifier, url, isContentScript, startLine, startColumn, endLine, endColumn, sourceMapURL)
411     {
412         // Don't add the script again if it is already known.
413         if (this._scriptIdMap[scriptIdentifier]) {
414             console.assert(this._scriptIdMap[scriptIdentifier].url === (url || null));
415             console.assert(this._scriptIdMap[scriptIdentifier].range.startLine === startLine);
416             console.assert(this._scriptIdMap[scriptIdentifier].range.startColumn === startColumn);
417             console.assert(this._scriptIdMap[scriptIdentifier].range.endLine === endLine);
418             console.assert(this._scriptIdMap[scriptIdentifier].range.endColumn === endColumn);
419             return;
420         }
421
422         var script = new WebInspector.Script(scriptIdentifier, new WebInspector.TextRange(startLine, startColumn, endLine, endColumn), url, isContentScript, sourceMapURL);
423
424         this._scriptIdMap[scriptIdentifier] = script;
425
426         if (script.url) {
427             var scripts = this._scriptURLMap[script.url];
428             if (!scripts)
429                 scripts = this._scriptURLMap[script.url] = [];
430             scripts.push(script);
431         }
432
433         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ScriptAdded, {script: script});
434     },
435
436     isBreakpointRemovable: function(breakpoint)
437     {
438         return breakpoint !== this._allExceptionsBreakpoint && breakpoint !== this._allUncaughtExceptionsBreakpoint;
439     },
440
441     isBreakpointEditable: function(breakpoint)
442     {
443         return this.isBreakpointRemovable(breakpoint);
444     },
445
446     get nextBreakpointActionIdentifier()
447     {
448         return this._nextBreakpointActionIdentifier++;
449     },
450
451     // Private
452
453     _sourceCodeLocationFromPayload: function(payload)
454     {
455         var script = this._scriptIdMap[payload.scriptId];
456         console.assert(script);
457         if (!script)
458             return null;
459
460         return script.createSourceCodeLocation(payload.lineNumber, payload.columnNumber);
461     },
462
463     _scopeChainFromPayload: function(payload)
464     {
465         var scopeChain = [];
466         for (var i = 0; i < payload.length; ++i)
467             scopeChain.push(this._scopeChainNodeFromPayload(payload[i]));
468         return scopeChain;
469     },
470
471     _scopeChainNodeFromPayload: function(payload)
472     {
473         var type = null;
474         switch (payload.type) {
475         case "local":
476             type = WebInspector.ScopeChainNode.Type.Local;
477             break;
478         case "global":
479             type = WebInspector.ScopeChainNode.Type.Global;
480             break;
481         case "with":
482             type = WebInspector.ScopeChainNode.Type.With;
483             break;
484         case "closure":
485             type = WebInspector.ScopeChainNode.Type.Closure;
486             break;
487         case "catch":
488             type = WebInspector.ScopeChainNode.Type.Catch;
489             break;
490         default:
491             console.error("Unknown type: " + payload.type);
492         }
493
494         var object = WebInspector.RemoteObject.fromPayload(payload.object);
495         return new WebInspector.ScopeChainNode(type, object);
496     },
497
498     _debuggerBreakpointActionType: function(type)
499     {
500         switch (type) {
501         case WebInspector.BreakpointAction.Type.Log:
502             return DebuggerAgent.BreakpointActionType.Log;
503         case WebInspector.BreakpointAction.Type.Evaluate:
504             return DebuggerAgent.BreakpointActionType.Evaluate;
505         case WebInspector.BreakpointAction.Type.Sound:
506             return DebuggerAgent.BreakpointActionType.Sound;
507         case WebInspector.BreakpointAction.Type.Probe:
508             return DebuggerAgent.BreakpointActionType.Probe;
509         default:
510             console.assert(false);
511             return DebuggerAgent.BreakpointActionType.Log;
512         }
513     },
514
515     _setBreakpoint: function(breakpoint, callback)
516     {
517         console.assert(!breakpoint.identifier);
518         console.assert(!breakpoint.disabled);
519
520         if (breakpoint.identifier || breakpoint.disabled)
521             return;
522
523         // Enable breakpoints since a breakpoint is being set. This eliminates
524         // a multi-step process for the user that can be confusing.
525         this.breakpointsEnabled = true;
526
527         function didSetBreakpoint(error, breakpointIdentifier)
528         {
529             if (error)
530                 return;
531
532             this._breakpointIdMap[breakpointIdentifier] = breakpoint;
533
534             breakpoint.identifier = breakpointIdentifier;
535             breakpoint.resolved = true;
536
537             if (typeof callback === "function")
538                 callback();
539         }
540
541         // The breakpoint will be resolved again by calling DebuggerAgent, so mark it as unresolved.
542         // If something goes wrong it will stay unresolved and show up as such in the user interface.
543         breakpoint.resolved = false;
544
545         // Convert BreakpointAction types to DebuggerAgent protocol types.
546         // NOTE: Breakpoint.options returns new objects each time, so it is safe to modify.
547         var options;
548         if (DebuggerAgent.BreakpointActionType) {
549             options = breakpoint.options;
550             if (options.actions.length) {
551                 for (var i = 0; i < options.actions.length; ++i)
552                     options.actions[i].type = this._debuggerBreakpointActionType(options.actions[i].type);
553             }
554         }
555
556         // COMPATIBILITY (iOS 7): iOS 7 and earlier, DebuggerAgent.setBreakpoint* took a "condition" string argument.
557         // This has been replaced with an "options" BreakpointOptions object.
558         if (breakpoint.url) {
559             DebuggerAgent.setBreakpointByUrl.invoke({
560                 lineNumber: breakpoint.sourceCodeLocation.lineNumber,
561                 url: breakpoint.url,
562                 urlRegex: undefined,
563                 columnNumber: breakpoint.sourceCodeLocation.columnNumber,
564                 condition: breakpoint.condition,
565                 options: options
566             }, didSetBreakpoint.bind(this));
567         } else if (breakpoint.scriptIdentifier) {
568             DebuggerAgent.setBreakpoint.invoke({
569                 location: {scriptId: breakpoint.scriptIdentifier, lineNumber: breakpoint.sourceCodeLocation.lineNumber, columnNumber: breakpoint.sourceCodeLocation.columnNumber},
570                 condition: breakpoint.condition,
571                 options: options
572             }, didSetBreakpoint.bind(this));
573         }
574     },
575
576     _removeBreakpoint: function(breakpoint, callback)
577     {
578         if (!breakpoint.identifier)
579             return;
580
581         function didRemoveBreakpoint(error)
582         {
583             if (error)
584                 console.error(error);
585
586             delete this._breakpointIdMap[breakpoint.identifier];
587
588             breakpoint.identifier = null;
589
590             // Don't reset resolved here since we want to keep disabled breakpoints looking like they
591             // are resolved in the user interface. They will get marked as unresolved in reset.
592
593             if (typeof callback === "function")
594                 callback();
595         }
596
597         DebuggerAgent.removeBreakpoint(breakpoint.identifier, didRemoveBreakpoint.bind(this));
598     },
599
600     _breakpointDisplayLocationDidChange: function(event)
601     {
602         if (this._ignoreBreakpointDisplayLocationDidChangeEvent)
603             return;
604
605         var breakpoint = event.target;
606         if (!breakpoint.identifier || breakpoint.disabled)
607             return;
608
609         // Remove the breakpoint with its old id.
610         this._removeBreakpoint(breakpoint, breakpointRemoved.bind(this));
611
612         function breakpointRemoved()
613         {
614             // Add the breakpoint at its new lineNumber and get a new id.
615             this._setBreakpoint(breakpoint);
616
617             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointMoved, {breakpoint: breakpoint});
618         }
619     },
620
621     _breakpointDisabledStateDidChange: function(event)
622     {
623         var breakpoint = event.target;
624
625         if (breakpoint === this._allExceptionsBreakpoint) {
626             if (!breakpoint.disabled)
627                 this.breakpointsEnabled = true;
628             this._allExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
629             this._updateBreakOnExceptionsState();
630             return;
631         }
632
633         if (breakpoint === this._allUncaughtExceptionsBreakpoint) {
634             if (!breakpoint.disabled)
635                 this.breakpointsEnabled = true;
636             this._allUncaughtExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
637             this._updateBreakOnExceptionsState();
638             return;
639         }
640
641         if (breakpoint.disabled)
642             this._removeBreakpoint(breakpoint);
643         else
644             this._setBreakpoint(breakpoint);
645     },
646
647     _breakpointEditablePropertyDidChange: function(event)
648     {
649         var breakpoint = event.target;
650         if (breakpoint.disabled)
651             return;
652
653         console.assert(this.isBreakpointEditable(breakpoint));
654         if (!this.isBreakpointEditable(breakpoint))
655             return;
656
657         // Remove the breakpoint with its old id.
658         this._removeBreakpoint(breakpoint, breakpointRemoved.bind(this));
659
660         function breakpointRemoved()
661         {
662             // Add the breakpoint with its new condition and get a new id.
663             this._setBreakpoint(breakpoint);
664         }
665     },
666
667     _updateBreakOnExceptionsState: function()
668     {
669         var state = "none";
670
671         if (this._breakpointsEnabledSetting.value) {
672             if (!this._allExceptionsBreakpoint.disabled)
673                 state = "all";
674             else if (!this._allUncaughtExceptionsBreakpoint.disabled)
675                 state = "uncaught";
676         }
677
678         switch (state) {
679         case "all":
680             // Mark the uncaught breakpoint as unresolved since "all" includes "uncaught".
681             // That way it is clear in the user interface that the breakpoint is ignored.
682             this._allUncaughtExceptionsBreakpoint.resolved = false;
683             break;
684         case "uncaught":
685         case "none":
686             // Mark the uncaught breakpoint as resolved again.
687             this._allUncaughtExceptionsBreakpoint.resolved = true;
688             break;
689         }
690
691         DebuggerAgent.setPauseOnExceptions(state);
692     },
693
694     _inspectorClosing: function(event)
695     {
696         this._saveBreakpoints();
697     },
698
699     _saveBreakpoints: function()
700     {
701         var savedBreakpoints = [];
702
703         for (var i = 0; i < this._breakpoints.length; ++i) {
704             var breakpoint = this._breakpoints[i];
705
706             // Only breakpoints with URLs can be saved. Breakpoints for transient scripts can't.
707             if (!breakpoint.url)
708                 continue;
709
710             savedBreakpoints.push(breakpoint.info);
711         }
712
713         this._breakpointsSetting.value = savedBreakpoints;
714     },
715
716     _associateBreakpointsWithSourceCode: function(breakpoints, sourceCode)
717     {
718         this._ignoreBreakpointDisplayLocationDidChangeEvent = true;
719
720         for (var i = 0; i < breakpoints.length; ++i) {
721             var breakpoint = breakpoints[i];
722             if (breakpoint.sourceCodeLocation.sourceCode === null)
723                 breakpoint.sourceCodeLocation.sourceCode = sourceCode;
724             // SourceCodes can be unequal if the SourceCodeLocation is associated with a Script and we are looking at the Resource.
725             console.assert(breakpoint.sourceCodeLocation.sourceCode === sourceCode || breakpoint.sourceCodeLocation.sourceCode.url === sourceCode.url);
726         }
727
728         delete this._ignoreBreakpointDisplayLocationDidChangeEvent;
729     }
730 };
731
732 WebInspector.DebuggerManager.prototype.__proto__ = WebInspector.Object.prototype;