details: https://code.tryton.org/tryton/commit/dbd215ffb071
branch: default
user: Cédric Krier <[email protected]>
date: Tue Aug 25 13:09:25 2026 +0200
description:
Ensure it is still the same record when executing the display callback
of a widget
When a widget display is asynchronous, we must ensure that it is still
the same
record that is being displayed when the callback is finally executed.
Otherwise
it may render the data of a different record in the HTML.
Closes #15041
diffstat:
sao/src/view/form.js | 82 +++++++++++++++++++++++++++++++++++++--------------
1 files changed, 59 insertions(+), 23 deletions(-)
diffs (189 lines):
diff -r e28ffb0ad70b -r dbd215ffb071 sao/src/view/form.js
--- a/sao/src/view/form.js Fri Aug 14 11:55:48 2026 +0200
+++ b/sao/src/view/form.js Tue Aug 25 13:09:25 2026 +0200
@@ -2283,14 +2283,22 @@
Sao.common.selection_mixin.init_selection.call(this, key,
this.set_selection.bind(this));
},
- update_selection: function(record, field, callbak) {
+ update_selection: function(record, field, callback) {
+ let deferred = jQuery.Deferred();
Sao.common.selection_mixin.update_selection.call(this, record,
field, (selection, help) => {
this.set_selection(selection, help);
- if (callbak) {
- callbak(selection, help);
+ let prm;
+ if (callback) {
+ prm = callback(selection, help);
+ }
+ if (prm) {
+ prm.always(deferred.resolve);
+ } else {
+ deferred.resolve();
}
});
+ return deferred;
},
set_selection: function(selection, help) {
var select = this.select;
@@ -2304,9 +2312,13 @@
}
},
display_update_selection: function() {
- var record = this.record;
- var field = this.field;
- this.update_selection(record, field, (selection, help) => {
+ let record = this.record,
+ field = this.field;
+ return this.update_selection(record, field, (selection, help) => {
+ if (record !== this.record) {
+ return;
+ }
+
if (!field) {
this.select.val('');
return;
@@ -2322,7 +2334,7 @@
if (!found) {
prm = Sao.common.selection_mixin.get_inactive_selection
.call(this, value);
- prm.done(inactive => {
+ prm = prm.then(inactive => {
this.select.append(jQuery('<option/>', {
value: JSON.stringify(inactive[0]),
text: inactive[1],
@@ -2332,7 +2344,7 @@
} else {
prm = jQuery.when();
}
- prm.done(() => {
+ return prm.then(() => {
this.select.val(JSON.stringify(value));
var title = help[value] || null;
if (this.attributes.help && title) {
@@ -2344,7 +2356,7 @@
},
display: function() {
Sao.View.Form.Selection._super.display.call(this);
- this.display_update_selection();
+ return this.display_update_selection();
},
focus: function() {
this.select.focus();
@@ -3147,13 +3159,21 @@
this.set_selection.bind(this));
},
update_selection: function(record, field, callback) {
+ let deferred = jQuery.Deferred();
Sao.common.selection_mixin.update_selection.call(this, record,
field, (selection, help) => {
this.set_selection(selection, help);
+ let prm;
if (callback) {
- callback();
+ prm = callback(help);
+ }
+ if (prm) {
+ prm.always(deferred.resolve);
+ } else {
+ deferred.resolve();
}
});
+ return deferred;
},
set_selection: function(selection, help) {
var select = this.select;
@@ -3276,7 +3296,11 @@
}
},
display: function() {
- this.update_selection(this.record, this.field, () => {
+ let record = this.record;
+ return this.update_selection(this.record, this.field, () => {
+ if (record !== this.record) {
+ return;
+ }
Sao.View.Form.Reference._super.display.call(this);
});
},
@@ -3643,13 +3667,17 @@
display: function() {
Sao.View.Form.One2Many._super.display.call(this);
- let display = function() {
+ let record = this.record,
+ field = this.field;
+
+ let display = () => {
+ if (record !== this.record) {
+ return;
+ }
+
this._set_button_sensitive();
- var record = this.record;
- var field = this.field;
-
- if (!field) {
+ if (!field) {
this.screen.new_group();
this.screen.current_record = null;
this.screen.group.parent = null;
@@ -3690,7 +3718,7 @@
.css('max-height', this.attributes.height + 'px');
}
return this.screen.display();
- }.bind(this);
+ };
if (this.prm.state() == 'pending') {
return this.prm.then(() => display());
@@ -4275,9 +4303,13 @@
display: function() {
Sao.View.Form.Many2Many._super.display.call(this);
- let display = function() {
- var record = this.record;
- var field = this.field;
+ let record = this.record,
+ field = this.field;
+
+ let display = () => {
+ if (record !== this.record) {
+ return;
+ }
if (!field) {
this.screen.new_group();
@@ -4297,7 +4329,7 @@
.css('max-height', this.attributes.height + 'px');
}
return this.screen.display();
- }.bind(this);
+ };
if (this.prm.state() == 'pending') {
return this.prm.then(() => display());
@@ -5458,8 +5490,8 @@
_display: function() {
Sao.View.Form.Dict._super.display.call(this);
- var record = this.record;
- var field = this.field;
+ let record = this.record,
+ field = this.field;
if (!field) {
return;
}
@@ -5485,6 +5517,10 @@
prm = jQuery.when();
}
prm.then(() => {
+ if (record !== this.record) {
+ return;
+ }
+
var i, len, key;
var keys = Object.keys(value)
.filter(function(key) {