Problem/Motivation
When an HTMX response contains JavaScript libraries that are not already present on the page, Drupal can trigger behavior attachment before those assets finish loading.
The affected scripts are eventually loaded and executed, but any Drupal behaviors they define are registered too late for the behavior attachment pass associated with the HTMX response. Their attach() methods are therefore never called for the inserted content.
Steps to reproduce
- Create an HTMX request whose response adds a JavaScript library that is not already loaded on the current page.
- Have that library define a Drupal behavior.
- Load the response into the page through HTMX.
- Observe that the JavaScript file loads and executes, but the behavior's
attach()method is not called.
Expected result
The htmx:drupal:load event should fire only after all assets from the response have finished loading. The subsequent call to Drupal.attachBehaviors() should include behaviors defined by those assets.
Actual result
The htmx:drupal:load event can fire while the new assets are still loading. Behaviors defined by those assets miss the attachment pass.
Cause
In core/misc/htmx/htmx-assets.js, htmx:beforeRequest stores an already resolved promise in requestAssetsLoaded.
During htmx:beforeSwap, asset loading is chained onto that promise:
requestAssetsLoaded.get(detail.xhr).then(() => Drupal.htmx.addAssets(data));The chained promise is discarded. The WeakMap still contains the original resolved promise. Consequently, htmx:afterSettle reads the resolved promise and triggers htmx:drupal:load without waiting for Drupal.htmx.addAssets().
Proposed resolution
Store the chained promise back in requestAssetsLoaded so that htmx:afterSettle waits for the assets:
const assetsLoaded = requestAssetsLoaded.get(detail.xhr) || Promise.resolve(); requestAssetsLoaded.set( detail.xhr, assetsLoaded.then(() => Drupal.htmx.addAssets(data)), );
Add regression coverage using a delayed asset-loading promise. The test should verify that htmx:drupal:load is not triggered before the promise resolves and is triggered after it resolves.
Related issue
This was exposed by a modeler extension loaded inside an HTMX overlay. A compatibility fix is available in modeler merge request !107.
Issue fork drupal-3617895
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
jurgenhaasThe failing tests are unrelated. The MR is now NR.
Comment #4
jurgenhaasComment #5
fathershawnThe effected code is refactored in #3555916: Update to htmx 4 and that implementation needs to be checked with this bug in mind.
Comment #6
latent commentedyeah the htmx4 proposed version already uses proper async behavior to handle this properly. The only possible edge case is js assets that were ES modules that always load later which could impact both versions but I can't see a valid reason to use these with Drupal behavior attachment.
Comment #7
smustgrave commented@jurgenhaas think this should be closed in favor of the HTMX4 ticket?
Comment #8
fathershawn@smustgrave The v4 ticket will only fix this behavior in D12 - I'm changing the version here to D11