diff --git a/addons/mrp_subcontracting/__manifest__.py b/addons/mrp_subcontracting/__manifest__.py index bce3ddf5d14..15ca6738e0c 100644 --- a/addons/mrp_subcontracting/__manifest__.py +++ b/addons/mrp_subcontracting/__manifest__.py @@ -68,7 +68,7 @@ 'web/static/lib/luxon/luxon.js', 'web/static/lib/owl/owl.js', 'web/static/lib/owl/odoo_module.js', - 'web/static/src/owl2_compatibility/*.js', + 'web/static/src/libs/owl.js', 'web/static/src/legacy/js/component_extension.js', 'web/static/src/legacy/legacy_component.js', 'web/static/lib/jquery/jquery.js', diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index ecf5455253e..6e8495984d8 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -154,7 +154,7 @@ ('include', 'point_of_sale._assets_pos'), # dependencies of web.tests_assets (in the web tests, these come from assets_backend) 'web/static/tests/patch_translations.js', - 'web/static/src/owl2_compatibility/app.js', + 'web/static/src/libs/owl.js', 'web/static/lib/jquery/jquery.js', 'web/static/lib/jquery/jquery.browser.js', 'web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js', diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index 9d1265950c0..39d55e7ca7c 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -119,7 +119,7 @@ 'web/static/lib/luxon/luxon.js', 'web/static/lib/owl/owl.js', 'web/static/lib/owl/odoo_module.js', - 'web/static/src/owl2_compatibility/*.js', + 'web/static/src/libs/owl.js', 'web/static/src/legacy/js/component_extension.js', 'web/static/src/legacy/legacy_component.js', 'web/static/lib/jquery/jquery.js', diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index 11f1a401e70..6d388ba30e0 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -212,7 +212,7 @@ This module provides the core of the Odoo Web Client. 'web/static/lib/moment/moment.js', 'web/static/lib/owl/owl.js', 'web/static/lib/owl/odoo_module.js', - 'web/static/src/owl2_compatibility/*.js', + 'web/static/src/libs/owl.js', 'web/static/src/legacy/js/component_extension.js', 'web/static/src/legacy/legacy_component.js', 'web/static/lib/jquery/jquery.js', @@ -388,7 +388,7 @@ This module provides the core of the Odoo Web Client. # libs 'web/static/lib/luxon/luxon.js', 'web/static/lib/owl/owl.js', - 'web/static/src/owl2_compatibility/*.js', + 'web/static/src/libs/owl.js', 'web/static/lib/owl/odoo_module.js', # core 'web/static/src/env.js', @@ -456,7 +456,7 @@ This module provides the core of the Odoo Web Client. # remove this bundle alongside the owl2 compatibility layer 'web.assets_backend_tests': [ ('include', 'web.assets_backend'), - ('after', 'web/static/src/owl2_compatibility/app.js', 'web/static/tests/owl2_compatibility_app.js'), + ('after', 'web/static/src/libs/owl.js', 'web/static/tests/owl2_compatibility_app.js'), ], 'web.tests_assets': [ 'web/static/tests/patch_translations.js', diff --git a/addons/web/static/src/legacy/js/public/public_widget.js b/addons/web/static/src/legacy/js/public/public_widget.js index 290e140a82e..8c049dd45d7 100644 --- a/addons/web/static/src/legacy/js/public/public_widget.js +++ b/addons/web/static/src/legacy/js/public/public_widget.js @@ -517,7 +517,8 @@ var RootWidget = PublicWidget.extend({ var defs = [this._super.apply(this, arguments)]; defs.push(this._attachComponents()); - this._getRegistry().on("UPDATE", this, ({ operation, value }) => { + this._getRegistry().addEventListener("UPDATE", ({ detail }) => { + const { operation, value } = detail; if (operation === "add") { this._attachComponent(value); } diff --git a/addons/web/static/src/libs/owl.js b/addons/web/static/src/libs/owl.js new file mode 100644 index 00000000000..4f4833b6989 --- /dev/null +++ b/addons/web/static/src/libs/owl.js @@ -0,0 +1 @@ +owl.App.validateTarget = () => {}; diff --git a/addons/web/static/src/owl2_compatibility/app.js b/addons/web/static/src/owl2_compatibility/app.js deleted file mode 100644 index ebf044f1828..00000000000 --- a/addons/web/static/src/owl2_compatibility/app.js +++ /dev/null @@ -1,36 +0,0 @@ -(function () { - const App = owl.App; - - // templates' code is shared between multiple instances of Apps - // This is useful primarly for the OWL2 to Legacy compatibility layer - // It is also useful for tests. - // The downside of this is that the compilation is done once with the compiling app's - // translate function and attributes. - const sharedTemplates = {}; - - owl.App = class extends App { - constructor(_, config) { - if (!config.test) { - const missingKeys = ["dev", "translateFn", "translatableAttributes"].filter( - (key) => !(key in config) - ); - if (missingKeys.length) { - throw new Error( - `Attempted to create an App without some required key(s) (${missingKeys.join( - ", " - )})` - ); - } - } - super(...arguments); - } - _compileTemplate(name) { - if (!(name in sharedTemplates)) { - sharedTemplates[name] = super._compileTemplate(...arguments); - } - return sharedTemplates[name]; - } - }; - owl.App.sharedTemplates = sharedTemplates; - owl.App.validateTarget = () => {}; -})(); diff --git a/addons/web/static/src/owl2_compatibility/event_target.js b/addons/web/static/src/owl2_compatibility/event_target.js deleted file mode 100644 index f56a99a5cd3..00000000000 --- a/addons/web/static/src/owl2_compatibility/event_target.js +++ /dev/null @@ -1,42 +0,0 @@ -(function () { - const { EventBus } = owl; - - function wrapCallback(owner, callback) { - return (ev) => { - callback.call(owner, ev.detail); - }; - } - - owl.EventBus = class extends EventBus { - constructor(...args) { - super(...args); - this.targetsCallbacks = new Map(); - } - on(type, target, callback) { - if (!this.targetsCallbacks.has(target)) { - this.targetsCallbacks.set(target, {}); - } - callback = wrapCallback(target, callback); - const listeners = this.targetsCallbacks.get(target); - if (!listeners[type]) { - listeners[type] = new Set(); - } - listeners[type].add(callback); - return this.addEventListener(type, callback); - } - off(type, target) { - const listeners = this.targetsCallbacks.get(target); - if (!listeners || !Object.hasOwnProperty.call(listeners, type)) { - return; - } - const cbs = listeners[type]; - for (const callback of cbs) { - this.removeEventListener(type, callback); - } - delete listeners[type]; - if (Object.keys(listeners).length === 0) { - this.targetsCallbacks.delete(target); - } - } - }; -})(); diff --git a/addons/web/static/src/webclient/navbar/navbar.js b/addons/web/static/src/webclient/navbar/navbar.js index c869b476641..a1e0de0f121 100644 --- a/addons/web/static/src/webclient/navbar/navbar.js +++ b/addons/web/static/src/webclient/navbar/navbar.js @@ -10,10 +10,10 @@ import { ErrorHandler } from "@web/core/utils/components"; import { Component, onWillDestroy, - onWillUnmount, useExternalListener, useEffect, useRef, + onWillUnmount, } from "@odoo/owl"; const systrayRegistry = registry.category("systray"); @@ -61,12 +61,12 @@ export class NavBar extends Component { this.render(); }; - systrayRegistry.on("UPDATE", this, renderAndAdapt); - this.env.bus.on("MENUS:APP-CHANGED", this, renderAndAdapt); + systrayRegistry.addEventListener("UPDATE", renderAndAdapt); + this.env.bus.addEventListener("MENUS:APP-CHANGED", renderAndAdapt); onWillUnmount(() => { - systrayRegistry.off("UPDATE", this); - this.env.bus.off("MENUS:APP-CHANGED", this); + systrayRegistry.removeEventListener("UPDATE", renderAndAdapt); + this.env.bus.removeEventListener("MENUS:APP-CHANGED", renderAndAdapt); }); // We don't want to adapt every time we are patched diff --git a/addons/web/static/tests/legacy/owl_compatibility_tests.js b/addons/web/static/tests/legacy/owl_compatibility_tests.js index c980f02a4ed..f664740bdde 100644 --- a/addons/web/static/tests/legacy/owl_compatibility_tests.js +++ b/addons/web/static/tests/legacy/owl_compatibility_tests.js @@ -1583,18 +1583,20 @@ QUnit.module("EventBus"); QUnit.test("unregister multiple listener from the same target", async function (assert) { - const target = Symbol("test"); const bus = new EventBus(); let i = 0; - bus.on("a", target, () => assert.step(`a:${i++}`)); - bus.on("b", target, () => assert.step(`b:${i++}`)); + const a = () => assert.step(`a:${i++}`); + const b = () => assert.step(`b:${i++}`); + + bus.addEventListener("a", a); + bus.addEventListener("b", b); bus.trigger("a"); bus.trigger("b"); - bus.off("a", target); - bus.off("b", target); + bus.removeEventListener("a", a); + bus.removeEventListener("b", b); bus.trigger("a"); bus.trigger("b"); diff --git a/addons/web/static/tests/search/custom_favorite_item_tests.js b/addons/web/static/tests/search/custom_favorite_item_tests.js index d32ee7a3b6e..32b8eca375f 100644 --- a/addons/web/static/tests/search/custom_favorite_item_tests.js +++ b/addons/web/static/tests/search/custom_favorite_item_tests.js @@ -189,7 +189,7 @@ QUnit.module("Search", (hooks) => { Component: TestComponent, searchViewId: false, }); - comp.env.bus.on("CLEAR-CACHES", comp, () => assert.step("CLEAR-CACHES")); + comp.env.bus.addEventListener("CLEAR-CACHES", () => assert.step("CLEAR-CACHES")); assert.verifySteps([]); diff --git a/addons/web/static/tests/search/search_bar_menu_tests.js b/addons/web/static/tests/search/search_bar_menu_tests.js index 86259b9fa94..8368fe888a6 100644 --- a/addons/web/static/tests/search/search_bar_menu_tests.js +++ b/addons/web/static/tests/search/search_bar_menu_tests.js @@ -283,7 +283,7 @@ QUnit.module("Search", (hooks) => { } }, }); - webClient.env.bus.on("CLEAR-CACHES", webClient, () => assert.step("CLEAR-CACHES")); + webClient.env.bus.addEventListener("CLEAR-CACHES", () => assert.step("CLEAR-CACHES")); await doAction(webClient, { name: "Action", res_model: "foo", diff --git a/addons/web/static/tests/setup.js b/addons/web/static/tests/setup.js index a971f47ce11..77e33825c5a 100644 --- a/addons/web/static/tests/setup.js +++ b/addons/web/static/tests/setup.js @@ -68,12 +68,6 @@ function patchOwlApp() { this.destroyed = true; } }, - addTemplate(name) { - registerCleanup(() => { - delete this.constructor.sharedTemplates[name]; - }); - return super.addTemplate(...arguments); - }, }); } @@ -89,82 +83,79 @@ function patchBrowserWithCleanup() { let nextAnimationFrameHandle = 1; const animationFrameHandles = new Set(); const mockLocation = makeMockLocation(() => hasHashChangeListeners); - patchWithCleanup( - browser, - { - // patch addEventListner to automatically remove listeners bound (via - // browser.addEventListener) during a test (e.g. during the deployment of a service) - addEventListener(evName) { - if (evName === "hashchange") { - hasHashChangeListeners = true; - } - originalAddEventListener(...arguments); - registerCleanup(() => { - originalRemoveEventListener(...arguments); - }); + patchWithCleanup(browser, { + // patch addEventListner to automatically remove listeners bound (via + // browser.addEventListener) during a test (e.g. during the deployment of a service) + addEventListener(evName) { + if (evName === "hashchange") { + hasHashChangeListeners = true; + } + originalAddEventListener(...arguments); + registerCleanup(() => { + 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; + }, + // patch setInterval to automatically remove callbacks registered (via + // browser.setInterval) during a test (e.g. during the deployment of a service) + setInterval() { + const interval = originalSetInterval(...arguments); + registerCleanup(() => { + originalClearInterval(interval); + }); + return interval; + }, + navigator: { + mediaDevices: browser.navigator.mediaDevices, + permissions: browser.navigator.permissions, + userAgent: browser.navigator.userAgent.replace(/\([^)]*\)/, "(X11; Linux x86_64)"), + sendBeacon: () => {}, + }, + // in tests, we never want to interact with the real url or reload the page + location: mockLocation, + history: { + pushState(state, title, url) { + mockLocation.assign(url); }, - // 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; + replaceState(state, title, url) { + mockLocation.assign(url); }, - // patch setInterval to automatically remove callbacks registered (via - // browser.setInterval) during a test (e.g. during the deployment of a service) - setInterval() { - const interval = originalSetInterval(...arguments); - registerCleanup(() => { - originalClearInterval(interval); - }); - return interval; - }, - navigator: { - mediaDevices: browser.navigator.mediaDevices, - permissions: browser.navigator.permissions, - userAgent: browser.navigator.userAgent.replace(/\([^)]*\)/, "(X11; Linux x86_64)"), - sendBeacon: () => {}, - }, - // in tests, we never want to interact with the real url or reload the page - location: mockLocation, - history: { - pushState(state, title, url) { - mockLocation.assign(url); - }, - replaceState(state, title, url) { - mockLocation.assign(url); - }, - }, - // in tests, we never want to interact with the real local/session storages. - localStorage: makeRAMLocalStorage(), - sessionStorage: makeRAMLocalStorage(), - // Don't want original animation frames in tests - requestAnimationFrame: (fn) => { - const handle = nextAnimationFrameHandle++; - animationFrameHandles.add(handle); + }, + // in tests, we never want to interact with the real local/session storages. + localStorage: makeRAMLocalStorage(), + sessionStorage: makeRAMLocalStorage(), + // Don't want original animation frames in tests + requestAnimationFrame: (fn) => { + const handle = nextAnimationFrameHandle++; + animationFrameHandles.add(handle); - Promise.resolve().then(() => { - if (animationFrameHandles.has(handle)) { - fn(16); - } - }); - - return handle; - }, - cancelAnimationFrame: (handle) => { - animationFrameHandles.delete(handle); - }, - // BroadcastChannels need to be closed to be garbage collected - BroadcastChannel: class SelfClosingBroadcastChannel extends BroadcastChannel { - constructor() { - super(...arguments); - registerCleanup(() => this.close()); + Promise.resolve().then(() => { + if (animationFrameHandles.has(handle)) { + fn(16); } - }, - } - ); + }); + + return handle; + }, + cancelAnimationFrame: (handle) => { + animationFrameHandles.delete(handle); + }, + // BroadcastChannels need to be closed to be garbage collected + BroadcastChannel: class SelfClosingBroadcastChannel extends BroadcastChannel { + constructor() { + super(...arguments); + registerCleanup(() => this.close()); + } + }, + }); } function patchBodyAddEventListener() { diff --git a/addons/web/static/tests/webclient/loading_indicator_tests.js b/addons/web/static/tests/webclient/loading_indicator_tests.js index 8398efe60fd..3649eb77ad1 100644 --- a/addons/web/static/tests/webclient/loading_indicator_tests.js +++ b/addons/web/static/tests/webclient/loading_indicator_tests.js @@ -156,10 +156,10 @@ QUnit.test("loading indicator doesn't unblock ui if it didn't block it", async ( const env = await makeTestEnv(); const { execRegisteredTimeouts } = mockTimeout(); const ui = env.services.ui; - ui.bus.on("BLOCK", null, () => { + ui.bus.addEventListener("BLOCK", () => { assert.step("block"); }); - ui.bus.on("UNBLOCK", null, () => { + ui.bus.addEventListener("UNBLOCK", () => { assert.step("unblock"); }); await mount(LoadingIndicator, target, { env }); diff --git a/addons/web_editor/__manifest__.py b/addons/web_editor/__manifest__.py index 6578fd77758..ff28843c606 100644 --- a/addons/web_editor/__manifest__.py +++ b/addons/web_editor/__manifest__.py @@ -58,8 +58,7 @@ Odoo Web Editor widget. 'web/static/lib/luxon/luxon.js', 'web/static/lib/owl/owl.js', 'web/static/lib/owl/odoo_module.js', - 'web/static/src/owl2_compatibility/*.js', - 'web/static/src/legacy/js/component_extension.js', + 'web/static/src/libs/owl.js', 'web/static/src/legacy/legacy_component.js', 'web/static/lib/jquery/jquery.js', 'web/static/lib/jquery.ui/jquery-ui.js', diff --git a/addons/website/static/src/components/fullscreen_indication/fullscreen_indication.js b/addons/website/static/src/components/fullscreen_indication/fullscreen_indication.js index da5d1ca8808..15e9a8051c1 100644 --- a/addons/website/static/src/components/fullscreen_indication/fullscreen_indication.js +++ b/addons/website/static/src/components/fullscreen_indication/fullscreen_indication.js @@ -1,12 +1,14 @@ /** @odoo-module **/ +import { useBus } from "@web/core/utils/hooks"; + const { EventBus, Component, xml, useState } = owl; export class FullscreenIndication extends Component { setup() { this.state = useState({ isVisible: false }); - this.props.bus.on('FULLSCREEN-INDICATION-SHOW', this, this.show); - this.props.bus.on('FULLSCREEN-INDICATION-HIDE', this, this.hide); + useBus(this.props.bus, "FULLSCREEN-INDICATION-SHOW", this.show.bind(this)); + useBus(this.props.bus, "FULLSCREEN-INDICATION-HIDE", this.hide.bind(this)); } show() { diff --git a/addons/website/static/src/components/website_loader/website_loader.js b/addons/website/static/src/components/website_loader/website_loader.js index 80a439d3c31..d38ae3870d9 100644 --- a/addons/website/static/src/components/website_loader/website_loader.js +++ b/addons/website/static/src/components/website_loader/website_loader.js @@ -1,6 +1,6 @@ /** @odoo-module **/ -import { useService } from "@web/core/utils/hooks"; +import { useBus, useService } from "@web/core/utils/hooks"; import { sprintf } from "@web/core/utils/strings"; import { _t } from "@web/core/l10n/translation"; const { EventBus, Component, markup, useEffect, useState } = owl; @@ -80,14 +80,15 @@ export class WebsiteLoader extends Component { () => [this.state.isVisible] ); - this.props.bus.on('SHOW-WEBSITE-LOADER', this, (props) => { + useBus(this.props.bus, "SHOW-WEBSITE-LOADER", (ev) => { + const props = ev.detail; this.state.isVisible = true; this.state.title = props && props.title; this.state.showTips = props && props.showTips; this.state.selectedFeatures = props && props.selectedFeatures; this.state.showWaitingMessages = props && props.showWaitingMessages; }); - this.props.bus.on('HIDE-WEBSITE-LOADER', this, () => { + useBus(this.props.bus, "HIDE-WEBSITE-LOADER", () => { for (const key of Object.keys(initialState)) { this.state[key] = initialState[key]; } @@ -97,7 +98,7 @@ export class WebsiteLoader extends Component { // Action needed if the app automatically refreshes or redirects the // page without hiding/removing the WebsiteLoader. This should be // called prior to any refresh/redirect if the loader is still visible. - this.props.bus.on("PREPARE-OUT-WEBSITE-LOADER", this, () => { + useBus(this.props.bus, "PREPARE-OUT-WEBSITE-LOADER", () => { window.removeEventListener("beforeunload", this.showRefreshConfirmation); }); } diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py index a62929e86d2..b2bd35bbfcf 100644 --- a/addons/website_slides/__manifest__.py +++ b/addons/website_slides/__manifest__.py @@ -159,7 +159,7 @@ Featuring 'web/static/lib/luxon/luxon.js', 'web/static/lib/owl/owl.js', 'web/static/lib/owl/odoo_module.js', - 'web/static/src/owl2_compatibility/*.js', + 'web/static/src/libs/owl.js', 'web/static/src/legacy/js/component_extension.js', 'web/static/src/legacy/legacy_component.js', 'web/static/lib/jquery/jquery.js',