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