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) {

Reply via email to