luci-static: fix race condition when probing features master
authorPaul Donald <newtwen+github@gmail.com>
Wed, 24 Dec 2025 16:34:16 +0000 (17:34 +0100)
committerPaul Donald <newtwen+github@gmail.com>
Wed, 24 Dec 2025 16:34:16 +0000 (17:34 +0100)
follow-up to a2fce95248a260d9471efbff94ad0cde7a90700d

hasSystemFeature() inspects the cached result of
probeSystemFeatures() which needs to have completed.

Once all of the legacy (Lua) stuff is ready, this block
runs:

initDOM() {
...
L.loaded = true;
document.dispatchEvent(new CustomEvent('luci-loaded'));
},

This commit now calls View.load() once system feature
probing has completed, and LuCI itself has loaded. This
ensures that the following load paradigm succeeds:

return view.extend({
load() {
return Promise.all([
...,
L.hasSystemFeature('x'),
]);
}, ...

The luci-loaded check prevents waiting when LuCI is already
loaded (the common case after initial page load), but the
listener handles the race condition where a View is
instantiated before initDOM() completes.

The flow:

If L.loaded is true: initDOM() already ran --> skip waiting,
proceed immediately.

If L.loaded is false: initDOM() hasn't run yet --> add
listener --> wait for it to complete.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
modules/luci-base/htdocs/luci-static/resources/luci.js

index 9f3b88302aeb5c5c2605b2a2266e33cdae582abc..4925c03434ef565c85b9db66bd8a34f7f3aa022b 100644 (file)
 
                        DOM.content(vp, E('div', { 'class': 'spinning' }, _('Loading view…')));
 
 
                        DOM.content(vp, E('div', { 'class': 'spinning' }, _('Loading view…')));
 
-                       return Promise.resolve(this.load())
-                               .then(function (...args) {
-                                       if (L.loaded) {
-                                               return Promise.resolve(...args);
-                                       } else {
-                                               return new Promise(function (resolve) {
-                                                       document.addEventListener('luci-loaded', resolve.bind(null, ...args), { once: true });
-                                               });
-                                       }
-                               })
+                       const ready = L.loaded
+                               ? Promise.resolve()
+                               : new Promise((resolve) => {
+                                       document.addEventListener('luci-loaded', resolve, { once: true });
+                               });
+
+                       return ready
+                               .then(LuCI.prototype.bind(this.load, this))
                                .then(LuCI.prototype.bind(this.render, this))
                                .then(LuCI.prototype.bind(function(nodes) {
                                        const vp = document.getElementById('view');
                                .then(LuCI.prototype.bind(this.render, this))
                                .then(LuCI.prototype.bind(function(nodes) {
                                        const vp = document.getElementById('view');