<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[59008] trunk: Media: Add auto sizes for lazy-loaded images.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/59008">59008</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/59008","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>joemcgill</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-09-10 23:59:54 +0000 (Tue, 10 Sep 2024)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Media: Add auto sizes for lazy-loaded images.

This implements the HTML spec for applying auto sizes to lazy-loaded images by prepending `auto` to the `sizes` attribute generated by WordPress if the image has a `loading` attribute set to `lazy`. For browser that support this HTML spec, the image's size value will be set to the concrete object size of the image. For browsers that don't support the spec, the word "auto" will be ignored when parsing the sizes value.

References:
- https://html.spec.whatwg.org/multipage/images.html#sizes-attributes
- https://github.com/whatwg/html/pull/8008

Props mukesh27, flixos90, joemcgill, westonruter, peterwilsoncc.
Fixes <a href="https://core.trac.wordpress.org/ticket/61847">#61847</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesmediaphp">trunk/src/wp-includes/media.php</a></li>
<li><a href="#trunktestsphpunittestsmediaphp">trunk/tests/phpunit/tests/media.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesmediaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/media.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/media.php   2024-09-10 16:17:47 UTC (rev 59007)
+++ trunk/src/wp-includes/media.php     2024-09-10 23:59:54 UTC (rev 59008)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1137,6 +1137,16 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Adds 'auto' to the sizes attribute if applicable.
+               if (
+                       isset( $attr['loading'] ) &&
+                       'lazy' === $attr['loading'] &&
+                       isset( $attr['sizes'] ) &&
+                       ! wp_sizes_attribute_includes_valid_auto( $attr['sizes'] )
+               ) {
+                       $attr['sizes'] = 'auto, ' . $attr['sizes'];
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 /**
</span><span class="cx" style="display: block; padding: 0 10px">                 * Filters the list of attachment image attributes.
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1917,6 +1927,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        // Add loading optimization attributes if applicable.
</span><span class="cx" style="display: block; padding: 0 10px">                        $filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        // Adds 'auto' to the sizes attribute if applicable.
+                       $filtered_image = wp_img_tag_add_auto_sizes( $filtered_image );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         /**
</span><span class="cx" style="display: block; padding: 0 10px">                         * Filters an img tag within the content for a given context.
</span><span class="cx" style="display: block; padding: 0 10px">                         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1964,6 +1977,59 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Adds 'auto' to the sizes attribute to the image, if the image is lazy loaded and does not already include it.
+ *
+ * @since 6.7.0
+ *
+ * @param string $image The image tag markup being filtered.
+ * @return string The filtered image tag markup.
+ */
+function wp_img_tag_add_auto_sizes( string $image ): string {
+       $processor = new WP_HTML_Tag_Processor( $image );
+
+       // Bail if there is no IMG tag.
+       if ( ! $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) {
+               return $image;
+       }
+
+       // 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" ) ) ) {
+               return $image;
+       }
+
+       $sizes = $processor->get_attribute( 'sizes' );
+
+       // Bail early if the image is not responsive.
+       if ( ! is_string( $sizes ) ) {
+               return $image;
+       }
+
+       // Don't add 'auto' to the sizes attribute if it already exists.
+       if ( wp_sizes_attribute_includes_valid_auto( $sizes ) ) {
+               return $image;
+       }
+
+       $processor->set_attribute( 'sizes', "auto, $sizes" );
+       return $processor->get_updated_html();
+}
+
+/**
+ * Checks whether the given 'sizes' attribute includes the 'auto' keyword as the first item in the list.
+ *
+ * Per the HTML spec, if present it must be the first entry.
+ *
+ * @since 6.7.0
+ *
+ * @param string $sizes_attr The 'sizes' attribute value.
+ * @return bool True if the 'auto' keyword is present, false otherwise.
+ */
+function wp_sizes_attribute_includes_valid_auto( string $sizes_attr ): bool {
+       list( $first_size ) = explode( ',', $sizes_attr, 2 );
+       return 'auto' === strtolower( trim( $first_size, " \t\f\r\n" ) );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Adds optimization attributes to an `img` HTML tag.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 6.3.0
</span></span></pre></div>
<a id="trunktestsphpunittestsmediaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/media.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/media.php       2024-09-10 16:17:47 UTC (rev 59007)
+++ trunk/tests/phpunit/tests/media.php 2024-09-10 23:59:54 UTC (rev 59008)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2467,6 +2467,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @requires function imagejpeg
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_schemes() {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Disable lazy loading attribute to not add the 'auto' keyword to the `sizes` attribute.
+               add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $image_meta = wp_get_attachment_metadata( self::$large_id );
</span><span class="cx" style="display: block; padding: 0 10px">                $size_array = $this->get_image_size_array_from_meta( $image_meta, 'medium' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2680,7 +2683,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'src="' . $uploads_url . 'test-image-testsize-999x999.jpg" ' .
</span><span class="cx" style="display: block; padding: 0 10px">                        'class="attachment-testsize size-testsize" alt="" decoding="async" loading="lazy" ' .
</span><span class="cx" style="display: block; padding: 0 10px">                        'srcset="' . $uploads_url . 'test-image-testsize-999x999.jpg 999w, ' . $uploads_url . $basename . '-150x150.jpg 150w" ' .
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'sizes="(max-width: 999px) 100vw, 999px" />';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'sizes="auto, (max-width: 999px) 100vw, 999px" />';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $actual = wp_get_attachment_image( self::$large_id, 'testsize' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5117,6 +5120,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Do not calculate sizes attribute as it is irrelevant for this test.
+               add_filter( 'wp_calculate_image_sizes', '__return_false' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 // Add shortcode that prints a large image, and a block type that wraps it.
</span><span class="cx" style="display: block; padding: 0 10px">                add_shortcode(
</span><span class="cx" style="display: block; padding: 0 10px">                        'full_image',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6029,6 +6035,277 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Test generated markup for an image with lazy loading gets auto-sizes.
+        *
+        * @ticket 61847
+        */
+       public function test_image_with_lazy_loading_has_auto_sizes() {
+               $this->assertStringContainsString(
+                       'sizes="auto, ',
+                       wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => 'lazy' ) ),
+                       'Failed asserting that the sizes attribute for a lazy-loaded image includes "auto".'
+               );
+       }
+
+       /**
+        * Test generated markup for an image without lazy loading does not get auto-sizes.
+        *
+        * @ticket 61847
+        */
+       public function test_image_without_lazy_loading_does_not_have_auto_sizes() {
+               $this->assertStringNotContainsString(
+                       'sizes="auto, ',
+                       wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => false ) ),
+                       'Failed asserting that the sizes attribute for an image without lazy loading does not include "auto".'
+               );
+       }
+
+       /**
+        * Test content filtered markup with lazy loading gets auto-sizes.
+        *
+        * @ticket 61847
+        *
+        * @covers ::wp_img_tag_add_auto_sizes
+        */
+       public function test_content_image_with_lazy_loading_has_auto_sizes() {
+               // Force lazy loading attribute.
+               add_filter( 'wp_img_tag_add_loading_attr', '__return_true' );
+
+               $this->assertStringContainsString(
+                       'sizes="auto, (max-width: 1024px) 100vw, 1024px"',
+                       wp_filter_content_tags( get_image_tag( self::$large_id, '', '', '', 'large' ) ),
+                       'Failed asserting that the sizes attribute for a content image with lazy loading includes "auto" with the expected sizes.'
+               );
+       }
+
+       /**
+        * Test content filtered markup without lazy loading does not get auto-sizes.
+        *
+        * @ticket 61847
+        *
+        * @covers ::wp_img_tag_add_auto_sizes
+        */
+       public function test_content_image_without_lazy_loading_does_not_have_auto_sizes() {
+               // Disable lazy loading attribute.
+               add_filter( 'wp_img_tag_add_loading_attr', '__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 without lazy loading does not include "auto" with the expected sizes.'
+               );
+       }
+
+       /**
+        * Test generated markup for an image with 'auto' keyword already present in sizes does not receive it again.
+        *
+        * @ticket 61847
+        *
+        * @covers ::wp_img_tag_add_auto_sizes
+        * @covers ::wp_sizes_attribute_includes_valid_auto
+        *
+        * @dataProvider data_image_with_existing_auto_sizes
+        *
+        * @param string $initial_sizes      The initial sizes attribute to test.
+        * @param bool   $expected_processed Whether the auto sizes should be processed or not.
+        */
+       public function test_image_with_existing_auto_sizes_is_not_processed_again( string $initial_sizes, bool $expected_processed ) {
+               $image_tag = wp_get_attachment_image(
+                       self::$large_id,
+                       'large',
+                       false,
+                       array(
+                               // Force pre-existing 'sizes' attribute and lazy-loading.
+                               'sizes'   => $initial_sizes,
+                               'loading' => 'lazy',
+                       )
+               );
+               if ( $expected_processed ) {
+                       $this->assertStringContainsString(
+                               'sizes="auto, ' . $initial_sizes . '"',
+                               $image_tag,
+                               'Failed asserting that "auto" keyword is not added to sizes attribute when it already exists.'
+                       );
+               } else {
+                       $this->assertStringContainsString(
+                               'sizes="' . $initial_sizes . '"',
+                               $image_tag,
+                               'Failed asserting that "auto" keyword is not added to sizes attribute when it already exists.'
+                       );
+               }
+       }
+
+       /**
+        * Test content filtered markup with 'auto' keyword already present in sizes does not receive it again.
+        *
+        * @ticket 61847
+        *
+        * @covers ::wp_img_tag_add_auto_sizes
+        * @covers ::wp_sizes_attribute_includes_valid_auto
+        *
+        * @dataProvider data_image_with_existing_auto_sizes
+        *
+        * @param string $initial_sizes      The initial sizes attribute to test.
+        * @param bool   $expected_processed Whether the auto sizes should be processed or not.
+        */
+       public function test_content_image_with_existing_auto_sizes_is_not_processed_again( string $initial_sizes, bool $expected_processed ) {
+               // Force lazy loading attribute.
+               add_filter( 'wp_img_tag_add_loading_attr', '__return_true' );
+
+               add_filter(
+                       'get_image_tag',
+                       static function ( $html ) use ( $initial_sizes ) {
+                               return str_replace(
+                                       '" />',
+                                       '" sizes="' . $initial_sizes . '" />',
+                                       $html
+                               );
+                       }
+               );
+
+               $image_content = wp_filter_content_tags( get_image_tag( self::$large_id, '', '', '', 'large' ) );
+               if ( $expected_processed ) {
+                       $this->assertStringContainsString(
+                               'sizes="auto, ' . $initial_sizes . '"',
+                               $image_content,
+                               'Failed asserting that "auto" keyword is not added to sizes attribute in filtered content when it already exists.'
+                       );
+               } else {
+                       $this->assertStringContainsString(
+                               'sizes="' . $initial_sizes . '"',
+                               $image_content,
+                               'Failed asserting that "auto" keyword is not added to sizes attribute in filtered content when it already exists.'
+                       );
+               }
+       }
+
+       /**
+        * Returns data for the above test methods to assert correct behavior with a pre-existing sizes attribute.
+        *
+        * @return array<string, mixed[]> Arguments for the test scenarios.
+        */
+       public function data_image_with_existing_auto_sizes() {
+               return array(
+                       'not present'                 => array(
+                               '(max-width: 1024px) 100vw, 1024px',
+                               true,
+                       ),
+                       'in beginning, without space' => array(
+                               'auto,(max-width: 1024px) 100vw, 1024px',
+                               false,
+                       ),
+                       'in beginning, with space'    => array(
+                               'auto, (max-width: 1024px) 100vw, 1024px',
+                               false,
+                       ),
+                       'sole keyword'                => array(
+                               'auto',
+                               false,
+                       ),
+                       'with space before'           => array(
+                               ' auto, (max-width: 1024px) 100vw, 1024px',
+                               false,
+                       ),
+                       'with uppercase'              => array(
+                               'AUTO, (max-width: 1024px) 100vw, 1024px',
+                               false,
+                       ),
+
+                       /*
+                        * The following scenarios technically include the 'auto' keyword,
+                        * but it is in the wrong place, as per the HTML spec it must be
+                        * the first entry in the list.
+                        * Therefore in these invalid cases the 'auto' keyword should still
+                        * be added to the beginning of the list.
+                        */
+                       'within, without space'       => array(
+                               '(max-width: 1024px) 100vw, auto,1024px',
+                               true,
+                       ),
+                       'within, with space'          => array(
+                               '(max-width: 1024px) 100vw, auto, 1024px',
+                               true,
+                       ),
+                       'at the end, without space'   => array(
+                               '(max-width: 1024px) 100vw,auto',
+                               true,
+                       ),
+                       'at the end, with space'      => array(
+                               '(max-width: 1024px) 100vw, auto',
+                               true,
+                       ),
+               );
+       }
+
+       /**
+        * Data provider for test_wp_img_tag_add_auto_sizes().
+        *
+        * @return array<string, mixed>
+        */
+       public function data_provider_to_test_wp_img_tag_add_auto_sizes() {
+               return array(
+                       'expected_with_single_quoted_attributes'       => array(
+                               'input'    => "<img src='https://example.com/foo-300x225.jpg' srcset='https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w' sizes='(max-width: 650px) 100vw, 650px' loading='lazy'>",
+                               'expected' => "<img src='https://example.com/foo-300x225.jpg' srcset='https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w' sizes=\"auto, (max-width: 650px) 100vw, 650px\" loading='lazy'>",
+                       ),
+                       'expected_with_data_sizes_attribute'           => array(
+                               'input'    => '<img data-tshirt-sizes="S M L" src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="(max-width: 650px) 100vw, 650px" loading="lazy">',
+                               'expected' => '<img data-tshirt-sizes="S M L" src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="auto, (max-width: 650px) 100vw, 650px" loading="lazy">',
+                       ),
+                       'expected_with_data_sizes_attribute_already_present' => array(
+                               'input'    => '<img data-tshirt-sizes="S M L" src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="AUTO, (max-width: 650px) 100vw, 650px" loading="lazy">',
+                               'expected' => '<img data-tshirt-sizes="S M L" src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="AUTO, (max-width: 650px) 100vw, 650px" loading="lazy">',
+                       ),
+                       'not_expected_with_loading_lazy_in_attr_value' => array(
+                               'input'    => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="(max-width: 650px) 100vw, 650px" alt=\'This is the LCP image and it should not get loading="lazy"!\'>',
+                               'expected' => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="(max-width: 650px) 100vw, 650px" alt=\'This is the LCP image and it should not get loading="lazy"!\'>',
+                       ),
+                       'not_expected_with_data_loading_attribute_present' => array(
+                               'input'    => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="(max-width: 650px) 100vw, 650px" data-removed-loading="lazy">',
+                               'expected' => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="(max-width: 650px) 100vw, 650px" data-removed-loading="lazy">',
+                       ),
+                       'expected_when_attributes_have_spaces_after_them' => array(
+                               'input'    => '<img src = "https://example.com/foo-300x225.jpg" srcset = "https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes = "(max-width: 650px) 100vw, 650px" loading = "lazy">',
+                               'expected' => '<img src = "https://example.com/foo-300x225.jpg" srcset = "https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="auto, (max-width: 650px) 100vw, 650px" loading = "lazy">',
+                       ),
+                       'expected_when_attributes_are_upper_case'      => array(
+                               'input'    => '<IMG SRC="https://example.com/foo-300x225.jpg" SRCSET="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" SIZES="(max-width: 650px) 100vw, 650px" LOADING="LAZY">',
+                               'expected' => '<IMG SRC="https://example.com/foo-300x225.jpg" SRCSET="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="auto, (max-width: 650px) 100vw, 650px" LOADING="LAZY">',
+                       ),
+                       'expected_when_loading_lazy_lacks_quotes'      => array(
+                               'input'    => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="(max-width: 650px) 100vw, 650px" loading=lazy>',
+                               'expected' => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="auto, (max-width: 650px) 100vw, 650px" loading=lazy>',
+                       ),
+                       'expected_when_loading_lazy_has_whitespace'    => array(
+                               'input'    => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="(max-width: 650px) 100vw, 650px" loading=" lazy ">',
+                               'expected' => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes="auto, (max-width: 650px) 100vw, 650px" loading=" lazy ">',
+                       ),
+                       'not_expected_when_sizes_auto_lacks_quotes'    => array(
+                               'input'    => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes=auto loading="lazy">',
+                               'expected' => '<img src="https://example.com/foo-300x225.jpg" srcset="https://example.com/foo-300x225.jpg 300w, https://example.com/foo-1024x768.jpg 1024w, https://example.com/foo-768x576.jpg 768w, https://example.com/foo-1536x1152.jpg 1536w, https://example.com/foo-2048x1536.jpg 2048w" sizes=auto loading="lazy">',
+                       ),
+               );
+       }
+
+       /**
+        * @ticket 61847
+        *
+        * @covers ::wp_img_tag_add_auto_sizes
+        *
+        * @dataProvider data_provider_to_test_wp_img_tag_add_auto_sizes
+        *
+        * @param string $input    The input HTML string.
+        * @param string $expected The expected output HTML string.
+        */
+       public function test_wp_img_tag_add_auto_sizes( string $input, string $expected ) {
+               $this->assertSame(
+                       $expected,
+                       wp_img_tag_add_auto_sizes( $input ),
+                       'Failed asserting that "auto" keyword is correctly added or not added to sizes attribute in the image tag.'
+               );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Helper method to keep track of the last context returned by the 'wp_get_attachment_image_context' filter.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * The method parameter is passed by reference and therefore will always contain the last context value.
</span></span></pre>
</div>
</div>

</body>
</html>