mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 768096 - Web Console remote debugging protocol support - Part 2: window.console API and JS evaluation; r=past,robcee
This commit is contained in:
parent
249b8eb7c6
commit
b06c454002
@ -536,12 +536,8 @@ WebConsole.prototype = {
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_messageListeners: ["JSTerm:EvalObject", "WebConsole:ConsoleAPI",
|
||||
"WebConsole:CachedMessages", "WebConsole:Initialized", "JSTerm:EvalResult",
|
||||
"JSTerm:AutocompleteProperties", "JSTerm:ClearOutput",
|
||||
"JSTerm:InspectObject", "WebConsole:NetworkActivity",
|
||||
"WebConsole:FileActivity", "WebConsole:LocationChange",
|
||||
"JSTerm:NonNativeConsoleAPI"],
|
||||
_messageListeners: ["WebConsole:Initialized", "WebConsole:NetworkActivity",
|
||||
"WebConsole:FileActivity", "WebConsole:LocationChange"],
|
||||
|
||||
/**
|
||||
* The xul:panel that holds the Web Console when it is positioned as a window.
|
||||
@ -901,10 +897,10 @@ WebConsole.prototype = {
|
||||
|
||||
/**
|
||||
* The clear output button handler.
|
||||
* @private
|
||||
*/
|
||||
onClearButton: function WC_onClearButton()
|
||||
_onClearButton: function WC__onClearButton()
|
||||
{
|
||||
this.ui.jsterm.clearOutput(true);
|
||||
this.chromeWindow.DeveloperToolbar.resetErrorsCount(this.tab);
|
||||
},
|
||||
|
||||
@ -924,10 +920,8 @@ WebConsole.prototype = {
|
||||
}, this);
|
||||
|
||||
let message = {
|
||||
features: ["ConsoleAPI", "JSTerm", "NetworkMonitor", "LocationChange"],
|
||||
cachedMessages: ["ConsoleAPI", "PageError"],
|
||||
features: ["NetworkMonitor", "LocationChange"],
|
||||
NetworkMonitor: { monitorFileActivity: true },
|
||||
JSTerm: { notifyNonNativeConsoleAPI: true },
|
||||
preferences: {
|
||||
"NetworkMonitor.saveRequestAndResponseBodies":
|
||||
this.ui.saveRequestAndResponseBodies,
|
||||
|
@ -27,7 +27,7 @@ var EXPORTED_SYMBOLS = ["PropertyPanel", "PropertyTreeView"];
|
||||
*/
|
||||
var PropertyTreeView = function() {
|
||||
this._rows = [];
|
||||
this._objectCache = {};
|
||||
this._objectActors = [];
|
||||
};
|
||||
|
||||
PropertyTreeView.prototype = {
|
||||
@ -44,10 +44,24 @@ PropertyTreeView.prototype = {
|
||||
_treeBox: null,
|
||||
|
||||
/**
|
||||
* Stores cached information about local objects being inspected.
|
||||
* Track known object actor IDs. We clean these when the panel is
|
||||
* destroyed/cleaned up.
|
||||
*
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_objectCache: null,
|
||||
_objectActors: null,
|
||||
|
||||
/**
|
||||
* Map fake object actors to their IDs. This is used when we inspect local
|
||||
* objects.
|
||||
* @private
|
||||
* @type Object
|
||||
*/
|
||||
_localObjectActors: null,
|
||||
|
||||
_releaseObject: null,
|
||||
_objectPropertiesProvider: null,
|
||||
|
||||
/**
|
||||
* Use this setter to update the content of the tree.
|
||||
@ -58,54 +72,47 @@ PropertyTreeView.prototype = {
|
||||
* - object:
|
||||
* This is the raw object you want to display. You can only provide
|
||||
* this object if you want the property panel to work in sync mode.
|
||||
* - remoteObject:
|
||||
* - objectProperties:
|
||||
* An array that holds information on the remote object being
|
||||
* inspected. Each element in this array describes each property in the
|
||||
* remote object. See WebConsoleUtils.namesAndValuesOf() for details.
|
||||
* - rootCacheId:
|
||||
* The cache ID where the objects referenced in remoteObject are found.
|
||||
* - panelCacheId:
|
||||
* The cache ID where any object retrieved by this property panel
|
||||
* instance should be stored into.
|
||||
* - remoteObjectProvider:
|
||||
* remote object. See WebConsoleUtils.inspectObject() for details.
|
||||
* - objectPropertiesProvider:
|
||||
* A function that is invoked when a new object is needed. This is
|
||||
* called when the user tries to expand an inspectable property. The
|
||||
* callback must take four arguments:
|
||||
* - fromCacheId:
|
||||
* Tells from where to retrieve the object the user picked (from
|
||||
* which cache ID).
|
||||
* - objectId:
|
||||
* The object ID the user wants.
|
||||
* - panelCacheId:
|
||||
* Tells in which cache ID to store the objects referenced by
|
||||
* objectId so they can be retrieved later.
|
||||
* - actorID:
|
||||
* The object actor ID from which we request the properties.
|
||||
* - callback:
|
||||
* The callback function to be invoked when the remote object is
|
||||
* received. This function takes one argument: the raw message
|
||||
* received from the Web Console content script.
|
||||
* received. This function takes one argument: the array of
|
||||
* descriptors for each property in the object represented by the
|
||||
* actor.
|
||||
* - releaseObject:
|
||||
* Function to invoke when an object actor should be released. The
|
||||
* function must take one argument: the object actor ID.
|
||||
*/
|
||||
set data(aData) {
|
||||
let oldLen = this._rows.length;
|
||||
|
||||
this._cleanup();
|
||||
this.cleanup();
|
||||
|
||||
if (!aData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aData.remoteObject) {
|
||||
this._rootCacheId = aData.rootCacheId;
|
||||
this._panelCacheId = aData.panelCacheId;
|
||||
this._remoteObjectProvider = aData.remoteObjectProvider;
|
||||
this._rows = [].concat(aData.remoteObject);
|
||||
this._updateRemoteObject(this._rows, 0);
|
||||
if (aData.objectPropertiesProvider) {
|
||||
this._objectPropertiesProvider = aData.objectPropertiesProvider;
|
||||
this._releaseObject = aData.releaseObject;
|
||||
this._propertiesToRows(aData.objectProperties, 0);
|
||||
this._rows = aData.objectProperties;
|
||||
}
|
||||
else if (aData.object) {
|
||||
this._localObjectActors = Object.create(null);
|
||||
this._rows = this._inspectObject(aData.object);
|
||||
}
|
||||
else {
|
||||
throw new Error("First argument must have a .remoteObject or " +
|
||||
"an .object property!");
|
||||
throw new Error("First argument must have an objectActor or an " +
|
||||
"object property!");
|
||||
}
|
||||
|
||||
if (this._treeBox) {
|
||||
@ -128,13 +135,22 @@ PropertyTreeView.prototype = {
|
||||
* @param number aLevel
|
||||
* The level you want to give to each property in the remote object.
|
||||
*/
|
||||
_updateRemoteObject: function PTV__updateRemoteObject(aObject, aLevel)
|
||||
_propertiesToRows: function PTV__propertiesToRows(aObject, aLevel)
|
||||
{
|
||||
aObject.forEach(function(aElement) {
|
||||
aElement.level = aLevel;
|
||||
aElement.isOpened = false;
|
||||
aElement.children = null;
|
||||
});
|
||||
aObject.forEach(function(aItem) {
|
||||
aItem._level = aLevel;
|
||||
aItem._open = false;
|
||||
aItem._children = null;
|
||||
|
||||
if (this._releaseObject) {
|
||||
["value", "get", "set"].forEach(function(aProp) {
|
||||
let val = aItem[aProp];
|
||||
if (val && val.actor) {
|
||||
this._objectActors.push(val.actor);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -143,42 +159,53 @@ PropertyTreeView.prototype = {
|
||||
* @private
|
||||
* @param object aObject
|
||||
* The object you want to inspect.
|
||||
* @return array
|
||||
* The array of properties, each being described in a way that is
|
||||
* usable by the tree view.
|
||||
*/
|
||||
_inspectObject: function PTV__inspectObject(aObject)
|
||||
{
|
||||
this._objectCache = {};
|
||||
this._remoteObjectProvider = this._localObjectProvider.bind(this);
|
||||
let children = WebConsoleUtils.namesAndValuesOf(aObject, this._objectCache);
|
||||
this._updateRemoteObject(children, 0);
|
||||
this._objectPropertiesProvider = this._localPropertiesProvider.bind(this);
|
||||
let children =
|
||||
WebConsoleUtils.inspectObject(aObject, this._localObjectGrip.bind(this));
|
||||
this._propertiesToRows(children, 0);
|
||||
return children;
|
||||
},
|
||||
|
||||
/**
|
||||
* An object provider for when the user inspects local objects (not remote
|
||||
* Make a local fake object actor for the given object.
|
||||
*
|
||||
* @private
|
||||
* @param object aObject
|
||||
* The object to make an actor for.
|
||||
* @return object
|
||||
* The fake actor grip that represents the given object.
|
||||
*/
|
||||
_localObjectGrip: function PTV__localObjectGrip(aObject)
|
||||
{
|
||||
let grip = WebConsoleUtils.getObjectGrip(aObject);
|
||||
grip.actor = "obj" + gSequenceId();
|
||||
this._localObjectActors[grip.actor] = aObject;
|
||||
return grip;
|
||||
},
|
||||
|
||||
/**
|
||||
* A properties provider for when the user inspects local objects (not remote
|
||||
* ones).
|
||||
*
|
||||
* @private
|
||||
* @param string aFromCacheId
|
||||
* The cache ID from where to retrieve the desired object.
|
||||
* @param string aObjectId
|
||||
* The ID of the object you want.
|
||||
* @param string aDestCacheId
|
||||
* The ID of the cache where to store any objects referenced by the
|
||||
* desired object.
|
||||
* @param string aActor
|
||||
* The ID of the object actor you want.
|
||||
* @param function aCallback
|
||||
* The function you want to receive the object.
|
||||
* The function you want to receive the list of properties.
|
||||
*/
|
||||
_localObjectProvider:
|
||||
function PTV__localObjectProvider(aFromCacheId, aObjectId, aDestCacheId,
|
||||
aCallback)
|
||||
_localPropertiesProvider:
|
||||
function PTV__localPropertiesProvider(aActor, aCallback)
|
||||
{
|
||||
let object = WebConsoleUtils.namesAndValuesOf(this._objectCache[aObjectId],
|
||||
this._objectCache);
|
||||
aCallback({cacheId: aFromCacheId,
|
||||
objectId: aObjectId,
|
||||
object: object,
|
||||
childrenCacheId: aDestCacheId || aFromCacheId,
|
||||
});
|
||||
let object = this._localObjectActors[aActor];
|
||||
let properties =
|
||||
WebConsoleUtils.inspectObject(object, this._localObjectGrip.bind(this));
|
||||
aCallback(properties);
|
||||
},
|
||||
|
||||
/** nsITreeView interface implementation **/
|
||||
@ -187,18 +214,20 @@ PropertyTreeView.prototype = {
|
||||
|
||||
get rowCount() { return this._rows.length; },
|
||||
setTree: function(treeBox) { this._treeBox = treeBox; },
|
||||
getCellText: function(idx, column) {
|
||||
getCellText: function PTV_getCellText(idx, column)
|
||||
{
|
||||
let row = this._rows[idx];
|
||||
return row.name + ": " + row.value;
|
||||
return row.name + ": " + WebConsoleUtils.getPropertyPanelValue(row);
|
||||
},
|
||||
getLevel: function(idx) {
|
||||
return this._rows[idx].level;
|
||||
return this._rows[idx]._level;
|
||||
},
|
||||
isContainer: function(idx) {
|
||||
return !!this._rows[idx].inspectable;
|
||||
return typeof this._rows[idx].value == "object" && this._rows[idx].value &&
|
||||
this._rows[idx].value.inspectable;
|
||||
},
|
||||
isContainerOpen: function(idx) {
|
||||
return this._rows[idx].isOpened;
|
||||
return this._rows[idx]._open;
|
||||
},
|
||||
isContainerEmpty: function(idx) { return false; },
|
||||
isSeparator: function(idx) { return false; },
|
||||
@ -221,22 +250,22 @@ PropertyTreeView.prototype = {
|
||||
|
||||
hasNextSibling: function(idx, after)
|
||||
{
|
||||
var thisLevel = this.getLevel(idx);
|
||||
return this._rows.slice(after + 1).some(function (r) r.level == thisLevel);
|
||||
let thisLevel = this.getLevel(idx);
|
||||
return this._rows.slice(after + 1).some(function (r) r._level == thisLevel);
|
||||
},
|
||||
|
||||
toggleOpenState: function(idx)
|
||||
{
|
||||
let item = this._rows[idx];
|
||||
if (!item.inspectable) {
|
||||
if (!this.isContainer(idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.isOpened) {
|
||||
if (item._open) {
|
||||
this._treeBox.beginUpdateBatch();
|
||||
item.isOpened = false;
|
||||
item._open = false;
|
||||
|
||||
var thisLevel = item.level;
|
||||
var thisLevel = item._level;
|
||||
var t = idx + 1, deleteCount = 0;
|
||||
while (t < this._rows.length && this.getLevel(t++) > thisLevel) {
|
||||
deleteCount++;
|
||||
@ -251,31 +280,27 @@ PropertyTreeView.prototype = {
|
||||
}
|
||||
else {
|
||||
let levelUpdate = true;
|
||||
let callback = function _onRemoteResponse(aResponse) {
|
||||
let callback = function _onRemoteResponse(aProperties) {
|
||||
this._treeBox.beginUpdateBatch();
|
||||
item.isOpened = true;
|
||||
|
||||
if (levelUpdate) {
|
||||
this._updateRemoteObject(aResponse.object, item.level + 1);
|
||||
item.children = aResponse.object;
|
||||
this._propertiesToRows(aProperties, item._level + 1);
|
||||
item._children = aProperties;
|
||||
}
|
||||
|
||||
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item.children));
|
||||
this._rows.splice.apply(this._rows, [idx + 1, 0].concat(item._children));
|
||||
|
||||
this._treeBox.rowCountChanged(idx + 1, item.children.length);
|
||||
this._treeBox.rowCountChanged(idx + 1, item._children.length);
|
||||
this._treeBox.invalidateRow(idx);
|
||||
this._treeBox.endUpdateBatch();
|
||||
item._open = true;
|
||||
}.bind(this);
|
||||
|
||||
if (!item.children) {
|
||||
let fromCacheId = item.level > 0 ? this._panelCacheId :
|
||||
this._rootCacheId;
|
||||
this._remoteObjectProvider(fromCacheId, item.objectId,
|
||||
this._panelCacheId, callback);
|
||||
if (!item._children) {
|
||||
this._objectPropertiesProvider(item.value.actor, callback);
|
||||
}
|
||||
else {
|
||||
levelUpdate = false;
|
||||
callback({object: item.children});
|
||||
callback(item._children);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -298,18 +323,23 @@ PropertyTreeView.prototype = {
|
||||
drop: function(index, orientation, dataTransfer) { },
|
||||
canDrop: function(index, orientation, dataTransfer) { return false; },
|
||||
|
||||
_cleanup: function PTV__cleanup()
|
||||
/**
|
||||
* Cleanup the property tree view.
|
||||
*/
|
||||
cleanup: function PTV_cleanup()
|
||||
{
|
||||
if (this._rows.length) {
|
||||
// Reset the existing _rows children to the initial state.
|
||||
this._updateRemoteObject(this._rows, 0);
|
||||
this._rows = [];
|
||||
if (this._releaseObject) {
|
||||
this._objectActors.forEach(this._releaseObject);
|
||||
delete this._objectPropertiesProvider;
|
||||
delete this._releaseObject;
|
||||
}
|
||||
if (this._localObjectActors) {
|
||||
delete this._localObjectActors;
|
||||
delete this._objectPropertiesProvider;
|
||||
}
|
||||
|
||||
delete this._objectCache;
|
||||
delete this._rootCacheId;
|
||||
delete this._panelCacheId;
|
||||
delete this._remoteObjectProvider;
|
||||
this._rows = [];
|
||||
this._objectActors = [];
|
||||
},
|
||||
};
|
||||
|
||||
@ -459,3 +489,9 @@ PropertyPanel.prototype.destroy = function PP_destroy()
|
||||
this.tree = null;
|
||||
}
|
||||
|
||||
|
||||
function gSequenceId()
|
||||
{
|
||||
return gSequenceId.n++;
|
||||
}
|
||||
gSequenceId.n = 0;
|
||||
|
@ -117,7 +117,7 @@ function testPropertyPanel(aPanel) {
|
||||
ok(find("iter1: Iterator", false),
|
||||
"iter1 is correctly displayed in the Property Panel");
|
||||
|
||||
ok(find("iter2: Iterator", false),
|
||||
ok(find("iter2: Object", false),
|
||||
"iter2 is correctly displayed in the Property Panel");
|
||||
|
||||
executeSoon(finishTest);
|
||||
|
@ -41,7 +41,7 @@ function testConsoleDir(outputNode) {
|
||||
if (text == "querySelectorAll: function querySelectorAll()") {
|
||||
foundQSA = true;
|
||||
}
|
||||
else if (text == "location: Object") {
|
||||
else if (text == "location: Location") {
|
||||
foundLocation = true;
|
||||
}
|
||||
else if (text == "write: function write()") {
|
||||
|
@ -111,10 +111,10 @@ function testJSTerm(hud)
|
||||
|
||||
let foundTab = null;
|
||||
waitForSuccess({
|
||||
name: "help tab opened",
|
||||
name: "help tabs opened",
|
||||
validatorFn: function()
|
||||
{
|
||||
let newTabOpen = gBrowser.tabs.length == tabs + 1;
|
||||
let newTabOpen = gBrowser.tabs.length == tabs + 3;
|
||||
if (!newTabOpen) {
|
||||
return false;
|
||||
}
|
||||
@ -124,7 +124,9 @@ function testJSTerm(hud)
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
gBrowser.removeTab(foundTab);
|
||||
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
|
||||
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
|
||||
gBrowser.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
|
||||
nextTest();
|
||||
},
|
||||
failureFn: nextTest,
|
||||
@ -176,7 +178,7 @@ function testJSTerm(hud)
|
||||
jsterm.clearOutput();
|
||||
jsterm.execute("pprint(print)");
|
||||
checkResult(function(nodes) {
|
||||
return nodes[0].textContent.indexOf("aJSTerm.") > -1;
|
||||
return nodes[0].textContent.indexOf("aOwner.helperResult") > -1;
|
||||
}, "pprint(function) shows source", 1);
|
||||
yield;
|
||||
|
||||
|
@ -49,6 +49,8 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/en/Security/MixedContent";
|
||||
|
||||
const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
|
||||
|
||||
// The amount of time in milliseconds that must pass between messages to
|
||||
// trigger the display of a new group.
|
||||
const NEW_GROUP_DELAY = 5000;
|
||||
@ -307,6 +309,12 @@ WebConsoleFrame.prototype = {
|
||||
*/
|
||||
filterBox: null,
|
||||
|
||||
/**
|
||||
* Getter for the debugger WebConsoleClient.
|
||||
* @type object
|
||||
*/
|
||||
get webConsoleClient() this.proxy ? this.proxy.webConsoleClient : null,
|
||||
|
||||
_saveRequestAndResponseBodies: false,
|
||||
|
||||
/**
|
||||
@ -413,8 +421,10 @@ WebConsoleFrame.prototype = {
|
||||
this.owner.onCloseButton.bind(this.owner));
|
||||
|
||||
let clearButton = doc.getElementsByClassName("webconsole-clear-console-button")[0];
|
||||
clearButton.addEventListener("command",
|
||||
this.owner.onClearButton.bind(this.owner));
|
||||
clearButton.addEventListener("command", function WCF__onClearButton() {
|
||||
this.owner._onClearButton();
|
||||
this.jsterm.clearOutput(true);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -657,27 +667,9 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "JSTerm:EvalResult":
|
||||
case "JSTerm:EvalObject":
|
||||
case "JSTerm:AutocompleteProperties":
|
||||
this.owner._receiveMessageWithCallback(aMessage.json);
|
||||
break;
|
||||
case "JSTerm:ClearOutput":
|
||||
this.jsterm.clearOutput();
|
||||
break;
|
||||
case "JSTerm:InspectObject":
|
||||
this.jsterm.handleInspectObject(aMessage.json);
|
||||
break;
|
||||
case "WebConsole:ConsoleAPI":
|
||||
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage,
|
||||
[aMessage.json]);
|
||||
break;
|
||||
case "WebConsole:Initialized":
|
||||
this._onMessageManagerInitComplete();
|
||||
break;
|
||||
case "WebConsole:CachedMessages":
|
||||
this._displayCachedConsoleMessages(aMessage.json.messages);
|
||||
break;
|
||||
case "WebConsole:NetworkActivity":
|
||||
this.handleNetworkActivity(aMessage.json);
|
||||
break;
|
||||
@ -688,9 +680,6 @@ WebConsoleFrame.prototype = {
|
||||
case "WebConsole:LocationChange":
|
||||
this.owner.onLocationChange(aMessage.json);
|
||||
break;
|
||||
case "JSTerm:NonNativeConsoleAPI":
|
||||
this.outputMessage(CATEGORY_JS, this.logWarningAboutReplacedAPI);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -1033,13 +1022,11 @@ WebConsoleFrame.prototype = {
|
||||
* Display cached messages that may have been collected before the UI is
|
||||
* displayed.
|
||||
*
|
||||
* @private
|
||||
* @param array aRemoteMessages
|
||||
* Array of cached messages coming from the remote Web Console
|
||||
* content instance.
|
||||
*/
|
||||
_displayCachedConsoleMessages:
|
||||
function WCF__displayCachedConsoleMessages(aRemoteMessages)
|
||||
displayCachedMessages: function WCF_displayCachedMessages(aRemoteMessages)
|
||||
{
|
||||
if (!aRemoteMessages.length) {
|
||||
return;
|
||||
@ -1062,19 +1049,11 @@ WebConsoleFrame.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Logs a message to the Web Console that originates from the remote Web
|
||||
* Console instance.
|
||||
* Logs a message to the Web Console that originates from the Web Console
|
||||
* server.
|
||||
*
|
||||
* @param object aMessage
|
||||
* The message received from the remote Web Console instance.
|
||||
* console service. This object needs to hold:
|
||||
* - hudId - the Web Console ID.
|
||||
* - apiMessage - a representation of the object sent by the console
|
||||
* storage service. This object holds the console message level, the
|
||||
* arguments that were passed to the console method and other
|
||||
* information.
|
||||
* - argumentsToString - the array of arguments passed to the console
|
||||
* method, each converted to a string.
|
||||
* The message received from the server.
|
||||
* @return nsIDOMElement|undefined
|
||||
* The message element to display in the Web Console output.
|
||||
*/
|
||||
@ -1084,9 +1063,9 @@ WebConsoleFrame.prototype = {
|
||||
let clipboardText = null;
|
||||
let sourceURL = null;
|
||||
let sourceLine = 0;
|
||||
let level = aMessage.apiMessage.level;
|
||||
let args = aMessage.apiMessage.arguments;
|
||||
let argsToString = aMessage.argumentsToString;
|
||||
let level = aMessage.level;
|
||||
let args = aMessage.arguments;
|
||||
let objectActors = [];
|
||||
|
||||
switch (level) {
|
||||
case "log":
|
||||
@ -1094,17 +1073,36 @@ WebConsoleFrame.prototype = {
|
||||
case "warn":
|
||||
case "error":
|
||||
case "debug":
|
||||
body = {
|
||||
cacheId: aMessage.objectsCacheId,
|
||||
remoteObjects: args,
|
||||
argsToString: argsToString,
|
||||
};
|
||||
clipboardText = argsToString.join(" ");
|
||||
sourceURL = aMessage.apiMessage.filename;
|
||||
sourceLine = aMessage.apiMessage.lineNumber;
|
||||
break;
|
||||
case "dir":
|
||||
case "groupEnd": {
|
||||
body = { arguments: args };
|
||||
let clipboardArray = [];
|
||||
args.forEach(function(aValue) {
|
||||
clipboardArray.push(WebConsoleUtils.objectActorGripToString(aValue));
|
||||
if (aValue && typeof aValue == "object" && aValue.actor) {
|
||||
objectActors.push(aValue.actor);
|
||||
}
|
||||
}, this);
|
||||
clipboardText = clipboardArray.join(" ");
|
||||
sourceURL = aMessage.filename;
|
||||
sourceLine = aMessage.lineNumber;
|
||||
|
||||
case "trace":
|
||||
if (level == "dir") {
|
||||
body.objectProperties = aMessage.objectProperties;
|
||||
}
|
||||
else if (level == "groupEnd") {
|
||||
objectActors.forEach(this._releaseObject, this);
|
||||
|
||||
if (this.groupDepth > 0) {
|
||||
this.groupDepth--;
|
||||
}
|
||||
return; // no need to continue
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "trace": {
|
||||
let filename = WebConsoleUtils.abbreviateSourceURL(args[0].filename);
|
||||
let functionName = args[0].functionName ||
|
||||
l10n.getStr("stacktrace.anonymousFunction");
|
||||
@ -1126,34 +1124,16 @@ WebConsoleFrame.prototype = {
|
||||
|
||||
clipboardText = clipboardText.trimRight();
|
||||
break;
|
||||
|
||||
case "dir":
|
||||
body = {
|
||||
cacheId: aMessage.objectsCacheId,
|
||||
resultString: argsToString[0],
|
||||
remoteObject: args[0],
|
||||
remoteObjectProvider:
|
||||
this.jsterm.remoteObjectProvider.bind(this.jsterm),
|
||||
};
|
||||
clipboardText = body.resultString;
|
||||
sourceURL = aMessage.apiMessage.filename;
|
||||
sourceLine = aMessage.apiMessage.lineNumber;
|
||||
break;
|
||||
}
|
||||
|
||||
case "group":
|
||||
case "groupCollapsed":
|
||||
clipboardText = body = args;
|
||||
sourceURL = aMessage.apiMessage.filename;
|
||||
sourceLine = aMessage.apiMessage.lineNumber;
|
||||
sourceURL = aMessage.filename;
|
||||
sourceLine = aMessage.lineNumber;
|
||||
this.groupDepth++;
|
||||
break;
|
||||
|
||||
case "groupEnd":
|
||||
if (this.groupDepth > 0) {
|
||||
this.groupDepth--;
|
||||
}
|
||||
return;
|
||||
|
||||
case "time":
|
||||
if (!args) {
|
||||
return;
|
||||
@ -1164,8 +1144,8 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
body = l10n.getFormatStr("timerStarted", [args.name]);
|
||||
clipboardText = body;
|
||||
sourceURL = aMessage.apiMessage.filename;
|
||||
sourceLine = aMessage.apiMessage.lineNumber;
|
||||
sourceURL = aMessage.filename;
|
||||
sourceLine = aMessage.lineNumber;
|
||||
break;
|
||||
|
||||
case "timeEnd":
|
||||
@ -1174,8 +1154,8 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
body = l10n.getFormatStr("timeEnd", [args.name, args.duration]);
|
||||
clipboardText = body;
|
||||
sourceURL = aMessage.apiMessage.filename;
|
||||
sourceLine = aMessage.apiMessage.lineNumber;
|
||||
sourceURL = aMessage.filename;
|
||||
sourceLine = aMessage.lineNumber;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1187,6 +1167,10 @@ WebConsoleFrame.prototype = {
|
||||
sourceURL, sourceLine, clipboardText,
|
||||
level, aMessage.timeStamp);
|
||||
|
||||
if (objectActors.length) {
|
||||
node._objectActors = objectActors;
|
||||
}
|
||||
|
||||
// Make the node bring up the property panel, to allow the user to inspect
|
||||
// the stack trace.
|
||||
if (level == "trace") {
|
||||
@ -1208,10 +1192,6 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
|
||||
if (level == "dir") {
|
||||
// Make sure the cached evaluated object will be purged when the node is
|
||||
// removed.
|
||||
node._evalCacheId = aMessage.objectsCacheId;
|
||||
|
||||
// Initialize the inspector message node, by setting the PropertyTreeView
|
||||
// object on the tree view. This has to be done *after* the node is
|
||||
// shown, because the tree binding must be attached first.
|
||||
@ -1223,6 +1203,18 @@ WebConsoleFrame.prototype = {
|
||||
return node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle ConsoleAPICall objects received from the server. This method outputs
|
||||
* the window.console API call.
|
||||
*
|
||||
* @param object aMessage
|
||||
* The console API message received from the server.
|
||||
*/
|
||||
handleConsoleAPICall: function WCF_handleConsoleAPICall(aMessage)
|
||||
{
|
||||
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage, [aMessage]);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click event handler for objects shown inline coming from the
|
||||
* window.console API.
|
||||
@ -1233,11 +1225,11 @@ WebConsoleFrame.prototype = {
|
||||
* @param nsIDOMNode aAnchor
|
||||
* The object inspector anchor element. This is the clickable element
|
||||
* in the console.log message we display.
|
||||
* @param array aRemoteObject
|
||||
* The remote object representation.
|
||||
* @param object aObjectActor
|
||||
* The object actor grip.
|
||||
*/
|
||||
_consoleLogClick:
|
||||
function WCF__consoleLogClick(aMessage, aAnchor, aRemoteObject)
|
||||
function WCF__consoleLogClick(aMessage, aAnchor, aObjectActor)
|
||||
{
|
||||
if (aAnchor._panelOpen) {
|
||||
return;
|
||||
@ -1249,29 +1241,28 @@ WebConsoleFrame.prototype = {
|
||||
|
||||
// Data to inspect.
|
||||
data: {
|
||||
// This is where the resultObject children are cached.
|
||||
rootCacheId: aMessage._evalCacheId,
|
||||
remoteObject: aRemoteObject,
|
||||
// This is where all objects retrieved by the panel will be cached.
|
||||
panelCacheId: "HUDPanel-" + gSequenceId(),
|
||||
remoteObjectProvider: this.jsterm.remoteObjectProvider.bind(this.jsterm),
|
||||
objectPropertiesProvider: this.objectPropertiesProvider.bind(this),
|
||||
releaseObject: this._releaseObject.bind(this),
|
||||
},
|
||||
};
|
||||
|
||||
let propPanel = this.jsterm.openPropertyPanel(options);
|
||||
propPanel.panel.setAttribute("hudId", this.hudId);
|
||||
|
||||
let onPopupHide = function JST__evalInspectPopupHide() {
|
||||
let propPanel;
|
||||
let onPopupHide = function _onPopupHide() {
|
||||
propPanel.panel.removeEventListener("popuphiding", onPopupHide, false);
|
||||
|
||||
this.jsterm.clearObjectCache(options.data.panelCacheId);
|
||||
|
||||
if (!aMessage.parentNode && aMessage._evalCacheId) {
|
||||
this.jsterm.clearObjectCache(aMessage._evalCacheId);
|
||||
if (!aMessage.parentNode && aMessage._objectActors) {
|
||||
aMessage._objectActors.forEach(this._releaseObject, this);
|
||||
aMessage._objectActors = null;
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
|
||||
this.objectPropertiesProvider(aObjectActor.actor,
|
||||
function _onObjectProperties(aProperties) {
|
||||
options.data.objectProperties = aProperties;
|
||||
propPanel = this.jsterm.openPropertyPanel(options);
|
||||
propPanel.panel.setAttribute("hudId", this.hudId);
|
||||
propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1453,14 +1444,12 @@ WebConsoleFrame.prototype = {
|
||||
/**
|
||||
* Inform user that the Web Console API has been replaced by a script
|
||||
* in a content page.
|
||||
*
|
||||
* @return nsIDOMElement|undefined
|
||||
* The message element to display in the Web Console output.
|
||||
*/
|
||||
logWarningAboutReplacedAPI: function WCF_logWarningAboutReplacedAPI()
|
||||
{
|
||||
return this.createMessageNode(CATEGORY_JS, SEVERITY_WARNING,
|
||||
l10n.getStr("ConsoleAPIDisabled"));
|
||||
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_WARNING,
|
||||
l10n.getStr("ConsoleAPIDisabled"));
|
||||
this.outputMessage(CATEGORY_JS, node);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1852,8 +1841,8 @@ WebConsoleFrame.prototype = {
|
||||
{
|
||||
let [category, methodOrNode, args] = aItem;
|
||||
if (typeof methodOrNode != "function" &&
|
||||
methodOrNode._evalCacheId && !methodOrNode._panelOpen) {
|
||||
this.jsterm.clearObjectCache(methodOrNode._evalCacheId);
|
||||
methodOrNode._objectActors && !methodOrNode._panelOpen) {
|
||||
methodOrNode._objectActors.forEach(this._releaseObject, this);
|
||||
}
|
||||
|
||||
if (category == CATEGORY_NETWORK) {
|
||||
@ -1870,9 +1859,29 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
else if (category == CATEGORY_WEBDEV &&
|
||||
methodOrNode == this.logConsoleAPIMessage) {
|
||||
let level = args[0].apiMessage.level;
|
||||
if (level == "dir") {
|
||||
this.jsterm.clearObjectCache(args[0].objectsCacheId);
|
||||
let level = args[0].level;
|
||||
let releaseObject = function _releaseObject(aValue) {
|
||||
if (aValue && typeof aValue == "object" && aValue.actor) {
|
||||
this._releaseObject(aValue.actor);
|
||||
}
|
||||
}.bind(this);
|
||||
switch (level) {
|
||||
case "log":
|
||||
case "info":
|
||||
case "warn":
|
||||
case "error":
|
||||
case "debug":
|
||||
case "dir":
|
||||
case "groupEnd": {
|
||||
args[0].arguments.forEach(releaseObject);
|
||||
if (level == "dir") {
|
||||
args[0].objectProperties.forEach(function(aObject) {
|
||||
["value", "get", "set"].forEach(function(aProp) {
|
||||
releaseObject(aObject[aProp]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1916,10 +1925,8 @@ WebConsoleFrame.prototype = {
|
||||
|
||||
let tree = aMessageNode.querySelector("tree");
|
||||
tree.parentNode.removeChild(tree);
|
||||
aMessageNode.propertyTreeView.data = null;
|
||||
aMessageNode.propertyTreeView = null;
|
||||
if (tree.view) {
|
||||
tree.view.data = null;
|
||||
}
|
||||
tree.view = null;
|
||||
},
|
||||
|
||||
@ -1931,8 +1938,8 @@ WebConsoleFrame.prototype = {
|
||||
*/
|
||||
removeOutputMessage: function WCF_removeOutputMessage(aNode)
|
||||
{
|
||||
if (aNode._evalCacheId && !aNode._panelOpen) {
|
||||
this.jsterm.clearObjectCache(aNode._evalCacheId);
|
||||
if (aNode._objectActors && !aNode._panelOpen) {
|
||||
aNode._objectActors.forEach(this._releaseObject, this);
|
||||
}
|
||||
|
||||
if (aNode.classList.contains("webconsole-msg-cssparser")) {
|
||||
@ -2057,7 +2064,7 @@ WebConsoleFrame.prototype = {
|
||||
else {
|
||||
let str = undefined;
|
||||
if (aLevel == "dir") {
|
||||
str = aBody.resultString;
|
||||
str = WebConsoleUtils.objectActorGripToString(aBody.arguments[0]);
|
||||
}
|
||||
else if (["log", "info", "warn", "error", "debug"].indexOf(aLevel) > -1 &&
|
||||
typeof aBody == "object") {
|
||||
@ -2132,10 +2139,9 @@ WebConsoleFrame.prototype = {
|
||||
let treeView = node.propertyTreeView = new PropertyTreeView();
|
||||
|
||||
treeView.data = {
|
||||
rootCacheId: body.cacheId,
|
||||
panelCacheId: body.cacheId,
|
||||
remoteObject: Array.isArray(body.remoteObject) ? body.remoteObject : [],
|
||||
remoteObjectProvider: body.remoteObjectProvider,
|
||||
objectPropertiesProvider: this.objectPropertiesProvider.bind(this),
|
||||
releaseObject: this._releaseObject.bind(this),
|
||||
objectProperties: body.objectProperties,
|
||||
};
|
||||
|
||||
tree.setAttribute("rows", treeView.rowCount);
|
||||
@ -2164,13 +2170,12 @@ WebConsoleFrame.prototype = {
|
||||
* output.
|
||||
* @param object aBody
|
||||
* The object given by this.logConsoleAPIMessage(). This object holds
|
||||
* the call information that we need to display.
|
||||
* the call information that we need to display - mainly the arguments
|
||||
* array of the given API call.
|
||||
*/
|
||||
_makeConsoleLogMessageBody:
|
||||
function WCF__makeConsoleLogMessageBody(aMessage, aContainer, aBody)
|
||||
{
|
||||
aMessage._evalCacheId = aBody.cacheId;
|
||||
|
||||
Object.defineProperty(aMessage, "_panelOpen", {
|
||||
get: function() {
|
||||
let nodes = aContainer.querySelectorAll(".hud-clickable");
|
||||
@ -2182,17 +2187,19 @@ WebConsoleFrame.prototype = {
|
||||
configurable: false
|
||||
});
|
||||
|
||||
aBody.remoteObjects.forEach(function(aItem, aIndex) {
|
||||
aBody.arguments.forEach(function(aItem) {
|
||||
if (aContainer.firstChild) {
|
||||
aContainer.appendChild(this.document.createTextNode(" "));
|
||||
}
|
||||
|
||||
let text = aBody.argsToString[aIndex];
|
||||
if (!Array.isArray(aItem)) {
|
||||
let text = WebConsoleUtils.objectActorGripToString(aItem);
|
||||
|
||||
if (aItem && typeof aItem != "object" || !aItem.inspectable) {
|
||||
aContainer.appendChild(this.document.createTextNode(text));
|
||||
return;
|
||||
}
|
||||
|
||||
// For inspectable objects.
|
||||
let elem = this.document.createElement("description");
|
||||
elem.classList.add("hud-clickable");
|
||||
elem.setAttribute("aria-haspopup", "true");
|
||||
@ -2393,6 +2400,41 @@ WebConsoleFrame.prototype = {
|
||||
clipboardHelper.copyString(strings.join("\n"), this.document);
|
||||
},
|
||||
|
||||
/**
|
||||
* Object properties provider. This function gives you the properties of the
|
||||
* remote object you want.
|
||||
*
|
||||
* @param string aActor
|
||||
* The object actor ID from which you want the properties.
|
||||
* @param function aCallback
|
||||
* Function you want invoked once the properties are received.
|
||||
*/
|
||||
objectPropertiesProvider:
|
||||
function WCF_objectPropertiesProvider(aActor, aCallback)
|
||||
{
|
||||
this.webConsoleClient.inspectObjectProperties(aActor,
|
||||
function(aResponse) {
|
||||
if (aResponse.error) {
|
||||
Cu.reportError("Failed to retrieve the object properties from the " +
|
||||
"server. Error: " + aResponse.error);
|
||||
return;
|
||||
}
|
||||
aCallback(aResponse.properties);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Release an object actor.
|
||||
*
|
||||
* @private
|
||||
* @param string aActor
|
||||
* The object actor ID you want to release.
|
||||
*/
|
||||
_releaseObject: function WCF__releaseObject(aActor)
|
||||
{
|
||||
this.proxy.releaseActor(aActor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the selected item's URL in a new tab.
|
||||
*/
|
||||
@ -2478,6 +2520,12 @@ JSTerm.prototype = {
|
||||
*/
|
||||
get outputNode() this.hud.outputNode,
|
||||
|
||||
/**
|
||||
* Getter for the debugger WebConsoleClient.
|
||||
* @type object
|
||||
*/
|
||||
get webConsoleClient() this.hud.webConsoleClient,
|
||||
|
||||
COMPLETE_FORWARD: 0,
|
||||
COMPLETE_BACKWARD: 1,
|
||||
COMPLETE_HINT_ONLY: 2,
|
||||
@ -2499,59 +2547,59 @@ JSTerm.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchronously evaluate a string in the content process sandbox.
|
||||
*
|
||||
* @param string aString
|
||||
* String to evaluate in the content process JavaScript sandbox.
|
||||
* @param function [aCallback]
|
||||
* Optional function to be invoked when the evaluation result is
|
||||
* received.
|
||||
*/
|
||||
evalInContentSandbox: function JST_evalInContentSandbox(aString, aCallback)
|
||||
{
|
||||
let message = {
|
||||
str: aString,
|
||||
resultCacheId: "HUDEval-" + gSequenceId(),
|
||||
};
|
||||
|
||||
this.hud.owner.sendMessageToContent("JSTerm:EvalRequest", message, aCallback);
|
||||
|
||||
return message;
|
||||
},
|
||||
|
||||
/**
|
||||
* The "JSTerm:EvalResult" message handler. This is the JSTerm execution
|
||||
* result callback which is invoked whenever JavaScript code evaluation
|
||||
* results come from the content process.
|
||||
* The JavaScript evaluation response handler.
|
||||
*
|
||||
* @private
|
||||
* @param nsIDOMElement [aAfterNode]
|
||||
* Optional DOM element after which the evaluation result will be
|
||||
* inserted.
|
||||
* @param function [aCallback]
|
||||
* Optional function to invoke when the evaluation result is added to
|
||||
* the output.
|
||||
* @param object aResponse
|
||||
* The JSTerm:EvalResult message received from the content process. See
|
||||
* JSTerm.handleEvalRequest() in HUDService-content.js for further
|
||||
* details.
|
||||
* @param object aRequest
|
||||
* The JSTerm:EvalRequest message we sent to the content process.
|
||||
* @see JSTerm.handleEvalRequest() in HUDService-content.js
|
||||
* The message received from the server.
|
||||
*/
|
||||
_executeResultCallback:
|
||||
function JST__executeResultCallback(aCallback, aResponse, aRequest)
|
||||
function JST__executeResultCallback(aAfterNode, aCallback, aResponse)
|
||||
{
|
||||
let errorMessage = aResponse.errorMessage;
|
||||
let resultString = aResponse.resultString;
|
||||
let result = aResponse.result;
|
||||
let inspectable = result && typeof result == "object" && result.inspectable;
|
||||
let helperResult = aResponse.helperResult;
|
||||
let helperHasRawOutput = !!(helperResult || {}).rawOutput;
|
||||
let resultString =
|
||||
WebConsoleUtils.objectActorGripToString(result,
|
||||
!helperHasRawOutput);
|
||||
|
||||
// Hide undefined results coming from JSTerm helper functions.
|
||||
if (!errorMessage &&
|
||||
resultString == "undefined" &&
|
||||
aResponse.helperResult &&
|
||||
!aResponse.inspectable &&
|
||||
!aResponse.helperRawOutput) {
|
||||
return;
|
||||
if (helperResult && helperResult.type) {
|
||||
switch (helperResult.type) {
|
||||
case "clearOutput":
|
||||
this.clearOutput();
|
||||
break;
|
||||
case "inspectObject":
|
||||
this.handleInspectObject(helperResult.input, helperResult.object);
|
||||
break;
|
||||
case "error":
|
||||
try {
|
||||
errorMessage = l10n.getStr(helperResult.message);
|
||||
}
|
||||
catch (ex) {
|
||||
errorMessage = helperResult.message;
|
||||
}
|
||||
break;
|
||||
case "help":
|
||||
this.hud.owner.openLink(HELP_URL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let afterNode = aRequest.outputNode;
|
||||
// Hide undefined results coming from JSTerm helper functions.
|
||||
if (!errorMessage && result && typeof result == "object" &&
|
||||
result.type == "undefined" &&
|
||||
helperResult && !helperHasRawOutput) {
|
||||
aCallback && aCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (aCallback) {
|
||||
let oldFlushCallback = this.hud._flushCallback;
|
||||
@ -2562,19 +2610,24 @@ JSTerm.prototype = {
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
if (aResponse.errorMessage) {
|
||||
this.writeOutput(aResponse.errorMessage, CATEGORY_OUTPUT, SEVERITY_ERROR,
|
||||
afterNode, aResponse.timestamp);
|
||||
let node;
|
||||
|
||||
if (errorMessage) {
|
||||
node = this.writeOutput(errorMessage, CATEGORY_OUTPUT, SEVERITY_ERROR,
|
||||
aAfterNode, aResponse.timestamp);
|
||||
}
|
||||
else if (aResponse.inspectable) {
|
||||
let node = this.writeOutputJS(aResponse.resultString,
|
||||
this._evalOutputClick.bind(this, aResponse),
|
||||
afterNode, aResponse.timestamp);
|
||||
node._evalCacheId = aResponse.childrenCacheId;
|
||||
else if (inspectable) {
|
||||
node = this.writeOutputJS(resultString,
|
||||
this._evalOutputClick.bind(this, aResponse),
|
||||
aAfterNode, aResponse.timestamp);
|
||||
}
|
||||
else {
|
||||
this.writeOutput(aResponse.resultString, CATEGORY_OUTPUT, SEVERITY_LOG,
|
||||
afterNode, aResponse.timestamp);
|
||||
node = this.writeOutput(resultString, CATEGORY_OUTPUT, SEVERITY_LOG,
|
||||
aAfterNode, aResponse.timestamp);
|
||||
}
|
||||
|
||||
if (result && typeof result == "object" && result.actor) {
|
||||
node._objectActors = [result.actor];
|
||||
}
|
||||
},
|
||||
|
||||
@ -2597,10 +2650,9 @@ JSTerm.prototype = {
|
||||
}
|
||||
|
||||
let node = this.writeOutput(aExecuteString, CATEGORY_INPUT, SEVERITY_LOG);
|
||||
let onResult = this._executeResultCallback.bind(this, node, aCallback);
|
||||
|
||||
let onResult = this._executeResultCallback.bind(this, aCallback);
|
||||
let messageToContent = this.evalInContentSandbox(aExecuteString, onResult);
|
||||
messageToContent.outputNode = node;
|
||||
this.webConsoleClient.evaluateJS(aExecuteString, onResult);
|
||||
|
||||
this.history.push(aExecuteString);
|
||||
this.historyIndex++;
|
||||
@ -2751,7 +2803,7 @@ JSTerm.prototype = {
|
||||
hud._cssNodes = {};
|
||||
|
||||
if (aClearStorage) {
|
||||
hud.owner.sendMessageToContent("ConsoleAPI:ClearCache", {});
|
||||
this.webConsoleClient.clearMessagesCache();
|
||||
}
|
||||
},
|
||||
|
||||
@ -3078,25 +3130,30 @@ JSTerm.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = {
|
||||
id: "HUDComplete-" + gSequenceId(),
|
||||
input: this.inputNode.value,
|
||||
};
|
||||
let requestId = gSequenceId();
|
||||
let input = this.inputNode.value;
|
||||
let cursor = this.inputNode.selectionStart;
|
||||
|
||||
// TODO: Bug 787986 - throttle/disable updates, deal with slow/high latency
|
||||
// network connections.
|
||||
this.lastCompletion = {
|
||||
requestId: message.id,
|
||||
requestId: requestId,
|
||||
completionType: aType,
|
||||
value: null,
|
||||
};
|
||||
let callback = this._receiveAutocompleteProperties.bind(this, aCallback);
|
||||
this.hud.owner.sendMessageToContent("JSTerm:Autocomplete", message, callback);
|
||||
|
||||
let callback = this._receiveAutocompleteProperties.bind(this, requestId,
|
||||
aCallback);
|
||||
this.webConsoleClient.autocomplete(input, cursor, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "JSTerm:AutocompleteProperties" message. This method takes
|
||||
* the completion result received from the content process and updates the UI
|
||||
* Handler for the autocompletion results. This method takes
|
||||
* the completion result received from the server and updates the UI
|
||||
* accordingly.
|
||||
*
|
||||
* @param number aRequestId
|
||||
* Request ID.
|
||||
* @param function [aCallback=null]
|
||||
* Optional, function to invoke when the completion result is received.
|
||||
* @param object aMessage
|
||||
@ -3104,13 +3161,12 @@ JSTerm.prototype = {
|
||||
* the content process.
|
||||
*/
|
||||
_receiveAutocompleteProperties:
|
||||
function JST__receiveAutocompleteProperties(aCallback, aMessage)
|
||||
function JST__receiveAutocompleteProperties(aRequestId, aCallback, aMessage)
|
||||
{
|
||||
let inputNode = this.inputNode;
|
||||
let inputValue = inputNode.value;
|
||||
if (aMessage.input != inputValue ||
|
||||
this.lastCompletion.value == inputValue ||
|
||||
aMessage.id != this.lastCompletion.requestId) {
|
||||
if (this.lastCompletion.value == inputValue ||
|
||||
aRequestId != this.lastCompletion.requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3266,7 +3322,7 @@ JSTerm.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* The "JSTerm:InspectObject" remote message handler. This allows the content
|
||||
* The JSTerm InspectObject remote message handler. This allows the remote
|
||||
* process to open the Property Panel for a given object.
|
||||
*
|
||||
* @param object aRequest
|
||||
@ -3274,29 +3330,31 @@ JSTerm.prototype = {
|
||||
* the user input string that was evaluated to inspect an object and
|
||||
* the result object which is to be inspected.
|
||||
*/
|
||||
handleInspectObject: function JST_handleInspectObject(aRequest)
|
||||
handleInspectObject: function JST_handleInspectObject(aInput, aActor)
|
||||
{
|
||||
let options = {
|
||||
title: aRequest.input,
|
||||
title: aInput,
|
||||
|
||||
data: {
|
||||
rootCacheId: aRequest.objectCacheId,
|
||||
panelCacheId: aRequest.objectCacheId,
|
||||
remoteObject: aRequest.resultObject,
|
||||
remoteObjectProvider: this.remoteObjectProvider.bind(this),
|
||||
objectPropertiesProvider: this.hud.objectPropertiesProvider.bind(this.hud),
|
||||
releaseObject: this.hud._releaseObject.bind(this.hud),
|
||||
},
|
||||
};
|
||||
|
||||
let propPanel = this.openPropertyPanel(options);
|
||||
propPanel.panel.setAttribute("hudId", this.hudId);
|
||||
let propPanel;
|
||||
|
||||
let onPopupHide = function JST__onPopupHide() {
|
||||
propPanel.panel.removeEventListener("popuphiding", onPopupHide, false);
|
||||
|
||||
this.clearObjectCache(options.data.panelCacheId);
|
||||
this.hud._releaseObject(aActor.actor);
|
||||
}.bind(this);
|
||||
|
||||
propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
|
||||
this.hud.objectPropertiesProvider(aActor.actor,
|
||||
function _onObjectProperties(aProperties) {
|
||||
options.data.objectProperties = aProperties;
|
||||
propPanel = this.openPropertyPanel(options);
|
||||
propPanel.panel.setAttribute("hudId", this.hudId);
|
||||
propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3304,7 +3362,7 @@ JSTerm.prototype = {
|
||||
*
|
||||
* @private
|
||||
* @param object aResponse
|
||||
* The JSTerm:EvalResult message received from the content process.
|
||||
* The JavaScript evaluation response received from the server.
|
||||
* @param nsIDOMNode aLink
|
||||
* The message node for which we are handling events.
|
||||
*/
|
||||
@ -3320,35 +3378,36 @@ JSTerm.prototype = {
|
||||
|
||||
// Data to inspect.
|
||||
data: {
|
||||
// This is where the resultObject children are cached.
|
||||
rootCacheId: aResponse.childrenCacheId,
|
||||
remoteObject: aResponse.resultObject,
|
||||
// This is where all objects retrieved by the panel will be cached.
|
||||
panelCacheId: "HUDPanel-" + gSequenceId(),
|
||||
remoteObjectProvider: this.remoteObjectProvider.bind(this),
|
||||
objectPropertiesProvider: this.hud.objectPropertiesProvider.bind(this.hud),
|
||||
releaseObject: this.hud._releaseObject.bind(this.hud),
|
||||
},
|
||||
};
|
||||
|
||||
options.updateButtonCallback = function JST__evalUpdateButton() {
|
||||
this.evalInContentSandbox(aResponse.input,
|
||||
this._evalOutputUpdatePanelCallback.bind(this, options, propPanel,
|
||||
aResponse));
|
||||
}.bind(this);
|
||||
let propPanel;
|
||||
|
||||
let propPanel = this.openPropertyPanel(options);
|
||||
propPanel.panel.setAttribute("hudId", this.hudId);
|
||||
options.updateButtonCallback = function JST__evalUpdateButton() {
|
||||
let onResult =
|
||||
this._evalOutputUpdatePanelCallback.bind(this, options, propPanel,
|
||||
aResponse);
|
||||
this.webConsoleClient.evaluateJS(aResponse.input, onResult);
|
||||
}.bind(this);
|
||||
|
||||
let onPopupHide = function JST__evalInspectPopupHide() {
|
||||
propPanel.panel.removeEventListener("popuphiding", onPopupHide, false);
|
||||
|
||||
this.clearObjectCache(options.data.panelCacheId);
|
||||
|
||||
if (!aLinkNode.parentNode && aLinkNode._evalCacheId) {
|
||||
this.clearObjectCache(aLinkNode._evalCacheId);
|
||||
if (!aLinkNode.parentNode && aLinkNode._objectActors) {
|
||||
aLinkNode._objectActors.forEach(this.hud._releaseObject, this.hud);
|
||||
aLinkNode._objectActors = null;
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
|
||||
this.hud.objectPropertiesProvider(aResponse.result.actor,
|
||||
function _onObjectProperties(aProperties) {
|
||||
options.data.objectProperties = aProperties;
|
||||
propPanel = this.openPropertyPanel(options);
|
||||
propPanel.panel.setAttribute("hudId", this.hudId);
|
||||
propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3377,32 +3436,40 @@ JSTerm.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aNewResponse.inspectable) {
|
||||
let result = aNewResponse.result;
|
||||
let inspectable = result && typeof result == "object" && result.inspectable;
|
||||
let newActor = result && typeof result == "object" ? result.actor : null;
|
||||
|
||||
let anchor = aOptions.anchor;
|
||||
if (anchor && newActor) {
|
||||
if (!anchor._objectActors) {
|
||||
anchor._objectActors = [];
|
||||
}
|
||||
if (anchor._objectActors.indexOf(newActor) == -1) {
|
||||
anchor._objectActors.push(newActor);
|
||||
}
|
||||
}
|
||||
|
||||
if (!inspectable) {
|
||||
this.writeOutput(l10n.getStr("JSTerm.updateNotInspectable"), CATEGORY_OUTPUT, SEVERITY_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearObjectCache(aOptions.data.panelCacheId);
|
||||
this.clearObjectCache(aOptions.data.rootCacheId);
|
||||
|
||||
if (aOptions.anchor && aOptions.anchor._evalCacheId) {
|
||||
aOptions.anchor._evalCacheId = aNewResponse.childrenCacheId;
|
||||
}
|
||||
|
||||
// Update the old response object such that when the panel is reopen, the
|
||||
// user sees the new response.
|
||||
aOldResponse.id = aNewResponse.id;
|
||||
aOldResponse.childrenCacheId = aNewResponse.childrenCacheId;
|
||||
aOldResponse.resultObject = aNewResponse.resultObject;
|
||||
aOldResponse.resultString = aNewResponse.resultString;
|
||||
aOldResponse.result = aNewResponse.result;
|
||||
aOldResponse.error = aNewResponse.error;
|
||||
aOldResponse.errorMessage = aNewResponse.errorMessage;
|
||||
aOldResponse.timestamp = aNewResponse.timestamp;
|
||||
|
||||
aOptions.data.rootCacheId = aNewResponse.childrenCacheId;
|
||||
aOptions.data.remoteObject = aNewResponse.resultObject;
|
||||
|
||||
// TODO: This updates the value of the tree.
|
||||
// However, the states of open nodes is not saved.
|
||||
// See bug 586246.
|
||||
aPropPanel.treeView.data = aOptions.data;
|
||||
this.hud.objectPropertiesProvider(newActor,
|
||||
function _onObjectProperties(aProperties) {
|
||||
aOptions.data.objectProperties = aProperties;
|
||||
// TODO: This updates the value of the tree.
|
||||
// However, the states of open nodes is not saved.
|
||||
// See bug 586246.
|
||||
aPropPanel.treeView.data = aOptions.data;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3633,6 +3700,7 @@ function WebConsoleConnectionProxy(aWebConsole)
|
||||
this.owner = aWebConsole;
|
||||
|
||||
this._onPageError = this._onPageError.bind(this);
|
||||
this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
|
||||
}
|
||||
|
||||
WebConsoleConnectionProxy.prototype = {
|
||||
@ -3666,6 +3734,14 @@ WebConsoleConnectionProxy.prototype = {
|
||||
*/
|
||||
_consoleActor: null,
|
||||
|
||||
/**
|
||||
* Tells if the window.console object of the remote web page is the native
|
||||
* object or not.
|
||||
* @private
|
||||
* @type boolean
|
||||
*/
|
||||
_hasNativeConsoleAPI: false,
|
||||
|
||||
/**
|
||||
* Initialize the debugger server.
|
||||
*/
|
||||
@ -3689,8 +3765,9 @@ WebConsoleConnectionProxy.prototype = {
|
||||
let client = this.client = new DebuggerClient(transport);
|
||||
|
||||
client.addListener("pageError", this._onPageError);
|
||||
client.addListener("consoleAPICall", this._onConsoleAPICall);
|
||||
|
||||
let listeners = ["PageError"];
|
||||
let listeners = ["PageError", "ConsoleAPI"];
|
||||
|
||||
client.connect(function(aType, aTraits) {
|
||||
client.listTabs(function(aResponse) {
|
||||
@ -3725,6 +3802,36 @@ WebConsoleConnectionProxy.prototype = {
|
||||
|
||||
this.webConsoleClient = aWebConsoleClient;
|
||||
|
||||
this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI;
|
||||
|
||||
let msgs = ["PageError", "ConsoleAPI"];
|
||||
this.webConsoleClient.getCachedMessages(msgs,
|
||||
this._onCachedMessages.bind(this, aCallback));
|
||||
},
|
||||
|
||||
/**
|
||||
* The "cachedMessages" response handler.
|
||||
*
|
||||
* @private
|
||||
* @param function [aCallback]
|
||||
* Optional function to invoke once the connection is established.
|
||||
* @param object aResponse
|
||||
* The JSON response object received from the server.
|
||||
*/
|
||||
_onCachedMessages: function WCCP__onCachedMessages(aCallback, aResponse)
|
||||
{
|
||||
if (aResponse.error) {
|
||||
Cu.reportError("Web Console getCachedMessages error: " + aResponse.error +
|
||||
" " + aResponse.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.owner.displayCachedMessages(aResponse.messages);
|
||||
|
||||
if (!this._hasNativeConsoleAPI) {
|
||||
this.owner.logWarningAboutReplacedAPI();
|
||||
}
|
||||
|
||||
this.connected = true;
|
||||
aCallback && aCallback();
|
||||
},
|
||||
@ -3746,6 +3853,36 @@ WebConsoleConnectionProxy.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "consoleAPICall" message type handler. We redirect any message to
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string aType
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onConsoleAPICall: function WCCP__onConsoleAPICall(aType, aPacket)
|
||||
{
|
||||
if (this.owner && aPacket.from == this._consoleActor) {
|
||||
this.owner.handleConsoleAPICall(aPacket.message);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Release an object actor.
|
||||
*
|
||||
* @param string aActor
|
||||
* The actor ID to send the request to.
|
||||
*/
|
||||
releaseActor: function WCCP_releaseActor(aActor)
|
||||
{
|
||||
if (this.client) {
|
||||
this.client.release(aActor);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnect the Web Console from the remote server.
|
||||
*
|
||||
@ -3760,6 +3897,7 @@ WebConsoleConnectionProxy.prototype = {
|
||||
}
|
||||
|
||||
this.client.removeListener("pageError", this._onPageError);
|
||||
this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
|
||||
this.client.close(aOnDisconnect);
|
||||
|
||||
this.client = null;
|
||||
|
@ -167,6 +167,7 @@ const ThreadStateTypes = {
|
||||
* by the client.
|
||||
*/
|
||||
const UnsolicitedNotifications = {
|
||||
"consoleAPICall": "consoleAPICall",
|
||||
"newScript": "newScript",
|
||||
"tabDetached": "tabDetached",
|
||||
"tabNavigated": "tabNavigated",
|
||||
@ -371,6 +372,20 @@ DebuggerClient.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Release an object actor.
|
||||
*
|
||||
* @param string aActor
|
||||
* The actor ID to send the request to.
|
||||
*/
|
||||
release: function DC_release(aActor) {
|
||||
let packet = {
|
||||
to: aActor,
|
||||
type: "release",
|
||||
};
|
||||
this.request(packet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a request to the debugging server.
|
||||
*
|
||||
|
@ -48,6 +48,75 @@ WebConsoleClient.prototype = {
|
||||
this._client.request(packet, aOnResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Inspect the properties of an object.
|
||||
*
|
||||
* @param string aActor
|
||||
* The WebConsoleObjectActor ID to send the request to.
|
||||
* @param function aOnResponse
|
||||
* The function invoked when the response is received.
|
||||
*/
|
||||
inspectObjectProperties:
|
||||
function WCC_inspectObjectProperties(aActor, aOnResponse)
|
||||
{
|
||||
let packet = {
|
||||
to: aActor,
|
||||
type: "inspectProperties",
|
||||
};
|
||||
this._client.request(packet, aOnResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Evaluate a JavaScript expression.
|
||||
*
|
||||
* @param string aString
|
||||
* The code you want to evaluate.
|
||||
* @param function aOnResponse
|
||||
* The function invoked when the response is received.
|
||||
*/
|
||||
evaluateJS: function WCC_evaluateJS(aString, aOnResponse)
|
||||
{
|
||||
let packet = {
|
||||
to: this._actor,
|
||||
type: "evaluateJS",
|
||||
text: aString,
|
||||
};
|
||||
this._client.request(packet, aOnResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Autocomplete a JavaScript expression.
|
||||
*
|
||||
* @param string aString
|
||||
* The code you want to autocomplete.
|
||||
* @param number aCursor
|
||||
* Cursor location inside the string. Index starts from 0.
|
||||
* @param function aOnResponse
|
||||
* The function invoked when the response is received.
|
||||
*/
|
||||
autocomplete: function WCC_autocomplete(aString, aCursor, aOnResponse)
|
||||
{
|
||||
let packet = {
|
||||
to: this._actor,
|
||||
type: "autocomplete",
|
||||
text: aString,
|
||||
cursor: aCursor,
|
||||
};
|
||||
this._client.request(packet, aOnResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the cache of messages (page errors and console API calls).
|
||||
*/
|
||||
clearMessagesCache: function WCC_clearMessagesCache()
|
||||
{
|
||||
let packet = {
|
||||
to: this._actor,
|
||||
type: "clearMessagesCache",
|
||||
};
|
||||
this._client.request(packet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the given Web Console listeners.
|
||||
*
|
||||
|
@ -15,8 +15,22 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider",
|
||||
"PageErrorListener"];
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIStorage",
|
||||
"resource://gre/modules/ConsoleAPIStorage.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider", "JSTermHelpers",
|
||||
"PageErrorListener", "ConsoleAPIListener"];
|
||||
|
||||
// Match the function name from the result of toString() or toSource().
|
||||
//
|
||||
// Examples:
|
||||
// (function foobar(a, b) { ...
|
||||
// function foobar2(a) { ...
|
||||
// function() { ...
|
||||
const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
|
||||
|
||||
// Match the function arguments from the result of toString() or toSource().
|
||||
const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
|
||||
|
||||
const TYPES = { OBJECT: 0,
|
||||
FUNCTION: 1,
|
||||
@ -213,7 +227,12 @@ var WebConsoleUtils = {
|
||||
case "error":
|
||||
case "number":
|
||||
case "regexp":
|
||||
output = aResult.toString();
|
||||
try {
|
||||
output = aResult + "";
|
||||
}
|
||||
catch (ex) {
|
||||
output = ex;
|
||||
}
|
||||
break;
|
||||
case "null":
|
||||
case "undefined":
|
||||
@ -309,8 +328,15 @@ var WebConsoleUtils = {
|
||||
getResultType: function WCU_getResultType(aResult)
|
||||
{
|
||||
let type = aResult === null ? "null" : typeof aResult;
|
||||
if (type == "object" && aResult.constructor && aResult.constructor.name) {
|
||||
type = aResult.constructor.name;
|
||||
try {
|
||||
if (type == "object" && aResult.constructor && aResult.constructor.name) {
|
||||
type = aResult.constructor.name + "";
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
// Prevent potential exceptions in page-provided objects from taking down
|
||||
// the Web Console. If the constructor.name is a getter that throws, or
|
||||
// something else bad happens.
|
||||
}
|
||||
|
||||
return type.toLowerCase();
|
||||
@ -442,27 +468,43 @@ var WebConsoleUtils = {
|
||||
if (typeof aObject != "object") {
|
||||
return false;
|
||||
}
|
||||
let desc;
|
||||
let desc = this.getPropertyDescriptor(aObject, aProp);
|
||||
return desc && desc.get && !this.isNativeFunction(desc.get);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the property descriptor for the given object.
|
||||
*
|
||||
* @param object aObject
|
||||
* The object that contains the property.
|
||||
* @param string aProp
|
||||
* The property you want to get the descriptor for.
|
||||
* @return object
|
||||
* Property descriptor.
|
||||
*/
|
||||
getPropertyDescriptor: function WCU_getPropertyDescriptor(aObject, aProp)
|
||||
{
|
||||
let desc = null;
|
||||
while (aObject) {
|
||||
try {
|
||||
if (desc = Object.getOwnPropertyDescriptor(aObject, aProp)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
catch (ex if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
|
||||
ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" ||
|
||||
ex.name == "TypeError")) {
|
||||
// Native getters throw here. See bug 520882.
|
||||
if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
|
||||
ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") {
|
||||
return false;
|
||||
}
|
||||
throw ex;
|
||||
// null throws TypeError.
|
||||
}
|
||||
try {
|
||||
aObject = Object.getPrototypeOf(aObject);
|
||||
}
|
||||
catch (ex if (ex.name == "TypeError")) {
|
||||
return desc;
|
||||
}
|
||||
aObject = Object.getPrototypeOf(aObject);
|
||||
}
|
||||
if (desc && desc.get && !this.isNativeFunction(desc.get)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return desc;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -549,37 +591,209 @@ var WebConsoleUtils = {
|
||||
pairs.push(pair);
|
||||
}
|
||||
|
||||
pairs.sort(function(a, b)
|
||||
{
|
||||
// Convert the pair.name to a number for later sorting.
|
||||
let aNumber = parseFloat(a.name);
|
||||
let bNumber = parseFloat(b.name);
|
||||
|
||||
// Sort numbers.
|
||||
if (!isNaN(aNumber) && isNaN(bNumber)) {
|
||||
return -1;
|
||||
}
|
||||
else if (isNaN(aNumber) && !isNaN(bNumber)) {
|
||||
return 1;
|
||||
}
|
||||
else if (!isNaN(aNumber) && !isNaN(bNumber)) {
|
||||
return aNumber - bNumber;
|
||||
}
|
||||
// Sort string.
|
||||
else if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
else if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
pairs.sort(this.propertiesSort);
|
||||
|
||||
return pairs;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort function for object properties.
|
||||
*
|
||||
* @param object a
|
||||
* Property descriptor.
|
||||
* @param object b
|
||||
* Property descriptor.
|
||||
* @return integer
|
||||
* -1 if a.name < b.name,
|
||||
* 1 if a.name > b.name,
|
||||
* 0 otherwise.
|
||||
*/
|
||||
propertiesSort: function WCU_propertiesSort(a, b)
|
||||
{
|
||||
// Convert the pair.name to a number for later sorting.
|
||||
let aNumber = parseFloat(a.name);
|
||||
let bNumber = parseFloat(b.name);
|
||||
|
||||
// Sort numbers.
|
||||
if (!isNaN(aNumber) && isNaN(bNumber)) {
|
||||
return -1;
|
||||
}
|
||||
else if (isNaN(aNumber) && !isNaN(bNumber)) {
|
||||
return 1;
|
||||
}
|
||||
else if (!isNaN(aNumber) && !isNaN(bNumber)) {
|
||||
return aNumber - bNumber;
|
||||
}
|
||||
// Sort string.
|
||||
else if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
else if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Inspect the properties of the given object. For each property a descriptor
|
||||
* object is created. The descriptor gives you information about the property
|
||||
* name, value, type, getter and setter. When the property value references
|
||||
* another object you get a wrapper that holds information about that object.
|
||||
*
|
||||
* @see this.inspectObjectProperty
|
||||
* @param object aObject
|
||||
* The object you want to inspect.
|
||||
* @param function aObjectWrapper
|
||||
* The function that creates wrappers for property values which
|
||||
* reference other objects. This function must take one argument, the
|
||||
* object to wrap, and it must return an object grip that gives
|
||||
* information about the referenced object.
|
||||
* @return array
|
||||
* An array of property descriptors.
|
||||
*/
|
||||
inspectObject: function WCU_inspectObject(aObject, aObjectWrapper)
|
||||
{
|
||||
let properties = [];
|
||||
let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
|
||||
let deprecated = ["width", "height", "inputEncoding"];
|
||||
|
||||
for (let name in aObject) {
|
||||
// See bug 632275: skip deprecated properties.
|
||||
if (isDOMDocument && deprecated.indexOf(name) > -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
properties.push(this.inspectObjectProperty(aObject, name, aObjectWrapper));
|
||||
}
|
||||
|
||||
return properties.sort(this.propertiesSort);
|
||||
},
|
||||
|
||||
/**
|
||||
* A helper method that creates a property descriptor for the provided object,
|
||||
* properly formatted for sending in a protocol response.
|
||||
*
|
||||
* The property value can reference other objects. Since actual objects cannot
|
||||
* be sent to the client, we need to send simple object grips - descriptors
|
||||
* for those objects. This is why you need to give an object wrapper function
|
||||
* that creates object grips.
|
||||
*
|
||||
* @param string aProperty
|
||||
* Property name for which we have the descriptor.
|
||||
* @param object aObject
|
||||
* The object that the descriptor is generated for.
|
||||
* @param function aObjectWrapper
|
||||
* This function is given the property value. Whatever the function
|
||||
* returns is used as the representation of the property value.
|
||||
* @return object
|
||||
* The property descriptor formatted for sending to the client.
|
||||
*/
|
||||
inspectObjectProperty:
|
||||
function WCU_inspectObjectProperty(aObject, aProperty, aObjectWrapper)
|
||||
{
|
||||
let descriptor = this.getPropertyDescriptor(aObject, aProperty) || {};
|
||||
|
||||
let result = { name: aProperty };
|
||||
result.configurable = descriptor.configurable;
|
||||
result.enumerable = descriptor.enumerable;
|
||||
result.writable = descriptor.writable;
|
||||
if (descriptor.value !== undefined) {
|
||||
result.value = this.createValueGrip(descriptor.value, aObjectWrapper);
|
||||
}
|
||||
else if (descriptor.get) {
|
||||
if (this.isNativeFunction(descriptor.get)) {
|
||||
result.value = this.createValueGrip(aObject[aProperty], aObjectWrapper);
|
||||
}
|
||||
else {
|
||||
result.get = this.createValueGrip(descriptor.get, aObjectWrapper);
|
||||
result.set = this.createValueGrip(descriptor.set, aObjectWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
// There are cases with properties that have no value and no getter. For
|
||||
// example window.screen.width.
|
||||
if (result.value === undefined && result.get === undefined) {
|
||||
result.value = this.createValueGrip(aObject[aProperty], aObjectWrapper);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Make an object grip for the given object. An object grip of the simplest
|
||||
* form with minimal information about the given object is returned. This
|
||||
* method is usually combined with other functions that add further state
|
||||
* information and object ID such that, later, the client is able to retrieve
|
||||
* more information about the object being represented by this grip.
|
||||
*
|
||||
* @param object aObject
|
||||
* The object you want to create a grip for.
|
||||
* @return object
|
||||
* The object grip.
|
||||
*/
|
||||
getObjectGrip: function WCU_getObjectGrip(aObject)
|
||||
{
|
||||
let className = null;
|
||||
let type = typeof aObject;
|
||||
|
||||
let result = {
|
||||
"type": type,
|
||||
"className": this.getObjectClassName(aObject),
|
||||
"displayString": this.formatResult(aObject),
|
||||
"inspectable": this.isObjectInspectable(aObject),
|
||||
};
|
||||
|
||||
if (type == "function") {
|
||||
result.functionName = this.getFunctionName(aObject);
|
||||
result.functionArguments = this.getFunctionArguments(aObject);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a grip for the given value. If the value is an object,
|
||||
* an object wrapper will be created.
|
||||
*
|
||||
* @param mixed aValue
|
||||
* The value you want to create a grip for, before sending it to the
|
||||
* client.
|
||||
* @param function aObjectWrapper
|
||||
* If the value is an object then the aObjectWrapper function is
|
||||
* invoked to give us an object grip. See this.getObjectGrip().
|
||||
* @return mixed
|
||||
* The value grip.
|
||||
*/
|
||||
createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper)
|
||||
{
|
||||
let type = typeof(aValue);
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
case "string":
|
||||
case "number":
|
||||
return aValue;
|
||||
case "object":
|
||||
case "function":
|
||||
if (aValue) {
|
||||
return aObjectWrapper(aValue);
|
||||
}
|
||||
default:
|
||||
if (aValue === null) {
|
||||
return { type: "null" };
|
||||
}
|
||||
|
||||
if (aValue === undefined) {
|
||||
return { type: "undefined" };
|
||||
}
|
||||
|
||||
Cu.reportError("Failed to provide a grip for value of " + type + ": " +
|
||||
aValue);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the given object is an iterator or a generator.
|
||||
*
|
||||
@ -638,6 +852,159 @@ var WebConsoleUtils = {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Make a string representation for an object actor grip.
|
||||
*
|
||||
* @param object aGrip
|
||||
* The object grip received from the server.
|
||||
* @param boolean [aFormatString=false]
|
||||
* Optional boolean that tells if you want strings to be unevaled or
|
||||
* not.
|
||||
* @return string
|
||||
* The object grip converted to a string.
|
||||
*/
|
||||
objectActorGripToString: function WCU_objectActorGripToString(aGrip, aFormatString)
|
||||
{
|
||||
// Primitives like strings and numbers are not sent as objects.
|
||||
// But null and undefined are sent as objects with the type property
|
||||
// telling which type of value we have.
|
||||
let type = typeof(aGrip);
|
||||
if (aGrip && type == "object") {
|
||||
return aGrip.displayString || aGrip.className || aGrip.type || type;
|
||||
}
|
||||
return type == "string" && aFormatString ?
|
||||
this.formatResultString(aGrip) : aGrip + "";
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to deduce the name of the provided function.
|
||||
*
|
||||
* @param funtion aFunction
|
||||
* The function whose name will be returned.
|
||||
* @return string
|
||||
* Function name.
|
||||
*/
|
||||
getFunctionName: function WCF_getFunctionName(aFunction)
|
||||
{
|
||||
let name = null;
|
||||
if (aFunction.name) {
|
||||
name = aFunction.name;
|
||||
}
|
||||
else {
|
||||
let desc;
|
||||
try {
|
||||
desc = aFunction.getOwnPropertyDescriptor("displayName");
|
||||
}
|
||||
catch (ex) { }
|
||||
if (desc && typeof desc.value == "string") {
|
||||
name = desc.value;
|
||||
}
|
||||
}
|
||||
if (!name) {
|
||||
try {
|
||||
let str = (aFunction.toString() || aFunction.toSource()) + "";
|
||||
name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
return name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to deduce the arguments of the provided function.
|
||||
*
|
||||
* @param funtion aFunction
|
||||
* The function whose name will be returned.
|
||||
* @return array
|
||||
* Function arguments.
|
||||
*/
|
||||
getFunctionArguments: function WCF_getFunctionArguments(aFunction)
|
||||
{
|
||||
let args = [];
|
||||
try {
|
||||
let str = (aFunction.toString() || aFunction.toSource()) + "";
|
||||
let argsString = (str.match(REGEX_MATCH_FUNCTION_ARGS) || [])[1];
|
||||
if (argsString) {
|
||||
args = argsString.split(/\s*,\s*/);
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
return args;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the object class name. For example, the |window| object has the Window
|
||||
* class name (based on [object Window]).
|
||||
*
|
||||
* @param object aObject
|
||||
* The object you want to get the class name for.
|
||||
* @return string
|
||||
* The object class name.
|
||||
*/
|
||||
getObjectClassName: function WCF_getObjectClassName(aObject)
|
||||
{
|
||||
if (aObject === null) {
|
||||
return "null";
|
||||
}
|
||||
if (aObject === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
let type = typeof aObject;
|
||||
if (type != "object") {
|
||||
return type;
|
||||
}
|
||||
|
||||
let className;
|
||||
|
||||
try {
|
||||
className = ((aObject + "").match(/^\[object (\S+)\]$/) || [])[1];
|
||||
if (!className) {
|
||||
className = ((aObject.constructor + "").match(/^\[object (\S+)\]$/) || [])[1];
|
||||
}
|
||||
if (!className && typeof aObject.constructor == "function") {
|
||||
className = this.getFunctionName(aObject.constructor);
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
|
||||
return className;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the string to display as a property value in the property panel.
|
||||
*
|
||||
* @param object aActor
|
||||
* Object actor grip.
|
||||
* @return string
|
||||
* Property value as suited for the property panel.
|
||||
*/
|
||||
getPropertyPanelValue: function WCU_getPropertyPanelValue(aActor)
|
||||
{
|
||||
if (aActor.get) {
|
||||
return "Getter";
|
||||
}
|
||||
|
||||
let val = aActor.value;
|
||||
if (typeof val == "string") {
|
||||
return this.formatResultString(val);
|
||||
}
|
||||
|
||||
if (typeof val != "object" || !val) {
|
||||
return val;
|
||||
}
|
||||
|
||||
if (val.type == "function" && val.functionName) {
|
||||
return "function " + val.functionName + "(" +
|
||||
val.functionArguments.join(", ") + ")";
|
||||
}
|
||||
if (val.type == "object" && val.className) {
|
||||
return val.className;
|
||||
}
|
||||
|
||||
return val.displayString || val.type;
|
||||
},
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@ -1163,3 +1530,327 @@ PageErrorListener.prototype =
|
||||
this.listener = this.window = null;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// The window.console API observer
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The window.console API observer. This allows the window.console API messages
|
||||
* to be sent to the remote Web Console instance.
|
||||
*
|
||||
* @constructor
|
||||
* @param nsIDOMWindow aWindow
|
||||
* The window object for which we are created.
|
||||
* @param object aOwner
|
||||
* The owner object must have the following methods:
|
||||
* - onConsoleAPICall(). This method is invoked with one argument, the
|
||||
* Console API message that comes from the observer service, whenever
|
||||
* a relevant console API call is received.
|
||||
*/
|
||||
function ConsoleAPIListener(aWindow, aOwner)
|
||||
{
|
||||
this.window = aWindow;
|
||||
this.owner = aOwner;
|
||||
}
|
||||
|
||||
ConsoleAPIListener.prototype =
|
||||
{
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
/**
|
||||
* The content window for which we listen to window.console API calls.
|
||||
* @type nsIDOMWindow
|
||||
*/
|
||||
window: null,
|
||||
|
||||
/**
|
||||
* The owner object which is notified of window.console API calls. It must
|
||||
* have a onConsoleAPICall method which is invoked with one argument: the
|
||||
* console API call object that comes from the observer service.
|
||||
*
|
||||
* @type object
|
||||
* @see WebConsoleActor
|
||||
*/
|
||||
owner: null,
|
||||
|
||||
/**
|
||||
* Initialize the window.console API observer.
|
||||
*/
|
||||
init: function CAL_init()
|
||||
{
|
||||
// Note that the observer is process-wide. We will filter the messages as
|
||||
// needed, see CAL_observe().
|
||||
Services.obs.addObserver(this, "console-api-log-event", false);
|
||||
},
|
||||
|
||||
/**
|
||||
* The console API message observer. When messages are received from the
|
||||
* observer service we forward them to the remote Web Console instance.
|
||||
*
|
||||
* @param object aMessage
|
||||
* The message object receives from the observer service.
|
||||
* @param string aTopic
|
||||
* The message topic received from the observer service.
|
||||
*/
|
||||
observe: function CAL_observe(aMessage, aTopic)
|
||||
{
|
||||
if (!this.owner || !this.window) {
|
||||
return;
|
||||
}
|
||||
|
||||
let apiMessage = aMessage.wrappedJSObject;
|
||||
let msgWindow = WebConsoleUtils.getWindowByOuterId(apiMessage.ID,
|
||||
this.window);
|
||||
if (!msgWindow || msgWindow.top != this.window) {
|
||||
// Not the same window!
|
||||
return;
|
||||
}
|
||||
|
||||
this.owner.onConsoleAPICall(apiMessage);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the cached messages for the current inner window.
|
||||
*
|
||||
* @return array
|
||||
* The array of cached messages. Each element is a Console API
|
||||
* prepared to be sent to the remote Web Console instance.
|
||||
*/
|
||||
getCachedMessages: function CAL_getCachedMessages()
|
||||
{
|
||||
let innerWindowId = WebConsoleUtils.getInnerWindowId(this.window);
|
||||
let messages = ConsoleAPIStorage.getEvents(innerWindowId);
|
||||
return messages;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the console API listener.
|
||||
*/
|
||||
destroy: function CAL_destroy()
|
||||
{
|
||||
Services.obs.removeObserver(this, "console-api-log-event");
|
||||
this.window = this.owner = null;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* JSTerm helper functions.
|
||||
*
|
||||
* Defines a set of functions ("helper functions") that are available from the
|
||||
* Web Console but not from the web page.
|
||||
*
|
||||
* A list of helper functions used by Firebug can be found here:
|
||||
* http://getfirebug.com/wiki/index.php/Command_Line_API
|
||||
*
|
||||
* @param object aOwner
|
||||
* The owning object.
|
||||
*/
|
||||
function JSTermHelpers(aOwner)
|
||||
{
|
||||
/**
|
||||
* Find a node by ID.
|
||||
*
|
||||
* @param string aId
|
||||
* The ID of the element you want.
|
||||
* @return nsIDOMNode or null
|
||||
* The result of calling document.querySelector(aSelector).
|
||||
*/
|
||||
aOwner.sandbox.$ = function JSTH_$(aSelector)
|
||||
{
|
||||
return aOwner.window.document.querySelector(aSelector);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the nodes matching a CSS selector.
|
||||
*
|
||||
* @param string aSelector
|
||||
* A string that is passed to window.document.querySelectorAll.
|
||||
* @return nsIDOMNodeList
|
||||
* Returns the result of document.querySelectorAll(aSelector).
|
||||
*/
|
||||
aOwner.sandbox.$$ = function JSTH_$$(aSelector)
|
||||
{
|
||||
return aOwner.window.document.querySelectorAll(aSelector);
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs an xPath query and returns all matched nodes.
|
||||
*
|
||||
* @param string aXPath
|
||||
* xPath search query to execute.
|
||||
* @param [optional] nsIDOMNode aContext
|
||||
* Context to run the xPath query on. Uses window.document if not set.
|
||||
* @return array of nsIDOMNode
|
||||
*/
|
||||
aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
|
||||
{
|
||||
let nodes = [];
|
||||
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);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the currently selected object in the highlighter.
|
||||
*
|
||||
* TODO: this implementation crosses the client/server boundaries! This is not
|
||||
* usable within a remote browser. To implement this feature correctly we need
|
||||
* support for remote inspection capabilities within the Inspector as well.
|
||||
* See bug 787975.
|
||||
*
|
||||
* @return nsIDOMElement|null
|
||||
* The DOM element currently selected in the highlighter.
|
||||
*/
|
||||
Object.defineProperty(aOwner.sandbox, "$0", {
|
||||
get: function() {
|
||||
try {
|
||||
return aOwner.chromeWindow().InspectorUI.selection;
|
||||
}
|
||||
catch (ex) {
|
||||
aOwner.window.console.error(ex.message);
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Clears the output of the JSTerm.
|
||||
*/
|
||||
aOwner.sandbox.clear = function JSTH_clear()
|
||||
{
|
||||
aOwner.helperResult = {
|
||||
type: "clearOutput",
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the result of Object.keys(aObject).
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to return the property names from.
|
||||
* @return array of strings
|
||||
*/
|
||||
aOwner.sandbox.keys = function JSTH_keys(aObject)
|
||||
{
|
||||
return Object.keys(WebConsoleUtils.unwrap(aObject));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the values of all properties on aObject.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to display the values from.
|
||||
* @return array of string
|
||||
*/
|
||||
aOwner.sandbox.values = function JSTH_values(aObject)
|
||||
{
|
||||
let arrValues = [];
|
||||
let obj = WebConsoleUtils.unwrap(aObject);
|
||||
|
||||
try {
|
||||
for (let prop in obj) {
|
||||
arrValues.push(obj[prop]);
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
aOwner.window.console.error(ex.message);
|
||||
}
|
||||
|
||||
return arrValues;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a help window in MDN.
|
||||
*/
|
||||
aOwner.sandbox.help = function JSTH_help()
|
||||
{
|
||||
aOwner.helperResult = { type: "help" };
|
||||
};
|
||||
|
||||
/**
|
||||
* Inspects the passed aObject. This is done by opening the PropertyPanel.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to inspect.
|
||||
*/
|
||||
aOwner.sandbox.inspect = function JSTH_inspect(aObject)
|
||||
{
|
||||
let obj = WebConsoleUtils.unwrap(aObject);
|
||||
if (!WebConsoleUtils.isObjectInspectable(obj)) {
|
||||
return aObject;
|
||||
}
|
||||
|
||||
aOwner.helperResult = {
|
||||
type: "inspectObject",
|
||||
input: aOwner.evalInput,
|
||||
object: aOwner.createValueGrip(obj),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Prints aObject to the output.
|
||||
*
|
||||
* @param object aObject
|
||||
* Object to print to the output.
|
||||
* @return string
|
||||
*/
|
||||
aOwner.sandbox.pprint = function JSTH_pprint(aObject)
|
||||
{
|
||||
if (aObject === null || aObject === undefined || aObject === true ||
|
||||
aObject === false) {
|
||||
aOwner.helperResult = {
|
||||
type: "error",
|
||||
message: "helperFuncUnsupportedTypeError",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
aOwner.helperResult = { rawOutput: true };
|
||||
|
||||
if (typeof aObject == "function") {
|
||||
return aObject + "\n";
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
return " " + output.join("\n ");
|
||||
};
|
||||
|
||||
/**
|
||||
* Print a string to the output, as-is.
|
||||
*
|
||||
* @param string aString
|
||||
* A string you want to output.
|
||||
* @return void
|
||||
*/
|
||||
aOwner.sandbox.print = function JSTH_print(aString)
|
||||
{
|
||||
aOwner.helperResult = { rawOutput: true };
|
||||
return String(aString);
|
||||
};
|
||||
}
|
||||
|
@ -15,9 +15,25 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
|
||||
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
|
||||
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIListener",
|
||||
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "JSTermHelpers",
|
||||
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "JSPropertyProvider",
|
||||
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIStorage",
|
||||
"resource://gre/modules/ConsoleAPIStorage.jsm");
|
||||
|
||||
|
||||
/**
|
||||
* The WebConsoleActor implements capabilities needed for the Web Console
|
||||
* feature.
|
||||
@ -32,6 +48,9 @@ function WebConsoleActor(aConnection, aTabActor)
|
||||
{
|
||||
this.conn = aConnection;
|
||||
this._browser = aTabActor.browser;
|
||||
|
||||
this._objectActorsPool = new ActorPool(this.conn);
|
||||
this.conn.addActorPool(this._objectActorsPool);
|
||||
}
|
||||
|
||||
WebConsoleActor.prototype =
|
||||
@ -43,6 +62,29 @@ WebConsoleActor.prototype =
|
||||
*/
|
||||
_browser: null,
|
||||
|
||||
/**
|
||||
* Actor pool for all of the object actors for objects we send to the client.
|
||||
* @private
|
||||
* @type object
|
||||
* @see ActorPool
|
||||
* @see this.objectGrip()
|
||||
*/
|
||||
_objectActorsPool: null,
|
||||
|
||||
/**
|
||||
* Tells the current page location associated to the sandbox. When the page
|
||||
* location is changed, we recreate the sandbox.
|
||||
* @private
|
||||
* @type object
|
||||
*/
|
||||
_sandboxLocation: null,
|
||||
|
||||
/**
|
||||
* The JavaScript Sandbox where code is evaluated.
|
||||
* @type object
|
||||
*/
|
||||
sandbox: null,
|
||||
|
||||
/**
|
||||
* The debugger server connection instance.
|
||||
* @type object
|
||||
@ -61,6 +103,11 @@ WebConsoleActor.prototype =
|
||||
*/
|
||||
pageErrorListener: null,
|
||||
|
||||
/**
|
||||
* The ConsoleAPIListener instance.
|
||||
*/
|
||||
consoleAPIListener: null,
|
||||
|
||||
actorPrefix: "console",
|
||||
|
||||
grip: function WCA_grip()
|
||||
@ -68,6 +115,24 @@ WebConsoleActor.prototype =
|
||||
return { actor: this.actorID };
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells if the window.console object is native or overwritten by script in
|
||||
* the page.
|
||||
*
|
||||
* @return boolean
|
||||
* True if the window.console object is native, or false otherwise.
|
||||
*/
|
||||
hasNativeConsoleAPI: function WCA_hasNativeConsoleAPI()
|
||||
{
|
||||
let isNative = false;
|
||||
try {
|
||||
let consoleObject = WebConsoleUtils.unwrap(this.window).console;
|
||||
isNative = "__mozillaConsole__" in consoleObject;
|
||||
}
|
||||
catch (ex) { }
|
||||
return isNative;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the current WebConsoleActor instance.
|
||||
*/
|
||||
@ -77,9 +142,69 @@ WebConsoleActor.prototype =
|
||||
this.pageErrorListener.destroy();
|
||||
this.pageErrorListener = null;
|
||||
}
|
||||
if (this.consoleAPIListener) {
|
||||
this.consoleAPIListener.destroy();
|
||||
this.consoleAPIListener = null;
|
||||
}
|
||||
this.conn.removeActorPool(this._objectActorsPool);
|
||||
this._objectActorsPool = null;
|
||||
this._sandboxLocation = this.sandbox = null;
|
||||
this.conn = this._browser = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a grip for the given value. If the value is an object,
|
||||
* a WebConsoleObjectActor will be created.
|
||||
*
|
||||
* @param mixed aValue
|
||||
* @return object
|
||||
*/
|
||||
createValueGrip: function WCA_createValueGrip(aValue)
|
||||
{
|
||||
return WebConsoleUtils.createValueGrip(aValue,
|
||||
this.createObjectActor.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a grip for the given object.
|
||||
*
|
||||
* @param object aObject
|
||||
* The object you want.
|
||||
* @param object
|
||||
* The object grip.
|
||||
*/
|
||||
createObjectActor: function WCA_createObjectActor(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._objectActorsPool.addActor(actor);
|
||||
return actor.grip();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an object actor by its ID.
|
||||
*
|
||||
* @param string aActorID
|
||||
* @return object
|
||||
*/
|
||||
getObjectActorByID: function WCA_getObjectActorByID(aActorID)
|
||||
{
|
||||
return this._objectActorsPool.get(aActorID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Release an object grip for the given object actor.
|
||||
*
|
||||
* @param object aActor
|
||||
* The WebConsoleObjectActor instance you want to release.
|
||||
*/
|
||||
releaseObject: function WCA_releaseObject(aActor)
|
||||
{
|
||||
this._objectActorsPool.removeActor(aActor.actorID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "startListeners" request.
|
||||
*
|
||||
@ -103,9 +228,20 @@ WebConsoleActor.prototype =
|
||||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
case "ConsoleAPI":
|
||||
if (!this.consoleAPIListener) {
|
||||
this.consoleAPIListener =
|
||||
new ConsoleAPIListener(this.window, this);
|
||||
this.consoleAPIListener.init();
|
||||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { startedListeners: startedListeners };
|
||||
return {
|
||||
startedListeners: startedListeners,
|
||||
nativeConsoleAPI: this.hasNativeConsoleAPI(),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
@ -123,7 +259,7 @@ WebConsoleActor.prototype =
|
||||
|
||||
// If no specific listeners are requested to be detached, we stop all
|
||||
// listeners.
|
||||
let toDetach = aRequest.listeners || ["PageError"];
|
||||
let toDetach = aRequest.listeners || ["PageError", "ConsoleAPI"];
|
||||
|
||||
while (toDetach.length > 0) {
|
||||
let listener = toDetach.shift();
|
||||
@ -135,12 +271,209 @@ WebConsoleActor.prototype =
|
||||
}
|
||||
stoppedListeners.push(listener);
|
||||
break;
|
||||
case "ConsoleAPI":
|
||||
if (this.consoleAPIListener) {
|
||||
this.consoleAPIListener.destroy();
|
||||
this.consoleAPIListener = null;
|
||||
}
|
||||
stoppedListeners.push(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { stoppedListeners: stoppedListeners };
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "getCachedMessages" request. This method sends the cached
|
||||
* error messages and the window.console API calls to the client.
|
||||
*
|
||||
* @param object aRequest
|
||||
* The JSON request object received from the Web Console client.
|
||||
* @return object
|
||||
* The response packet to send to the client: it holds the cached
|
||||
* messages array.
|
||||
*/
|
||||
onGetCachedMessages: function WCA_onGetCachedMessages(aRequest)
|
||||
{
|
||||
let types = aRequest.messageTypes;
|
||||
if (!types) {
|
||||
return {
|
||||
error: "missingParameter",
|
||||
message: "The messageTypes parameter is missing.",
|
||||
};
|
||||
}
|
||||
|
||||
let messages = [];
|
||||
|
||||
while (types.length > 0) {
|
||||
let type = types.shift();
|
||||
switch (type) {
|
||||
case "ConsoleAPI":
|
||||
if (this.consoleAPIListener) {
|
||||
let cache = this.consoleAPIListener.getCachedMessages();
|
||||
cache.forEach(function(aMessage) {
|
||||
let message = this.prepareConsoleMessageForRemote(aMessage);
|
||||
message._type = type;
|
||||
messages.push(message);
|
||||
}, this);
|
||||
}
|
||||
break;
|
||||
case "PageError":
|
||||
if (this.pageErrorListener) {
|
||||
let cache = this.pageErrorListener.getCachedMessages();
|
||||
cache.forEach(function(aMessage) {
|
||||
let message = this.preparePageErrorForRemote(aMessage);
|
||||
message._type = type;
|
||||
messages.push(message);
|
||||
}, this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; });
|
||||
|
||||
return {
|
||||
from: this.actorID,
|
||||
messages: messages,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "evaluateJS" request. This method evaluates the given
|
||||
* JavaScript string and sends back the result.
|
||||
*
|
||||
* @param object aRequest
|
||||
* The JSON request object received from the Web Console client.
|
||||
* @return object
|
||||
* The evaluation response packet.
|
||||
*/
|
||||
onEvaluateJS: function WCA_onEvaluateJS(aRequest)
|
||||
{
|
||||
let input = aRequest.text;
|
||||
let result, error = null;
|
||||
let timestamp;
|
||||
|
||||
this.helperResult = null;
|
||||
this.evalInput = input;
|
||||
try {
|
||||
timestamp = Date.now();
|
||||
result = this.evalInSandbox(input);
|
||||
}
|
||||
catch (ex) {
|
||||
error = ex;
|
||||
}
|
||||
|
||||
let helperResult = this.helperResult;
|
||||
delete this.helperResult;
|
||||
delete this.evalInput;
|
||||
|
||||
return {
|
||||
from: this.actorID,
|
||||
input: input,
|
||||
result: this.createValueGrip(result),
|
||||
timestamp: timestamp,
|
||||
error: error,
|
||||
errorMessage: error ? String(error) : null,
|
||||
helperResult: helperResult,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* The Autocomplete request handler.
|
||||
*
|
||||
* @param object aRequest
|
||||
* The request message - what input to autocomplete.
|
||||
* @return object
|
||||
* The response message - matched properties.
|
||||
*/
|
||||
onAutocomplete: function WCA_onAutocomplete(aRequest)
|
||||
{
|
||||
let result = JSPropertyProvider(this.window, aRequest.text) || {};
|
||||
return {
|
||||
from: this.actorID,
|
||||
matches: result.matches || [],
|
||||
matchProp: result.matchProp,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* The "clearMessagesCache" request handler.
|
||||
*/
|
||||
onClearMessagesCache: function WCA_onClearMessagesCache()
|
||||
{
|
||||
// TODO: Bug 717611 - Web Console clear button does not clear cached errors
|
||||
let windowId = WebConsoleUtils.getInnerWindowId(this.window);
|
||||
ConsoleAPIStorage.clearEvents(windowId);
|
||||
return {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Create the JavaScript sandbox where user input is evaluated.
|
||||
* @private
|
||||
*/
|
||||
_createSandbox: function WCA__createSandbox()
|
||||
{
|
||||
this._sandboxLocation = this.window.location;
|
||||
this.sandbox = new Cu.Sandbox(this.window, {
|
||||
sandboxPrototype: this.window,
|
||||
wantXrays: false,
|
||||
});
|
||||
|
||||
this.sandbox.console = this.window.console;
|
||||
|
||||
JSTermHelpers(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Evaluates a string in the sandbox.
|
||||
*
|
||||
* @param string aString
|
||||
* String to evaluate in the sandbox.
|
||||
* @return mixed
|
||||
* The result of the evaluation.
|
||||
*/
|
||||
evalInSandbox: function WCA_evalInSandbox(aString)
|
||||
{
|
||||
// If the user changed to a different location, we need to update the
|
||||
// sandbox.
|
||||
if (this._sandboxLocation !== this.window.location) {
|
||||
this._createSandbox();
|
||||
}
|
||||
|
||||
// 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 $ = null, $$ = null;
|
||||
|
||||
// We prefer to execute the page-provided implementations for the $() and
|
||||
// $$() functions.
|
||||
if (typeof window.$ == "function") {
|
||||
$ = this.sandbox.$;
|
||||
delete this.sandbox.$;
|
||||
}
|
||||
if (typeof window.$$ == "function") {
|
||||
$$ = this.sandbox.$$;
|
||||
delete this.sandbox.$$;
|
||||
}
|
||||
|
||||
let result = Cu.evalInSandbox(aString, this.sandbox, "1.8",
|
||||
"Web Console", 1);
|
||||
|
||||
if ($) {
|
||||
this.sandbox.$ = $;
|
||||
}
|
||||
if ($$) {
|
||||
this.sandbox.$$ = $$;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for page errors received from the PageErrorListener. This method
|
||||
* sends the nsIScriptError to the remote Web Console client.
|
||||
@ -183,11 +516,165 @@ WebConsoleActor.prototype =
|
||||
strict: !!(aPageError.flags & aPageError.strictFlag),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for window.console API calls received from the ConsoleAPIListener.
|
||||
* This method sends the object to the remote Web Console client.
|
||||
*
|
||||
* @param object aMessage
|
||||
* The console API call we need to send to the remote client.
|
||||
*/
|
||||
onConsoleAPICall: function WCA_onConsoleAPICall(aMessage)
|
||||
{
|
||||
let packet = {
|
||||
from: this.actorID,
|
||||
type: "consoleAPICall",
|
||||
message: this.prepareConsoleMessageForRemote(aMessage),
|
||||
};
|
||||
this.conn.send(packet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepare a message from the console API to be sent to the remote Web Console
|
||||
* instance.
|
||||
*
|
||||
* @param object aMessage
|
||||
* The original message received from console-api-log-event.
|
||||
* @return object
|
||||
* The object that can be sent to the remote client.
|
||||
*/
|
||||
prepareConsoleMessageForRemote:
|
||||
function WCA_prepareConsoleMessageForRemote(aMessage)
|
||||
{
|
||||
let result = {
|
||||
level: aMessage.level,
|
||||
filename: aMessage.filename,
|
||||
lineNumber: aMessage.lineNumber,
|
||||
functionName: aMessage.functionName,
|
||||
timeStamp: aMessage.timeStamp,
|
||||
};
|
||||
|
||||
switch (result.level) {
|
||||
case "trace":
|
||||
case "group":
|
||||
case "groupCollapsed":
|
||||
case "time":
|
||||
case "timeEnd":
|
||||
result.arguments = aMessage.arguments;
|
||||
break;
|
||||
default:
|
||||
result.arguments = Array.map(aMessage.arguments || [],
|
||||
function(aObj) {
|
||||
return this.createValueGrip(aObj);
|
||||
}, this);
|
||||
|
||||
if (result.level == "dir") {
|
||||
result.objectProperties = [];
|
||||
let first = result.arguments[0];
|
||||
if (typeof first == "object" && first && first.inspectable) {
|
||||
let actor = this.getObjectActorByID(first.actor);
|
||||
result.objectProperties = actor.onInspectProperties().properties;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the XUL window that owns the content window.
|
||||
*
|
||||
* @return Window
|
||||
* The XUL window that owns the content window.
|
||||
*/
|
||||
chromeWindow: function WCA_chromeWindow()
|
||||
{
|
||||
return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler.ownerDocument.defaultView;
|
||||
},
|
||||
};
|
||||
|
||||
WebConsoleActor.prototype.requestTypes =
|
||||
{
|
||||
startListeners: WebConsoleActor.prototype.onStartListeners,
|
||||
stopListeners: WebConsoleActor.prototype.onStopListeners,
|
||||
getCachedMessages: WebConsoleActor.prototype.onGetCachedMessages,
|
||||
evaluateJS: WebConsoleActor.prototype.onEvaluateJS,
|
||||
autocomplete: WebConsoleActor.prototype.onAutocomplete,
|
||||
clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
return grip;
|
||||
},
|
||||
|
||||
/**
|
||||
* Releases this actor from the pool.
|
||||
*/
|
||||
release: function WCOA_release()
|
||||
{
|
||||
this.parent.releaseObject(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()
|
||||
{
|
||||
// TODO: Bug 787981 - use LongStringActor for strings that are too long.
|
||||
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,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user