Bug 1238128 - Ensure that the details passed to WebChannelMessageToChrome is a string, with a whitelist for messages from existing users r=Margaret,markh,MattN

MozReview-Commit-ID: DpdJ5bUcBdQ

--HG--
extra : rebase_source : b240484b44e9040b2d4e8509589f288e896a4267
This commit is contained in:
Thom Chiovoloni 2016-07-12 19:34:41 -04:00
parent 5b39e168bb
commit 838730ae1b
22 changed files with 323 additions and 106 deletions

View File

@ -1454,3 +1454,6 @@ 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");

View File

@ -8,6 +8,8 @@
<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: {

View File

@ -309,9 +309,17 @@ 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);

View File

@ -8,7 +8,9 @@
<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",
@ -21,7 +23,7 @@
keys: { kAr: { k: 'kAr' }, kBr: { k: 'kBr' }},
},
},
},
}),
});
window.dispatchEvent(event);

View File

@ -32,7 +32,7 @@
function test_profile_change() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
detail: JSON.stringify({
id: webChannelId,
message: {
command: "profile:change",
@ -40,7 +40,7 @@
uid: "abc123",
},
},
},
}),
});
window.dispatchEvent(event);
@ -48,7 +48,7 @@
function test_login() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
detail: JSON.stringify({
id: webChannelId,
message: {
command: "fxaccounts:login",
@ -63,7 +63,7 @@
},
messageId: 1,
},
},
}),
});
window.dispatchEvent(event);
@ -75,17 +75,17 @@
// 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",
@ -94,7 +94,7 @@
},
messageId: 2,
},
},
}),
});
window.dispatchEvent(event);
@ -102,7 +102,7 @@
function test_logout() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
detail: JSON.stringify({
id: webChannelId,
message: {
command: "fxaccounts:logout",
@ -111,7 +111,7 @@
},
messageId: 3,
},
},
}),
});
window.dispatchEvent(event);
@ -119,7 +119,7 @@
function test_delete() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
detail: JSON.stringify({
id: webChannelId,
message: {
command: "fxaccounts:delete",
@ -128,7 +128,7 @@
},
messageId: 4,
},
},
}),
});
window.dispatchEvent(event);

View File

@ -7,6 +7,7 @@ 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) {
@ -78,4 +79,15 @@ add_task(function*() {
// 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");
});

View File

@ -33,6 +33,9 @@
case "bubbles":
test_bubbles();
break;
case "object":
test_object();
break;
default:
throw new Error(`INVALID TEST NAME ${testName}`);
break;
@ -41,14 +44,14 @@
function test_generic() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
detail: JSON.stringify({
id: "generic",
message: {
something: {
nested: "hello",
},
}
}
})
});
window.dispatchEvent(event);
@ -56,23 +59,23 @@
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) {
@ -85,17 +88,17 @@
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);
@ -132,12 +135,12 @@
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");
@ -150,12 +153,32 @@
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);

View File

@ -44,8 +44,8 @@ var gTests = [
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);
@ -74,8 +74,8 @@ var gTests = [
});
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);
@ -328,6 +328,75 @@ var gTests = [
});
}
},
{
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() {

View File

@ -28,23 +28,23 @@
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) {
@ -58,12 +58,12 @@
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";
@ -72,10 +72,10 @@
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);
@ -83,12 +83,12 @@
// 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);
}

View File

@ -1,17 +1,26 @@
<!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.
@ -23,12 +32,12 @@ window.addEventListener("WebChannelMessageToContent", function (event) {
// 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);
}

View File

@ -152,12 +152,14 @@ add_task(function* webchannel_switch() {
}
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() {
@ -188,9 +190,20 @@ add_task(function* webchannel_switch() {
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);
}

View File

@ -11,20 +11,20 @@
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);
}
@ -36,10 +36,10 @@
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);
}

View File

@ -8,10 +8,10 @@
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);
}
@ -19,10 +19,10 @@
document.onreadystatechange = function () {
let msg = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
detail: JSON.stringify({
id: "newtab",
message: JSON.stringify({type: "REQUEST_PREFS"}),
}
})
});
window.dispatchEvent(msg);
};

View File

@ -11,10 +11,10 @@
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);
}
@ -24,10 +24,10 @@
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);
}

View File

@ -7,21 +7,25 @@
<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);
}

View File

@ -495,11 +495,11 @@ loop.store.ActiveRoomStore = function (mozL10n) {
// 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 } }) }));}.
@ -621,11 +621,11 @@ loop.store.ActiveRoomStore = function (mozL10n) {
// 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 } }) }));},

View File

@ -890,11 +890,11 @@ describe("loop.store.ActiveRoomStore", function () {
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" } }) }));});

View File

@ -15,6 +15,7 @@ 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";
@ -165,4 +166,18 @@ add_task(function* test_loopRooms_webchannel_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");
});

View File

@ -2,18 +2,27 @@
<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.
@ -27,13 +36,13 @@ 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);
};

View File

@ -24,14 +24,14 @@
function test_generic() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
detail: JSON.stringify({
id: "generic",
message: {
something: {
nested: "hello",
},
}
}
})
});
window.dispatchEvent(event);
@ -39,23 +39,23 @@
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) {
@ -68,17 +68,17 @@
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);

View File

@ -814,17 +814,56 @@ var FindBar = {
};
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",
if (e.detail) {
sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
} else {
Cu.reportError("WebChannel message failed. No message detail.");
// 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);
},
_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.

View File

@ -71,6 +71,15 @@ var WebChannelBroker = Object.create({
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) {