diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 961b37f9aa1bb..ae654605e8f4b 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -611,6 +611,7 @@ add_action( 'wp_default_styles', 'wp_default_styles' ); add_filter( 'style_loader_src', 'wp_style_loader_src', 10, 2 ); +add_action( 'wp_head', 'wp_print_auto_sizes_contain_css_fix', 1 ); add_action( 'wp_head', 'wp_maybe_inline_styles', 1 ); // Run for styles enqueued in
. add_action( 'wp_footer', 'wp_maybe_inline_styles', 1 ); // Run for late-loaded styles in the footer. diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 6e58658189816..00e23ff53d7ef 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -1137,8 +1137,12 @@ function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = f } } + /** This filter is documented in wp-includes/media.php */ + $add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true ); + // Adds 'auto' to the sizes attribute if applicable. if ( + $add_auto_sizes && isset( $attr['loading'] ) && 'lazy' === $attr['loading'] && isset( $attr['sizes'] ) && @@ -1985,6 +1989,17 @@ function wp_filter_content_tags( $content, $context = null ) { * @return string The filtered image tag markup. */ function wp_img_tag_add_auto_sizes( string $image ): string { + /** + * Filters whether auto-sizes for lazy loaded images is enabled. + * + * @since 6.7.1 + * + * @param boolean $enabled Whether auto-sizes for lazy loaded images is enabled. + */ + if ( ! apply_filters( 'wp_img_tag_add_auto_sizes', true ) ) { + return $image; + } + $processor = new WP_HTML_Tag_Processor( $image ); // Bail if there is no IMG tag. @@ -1993,8 +2008,19 @@ function wp_img_tag_add_auto_sizes( string $image ): string { } // Bail early if the image is not lazy-loaded. - $value = $processor->get_attribute( 'loading' ); - if ( ! is_string( $value ) || 'lazy' !== strtolower( trim( $value, " \t\f\r\n" ) ) ) { + $loading = $processor->get_attribute( 'loading' ); + if ( ! is_string( $loading ) || 'lazy' !== strtolower( trim( $loading, " \t\f\r\n" ) ) ) { + return $image; + } + + /* + * Bail early if the image doesn't have a width attribute. + * Per WordPress Core itself, lazy-loaded images should always have a width attribute. + * However, it is possible that lazy-loading could be added by a plugin, where we don't have that guarantee. + * As such, it still makes sense to ensure presence of a width attribute here in order to use `sizes=auto`. + */ + $width = $processor->get_attribute( 'width' ); + if ( ! is_string( $width ) || '' === $width ) { return $image; } @@ -2029,6 +2055,28 @@ function wp_sizes_attribute_includes_valid_auto( string $sizes_attr ): bool { return 'auto' === strtolower( trim( $first_size, " \t\f\r\n" ) ); } +/** + * Prints a CSS rule to fix potential visual issues with images using `sizes=auto`. + * + * This rule overrides the similar rule in the default user agent stylesheet, to avoid images that use e.g. + * `width: auto` or `width: fit-content` to appear smaller. + * + * @since 6.7.1 + * @see https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size + * @see https://core.trac.wordpress.org/ticket/62413 + */ +function wp_print_auto_sizes_contain_css_fix() { + /** This filter is documented in wp-includes/media.php */ + $add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true ); + if ( ! $add_auto_sizes ) { + return; + } + + ?> + + false ) ); + + $this->assertStringNotContainsString( + 'width="', + $markup, + 'Failed confirming the test markup did not include a width attribute.' + ); + + $this->assertStringNotContainsString( + 'sizes="auto, ', + $markup, + 'Failed asserting that the sizes attribute for an image without a width does not include "auto".' + ); + } + /** * Test content filtered markup with lazy loading gets auto-sizes. * @@ -6236,6 +6266,46 @@ public function test_content_image_without_lazy_loading_does_not_have_auto_sizes ); } + /** + * Test content filtered markup with lazy loading does not get auto-sizes when disabled. + * + * @ticket 61847 + * @ticket 62413 + * + * @covers ::wp_img_tag_add_auto_sizes + */ + public function test_content_image_does_not_have_auto_sizes_when_disabled() { + // Force lazy loading attribute. + add_filter( 'wp_img_tag_add_loading_attr', '__return_true' ); + // Disable auto-sizes attribute. + add_filter( 'wp_img_tag_add_auto_sizes', '__return_false' ); + + $this->assertStringNotContainsString( + 'sizes="auto, ', + wp_filter_content_tags( get_image_tag( self::$large_id, '', '', '', 'large' ) ), + 'Failed asserting that the sizes attribute for a content image with lazy loading does not include "auto" when disabled.' + ); + } + + /** + * Test generated image markup with lazy loading does not get auto-sizes when disabled. + * + * @ticket 61847 + * @ticket 62413 + * + * @covers ::wp_img_tag_add_auto_sizes + */ + public function test_generated_image_does_not_have_auto_sizes_when_disabled() { + // Disable auto-sizes attribute. + add_filter( 'wp_img_tag_add_auto_sizes', '__return_false' ); + + $this->assertStringNotContainsString( + 'sizes="auto, ', + wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => 'lazy' ) ), + 'Failed asserting that the sizes attribute for an image with lazy loading does not include "auto" when disabled.' + ); + } + /** * Test generated markup for an image with 'auto' keyword already present in sizes does not receive it again. * @@ -6385,44 +6455,48 @@ public function data_image_with_existing_auto_sizes() { public function data_provider_to_test_wp_img_tag_add_auto_sizes() { return array( 'expected_with_single_quoted_attributes' => array( - 'input' => "", - 'expected' => "", + 'input' => "", + 'expected' => "", ), 'expected_with_data_sizes_attribute' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', ), 'expected_with_data_sizes_attribute_already_present' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', ), 'not_expected_with_loading_lazy_in_attr_value' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', ), 'not_expected_with_data_loading_attribute_present' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', ), 'expected_when_attributes_have_spaces_after_them' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', ), 'expected_when_attributes_are_upper_case' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', ), 'expected_when_loading_lazy_lacks_quotes' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', ), 'expected_when_loading_lazy_has_whitespace' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', ), 'not_expected_when_sizes_auto_lacks_quotes' => array( - 'input' => '', - 'expected' => '', + 'input' => '', + 'expected' => '', + ), + 'not_expected_when_img_lacks_dimensions' => array( + 'input' => '', + 'expected' => '', ), ); }