Skip to content
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

Fix handling NOSCRIPT and IMG tags with JS-based lazy-loading #1745

Draft
wants to merge 3 commits into
base: trunk
Choose a base branch
from

Conversation

westonruter
Copy link
Member

@westonruter westonruter commented Dec 16, 2024

When analyzing sites using Image Prioritizer in HTTP Archive, I came across some cases that hadn't been accounted for.

Tags inside NOSCRIPT

First of all, any tag which appears in a NOSCRIPT should always be excluded from visiting by any tag visitor. For example:

<noscript>
	<img src="https://example.com/pixel.gif" alt="" width="1" height="1">
</noscript>

This was always showing up as:

<noscript>
	<img data-od-unknown-tag src="https://example.com/pixel.gif" alt="" width="1" height="1">
</noscript>

Image Prioritizer adds the data-od-unknown-tag attribute to any visited tag IMG which is not located in URL Metrics. However, such an IMG can never be added to URL Metrics since it is not located in the DOM.

This PR skips over visiting any tag which is inside of a NOSCRIPT element.

Such NOSCRIPT tags are often found with JS-based lazy-loading implementations.

JS-based Lazy-Loading

On quite a few sites I found JS-based lazy-loading causing problems with Image Prioritizer. In particular, the Avada theme's Fusion Images lazy-loading functionality replaces srcset with data-srcset but it doesn't change the src, for example:

<img
	src="https://example.com/foo.webp"
	data-orig-src="https://example.com/foo.webp"
	width="1000"
	height="800"
	class="lazyload wp-image-1"
	srcset="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271000%27%20height%3D%27800%27%20viewBox%3D%270%200%201000%20800%27%3E%3Crect%20width%3D%271000%27%20height%3D%27800%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E"
	data-srcset="https://example.com/foo-200x91.webp 200w, https://example.com/foo-300x136.webp 300w, https://example.com/foo-400x181.webp 400w, https://example.com/foo-600x272.webp 600w, https://example.com/foo-768x348.webp 768w, https://example.com/foo-800x362.webp 800w, https://example.com/foo-1024x463.webp 1024w, https://example.com/foo-1200x543.webp 1200w, https://example.com/foo-1536x695.webp 1536w, https://example.com/foo.webp 1920w"
	data-sizes="auto"
	data-orig-sizes="(max-width: 767px) 100vw, 1920px"
>

This was resulting in an erroneous preload link being added, in particular the imagesrcset is pointing to a data: URL:

<link data-od-added-tag rel="preload" fetchpriority="high" as="image" href="https://example.com/foo.webp" imagesrcset="data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%271000%27%20height%3D%27800%27%20viewBox%3D%270%200%201000%20800%27%3E%3Crect%20width%3D%271000%27%20height%3D%27800%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E" media="screen and (min-width: 783px)">

So this PR adds checks to make sure that if an IMG has such a srcset it is also excluded from processing.

In a future PR we could consider undoing such JS-based lazy-loading, rewriting data-src to src, data-srcset back to srcset and so on, and then to process the IMG as normal. This would have a dramatic improvement to LCP especially when the such JS-based lazy-loading is being done for images in the initial viewport, especially for an IMG which is the LCP element. See #1746.

Copy link

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: westonruter <westonruter@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@westonruter westonruter added [Type] Bug An existing feature is broken [Plugin] Optimization Detective Issues for the Optimization Detective plugin [Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) labels Dec 16, 2024
return false;
}

// Abort if the srcset is a data: URL since there is nothing to optimize.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, there could be something to optimize. We can't preload the URL, but we could still add fetchpriority=high to the IMG.

@@ -53,13 +97,21 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool {
* @return bool Whether the tag should be tracked in URL Metrics.
*/
private function process_img( OD_HTML_Tag_Processor $processor, OD_Tag_Visitor_Context $context ): bool {
$src = $this->get_valid_src( $processor );
if ( null === $src ) {
if ( ! $this->is_img_with_valid_src_and_srcset( $processor ) ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted below, this may be too conservative. We could still process this IMG. It's just we would skip any parts related to preloading the src or srcset.

@westonruter
Copy link
Member Author

I'm bumping this to the next release.

@westonruter westonruter marked this pull request as draft December 17, 2024 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) [Plugin] Optimization Detective Issues for the Optimization Detective plugin [Type] Bug An existing feature is broken
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant