forked from mirrors/gecko-dev
Before this patch, the about:home/about:newtab Redux store code had some middleware that queued any messages sent from the page before the parent had sent any messages. Presumably this was so that those messages wouldn't be dropped if they were sent while the parent process was still setting up its Feeds. Unfortunately, there's a race here - if the parent process _is_ ready and just chooses not to send any messages right away, the loaded about:home/about:newtab document will just hold on to any actions until the parent process has sent something down to it. The Talos test that was failing here was waiting for the initial about:home page to send a message which would record a Telemetry probe. That message wasn't arriving in time. Presumably, _eventually_ the parent process would have sent a message down to the about:home page which would flush the actions, but the Talos test would time out before that would occur. This patch changes things by having the _parent_ process queue any messages sent from the content in the event that the ActivityStreamMessageChannel is not yet set up. Once it is set up, those messages are dispatched after the simulated NEW_TAB_INIT and NEW_TAB_LOAD for those early tabs are sent to the parent process Redux store. Differential Revision: https://phabricator.services.mozilla.com/D195179
155 lines
5.3 KiB
JavaScript
155 lines
5.3 KiB
JavaScript
import {
|
|
actionCreators as ac,
|
|
actionTypes as at,
|
|
} from "common/Actions.sys.mjs";
|
|
import { addNumberReducer, GlobalOverrider } from "test/unit/utils";
|
|
import {
|
|
INCOMING_MESSAGE_NAME,
|
|
initStore,
|
|
MERGE_STORE_ACTION,
|
|
OUTGOING_MESSAGE_NAME,
|
|
rehydrationMiddleware,
|
|
} from "content-src/lib/init-store";
|
|
|
|
describe("initStore", () => {
|
|
let globals;
|
|
let store;
|
|
beforeEach(() => {
|
|
globals = new GlobalOverrider();
|
|
globals.set("RPMSendAsyncMessage", globals.sandbox.spy());
|
|
globals.set("RPMAddMessageListener", globals.sandbox.spy());
|
|
store = initStore({ number: addNumberReducer });
|
|
});
|
|
afterEach(() => globals.restore());
|
|
it("should create a store with the provided reducers", () => {
|
|
assert.ok(store);
|
|
assert.property(store.getState(), "number");
|
|
});
|
|
it("should add a listener that dispatches actions", () => {
|
|
assert.calledWith(global.RPMAddMessageListener, INCOMING_MESSAGE_NAME);
|
|
const [, listener] = global.RPMAddMessageListener.firstCall.args;
|
|
globals.sandbox.spy(store, "dispatch");
|
|
const message = { name: INCOMING_MESSAGE_NAME, data: { type: "FOO" } };
|
|
|
|
listener(message);
|
|
|
|
assert.calledWith(store.dispatch, message.data);
|
|
});
|
|
it("should not throw if RPMAddMessageListener is not defined", () => {
|
|
// Note: this is being set/restored by GlobalOverrider
|
|
delete global.RPMAddMessageListener;
|
|
|
|
assert.doesNotThrow(() => initStore({ number: addNumberReducer }));
|
|
});
|
|
it("should log errors from failed messages", () => {
|
|
const [, callback] = global.RPMAddMessageListener.firstCall.args;
|
|
globals.sandbox.stub(global.console, "error");
|
|
globals.sandbox.stub(store, "dispatch").throws(Error("failed"));
|
|
|
|
const message = {
|
|
name: INCOMING_MESSAGE_NAME,
|
|
data: { type: MERGE_STORE_ACTION },
|
|
};
|
|
callback(message);
|
|
|
|
assert.calledOnce(global.console.error);
|
|
});
|
|
it("should replace the state if a MERGE_STORE_ACTION is dispatched", () => {
|
|
store.dispatch({ type: MERGE_STORE_ACTION, data: { number: 42 } });
|
|
assert.deepEqual(store.getState(), { number: 42 });
|
|
});
|
|
it("should call .send and update the local store if an AlsoToMain action is dispatched", () => {
|
|
const subscriber = sinon.spy();
|
|
const action = ac.AlsoToMain({ type: "FOO" });
|
|
|
|
store.subscribe(subscriber);
|
|
store.dispatch(action);
|
|
|
|
assert.calledWith(
|
|
global.RPMSendAsyncMessage,
|
|
OUTGOING_MESSAGE_NAME,
|
|
action
|
|
);
|
|
assert.calledOnce(subscriber);
|
|
});
|
|
it("should call .send but not update the local store if an OnlyToMain action is dispatched", () => {
|
|
const subscriber = sinon.spy();
|
|
const action = ac.OnlyToMain({ type: "FOO" });
|
|
|
|
store.subscribe(subscriber);
|
|
store.dispatch(action);
|
|
|
|
assert.calledWith(
|
|
global.RPMSendAsyncMessage,
|
|
OUTGOING_MESSAGE_NAME,
|
|
action
|
|
);
|
|
assert.notCalled(subscriber);
|
|
});
|
|
it("should not send out other types of actions", () => {
|
|
store.dispatch({ type: "FOO" });
|
|
assert.notCalled(global.RPMSendAsyncMessage);
|
|
});
|
|
describe("rehydrationMiddleware", () => {
|
|
it("should allow NEW_TAB_STATE_REQUEST to go through", () => {
|
|
const action = ac.AlsoToMain({ type: at.NEW_TAB_STATE_REQUEST });
|
|
const next = sinon.spy();
|
|
rehydrationMiddleware(store)(next)(action);
|
|
assert.calledWith(next, action);
|
|
});
|
|
it("should dispatch an additional NEW_TAB_STATE_REQUEST if INIT was received after a request", () => {
|
|
const requestAction = ac.AlsoToMain({ type: at.NEW_TAB_STATE_REQUEST });
|
|
const next = sinon.spy();
|
|
const dispatch = rehydrationMiddleware(store)(next);
|
|
|
|
dispatch(requestAction);
|
|
next.resetHistory();
|
|
dispatch({ type: at.INIT });
|
|
|
|
assert.calledWith(next, requestAction);
|
|
});
|
|
it("should allow MERGE_STORE_ACTION to go through", () => {
|
|
const action = { type: MERGE_STORE_ACTION };
|
|
const next = sinon.spy();
|
|
rehydrationMiddleware(store)(next)(action);
|
|
assert.calledWith(next, action);
|
|
});
|
|
it("should not allow actions from main to go through before MERGE_STORE_ACTION was received", () => {
|
|
const next = sinon.spy();
|
|
const dispatch = rehydrationMiddleware(store)(next);
|
|
|
|
dispatch(ac.BroadcastToContent({ type: "FOO" }));
|
|
dispatch(ac.AlsoToOneContent({ type: "FOO" }, 123));
|
|
|
|
assert.notCalled(next);
|
|
});
|
|
it("should allow all local actions to go through", () => {
|
|
const action = { type: "FOO" };
|
|
const next = sinon.spy();
|
|
rehydrationMiddleware(store)(next)(action);
|
|
assert.calledWith(next, action);
|
|
});
|
|
it("should allow actions from main to go through after MERGE_STORE_ACTION has been received", () => {
|
|
const next = sinon.spy();
|
|
const dispatch = rehydrationMiddleware(store)(next);
|
|
|
|
dispatch({ type: MERGE_STORE_ACTION });
|
|
next.resetHistory();
|
|
|
|
const action = ac.AlsoToOneContent({ type: "FOO" }, 123);
|
|
dispatch(action);
|
|
assert.calledWith(next, action);
|
|
});
|
|
it("should not let startup actions go through for the preloaded about:home document", () => {
|
|
globals.set("__FROM_STARTUP_CACHE__", true);
|
|
const next = sinon.spy();
|
|
const dispatch = rehydrationMiddleware(store)(next);
|
|
const action = ac.BroadcastToContent(
|
|
{ type: "FOO", meta: { isStartup: true } },
|
|
123
|
|
);
|
|
dispatch(action);
|
|
assert.notCalled(next);
|
|
});
|
|
});
|
|
});
|