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