Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -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` so that `loading=lazy` is not added and the media count is not increased.
*
* @global WP_Query $wp_query WordPress Query object.
*
Expand Down Expand Up @@ -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.
Expand All @@ -6090,6 +6093,13 @@ 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, as it may be a hidden in the Navigation Overlay,
* or it may be occluded in a non-initial carousel slide. Such images must not be lazy-loaded since the browser
* has no heuristic to know when to start loading them prior the user needing to see them.
*/
$maybe_in_viewport = false;
}

if ( null === $maybe_in_viewport ) {
Expand Down Expand Up @@ -6140,7 +6150,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;
}
Expand All @@ -6149,18 +6159,25 @@ 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';
}
}

// Preserve fetchpriorit=low.
if ( $is_low_fetchpriority ) {
$loading_attrs['fetchpriority'] = 'low';
}

/*
* If flag was set based on contextual logic above, increase the content
* media count, either unconditionally, or based on whether the image size
Expand Down
55 changes: 55 additions & 0 deletions tests/phpunit/tests/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -4513,6 +4513,8 @@ public function test_wp_get_loading_optimization_attributes( $context ) {
wp_get_loading_optimization_attributes( 'img', $attr, 'wp_get_attachment_image' )
);

$this->assert_fetchpriority_low_loading_attrs( $attr, 'wp_get_attachment_image' );

// Return 'lazy' if not in the loop or the main query.
$this->assertSameSetsWithIndex(
array(
Expand All @@ -4527,6 +4529,8 @@ public function test_wp_get_loading_optimization_attributes( $context ) {
while ( have_posts() ) {
the_post();

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

// Return 'lazy' if in the loop but not in the main query.
$this->assertSameSetsWithIndex(
array(
Expand All @@ -4539,6 +4543,8 @@ public function test_wp_get_loading_optimization_attributes( $context ) {
// Set as main query.
$this->set_main_query( $query );

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

// First three element are not lazy loaded. However, first image is loaded with fetchpriority high.
$this->assertSameSetsWithIndex(
array(
Expand All @@ -4548,13 +4554,19 @@ public function test_wp_get_loading_optimization_attributes( $context ) {
wp_get_loading_optimization_attributes( 'img', $attr, $context ),
"Expected first image to not be lazy-loaded. First large image get's high fetchpriority."
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

$this->assertSameSetsWithIndex(
array(
'decoding' => 'async',
),
wp_get_loading_optimization_attributes( 'img', $attr, $context ),
'Expected second image to not be lazy-loaded.'
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

$this->assertSameSetsWithIndex(
array(
'decoding' => 'async',
Expand All @@ -4563,6 +4575,8 @@ public function test_wp_get_loading_optimization_attributes( $context ) {
'Expected third image to not be lazy-loaded.'
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

// Return 'lazy' if in the loop and in the main query for any subsequent elements.
$this->assertSameSetsWithIndex(
array(
Expand All @@ -4572,6 +4586,8 @@ public function test_wp_get_loading_optimization_attributes( $context ) {
wp_get_loading_optimization_attributes( 'img', $attr, $context )
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

// Yes, for all subsequent elements.
$this->assertSameSetsWithIndex(
array(
Expand All @@ -4580,6 +4596,8 @@ public function test_wp_get_loading_optimization_attributes( $context ) {
),
wp_get_loading_optimization_attributes( 'img', $attr, $context )
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );
}
}

Expand All @@ -4606,12 +4624,17 @@ public function test_wp_get_loading_optimization_attributes_with_arbitrary_conte
'The "loading" attribute should be "lazy" when not in the loop or the main query.'
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

$query = $this->get_new_wp_query_for_published_post();

// Set as main query.
$this->set_main_query( $query );

while ( have_posts() ) {

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

the_post();

$this->assertSameSetsWithIndex(
Expand Down Expand Up @@ -4656,9 +4679,13 @@ public function test_wp_get_loading_optimization_attributes_with_arbitrary_conte
'The "loading" attribute should be "lazy" before the main query loop.'
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

while ( have_posts() ) {
the_post();

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

$this->assertSameSetsWithIndex(
array(
'decoding' => 'async',
Expand Down Expand Up @@ -4740,6 +4767,8 @@ public function test_wp_get_loading_optimization_attributes_header_contexts( $co
wp_get_loading_optimization_attributes( 'img', $attr, $context ),
'Images in the header context should get lazy-loaded after the wp_loading_optimization_force_header_contexts filter.'
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );
}

/**
Expand Down Expand Up @@ -4770,6 +4799,8 @@ function ( $context ) {
}
);

$this->assert_fetchpriority_low_loading_attrs( $attr, 'something_completely_arbitrary' );

$this->assertSameSetsWithIndex(
array(
'decoding' => 'async',
Expand Down Expand Up @@ -4809,6 +4840,8 @@ public function test_wp_get_loading_optimization_attributes_before_loop_if_not_m
),
wp_get_loading_optimization_attributes( 'img', $attr, $context )
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );
}

/**
Expand Down Expand Up @@ -4840,6 +4873,8 @@ public function test_wp_get_loading_optimization_attributes_before_loop_in_main_
),
wp_get_loading_optimization_attributes( 'img', $attr, $context )
);

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );
}

/**
Expand All @@ -4863,6 +4898,8 @@ public function test_wp_get_loading_optimization_attributes_before_loop_if_main_

$attr = $this->get_width_height_for_high_priority();

$this->assert_fetchpriority_low_loading_attrs( $attr, $context );

// First image is loaded with high fetchpriority.
$this->assertSameSetsWithIndex(
array(
Expand Down Expand Up @@ -6309,6 +6346,24 @@ static function ( $loading_attrs ) {
);
}

/**
* Asserts that loading attributes for IMG with fetchpriority=low.
*
* It must not get lazy-loaded or increase the counter since they may be in the Navigation Overlay.
*/
protected function assert_fetchpriority_low_loading_attrs( array $attr, string $context ): void {
$this->assertSameSetsWithIndex(
array(
'fetchpriority' => 'low',
'decoding' => 'async',
),
wp_get_loading_optimization_attributes(
'img',
array_merge( $attr, array( 'fetchpriority' => 'low' ) ),
$context
)
);
}

/**
* Test WebP lossless quality is handled correctly.
Expand Down
Loading