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