Problem/Motivation
Found while digging into #3011426: Page cache size is unlimited when arbitrary query parameters are requested (page cache bloat from arbitrary query strings). SystemHooks::jsSettingsAlter() (hook_js_settings_alter) reads $request->query->all() and dumps it into drupalSettings.path.currentQuery on every page with a query string. No cache context declared for it anywhere. active-link.js is the only Drupal Core consumer (core/misc/active-link.js) of it. Uses JSON.stringify(path.currentQuery) to build a selector matched against each link's data-drupal-link-query attribute, to decide is-active state.
Confirmed on a vanilla umami install: view source of the same page with vs without a random query string, only real diff in the HTML is this value. No exposed filter, no custom code involved. Blocks any attempt (e.g. contrib #3610948: Automatically cache pages that don't vary by query args) at sharing page cache entries across query string variants, since the dependency on query strings as cache context isn't noted. It also risks e.g. a newsletter system having query string value containing PII getting cached e.g. by contrib. As noted in that issue, window.location has this info already.
Steps to reproduce
Visit any page with a query string, e.g. /node/1?foo=bar. View source, search drupalSettings, see path.currentQuery reflects it.
Proposed resolution
active-link.js builds current query from window.location.search (URLSearchParams) instead of drupalSettings.path.currentQuery. Needs to replicate ksort + Json::encode format used both in SystemHooks::jsSettingsAlter and LinkGenerator::generate() (data-drupal-link-query), so sort keys then JSON.stringify, same as PHP side does now. Once nothing reads currentQuery, file follow-up to deprecate/remove it.
Remaining tasks
Confirm JS JSON.stringify output matches PHP Json::encode byte for byte for typical query shapes (strings, nested arrays via foo[]=). Test coverage for active-link with query args. Make a change record so contrib knows not to rely on currentQuery.
User interface changes
None.
Introduced terminology
None.
API changes
None, JS behavior only.
Data model changes
None.
Release notes snippet
active-link.js now reads the current query string from window.location instead of drupalSettings.path.currentQuery.
Issue fork drupal-3620504
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
scott_euser commentedComment #4
berdirone thing I'm not sure about is whether there are any special cases around ajax and updating URLs, for example the new views ajax behavior. Maybe this is actually better, but I haven't thought it through.
Comment #5
scott_euser commentedFollow-up issue added as a related issue
Comment #6
scott_euser commentedAdded some related issues; when converting from currentQuery I think it makes sense to switch to a more reliable approach altogether. Will have a play and see if those two issues can be solved at the same time
Comment #7
scott_euser commentedHmmmm will try to explore it a bit
Comment #8
scott_euser commentedOkay I think its this in
AjaxResponseAttachmentsProcessor.phpSo I think that means we don't need to worry about views ajax here
Comment #9
scott_euser commentedI think the php 8.6 warnings are expected and unrelated.
Comment #10
smustgrave commentedLeft some comments on the MR.
@berdir mentions concern around ajax, wonder if we need any test for that or the fact our existing ones pass is enough? Also I do see the comment in #8.
Lets see if we can sub-maintainer to weigh in.
Comment #11
pwolanin commentedA little confused about the suggested impact of this change, since I thought random query strings pollute the page cache regardless of what's in the page content?
Trying to look back in history to see why it's in drupal settings. If I got back to 10.x, the current code more-or-less comes from:
Going before that commit, it looks like the authenticated vs anonymous code paths differed and for anonymous it was using PHP to add the class (maybe JS not loaded for anonymous?). The query string was still always going into drupal settings via code in core/includes/common.inc and maybe there was an intent for it to be consistent between the PHP and JS implementations?
The code there comes from this commit which from the commit message looks to be trying to solve the same kind of problem as this current issue!
https://www.drupal.org/node/1979468
Apparently I worked on that patch long ago...
So, assuming we are currently relying on JS to set the active class for both anonymous and authenticated users, taking the query string out of the window location seems reasonable if it will improve caching.
Comment #12
scott_euser commentedThanks for having a look @pwolanin - yes at the moment random query strings pollute the page cache, but it would allow:
But I think regardless of whether we can land anything like either of those, we shouldn't be caching the query string in something that does not vary by query string, or the example of the email address coming from some newsletter system can get cached when it shouldn't get cached becomes a possibility I believe.
On anonymous vs authentication: only authenticated users load active-link.js (see BareHtmlPageRenderer). Anonymous users still get the class in PHP via ActiveLinkResponseFilter, which reads the request query. So this MR only changes the authenticated JS path.
Re #10 @smustgrave, thanks I addressed the comments. I am unsure on whether there is anything more to test on, open to suggestions.
Thanks all!