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