/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @fileoverview Provides communication interface to remote v8 debugger. See
* protocol decription at http://code.google.com/p/v8/wiki/DebuggerProtocol
*/
/**
* FIXME: change field naming style to use trailing underscore.
* @constructor
*/
devtools.DebuggerAgent = function()
{
RemoteDebuggerAgent.debuggerOutput = this.handleDebuggerOutput_.bind(this);
RemoteDebuggerAgent.setContextId = this.setContextId_.bind(this);
/**
* Id of the inspected page global context. It is used for filtering scripts.
* @type {number}
*/
this.contextId_ = null;
/**
* Mapping from script id to script info.
* @type {Object}
*/
this.parsedScripts_ = null;
/**
* Mapping from the request id to the devtools.BreakpointInfo for the
* breakpoints whose v8 ids are not set yet. These breakpoints are waiting for
* "setbreakpoint" responses to learn their ids in the v8 debugger.
* @see #handleSetBreakpointResponse_
* @type {Object}
*/
this.requestNumberToBreakpointInfo_ = null;
/**
* Information on current stack frames.
* @type {Array.<devtools.CallFrame>}
*/
this.callFrames_ = [];
/**
* Whether to stop in the debugger on the exceptions.
* @type {boolean}
*/
this.pauseOnExceptions_ = false;
/**
* Mapping: request sequence number->callback.
* @type {Object}
*/
this.requestSeqToCallback_ = null;
/**
* Whether the scripts panel has been shown and initialilzed.
* @type {boolean}
*/
this.scriptsPanelInitialized_ = false;
/**
* Whether the scripts list should be requested next time when context id is
* set.
* @type {boolean}
*/
this.requestScriptsWhenContextIdSet_ = false;
/**
* Whether the agent is waiting for initial scripts response.
* @type {boolean}
*/
this.waitingForInitialScriptsResponse_ = false;
/**
* If backtrace response is received when initial scripts response
* is not yet processed the backtrace handling will be postponed until
* after the scripts response processing. The handler bound to its arguments
* and this agent will be stored in this field then.
* @type {?function()}
*/
this.pendingBacktraceResponseHandler_ = null;
/**
* Container of all breakpoints set using resource URL. These breakpoints
* survive page reload. Breakpoints set by script id(for scripts that don't
* have URLs) are stored in ScriptInfo objects.
* @type {Object}
*/
this.urlToBreakpoints_ = {};
/**
* Exception message that is shown to user while on exception break.
* @type {WebInspector.ConsoleMessage}
*/
this.currentExceptionMessage_ = null;
};
/**
* A copy of the scope types from v8/src/mirror-delay.js
* @enum {number}
*/
devtools.DebuggerAgent.ScopeType = {
Global: 0,
Local: 1,
With: 2,
Closure: 3,
Catch: 4
};
/**
* Resets debugger agent to its initial state.
*/
devtools.DebuggerAgent.prototype.reset = function()
{
this.contextId_ = null;
// No need to request scripts since they all will be pushed in AfterCompile
// events.
this.requestScriptsWhenContextIdSet_ = false;
this.waitingForInitialScriptsResponse_ = false;
this.parsedScripts_ = {};
this.requestNumberToBreakpointInfo_ = {};
this.callFrames_ = [];
this.requestSeqToCallback_ = {};
};
/**
* Initializes scripts UI. This method is called every time Scripts panel
* is shown. It will send request for context id if it's not set yet.
*/
devtools.DebuggerAgent.prototype.initUI = function()
{
// Initialize scripts cache when Scripts panel is shown first time.
if (this.scriptsPanelInitialized_)
return;
this.scriptsPanelInitialized_ = true;
if (this.contextId_) {
// We already have context id. This means that we are here from the
// very beginning of the page load cycle and hence will get all scripts
// via after-compile events. No need to request scripts for this session.
//
// There can be a number of scripts from after-compile events that are
// pending addition into the UI.
for (var scriptId in this.parsedScripts_) {
var script = this.parsedScripts_[scriptId];
WebInspector.parsedScriptSource(scriptId, script.getUrl(), undefined /* script source */, script.getLineOffset());
}
return;
}
this.waitingForInitialScriptsResponse_ = true;
// Script list should be requested only when current context id is known.
RemoteDebuggerAgent.getContextId();
this.requestScriptsWhenContextIdSet_ = true;
};
/**
* Asynchronously requests the debugger for the script source.
* @param {number} scriptId Id of the script whose source should be resolved.
* @param {function(source:?string):void} callback Function that will be called
* when the source resolution is completed. "source" parameter will be null
* if the resolution fails.
*/
devtools.DebuggerAgent.prototype.resolveScriptSource = function(scriptId, callback)
{
var script = this.parsedScripts_[scriptId];
if (!script || script.isUnresolved()) {
callback(null);
return;
}
var cmd = new devtools.DebugCommand("scripts", {
"ids": [scriptId],
"includeSource": true
});
devtools.DebuggerAgent.sendCommand_(cmd);
// Force v8 execution so that it gets to processing the requested command.
RemoteDebuggerAgent.processDebugCommands();
this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
if (msg.isSuccess()) {
var scriptJson = msg.getBody()[0];
if (scriptJson)
callback(scriptJson.source);
else
callback(null);
} else
callback(null);
};
};
/**
* Tells the v8 debugger to stop on as soon as possible.
*/
devtools.DebuggerAgent.prototype.pauseExecution = function()
{
RemoteDebuggerCommandExecutor.DebuggerPauseScript();
};
/**
* @param {number} sourceId Id of the script fot the breakpoint.
* @param {number} line Number of the line for the breakpoint.
* @param {?string} condition The breakpoint condition.
*/
devtools.DebuggerAgent.prototype.addBreakpoint = function(sourceId, line, condition)
{
var script = this.parsedScripts_[sourceId];
if (!script)
return;
line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
var commandArguments;
if (script.getUrl()) {
var breakpoints = this.urlToBreakpoints_[script.getUrl()];
if (breakpoints && breakpoints[line])
return;
if (!breakpoints) {
breakpoints = {};
this.urlToBreakpoints_[script.getUrl()] = breakpoints;
}
var breakpointInfo = new devtools.BreakpointInfo(line);
breakpoints[line] = breakpointInfo;
commandArguments = {
"groupId": this.contextId_,
"type": "script",
"target": script.getUrl(),
"line": line,
"condition": condition
};
} else {
var breakpointInfo = script.getBreakpointInfo(line);
if (breakpointInfo)
return;
breakpointInfo = new devtools.BreakpointInfo(line);
script.addBreakpointInfo(breakpointInfo);
commandArguments = {
"groupId": this.contextId_,
"type": "scriptId",
"target": sourceId,
"line": line,
"condition": condition
};
}
var cmd = new devtools.DebugCommand("setbreakpoint", commandArguments);
this.requestNumberToBreakpointInfo_[cmd.getSequenceNumber()] = breakpointInfo;
devtools.DebuggerAgent.sendCommand_(cmd);
// Force v8 execution so that it gets to processing the requested command.
// It is necessary for being able to change a breakpoint just after it
// has been created (since we need an existing breakpoint id for that).
RemoteDebuggerAgent.processDebugCommands();
};
/**
* @param {number} sourceId Id of the script for the breakpoint.
* @param {number} line Number of the line for the breakpoint.
*/
devtools.DebuggerAgent.prototype.removeBreakpoint = function(sourceId, line)
{
var script = this.parsedScripts_[sourceId];
if (!script)
return;
line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
var breakpointInfo;
if (script.getUrl()) {
var breakpoints = this.urlToBreakpoints_[script.getUrl()];
breakpointInfo = breakpoints[line];
delete breakpoints[line];
} else {
breakpointInfo = script.getBreakpointInfo(line);
if (breakpointInfo)
script.removeBreakpointInfo(breakpointInfo);
}
if (!breakpointInfo)
return;
breakpointInfo.markAsRemoved();
var id = breakpointInfo.getV8Id();
// If we don't know id of this breakpoint in the v8 debugger we cannot send
// "clearbreakpoint" request. In that case it will be removed in
// "setbreakpoint" response handler when we learn the id.
if (id !== -1) {
this.requestClearBreakpoint_(id);
}
};
/**
* @param {number} sourceId Id of the script for the breakpoint.
* @param {number} line Number of the line for the breakpoint.
* @param {?string} condition New breakpoint condition.
*/
devtools.DebuggerAgent.prototype.updateBreakpoint = function(sourceId, line, condition)
{
var script = this.parsedScripts_[sourceId];
if (!script)
return;
line = devtools.DebuggerAgent.webkitToV8LineNumber_(line);
var breakpointInfo;
if (script.getUrl()) {
var breakpoints = this.urlToBreakpoints_[script.getUrl()];
breakpointInfo = breakpoints[line];
} else
breakpointInfo = script.getBreakpointInfo(line);
var id = breakpointInfo.getV8Id();
// If we don't know id of this breakpoint in the v8 debugger we cannot send
// the "changebreakpoint" request.
if (id !== -1) {
// TODO(apavlov): make use of the real values for "enabled" and
// "ignoreCount" when appropriate.
this.requestChangeBreakpoint_(id, true, condition, null);
}
};
/**
* Tells the v8 debugger to step into the next statement.
*/
devtools.DebuggerAgent.prototype.stepIntoStatement = function()
{
this.stepCommand_("in");
};
/**
* Tells the v8 debugger to step out of current function.
*/
devtools.DebuggerAgent.prototype.stepOutOfFunction = function()
{
this.stepCommand_("out");
};
/**
* Tells the v8 debugger to step over the next statement.
*/
devtools.DebuggerAgent.prototype.stepOverStatement = function()
{
this.stepCommand_("next");
};
/**
* Tells the v8 debugger to continue execution after it has been stopped on a
* breakpoint or an exception.
*/
devtools.DebuggerAgent.prototype.resumeExecution = function()
{
this.clearExceptionMessage_();
var cmd = new devtools.DebugCommand("continue");
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Creates exception message and schedules it for addition to the resource upon
* backtrace availability.
* @param {string} url Resource url.
* @param {number} line Resource line number.
* @param {string} message Exception text.
*/
devtools.DebuggerAgent.prototype.createExceptionMessage_ = function(url, line, message)
{
this.currentExceptionMessage_ = new WebInspector.ConsoleMessage(
WebInspector.ConsoleMessage.MessageSource.JS,
WebInspector.ConsoleMessage.MessageType.Log,
WebInspector.ConsoleMessage.MessageLevel.Error,
line,
url,
0 /* group level */,
1 /* repeat count */,
"[Exception] " + message);
};
/**
* Shows pending exception message that is created with createExceptionMessage_
* earlier.
*/
devtools.DebuggerAgent.prototype.showPendingExceptionMessage_ = function()
{
if (!this.currentExceptionMessage_)
return;
var msg = this.currentExceptionMessage_;
var resource = WebInspector.resourceURLMap[msg.url];
if (resource) {
msg.resource = resource;
WebInspector.panels.resources.addMessageToResource(resource, msg);
} else
this.currentExceptionMessage_ = null;
};
/**
* Clears exception message from the resource.
*/
devtools.DebuggerAgent.prototype.clearExceptionMessage_ = function()
{
if (this.currentExceptionMessage_) {
var messageElement = this.currentExceptionMessage_._resourceMessageLineElement;
var bubble = messageElement.parentElement;
bubble.removeChild(messageElement);
if (!bubble.firstChild) {
// Last message in bubble removed.
bubble.parentElement.removeChild(bubble);
}
this.currentExceptionMessage_ = null;
}
};
/**
* @return {boolean} True iff the debugger will pause execution on the
* exceptions.
*/
devtools.DebuggerAgent.prototype.pauseOnExceptions = function()
{
return this.pauseOnExceptions_;
};
/**
* Tells whether to pause in the debugger on the exceptions or not.
* @param {boolean} value True iff execution should be stopped in the debugger
* on the exceptions.
*/
devtools.DebuggerAgent.prototype.setPauseOnExceptions = function(value)
{
this.pauseOnExceptions_ = value;
};
/**
* Sends "evaluate" request to the debugger.
* @param {Object} arguments Request arguments map.
* @param {function(devtools.DebuggerMessage)} callback Callback to be called
* when response is received.
*/
devtools.DebuggerAgent.prototype.requestEvaluate = function(arguments, callback)
{
var cmd = new devtools.DebugCommand("evaluate", arguments);
devtools.DebuggerAgent.sendCommand_(cmd);
this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback;
};
/**
* Sends "lookup" request for each unresolved property of the object. When
* response is received the properties will be changed with their resolved
* values.
* @param {Object} object Object whose properties should be resolved.
* @param {function(devtools.DebuggerMessage)} Callback to be called when all
* children are resolved.
* @param {boolean} noIntrinsic Whether intrinsic properties should be included.
*/
devtools.DebuggerAgent.prototype.resolveChildren = function(object, callback, noIntrinsic)
{
if ("handle" in object) {
var result = [];
devtools.DebuggerAgent.formatObjectProperties_(object, result, noIntrinsic);
callback(result);
} else {
this.requestLookup_([object.ref], function(msg) {
var result = [];
if (msg.isSuccess()) {
var handleToObject = msg.getBody();
var resolved = handleToObject[object.ref];
devtools.DebuggerAgent.formatObjectProperties_(resolved, result, noIntrinsic);
callback(result);
} else
callback([]);
});
}
};
/**
* Sends "scope" request for the scope object to resolve its variables.
* @param {Object} scope Scope to be resolved.
* @param {function(Array.<WebInspector.ObjectPropertyProxy>)} callback
* Callback to be called when all scope variables are resolved.
*/
devtools.DebuggerAgent.prototype.resolveScope = function(scope, callback)
{
var cmd = new devtools.DebugCommand("scope", {
"frameNumber": scope.frameNumber,
"number": scope.index,
"compactFormat": true
});
devtools.DebuggerAgent.sendCommand_(cmd);
this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
var result = [];
if (msg.isSuccess()) {
var scopeObjectJson = msg.getBody().object;
devtools.DebuggerAgent.formatObjectProperties_(scopeObjectJson, result, true /* no intrinsic */);
}
callback(result);
};
};
/**
* Sends "scopes" request for the frame object to resolve all variables
* available in the frame.
* @param {number} callFrameId Id of call frame whose variables need to
* be resolved.
* @param {function(Object)} callback Callback to be called when all frame
* variables are resolved.
*/
devtools.DebuggerAgent.prototype.resolveFrameVariables_ = function(callFrameId, callback)
{
var result = {};
var frame = this.callFrames_[callFrameId];
if (!frame) {
callback(result);
return;
}
var waitingResponses = 0;
function scopeResponseHandler(msg) {
waitingResponses--;
if (msg.isSuccess()) {
var properties = msg.getBody().object.properties;
for (var j = 0; j < properties.length; j++)
result[properties[j].name] = true;
}
// When all scopes are resolved invoke the callback.
if (waitingResponses === 0)
callback(result);
};
for (var i = 0; i < frame.scopeChain.length; i++) {
var scope = frame.scopeChain[i].objectId;
if (scope.type === devtools.DebuggerAgent.ScopeType.Global) {
// Do not resolve global scope since it takes for too long.
// TODO(yurys): allow to send only property names in the response.
continue;
}
var cmd = new devtools.DebugCommand("scope", {
"frameNumber": scope.frameNumber,
"number": scope.index,
"compactFormat": true
});
devtools.DebuggerAgent.sendCommand_(cmd);
this.requestSeqToCallback_[cmd.getSequenceNumber()] = scopeResponseHandler;
waitingResponses++;
}
};
/**
* Evaluates the expressionString to an object in the call frame and reports
* all its properties.
* @param{string} expressionString Expression whose properties should be
* collected.
* @param{number} callFrameId The frame id.
* @param{function(Object result,bool isException)} reportCompletions Callback
* function.
*/
devtools.DebuggerAgent.prototype.resolveCompletionsOnFrame = function(expressionString, callFrameId, reportCompletions)
{
if (expressionString) {
expressionString = "var obj = " + expressionString +
"; var names = {}; for (var n in obj) { names[n] = true; };" +
"names;";
this.evaluateInCallFrame(
callFrameId,
expressionString,
function(result) {
var names = {};
if (!result.isException) {
var props = result.value.objectId.properties;
// Put all object properties into the map.
for (var i = 0; i < props.length; i++)
names[props[i].name] = true;
}
reportCompletions(names, result.isException);
});
} else {
this.resolveFrameVariables_(callFrameId,
function(result) {
reportCompletions(result, false /* isException */);
});
}
};
/**
* @param{number} scriptId
* @return {string} Type of the context of the script with specified id.
*/
devtools.DebuggerAgent.prototype.getScriptContextType = function(scriptId)
{
return this.parsedScripts_[scriptId].getContextType();
};
/**
* Removes specified breakpoint from the v8 debugger.
* @param {number} breakpointId Id of the breakpoint in the v8 debugger.
*/
devtools.DebuggerAgent.prototype.requestClearBreakpoint_ = function(breakpointId)
{
var cmd = new devtools.DebugCommand("clearbreakpoint", {
"breakpoint": breakpointId
});
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Changes breakpoint parameters in the v8 debugger.
* @param {number} breakpointId Id of the breakpoint in the v8 debugger.
* @param {boolean} enabled Whether to enable the breakpoint.
* @param {?string} condition New breakpoint condition.
* @param {number} ignoreCount New ignore count for the breakpoint.
*/
devtools.DebuggerAgent.prototype.requestChangeBreakpoint_ = function(breakpointId, enabled, condition, ignoreCount)
{
var cmd = new devtools.DebugCommand("changebreakpoint", {
"breakpoint": breakpointId,
"enabled": enabled,
"condition": condition,
"ignoreCount": ignoreCount
});
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Sends "backtrace" request to v8.
*/
devtools.DebuggerAgent.prototype.requestBacktrace_ = function()
{
var cmd = new devtools.DebugCommand("backtrace", {
"compactFormat":true
});
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Sends command to v8 debugger.
* @param {devtools.DebugCommand} cmd Command to execute.
*/
devtools.DebuggerAgent.sendCommand_ = function(cmd)
{
RemoteDebuggerCommandExecutor.DebuggerCommand(cmd.toJSONProtocol());
};
/**
* Tells the v8 debugger to make the next execution step.
* @param {string} action "in", "out" or "next" action.
*/
devtools.DebuggerAgent.prototype.stepCommand_ = function(action)
{
this.clearExceptionMessage_();
var cmd = new devtools.DebugCommand("continue", {
"stepaction": action,
"stepcount": 1
});
devtools.DebuggerAgent.sendCommand_(cmd);
};
/**
* Sends "lookup" request to v8.
* @param {number} handle Handle to the object to lookup.
*/
devtools.DebuggerAgent.prototype.requestLookup_ = function(handles, callback)
{
var cmd = new devtools.DebugCommand("lookup", {
"compactFormat":true,
"handles": handles
});
devtools.DebuggerAgent.sendCommand_(cmd);
this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback;
};
/**
* Sets debugger context id for scripts filtering.
* @param {number} contextId Id of the inspected page global context.
*/
devtools.DebuggerAgent.prototype.setContextId_ = function(contextId)
{
this.contextId_ = contextId;
// If it's the first time context id is set request scripts list.
if (this.requestScriptsWhenContextIdSet_) {
this.requestScriptsWhenContextIdSet_ = false;
var cmd = new devtools.DebugCommand("scripts", {
"includeSource": false
});
devtools.DebuggerAgent.sendCommand_(cmd);
// Force v8 execution so that it gets to processing the requested command.
RemoteDebuggerAgent.processDebugCommands();
var debuggerAgent = this;
this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) {
// Handle the response iff the context id hasn't changed since the request
// was issued. Otherwise if the context id did change all up-to-date
// scripts will be pushed in after compile events and there is no need to
// handle the response.
if (contextId === debuggerAgent.contextId_)
debuggerAgent.handleScriptsResponse_(msg);
// We received initial scripts response so flush the flag and
// see if there is an unhandled backtrace response.
debuggerAgent.waitingForInitialScriptsResponse_ = false;
if (debuggerAgent.pendingBacktraceResponseHandler_) {
debuggerAgent.pendingBacktraceResponseHandler_();
debuggerAgent.pendingBacktraceResponseHandler_ = null;
}
};
}
};
/**
* Handles output sent by v8 debugger. The output is either asynchronous event
* or response to a previously sent request. See protocol definitioun for more
* details on the output format.
* @param {string} output
*/
devtools.DebuggerAgent.prototype.handleDebuggerOutput_ = function(output)
{
var msg;
try {
msg = new devtools.DebuggerMessage(output);
} catch(e) {
debugPrint("Failed to handle debugger response:\n" + e);
throw e;
}
if (msg.getType() === "event") {
if (msg.getEvent() === "break")
this.handleBreakEvent_(msg);
else if (msg.getEvent() === "exception")
this.handleExceptionEvent_(msg);
else if (msg.getEvent() === "afterCompile")
this.handleAfterCompileEvent_(msg);
} else if (msg.getType() === "response") {
if (msg.getCommand() === "scripts")
this.invokeCallbackForResponse_(msg);
else if (msg.getCommand() === "setbreakpoint")
this.handleSetBreakpointResponse_(msg);
else if (msg.getCommand() === "clearbreakpoint")
this.handleClearBreakpointResponse_(msg);
else if (msg.getCommand() === "backtrace")
this.handleBacktraceResponse_(msg);
else if (msg.getCommand() === "lookup")
this.invokeCallbackForResponse_(msg);
else if (msg.getCommand() === "evaluate")
this.invokeCallbackForResponse_(msg);
else if (msg.getCommand() === "scope")
this.invokeCallbackForResponse_(msg);
}
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleBreakEvent_ = function(msg)
{
// Force scrips panel to be shown first.
WebInspector.currentPanel = WebInspector.panels.scripts;
var body = msg.getBody();
var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine);
this.requestBacktrace_();
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleExceptionEvent_ = function(msg)
{
// Force scrips panel to be shown first.
WebInspector.currentPanel = WebInspector.panels.scripts;
var body = msg.getBody();
// No script field in the body means that v8 failed to parse the script. We
// resume execution on parser errors automatically.
if (this.pauseOnExceptions_ && body.script) {
var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine);
this.createExceptionMessage_(body.script.name, line, body.exception.text);
this.requestBacktrace_();
} else
this.resumeExecution();
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleScriptsResponse_ = function(msg)
{
var scripts = msg.getBody();
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
// Skip scripts from other tabs.
if (!this.isScriptFromInspectedContext_(script, msg))
continue;
// We may already have received the info in an afterCompile event.
if (script.id in this.parsedScripts_)
continue;
this.addScriptInfo_(script, msg);
}
};
/**
* @param {Object} script Json object representing script.
* @param {devtools.DebuggerMessage} msg Debugger response.
*/
devtools.DebuggerAgent.prototype.isScriptFromInspectedContext_ = function(script, msg)
{
if (!script.context) {
// Always ignore scripts from the utility context.
return false;
}
var context = msg.lookup(script.context.ref);
var scriptContextId = context.data;
if (typeof scriptContextId === "undefined")
return false; // Always ignore scripts from the utility context.
if (this.contextId_ === null)
return true;
// Find the id from context data. The context data has the format "type,id".
var comma = context.data.indexOf(",");
if (comma < 0)
return false;
return (context.data.substring(comma + 1) == this.contextId_);
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleSetBreakpointResponse_ = function(msg)
{
var requestSeq = msg.getRequestSeq();
var breakpointInfo = this.requestNumberToBreakpointInfo_[requestSeq];
if (!breakpointInfo) {
// TODO(yurys): handle this case
return;
}
delete this.requestNumberToBreakpointInfo_[requestSeq];
if (!msg.isSuccess()) {
// TODO(yurys): handle this case
return;
}
var idInV8 = msg.getBody().breakpoint;
breakpointInfo.setV8Id(idInV8);
if (breakpointInfo.isRemoved())
this.requestClearBreakpoint_(idInV8);
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleAfterCompileEvent_ = function(msg)
{
if (!this.contextId_) {
// Ignore scripts delta if main request has not been issued yet.
return;
}
var script = msg.getBody().script;
// Ignore scripts from other tabs.
if (!this.isScriptFromInspectedContext_(script, msg))
return;
this.addScriptInfo_(script, msg);
};
/**
* Adds the script info to the local cache. This method assumes that the script
* is not in the cache yet.
* @param {Object} script Script json object from the debugger message.
* @param {devtools.DebuggerMessage} msg Debugger message containing the script
* data.
*/
devtools.DebuggerAgent.prototype.addScriptInfo_ = function(script, msg)
{
var context = msg.lookup(script.context.ref);
var contextType;
// Find the type from context data. The context data has the format
// "type,id".
var comma = context.data.indexOf(",");
if (comma < 0)
return
contextType = context.data.substring(0, comma);
this.parsedScripts_[script.id] = new devtools.ScriptInfo(script.id, script.name, script.lineOffset, contextType);
if (this.scriptsPanelInitialized_) {
// Only report script as parsed after scripts panel has been shown.
WebInspector.parsedScriptSource(script.id, script.name, script.source, script.lineOffset);
}
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleClearBreakpointResponse_ = function(msg)
{
// Do nothing.
};
/**
* Handles response to "backtrace" command.
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.handleBacktraceResponse_ = function(msg)
{
if (this.waitingForInitialScriptsResponse_)
this.pendingBacktraceResponseHandler_ = this.doHandleBacktraceResponse_.bind(this, msg);
else
this.doHandleBacktraceResponse_(msg);
};
/**
* @param {devtools.DebuggerMessage} msg
*/
devtools.DebuggerAgent.prototype.doHandleBacktraceResponse_ = function(msg)
{
var frames = msg.getBody().frames;
this.callFrames_ = [];
for (var i = 0; i < frames.length; ++i)
this.callFrames_.push(this.formatCallFrame_(frames[i]));
WebInspector.pausedScript(this.callFrames_);
this.showPendingExceptionMessage_();
InspectorFrontendHost.activateWindow();
};
/**
* Evaluates code on given callframe.
*/
devtools.DebuggerAgent.prototype.evaluateInCallFrame = function(callFrameId, code, callback)
{
var callFrame = this.callFrames_[callFrameId];
callFrame.evaluate_(code, callback);
};
/**
* Handles response to a command by invoking its callback (if any).
* @param {devtools.DebuggerMessage} msg
* @return {boolean} Whether a callback for the given message was found and
* excuted.
*/
devtools.DebuggerAgent.prototype.invokeCallbackForResponse_ = function(msg)
{
var callback = this.requestSeqToCallback_[msg.getRequestSeq()];
if (!callback) {
// It may happend if reset was called.
return false;
}
delete this.requestSeqToCallback_[msg.getRequestSeq()];
callback(msg);
return true;
};
/**
* @param {Object} stackFrame Frame json object from "backtrace" response.
* @return {!devtools.CallFrame} Object containing information related to the
* call frame in the format expected by ScriptsPanel and its panes.
*/
devtools.DebuggerAgent.prototype.formatCallFrame_ = function(stackFrame)
{
var func = stackFrame.func;
var sourceId = func.scriptId;
// Add service script if it does not exist.
var existingScript = this.parsedScripts_[sourceId];
if (!existingScript) {
this.parsedScripts_[sourceId] = new devtools.ScriptInfo(sourceId, null /* name */, 0 /* line */, "unknown" /* type */, true /* unresolved */);
WebInspector.parsedScriptSource(sourceId, null, null, 0);
}
var funcName = func.name || func.inferredName || "(anonymous function)";
var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(stackFrame.line);
// Add basic scope chain info with scope variables.
var scopeChain = [];
var ScopeType = devtools.DebuggerAgent.ScopeType;
for (var i = 0; i < stackFrame.scopes.length; i++) {
var scope = stackFrame.scopes[i];
scope.frameNumber = stackFrame.index;
var scopeObjectProxy = new WebInspector.ObjectProxy(0, scope, [], 0, "", true);
scopeObjectProxy.isScope = true;
switch(scope.type) {
case ScopeType.Global:
scopeObjectProxy.isDocument = true;
break;
case ScopeType.Local:
scopeObjectProxy.isLocal = true;
scopeObjectProxy.thisObject = devtools.DebuggerAgent.formatObjectProxy_(stackFrame.receiver);
break;
case ScopeType.With:
// Catch scope is treated as a regular with scope by WebKit so we
// also treat it this way.
case ScopeType.Catch:
scopeObjectProxy.isWithBlock = true;
break;
case ScopeType.Closure:
scopeObjectProxy.isClosure = true;
break;
}
scopeChain.push(scopeObjectProxy);
}
return new devtools.CallFrame(stackFrame.index, "function", funcName, sourceId, line, scopeChain);
};
/**
* Collects properties for an object from the debugger response.
* @param {Object} object An object from the debugger protocol response.
* @param {Array.<WebInspector.ObjectPropertyProxy>} result An array to put the
* properties into.
* @param {boolean} noIntrinsic Whether intrinsic properties should be
* included.
*/
devtools.DebuggerAgent.formatObjectProperties_ = function(object, result, noIntrinsic)
{
devtools.DebuggerAgent.propertiesToProxies_(object.properties, result);
if (noIntrinsic)
return;
result.push(new WebInspector.ObjectPropertyProxy("__proto__", devtools.DebuggerAgent.formatObjectProxy_(object.protoObject)));
result.push(new WebInspector.ObjectPropertyProxy("constructor", devtools.DebuggerAgent.formatObjectProxy_(object.constructorFunction)));
// Don't add 'prototype' property since it is one of the regualar properties.
};
/**
* For each property in "properties" creates its proxy representative.
* @param {Array.<Object>} properties Receiver properties or locals array from
* "backtrace" response.
* @param {Array.<WebInspector.ObjectPropertyProxy>} Results holder.
*/
devtools.DebuggerAgent.propertiesToProxies_ = function(properties, result)
{
var map = {};
for (var i = 0; i < properties.length; ++i) {
var property = properties[i];
var name = String(property.name);
if (name in map)
continue;
map[name] = true;
var value = devtools.DebuggerAgent.formatObjectProxy_(property.value);
var propertyProxy = new WebInspector.ObjectPropertyProxy(name, value);
result.push(propertyProxy);
}
};
/**
* @param {Object} v An object reference from the debugger response.
* @return {*} The value representation expected by ScriptsPanel.
*/
devtools.DebuggerAgent.formatObjectProxy_ = function(v)
{
var description;
var hasChildren = false;
if (v.type === "object") {
description = v.className;
hasChildren = true;
} else if (v.type === "function") {
if (v.source)
description = v.source;
else
description = "function " + v.name + "()";
hasChildren = true;
} else if (v.type === "undefined")
description = "undefined";
else if (v.type === "null")
description = "null";
else if (typeof v.value !== "undefined") {
// Check for undefined and null types before checking the value, otherwise
// null/undefined may have blank value.
description = v.value;
} else
description = "<unresolved ref: " + v.ref + ", type: " + v.type + ">";
var proxy = new WebInspector.ObjectProxy(0, v, [], 0, description, hasChildren);
proxy.type = v.type;
proxy.isV8Ref = true;
return proxy;
};
/**
* Converts line number from Web Inspector UI(1-based) to v8(0-based).
* @param {number} line Resource line number in Web Inspector UI.
* @return {number} The line number in v8.
*/
devtools.DebuggerAgent.webkitToV8LineNumber_ = function(line)
{
return line - 1;
};
/**
* Converts line number from v8(0-based) to Web Inspector UI(1-based).
* @param {number} line Resource line number in v8.
* @return {number} The line number in Web Inspector.
*/
devtools.DebuggerAgent.v8ToWwebkitLineNumber_ = function(line)
{
return line + 1;
};
/**
* @param {number} scriptId Id of the script.
* @param {?string} url Script resource URL if any.
* @param {number} lineOffset First line 0-based offset in the containing
* document.
* @param {string} contextType Type of the script's context:
* "page" - regular script from html page
* "injected" - extension content script
* @param {bool} opt_isUnresolved If true, script will not be resolved.
* @constructor
*/
devtools.ScriptInfo = function(scriptId, url, lineOffset, contextType, opt_isUnresolved)
{
this.scriptId_ = scriptId;
this.lineOffset_ = lineOffset;
this.contextType_ = contextType;
this.url_ = url;
this.isUnresolved_ = opt_isUnresolved;
this.lineToBreakpointInfo_ = {};
};
/**
* @return {number}
*/
devtools.ScriptInfo.prototype.getLineOffset = function()
{
return this.lineOffset_;
};
/**
* @return {string}
*/
devtools.ScriptInfo.prototype.getContextType = function()
{
return this.contextType_;
};
/**
* @return {?string}
*/
devtools.ScriptInfo.prototype.getUrl = function()
{
return this.url_;
};
/**
* @return {?bool}
*/
devtools.ScriptInfo.prototype.isUnresolved = function()
{
return this.isUnresolved_;
};
/**
* @param {number} line 0-based line number in the script.
* @return {?devtools.BreakpointInfo} Information on a breakpoint at the
* specified line in the script or undefined if there is no breakpoint at
* that line.
*/
devtools.ScriptInfo.prototype.getBreakpointInfo = function(line)
{
return this.lineToBreakpointInfo_[line];
};
/**
* Adds breakpoint info to the script.
* @param {devtools.BreakpointInfo} breakpoint
*/
devtools.ScriptInfo.prototype.addBreakpointInfo = function(breakpoint)
{
this.lineToBreakpointInfo_[breakpoint.getLine()] = breakpoint;
};
/**
* @param {devtools.BreakpointInfo} breakpoint Breakpoint info to be removed.
*/
devtools.ScriptInfo.prototype.removeBreakpointInfo = function(breakpoint)
{
var line = breakpoint.getLine();
delete this.lineToBreakpointInfo_[line];
};
/**
* @param {number} line Breakpoint 0-based line number in the containing script.
* @constructor
*/
devtools.BreakpointInfo = function(line)
{
this.line_ = line;
this.v8id_ = -1;
this.removed_ = false;
};
/**
* @return {number}
*/
devtools.BreakpointInfo.prototype.getLine = function(n)
{
return this.line_;
};
/**
* @return {number} Unique identifier of this breakpoint in the v8 debugger.
*/
devtools.BreakpointInfo.prototype.getV8Id = function(n)
{
return this.v8id_;
};
/**
* Sets id of this breakpoint in the v8 debugger.
* @param {number} id
*/
devtools.BreakpointInfo.prototype.setV8Id = function(id)
{
this.v8id_ = id;
};
/**
* Marks this breakpoint as removed from the front-end.
*/
devtools.BreakpointInfo.prototype.markAsRemoved = function()
{
this.removed_ = true;
};
/**
* @return {boolean} Whether this breakpoint has been removed from the
* front-end.
*/
devtools.BreakpointInfo.prototype.isRemoved = function()
{
return this.removed_;
};
/**
* Call stack frame data.
* @param {string} id CallFrame id.
* @param {string} type CallFrame type.
* @param {string} functionName CallFrame type.
* @param {string} sourceID Source id.
* @param {number} line Source line.
* @param {Array.<Object>} scopeChain Array of scoped objects.
* @construnctor
*/
devtools.CallFrame = function(id, type, functionName, sourceID, line, scopeChain)
{
this.id = id;
this.type = type;
this.functionName = functionName;
this.sourceID = sourceID;
this.line = line;
this.scopeChain = scopeChain;
};
/**
* This method issues asynchronous evaluate request, reports result to the
* callback.
* @param {string} expression An expression to be evaluated in the context of
* this call frame.
* @param {function(Object):undefined} callback Callback to report result to.
*/
devtools.CallFrame.prototype.evaluate_ = function(expression, callback)
{
devtools.tools.getDebuggerAgent().requestEvaluate({
"expression": expression,
"frame": this.id,
"global": false,
"disable_break": false,
"compactFormat": true
},
function(response) {
var result = {};
if (response.isSuccess())
result.value = devtools.DebuggerAgent.formatObjectProxy_(response.getBody());
else {
result.value = response.getMessage();
result.isException = true;
}
callback(result);
});
};
/**
* JSON based commands sent to v8 debugger.
* @param {string} command Name of the command to execute.
* @param {Object} opt_arguments Command-specific arguments map.
* @constructor
*/
devtools.DebugCommand = function(command, opt_arguments)
{
this.command_ = command;
this.type_ = "request";
this.seq_ = ++devtools.DebugCommand.nextSeq_;
if (opt_arguments)
this.arguments_ = opt_arguments;
};
/**
* Next unique number to be used as debugger request sequence number.
* @type {number}
*/
devtools.DebugCommand.nextSeq_ = 1;
/**
* @return {number}
*/
devtools.DebugCommand.prototype.getSequenceNumber = function()
{
return this.seq_;
};
/**
* @return {string}
*/
devtools.DebugCommand.prototype.toJSONProtocol = function()
{
var json = {
"seq": this.seq_,
"type": this.type_,
"command": this.command_
}
if (this.arguments_)
json.arguments = this.arguments_;
return JSON.stringify(json);
};
/**
* JSON messages sent from v8 debugger. See protocol definition for more
* details: http://code.google.com/p/v8/wiki/DebuggerProtocol
* @param {string} msg Raw protocol packet as JSON string.
* @constructor
*/
devtools.DebuggerMessage = function(msg)
{
this.packet_ = JSON.parse(msg);
this.refs_ = [];
if (this.packet_.refs) {
for (var i = 0; i < this.packet_.refs.length; i++)
this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i];
}
};
/**
* @return {string} The packet type.
*/
devtools.DebuggerMessage.prototype.getType = function()
{
return this.packet_.type;
};
/**
* @return {?string} The packet event if the message is an event.
*/
devtools.DebuggerMessage.prototype.getEvent = function()
{
return this.packet_.event;
};
/**
* @return {?string} The packet command if the message is a response to a
* command.
*/
devtools.DebuggerMessage.prototype.getCommand = function()
{
return this.packet_.command;
};
/**
* @return {number} The packet request sequence.
*/
devtools.DebuggerMessage.prototype.getRequestSeq = function()
{
return this.packet_.request_seq;
};
/**
* @return {number} Whether the v8 is running after processing the request.
*/
devtools.DebuggerMessage.prototype.isRunning = function()
{
return this.packet_.running ? true : false;
};
/**
* @return {boolean} Whether the request succeeded.
*/
devtools.DebuggerMessage.prototype.isSuccess = function()
{
return this.packet_.success ? true : false;
};
/**
* @return {string}
*/
devtools.DebuggerMessage.prototype.getMessage = function()
{
return this.packet_.message;
};
/**
* @return {Object} Parsed message body json.
*/
devtools.DebuggerMessage.prototype.getBody = function()
{
return this.packet_.body;
};
/**
* @param {number} handle Object handle.
* @return {?Object} Returns the object with the handle if it was sent in this
* message(some objects referenced by handles may be missing in the message).
*/
devtools.DebuggerMessage.prototype.lookup = function(handle)
{
return this.refs_[handle];
};