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