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