Bugzilla #1320189 - Fix WebChannel error reporting for content errors r?markh
MozReview-Commit-ID: JT0KmWpIL0V
--- a/browser/base/content/test/general/browser_remoteTroubleshoot.js
+++ b/browser/base/content/test/general/browser_remoteTroubleshoot.js
@@ -32,18 +32,18 @@ function promiseNewChannelResponse(uri)
gBrowser.removeTab(tab);
return data;
});
}
add_task(function*() {
// We haven't set a permission yet - so even the "good" URI should fail.
let got = yield promiseNewChannelResponse(TEST_URI_GOOD);
- // Should have no data.
- Assert.ok(got.message === undefined, "should have failed to get any data");
+ // Should return an error.
+ Assert.ok(got.message.errno === 2, "should have failed with errno 2, no such channel");
// Add a permission manager entry for our URI.
Services.perms.add(TEST_URI_GOOD,
"remote-troubleshooting",
Services.perms.ALLOW_ACTION);
registerCleanupFunction(() => {
Services.perms.remove(TEST_URI_GOOD, "remote-troubleshooting");
});
@@ -71,19 +71,19 @@ add_task(function*() {
"should have correct update channel.");
}
// And check some keys we know we decline to return.
Assert.ok(!got.message.modifiedPreferences, "should not have a modifiedPreferences key");
Assert.ok(!got.message.crashes, "should not have crash info");
- // Now a http:// URI - should get nothing even with the permission setup.
+ // Now a http:// URI - should receive an error
got = yield promiseNewChannelResponse(TEST_URI_BAD);
- Assert.ok(got.message === undefined, "should have failed to get any data");
+ Assert.ok(got.message.errno === 2, "should have failed with errno 2, no such channel");
// Check that the page can send an object as well if it's in the whitelist
let webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
let newWhitelist = origWhitelist + " https://example.com";
Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
registerCleanupFunction(() => {
Services.prefs.clearUserPref(webchannelWhitelistPref);
--- a/browser/base/content/test/general/browser_web_channel.html
+++ b/browser/base/content/test/general/browser_web_channel.html
@@ -31,16 +31,22 @@
test_unsolicited();
break;
case "bubbles":
test_bubbles();
break;
case "object":
test_object();
break;
+ case "error_thrown":
+ test_error_thrown();
+ break;
+ case "error_invalid_channel":
+ test_error_invalid_channel();
+ break;
default:
throw new Error(`INVALID TEST NAME ${testName}`);
}
};
function test_generic() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: JSON.stringify({
@@ -167,16 +173,54 @@
})
});
// Test fails if objectMessage is received, we send stringMessage to know
// when we should stop listening for objectMessage
window.dispatchEvent(objectMessage);
window.dispatchEvent(stringMessage);
}
+ function test_error_thrown() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "error",
+ message: {
+ command: "oops"
+ }
+ })
+ });
+
+ // echo the response back to chrome - chrome will check it is the
+ // expected error.
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ echoEventToChannel(e, "echo");
+ }, true);
+
+ window.dispatchEvent(event);
+ }
+
+ function test_error_invalid_channel() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "invalid-channel",
+ message: {
+ command: "oops"
+ }
+ })
+ });
+
+ // echo the response back to chrome - chrome will check it is the
+ // expected error.
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ echoEventToChannel(e, "echo");
+ }, true);
+
+ window.dispatchEvent(event);
+ }
+
function echoEventToChannel(e, channelId) {
var echoedEvent = new window.CustomEvent("WebChannelMessageToChrome", {
detail: JSON.stringify({
id: channelId,
message: e.detail.message,
})
});
--- a/browser/base/content/test/general/browser_web_channel.js
+++ b/browser/base/content/test/general/browser_web_channel.js
@@ -391,17 +391,78 @@ var gTests = [
gBrowser,
url: HTTP_PATH + HTTP_ENDPOINT + "?object"
}, function* () {
yield testDonePromise;
Services.prefs.setCharPref(webchannelWhitelistPref, origWhitelist);
channel.stopListening();
});
}
- }
+ },
+ {
+ desc: "WebChannel errors handling the message are delivered back to content",
+ run: function* () {
+ const ERRNO_UNKNOWN_ERROR = 999; // WebChannel.jsm doesn't export this.
+
+ // The channel where we purposely fail responding to a command.
+ let channel = new WebChannel("error", Services.io.newURI(HTTP_PATH, null, null));
+ // The channel where we see the response when the content sees the error
+ let echoChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+
+ let testDonePromise = new Promise((resolve, reject) => {
+ // listen for the confirmation that content saw the error.
+ echoChannel.listen((id, message, sender) => {
+ is(id, "echo");
+ is(message.error, "oh no");
+ is(message.errno, ERRNO_UNKNOWN_ERROR);
+ resolve();
+ });
+
+ // listen for a message telling us to simulate an error.
+ channel.listen((id, message, sender) => {
+ is(id, "error");
+ is(message.command, "oops");
+ throw new Error("oh no");
+ });
+ });
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?error_thrown"
+ }, function* () {
+ yield testDonePromise;
+ channel.stopListening();
+ echoChannel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel errors due to an invalid channel are delivered back to content",
+ run: function* () {
+ const ERRNO_NO_SUCH_CHANNEL = 2; // WebChannel.jsm doesn't export this.
+ // The channel where we see the response when the content sees the error
+ let echoChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+
+ let testDonePromise = new Promise((resolve, reject) => {
+ // listen for the confirmation that content saw the error.
+ echoChannel.listen((id, message, sender) => {
+ is(id, "echo");
+ is(message.error, "No Such Channel");
+ is(message.errno, ERRNO_NO_SUCH_CHANNEL);
+ resolve();
+ });
+ });
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?error_invalid_channel"
+ }, function* () {
+ yield testDonePromise;
+ echoChannel.stopListening();
+ });
+ }
+ },
]; // gTests
function test() {
waitForExplicitFinish();
Task.spawn(function* () {
for (let testCase of gTests) {
info("Running: " + testCase.desc);
--- a/toolkit/modules/WebChannel.jsm
+++ b/toolkit/modules/WebChannel.jsm
@@ -4,16 +4,18 @@
/**
* WebChannel is an abstraction that uses the Message Manager and Custom Events
* to create a two-way communication channel between chrome and content code.
*/
this.EXPORTED_SYMBOLS = ["WebChannel", "WebChannelBroker"];
+const ERRNO_MISSING_PRINCIPAL = 1;
+const ERRNO_NO_SUCH_CHANNEL = 2;
const ERRNO_UNKNOWN_ERROR = 999;
const ERROR_UNKNOWN = "UNKNOWN_ERROR";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@@ -78,32 +80,32 @@ var WebChannelBroker = Object.create({
} catch (e) {
Cu.reportError("Failed to parse WebChannel data as a JSON object");
return;
}
}
if (data && data.id) {
if (!event.principal) {
- this._sendErrorEventToContent(data.id, sendingContext, "Message principal missing");
+ this._sendErrorEventToContent(data.id, sendingContext, ERRNO_MISSING_PRINCIPAL, "Message principal missing");
} else {
let validChannelFound = false;
data.message = data.message || {};
for (var channel of this._channelMap.keys()) {
if (channel.id === data.id &&
channel._originCheckCallback(event.principal)) {
validChannelFound = true;
channel.deliver(data, sendingContext);
}
}
// if no valid origins send an event that there is no such valid channel
if (!validChannelFound) {
- this._sendErrorEventToContent(data.id, sendingContext, "No Such Channel");
+ this._sendErrorEventToContent(data.id, sendingContext, ERRNO_NO_SUCH_CHANNEL, "No Such Channel");
}
}
} else {
Cu.reportError("WebChannel channel id missing");
}
},
/**
* The global message manager operates on every <browser>
@@ -122,25 +124,28 @@ var WebChannelBroker = Object.create({
* @param id {String}
* The WebChannel id to include in the message
* @param sendingContext {Object}
* Message sending context
* @param [errorMsg] {String}
* Error message
* @private
*/
- _sendErrorEventToContent: function(id, sendingContext, errorMsg) {
+ _sendErrorEventToContent: function(id, sendingContext, errorNo, errorMsg) {
let { browser: targetBrowser, eventTarget, principal: targetPrincipal } = sendingContext;
errorMsg = errorMsg || "Web Channel Broker error";
if (targetBrowser && targetBrowser.messageManager) {
targetBrowser.messageManager.sendAsyncMessage("WebChannelMessageToContent", {
id: id,
- error: errorMsg,
+ message: {
+ errno: errorNo,
+ error: errorMsg,
+ },
}, { eventTarget: eventTarget }, targetPrincipal);
} else {
Cu.reportError("Failed to send a WebChannel error. Target invalid.");
}
Cu.reportError(id.toString() + " error message. " + errorMsg);
},
});