tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ActivityStreamMessageChannel.test.js (15102B)


      1 import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
      2 import {
      3  ActivityStreamMessageChannel,
      4  DEFAULT_OPTIONS,
      5 } from "lib/ActivityStreamMessageChannel.sys.mjs";
      6 import { addNumberReducer, GlobalOverrider } from "test/unit/utils";
      7 import { applyMiddleware, createStore } from "redux";
      8 
      9 const OPTIONS = [
     10  "pageURL, outgoingMessageName",
     11  "incomingMessageName",
     12  "dispatch",
     13 ];
     14 
     15 // Create an object containing details about a tab as expected within
     16 // the loaded tabs map in ActivityStreamMessageChannel.sys.mjs.
     17 function getTabDetails(portID, url = "about:newtab", extraArgs = {}) {
     18  let actor = {
     19    portID,
     20    sendAsyncMessage: sinon.spy(),
     21  };
     22  let browser = {
     23    getAttribute: () => (extraArgs.preloaded ? "preloaded" : ""),
     24    ownerGlobal: {},
     25  };
     26  let browsingContext = {
     27    top: {
     28      embedderElement: browser,
     29    },
     30  };
     31 
     32  let data = {
     33    data: {
     34      actor,
     35      browser,
     36      browsingContext,
     37      portID,
     38      url,
     39    },
     40    target: {
     41      browsingContext,
     42    },
     43  };
     44 
     45  if (extraArgs.loaded) {
     46    data.data.loaded = extraArgs.loaded;
     47  }
     48  if (extraArgs.simulated) {
     49    data.data.simulated = extraArgs.simulated;
     50  }
     51 
     52  return data;
     53 }
     54 
     55 describe("ActivityStreamMessageChannel", () => {
     56  let globals;
     57  let dispatch;
     58  let mm;
     59  beforeEach(() => {
     60    globals = new GlobalOverrider();
     61    globals.set("AboutNewTab", {
     62      reset: globals.sandbox.spy(),
     63    });
     64    globals.set("AboutHomeStartupCache", { onPreloadedNewTabMessage() {} });
     65    globals.set("AboutNewTabParent", {
     66      flushQueuedMessagesFromContent: globals.sandbox.stub(),
     67    });
     68 
     69    dispatch = globals.sandbox.spy();
     70    mm = new ActivityStreamMessageChannel({ dispatch });
     71 
     72    assert.ok(mm.loadedTabs, []);
     73 
     74    let loadedTabs = new Map();
     75    let sandbox = sinon.createSandbox();
     76    sandbox.stub(mm, "loadedTabs").get(() => loadedTabs);
     77  });
     78 
     79  afterEach(() => globals.restore());
     80 
     81  describe("portID validation", () => {
     82    let sandbox;
     83    beforeEach(() => {
     84      sandbox = sinon.createSandbox();
     85      sandbox.spy(global.console, "error");
     86    });
     87    afterEach(() => {
     88      sandbox.restore();
     89    });
     90    it("should log errors for an invalid portID", () => {
     91      mm.validatePortID({});
     92      mm.validatePortID({});
     93      mm.validatePortID({});
     94 
     95      assert.equal(global.console.error.callCount, 3);
     96    });
     97  });
     98 
     99  it("should exist", () => {
    100    assert.ok(ActivityStreamMessageChannel);
    101  });
    102  it("should apply default options", () => {
    103    mm = new ActivityStreamMessageChannel();
    104    OPTIONS.forEach(o => assert.equal(mm[o], DEFAULT_OPTIONS[o], o));
    105  });
    106  it("should add options", () => {
    107    const options = {
    108      dispatch: () => {},
    109      pageURL: "FOO.html",
    110      outgoingMessageName: "OUT",
    111      incomingMessageName: "IN",
    112    };
    113    mm = new ActivityStreamMessageChannel(options);
    114    OPTIONS.forEach(o => assert.equal(mm[o], options[o], o));
    115  });
    116  it("should throw an error if no dispatcher was provided", () => {
    117    mm = new ActivityStreamMessageChannel();
    118    assert.throws(() => mm.dispatch({ type: "FOO" }));
    119  });
    120  describe("Creating/destroying the channel", () => {
    121    describe("#simulateMessagesForExistingTabs", () => {
    122      beforeEach(() => {
    123        sinon.stub(mm, "onActionFromContent");
    124      });
    125      it("should simulate init for existing ports", () => {
    126        let msg1 = getTabDetails("inited", "about:monkeys", {
    127          simulated: true,
    128        });
    129        mm.loadedTabs.set(msg1.data.browser, msg1.data);
    130 
    131        let msg2 = getTabDetails("loaded", "about:sheep", {
    132          simulated: true,
    133        });
    134        mm.loadedTabs.set(msg2.data.browser, msg2.data);
    135 
    136        mm.simulateMessagesForExistingTabs();
    137 
    138        assert.calledWith(mm.onActionFromContent.firstCall, {
    139          type: at.NEW_TAB_INIT,
    140          data: msg1.data,
    141        });
    142        assert.calledWith(mm.onActionFromContent.secondCall, {
    143          type: at.NEW_TAB_INIT,
    144          data: msg2.data,
    145        });
    146      });
    147      it("should simulate load for loaded ports", () => {
    148        let msg3 = getTabDetails("foo", null, {
    149          preloaded: true,
    150          loaded: true,
    151        });
    152        mm.loadedTabs.set(msg3.data.browser, msg3.data);
    153 
    154        mm.simulateMessagesForExistingTabs();
    155 
    156        assert.calledWith(
    157          mm.onActionFromContent,
    158          { type: at.NEW_TAB_LOAD },
    159          "foo"
    160        );
    161      });
    162      it("should set renderLayers on preloaded browsers after load", () => {
    163        let msg4 = getTabDetails("foo", null, {
    164          preloaded: true,
    165          loaded: true,
    166        });
    167        msg4.data.browser.ownerGlobal = {
    168          STATE_MAXIMIZED: 1,
    169          STATE_MINIMIZED: 2,
    170          STATE_NORMAL: 3,
    171          STATE_FULLSCREEN: 4,
    172          windowState: 3,
    173          isFullyOccluded: false,
    174        };
    175        mm.loadedTabs.set(msg4.data.browser, msg4.data);
    176        mm.simulateMessagesForExistingTabs();
    177        assert.equal(msg4.data.browser.renderLayers, true);
    178      });
    179      it("should flush queued messages from content when doing the simulation", () => {
    180        assert.notCalled(
    181          global.AboutNewTabParent.flushQueuedMessagesFromContent
    182        );
    183        mm.simulateMessagesForExistingTabs();
    184        assert.calledOnce(
    185          global.AboutNewTabParent.flushQueuedMessagesFromContent
    186        );
    187      });
    188    });
    189  });
    190  describe("Message handling", () => {
    191    describe("#getTargetById", () => {
    192      it("should get an id if it exists", () => {
    193        let msg = getTabDetails("foo:1");
    194        mm.loadedTabs.set(msg.data.browser, msg.data);
    195        assert.equal(mm.getTargetById("foo:1"), msg.data.actor);
    196      });
    197      it("should return null if the target doesn't exist", () => {
    198        let msg = getTabDetails("foo:2");
    199        mm.loadedTabs.set(msg.data.browser, msg.data);
    200        assert.equal(mm.getTargetById("bar:3"), null);
    201      });
    202    });
    203    describe("#getPreloadedActors", () => {
    204      it("should get a preloaded actor if it exists", () => {
    205        let msg = getTabDetails("foo:3", null, { preloaded: true });
    206        mm.loadedTabs.set(msg.data.browser, msg.data);
    207        assert.equal(mm.getPreloadedActors()[0].portID, "foo:3");
    208      });
    209      it("should get all the preloaded actors across windows if they exist", () => {
    210        let msg = getTabDetails("foo:4a", null, { preloaded: true });
    211        mm.loadedTabs.set(msg.data.browser, msg.data);
    212        msg = getTabDetails("foo:4b", null, { preloaded: true });
    213        mm.loadedTabs.set(msg.data.browser, msg.data);
    214        assert.equal(mm.getPreloadedActors().length, 2);
    215      });
    216      it("should return null if there is no preloaded actor", () => {
    217        let msg = getTabDetails("foo:5");
    218        mm.loadedTabs.set(msg.data.browser, msg.data);
    219        assert.equal(mm.getPreloadedActors(), null);
    220      });
    221    });
    222    describe("#onNewTabInit", () => {
    223      it("should dispatch a NEW_TAB_INIT action", () => {
    224        let msg = getTabDetails("foo", "about:monkeys");
    225        sinon.stub(mm, "onActionFromContent");
    226 
    227        mm.onNewTabInit(msg, msg.data);
    228 
    229        assert.calledWith(mm.onActionFromContent, {
    230          type: at.NEW_TAB_INIT,
    231          data: msg.data,
    232        });
    233      });
    234    });
    235    describe("#onNewTabLoad", () => {
    236      it("should dispatch a NEW_TAB_LOAD action", () => {
    237        let msg = getTabDetails("foo", null, { preloaded: true });
    238        mm.loadedTabs.set(msg.data.browser, msg.data);
    239        sinon.stub(mm, "onActionFromContent");
    240        mm.onNewTabLoad({ target: msg.target }, msg.data);
    241        assert.calledWith(
    242          mm.onActionFromContent,
    243          { type: at.NEW_TAB_LOAD },
    244          "foo"
    245        );
    246      });
    247    });
    248    describe("#onNewTabUnload", () => {
    249      it("should dispatch a NEW_TAB_UNLOAD action", () => {
    250        let msg = getTabDetails("foo");
    251        mm.loadedTabs.set(msg.data.browser, msg.data);
    252        sinon.stub(mm, "onActionFromContent");
    253        mm.onNewTabUnload({ target: msg.target }, msg.data);
    254        assert.calledWith(
    255          mm.onActionFromContent,
    256          { type: at.NEW_TAB_UNLOAD },
    257          "foo"
    258        );
    259      });
    260    });
    261    describe("#onMessage", () => {
    262      let sandbox;
    263      beforeEach(() => {
    264        sandbox = sinon.createSandbox();
    265        sandbox.spy(global.console, "error");
    266      });
    267      afterEach(() => sandbox.restore());
    268      it("return early when tab details are not present", () => {
    269        let msg = getTabDetails("foo");
    270        sinon.stub(mm, "onActionFromContent");
    271        mm.onMessage(msg, msg.data);
    272        assert.notCalled(mm.onActionFromContent);
    273      });
    274      it("should report an error if the msg.data is missing", () => {
    275        let msg = getTabDetails("foo");
    276        mm.loadedTabs.set(msg.data.browser, msg.data);
    277        let tabDetails = msg.data;
    278        delete msg.data;
    279        mm.onMessage(msg, tabDetails);
    280        assert.calledOnce(global.console.error);
    281      });
    282      it("should report an error if the msg.data.type is missing", () => {
    283        let msg = getTabDetails("foo");
    284        mm.loadedTabs.set(msg.data.browser, msg.data);
    285        msg.data = "foo";
    286        mm.onMessage(msg, msg.data);
    287        assert.calledOnce(global.console.error);
    288      });
    289      it("should call onActionFromContent", () => {
    290        sinon.stub(mm, "onActionFromContent");
    291        let msg = getTabDetails("foo");
    292        mm.loadedTabs.set(msg.data.browser, msg.data);
    293        let action = {
    294          data: { data: {}, type: "FOO" },
    295          target: msg.target,
    296        };
    297        const expectedAction = {
    298          type: action.data.type,
    299          data: action.data.data,
    300          _target: { browser: msg.data.browser },
    301        };
    302        mm.onMessage(action, msg.data);
    303        assert.calledWith(mm.onActionFromContent, expectedAction, "foo");
    304      });
    305    });
    306  });
    307  describe("Sending and broadcasting", () => {
    308    describe("#send", () => {
    309      it("should send a message on the right port", () => {
    310        let msg = getTabDetails("foo:6");
    311        mm.loadedTabs.set(msg.data.browser, msg.data);
    312        const action = ac.AlsoToOneContent({ type: "HELLO" }, "foo:6");
    313        mm.send(action);
    314        assert.calledWith(
    315          msg.data.actor.sendAsyncMessage,
    316          DEFAULT_OPTIONS.outgoingMessageName,
    317          action
    318        );
    319      });
    320      it("should not throw if the target isn't around", () => {
    321        // port is not added to the channel
    322        const action = ac.AlsoToOneContent({ type: "HELLO" }, "foo:7");
    323 
    324        assert.doesNotThrow(() => mm.send(action));
    325      });
    326    });
    327    describe("#broadcast", () => {
    328      it("should send a message on the channel", () => {
    329        let msg = getTabDetails("foo:8");
    330        mm.loadedTabs.set(msg.data.browser, msg.data);
    331        const action = ac.BroadcastToContent({ type: "HELLO" });
    332        mm.broadcast(action);
    333        assert.calledWith(
    334          msg.data.actor.sendAsyncMessage,
    335          DEFAULT_OPTIONS.outgoingMessageName,
    336          action
    337        );
    338      });
    339    });
    340    describe("#preloaded browser", () => {
    341      it("should send the message to the preloaded browser if there's data and a preloaded browser exists", () => {
    342        let msg = getTabDetails("foo:9", null, { preloaded: true });
    343        mm.loadedTabs.set(msg.data.browser, msg.data);
    344        const action = ac.AlsoToPreloaded({ type: "HELLO", data: 10 });
    345        mm.sendToPreloaded(action);
    346        assert.calledWith(
    347          msg.data.actor.sendAsyncMessage,
    348          DEFAULT_OPTIONS.outgoingMessageName,
    349          action
    350        );
    351      });
    352      it("should send the message to all the preloaded browsers if there's data and they exist", () => {
    353        let msg1 = getTabDetails("foo:10a", null, { preloaded: true });
    354        mm.loadedTabs.set(msg1.data.browser, msg1.data);
    355 
    356        let msg2 = getTabDetails("foo:10b", null, { preloaded: true });
    357        mm.loadedTabs.set(msg2.data.browser, msg2.data);
    358 
    359        mm.sendToPreloaded(ac.AlsoToPreloaded({ type: "HELLO", data: 10 }));
    360        assert.calledOnce(msg1.data.actor.sendAsyncMessage);
    361        assert.calledOnce(msg2.data.actor.sendAsyncMessage);
    362      });
    363      it("should not send the message to the preloaded browser if there's no data and a preloaded browser does not exists", () => {
    364        let msg = getTabDetails("foo:11");
    365        mm.loadedTabs.set(msg.data.browser, msg.data);
    366        const action = ac.AlsoToPreloaded({ type: "HELLO" });
    367        mm.sendToPreloaded(action);
    368        assert.notCalled(msg.data.actor.sendAsyncMessage);
    369      });
    370    });
    371  });
    372  describe("Handling actions", () => {
    373    describe("#onActionFromContent", () => {
    374      beforeEach(() => mm.onActionFromContent({ type: "FOO" }, "foo:12"));
    375      it("should dispatch a AlsoToMain action", () => {
    376        assert.calledOnce(dispatch);
    377        const [action] = dispatch.firstCall.args;
    378        assert.equal(action.type, "FOO", "action.type");
    379      });
    380      it("should have the right fromTarget", () => {
    381        const [action] = dispatch.firstCall.args;
    382        assert.equal(action.meta.fromTarget, "foo:12", "meta.fromTarget");
    383      });
    384    });
    385    describe("#middleware", () => {
    386      let store;
    387      beforeEach(() => {
    388        store = createStore(addNumberReducer, applyMiddleware(mm.middleware));
    389      });
    390      it("should just call next if no channel is found", () => {
    391        store.dispatch({ type: "ADD", data: 10 });
    392        assert.equal(store.getState(), 10);
    393      });
    394      it("should call .send but not affect the main store if an OnlyToOneContent action is dispatched", () => {
    395        sinon.stub(mm, "send");
    396        const action = ac.OnlyToOneContent({ type: "ADD", data: 10 }, "foo");
    397 
    398        store.dispatch(action);
    399 
    400        assert.calledWith(mm.send, action);
    401        assert.equal(store.getState(), 0);
    402      });
    403      it("should call .send and update the main store if an AlsoToOneContent action is dispatched", () => {
    404        sinon.stub(mm, "send");
    405        const action = ac.AlsoToOneContent({ type: "ADD", data: 10 }, "foo");
    406 
    407        store.dispatch(action);
    408 
    409        assert.calledWith(mm.send, action);
    410        assert.equal(store.getState(), 10);
    411      });
    412      it("should call .broadcast if the action is BroadcastToContent", () => {
    413        sinon.stub(mm, "broadcast");
    414        const action = ac.BroadcastToContent({ type: "FOO" });
    415 
    416        store.dispatch(action);
    417 
    418        assert.calledWith(mm.broadcast, action);
    419      });
    420      it("should call .sendToPreloaded if the action is AlsoToPreloaded", () => {
    421        sinon.stub(mm, "sendToPreloaded");
    422        const action = ac.AlsoToPreloaded({ type: "FOO" });
    423 
    424        store.dispatch(action);
    425 
    426        assert.calledWith(mm.sendToPreloaded, action);
    427      });
    428      it("should dispatch other actions normally", () => {
    429        sinon.stub(mm, "send");
    430        sinon.stub(mm, "broadcast");
    431        sinon.stub(mm, "sendToPreloaded");
    432 
    433        store.dispatch({ type: "ADD", data: 1 });
    434 
    435        assert.equal(store.getState(), 1);
    436        assert.notCalled(mm.send);
    437        assert.notCalled(mm.broadcast);
    438        assert.notCalled(mm.sendToPreloaded);
    439      });
    440    });
    441  });
    442 });