mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
ecab54a7c9
MozReview-Commit-ID: 7E7LPorrEje --HG-- extra : rebase_source : 0572a35415a766a3f31d266760ecd07f0dcc3f72
296 lines
7.8 KiB
JavaScript
296 lines
7.8 KiB
JavaScript
/* 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://devtools/shared/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});
|
|
} 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();
|