From a7a7c150aa9bb6c3e36f3842d6df6f7316fe8e95 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 17 Jan 2023 07:36:48 +0000 Subject: [PATCH] [FIX] web: crash when clicking deleted records in breadcrumbs Here is a way to reproduce the problematic situation: with CRM installed, go to Contacts, open one, clicks on its Opportunities stat button, open one in form view, click on the "Customer" field internal link to open again the contact in form view, delete the record, and finally click on the first occurence of the contact in the breadcrumbs. Before this commit, it crashed because we ended up in an error handler (formSaveErrorHandler) that doesn't deal with the fact that originalError could be falsy. This is handled by the legacyRejectPromiseHandler, but due to the low sequence of the faulty handler, this one was executed after. Same error would occur if you tried to open an url of a record that doesn't exist. This commit fixes the error handler sequences s.t. the crash no longer occurs. Issue reported in the feedback pad after migrating odoo.com to 16.0. closes odoo/odoo#110545 X-original-commit: 8e78efa3dda2484b34422a66d3963c7b20915036 Signed-off-by: Samuel Degueldre Signed-off-by: Aaron Bohy (aab) --- .../legacy/legacy_promise_error_handler.js | 2 +- .../src/legacy/legacy_rpc_error_handler.js | 2 +- .../form_error_dialog/form_error_dialog.js | 2 +- addons/web/static/tests/qunit.js | 4 +- .../webclient/actions/window_action_tests.js | 73 +++++++++++++++++++ 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/legacy/legacy_promise_error_handler.js b/addons/web/static/src/legacy/legacy_promise_error_handler.js index 0fc7cddfeda..0b5eab6c0a1 100644 --- a/addons/web/static/src/legacy/legacy_promise_error_handler.js +++ b/addons/web/static/src/legacy/legacy_promise_error_handler.js @@ -18,7 +18,7 @@ import { registry } from "@web/core/registry"; * @returns {boolean} */ function legacyRejectPromiseHandler(env, error, originalError) { - if (error.name === "UncaughtPromiseError") { + if (error.name.startsWith("UncaughtPromiseError")) { const isLegitError = originalError && originalError instanceof Error; const isLegacyRPC = originalError && originalError.legacy; if (!isLegitError && !isLegacyRPC) { diff --git a/addons/web/static/src/legacy/legacy_rpc_error_handler.js b/addons/web/static/src/legacy/legacy_rpc_error_handler.js index 9321da2fa89..15158225b8d 100644 --- a/addons/web/static/src/legacy/legacy_rpc_error_handler.js +++ b/addons/web/static/src/legacy/legacy_rpc_error_handler.js @@ -44,4 +44,4 @@ function legacyRPCErrorHandler(env, error, originalError) { } return false; } -errorHandlerRegistry.add("legacyRPCErrorHandler", legacyRPCErrorHandler, { sequence: 2 }); +errorHandlerRegistry.add("legacyRPCErrorHandler", legacyRPCErrorHandler, { sequence: 97 }); diff --git a/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.js b/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.js index f08d67ad123..9f9a9a40826 100644 --- a/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.js +++ b/addons/web/static/src/views/form/form_error_dialog/form_error_dialog.js @@ -55,4 +55,4 @@ function formSaveErrorHandler(env, error, originalError) { return true; } } -errorHandlerRegistry.add("formSaveErrorHandler", formSaveErrorHandler, { sequence: 1 }); +errorHandlerRegistry.add("formSaveErrorHandler", formSaveErrorHandler); diff --git a/addons/web/static/tests/qunit.js b/addons/web/static/tests/qunit.js index 62810adc320..a640ba5bb74 100644 --- a/addons/web/static/tests/qunit.js +++ b/addons/web/static/tests/qunit.js @@ -559,8 +559,8 @@ export function setupQUnit() { if (originalError instanceof Error) { originalError.errorEvent = ev; await completeUncaughtError(uncaughtError, originalError); + originalError.stacktrace = uncaughtError.traceback; } - originalError.stacktrace = uncaughtError.traceback; onError(originalError); }); @@ -577,8 +577,8 @@ export function setupQUnit() { if (originalError instanceof Error) { originalError.errorEvent = ev; await completeUncaughtError(uncaughtError, originalError); + originalError.stack = uncaughtError.traceback; } - originalError.stack = uncaughtError.traceback; onUnhandledRejection(originalError); }); } diff --git a/addons/web/static/tests/webclient/actions/window_action_tests.js b/addons/web/static/tests/webclient/actions/window_action_tests.js index d3dbf7f3ad1..2a6d0fe9118 100644 --- a/addons/web/static/tests/webclient/actions/window_action_tests.js +++ b/addons/web/static/tests/webclient/actions/window_action_tests.js @@ -2631,4 +2631,77 @@ QUnit.module("ActionManager", (hooks) => { assert.containsOnce(target, ".o_pivot_view .o_view_sample_data"); }); + + QUnit.test("click on breadcrumb of a deleted record", async function (assert) { + serviceRegistry.add("error", errorService); + // In tests we catch unhandledrejection of promises rejected with something that isn't an + // error (see tests/qunit.js). In this scenario, with the legacy basic_model, the promise + // is rejected with undefined, so without the following lines, we can't reproduce the issue, + // which was that the error handlers were executed in a wrong order, and one of them crashed. + const windowUnhandledReject = window.onunhandledrejection; + window.onunhandledrejection = null; + registerCleanup(() => { + window.onunhandledrejection = windowUnhandledReject; + }); + + const handler = (ev) => { + // need to preventDefault to remove error from console (so python test pass) + ev.preventDefault(); + }; + window.addEventListener("unhandledrejection", handler); + registerCleanup(() => window.removeEventListener("unhandledrejection", handler)); + patchWithCleanup(QUnit, { + onUnhandledRejection: () => {}, + }); + + serverData.views["partner,false,form"] = ` +
+