From 9db9dbd38aab83c4154b2033708eedfcd842c7eb Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 7 Oct 2022 14:35:32 +0000 Subject: [PATCH] [FIX] web(_editor): protect RPCs from component destruction The rpc service has a protection (when used via useService) that prevents the returned promise from being resolved/rejected if the component has been destroyed meanwhile. However, as the FormView still uses the legacy BasicModel, all rpcs done by its model do not go through our new rpc service. As a consequence, before this commit, they did not benefit from the protection. This caused a non deterministic issue in the main flow tour. This commit implements a similar protection as the rpc service, but specialized for the BasicRelationalModel, which redirects all RPCs done by the BasicModel. This change highlighted two scenarios where we sometimes performed RPCs (or could perform RPCs) on a destroyed component. This commit fixes those as well. X-original-commit: f613e916d5e8b465fc360e340094c09037450ff1 Part-of: odoo/odoo#105332 --- .../src/legacy/js/core/service_mixins.js | 5 ++--- .../src/views/basic_relational_model.js | 22 ++++++++++++++++++- .../static/src/views/form/form_controller.js | 1 + .../src/views/view_button/view_button_hook.js | 4 ++-- .../static/src/js/backend/html_field.js | 1 - 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/legacy/js/core/service_mixins.js b/addons/web/static/src/legacy/js/core/service_mixins.js index ab3b6b38d0b..5c30d583545 100644 --- a/addons/web/static/src/legacy/js/core/service_mixins.js +++ b/addons/web/static/src/legacy/js/core/service_mixins.js @@ -141,10 +141,9 @@ var ServicesMixin = { prom.abort = function () {}; } var abort = prom.abort ? prom.abort : prom.reject; - if (!abort) { - throw new Error("a rpc promise should always have a reject function"); + if (abort) { + prom.abort = abort.bind(prom); } - prom.abort = abort.bind(prom); return prom; }, loadFieldView: function (modelName, context, view_id, view_type, options) { diff --git a/addons/web/static/src/views/basic_relational_model.js b/addons/web/static/src/views/basic_relational_model.js index 761fa13d535..5e75e0ed1ad 100644 --- a/addons/web/static/src/views/basic_relational_model.js +++ b/addons/web/static/src/views/basic_relational_model.js @@ -1099,6 +1099,8 @@ export class RelationalModel extends Model { throw "only record root type is supported"; } + this.__component = params.component; + this.root = null; this.__bm__ = new BasicModel(this, { @@ -1320,7 +1322,25 @@ export class RelationalModel extends Model { if (payload.service === "ajax" && payload.method === "rpc") { // ajax service uses an extra 'target' argument for rpc args = args.concat(ev.target); - return payload.callback(owl.Component.env.session.rpc(...args)); + if (owl.status(this.__component) === "destroyed") { + console.warn("Component is destroyed"); + return payload.callback(Promise.resolve()); + } + const prom = new Promise((resolve, reject) => { + owl.Component.env.session + .rpc(...args) + .then((value) => { + if (owl.status(this.__component) !== "destroyed") { + resolve(value); + } + }) + .guardedCatch((reason) => { + if (owl.status(this.__component) !== "destroyed") { + reject(reason); + } + }); + }); + return payload.callback(prom); } else if (payload.service === "notification") { return this.notificationService.add(payload.message, { className: payload.className, diff --git a/addons/web/static/src/views/form/form_controller.js b/addons/web/static/src/views/form/form_controller.js index f0ced090f9f..6fc65afc574 100644 --- a/addons/web/static/src/views/form/form_controller.js +++ b/addons/web/static/src/views/form/form_controller.js @@ -130,6 +130,7 @@ export class FormController extends Component { rootType: "record", mode, beforeLoadProm, + component: this, }, { ignoreUseSampleModel: true, diff --git a/addons/web/static/src/views/view_button/view_button_hook.js b/addons/web/static/src/views/view_button/view_button_hook.js index 8281180c562..f7d194e0bca 100644 --- a/addons/web/static/src/views/view_button/view_button_hook.js +++ b/addons/web/static/src/views/view_button/view_button_hook.js @@ -4,7 +4,7 @@ import { useService } from "@web/core/utils/hooks"; import { evaluateExpr } from "@web/core/py_js/py"; import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; -import { useEnv, useSubEnv } from "@odoo/owl"; +import { status, useEnv, useSubEnv } from "@odoo/owl"; function disableButtons(el) { // WOWL: can we do this non-imperatively? @@ -86,7 +86,7 @@ export function useViewButtons(model, ref, options = {}) { context: params.context || {}, //LPE FIXME new Context(payload.env.context).eval(); buttonContext, onClose: async () => { - if (!closeDialog) { + if (!closeDialog && status(comp) !== "destroyed") { const reload = options.reload || (() => model.root.load()); await reload(); comp.render(true); // FIXME WOWL reactivity diff --git a/addons/web_editor/static/src/js/backend/html_field.js b/addons/web_editor/static/src/js/backend/html_field.js index 7d4356d06aa..e6b14141425 100644 --- a/addons/web_editor/static/src/js/backend/html_field.js +++ b/addons/web_editor/static/src/js/backend/html_field.js @@ -160,7 +160,6 @@ export class HtmlField extends Component { if (this.resizerHandleObserver) { this.resizerHandleObserver.disconnect(); } - this.updateValue(); }); }