Media: Add optimization support for IMG tags with fetchpriority=low or fetchpriority=auto.

This updates `wp_get_loading_optimization_attributes()` and `wp_maybe_add_fetchpriority_high_attr()` to account for cases where an `IMG` has `fetchpriority=low` or `fetchpriority=auto`:

* `IMG` tags with `fetchpriority=low` are not lazy-loaded since they may be in a Navigation overlay, Details block, or Accordion Item block and need to be loaded the instant the user toggles the block.  
* `IMG` tags with `fetchpriority=auto` do not increase the media count since they may be hidden in a viewport by block visibility settings.
* Blocks with conditional visibility (such as hidden on mobile or desktop) now automatically add `fetchpriority="auto"` to their contained `IMG` tags to prevent them from erroneously receiving `fetchpriority=high` or affecting the lazy-loading of subsequent images. 
* An `IMG` with `fetchpriority=auto` which also surpasses the `wp_min_priority_img_pixels` threshold will prevent a subsequent image from getting `fetchpriority=high`. 

Developed in https://github.com/WordPress/wordpress-develop/pull/11196
Includes backport of [https://github.com/WordPress/gutenberg/pull/76302 Gutenberg#76302]. 

See related Gutenberg issues:

- [https://github.com/WordPress/gutenberg/issues/76181 76181]: Image in navigation overlay can get `fetchpriority=high` and degrade LCP metric for page.
- [https://github.com/WordPress/gutenberg/issues/76268 76268]: Image in collapsed Details block may erroneously get `fetchpriority=high` even though hidden.
- [https://github.com/WordPress/gutenberg/issues/76301 76301]: Block Visibility: `IMG` in viewport-conditional block may get `fetchpriority=high` even when not displayed.
- [https://github.com/WordPress/gutenberg/issues/76335 76335]: Image in collapsed Accordion block may erroneously get `fetchpriority=high` even though hidden.

Follow-up to r56347, r56037.

Props westonruter, mukesh27, ramonopoly, wildworks.
See #58235.
Fixes #64823.

Built from https://develop.svn.wordpress.org/trunk@61934


git-svn-id: http://core.svn.wordpress.org/trunk@61216 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Weston Ruter
2026-03-11 06:20:49 +00:00
parent 6adc3c2417
commit 4a0e4153eb
4 changed files with 79 additions and 27 deletions
@@ -139,6 +139,16 @@ function wp_render_block_visibility_support( $block_content, $block ) {
$processor = new WP_HTML_Tag_Processor( $block_content );
if ( $processor->next_tag() ) {
$processor->add_class( implode( ' ', $class_names ) );
/*
* Set all IMG tags to be `fetchpriority=auto` so that wp_get_loading_optimization_attributes() won't add
* `fetchpriority=high` or increment the media count to affect whether subsequent IMG tags get `loading=lazy`.
*/
do {
if ( 'IMG' === $processor->get_tag() ) {
$processor->set_attribute( 'fetchpriority', 'auto' );
}
} while ( $processor->next_tag() );
$block_content = $processor->get_updated_html();
}
}
@@ -881,6 +881,8 @@ class WP_HTML_Tag_Processor {
* @type string|null $tag_closers "visit" or "skip": whether to stop on tag closers, e.g. </div>.
* }
* @return bool Whether a tag was matched.
*
* @phpstan-impure
*/
public function next_tag( $query = null ): bool {
$this->parse_query( $query );
+66 -26
View File
@@ -5967,6 +5967,7 @@ function wp_get_webp_info( $filename ) {
* both attributes are present with those values.
*
* @since 6.3.0
* @since 7.0.0 Support `fetchpriority=low` and `fetchpriority=auto` so that `loading=lazy` is not added and the media count is not increased.
*
* @global WP_Query $wp_query WordPress Query object.
*
@@ -6067,7 +6068,9 @@ function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
}
// Logic to handle a `fetchpriority` attribute that is already provided.
if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) {
$existing_fetchpriority = ( $attr['fetchpriority'] ?? null );
$is_low_fetchpriority = ( 'low' === $existing_fetchpriority );
if ( 'high' === $existing_fetchpriority ) {
/*
* If the image was already determined to not be in the viewport (e.g.
* from an already provided `loading` attribute), trigger a warning.
@@ -6090,6 +6093,31 @@ function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
} else {
$maybe_in_viewport = true;
}
} elseif ( $is_low_fetchpriority ) {
/*
* An IMG with fetchpriority=low is not initially displayed; it may be hidden in the Navigation Overlay,
* or it may be occluded in a non-initial carousel slide. Such images must not be lazy-loaded because the browser
* has no heuristic to know when to start loading them before the user needs to see them.
*/
$maybe_in_viewport = false;
// Preserve fetchpriority=low.
$loading_attrs['fetchpriority'] = 'low';
} elseif ( 'auto' === $existing_fetchpriority ) {
/*
* When a block's visibility support identifies that the block is conditionally displayed based on the viewport
* size, then it adds `fetchpriority=auto` to the block's IMG tags. These images must not be fetched with high
* priority because they could be erroneously loaded in viewports which do not even display them. Contrarily,
* they must not get `fetchpriority=low` because they may in fact be displayed in the current viewport. So as
* a signal to indicate that an IMG may be in the viewport, `fetchpriority=auto` is added. This has the effect
* here of preventing the media count from being increased, so that images hidden with block visibility do not
* affect whether a following IMG gets `loading=lazy`. In particular, `loading=lazy` should still be omitted
* on an IMG following any number of initial IMGs with `fetchpriority=auto` since those initial images may not
* be displayed.
*/
// Preserve fetchpriority=auto.
$loading_attrs['fetchpriority'] = 'auto';
}
if ( null === $maybe_in_viewport ) {
@@ -6140,7 +6168,7 @@ function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
* does not include any loop.
*/
&& did_action( 'get_header' ) && ! did_action( 'get_footer' )
) {
) {
$maybe_in_viewport = true;
$maybe_increase_count = true;
}
@@ -6149,12 +6177,14 @@ function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
/*
* If the element is in the viewport (`true`), potentially add
* `fetchpriority` with a value of "high". Otherwise, i.e. if the element
* is not not in the viewport (`false`) or it is unknown (`null`), add
* `loading` with a value of "lazy".
* is not in the viewport (`false`) or it is unknown (`null`), add
* `loading` with a value of "lazy" if the element is not already being
* de-prioritized with `fetchpriority=low` due to occlusion in
* Navigation Overlay, non-initial carousel slides, or a collapsed Details block.
*/
if ( $maybe_in_viewport ) {
$loading_attrs = wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr );
} else {
} elseif ( ! $is_low_fetchpriority ) {
// Only add `loading="lazy"` if the feature is enabled.
if ( wp_lazy_loading_enabled( $tag_name, $context ) ) {
$loading_attrs['loading'] = 'lazy';
@@ -6164,16 +6194,20 @@ function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
/*
* If flag was set based on contextual logic above, increase the content
* media count, either unconditionally, or based on whether the image size
* is larger than the threshold.
* is larger than the threshold. This does not apply when the IMG has
* fetchpriority=auto because it may be conditionally displayed by viewport
* size.
*/
if ( $increase_count ) {
wp_increase_content_media_count();
} elseif ( $maybe_increase_count ) {
/** This filter is documented in wp-includes/media.php */
$wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );
if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
if ( 'auto' !== $existing_fetchpriority ) {
if ( $increase_count ) {
wp_increase_content_media_count();
} elseif ( $maybe_increase_count ) {
/** This filter is documented in wp-includes/media.php */
$wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );
if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
wp_increase_content_media_count();
}
}
}
@@ -6245,12 +6279,13 @@ function wp_increase_content_media_count( $amount = 1 ) {
* Determines whether to add `fetchpriority='high'` to loading attributes.
*
* @since 6.3.0
* @since 7.0.0 Support is added for IMG tags with `fetchpriority='low'` and `fetchpriority='auto'`.
* @access private
*
* @param array $loading_attrs Array of the loading optimization attributes for the element.
* @param string $tag_name The tag name.
* @param array $attr Array of the attributes for the element.
* @return array Updated loading optimization attributes for the element.
* @param array<string, string> $loading_attrs Array of the loading optimization attributes for the element.
* @param string $tag_name The tag name.
* @param array<string, mixed> $attr Array of the attributes for the element.
* @return array<string, string> Updated loading optimization attributes for the element.
*/
function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr ) {
// For now, adding `fetchpriority="high"` is only supported for images.
@@ -6258,14 +6293,17 @@ function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr
return $loading_attrs;
}
if ( isset( $attr['fetchpriority'] ) ) {
$existing_fetchpriority = $attr['fetchpriority'] ?? null;
if ( null !== $existing_fetchpriority && 'auto' !== $existing_fetchpriority ) {
/*
* While any `fetchpriority` value could be set in `$loading_attrs`,
* for consistency we only do it for `fetchpriority="high"` since that
* is the only possible value that WordPress core would apply on its
* own.
* When an IMG has been explicitly marked with `fetchpriority=high`, then honor that this is the element that
* should have the priority. In contrast, the Navigation block may add `fetchpriority=low` to an IMG which
* appears in the Navigation Overlay; such images should never be considered candidates for
* `fetchpriority=high`. Lastly, block visibility may add `fetchpriority=auto` to an IMG when the block is
* conditionally displayed based on viewport size. Such an image is considered an LCP element candidate if it
* exceeds the threshold for the minimum number of square pixels.
*/
if ( 'high' === $attr['fetchpriority'] ) {
if ( 'high' === $existing_fetchpriority ) {
$loading_attrs['fetchpriority'] = 'high';
wp_high_priority_element_flag( false );
}
@@ -6292,7 +6330,9 @@ function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr
$wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );
if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
$loading_attrs['fetchpriority'] = 'high';
if ( 'auto' !== $existing_fetchpriority ) {
$loading_attrs['fetchpriority'] = 'high';
}
wp_high_priority_element_flag( false );
}
@@ -6306,9 +6346,9 @@ function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr
* @access private
*
* @param bool $value Optional. Used to change the static variable. Default null.
* @return bool Returns true if high-priority element was marked already, otherwise false.
* @return bool Returns true if the high-priority element was not already marked.
*/
function wp_high_priority_element_flag( $value = null ) {
function wp_high_priority_element_flag( $value = null ): bool {
static $high_priority_element = true;
if ( is_bool( $value ) ) {
+1 -1
View File
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '7.0-beta4-61927';
$wp_version = '7.0-beta4-61934';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.