Bug 783499 - Web Console should use the debugger API; r=past

This commit is contained in:
Mihai Sucan 2013-03-30 13:31:10 +02:00
parent 73fe13b2fe
commit 6f15b1eb06
10 changed files with 591 additions and 378 deletions

View File

@ -404,6 +404,40 @@ WebConsole.prototype = {
}
},
/**
* Retrieve information about the JavaScript debugger's stackframes list. This
* is used to allow the Web Console to evaluate code in the selected
* stackframe.
*
* @return object|null
* An object which holds:
* - frames: the active ThreadClient.cachedFrames array.
* - selected: depth/index of the selected stackframe in the debugger
* UI.
* If the debugger is not open or if it's not paused, then |null| is
* returned.
*/
getDebuggerFrames: function WC_getDebuggerFrames()
{
let toolbox = gDevTools.getToolbox(this.target);
if (!toolbox) {
return null;
}
let panel = toolbox.getPanel("jsdebugger");
if (!panel) {
return null;
}
let framesController = panel.panelWin.gStackFrames;
let thread = framesController.activeThread;
if (thread && thread.paused) {
return {
frames: thread.cachedFrames,
selected: framesController.currentFrame,
};
}
return null;
},
/**
* Destroy the object. Call this method to avoid memory leaks when the Web
* Console is closed.

View File

@ -13,7 +13,8 @@ const Cr = Components.results;
this.EXPORTED_SYMBOLS = ["DebuggerTransport",
"DebuggerClient",
"debuggerSocketConnect",
"LongStringClient"];
"LongStringClient",
"GripClient"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
@ -173,7 +174,6 @@ const UnsolicitedNotifications = {
"consoleAPICall": "consoleAPICall",
"eventNotification": "eventNotification",
"fileActivity": "fileActivity",
"locationChange": "locationChange",
"networkEvent": "networkEvent",
"networkEventUpdate": "networkEventUpdate",
"newGlobal": "newGlobal",
@ -514,7 +514,7 @@ DebuggerClient.prototype = {
}
} catch(ex) {
dumpn("Error handling response: " + ex + " - stack:\n" + ex.stack);
Cu.reportError(ex.message + "\n" + ex.stack);
Cu.reportError(ex + "\n" + ex.stack);
}
this._sendRequests();

View File

@ -900,7 +900,7 @@ ThreadActor.prototype = {
return this.threadLifetimePool.objectActors.get(aValue).grip();
}
let actor = new ObjectActor(aValue, this);
let actor = new PauseScopedObjectActor(aValue, this);
aPool.addActor(actor);
aPool.objectActors.set(aValue, actor);
return actor.grip();
@ -1515,10 +1515,7 @@ function ObjectActor(aObj, aThreadActor)
this.threadActor = aThreadActor;
}
ObjectActor.prototype = Object.create(PauseScopedActor.prototype);
update(ObjectActor.prototype, {
constructor: ObjectActor,
ObjectActor.prototype = {
actorPrefix: "obj",
/**
@ -1552,7 +1549,9 @@ update(ObjectActor.prototype, {
* Releases this actor from the pool.
*/
release: function OA_release() {
this.registeredPool.objectActors.delete(this.obj);
if (this.registeredPool.objectActors) {
this.registeredPool.objectActors.delete(this.obj);
}
this.registeredPool.removeActor(this);
this.disconnect();
},
@ -1568,11 +1567,10 @@ update(ObjectActor.prototype, {
* @param aRequest object
* The protocol request object.
*/
onOwnPropertyNames:
PauseScopedActor.withPaused(function OA_onOwnPropertyNames(aRequest) {
onOwnPropertyNames: function OA_onOwnPropertyNames(aRequest) {
return { from: this.actorID,
ownPropertyNames: this.obj.getOwnPropertyNames() };
}),
},
/**
* Handle a protocol request to provide the prototype and own properties of
@ -1581,8 +1579,7 @@ update(ObjectActor.prototype, {
* @param aRequest object
* The protocol request object.
*/
onPrototypeAndProperties:
PauseScopedActor.withPaused(function OA_onPrototypeAndProperties(aRequest) {
onPrototypeAndProperties: function OA_onPrototypeAndProperties(aRequest) {
if (this.obj.proto) {
// Store the object and its prototype to the prototype chain cache, so that
// we can evaluate native getter methods for WebIDL attributes that are
@ -1608,7 +1605,7 @@ update(ObjectActor.prototype, {
return { from: this.actorID,
prototype: this.threadActor.createValueGrip(this.obj.proto),
ownProperties: ownProperties };
}),
},
/**
* Handle a protocol request to provide the prototype of the object.
@ -1616,10 +1613,10 @@ update(ObjectActor.prototype, {
* @param aRequest object
* The protocol request object.
*/
onPrototype: PauseScopedActor.withPaused(function OA_onPrototype(aRequest) {
onPrototype: function OA_onPrototype(aRequest) {
return { from: this.actorID,
prototype: this.threadActor.createValueGrip(this.obj.proto) };
}),
},
/**
* Handle a protocol request to provide the property descriptor of the
@ -1628,7 +1625,7 @@ update(ObjectActor.prototype, {
* @param aRequest object
* The protocol request object.
*/
onProperty: PauseScopedActor.withPaused(function OA_onProperty(aRequest) {
onProperty: function OA_onProperty(aRequest) {
if (!aRequest.name) {
return { error: "missingParameter",
message: "no property name was specified" };
@ -1636,7 +1633,7 @@ update(ObjectActor.prototype, {
return { from: this.actorID,
descriptor: this._propertyDescriptor(aRequest.name) };
}),
},
/**
* A helper method that creates a property descriptor for the provided object,
@ -1724,7 +1721,7 @@ update(ObjectActor.prototype, {
* @param aRequest object
* The protocol request object.
*/
onDecompile: PauseScopedActor.withPaused(function OA_onDecompile(aRequest) {
onDecompile: function OA_onDecompile(aRequest) {
if (this.obj.class !== "Function") {
return { error: "objectNotFunction",
message: "decompile request is only valid for object grips " +
@ -1733,7 +1730,75 @@ update(ObjectActor.prototype, {
return { from: this.actorID,
decompiledCode: this.obj.decompile(!!aRequest.pretty) };
}),
},
/**
* Handle a protocol request to provide the parameters of a function.
*
* @param aRequest object
* The protocol request object.
*/
onParameterNames: function OA_onParameterNames(aRequest) {
if (this.obj.class !== "Function") {
return { error: "objectNotFunction",
message: "'parameterNames' request is only valid for object " +
"grips with a 'Function' class." };
}
return { parameterNames: this.obj.parameterNames };
},
/**
* Handle a protocol request to release a thread-lifetime grip.
*
* @param aRequest object
* The protocol request object.
*/
onRelease: function OA_onRelease(aRequest) {
this.release();
return {};
},
};
ObjectActor.prototype.requestTypes = {
"parameterNames": ObjectActor.prototype.onParameterNames,
"prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
"prototype": ObjectActor.prototype.onPrototype,
"property": ObjectActor.prototype.onProperty,
"ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
"decompile": ObjectActor.prototype.onDecompile,
"release": ObjectActor.prototype.onRelease,
};
/**
* Creates a pause-scoped actor for the specified object.
* @see ObjectActor
*/
function PauseScopedObjectActor()
{
ObjectActor.apply(this, arguments);
}
PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype);
update(PauseScopedObjectActor.prototype, ObjectActor.prototype);
update(PauseScopedObjectActor.prototype, {
constructor: PauseScopedObjectActor,
onOwnPropertyNames:
PauseScopedActor.withPaused(ObjectActor.prototype.onOwnPropertyNames),
onPrototypeAndProperties:
PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties),
onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype),
onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
onParameterNames:
PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),
/**
* Handle a protocol request to provide the lexical scope of a function.
@ -1758,22 +1823,6 @@ update(ObjectActor.prototype, {
return { from: this.actorID, scope: envActor.form() };
}),
/**
* Handle a protocol request to provide the parameters of a function.
*
* @param aRequest object
* The protocol request object.
*/
onParameterNames: PauseScopedActor.withPaused(function OA_onParameterNames(aRequest) {
if (this.obj.class !== "Function") {
return { error: "objectNotFunction",
message: "'parameterNames' request is only valid for object " +
"grips with a 'Function' class." };
}
return { parameterNames: this.obj.parameterNames };
}),
/**
* Handle a protocol request to promote a pause-lifetime grip to a
* thread-lifetime grip.
@ -1803,17 +1852,10 @@ update(ObjectActor.prototype, {
}),
});
ObjectActor.prototype.requestTypes = {
"parameterNames": ObjectActor.prototype.onParameterNames,
"prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
"prototype": ObjectActor.prototype.onPrototype,
"property": ObjectActor.prototype.onProperty,
"ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
"scope": ObjectActor.prototype.onScope,
"decompile": ObjectActor.prototype.onDecompile,
"threadGrip": ObjectActor.prototype.onThreadGrip,
"release": ObjectActor.prototype.onRelease,
};
update(PauseScopedObjectActor.prototype.requestTypes, {
"scope": PauseScopedObjectActor.prototype.onScope,
"threadGrip": PauseScopedObjectActor.prototype.onThreadGrip,
});
/**

View File

@ -81,13 +81,35 @@ WebConsoleClient.prototype = {
* The code you want to evaluate.
* @param function aOnResponse
* The function invoked when the response is received.
* @param object [aOptions={}]
* Options for evaluation:
*
* - bindObjectActor: an ObjectActor ID. The OA holds a reference to
* a Debugger.Object that wraps a content object. This option allows
* you to bind |_self| to the D.O of the given OA, during string
* evaluation.
*
* See: Debugger.Object.evalInGlobalWithBindings() for information
* about bindings.
*
* Use case: the variable view needs to update objects and it does so
* by knowing the ObjectActor it inspects and binding |_self| to the
* D.O of the OA. As such, variable view sends strings like these for
* eval:
* _self["prop"] = value;
*
* - frameActor: a FrameActor ID. The FA holds a reference to
* a Debugger.Frame. This option allows you to evaluate the string in
* the frame of the given FA.
*/
evaluateJS: function WCC_evaluateJS(aString, aOnResponse)
evaluateJS: function WCC_evaluateJS(aString, aOnResponse, aOptions = {})
{
let packet = {
to: this._actor,
type: "evaluateJS",
text: aString,
bindObjectActor: aOptions.bindObjectActor,
frameActor: aOptions.frameActor,
};
this._client.request(packet, aOnResponse);
},

View File

@ -28,12 +28,17 @@ XPCOMUtils.defineLazyServiceGetter(this, "gActivityDistributor",
"@mozilla.org/network/http-activity-distributor;1",
"nsIHttpActivityDistributor");
// TODO: Bug 842672 - toolkit/ imports modules from browser/.
// Note that these are only used in JSTermHelpers, see $0 and pprint().
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
"resource:///modules/devtools/gDevTools.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
"resource:///modules/devtools/Target.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
"resource:///modules/devtools/VariablesView.jsm");
this.EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider", "JSTermHelpers",
"PageErrorListener", "ConsoleAPIListener",
"NetworkResponseListener", "NetworkMonitor",
@ -786,7 +791,7 @@ this.WebConsoleUtils = {
* @return string
* The object class name.
*/
getObjectClassName: function WCF_getObjectClassName(aObject)
getObjectClassName: function WCU_getObjectClassName(aObject)
{
if (aObject === null) {
return "null";
@ -859,6 +864,19 @@ this.WebConsoleUtils = {
return val.displayString || val.type;
},
/**
* Check if the given value is a grip with an actor.
*
* @param mixed aGrip
* Value you want to check if it is a grip with an actor.
* @return boolean
* True if the given value is a grip with an actor.
*/
isActorGrip: function WCU_isActorGrip(aGrip)
{
return aGrip && typeof(aGrip) == "object" && aGrip.actor;
},
};
//////////////////////////////////////////////////////////////////////////
@ -1542,20 +1560,15 @@ this.JSTermHelpers = function JSTermHelpers(aOwner)
*/
aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
{
let nodes = [];
let nodes = new aOwner.window.wrappedJSObject.Array();
let doc = aOwner.window.document;
let aContext = aContext || doc;
try {
let results = doc.evaluate(aXPath, aContext, null,
Ci.nsIDOMXPathResult.ANY_TYPE, null);
let node;
while (node = results.iterateNext()) {
nodes.push(node);
}
}
catch (ex) {
aOwner.window.console.error(ex.message);
let results = doc.evaluate(aXPath, aContext, null,
Ci.nsIDOMXPathResult.ANY_TYPE, null);
let node;
while (node = results.iterateNext()) {
nodes.push(node);
}
return nodes;
@ -1572,20 +1585,18 @@ this.JSTermHelpers = function JSTermHelpers(aOwner)
* @return nsIDOMElement|null
* The DOM element currently selected in the highlighter.
*/
Object.defineProperty(aOwner.sandbox, "$0", {
Object.defineProperty(aOwner.sandbox, "$0", {
get: function() {
try {
let window = aOwner.chromeWindow();
let target = TargetFactory.forTab(window.gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
let window = aOwner.chromeWindow();
if (!window) {
return null;
}
let target = TargetFactory.forTab(window.gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
let panel = toolbox ? toolbox.getPanel("inspector") : null;
let node = panel ? panel.selection.node : null;
return toolbox == null ?
undefined :
toolbox.getPanel("inspector").selection.node;
}
catch (ex) {
aOwner.window.console.error(ex.message);
}
return node ? aOwner.makeDebuggeeValue(node) : null;
},
enumerable: true,
configurable: false
@ -1610,7 +1621,7 @@ this.JSTermHelpers = function JSTermHelpers(aOwner)
*/
aOwner.sandbox.keys = function JSTH_keys(aObject)
{
return Object.keys(WebConsoleUtils.unwrap(aObject));
return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
};
/**
@ -1622,16 +1633,11 @@ this.JSTermHelpers = function JSTermHelpers(aOwner)
*/
aOwner.sandbox.values = function JSTH_values(aObject)
{
let arrValues = [];
let arrValues = new aOwner.window.wrappedJSObject.Array();
let obj = WebConsoleUtils.unwrap(aObject);
try {
for (let prop in obj) {
arrValues.push(obj[prop]);
}
}
catch (ex) {
aOwner.window.console.error(ex.message);
for (let prop in obj) {
arrValues.push(obj[prop]);
}
return arrValues;
@ -1653,15 +1659,12 @@ this.JSTermHelpers = function JSTermHelpers(aOwner)
*/
aOwner.sandbox.inspect = function JSTH_inspect(aObject)
{
let obj = WebConsoleUtils.unwrap(aObject);
if (!WebConsoleUtils.isObjectInspectable(obj)) {
return aObject;
}
let dbgObj = aOwner.makeDebuggeeValue(aObject);
let grip = aOwner.createValueGrip(dbgObj);
aOwner.helperResult = {
type: "inspectObject",
input: aOwner.evalInput,
object: aOwner.createValueGrip(obj),
object: grip,
};
};
@ -1690,13 +1693,24 @@ this.JSTermHelpers = function JSTermHelpers(aOwner)
}
let output = [];
let getObjectGrip = WebConsoleUtils.getObjectGrip.bind(WebConsoleUtils);
let obj = WebConsoleUtils.unwrap(aObject);
let props = WebConsoleUtils.inspectObject(obj, getObjectGrip);
props.forEach(function(aProp) {
output.push(aProp.name + ": " +
WebConsoleUtils.getPropertyPanelValue(aProp));
});
for (let name in obj) {
let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
if (desc.get || desc.set) {
// TODO: Bug 842672 - toolkit/ imports modules from browser/.
let getGrip = VariablesView.getGrip(desc.get);
let setGrip = VariablesView.getGrip(desc.set);
let getString = VariablesView.getString(getGrip);
let setString = VariablesView.getString(setGrip);
output.push(name + ":", " get: " + getString, " set: " + setString);
}
else {
let valueGrip = VariablesView.getGrip(obj[name]);
let valueString = VariablesView.getString(valueGrip);
output.push(name + ": " + valueString);
}
}
return " " + output.join("\n ");
};

View File

@ -71,10 +71,22 @@ function WebConsoleActor(aConnection, aParentActor)
this.conn.addActorPool(this._actorPool);
this._prefs = {};
this.dbg = new Debugger();
this._createGlobal();
this._protoChains = new Map();
}
WebConsoleActor.prototype =
{
/**
* Debugger instance.
*
* @see jsdebugger.jsm
*/
dbg: null,
/**
* Tells if this Web Console actor is a global actor or not.
* @private
@ -98,18 +110,39 @@ WebConsoleActor.prototype =
_prefs: null,
/**
* Tells the current inner window associated to the sandbox. When the page
* is navigated, we recreate the sandbox.
* Tells the current inner window of the window of |this._dbgWindow|. When the
* page is navigated, we recreate the debugger object.
* @private
* @type object
*/
_sandboxWindowId: 0,
_globalWindowId: 0,
/**
* The JavaScript Sandbox where code is evaluated.
* The Debugger.Object that wraps the content window.
* @private
* @type object
*/
sandbox: null,
_dbgWindow: null,
/**
* Object that holds the API we give to the JSTermHelpers constructor. This is
* where the JSTerm helper functions are added.
*
* @see this._getJSTermHelpers()
* @private
* @type object
*/
_jstermHelpers: null,
/**
* A cache of prototype chains for objects that have received a
* prototypeAndProperties request.
*
* @private
* @type Map
* @see dbg-script-actors.js, ThreadActor._protoChains
*/
_protoChains: null,
/**
* The debugger server connection instance.
@ -162,6 +195,11 @@ WebConsoleActor.prototype =
hasNativeConsoleAPI: BrowserTabActor.prototype.hasNativeConsoleAPI,
_createValueGrip: ThreadActor.prototype.createValueGrip,
_stringIsLong: ThreadActor.prototype._stringIsLong,
_findProtoChain: ThreadActor.prototype._findProtoChain,
_removeFromProtoChain: ThreadActor.prototype._removeFromProtoChain,
/**
* Destroy the current WebConsoleActor instance.
*/
@ -185,22 +223,36 @@ WebConsoleActor.prototype =
}
this.conn.removeActorPool(this._actorPool);
this._actorPool = null;
this.sandbox = null;
this._sandboxWindowId = 0;
this._protoChains.clear();
this.dbg.enabled = false;
this.dbg = null;
this._dbgWindow = null;
this._globalWindowId = 0;
this.conn = this._window = null;
},
/**
* Create a grip for the given value. If the value is an object,
* a WebConsoleObjectActor will be created.
* Create a grip for the given value.
*
* @param mixed aValue
* @return object
*/
createValueGrip: function WCA_createValueGrip(aValue)
{
return WebConsoleUtils.createValueGrip(aValue,
this.createObjectActor.bind(this));
return this._createValueGrip(aValue, this._actorPool);
},
/**
* Make a debuggee value for the given value.
*
* @param mixed aValue
* The value you want to get a debuggee value for.
* @return object
* Debuggee value for |aValue|.
*/
makeDebuggeeValue: function WCA_makeDebuggeeValue(aValue)
{
return this._dbgWindow.makeDebuggeeValue(aValue);
},
/**
@ -208,41 +260,33 @@ WebConsoleActor.prototype =
*
* @param object aObject
* The object you want.
* @param object aPool
* An ActorPool where the new actor instance is added.
* @param object
* The object grip.
*/
createObjectActor: function WCA_createObjectActor(aObject)
objectGrip: function WCA_objectGrip(aObject, aPool)
{
if (typeof aObject == "string") {
return this.createStringGrip(aObject);
}
// We need to unwrap the object, otherwise we cannot access the properties
// and methods added by the content scripts.
let obj = WebConsoleUtils.unwrap(aObject);
let actor = new WebConsoleObjectActor(obj, this);
this._actorPool.addActor(actor);
let actor = new ObjectActor(aObject, this);
aPool.addActor(actor);
return actor.grip();
},
/**
* Create a grip for the given string. If the given string is a long string,
* then a LongStringActor grip will be used.
* Create a grip for the given string.
*
* @param string aString
* The string you want to create the grip for.
* @return string|object
* The same string, as is, or a LongStringActor object that wraps the
* given string.
* @param object aPool
* An ActorPool where the new actor instance is added.
* @return object
* A LongStringActor object that wraps the given string.
*/
createStringGrip: function WCA_createStringGrip(aString)
longStringGrip: function WCA_longStringGrip(aString, aPool)
{
if (aString.length >= DebuggerServer.LONG_STRING_LENGTH) {
let actor = new LongStringActor(aString, this);
this._actorPool.addActor(actor);
return actor.grip();
}
return aString;
let actor = new LongStringActor(aString, this);
aPool.addActor(actor);
return actor.grip();
},
/**
@ -452,30 +496,42 @@ WebConsoleActor.prototype =
onEvaluateJS: function WCA_onEvaluateJS(aRequest)
{
let input = aRequest.text;
let result, error = null;
let timestamp;
let timestamp = Date.now();
this.helperResult = null;
this.evalInput = input;
try {
timestamp = Date.now();
result = this.evalInSandbox(input);
}
catch (ex) {
error = ex;
}
let evalOptions = {
bindObjectActor: aRequest.bindObjectActor,
frameActor: aRequest.frameActor,
};
let evalInfo = this.evalWithDebugger(input, evalOptions);
let evalResult = evalInfo.result;
let helperResult = this._jstermHelpers.helperResult;
delete this._jstermHelpers.helperResult;
let helperResult = this.helperResult;
delete this.helperResult;
delete this.evalInput;
let result, error, errorMessage;
if (evalResult) {
if ("return" in evalResult) {
result = evalResult.return;
}
else if ("yield" in evalResult) {
result = evalResult.yield;
}
else if ("throw" in evalResult) {
error = evalResult.throw;
let errorToString = evalInfo.window
.evalInGlobalWithBindings("ex + ''", {ex: error});
if (errorToString && typeof errorToString.return == "string") {
errorMessage = errorToString.return;
}
}
}
return {
from: this.actorID,
input: input,
result: this.createValueGrip(result),
timestamp: timestamp,
error: error,
errorMessage: error ? String(error) : null,
exception: error ? this.createValueGrip(error) : null,
exceptionMessage: errorMessage,
helperResult: helperResult,
};
},
@ -490,6 +546,8 @@ WebConsoleActor.prototype =
*/
onAutocomplete: function WCA_onAutocomplete(aRequest)
{
// TODO: Bug 842682 - use the debugger API for autocomplete in the Web
// Console, and provide suggestions from the selected debugger stack frame.
let result = JSPropertyProvider(this.window, aRequest.text) || {};
return {
from: this.actorID,
@ -529,68 +587,201 @@ WebConsoleActor.prototype =
//////////////////
/**
* Create the JavaScript sandbox where user input is evaluated.
* Create the Debugger.Object for the current window.
* @private
*/
_createSandbox: function WCA__createSandbox()
_createGlobal: function WCA__createGlobal()
{
this._sandboxWindowId = WebConsoleUtils.getInnerWindowId(this.window);
this.sandbox = new Cu.Sandbox(this.window, {
sandboxPrototype: this.window,
wantXrays: false,
});
let windowId = WebConsoleUtils.getInnerWindowId(this.window);
if (this._globalWindowId == windowId) {
return;
}
this.sandbox.console = this.window.console;
this._globalWindowId = windowId;
JSTermHelpers(this);
this._dbgWindow = this.dbg.addDebuggee(this.window);
this.dbg.removeDebuggee(this.window);
// Update the JSTerm helpers.
this._jstermHelpers = this._getJSTermHelpers(this._dbgWindow);
},
/**
* Evaluates a string in the sandbox.
* Create an object with the API we expose to the JSTermHelpers constructor.
* This object inherits properties and methods from the Web Console actor.
*
* @private
* @param object aDebuggerObject
* A Debugger.Object that wraps a content global. This is used for the
* JSTerm helpers.
* @return object
*/
_getJSTermHelpers: function WCA__getJSTermHelpers(aDebuggerObject)
{
let helpers = Object.create(this);
helpers.sandbox = Object.create(null);
helpers._dbgWindow = aDebuggerObject;
JSTermHelpers(helpers);
// Make sure the helpers can be used during eval.
for (let name in helpers.sandbox) {
let desc = Object.getOwnPropertyDescriptor(helpers.sandbox, name);
if (desc.get || desc.set) {
continue;
}
helpers.sandbox[name] = helpers.makeDebuggeeValue(desc.value);
}
return helpers;
},
/**
* Evaluates a string using the debugger API.
*
* To allow the variables view to update properties from the web console we
* provide the "bindObjectActor" mechanism: the Web Console tells the
* ObjectActor ID for which it desires to evaluate an expression. The
* Debugger.Object pointed at by the actor ID is bound such that it is
* available during expression evaluation (evalInGlobalWithBindings()).
*
* Example:
* _self['foobar'] = 'test'
* where |_self| refers to the desired object.
*
* The |frameActor| property allows the Web Console client to provide the
* frame actor ID, such that the expression can be evaluated in the
* user-selected stack frame.
*
* For the above to work we need the debugger and the web console to share
* a connection, otherwise the Web Console actor will not find the frame
* actor.
*
* The Debugger.Frame comes from the jsdebugger's Debugger instance, which
* is different from the Web Console's Debugger instance. This means that
* for evaluation to work, we need to create a new instance for the jsterm
* helpers - they need to be Debugger.Objects coming from the jsdebugger's
* Debugger instance.
*
* @param string aString
* String to evaluate in the sandbox.
* @return mixed
* The result of the evaluation.
* String to evaluate.
* @param object [aOptions]
* Options for evaluation:
* - bindObjectActor: the ObjectActor ID to use for evaluation.
* |evalWithBindings()| will be called with one additional binding:
* |_self| which will point to the Debugger.Object of the given
* ObjectActor.
* - frameActor: the FrameActor ID to use for evaluation. The given
* debugger frame is used for evaluation, instead of the global window.
* @return object
* An object that holds the following properties:
* - dbg: the debugger where the string was evaluated.
* - frame: (optional) the frame where the string was evaluated.
* - window: the Debugger.Object for the global where the string was
* evaluated.
* - result: the result of the evaluation.
*/
evalInSandbox: function WCA_evalInSandbox(aString)
evalWithDebugger: function WCA_evalWithDebugger(aString, aOptions = {})
{
// If the user changed to a different location, we need to update the
// sandbox.
if (this._sandboxWindowId !== WebConsoleUtils.getInnerWindowId(this.window)) {
this._createSandbox();
}
this._createGlobal();
// The help function needs to be easy to guess, so we make the () optional
// The help function needs to be easy to guess, so we make the () optional.
if (aString.trim() == "help" || aString.trim() == "?") {
aString = "help()";
}
let window = WebConsoleUtils.unwrap(this.sandbox.window);
let bindSelf = null;
if (aOptions.bindObjectActor) {
let objActor = this.getActorByID(aOptions.bindObjectActor);
if (objActor) {
bindSelf = objActor.obj;
}
}
let helpers = this._jstermHelpers;
let found$ = false, found$$ = false;
let frame = null, frameActor = null;
if (aOptions.frameActor) {
frameActor = this.conn.getActor(aOptions.frameActor);
if (frameActor) {
frame = frameActor.frame;
}
else {
Cu.reportError("Web Console Actor: the frame actor was not found: " +
aOptions.frameActor);
}
}
let dbg = this.dbg;
let dbgWindow = this._dbgWindow;
if (frame) {
// Avoid having bindings from a different Debugger. The Debugger.Frame
// comes from the jsdebugger's Debugger instance.
dbg = frameActor.threadActor.dbg;
dbgWindow = dbg.addDebuggee(this.window);
helpers = this._getJSTermHelpers(dbgWindow);
let env = frame.environment;
if (env) {
found$ = !!env.find("$");
found$$ = !!env.find("$$");
}
}
else {
found$ = !!this._dbgWindow.getOwnPropertyDescriptor("$");
found$$ = !!this._dbgWindow.getOwnPropertyDescriptor("$$");
}
let bindings = helpers.sandbox;
if (bindSelf) {
let jsObj = bindSelf.unsafeDereference();
bindings._self = helpers.makeDebuggeeValue(jsObj);
}
let $ = null, $$ = null;
// We prefer to execute the page-provided implementations for the $() and
// $$() functions.
if (typeof window.$ == "function") {
$ = this.sandbox.$;
delete this.sandbox.$;
if (found$) {
$ = bindings.$;
delete bindings.$;
}
if (typeof window.$$ == "function") {
$$ = this.sandbox.$$;
delete this.sandbox.$$;
if (found$$) {
$$ = bindings.$$;
delete bindings.$$;
}
let result = Cu.evalInSandbox(aString, this.sandbox, "1.8",
"Web Console", 1);
helpers.helperResult = null;
helpers.evalInput = aString;
let result;
if (frame) {
result = frame.evalWithBindings(aString, bindings);
}
else {
result = this._dbgWindow.evalInGlobalWithBindings(aString, bindings);
}
delete helpers.evalInput;
if (helpers != this._jstermHelpers) {
this._jstermHelpers.helperResult = helpers.helperResult;
delete helpers.helperResult;
}
if ($) {
this.sandbox.$ = $;
bindings.$ = $;
}
if ($$) {
this.sandbox.$$ = $$;
bindings.$$ = $$;
}
return result;
if (bindings._self) {
delete bindings._self;
}
return {
result: result,
dbg: dbg,
frame: frame,
window: dbgWindow,
};
},
//////////////////
@ -726,18 +917,10 @@ WebConsoleActor.prototype =
result.arguments = Array.map(aMessage.arguments || [],
function(aObj) {
return this.createValueGrip(aObj);
let dbgObj = this.makeDebuggeeValue(aObj);
return this.createValueGrip(dbgObj);
}, this);
if (result.level == "dir") {
result.objectProperties = [];
let first = result.arguments[0];
if (typeof first == "object" && first && first.inspectable) {
let actor = this.getActorByID(first.actor);
result.objectProperties = actor.onInspectProperties().properties;
}
}
return result;
},
@ -749,9 +932,18 @@ WebConsoleActor.prototype =
*/
chromeWindow: function WCA_chromeWindow()
{
return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell)
.chromeEventHandler.ownerDocument.defaultView;
let window = null;
try {
window = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell)
.chromeEventHandler.ownerDocument.defaultView;
}
catch (ex) {
// The above can fail because chromeEventHandler is not available for all
// kinds of |this.window|.
}
return window;
},
};
@ -766,79 +958,6 @@ WebConsoleActor.prototype.requestTypes =
setPreferences: WebConsoleActor.prototype.onSetPreferences,
};
/**
* Creates an actor for the specified object.
*
* @constructor
* @param object aObj
* The object you want.
* @param object aWebConsoleActor
* The parent WebConsoleActor instance for this object.
*/
function WebConsoleObjectActor(aObj, aWebConsoleActor)
{
this.obj = aObj;
this.parent = aWebConsoleActor;
}
WebConsoleObjectActor.prototype =
{
actorPrefix: "consoleObj",
/**
* Returns a grip for this actor for returning in a protocol message.
*/
grip: function WCOA_grip()
{
let grip = WebConsoleUtils.getObjectGrip(this.obj);
grip.actor = this.actorID;
grip.displayString = this.parent.createStringGrip(grip.displayString);
return grip;
},
/**
* Releases this actor from the pool.
*/
release: function WCOA_release()
{
this.parent.releaseActor(this);
this.parent = this.obj = null;
},
/**
* Handle a protocol request to inspect the properties of the object.
*
* @return object
* Message to send to the client. This holds the 'properties' property
* - an array with a descriptor for each property in the object.
*/
onInspectProperties: function WCOA_onInspectProperties()
{
let createObjectActor = this.parent.createObjectActor.bind(this.parent);
let props = WebConsoleUtils.inspectObject(this.obj, createObjectActor);
return {
from: this.actorID,
properties: props,
};
},
/**
* Handle a protocol request to release a grip.
*/
onRelease: function WCOA_onRelease()
{
this.release();
return {};
},
};
WebConsoleObjectActor.prototype.requestTypes =
{
"inspectProperties": WebConsoleObjectActor.prototype.onInspectProperties,
"release": WebConsoleObjectActor.prototype.onRelease,
};
/**
* Creates an actor for a network event.
*
@ -1083,7 +1202,7 @@ NetworkEventActor.prototype =
addRequestPostData: function NEA_addRequestPostData(aPostData)
{
this._request.postData = aPostData;
aPostData.text = this.parent.createStringGrip(aPostData.text);
aPostData.text = this._createStringGrip(aPostData.text);
if (typeof aPostData.text == "object") {
this._longStringActors.add(aPostData.text);
}
@ -1178,7 +1297,7 @@ NetworkEventActor.prototype =
function NEA_addResponseContent(aContent, aDiscardedResponseBody)
{
this._response.content = aContent;
aContent.text = this.parent.createStringGrip(aContent.text);
aContent.text = this._createStringGrip(aContent.text);
if (typeof aContent.text == "object") {
this._longStringActors.add(aContent.text);
}
@ -1228,12 +1347,30 @@ NetworkEventActor.prototype =
_prepareHeaders: function NEA__prepareHeaders(aHeaders)
{
for (let header of aHeaders) {
header.value = this.parent.createStringGrip(header.value);
header.value = this._createStringGrip(header.value);
if (typeof header.value == "object") {
this._longStringActors.add(header.value);
}
}
},
/**
* Create a long string grip if needed for the given string.
*
* @private
* @param string aString
* The string you want to create a long string grip for.
* @return string|object
* A string is returned if |aString| is not a long string.
* A LongStringActor grip is returned if |aString| is a long string.
*/
_createStringGrip: function NEA__createStringGrip(aString)
{
if (this.parent._stringIsLong(aString)) {
return this.parent.longStringGrip(aString, this.parent._actorPool);
}
return aString;
},
};
NetworkEventActor.prototype.requestTypes =

View File

@ -34,33 +34,33 @@ function onEvaluate(aState, aResponse)
result: {
type: "object",
actor: /[a-z]/,
inspectable: true,
},
});
ok(!aResponse.error, "no js error");
ok(!aResponse.exception, "no eval exception");
ok(!aResponse.helperResult, "no helper result");
onInspect = onInspect.bind(null, aState);
aState.client.inspectObjectProperties(aResponse.result.actor, onInspect);
let client = new GripClient(aState.dbgClient, aResponse.result);
client.getPrototypeAndProperties(onInspect);
}
function onInspect(aState, aResponse)
{
ok(!aResponse.error, "no response error");
let expectedProps = [
{ name: "ATTRIBUTE_NODE", value: 2 },
{ name: "CDATA_SECTION_NODE", value: 4 },
{ name: "COMMENT_NODE", value: 8 },
{ name: "DOCUMENT_FRAGMENT_NODE", value: 11 },
];
let expectedProps = {
"addBroadcastListenerFor": { value: { type: "object" } },
"commandDispatcher": { get: { type: "object" } },
"getBoxObjectFor": { value: { type: "object" } },
"getElementsByAttribute": { value: { type: "object" } },
};
let props = aResponse.properties;
let props = aResponse.ownProperties;
ok(props, "response properties available");
if (props) {
ok(props.length > expectedProps.length,
ok(Object.keys(props).length > Object.keys(expectedProps).length,
"number of enumerable properties");
checkObject(props, expectedProps);
}

View File

@ -20,13 +20,13 @@ function doConsoleCalls(aState)
{
let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a");
console.log("foobarBaz-log", undefined);
console.info("foobarBaz-info", null);
console.warn("foobarBaz-warn", document.body);
console.debug(null);
console.trace();
console.dir(document, window);
console.log("foo", longString);
top.console.log("foobarBaz-log", undefined);
top.console.info("foobarBaz-info", null);
top.console.warn("foobarBaz-warn", top.document.documentElement);
top.console.debug(null);
top.console.trace();
top.console.dir(top.document, top.location);
top.console.log("foo", longString);
expectedConsoleCalls = [
{
@ -82,24 +82,14 @@ function doConsoleCalls(aState)
{
type: "object",
actor: /[a-z]/,
className: "HTMLDocument",
class: "XULDocument",
},
{
type: "object",
actor: /[a-z]/,
className: "Window",
class: "Location",
}
],
objectProperties: [
{
name: "ATTRIBUTE_NODE",
value: 2,
},
{
name: "CDATA_SECTION_NODE",
value: 4,
}, // ...
],
},
{
level: "log",
@ -124,7 +114,7 @@ function startTest()
{
removeEventListener("load", startTest);
attachConsole(["ConsoleAPI"], onAttach);
attachConsole(["ConsoleAPI"], onAttach, true);
}
function onAttach(aState, aResponse)

View File

@ -91,7 +91,7 @@ function onSimpleEval(aResponse)
result: 4,
});
ok(!aResponse.error, "no js error");
ok(!aResponse.exception, "no eval exception");
ok(!aResponse.helperResult, "no helper result");
nextTest();
@ -99,23 +99,23 @@ function onSimpleEval(aResponse)
function doWindowEval()
{
info("test eval 'window'");
gState.client.evaluateJS("window", onWindowEval);
info("test eval 'document'");
gState.client.evaluateJS("document", onWindowEval);
}
function onWindowEval(aResponse)
{
checkObject(aResponse, {
from: gState.actor,
input: "window",
input: "document",
result: {
type: "object",
className: "Window",
class: "XULDocument",
actor: /[a-z]/,
},
});
ok(!aResponse.error, "no js error");
ok(!aResponse.exception, "no eval exception");
ok(!aResponse.helperResult, "no helper result");
nextTest();
@ -135,10 +135,10 @@ function onEvalWithException(aResponse)
result: {
type: "undefined",
},
errorMessage: /doTheImpossible/,
exceptionMessage: /doTheImpossible/,
});
ok(aResponse.error, "js error object");
ok(aResponse.exception, "js eval exception");
ok(!aResponse.helperResult, "no helper result");
nextTest();
@ -161,7 +161,7 @@ function onEvalWithHelper(aResponse)
helperResult: { type: "clearOutput" },
});
ok(!aResponse.error, "no js error");
ok(!aResponse.exception, "no eval exception");
nextTest();
}

View File

@ -30,121 +30,95 @@ function onAttach(aState, aResponse)
let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 3)).join("\u0629");
window.foobarObject = Object.create(null);
// Here we put the objects in the correct window, to avoid having them all
// wrapped by proxies for cross-compartment access.
let foobarObject = top.Object.create(null);
foobarObject.tamarbuta = longString;
foobarObject.foo = 1;
foobarObject.foobar = "hello";
foobarObject.foobaz = document;
foobarObject.omg = null;
foobarObject.testfoo = false;
foobarObject.notInspectable = {};
foobarObject.omgfn = function _omgfn() {
return "myResult";
};
foobarObject.abArray = ["a", "b"];
foobarObject.notInspectable = top.Object.create(null);
foobarObject.omgfn = new top.Function("return 'myResult'");
foobarObject.abArray = new top.Array("a", "b");
foobarObject.foobaz = top.document;
Object.defineProperty(foobarObject, "getterAndSetter", {
top.Object.defineProperty(foobarObject, "getterAndSetter", {
enumerable: true,
get: function fooGet() { return "foo"; },
set: function fooSet() { 1+2 },
get: new top.Function("return 'foo';"),
set: new top.Function("1+2"),
});
foobarObject.longStringObj = {
toSource: function() longString,
toString: function() longString,
boom: "explode",
};
foobarObject.longStringObj = top.Object.create(null);
foobarObject.longStringObj.toSource = new top.Function("'" + longString + "'");
foobarObject.longStringObj.toString = new top.Function("'" + longString + "'");
foobarObject.longStringObj.boom = "explode";
console.log("hello", foobarObject);
top.wrappedJSObject.foobarObject = foobarObject;
top.console.log("hello", top.wrappedJSObject.foobarObject);
expectedProps = [
{
name: "abArray",
expectedProps = {
"abArray": {
value: {
type: "object",
className: "Array",
class: "Array",
actor: /[a-z]/,
inspectable: true,
},
},
{
name: "foo",
"foo": {
configurable: true,
enumerable: true,
writable: true,
value: 1,
},
{
name: "foobar",
"foobar": {
value: "hello",
},
{
name: "foobaz",
"foobaz": {
value: {
type: "object",
className: "HTMLDocument",
displayString: /\[object HTMLDocument/,
inspectable: true,
class: "XULDocument",
actor: /[a-z]/,
},
},
{
name: "getterAndSetter",
"getterAndSetter": {
get: {
type: "function",
className: "Function",
displayString: /function fooGet/,
type: "object",
class: "Function",
actor: /[a-z]/,
inspectable: false,
},
set: {
type: "function",
className: "Function",
displayString: /function fooSet/,
type: "object",
class: "Function",
actor: /[a-z]/,
inspectable: false,
},
},
{
name: "longStringObj",
"longStringObj": {
value: {
type: "object",
className: "Object",
class: "Object",
actor: /[a-z]/,
inspectable: true,
displayString: {
type: "longString",
initial: longString.substring(0,
DebuggerServer.LONG_STRING_INITIAL_LENGTH),
length: longString.length,
},
},
},
{
name: "notInspectable",
"notInspectable": {
value: {
type: "object",
className: "Object",
class: "Object",
actor: /[a-z]/,
inspectable: false,
},
},
{
name: "omg",
"omg": {
value: { type: "null" },
},
{
name: "omgfn",
"omgfn": {
value: {
type: "function",
className: "Function",
displayString: /function _omgfn/,
type: "object",
class: "Function",
actor: /[a-z]/,
inspectable: false,
},
},
{
name: "tamarbuta",
"tamarbuta": {
value: {
type: "longString",
initial: longString.substring(0,
@ -152,11 +126,10 @@ function onAttach(aState, aResponse)
length: longString.length,
},
},
{
name: "testfoo",
"testfoo": {
value: false,
},
];
};
}
function onConsoleCall(aState, aType, aPacket)
@ -172,7 +145,6 @@ function onConsoleCall(aState, aType, aPacket)
arguments: ["hello", {
type: "object",
actor: /[a-z]/,
inspectable: true,
}],
});
@ -181,13 +153,15 @@ function onConsoleCall(aState, aType, aPacket)
info("inspecting object properties");
let args = aPacket.message.arguments;
onProperties = onProperties.bind(null, aState);
aState.client.inspectObjectProperties(args[1].actor, onProperties);
let client = new GripClient(aState.dbgClient, args[1]);
client.getPrototypeAndProperties(onProperties);
}
function onProperties(aState, aResponse)
{
let props = aResponse.properties;
is(props.length, expectedProps.length,
let props = aResponse.ownProperties;
is(Object.keys(props).length, Object.keys(expectedProps).length,
"number of enumerable properties");
checkObject(props, expectedProps);