diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 2b46167afc..b3e73ed944 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -1011,16 +1011,18 @@ function wp_get_attachment_image_src( $attachment_id, $size = 'thumbnail', $icon * @param string|array $attr { * Optional. Attributes for the image markup. * - * @type string $src Image attachment URL. - * @type string $class CSS class name or space-separated list of classes. - * Default `attachment-$size_class size-$size_class`, - * where `$size_class` is the image size being requested. - * @type string $alt Image description for the alt attribute. - * @type string $srcset The 'srcset' attribute value. - * @type string $sizes The 'sizes' attribute value. - * @type string|false $loading The 'loading' attribute value. Passing a value of false - * will result in the attribute being omitted for the image. - * Defaults to 'lazy', depending on wp_lazy_loading_enabled(). + * @type string $src Image attachment URL. + * @type string $class CSS class name or space-separated list of classes. + * Default `attachment-$size_class size-$size_class`, + * where `$size_class` is the image size being requested. + * @type string $alt Image description for the alt attribute. + * @type string $srcset The 'srcset' attribute value. + * @type string $sizes The 'sizes' attribute value. + * @type string|false $loading The 'loading' attribute value. Passing a value of false + * will result in the attribute being omitted for the image. + * Defaults to 'lazy', depending on wp_lazy_loading_enabled(). + * @type string $decoding The 'decoding' attribute value. Possible values are + * 'async' (default), 'sync', or 'auto'. * } * @return string HTML img element or empty string on failure. */ @@ -1040,9 +1042,10 @@ function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = f } $default_attr = array( - 'src' => $src, - 'class' => "attachment-$size_class size-$size_class", - 'alt' => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ), + 'src' => $src, + 'class' => "attachment-$size_class size-$size_class", + 'alt' => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ), + 'decoding' => 'async', ); // Add `loading` attribute. @@ -1843,6 +1846,11 @@ function wp_filter_content_tags( $content, $context = null ) { $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context ); } + // Add 'decoding=async' attribute unless a 'decoding' attribute is already present. + if ( ! str_contains( $filtered_image, ' decoding=' ) ) { + $filtered_image = wp_img_tag_add_decoding_attr( $filtered_image, $context ); + } + /** * Filters an img tag within the content for a given context. * @@ -1934,6 +1942,46 @@ function wp_img_tag_add_loading_attr( $image, $context ) { return $image; } +/** + * Add `decoding` attribute to an `img` HTML tag. + * + * The `decoding` attribute allows developers to indicate whether the + * browser can decode the image off the main thread (`async`), on the + * main thread (`sync`) or as determined by the browser (`auto`). + * + * By default WordPress adds `decoding="async"` to images but developers + * can use the {@see 'wp_img_tag_add_decoding_attr'} filter to modify this + * to remove the attribute or set it to another accepted value. + * + * @since 6.1.0 + * + * @param string $image The HTML `img` tag where the attribute should be added. + * @param string $context Additional context to pass to the filters. + * + * @return string Converted `img` tag with `decoding` attribute added. + */ +function wp_img_tag_add_decoding_attr( $image, $context ) { + /** + * Filters the `decoding` attribute value to add to an image. Default `async`. + * + * Returning a falsey value will omit the attribute. + * + * @since 6.1.0 + * + * @param string|false|null $value The `decoding` attribute value. Returning a falsey value will result in + * the attribute being omitted for the image. Otherwise, it may be: + * 'async' (default), 'sync', or 'auto'. + * @param string $image The HTML `img` tag to be filtered. + * @param string $context Additional context about how the function was called or where the img tag is. + */ + $value = apply_filters( 'wp_img_tag_add_decoding_attr', 'async', $image, $context ); + if ( in_array( $value, array( 'async', 'sync', 'auto' ), true ) ) { + $image = str_replace( ' false, 'loading' => null, 'extra_attr' => '', + 'decoding' => 'async', ); if ( wp_lazy_loading_enabled( 'img', 'get_avatar' ) ) { @@ -2851,6 +2852,13 @@ if ( ! function_exists( 'get_avatar' ) ) : $extra_attr .= "loading='{$loading}'"; } + if ( in_array( $args['decoding'], array( 'async', 'sync', 'auto' ) ) && ! preg_match( '/\bdecoding\s*=/', $extra_attr ) ) { + if ( ! empty( $extra_attr ) ) { + $extra_attr .= ' '; + } + $extra_attr .= "decoding='{$args['decoding']}'"; + } + $avatar = sprintf( "%s", esc_attr( $args['alt'] ), diff --git a/tests/phpunit/tests/avatar.php b/tests/phpunit/tests/avatar.php index 336ab611e2..026809b4a1 100644 --- a/tests/phpunit/tests/avatar.php +++ b/tests/phpunit/tests/avatar.php @@ -169,7 +169,7 @@ class Tests_Avatar extends WP_UnitTestCase { public function test_get_avatar() { $img = get_avatar( 1 ); - $this->assertSame( preg_match( "|^[^$|", $img ), 1 ); + $this->assertSame( preg_match( "|^[^$|", $img ), 1 ); } public function test_get_avatar_size() { diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index 25e992abfd..bc6967e463 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -1474,7 +1474,7 @@ EOF; public function test_wp_get_attachment_image_defaults() { $image = image_downsize( self::$large_id, 'thumbnail' ); $expected = sprintf( - '', + '', $image[1], $image[2], $image[0] @@ -1512,7 +1512,7 @@ EOF; $image = image_downsize( self::$large_id, 'thumbnail' ); $expected = sprintf( - 'Some very clever alt text', + 'Some very clever alt text', $image[1], $image[2], $image[0] @@ -2247,6 +2247,7 @@ EOF; $respimg_xhtml, $respimg_html5 ); + $content_filtered = wp_img_tag_add_decoding_attr( $content_filtered, 'the_content' ); // Do not add width, height, and loading. add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' ); @@ -2272,6 +2273,7 @@ EOF; public function test_wp_filter_content_tags_srcset_sizes_wrong() { $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); $img = wp_img_tag_add_loading_attr( $img, 'test' ); + $img = wp_img_tag_add_decoding_attr( $img, 'the_content' ); // Replace the src URL. $image_wrong_src = preg_replace( '|src="[^"]+"|', 'src="http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/foo.jpg"', $img ); @@ -2286,6 +2288,7 @@ EOF; // Generate HTML and add a dummy srcset attribute. $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); $img = wp_img_tag_add_loading_attr( $img, 'test' ); + $img = wp_img_tag_add_decoding_attr( $img, 'the_content' ); $img = preg_replace( '|]+) />|', '', $img ); // The content filter should return the image unchanged. @@ -2333,7 +2336,7 @@ EOF; */ public function test_wp_filter_content_tags_filter_with_identical_image_tags_custom_attributes() { $img = get_image_tag( self::$large_id, '', '', '', 'large' ); - $img = str_replace( ''; @@ -2909,7 +2914,7 @@ EOF; %4$s'; $content_unfiltered = sprintf( $content, $img, $img_no_width_height, $img_no_width, $img_no_height ); - $content_filtered = sprintf( $content, $img, $respimg_no_width_height, $img_no_width, $img_no_height ); + $content_filtered = wp_img_tag_add_decoding_attr( sprintf( $content, $img, $respimg_no_width_height, $img_no_width, $img_no_height ), 'the_content' ); // Do not add loading, srcset, and sizes. add_filter( 'wp_img_tag_add_loading_attr', '__return_false' ); @@ -2967,7 +2972,7 @@ EOF; %8$s'; $content_unfiltered = sprintf( $content, $img, $img_xhtml, $img_html5, $img_eager, $img_no_width_height, $iframe, $iframe_eager, $iframe_no_width_height ); - $content_filtered = sprintf( $content, $lazy_img, $lazy_img_xhtml, $lazy_img_html5, $img_eager, $img_no_width_height, $lazy_iframe, $iframe_eager, $iframe_no_width_height ); + $content_filtered = wp_img_tag_add_decoding_attr( sprintf( $content, $lazy_img, $lazy_img_xhtml, $lazy_img_html5, $img_eager, $img_no_width_height, $lazy_iframe, $iframe_eager, $iframe_no_width_height ), 'the_content' ); // Do not add width, height, srcset, and sizes. add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' ); @@ -2996,7 +3001,7 @@ EOF; %2$s'; $content_unfiltered = sprintf( $content, $img, $iframe ); - $content_filtered = sprintf( $content, $lazy_img, $lazy_iframe ); + $content_filtered = sprintf( $content, wp_img_tag_add_decoding_attr( $lazy_img, 'the_content' ), $lazy_iframe, 'the_content' ); // Do not add srcset and sizes while testing. add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); @@ -3014,7 +3019,7 @@ EOF; * @ticket 50756 */ public function test_wp_filter_content_tags_loading_lazy_opted_out() { - $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); + $img = wp_img_tag_add_decoding_attr( get_image_tag( self::$large_id, '', '', '', 'medium' ), 'the_content' ); $iframe = ''; $content = ' @@ -3478,7 +3483,7 @@ EOF; // Following the threshold of 2, the first two content media elements should not be lazy-loaded. $content_unfiltered = $img1 . $iframe1 . $img2 . $img3 . $iframe2; - $content_expected = $img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2; + $content_expected = wp_img_tag_add_decoding_attr( $img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2, 'the_content' ); $wp_query = new WP_Query( array( 'post__in' => array( self::$post_ids['publish'] ) ) ); $wp_the_query = $wp_query; diff --git a/tests/phpunit/tests/media/getAdjacentImageLink.php b/tests/phpunit/tests/media/getAdjacentImageLink.php index 735c0ff15c..7f413cab2e 100644 --- a/tests/phpunit/tests/media/getAdjacentImageLink.php +++ b/tests/phpunit/tests/media/getAdjacentImageLink.php @@ -32,7 +32,7 @@ class Tests_Media_GetAdjacentImageLink extends WP_Test_Adjacent_Image_Link_TestC 'when has previous link' => array( 'current_attachment_index' => 3, 'expected_attachment_index' => 2, - 'expected' => '', + 'expected' => '', ), 'with text when has previous link' => array( 'current_attachment_index' => 3, @@ -43,7 +43,7 @@ class Tests_Media_GetAdjacentImageLink extends WP_Test_Adjacent_Image_Link_TestC 'when has next link' => array( 'current_attachment_index' => 4, 'expected_attachment_index' => 5, - 'expected' => '', + 'expected' => '', 'args' => array( 'prev' => false ), ), 'with text when has next link' => array( diff --git a/tests/phpunit/tests/media/getNextImageLink.php b/tests/phpunit/tests/media/getNextImageLink.php index 86a843cbfc..b26f1fcd69 100644 --- a/tests/phpunit/tests/media/getNextImageLink.php +++ b/tests/phpunit/tests/media/getNextImageLink.php @@ -31,7 +31,7 @@ class Tests_Media_GetNextImageLink extends WP_Test_Adjacent_Image_Link_TestCase 'when has next link' => array( 'current_attachment_index' => 4, 'expected_attachment_index' => 5, - 'expected' => '', + 'expected' => '', ), 'with text when has next link' => array( 'current_attachment_index' => 4, diff --git a/tests/phpunit/tests/media/getPreviousImageLink.php b/tests/phpunit/tests/media/getPreviousImageLink.php index 2d2d511e4f..36652e75ad 100644 --- a/tests/phpunit/tests/media/getPreviousImageLink.php +++ b/tests/phpunit/tests/media/getPreviousImageLink.php @@ -31,7 +31,7 @@ class Tests_Media_GetPreviousImageLink extends WP_Test_Adjacent_Image_Link_TestC 'when has previous link' => array( 'current_attachment_index' => 3, 'expected_attachment_index' => 2, - 'expected' => '', + 'expected' => '', ), 'with text when has previous link' => array( 'current_attachment_index' => 3, diff --git a/tests/phpunit/tests/media/nextImageLink.php b/tests/phpunit/tests/media/nextImageLink.php index 7799779fa8..569e1900ce 100644 --- a/tests/phpunit/tests/media/nextImageLink.php +++ b/tests/phpunit/tests/media/nextImageLink.php @@ -30,7 +30,7 @@ class Tests_Media_NextImageLink extends WP_Test_Adjacent_Image_Link_TestCase { 'when has next link' => array( 'current_attachment_index' => 4, 'expected_attachment_index' => 5, - 'expected' => '', + 'expected' => '', ), 'with text when has next link' => array( 'current_attachment_index' => 4, diff --git a/tests/phpunit/tests/media/previousImageLink.php b/tests/phpunit/tests/media/previousImageLink.php index 11d6583d6a..c75e083537 100644 --- a/tests/phpunit/tests/media/previousImageLink.php +++ b/tests/phpunit/tests/media/previousImageLink.php @@ -30,7 +30,7 @@ class Tests_Media_PreviousImageLink extends WP_Test_Adjacent_Image_Link_TestCase 'when has previous link' => array( 'current_attachment_index' => 3, 'expected_attachment_index' => 2, - 'expected' => '', + 'expected' => '', ), 'with text when has previous link' => array( 'current_attachment_index' => 3,