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