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