diff --git a/addons/mail/static/src/env/env.js b/addons/mail/static/src/js/main.js
similarity index 97%
rename from addons/mail/static/src/env/env.js
rename to addons/mail/static/src/js/main.js
index ea760ba6e118a5fd93f5928575e51619c8746cd5..95c8c4153b1a71061e56f7104c661cedb7079fc3 100644
--- a/addons/mail/static/src/env/env.js
+++ b/addons/mail/static/src/js/main.js
@@ -1,4 +1,4 @@
-odoo.define('mail/static/src/env/env.js', function (require) {
+odoo.define('mail/static/src/js/main.js', function (require) {
 'use strict';
 
 const ModelManager = require('mail/static/src/model/model_manager.js');
diff --git a/addons/mail/views/assets.xml b/addons/mail/views/assets.xml
index 1abab3c4fd8e17e57d9ed61e37b841b05386bddc..8817822ef48ac3e3e0fdb36ae64d78afd1568f9c 100644
--- a/addons/mail/views/assets.xml
+++ b/addons/mail/views/assets.xml
@@ -108,7 +108,6 @@
                 <script type="text/javascript" src="/mail/static/src/components/thread_textual_typing_status/thread_textual_typing_status.js"></script>
                 <script type="text/javascript" src="/mail/static/src/components/thread_typing_icon/thread_typing_icon.js"></script>
                 <script type="text/javascript" src="/mail/static/src/components/thread_viewer/thread_viewer.js"></script>
-                <script type="text/javascript" src="/mail/static/src/env/env.js"></script>
                 <script type="text/javascript" src="/mail/static/src/model/model_core.js"></script>
                 <script type="text/javascript" src="/mail/static/src/model/model_errors.js"></script>
                 <script type="text/javascript" src="/mail/static/src/model/model_field.js"></script>
@@ -213,6 +212,12 @@
             </xpath>
         </template>
 
+        <template id="assets_backend_prod_only" name="mail prod only assets" inherit_id="web.assets_backend_prod_only">
+            <xpath expr="." position="inside">
+                <script type="text/javascript" src="/mail/static/src/js/main.js"></script>
+            </xpath>
+        </template>
+
         <template id="tests_assets" name="mail_tests_assets" inherit_id="web.tests_assets">
             <xpath expr="." position="inside">
                 <script type="text/javascript" src="/mail/static/src/env/test_env.js"></script>
diff --git a/addons/point_of_sale/static/src/js/main.js b/addons/point_of_sale/static/src/js/main.js
index 3258481198f3b97d2999567a5b70a351bfbb0181..b765e6ccd296774a682387af2394b5e21630e63c 100644
--- a/addons/point_of_sale/static/src/js/main.js
+++ b/addons/point_of_sale/static/src/js/main.js
@@ -1,6 +1,7 @@
 odoo.define('web.web_client', function (require) {
     'use strict';
 
+    const AbstractService = require('web.AbstractService');
     const env = require('web.env');
     const WebClient = require('web.AbstractWebClient');
     const Chrome = require('point_of_sale.Chrome');
@@ -37,6 +38,7 @@ odoo.define('web.web_client', function (require) {
         configureGui({ component: chrome });
     }
 
+    AbstractService.prototype.deployServices(env);
     const webClient = new WebClient();
     startPosApp(webClient);
     return webClient;
diff --git a/addons/point_of_sale/static/tests/unit/helpers/test_main.js b/addons/point_of_sale/static/tests/unit/helpers/test_main.js
index a5473389092c7c72bd975e2974c95b90624a519c..f42e01cbd6e2f42d14179a8fbf69340ece7e69d0 100644
--- a/addons/point_of_sale/static/tests/unit/helpers/test_main.js
+++ b/addons/point_of_sale/static/tests/unit/helpers/test_main.js
@@ -1,6 +1,23 @@
 odoo.define('web.web_client', function (require) {
     // this module is required by the test
+    const { bus } = require('web.core');
     const WebClient = require('web.AbstractWebClient');
+
+    // listen to unhandled rejected promises, and when the rejection is not due
+    // to a crash, prevent the browser from displaying an 'unhandledrejection'
+    // error in the console, which would make tests crash on each Promise.reject()
+    // something similar is done by the CrashManagerService, but by default, it
+    // isn't deployed in tests
+    bus.on('crash_manager_unhandledrejection', this, function (ev) {
+        if (!ev.reason || !(ev.reason instanceof Error)) {
+            ev.stopPropagation();
+            ev.stopImmediatePropagation();
+            ev.preventDefault();
+        }
+    });
+
+    owl.config.mode = "dev";
+
     const webClient = new WebClient();
     return webClient;
 });
diff --git a/addons/web/static/src/js/common_env.js b/addons/web/static/src/js/common_env.js
index 567607d50b501b22cf988454434b28653a047e7e..792c3b966d4f59f6aed44d643037e51e93108e35 100644
--- a/addons/web/static/src/js/common_env.js
+++ b/addons/web/static/src/js/common_env.js
@@ -19,7 +19,7 @@ odoo.define("web.commonEnv", function (require) {
 
     const { jsonRpc } = require("web.ajax");
     const { device, isDebug } = require("web.config");
-    const { bus, serviceRegistry } = require("web.core");
+    const { bus } = require("web.core");
     const rpc = require("web.rpc");
     const session = require("web.session");
     const { _t } = require("web.translation");
@@ -97,40 +97,5 @@ odoo.define("web.commonEnv", function (require) {
         session,
     };
 
-
-    // Deploy services in the env (specializations of AbstractService registered
-    // into the serviceRegistry)
-    const UndeployedServices = Object.assign({}, serviceRegistry.map);
-    function _deployServices() {
-        let done = false;
-        while (!done) {
-            // find a service with no missing dependency
-            const serviceName = Object.keys(UndeployedServices).find(serviceName => {
-                const Service = UndeployedServices[serviceName];
-                return Service.prototype.dependencies.every(depName => {
-                    return env.services[depName];
-                });
-            });
-            if (serviceName) {
-                const Service = UndeployedServices[serviceName];
-                const service = new Service(env);
-                env.services[serviceName] = service;
-                delete UndeployedServices[serviceName];
-                service.start();
-            } else {
-                done = true;
-            }
-        }
-    }
-    serviceRegistry.onAdd((serviceName, Service) => {
-        if (serviceName in env.services || serviceName in UndeployedServices) {
-            throw new Error(`Service ${serviceName} is already loaded.`);
-        }
-        UndeployedServices[serviceName] = Service;
-        _deployServices();
-    });
-    _deployServices();
-
-
     return env;
 });
diff --git a/addons/web/static/src/js/core/abstract_service.js b/addons/web/static/src/js/core/abstract_service.js
index f9ad859fef2a8cb67744bd55a339513e564576c8..5157b5b55f4ee6212ea1e02e4c4278f88b5ad747 100644
--- a/addons/web/static/src/js/core/abstract_service.js
+++ b/addons/web/static/src/js/core/abstract_service.js
@@ -2,6 +2,7 @@ odoo.define('web.AbstractService', function (require) {
 "use strict";
 
 var Class = require('web.Class');
+const { serviceRegistry } = require("web.core");
 var Mixins = require('web.mixins');
 var ServicesMixin = require('web.ServicesMixin');
 
@@ -40,6 +41,50 @@ var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
             this.env.bus.trigger('do-action', payload);
         }
     },
+
+    //--------------------------------------------------------------------------
+    // Static
+    //--------------------------------------------------------------------------
+
+    /**
+     * Deploy services in the env (specializations of AbstractService registered
+     * into the serviceRegistry).
+     *
+     * @static
+     * @param {Object} env
+     */
+    deployServices(env) {
+        const UndeployedServices = Object.assign({}, serviceRegistry.map);
+        function _deployServices() {
+            let done = false;
+            while (!done) {
+                // find a service with no missing dependency
+                const serviceName = Object.keys(UndeployedServices).find(serviceName => {
+                    const Service = UndeployedServices[serviceName];
+                    return Service.prototype.dependencies.every(depName => {
+                        return env.services[depName];
+                    });
+                });
+                if (serviceName) {
+                    const Service = UndeployedServices[serviceName];
+                    const service = new Service(env);
+                    env.services[serviceName] = service;
+                    delete UndeployedServices[serviceName];
+                    service.start();
+                } else {
+                    done = true;
+                }
+            }
+        }
+        serviceRegistry.onAdd((serviceName, Service) => {
+            if (serviceName in env.services || serviceName in UndeployedServices) {
+                throw new Error(`Service ${serviceName} is already loaded.`);
+            }
+            UndeployedServices[serviceName] = Service;
+            _deployServices();
+        });
+        _deployServices();
+    }
 });
 
 return AbstractService;
diff --git a/addons/web/static/src/js/main.js b/addons/web/static/src/js/main.js
index 49b90228ba238094dc9aeb7065a2335bcbcfe431..465ea0d63d460bdf38977028384d47cdeee5b41d 100644
--- a/addons/web/static/src/js/main.js
+++ b/addons/web/static/src/js/main.js
@@ -1,18 +1,20 @@
 odoo.define('web.web_client', function (require) {
     "use strict";
 
+    const AbstractService = require('web.AbstractService');
     const env = require('web.env');
     const session = require("web.session");
     const WebClient = require('web.WebClient');
 
+    // configure the env and set it on Owl Component
     owl.config.mode = env.isDebug() ? "dev" : "prod";
     owl.Component.env = env;
 
-    const webClient = new WebClient();
+    // deploy services in the env
+    AbstractService.prototype.deployServices(env);
 
-    /**
-     * Add the owl templates to the environment and start the web client.
-     */
+    // add the owl templates to the environment and start the web client
+    const webClient = new WebClient();
     async function startWebClient() {
         await session.is_bound;
         env.qweb.addTemplates(session.owlTemplates);
@@ -21,7 +23,6 @@ odoo.define('web.web_client', function (require) {
         webClient.setElement($(document.body));
         webClient.start();
     }
-
     startWebClient();
 
     return webClient;
diff --git a/addons/web/static/src/js/public/public_root_instance.js b/addons/web/static/src/js/public/public_root_instance.js
index e368d421fbe4770a9fafe98ca0418d06ac56301b..a7422a97c26ac1a33f094ba17c3e31344ca22729 100644
--- a/addons/web/static/src/js/public/public_root_instance.js
+++ b/addons/web/static/src/js/public/public_root_instance.js
@@ -1,6 +1,7 @@
 odoo.define('root.widget', function (require) {
 'use strict';
 
+const AbstractService = require('web.AbstractService');
 const env = require('web.public_env');
 var lazyloader = require('web.public.lazyloader');
 var rootData = require('web.public.root');
@@ -11,6 +12,11 @@ var rootData = require('web.public.root');
 owl.config.mode = env.isDebug() ? "dev" : "prod";
 owl.Component.env = env;
 
+/**
+ * Deploy services in the env
+ */
+AbstractService.prototype.deployServices(env);
+
 /**
  * This widget is important, because the tour manager needs a root widget in
  * order to work. The root widget must be a service provider with the ajax
diff --git a/addons/web/static/tests/main_tests.js b/addons/web/static/tests/main_tests.js
index 97d9b051bbbdcc4aa7593babc7f2bdfa38aafd03..30164cea61bcd9e191279598b9f6913cddaf103d 100644
--- a/addons/web/static/tests/main_tests.js
+++ b/addons/web/static/tests/main_tests.js
@@ -3,14 +3,23 @@ odoo.define('web.web_client', async function (require) {
     "use strict";
 
     const session = require("web.session");
-    const WebClient = require('web.WebClient');
+    const { bus } = require('web.core');
 
-    owl.config.mode = "dev";
+    // listen to unhandled rejected promises, and when the rejection is not due
+    // to a crash, prevent the browser from displaying an 'unhandledrejection'
+    // error in the console, which would make tests crash on each Promise.reject()
+    // something similar is done by the CrashManagerService, but by default, it
+    // isn't deployed in tests
+    bus.on('crash_manager_unhandledrejection', this, function (ev) {
+        if (!ev.reason || !(ev.reason instanceof Error)) {
+            ev.stopPropagation();
+            ev.stopImmediatePropagation();
+            ev.preventDefault();
+        }
+    });
 
-    const webClient = new WebClient();
+    owl.config.mode = "dev";
 
     await session.is_bound;
     session.owlTemplates = session.owlTemplates.replace(/t-transition/g, 'transition');
-
-    return webClient;
 });
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index c074328ac60d842fbbcefcba44f66861c86868fa..0323694b5c4c523ef1b89e923a4a0a8543b4fe0b 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -879,6 +879,10 @@
         </t>
     </template>
 
+    <template id="web.assets_backend_prod_only">
+        <script type="text/javascript" src="/web/static/src/js/main.js"></script>
+    </template>
+
     <template id="web.webclient_bootstrap">
         <t t-call="web.layout">
             <t t-set="head_web">
@@ -891,9 +895,8 @@
                 <t t-call-assets="web.assets_backend" t-js="false"/>
                 <t t-call-assets="web.assets_common" t-css="false"/>
                 <t t-call-assets="web.assets_backend" t-css="false"/>
+                <t t-call-assets="web.assets_backend_prod_only" t-css="false"/>
                 <t t-call="web.conditional_assets_tests"/>
-
-                <script type="text/javascript" src="/web/static/src/js/main.js"></script>
             </t>
             <t t-set="head" t-value="head_web + (head or '')"/>
             <t t-set="body_classname" t-value="'o_web_client'"/>
diff --git a/addons/web_editor/static/tests/field_html_tests.js b/addons/web_editor/static/tests/field_html_tests.js
index 52f55653c7266f486900ba4e9a22b02fbdf333e6..21c54266fae2cf4bbf1a2743dedabdd1d81df2ed 100644
--- a/addons/web_editor/static/tests/field_html_tests.js
+++ b/addons/web_editor/static/tests/field_html_tests.js
@@ -176,6 +176,13 @@ QUnit.module('web_editor', {}, function () {
                     '</form>',
                 res_id: 1,
             });
+
+            // Summernote needs a RootWidget to set as parent of the ColorPaletteWidget. In the
+            // tests, there is no RootWidget, so we set it here to the parent of the form view, which
+            // can act as RootWidget, as it will honor rpc requests correctly (to the MockServer).
+            const rootWidget = odoo.__DEBUG__.services['root.widget'];
+            odoo.__DEBUG__.services['root.widget'] = form.getParent();
+
             await testUtils.form.clickEdit(form);
             var $field = form.$('.oe_form_field[name="body"]');
 
@@ -233,6 +240,7 @@ QUnit.module('web_editor', {}, function () {
                 '<p>t<font style="background-color: rgb(0, 255, 255);">oto t</font><font style="" class="bg-o-color-3">oto&nbsp;</font><font class="bg-o-color-3" style="">to</font>to</p><p>tata</p>',
                 "should have rendered the field correctly in edit");
 
+            odoo.__DEBUG__.services['root.widget'] = rootWidget;
             form.destroy();
         });
 
diff --git a/addons/website/static/src/js/content/website_root_instance.js b/addons/website/static/src/js/content/website_root_instance.js
index 657ed16784cee5be41ed018695983ce9f10f37ed..fbbefb03651c0b585c9e99bfbd8248b80f28ca1a 100644
--- a/addons/website/static/src/js/content/website_root_instance.js
+++ b/addons/website/static/src/js/content/website_root_instance.js
@@ -1,9 +1,22 @@
 odoo.define('root.widget', function (require) {
 'use strict';
 
+const AbstractService = require('web.AbstractService');
+const env = require('web.public_env');
 var lazyloader = require('web.public.lazyloader');
 var websiteRootData = require('website.root');
 
+/**
+ * Configure Owl with the public env
+ */
+owl.config.mode = env.isDebug() ? "dev" : "prod";
+owl.Component.env = env;
+
+/**
+ * Deploy services in the env
+ */
+AbstractService.prototype.deployServices(env);
+
 var websiteRoot = new websiteRootData.WebsiteRoot(null);
 return lazyloader.allScriptsLoaded.then(function () {
     return websiteRoot.attachTo(document.body).then(function () {