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