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