diff --git a/addons/bus/static/src/js/crosstab_bus.js b/addons/bus/static/src/js/crosstab_bus.js index df050b91e4c..46643cad0de 100644 --- a/addons/bus/static/src/js/crosstab_bus.js +++ b/addons/bus/static/src/js/crosstab_bus.js @@ -105,7 +105,7 @@ var CrossTabBus = Longpolling.extend({ $(window).on('unload.' + this._id, this._onUnload.bind(this)); - if (!this._callLocalStorage('getItem', 'peers')) { + if (!this._callLocalStorage('getItem', 'master')) { this._startElection(); } @@ -252,6 +252,7 @@ var CrossTabBus = Longpolling.extend({ //we're next in queue. Electing as master this.lastHeartbeat = now; this._callLocalStorage('setItem', 'heartbeat', this.lastHeartbeat); + this._callLocalStorage('setItem', 'master', true); this._isMasterTab = true; this.startPolling(); this.trigger('become_master'); @@ -297,7 +298,7 @@ var CrossTabBus = Longpolling.extend({ var value = JSON.parse(e.newValue); var key = e.key; - if (this._isRegistered && key === this._generateKey('peers') && !value) { + if (this._isRegistered && key === this._generateKey('master') && !value) { //master was unloaded this._startElection(); } @@ -341,7 +342,7 @@ var CrossTabBus = Longpolling.extend({ // unload master if (this._isMasterTab) { - this._callLocalStorage('removeItem', 'peers'); + this._callLocalStorage('removeItem', 'master'); } }, }); diff --git a/addons/bus/static/tests/bus_tests.js b/addons/bus/static/tests/bus_tests.js index e90f5d6cf30..1451feef390 100644 --- a/addons/bus/static/tests/bus_tests.js +++ b/addons/bus/static/tests/bus_tests.js @@ -214,4 +214,106 @@ QUnit.module('Bus', { }, 3); }); + + QUnit.test('cross tab bus elect new master on master unload', function (assert) { + var done = assert.async(); + assert.expect(8); + + // master + var pollDeferredMaster = $.Deferred(); + + var parentMaster = new Widget(); + testUtils.addMockEnvironment(parentMaster, { + data: {}, + services: { + bus_service: BusService, + local_storage: LocalStorageServiceMock, + }, + mockRPC: function (route, args) { + if (route === '/longpolling/poll') { + assert.step(['master', route, args.channels.join(',')]); + + pollDeferredMaster = $.Deferred(); + pollDeferredMaster.abort = (function () { + this.reject({message: "XmlHttpRequestError abort"}, $.Event()); + }).bind(pollDeferredMaster); + return pollDeferredMaster; + } + return this._super.apply(this, arguments); + } + }); + + var master = new Widget(parentMaster); + master.appendTo($('#qunit-fixture')); + + master.call('bus_service', 'onNotification', master, function (notifications) { + assert.step(['master', 'notification', notifications]); + }); + master.call('bus_service', 'addChannel', 'lambda'); + + // slave + setTimeout(function () { + var parentSlave = new Widget(); + var pollDeferredSlave = $.Deferred(); + testUtils.addMockEnvironment(parentSlave, { + data: {}, + services: { + bus_service: BusService, + local_storage: LocalStorageServiceMock, + }, + mockRPC: function (route, args) { + if (route === '/longpolling/poll') { + assert.step(['slave', route, args.channels.join(',')]); + + pollDeferredSlave = $.Deferred(); + pollDeferredSlave.abort = (function () { + this.reject({message: "XmlHttpRequestError abort"}, $.Event()); + }).bind(pollDeferredSlave); + return pollDeferredSlave; + } + return this._super.apply(this, arguments); + } + }); + + var slave = new Widget(parentSlave); + slave.appendTo($('#qunit-fixture')); + + slave.call('bus_service', 'onNotification', slave, function (notifications) { + assert.step(['slave', 'notification', notifications]); + }); + slave.call('bus_service', 'addChannel', 'lambda'); + + pollDeferredMaster.resolve([{ + id: 1, + channel: 'lambda', + message: 'beta', + }]); + + // simulate unloading master + master.call('bus_service', '_onUnload'); + + pollDeferredSlave.resolve([{ + id: 2, + channel: 'lambda', + message: 'gamma', + }]); + + assert.verifySteps([ + ["master", "/longpolling/poll", "lambda"], + ["master", "notification", [["lambda", "beta"]]], + ["slave", "notification", [["lambda", "beta"]]], + ["master", "/longpolling/poll", "lambda"], + ["slave", "/longpolling/poll", "lambda"], + ["slave", "notification", [["lambda", "gamma"]]], + ["slave", "/longpolling/poll", "lambda"], + ]); + + parentMaster.destroy(); + parentSlave.destroy(); + + done(); + + }, 3); + }); + });});