diff --git a/browser/components/newtab/NewTabMessages.jsm b/browser/components/newtab/NewTabMessages.jsm new file mode 100644 index 000000000000..560d559b7a31 --- /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); + } + } + } +}; diff --git a/browser/components/newtab/NewTabPrefsProvider.jsm b/browser/components/newtab/NewTabPrefsProvider.jsm index 5c4c12e27554..c6bf220791c7 100644 --- a/browser/components/newtab/NewTabPrefsProvider.jsm +++ b/browser/components/newtab/NewTabPrefsProvider.jsm @@ -21,11 +21,24 @@ const gPrefsMap = new Map([ ["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); }; @@ -59,6 +72,17 @@ PrefsProvider.prototype = { } }, + /* + * 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; }, @@ -83,4 +107,5 @@ const gPrefs = new PrefsProvider(); let NewTabPrefsProvider = { prefs: gPrefs, + newtabPagePrefSet: gNewtabPagePrefs, }; diff --git a/browser/components/newtab/NewTabWebChannel.jsm b/browser/components/newtab/NewTabWebChannel.jsm new file mode 100644 index 000000000000..37a50282572c --- /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(); diff --git a/browser/components/newtab/moz.build b/browser/components/newtab/moz.build index 5a1c4008b643..501bd9d4da58 100644 --- a/browser/components/newtab/moz.build +++ b/browser/components/newtab/moz.build @@ -11,9 +11,11 @@ XPCSHELL_TESTS_MANIFESTS += [ ] EXTRA_JS_MODULES += [ + 'NewTabMessages.jsm', 'NewTabPrefsProvider.jsm', 'NewTabRemoteResources.jsm', 'NewTabURL.jsm', + 'NewTabWebChannel.jsm', 'PlacesProvider.jsm' ] diff --git a/browser/components/newtab/tests/browser/browser.ini b/browser/components/newtab/tests/browser/browser.ini index 637f1a46d32a..67d6d3a8a27c 100644 --- 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] diff --git a/browser/components/newtab/tests/browser/browser_newtabmessages.js b/browser/components/newtab/tests/browser/browser_newtabmessages.js new file mode 100644 index 000000000000..52d33aebb4aa --- /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(); +}); diff --git a/browser/components/newtab/tests/browser/browser_newtabwebchannel.js b/browser/components/newtab/tests/browser/browser_newtabwebchannel.js new file mode 100644 index 000000000000..94fee6eea35e --- /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(); +}); diff --git a/browser/components/newtab/tests/browser/newtabmessages_prefs.html b/browser/components/newtab/tests/browser/newtabmessages_prefs.html new file mode 100644 index 000000000000..f2c0ba4229aa --- /dev/null +++ b/browser/components/newtab/tests/browser/newtabmessages_prefs.html @@ -0,0 +1,32 @@ + + + + Newtab WebChannel test + + + + + diff --git a/browser/components/newtab/tests/browser/newtabwebchannel_basic.html b/browser/components/newtab/tests/browser/newtabwebchannel_basic.html new file mode 100644 index 000000000000..84bdbeb12de2 --- /dev/null +++ b/browser/components/newtab/tests/browser/newtabwebchannel_basic.html @@ -0,0 +1,32 @@ + + + + Newtab WebChannel test + + + + + diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index d684e07db1b3..253eb3f8f509 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -28,6 +28,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils", 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"); @@ -749,6 +755,8 @@ BrowserGlue.prototype = { AboutNewTab.init(); NewTabPrefsProvider.prefs.init(); + NewTabWebChannel.init(); + NewTabMessages.init(); SessionStore.init(); BrowserUITelemetry.init(); @@ -1054,6 +1062,9 @@ BrowserGlue.prototype = { SelfSupportBackend.uninit(); NewTabPrefsProvider.prefs.uninit(); + NewTabWebChannel.uninit(); + NewTabMessages.uninit(); + AboutNewTab.uninit(); webrtcUI.uninit(); FormValidationHandler.uninit();