Bug 859756 - [browserconsole] Show nsIConsoleMessages in the Browser Console; r=robcee

This commit is contained in:
Mihai Sucan 2013-05-28 18:24:31 +03:00
parent 1611b7310d
commit 4145f0a3e7
11 changed files with 318 additions and 94 deletions

View File

@ -25,7 +25,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "gcli",
XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
"resource:///modules/devtools/BuiltinCommands.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleServiceListener",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
@ -444,8 +444,8 @@ DeveloperToolbar.prototype._initErrorsCount = function DT__initErrorsCount(aTab)
}
let window = aTab.linkedBrowser.contentWindow;
let listener = new PageErrorListener(window, {
onPageError: this._onPageError.bind(this, tabId),
let listener = new ConsoleServiceListener(window, {
onConsoleServiceMessage: this._onPageError.bind(this, tabId),
});
listener.init();

View File

@ -131,6 +131,7 @@ MOCHITEST_BROWSER_FILES = \
browser_console_native_getters.js \
browser_bug_871156_ctrlw_close_tab.js \
browser_console_private_browsing.js \
browser_console_nsiconsolemessage.js \
head.js \
$(NULL)

View File

@ -0,0 +1,79 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Check that nsIConsoleMessages are displayed in the Browser Console.
// See bug 859756.
const TEST_URI = "data:text/html;charset=utf8,<title>bug859756</title>\n" +
"<p>hello world\n<p>nsIConsoleMessages ftw!";
function test()
{
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
// Test for cached nsIConsoleMessages.
Services.console.logStringMessage("test1 for bug859756");
info("open web console");
openConsole(null, consoleOpened);
}, true);
}
function consoleOpened(hud)
{
ok(hud, "web console opened");
Services.console.logStringMessage("do-not-show-me");
content.console.log("foobarz");
waitForMessages({
webconsole: hud,
messages: [
{
text: "foobarz",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
},
],
}).then(() => {
let text = hud.outputNode.textContent;
is(text.indexOf("do-not-show-me"), -1,
"nsIConsoleMessages are not displayed");
is(text.indexOf("test1 for bug859756"), -1,
"nsIConsoleMessages are not displayed (confirmed)");
closeConsole(null, onWebConsoleClose);
});
}
function onWebConsoleClose()
{
info("web console closed");
HUDConsoleUI.toggleBrowserConsole().then(onBrowserConsoleOpen);
}
function onBrowserConsoleOpen(hud)
{
ok(hud, "browser console opened");
Services.console.logStringMessage("test2 for bug859756");
waitForMessages({
webconsole: hud,
messages: [
{
text: "test1 for bug859756",
category: CATEGORY_JS,
},
{
text: "test2 for bug859756",
category: CATEGORY_JS,
},
{
text: "do-not-show-me",
category: CATEGORY_JS,
},
],
}).then(finishTest);
}

View File

@ -961,6 +961,9 @@ WebConsoleFrame.prototype = {
[category, aMessage]);
break;
}
case "LogMessage":
this.handleLogMessage(aMessage);
break;
case "ConsoleAPI":
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage,
[aMessage]);
@ -1198,6 +1201,21 @@ WebConsoleFrame.prototype = {
this.outputMessage(category, this.reportPageError, [category, aPageError]);
},
/**
* Handle log messages received from the server. This method outputs the given
* message.
*
* @param object aPacket
* The message packet received from the server.
*/
handleLogMessage: function WCF_handleLogMessage(aPacket)
{
this.outputMessage(CATEGORY_JS, () => {
return this.createMessageNode(CATEGORY_JS, SEVERITY_LOG, aPacket.message,
null, null, null, null, aPacket.timeStamp);
});
},
/**
* Log network event.
*
@ -4530,6 +4548,7 @@ function WebConsoleConnectionProxy(aWebConsole, aTarget)
this.target = aTarget;
this._onPageError = this._onPageError.bind(this);
this._onLogMessage = this._onLogMessage.bind(this);
this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
this._onNetworkEvent = this._onNetworkEvent.bind(this);
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
@ -4634,6 +4653,7 @@ WebConsoleConnectionProxy.prototype = {
let client = this.client = this.target.client;
client.addListener("logMessage", this._onLogMessage);
client.addListener("pageError", this._onPageError);
client.addListener("consoleAPICall", this._onConsoleAPICall);
client.addListener("networkEvent", this._onNetworkEvent);
@ -4755,6 +4775,23 @@ WebConsoleConnectionProxy.prototype = {
}
},
/**
* The "logMessage" 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.
*/
_onLogMessage: function WCCP__onLogMessage(aType, aPacket)
{
if (this.owner && aPacket.from == this._consoleActor) {
this.owner.handleLogMessage(aPacket);
}
},
/**
* The "consoleAPICall" message type handler. We redirect any message to
* the UI for displaying.
@ -4899,6 +4936,7 @@ WebConsoleConnectionProxy.prototype = {
return this._disconnecter.promise;
}
this.client.removeListener("logMessage", this._onLogMessage);
this.client.removeListener("pageError", this._onPageError);
this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
this.client.removeListener("networkEvent", this._onNetworkEvent);

View File

@ -175,6 +175,7 @@ const UnsolicitedNotifications = {
"eventNotification": "eventNotification",
"fileActivity": "fileActivity",
"lastPrivateContextExited": "lastPrivateContextExited",
"logMessage": "logMessage",
"networkEvent": "networkEvent",
"networkEventUpdate": "networkEventUpdate",
"newGlobal": "newGlobal",

View File

@ -18,7 +18,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleServiceListener",
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIListener",
@ -182,10 +182,10 @@ WebConsoleActor.prototype =
_window: null,
/**
* The PageErrorListener instance.
* The ConsoleServiceListener instance.
* @type object
*/
pageErrorListener: null,
consoleServiceListener: null,
/**
* The ConsoleAPIListener instance.
@ -228,9 +228,9 @@ WebConsoleActor.prototype =
*/
disconnect: function WCA_disconnect()
{
if (this.pageErrorListener) {
this.pageErrorListener.destroy();
this.pageErrorListener = null;
if (this.consoleServiceListener) {
this.consoleServiceListener.destroy();
this.consoleServiceListener = null;
}
if (this.consoleAPIListener) {
this.consoleAPIListener.destroy();
@ -384,10 +384,10 @@ WebConsoleActor.prototype =
let listener = aRequest.listeners.shift();
switch (listener) {
case "PageError":
if (!this.pageErrorListener) {
this.pageErrorListener =
new PageErrorListener(window, this);
this.pageErrorListener.init();
if (!this.consoleServiceListener) {
this.consoleServiceListener =
new ConsoleServiceListener(window, this);
this.consoleServiceListener.init();
}
startedListeners.push(listener);
break;
@ -447,9 +447,9 @@ WebConsoleActor.prototype =
let listener = toDetach.shift();
switch (listener) {
case "PageError":
if (this.pageErrorListener) {
this.pageErrorListener.destroy();
this.pageErrorListener = null;
if (this.consoleServiceListener) {
this.consoleServiceListener.destroy();
this.consoleServiceListener = null;
}
stoppedListeners.push(listener);
break;
@ -505,28 +505,42 @@ WebConsoleActor.prototype =
while (types.length > 0) {
let type = types.shift();
switch (type) {
case "ConsoleAPI":
if (this.consoleAPIListener) {
let cache = this.consoleAPIListener
.getCachedMessages(!this._isGlobalActor);
cache.forEach((aMessage) => {
let message = this.prepareConsoleMessageForRemote(aMessage);
message._type = type;
messages.push(message);
});
case "ConsoleAPI": {
if (!this.consoleAPIListener) {
break;
}
let cache = this.consoleAPIListener
.getCachedMessages(!this._isGlobalActor);
cache.forEach((aMessage) => {
let message = this.prepareConsoleMessageForRemote(aMessage);
message._type = type;
messages.push(message);
});
break;
case "PageError":
if (this.pageErrorListener) {
let cache = this.pageErrorListener
.getCachedMessages(!this._isGlobalActor);
cache.forEach((aMessage) => {
let message = this.preparePageErrorForRemote(aMessage);
message._type = type;
messages.push(message);
});
}
case "PageError": {
if (!this.consoleServiceListener) {
break;
}
let cache = this.consoleServiceListener
.getCachedMessages(!this._isGlobalActor);
cache.forEach((aMessage) => {
let message = null;
if (aMessage instanceof Ci.nsIScriptError) {
message = this.preparePageErrorForRemote(aMessage);
message._type = type;
}
else {
message = {
_type: "LogMessage",
message: aMessage.message,
timeStamp: aMessage.timeStamp,
};
}
messages.push(message);
});
break;
}
}
}
@ -618,6 +632,10 @@ WebConsoleActor.prototype =
let windowId = !this._isGlobalActor ?
WebConsoleUtils.getInnerWindowId(this.window) : null;
ConsoleAPIStorage.clearEvents(windowId);
if (this._isGlobalActor) {
Services.console.logStringMessage(null); // for the Error Console
Services.console.reset();
}
return {};
},
@ -875,19 +893,30 @@ WebConsoleActor.prototype =
//////////////////
/**
* Handler for page errors received from the PageErrorListener. This method
* sends the nsIScriptError to the remote Web Console client.
* Handler for messages received from the ConsoleServiceListener. This method
* sends the nsIConsoleMessage to the remote Web Console client.
*
* @param nsIScriptError aPageError
* The page error we need to send to the client.
* @param nsIConsoleMessage aMessage
* The message we need to send to the client.
*/
onPageError: function WCA_onPageError(aPageError)
onConsoleServiceMessage: function WCA_onConsoleServiceMessage(aMessage)
{
let packet = {
from: this.actorID,
type: "pageError",
pageError: this.preparePageErrorForRemote(aPageError),
};
let packet;
if (aMessage instanceof Ci.nsIScriptError) {
packet = {
from: this.actorID,
type: "pageError",
pageError: this.preparePageErrorForRemote(aMessage),
};
}
else {
packet = {
from: this.actorID,
type: "logMessage",
message: aMessage.message,
timeStamp: aMessage.timeStamp,
};
}
this.conn.send(packet);
},

View File

@ -42,7 +42,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
"resource:///modules/devtools/VariablesView.jsm");
this.EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider", "JSTermHelpers",
"PageErrorListener", "ConsoleAPIListener",
"ConsoleServiceListener", "ConsoleAPIListener",
"NetworkResponseListener", "NetworkMonitor",
"ConsoleProgressListener"];
@ -876,25 +876,25 @@ return JSPropertyProvider;
///////////////////////////////////////////////////////////////////////////////
/**
* The nsIConsoleService listener. This is used to send all the page errors
* (JavaScript, CSS and more) to the remote Web Console instance.
* The nsIConsoleService listener. This is used to send all of the console
* messages (JavaScript, CSS and more) to the remote Web Console instance.
*
* @constructor
* @param nsIDOMWindow [aWindow]
* Optional - the window object for which we are created. This is used
* for filtering out messages that belong to other windows.
* @param object aListener
* The listener object must have a method: onPageError. This method is
* invoked with one argument, the nsIScriptError, whenever a relevant
* page error is received.
* The listener object must have one method:
* - onConsoleServiceMessage(). This method is invoked with one argument, the
* nsIConsoleMessage, whenever a relevant message is received.
*/
this.PageErrorListener = function PageErrorListener(aWindow, aListener)
this.ConsoleServiceListener = function ConsoleServiceListener(aWindow, aListener)
{
this.window = aWindow;
this.listener = aListener;
}
PageErrorListener.prototype =
ConsoleServiceListener.prototype =
{
QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
@ -905,8 +905,7 @@ PageErrorListener.prototype =
window: null,
/**
* The listener object which is notified of page errors. It must have
* a onPageError method which is invoked with one argument: the nsIScriptError.
* The listener object which is notified of messages from the console service.
* @type object
*/
listener: null,
@ -914,7 +913,7 @@ PageErrorListener.prototype =
/**
* Initialize the nsIConsoleService listener.
*/
init: function PEL_init()
init: function CSL_init()
{
Services.console.registerListener(this);
},
@ -924,43 +923,48 @@ PageErrorListener.prototype =
* messages belonging to the current window and sends them to the remote Web
* Console instance.
*
* @param nsIScriptError aScriptError
* The script error object coming from the nsIConsoleService.
* @param nsIConsoleMessage aMessage
* The message object coming from the nsIConsoleService.
*/
observe: function PEL_observe(aScriptError)
observe: function CSL_observe(aMessage)
{
if (!this.listener ||
!(aScriptError instanceof Ci.nsIScriptError)) {
if (!this.listener) {
return;
}
if (this.window) {
if (!aScriptError.outerWindowID ||
!this.isCategoryAllowed(aScriptError.category)) {
if (!(aMessage instanceof Ci.nsIScriptError) ||
!aMessage.outerWindowID ||
!this.isCategoryAllowed(aMessage.category)) {
return;
}
let errorWindow =
Services.wm.getOuterWindowWithId(aScriptError.outerWindowID);
let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
if (!errorWindow || errorWindow.top != this.window) {
return;
}
}
this.listener.onPageError(aScriptError);
if (aMessage.message) {
this.listener.onConsoleServiceMessage(aMessage);
}
},
/**
* Check if the given script error category is allowed to be tracked or not.
* Check if the given message category is allowed to be tracked or not.
* We ignore chrome-originating errors as we only care about content.
*
* @param string aCategory
* The nsIScriptError category you want to check.
* The message category you want to check.
* @return boolean
* True if the category is allowed to be logged, false otherwise.
*/
isCategoryAllowed: function PEL_isCategoryAllowed(aCategory)
isCategoryAllowed: function CSL_isCategoryAllowed(aCategory)
{
if (!aCategory) {
return false;
}
switch (aCategory) {
case "XPConnect JavaScript":
case "component javascript":
@ -983,26 +987,32 @@ PageErrorListener.prototype =
* Tells if you want to also retrieve messages coming from private
* windows. Defaults to false.
* @return array
* The array of cached messages.
* The array of cached messages. Each element is an nsIScriptError or
* an nsIConsoleMessage
*/
getCachedMessages: function PEL_getCachedMessages(aIncludePrivate = false)
getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false)
{
let innerWindowId = this.window ?
let innerWindowID = this.window ?
WebConsoleUtils.getInnerWindowId(this.window) : null;
let errors = Services.console.getMessageArray() || [];
return errors.filter((aError) => {
if (!(aError instanceof Ci.nsIScriptError)) {
return false;
}
if (!aIncludePrivate && aError.isFromPrivateWindow) {
return false;
}
if (innerWindowId &&
(aError.innerWindowID != innerWindowId ||
!this.isCategoryAllowed(aError.category))) {
if (aError instanceof Ci.nsIScriptError) {
if (!aIncludePrivate && aError.isFromPrivateWindow) {
return false;
}
if (innerWindowID &&
(aError.innerWindowID != innerWindowID ||
!this.isCategoryAllowed(aError.category))) {
return false;
}
}
else if (innerWindowID) {
// If this is not an nsIScriptError and we need to do window-based
// filtering we skip this message.
return false;
}
return true;
});
},
@ -1010,7 +1020,7 @@ PageErrorListener.prototype =
/**
* Remove the nsIConsoleService listener.
*/
destroy: function PEL_destroy()
destroy: function CSL_destroy()
{
Services.console.unregisterListener(this);
this.listener = this.window = null;
@ -1112,12 +1122,12 @@ ConsoleAPIListener.prototype =
{
let innerWindowId = this.window ?
WebConsoleUtils.getInnerWindowId(this.window) : null;
return ConsoleAPIStorage.getEvents(innerWindowId).filter((aMessage) => {
if (!aIncludePrivate && aMessage.private) {
return false;
}
return true;
});
let events = ConsoleAPIStorage.getEvents(innerWindowId);
if (aIncludePrivate) {
return events;
}
return events.filter((m) => !m.private);
},
/**

View File

@ -24,6 +24,7 @@ MOCHITEST_CHROME_FILES = \
test_bug819670_getter_throws.html \
test_object_actor_native_getters.html \
test_object_actor_native_getters_lenient_this.html \
test_nsiconsolemessage.html \
network_requests_iframe.html \
data.json \
data.json^headers^ \

View File

@ -106,7 +106,10 @@ function checkObject(aObject, aExpected)
function checkValue(aName, aValue, aExpected)
{
if (aValue === undefined) {
if (aExpected === null) {
ok(!aValue, "'" + aName + "' is null");
}
else if (aValue === undefined) {
ok(false, "'" + aName + "' is undefined");
}
else if (typeof aExpected == "string" || typeof aExpected == "number" ||

View File

@ -95,7 +95,7 @@ function doConsoleCalls()
<script class="testbody" type="text/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
let consoleAPIListener, pageErrorListener;
let consoleAPIListener, consoleServiceListener;
let consoleAPICalls = 0;
let pageErrors = 0;
@ -108,7 +108,7 @@ let handlers = {
}
},
onPageError: function onPageError()
onConsoleServiceMessage: function onConsoleServiceMessage()
{
pageErrors++;
if (pageErrors == expectedPageErrors.length) {
@ -153,16 +153,16 @@ function onCachedConsoleAPI(aState, aResponse)
});
closeDebugger(aState, function() {
pageErrorListener = new PageErrorListener(null, handlers);
pageErrorListener.init();
consoleServiceListener = new ConsoleServiceListener(null, handlers);
consoleServiceListener.init();
doPageErrors();
});
}
function testPageErrors()
{
pageErrorListener.destroy();
pageErrorListener = null;
consoleServiceListener.destroy();
consoleServiceListener = null;
attachConsole(["PageError"], onAttach2);
}

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf8">
<title>Test for nsIConsoleMessages</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript;version=1.8" src="common.js"></script>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>Make sure that nsIConsoleMessages are logged. See bug 859756.</p>
<script class="testbody" type="text/javascript;version=1.8">
"use strict";
SimpleTest.waitForExplicitFinish();
let expectedMessages = [];
function startTest()
{
removeEventListener("load", startTest);
attachConsole(["PageError"], onAttach);
}
function onAttach(aState, aResponse)
{
onLogMessage = onLogMessage.bind(null, aState);
aState.dbgClient.addListener("logMessage", onLogMessage);
expectedMessages = [{
message: "hello world! bug859756",
timeStamp: /^\d+$/,
}];
Services.console.logStringMessage("hello world! bug859756");
info("waiting for messages");
}
let receivedMessages = [];
function onLogMessage(aState, aType, aPacket)
{
is(aPacket.from, aState.actor, "packet actor");
receivedMessages.push(aPacket);
if (receivedMessages.length != expectedMessages.length) {
return;
}
aState.dbgClient.removeListener("logMessage", onLogMessage);
checkObject(receivedMessages, expectedMessages);
closeDebugger(aState, () => SimpleTest.finish());
}
addEventListener("load", startTest);
</script>
</body>
</html>