From 3ae11c87e8a395219229416c2777bcdcdacd08ac Mon Sep 17 00:00:00 2001 From: Matthieu Stockbauer Date: Mon, 4 Jul 2022 10:30:03 +0000 Subject: [PATCH] [IMP] mail, *: adapt bus service to wowl env. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *: bus, calendar, iap_mail, im_livechat, project, web. In order to ease the PR introducing the websockets in Odoo, the bus service has to be updated to be a wowl service. This PR takes care of it. task-2053917 closes odoo/odoo#95824 Related: odoo/enterprise#29361 Signed-off-by: Sébastien Theys (seb) --- addons/bus/__manifest__.py | 6 +- .../bus/static/src/{js => }/crosstab_bus.js | 169 +++++++----- .../js/services/legacy/legacy_bus_service.js | 13 - .../static/src/{js => }/longpolling_bus.js | 141 +++++----- .../services/assets_watchdog_service.js | 11 +- .../src/{js => }/services/bus_service.js | 95 ++----- .../legacy/make_bus_service_to_legacy_env.js | 14 + .../bus/static/tests/assets_watchdog_tests.js | 22 +- addons/bus/static/tests/bus_tests.js | 260 +++++------------- .../services/calendar_notification_service.js | 11 +- .../tests/calendar_notification_tests.js | 19 +- .../js/services/iap_notification_service.js | 9 +- addons/im_livechat/__manifest__.py | 12 +- addons/im_livechat/static/src/public/main.js | 3 +- addons/mail/__manifest__.py | 6 +- .../notification_request.js | 2 +- addons/mail/static/src/models/messaging.js | 6 +- .../models/messaging_notification_handler.js | 16 +- .../static/src/public/discuss_public_boot.js | 5 +- .../static/src/services/messaging_service.js | 2 +- .../mail/static/tests/helpers/test_utils.js | 1 - .../static/tests/helpers/webclient_setup.js | 76 ++--- .../components/discuss_tests.js | 51 +++- addons/project/__manifest__.py | 2 +- addons/web/static/src/core/browser/browser.js | 6 + addons/web/static/src/legacy/js/core/ajax.js | 16 +- .../src/legacy/js/public/public_root.js | 4 + addons/web/static/src/legacy/legacy_setup.js | 4 + addons/web/static/tests/helpers/mock_env.js | 2 + .../web/static/tests/helpers/mock_services.js | 12 +- addons/web/static/tests/setup.js | 11 + addons/web/static/tests/webclient/helpers.js | 8 +- 32 files changed, 432 insertions(+), 583 deletions(-) rename addons/bus/static/src/{js => }/crosstab_bus.js (85%) delete mode 100644 addons/bus/static/src/js/services/legacy/legacy_bus_service.js rename addons/bus/static/src/{js => }/longpolling_bus.js (72%) rename addons/bus/static/src/{js => }/services/assets_watchdog_service.js (90%) rename addons/bus/static/src/{js => }/services/bus_service.js (61%) create mode 100644 addons/bus/static/src/services/legacy/make_bus_service_to_legacy_env.js diff --git a/addons/bus/__manifest__.py b/addons/bus/__manifest__.py index c9f9242377d..5ec703dcd4a 100644 --- a/addons/bus/__manifest__.py +++ b/addons/bus/__manifest__.py @@ -13,11 +13,7 @@ 'bus/static/src/**/*', ], 'web.assets_frontend': [ - 'bus/static/src/js/longpolling_bus.js', - 'bus/static/src/js/crosstab_bus.js', - 'bus/static/src/js/services/bus_service.js', - 'bus/static/src/js/services/legacy/*.js', - 'bus/static/src/js/*.js', + 'bus/static/src/**/*.js', ], 'web.qunit_suite_tests': [ 'bus/static/tests/*.js', diff --git a/addons/bus/static/src/js/crosstab_bus.js b/addons/bus/static/src/crosstab_bus.js similarity index 85% rename from addons/bus/static/src/js/crosstab_bus.js rename to addons/bus/static/src/crosstab_bus.js index ad90bdb217b..179f21a9139 100644 --- a/addons/bus/static/src/js/crosstab_bus.js +++ b/addons/bus/static/src/crosstab_bus.js @@ -1,9 +1,9 @@ -odoo.define('bus.CrossTab', function (require) { -"use strict"; +/** @odoo-module **/ -var Longpolling = require('bus.Longpolling'); +import { browser } from '@web/core/browser/browser'; +import session from 'web.session'; -var session = require('web.session'); +import { Longpolling } from '@bus/longpolling_bus'; /** * CrossTab @@ -26,23 +26,20 @@ var session = require('web.session'); * - become_master : when this tab became the master * - no_longer_master : when this tab is not longer the master (the user swith tab) */ -var CrossTabBus = Longpolling.extend({ - // constants - TAB_HEARTBEAT_PERIOD: 10000, // 10 seconds - MASTER_TAB_HEARTBEAT_PERIOD: 1500, // 1.5 seconds - HEARTBEAT_OUT_OF_DATE_PERIOD: 5000, // 5 seconds - HEARTBEAT_KILL_OLD_PERIOD: 15000, // 15 seconds - LOCAL_STORAGE_PREFIX: 'bus', +export class CrossTab extends Longpolling { + constructor(env, services) { + super(env, services); + // constants + this.TAB_HEARTBEAT_PERIOD = 10000; // 10 seconds + this.MASTER_TAB_HEARTBEAT_PERIOD = 1500; // 1.5 seconds + this.HEARTBEAT_OUT_OF_DATE_PERIOD = 5000; // 5 seconds + this.HEARTBEAT_KILL_OLD_PERIOD = 15000; // 15 seconds + this.LOCAL_STORAGE_PREFIX = 'bus'; - // properties - _isMasterTab: false, - _isRegistered: false, + // properties + this._isMasterTab = false; + this._isRegistered = false; - /** - * @override - */ - init: function () { - this._super.apply(this, arguments); var now = new Date().getTime(); // used to prefix localStorage keys this._sanitizedOrigin = session.origin.replace(/:\/{0,2}/g, '_'); @@ -53,55 +50,57 @@ var CrossTabBus = Longpolling.extend({ this._callLocalStorage('removeItem', 'last'); } this._lastNotificationID = this._callLocalStorage('getItem', 'last', 0); - this.call('local_storage', 'onStorage', this, this._onStorage); - }, - destroy: function () { - this._super(); - clearTimeout(this._heartbeatTimeout); - }, + browser.addEventListener('storage', this._onStorage.bind(this)); + } + //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- + /** * Share the bus channels with the others tab by the local storage * * @override */ - addChannel: function (channel) { + addChannel(channel) { this._currentTabChannels.add(channel); - this._super.apply(this, arguments); + super.addChannel(channel); this._updateChannels(); - }, + } + /** * Share the bus channels with the others tab by the local storage * * @override */ - deleteChannel: function (channel) { + deleteChannel(channel) { this._currentTabChannels.delete(channel); - this._super.apply(this, arguments); + super.deleteChannel(channel); this._updateChannels(); - }, + } + /** * @return {string} */ - getTabId: function () { + getTabId() { return this._id; - }, + } + /** * Tells whether this bus is related to the master tab. * * @returns {boolean} */ - isMasterTab: function () { + isMasterTab() { return this._isMasterTab; - }, + } + /** * Use the local storage to share the long polling from the master tab. * * @override */ - startPolling: function () { + startPolling() { if (this._isActive === null) { this._heartbeat = this._heartbeat.bind(this); } @@ -130,23 +129,26 @@ var CrossTabBus = Longpolling.extend({ } if (this._isMasterTab) { - this._super.apply(this, arguments); + super.startPolling(); } - }, + } + /** * Share the option with the local storage * * @override */ - updateOption: function () { - this._super.apply(this, arguments); + updateOption(key, value) { + super.updateOption(key, value); this._callLocalStorage('setItem', 'options', this._options); - }, + } + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + /** - * Call local_storage service + * Call browser localStorage. * * @private * @param {string} method (getItem, setItem, removeItem, on) @@ -154,9 +156,17 @@ var CrossTabBus = Longpolling.extend({ * @param {any} param * @returns service information */ - _callLocalStorage: function (method, key, param) { - return this.call('local_storage', method, this._generateKey(key), param); - }, + _callLocalStorage(method, key, param) { + if (method === 'setItem') { + param = JSON.stringify(param); + } + const result = browser.localStorage[method](this._generateKey(key), param); + if (method === 'getItem') { + return result ? JSON.parse(result) : param; + } + + } + /** * Generates localStorage keys prefixed by bus. (LOCAL_STORAGE_PREFIX = the name * of this addon), and the sanitized origin, to prevent keys from @@ -167,16 +177,18 @@ var CrossTabBus = Longpolling.extend({ * @param {string} key * @returns key prefixed with the origin */ - _generateKey: function (key) { + _generateKey(key) { return this.LOCAL_STORAGE_PREFIX + '.' + this._sanitizedOrigin + '.' + key; - }, + } + /** * @override * @returns {integer} number of milliseconds since 1 January 1970 00:00:00 */ - _getLastPresence: function () { - return this._callLocalStorage('getItem', 'lastPresence') || this._super(); - }, + _getLastPresence() { + return this._callLocalStorage('getItem', 'lastPresence') || super._getLastPresence(); + } + /** * Check all the time (according to the constants) if the tab is the master tab and * check if it is active. Use the local storage for this checks. @@ -184,7 +196,7 @@ var CrossTabBus = Longpolling.extend({ * @private * @see _startElection method */ - _heartbeat: function () { + _heartbeat() { var now = new Date().getTime(); var heartbeatValue = parseInt(this._callLocalStorage('getItem', 'heartbeat', 0)); var peers = this._callLocalStorage('getItem', 'peers', {}); @@ -229,22 +241,23 @@ var CrossTabBus = Longpolling.extend({ if (this._lastPresenceTime + hbPeriod > now) { this._callLocalStorage('setItem', 'lastPresence', this._lastPresenceTime); } + this._heartbeatTimeout = browser.setTimeout(this._heartbeat.bind(this), hbPeriod); + } - this._heartbeatTimeout = setTimeout(this._heartbeat.bind(this), hbPeriod); - }, /** * @private */ - _registerWindowUnload: function () { - $(window).on('unload.' + this._id, this._onUnload.bind(this)); - }, + _registerWindowUnload() { + browser.addEventListener('unload', this._onUnload.bind(this)); + } + /** * Check with the local storage if the current tab is the master tab. * If this tab became the master, trigger 'become_master' event * * @private */ - _startElection: function () { + _startElection() { if (this._isMasterTab) { return; } @@ -275,14 +288,15 @@ var CrossTabBus = Longpolling.extend({ delete peers[newMaster]; this._callLocalStorage('setItem', 'peers', peers); } - }, + } + /** * Update localstorage channels of with the channels of this tab. * * @private * @return {boolean} true if the aggregated channels has changed. */ - _updateChannels: function () { + _updateChannels() { const currentPeerIds = new Set(Object.keys(this._callLocalStorage('getItem', 'peers') || {})); const peerChannels = this._callLocalStorage('getItem', 'channels') || {}; const peerChannelsBefore = JSON.stringify(peerChannels); @@ -314,31 +328,35 @@ var CrossTabBus = Longpolling.extend({ } this._channels = Array.from(allChannels); return true; - }, + } + //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- + /** * @override */ - _onFocusChange: function (params) { - this._super.apply(this, arguments); + _onFocusChange(params) { + super._onFocusChange(params); this._callLocalStorage('setItem', 'focus', params.focus); - }, + } + /** * If it's the master tab, the notifications ares broadcasted to other tabs by the * local storage. * * @override */ - _onPoll: function (notifications) { - var notifs = this._super(notifications); + _onPoll(notifications) { + var notifs = super._onPoll(notifications); if (this._isMasterTab && notifs.length) { this._callLocalStorage('setItem', 'last', this._lastNotificationID); this._callLocalStorage('setItem', 'last_ts', new Date().getTime()); this._callLocalStorage('setItem', 'notification', notifs); } - }, + } + /** * Handler when the local storage is updated * @@ -347,7 +365,10 @@ var CrossTabBus = Longpolling.extend({ * @param {string} event.key * @param {string} event.newValue */ - _onStorage: function (e) { + _onStorage(e) { + if (!e.key || !e.key.includes(this.LOCAL_STORAGE_PREFIX)) { + return; + } var value = JSON.parse(e.newValue); var key = e.key; @@ -381,13 +402,14 @@ var CrossTabBus = Longpolling.extend({ this._isOdooFocused = value; this.trigger('window_focus', this._isOdooFocused); } - }, + } + /** * Handler when unload the window * * @private */ - _onUnload: function () { + _onUnload() { // unload peer var peers = this._callLocalStorage('getItem', 'peers') || {}; delete peers[this._id]; @@ -399,10 +421,5 @@ var CrossTabBus = Longpolling.extend({ if (this._isMasterTab) { this._callLocalStorage('removeItem', 'master'); } - }, -}); - -return CrossTabBus; - -}); - + } +} diff --git a/addons/bus/static/src/js/services/legacy/legacy_bus_service.js b/addons/bus/static/src/js/services/legacy/legacy_bus_service.js deleted file mode 100644 index f9d42c3fb2c..00000000000 --- a/addons/bus/static/src/js/services/legacy/legacy_bus_service.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @odoo-module **/ - -import { registry } from '@web/core/registry'; - -const { Component } = owl; - -export const legacyBusService = { - start() { - return Component.env.services.bus_service; - }, -}; - -registry.category('services').add('legacy_bus_service', legacyBusService); diff --git a/addons/bus/static/src/js/longpolling_bus.js b/addons/bus/static/src/longpolling_bus.js similarity index 72% rename from addons/bus/static/src/js/longpolling_bus.js rename to addons/bus/static/src/longpolling_bus.js index 75c0c276ba5..fc4c8e8bf11 100644 --- a/addons/bus/static/src/js/longpolling_bus.js +++ b/addons/bus/static/src/longpolling_bus.js @@ -1,9 +1,8 @@ -odoo.define('bus.Longpolling', function (require) { -"use strict"; +/** @odoo-module **/ -var Bus = require('web.Bus'); -var ServicesMixin = require('web.ServicesMixin'); +import { browser } from '@web/core/browser/browser'; +const { EventBus } = owl; /** * Event Longpolling bus used to bind events on the server long polling return @@ -11,27 +10,23 @@ var ServicesMixin = require('web.ServicesMixin'); * trigger: * - window_focus : when the window focus change (true for focused, false for blur) * - notification : when a notification is receive from the long polling - * - * @class Longpolling */ -var LongpollingBus = Bus.extend(ServicesMixin, { - // constants - PARTNERS_PRESENCE_CHECK_PERIOD: 30000, // don't check presence more than once every 30s - ERROR_RETRY_DELAY: 10000, // 10 seconds - POLL_ROUTE: '/longpolling/poll', +export class Longpolling extends EventBus { + constructor(env) { + super(); + this.env = env; - // properties - _isActive: null, - _lastNotificationID: 0, - _isOdooFocused: true, - _pollRetryTimeout: null, + // CONSTANTS + this.PARTNERS_PRESENCE_CHECK_PERIOD = 30000; // don't check presence more than once every 30s + this.ERROR_RETRY_DELAY = 10000; // 10 seconds + this.POLL_ROUTE = '/longpolling/poll'; - /** - * @override - */ - init: function (parent, params) { - this._super.apply(this, arguments); + // PROPERTIES + this._isActive = null; this._id = _.uniqueId('bus'); + this._lastNotificationID = 0; + this._isOdooFocused = true; + this._pollRetryTimeout = null; // the _id is modified by crosstab_bus, so we can't use it to unbind the events in the destroy. this._longPollingBusId = this._id; @@ -40,30 +35,19 @@ var LongpollingBus = Bus.extend(ServicesMixin, { // bus presence this._lastPresenceTime = new Date().getTime(); - $(window).on("focus." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: true})); - $(window).on("blur." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false})); - $(window).on("unload." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false})); - $(window).on("click." + this._longPollingBusId, this._onPresence.bind(this)); - $(window).on("keydown." + this._longPollingBusId, this._onPresence.bind(this)); - $(window).on("keyup." + this._longPollingBusId, this._onPresence.bind(this)); - }, - /** - * @override - */ - destroy: function () { - this.stopPolling(); - $(window).off("focus." + this._longPollingBusId); - $(window).off("blur." + this._longPollingBusId); - $(window).off("unload." + this._longPollingBusId); - $(window).off("click." + this._longPollingBusId); - $(window).off("keydown." + this._longPollingBusId); - $(window).off("keyup." + this._longPollingBusId); - this._super(); - }, + browser.addEventListener("focus", this._onFocusChange.bind(this, { focus: true })); + browser.addEventListener("blur", this._onFocusChange.bind(this, { focus: false })); + browser.addEventListener("unload", this._onFocusChange.bind(this, { focus: false })); + browser.addEventListener("click", this._onPresence.bind(this)); + browser.addEventListener("keydown", this._onPresence.bind(this)); + browser.addEventListener("keyup", this._onPresence.bind(this)); + } + //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- + /** * Register a new channel to listen on the longpoll (ignore if already * listening on this channel). @@ -72,12 +56,13 @@ var LongpollingBus = Bus.extend(ServicesMixin, { * * @param {string} channel */ - addChannel: function (channel) { + addChannel(channel) { if (this._channels.indexOf(channel) === -1) { this._channels.push(channel); this._restartPolling(); } - }, + } + /** * Unregister a channel from listening on the longpoll. * @@ -87,7 +72,7 @@ var LongpollingBus = Bus.extend(ServicesMixin, { * * @param {string} channel */ - deleteChannel: function (channel) { + deleteChannel(channel) { var index = this._channels.indexOf(channel); if (index !== -1) { this._channels.splice(index, 1); @@ -95,20 +80,22 @@ var LongpollingBus = Bus.extend(ServicesMixin, { this._pollRpc.abort(); } } - }, + } + /** * Tell whether odoo is focused or not * * @returns {boolean} */ - isOdooFocused: function () { + isOdooFocused() { return this._isOdooFocused; - }, + } + /** * Start a long polling, i.e. it continually opens a long poll * connection as long as it is not stopped (@see `stopPolling`) */ - startPolling: function () { + startPolling() { if (this._isActive === null) { this._poll = this._poll.bind(this); } @@ -116,21 +103,23 @@ var LongpollingBus = Bus.extend(ServicesMixin, { this._isActive = true; this._poll(); } - }, + } + /** * Stops any started long polling * * Aborts a pending longpoll so that we immediately remove ourselves * from listening on notifications on this channel. */ - stopPolling: function () { + stopPolling() { this._isActive = false; this._channels = []; clearTimeout(this._pollRetryTimeout); if (this._pollRpc) { this._pollRpc.abort(); } - }, + } + /** * Add or update an option on the longpoll bus. * Stored options are sent to the server whenever a poll is started. @@ -138,21 +127,25 @@ var LongpollingBus = Bus.extend(ServicesMixin, { * @param {string} key * @param {any} value */ - updateOption: function (key, value) { + updateOption(key, value) { this._options[key] = value; - }, + } + + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + /** * returns the last recorded presence * * @private * @returns {integer} number of milliseconds since 1 January 1970 00:00:00 */ - _getLastPresence: function () { + _getLastPresence() { return this._lastPresenceTime; - }, + } + /** * Continually start a poll: * @@ -167,7 +160,7 @@ var LongpollingBus = Bus.extend(ServicesMixin, { * * @private */ - _poll: function () { + _poll() { var self = this; if (!this._isActive) { return; @@ -183,10 +176,8 @@ var LongpollingBus = Bus.extend(ServicesMixin, { self._pollRpc = false; self._onPoll(result); self._poll(); - }).guardedCatch(function (result) { + }).catch(function (result) { self._pollRpc = false; - // no error popup if request is interrupted or fails for any reason - result.event.preventDefault(); if (result.message === "XmlHttpRequestError abort") { self._poll(); } else { @@ -194,19 +185,20 @@ var LongpollingBus = Bus.extend(ServicesMixin, { self._pollRetryTimeout = setTimeout(self._poll, self.ERROR_RETRY_DELAY + (Math.floor((Math.random()*20)+1)*1000)); } }); - }, + } /** * @private * @param data: object with poll parameters */ - _makePoll: function(data) { - return this._rpc({route: this.POLL_ROUTE, params: data}, {shadow : true, timeout: 60000}); - }, + _makePoll(params) { + return this.env.services.rpc(this.POLL_ROUTE, params, { silent: true, timeout: 60000 }); + } //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- + /** * Handler when the focus of the window change. * Trigger the 'window_focus' event. @@ -215,13 +207,14 @@ var LongpollingBus = Bus.extend(ServicesMixin, { * @param {Object} params * @param {Boolean} params.focus */ - _onFocusChange: function (params) { + _onFocusChange(params) { this._isOdooFocused = params.focus; if (params.focus) { this._lastPresenceTime = new Date().getTime(); this.trigger('window_focus', this._isOdooFocused); } - }, + } + /** * Handler when the long polling receive the new notifications * Update the last notification id received. @@ -231,7 +224,7 @@ var LongpollingBus = Bus.extend(ServicesMixin, { * @param {Object[]} notifications, Input notifications have an id, channel, message * @returns {Array[]} Output arrays have notification's channel and message */ - _onPoll: function (notifications) { + _onPoll(notifications) { var self = this; var notifs = _.map(notifications, function (notif) { if (notif.id > self._lastNotificationID) { @@ -241,16 +234,18 @@ var LongpollingBus = Bus.extend(ServicesMixin, { }); this.trigger("notification", notifs); return notifs; - }, + } + /** * Handler when they are an activity on the window (click, keydown, keyup) * Update the last presence date. * * @private */ - _onPresence: function () { + _onPresence() { this._lastPresenceTime = new Date().getTime(); - }, + } + /** * Restart polling. * @@ -262,9 +257,5 @@ var LongpollingBus = Bus.extend(ServicesMixin, { } else { this.startPolling(); } - }, -}); - -return LongpollingBus; - -}); + } +} diff --git a/addons/bus/static/src/js/services/assets_watchdog_service.js b/addons/bus/static/src/services/assets_watchdog_service.js similarity index 90% rename from addons/bus/static/src/js/services/assets_watchdog_service.js rename to addons/bus/static/src/services/assets_watchdog_service.js index 1dca8c8f22d..8b73f9a44e1 100644 --- a/addons/bus/static/src/js/services/assets_watchdog_service.js +++ b/addons/bus/static/src/services/assets_watchdog_service.js @@ -4,19 +4,16 @@ import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; import { session } from "@web/session"; -const { Component } = owl; - export const assetsWatchdogService = { - dependencies: ["notification"], + dependencies: ["bus_service", "notification"], - start(env, { notification }) { + start(env, { bus_service, notification }) { let isNotificationDisplayed = false; let bundleNotifTimerID = null; env.bus.on("WEB_CLIENT_READY", null, async () => { - const legacyEnv = Component.env; - legacyEnv.services.bus_service.onNotification(this, onNotification); - legacyEnv.services.bus_service.startPolling(); + bus_service.onNotification(this, onNotification); + bus_service.startPolling(); }); /** diff --git a/addons/bus/static/src/js/services/bus_service.js b/addons/bus/static/src/services/bus_service.js similarity index 61% rename from addons/bus/static/src/js/services/bus_service.js rename to addons/bus/static/src/services/bus_service.js index f71bf620d41..bd4b86920a0 100644 --- a/addons/bus/static/src/js/services/bus_service.js +++ b/addons/bus/static/src/services/bus_service.js @@ -1,55 +1,17 @@ -odoo.define('bus.BusService', function (require) { -"use strict"; +/** @odoo-module **/ -var CrossTab = require('bus.CrossTab'); -var core = require('web.core'); -var ServicesMixin = require('web.ServicesMixin'); -const session = require('web.session'); +import { CrossTab } from '@bus/crosstab_bus'; -var BusService = CrossTab.extend(ServicesMixin, { - dependencies : ['local_storage'], +import { registry } from '@web/core/registry'; +import session from 'web.session'; - // properties - _audio: null, +export class BusService extends CrossTab { + constructor(env, services) { + super(env, services); - /** - * As the BusService doesn't extend AbstractService, we have to replicate - * here what is done in AbstractService - * - * @param {Object} env - */ - init: function (env) { - this.env = env; - this._super(); - }, - - /** - * Replicate the behavior of AbstractService: - * - * Directly calls the requested service, instead of triggering a - * 'call_service' event up, which wouldn't work as services have no parent. - * - * @param {OdooEvent} ev - */ - _trigger_up: function (ev) { - if (ev.name === 'call_service') { - const payload = ev.data; - let args = payload.args || []; - if (payload.service === 'ajax' && payload.method === 'rpc') { - // ajax service uses an extra 'target' argument for rpc - args = args.concat(ev.target); - } - const service = this.env.services[payload.service]; - const result = service[payload.method].apply(service, args); - payload.callback(result); - } - }, - /** - * This method is necessary in order for this Class to be used to instantiate services - * - * @abstract - */ - start: function () {}, + // properties + this._audio = null; + } //-------------------------------------------------------------------------- // Public @@ -71,10 +33,10 @@ var BusService = CrossTab.extend(ServicesMixin, { this._sendNativeNotification(options.title, options.message, callback); } catch (error) { // Notification without Serviceworker in Chrome Android doesn't works anymore - // So we fallback to displayNotification() in this case + // So we fallback to the notification service in this case // https://bugs.chromium.org/p/chromium/issues/detail?id=481856 if (error.message.indexOf('ServiceWorkerRegistration') > -1) { - this.displayNotification(options); + this.env.services['notification'].add(options.message, options); this._beep(); } else { throw error; @@ -82,21 +44,22 @@ var BusService = CrossTab.extend(ServicesMixin, { } } } else { - this.displayNotification(options); + this.env.services['notification'].add(options.message, options); if (this.isMasterTab()) { this._beep(); } } - }, + } + /** * Register listeners on notifications received on this bus service * * @param {Object} receiver * @param {function} func */ - onNotification: function () { + onNotification() { this.on.apply(this, ["notification"].concat(Array.prototype.slice.call(arguments))); - }, + } //-------------------------------------------------------------------------- // Private @@ -107,7 +70,7 @@ var BusService = CrossTab.extend(ServicesMixin, { * * @private */ - _beep: function () { + _beep() { if (typeof(Audio) !== "undefined") { if (!this._audio) { this._audio = new Audio(); @@ -116,7 +79,8 @@ var BusService = CrossTab.extend(ServicesMixin, { } Promise.resolve(this._audio.play()).catch(_.noop); } - }, + } + /** * Show a browser notification * @@ -125,7 +89,7 @@ var BusService = CrossTab.extend(ServicesMixin, { * @param {string} content * @param {function} [callback] if given callback will be called when user clicks on notification */ - _sendNativeNotification: function (title, content, callback) { + _sendNativeNotification(title, content, callback) { var notification = new Notification( // The native Notification API works with plain text and not HTML // unescaping is safe because done only at the **last** step @@ -145,12 +109,13 @@ var BusService = CrossTab.extend(ServicesMixin, { callback(); } }; + } +} + +export const busService = { + dependencies: ['notification'], + start(env, services) { + return new BusService(env, services); }, - -}); - -core.serviceRegistry.add('bus_service', BusService); - -return BusService; - -}); +}; +registry.category('services').add('bus_service', busService); diff --git a/addons/bus/static/src/services/legacy/make_bus_service_to_legacy_env.js b/addons/bus/static/src/services/legacy/make_bus_service_to_legacy_env.js new file mode 100644 index 00000000000..317cf2d4172 --- /dev/null +++ b/addons/bus/static/src/services/legacy/make_bus_service_to_legacy_env.js @@ -0,0 +1,14 @@ +/** @odoo-module **/ + +import { registry } from '@web/core/registry'; + +export function makeBusServiceToLegacyEnv(legacyEnv) { + return { + dependencies: ['bus_service'], + start(_, { bus_service }) { + legacyEnv.services['bus_service'] = bus_service; + }, + }; +} + +registry.category('wowlToLegacyServiceMappers').add('bus_service_to_legacy_env', makeBusServiceToLegacyEnv); diff --git a/addons/bus/static/tests/assets_watchdog_tests.js b/addons/bus/static/tests/assets_watchdog_tests.js index 5c85599dbc8..4849f2e9206 100644 --- a/addons/bus/static/tests/assets_watchdog_tests.js +++ b/addons/bus/static/tests/assets_watchdog_tests.js @@ -1,33 +1,24 @@ /** @odoo-module */ -import * as legacyRegistry from "web.Registry"; -import * as BusService from "bus.BusService"; -import * as RamStorage from "web.RamStorage"; -import * as AbstractStorageService from "web.AbstractStorageService"; +import { busService } from "@bus/services/bus_service"; import { createWebClient } from "@web/../tests/webclient/helpers"; -import { assetsWatchdogService } from "@bus/js/services/assets_watchdog_service"; +import { assetsWatchdogService } from "@bus/services/assets_watchdog_service"; import { click, getFixture, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils"; import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; -const LocalStorageService = AbstractStorageService.extend({ - storage: new RamStorage(), -}); const serviceRegistry = registry.category("services"); QUnit.module("Bus Assets WatchDog", (hooks) => { - let legacyServicesRegistry; let target; hooks.beforeEach((assert) => { - legacyServicesRegistry = new legacyRegistry(); - legacyServicesRegistry.add("bus_service", BusService); - legacyServicesRegistry.add("local_storage", LocalStorageService); - serviceRegistry.add("assetsWatchdog", assetsWatchdogService); + serviceRegistry.add("bus_service", busService); patchWithCleanup(browser, { - setTimeout: (fn) => fn(), - clearTimeout: () => {}, + setTimeout(fn) { + return this._super(fn, 0); + }, location: { reload: () => assert.step("reloadPage"), }, @@ -56,7 +47,6 @@ QUnit.module("Bus Assets WatchDog", (hooks) => { }; await createWebClient({ - legacyParams: { serviceRegistry: legacyServicesRegistry }, mockRPC, }); diff --git a/addons/bus/static/tests/bus_tests.js b/addons/bus/static/tests/bus_tests.js index b29d3be8a98..28451975372 100644 --- a/addons/bus/static/tests/bus_tests.js +++ b/addons/bus/static/tests/bus_tests.js @@ -1,45 +1,35 @@ odoo.define('web.bus_tests', function (require) { "use strict"; -var BusService = require('bus.BusService'); -var CrossTabBus = require('bus.CrossTab'); -var AbstractStorageService = require('web.AbstractStorageService'); -var RamStorage = require('web.RamStorage'); +var { busService } = require('@bus/services/bus_service'); +var { CrossTab } = require('@bus/crosstab_bus'); var testUtils = require('web.test_utils'); -var Widget = require('web.Widget'); -const LegacyRegistry = require("web.Registry"); +const { browser } = require("@web/core/browser/browser"); const { ConnectionLostError } = require("@web/core/network/rpc_service"); +const { registry } = require("@web/core/registry"); const { patchWithCleanup, nextTick } = require("@web/../tests/helpers/utils"); -const { createWebClient } = require('@web/../tests/webclient/helpers'); - -const { Component } = owl; - -var LocalStorageServiceMock; - -BusService = BusService.extend({ - TAB_HEARTBEAT_PERIOD: 10, - MASTER_TAB_HEARTBEAT_PERIOD: 1, -}); - +const { makeTestEnv } = require('@web/../tests/helpers/mock_env'); QUnit.module('Bus', { beforeEach: function () { - LocalStorageServiceMock = AbstractStorageService.extend({storage: new RamStorage()}); + const customBusService = { + ...busService, + start() { + const originalBusService = busService.start(...arguments); + originalBusService.TAB_HEARTBEAT_PERIOD = 10; + originalBusService.MASTER_TAB_HEARTBEAT_PERIOD = 1; + return originalBusService; + }, + }; + registry.category('services').add('bus_service', customBusService); }, }, function () { QUnit.test('notifications received from the longpolling channel', async function (assert) { assert.expect(6); var pollPromise = testUtils.makeTestPromise(); - - var parent = new Widget(); - await testUtils.mock.addMockEnvironment(parent, { - data: {}, - services: { - bus_service: BusService, - local_storage: LocalStorageServiceMock, - }, - mockRPC: function (route, args) { + const env = await makeTestEnv({ + mockRPC(route, args) { if (route === '/longpolling/poll') { assert.step(route + ' - ' + args.channels.join(',')); @@ -49,17 +39,12 @@ QUnit.module('Bus', { }).bind(pollPromise); return pollPromise; } - return this._super.apply(this, arguments); - } + }, }); - - var widget = new Widget(parent); - await widget.appendTo($('#qunit-fixture')); - - widget.call('bus_service', 'onNotification', this, function (notifications) { + env.services['bus_service'].onNotification(null, notifications => { assert.step('notification - ' + notifications.toString()); }); - widget.call('bus_service', 'addChannel', 'lambda'); + env.services['bus_service'].addChannel('lambda'); pollPromise.resolve([{ message: 'beta', @@ -78,15 +63,10 @@ QUnit.module('Bus', { 'notification - epsilon', '/longpolling/poll - lambda', ]); - - parent.destroy(); }); QUnit.test('longpolling restarts when connection is lost', async function (assert) { assert.expect(4); - const legacyRegistry = new LegacyRegistry(); - legacyRegistry.add("bus_service", BusService); - legacyRegistry.add("local_storage", LocalStorageServiceMock); const oldSetTimeout = window.setTimeout; patchWithCleanup( @@ -99,23 +79,21 @@ QUnit.module('Bus', { let busService; let rpcCount = 0; - // Using createWebclient to get the compatibility layer between the old services and the new - await createWebClient({ + const env = await makeTestEnv({ mockRPC(route) { if (route === '/longpolling/poll') { rpcCount++; assert.step(`polling ${rpcCount}`); if (rpcCount == 1) { - return Promise.reject(new ConnectionLostError()); + return new ConnectionLostError(); } assert.equal(rpcCount, 2, "Should not be called after stopPolling"); busService.stopPolling(); - return Promise.reject(new ConnectionLostError()); + return new ConnectionLostError(); } }, - legacyParams: { serviceRegistry: legacyRegistry }, }); - busService = Component.env.services.bus_service; + busService = env.services['bus_service']; busService.startPolling(); // Give longpolling bus a tick to try to restart polling await nextTick(); @@ -136,48 +114,32 @@ QUnit.module('Bus', { assert.expect(3); // Simulate no ID of last notification in the local storage - testUtils.mock.patch(LocalStorageServiceMock, { - getItem: function (key) { + patchWithCleanup(browser, { + getItem(key) { if (key === 'last_ts') { return 0; } - return this._super.apply(this, arguments); + return this._super(...arguments); }, }); var pollPromise = testUtils.makeTestPromise(); - var parent = new Widget(); - await testUtils.mock.addMockEnvironment(parent, { - data: {}, - services: { - bus_service: BusService, - local_storage: LocalStorageServiceMock, - }, - mockRPC: function (route, args) { + const env = await makeTestEnv({ + mockRPC(route, args) { if (route === '/longpolling/poll') { assert.step(route); assert.strictEqual(args.last, 0, "provided last notification ID should be 0"); pollPromise = testUtils.makeTestPromise(); - pollPromise.abort = (function () { - this.reject({message: "XmlHttpRequestError abort"}, $.Event()); - }).bind(pollPromise); return pollPromise; } - return this._super.apply(this, arguments); } }); - var widget = new Widget(parent); - await widget.appendTo($('#qunit-fixture')); - // trigger longpolling poll RPC - widget.call('bus_service', 'addChannel', 'lambda'); + env.services['bus_service'].addChannel('lambda'); assert.verifySteps(['/longpolling/poll']); - - testUtils.mock.unpatch(LocalStorageServiceMock); - parent.destroy(); }); QUnit.test('cross tab bus share message from a channel', async function (assert) { @@ -186,60 +148,33 @@ QUnit.module('Bus', { // master var pollPromiseMaster = testUtils.makeTestPromise(); - - var parentMaster = new Widget(); - await testUtils.mock.addMockEnvironment(parentMaster, { - data: {}, - services: { - bus_service: BusService, - local_storage: LocalStorageServiceMock, - }, - mockRPC: function (route, args) { + const masterEnv = await makeTestEnv({ + mockRPC(route, args) { if (route === '/longpolling/poll') { assert.step('master' + ' - ' + route + ' - ' + args.channels.join(',')); pollPromiseMaster = testUtils.makeTestPromise(); - pollPromiseMaster.abort = (function () { - this.reject({message: "XmlHttpRequestError abort"}, $.Event()); - }).bind(pollPromiseMaster); return pollPromiseMaster; } - return this._super.apply(this, arguments); } }); - - var master = new Widget(parentMaster); - await master.appendTo($('#qunit-fixture')); - - master.call('bus_service', 'onNotification', master, function (notifications) { + masterEnv.services['bus_service'].onNotification(null, notifications => { assert.step('master - notification - ' + notifications.toString()); }); - master.call('bus_service', 'addChannel', 'lambda'); + masterEnv.services['bus_service'].addChannel('lambda'); // slave - await testUtils.nextTick(); - var parentSlave = new Widget(); - await testUtils.mock.addMockEnvironment(parentSlave, { - data: {}, - services: { - bus_service: BusService, - local_storage: LocalStorageServiceMock, - }, - mockRPC: function (route, args) { + const slaveEnv = await makeTestEnv({ + mockRPC(route, args) { if (route === '/longpolling/poll') { throw new Error("Can not use the longpolling of the slave client"); } - return this._super.apply(this, arguments); } }); - - var slave = new Widget(parentSlave); - await slave.appendTo($('#qunit-fixture')); - - slave.call('bus_service', 'onNotification', slave, function (notifications) { + slaveEnv.services['bus_service'].onNotification(null, notifications => { assert.step('slave - notification - ' + notifications.toString()); }); - slave.call('bus_service', 'addChannel', 'lambda'); + slaveEnv.services['bus_service'].addChannel('lambda'); pollPromiseMaster.resolve([{ id: 1, @@ -254,9 +189,6 @@ QUnit.module('Bus', { 'slave - notification - beta', 'master - /longpolling/poll - lambda', ]); - - parentMaster.destroy(); - parentSlave.destroy(); }); QUnit.test('cross tab bus elect new master on master unload', async function (assert) { @@ -265,67 +197,38 @@ QUnit.module('Bus', { // master var pollPromiseMaster = testUtils.makeTestPromise(); - var parentMaster = new Widget(); - await testUtils.mock.addMockEnvironment(parentMaster, { - data: {}, - services: { - bus_service: BusService, - local_storage: LocalStorageServiceMock, - }, - mockRPC: function (route, args) { + const masterEnv = await makeTestEnv({ + mockRPC(route, args) { if (route === '/longpolling/poll') { assert.step('master - ' + route + ' - ' + args.channels.join(',')); pollPromiseMaster = testUtils.makeTestPromise(); - pollPromiseMaster.abort = (function () { - this.reject({message: "XmlHttpRequestError abort"}, $.Event()); - }).bind(pollPromiseMaster); return pollPromiseMaster; } - return this._super.apply(this, arguments); } }); - var master = new Widget(parentMaster); - await master.appendTo($('#qunit-fixture')); - - master.call('bus_service', 'onNotification', master, function (notifications) { + masterEnv.services['bus_service'].onNotification(null, notifications => { assert.step('master - notification - ' + notifications.toString()); }); - master.call('bus_service', 'addChannel', 'lambda'); + masterEnv.services['bus_service'].addChannel('lambda'); // slave - await testUtils.nextTick(); - var parentSlave = new Widget(); var pollPromiseSlave = testUtils.makeTestPromise(); - await testUtils.mock.addMockEnvironment(parentSlave, { - data: {}, - services: { - bus_service: BusService, - local_storage: LocalStorageServiceMock, - }, - mockRPC: function (route, args) { + const slaveEnv = await makeTestEnv({ + mockRPC(route, args) { if (route === '/longpolling/poll') { assert.step('slave - ' + route + ' - ' + args.channels.join(',')); pollPromiseSlave = testUtils.makeTestPromise(); - pollPromiseSlave.abort = (function () { - this.reject({message: "XmlHttpRequestError abort"}, $.Event()); - }).bind(pollPromiseSlave); return pollPromiseSlave; } - return this._super.apply(this, arguments); } }); - - var slave = new Widget(parentSlave); - await slave.appendTo($('#qunit-fixture')); - - slave.call('bus_service', 'onNotification', slave, function (notifications) { + slaveEnv.services['bus_service'].onNotification(null, notifications => { assert.step('slave - notification - ' + notifications.toString()); }); - slave.call('bus_service', 'addChannel', 'lambda'); - + slaveEnv.services['bus_service'].addChannel('lambda'); pollPromiseMaster.resolve([{ id: 1, channel: 'lambda', @@ -334,7 +237,7 @@ QUnit.module('Bus', { await testUtils.nextTick(); // simulate unloading master - master.call('bus_service', '_onUnload'); + masterEnv.services['bus_service']._onUnload(); pollPromiseSlave.resolve([{ id: 2, @@ -352,73 +255,46 @@ QUnit.module('Bus', { 'slave - notification - gamma', 'slave - /longpolling/poll - lambda', ]); - - parentMaster.destroy(); - parentSlave.destroy(); }); QUnit.test('two tabs calling addChannel simultaneously', async function (assert) { assert.expect(5); - let id = 1; - testUtils.mock.patch(CrossTabBus, { - init: function () { - this._super.apply(this, arguments); - this.__tabId__ = id++; - }, - addChannel: function (channel) { + patchWithCleanup(CrossTab.prototype, { + addChannel(channel) { assert.step('Tab ' + this.__tabId__ + ': addChannel ' + channel); this._super.apply(this, arguments); }, - deleteChannel: function (channel) { + deleteChannel(channel) { assert.step('Tab ' + this.__tabId__ + ': deleteChannel ' + channel); this._super.apply(this, arguments); }, }); let pollPromise; - const parentTab1 = new Widget(); - await testUtils.mock.addMockEnvironment(parentTab1, { - data: {}, - services: { - local_storage: LocalStorageServiceMock, - }, - mockRPC: function (route) { + const firstTabEnv = await makeTestEnv({ + mockRPC(route) { if (route === '/longpolling/poll') { pollPromise = testUtils.makeTestPromise(); - pollPromise.abort = (function () { - this.reject({message: "XmlHttpRequestError abort"}, $.Event()); - }).bind(pollPromise); return pollPromise; } - return this._super.apply(this, arguments); - } - }); - const parentTab2 = new Widget(); - await testUtils.mock.addMockEnvironment(parentTab2, { - data: {}, - services: { - local_storage: LocalStorageServiceMock, - }, - mockRPC: function (route) { - if (route === '/longpolling/poll') { - pollPromise = testUtils.makeTestPromise(); - pollPromise.abort = (function () { - this.reject({message: "XmlHttpRequestError abort"}, $.Event()); - }).bind(pollPromise); - return pollPromise; - } - return this._super.apply(this, arguments); } }); - const tab1 = new CrossTabBus(parentTab1); - const tab2 = new CrossTabBus(parentTab2); - - tab1.addChannel("alpha"); - tab2.addChannel("alpha"); - tab1.addChannel("beta"); - tab2.addChannel("beta"); + const secondTabEnv = await makeTestEnv({ + mockRPC(route) { + if (route === '/longpolling/poll') { + pollPromise = testUtils.makeTestPromise(); + return pollPromise; + } + } + }); + firstTabEnv.services['bus_service'].__tabId__ = 1; + secondTabEnv.services['bus_service'].__tabId__ = 2; + firstTabEnv.services['bus_service'].addChannel('alpha'); + secondTabEnv.services['bus_service'].addChannel('alpha'); + firstTabEnv.services['bus_service'].addChannel('beta'); + secondTabEnv.services['bus_service'].addChannel('beta'); assert.verifySteps([ "Tab 1: addChannel alpha", @@ -426,10 +302,6 @@ QUnit.module('Bus', { "Tab 1: addChannel beta", "Tab 2: addChannel beta", ]); - - testUtils.mock.unpatch(CrossTabBus); - parentTab1.destroy(); - parentTab2.destroy(); }); }); diff --git a/addons/calendar/static/src/js/services/calendar_notification_service.js b/addons/calendar/static/src/js/services/calendar_notification_service.js index 1d636aed229..de44fe1b787 100644 --- a/addons/calendar/static/src/js/services/calendar_notification_service.js +++ b/addons/calendar/static/src/js/services/calendar_notification_service.js @@ -4,26 +4,23 @@ import { browser } from "@web/core/browser/browser"; import { ConnectionLostError } from "@web/core/network/rpc_service"; import { registry } from "@web/core/registry"; -const { Component } = owl; - export const calendarNotificationService = { - dependencies: ["action", "notification", "rpc"], + dependencies: ["action", "bus_service", "notification", "rpc"], - start(env, { action, notification, rpc }) { + start(env, { action, bus_service, notification, rpc }) { let calendarNotifTimeouts = {}; let nextCalendarNotifTimeout = null; const displayedNotifications = new Set(); env.bus.on("WEB_CLIENT_READY", null, async () => { - const legacyEnv = Component.env; - legacyEnv.services.bus_service.onNotification(this, (notifications) => { + bus_service.onNotification(this, (notifications) => { for (const { payload, type } of notifications) { if (type === "calendar.alarm") { displayCalendarNotification(payload); } } }); - legacyEnv.services.bus_service.startPolling(); + bus_service.startPolling(); }); /** diff --git a/addons/calendar/static/tests/calendar_notification_tests.js b/addons/calendar/static/tests/calendar_notification_tests.js index 1a4d9d0d59f..9f7a81e06b4 100644 --- a/addons/calendar/static/tests/calendar_notification_tests.js +++ b/addons/calendar/static/tests/calendar_notification_tests.js @@ -1,9 +1,6 @@ /** @odoo-module */ -import * as legacyRegistry from "web.Registry"; -import * as BusService from "bus.BusService"; -import * as RamStorage from "web.RamStorage"; -import * as AbstractStorageService from "web.AbstractStorageService"; +import { busService } from "@bus/services/bus_service"; import { createWebClient } from "@web/../tests/webclient/helpers"; import { calendarNotificationService } from "@calendar/js/services/calendar_notification_service"; @@ -11,9 +8,6 @@ import { click, getFixture, nextTick, patchWithCleanup } from "@web/../tests/hel import { browser } from "@web/core/browser/browser"; import { registry } from "@web/core/registry"; -const LocalStorageService = AbstractStorageService.extend({ - storage: new RamStorage(), -}); const serviceRegistry = registry.category("services"); QUnit.module("Calendar Notification", (hooks) => { @@ -22,15 +16,12 @@ QUnit.module("Calendar Notification", (hooks) => { hooks.beforeEach(() => { target = getFixture(); - legacyServicesRegistry = new legacyRegistry(); - legacyServicesRegistry.add("bus_service", BusService); - legacyServicesRegistry.add("local_storage", LocalStorageService); - serviceRegistry.add("calendarNotification", calendarNotificationService); - + serviceRegistry.add("bus_service", busService); patchWithCleanup(browser, { - setTimeout: (fn) => fn(), - clearTimeout: () => {}, + setTimeout(fn) { + this._super(fn, 0); + }, }); }); diff --git a/addons/iap_mail/static/src/js/services/iap_notification_service.js b/addons/iap_mail/static/src/js/services/iap_notification_service.js index cd9098fc493..9499cbd9052 100644 --- a/addons/iap_mail/static/src/js/services/iap_notification_service.js +++ b/addons/iap_mail/static/src/js/services/iap_notification_service.js @@ -4,12 +4,11 @@ import { Markup } from 'web.utils'; import { registry } from "@web/core/registry"; export const iapNotificationService = { - dependencies: ["notification"], + dependencies: ["bus_service", "notification"], - start(env, { notification }) { + start(env, { bus_service, notification }) { env.bus.on("WEB_CLIENT_READY", null, async () => { - const legacyEnv = owl.Component.env; - legacyEnv.services.bus_service.onNotification(this, (notifications) => { + bus_service.onNotification(this, (notifications) => { for (const { payload, type } of notifications) { if (type === 'iap_notification') { if (payload.error_type == 'success') { @@ -20,7 +19,7 @@ export const iapNotificationService = { } } }); - legacyEnv.services.bus_service.startPolling(); + bus_service.startPolling(); }); /** diff --git a/addons/im_livechat/__manifest__.py b/addons/im_livechat/__manifest__.py index d29c000b6c3..19acea341fd 100644 --- a/addons/im_livechat/__manifest__.py +++ b/addons/im_livechat/__manifest__.py @@ -182,9 +182,9 @@ Help your customers with this chat, and analyse their feedback. 'web/static/src/legacy/js/services/ajax_service.js', 'web/static/src/legacy/js/services/local_storage_service.js', # Bus, Mail, Livechat - 'bus/static/src/js/longpolling_bus.js', - 'bus/static/src/js/crosstab_bus.js', - 'bus/static/src/js/services/bus_service.js', + 'bus/static/src/longpolling_bus.js', + 'bus/static/src/crosstab_bus.js', + 'bus/static/src/services/bus_service.js', 'mail/static/src/js/utils.js', 'im_livechat/static/src/legacy/public_livechat_history_tracking.js', 'im_livechat/static/src/legacy/public_livechat_chatbot.js', @@ -207,9 +207,9 @@ Help your customers with this chat, and analyse their feedback. ('include', 'mail.assets_messaging'), 'mail/static/src/services/messaging_service.js', # Framework JS - 'bus/static/src/js/*.js', - 'bus/static/src/js/services/bus_service.js', - 'bus/static/src/js/services/legacy/legacy_bus_service.js', + 'bus/static/src/*.js', + 'bus/static/src/services/bus_service.js', + 'bus/static/src/services/legacy/make_bus_service_to_legacy_env.js', 'web/static/lib/luxon/luxon.js', 'web/static/src/core/**/*', # FIXME: debug menu currently depends on webclient, once it doesn't we don't need to remove the contents of the debug folder diff --git a/addons/im_livechat/static/src/public/main.js b/addons/im_livechat/static/src/public/main.js index 0638886f74e..159711679f4 100644 --- a/addons/im_livechat/static/src/public/main.js +++ b/addons/im_livechat/static/src/public/main.js @@ -22,5 +22,6 @@ const messagingValuesService = { const serviceRegistry = registry.category('services'); serviceRegistry.add('messaging', messagingService); serviceRegistry.add('messagingValues', messagingValuesService); -serviceRegistry.add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv(owl.Component.env)); serviceRegistry.add('public_livechat_service', publicLivechatService); + +registry.category('wowlToLegacyServiceMappers').add('make_messaging_to_legacy_env', makeMessagingToLegacyEnv); diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index d58739e6331..07b0c2f512a 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -95,9 +95,9 @@ 'mail/static/src/services/*.js', 'mail/static/src/utils/*.js', # Framework JS - 'bus/static/src/js/*.js', - 'bus/static/src/js/services/bus_service.js', - 'bus/static/src/js/services/legacy/legacy_bus_service.js', + 'bus/static/src/*.js', + 'bus/static/src/services/bus_service.js', + 'bus/static/src/services/legacy/make_bus_service_to_legacy_env.js', 'web/static/lib/luxon/luxon.js', 'web/static/src/core/**/*', # FIXME: debug menu currently depends on webclient, once it doesn't we don't need to remove the contents of the debug folder diff --git a/addons/mail/static/src/components/notification_request/notification_request.js b/addons/mail/static/src/components/notification_request/notification_request.js index 1730860a0e1..f4ba5154762 100644 --- a/addons/mail/static/src/components/notification_request/notification_request.js +++ b/addons/mail/static/src/components/notification_request/notification_request.js @@ -31,7 +31,7 @@ export class NotificationRequest extends Component { _handleResponseNotificationPermission(value) { this.messaging.refreshIsNotificationPermissionDefault(); if (value !== 'granted') { - this.env.services['legacy_bus_service'].sendNotification({ + this.env.services['bus_service'].sendNotification({ message: this.env._t("Odoo will not have the permission to send native notifications on this device."), title: this.env._t("Permission denied"), }); diff --git a/addons/mail/static/src/models/messaging.js b/addons/mail/static/src/models/messaging.js index 09507419d40..730805d4ed2 100644 --- a/addons/mail/static/src/models/messaging.js +++ b/addons/mail/static/src/models/messaging.js @@ -18,8 +18,8 @@ registerModel({ odoo.__DEBUG__.messaging = this; }, _willDelete() { - if (this.env.services['legacy_bus_service']) { - this.env.services['legacy_bus_service'].off('window_focus', null, this._handleGlobalWindowFocus); + if (this.env.services['bus_service']) { + this.env.services['bus_service'].off('window_focus', null, this._handleGlobalWindowFocus); } delete odoo.__DEBUG__.messaging; }, @@ -29,7 +29,7 @@ registerModel({ * Starts messaging and related records. */ async start() { - this.env.services['legacy_bus_service'].on('window_focus', null, this._handleGlobalWindowFocus); + this.env.services['bus_service'].on('window_focus', null, this._handleGlobalWindowFocus); await this.initializer.start(); if (!this.exists()) { return; diff --git a/addons/mail/static/src/models/messaging_notification_handler.js b/addons/mail/static/src/models/messaging_notification_handler.js index 078e63797fe..c04a69afe47 100644 --- a/addons/mail/static/src/models/messaging_notification_handler.js +++ b/addons/mail/static/src/models/messaging_notification_handler.js @@ -16,9 +16,9 @@ registerModel({ identifyingFields: ['messaging'], lifecycleHooks: { _willDelete() { - if (this.env.services['legacy_bus_service']) { - this.env.services['legacy_bus_service'].off('notification'); - this.env.services['legacy_bus_service'].stopPolling(); + if (this.env.services['bus_service']) { + this.env.services['bus_service'].off('notification'); + this.env.services['bus_service'].stopPolling(); } }, }, @@ -28,8 +28,8 @@ registerModel({ * the current users. This includes pinned channels for instance. */ start() { - this.env.services.legacy_bus_service.onNotification(null, notifs => this._handleNotifications(notifs)); - this.env.services.legacy_bus_service.startPolling(); + this.env.services['bus_service'].onNotification(null, notifs => this._handleNotifications(notifs)); + this.env.services['bus_service'].startPolling(); }, /** * @private @@ -269,7 +269,7 @@ registerModel({ channel.correspondent === this.messaging.partnerRoot ); if (!isChatWithOdooBot) { - const isOdooFocused = this.env.services['legacy_bus_service'].isOdooFocused(); + const isOdooFocused = this.env.services['bus_service'].isOdooFocused(); // Notify if out of focus if (!isOdooFocused && channel.isChatChannel) { this._notifyNewChannelMessageWhileOutOfFocus({ @@ -620,7 +620,7 @@ registerModel({ // then open a chat for the current user with the new user. const message = sprintf(this.env._t('%s connected'), username); const title = this.env._t("This is their first connection. Wish them luck."); - this.env.services['legacy_bus_service'].sendNotification({ message, title, type: 'info' }); + this.env.services['bus_service'].sendNotification({ message, title, type: 'info' }); const chat = await this.messaging.getChat({ partnerId }); if (!this.exists() || !chat || this.messaging.device.isSmall) { return; @@ -662,7 +662,7 @@ registerModel({ const notificationContent = escape( htmlToTextContentInline(message.body).substr(0, PREVIEW_MSG_MAX_SIZE) ); - this.env.services['legacy_bus_service'].sendNotification({ + this.env.services['bus_service'].sendNotification({ message: notificationContent, title: notificationTitle, type: 'info', diff --git a/addons/mail/static/src/public/discuss_public_boot.js b/addons/mail/static/src/public/discuss_public_boot.js index 51c53f3b2c2..dbccc898c75 100644 --- a/addons/mail/static/src/public/discuss_public_boot.js +++ b/addons/mail/static/src/public/discuss_public_boot.js @@ -1,7 +1,7 @@ /** @odoo-module **/ import { data } from 'mail.discuss_public_channel_template'; -import { makeMessagingToLegacyEnv } from '@mail/utils/make_messaging_to_legacy_env'; +import { messagingToLegacyEnv } from '@mail/utils/make_messaging_to_legacy_env'; import { DialogManagerContainer } from '@mail/components/dialog_manager_container/dialog_manager_container'; import { DiscussPublicViewContainer } from '@mail/components/discuss_public_view_container/discuss_public_view_container'; @@ -46,11 +46,12 @@ Component.env = legacyEnv; serviceRegistry.add('legacy_notification', makeLegacyNotificationService(Component.env)); serviceRegistry.add('legacy_crash_manager', makeLegacyCrashManagerService(Component.env)); serviceRegistry.add('legacy_dialog_mapping', makeLegacyDialogMappingService(Component.env)); - serviceRegistry.add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv(Component.env)); serviceRegistry.add('messaging', messagingService); serviceRegistry.add('messagingValues', messagingValuesService); + registry.category('wowlToLegacyServiceMappers').add('make_messaging_to_legacy_env', messagingToLegacyEnv); + const mainComponentsRegistry = registry.category('main_components'); mainComponentsRegistry.add('DiscussPublicViewContainer', { Component: DiscussPublicViewContainer }); // needed by the attachment viewer diff --git a/addons/mail/static/src/services/messaging_service.js b/addons/mail/static/src/services/messaging_service.js index 499d7a3906b..30ce2a7c53e 100644 --- a/addons/mail/static/src/services/messaging_service.js +++ b/addons/mail/static/src/services/messaging_service.js @@ -5,7 +5,7 @@ import { ModelManager } from '@mail/model/model_manager'; export const messagingService = { dependencies: [ 'effect', - 'legacy_bus_service', + 'bus_service', 'localization', 'messagingValues', 'orm', diff --git a/addons/mail/static/tests/helpers/test_utils.js b/addons/mail/static/tests/helpers/test_utils.js index 435108523cf..4a97e954948 100644 --- a/addons/mail/static/tests/helpers/test_utils.js +++ b/addons/mail/static/tests/helpers/test_utils.js @@ -566,7 +566,6 @@ async function start(param0 = {}) { registerCleanup(async () => { await webClient.env.services.messaging.modelManager.messagingInitializedPromise; webClient.env.services.messaging.modelManager.destroy(); - webClient.env.services.legacy_bus_service.destroy(); delete webClient.env.services.messaging; delete owl.Component.env.services.messaging; delete owl.Component.env[wowlServicesSymbol].messaging; diff --git a/addons/mail/static/tests/helpers/webclient_setup.js b/addons/mail/static/tests/helpers/webclient_setup.js index f8dfd8d31b1..b6f7d3aa3df 100644 --- a/addons/mail/static/tests/helpers/webclient_setup.js +++ b/addons/mail/static/tests/helpers/webclient_setup.js @@ -1,8 +1,7 @@ /** @odoo-module **/ -import { legacyBusService } from '@bus/js/services/legacy/legacy_bus_service'; - -import BusService from 'bus.BusService'; +import { busService } from '@bus/services/bus_service'; +import { makeBusServiceToLegacyEnv } from '@bus/services/legacy/make_bus_service_to_legacy_env'; import { ChatWindowManagerContainer } from '@mail/components/chat_window_manager_container/chat_window_manager_container'; import { DialogManagerContainer } from '@mail/components/dialog_manager_container/dialog_manager_container'; @@ -16,10 +15,6 @@ import { registry } from '@web/core/registry'; import { patchWithCleanup } from "@web/../tests/helpers/utils"; import { createWebClient } from "@web/../tests/webclient/helpers"; -import AbstractStorageService from 'web.AbstractStorageService'; -import RamStorage from 'web.RamStorage'; -import LegacyRegistry from 'web.Registry'; - const WEBCLIENT_LOAD_ROUTES = [ '/web/webclient/load_menus', '/web/dataset/call_kw/res.users/load_views', @@ -46,7 +41,6 @@ const SERVICES_PARAMETER_NAMES = new Set([ * * @param {Object} param0 * @param {Object} [param0.services] - * @param {Object} [param0.legacyServices] * @param {number} [param0.loadingBaseDelayDuration=0] * @param {Promise} [param0.messagingBeforeCreationDeferred=Promise.resolve()] * Deferred that let tests block messaging creation and simulate resolution. @@ -58,7 +52,6 @@ const SERVICES_PARAMETER_NAMES = new Set([ * to the webClient as a legacy parameter. */ function setupMessagingServiceRegistries({ - legacyServices, loadingBaseDelayDuration = 0, messagingBeforeCreationDeferred = Promise.resolve(), messagingBus, @@ -66,20 +59,6 @@ function setupMessagingServiceRegistries({ testSetupDoneDeferred, }) { const serviceRegistry = registry.category('services'); - const legacyServiceRegistry = new LegacyRegistry(); - legacyServices = { - bus_service: BusService.extend({ - _beep() {}, // Do nothing - _poll() {}, // Do nothing - _registerWindowUnload() {}, // Do nothing - isOdooFocused() { - return true; - }, - updateOption() {}, - }), - local_storage: AbstractStorageService.extend({ storage: new RamStorage() }), - ...legacyServices, - }; patchWithCleanup(messagingService, { async _startModelManager() { @@ -101,31 +80,36 @@ function setupMessagingServiceRegistries({ } }; + const customBusService = { + start() { + const originalService = busService.start(...arguments); + Object.assign(originalService, { + _beep() {}, // Do nothing + _poll() {}, // Do nothing + _registerWindowUnload() {}, // Do nothing + isOdooFocused() { + return true; + }, + updateOption() {}, + }); + return originalService; + }, + }; + services = { + bus_service: customBusService, + messaging: messagingService, messagingValues, + systrayService, ...services, }; - // during tests, we need to wait for the legacyBusService to be added before adding the messaging - // service to the registry or it will result in an error during the webClient creation. - const addMessagingToRegistryFn = ev => { - const { key, operation } = ev.detail; - if (key === 'legacy_bus_service' && operation === 'add') { - serviceRegistry.add('messaging', messagingService); - serviceRegistry.add('systray_service', systrayService); - serviceRegistry.add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv(owl.Component.env)); - serviceRegistry.removeEventListener('UPDATE', addMessagingToRegistryFn); - } - }; - serviceRegistry.addEventListener('UPDATE', addMessagingToRegistryFn); Object.entries(services).forEach(([serviceName, service]) => { serviceRegistry.add(serviceName, service); }); - Object.entries(legacyServices).forEach(([serviceName, service]) => { - legacyServiceRegistry.add(serviceName, service); - }); - return legacyServiceRegistry; + registry.category('wowlToLegacyServiceMappers').add('bus_service_to_legacy_env', makeBusServiceToLegacyEnv); + registry.category('wowlToLegacyServiceMappers').add('messaging_service_to_legacy_env', makeMessagingToLegacyEnv); } /** @@ -135,8 +119,6 @@ function setupMessagingServiceRegistries({ * @param {Object} param0 * @param {Object} [param0.serverData] * @param {Object} [param0.services] - * @param {Object} [param0.legacyServices] - * @param {Object} [param0.legacyParams={}] * @param {Object} [param0.loadingBaseDelayDuration] * @param {Object} [param0.messagingBeforeCreationDeferred] * @param {Promise} [param0.testSetupDoneDeferred] The Promise to whose revolution has to be @@ -154,7 +136,7 @@ function setupMessagingServiceRegistries({ servicesParameters[parameterName] = value; } } - const legacyServiceRegistry = setupMessagingServiceRegistries(servicesParameters); + setupMessagingServiceRegistries(servicesParameters); const webClientParameters = {}; for (const [parameterName, value] of param0Entries) { @@ -162,15 +144,7 @@ function setupMessagingServiceRegistries({ webClientParameters[parameterName] = value; } } - webClientParameters.legacyParams = { - legacyServicesToDeployInWowlEnv: { - legacy_bus_service: legacyBusService, - }, - serviceRegistry: legacyServiceRegistry, - ...webClientParameters.legacyParams, - }; - const webClient = await createWebClient(webClientParameters); - return webClient; + return createWebClient(webClientParameters); } export { diff --git a/addons/mail/static/tests/qunit_suite_tests/components/discuss_tests.js b/addons/mail/static/tests/qunit_suite_tests/components/discuss_tests.js index d20fe1d44fe..b4a2269331c 100644 --- a/addons/mail/static/tests/qunit_suite_tests/components/discuss_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/components/discuss_tests.js @@ -1,7 +1,5 @@ /** @odoo-module **/ -import BusService from 'bus.BusService'; - import { afterNextRender, isScrolledToBottom, @@ -14,6 +12,7 @@ import { makeFakeNotificationService } from "@web/../tests/helpers/mock_services import { destroy } from '@web/../tests/helpers/utils'; import { makeTestPromise, file } from 'web.test_utils'; +import { busService } from '@bus/services/bus_service'; const { createFile, inputFiles } = file; @@ -3561,15 +3560,23 @@ QUnit.test('receive new chat message: out of odoo focus (notification, channel)' const pyEnv = await startServer(); const mailChannelId1 = pyEnv['mail.channel'].create({ channel_type: 'chat' }); - const { env, openDiscuss } = await start({ - legacyServices: { - bus_service: BusService.extend({ + const customBusService = { + ...busService, + start() { + const originalBusService = busService.start(...arguments); + Object.assign(originalBusService, { _beep() {}, // Do nothing _poll() {}, // Do nothing _registerWindowUnload() {}, // Do nothing isOdooFocused: () => false, updateOption() {}, - }), + }); + return originalBusService; + }, + }; + const { env, openDiscuss } = await start({ + services: { + bus_service: customBusService, }, }); await openDiscuss(); @@ -3599,15 +3606,23 @@ QUnit.test('receive new chat message: out of odoo focus (notification, chat)', a const pyEnv = await startServer(); const mailChannelId1 = pyEnv['mail.channel'].create({ channel_type: "chat" }); - const { env, openDiscuss } = await start({ - legacyServices: { - bus_service: BusService.extend({ + const customBusService = { + ...busService, + start() { + const originalBusService = busService.start(...arguments); + Object.assign(originalBusService, { _beep() {}, // Do nothing _poll() {}, // Do nothing _registerWindowUnload() {}, // Do nothing isOdooFocused: () => false, updateOption() {}, - }), + }); + return originalBusService; + }, + }; + const { env, openDiscuss } = await start({ + services: { + bus_service: customBusService, }, }); await openDiscuss(); @@ -3641,15 +3656,23 @@ QUnit.test('receive new chat messages: out of odoo focus (tab title)', async fun { channel_type: 'chat', public: 'private' }, { channel_type: 'chat', public: 'private' }, ]); - const { env, openDiscuss } = await start({ - legacyServices: { - bus_service: BusService.extend({ + const customBusService = { + ...busService, + start() { + const originalBusService = busService.start(...arguments); + Object.assign(originalBusService, { _beep() {}, // Do nothing _poll() {}, // Do nothing _registerWindowUnload() {}, // Do nothing isOdooFocused: () => false, updateOption() {}, - }), + }); + return originalBusService; + }, + }; + const { env, openDiscuss } = await start({ + services: { + bus_service: customBusService, }, }); await openDiscuss(); diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index 7a0a2137bde..82c646e007a 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -112,7 +112,7 @@ ('include', 'web.assets_backend'), # Remove Longpolling bus and packages needed this bus - ('remove', 'bus/static/src/js/services/assets_watchdog_service.js'), + ('remove', 'bus/static/src/services/assets_watchdog_service.js'), ('remove', 'mail/static/src/services/messaging_service.js'), ('remove', 'mail/static/src/components/dialog_manager/dialog_manager.js'), diff --git a/addons/web/static/src/core/browser/browser.js b/addons/web/static/src/core/browser/browser.js index eb341750fa5..a43a27da704 100644 --- a/addons/web/static/src/core/browser/browser.js +++ b/addons/web/static/src/core/browser/browser.js @@ -73,6 +73,9 @@ export function makeRAMLocalStorage() { return { setItem(key, value) { store[key] = value; + window.dispatchEvent( + new StorageEvent('storage', { key, newValue: value }) + ); }, getItem(key) { return store[key]; @@ -82,6 +85,9 @@ export function makeRAMLocalStorage() { }, removeItem(key) { delete store[key]; + window.dispatchEvent( + new StorageEvent('storage', { key, newValue: null }) + ); }, get length() { return Object.keys(store).length; diff --git a/addons/web/static/src/legacy/js/core/ajax.js b/addons/web/static/src/legacy/js/core/ajax.js index 80a45c02eb0..27d7eebbd67 100644 --- a/addons/web/static/src/legacy/js/core/ajax.js +++ b/addons/web/static/src/legacy/js/core/ajax.js @@ -34,15 +34,13 @@ function _genericJsonRpc (fct_name, params, settings, fct) { var result = xhr.then(function(result) { core.bus.trigger('rpc:result', data, result); if (result.error !== undefined) { - if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") { - console.debug( - "Server application error\n", - "Error code:", result.error.code, "\n", - "Error message:", result.error.message, "\n", - "Error data message:\n", result.error.data.message, "\n", - "Error data debug:\n", result.error.data.debug - ); - } + console.debug( + "Server application error\n", + "Error code:", result.error.code, "\n", + "Error message:", result.error.message, "\n", + "Error data message:\n", result.error.data.message, "\n", + "Error data debug:\n", result.error.data.debug + ); return Promise.reject({type: "server", error: result.error}); } else { return result.result; diff --git a/addons/web/static/src/legacy/js/public/public_root.js b/addons/web/static/src/legacy/js/public/public_root.js index 0d1c41b1884..43800b5d713 100644 --- a/addons/web/static/src/legacy/js/public/public_root.js +++ b/addons/web/static/src/legacy/js/public/public_root.js @@ -377,6 +377,10 @@ export async function createPublicRoot(RootWidget) { serviceRegistry.add("legacy_notification", makeLegacyNotificationService(legacyEnv)); serviceRegistry.add("legacy_dialog_mapping", makeLegacyDialogMappingService(legacyEnv)); serviceRegistry.add("legacy_rainbowman_service", makeLegacyRainbowManService(legacyEnv)); + const wowlToLegacyServiceMappers = registry.category('wowlToLegacyServiceMappers').getEntries(); + for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) { + serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv)); + } await Promise.all([whenReady(), session.is_bound]); // Patch browser.fetch and the rpc service to use the correct base url when diff --git a/addons/web/static/src/legacy/legacy_setup.js b/addons/web/static/src/legacy/legacy_setup.js index 74db828eb04..ea3cc60e0aa 100644 --- a/addons/web/static/src/legacy/legacy_setup.js +++ b/addons/web/static/src/legacy/legacy_setup.js @@ -46,6 +46,10 @@ export const legacySetupProm = new Promise((resolve) => { const legacyCommandService = makeLegacyCommandService(legacyEnv); serviceRegistry.add("legacy_command", legacyCommandService); serviceRegistry.add("legacy_dropdown", makeLegacyDropdownService(legacyEnv)); + const wowlToLegacyServiceMappers = registry.category('wowlToLegacyServiceMappers').getEntries(); + for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) { + serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv)); + } await Promise.all([whenReady(), session.is_bound]); legacyEnv.templates = session.owlTemplates; legacySetupResolver(legacyEnv); diff --git a/addons/web/static/tests/helpers/mock_env.js b/addons/web/static/tests/helpers/mock_env.js index f2c76adeafa..7433fe0e098 100644 --- a/addons/web/static/tests/helpers/mock_env.js +++ b/addons/web/static/tests/helpers/mock_env.js @@ -53,6 +53,7 @@ function prepareRegistriesWithCleanup() { cloneRegistryWithCleanup(registry.category("command_setup")); cloneRegistryWithCleanup(registry.category("view_widgets")); cloneRegistryWithCleanup(registry.category("fields")); + cloneRegistryWithCleanup(registry.category('wowlToLegacyServiceMappers')); cloneRegistryWithCleanup(registry.category("main_components")); cloneRegistryWithCleanup(registry.category("fields")); @@ -63,6 +64,7 @@ function prepareRegistriesWithCleanup() { clearRegistryWithCleanup(registry.category("error_dialogs")); clearRegistryWithCleanup(registry.category("favoriteMenu")); clearRegistryWithCleanup(registry.category("ir.actions.report handlers")); + clearRegistryWithCleanup(registry.category('wowlToLegacyServiceMappers')); clearRegistryWithCleanup(registry.category("services")); clearServicesMetadataWithCleanup(); diff --git a/addons/web/static/tests/helpers/mock_services.js b/addons/web/static/tests/helpers/mock_services.js index 6f4adbc0073..bd38caadec1 100644 --- a/addons/web/static/tests/helpers/mock_services.js +++ b/addons/web/static/tests/helpers/mock_services.js @@ -12,6 +12,7 @@ import { registerCleanup } from "./cleanup"; import { patchWithCleanup } from "./utils"; import { companyService } from "@web/webclient/company_service"; import { uiService } from "@web/core/ui/ui_service"; +import { ConnectionAbortedError } from "../../src/core/network/rpc_service"; const { Component, status } = owl; @@ -60,7 +61,16 @@ export function makeFakeRPCService(mockRPC) { return { name: "rpc", start() { - return buildMockRPC(mockRPC); + const rpcService = buildMockRPC(mockRPC); + return function () { + let rejectFn; + const rpcProm = new Promise((resolve, reject) => { + rejectFn = reject; + resolve(rpcService(...arguments)); + }); + rpcProm.abort = () => rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort")); + return rpcProm; + }; }, specializeForComponent: rpcService.specializeForComponent, }; diff --git a/addons/web/static/tests/setup.js b/addons/web/static/tests/setup.js index 2dd44f517a8..2e61d6c24a4 100644 --- a/addons/web/static/tests/setup.js +++ b/addons/web/static/tests/setup.js @@ -117,6 +117,8 @@ function makeMockLocation(hasListeners = () => true) { function patchBrowserWithCleanup() { const originalAddEventListener = browser.addEventListener; const originalRemoveEventListener = browser.removeEventListener; + const originalSetTimeout = browser.setTimeout; + const originalClearTimeout = browser.clearTimeout; let hasHashChangeListeners = false; const mockLocation = makeMockLocation(() => hasHashChangeListeners); @@ -134,6 +136,15 @@ function patchBrowserWithCleanup() { originalRemoveEventListener(...arguments); }); }, + // patch setTimeout to automatically remove timeouts bound (via + // browser.setTimeout) during a test (e.g. during the deployment of a service) + setTimeout() { + const timeout = originalSetTimeout(...arguments); + registerCleanup(() => { + originalClearTimeout(timeout); + }); + return timeout; + }, navigator: { userAgent: browser.navigator.userAgent.replace(/\([^)]*\)/, "(X11; Linux x86_64)"), }, diff --git a/addons/web/static/tests/webclient/helpers.js b/addons/web/static/tests/webclient/helpers.js index a60905a77c1..7451b1a7360 100644 --- a/addons/web/static/tests/webclient/helpers.js +++ b/addons/web/static/tests/webclient/helpers.js @@ -194,10 +194,10 @@ export async function addLegacyMockEnvironment(env, legacyParams = {}) { const legacyActionManagerService = makeLegacyActionManagerService(legacyEnv); serviceRegistry.add("legacy_action_manager", legacyActionManagerService); serviceRegistry.add("legacy_notification", makeLegacyNotificationService(legacyEnv)); - // deploy the legacyServices given in parameter in WowlEnv - const { legacyServicesToDeployInWowlEnv } = legacyParams; - for (const legacyServiceName in legacyServicesToDeployInWowlEnv) { - serviceRegistry.add(legacyServiceName, legacyServicesToDeployInWowlEnv[legacyServiceName]); + // deploy wowl services into the legacy env. + const wowlToLegacyServiceMappers = registry.category('wowlToLegacyServiceMappers').getEntries(); + for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) { + serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv)); } // patch DebouncedField delay const debouncedField = basicFields.DebouncedField;