Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-02-22 14:41:37 +01:00
commit 53eeadbacf
14 changed files with 2252 additions and 1693 deletions

View File

@ -1318,6 +1318,7 @@ pref("services.sync.prefs.sync.security.OCSP.require", true);
pref("services.sync.prefs.sync.security.default_personal_cert", true);
pref("services.sync.prefs.sync.security.tls.version.min", true);
pref("services.sync.prefs.sync.security.tls.version.max", true);
pref("services.sync.prefs.sync.services.sync.syncedTabs.showRemoteIcons", true);
pref("services.sync.prefs.sync.signon.rememberSignons", true);
pref("services.sync.prefs.sync.spellchecker.dictionary", true);
pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
@ -1328,6 +1329,12 @@ pref("services.sync.syncedTabsUIRefresh", true);
pref("services.sync.syncedTabsUIRefresh", false);
#endif
// A preference that controls whether we should show the icon for a remote tab.
// This pref has no UI but exists because some people may be concerned that
// fetching these icons to show remote tabs may leak information about that
// user's tabs and bookmarks. Note this pref is also synced.
pref("services.sync.syncedTabs.showRemoteIcons", true);
// Developer edition preferences
#ifdef MOZ_DEV_EDITION
sticky_pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");

View File

@ -19,6 +19,7 @@ support-files =
browser_star_hsts.sjs
browser_tab_dragdrop2_frame1.xul
browser_web_channel.html
browser_web_channel_iframe.html
bug592338.html
bug792517-2.html
bug792517.html

View File

@ -6,6 +6,8 @@
</head>
<body>
<script>
var IFRAME_SRC_ROOT = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_web_channel_iframe.html";
window.onload = function() {
var testName = window.location.search.replace(/^\?/, "");
@ -19,6 +21,21 @@
case "multichannel":
test_multichannel();
break;
case "iframe":
test_iframe();
break;
case "iframe_pre_redirect":
test_iframe_pre_redirect();
break;
case "unsolicited":
test_unsolicited();
break;
case "bubbles":
test_bubbles();
break;
default:
throw new Error(`INVALID TEST NAME ${testName}`);
break;
}
};
@ -84,6 +101,67 @@
window.dispatchEvent(event1);
window.dispatchEvent(event2);
}
function test_iframe() {
// Note that this message is the response to the message sent
// by the iframe! This is bad, as this page is *not* trusted.
window.addEventListener("WebChannelMessageToContent", function(e) {
// the test parent will fail if the echo message is received.
echoEventToChannel(e, "echo");
});
// only attach the iframe for the iframe test to avoid
// interfering with other tests.
var iframe = document.createElement("iframe");
iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe");
document.body.appendChild(iframe);
}
function test_iframe_pre_redirect() {
var iframe = document.createElement("iframe");
iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe_pre_redirect");
document.body.appendChild(iframe);
}
function test_unsolicited() {
// 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: {
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 echoEventToChannel(e, channelId) {
var echoedEvent = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: channelId,
message: e.detail.message,
}
});
e.target.dispatchEvent(echoedEvent);
}
</script>
<div id="not_a_window"></div>
</body>
</html>

View File

@ -10,6 +10,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
const HTTP_PATH = "http://example.com";
const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_web_channel.html";
const HTTP_MISMATCH_PATH = "http://example.org";
const HTTP_IFRAME_PATH = "http://mochi.test:8888";
const HTTP_REDIRECTED_IFRAME_PATH = "http://example.org";
// 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
@ -60,6 +63,104 @@ var gTests = [
});
}
},
{
desc: "WebChannel two way communication in an iframe",
run: function* () {
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);
if (message.command === "one") {
iframeChannel.send({ data: { nested: true } }, sender);
}
if (message.command === "two") {
is(message.detail.data.nested, true);
resolve();
}
});
});
yield BrowserTestUtils.withNewTab({
gBrowser: gBrowser,
url: HTTP_PATH + HTTP_ENDPOINT + "?iframe"
}, function* () {
yield promiseTestDone;
parentChannel.stopListening();
iframeChannel.stopListening();
});
}
},
{
desc: "WebChannel response to a redirected iframe",
run: function* () {
/**
* This test checks that WebChannel responses are only sent
* to an iframe if the iframe has not redirected to another origin.
* Test flow:
* 1. create a page, embed an iframe on origin A.
* 2. the iframe sends a message `redirecting`, then redirects to
* origin B.
* 3. the iframe at origin B is set up to echo any messages back to the
* test parent.
* 4. the test parent receives the `redirecting` message from origin A.
* the test parent creates a new channel with origin B.
* 5. when origin B is ready, it sends a `loaded` message to the test
* parent, letting the test parent know origin B is ready to echo
* messages.
* 5. the test parent tries to send a response to origin A. If the
* WebChannel does not perform a valid origin check, the response
* will be received by origin B. If the WebChannel does perform
* a valid origin check, the response will not be sent.
* 6. the test parent sends a `done` message to origin B, which origin
* B echoes back. If the response to origin A is not echoed but
* the message to origin B is, then hooray, the test passes.
*/
let preRedirectChannel = new WebChannel("pre_redirect", Services.io.newURI(HTTP_IFRAME_PATH, null, null));
let postRedirectChannel = new WebChannel("post_redirect", Services.io.newURI(HTTP_REDIRECTED_IFRAME_PATH, null, null));
let promiseTestDone = new Promise(function (resolve, reject) {
preRedirectChannel.listen(function (id, message, preRedirectSender) {
if (message.command === "redirecting") {
postRedirectChannel.listen(function (id, message, postRedirectSender) {
is(id, "post_redirect");
isnot(message.command, "no_response_expected");
if (message.command === "loaded") {
// The message should not be received on the preRedirectChannel
// because the target window has redirected.
preRedirectChannel.send({ command: "no_response_expected" }, preRedirectSender);
postRedirectChannel.send({ command: "done" }, postRedirectSender);
} else if (message.command === "done") {
resolve();
} else {
reject(new Error(`Unexpected command ${message.command}`));
}
});
} else {
reject(new Error(`Unexpected command ${message.command}`));
}
});
});
yield BrowserTestUtils.withNewTab({
gBrowser: gBrowser,
url: HTTP_PATH + HTTP_ENDPOINT + "?iframe_pre_redirect"
}, function* () {
yield promiseTestDone;
preRedirectChannel.stopListening();
postRedirectChannel.stopListening();
});
}
},
{
desc: "WebChannel multichannel",
run: function* () {
@ -76,7 +177,157 @@ var gTests = [
tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?multichannel");
});
}
}
},
{
desc: "WebChannel unsolicited send, using system principal",
run: function* () {
let channel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
// an unsolicted message is sent from Chrome->Content which is then
// echoed back. If the echo is received here, then the content
// received the message.
let messagePromise = new Promise(function (resolve, reject) {
channel.listen(function (id, message, sender) {
is(id, "echo");
is(message.command, "unsolicited");
resolve()
});
});
yield BrowserTestUtils.withNewTab({
gBrowser,
url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
}, function* (targetBrowser) {
channel.send({ command: "unsolicited" }, {
browser: targetBrowser,
principal: Services.scriptSecurityManager.getSystemPrincipal()
});
yield messagePromise;
channel.stopListening();
});
}
},
{
desc: "WebChannel unsolicited send, using target origin's principal",
run: function* () {
let targetURI = Services.io.newURI(HTTP_PATH, null, null);
let channel = new WebChannel("echo", targetURI);
// an unsolicted message is sent from Chrome->Content which is then
// echoed back. If the echo is received here, then the content
// received the message.
let messagePromise = new Promise(function (resolve, reject) {
channel.listen(function (id, message, sender) {
is(id, "echo");
is(message.command, "unsolicited");
resolve();
});
});
yield BrowserTestUtils.withNewTab({
gBrowser,
url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
}, function* (targetBrowser) {
channel.send({ command: "unsolicited" }, {
browser: targetBrowser,
principal: Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI)
});
yield messagePromise;
channel.stopListening();
});
}
},
{
desc: "WebChannel unsolicited send with principal mismatch",
run: function* () {
let targetURI = Services.io.newURI(HTTP_PATH, null, null);
let channel = new WebChannel("echo", targetURI);
// two unsolicited messages are sent from Chrome->Content. The first,
// `unsolicited_no_response_expected` is sent to the wrong principal
// and should not be echoed back. The second, `done`, is sent to the
// correct principal and should be echoed back.
let messagePromise = new Promise(function (resolve, reject) {
channel.listen(function (id, message, sender) {
is(id, "echo");
if (message.command === "done") {
resolve();
} else {
reject(new Error(`Unexpected command ${message.command}`));
}
});
});
yield BrowserTestUtils.withNewTab({
gBrowser: gBrowser,
url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
}, function* (targetBrowser) {
let mismatchURI = Services.io.newURI(HTTP_MISMATCH_PATH, null, null);
let mismatchPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(mismatchURI);
// send a message to the wrong principal. It should not be delivered
// to content, and should not be echoed back.
channel.send({ command: "unsolicited_no_response_expected" }, {
browser: targetBrowser,
principal: mismatchPrincipal
});
let targetPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI);
// send the `done` message to the correct principal. It
// should be echoed back.
channel.send({ command: "done" }, {
browser: targetBrowser,
principal: targetPrincipal
});
yield messagePromise;
channel.stopListening();
});
}
},
{
desc: "WebChannel non-window target",
run: function* () {
/**
* This test ensures messages can be received from and responses
* sent to non-window elements.
*
* First wait for the non-window element to send a "start" message.
* Then send the non-window element a "done" message.
* The non-window element will echo the "done" message back, if it
* receives the message.
* Listen for the response. If received, good to go!
*/
let channel = new WebChannel("not_a_window", Services.io.newURI(HTTP_PATH, null, null));
let testDonePromise = new Promise(function (resolve, reject) {
channel.listen(function (id, message, sender) {
if (message.command === "start") {
channel.send({ command: "done" }, sender);
} else if (message.command === "done") {
resolve();
} else {
reject(new Error(`Unexpected command ${message.command}`));
}
});
});
yield BrowserTestUtils.withNewTab({
gBrowser,
url: HTTP_PATH + HTTP_ENDPOINT + "?bubbles"
}, function* () {
yield testDonePromise;
channel.stopListening();
});
}
},
]; // gTests
function test() {

View File

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web_channel_test (iframe)</title>
</head>
<body>
<script>
var REDIRECTED_IFRAME_SRC_ROOT = "http://example.org/browser/browser/base/content/test/general/browser_web_channel_iframe.html";
window.onload = function() {
var testName = window.location.search.replace(/^\?/, "");
switch(testName) {
case "iframe":
test_iframe();
break;
case "iframe_pre_redirect":
test_iframe_pre_redirect();
break;
case "iframe_post_redirect":
test_iframe_post_redirect();
break;
default:
throw new Error(`INVALID TEST NAME ${testName}`);
break;
}
};
function test_iframe() {
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_iframe_pre_redirect() {
var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
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: {
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: {
id: "post_redirect",
message: {
command: "loaded",
},
},
});
window.dispatchEvent(loadedMessage);
}
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ DevToolsModules(
'console-commands.js',
'console-output.js',
'hudservice.js',
'jsterm.js',
'panel.js',
'webconsole.js',
)

File diff suppressed because it is too large Load Diff

View File

@ -62,8 +62,11 @@ let SyncedTabsInternal = {
},
/* Make a "tab" record. Returns a promise */
_makeTab: Task.async(function* (client, tab, url) {
let icon = tab.icon;
_makeTab: Task.async(function* (client, tab, url, showRemoteIcons) {
let icon;
if (showRemoteIcons) {
icon = tab.icon;
}
if (!icon) {
try {
icon = (yield PlacesUtils.promiseFaviconLinkUrl(url)).spec;
@ -108,6 +111,9 @@ let SyncedTabsInternal = {
return result;
}
// A boolean that controls whether we should show the icon from the remote tab.
const showRemoteIcons = Preferences.get("services.sync.syncedTabs.showRemoteIcons", true);
let engine = Weave.Service.engineManager.get("tabs");
let seenURLs = new Set();
@ -134,7 +140,7 @@ let SyncedTabsInternal = {
if (!url || seenURLs.has(url)) {
continue;
}
let tabRepr = yield this._makeTab(client, tab, url);
let tabRepr = yield this._makeTab(client, tab, url, showRemoteIcons);
if (filter && !this._tabMatchesFilter(tabRepr, filter)) {
continue;
}

View File

@ -184,7 +184,9 @@ TabStore.prototype = {
allTabs.push({
title: current.title || "",
urlHistory: urls,
icon: tabState.attributes && tabState.attributes.image || "",
icon: tabState.image ||
(tabState.attributes && tabState.attributes.image) ||
"",
lastUsed: Math.floor((tabState.lastAccessed || 0) / 1000),
});
}

View File

@ -7,6 +7,9 @@ Cu.import("resource://services-sync/main.js");
Cu.import("resource://services-sync/SyncedTabs.jsm");
Cu.import("resource://gre/modules/Log.jsm");
const faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
.getService(Ci.nsIFaviconService);
Log.repository.getLogger("Sync.RemoteTabs").addAppender(new Log.DumpAppender());
// A mock "Tabs" engine which the SyncedTabs module will use instead of the real
@ -79,6 +82,7 @@ add_task(function* test_clientWithTabs() {
tabs: [
{
urlHistory: ["http://foo.com/"],
icon: "http://foo.com/favicon",
}],
},
guid_mobile: {
@ -92,10 +96,34 @@ add_task(function* test_clientWithTabs() {
clients.sort((a, b) => { return a.name.localeCompare(b.name);});
equal(clients[0].tabs.length, 1);
equal(clients[0].tabs[0].url, "http://foo.com/");
equal(clients[0].tabs[0].icon, "http://foo.com/favicon");
// second client has no tabs.
equal(clients[1].tabs.length, 0);
});
add_task(function* test_clientWithTabsIconsDisabled() {
Services.prefs.setBoolPref("services.sync.syncedTabs.showRemoteIcons", false);
yield configureClients({
guid_desktop: {
clientName: "My Desktop",
tabs: [
{
urlHistory: ["http://foo.com/"],
icon: "http://foo.com/favicon",
}],
},
});
let clients = yield SyncedTabs.getTabClients();
equal(clients.length, 1);
clients.sort((a, b) => { return a.name.localeCompare(b.name);});
equal(clients[0].tabs.length, 1);
equal(clients[0].tabs[0].url, "http://foo.com/");
// expect the default favicon due to the pref being false.
equal(clients[0].tabs[0].icon, faviconService.defaultFavicon.spec);
Services.prefs.clearUserPref("services.sync.syncedTabs.showRemoteIcons");
});
add_task(function* test_filter() {
// Nothing matches.
yield configureClients({

View File

@ -63,7 +63,11 @@ function ensureItems() {
return _items;
}
// An observer to invalidate _items.
// A preference used to disable the showing of icons in remote tab records.
const PREF_SHOW_REMOTE_ICONS = "services.sync.syncedTabs.showRemoteIcons";
let showRemoteIcons;
// An observer to invalidate _items and watch for changed prefs.
function observe(subject, topic, data) {
switch (topic) {
case "weave:engine:sync:finish":
@ -80,6 +84,16 @@ function observe(subject, topic, data) {
_items = null;
break;
case "nsPref:changed":
if (data == PREF_SHOW_REMOTE_ICONS) {
try {
showRemoteIcons = Services.prefs.getBoolPref(PREF_SHOW_REMOTE_ICONS);
} catch(_) {
showRemoteIcons = true; // no such pref - default is to show the icons.
}
}
break;
default:
break;
}
@ -88,6 +102,10 @@ function observe(subject, topic, data) {
Services.obs.addObserver(observe, "weave:engine:sync:finish", false);
Services.obs.addObserver(observe, "weave:service:start-over", false);
// Observe the pref for showing remote icons and prime our bool that reflects its value.
Services.prefs.addObserver(PREF_SHOW_REMOTE_ICONS, observe, false);
observe(null, "nsPref:changed", PREF_SHOW_REMOTE_ICONS);
// This public object is a static singleton.
this.PlacesRemoteTabsAutocompleteProvider = {
// a promise that resolves with an array of matching remote tabs.
@ -105,10 +123,10 @@ this.PlacesRemoteTabsAutocompleteProvider = {
if (url.match(re) || (title && title.match(re))) {
// lookup the client record.
let client = clients.get(clientId);
let icon = showRemoteIcons ? tab.icon : null;
// create the record we return for auto-complete.
let record = {
url, title,
icon: tab.icon,
url, title, icon,
deviceClass: Weave.Service.clientsEngine.isMobile(clientId) ? "mobile" : "desktop",
deviceName: client.clientName,
};

View File

@ -54,6 +54,7 @@ function makeRemoteTabMatch(url, deviceName, extra = {}) {
uri: makeActionURI("remotetab", {url, deviceName}),
title: extra.title || url,
style: [ "action" ],
icon: extra.icon,
}
}
@ -115,12 +116,39 @@ add_task(function* test_maximal() {
matches: [ makeSearchMatch("ex", { heuristic: true }),
makeRemoteTabMatch("http://example.com/", "My Phone",
{ title: "An Example",
icon: "moz-anno:favicon:http://favicon"
icon: "moz-anno:favicon:http://favicon/"
}),
],
});
});
add_task(function* test_noShowIcons() {
Services.prefs.setBoolPref("services.sync.syncedTabs.showRemoteIcons", false);
configureEngine({
guid_mobile: {
clientName: "My Phone",
tabs: [{
urlHistory: ["http://example.com/"],
title: "An Example",
icon: "http://favicon",
}],
}
});
yield check_autocomplete({
search: "ex",
searchParam: "enable-actions",
matches: [ makeSearchMatch("ex", { heuristic: true }),
makeRemoteTabMatch("http://example.com/", "My Phone",
{ title: "An Example",
// expecting the default favicon due to that pref.
icon: PlacesUtils.favicons.defaultFavicon.spec,
}),
],
});
Services.prefs.clearUserPref("services.sync.syncedTabs.showRemoteIcons");
});
add_task(function* test_matches_title() {
// URL doesn't match search expression, should still match the title.
configureEngine({

View File

@ -61,6 +61,7 @@ add_task(function test_web_channel_broker_listener() {
deliver: function(data, sender) {
do_check_eq(data.id, VALID_WEB_CHANNEL_ID);
do_check_eq(data.message.command, "hello");
do_check_neq(sender, undefined);
WebChannelBroker.unregisterChannel(channel);
resolve();
}