--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1449,8 +1449,11 @@ pref("dom.mozBrowserFramesEnabled", true
pref("extensions.pocket.enabled", true);
pref("signon.schemeUpgrades", true);
// Enable the "Simplify Page" feature in Print Preview
pref("print.use_simplify_page", true);
+// Space separated list of URLS that are allowed to send objects (instead of
+// only strings) through webchannels.
+pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://hello.firefox.com https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
--- a/browser/base/content/test/general/browser_fxa_oauth.html
+++ b/browser/base/content/test/general/browser_fxa_oauth.html
@@ -3,16 +3,18 @@
<head>
<meta charset="utf-8">
<title>fxa_oauth_test</title>
</head>
<body>
<script>
window.onload = function(){
var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ // Note: This intentionally sends an object instead of a string, to ensure both work
+ // (see browser_fxa_oauth_with_keys.html for the other test)
detail: {
id: "oauth_client_id",
message: {
command: "oauth_complete",
data: {
state: "state",
code: "code1",
closeWindow: "signin",
--- a/browser/base/content/test/general/browser_fxa_oauth.js
+++ b/browser/base/content/test/general/browser_fxa_oauth.js
@@ -304,17 +304,25 @@ function waitForTab(aCallback) {
}, true);
}, false);
}
function test() {
waitForExplicitFinish();
Task.spawn(function () {
- for (let test of gTests) {
- info("Running: " + test.desc);
- yield test.run();
+ const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " http://example.com";
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ try {
+ for (let test of gTests) {
+ info("Running: " + test.desc);
+ yield test.run();
+ }
+ } finally {
+ Services.prefs.clearUserPref(webchannelWhitelistPref);
}
}).then(finish, ex => {
Assert.ok(false, "Unexpected Exception: " + ex);
finish();
});
}
--- a/browser/base/content/test/general/browser_fxa_oauth_with_keys.html
+++ b/browser/base/content/test/general/browser_fxa_oauth_with_keys.html
@@ -3,29 +3,31 @@
<head>
<meta charset="utf-8">
<title>fxa_oauth_test</title>
</head>
<body>
<script>
window.onload = function(){
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ // Note: This intentionally sends a string instead of an object, to ensure both work
+ // (see browser_fxa_oauth.html for the other test)
+ detail: JSON.stringify({
id: "oauth_client_id",
message: {
command: "oauth_complete",
data: {
state: "state",
code: "code1",
closeWindow: "signin",
// Keys normally contain more information, but this is enough
// to keep Loop's tests happy.
keys: { kAr: { k: 'kAr' }, kBr: { k: 'kBr' }},
},
},
- },
+ }),
});
window.dispatchEvent(event);
};
</script>
</body>
</html>
--- a/browser/base/content/test/general/browser_fxa_web_channel.html
+++ b/browser/base/content/test/general/browser_fxa_web_channel.html
@@ -27,112 +27,112 @@
case "delete":
test_delete();
break;
}
};
function test_profile_change() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: webChannelId,
message: {
command: "profile:change",
data: {
uid: "abc123",
},
},
- },
+ }),
});
window.dispatchEvent(event);
}
function test_login() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: webChannelId,
message: {
command: "fxaccounts:login",
data: {
authAt: Date.now(),
email: "testuser@testuser.com",
keyFetchToken: 'key_fetch_token',
sessionToken: 'session_token',
uid: 'uid',
unwrapBKey: 'unwrap_b_key',
verified: true,
},
messageId: 1,
},
- },
+ }),
});
window.dispatchEvent(event);
}
function test_can_link_account() {
window.addEventListener("WebChannelMessageToContent", function (e) {
// echo any responses from the browser back to the tests on the
// fxaccounts_webchannel_response_echo WebChannel. The tests are
// listening for events and do the appropriate checks.
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: 'fxaccounts_webchannel_response_echo',
message: e.detail.message,
- }
+ })
});
window.dispatchEvent(event);
}, true);
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: webChannelId,
message: {
command: "fxaccounts:can_link_account",
data: {
email: "testuser@testuser.com",
},
messageId: 2,
},
- },
+ }),
});
window.dispatchEvent(event);
}
function test_logout() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: webChannelId,
message: {
command: "fxaccounts:logout",
data: {
uid: 'uid'
},
messageId: 3,
},
- },
+ }),
});
window.dispatchEvent(event);
}
function test_delete() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: webChannelId,
message: {
command: "fxaccounts:delete",
data: {
uid: 'uid'
},
messageId: 4,
},
- },
+ }),
});
window.dispatchEvent(event);
}
</script>
</body>
</html>
--- a/browser/base/content/test/general/browser_remoteTroubleshoot.js
+++ b/browser/base/content/test/general/browser_remoteTroubleshoot.js
@@ -2,16 +2,17 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var {WebChannel} = Cu.import("resource://gre/modules/WebChannel.jsm", {});
const TEST_URL_TAIL = "example.com/browser/browser/base/content/test/general/test_remoteTroubleshoot.html"
const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URL_TAIL, null, null);
const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URL_TAIL, null, null);
+const TEST_URI_GOOD_OBJECT = Services.io.newURI("https://" + TEST_URL_TAIL + "?object", null, null);
// Creates a one-shot web-channel for the test data to be sent back from the test page.
function promiseChannelResponse(channelID, originOrPermission) {
return new Promise((resolve, reject) => {
let channel = new WebChannel(channelID, originOrPermission);
channel.listen((id, data, target) => {
channel.stopListening();
resolve(data);
@@ -73,9 +74,20 @@ add_task(function*() {
// 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.
got = yield promiseNewChannelResponse(TEST_URI_BAD);
Assert.ok(got.message === undefined, "should have failed to get any data");
+
+ // 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);
+ });
+ got = yield promiseNewChannelResponse(TEST_URI_GOOD_OBJECT);
+ Assert.ok(got.message, "should have gotten some data back");
});
--- a/browser/base/content/test/general/browser_web_channel.html
+++ b/browser/base/content/test/general/browser_web_channel.html
@@ -28,79 +28,82 @@
test_iframe_pre_redirect();
break;
case "unsolicited":
test_unsolicited();
break;
case "bubbles":
test_bubbles();
break;
+ case "object":
+ test_object();
+ break;
default:
throw new Error(`INVALID TEST NAME ${testName}`);
break;
}
};
function test_generic() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "generic",
message: {
something: {
nested: "hello",
},
}
- }
+ })
});
window.dispatchEvent(event);
}
function test_twoWay() {
var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "twoway",
message: {
command: "one",
},
- }
+ })
});
window.addEventListener("WebChannelMessageToContent", function(e) {
var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "twoway",
message: {
command: "two",
detail: e.detail.message,
},
- },
+ }),
});
if (!e.detail.message.error) {
window.dispatchEvent(secondMessage);
}
}, true);
window.dispatchEvent(firstMessage);
}
function test_multichannel() {
var event1 = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "wrongchannel",
message: {},
- }
+ })
});
var event2 = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "multichannel",
message: {},
- }
+ })
});
window.dispatchEvent(event1);
window.dispatchEvent(event2);
}
function test_iframe() {
// Note that this message is the response to the message sent
@@ -127,40 +130,60 @@
// echo any unsolicted events back to chrome.
window.addEventListener("WebChannelMessageToContent", function(e) {
echoEventToChannel(e, "echo");
}, true);
}
function test_bubbles() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "not_a_window",
message: {
command: "start"
}
- }
+ })
});
var nonWindowTarget = document.getElementById("not_a_window");
nonWindowTarget.addEventListener("WebChannelMessageToContent", function(e) {
echoEventToChannel(e, "not_a_window");
}, true);
nonWindowTarget.dispatchEvent(event);
}
+ function test_object() {
+ let objectMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "objects",
+ message: { type: "object" }
+ }
+ });
+
+ let stringMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "objects",
+ message: { type: "string" }
+ })
+ });
+ // 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 echoEventToChannel(e, channelId) {
var echoedEvent = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: channelId,
message: e.detail.message,
- }
+ })
});
e.target.dispatchEvent(echoedEvent);
}
</script>
<div id="not_a_window"></div>
</body>
--- a/browser/base/content/test/general/browser_web_channel.js
+++ b/browser/base/content/test/general/browser_web_channel.js
@@ -39,18 +39,18 @@ var gTests = [
{
desc: "WebChannel two way communication",
run: function* () {
return new Promise(function(resolve, reject) {
let tab;
let channel = new WebChannel("twoway", Services.io.newURI(HTTP_PATH, null, null));
channel.listen(function (id, message, sender) {
- is(id, "twoway");
- ok(message.command);
+ is(id, "twoway", "bad id");
+ ok(message.command, "command not ok");
if (message.command === "one") {
channel.send({ data: { nested: true } }, sender);
}
if (message.command === "two") {
is(message.detail.data.nested, true);
channel.stopListening();
@@ -69,18 +69,18 @@ var gTests = [
let parentChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
let iframeChannel = new WebChannel("twoway", Services.io.newURI(HTTP_IFRAME_PATH, null, null));
let promiseTestDone = new Promise(function (resolve, reject) {
parentChannel.listen(function (id, message, sender) {
reject(new Error("WebChannel message incorrectly sent to parent"));
});
iframeChannel.listen(function (id, message, sender) {
- is(id, "twoway");
- ok(message.command);
+ is(id, "twoway", "bad id (2)");
+ ok(message.command, "command not ok (2)");
if (message.command === "one") {
iframeChannel.send({ data: { nested: true } }, sender);
}
if (message.command === "two") {
is(message.detail.data.nested, true);
resolve();
@@ -323,16 +323,85 @@ var gTests = [
gBrowser,
url: HTTP_PATH + HTTP_ENDPOINT + "?bubbles"
}, function* () {
yield testDonePromise;
channel.stopListening();
});
}
},
+ {
+ desc: "WebChannel disallows non-string message from non-whitelisted origin",
+ run: function* () {
+ /**
+ * This test ensures that non-string messages can't be sent via WebChannels.
+ * We create a page (on a non-whitelisted origin) which should send us two
+ * messages immediately. The first message has an object for it's detail,
+ * and the second has a string. We check that we only get the second
+ * message.
+ */
+ let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH, null, null));
+ let testDonePromise = new Promise((resolve, reject) => {
+ channel.listen((id, message, sender) => {
+ is(id, "objects");
+ is(message.type, "string");
+ resolve();
+ });
+ });
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?object"
+ }, function* () {
+ yield testDonePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel allows both string and non-string message from whitelisted origin",
+ run: function* () {
+ /**
+ * Same process as above, but we whitelist the origin before loading the page,
+ * and expect to get *both* messages back (each exactly once).
+ */
+ let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH, null, null));
+
+ let testDonePromise = new Promise((resolve, reject) => {
+ let sawObject = false;
+ let sawString = false;
+ channel.listen((id, message, sender) => {
+ is(id, "objects");
+ if (message.type === "object") {
+ ok(!sawObject);
+ sawObject = true;
+ } else if (message.type === "string") {
+ ok(!sawString);
+ sawString = true;
+ } else {
+ reject(new Error(`Unknown message type: ${message.type}`))
+ }
+ if (sawObject && sawString) {
+ resolve();
+ }
+ });
+ });
+ const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " " + HTTP_PATH;
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?object"
+ }, function* () {
+ yield testDonePromise;
+ Services.prefs.setCharPref(webchannelWhitelistPref, origWhitelist);
+ channel.stopListening();
+ });
+ }
+ }
]; // gTests
function test() {
waitForExplicitFinish();
Task.spawn(function () {
for (let test of gTests) {
info("Running: " + test.desc);
--- a/browser/base/content/test/general/browser_web_channel_iframe.html
+++ b/browser/base/content/test/general/browser_web_channel_iframe.html
@@ -23,75 +23,75 @@
default:
throw new Error(`INVALID TEST NAME ${testName}`);
break;
}
};
function test_iframe() {
var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "twoway",
message: {
command: "one",
},
- }
+ })
});
window.addEventListener("WebChannelMessageToContent", function(e) {
var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "twoway",
message: {
command: "two",
detail: e.detail.message,
},
- },
+ }),
});
if (!e.detail.message.error) {
window.dispatchEvent(secondMessage);
}
}, true);
window.dispatchEvent(firstMessage);
}
function test_iframe_pre_redirect() {
var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "pre_redirect",
message: {
command: "redirecting",
},
- },
+ }),
});
window.dispatchEvent(firstMessage);
document.location = REDIRECTED_IFRAME_SRC_ROOT + "?iframe_post_redirect";
}
function test_iframe_post_redirect() {
window.addEventListener("WebChannelMessageToContent", function(e) {
var echoMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "post_redirect",
message: e.detail.message,
- },
+ }),
});
window.dispatchEvent(echoMessage);
}, true);
// Let the test parent know the page has loaded and is ready to echo events
var loadedMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "post_redirect",
message: {
command: "loaded",
},
- },
+ }),
});
window.dispatchEvent(loadedMessage);
}
</script>
</body>
</html>
--- a/browser/base/content/test/general/test_remoteTroubleshoot.html
+++ b/browser/base/content/test/general/test_remoteTroubleshoot.html
@@ -1,39 +1,48 @@
<!DOCTYPE HTML>
<html>
<script>
+// This test is run multiple times, once with only strings allowed through the
+// WebChannel, and once with objects allowed. This function allows us to handle
+// both cases without too much pain.
+function makeDetails(object) {
+ if (window.location.search.indexOf("object") >= 0) {
+ return object;
+ }
+ return JSON.stringify(object)
+}
// Add a listener for responses to our remote requests.
window.addEventListener("WebChannelMessageToContent", function (event) {
if (event.detail.id == "remote-troubleshooting") {
// Send what we got back to the test.
var backEvent = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: makeDetails({
id: "test-remote-troubleshooting-backchannel",
message: {
message: event.detail.message,
},
- },
+ }),
});
window.dispatchEvent(backEvent);
// and stick it in our DOM just for good measure/diagnostics.
document.getElementById("troubleshooting").textContent =
JSON.stringify(event.detail.message, null, 2);
}
});
// Make a request for the troubleshooting data as we load.
window.onload = function() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: makeDetails({
id: "remote-troubleshooting",
message: {
command: "request",
},
- },
+ }),
});
window.dispatchEvent(event);
}
</script>
<body>
<pre id="troubleshooting"/>
</body>
--- a/browser/components/newtab/tests/browser/browser_newtabwebchannel.js
+++ b/browser/components/newtab/tests/browser/browser_newtabwebchannel.js
@@ -147,22 +147,24 @@ add_task(function* webchannel_switch() {
return new Promise(resolve => {
NewTabWebChannel.once("foo", function(name, msg) {
resolve(msg.target);
}.bind(this));
});
}
let replyCount = 0;
- let replyPromise = new Promise(resolve => {
- NewTabWebChannel.on("reply", function() {
- replyCount += 1;
- resolve();
- }.bind(this));
- });
+ function newReplyPromise() {
+ return new Promise(resolve => {
+ NewTabWebChannel.on("reply", function() {
+ replyCount += 1;
+ resolve();
+ });
+ });
+ }
let unloadPromise = new Promise(resolve => {
NewTabWebChannel.once("targetUnload", function() {
resolve();
});
});
let unloadAllPromise = new Promise(resolve => {
@@ -183,19 +185,30 @@ add_task(function* webchannel_switch() {
messagePromise = newMessagePromise();
Preferences.set("browser.newtabpage.remote.mode", "test2");
tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL_2));
yield unloadAllPromise;
yield messagePromise;
is(NewTabWebChannel.numBrowsers, 1, "Correct number of targets");
NewTabWebChannel.broadcast("respond", null);
- yield replyPromise;
+ yield newReplyPromise();
is(replyCount, 1, "only current channel is listened to for replies");
+ const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " http://mochi.test:8888";
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ try {
+ NewTabWebChannel.broadcast("respond_object", null);
+ yield newReplyPromise();
+ } finally {
+ Services.prefs.clearUserPref(webchannelWhitelistPref);
+ }
+
for (let tab of tabs) {
yield BrowserTestUtils.removeTab(tab);
}
Cu.forceGC();
is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
yield unloadPromise;
cleanup();
--- a/browser/components/newtab/tests/browser/newtabmessages_places.html
+++ b/browser/components/newtab/tests/browser/newtabmessages_places.html
@@ -6,44 +6,44 @@
<body>
<script>
window.addEventListener("WebChannelMessageToContent", function(e) {
if (e.detail.message) {
let reply;
switch (e.detail.message.type) {
case "RECEIVE_FRECENT":
reply = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "numItemsAck", data: e.detail.message.data.length}),
- }
+ })
});
window.dispatchEvent(reply);
break;
case "RECEIVE_PLACES_CHANGE":
if (e.detail.message.data.type === "clearHistory") {
reply = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "clearHistoryAck", data: e.detail.message.data.type}),
- }
+ })
});
window.dispatchEvent(reply);
}
break;
}
}
}, true);
document.onreadystatechange = function () {
if (document.readyState === "complete") {
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "REQUEST_FRECENT"}),
- }
+ })
});
window.dispatchEvent(msg);
}
}
</script>
</body>
</html>
--- a/browser/components/newtab/tests/browser/newtabmessages_prefs.html
+++ b/browser/components/newtab/tests/browser/newtabmessages_prefs.html
@@ -3,30 +3,30 @@
<meta charset="utf8">
<title>Newtab WebChannel test</title>
</head>
<body>
<script>
window.addEventListener("WebChannelMessageToContent", function(e) {
if (e.detail.message && e.detail.message.type === "RECEIVE_PREFS") {
let reply = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "responseAck"}),
- }
+ })
});
window.dispatchEvent(reply);
}
}, true);
document.onreadystatechange = function () {
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "REQUEST_PREFS"}),
- }
+ })
});
window.dispatchEvent(msg);
};
</script>
</body>
</html>
--- a/browser/components/newtab/tests/browser/newtabmessages_preview.html
+++ b/browser/components/newtab/tests/browser/newtabmessages_preview.html
@@ -6,32 +6,32 @@
<body>
<script>
let thumbURL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
window.addEventListener("WebChannelMessageToContent", function(e) {
if (e.detail.message && e.detail.message.type === "RECEIVE_THUMB") {
if (e.detail.message.data.imgData && e.detail.message.data.url === thumbURL) {
let reply = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "responseAck"}),
- }
+ })
});
window.dispatchEvent(reply);
}
}
}, true);
document.onreadystatechange = function () {
if (document.readyState === "complete") {
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "REQUEST_THUMB", data: thumbURL}),
- }
+ })
});
window.dispatchEvent(msg);
}
};
</script>
</body>
</html>
--- a/browser/components/newtab/tests/browser/newtabwebchannel_basic.html
+++ b/browser/components/newtab/tests/browser/newtabwebchannel_basic.html
@@ -2,31 +2,35 @@
<head>
<meta charset="utf8">
<title>Newtab WebChannel test</title>
</head>
<body>
<script>
document.onreadystatechange = function () {
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "foo", data: "bar"}),
- }
+ })
});
window.dispatchEvent(msg);
};
window.addEventListener("WebChannelMessageToContent", function(e) {
- if (e.detail.message && e.detail.message.type === "respond") {
+ if (e.detail.message && e.detail.message.type.startsWith("respond")) {
+ var detail = {
+ id: "newtab",
+ message: JSON.stringify({type: "reply", data: "quuz"}),
+ };
+ if (e.detail.message.type !== "respond_object") {
+ detail = JSON.stringify(detail);
+ }
let reply = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
- id: "newtab",
- message: JSON.stringify({type: "reply", data: "quuz"}),
- }
+ detail: detail
});
window.dispatchEvent(reply);
}
}, true);
</script>
</body>
</html>
--- a/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
+++ b/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
@@ -490,21 +490,21 @@ loop.store.ActiveRoomStore = function (m
webChannelListenerFunc = webChannelListener.bind(this);
window.addEventListener("WebChannelMessageToContent", webChannelListenerFunc);
// Now send a message to the chrome to see if it can handle this room.
window.dispatchEvent(new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "loop-link-clicker",
message: {
command: "checkWillOpenRoom",
- roomToken: this._storeState.roomToken } } }));}.
+ roomToken: this._storeState.roomToken } }) }));}.
bind(this));},
/**
* Handles the updateRoomInfo action. Updates the room data.
@@ -616,21 +616,21 @@ loop.store.ActiveRoomStore = function (m
channelListener = handleRoomJoinResponse.bind(this);
window.addEventListener("WebChannelMessageToContent", channelListener);
// Now we're set up, dispatch an event.
window.dispatchEvent(new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "loop-link-clicker",
message: {
command: "openRoom",
- roomToken: this._storeState.roomToken } } }));},
+ roomToken: this._storeState.roomToken } }) }));},
/**
* Handles the action to join to a room.
*/
--- a/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
@@ -885,21 +885,21 @@ describe("loop.store.ActiveRoomStore", f
it("should dispatch an event to Firefox", function () {
sandbox.stub(window, "dispatchEvent");
store.joinRoom();
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent, new window.CustomEvent(
"WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "loop-link-clicker",
message: {
command: "openRoom",
- roomToken: "fakeToken" } } }));});
+ roomToken: "fakeToken" } }) }));});
it("should log an error if Firefox doesn't handle the room", function () {
// Start the join.
store.joinRoom();
--- a/browser/extensions/loop/chrome/test/mochitest/browser_LoopRooms_channel.js
+++ b/browser/extensions/loop/chrome/test/mochitest/browser_LoopRooms_channel.js
@@ -10,16 +10,17 @@
var { WebChannel } = Cu.import("resource://gre/modules/WebChannel.jsm", {});
var { Chat } = Cu.import("resource:///modules/Chat.jsm", {});
const TEST_URI =
"example.com/browser/browser/extensions/loop/chrome/test/mochitest/test_loopLinkClicker_channel.html";
const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URI, null, null);
const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URI, null, null);
+const TEST_URI_GOOD_OBJECT = Services.io.newURI("https://" + TEST_URI + "?object", null, null);
const ROOM_TOKEN = "fake1234";
const LINKCLICKER_URL_PREFNAME = "loop.linkClicker.url";
var openChatOrig = Chat.open;
var fakeRoomList = new Map([[ROOM_TOKEN, { roomToken: ROOM_TOKEN }]]);
@@ -160,9 +161,23 @@ add_task(function* test_loopRooms_webcha
// Simulate a window already being open.
MozLoopServiceInternal.mocks.isChatWindowOpen = true;
got = yield promiseNewChannelResponse(TEST_URI_GOOD, gGoodBackChannel, "openRoom");
Assert.equal(got.message.response, true, "should have got a response of true");
Assert.equal(got.message.alreadyOpen, true, "should indicate the room is already open");
+
+ // Ensure this still works properly when passing an object through the WebChannel
+ 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);
+ });
+ got = yield promiseNewChannelResponse(TEST_URI_GOOD_OBJECT, gGoodBackChannel, "openRoom");
+
+ Assert.equal(got.message.response, true, "should have got a response of true with objects");
+ Assert.equal(got.message.alreadyOpen, true, "should indicate the room is already open with objects");
+
});
--- a/browser/extensions/loop/chrome/test/mochitest/test_loopLinkClicker_channel.html
+++ b/browser/extensions/loop/chrome/test/mochitest/test_loopLinkClicker_channel.html
@@ -1,44 +1,53 @@
<!DOCTYPE HTML>
<html>
<script>
"use strict";
+// This test is run multiple times, once with only strings allowed through the
+// WebChannel, and once with objects allowed. This function allows us to handle
+// both cases without too much pain.
+function makeDetails(object) {
+ if (window.location.search.indexOf("object") >= 0) {
+ return object;
+ }
+ return JSON.stringify(object)
+}
// Add a listener for responses to our remote requests.
window.addEventListener("WebChannelMessageToContent", function (event) {
if (event.detail.id == "loop-link-clicker") {
// Send what we got back to the test.
var backEvent = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: makeDetails({
id: "test-loop-link-clicker-backchannel",
message: {
message: event.detail.message
}
- }
+ })
});
window.dispatchEvent(backEvent);
// and stick it in our DOM just for good measure/diagnostics.
document.getElementById("troubleshooting").textContent =
JSON.stringify(event.detail.message, null, 2);
}
});
// Send a message on load requesting that the room is opened.
window.onload = function() {
var hash = window.location.hash;
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: makeDetails({
id: "loop-link-clicker",
message: {
command: hash.substring(1, hash.length),
roomToken: "fake1234"
}
- }
+ })
});
window.dispatchEvent(event);
};
</script>
<body>
<pre id="troubleshooting"/>
</body>
--- a/mobile/android/tests/browser/chrome/web_channel.html
+++ b/mobile/android/tests/browser/chrome/web_channel.html
@@ -19,71 +19,71 @@
case "multichannel":
test_multichannel();
break;
}
};
function test_generic() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "generic",
message: {
something: {
nested: "hello",
},
}
- }
+ })
});
window.dispatchEvent(event);
}
function test_twoWay() {
var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "twoway",
message: {
command: "one",
},
- }
+ })
});
window.addEventListener("WebChannelMessageToContent", function(e) {
var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "twoway",
message: {
command: "two",
detail: e.detail.message,
},
- },
+ }),
});
if (!e.detail.message.error) {
window.dispatchEvent(secondMessage);
}
}, true);
window.dispatchEvent(firstMessage);
}
function test_multichannel() {
var event1 = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "wrongchannel",
message: {},
- }
+ })
});
var event2 = new window.CustomEvent("WebChannelMessageToChrome", {
- detail: {
+ detail: JSON.stringify({
id: "multichannel",
message: {},
- }
+ })
});
window.dispatchEvent(event1);
window.dispatchEvent(event2);
}
</script>
</body>
</html>
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -809,27 +809,66 @@ var FindBar = {
_onMouseup(event) {
if (this._findMode != this.FIND_NORMAL)
sendAsyncMessage("Findbar:Mouseup");
},
};
FindBar.init();
-// An event listener for custom "WebChannelMessageToChrome" events on pages.
-addEventListener("WebChannelMessageToChrome", function (e) {
- // If target is window then we want the document principal, otherwise fallback to target itself.
- let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
+let WebChannelMessageToChromeListener = {
+ // Preference containing the list (space separated) of origins that are
+ // allowed to send non-string values through a WebChannel, mainly for
+ // backwards compatability. See bug 1238128 for more information.
+ URL_WHITELIST_PREF: "webchannel.allowObject.urlWhitelist",
+
+ // Cached list of whitelisted principals, we avoid constructing this if the
+ // value in `_lastWhitelistValue` hasn't changed since we constructed it last.
+ _cachedWhitelist: [],
+ _lastWhitelistValue: "",
+
+ init() {
+ addEventListener("WebChannelMessageToChrome", e => {
+ this._onMessageToChrome(e);
+ }, true, true);
+ },
- if (e.detail) {
- sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
- } else {
- Cu.reportError("WebChannel message failed. No message detail.");
+ _getWhitelistedPrincipals() {
+ let whitelist = Services.prefs.getCharPref(this.URL_WHITELIST_PREF);
+ if (whitelist != this._lastWhitelistValue) {
+ let urls = whitelist.split(/\s+/);
+ this._cachedWhitelist = urls.map(origin =>
+ Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin));
+ }
+ return this._cachedWhitelist;
+ },
+
+ _onMessageToChrome(e) {
+ // If target is window then we want the document principal, otherwise fallback to target itself.
+ let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
+
+ if (e.detail) {
+ if (typeof e.detail != 'string') {
+ // Check if the principal is one of the ones that's allowed to send
+ // non-string values for e.detail.
+ let objectsAllowed = this._getWhitelistedPrincipals().some(whitelisted =>
+ principal.originNoSuffix == whitelisted.originNoSuffix);
+ if (!objectsAllowed) {
+ Cu.reportError("WebChannelMessageToChrome sent with an object from a non-whitelisted principal");
+ return;
+ }
+ }
+ sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
+ } else {
+ Cu.reportError("WebChannel message failed. No message detail.");
+ }
}
-}, true, true);
+};
+
+WebChannelMessageToChromeListener.init();
// This should be kept in sync with /browser/base/content.js.
// Add message listener for "WebChannelMessageToContent" messages from chrome scripts.
addMessageListener("WebChannelMessageToContent", function (e) {
if (e.data) {
// e.objects.eventTarget will be defined if sending a response to
// a WebChannelMessageToChrome event. An unsolicited send
// may not have an eventTarget defined, in this case send to the
--- a/toolkit/modules/WebChannel.jsm
+++ b/toolkit/modules/WebChannel.jsm
@@ -66,16 +66,25 @@ var WebChannelBroker = Object.create({
*/
_listener: function (event) {
let data = event.data;
let sendingContext = {
browser: event.target,
eventTarget: event.objects.eventTarget,
principal: event.principal,
};
+ // data must be a string except for a few legacy origins allowed by browser-content.js.
+ if (typeof data == "string") {
+ try {
+ data = JSON.parse(data);
+ } 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");
} else {
let validChannelFound = false;
data.message = data.message || {};