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