Bug 1174458 - Move WebChannel message support to toolkit/content. r=markh

This makes WebChannel support available to all XUL applications that
use toolkit/, including browser/ and mobile/android/.

The new Robocop tests are necessary because we can't run the existing
browser-chrome tests on Android (yet).
This commit is contained in:
Nick Alexander 2015-06-30 11:46:27 -07:00
parent 6240a34ae4
commit f94ed4ffb8
8 changed files with 284 additions and 45 deletions

View File

@ -318,50 +318,6 @@ let AboutNetErrorListener = {
AboutNetErrorListener.init(this);
// 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;
if (e.detail) {
sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
} else {
Cu.reportError("WebChannel message failed. No message detail.");
}
}, true, true);
// 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
// main content window.
let eventTarget = e.objects.eventTarget || content;
// if eventTarget is window then we want the document principal,
// otherwise use target itself.
let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;
if (e.principal.subsumes(targetPrincipal)) {
// if eventTarget is a window, use it as the targetWindow, otherwise
// find the window that owns the eventTarget.
let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerDocument.defaultView;
eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
detail: Cu.cloneInto({
id: e.data.id,
message: e.data.message,
}, targetWindow),
}));
} else {
Cu.reportError("WebChannel message failed. Principal mismatch.");
}
} else {
Cu.reportError("WebChannel message failed. No message data.");
}
});
let ClickEventHandler = {
init: function init() {

View File

@ -11,6 +11,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
const HTTP_PATH = "http://example.com";
const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_web_channel.html";
// Keep this synced with /mobile/android/tests/browser/robocop/testWebChannel.js
// as much as possible. (We only have that since we can't run browser chrome
// tests on Android. Yet?)
let gTests = [
{
desc: "WebChannel generic message",

View File

@ -149,6 +149,7 @@ skip-if = android_version == "18"
# disabled on Android 2.3 due to video playback issues, bug 1088038; on 4.3, bug 1098532
skip-if = android_version == "10" || android_version == "18"
[testVideoDiscovery.java]
[testWebChannel.java]
# Used for Talos, please don't use in mochitest
#[testCheck2.java]

View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web_channel_test</title>
</head>
<body>
<script>
window.onload = function() {
var testName = window.location.search.replace(/^\?/, "");
switch(testName) {
case "generic":
test_generic();
break;
case "twoway":
test_twoWay();
break;
case "multichannel":
test_multichannel();
break;
}
};
function test_generic() {
var event = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "generic",
message: {
something: {
nested: "hello",
},
}
}
});
window.dispatchEvent(event);
}
function test_twoWay() {
var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "twoway",
message: {
command: "one",
},
}
});
window.addEventListener("WebChannelMessageToContent", function(e) {
var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
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: {
id: "wrongchannel",
message: {},
}
});
var event2 = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "multichannel",
message: {},
}
});
window.dispatchEvent(event1);
window.dispatchEvent(event2);
}
</script>
</body>
</html>

View File

@ -0,0 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
package org.mozilla.gecko.tests;
public class testWebChannel extends JavascriptTest {
public testWebChannel() {
super("testWebChannel.js");
}
}

View File

@ -0,0 +1,107 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
const { classes: Cc, interfaces: Ci, utils: Cu } = Components; /*global Components */
Cu.import("resource://gre/modules/Promise.jsm"); /*global Promise */
Cu.import("resource://gre/modules/Services.jsm"); /*global Services */
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global XPCOMUtils */
XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
"resource://gre/modules/WebChannel.jsm"); /*global WebChannel */
const HTTP_PATH = "http://mochi.test:8888";
const HTTP_ENDPOINT = "/tests/robocop/testWebChannel.html";
const gChromeWin = Services.wm.getMostRecentWindow("navigator:browser");
let BrowserApp = gChromeWin.BrowserApp;
/**
* Robocop test helpers.
*/
function ok(passed, text) {
do_report_result(passed, text, Components.stack.caller, false);
}
function is(lhs, rhs, text) {
do_report_result(lhs === rhs, "[ " + lhs + " === " + rhs + " ] " + text,
Components.stack.caller, false);
}
// Keep this synced with /browser/base/content/test/general/browser_web_channel.js
// as much as possible. (We only have this since we can't run browser chrome
// tests on Android. Yet?)
let gTests = [
{
desc: "WebChannel generic message",
run: function* () {
return new Promise(function(resolve, reject) {
let tab;
let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null));
channel.listen(function (id, message, target) {
is(id, "generic");
is(message.something.nested, "hello");
channel.stopListening();
BrowserApp.closeTab(tab);
resolve();
});
tab = BrowserApp.addTab(HTTP_PATH + HTTP_ENDPOINT + "?generic");
});
}
},
{
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);
if (message.command === "one") {
channel.send({ data: { nested: true } }, sender);
}
if (message.command === "two") {
is(message.detail.data.nested, true);
channel.stopListening();
BrowserApp.closeTab(tab);
resolve();
}
});
tab = BrowserApp.addTab(HTTP_PATH + HTTP_ENDPOINT + "?twoway");
});
}
},
{
desc: "WebChannel multichannel",
run: function* () {
return new Promise(function(resolve, reject) {
let tab;
let channel = new WebChannel("multichannel", Services.io.newURI(HTTP_PATH, null, null));
channel.listen(function (id, message, sender) {
is(id, "multichannel");
BrowserApp.closeTab(tab);
resolve();
});
tab = BrowserApp.addTab(HTTP_PATH + HTTP_ENDPOINT + "?multichannel");
});
}
}
]; // gTests
add_task(function test() {
for (let test of gTests) {
do_print("Running: " + test.desc);
yield test.run();
}
});
run_next_test();

View File

@ -647,3 +647,47 @@ let 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;
if (e.detail) {
sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
} else {
Cu.reportError("WebChannel message failed. No message detail.");
}
}, true, true);
// 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
// main content window.
let eventTarget = e.objects.eventTarget || content;
// Use nodePrincipal if available, otherwise fallback to document principal.
let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;
if (e.principal.subsumes(targetPrincipal)) {
// If eventTarget is a window, use it as the targetWindow, otherwise
// find the window that owns the eventTarget.
let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerDocument.defaultView;
eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
detail: Cu.cloneInto({
id: e.data.id,
message: e.data.message,
}, targetWindow),
}));
} else {
Cu.reportError("WebChannel message failed. Principal mismatch.");
}
} else {
Cu.reportError("WebChannel message failed. No message data.");
}
});

View File

@ -1,7 +1,8 @@
[DEFAULT]
head =
tail =
skip-if = toolkit == 'android' || toolkit == 'gonk'
# Per-file skip-if is logical or'd with DEFAULT skip-if.
skip-if = toolkit == 'gonk'
support-files =
propertyLists/bug710259_propertyListBinary.plist
propertyLists/bug710259_propertyListXML.plist
@ -9,29 +10,54 @@ support-files =
zips/zen.zip
[test_BinarySearch.js]
skip-if = toolkit == 'android'
[test_client_id.js]
skip-if = toolkit == 'android'
[test_DeferredTask.js]
skip-if = toolkit == 'android'
[test_FileUtils.js]
skip-if = toolkit == 'android'
[test_GMPInstallManager.js]
skip-if = toolkit == 'android'
[test_Http.js]
skip-if = toolkit == 'android'
[test_Log.js]
skip-if = toolkit == 'android'
[test_MatchPattern.js]
skip-if = toolkit == 'android'
[test_NewTabUtils.js]
skip-if = toolkit == 'android'
[test_ObjectUtils.js]
skip-if = toolkit == 'android'
[test_ObjectUtils_strict.js]
skip-if = toolkit == 'android'
[test_PermissionsUtils.js]
skip-if = toolkit == 'android'
[test_Preferences.js]
skip-if = toolkit == 'android'
[test_Promise.js]
skip-if = toolkit == 'android'
[test_PromiseUtils.js]
skip-if = toolkit == 'android'
[test_propertyListsUtils.js]
skip-if = toolkit == 'android'
[test_readCertPrefs.js]
skip-if = toolkit == 'android'
[test_Services.js]
skip-if = toolkit == 'android'
[test_session_recorder.js]
skip-if = toolkit == 'android'
[test_sqlite.js]
skip-if = toolkit == 'android'
[test_sqlite_shutdown.js]
skip-if = toolkit == 'android'
[test_task.js]
skip-if = toolkit == 'android'
[test_TelemetryTimestamps.js]
skip-if = toolkit == 'android'
[test_timer.js]
skip-if = toolkit == 'android'
[test_web_channel.js]
[test_web_channel_broker.js]
[test_ZipUtils.js]
skip-if = toolkit == 'android'