Skip to content
Permalink
Browse files

Merge pull request #2448 from k88hudson/bug1350411-backports

chore(mc): Backport changes from Bug 1350411
  • Loading branch information...
k88hudson committed Apr 20, 2017
2 parents 5825948 + 9ea3181 commit 585c01acebc5595419954791957c9486e3de889d
@@ -7,7 +7,6 @@

const {utils: Cu} = Components;

Cu.import("resource://gre/modules/RemotePageManager.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

const {
@@ -19,16 +18,38 @@ const {
XPCOMUtils.defineLazyModuleGetter(this, "AboutNewTab",
"resource:///modules/AboutNewTab.jsm");

XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
"resource://gre/modules/RemotePageManager.jsm");

const ABOUT_NEW_TAB_URL = "about:newtab";

const DEFAULT_OPTIONS = {
dispatch(action) { dump(`\nMessage manager: Received action ${action.type}, but no dispatcher was defined.\n`); },
dispatch(action) {
throw new Error(`\nMessageChannel: Received action ${action.type}, but no dispatcher was defined.\n`);
},
pageURL: ABOUT_NEW_TAB_URL,
outgoingMessageName: "ActivityStream:MainToContent",
incomingMessageName: "ActivityStream:ContentToMain"
};

class MessageManager {
class ActivityStreamMessageChannel {

/**
* ActivityStreamMessageChannel - This module connects a Redux store to a RemotePageManager in Firefox.
* Call .createChannel to start the connection, and .destroyChannel to destroy it.
* You should use the BroadcastToContent, SendToContent, and SendToMain action creators
* in common/Actions.jsm to help you create actions that will be automatically routed
* to the correct location.
*
* @param {object} options
* @param {function} options.dispatch The dispatch method from a Redux store
* @param {string} options.pageURL The URL to which a RemotePageManager should be attached.
* Note that if it is about:newtab, the existing RemotePageManager
* for about:newtab will also be disabled
* @param {string} options.outgoingMessageName The name of the message sent to child processes
* @param {string} options.incomingMessageName The name of the message received from child processes
* @return {ActivityStreamMessageChannel}
*/
constructor(options = {}) {
Object.assign(this, DEFAULT_OPTIONS, options);
this.channel = null;
@@ -39,6 +60,13 @@ class MessageManager {
this.onNewTabUnload = this.onNewTabUnload.bind(this);
}

/**
* middleware - Redux middleware that looks for SendToContent and BroadcastToContent type
* actions, and sends them out.
*
* @param {object} store A redux store
* @return {function} Redux middleware
*/
middleware(store) {
return next => action => {
if (!this.channel) {
@@ -54,19 +82,35 @@ class MessageManager {
};
}

/**
* onActionFromContent - Handler for actions from a content processes
*
* @param {object} action A Redux action
* @param {string} targetId The portID of the port that sent the message
*/
onActionFromContent(action, targetId) {
this.dispatch(ac.SendToMain(action, {fromTarget: targetId}));
}

/**
* broadcast - Sends an action to all ports
*
* @param {object} action A Redux action
*/
broadcast(action) {
this.channel.sendAsyncMessage(this.outgoingMessageName, action);
}

/**
* send - Sends an action to a specific port
*
* @param {obj} action A redux action; it should contain a portID in the meta.toTarget property
*/
send(action) {
const targetId = action.meta && action.meta.toTarget;
const target = this.getTargetById(targetId);
if (!target) {
Cu.reportError(new Error(`Tried to send a message to a target (${targetId}) that was no longer around.`));
// The target is no longer around - maybe the user closed the page
return;
}
target.sendAsyncMessage(this.outgoingMessageName, action);
@@ -104,8 +148,6 @@ class MessageManager {

/**
* destroyChannel - Destroys the RemotePages channel
*
* @return {type} description
*/
destroyChannel() {
this.channel.destroy();
@@ -156,6 +198,6 @@ class MessageManager {
}
}

this.MessageManager = MessageManager;
this.ActivityStreamMessageChannel = ActivityStreamMessageChannel;
this.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
this.EXPORTED_SYMBOLS = ["MessageManager", "DEFAULT_OPTIONS"];
this.EXPORTED_SYMBOLS = ["ActivityStreamMessageChannel", "DEFAULT_OPTIONS"];
@@ -14,7 +14,6 @@ this.NewTabInit = class NewTabInit {
onAction(action) {
let newAction;
switch (action.type) {
// TODO: Replace with sending a copy of the state when a NEW_TAB_LOAD action is received
case at.NEW_TAB_LOAD:
newAction = {type: at.NEW_TAB_INITIAL_STATE, data: this.store.getState()};
this.store.dispatch(ac.SendToContent(newAction, action.meta.fromTarget));
@@ -8,15 +8,15 @@ const {utils: Cu} = Components;

const {redux} = Cu.import("resource://activity-stream/vendor/Redux.jsm", {});
const {reducers} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
const {MessageManager} = Cu.import("resource://activity-stream/lib/MessageManager.jsm", {});
const {ActivityStreamMessageChannel} = Cu.import("resource://activity-stream/lib/ActivityStreamMessageChannel.jsm", {});

const PREF_PREFIX = "browser.newtabpage.activity-stream.";
Cu.import("resource://gre/modules/Preferences.jsm");

/**
* Store - This has a similar structure to a redux store, but includes some
* extra functionality to allow for routing of actions between the Main
* processes and child processes via a MessageManager.
* Store - This has a similar structure to a redux store, but includes some extra
* functionality to allow for routing of actions between the Main processes
* and child processes via a ActivityStreamMessageChannel.
* It also accepts an array of "Feeds" on inititalization, which
* can listen for any action that is dispatched through the store.
*/
@@ -38,10 +38,10 @@ this.Store = class Store {
this.feeds = new Map();
this._feedFactories = null;
this._prefHandlers = new Map();
this._mm = new MessageManager({dispatch: this.dispatch});
this._messageChannel = new ActivityStreamMessageChannel({dispatch: this.dispatch});
this._store = redux.createStore(
redux.combineReducers(reducers),
redux.applyMiddleware(this._middleware, this._mm.middleware)
redux.applyMiddleware(this._middleware, this._messageChannel.middleware)
);
}

@@ -116,7 +116,7 @@ this.Store = class Store {
}

/**
* init - Initializes the MessageManager channel, and adds feeds.
* init - Initializes the ActivityStreamMessageChannel channel, and adds feeds.
*
* @param {array} feeds An array of objects with an optional .onAction method
*/
@@ -127,7 +127,7 @@ this.Store = class Store {
this.maybeStartFeedAndListenForPrefChanges(name);
}
}
this._mm.createChannel();
this._messageChannel.createChannel();
}

/**
@@ -142,7 +142,7 @@ this.Store = class Store {
this._prefHandlers.clear();
this._feedFactories = null;
this.feeds.clear();
this._mm.destroyChannel();
this._messageChannel.destroyChannel();
}
};

@@ -1,11 +1,11 @@
const {MessageManager, DEFAULT_OPTIONS} = require("lib/MessageManager.jsm");
const {ActivityStreamMessageChannel, DEFAULT_OPTIONS} = require("lib/ActivityStreamMessageChannel.jsm");
const {addNumberReducer, GlobalOverrider} = require("test/unit/utils");
const {createStore, applyMiddleware} = require("redux");
const {actionTypes: at, actionCreators: ac} = require("common/Actions.jsm");

const OPTIONS = ["pageURL, outgoingMessageName", "incomingMessageName", "dispatch"];

describe("MessageManager", () => {
describe("ActivityStreamMessageChannel", () => {
let globals;
let dispatch;
let mm;
@@ -26,28 +26,27 @@ describe("MessageManager", () => {
dispatch = globals.sandbox.spy();
});
beforeEach(() => {
mm = new MessageManager({dispatch});
mm = new ActivityStreamMessageChannel({dispatch});
});

afterEach(() => globals.reset());
after(() => globals.restore());

it("should exist", () => {
assert.ok(MessageManager);
assert.ok(ActivityStreamMessageChannel);
});
it("should apply default options", () => {
mm = new MessageManager();
mm = new ActivityStreamMessageChannel();
OPTIONS.forEach(o => assert.equal(mm[o], DEFAULT_OPTIONS[o], o));
});
it("should add options", () => {
const options = {dispatch: () => {}, pageURL: "FOO.html", outgoingMessageName: "OUT", incomingMessageName: "IN"};
mm = new MessageManager(options);
mm = new ActivityStreamMessageChannel(options);
OPTIONS.forEach(o => assert.equal(mm[o], options[o], o));
});
it("should log a message if no dispatcher was provided", () => {
mm = new MessageManager();
mm.dispatch({type: "FOO"});
assert.calledOnce(global.dump);
it("should throw an error if no dispatcher was provided", () => {
mm = new ActivityStreamMessageChannel();
assert.throws(() => mm.dispatch({type: "FOO"}));
});
describe("Creating/destroying the channel", () => {
describe("#createChannel", () => {
@@ -69,7 +68,7 @@ describe("MessageManager", () => {
assert.calledOnce(global.AboutNewTab.override);
});
it("should not override AboutNewTab if the pageURL is not about:newtab", () => {
mm = new MessageManager({pageURL: "foo.html"});
mm = new ActivityStreamMessageChannel({pageURL: "foo.html"});
mm.createChannel();
assert.notCalled(global.AboutNewTab.override);
});
@@ -93,7 +92,7 @@ describe("MessageManager", () => {
assert.calledOnce(global.AboutNewTab.reset);
});
it("should not reset AboutNewTab if the pageURL is not about:newtab", () => {
mm = new MessageManager({pageURL: "foo.html"});
mm = new ActivityStreamMessageChannel({pageURL: "foo.html"});
mm.createChannel();
mm.destroyChannel();
assert.notCalled(global.AboutNewTab.reset);
@@ -163,12 +162,12 @@ describe("MessageManager", () => {
mm.send(action, "foo");
assert.calledWith(t.sendAsyncMessage, DEFAULT_OPTIONS.outgoingMessageName, action);
});
it("should report an error if the target isn't arround", () => {
it("should not throw if the target isn't around", () => {
mm.createChannel();
// port is not added to the channel
const action = ac.SendToContent({type: "HELLO"}, "foo");
mm.send(action, "foo");
assert.calledOnce(global.Components.utils.reportError);

assert.doesNotThrow(() => mm.send(action, "foo"));
});
});
describe("#broadcast", () => {
@@ -16,13 +16,13 @@ describe("Store", () => {
Preferences.observe = sandbox.spy();
Preferences.ignore = sandbox.spy();
globals.set("Preferences", Preferences);
function MessageManager(options) {
function ActivityStreamMessageChannel(options) {
this.dispatch = options.dispatch;
this.createChannel = sandbox.spy();
this.destroyChannel = sandbox.spy();
this.middleware = sandbox.spy(s => next => action => next(action));
}
({Store, PREF_PREFIX} = injector({"lib/MessageManager.jsm": {MessageManager}}));
({Store, PREF_PREFIX} = injector({"lib/ActivityStreamMessageChannel.jsm": {ActivityStreamMessageChannel}}));
store = new Store();
});
afterEach(() => {
@@ -38,13 +38,13 @@ describe("Store", () => {
assert.property(store, "dispatch");
assert.property(store, "getState");
});
it("should create a MessageManager with the right dispatcher", () => {
assert.ok(store._mm);
assert.equal(store._mm.dispatch, store.dispatch);
it("should create a ActivityStreamMessageChannel with the right dispatcher", () => {
assert.ok(store._messageChannel);
assert.equal(store._messageChannel.dispatch, store.dispatch);
});
it("should connect the MessageManager's middleware", () => {
it("should connect the ActivityStreamMessageChannel's middleware", () => {
store.dispatch({type: "FOO"});
assert.calledOnce(store._mm.middleware);
assert.calledOnce(store._messageChannel.middleware);
});
describe("#initFeed", () => {
it("should add an instance of the feed to .feeds", () => {
@@ -144,9 +144,9 @@ describe("Store", () => {
assert.calledWith(store.maybeStartFeedAndListenForPrefChanges, "foo");
assert.calledWith(store.maybeStartFeedAndListenForPrefChanges, "bar");
});
it("should initialize the MessageManager channel", () => {
it("should initialize the ActivityStreamMessageChannel channel", () => {
store.init();
assert.calledOnce(store._mm.createChannel);
assert.calledOnce(store._messageChannel.createChannel);
});
});
describe("#uninit", () => {
@@ -163,9 +163,9 @@ describe("Store", () => {
assert.equal(store._prefHandlers.size, 0);
assert.isNull(store._feedFactories);
});
it("should destroy the MessageManager channel", () => {
it("should destroy the ActivityStreamMessageChannel channel", () => {
store.uninit();
assert.calledOnce(store._mm.destroyChannel);
assert.calledOnce(store._messageChannel.destroyChannel);
});
});
describe("#getState", () => {

0 comments on commit 585c01a

Please sign in to comment.
You can’t perform that action at this time.