-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Filter wp_get_attachment_image_context context for shortcodes #4799
Filter wp_get_attachment_image_context context for shortcodes #4799
Conversation
@joemcgill One issue with this, is what if you call the gallery shortcode manually? Example
|
@joemcgill I like the idea here, potentially. Please see my comment in https://core.trac.wordpress.org/ticket/58681#comment:13 for the root issue. Depending on what we decide to address it, maybe the approach here could be a starting point, but I think it requires some more holistic thought on how we want to go about the problem. To me that is 6.4 material rather than 6.3, except if we feel strongly that we should restore the previous behavior (even though it's just as incorrect or potentially worse). |
I've left a comment on the ticket, but in general, I came to the same conclusion as you, @felixarntz. The changes introduced to the optimization heuristics in this cycle have exposed a bug that already existed with how images created via shortcodes were being handled. I'm not confident in this being a viable solution either, but it does at least ensure that shortcodes will be excluded from the logic that skips over dynamic images generated in content. I don't know how common it is for content images to be generated from shortcodes, but it does seem like something we need to account for. I don't think making each shortcode handle it's own image optimization is the right path forward. @spacedmonkey re:
I think we only need this filter to be in place when doing |
Right. Excluding shortcodes from that logic will result in shortcode images always receiving If shortcodes are primarily used for content below the fold, probably this PR is a good solution. But for shortcode usage above the fold it would potentially worsen the LCP problems caused by "excessive" lazy-loading that we've been working on fixing in this release. |
In this case It may even worsen CLS. However excluding the logic will result in equally worst experience. This is a long short but what if we exclude all shortcodes as it is currently. We can have another
This way we will not exclude lazy-loading or blindly add lazy-loading to shortcode images. Later, we may further enhance it to remove our current heuristic and create a new one that checks for image towards the end and not at the start. |
Welcome @joemcgill . This seems to fix the issue and is a nice workaround for this issue. One thing I noticed was that wordpress-develop/src/wp-includes/media.php Line 5697 in bc526d3
I changed it to.
This feels like a workaround, so it might work add a comment / todo in the code to fix this in a cleaner way in a future release. |
If we use this type of workaround, I think we could update the logic so that the I've updated the PR with that change for consideration. I think trying to ensure shortcodes are processed prior to applying image optimizations is a bigger challenge that should be looked into separately. |
@joemcgill Those are some good points. While this is surely not a proper solution, having the The main only issue here is really when the shortcode is at the beginning of the post content. In that case, it'll still only be parsed after all the rest of the content, and if the rest of the content already includes images, the shortcode images will all be lazy-loaded rather than potentially receiving It really is a tradeoff: Do we avoid lazy-loading benefits for shortcode images entirely, or do we risk LCP regression due to shortcode images? I believe the latter is a much smaller surface area, but at the same time more harmful to performance than not lazy-loading. I am inclined to say this fix here is okay, but it'll still have notable flaws, and part of me thinks it would be better to just leave as is and try to properly fix for 6.4. 🤔 |
In my testing, I saw the lazy loading being added to the gallery shortcode images, which fixes the issues at hand. I think I would for commit this change to fix this issue for now and work on a large refactor of the code in WP 6.4 to solve this once and for all. |
Agree that it's a tradeoff. I would think that the probability of lazy loading the LCP image is likely and edge case compared with not lazy-loading gallery images, or other images created as part of a shortcode callback. Given that these images were previously being lazy-loaded, I'd call the removal of that behavior a regression and commit something like this for the time being. |
Thanks @joemcgill @spacedmonkey, that works for me. Let's make sure to add code comments to clarify why the decisions here were being taken. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joemcgill Left some feedback on the code below, the overall approach is good to move forward with.
src/wp-includes/shortcodes.php
Outdated
return 'do_shortcode'; | ||
}; | ||
|
||
add_filter( 'wp_get_attachment_image_context', $callback ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shortcodes are sometimes nested, particularly by plugins that make more complex usage of shortcodes. We should make sure that adding the filter here and removing it below doesn't cause weird behaviors.
I think this code would be more error-proof if we did the following:
- Only add the filter callback if it's not already added to the filter hook.
- Below, only remove the filter callback if it was added in the same
do_shortcode()
execution. - To ensure that we reference the same callback throughout different
do_shortcode()
calls, we could either make$callback
a static variable so that it always references the same closure or replace it with a private function (or rather marked as private) like_return_do_shortcode_context()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some unit tests with nest shortcodes would be handy. Also a test for gallery and caption shortcodes, as these are used a lot in wild.
src/wp-includes/shortcodes.php
Outdated
$callback = static function() { | ||
return 'do_shortcode'; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love this pattern of adding static closures to hooks. It makes it impossible unhook then. They are harder to test. Can we make a private function for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree here. Not everything that is in a hook is intended to be unhooked. While hooks are great, there are things for which unhooking will simply break expected behavior, so I find the idea of requiring every hook to be removable questionable.
IMO a private function is a less clean approach here than this closure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually would agree, and was a bit on the fence about this one so I'm happy to change it if needed. For now, I've updated an inline comment (ee97a6c) where that callback is added to give more context for why it's there. I agree that the intent is not to have folks unhook it, but we could consider adding it to an earlier priority to ensure any other filters added overtake this one if you think it would help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closure are horrible for unit test coverage reports and for profiling tools. See this example screenshot. I think closures make sense in plugins, but as a framework, WordPress does and should always, allow you to remove filters.
Look at this profile, how can find these closures? Are they even the same closure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wary of this turning into a broader conversation about closure usage. The closure here is tiny and introducing a private function for it instead would add things to the global scope, which is usually not a good idea for things that are private.
For profiling, I'd argue seeing the parent functions below should clarify where the closure is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a closure like this may also result in the same filter being added multiple files, as _wp_filter_build_unique_id generates a new value for everytime the closure is used. This may end up adding many many keys to the callbacks property of WP_Hook.
But if you're passing the same variable referencing the same closure then spl_object_hash()
returns the same hash, no? Don't wanna argue about the usefulness of closure, but pretty sure it would work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@swissspidy That's what I was thinking as well.
@spacedmonkey I think the only reason that the hashes in your screenshots in the other thread #4799 (comment) are different is because they are not the same closure. We would need to put the closure into a static
variable and only then it would be the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felixarntz This screen was from a static variable like this
$callback = static function() {
return 'do_shortcode';
};
I replicate it again with commit c3776ab
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That‘s just a static closure but not a static variable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@spacedmonkey That's not a static variable, only the closure is a static function. For it to be a static variable, it would need to be
static $callback = static function() {
return 'do_shortcode';
};
Hmmm....these unit tests were passing in isolation, so it seems like there is state that's not properly being reset between tests, which could have odd effects on all of these unit test cases for optimization. I'm going to look into this today. |
I did some testing. Created a new shortcode.
Then inserted a gallery shortcode + div shortcode like this.
One thing that is interesting, even through this image is the second on the page, has lazy loading attributes. |
Thanks, @spacedmonkey I'll give it a look. |
After doing some more looking into why the shortcode tests were failing when run in the whole group, but not when run in isolation, I discovered that the places where we are setting the main query using |
Are you sure that's the issue? It baffles me reading this because the core test suite resets all globals after each test, or at least it should. That's why generally cleanup of global changes in tests are unnecessary. If this global is not reset, then maybe there's a broader problem somewhere? |
I've added an additional test case for the nested shortcode use, using the example code from @spacedmonkey in this comment. While reviewing the logic for this use case, it looks like everything is happening as expected. @spacedmonkey I assume that the demo you set up to test this included enough content images after the shortcode that by the time the shortcodes were processed, the threshold for omitting |
@joemcgill While your new test looks good to me and passes as expected, I'd like to circle back to what I mentioned in https://github.com/WordPress/wordpress-develop/pull/4799/files#r1254798014: Adding the filter in every call of I would advise to use a conditional and only add/remove the filter in the outermost |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify what I mean in #4799 (comment), here's what I'm suggesting:
I was surprised by this as well, so I did some more investigation and I can confirm that the global Really, all of the resetting that we are doing should happen after each test rather than before, so I've updated things a bit in c809901 so that the main query gets reset to an empty query after each test, and all of the other resets happen during the |
Thanks @joemcgill. While resetting after the tests here specifically works well as a short-term fix, I opened #4823 for consideration of a holistic fix to this problem. The global should really be reset in the same place where the other WP query loop related globals are being reset, similar to how |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix up debugging leftovers and spacing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @joemcgill, LGTM! Just one non-blocking comment.
tests/phpunit/tests/media.php
Outdated
|
||
public function data_wp_get_loading_optimization_attributes_in_shortcodes() { | ||
return array( | ||
'default' => array( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These keys normally describe the test case a little.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved in b9a8698
This change looks good. I have had a chance to do a full review. But you can commit if you want to. If not committed in the morning, I will do a full review and test. |
Co-authored-by: Felix Arntz <felixarntz@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
Committed in https://core.trac.wordpress.org/changeset/56214. |
Trac ticket: https://core.trac.wordpress.org/ticket/58681
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.