From bdf0d6e22ec82491b31daeec726d7b678262d331 Mon Sep 17 00:00:00 2001 From: Martin Quinteros Date: Thu, 1 Oct 2026 10:35:19 -0300 Subject: [PATCH] [FIX] mail_ux: reuse the pending initial onchange of the mail composer While the composer dialog is still loading, any overlay change (for example a tooltip shown or hidden) re-renders the overlay container. Owl cancels the pending dialog and builds it again, which sends the same initial onchange once more. Each of those requests renders the template reports, so a single opening can render the PDF several times in parallel and keep the workers busy. An identical initial onchange of mail.compose.message now gets the result of the request already in flight instead of sending a new one. closes ingadhoc/miscellaneous#456 Signed-off-by: Juan Carreras --- mail_ux/__manifest__.py | 1 + mail_ux/static/src/views/composer_onchange.js | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 mail_ux/static/src/views/composer_onchange.js diff --git a/mail_ux/__manifest__.py b/mail_ux/__manifest__.py index 9980c4cb..228404f0 100644 --- a/mail_ux/__manifest__.py +++ b/mail_ux/__manifest__.py @@ -30,6 +30,7 @@ "assets": { "web.assets_backend": [ "mail_ux/static/src/core/common/**/*", + "mail_ux/static/src/views/composer_onchange.js", ], }, "depends": [ diff --git a/mail_ux/static/src/views/composer_onchange.js b/mail_ux/static/src/views/composer_onchange.js new file mode 100644 index 00000000..86a71d54 --- /dev/null +++ b/mail_ux/static/src/views/composer_onchange.js @@ -0,0 +1,27 @@ +import { ORM } from "@web/core/orm_service"; +import { patch } from "@web/core/utils/patch"; + +// The composer dialog is rebuilt when an overlay (e.g. a tooltip) changes while it loads, +// which sends the same initial onchange again: reuse the pending one. +const pendingComposerOnchanges = new Map(); + +patch(ORM.prototype, { + call(model, method, args = [], kwargs = {}) { + const isInitialComposerOnchange = + model === "mail.compose.message" && + method === "onchange" && + Array.isArray(args[2]) && + !args[2].length; + if (!isInitialComposerOnchange) { + return super.call(...arguments); + } + const key = JSON.stringify([args, kwargs]); + let prom = pendingComposerOnchanges.get(key); + if (!prom) { + prom = super.call(...arguments); + pendingComposerOnchanges.set(key, prom); + prom.finally(() => pendingComposerOnchanges.delete(key)).catch(() => {}); + } + return prom.then((result) => structuredClone(result)); + }, +});