diff options
| author | Christophe Matthieu <chm@odoo.com> | 2018-09-10 10:26:45 +0200 |
|---|---|---|
| committer | Christophe Matthieu <chm@odoo.com> | 2018-09-10 16:36:43 +0200 |
| commit | dabe5bfbae10de983bd1cbdba38949f17a62562e (patch) | |
| tree | 94731f1082ec869af188a805007bf9409274a4b4 | |
| parent | 1199451606cab7342e34fc9c41a2085b8beb1243 (diff) | |
[FIX] web: ajax must return a promise with abort method
Remove part in service_mixins who hide this error. Queries must be able
to be canceled for better performance. Also in the case of longpolling,
if the request is not canceled, wait for a server message or the maximum
time (30 sec) to add or delete channels.
| -rw-r--r-- | addons/account/static/tests/reconciliation_tests.js | 9 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/ajax.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/service_mixins.js | 10 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/session.js | 9 | ||||
| -rw-r--r-- | addons/web/static/src/js/services/ajax_service.js | 25 | ||||
| -rw-r--r-- | addons/web/static/tests/core/widget_tests.js | 37 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/mock_server.js | 17 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/test_utils.js | 10 |
8 files changed, 95 insertions, 24 deletions
diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 93a8a1818b4..4d5b04bf50f 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -692,6 +692,9 @@ QUnit.module('account', { }], payment_aml_ids: [], new_aml_dicts: []}]], "Should call process_bank_statement_line with ids"); + var def = $.Deferred(); + def.abort = function () {}; + event.data.callback(def); }); // click on reconcile button @@ -734,6 +737,9 @@ QUnit.module('account', { analytic_tag_ids: [[6, null, []]] }]}]], "Should call process_bank_statement_line with ids"); + var def = $.Deferred(); + def.abort = function () {}; + event.data.callback(def); }); // click on validate button @@ -787,6 +793,9 @@ QUnit.module('account', { name: 'SAJ/2014/002 and SAJ/2014/003 : Open balance' }]}]], "Should call process_bank_statement_line with ids"); + var def = $.Deferred(); + def.abort = function () {}; + event.data.callback(def); }); // click on validate button diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js index fa79c65ef45..036af275f4e 100644 --- a/addons/web/static/src/js/core/ajax.js +++ b/addons/web/static/src/js/core/ajax.js @@ -38,7 +38,7 @@ function genericJsonRpc (fct_name, params, settings, fct) { }); // FIXME: jsonp? deferred.abort = function () { - deferred.reject('communication', $.Event(), 'abort', 'abort'); + deferred.reject({message: "XmlHttpRequestError abort"}, $.Event('abort')); if (xhr.abort) { xhr.abort(); } diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index 463b0aef4b1..7ca691cb2ad 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -125,8 +125,14 @@ var ServicesMixin = { */ _rpc: function (params, options) { var query = rpc.buildQuery(params); - var def = this.call('ajax', 'rpc', query.route, query.params, options); - return def ? def.promise() : $.Deferred().promise(); + var def = this.call('ajax', 'rpc', query.route, query.params, options, this); + if (!def && this.isDestroyed()) { + def = $.Deferred(); + def.abort = function () {}; + } + var promise = def.promise(); + promise.abort = def.abort.bind(def); + return promise; }, loadFieldView: function (dataset, view_id, view_type, options) { return this.loadViews(dataset.model, dataset.get_context().eval(), [[view_id, view_type]], options).then(function (result) { diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js index 09b65357404..694285cccdc 100644 --- a/addons/web/static/src/js/core/session.js +++ b/addons/web/static/src/js/core/session.js @@ -344,9 +344,11 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { } }; - return deferred.then(function () { + var promise = deferred.then(function () { if (aborted) { - return $.Deferred().reject('communication', $.Event(), 'abort', 'abort'); + var def = $.Deferred().reject({message: "XmlHttpRequestError abort"}, $.Event('abort')); + def.abort = function () {}; + return def; } // TODO: remove if (! _.isString(url)) { @@ -374,6 +376,9 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { xhrDef = fct(url, "call", params, options); return xhrDef; }); + + promise.abort = deferred.abort; + return promise; }, url: function (path, params) { params = _.extend(params || {}); diff --git a/addons/web/static/src/js/services/ajax_service.js b/addons/web/static/src/js/services/ajax_service.js index 3446f651a59..6c61fdbf9ed 100644 --- a/addons/web/static/src/js/services/ajax_service.js +++ b/addons/web/static/src/js/services/ajax_service.js @@ -8,17 +8,20 @@ var session = require('web.session'); var AjaxService = AbstractService.extend({ name: 'ajax', rpc: function (route, args, options, target) { - return $.Deferred(function (def) { - session.rpc(route, args, options).then(function () { - if (!target.isDestroyed()) { - def.resolve.apply(def, arguments); - } - }, function () { - if (!target.isDestroyed()) { - def.reject.apply(def, arguments); - } - }); - }).promise(); + var def = $.Deferred(); + var promise = def.promise(); + var xhrDef = session.rpc(route, args, options); + promise.abort = xhrDef.abort.bind(xhrDef); + xhrDef.then(function () { + if (!target.isDestroyed()) { + def.resolve.apply(def, arguments); + } + }).fail(function () { + if (!target.isDestroyed()) { + def.reject.apply(def, arguments); + } + }); + return promise; }, }); diff --git a/addons/web/static/tests/core/widget_tests.js b/addons/web/static/tests/core/widget_tests.js index 19e72cb97b6..9510079e380 100644 --- a/addons/web/static/tests/core/widget_tests.js +++ b/addons/web/static/tests/core/widget_tests.js @@ -1,10 +1,12 @@ odoo.define('web.widget_tests', function (require) { "use strict"; +var AjaxService = require('web.AjaxService'); var concurrency = require('web.concurrency'); var core = require('web.core'); var QWeb = require('web.QWeb'); var Widget = require('web.Widget'); +var testUtils = require('web.test_utils'); QUnit.module('core', {}, function () { @@ -377,16 +379,47 @@ QUnit.module('core', {}, function () { }); QUnit.test("calling _rpc on destroyed widgets", function (assert) { - assert.expect(1); + assert.expect(3); + + var def; + var parent = new Widget(); + testUtils.addMockEnvironment(parent, { + session: { + rpc: function () { + def = $.Deferred(); + def.abort = def.reject; + return def; + }, + }, + services: [AjaxService], + }); + var widget = new Widget(parent); + + widget._rpc({route: '/a/route'}).then(function () { + assert.ok(true, "The ajax call should be resolve"); + }); + def.resolve(); + def = null; - var widget = new Widget(); + 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"); + }); widget.destroy(); + def.resolve(); + def = null; + 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(!def, + "The trigger_up is not performed and the call returns a deferred "+ + "never resolved nor rejected"); + assert.ok(true, "there should be no crash when calling _rpc on a destroyed widget"); + parent.destroy(); }); QUnit.test('start is not called when widget is destroyed', function (assert) { diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index 75d99621c4b..6a6dd62f2df 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -110,7 +110,18 @@ var MockServer = Class.extend({ console.log('%c[rpc] request ' + route, 'color: blue; font-weight: bold;', args); args = JSON.parse(JSON.stringify(args)); } - return this._performRpc(route, args).then(function (result) { + var def = this._performRpc(route, args); + + var abort = def.abort || def.reject; + if (abort) { + abort = abort.bind(def); + } else { + abort = function () { + throw new Error("Can't abort this request"); + }; + } + + def = def.then(function (result) { var resultString = JSON.stringify(result || false); if (debug) { console.log('%c[rpc] response' + route, 'color: blue; font-weight: bold;', JSON.parse(resultString)); @@ -123,6 +134,10 @@ var MockServer = Class.extend({ } return $.Deferred().reject(errorString, event || $.Event()); }); + + var promise = def.promise(); + promise.abort = abort; + return promise; }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index 4720d166a4c..beb39687364 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -407,15 +407,15 @@ function addMockEnvironment(widget, params) { var services = {ajax: null}; // mocked ajax service already loaded intercept(widget, 'call_service', function (ev) { var args, result; - if (ev.data.service === 'ajax') { + if (services[ev.data.service]) { + var service = services[ev.data.service]; + args = (ev.data.args || []); + result = service[ev.data.method].apply(service, args); + } else if (ev.data.service === 'ajax') { // ajax service is already mocked by the server var route = ev.data.args[0]; args = ev.data.args[1]; result = mockServer.performRpc(route, args); - } else if (services[ev.data.service]) { - var service = services[ev.data.service]; - args = (ev.data.args || []); - result = service[ev.data.method].apply(service, args); } ev.data.callback(result); }); |
