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