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