From 380550f62fd8418f7a425c08720bc9d3cfc79131 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 4 Mar 2019 10:35:52 +0100 Subject: [PATCH] [IMP] web: don't swallow errors in async handlers This commit adds a 'guardedCatch' function to the Promise API. This function has to be used when we don't want to swallow real errors (crashes), like 'catch' does (i.e. basically all the time in Odoo). When the promise has been rejected because of a real error, the rejected Promises chain bubbles up to the top and triggers the 'unhandledrejection' event (triggered on all browsers thanks to the unhandled-rejection-polyfill lib). We then add a listener on that event to display real errors with the CrashManager. Part of task 1896658 Co-authored-by: Aaron Bohy Co-authored-by: Christophe Matthieu Co-authored-by: Mathieu Duckerts-Antoine Co-authored-by: David Monjoie Co-authored-by: Martin Geubelle Co-authored-by: svs-odoo Co-authored-by: Vincent Schippefilt --- addons/web/static/src/js/promise_extension.js | 22 +++++++++++++++++++ .../static/src/js/services/crash_manager.js | 22 ++++++++++++++++++- addons/web/views/webclient_templates.xml | 1 + 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 addons/web/static/src/js/promise_extension.js diff --git a/addons/web/static/src/js/promise_extension.js b/addons/web/static/src/js/promise_extension.js new file mode 100644 index 00000000000..4758975c712 --- /dev/null +++ b/addons/web/static/src/js/promise_extension.js @@ -0,0 +1,22 @@ +/** + * This file adds a 'guardedCatch' function to the Promise API. This function + * has to be used when we don't want to swallow real errors (crashes), like + * 'catch' does (i.e. basically all the time in Odoo). We only execute the + * 'onRejected' handler if the rejection's reason is not an Error, and we always + * return a rejected Promise to let the rejection bubble up (and trigger the + * 'unhandledrejection' event). + */ + +(function () { + var _catch = Promise.prototype.catch; + Promise.prototype.guardedCatch = function (onRejected) { + return _catch.call(this, function (reason) { + if (!reason || !(reason instanceof Error)) { + if (onRejected) { + onRejected.call(this, reason); + } + } + return Promise.reject(reason); + }); + }; +})(); diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js index 31aa178dc1b..db88d688505 100644 --- a/addons/web/static/src/js/services/crash_manager.js +++ b/addons/web/static/src/js/services/crash_manager.js @@ -20,9 +20,29 @@ var map_title ={ }; var CrashManager = core.Class.extend({ - init: function() { + init: function () { + var self = this; this.active = true; this.isConnected = true; + + // listen to unhandled rejected promises, and throw an error when the + // promise has been rejected due to a crash + window.addEventListener('unhandledrejection', function (ev) { + if (ev.reason && ev.reason instanceof Error) { + var traceback = ev.reason.stack; + self.show_error({ + type: _t("Odoo Client Error"), + message: '', + data: {debug: _t('Traceback:') + "\n" + traceback}, + }); + } else { + // the rejection is not due to an Error, so prevent the browser + // from displaying an 'unhandledrejection' error in the console + ev.stopPropagation(); + ev.stopImmediatePropagation(); + ev.preventDefault(); + } + }); }, enable: function () { this.active = true; diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 5484db9294c..764084dd0ac 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -104,6 +104,7 @@ +