new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/NewTabMessages.jsm
@@ -0,0 +1,95 @@
+/*global
+ NewTabWebChannel,
+ NewTabPrefsProvider,
+ Preferences,
+ XPCOMUtils
+*/
+
+/* exported NewTabMessages */
+
+"use strict";
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+ "resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
+ "resource:///modules/NewTabWebChannel.jsm");
+
+this.EXPORTED_SYMBOLS = ["NewTabMessages"];
+
+const PREF_ENABLED = "browser.newtabpage.remote";
+
+// Action names are from the content's perspective. in from chrome == out from content
+// Maybe replace the ACTION objects by a bi-directional Map a bit later?
+const ACTIONS = {
+ prefs: {
+ inPrefs: "REQUEST_PREFS",
+ outPrefs: "RECEIVE_PREFS",
+ action_types: new Set(["REQUEST_PREFS", "RECEIVE_PREFS"]),
+ }
+};
+
+let NewTabMessages = {
+
+ _prefs: {},
+
+ /** NEWTAB EVENT HANDLERS **/
+
+ /*
+ * Return to the originator all newtabpage prefs. A point-to-point request.
+ */
+ handlePrefRequest(actionName, {target}) {
+ if (ACTIONS.prefs.action_types.has(actionName)) {
+ let results = NewTabPrefsProvider.prefs.newtabPagePrefs;
+ NewTabWebChannel.send(ACTIONS.prefs.outPrefs, results, target);
+ }
+ },
+
+ /*
+ * Broadcast preference changes to all open newtab pages
+ */
+ handlePrefChange(actionName, value) {
+ let prefChange = {};
+ prefChange[actionName] = value;
+ NewTabWebChannel.broadcast(ACTIONS.prefs.outPrefs, prefChange);
+ },
+
+ _handleEnabledChange(prefName, value) {
+ if (prefName === PREF_ENABLED) {
+ if (this._prefs.enabled && !value) {
+ this.uninit();
+ } else if (!this._prefs.enabled && value) {
+ this.init();
+ }
+ }
+ },
+
+ init() {
+ this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
+
+ if (this._prefs.enabled) {
+ NewTabWebChannel.on(ACTIONS.prefs.inPrefs, this.handlePrefRequest.bind(this));
+ NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handleEnabledChange.bind(this));
+
+ for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
+ NewTabPrefsProvider.prefs.on(pref, this.handlePrefChange.bind(this));
+ }
+ }
+ },
+
+ uninit() {
+ this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
+
+ if (this._prefs.enabled) {
+ NewTabPrefsProvider.prefs.off(PREF_ENABLED, this._handleEnabledChange);
+
+ NewTabWebChannel.off(ACTIONS.prefs.inPrefs, this.handlePrefRequest);
+ for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
+ NewTabPrefsProvider.prefs.off(pref, this.handlePrefChange);
+ }
+ }
+ }
+};
--- a/browser/components/newtab/NewTabPrefsProvider.jsm
+++ b/browser/components/newtab/NewTabPrefsProvider.jsm
@@ -16,21 +16,34 @@ XPCOMUtils.defineLazyGetter(this, "Event
});
// Supported prefs and data type
const gPrefsMap = new Map([
["browser.newtabpage.remote", "bool"],
["browser.newtabpage.remote.mode", "str"],
["browser.newtabpage.enabled", "bool"],
["browser.newtabpage.enhanced", "bool"],
+ ["browser.newtabpage.introShown", "bool"],
+ ["browser.newtabpage.updateIntroShown", "bool"],
["browser.newtabpage.pinned", "str"],
+ ["browser.newtabpage.blocked", "str"],
["intl.locale.matchOS", "bool"],
["general.useragent.locale", "localized"],
]);
+// prefs that are important for the newtab page
+const gNewtabPagePrefs = new Set([
+ "browser.newtabpage.enabled",
+ "browser.newtabpage.enhanced",
+ "browser.newtabpage.pinned",
+ "browser.newtabpage.blocked",
+ "browser.newtabpage.introShown",
+ "browser.newtabpage.updateIntroShown"
+]);
+
let PrefsProvider = function PrefsProvider() {
EventEmitter.decorate(this);
};
PrefsProvider.prototype = {
observe(subject, topic, data) { // jshint ignore:line
if (topic === "nsPref:changed") {
@@ -54,16 +67,27 @@ PrefsProvider.prototype = {
break;
}
}
} else {
Cu.reportError(new Error("NewTabPrefsProvider observing unknown topic"));
}
},
+ /*
+ * Return the preferences that are important to the newtab page
+ */
+ get newtabPagePrefs() {
+ let results = {};
+ for (let pref of gNewtabPagePrefs) {
+ results[pref] = Preferences.get(pref, null);
+ }
+ return results;
+ },
+
get prefsMap() {
return gPrefsMap;
},
init() {
for (let pref of gPrefsMap.keys()) {
Services.prefs.addObserver(pref, this, false);
}
@@ -78,9 +102,10 @@ PrefsProvider.prototype = {
/**
* Singleton that serves as the default new tab pref provider for the grid.
*/
const gPrefs = new PrefsProvider();
let NewTabPrefsProvider = {
prefs: gPrefs,
+ newtabPagePrefSet: gNewtabPagePrefs,
};
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/NewTabWebChannel.jsm
@@ -0,0 +1,295 @@
+/* global
+ NewTabPrefsProvider,
+ Services,
+ EventEmitter,
+ Preferences,
+ XPCOMUtils,
+ WebChannel,
+ NewTabRemoteResources
+*/
+/* exported NewTabWebChannel */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["NewTabWebChannel"];
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+ "resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
+ "resource:///modules/NewTabRemoteResources.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
+ "resource://gre/modules/WebChannel.jsm");
+XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
+ const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
+ return EventEmitter;
+});
+
+const CHAN_ID = "newtab";
+const PREF_ENABLED = "browser.newtabpage.remote";
+const PREF_MODE = "browser.newtabpage.remote.mode";
+
+/**
+ * NewTabWebChannel is the conduit for all communication with unprivileged newtab instances.
+ *
+ * It allows for the ability to broadcast to all newtab browsers.
+ * If the browser.newtab.remote pref is false, the object will be in an uninitialized state.
+ *
+ * Mode choices:
+ * 'production': pages from our production CDN
+ * 'staging': pages from our staging CDN
+ * 'test': intended for tests
+ * 'test2': intended for tests
+ * 'dev': intended for development
+ *
+ * An unknown mode will result in 'production' mode, which is the default
+ *
+ * Incoming messages are expected to be JSON-serialized and in the format:
+ *
+ * {
+ * type: "REQUEST_SCREENSHOT",
+ * data: {
+ * url: "https://example.com"
+ * }
+ * }
+ *
+ * Or:
+ *
+ * {
+ * type: "REQUEST_SCREENSHOT",
+ * }
+ *
+ * Outgoing messages are expected to be objects serializable by structured cloning, in a similar format:
+ * {
+ * type: "RECEIVE_SCREENSHOT",
+ * data: {
+ * "url": "https://example.com",
+ * "image": "dataURi:....."
+ * }
+ * }
+ */
+let NewTabWebChannelImpl = function NewTabWebChannelImpl() {
+ EventEmitter.decorate(this);
+ this._handlePrefChange = this._handlePrefChange.bind(this);
+ this._incomingMessage = this._incomingMessage.bind(this);
+};
+
+NewTabWebChannelImpl.prototype = {
+ _prefs: {},
+ _channel: null,
+
+ // a WeakMap containing browsers as keys and a weak ref to their principal
+ // as value
+ _principals: null,
+
+ // a Set containing weak refs to browsers
+ _browsers: null,
+
+ /*
+ * Returns current channel's ID
+ */
+ get chanId() {
+ return CHAN_ID;
+ },
+
+ /*
+ * Returns the number of browsers currently tracking
+ */
+ get numBrowsers() {
+ return this._getBrowserRefs().length;
+ },
+
+ /*
+ * Returns current channel's origin
+ */
+ get origin() {
+ if (!(this._prefs.mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
+ this._prefs.mode = "production";
+ }
+ return NewTabRemoteResources.MODE_CHANNEL_MAP[this._prefs.mode].origin;
+ },
+
+ /*
+ * Unloads all browsers and principals
+ */
+ _unloadAll() {
+ if (this._principals != null) {
+ this._principals = new WeakMap();
+ }
+ this._browsers = new Set();
+ this.emit("targetUnloadAll");
+ },
+
+ /*
+ * Checks if a browser is known
+ *
+ * This will cause an iteration through all known browsers.
+ * That's ok, we don't expect a lot of browsers
+ */
+ _isBrowserKnown(browser) {
+ for (let bRef of this._getBrowserRefs()) {
+ let b = bRef.get();
+ if (b && b.permanentKey === browser.permanentKey) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /*
+ * Obtains all known browser refs
+ */
+ _getBrowserRefs() {
+ let refs = [];
+ for (let bRef of this._browsers) {
+ /*
+ * even though we hold a weak ref to browser, it seems that browser
+ * objects aren't gc'd immediately after a tab closes. They stick around
+ * in memory, but thankfully they don't have a documentURI in that case
+ */
+ let browser = bRef.get();
+ if (browser && browser.documentURI) {
+ refs.push(bRef);
+ } else {
+ // need to clean up principals because the browser object is not gc'ed
+ // immediately
+ this._principals.delete(browser);
+ this._browsers.delete(bRef);
+ this.emit("targetUnload");
+ }
+ }
+ return refs;
+ },
+
+ /*
+ * Receives a message from content.
+ *
+ * Keeps track of browsers for broadcast, relays messages to listeners.
+ */
+ _incomingMessage(id, message, target) {
+ if (this.chanId !== id) {
+ Cu.reportError(new Error("NewTabWebChannel unexpected message destination"));
+ }
+
+ /*
+ * need to differentiate by browser, because event targets are created each
+ * time a message is sent.
+ */
+ if (!this._isBrowserKnown(target.browser)) {
+ this._browsers.add(Cu.getWeakReference(target.browser));
+ this._principals.set(target.browser, Cu.getWeakReference(target.principal));
+ this.emit("targetAdd");
+ }
+
+ try {
+ let msg = JSON.parse(message);
+ this.emit(msg.type, {data: msg.data, target: target});
+ } catch (err) {
+ Cu.reportError(err);
+ }
+ },
+
+ /*
+ * Sends a message to all known browsers
+ */
+ broadcast(actionType, message) {
+ for (let bRef of this._getBrowserRefs()) {
+ let browser = bRef.get();
+ try {
+ let principal = this._principals.get(browser).get();
+ if (principal && browser && browser.documentURI) {
+ this._channel.send({type: actionType, data: message}, {browser, principal});
+ }
+ } catch (e) {
+ Cu.reportError(new Error("NewTabWebChannel WeakRef is dead"));
+ this._principals.delete(browser);
+ }
+ }
+ },
+
+ /*
+ * Sends a message to a specific target
+ */
+ send(actionType, message, target) {
+ try {
+ this._channel.send({type: actionType, data: message}, target);
+ } catch (e) {
+ // Web Channel might be dead
+ Cu.reportError(e);
+ }
+ },
+
+ /*
+ * Pref change observer callback
+ */
+ _handlePrefChange(prefName, newState, forceState) { // eslint-disable-line no-unused-vars
+ switch (prefName) {
+ case PREF_ENABLED:
+ if (!this._prefs.enabled && newState) {
+ // changing state from disabled to enabled
+ this.setupState();
+ } else if (this._prefs.enabled && !newState) {
+ // changing state from enabled to disabled
+ this.tearDownState();
+ }
+ break;
+ case PREF_MODE:
+ if (this._prefs.mode !== newState) {
+ // changing modes
+ this.tearDownState();
+ this.setupState();
+ }
+ break;
+ }
+ },
+
+ /*
+ * Sets up the internal state
+ */
+ setupState() {
+ this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
+
+ let mode = Preferences.get(PREF_MODE, "production");
+ if (!(mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
+ mode = "production";
+ }
+ this._prefs.mode = mode;
+ this._principals = new WeakMap();
+ this._browsers = new Set();
+
+ if (this._prefs.enabled) {
+ this._channel = new WebChannel(this.chanId, Services.io.newURI(this.origin, null, null));
+ this._channel.listen(this._incomingMessage);
+ }
+ },
+
+ tearDownState() {
+ if (this._channel) {
+ this._channel.stopListening();
+ }
+ this._prefs = {};
+ this._unloadAll();
+ this._channel = null;
+ this._principals = null;
+ this._browsers = null;
+ },
+
+ init() {
+ this.setupState();
+ NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handlePrefChange);
+ NewTabPrefsProvider.prefs.on(PREF_MODE, this._handlePrefChange);
+ },
+
+ uninit() {
+ this.tearDownState();
+ NewTabPrefsProvider.prefs.off(PREF_ENABLED, this._handlePrefChange);
+ NewTabPrefsProvider.prefs.off(PREF_MODE, this._handlePrefChange);
+ }
+};
+
+let NewTabWebChannel = new NewTabWebChannelImpl();
--- a/browser/components/newtab/moz.build
+++ b/browser/components/newtab/moz.build
@@ -6,19 +6,21 @@
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
XPCSHELL_TESTS_MANIFESTS += [
'tests/xpcshell/xpcshell.ini',
]
EXTRA_JS_MODULES += [
+ 'NewTabMessages.jsm',
'NewTabPrefsProvider.jsm',
'NewTabRemoteResources.jsm',
'NewTabURL.jsm',
+ 'NewTabWebChannel.jsm',
'PlacesProvider.jsm'
]
XPIDL_SOURCES += [
'nsIAboutNewTabService.idl',
]
XPIDL_MODULE = 'browser-newtab'
--- a/browser/components/newtab/tests/browser/browser.ini
+++ b/browser/components/newtab/tests/browser/browser.ini
@@ -1,6 +1,10 @@
[DEFAULT]
support-files =
dummy_page.html
+ newtabwebchannel_basic.html
+ newtabmessages_prefs.html
[browser_remotenewtab_pageloads.js]
[browser_newtab_overrides.js]
+[browser_newtabmessages.js]
+[browser_newtabwebchannel.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_newtabmessages.js
@@ -0,0 +1,57 @@
+/* globals Cu, XPCOMUtils, Preferences, is, registerCleanupFunction, NewTabWebChannel */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
+ "resource:///modules/NewTabWebChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
+ "resource:///modules/NewTabMessages.jsm");
+
+function setup() {
+ Preferences.set("browser.newtabpage.enhanced", true);
+ Preferences.set("browser.newtabpage.remote.mode", "test");
+ Preferences.set("browser.newtabpage.remote", true);
+ NewTabMessages.init();
+}
+
+function cleanup() {
+ NewTabMessages.uninit();
+ NewTabWebChannel.tearDownState();
+ Preferences.set("browser.newtabpage.remote", false);
+ Preferences.set("browser.newtabpage.remote.mode", "production");
+}
+registerCleanupFunction(cleanup);
+
+/*
+ * Sanity tests for pref messages
+ */
+add_task(function* prefMessages_request() {
+ setup();
+ let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_prefs.html";
+
+ let tabOptions = {
+ gBrowser,
+ url: testURL
+ };
+
+ let prefResponseAck = new Promise(resolve => {
+ NewTabWebChannel.once("responseAck", () => {
+ ok(true, "a request response has been received");
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab(tabOptions, function*() {
+ yield prefResponseAck;
+ let prefChangeAck = new Promise(resolve => {
+ NewTabWebChannel.once("responseAck", () => {
+ ok(true, "a change response has been received");
+ resolve();
+ });
+ });
+ Preferences.set("browser.newtabpage.enhanced", false);
+ yield prefChangeAck;
+ });
+ cleanup();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_newtabwebchannel.js
@@ -0,0 +1,232 @@
+/* globals XPCOMUtils, Cu, Preferences, NewTabWebChannel, is, registerCleanupFunction */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
+ "resource:///modules/NewTabWebChannel.jsm");
+
+const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
+const TEST_URL_2 = "http://mochi.test:8888/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
+
+function cleanup() {
+ NewTabWebChannel.tearDownState();
+ Preferences.set("browser.newtabpage.remote", false);
+ Preferences.set("browser.newtabpage.remote.mode", "production");
+}
+registerCleanupFunction(cleanup);
+
+/*
+ * Tests flow of messages from newtab to chrome and chrome to newtab
+ */
+add_task(function* open_webchannel_basic() {
+ Preferences.set("browser.newtabpage.remote.mode", "test");
+ Preferences.set("browser.newtabpage.remote", true);
+
+ let tabOptions = {
+ gBrowser,
+ url: TEST_URL
+ };
+
+ let messagePromise = new Promise(resolve => {
+ NewTabWebChannel.once("foo", function(name, msg) {
+ is(name, "foo", "Correct message type sent: foo");
+ is(msg.data, "bar", "Correct data sent: bar");
+ resolve(msg.target);
+ });
+ });
+
+ let replyPromise = new Promise(resolve => {
+ NewTabWebChannel.once("reply", function(name, msg) {
+ is(name, "reply", "Correct message type sent: reply");
+ is(msg.data, "quuz", "Correct data sent: quuz");
+ resolve(msg.target);
+ });
+ });
+
+ let unloadPromise = new Promise(resolve => {
+ NewTabWebChannel.once("targetUnload", function(name) {
+ is(name, "targetUnload", "Correct message type sent: targetUnload");
+ resolve();
+ });
+ });
+
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
+ let target = yield messagePromise;
+ is(NewTabWebChannel.numBrowsers, 1, "One target expected");
+ is(target.browser, browser, "Same browser");
+ NewTabWebChannel.send("respond", null, target);
+ yield replyPromise;
+ });
+
+ Cu.forceGC();
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield unloadPromise;
+ cleanup();
+});
+
+/*
+ * Tests message broadcast reaches all open newtab pages
+ */
+add_task(function* webchannel_broadcast() {
+ Preferences.set("browser.newtabpage.remote.mode", "test");
+ Preferences.set("browser.newtabpage.remote", true);
+
+ let countingMessagePromise = new Promise(resolve => {
+ let count = 0;
+ NewTabWebChannel.on("foo", function test_message(name, msg) {
+ count += 1;
+ if (count === 2) {
+ NewTabWebChannel.off("foo", test_message);
+ resolve(msg.target);
+ }
+ }.bind(this));
+ });
+
+ let countingReplyPromise = new Promise(resolve => {
+ let count = 0;
+ NewTabWebChannel.on("reply", function test_message(name, msg) {
+ count += 1;
+ if (count === 2) {
+ NewTabWebChannel.off("reply", test_message);
+ resolve(msg.target);
+ }
+ }.bind(this));
+ });
+
+ let countingUnloadPromise = new Promise(resolve => {
+ let count = 0;
+ NewTabWebChannel.on("targetUnload", function test_message() {
+ count += 1;
+ if (count === 2) {
+ NewTabWebChannel.off("targetUnload", test_message);
+ resolve();
+ }
+ });
+ });
+
+ let tabs = [];
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
+ tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
+
+ yield countingMessagePromise;
+ is(NewTabWebChannel.numBrowsers, 2, "Two targets expected");
+
+ NewTabWebChannel.broadcast("respond", null);
+ yield countingReplyPromise;
+
+ for (let tab of tabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ Cu.forceGC();
+
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield countingUnloadPromise;
+ cleanup();
+});
+
+/*
+ * Tests switching modes
+ */
+add_task(function* webchannel_switch() {
+ Preferences.set("browser.newtabpage.remote.mode", "test");
+ Preferences.set("browser.newtabpage.remote", true);
+
+ function newMessagePromise() {
+ 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));
+ });
+
+ let unloadPromise = new Promise(resolve => {
+ NewTabWebChannel.once("targetUnload", function() {
+ resolve();
+ });
+ });
+
+ let unloadAllPromise = new Promise(resolve => {
+ NewTabWebChannel.once("targetUnloadAll", function() {
+ resolve();
+ });
+ });
+
+ let tabs = [];
+ let messagePromise;
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+
+ messagePromise = newMessagePromise();
+ tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
+ yield messagePromise;
+ is(NewTabWebChannel.numBrowsers, 1, "Correct number of targets");
+
+ 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;
+ is(replyCount, 1, "only current channel is listened to for replies");
+
+ for (let tab of tabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+
+ Cu.forceGC();
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield unloadPromise;
+ cleanup();
+});
+
+add_task(function* open_webchannel_reload() {
+ Preferences.set("browser.newtabpage.remote.mode", "test");
+ Preferences.set("browser.newtabpage.remote", true);
+
+ let tabOptions = {
+ gBrowser,
+ url: TEST_URL
+ };
+
+ let messagePromise = new Promise(resolve => {
+ NewTabWebChannel.once("foo", function(name, msg) {
+ is(name, "foo", "Correct message type sent: foo");
+ is(msg.data, "bar", "Correct data sent: bar");
+ resolve(msg.target);
+ });
+ });
+ let unloadPromise = new Promise(resolve => {
+ NewTabWebChannel.once("targetUnload", function() {
+ resolve();
+ });
+ });
+
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
+ let target = yield messagePromise;
+ is(NewTabWebChannel.numBrowsers, 1, "One target expected");
+ is(target.browser, browser, "Same browser");
+
+ browser.contentWindow.location.reload();
+ });
+
+ Cu.forceGC();
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield unloadPromise;
+ cleanup();
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/newtabmessages_prefs.html
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <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: {
+ id: "newtab",
+ message: JSON.stringify({type: "responseAck"}),
+ }
+ });
+ window.dispatchEvent(reply);
+ }
+ }, true);
+
+ document.onreadystatechange = function () {
+ let msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_PREFS"}),
+ }
+ });
+ window.dispatchEvent(msg);
+ };
+
+ </script>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/newtabwebchannel_basic.html
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Newtab WebChannel test</title>
+ </head>
+ <body>
+ <script>
+ document.onreadystatechange = function () {
+ let msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ 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") {
+ let reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "reply", data: "quuz"}),
+ }
+ });
+ window.dispatchEvent(reply);
+ }
+ }, true);
+
+ </script>
+ </body>
+</html>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -23,16 +23,22 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource:///modules/DirectoryLinksProvider.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
"resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
+ "resource:///modules/NewTabWebChannel.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
+ "resource:///modules/NewTabMessages.jsm");
+
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
"resource:///modules/UITour.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentClick",
"resource:///modules/ContentClick.jsm");
@@ -748,16 +754,18 @@ BrowserGlue.prototype = {
AboutHome.init();
DirectoryLinksProvider.init();
NewTabUtils.init();
NewTabUtils.links.addProvider(DirectoryLinksProvider);
AboutNewTab.init();
NewTabPrefsProvider.prefs.init();
+ NewTabWebChannel.init();
+ NewTabMessages.init();
SessionStore.init();
BrowserUITelemetry.init();
ContentSearch.init();
FormValidationHandler.init();
ContentClick.init();
RemotePrompt.init();
@@ -1056,16 +1064,19 @@ BrowserGlue.prototype = {
Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
}
SelfSupportBackend.uninit();
WebappManager.uninit();
NewTabPrefsProvider.prefs.uninit();
+ NewTabWebChannel.uninit();
+ NewTabMessages.uninit();
+
AboutNewTab.uninit();
webrtcUI.uninit();
FormValidationHandler.uninit();
if (AppConstants.NIGHTLY_BUILD) {
AddonWatcher.uninit();
}
},