1/*
2 * Copyright (C) 2010 Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 * @extends {WebInspector.Object}
34 */
35WebInspector.DebuggerModel = function()
36{
37    InspectorBackend.registerDebuggerDispatcher(new WebInspector.DebuggerDispatcher(this));
38
39    /** @type {?WebInspector.DebuggerPausedDetails} */
40    this._debuggerPausedDetails = null;
41    /** @type {!Object.<string, !WebInspector.Script>} */
42    this._scripts = {};
43    /** @type {!Object.<!string, !Array.<!WebInspector.Script>>} */
44    this._scriptsBySourceURL = {};
45
46    this._canSetScriptSource = false;
47    this._breakpointsActive = true;
48
49    WebInspector.settings.pauseOnExceptionStateString = WebInspector.settings.createSetting("pauseOnExceptionStateString", WebInspector.DebuggerModel.PauseOnExceptionsState.DontPauseOnExceptions);
50    WebInspector.settings.pauseOnExceptionStateString.addChangeListener(this._pauseOnExceptionStateChanged, this);
51
52    WebInspector.settings.enableAsyncStackTraces.addChangeListener(this._asyncStackTracesStateChanged, this);
53
54    this.enableDebugger();
55
56    WebInspector.DebuggerModel.applySkipStackFrameSettings();
57}
58
59// Keep these in sync with WebCore::ScriptDebugServer
60WebInspector.DebuggerModel.PauseOnExceptionsState = {
61    DontPauseOnExceptions : "none",
62    PauseOnAllExceptions : "all",
63    PauseOnUncaughtExceptions: "uncaught"
64};
65
66/**
67 * @constructor
68 * @implements {WebInspector.RawLocation}
69 * @param {string} scriptId
70 * @param {number} lineNumber
71 * @param {number} columnNumber
72 */
73WebInspector.DebuggerModel.Location = function(scriptId, lineNumber, columnNumber)
74{
75    this.scriptId = scriptId;
76    this.lineNumber = lineNumber;
77    this.columnNumber = columnNumber;
78}
79
80WebInspector.DebuggerModel.Events = {
81    DebuggerWasEnabled: "DebuggerWasEnabled",
82    DebuggerWasDisabled: "DebuggerWasDisabled",
83    DebuggerPaused: "DebuggerPaused",
84    DebuggerResumed: "DebuggerResumed",
85    ParsedScriptSource: "ParsedScriptSource",
86    FailedToParseScriptSource: "FailedToParseScriptSource",
87    BreakpointResolved: "BreakpointResolved",
88    GlobalObjectCleared: "GlobalObjectCleared",
89    CallFrameSelected: "CallFrameSelected",
90    ConsoleCommandEvaluatedInSelectedCallFrame: "ConsoleCommandEvaluatedInSelectedCallFrame",
91    BreakpointsActiveStateChanged: "BreakpointsActiveStateChanged"
92}
93
94WebInspector.DebuggerModel.BreakReason = {
95    DOM: "DOM",
96    EventListener: "EventListener",
97    XHR: "XHR",
98    Exception: "exception",
99    Assert: "assert",
100    CSPViolation: "CSPViolation",
101    DebugCommand: "debugCommand"
102}
103
104WebInspector.DebuggerModel.prototype = {
105    /**
106     * @return {boolean}
107     */
108    debuggerEnabled: function()
109    {
110        return !!this._debuggerEnabled;
111    },
112
113    enableDebugger: function()
114    {
115        if (this._debuggerEnabled)
116            return;
117
118        /**
119         * @param {?Protocol.Error} error
120         * @param {boolean} result
121         * @this {WebInspector.DebuggerModel}
122         */
123        function callback(error, result)
124        {
125            this._canSetScriptSource = result;
126        }
127        DebuggerAgent.canSetScriptSource(callback.bind(this));
128        DebuggerAgent.enable(this._debuggerWasEnabled.bind(this));
129    },
130
131    disableDebugger: function()
132    {
133        if (!this._debuggerEnabled)
134            return;
135
136        DebuggerAgent.disable(this._debuggerWasDisabled.bind(this));
137    },
138
139    /**
140     * @param {boolean} skip
141     * @param {boolean=} untilReload
142     */
143    skipAllPauses: function(skip, untilReload)
144    {
145        if (this._skipAllPausesTimeout) {
146            clearTimeout(this._skipAllPausesTimeout);
147            delete this._skipAllPausesTimeout;
148        }
149        DebuggerAgent.setSkipAllPauses(skip, untilReload);
150    },
151
152    /**
153     * @param {number} timeout
154     */
155    skipAllPausesUntilReloadOrTimeout: function(timeout)
156    {
157        if (this._skipAllPausesTimeout)
158            clearTimeout(this._skipAllPausesTimeout);
159        DebuggerAgent.setSkipAllPauses(true, true);
160        // If reload happens before the timeout, the flag will be already unset and the timeout callback won't change anything.
161        this._skipAllPausesTimeout = setTimeout(this.skipAllPauses.bind(this, false), timeout);
162    },
163
164    /**
165     * @return {boolean}
166     */
167    canSetScriptSource: function()
168    {
169        return this._canSetScriptSource;
170    },
171
172    _debuggerWasEnabled: function()
173    {
174        this._debuggerEnabled = true;
175        this._pauseOnExceptionStateChanged();
176        this._asyncStackTracesStateChanged();
177        this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerWasEnabled);
178    },
179
180    _pauseOnExceptionStateChanged: function()
181    {
182        DebuggerAgent.setPauseOnExceptions(WebInspector.settings.pauseOnExceptionStateString.get());
183    },
184
185    _asyncStackTracesStateChanged: function()
186    {
187        const maxAsyncStackChainDepth = 4;
188        var enabled = WebInspector.settings.enableAsyncStackTraces.get() && WebInspector.experimentsSettings.asyncStackTraces.isEnabled();
189        DebuggerAgent.setAsyncCallStackDepth(enabled ? maxAsyncStackChainDepth : 0);
190    },
191
192    _debuggerWasDisabled: function()
193    {
194        this._debuggerEnabled = false;
195        this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerWasDisabled);
196    },
197
198    /**
199     * @param {!WebInspector.DebuggerModel.Location} rawLocation
200     */
201    continueToLocation: function(rawLocation)
202    {
203        DebuggerAgent.continueToLocation(rawLocation);
204    },
205
206    /**
207     * @param {!WebInspector.DebuggerModel.Location} rawLocation
208     */
209    stepIntoSelection: function(rawLocation)
210    {
211        /**
212         * @param {!WebInspector.DebuggerModel.Location} requestedLocation
213         * @param {?string} error
214         * @this {WebInspector.DebuggerModel}
215         */
216        function callback(requestedLocation, error)
217        {
218           if (error)
219               return;
220           this._pendingStepIntoLocation = requestedLocation;
221        };
222        DebuggerAgent.continueToLocation(rawLocation, true, callback.bind(this, rawLocation));
223    },
224
225    stepInto: function()
226    {
227        function callback()
228        {
229            DebuggerAgent.stepInto();
230        }
231        DebuggerAgent.setOverlayMessage(undefined, callback.bind(this));
232    },
233
234    stepOver: function()
235    {
236        function callback()
237        {
238            DebuggerAgent.stepOver();
239        }
240        DebuggerAgent.setOverlayMessage(undefined, callback.bind(this));
241    },
242
243    stepOut: function()
244    {
245        function callback()
246        {
247            DebuggerAgent.stepOut();
248        }
249        DebuggerAgent.setOverlayMessage(undefined, callback.bind(this));
250    },
251
252    resume: function()
253    {
254        function callback()
255        {
256            DebuggerAgent.resume();
257        }
258        DebuggerAgent.setOverlayMessage(undefined, callback.bind(this));
259    },
260
261    /**
262     * @param {!WebInspector.DebuggerModel.Location} rawLocation
263     * @param {string} condition
264     * @param {function(?DebuggerAgent.BreakpointId, !Array.<!WebInspector.DebuggerModel.Location>):void=} callback
265     */
266    setBreakpointByScriptLocation: function(rawLocation, condition, callback)
267    {
268        var script = this.scriptForId(rawLocation.scriptId);
269        if (script.sourceURL)
270            this.setBreakpointByURL(script.sourceURL, rawLocation.lineNumber, rawLocation.columnNumber, condition, callback);
271        else
272            this.setBreakpointBySourceId(rawLocation, condition, callback);
273    },
274
275    /**
276     * @param {string} url
277     * @param {number} lineNumber
278     * @param {number=} columnNumber
279     * @param {string=} condition
280     * @param {function(?DebuggerAgent.BreakpointId, !Array.<!WebInspector.DebuggerModel.Location>)=} callback
281     */
282    setBreakpointByURL: function(url, lineNumber, columnNumber, condition, callback)
283    {
284        // Adjust column if needed.
285        var minColumnNumber = 0;
286        var scripts = this._scriptsBySourceURL[url] || [];
287        for (var i = 0, l = scripts.length; i < l; ++i) {
288            var script = scripts[i];
289            if (lineNumber === script.lineOffset)
290                minColumnNumber = minColumnNumber ? Math.min(minColumnNumber, script.columnOffset) : script.columnOffset;
291        }
292        columnNumber = Math.max(columnNumber, minColumnNumber);
293
294        /**
295         * @this {WebInspector.DebuggerModel}
296         * @param {?Protocol.Error} error
297         * @param {!DebuggerAgent.BreakpointId} breakpointId
298         * @param {!Array.<!DebuggerAgent.Location>} locations
299         */
300        function didSetBreakpoint(error, breakpointId, locations)
301        {
302            if (callback) {
303                var rawLocations = /** @type {!Array.<!WebInspector.DebuggerModel.Location>} */ (locations);
304                callback(error ? null : breakpointId, rawLocations);
305            }
306        }
307        DebuggerAgent.setBreakpointByUrl(lineNumber, url, undefined, columnNumber, condition, undefined, didSetBreakpoint.bind(this));
308        WebInspector.userMetrics.ScriptsBreakpointSet.record();
309    },
310
311    /**
312     * @param {!WebInspector.DebuggerModel.Location} rawLocation
313     * @param {string} condition
314     * @param {function(?DebuggerAgent.BreakpointId, !Array.<!WebInspector.DebuggerModel.Location>)=} callback
315     */
316    setBreakpointBySourceId: function(rawLocation, condition, callback)
317    {
318        /**
319         * @this {WebInspector.DebuggerModel}
320         * @param {?Protocol.Error} error
321         * @param {!DebuggerAgent.BreakpointId} breakpointId
322         * @param {!DebuggerAgent.Location} actualLocation
323         */
324        function didSetBreakpoint(error, breakpointId, actualLocation)
325        {
326            if (callback) {
327                var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (actualLocation);
328                callback(error ? null : breakpointId, [rawLocation]);
329            }
330        }
331        DebuggerAgent.setBreakpoint(rawLocation, condition, didSetBreakpoint.bind(this));
332        WebInspector.userMetrics.ScriptsBreakpointSet.record();
333    },
334
335    /**
336     * @param {!DebuggerAgent.BreakpointId} breakpointId
337     * @param {function(?Protocol.Error)=} callback
338     */
339    removeBreakpoint: function(breakpointId, callback)
340    {
341        DebuggerAgent.removeBreakpoint(breakpointId, callback);
342    },
343
344    /**
345     * @param {!DebuggerAgent.BreakpointId} breakpointId
346     * @param {!DebuggerAgent.Location} location
347     */
348    _breakpointResolved: function(breakpointId, location)
349    {
350        this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointResolved, {breakpointId: breakpointId, location: location});
351    },
352
353    _globalObjectCleared: function()
354    {
355        this._setDebuggerPausedDetails(null);
356        this._reset();
357        this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.GlobalObjectCleared);
358    },
359
360    _reset: function()
361    {
362        this._scripts = {};
363        this._scriptsBySourceURL = {};
364    },
365
366    /**
367     * @return {!Object.<string, !WebInspector.Script>}
368     */
369    get scripts()
370    {
371        return this._scripts;
372    },
373
374    /**
375     * @param {!DebuggerAgent.ScriptId} scriptId
376     * @return {!WebInspector.Script}
377     */
378    scriptForId: function(scriptId)
379    {
380        return this._scripts[scriptId] || null;
381    },
382
383    /**
384     * @return {!Array.<!WebInspector.Script>}
385     */
386    scriptsForSourceURL: function(sourceURL)
387    {
388        if (!sourceURL)
389            return [];
390        return this._scriptsBySourceURL[sourceURL] || [];
391    },
392
393    /**
394     * @param {!DebuggerAgent.ScriptId} scriptId
395     * @param {string} newSource
396     * @param {function(?Protocol.Error, !DebuggerAgent.SetScriptSourceError=)} callback
397     */
398    setScriptSource: function(scriptId, newSource, callback)
399    {
400        this._scripts[scriptId].editSource(newSource, this._didEditScriptSource.bind(this, scriptId, newSource, callback));
401    },
402
403    /**
404     * @param {!DebuggerAgent.ScriptId} scriptId
405     * @param {string} newSource
406     * @param {function(?Protocol.Error, !DebuggerAgent.SetScriptSourceError=)} callback
407     * @param {?Protocol.Error} error
408     * @param {!DebuggerAgent.SetScriptSourceError=} errorData
409     * @param {!Array.<!DebuggerAgent.CallFrame>=} callFrames
410     * @param {!DebuggerAgent.StackTrace=} asyncStackTrace
411     * @param {boolean=} needsStepIn
412     */
413    _didEditScriptSource: function(scriptId, newSource, callback, error, errorData, callFrames, asyncStackTrace, needsStepIn)
414    {
415        callback(error, errorData);
416        if (needsStepIn)
417            this.stepInto();
418        else if (!error && callFrames && callFrames.length)
419            this._pausedScript(callFrames, this._debuggerPausedDetails.reason, this._debuggerPausedDetails.auxData, this._debuggerPausedDetails.breakpointIds, asyncStackTrace);
420    },
421
422    /**
423     * @return {?Array.<!WebInspector.DebuggerModel.CallFrame>}
424     */
425    get callFrames()
426    {
427        return this._debuggerPausedDetails ? this._debuggerPausedDetails.callFrames : null;
428    },
429
430    /**
431     * @return {?WebInspector.DebuggerPausedDetails}
432     */
433    debuggerPausedDetails: function()
434    {
435        return this._debuggerPausedDetails;
436    },
437
438    /**
439     * @param {?WebInspector.DebuggerPausedDetails} debuggerPausedDetails
440     */
441    _setDebuggerPausedDetails: function(debuggerPausedDetails)
442    {
443        if (this._debuggerPausedDetails)
444            this._debuggerPausedDetails.dispose();
445        this._debuggerPausedDetails = debuggerPausedDetails;
446        if (this._debuggerPausedDetails)
447            this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPausedDetails);
448        if (debuggerPausedDetails) {
449            this.setSelectedCallFrame(debuggerPausedDetails.callFrames[0]);
450            DebuggerAgent.setOverlayMessage(WebInspector.UIString("Paused in debugger"));
451        } else {
452            this.setSelectedCallFrame(null);
453            DebuggerAgent.setOverlayMessage();
454        }
455    },
456
457    /**
458     * @param {!Array.<!DebuggerAgent.CallFrame>} callFrames
459     * @param {string} reason
460     * @param {!Object|undefined} auxData
461     * @param {!Array.<string>} breakpointIds
462     * @param {!DebuggerAgent.StackTrace=} asyncStackTrace
463     */
464    _pausedScript: function(callFrames, reason, auxData, breakpointIds, asyncStackTrace)
465    {
466        if (this._pendingStepIntoLocation) {
467            var requestedLocation = this._pendingStepIntoLocation;
468            delete this._pendingStepIntoLocation;
469
470            if (callFrames.length > 0) {
471                var topLocation = callFrames[0].location;
472                if (topLocation.lineNumber == requestedLocation.lineNumber && topLocation.columnNumber == requestedLocation.columnNumber && topLocation.scriptId == requestedLocation.scriptId) {
473                    this.stepInto();
474                    return;
475                }
476            }
477        }
478
479        this._setDebuggerPausedDetails(new WebInspector.DebuggerPausedDetails(callFrames, reason, auxData, breakpointIds, asyncStackTrace));
480    },
481
482    _resumedScript: function()
483    {
484        this._setDebuggerPausedDetails(null);
485        this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerResumed);
486    },
487
488    /**
489     * @param {!DebuggerAgent.ScriptId} scriptId
490     * @param {string} sourceURL
491     * @param {number} startLine
492     * @param {number} startColumn
493     * @param {number} endLine
494     * @param {number} endColumn
495     * @param {boolean} isContentScript
496     * @param {string=} sourceMapURL
497     * @param {boolean=} hasSourceURL
498     */
499    _parsedScriptSource: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL)
500    {
501        var script = new WebInspector.Script(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL);
502        this._registerScript(script);
503        this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ParsedScriptSource, script);
504    },
505
506    /**
507     * @param {!WebInspector.Script} script
508     */
509    _registerScript: function(script)
510    {
511        this._scripts[script.scriptId] = script;
512        if (script.isAnonymousScript())
513            return;
514
515        var scripts = this._scriptsBySourceURL[script.sourceURL];
516        if (!scripts) {
517            scripts = [];
518            this._scriptsBySourceURL[script.sourceURL] = scripts;
519        }
520        scripts.push(script);
521    },
522
523    /**
524     * @param {!WebInspector.Script} script
525     * @param {number} lineNumber
526     * @param {number} columnNumber
527     * @return {?WebInspector.DebuggerModel.Location}
528     */
529    createRawLocation: function(script, lineNumber, columnNumber)
530    {
531        if (script.sourceURL)
532            return this.createRawLocationByURL(script.sourceURL, lineNumber, columnNumber)
533        return new WebInspector.DebuggerModel.Location(script.scriptId, lineNumber, columnNumber);
534    },
535
536    /**
537     * @param {string} sourceURL
538     * @param {number} lineNumber
539     * @param {number} columnNumber
540     * @return {?WebInspector.DebuggerModel.Location}
541     */
542    createRawLocationByURL: function(sourceURL, lineNumber, columnNumber)
543    {
544        var closestScript = null;
545        var scripts = this._scriptsBySourceURL[sourceURL] || [];
546        for (var i = 0, l = scripts.length; i < l; ++i) {
547            var script = scripts[i];
548            if (!closestScript)
549                closestScript = script;
550            if (script.lineOffset > lineNumber || (script.lineOffset === lineNumber && script.columnOffset > columnNumber))
551                continue;
552            if (script.endLine < lineNumber || (script.endLine === lineNumber && script.endColumn <= columnNumber))
553                continue;
554            closestScript = script;
555            break;
556        }
557        return closestScript ? new WebInspector.DebuggerModel.Location(closestScript.scriptId, lineNumber, columnNumber) : null;
558    },
559
560    /**
561     * @return {boolean}
562     */
563    isPaused: function()
564    {
565        return !!this.debuggerPausedDetails();
566    },
567
568    /**
569     * @param {?WebInspector.DebuggerModel.CallFrame} callFrame
570     */
571    setSelectedCallFrame: function(callFrame)
572    {
573        this._selectedCallFrame = callFrame;
574        if (!this._selectedCallFrame)
575            return;
576
577        this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.CallFrameSelected, callFrame);
578    },
579
580    /**
581     * @return {?WebInspector.DebuggerModel.CallFrame}
582     */
583    selectedCallFrame: function()
584    {
585        return this._selectedCallFrame;
586    },
587
588    /**
589     * @return {!DebuggerAgent.CallFrameId|undefined}
590     */
591    _selectedCallFrameId: function()
592    {
593        var callFrame = this.selectedCallFrame();
594        return callFrame ? callFrame.id : undefined;
595    },
596
597    /**
598     * @param {string} code
599     * @param {string} objectGroup
600     * @param {boolean} includeCommandLineAPI
601     * @param {boolean} doNotPauseOnExceptionsAndMuteConsole
602     * @param {boolean} returnByValue
603     * @param {boolean} generatePreview
604     * @param {function(?WebInspector.RemoteObject, boolean, ?RuntimeAgent.RemoteObject=)} callback
605     */
606    evaluateOnSelectedCallFrame: function(code, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback)
607    {
608        /**
609         * @param {?RuntimeAgent.RemoteObject} result
610         * @param {boolean=} wasThrown
611         * @this {WebInspector.DebuggerModel}
612         */
613        function didEvaluate(result, wasThrown)
614        {
615            if (!result)
616                callback(null, false);
617            else if (returnByValue)
618                callback(null, !!wasThrown, wasThrown ? null : result);
619            else
620                callback(WebInspector.RemoteObject.fromPayload(result), !!wasThrown);
621
622            if (objectGroup === "console")
623                this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ConsoleCommandEvaluatedInSelectedCallFrame);
624        }
625
626        this.selectedCallFrame().evaluate(code, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, didEvaluate.bind(this));
627    },
628
629    /**
630     * @param {function(!Object)} callback
631     */
632    getSelectedCallFrameVariables: function(callback)
633    {
634        var result = { this: true };
635
636        var selectedCallFrame = this._selectedCallFrame;
637        if (!selectedCallFrame)
638            callback(result);
639
640        var pendingRequests = 0;
641
642        function propertiesCollected(properties)
643        {
644            for (var i = 0; properties && i < properties.length; ++i)
645                result[properties[i].name] = true;
646            if (--pendingRequests == 0)
647                callback(result);
648        }
649
650        for (var i = 0; i < selectedCallFrame.scopeChain.length; ++i) {
651            var scope = selectedCallFrame.scopeChain[i];
652            var object = WebInspector.RemoteObject.fromPayload(scope.object);
653            pendingRequests++;
654            object.getAllProperties(false, propertiesCollected);
655        }
656    },
657
658    /**
659     * @param {boolean} active
660     */
661    setBreakpointsActive: function(active)
662    {
663        if (this._breakpointsActive === active)
664            return;
665        this._breakpointsActive = active;
666        DebuggerAgent.setBreakpointsActive(active);
667        this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointsActiveStateChanged, active);
668    },
669
670    /**
671     * @return {boolean}
672     */
673    breakpointsActive: function()
674    {
675        return this._breakpointsActive;
676    },
677
678    /**
679     * @param {!WebInspector.DebuggerModel.Location} rawLocation
680     * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
681     * @return {!WebInspector.Script.Location}
682     */
683    createLiveLocation: function(rawLocation, updateDelegate)
684    {
685        var script = this._scripts[rawLocation.scriptId];
686        return script.createLiveLocation(rawLocation, updateDelegate);
687    },
688
689    /**
690     * @param {!WebInspector.DebuggerModel.Location|!DebuggerAgent.Location} rawLocation
691     * @return {?WebInspector.UILocation}
692     */
693    rawLocationToUILocation: function(rawLocation)
694    {
695        var script = this._scripts[rawLocation.scriptId];
696        if (!script)
697            return null;
698        return script.rawLocationToUILocation(rawLocation.lineNumber, rawLocation.columnNumber);
699    },
700
701    /**
702     * Handles notification from JavaScript VM about updated stack (liveedit or frame restart action).
703     * @this {WebInspector.DebuggerModel}
704     * @param {!Array.<!DebuggerAgent.CallFrame>=} newCallFrames
705     * @param {!Object=} details
706     * @param {!DebuggerAgent.StackTrace=} asyncStackTrace
707     */
708    callStackModified: function(newCallFrames, details, asyncStackTrace)
709    {
710        // FIXME: declare this property in protocol and in JavaScript.
711        if (details && details["stack_update_needs_step_in"])
712            this.stepInto();
713        else if (newCallFrames && newCallFrames.length)
714            this._pausedScript(newCallFrames, this._debuggerPausedDetails.reason, this._debuggerPausedDetails.auxData, this._debuggerPausedDetails.breakpointIds, asyncStackTrace);
715    },
716
717    __proto__: WebInspector.Object.prototype
718}
719
720WebInspector.DebuggerModel.applySkipStackFrameSettings = function()
721{
722    if (!WebInspector.experimentsSettings.frameworksDebuggingSupport.isEnabled())
723        return;
724    var settings = WebInspector.settings;
725    var patternParameter = settings.skipStackFramesSwitch.get() ? settings.skipStackFramesPattern.get() : undefined;
726    DebuggerAgent.skipStackFrames(patternParameter);
727}
728
729WebInspector.DebuggerEventTypes = {
730    JavaScriptPause: 0,
731    JavaScriptBreakpoint: 1,
732    NativeBreakpoint: 2
733};
734
735/**
736 * @constructor
737 * @implements {DebuggerAgent.Dispatcher}
738 * @param {!WebInspector.DebuggerModel} debuggerModel
739 */
740WebInspector.DebuggerDispatcher = function(debuggerModel)
741{
742    this._debuggerModel = debuggerModel;
743}
744
745WebInspector.DebuggerDispatcher.prototype = {
746    /**
747     * @param {!Array.<!DebuggerAgent.CallFrame>} callFrames
748     * @param {string} reason
749     * @param {!Object=} auxData
750     * @param {!Array.<string>=} breakpointIds
751     * @param {!DebuggerAgent.StackTrace=} asyncStackTrace
752     */
753    paused: function(callFrames, reason, auxData, breakpointIds, asyncStackTrace)
754    {
755        this._debuggerModel._pausedScript(callFrames, reason, auxData, breakpointIds || [], asyncStackTrace);
756    },
757
758    /**
759     * @override
760     */
761    resumed: function()
762    {
763        this._debuggerModel._resumedScript();
764    },
765
766    /**
767     * @override
768     */
769    globalObjectCleared: function()
770    {
771        this._debuggerModel._globalObjectCleared();
772    },
773
774    /**
775     * @param {!DebuggerAgent.ScriptId} scriptId
776     * @param {string} sourceURL
777     * @param {number} startLine
778     * @param {number} startColumn
779     * @param {number} endLine
780     * @param {number} endColumn
781     * @param {boolean=} isContentScript
782     * @param {string=} sourceMapURL
783     * @param {boolean=} hasSourceURL
784     */
785    scriptParsed: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL)
786    {
787        this._debuggerModel._parsedScriptSource(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, !!isContentScript, sourceMapURL, hasSourceURL);
788    },
789
790    /**
791     * @param {string} sourceURL
792     * @param {string} source
793     * @param {number} startingLine
794     * @param {number} errorLine
795     * @param {string} errorMessage
796     */
797    scriptFailedToParse: function(sourceURL, source, startingLine, errorLine, errorMessage)
798    {
799    },
800
801    /**
802     * @param {!DebuggerAgent.BreakpointId} breakpointId
803     * @param {!DebuggerAgent.Location} location
804     */
805    breakpointResolved: function(breakpointId, location)
806    {
807        this._debuggerModel._breakpointResolved(breakpointId, location);
808    }
809}
810
811/**
812 * @constructor
813 * @param {!WebInspector.Script} script
814 * @param {!DebuggerAgent.CallFrame} payload
815 * @param {boolean=} isAsync
816 */
817WebInspector.DebuggerModel.CallFrame = function(script, payload, isAsync)
818{
819    this._script = script;
820    this._payload = payload;
821    /** @type {!Array.<!WebInspector.Script.Location>} */
822    this._locations = [];
823    this._isAsync = isAsync;
824}
825
826/**
827 * @param {!Array.<!DebuggerAgent.CallFrame>} callFrames
828 * @param {boolean=} isAsync
829 * @return {!Array.<!WebInspector.DebuggerModel.CallFrame>}
830 */
831WebInspector.DebuggerModel.CallFrame.fromPayloadArray = function(callFrames, isAsync)
832{
833    var result = [];
834    for (var i = 0; i < callFrames.length; ++i) {
835        var callFrame = callFrames[i];
836        var script = WebInspector.debuggerModel.scriptForId(callFrame.location.scriptId);
837        if (script)
838            result.push(new WebInspector.DebuggerModel.CallFrame(script, callFrame, isAsync));
839    }
840    return result;
841}
842
843WebInspector.DebuggerModel.CallFrame.prototype = {
844    /**
845     * @return {!WebInspector.Script}
846     */
847    get script()
848    {
849        return this._script;
850    },
851
852    /**
853     * @return {string}
854     */
855    get type()
856    {
857        return this._payload.type;
858    },
859
860    /**
861     * @return {string}
862     */
863    get id()
864    {
865        return this._payload.callFrameId;
866    },
867
868    /**
869     * @return {!Array.<!DebuggerAgent.Scope>}
870     */
871    get scopeChain()
872    {
873        return this._payload.scopeChain;
874    },
875
876    /**
877     * @return {!RuntimeAgent.RemoteObject}
878     */
879    get this()
880    {
881        return this._payload.this;
882    },
883
884    /**
885     * @return {!RuntimeAgent.RemoteObject|undefined}
886     */
887    get returnValue()
888    {
889        return this._payload.returnValue;
890    },
891
892    /**
893     * @return {string}
894     */
895    get functionName()
896    {
897        return this._payload.functionName;
898    },
899
900    /**
901     * @return {!WebInspector.DebuggerModel.Location}
902     */
903    get location()
904    {
905        var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (this._payload.location);
906        return rawLocation;
907    },
908
909    /**
910     * @return {boolean}
911     */
912    isAsync: function()
913    {
914        return !!this._isAsync;
915    },
916
917    /**
918     * @param {string} code
919     * @param {string} objectGroup
920     * @param {boolean} includeCommandLineAPI
921     * @param {boolean} doNotPauseOnExceptionsAndMuteConsole
922     * @param {boolean} returnByValue
923     * @param {boolean} generatePreview
924     * @param {function(?RuntimeAgent.RemoteObject, boolean=)=} callback
925     */
926    evaluate: function(code, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback)
927    {
928        /**
929         * @this {WebInspector.DebuggerModel.CallFrame}
930         * @param {?Protocol.Error} error
931         * @param {!RuntimeAgent.RemoteObject} result
932         * @param {boolean=} wasThrown
933         */
934        function didEvaluateOnCallFrame(error, result, wasThrown)
935        {
936            if (error) {
937                console.error(error);
938                callback(null, false);
939                return;
940            }
941            callback(result, wasThrown);
942        }
943        DebuggerAgent.evaluateOnCallFrame(this._payload.callFrameId, code, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, didEvaluateOnCallFrame.bind(this));
944    },
945
946    /**
947     * @param {function(?Protocol.Error=)=} callback
948     */
949    restart: function(callback)
950    {
951        /**
952         * @this {WebInspector.DebuggerModel.CallFrame}
953         * @param {?Protocol.Error} error
954         * @param {!Array.<!DebuggerAgent.CallFrame>=} callFrames
955         * @param {!Object=} details
956         * @param {!DebuggerAgent.StackTrace=} asyncStackTrace
957         */
958        function protocolCallback(error, callFrames, details, asyncStackTrace)
959        {
960            if (!error)
961                WebInspector.debuggerModel.callStackModified(callFrames, details, asyncStackTrace);
962            if (callback)
963                callback(error);
964        }
965        DebuggerAgent.restartFrame(this._payload.callFrameId, protocolCallback);
966    },
967
968    /**
969     * @param {function(!Array.<!DebuggerAgent.Location>)} callback
970     */
971    getStepIntoLocations: function(callback)
972    {
973        if (this._stepInLocations) {
974            callback(this._stepInLocations.slice(0));
975            return;
976        }
977        /**
978         * @param {?string} error
979         * @param {!Array.<!DebuggerAgent.Location>=} stepInPositions
980         * @this {WebInspector.DebuggerModel.CallFrame}
981         */
982        function getStepInPositionsCallback(error, stepInPositions)
983        {
984            if (error)
985                return;
986            this._stepInLocations = stepInPositions;
987            callback(this._stepInLocations.slice(0));
988        }
989        DebuggerAgent.getStepInPositions(this.id, getStepInPositionsCallback.bind(this));
990    },
991
992    /**
993     * @param {function(!WebInspector.UILocation):(boolean|undefined)} updateDelegate
994     */
995    createLiveLocation: function(updateDelegate)
996    {
997        var location = this._script.createLiveLocation(this.location, updateDelegate);
998        this._locations.push(location);
999        return location;
1000    },
1001
1002    dispose: function()
1003    {
1004        for (var i = 0; i < this._locations.length; ++i)
1005            this._locations[i].dispose();
1006        this._locations = [];
1007    }
1008}
1009
1010/**
1011 * @constructor
1012 * @param {!Array.<!WebInspector.DebuggerModel.CallFrame>} callFrames
1013 * @param {?WebInspector.DebuggerModel.StackTrace} asyncStackTrace
1014 * @param {string=} description
1015 */
1016WebInspector.DebuggerModel.StackTrace = function(callFrames, asyncStackTrace, description)
1017{
1018    this.callFrames = callFrames;
1019    this.asyncStackTrace = asyncStackTrace;
1020    this.description = description;
1021}
1022
1023/**
1024 * @param {!DebuggerAgent.StackTrace=} payload
1025 * @param {boolean=} isAsync
1026 * @return {?WebInspector.DebuggerModel.StackTrace}
1027 */
1028WebInspector.DebuggerModel.StackTrace.fromPayload = function(payload, isAsync)
1029{
1030    if (!payload)
1031        return null;
1032    var callFrames = WebInspector.DebuggerModel.CallFrame.fromPayloadArray(payload.callFrames, isAsync);
1033    if (!callFrames.length)
1034        return null;
1035    var asyncStackTrace = WebInspector.DebuggerModel.StackTrace.fromPayload(payload.asyncStackTrace, true);
1036    return new WebInspector.DebuggerModel.StackTrace(callFrames, asyncStackTrace, payload.description);
1037}
1038
1039WebInspector.DebuggerModel.StackTrace.prototype = {
1040    dispose: function()
1041    {
1042        for (var i = 0; i < this.callFrames.length; ++i)
1043            this.callFrames[i].dispose();
1044        if (this.asyncStackTrace)
1045            this.asyncStackTrace.dispose();
1046    }
1047}
1048
1049/**
1050 * @constructor
1051 * @param {!Array.<!DebuggerAgent.CallFrame>} callFrames
1052 * @param {string} reason
1053 * @param {!Object|undefined} auxData
1054 * @param {!Array.<string>} breakpointIds
1055 * @param {!DebuggerAgent.StackTrace=} asyncStackTrace
1056 */
1057WebInspector.DebuggerPausedDetails = function(callFrames, reason, auxData, breakpointIds, asyncStackTrace)
1058{
1059    this.callFrames = WebInspector.DebuggerModel.CallFrame.fromPayloadArray(callFrames);
1060    this.reason = reason;
1061    this.auxData = auxData;
1062    this.breakpointIds = breakpointIds;
1063    this.asyncStackTrace = WebInspector.DebuggerModel.StackTrace.fromPayload(asyncStackTrace, true);
1064}
1065
1066WebInspector.DebuggerPausedDetails.prototype = {
1067    dispose: function()
1068    {
1069        for (var i = 0; i < this.callFrames.length; ++i)
1070            this.callFrames[i].dispose();
1071        if (this.asyncStackTrace)
1072            this.asyncStackTrace.dispose();
1073    }
1074}
1075
1076/**
1077 * @type {!WebInspector.DebuggerModel}
1078 */
1079WebInspector.debuggerModel;
1080