Make WordPress Core

Changeset 53480


Ignore:
Timestamp:
06/09/2022 05:29:57 AM (2 years ago)
Author:
peterwilsoncc
Message:

Media: Add decoding="async" to image attributes.

Dynamically add decoding="async" to image tags on the front end of a site to instruct browsers to download them in parallel.

Modifies wp_get_attachment_image(), get_avatar() to include the attribute by default. Modifies wp_filter_content_tags() to add the attribute during the front-end render of the site.

Introduces wp_img_tag_add_decoding_attr() to take an image tag and modify it to include the attribute. Introduces the filter wp_img_tag_add_decoding_attr used to define the default value for the attribute.

Props adamsilverstein, ayeshrajans, costdev, flixos90, hellofromtonya, isaumya, michaelbourne, mihai2u, mitogh, sergiomdgomes, spacedmonkey, westonruter, peterwilsoncc.
Fixes #53232.

Location:
trunk
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/media.php

    r53149 r53480  
    10121012 *     Optional. Attributes for the image markup.
    10131013 *
    1014  *     @type string       $src     Image attachment URL.
    1015  *     @type string       $class   CSS class name or space-separated list of classes.
    1016  *                                 Default `attachment-$size_class size-$size_class`,
    1017  *                                 where `$size_class` is the image size being requested.
    1018  *     @type string       $alt     Image description for the alt attribute.
    1019  *     @type string       $srcset  The 'srcset' attribute value.
    1020  *     @type string       $sizes   The 'sizes' attribute value.
    1021  *     @type string|false $loading The 'loading' attribute value. Passing a value of false
    1022  *                                 will result in the attribute being omitted for the image.
    1023  *                                 Defaults to 'lazy', depending on wp_lazy_loading_enabled().
     1014 *     @type string       $src      Image attachment URL.
     1015 *     @type string       $class    CSS class name or space-separated list of classes.
     1016 *                                  Default `attachment-$size_class size-$size_class`,
     1017 *                                  where `$size_class` is the image size being requested.
     1018 *     @type string       $alt      Image description for the alt attribute.
     1019 *     @type string       $srcset   The 'srcset' attribute value.
     1020 *     @type string       $sizes    The 'sizes' attribute value.
     1021 *     @type string|false $loading  The 'loading' attribute value. Passing a value of false
     1022 *                                  will result in the attribute being omitted for the image.
     1023 *                                  Defaults to 'lazy', depending on wp_lazy_loading_enabled().
     1024 *     @type string       $decoding The 'decoding' attribute value. Possible values are
     1025 *                                  'async' (default), 'sync', or 'auto'.
    10241026 * }
    10251027 * @return string HTML img element or empty string on failure.
     
    10411043
    10421044        $default_attr = array(
    1043             'src'   => $src,
    1044             'class' => "attachment-$size_class size-$size_class",
    1045             'alt'   => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ),
     1045            'src'      => $src,
     1046            'class'    => "attachment-$size_class size-$size_class",
     1047            'alt'      => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ),
     1048            'decoding' => 'async',
    10461049        );
    10471050
     
    18441847            }
    18451848
     1849            // Add 'decoding=async' attribute unless a 'decoding' attribute is already present.
     1850            if ( ! str_contains( $filtered_image, ' decoding=' ) ) {
     1851                $filtered_image = wp_img_tag_add_decoding_attr( $filtered_image, $context );
     1852            }
     1853
    18461854            /**
    18471855             * Filters an img tag within the content for a given context.
     
    19301938
    19311939        return str_replace( '<img', '<img loading="' . esc_attr( $value ) . '"', $image );
     1940    }
     1941
     1942    return $image;
     1943}
     1944
     1945/**
     1946 * Add `decoding` attribute to an `img` HTML tag.
     1947 *
     1948 * The `decoding` attribute allows developers to indicate whether the
     1949 * browser can decode the image off the main thread (`async`), on the
     1950 * main thread (`sync`) or as determined by the browser (`auto`).
     1951 *
     1952 * By default WordPress adds `decoding="async"` to images but developers
     1953 * can use the {@see 'wp_img_tag_add_decoding_attr'} filter to modify this
     1954 * to remove the attribute or set it to another accepted value.
     1955 *
     1956 * @since 6.1.0
     1957 *
     1958 * @param string $image   The HTML `img` tag where the attribute should be added.
     1959 * @param string $context Additional context to pass to the filters.
     1960 *
     1961 * @return string Converted `img` tag with `decoding` attribute added.
     1962 */
     1963function wp_img_tag_add_decoding_attr( $image, $context ) {
     1964    /**
     1965     * Filters the `decoding` attribute value to add to an image. Default `async`.
     1966     *
     1967     * Returning a falsey value will omit the attribute.
     1968     *
     1969     * @since 6.1.0
     1970     *
     1971     * @param string|false|null $value   The `decoding` attribute value. Returning a falsey value will result in
     1972     *                                   the attribute being omitted for the image. Otherwise, it may be:
     1973     *                                   'async' (default), 'sync', or 'auto'.
     1974     * @param string            $image   The HTML `img` tag to be filtered.
     1975     * @param string            $context Additional context about how the function was called or where the img tag is.
     1976     */
     1977    $value = apply_filters( 'wp_img_tag_add_decoding_attr', 'async', $image, $context );
     1978    if ( in_array( $value, array( 'async', 'sync', 'auto' ), true ) ) {
     1979        $image = str_replace( '<img ', '<img decoding="' . esc_attr( $value ) . '" ', $image );
    19321980    }
    19331981
  • trunk/src/wp-includes/pluggable.php

    r53473 r53480  
    27652765            'loading'       => null,
    27662766            'extra_attr'    => '',
     2767            'decoding'      => 'async',
    27672768        );
    27682769
     
    28502851
    28512852            $extra_attr .= "loading='{$loading}'";
     2853        }
     2854
     2855        if ( in_array( $args['decoding'], array( 'async', 'sync', 'auto' ) ) && ! preg_match( '/\bdecoding\s*=/', $extra_attr ) ) {
     2856            if ( ! empty( $extra_attr ) ) {
     2857                $extra_attr .= ' ';
     2858            }
     2859            $extra_attr .= "decoding='{$args['decoding']}'";
    28522860        }
    28532861
  • trunk/tests/phpunit/tests/avatar.php

    r51565 r53480  
    170170    public function test_get_avatar() {
    171171        $img = get_avatar( 1 );
    172         $this->assertSame( preg_match( "|^<img alt='[^']*' src='[^']*' srcset='[^']*' class='[^']*' height='[^']*' width='[^']*' loading='lazy'/>$|", $img ), 1 );
     172        $this->assertSame( preg_match( "|^<img alt='[^']*' src='[^']*' srcset='[^']*' class='[^']*' height='[^']*' width='[^']*' loading='lazy' decoding='async'/>$|", $img ), 1 );
    173173    }
    174174
  • trunk/tests/phpunit/tests/media.php

    r53465 r53480  
    14751475        $image    = image_downsize( self::$large_id, 'thumbnail' );
    14761476        $expected = sprintf(
    1477             '<img width="%1$d" height="%2$d" src="%3$s" class="attachment-thumbnail size-thumbnail" alt="" loading="lazy" />',
     1477            '<img width="%1$d" height="%2$d" src="%3$s" class="attachment-thumbnail size-thumbnail" alt="" decoding="async" loading="lazy" />',
    14781478            $image[1],
    14791479            $image[2],
     
    15131513        $image    = image_downsize( self::$large_id, 'thumbnail' );
    15141514        $expected = sprintf(
    1515             '<img width="%1$d" height="%2$d" src="%3$s" class="attachment-thumbnail size-thumbnail" alt="Some very clever alt text" loading="lazy" />',
     1515            '<img width="%1$d" height="%2$d" src="%3$s" class="attachment-thumbnail size-thumbnail" alt="Some very clever alt text" decoding="async" loading="lazy" />',
    15161516            $image[1],
    15171517            $image[2],
     
    22482248            $respimg_html5
    22492249        );
     2250        $content_filtered = wp_img_tag_add_decoding_attr( $content_filtered, 'the_content' );
    22502251
    22512252        // Do not add width, height, and loading.
     
    22732274        $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
    22742275        $img = wp_img_tag_add_loading_attr( $img, 'test' );
     2276        $img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
    22752277
    22762278        // Replace the src URL.
     
    22872289        $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
    22882290        $img = wp_img_tag_add_loading_attr( $img, 'test' );
     2291        $img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
    22892292        $img = preg_replace( '|<img ([^>]+) />|', '<img $1 ' . 'srcset="image2x.jpg 2x" />', $img );
    22902293
     
    23342337    public function test_wp_filter_content_tags_filter_with_identical_image_tags_custom_attributes() {
    23352338        $img     = get_image_tag( self::$large_id, '', '', '', 'large' );
    2336         $img     = str_replace( '<img ', '<img srcset="custom" sizes="custom" loading="custom" ', $img );
     2339        $img     = str_replace( '<img ', '<img srcset="custom" sizes="custom" loading="custom" decoding="custom"', $img );
    23372340        $content = "$img\n$img";
    23382341
     
    23592362        add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    23602363        add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2364        add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' );
    23612365
    23622366        add_filter(
     
    24612465            $respimg_relative
    24622466        );
     2467        $expected = wp_img_tag_add_decoding_attr( $expected, 'the_content' );
    24632468
    24642469        $actual = wp_filter_content_tags( $unfiltered );
     
    26032608        $expected = '<img width="999" height="999" ' .
    26042609            'src="' . $uploads_url . 'test-image-testsize-999x999.jpg" ' .
    2605             'class="attachment-testsize size-testsize" alt="" loading="lazy" ' .
     2610            'class="attachment-testsize size-testsize" alt="" decoding="async" loading="lazy" ' .
    26062611            'srcset="' . $uploads_url . 'test-image-testsize-999x999.jpg 999w, ' . $uploads_url . $basename . '-150x150.jpg 150w" ' .
    26072612            'sizes="(max-width: 999px) 100vw, 999px" />';
     
    29102915
    29112916        $content_unfiltered = sprintf( $content, $img, $img_no_width_height, $img_no_width, $img_no_height );
    2912         $content_filtered   = sprintf( $content, $img, $respimg_no_width_height, $img_no_width, $img_no_height );
     2917        $content_filtered   = wp_img_tag_add_decoding_attr( sprintf( $content, $img, $respimg_no_width_height, $img_no_width, $img_no_height ), 'the_content' );
    29132918
    29142919        // Do not add loading, srcset, and sizes.
     
    29682973
    29692974        $content_unfiltered = sprintf( $content, $img, $img_xhtml, $img_html5, $img_eager, $img_no_width_height, $iframe, $iframe_eager, $iframe_no_width_height );
    2970         $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 );
     2975        $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' );
    29712976
    29722977        // Do not add width, height, srcset, and sizes.
     
    29973002
    29983003        $content_unfiltered = sprintf( $content, $img, $iframe );
    2999         $content_filtered   = sprintf( $content, $lazy_img, $lazy_iframe );
     3004        $content_filtered   = sprintf( $content, wp_img_tag_add_decoding_attr( $lazy_img, 'the_content' ), $lazy_iframe, 'the_content' );
    30003005
    30013006        // Do not add srcset and sizes while testing.
     
    30153020     */
    30163021    public function test_wp_filter_content_tags_loading_lazy_opted_out() {
    3017         $img    = get_image_tag( self::$large_id, '', '', '', 'medium' );
     3022        $img    = wp_img_tag_add_decoding_attr( get_image_tag( self::$large_id, '', '', '', 'medium' ), 'the_content' );
    30183023        $iframe = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
    30193024
     
    34793484        // Following the threshold of 2, the first two content media elements should not be lazy-loaded.
    34803485        $content_unfiltered = $img1 . $iframe1 . $img2 . $img3 . $iframe2;
    3481         $content_expected   = $img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2;
     3486        $content_expected   = wp_img_tag_add_decoding_attr( $img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2, 'the_content' );
    34823487
    34833488        $wp_query     = new WP_Query( array( 'post__in' => array( self::$post_ids['publish'] ) ) );
  • trunk/tests/phpunit/tests/media/getAdjacentImageLink.php

    r52010 r53480  
    3333                'current_attachment_index'  => 3,
    3434                'expected_attachment_index' => 2,
    35                 'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="http://example.org/wp-content/uploads/image2.jpg" class="attachment-thumbnail size-thumbnail" alt="" loading="lazy" /></a>',
     35                'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="' . WP_CONTENT_URL . '/uploads/image2.jpg" class="attachment-thumbnail size-thumbnail" alt="" decoding="async" loading="lazy" /></a>',
    3636            ),
    3737            'with text when has previous link' => array(
     
    4444                'current_attachment_index'  => 4,
    4545                'expected_attachment_index' => 5,
    46                 'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="http://example.org/wp-content/uploads/image5.jpg" class="attachment-thumbnail size-thumbnail" alt="" loading="lazy" /></a>',
     46                'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="' . WP_CONTENT_URL . '/uploads/image5.jpg" class="attachment-thumbnail size-thumbnail" alt="" decoding="async" loading="lazy" /></a>',
    4747                'args'                      => array( 'prev' => false ),
    4848            ),
  • trunk/tests/phpunit/tests/media/getNextImageLink.php

    r52010 r53480  
    3232                'current_attachment_index'  => 4,
    3333                'expected_attachment_index' => 5,
    34                 'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="http://example.org/wp-content/uploads/image5.jpg" class="attachment-thumbnail size-thumbnail" alt="" loading="lazy" /></a>',
     34                'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="' . WP_CONTENT_URL . '/uploads/image5.jpg" class="attachment-thumbnail size-thumbnail" alt="" decoding="async" loading="lazy" /></a>',
    3535            ),
    3636            'with text when has next link' => array(
  • trunk/tests/phpunit/tests/media/getPreviousImageLink.php

    r52010 r53480  
    3232                'current_attachment_index'  => 3,
    3333                'expected_attachment_index' => 2,
    34                 'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="http://example.org/wp-content/uploads/image2.jpg" class="attachment-thumbnail size-thumbnail" alt="" loading="lazy" /></a>',
     34                'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="' . WP_CONTENT_URL . '/uploads/image2.jpg" class="attachment-thumbnail size-thumbnail" alt="" decoding="async" loading="lazy" /></a>',
    3535            ),
    3636            'with text when has previous link' => array(
  • trunk/tests/phpunit/tests/media/nextImageLink.php

    r52010 r53480  
    3131                'current_attachment_index'  => 4,
    3232                'expected_attachment_index' => 5,
    33                 'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="http://example.org/wp-content/uploads/image5.jpg" class="attachment-thumbnail size-thumbnail" alt="" loading="lazy" /></a>',
     33                'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="' . WP_CONTENT_URL . '/uploads/image5.jpg" class="attachment-thumbnail size-thumbnail" alt="" decoding="async" loading="lazy" /></a>',
    3434            ),
    3535            'with text when has next link' => array(
  • trunk/tests/phpunit/tests/media/previousImageLink.php

    r52010 r53480  
    3131                'current_attachment_index'  => 3,
    3232                'expected_attachment_index' => 2,
    33                 'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="http://example.org/wp-content/uploads/image2.jpg" class="attachment-thumbnail size-thumbnail" alt="" loading="lazy" /></a>',
     33                'expected'                  => '<a href=\'http://example.org/?attachment_id=%%ID%%\'><img width="1" height="1" src="' . WP_CONTENT_URL . '/uploads/image2.jpg" class="attachment-thumbnail size-thumbnail" alt="" decoding="async" loading="lazy" /></a>',
    3434            ),
    3535            'with text when has previous link' => array(
Note: See TracChangeset for help on using the changeset viewer.