Bug 1239118 - Send prefs to Remote Newtab page using WebChannel r=ursula

MozReview-Commit-ID: CQQQmgrXSDt

--HG--
extra : rebase_source : fafebd3deaaff3a6afb1b211c58bd5fed023efc5
This commit is contained in:
Olivier Yiptong 2016-03-03 22:20:23 -05:00
Родитель 9f92cc4921
Коммит fb47188c38
10 изменённых файлов: 785 добавлений и 0 удалений

Просмотреть файл

@ -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);
}
}
}
};

Просмотреть файл

@ -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,
};

Просмотреть файл

@ -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();

Просмотреть файл

@ -11,9 +11,11 @@ XPCSHELL_TESTS_MANIFESTS += [
]
EXTRA_JS_MODULES += [
'NewTabMessages.jsm',
'NewTabPrefsProvider.jsm',
'NewTabRemoteResources.jsm',
'NewTabURL.jsm',
'NewTabWebChannel.jsm',
'PlacesProvider.jsm'
]

Просмотреть файл

@ -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]

Просмотреть файл

@ -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();
});

Просмотреть файл

@ -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();
});

Просмотреть файл

@ -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>

Просмотреть файл

@ -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>

Просмотреть файл

@ -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();