Logic error in Drupal's lazy load for asset aggregation

Created on 13 August 2024, 5 months ago

Problem/Motivation

This issue has probably been around for a while, but getting a setup where the error happens is a bit tricky and it's very hard to understand what is actually going on. I'll start by giving some background info on how Drupal's asset system works when aggregation is turned on. This is based on my understanding have looked into this a few times and needed to fix issues with assets management in Drupal in the past. If you are well versed with asset management, feel free to skip this.

Asset url generation

The first part of Drupals asset management with aggregation turned on, is the process to generate the script / link tags to include on the page which are grouped into header and footer for scripts to be placed in the header and footer section respectively. The way this works is

1. Get a list of libraries to include on the page (this list is collected from the render array where every template rendering will include a set of libraries needed. This means that the order of this list is influenced by the render process.
2. Group libraries into header and footer section
3. For each section group the scripts into groups
4. Each group is represented by a script / link tag. If preprocess is enabled for a group a magic url is generated with various information included, which can be used to generated the script file on request. the idea is that one on JS file is generated per request to protect against attacks where user would force Drupal to generate many script files per request. The information included in the script is:
- Minimum representation of libraries to include
- Minimum representation of libraries to exclude
- theme
- language
- delta (the group number from above)

Asset generation from url

When Drupal is requested for a script or a CSS file, if the file is not present on disk it will be generated, this process is managed with the information listed above:

- Minimum representation of libraries to include
- Minimum representation of libraries to exclude
- theme
- language
- delta (the group number from above)

The way this works is that pretty much the same thing happens as before. From the minimum representation of libraries to include / exclude, the full list of libraries to load is generated. This is then ordered by section and then by group.

Steps to reproduce (the logic error)

The problem we have right now, is that the order of scripts from the page render and from the minimum representation is for the most part never the same. This is a problem problem as the order can influence how many groups exists and the contents of each group.

If we take a simple example with 4 libraries:

extA and extB are external scripts or minified scripts where preprocsing is disabled. intA and intB are internal scripts where Drupal has the file and they can be preprocess. intA has a dependency to extA and extB.

With the above context, now imagine some page which includes libraries on the page, from varous elements the following is collected

  1. extA
  2. intA
  3. extB
  4. intB
    1. When the page is rendered, 4 groups are thus generated, which the 4 scripts, one in each group. The reason for this is that the logic for grouping maintains the order of the scripts and generate a new group for each libraries which can't be preprocessed.

      The mininum presentation of the 4 libraries is intA and intB (as intA and a dependency to extA and extB, these do not need to be included)

      The scripts generated on the page would be

      1. extA
      2. hash+info group=1
      3. extB
      4. hash+info group=3
        1. this means that the user's browser will try to fetch group 1 and 3 which is aggregated JS file containing intA and intB

          When the code which is processing these requests need to construct the dependency the list could look like this:

          1. intA
          2. intB
          3. extA
          4. extB
            1. (the two includes + the 2 from dependencies). When these scripts with this order goes through the same process, we end up with 3 groups

              1. intA + intB (group 0)
              2. extA (group 1)
              3. extB (group 2)
                1. As Drupal is requested for group 1 and 3, group 1 would throw and error as it tried to fetch a script with preprocessing disabled and group 3 would throw an error because the group doesn't exist. The result is that intA and intB is not included on the page.

                  Assuming this simple case is for the header section, intA could be core/drupal or some other very important script which would make most of the script on the page fail an there wouldn't really be any working JS on the page.

                  Proposed resolution

                  There are different ways of trying to deal with this issue.

                  One solution could be to not minify the list of libraries to add to ensure we have the same order of libraries (same amount of groups and same contents of groups).
                  Another solution would be to change the logic of the grouping, so that in the above example would always produce the same groups regardless of the order of items. This could be done by ensuring that the grouping class does sorting based on some logic to ensure that the order of lists doesn't matter.

                  Remaining tasks

                  Figure out a solution

                  User interface changes

                  None

                  Introduced terminology

                  API changes

                  Data model changes

                  Release notes snippet

πŸ› Bug report
Status

Active

Version

10.3 ✨

Component
Asset libraryΒ  β†’

Last updated about 5 hours ago

No maintainer
Created by

πŸ‡©πŸ‡°Denmark googletorp

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @googletorp
  • Status changed to Needs review 5 months ago
  • πŸ‡©πŸ‡°Denmark googletorp

    I did a simple approach which is to ensure that the libraries that needs to be processed is sorted. This means that the order of the libraries doesn't influence the specific groups that are created, which in turn ensures as consistent behaviour.

    I'm not sure if and how we should write tests for this, as the whole cycle is pretty complicated and not sure how we could write tests to actually capture this.

  • Pipeline finished with Failed
    5 months ago
    Total: 596s
    #253793
  • Status changed to Needs work 5 months ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    MR should be pointed to 11.x as latest development branch.

    As far as tests go, if this is a major bug do believe we need someway to see that this is a problem and is being solved.

    But appears to have test failures.

  • Merge request !9217Create test module to showcase issue β†’ (Open) created by googletorp
  • Pipeline finished with Failed
    5 months ago
    Total: 426s
    #254749
  • Pipeline finished with Failed
    5 months ago
    Total: 188s
    #254766
  • Pipeline finished with Failed
    5 months ago
    Total: 137s
    #254800
  • Pipeline finished with Failed
    5 months ago
    Total: 3663s
    #254806
  • πŸ‡§πŸ‡ͺBelgium swentel

    We've been hit by this one as well. In our case, it happens while using gin admin theme, which also has two JS files with preprocess set to FALSE, and only under a specific case being a webmaster role which has less permissions than say user 1 (so hard to reproduce as well, as I'm not totally known with the internal system)

    The JS aggregation URL triggers following error: Symfony\Component\HttpKernel\Exception\BadRequestHttpException: Invalid filename. in Drupal\system\Controller\AssetControllerBase->getGroup() (line 225)

    The MR in #4 fixes it for us, and the page now renders fine, so plus one from me at least.

    It's close to being critical, because in our case the webmasters, while they don't get a blank screen, they couldn't edit their content as for instance CKeditor isn't loaded at all or use the media browser to select images.

  • πŸ‡§πŸ‡ͺBelgium swentel

    Actually, I spoke to fast, while node edit page is now fine with the patch, admin/content is now broken, so it looks like the sort isn't solving everything.

  • πŸ‡¬πŸ‡§United Kingdom catch

    Adding πŸ› Aggregated URL hashes for assets can mismatch due to different order of library assets Active as a related issue. We definitely have an issue with the current ordering not being reliable enough, whether it's fixable without the before/after issue or not I'm less sure about, but this is a slightly different approach from the ones I've tried (and failed) previously.

  • Pipeline finished with Failed
    5 months ago
    Total: 555s
    #255042
  • πŸ‡©πŸ‡°Denmark googletorp

    I spent quite some time trying to figure out what is going on. I also saw the issue with gin theme and had some node forms which didn't work depending on which additional libraries were pulled in via the field_group module.

    Originally I thought the problem was the ordering of libraries, but upon further investigation it seems the root issue is that the order of all libraries vs the ordering of only js assets isn't necessarily the same, which then produces the error @swentel mentions in #11.

    I closed the MR from #4 since it doesn't necessarily fix things and made a new patch in !9219 which I think actually solves the issue.

    I don't know how to do a an actual test case for this - I made a test module which has two pages, one failing and one before the fix where both are working after the fix. For some reason some functional JS unit tests are failing in the MR which introduces the test module used to demonstrate the issue, which seems really strange to me, as the introduction of a test module shouldn't make test fails, so wonder if something else is going on.

    I'll try to see if I can come up with a way of testing this, but would be great if some one could take a look at things mentioned and provide feedback on the suggested fix.

  • Status changed to Needs review 5 months ago
  • Pipeline finished with Failed
    5 months ago
    Total: 606s
    #255148
  • πŸ‡¬πŸ‡§United Kingdom catch

    Left one comment on the MR - currently it's repeating logic done earlier to remove non-js libraries from the list. Is that duplication intentional? Could we remove the unset in the second foreach maybe?

    The AjaxPageState test is a real failure.

    My attempt to fix this issue was in πŸ› Aggregated URL hashes for assets can mismatch due to different order of library assets Active and was clearly incomplete. If we can get this MR to green with a test that fail successfully, it might be worth looking to see whether we can revert the fix from there (but also possible that we actually need both changes).

  • πŸ‡¬πŸ‡§United Kingdom catch

    Pushed a commit to fix the AjaxPageState failure.

  • πŸ‡¬πŸ‡§United Kingdom catch

    functional JS unit tests are failing in the MR which introduces the test module used to demonstrate the issue

    If this is ckeditor5 image tests, it's a random but frequent test failure in HEAD and unrelated.

  • Pipeline finished with Canceled
    5 months ago
    Total: 91s
    #257180
  • Pipeline finished with Success
    5 months ago
    Total: 621s
    #257182
  • Pipeline finished with Failed
    5 months ago
    Total: 183s
    #257211
  • Pipeline finished with Failed
    5 months ago
    Total: 156s
    #257213
  • Pipeline finished with Failed
    5 months ago
    Total: 595s
    #257216
  • Pipeline finished with Failed
    5 months ago
    Total: 693s
    #257225
  • Pipeline finished with Canceled
    5 months ago
    Total: 202s
    #257235
  • Pipeline finished with Success
    5 months ago
    #257237
  • πŸ‡©πŸ‡°Denmark googletorp

    I have done a couple of things now, to summarize what we have.

    1. Improved existing test cases in AssetResolverTest and correct a wrong assertion (different libraries, same timestamps should have 2 / 4, not 2/3). This was a result of the way libraryDiscovery was mocked
    2. Create a failing unit test case where a CSS on library influence the contents of JS groups
    3. Adjusted fix to ensure that new test case is now green (needed to both do sorting and re-calculation of dependencies from minimal dependencies to get a working solution

    With regards to the comment in the PR, we need to double unset (explained in detail in the PR). The root issue is caused by CSS dependency changing the order or dependencies which can result in different groups. The reason why this fails is that when Drupal generates the aggregated it does so from the minimal JS only libraries.

    The test cases for the fix is now all green (we got lucky with the ckeditor test case which was failing before), so I think this is ready now.

  • Status changed to RTBC 5 months ago
  • πŸ‡―πŸ‡΅Japan orakili

    We faced the same issue and the changes from the MR seem to have fixed the problem.

    Attaching the a patch for Drupal 10.3.2 backported from the MR as well since this version was also affected.

  • Status changed to Needs work 5 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Needs a rebase for merge conflicts in the test..

  • πŸ‡¬πŸ‡§United Kingdom catch

    Re-titling to make it clearer what the issue is.

  • Status changed to Needs review 5 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Did a couple of things here:
    1. Rebased to resolve the merge conflicts. The unit test/mocking fix implemented here was similar to the one in HEAD, but I prefer the use of withCallback() here so stuck with that from this MR.

    2. Factored out the library loading/filtering logic to a protected method and calling that from getCssAssets() and getJsAssets() to reduce code duplication.

    3. Both before and after #2, and also in similar attempts elsewhere, MediaStandardTest fails on vertical-tabs.js being loaded before form.js - this is because vertical-tabs.js specifies a negative weight and form.js doesn't, and weight overrides dependencies even though it shouldn't.

    I tried changing the ordering logic and then stopped again. Then looked back to see why vertical-tabs.js is weighted before announce.js, and... I cannot find any reason. It was added back in #1996238: Replace hook_library_info() by *.libraries.yml file β†’ which was a huge MR, and didn't have a weight before that.

    Removing the unnecessary weight I get a green MR.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    1 nitpicky comment but can't apply since it's a committer MR.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    May want a 2nd thumbs up before marking. But could issue summary be updated also. Solution still seems to be TBD.

  • πŸ‡¬πŸ‡§United Kingdom catch
  • πŸ‡¬πŸ‡§United Kingdom catch

    Updated the issue summary.

  • Pipeline finished with Failed
    4 months ago
    Total: 418s
    #279345
  • Status changed to Needs work 4 months ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Rebased as I can't re-run tests and javascript tests appear to be consistently failing.

  • πŸ‡¬πŸ‡§United Kingdom catch

    I think I can reproduce the bug manually, it silently doesn't do the thing the test is checking for (maintaining vertical tabs state when ckeditor5 buttons are moved around), which is probably something to do with the vertical tabs weight change, but there's no error at all and I'm not clear what even provides that functionality in core.

  • Status changed to Needs review 4 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Apart from one random test failure, that results in a green run again.

  • Status changed to RTBC 4 months ago
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Will go out on a limb and say this one is ready.

    Marking since it will go through 2 committer eyes too.

  • Status changed to Needs work 4 months ago
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    I've added couple of comments to the MR that need addressing.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    I think we need a CR here that notes that libraries need to ensure they list the vertical tabs as a dependency if it is one so it's not missing like it was for ckedtior.

    Also think the CR should note the potential for the JS order to change if a contrib or custom extension adds a library with an impossible weight (i.e. trying to come before it's own dependencies).

    I asked @catch if we could produce a warning if we determined that a library had an impossible weight and he pointed out he'd rather do πŸ“Œ Replace custom weights with dependencies in library declarations; introduce "before" and "after" for conditional ordering Needs work and deprecate weights altogether.

  • Pipeline finished with Failed
    4 months ago
    Total: 396s
    #280113
  • Status changed to Needs review 4 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Added the change record.

  • Status changed to Needs work 4 months ago
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Unfortauntely testJsAssetsOrder failed in the last run and that's definitely related.

  • πŸ‡¬πŸ‡§United Kingdom catch

    @alexpott I think you were looking at https://git.drupalcode.org/project/drupal/-/merge_requests/9219 which is tests only - going to hide that MR.

  • πŸ‡¬πŸ‡§United Kingdom catch

    catch β†’ changed the visibility of the branch 3467860-11x-test to hidden.

  • Pipeline finished with Success
    4 months ago
    Total: 542s
    #280415
  • Status changed to Needs review 4 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    #36 was looking at the wrong MR, but I had failed to push a commit fixing the ckeditor5 dependency from local and now that's pushed.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Oops yeah I must have been. Sorry.

  • Status changed to RTBC 4 months ago
  • πŸ‡¬πŸ‡§United Kingdom catch

    Moving this back to RTBC since only trivial changes and a CR since #33.

  • πŸ‡¬πŸ‡§United Kingdom catch

    This doesn't only affect aggregate assets, it also ensures the correct order when aggregation is off, re-titling.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Committed and pushed 0c9e4a4b8e to 11.x and 4c3bada24c to 10.4.x. Thanks!

    • alexpott β†’ committed 4c3bada2 on 10.4.x
      Issue #3467860 by googletorp, catch, smustgrave, alexpott, swentel:...
    • alexpott β†’ committed 0c9e4a4b on 11.x
      Issue #3467860 by googletorp, catch, smustgrave, alexpott, swentel:...
  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Tagging after discussion with @xjm, @catch and @longwave

  • πŸ‡¬πŸ‡§United Kingdom catch
  • πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • Automatically closed - issue fixed for 2 weeks with no activity.

  • πŸ‡ΊπŸ‡ΈUnited States thefancywizard

    Rerolled the patch from #21 against 10.3.6.

  • πŸ‡ΊπŸ‡ΈUnited States thefancywizard

    Added the changes needed to support vertical tabs.

  • πŸ‡¨πŸ‡­Switzerland berdir Switzerland

    Views exposed filters in entity browser dialog/iframe are somehow no longer working in 10.4.x when testing on our install profile, git bisect points to this issue.

    I guess there might be also an incorrect or missing library dependency, but there are no JS errors or so, pressing the submit button just reloads the page without applying the filter. maybe something related to jquery.form or views JS?

    entity_browser tests on D11 are broken atm, but I did manage to at least fix previous major and set up a job for 10.4 that is also failing on various tests: https://git.drupalcode.org/project/entity_browser/-/jobs/3221250

  • πŸ‡¬πŸ‡§United Kingdom catch

    I don't know if it's what's causing the issue, but the dependencies do seem to be incorrect:

    entity_browser.common depends on drupalSettings but doesn't declare it. Other libraries depend on entity_browser.common then separately depend on drupalSettings.

    Couple of other libraries declare dependencies on indirect dependencies of libraries they depend on.

    Something like this (completely untested) diff:

    diff --git a/entity_browser.libraries.yml b/entity_browser.libraries.yml
    index bf6d05d..21e512e 100644
    --- a/entity_browser.libraries.yml
    +++ b/entity_browser.libraries.yml
    @@ -2,9 +2,10 @@ common:
       js:
         js/entity_browser.common.js: {}
       dependencies:
    -    - core/drupal.dialog.ajax
    +    - core/drupalSettings
         - core/drupal
         - core/jquery
    +    - core/drupal.dialog.ajax
     
     pager:
       css:
    @@ -28,10 +29,7 @@ iframe:
       js:
         js/entity_browser.iframe.js: {}
       dependencies:
    -    - core/drupalSettings
         - core/once
    -    - core/drupal
    -    - core/jquery
         - entity_browser/common
     
     iframe_selection:
    @@ -47,8 +45,6 @@ entity_reference:
         - entity_browser/common
         - entity_browser/entity_list
         - core/sortable
    -    - core/drupal
    -    - core/jquery
     
     file_browser:
       css:
    
  • Status changed to Fixed about 2 months ago
  • πŸ‡³πŸ‡ΏNew Zealand quietone

    This was committed to 10.4.x so updating version.

  • πŸ‡ΊπŸ‡ΈUnited States tim.plunkett Philadelphia

    From the CR:

    If custom or contrib JavaScript is implicitly depending on vertical-tabs.js being loaded very early, this might result in regressions

    more like custom, contrib, or core ;)
    πŸ› Block visibility settings have summary duplicated in the title Active

  • πŸ‡¬πŸ‡§United Kingdom catch
  • miiimooo Europe

    Yikes, while I can see the reason for this change, it should come with a big warning sign as it changes the order of assets like CSS and JS files, also the ones loaded in custom themes and modules. Especially CSS specificity which is based among others by order this will break most sites in subtle ways. I just tested this with a bigger site and I'm not sure I'll be able to identify all problems and upgrade to 10.4.

    If I'm not alone in this, maybe there could be a backwards compatibility patch or setting that allows site maintainers non-breaking upgrades?

  • As @miiimooo predicted, this broke several of our sites that had multiple custom subthemes. Per https://www.drupal.org/project/drupal/issues/1945262 πŸ“Œ Replace custom weights with dependencies in library declarations; introduce "before" and "after" for conditional ordering Needs work , I added weight to the libraries of my custom themes and it fixed the obvious issues, but we are continuing to monitor for other issues.

    example fix in subtheme.libraries.yml:

    global-styling:
      css:
        theme:
          css/style.css: { weight: 10 }
  • As miiimooo predicted, this broke several of our sites that had multiple custom subthemes. Per Replace custom weights with dependencies in library declarations; introduce "before" and "after" for conditional ordering πŸ“Œ Replace custom weights with dependencies in library declarations; introduce "before" and "after" for conditional ordering Needs work , I added weight to the libraries of my custom themes and it fixed the obvious issues, but we are continuing to monitor for other issues.

    example fix in subtheme.libraries.yml:

    global-styling:
      css:
        theme:
          css/style.css: { weight: 10 }
  • As miiimooo predicted, this broke several of our sites that had multiple custom subthemes. Per Replace custom weights with dependencies in library declarations; introduce "before" and "after" for conditional ordering πŸ“Œ Replace custom weights with dependencies in library declarations; introduce "before" and "after" for conditional ordering Needs work , I added weight to the libraries of my custom themes and it fixed the obvious issues, but we are continuing to monitor for other issues.

    example fix in subtheme.libraries.yml:

    global-styling:
      css:
        theme:
          css/style.css: { weight: 10 }
  • πŸ‡ΊπŸ‡ΈUnited States mmenavas

    I found more side effects that affect Drupal core. I created https://www.drupal.org/project/drupal/issues/3498100 πŸ› Duplicate summary on some admin pages using vertical tabs Active to report duplicate summaries (similar to https://www.drupal.org/project/drupal/issues/3493182 πŸ› Block visibility settings have summary duplicated in the title Active ) on vertical tabs in Media, Content Block, and Taxonomy Term add/edit forms. I also provided a patch but I'm not sure the solution I proposed is the best.

  • πŸ‡ΊπŸ‡ΈUnited States natefollmer

    This has changed the way some of our custom modules interact with contrib modules. We're using DataTables with Views and wrote custom jQuery to manipulate some data on page load. Even after adding datatables as a dependency to our custom module the code no longer functions as it did on 10.3.10 (downgraded to confirm). I'm currently debugging to see if I can add any helpful information, but I'd be grateful if anybody has any debugging tips.

Production build 0.71.5 2024