Bug 843004 - Part 1: use the ConsoleOutput API for eval results and for console API messages; r=robcee

This commit is contained in:
Mihai Sucan 2013-11-25 14:15:40 +02:00
parent ce675de801
commit 1223210813
3 changed files with 1015 additions and 345 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1072,20 +1072,10 @@ function waitForMessages(aOptions)
return false; return false;
} }
if (aRule.type) { // The rule tries to match the newer types of messages, based on their
// The rule tries to match the newer types of messages, based on their // object constructor.
// object constructor. if (aRule.type && (!aElement._messageObject ||
if (!aElement._messageObject || !(aElement._messageObject instanceof aRule.type))) {
!(aElement._messageObject instanceof aRule.type)) {
return false;
}
}
else if (aElement._messageObject) {
// If the message element holds a reference to its object, it means this
// is a newer message type. All of the older waitForMessages() rules do
// not expect this kind of messages. We return false here.
// TODO: we keep this behavior until bug 778766 is fixed. After that we
// will not require |type| to match newer types of messages.
return false; return false;
} }

View File

@ -52,10 +52,6 @@ const CONSOLE_DIR_VIEW_HEIGHT = 0.6;
const IGNORED_SOURCE_URLS = ["debugger eval code", "self-hosted"]; const IGNORED_SOURCE_URLS = ["debugger eval code", "self-hosted"];
// The amount of time in milliseconds that must pass between messages to
// trigger the display of a new group.
const NEW_GROUP_DELAY = 5000;
// The amount of time in milliseconds that we wait before performing a live // The amount of time in milliseconds that we wait before performing a live
// search. // search.
const SEARCH_DELAY = 200; const SEARCH_DELAY = 200;
@ -166,9 +162,6 @@ const FILTER_PREFS_PREFIX = "devtools.webconsole.filter.";
// The minimum font size. // The minimum font size.
const MIN_FONT_SIZE = 10; const MIN_FONT_SIZE = 10;
// The maximum length of strings to be displayed by the Web Console.
const MAX_LONG_STRING_LENGTH = 200000;
const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout"; const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
const PREF_PERSISTLOG = "devtools.webconsole.persistlog"; const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages"; const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
@ -1162,6 +1155,7 @@ WebConsoleFrame.prototype = {
let level = aMessage.level; let level = aMessage.level;
let args = aMessage.arguments; let args = aMessage.arguments;
let objectActors = new Set(); let objectActors = new Set();
let node = null;
// Gather the actor IDs. // Gather the actor IDs.
args.forEach((aValue) => { args.forEach((aValue) => {
@ -1175,7 +1169,11 @@ WebConsoleFrame.prototype = {
case "info": case "info":
case "warn": case "warn":
case "error": case "error":
case "debug": case "debug": {
let msg = new Messages.ConsoleGeneric(aMessage);
node = msg.init(this.output).render().element;
break;
}
case "dir": { case "dir": {
body = { arguments: args }; body = { arguments: args };
let clipboardArray = []; let clipboardArray = [];
@ -1283,18 +1281,22 @@ WebConsoleFrame.prototype = {
return null; // no need to continue return null; // no need to continue
} }
let node = this.createMessageNode(CATEGORY_WEBDEV, LEVELS[level], body, if (!node) {
sourceURL, sourceLine, clipboardText, node = this.createMessageNode(CATEGORY_WEBDEV, LEVELS[level], body,
level, aMessage.timeStamp); sourceURL, sourceLine, clipboardText,
if (aMessage.private) { level, aMessage.timeStamp);
node.setAttribute("private", true); if (aMessage.private) {
node.setAttribute("private", true);
}
} }
if (objectActors.size > 0) { if (objectActors.size > 0) {
node._objectActors = objectActors; node._objectActors = objectActors;
let repeatNode = node.getElementsByClassName("repeats")[0]; if (!node._messageObject) {
repeatNode._uid += [...objectActors].join("-"); let repeatNode = node.getElementsByClassName("repeats")[0];
repeatNode._uid += [...objectActors].join("-");
}
} }
if (level == "trace") { if (level == "trace") {
@ -1316,26 +1318,6 @@ WebConsoleFrame.prototype = {
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage, [aMessage]); this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage, [aMessage]);
}, },
/**
* The click event handler for objects shown inline coming from the
* window.console API.
*
* @private
* @param nsIDOMNode aAnchor
* The object inspector anchor element. This is the clickable element
* in the console.log message we display.
* @param object aObjectActor
* The object actor grip.
*/
_consoleLogClick: function WCF__consoleLogClick(aAnchor, aObjectActor)
{
this.jsterm.openVariablesView({
label: aAnchor.textContent,
objectActor: aObjectActor,
autofocus: true,
});
},
/** /**
* Reports an error in the page source, either JavaScript or CSS. * Reports an error in the page source, either JavaScript or CSS.
* *
@ -1540,7 +1522,7 @@ WebConsoleFrame.prototype = {
aLinkNode.appendChild(mixedContentWarningNode); aLinkNode.appendChild(mixedContentWarningNode);
this._addMessageLinkCallback(mixedContentWarningNode, (aNode, aEvent) => { this._addMessageLinkCallback(mixedContentWarningNode, (aEvent) => {
aEvent.stopPropagation(); aEvent.stopPropagation();
this.owner.openLink(MIXED_CONTENT_LEARN_MORE); this.owner.openLink(MIXED_CONTENT_LEARN_MORE);
}); });
@ -1601,7 +1583,7 @@ WebConsoleFrame.prototype = {
warningNode.textContent = moreInfoLabel; warningNode.textContent = moreInfoLabel;
warningNode.className = "learn-more-link"; warningNode.className = "learn-more-link";
this._addMessageLinkCallback(warningNode, (aNode, aEvent) => { this._addMessageLinkCallback(warningNode, (aEvent) => {
aEvent.stopPropagation(); aEvent.stopPropagation();
this.owner.openLink(aURL); this.owner.openLink(aURL);
}); });
@ -1694,16 +1676,6 @@ WebConsoleFrame.prototype = {
this.outputMessage(CATEGORY_JS, node); this.outputMessage(CATEGORY_JS, node);
}, },
/**
* Inform user that the string he tries to view is too long.
*/
logWarningAboutStringTooLong: function WCF_logWarningAboutStringTooLong()
{
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_WARNING,
l10n.getStr("longStringTooLong"));
this.outputMessage(CATEGORY_JS, node);
},
/** /**
* Handle the network events coming from the remote Web Console. * Handle the network events coming from the remote Web Console.
* *
@ -2302,6 +2274,9 @@ WebConsoleFrame.prototype = {
*/ */
_pruneItemFromQueue: function WCF__pruneItemFromQueue(aItem) _pruneItemFromQueue: function WCF__pruneItemFromQueue(aItem)
{ {
// TODO: handle object releasing in a more elegant way once all console
// messages use the new API - bug 778766.
let [category, methodOrNode, args] = aItem; let [category, methodOrNode, args] = aItem;
if (typeof methodOrNode != "function" && methodOrNode._objectActors) { if (typeof methodOrNode != "function" && methodOrNode._objectActors) {
for (let actor of methodOrNode._objectActors) { for (let actor of methodOrNode._objectActors) {
@ -2310,6 +2285,19 @@ WebConsoleFrame.prototype = {
methodOrNode._objectActors.clear(); methodOrNode._objectActors.clear();
} }
if (methodOrNode == this.output._flushMessageQueue &&
args[0]._objectActors) {
for (let arg of args) {
if (!arg._objectActors) {
continue;
}
for (let actor of arg._objectActors) {
this._releaseObject(actor);
}
arg._objectActors.clear();
}
}
if (category == CATEGORY_NETWORK) { if (category == CATEGORY_NETWORK) {
let connectionId = null; let connectionId = null;
if (methodOrNode == this.logNetEvent) { if (methodOrNode == this.logNetEvent) {
@ -2482,10 +2470,6 @@ WebConsoleFrame.prototype = {
if (aLevel == "dir") { if (aLevel == "dir") {
str = VariablesView.getString(aBody.arguments[0]); str = VariablesView.getString(aBody.arguments[0]);
} }
else if (["log", "info", "warn", "error", "debug"].indexOf(aLevel) > -1 &&
typeof aBody == "object") {
this._makeConsoleLogMessageBody(node, bodyNode, aBody);
}
else { else {
str = aBody; str = aBody;
} }
@ -2561,126 +2545,6 @@ WebConsoleFrame.prototype = {
return node; return node;
}, },
/**
* Make the message body for console.log() calls.
*
* @private
* @param nsIDOMElement aMessage
* The message element that holds the output for the given call.
* @param nsIDOMElement aContainer
* The specific element that will hold each part of the console.log
* output.
* @param object aBody
* The object given by this.logConsoleAPIMessage(). This object holds
* the call information that we need to display - mainly the arguments
* array of the given API call.
*/
_makeConsoleLogMessageBody:
function WCF__makeConsoleLogMessageBody(aMessage, aContainer, aBody)
{
Object.defineProperty(aMessage, "_panelOpen", {
get: function() {
let nodes = aContainer.getElementsByTagName("a");
return Array.prototype.some.call(nodes, function(aNode) {
return aNode._panelOpen;
});
},
enumerable: true,
configurable: false
});
aBody.arguments.forEach(function(aItem) {
if (aContainer.firstChild) {
aContainer.appendChild(this.document.createTextNode(" "));
}
let text = VariablesView.getString(aItem);
let inspectable = !VariablesView.isPrimitive({ value: aItem });
if (aItem && typeof aItem != "object" || !inspectable) {
aContainer.appendChild(this.document.createTextNode(text));
if (aItem.type && aItem.type == "longString") {
let ellipsis = this.document.createElementNS(XHTML_NS, "a");
ellipsis.classList.add("longStringEllipsis");
ellipsis.textContent = l10n.getStr("longStringEllipsis");
ellipsis.href = "#";
ellipsis.draggable = false;
let formatter = function(s) '"' + s + '"';
this._addMessageLinkCallback(ellipsis,
this._longStringClick.bind(this, aMessage, aItem, formatter));
aContainer.appendChild(ellipsis);
}
return;
}
// For inspectable objects.
let elem = this.document.createElementNS(XHTML_NS, "a");
elem.setAttribute("aria-haspopup", "true");
elem.textContent = text;
elem.href = "#";
elem.draggable = false;
this._addMessageLinkCallback(elem,
this._consoleLogClick.bind(this, elem, aItem));
aContainer.appendChild(elem);
}, this);
},
/**
* Click event handler for the ellipsis shown immediately after a long string.
* This method retrieves the full string and updates the console output to
* show it.
*
* @private
* @param nsIDOMElement aMessage
* The message element.
* @param object aActor
* The LongStringActor instance we work with.
* @param [function] aFormatter
* Optional function you can use to format the string received from the
* server, before being displayed in the console.
* @param nsIDOMElement aEllipsis
* The DOM element the user can click on to expand the string.
*/
_longStringClick:
function WCF__longStringClick(aMessage, aActor, aFormatter, aEllipsis)
{
if (!aFormatter) {
aFormatter = function(s) s;
}
let longString = this.webConsoleClient.longString(aActor);
let toIndex = Math.min(longString.length, MAX_LONG_STRING_LENGTH);
longString.substring(longString.initial.length, toIndex,
function WCF__onSubstring(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF__longStringClick substring failure: " +
aResponse.error);
return;
}
let node = aEllipsis.previousSibling;
node.textContent = aFormatter(longString.initial + aResponse.substring);
aEllipsis.parentNode.removeChild(aEllipsis);
if (aMessage.category == CATEGORY_WEBDEV ||
aMessage.category == CATEGORY_OUTPUT) {
aMessage.clipboardText = aMessage.textContent;
}
this.emit("messages-updated", new Set([aMessage]));
if (toIndex != longString.length) {
this.logWarningAboutStringTooLong();
}
}.bind(this));
},
/** /**
* Creates the anchor that displays the textual location of an incoming * Creates the anchor that displays the textual location of an incoming
* message. * message.
@ -2807,7 +2671,7 @@ WebConsoleFrame.prototype = {
return; return;
} }
aCallback(this, aEvent); aCallback.call(this, aEvent);
}, false); }, false);
}, },
@ -3196,8 +3060,8 @@ JSTerm.prototype = {
* The JavaScript evaluation response handler. * The JavaScript evaluation response handler.
* *
* @private * @private
* @param nsIDOMElement [aAfterNode] * @param object [aAfterMessage]
* Optional DOM element after which the evaluation result will be * Optional message after which the evaluation result will be
* inserted. * inserted.
* @param function [aCallback] * @param function [aCallback]
* Optional function to invoke when the evaluation result is added to * Optional function to invoke when the evaluation result is added to
@ -3206,7 +3070,7 @@ JSTerm.prototype = {
* The message received from the server. * The message received from the server.
*/ */
_executeResultCallback: _executeResultCallback:
function JST__executeResultCallback(aAfterNode, aCallback, aResponse) function JST__executeResultCallback(aAfterMessage, aCallback, aResponse)
{ {
if (!this.hud) { if (!this.hud) {
return; return;
@ -3218,13 +3082,8 @@ JSTerm.prototype = {
} }
let errorMessage = aResponse.exceptionMessage; let errorMessage = aResponse.exceptionMessage;
let result = aResponse.result; let result = aResponse.result;
let inspectable = false;
if (result && !VariablesView.isPrimitive({ value: result })) {
inspectable = true;
}
let helperResult = aResponse.helperResult; let helperResult = aResponse.helperResult;
let helperHasRawOutput = !!(helperResult || {}).rawOutput; let helperHasRawOutput = !!(helperResult || {}).rawOutput;
let resultString = VariablesView.getString(result);
if (helperResult && helperResult.type) { if (helperResult && helperResult.type) {
switch (helperResult.type) { switch (helperResult.type) {
@ -3232,11 +3091,11 @@ JSTerm.prototype = {
this.clearOutput(); this.clearOutput();
break; break;
case "inspectObject": case "inspectObject":
if (aAfterNode) { if (aAfterMessage) {
if (!aAfterNode._objectActors) { if (!aAfterMessage._objectActors) {
aAfterNode._objectActors = new Set(); aAfterMessage._objectActors = new Set();
} }
aAfterNode._objectActors.add(helperResult.object.actor); aAfterMessage._objectActors.add(helperResult.object.actor);
} }
this.openVariablesView({ this.openVariablesView({
label: VariablesView.getString(helperResult.object), label: VariablesView.getString(helperResult.object),
@ -3265,26 +3124,13 @@ JSTerm.prototype = {
return; return;
} }
let node; let msg = new Messages.JavaScriptEvalOutput(aResponse, errorMessage);
this.hud.output.addMessage(msg);
if (errorMessage) {
node = this.writeOutput(errorMessage, CATEGORY_OUTPUT, SEVERITY_ERROR,
aAfterNode, aResponse.timestamp);
}
else if (inspectable) {
node = this.writeOutputJS(resultString,
this._evalOutputClick.bind(this, aResponse),
aAfterNode, aResponse.timestamp);
}
else {
node = this.writeOutput(resultString, CATEGORY_OUTPUT, SEVERITY_LOG,
aAfterNode, aResponse.timestamp);
}
if (aCallback) { if (aCallback) {
let oldFlushCallback = this.hud._flushCallback; let oldFlushCallback = this.hud._flushCallback;
this.hud._flushCallback = () => { this.hud._flushCallback = () => {
aCallback(node); aCallback(msg.element);
if (oldFlushCallback) { if (oldFlushCallback) {
oldFlushCallback(); oldFlushCallback();
this.hud._flushCallback = oldFlushCallback; this.hud._flushCallback = oldFlushCallback;
@ -3295,36 +3141,15 @@ JSTerm.prototype = {
}; };
} }
node._objectActors = new Set(); msg._afterMessage = aAfterMessage;
msg._objectActors = new Set();
let error = aResponse.exception; if (WebConsoleUtils.isActorGrip(aResponse.exception)) {
if (WebConsoleUtils.isActorGrip(error)) { msg._objectActors.add(aResponse.exception.actor);
node._objectActors.add(error.actor);
} }
if (WebConsoleUtils.isActorGrip(result)) { if (WebConsoleUtils.isActorGrip(result)) {
node._objectActors.add(result.actor); msg._objectActors.add(result.actor);
if (result.type == "longString") {
// Add an ellipsis to expand the short string if the object is not
// inspectable.
let body = node.getElementsByClassName("body")[0];
let ellipsis = this.hud.document.createElementNS(XHTML_NS, "a");
ellipsis.classList.add("longStringEllipsis");
ellipsis.textContent = l10n.getStr("longStringEllipsis");
ellipsis.href = "#";
ellipsis.draggable = false;
let formatter = function(s) '"' + s + '"';
let onclick = this.hud._longStringClick.bind(this.hud, node, result,
formatter);
this.hud._addMessageLinkCallback(ellipsis, onclick);
body.appendChild(ellipsis);
node.clipboardText += " " + ellipsis.textContent;
}
} }
}, },
@ -3345,8 +3170,12 @@ JSTerm.prototype = {
return; return;
} }
let node = this.writeOutput(aExecuteString, CATEGORY_INPUT, SEVERITY_LOG); let message = new Messages.Simple(aExecuteString, {
let onResult = this._executeResultCallback.bind(this, node, aCallback); category: "input",
severity: "log",
});
this.hud.output.addMessage(message);
let onResult = this._executeResultCallback.bind(this, message, aCallback);
let options = { frame: this.SELECTED_FRAME }; let options = { frame: this.SELECTED_FRAME };
this.requestEvaluation(aExecuteString, options).then(onResult, onResult); this.requestEvaluation(aExecuteString, options).then(onResult, onResult);
@ -3782,14 +3611,16 @@ JSTerm.prototype = {
return; return;
} }
let exception = aResponse.exception; if (aResponse.exceptionMessage) {
if (exception) { let message = new Messages.Simple(aResponse.exceptionMessage, {
let node = this.writeOutput(aResponse.exceptionMessage, category: "output",
CATEGORY_OUTPUT, SEVERITY_ERROR, severity: "error",
null, aResponse.timestamp); timestamp: aResponse.timestamp,
node._objectActors = new Set(); });
if (WebConsoleUtils.isActorGrip(exception)) { this.hud.output.addMessage(message);
node._objectActors.add(exception.actor); message._objectActors = new Set();
if (WebConsoleUtils.isActorGrip(aResponse.exception)) {
message._objectActors.add(aResponse.exception.actor);
} }
} }
@ -3810,74 +3641,6 @@ JSTerm.prototype = {
}, },
/**
* Writes a JS object to the JSTerm outputNode.
*
* @param string aOutputMessage
* The message to display.
* @param function [aCallback]
* Optional function to invoke when users click the message.
* @param nsIDOMNode [aNodeAfter]
* Optional DOM node after which you want to insert the new message.
* This is used when execution results need to be inserted immediately
* after the user input.
* @param number [aTimestamp]
* Optional timestamp to show for the output message (millisconds since
* the UNIX epoch). If no timestamp is provided then Date.now() is
* used.
* @return nsIDOMNode
* The new message node.
*/
writeOutputJS:
function JST_writeOutputJS(aOutputMessage, aCallback, aNodeAfter, aTimestamp)
{
let link = null;
if (aCallback) {
link = this.hud.document.createElementNS(XHTML_NS, "a");
link.setAttribute("aria-haspopup", true);
link.textContent = aOutputMessage;
link.href = "#";
link.draggable = false;
this.hud._addMessageLinkCallback(link, aCallback);
}
return this.writeOutput(link || aOutputMessage, CATEGORY_OUTPUT,
SEVERITY_LOG, aNodeAfter, aTimestamp);
},
/**
* Writes a message to the HUD that originates from the interactive
* JavaScript console.
*
* @param nsIDOMNode|string aOutputMessage
* The message to display.
* @param number aCategory
* The category of message: one of the CATEGORY_ constants.
* @param number aSeverity
* The severity of message: one of the SEVERITY_ constants.
* @param nsIDOMNode [aNodeAfter]
* Optional DOM node after which you want to insert the new message.
* This is used when execution results need to be inserted immediately
* after the user input.
* @param number [aTimestamp]
* Optional timestamp to show for the output message (millisconds since
* the UNIX epoch). If no timestamp is provided then Date.now() is
* used.
* @return nsIDOMNode
* The new message node.
*/
writeOutput:
function JST_writeOutput(aOutputMessage, aCategory, aSeverity, aNodeAfter,
aTimestamp)
{
let node = this.hud.createMessageNode(aCategory, aSeverity, aOutputMessage,
null, null, null, null, aTimestamp);
node._outputAfterNode = aNodeAfter;
this.hud.outputMessage(aCategory, node);
return node;
},
/** /**
* Clear the Web Console output. * Clear the Web Console output.
* *
@ -4562,21 +4325,6 @@ JSTerm.prototype = {
this.completeNode.value = prefix + aSuffix; this.completeNode.value = prefix + aSuffix;
}, },
/**
* The click event handler for evaluation results in the output.
*
* @private
* @param object aResponse
* The JavaScript evaluation response received from the server.
*/
_evalOutputClick: function JST__evalOutputClick(aResponse)
{
this.openVariablesView({
label: VariablesView.getString(aResponse.result),
objectActor: aResponse.result,
autofocus: true,
});
},
/** /**
* Destroy the sidebar. * Destroy the sidebar.