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