Web Inspector: Scope chain shows too many scopes for functions (`let` and `var` in...
[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         // Map this to "Closure" type. The frontend can recalculate this when needed.
627         case DebuggerAgent.ScopeType.Local:
628             type = WebInspector.ScopeChainNode.Type.Closure;
629             break;
630
631         default:
632             console.error("Unknown type: " + payload.type);
633         }
634
635         var object = WebInspector.RemoteObject.fromPayload(payload.object);
636         return new WebInspector.ScopeChainNode(type, [object]);
637     }
638
639     _pauseReasonFromPayload(payload)
640     {
641         // FIXME: Handle other backend pause reasons.
642         switch (payload) {
643         case DebuggerAgent.PausedReason.Assert:
644             return WebInspector.DebuggerManager.PauseReason.Assertion;
645         case DebuggerAgent.PausedReason.Breakpoint:
646             return WebInspector.DebuggerManager.PauseReason.Breakpoint;
647         case DebuggerAgent.PausedReason.CSPViolation:
648             return WebInspector.DebuggerManager.PauseReason.CSPViolation;
649         case DebuggerAgent.PausedReason.DebuggerStatement:
650             return WebInspector.DebuggerManager.PauseReason.DebuggerStatement;
651         case DebuggerAgent.PausedReason.Exception:
652             return WebInspector.DebuggerManager.PauseReason.Exception;
653         case DebuggerAgent.PausedReason.PauseOnNextStatement:
654             return WebInspector.DebuggerManager.PauseReason.PauseOnNextStatement;
655         default:
656             return WebInspector.DebuggerManager.PauseReason.Other;
657         }
658     }
659
660     _debuggerBreakpointActionType(type)
661     {
662         switch (type) {
663         case WebInspector.BreakpointAction.Type.Log:
664             return DebuggerAgent.BreakpointActionType.Log;
665         case WebInspector.BreakpointAction.Type.Evaluate:
666             return DebuggerAgent.BreakpointActionType.Evaluate;
667         case WebInspector.BreakpointAction.Type.Sound:
668             return DebuggerAgent.BreakpointActionType.Sound;
669         case WebInspector.BreakpointAction.Type.Probe:
670             return DebuggerAgent.BreakpointActionType.Probe;
671         default:
672             console.assert(false);
673             return DebuggerAgent.BreakpointActionType.Log;
674         }
675     }
676
677     _setBreakpoint(breakpoint, callback)
678     {
679         console.assert(!breakpoint.identifier);
680         console.assert(!breakpoint.disabled);
681
682         if (breakpoint.identifier || breakpoint.disabled)
683             return;
684
685         if (!this._restoringBreakpoints) {
686             // Enable breakpoints since a breakpoint is being set. This eliminates
687             // a multi-step process for the user that can be confusing.
688             this.breakpointsEnabled = true;
689         }
690
691         function didSetBreakpoint(error, breakpointIdentifier, locations)
692         {
693             if (error)
694                 return;
695
696             this._breakpointIdMap.set(breakpointIdentifier, breakpoint);
697
698             breakpoint.identifier = breakpointIdentifier;
699
700             // Debugger.setBreakpoint returns a single location.
701             if (!(locations instanceof Array))
702                 locations = [locations];
703
704             for (var location of locations)
705                 this.breakpointResolved(breakpointIdentifier, location);
706
707             if (typeof callback === "function")
708                 callback();
709         }
710
711         // The breakpoint will be resolved again by calling DebuggerAgent, so mark it as unresolved.
712         // If something goes wrong it will stay unresolved and show up as such in the user interface.
713         breakpoint.resolved = false;
714
715         // Convert BreakpointAction types to DebuggerAgent protocol types.
716         // NOTE: Breakpoint.options returns new objects each time, so it is safe to modify.
717         // COMPATIBILITY (iOS 7): Debugger.BreakpointActionType did not exist yet.
718         var options;
719         if (DebuggerAgent.BreakpointActionType) {
720             options = breakpoint.options;
721             if (options.actions.length) {
722                 for (var i = 0; i < options.actions.length; ++i)
723                     options.actions[i].type = this._debuggerBreakpointActionType(options.actions[i].type);
724             }
725         }
726
727         // COMPATIBILITY (iOS 7): iOS 7 and earlier, DebuggerAgent.setBreakpoint* took a "condition" string argument.
728         // This has been replaced with an "options" BreakpointOptions object.
729         if (breakpoint.url) {
730             DebuggerAgent.setBreakpointByUrl.invoke({
731                 lineNumber: breakpoint.sourceCodeLocation.lineNumber,
732                 url: breakpoint.url,
733                 urlRegex: undefined,
734                 columnNumber: breakpoint.sourceCodeLocation.columnNumber,
735                 condition: breakpoint.condition,
736                 options
737             }, didSetBreakpoint.bind(this));
738         } else if (breakpoint.scriptIdentifier) {
739             DebuggerAgent.setBreakpoint.invoke({
740                 location: {scriptId: breakpoint.scriptIdentifier, lineNumber: breakpoint.sourceCodeLocation.lineNumber, columnNumber: breakpoint.sourceCodeLocation.columnNumber},
741                 condition: breakpoint.condition,
742                 options
743             }, didSetBreakpoint.bind(this));
744         }
745     }
746
747     _removeBreakpoint(breakpoint, callback)
748     {
749         if (!breakpoint.identifier)
750             return;
751
752         function didRemoveBreakpoint(error)
753         {
754             if (error)
755                 console.error(error);
756
757             this._breakpointIdMap.delete(breakpoint.identifier);
758
759             breakpoint.identifier = null;
760
761             // Don't reset resolved here since we want to keep disabled breakpoints looking like they
762             // are resolved in the user interface. They will get marked as unresolved in reset.
763
764             if (typeof callback === "function")
765                 callback();
766         }
767
768         DebuggerAgent.removeBreakpoint(breakpoint.identifier, didRemoveBreakpoint.bind(this));
769     }
770
771     _breakpointDisplayLocationDidChange(event)
772     {
773         if (this._ignoreBreakpointDisplayLocationDidChangeEvent)
774             return;
775
776         var breakpoint = event.target;
777         if (!breakpoint.identifier || breakpoint.disabled)
778             return;
779
780         // Remove the breakpoint with its old id.
781         this._removeBreakpoint(breakpoint, breakpointRemoved.bind(this));
782
783         function breakpointRemoved()
784         {
785             // Add the breakpoint at its new lineNumber and get a new id.
786             this._setBreakpoint(breakpoint);
787
788             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.BreakpointMoved, {breakpoint});
789         }
790     }
791
792     _breakpointDisabledStateDidChange(event)
793     {
794         this._saveBreakpoints();
795
796         let breakpoint = event.target;
797         if (breakpoint === this._allExceptionsBreakpoint) {
798             if (!breakpoint.disabled)
799                 this.breakpointsEnabled = true;
800             this._allExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
801             this._updateBreakOnExceptionsState();
802             return;
803         }
804
805         if (breakpoint === this._allUncaughtExceptionsBreakpoint) {
806             if (!breakpoint.disabled)
807                 this.breakpointsEnabled = true;
808             this._allUncaughtExceptionsBreakpointEnabledSetting.value = !breakpoint.disabled;
809             this._updateBreakOnExceptionsState();
810             return;
811         }
812
813         if (breakpoint.disabled)
814             this._removeBreakpoint(breakpoint);
815         else
816             this._setBreakpoint(breakpoint);
817     }
818
819     _breakpointEditablePropertyDidChange(event)
820     {
821         this._saveBreakpoints();
822
823         let breakpoint = event.target;
824         if (breakpoint.disabled)
825             return;
826
827         console.assert(this.isBreakpointEditable(breakpoint));
828         if (!this.isBreakpointEditable(breakpoint))
829             return;
830
831         // Remove the breakpoint with its old id.
832         this._removeBreakpoint(breakpoint, breakpointRemoved.bind(this));
833
834         function breakpointRemoved()
835         {
836             // Add the breakpoint with its new condition and get a new id.
837             this._setBreakpoint(breakpoint);
838         }
839     }
840
841     _mainResourceDidChange(event)
842     {
843         if (!event.target.isMainFrame())
844             return;
845
846         this._didResumeInternal();
847     }
848
849     _didResumeInternal()
850     {
851         if (!this._activeCallFrame)
852             return;
853
854         if (this._delayedResumeTimeout) {
855             clearTimeout(this._delayedResumeTimeout);
856             this._delayedResumeTimeout = undefined;
857         }
858
859         this._paused = false;
860         this._callFrames = null;
861         this._activeCallFrame = null;
862
863         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
864         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange);
865         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
866     }
867
868     _updateBreakOnExceptionsState()
869     {
870         var state = "none";
871
872         if (this._breakpointsEnabledSetting.value) {
873             if (!this._allExceptionsBreakpoint.disabled)
874                 state = "all";
875             else if (!this._allUncaughtExceptionsBreakpoint.disabled)
876                 state = "uncaught";
877         }
878
879         switch (state) {
880         case "all":
881             // Mark the uncaught breakpoint as unresolved since "all" includes "uncaught".
882             // That way it is clear in the user interface that the breakpoint is ignored.
883             this._allUncaughtExceptionsBreakpoint.resolved = false;
884             break;
885         case "uncaught":
886         case "none":
887             // Mark the uncaught breakpoint as resolved again.
888             this._allUncaughtExceptionsBreakpoint.resolved = true;
889             break;
890         }
891
892         DebuggerAgent.setPauseOnExceptions(state);
893     }
894
895     _saveBreakpoints()
896     {
897         if (this._restoringBreakpoints)
898             return;
899
900         let breakpointsToSave = this._breakpoints.filter((breakpoint) => !!breakpoint.url);
901         let serializedBreakpoints = breakpointsToSave.map((breakpoint) => breakpoint.info);
902         this._breakpointsSetting.value = serializedBreakpoints;
903     }
904
905     _associateBreakpointsWithSourceCode(breakpoints, sourceCode)
906     {
907         this._ignoreBreakpointDisplayLocationDidChangeEvent = true;
908
909         for (var i = 0; i < breakpoints.length; ++i) {
910             var breakpoint = breakpoints[i];
911             if (breakpoint.sourceCodeLocation.sourceCode === null)
912                 breakpoint.sourceCodeLocation.sourceCode = sourceCode;
913             // SourceCodes can be unequal if the SourceCodeLocation is associated with a Script and we are looking at the Resource.
914             console.assert(breakpoint.sourceCodeLocation.sourceCode === sourceCode || breakpoint.sourceCodeLocation.sourceCode.url === sourceCode.url);
915         }
916
917         this._ignoreBreakpointDisplayLocationDidChangeEvent = false;
918     }
919
920     _debugUIEnabledDidChange()
921     {
922         let eventType = WebInspector.isDebugUIEnabled() ? WebInspector.DebuggerManager.Event.ScriptAdded : WebInspector.DebuggerManager.Event.ScriptRemoved;
923         for (let script of this._inspectorDebugScripts)
924             this.dispatchEventToListeners(eventType, {script});
925     }
926 };
927
928 WebInspector.DebuggerManager.Event = {
929     BreakpointAdded: "debugger-manager-breakpoint-added",
930     BreakpointRemoved: "debugger-manager-breakpoint-removed",
931     BreakpointMoved: "debugger-manager-breakpoint-moved",
932     WaitingToPause: "debugger-manager-waiting-to-pause",
933     Paused: "debugger-manager-paused",
934     Resumed: "debugger-manager-resumed",
935     CallFramesDidChange: "debugger-manager-call-frames-did-change",
936     ActiveCallFrameDidChange: "debugger-manager-active-call-frame-did-change",
937     ScriptAdded: "debugger-manager-script-added",
938     ScriptRemoved: "debugger-manager-script-removed",
939     ScriptsCleared: "debugger-manager-scripts-cleared",
940     BreakpointsEnabledDidChange: "debugger-manager-breakpoints-enabled-did-change"
941 };
942
943 WebInspector.DebuggerManager.PauseReason = {
944     Assertion: "assertion",
945     Breakpoint: "breakpoint",
946     CSPViolation: "CSP-violation",
947     DebuggerStatement: "debugger-statement",
948     Exception: "exception",
949     PauseOnNextStatement: "pause-on-next-statement",
950     Other: "other",
951 };