Commit b716c457 authored by Eduardo Sanz García's avatar Eduardo Sanz García Committed by Robert Knight

Add support for `MessagePort` in `shared/bridge.js`

Initially, I tried to if/else portions of the code to accommodate for
`MessagePort`, aiming to avoid duplication of the code (like I did for
`shared/frame-rpc.js` #3565). However, I found that, unlike
`shared/frame-rpc.js`, this resulted into a spaghetti-type of code, not
very understandable.

Then, I decided to create two internal methods to support both the
current communication using `Window` and the new `MessagePort`. This in
my opinion leads to a clearer results, although some code is duplicated
in both methods.

This PR will result on a reduction in code coverage, which will be fix
by #3590.
parent 86bcb87d
...@@ -64,9 +64,9 @@ export class CrossFrame { ...@@ -64,9 +64,9 @@ export class CrossFrame {
}; };
// Initiate connection to the sidebar. // Initiate connection to the sidebar.
const onDiscoveryCallback = (source, origin, token) => discovery.startDiscovery((source, origin, token) =>
bridge.createChannel(source, origin, token); bridge.createChannel({ source, origin, token })
discovery.startDiscovery(onDiscoveryCallback); );
frameObserver.observe(injectIntoFrame, iframeUnloaded); frameObserver.observe(injectIntoFrame, iframeUnloaded);
/** /**
......
...@@ -88,7 +88,11 @@ describe('CrossFrame', () => { ...@@ -88,7 +88,11 @@ describe('CrossFrame', () => {
createCrossFrame(); createCrossFrame();
fakeDiscovery.startDiscovery.yield('SOURCE', 'ORIGIN', 'TOKEN'); fakeDiscovery.startDiscovery.yield('SOURCE', 'ORIGIN', 'TOKEN');
assert.called(fakeBridge.createChannel); assert.called(fakeBridge.createChannel);
assert.calledWith(fakeBridge.createChannel, 'SOURCE', 'ORIGIN', 'TOKEN'); assert.calledWith(fakeBridge.createChannel, {
source: 'SOURCE',
origin: 'ORIGIN',
token: 'TOKEN',
});
}); });
}); });
......
...@@ -14,7 +14,7 @@ export default class Bridge { ...@@ -14,7 +14,7 @@ export default class Bridge {
this.links = []; this.links = [];
/** @type {Record<string, (...args: any[]) => void>} */ /** @type {Record<string, (...args: any[]) => void>} */
this.channelListeners = {}; this.channelListeners = {};
/** @type {Array<(channel: RPC, window: Window) => void>} */ /** @type {Array<(channel: RPC, window?: Window) => void>} */
this.onConnectListeners = []; this.onConnectListeners = [];
} }
...@@ -27,18 +27,83 @@ export default class Bridge { ...@@ -27,18 +27,83 @@ export default class Bridge {
this.links.forEach(channel => channel.destroy()); this.links.forEach(channel => channel.destroy());
} }
/**
* Deprecated - Remove after MessagePort conversion
* @typedef windowOptions
* @prop {Window} source - The source window
* @prop {string} origin - The origin of the document in `source`
* @prop {string} token - Shared token between the `source` and this window
* agreed during the discovery process
*/
/**
* Create a communication channel between frames using either `MessagePort` or
* `Window`.
*
* The created channel is added to the list of channels which `call`
* and `on` send and receive messages over.
*
* @param {windowOptions|MessagePort} options
* @return {RPC} - Channel for communicating with the window.
*/
createChannel(options) {
if (options instanceof MessagePort) {
return this._createChannelForPort(options);
} else {
// Deprecated - Remove after MessagePort conversion
return this._createChannelForWindow(options);
}
}
/**
* Create a communication channel using a `MessagePort`.
*
* The created channel is added to the list of channels which `call`
* and `on` send and receive messages over.
*
* @param {MessagePort} port
* @return {RPC} - Channel for communicating with the window.
*/
_createChannelForPort(port) {
const listeners = { connect: cb => cb(), ...this.channelListeners };
// Set up a channel
const channel = new RPC(
window /* dummy */,
port,
'*' /* dummy */,
listeners
);
let connected = false;
const ready = () => {
if (connected) {
return;
}
connected = true;
this.onConnectListeners.forEach(cb => cb(channel));
};
// Fire off a connection attempt
channel.call('connect', ready);
// Store the newly created channel in our collection
this.links.push(channel);
return channel;
}
/** /**
* Create a communication channel between this window and `source`. * Create a communication channel between this window and `source`.
* *
* The created channel is added to the list of channels which `call` * The created channel is added to the list of channels which `call`
* and `on` send and receive messages over. * and `on` send and receive messages over.
* *
* @param {Window} source - The source window. * @param {windowOptions} options
* @param {string} origin - The origin of the document in `source`.
* @param {string} token
* @return {RPC} - Channel for communicating with the window. * @return {RPC} - Channel for communicating with the window.
* @deprecated
*/ */
createChannel(source, origin, token) { _createChannelForWindow({ source, origin, token }) {
let channel = null; let channel = null;
let connected = false; let connected = false;
...@@ -53,7 +118,7 @@ export default class Bridge { ...@@ -53,7 +118,7 @@ export default class Bridge {
const connect = (_token, cb) => { const connect = (_token, cb) => {
if (_token === token) { if (_token === token) {
cb(); cb();
ready(); ready(); // This is necessary for testing only.
} }
}; };
...@@ -162,7 +227,7 @@ export default class Bridge { ...@@ -162,7 +227,7 @@ export default class Bridge {
/** /**
* Add a listener to be called upon a new connection. * Add a listener to be called upon a new connection.
* *
* @param {(channel: RPC, window: Window) => void} listener * @param {(channel: RPC, window?: Window) => void} listener
*/ */
onConnect(listener) { onConnect(listener) {
this.onConnectListeners.push(listener); this.onConnectListeners.push(listener);
......
...@@ -22,7 +22,11 @@ describe('shared/bridge', () => { ...@@ -22,7 +22,11 @@ describe('shared/bridge', () => {
bridge = new Bridge(); bridge = new Bridge();
createChannel = () => createChannel = () =>
bridge.createChannel(fakeWindow, 'http://example.com', 'TOKEN'); bridge.createChannel({
source: fakeWindow,
origin: 'http://example.com',
token: 'TOKEN',
});
fakeWindow = { fakeWindow = {
postMessage: sandbox.stub(), postMessage: sandbox.stub(),
...@@ -85,11 +89,11 @@ describe('shared/bridge', () => { ...@@ -85,11 +89,11 @@ describe('shared/bridge', () => {
it('calls a callback when all channels return successfully', done => { it('calls a callback when all channels return successfully', done => {
const channel1 = createChannel(); const channel1 = createChannel();
const channel2 = bridge.createChannel( const channel2 = bridge.createChannel({
fakeWindow, source: fakeWindow,
'http://example.com', origin: 'http://example.com',
'NEKOT' token: 'NEKOT',
); });
channel1.call.yields(null, 'result1'); channel1.call.yields(null, 'result1');
channel2.call.yields(null, 'result2'); channel2.call.yields(null, 'result2');
...@@ -105,11 +109,11 @@ describe('shared/bridge', () => { ...@@ -105,11 +109,11 @@ describe('shared/bridge', () => {
it('calls a callback with an error when a channels fails', done => { it('calls a callback with an error when a channels fails', done => {
const error = new Error('Uh oh'); const error = new Error('Uh oh');
const channel1 = createChannel(); const channel1 = createChannel();
const channel2 = bridge.createChannel( const channel2 = bridge.createChannel({
fakeWindow, source: fakeWindow,
'http://example.com', origin: 'http://example.com',
'NEKOT' token: 'NEKOT',
); });
channel1.call.throws(error); channel1.call.throws(error);
channel2.call.yields(null, 'result2'); channel2.call.yields(null, 'result2');
...@@ -260,16 +264,16 @@ describe('shared/bridge', () => { ...@@ -260,16 +264,16 @@ describe('shared/bridge', () => {
describe('#destroy', () => describe('#destroy', () =>
it('destroys all opened channels', () => { it('destroys all opened channels', () => {
const channel1 = bridge.createChannel( const channel1 = bridge.createChannel({
fakeWindow, source: fakeWindow,
'http://example.com', origin: 'http://example.com',
'foo' token: 'foo',
); });
const channel2 = bridge.createChannel( const channel2 = bridge.createChannel({
fakeWindow, source: fakeWindow,
'http://example.com', origin: 'http://example.com',
'bar' token: 'bar',
); });
bridge.destroy(); bridge.destroy();
......
...@@ -236,7 +236,9 @@ export class FrameSyncService { ...@@ -236,7 +236,9 @@ export class FrameSyncService {
}; };
const discovery = new Discovery(window, { server: true }); const discovery = new Discovery(window, { server: true });
discovery.startDiscovery(this._bridge.createChannel.bind(this._bridge)); discovery.startDiscovery((source, origin, token) =>
this._bridge.createChannel({ source, origin, token })
);
this._bridge.onConnect(addFrame); this._bridge.onConnect(addFrame);
this._setupSyncToFrame(); this._setupSyncToFrame();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment