Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion src/wp-includes/block-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ function get_the_block_template_html() {
$content = wptexturize( $content );
$content = convert_smilies( $content );
$content = shortcode_unautop( $content );
$content = wp_filter_content_tags( $content );
$content = wp_filter_content_tags( $content, 'template' );
$content = do_shortcode( $content );
$content = str_replace( ']]>', ']]>', $content );

Expand Down
39 changes: 27 additions & 12 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -5444,25 +5444,40 @@ function wp_get_webp_info( $filename ) {
* that the `loading` attribute should be skipped.
*/
function wp_get_loading_attr_default( $context ) {
// Only elements with 'the_content' or 'the_post_thumbnail' context have special handling.
if ( 'the_content' !== $context && 'the_post_thumbnail' !== $context ) {
return 'lazy';
// Skip lazy-loading for the overall block template, as it is handled more granularly.
if ( 'template' === $context ) {
return false;
}

// Only elements within the main query loop have special handling.
if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
return 'lazy';
// Do not lazy-load images in the header block template part, as they are likely above the fold.
$header_area = WP_TEMPLATE_PART_AREA_HEADER;
if ( "template_part_{$header_area}" === $context ) {
return false;
}

// Increase the counter since this is a main query content element.
$content_media_count = wp_increase_content_media_count();
/*
* The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
* as they are likely above the fold.
*/
if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) {
// Only elements within the main query loop have special handling.
if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
return 'lazy';
}

// If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted.
if ( $content_media_count <= wp_omit_loading_attr_threshold() ) {
return false;
// Increase the counter since this is a main query content element.
$content_media_count = wp_increase_content_media_count();

// If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted.
if ( $content_media_count <= wp_omit_loading_attr_threshold() ) {
return false;
}

// For elements after the threshold, lazy-load them as usual.
return 'lazy';
}

// For elements after the threshold, lazy-load them as usual.
// Lazy-load by default for any unknown context.
return 'lazy';
}

Expand Down
163 changes: 163 additions & 0 deletions tests/phpunit/tests/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -3547,7 +3547,13 @@ public function data_attachment_permalinks_based_on_parent_status() {
}

/**
* Tests that wp_get_loading_attr_default() returns the expected loading attribute value.
*
* @ticket 53675
* @ticket 56930
*
* @covers ::wp_get_loading_attr_default
*
* @dataProvider data_wp_get_loading_attr_default
*
* @param string $context
Expand Down Expand Up @@ -3588,6 +3594,10 @@ public function test_wp_get_loading_attr_default( $context ) {
// Yes, for all subsequent elements.
$this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) );
}

// Exceptions: In the following contexts, images shouldn't be lazy-loaded by default.
$this->assertFalse( wp_get_loading_attr_default( 'template' ), 'Images run through the overall block template filter should not be lazy-loaded.' );
$this->assertFalse( wp_get_loading_attr_default( 'template_part_' . WP_TEMPLATE_PART_AREA_HEADER ), 'Images in the footer block template part should not be lazy-loaded.' );
}

public function data_wp_get_loading_attr_default() {
Expand Down Expand Up @@ -3700,6 +3710,159 @@ function() {
$this->assertSame( 3, $omit_threshold );
}

/**
* Tests that wp_filter_content_tags() does not add loading="lazy" to the first
* image in the loop when using a block theme.
*
* @ticket 56930
*
* @covers ::wp_filter_content_tags
* @covers ::wp_get_loading_attr_default
*/
public function test_wp_filter_content_tags_does_not_lazy_load_first_image_in_block_theme() {
global $_wp_current_template_content, $wp_query, $wp_the_query, $post;

// Do not add srcset, sizes, or decoding attributes as they are irrelevant for this test.
add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' );

$img1 = get_image_tag( self::$large_id, '', '', '', 'large' );
$img2 = get_image_tag( self::$large_id, '', '', '', 'medium' );
$lazy_img2 = wp_img_tag_add_loading_attr( $img2, 'the_content' );

// Only the second image should be lazy-loaded.
$post_content = $img1 . $img2;
$expected_content = wpautop( $img1 . $lazy_img2 );

// Update the post to test with so that it has the above post content.
wp_update_post(
array(
'ID' => self::$post_ids['publish'],
'post_content' => $post_content,
'post_content_filtered' => $post_content,
)
);

$wp_query = new WP_Query( array( 'p' => self::$post_ids['publish'] ) );
$wp_the_query = $wp_query;
$post = get_post( self::$post_ids['publish'] );
$this->reset_content_media_count();
$this->reset_omit_loading_attr_filter();

$_wp_current_template_content = '<!-- wp:post-content /-->';

$html = get_the_block_template_html();
$this->assertSame( '<div class="wp-site-blocks"><div class="entry-content wp-block-post-content is-layout-flow">' . $expected_content . '</div></div>', $html );
}

/**
* Tests that wp_filter_content_tags() does not add loading="lazy"
* to the featured image when using a block theme.
*
* @ticket 56930
*
* @covers ::wp_filter_content_tags
* @covers ::wp_get_loading_attr_default
*/
public function test_wp_filter_content_tags_does_not_lazy_load_first_featured_image_in_block_theme() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Query: Should this have "first_featured_image", or just "featured_image"?

Copy link
Member Author

Choose a reason for hiding this comment

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

"first_featured_image" is correct, as other featured images further down in the page should still be lazy-loaded.

Copy link
Contributor

Choose a reason for hiding this comment

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

In addition to testing:

  • Doesn't have a featured image, and the first image is not lazy loaded.
  • Has a featured image, which is not lazy loaded.

Should we also cover this?

  • Has a featured image, and the first image afterwards is lazy loaded.

Copy link
Member Author

Choose a reason for hiding this comment

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

@costdev

Has a featured image, and the first image afterwards is lazy loaded.

Hmm, this is already being tested here I think. The final assertion tests that the featured image is not lazy-loaded, while the first image afterwards (an in-content image) is lazy-loaded.

global $_wp_current_template_content, $wp_query, $wp_the_query, $post;

// Do not add srcset, sizes, or decoding attributes as they are irrelevant for this test.
add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' );
add_filter(
'wp_get_attachment_image_attributes',
function( $attr ) {
unset( $attr['srcset'], $attr['sizes'], $attr['decoding'] );
return $attr;
}
);

$content_img = get_image_tag( self::$large_id, '', '', '', 'large' );
$lazy_content_img = wp_img_tag_add_loading_attr( $content_img, 'the_content' );

// The featured image should not be lazy-loaded as it is the first image.
$featured_image_id = self::$large_id;
update_post_meta( self::$post_ids['publish'], '_thumbnail_id', $featured_image_id );
$expected_featured_image = '<figure class="wp-block-post-featured-image">' . get_the_post_thumbnail( self::$post_ids['publish'], 'post-thumbnail', array( 'loading' => false ) ) . '</figure>';

// The post content image should be lazy-loaded since the featured image appears above.
$post_content = $content_img;
$expected_content = wpautop( $lazy_content_img );

// Update the post to test with so that it has the above post content.
wp_update_post(
array(
'ID' => self::$post_ids['publish'],
'post_content' => $post_content,
'post_content_filtered' => $post_content,
)
);

$wp_query = new WP_Query( array( 'p' => self::$post_ids['publish'] ) );
$wp_the_query = $wp_query;
$post = get_post( self::$post_ids['publish'] );
$this->reset_content_media_count();
$this->reset_omit_loading_attr_filter();

$_wp_current_template_content = '<!-- wp:post-featured-image /--> <!-- wp:post-content /-->';

$html = get_the_block_template_html();
$this->assertSame( '<div class="wp-site-blocks">' . $expected_featured_image . ' <div class="entry-content wp-block-post-content is-layout-flow">' . $expected_content . '</div></div>', $html );
}

/**
* Tests that wp_filter_content_tags() does not add loading="lazy" to images
* in a "Header" template part.
*
* @ticket 56930
*
* @covers ::wp_filter_content_tags
* @covers ::wp_get_loading_attr_default
*/
public function test_wp_filter_content_tags_does_not_lazy_load_images_in_header() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also add another test to ensure that images in a footer template part are always lazy loaded? I'm thinking in case something is later changed that accidentally excludes footer images from lazy loading.

Copy link
Member Author

Choose a reason for hiding this comment

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

@costdev This is already being tested by this method, the final assertion checks the entire template which includes both a header and footer template part, each with an image. Only the one in the header template part is expected to not be lazy-loaded, while the one in the footer should be.

global $_wp_current_template_content;

// Do not add srcset, sizes, or decoding attributes as they are irrelevant for this test.
add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' );

// Use a single image for each header and footer template parts.
$header_img = get_image_tag( self::$large_id, '', '', '', 'large' );
$footer_img = get_image_tag( self::$large_id, '', '', '', 'medium' );

// Create header and footer template parts.
$header_post_id = self::factory()->post->create(
array(
'post_type' => 'wp_template_part',
'post_status' => 'publish',
'post_name' => 'header',
'post_content' => $header_img,
)
);
wp_set_post_terms( $header_post_id, WP_TEMPLATE_PART_AREA_HEADER, 'wp_template_part_area' );
wp_set_post_terms( $header_post_id, get_stylesheet(), 'wp_theme' );
$footer_post_id = self::factory()->post->create(
array(
'post_type' => 'wp_template_part',
'post_status' => 'publish',
'post_name' => 'footer',
'post_content' => $footer_img,
)
);
wp_set_post_terms( $footer_post_id, WP_TEMPLATE_PART_AREA_FOOTER, 'wp_template_part_area' );
wp_set_post_terms( $footer_post_id, get_stylesheet(), 'wp_theme' );

$_wp_current_template_content = '<!-- wp:template-part {"slug":"header","theme":"' . get_stylesheet() . '","tagName":"header"} /--><!-- wp:template-part {"slug":"footer","theme":"' . get_stylesheet() . '","tagName":"footer"} /-->';

// Header image should not be lazy-loaded, footer image should be lazy-loaded.
$expected_template_content = '<header class="wp-block-template-part">' . $header_img . '</header>';
$expected_template_content .= '<footer class="wp-block-template-part">' . wp_img_tag_add_loading_attr( $footer_img, 'force-lazy' ) . '</footer>';

$html = get_the_block_template_html();
$this->assertSame( '<div class="wp-site-blocks">' . $expected_template_content . '</div>', $html );
}

private function reset_content_media_count() {
// Get current value without increasing.
$content_media_count = wp_increase_content_media_count( 0 );
Expand Down