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