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