From b6ce7b136f561f156fb9ed1cc8694da86aff466e Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 7 Apr 2017 10:28:59 +0200 Subject: [PATCH] [IMP] web: protect _rpc calls on destroyed widgets It might happen that some code in a widget (callbacks given to other function calls) is executed after the widget's destruction. For example, trigger an event up with a callback in option, the event is handled by a parent that performs an rpc, meanwhile, the child widget is destroyed, then, the rpc returns and the parent executes the callback. This shouldn't happen, because it means that the callback is executed for nothing. Worst, it crashes if the function _rpc is called inside the callback, because the widget being destroyed, there is no parent to handle the request, and _rpc thus returns undefined. This rev. protects the code from crashing by making _rpc return a pending deferred instead of undefined. --- addons/web/static/src/js/core/mixins.js | 8 +++++--- addons/web/static/test/core/widget_tests.js | 13 +++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/core/mixins.js b/addons/web/static/src/js/core/mixins.js index ae513b0b506..fff2a013bb5 100644 --- a/addons/web/static/src/js/core/mixins.js +++ b/addons/web/static/src/js/core/mixins.js @@ -447,15 +447,17 @@ var ServicesMixin = { return result; }, /** - * Build a RPC query + * Builds and executes RPC query. Returns a deferred's promise resolved with + * the RPC result. * * @param {string} arg1 either a route or a model * @param {string} method if a model is given, this argument is a method - * @returns {RPCBuilder} + * @returns {Deferred's Promise} */ _rpc: function (params, options) { var query = rpc.buildQuery(params); - return this.call('ajax', 'rpc', query.route, query.params, options); + var def = this.call('ajax', 'rpc', query.route, query.params, options); + return def ? def.promise() : $.Deferred().promise(); }, loadFieldView: function (dataset, view_id, view_type, options) { return this.loadViews(dataset.model, dataset.get_context(), [[view_id, view_type]], options).then(function (result) { diff --git a/addons/web/static/test/core/widget_tests.js b/addons/web/static/test/core/widget_tests.js index c0a0012f931..8988002734b 100644 --- a/addons/web/static/test/core/widget_tests.js +++ b/addons/web/static/test/core/widget_tests.js @@ -377,6 +377,19 @@ QUnit.module('core', {}, function () { done(); }); }); + + QUnit.test("calling _rpc on destroyed widgets", function (assert) { + assert.expect(1); + + var widget = new Widget(); + widget.destroy(); + widget._rpc({route: '/a/route'}).always(function () { + throw Error("Calling _rpc on a destroyed widget should return a " + + "deferred that is never resolved nor rejected"); + }); + assert.ok(true, + "there should be no crash when calling _rpc on a destroyed widget"); + }); }); }); \ No newline at end of file