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