Bug 1006102 - Add support for filtering errors by addonId in webconsole ConsoleServiceListener helper.
MozReview-Commit-ID: hoLAJDNLbB
--- a/devtools/server/actors/addon.js
+++ b/devtools/server/actors/addon.js
@@ -4,17 +4,17 @@
"use strict";
var { Ci, Cu } = require("chrome");
var Services = require("Services");
var { ActorPool } = require("devtools/server/actors/common");
var { TabSources } = require("./utils/TabSources");
var makeDebugger = require("./utils/make-debugger");
-var { ConsoleAPIListener } = require("devtools/shared/webconsole/utils");
+var { ConsoleAPIListener, ConsoleServiceListener } = require("devtools/shared/webconsole/utils");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { assert, update } = DevToolsUtils;
loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
loader.lazyRequireGetter(this, "WebConsoleActor", "devtools/server/actors/webconsole", true);
@@ -321,16 +321,24 @@ update(AddonConsoleActor.prototype, {
case "ConsoleAPI":
if (!this.consoleAPIListener) {
this.consoleAPIListener =
new ConsoleAPIListener(null, this, { addonId: this.addon.id });
this.consoleAPIListener.init();
}
startedListeners.push(listener);
break;
+ case "PageError":
+ if (!this.consoleServiceListener) {
+ this.consoleServiceListener =
+ new ConsoleServiceListener(null, this, { addonIdL: this.addon.id });
+ this.consoleServiceListener.init();
+ }
+ startedListeners.push(listener);
+ break;
}
}
return {
startedListeners: startedListeners,
nativeConsoleAPI: true,
traits: this.traits,
};
},
new file mode 100644
--- /dev/null
+++ b/devtools/shared/tests/unit/test_errors_filtering.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ConsoleServiceListener } = require("devtools/shared/webconsole/utils");
+const Services = require("Services");
+
+function createFakeAddonWindow({addonId} = {}) {
+ let baseURI = Services.io.newURI("about:blank", null, null);
+ let originAttributes = {addonId};
+ let principal = Services.scriptSecurityManager
+ .createCodebasePrincipal(baseURI, originAttributes);
+ let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
+ let docShell = chromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ docShell.createAboutBlankContentViewer(principal);
+ let window = docShell.contentViewer.DOMDocument.defaultView;
+
+ return {window, chromeWebNav};
+}
+
+/**
+ * Tests that the originAttributes property of the Error object gets passed
+ * through to console service.
+ */
+add_task(function* test_console_service_listener_filtering() {
+ // WebExtension page's errors tagged
+ // by 'originAttributes.addonId' are filtered correctly.
+ let addonIdOption = {addonId: "bar"};
+ let fakeAddon = createFakeAddonWindow(addonIdOption);
+
+ let waitForErrorMessage = new Promise((resolve, reject) => {
+ let listener = new ConsoleServiceListener(null, {
+ onConsoleServiceMessage: (msg) => {
+ listener.destroy();
+ resolve(msg);
+ },
+ }, addonIdOption);
+ listener.init();
+ });
+
+ let notAnAddon = createFakeAddonWindow({addonId:""});
+
+ // Produce an error from a non addon window
+ notAnAddon.window.eval("setTimeout(() => errorNotAddon(), 0);");
+
+ // Produce an error from the addon window
+ fakeAddon.window.eval("setTimeout(() => errorOnSetTimeout(), 0);");
+
+ // Check onConsoleServiceMessage receive the Error raised in the addonWindow
+ // filtered with the expected originAttributes
+ let error = yield waitForErrorMessage;
+
+ ok(error instanceof Ci.nsIScriptError, "Got an nsIScriptError instance");
+ ok(/errorOnSetTimeout/.test(error.message), "Got the expected error message");
+ ok(error.originAttributes && error.originAttributes.addonId == addonIdOption.addonId,
+ "Got the expected originAttributes");
+
+ let listener = new ConsoleServiceListener(null, {
+ onConsoleServiceMessage: () => {},
+ }, addonIdOption);
+ let errors = listener.getCachedMessages();
+
+ // Check filtered cached messages
+ do_check_eq(errors.length, 1, "Got the expected number of filtered cached messages");
+ ok(errors[0] instanceof Ci.nsIScriptError, "Got an nsIScriptError instance");
+ ok(/errorOnSetTimeout/.test(errors[0].message), "Got the expected error message");
+ ok(errors[0].originAttributes && errors[0].originAttributes.addonId == addonIdOption.addonId,
+ "Got the expected originAttributes");
+
+ let unfilteredListener = new ConsoleServiceListener(null, {
+ onConsoleServiceMessage: () => {},
+ });
+ let allErrors = unfilteredListener.getCachedMessages();
+ // Check non filtered cached messages
+ ok(allErrors.length > 1, "Got the expected number of unfiltered cached messages");
+
+ let foundError;
+ for (let error of allErrors) {
+ if (error instanceof Ci.nsIScriptError) {
+ if (/errorNotAddon/.test(error.message)) {
+ // Non Addon Errors
+ foundError = error;
+ }
+ }
+ }
+
+ ok(foundError, "Got the expected non addon error from the unfiltered listener");
+ do_check_eq(foundError.originAttributes.addonId, "",
+ `1 Got empty originAttributes on errors from non addon windows`);
+
+ // Close the addon window's chromeWebNav.
+ fakeAddon.chromeWebNav.close();
+ notAnAddon.chromeWebNav.close();
+});
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -3,28 +3,29 @@ tags = devtools
head = head_devtools.js
tail =
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
support-files =
exposeLoader.js
[test_assert.js]
+[test_console_filtering.js]
+[test_errors_filtering.js]
[test_csslexer.js]
[test_fetch-chrome.js]
[test_fetch-file.js]
[test_fetch-http.js]
[test_fetch-resource.js]
[test_flatten.js]
[test_indentation.js]
[test_independent_loaders.js]
[test_invisible_loader.js]
[test_isSet.js]
[test_safeErrorString.js]
[test_defineLazyPrototypeGetter.js]
[test_async-utils.js]
-[test_console_filtering.js]
[test_prettifyCSS.js]
[test_require_lazy.js]
[test_require.js]
[test_stack.js]
[test_defer.js]
[test_executeSoon.js]
--- a/devtools/shared/webconsole/utils.js
+++ b/devtools/shared/webconsole/utils.js
@@ -626,34 +626,44 @@ WebConsoleUtils.L10n.prototype = {
* @constructor
* @param nsIDOMWindow [window]
* Optional - the window object for which we are created. This is used
* for filtering out messages that belong to other windows.
* @param object listener
* The listener object must have one method:
* - onConsoleServiceMessage(). This method is invoked with one argument,
* the nsIConsoleMessage, whenever a relevant message is received.
+ * @param object filteringOptions
+ * Optional - The filteringOptions that this listener should listen to:
+ * - addonId: filter error messages based on the addonId.
*/
-function ConsoleServiceListener(window, listener) {
+function ConsoleServiceListener(window, listener, {addonId} = {}) {
this.window = window;
this.listener = listener;
+ this.addonId = addonId;
}
exports.ConsoleServiceListener = ConsoleServiceListener;
ConsoleServiceListener.prototype =
{
QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
/**
* The content window for which we listen to page errors.
* @type nsIDOMWindow
*/
window: null,
/**
+ * The addonId that we listen for. If not null then only messages from this
+ * console will be returned.
+ */
+ addonId: null,
+
+ /**
* The listener object which is notified of messages from the console service.
* @type object
*/
listener: null,
/**
* Initialize the nsIConsoleService listener.
*/
@@ -682,16 +692,24 @@ ConsoleServiceListener.prototype =
}
let errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) {
return;
}
}
+ if (this.addonId) {
+ if (message instanceof Ci.nsIScriptError &&
+ message.originAttributes &&
+ message.originAttributes.addonId !== this.addonId) {
+ return;
+ }
+ }
+
this.listener.onConsoleServiceMessage(message);
},
/**
* 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 category
@@ -727,16 +745,27 @@ ConsoleServiceListener.prototype =
* windows. Defaults to false.
* @return array
* The array of cached messages. Each element is an nsIScriptError or
* an nsIConsoleMessage
*/
getCachedMessages: function (includePrivate = false) {
let errors = Services.console.getMessageArray() || [];
+ if (this.addonId) {
+ errors = errors.filter((aError) => {
+ if (aError instanceof Ci.nsIScriptError) {
+ return aError.originAttributes &&
+ aError.originAttributes.addonId == this.addonId;
+ }
+
+ return false;
+ });
+ }
+
// if !this.window, we're in a browser console. Still need to filter
// private messages.
if (!this.window) {
return errors.filter((error) => {
if (error instanceof Ci.nsIScriptError) {
if (!includePrivate && error.isFromPrivateWindow) {
return false;
}