Make WordPress Core


Ignore:
Timestamp:
06/26/2023 04:15:12 PM (2 years ago)
Author:
flixos90
Message:

Media: Automatically add fetchpriority="high" to hero image to improve load time performance.

This changeset adds support for the fetchpriority attribute, which is typically added to a single image in each HTML response with a value of "high". This enhances load time performance (also Largest Contentful Paint, or LCP) by telling the browser to prioritize this image for downloading even before the layout of the page has been computed. In lab tests, this has shown to improve LCP performance by ~10% on average.

Specifically, fetchpriority="high" is added to the first image that satisfies all of the following conditions:

  • The image is not lazy-loaded, i.e. does not have loading="lazy".
  • The image does not already have a (conflicting) fetchpriority attribute.
  • The size of of the image (i.e. width * height) is greater than 50,000 squarepixels.

While these heuristics are based on several field analyses, there will always be room for optimization. Sites can customize the squarepixel threshold using a new filter wp_min_priority_img_pixels which should return an integer for the value.

Since the logic for adding fetchpriority="high" is heavily intertwined with the logic for adding loading="lazy", yet the features should work decoupled from each other, the majority of code changes in this changeset is refactoring of the existing lazy-loading logic to be reusable. For this purpose, a new function wp_get_loading_optimization_attributes() has been introduced which returns an associative array of performance-relevant attributes for a given HTML element. This function replaces wp_get_loading_attr_default(), which has been deprecated. As another result of that change, a new function wp_img_tag_add_loading_optimization_attrs() replaces the more specific wp_img_tag_add_loading_attr(), which has been deprecated as well.

See https://make.wordpress.org/core/2023/05/02/proposal-for-enhancing-lcp-image-performance-with-fetchpriority/ for the original proposal and additional context.

Props thekt12, joemcgill, spacedmonkey, mukesh27, costdev, 10upsimon.
Fixes #58235.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/media.php

    r55956 r56037  
    7777
    7878    /**
    79      * Ensures that the static content media count and related filter are reset between tests.
     79     * Ensures that the static content media count, fetchpriority element flag and related filter are reset between tests.
    8080     */
    8181    public function set_up() {
     
    8484        $this->reset_content_media_count();
    8585        $this->reset_omit_loading_attr_filter();
     86        $this->reset_high_priority_element_flag();
    8687    }
    8788
     
    22902291    public function test_wp_filter_content_tags_srcset_sizes_wrong() {
    22912292        $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
    2292         $img = wp_img_tag_add_loading_attr( $img, 'test' );
     2293        $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
    22932294        $img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
    22942295
     
    23052306        // Generate HTML and add a dummy srcset attribute.
    23062307        $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
    2307         $img = wp_img_tag_add_loading_attr( $img, 'test' );
     2308        $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
    23082309        $img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
    23092310        $img = preg_replace( '|<img ([^>]+) />|', '<img $1 ' . 'srcset="image2x.jpg 2x" />', $img );
     
    24502451        // Build HTML for the editor.
    24512452        $img          = get_image_tag( self::$large_id, '', '', '', 'medium' );
    2452         $img          = wp_img_tag_add_loading_attr( $img, 'test' );
     2453        $img          = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
    24532454        $img_https    = str_replace( 'http://', 'https://', $img );
    24542455        $img_relative = str_replace( 'http://', '//', $img );
     
    29912992     * @ticket 50367
    29922993     * @ticket 50756
     2994     * @ticket 58235
    29932995     * @requires function imagejpeg
    29942996     */
     
    30053007        $iframe_no_width_height = '<iframe src="https://www.example.com"></iframe>';
    30063008
    3007         $lazy_img       = wp_img_tag_add_loading_attr( $img, 'test' );
    3008         $lazy_img_xhtml = wp_img_tag_add_loading_attr( $img_xhtml, 'test' );
    3009         $lazy_img_html5 = wp_img_tag_add_loading_attr( $img_html5, 'test' );
     3009        $lazy_img       = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
     3010        $lazy_img_xhtml = wp_img_tag_add_loading_optimization_attrs( $img_xhtml, 'test' );
     3011        $lazy_img_html5 = wp_img_tag_add_loading_optimization_attrs( $img_html5, 'test' );
    30103012        $lazy_iframe    = wp_iframe_tag_add_loading_attr( $iframe, 'test' );
    30113013
    30123014        // The following should not be modified because there already is a 'loading' attribute.
    3013         $img_eager    = str_replace( ' />', ' loading="eager" />', $img );
     3015        $img_eager    = str_replace( ' />', ' loading="eager" fetchpriority="high" />', $img );
    30143016        $iframe_eager = str_replace( '">', '" loading="eager">', $iframe );
    30153017
     
    30703072     * @ticket 44427
    30713073     * @ticket 50756
     3074     * @ticket 58235
    30723075     */
    30733076    public function test_wp_filter_content_tags_loading_lazy_opted_in() {
    30743077        $img         = get_image_tag( self::$large_id, '', '', '', 'medium' );
    3075         $lazy_img    = wp_img_tag_add_loading_attr( $img, 'test' );
     3078        $lazy_img    = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
    30763079        $lazy_img    = wp_img_tag_add_decoding_attr( $lazy_img, 'the_content' );
    30773080        $iframe      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
     
    31283131     * @ticket 44427
    31293132     * @ticket 50367
     3133     *
     3134     * @expectedDeprecated wp_img_tag_add_loading_attr
     3135     * @expectedDeprecated wp_get_loading_attr_default
    31303136     */
    31313137    public function test_wp_img_tag_add_loading_attr() {
     
    31393145     * @ticket 44427
    31403146     * @ticket 50367
     3147     *
     3148     * @expectedDeprecated wp_img_tag_add_loading_attr
     3149     * @expectedDeprecated wp_get_loading_attr_default
    31413150     */
    31423151    public function test_wp_img_tag_add_loading_attr_without_src() {
     
    31503159     * @ticket 44427
    31513160     * @ticket 50367
     3161     *
     3162     * @expectedDeprecated wp_img_tag_add_loading_attr
     3163     * @expectedDeprecated wp_get_loading_attr_default
    31523164     */
    31533165    public function test_wp_img_tag_add_loading_attr_with_single_quotes() {
     
    32873299        // There should not be any loading attribute in this case.
    32883300        $this->assertStringNotContainsString( ' loading=', $img );
     3301    }
     3302
     3303    /**
     3304     * @ticket 58235
     3305     *
     3306     * @covers ::wp_get_attachment_image
     3307     * @covers ::wp_get_loading_optimization_attributes
     3308     */
     3309    public function test_wp_get_attachment_image_fetchpriority_not_present_by_default() {
     3310        $img = wp_get_attachment_image( self::$large_id );
     3311
     3312        $this->assertStringNotContainsString( ' fetchpriority="high"', $img );
     3313    }
     3314
     3315    /**
     3316     * @ticket 58235
     3317     *
     3318     * @covers ::wp_get_attachment_image
     3319     * @covers ::wp_get_loading_optimization_attributes
     3320     */
     3321    public function test_wp_get_attachment_image_fetchpriority_high_when_not_lazy_loaded() {
     3322        $img = wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => false ) );
     3323
     3324        $this->assertStringContainsString( ' fetchpriority="high"', $img );
     3325    }
     3326
     3327    /**
     3328     * @ticket 58235
     3329     *
     3330     * @dataProvider data_provider_fetchpriority_values
     3331     *
     3332     * @covers ::wp_get_attachment_image
     3333     * @covers ::wp_get_loading_optimization_attributes
     3334     */
     3335    public function test_wp_get_attachment_image_fetchpriority_original_value_respected( $value ) {
     3336        $img = wp_get_attachment_image(
     3337            self::$large_id,
     3338            'large',
     3339            false,
     3340            array(
     3341                'loading'       => false,
     3342                'fetchpriority' => $value,
     3343            )
     3344        );
     3345
     3346        $this->assertStringContainsString( ' fetchpriority="' . $value . '"', $img );
     3347    }
     3348
     3349    /**
     3350     * Data provider.
     3351     *
     3352     * @return array[]
     3353     */
     3354    public function data_provider_fetchpriority_values() {
     3355        return self::text_array_to_dataprovider( array( 'high', 'low', 'auto' ) );
     3356    }
     3357
     3358    /**
     3359     * @ticket 58235
     3360     *
     3361     * @covers ::wp_get_attachment_image
     3362     * @covers ::wp_get_loading_optimization_attributes
     3363     */
     3364    public function test_wp_get_attachment_image_fetchpriority_stripped_when_false() {
     3365        $img = wp_get_attachment_image(
     3366            self::$large_id,
     3367            'large',
     3368            false,
     3369            array(
     3370                'loading'       => false,
     3371                'fetchpriority' => false,
     3372            )
     3373        );
     3374
     3375        $this->assertStringNotContainsString( ' fetchpriority=', $img );
     3376    }
     3377
     3378    /**
     3379     * @ticket 58235
     3380     *
     3381     * @covers ::wp_get_attachment_image
     3382     * @covers ::wp_get_loading_optimization_attributes
     3383     */
     3384    public function test_wp_get_attachment_image_fetchpriority_high_prevents_lazy_loading() {
     3385        $img = wp_get_attachment_image( self::$large_id, 'large', false, array( 'fetchpriority' => 'high' ) );
     3386
     3387        $this->assertStringNotContainsString( ' loading="lazy"', $img );
    32893388    }
    32903389
     
    35653664     * @covers ::wp_get_loading_attr_default
    35663665     *
     3666     * @expectedDeprecated wp_get_loading_attr_default
     3667     *
    35673668     * @dataProvider data_wp_get_loading_attr_default
    35683669     *
     
    35883689            $this->set_main_query( $query );
    35893690
    3590             // For contexts other than for the main content, still return 'lazy' even in the loop
    3591             // and in the main query, and do not increase the content media count.
     3691            /*
     3692             * For contexts other than for the main content, still return 'lazy' even in the loop
     3693             * and in the main query, and do not increase the content media count.
     3694             */
    35923695            $this->assertSame( 'lazy', wp_get_loading_attr_default( 'wp_get_attachment_image' ) );
    35933696
     
    36183721    /**
    36193722     * @ticket 53675
     3723     * @ticket 58235
    36203724     */
    36213725    public function test_wp_omit_loading_attr_threshold_filter() {
     3726        // Using a smaller image here.
     3727        $attr = array(
     3728            'width'  => 100,
     3729            'height' => 100,
     3730        );
     3731
    36223732        $query = $this->get_new_wp_query_for_published_post();
    36233733        $this->set_main_query( $query );
     
    36313741            // Due to the filter, now the first five elements should not be lazy-loaded, i.e. return `false`.
    36323742            for ( $i = 0; $i < 5; $i++ ) {
    3633                 $this->assertFalse( wp_get_loading_attr_default( 'the_content' ) );
     3743                $this->assertEmpty(
     3744                    wp_get_loading_optimization_attributes( 'img', $attr, 'the_content' ),
     3745                    'Expected second image to not be lazy-loaded.'
     3746                );
    36343747            }
    36353748
    36363749            // For following elements, lazy-load them again.
    3637             $this->assertSame( 'lazy', wp_get_loading_attr_default( 'the_content' ) );
     3750            $this->assertSame(
     3751                array( 'loading' => 'lazy' ),
     3752                wp_get_loading_optimization_attributes( 'img', $attr, 'the_content' )
     3753            );
    36383754        }
    36393755    }
     
    36413757    /**
    36423758     * @ticket 53675
    3643      */
    3644     public function test_wp_filter_content_tags_with_wp_get_loading_attr_default() {
     3759     * @ticket 58235
     3760     *
     3761     * @covers ::wp_filter_content_tags
     3762     * @covers ::wp_img_tag_add_loading_optimization_attrs
     3763     * @covers ::wp_get_loading_optimization_attributes
     3764     */
     3765    public function test_wp_filter_content_tags_with_loading_optimization_attrs() {
    36453766        $img1         = get_image_tag( self::$large_id, '', '', '', 'large' );
    36463767        $iframe1      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
     
    36483769        $img3         = get_image_tag( self::$large_id, '', '', '', 'thumbnail' );
    36493770        $iframe2      = '<iframe src="https://wordpress.org" width="640" height="360"></iframe>';
    3650         $lazy_img2    = wp_img_tag_add_loading_attr( $img2, 'the_content' );
    3651         $lazy_img3    = wp_img_tag_add_loading_attr( $img3, 'the_content' );
     3771        $prio_img1    = str_replace( ' src=', ' fetchpriority="high" src=', $img1 );
     3772        $lazy_img2    = wp_img_tag_add_loading_optimization_attrs( $img2, 'the_content' );
     3773        $lazy_img3    = wp_img_tag_add_loading_optimization_attrs( $img3, 'the_content' );
    36523774        $lazy_iframe2 = wp_iframe_tag_add_loading_attr( $iframe2, 'the_content' );
    36533775
     
    36573779        // Following the threshold of 2, the first two content media elements should not be lazy-loaded.
    36583780        $content_unfiltered = $img1 . $iframe1 . $img2 . $img3 . $iframe2;
    3659         $content_expected   = $img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2;
     3781        $content_expected   = $prio_img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2;
    36603782        $content_expected   = wp_img_tag_add_decoding_attr( $content_expected, 'the_content' );
    36613783
     
    37063828     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
    37073829     *
     3830     * @expectedDeprecated wp_get_loading_attr_default
     3831     *
    37083832     * @param string $context Context for the element for which the `loading` attribute value is requested.
    37093833     */
     
    37283852     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
    37293853     *
     3854     * @expectedDeprecated wp_get_loading_attr_default
     3855     *
    37303856     * @param string $context Context for the element for which the `loading` attribute value is requested.
    37313857     */
     
    37493875     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
    37503876     *
     3877     * @expectedDeprecated wp_get_loading_attr_default
     3878     *
    37513879     * @param string $context Context for the element for which the `loading` attribute value is requested.
    37523880     */
     
    37693897     *
    37703898     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
     3899     *
     3900     * @expectedDeprecated wp_get_loading_attr_default
    37713901     *
    37723902     * @param string $context Context for the element for which the `loading` attribute value is requested.
     
    37953925     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
    37963926     *
     3927     * @expectedDeprecated wp_get_loading_attr_default
     3928     *
    37973929     * @param string $context Context for the element for which the `loading` attribute value is requested.
    37983930     */
     
    38293961     * @ticket 56930
    38303962     * @ticket 58548
     3963     * @ticket 58235
    38313964     *
    38323965     * @covers ::wp_filter_content_tags
    3833      * @covers ::wp_get_loading_attr_default
     3966     * @covers ::wp_img_tag_add_loading_optimization_attrs
     3967     * @covers ::wp_get_loading_optimization_attributes
    38343968     */
    38353969    public function test_wp_filter_content_tags_does_not_lazy_load_first_image_in_block_theme() {
     
    38433977        $img1      = get_image_tag( self::$large_id, '', '', '', 'large' );
    38443978        $img2      = get_image_tag( self::$large_id, '', '', '', 'medium' );
    3845         $lazy_img2 = wp_img_tag_add_loading_attr( $img2, 'the_content' );
     3979        $prio_img1 = str_replace( ' src=', ' fetchpriority="high" src=', $img1 );
     3980        $lazy_img2 = wp_img_tag_add_loading_optimization_attrs( $img2, 'the_content' );
    38463981
    38473982        // Only the second image should be lazy-loaded.
    38483983        $post_content     = $img1 . $img2;
    3849         $expected_content = wpautop( $img1 . $lazy_img2 );
     3984        $expected_content = wpautop( $prio_img1 . $lazy_img2 );
    38503985
    38513986        // Update the post to test with so that it has the above post content.
     
    38744009     * @ticket 56930
    38754010     * @ticket 58548
     4011     * @ticket 58235
    38764012     *
    38774013     * @covers ::wp_filter_content_tags
    3878      * @covers ::wp_get_loading_attr_default
     4014     * @covers ::wp_img_tag_add_loading_optimization_attrs
     4015     * @covers ::wp_get_loading_optimization_attributes
    38794016     */
    38804017    public function test_wp_filter_content_tags_does_not_lazy_load_first_featured_image_in_block_theme() {
     
    38944031
    38954032        $content_img      = get_image_tag( self::$large_id, '', '', '', 'large' );
    3896         $lazy_content_img = wp_img_tag_add_loading_attr( $content_img, 'the_content' );
     4033        $lazy_content_img = wp_img_tag_add_loading_optimization_attrs( $content_img, 'the_content' );
    38974034
    38984035        // The featured image should not be lazy-loaded as it is the first image.
    38994036        $featured_image_id = self::$large_id;
    39004037        update_post_meta( self::$post_ids['publish'], '_thumbnail_id', $featured_image_id );
    3901         $expected_featured_image = '<figure class="wp-block-post-featured-image">' . get_the_post_thumbnail( self::$post_ids['publish'], 'post-thumbnail', array( 'loading' => false ) ) . '</figure>';
     4038        $expected_featured_image = '<figure class="wp-block-post-featured-image">' . get_the_post_thumbnail(
     4039            self::$post_ids['publish'],
     4040            'post-thumbnail',
     4041            array(
     4042                'loading'       => false,
     4043                'fetchpriority' => 'high',
     4044            )
     4045        ) . '</figure>';
     4046
     4047        // Reset high priority flag as the forced `fetchpriority="high"` above already modified it.
     4048        $this->reset_high_priority_element_flag();
    39024049
    39034050        // The post content image should be lazy-loaded since the featured image appears above.
     
    39134060            )
    39144061        );
    3915 
    39164062        $wp_query     = new WP_Query( array( 'p' => self::$post_ids['publish'] ) );
    39174063        $wp_the_query = $wp_query;
     
    39294075     *
    39304076     * @ticket 56930
     4077     * @ticket 58235
    39314078     *
    39324079     * @covers ::wp_filter_content_tags
    3933      * @covers ::wp_get_loading_attr_default
     4080     * @covers ::wp_img_tag_add_loading_optimization_attrs
     4081     * @covers ::wp_get_loading_optimization_attributes
    39344082     */
    39354083    public function test_wp_filter_content_tags_does_not_lazy_load_images_in_header() {
     
    39424090        // Use a single image for each header and footer template parts.
    39434091        $header_img = get_image_tag( self::$large_id, '', '', '', 'large' );
     4092        // Since header_img is qualified candidate for LCP, fetchpriority high is applied to it.
     4093        $header_img = str_replace( '<img', '<img fetchpriority="high"', $header_img );
     4094
    39444095        $footer_img = get_image_tag( self::$large_id, '', '', '', 'medium' );
    39454096
     
    39704121        // Header image should not be lazy-loaded, footer image should be lazy-loaded.
    39714122        $expected_template_content  = '<header class="wp-block-template-part">' . $header_img . '</header>';
    3972         $expected_template_content .= '<footer class="wp-block-template-part">' . wp_img_tag_add_loading_attr( $footer_img, 'force-lazy' ) . '</footer>';
     4123        $expected_template_content .= '<footer class="wp-block-template-part">' . wp_img_tag_add_loading_optimization_attrs( $footer_img, 'force-lazy' ) . '</footer>';
    39734124
    39744125        $html = get_the_block_template_html();
     
    39784129    /**
    39794130     * @ticket 58089
     4131     * @ticket 58235
    39804132     *
    39814133     * @covers ::wp_filter_content_tags
    3982      * @covers ::wp_get_loading_attr_default
     4134     * @covers ::wp_get_loading_optimization_attributes
    39834135     */
    39844136    public function test_wp_filter_content_tags_does_not_lazy_load_special_images_within_the_content() {
     
    39864138
    39874139        // Force no lazy-loading on the image tag expected in the content.
    3988         $expected_content = wpautop( wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => false ) ) );
     4140        $expected_content = wpautop(
     4141            wp_get_attachment_image(
     4142                self::$large_id,
     4143                'large',
     4144                false,
     4145                array(
     4146                    'loading'       => false,
     4147                    'fetchpriority' => 'high',
     4148                )
     4149            )
     4150        );
     4151
     4152        // Reset high priority flag as the forced `fetchpriority="high"` above already modified it.
     4153        $this->reset_high_priority_element_flag();
    39894154
    39904155        // Overwrite post content with an image.
     
    40244189     * @covers ::wp_get_loading_attr_default
    40254190     *
     4191     * @expectedDeprecated wp_get_loading_attr_default
     4192     *
    40264193     * @dataProvider data_special_contexts_for_the_content
    40274194     *
     
    40384205     *
    40394206     * @covers ::wp_get_loading_attr_default
     4207     *
     4208     * @expectedDeprecated wp_get_loading_attr_default
    40404209     *
    40414210     * @dataProvider data_special_contexts_for_the_content
     
    40684237            'wp_get_attachment_image' => array( 'context' => 'wp_get_attachment_image' ),
    40694238        );
     4239    }
     4240
     4241    /**
     4242     * Tests that wp_get_loading_attr_default() returns the expected loading attribute value.
     4243     *
     4244     * @ticket 53675
     4245     * @ticket 56930
     4246     * @ticket 58235
     4247     *
     4248     * @covers ::wp_get_loading_optimization_attributes
     4249     *
     4250     * @dataProvider data_wp_get_loading_attr_default
     4251     *
     4252     * @param string $context
     4253     */
     4254    public function test_wp_get_loading_optimization_attributes( $context ) {
     4255        $attr = $this->get_width_height_for_high_priority();
     4256
     4257        // Return 'lazy' by default.
     4258        $this->assertSame(
     4259            array( 'loading' => 'lazy' ),
     4260            wp_get_loading_optimization_attributes( 'img', $attr, 'test' )
     4261        );
     4262        $this->assertSame(
     4263            array( 'loading' => 'lazy' ),
     4264            wp_get_loading_optimization_attributes( 'img', $attr, 'wp_get_attachment_image' )
     4265        );
     4266
     4267        // Return 'lazy' if not in the loop or the main query.
     4268        $this->assertSame(
     4269            array( 'loading' => 'lazy' ),
     4270            wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4271        );
     4272
     4273        $query = $this->get_new_wp_query_for_published_post();
     4274
     4275        while ( have_posts() ) {
     4276            the_post();
     4277
     4278            // Return 'lazy' if in the loop but not in the main query.
     4279            $this->assertSame(
     4280                array( 'loading' => 'lazy' ),
     4281                wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4282            );
     4283
     4284            // Set as main query.
     4285            $this->set_main_query( $query );
     4286
     4287            /*
     4288             * For contexts other than for the main content, still return 'lazy' even in the loop
     4289             * and in the main query, and do not increase the content media count.
     4290             */
     4291            $this->assertSame(
     4292                array( 'loading' => 'lazy' ),
     4293                wp_get_loading_optimization_attributes( 'img', $attr, 'wp_get_attachment_image' )
     4294            );
     4295
     4296            // First three element are not lazy loaded. However, first image is loaded with fetchpriority high.
     4297            $this->assertSame(
     4298                array( 'fetchpriority' => 'high' ),
     4299                wp_get_loading_optimization_attributes( 'img', $attr, $context ),
     4300                "Expected first image to not be lazy-loaded. First large image get's high fetchpriority."
     4301            );
     4302            $this->assertEmpty(
     4303                wp_get_loading_optimization_attributes( 'img', $attr, $context ),
     4304                'Expected second image to not be lazy-loaded.'
     4305            );
     4306            $this->assertEmpty(
     4307                wp_get_loading_optimization_attributes( 'img', $attr, $context ),
     4308                'Expected third image to not be lazy-loaded.'
     4309            );
     4310
     4311            // Return 'lazy' if in the loop and in the main query for any subsequent elements.
     4312            $this->assertSame(
     4313                array( 'loading' => 'lazy' ),
     4314                wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4315            );
     4316
     4317            // Yes, for all subsequent elements.
     4318            $this->assertSame(
     4319                array( 'loading' => 'lazy' ),
     4320                wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4321            );
     4322        }
     4323    }
     4324
     4325    /**
     4326     * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute value before loop but after get_header if not main query.
     4327     *
     4328     * @ticket 58211
     4329     * @ticket 58235
     4330     *
     4331     * @covers ::wp_get_loading_optimization_attributes
     4332     *
     4333     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
     4334     *
     4335     * @param string $context Context for the element for which the `loading` attribute value is requested.
     4336     */
     4337    public function test_wp_get_loading_optimization_attributes_before_loop_if_not_main_query( $context ) {
     4338        global $wp_query;
     4339
     4340        $wp_query = $this->get_new_wp_query_for_published_post();
     4341
     4342        do_action( 'get_header' );
     4343
     4344        $attr = $this->get_width_height_for_high_priority();
     4345
     4346        // Lazy if not main query.
     4347        $this->assertSame(
     4348            array( 'loading' => 'lazy' ),
     4349            wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4350        );
     4351    }
     4352
     4353    /**
     4354     * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute value before loop but after get_header in main query but header was not called.
     4355     *
     4356     * @ticket 58211
     4357     * @ticket 58235
     4358     *
     4359     * @covers ::wp_get_loading_optimization_attributes
     4360     *
     4361     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
     4362     *
     4363     * @param string $context Context for the element for which the `loading` attribute value is requested.
     4364     */
     4365    public function test_wp_get_loading_optimization_attributes_before_loop_in_main_query_but_header_not_called( $context ) {
     4366        global $wp_query;
     4367
     4368        $wp_query = $this->get_new_wp_query_for_published_post();
     4369        $this->set_main_query( $wp_query );
     4370
     4371        $attr = $this->get_width_height_for_high_priority();
     4372
     4373        // Lazy if header not called.
     4374        $this->assertSame(
     4375            array( 'loading' => 'lazy' ),
     4376            wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4377        );
     4378    }
     4379
     4380    /**
     4381     * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute value before loop but after get_header for main query.
     4382     *
     4383     * @ticket 58211
     4384     * @ticket 58235
     4385     *
     4386     * @covers ::wp_get_loading_optimization_attributes
     4387     *
     4388     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
     4389     *
     4390     * @param string $context Context for the element for which the `loading` attribute value is requested.
     4391     */
     4392    public function test_wp_get_loading_optimization_attributes_before_loop_if_main_query( $context ) {
     4393        global $wp_query;
     4394
     4395        $wp_query = $this->get_new_wp_query_for_published_post();
     4396        $this->set_main_query( $wp_query );
     4397        do_action( 'get_header' );
     4398
     4399        $attr = $this->get_width_height_for_high_priority();
     4400
     4401        // First image is loaded with high fetchpriority.
     4402        $this->assertSame(
     4403            array( 'fetchpriority' => 'high' ),
     4404            wp_get_loading_optimization_attributes( 'img', $attr, $context ),
     4405            'Expected first image to not be lazy-loaded. First large image is loaded with high fetchpriority.'
     4406        );
     4407    }
     4408
     4409    /**
     4410     * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute value after get_header and after loop.
     4411     *
     4412     * @ticket 58211
     4413     * @ticket 58235
     4414     *
     4415     * @covers ::wp_get_loading_optimization_attributes
     4416     *
     4417     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
     4418     *
     4419     * @param string $context Context for the element for which the `loading` attribute value is requested.
     4420     */
     4421    public function test_wp_get_loading_optimization_attributes_after_loop( $context ) {
     4422        global $wp_query;
     4423
     4424        $wp_query = $this->get_new_wp_query_for_published_post();
     4425        $this->set_main_query( $wp_query );
     4426
     4427        do_action( 'get_header' );
     4428
     4429        while ( have_posts() ) {
     4430            the_post();
     4431        }
     4432
     4433        $attr = $this->get_width_height_for_high_priority();
     4434        $this->assertSame(
     4435            array( 'loading' => 'lazy' ),
     4436            wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4437        );
     4438    }
     4439
     4440    /**
     4441     * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute if no loop.
     4442     *
     4443     * @ticket 58211
     4444     * @ticket 58235
     4445     *
     4446     * @covers ::wp_get_loading_optimization_attributes
     4447     *
     4448     * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
     4449     *
     4450     * @param string $context Context for the element for which the `loading` attribute value is requested.
     4451     */
     4452    public function test_wp_get_loading_optimization_attributes_no_loop( $context ) {
     4453        global $wp_query;
     4454
     4455        $wp_query = $this->get_new_wp_query_for_published_post();
     4456        $this->set_main_query( $wp_query );
     4457
     4458        // Ensure header and footer is called.
     4459        do_action( 'get_header' );
     4460        do_action( 'get_footer' );
     4461
     4462        $attr = $this->get_width_height_for_high_priority();
     4463
     4464        // Load lazy if the there is no loop and footer was called.
     4465        $this->assertSame(
     4466            array( 'loading' => 'lazy' ),
     4467            wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4468        );
     4469    }
     4470
     4471    /**
     4472     * Tests that wp_get_loading_optimization_attributes() returns 'lazy' for special contexts when they're used outside of 'the_content' filter.
     4473     *
     4474     * @ticket 58089
     4475     * @ticket 58235
     4476     *
     4477     * @covers ::wp_get_loading_optimization_attributes
     4478     *
     4479     * @dataProvider data_special_contexts_for_the_content
     4480     *
     4481     * @param string $context Context for the element for which the `loading` attribute value is requested.
     4482     */
     4483    public function test_wp_get_loading_optimization_attributes_should_return_lazy_for_special_contexts_outside_of_the_content( $context ) {
     4484        $attr = $this->get_width_height_for_high_priority();
     4485        $this->assertSame(
     4486            array( 'loading' => 'lazy' ),
     4487            wp_get_loading_optimization_attributes( 'img', $attr, $context )
     4488        );
     4489    }
     4490
     4491    /**
     4492     * Tests that wp_get_loading_optimization_attributes() returns false for special contexts when they're used within 'the_content' filter.
     4493     *
     4494     * @ticket 58089
     4495     * @ticket 58235
     4496     *
     4497     * @covers ::wp_get_loading_optimization_attributes
     4498     *
     4499     * @dataProvider data_special_contexts_for_the_content
     4500     *
     4501     * @param string $context Context for the element for which the `loading` attribute value is requested.
     4502     */
     4503    public function test_wp_get_loading_optimization_attributes_should_return_false_for_special_contexts_within_the_content( $context ) {
     4504        remove_all_filters( 'the_content' );
     4505
     4506        $result = null;
     4507        add_filter(
     4508            'the_content',
     4509            function( $content ) use ( &$result, $context ) {
     4510                $attr   = $this->get_width_height_for_high_priority();
     4511                $result = wp_get_loading_optimization_attributes( 'img', $attr, $context );
     4512                return $content;
     4513            }
     4514        );
     4515        apply_filters( 'the_content', '' );
     4516
     4517        $this->assertSame(
     4518            array( 'fetchpriority' => 'high' ),
     4519            $result,
     4520            'First large image is loaded with high fetchpriority.'
     4521        );
     4522    }
     4523
     4524    /**
     4525     * @ticket 44427
     4526     * @ticket 50367
     4527     * @ticket 58235
     4528     */
     4529    public function test_wp_img_tag_add_loading_optimization_attrs() {
     4530        $img = '<img src="example.png" alt=" width="300" height="225" />';
     4531        $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
     4532
     4533        $this->assertStringContainsString( ' loading="lazy"', $img );
     4534    }
     4535
     4536    /**
     4537     * @ticket 44427
     4538     * @ticket 50367
     4539     * @ticket 58235
     4540     */
     4541    public function test_wp_img_tag_add_loading_optimization_attrs_without_src() {
     4542        $img = '<img alt="" width="300" height="225" />';
     4543        $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
     4544
     4545        $this->assertStringNotContainsString( ' loading=', $img );
    40704546    }
    40714547
     
    41214597     *
    41224598     * @ticket 56588
     4599     * @ticket 58235
    41234600     *
    41244601     * @covers ::wp_trim_excerpt
     
    41324609         */
    41334610        $this->force_omit_loading_attr_threshold( 2 );
     4611
    41344612        $post_content  = '<img src="example.jpg" width="800" height="600">';
    41354613        $post_content .= '<p>Some text.</p>';
     
    41454623        update_post_meta( $post_id, '_thumbnail_id', $featured_image_id );
    41464624
    4147         $expected_image_tag = get_the_post_thumbnail( $post_id, 'post-thumbnail', array( 'loading' => false ) );
     4625        $expected_image_tag = get_the_post_thumbnail(
     4626            $post_id,
     4627            'post-thumbnail',
     4628            array(
     4629                'loading'       => false,
     4630                'fetchpriority' => 'high',
     4631            )
     4632        );
     4633
     4634        // Reset high priority flag as the forced `fetchpriority="high"` above already modified it.
     4635        $this->reset_high_priority_element_flag();
    41484636
    41494637        $wp_query     = new WP_Query( array( 'post__in' => array( $post_id ) ) );
     
    41794667        // Clean up the above filter.
    41804668        remove_filter( 'wp_omit_loading_attr_threshold', '__return_null', 100 );
     4669    }
     4670
     4671    private function reset_high_priority_element_flag() {
     4672        wp_high_priority_element_flag( true );
    41814673    }
    41824674
     
    42774769     * @ticket 58212
    42784770     *
    4279      * @covers ::wp_get_attachment_image()
     4771     * @covers ::wp_get_attachment_image
    42804772     */
    42814773    public function test_wp_get_attachment_image_context_filter_default() {
     
    42924784     * @ticket 58212
    42934785     *
    4294      * @covers ::wp_get_attachment_image()
     4786     * @covers ::wp_get_attachment_image
    42954787     */
    42964788    public function test_wp_get_attachment_image_context_filter_value_is_passed_correctly() {
     
    43084800        wp_get_attachment_image( self::$large_id );
    43094801        $this->assertSame( 'my_custom_context', $last_context );
     4802    }
     4803
     4804    /**
     4805     * Tests tag restriction for `wp_get_loading_optimization_attributes()`.
     4806     *
     4807     * @ticket 58235
     4808     *
     4809     * @covers ::wp_get_loading_optimization_attributes
     4810     *
     4811     * @dataProvider data_wp_get_loading_optimization_attributes_min_required_attrs
     4812     *
     4813     * @param string $tag_name The tag name.
     4814     * @param string $attr Element attributes.
     4815     * @param array  $expected Expected return value.
     4816     * @param string $message Message to display if the test fails.
     4817     */
     4818    public function test_wp_get_loading_optimization_attributes_min_required_attrs( $tag_name, $attr, $expected, $message ) {
     4819        $context = 'the_post_thumbnail';
     4820        $this->assertSame( wp_get_loading_optimization_attributes( $tag_name, $attr, $context ), $expected, $message );
     4821    }
     4822
     4823    /**
     4824     * Data provider.
     4825     *
     4826     * @return array[]
     4827     */
     4828    public function data_wp_get_loading_optimization_attributes_min_required_attrs() {
     4829        return array(
     4830            'img_with_min_attrs' => array(
     4831                'img',
     4832                array(
     4833                    'width'  => 100,
     4834                    'height' => 100,
     4835                ),
     4836                array( 'loading' => 'lazy' ),
     4837                'Expected default `loading="lazy"`.',
     4838            ),
     4839            'img_without_height' => array(
     4840                'img',
     4841                array( 'width' => 100 ),
     4842                array(),
     4843                'Expected blank array as height is required.',
     4844            ),
     4845            'img_without_width'  => array(
     4846                'img',
     4847                array( 'height' => 100 ),
     4848                array(),
     4849                'Expected blank array as width is required.',
     4850            ),
     4851        );
     4852    }
     4853
     4854    /**
     4855     * Tests tag restriction for `wp_get_loading_optimization_attributes()`.
     4856     *
     4857     * @ticket 58235
     4858     *
     4859     * @covers ::wp_get_loading_optimization_attributes
     4860     *
     4861     * @dataProvider data_wp_get_loading_optimization_attributes_check_allowed_tags
     4862     *
     4863     * @param string $tag_name The tag name.
     4864     * @param array  $expected Expected return value.
     4865     * @param string $message Message to display if the test fails.
     4866     */
     4867    public function test_wp_get_loading_optimization_attributes_check_allowed_tags( $tag_name, $expected, $message ) {
     4868        $attr    = $this->get_width_height_for_high_priority();
     4869        $context = 'the_post_thumbnail';
     4870        $this->assertSame( wp_get_loading_optimization_attributes( $tag_name, $attr, $context ), $expected, $message );
     4871    }
     4872
     4873    /**
     4874     * Data provider.
     4875     *
     4876     * @return array[]
     4877     */
     4878    public function data_wp_get_loading_optimization_attributes_check_allowed_tags() {
     4879        return array(
     4880            'img'    => array(
     4881                'img',
     4882                array( 'loading' => 'lazy' ),
     4883                'Expected `loading="lazy"` for the img.',
     4884            ),
     4885            'iframe' => array(
     4886                'iframe',
     4887                array(
     4888                    'loading' => 'lazy',
     4889                ),
     4890                'Expected `loading="lazy"` for the iframe.',
     4891            ),
     4892            'video'  =>
     4893            array(
     4894                'video',
     4895                array(),
     4896                'Function should return empty array as video tag is not supported.',
     4897            ),
     4898        );
     4899    }
     4900
     4901    /**
     4902     * @ticket 58235
     4903     *
     4904     * @covers ::wp_get_loading_optimization_attributes
     4905     */
     4906    public function test_wp_get_loading_optimization_attributes_skip_for_block_template() {
     4907        $attr = $this->get_width_height_for_high_priority();
     4908
     4909        // Skip logic if context is `template`.
     4910        $this->assertSame(
     4911            array(),
     4912            wp_get_loading_optimization_attributes( 'img', $attr, 'template' ),
     4913            'Skip logic and return blank array for block template.'
     4914        );
     4915    }
     4916
     4917    /**
     4918     * @ticket 58235
     4919     *
     4920     * @covers ::wp_get_loading_optimization_attributes
     4921     */
     4922    public function test_wp_get_loading_optimization_attributes_header_block_template() {
     4923        $attr = $this->get_width_height_for_high_priority();
     4924
     4925        // Skip logic if context is `template`.
     4926        $this->assertSame(
     4927            array( 'fetchpriority' => 'high' ),
     4928            wp_get_loading_optimization_attributes( 'img', $attr, 'template_part_' . WP_TEMPLATE_PART_AREA_HEADER ),
     4929            'Images in the header block template part should not be lazy-loaded and first large image is set high fetchpriority.'
     4930        );
     4931    }
     4932
     4933    /**
     4934     * @ticket 58235
     4935     *
     4936     * @covers ::wp_get_loading_optimization_attributes
     4937     * @expectedIncorrectUsage wp_get_loading_optimization_attributes
     4938     */
     4939    public function test_wp_get_loading_optimization_attributes_incorrect_loading_attrs() {
     4940        $attr                  = $this->get_width_height_for_high_priority();
     4941        $attr['loading']       = 'lazy';
     4942        $attr['fetchpriority'] = 'high';
     4943
     4944        $this->assertSame(
     4945            array(
     4946                'loading'       => 'lazy',
     4947                'fetchpriority' => 'high',
     4948            ),
     4949            wp_get_loading_optimization_attributes( 'img', $attr, 'test' ),
     4950            'This should return both lazy-loading and high fetchpriority, but with doing_it_wrong message.'
     4951        );
     4952    }
     4953
     4954    /**
     4955     * @ticket 58235
     4956     *
     4957     * @covers ::wp_get_loading_optimization_attributes
     4958     */
     4959    public function test_wp_get_loading_optimization_attributes_if_loading_attr_present() {
     4960        $attr            = $this->get_width_height_for_high_priority();
     4961        $attr['loading'] = 'eager';
     4962
     4963        // Check fetchpriority high logic if loading attribute is present.
     4964        $this->assertSame(
     4965            array(
     4966                'fetchpriority' => 'high',
     4967            ),
     4968            wp_get_loading_optimization_attributes( 'img', $attr, 'test' ),
     4969            'fetchpriority should be set to high.'
     4970        );
     4971    }
     4972
     4973    /**
     4974     * @ticket 58235
     4975     *
     4976     * @covers ::wp_get_loading_optimization_attributes
     4977     */
     4978    public function test_wp_get_loading_optimization_attributes_low_res_image() {
     4979        $attr = array(
     4980            'width'   => 100,
     4981            'height'  => 100,
     4982            'loading' => 'eager',
     4983        );
     4984
     4985        // fetchpriority not set as image is of lower resolution.
     4986        $this->assertSame(
     4987            array(),
     4988            wp_get_loading_optimization_attributes( 'img', $attr, 'test' ),
     4989            'loading optimization attr array should be empty.'
     4990        );
     4991    }
     4992
     4993    /**
     4994     * @ticket 58235
     4995     *
     4996     * @covers ::wp_maybe_add_fetchpriority_high_attr
     4997     *
     4998     * @dataProvider data_wp_maybe_add_fetchpriority_high_attr
     4999     */
     5000    public function test_wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr, $expected_fetchpriority ) {
     5001        $loading_attrs = wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr );
     5002
     5003        if ( $expected_fetchpriority ) {
     5004            $this->assertArrayHasKey( 'fetchpriority', $loading_attrs, 'fetchpriority attribute should be present' );
     5005            $this->assertSame( $expected_fetchpriority, $loading_attrs['fetchpriority'], 'fetchpriority attribute has incorrect value' );
     5006        } else {
     5007            $this->assertArrayNotHasKey( 'fetchpriority', $loading_attrs, 'fetchpriority attribute should not be present' );
     5008        }
     5009    }
     5010
     5011    /**
     5012     * Data provider.
     5013     *
     5014     * @return array[]
     5015     */
     5016    public function data_wp_maybe_add_fetchpriority_high_attr() {
     5017        return array(
     5018            'small image'                   => array(
     5019                array(),
     5020                'img',
     5021                $this->get_insufficient_width_height_for_high_priority(),
     5022                false,
     5023            ),
     5024            'large image'                   => array(
     5025                array(),
     5026                'img',
     5027                $this->get_width_height_for_high_priority(),
     5028                'high',
     5029            ),
     5030            'image with loading=lazy'       => array(
     5031                array( 'loading' => 'lazy' ),
     5032                'img',
     5033                $this->get_width_height_for_high_priority(),
     5034                false,
     5035            ),
     5036            'image with loading=eager'      => array(
     5037                array( 'loading' => 'eager' ),
     5038                'img',
     5039                $this->get_width_height_for_high_priority(),
     5040                'high',
     5041            ),
     5042            'image with fetchpriority=high' => array(
     5043                array(),
     5044                'img',
     5045                array_merge(
     5046                    $this->get_insufficient_width_height_for_high_priority(),
     5047                    array( 'fetchpriority' => 'high' )
     5048                ),
     5049                'high',
     5050            ),
     5051            'image with fetchpriority=low'  => array(
     5052                array(),
     5053                'img',
     5054                array_merge(
     5055                    $this->get_insufficient_width_height_for_high_priority(),
     5056                    array( 'fetchpriority' => 'low' )
     5057                ),
     5058                false,
     5059            ),
     5060            'non-image element'             => array(
     5061                array(),
     5062                'video',
     5063                $this->get_width_height_for_high_priority(),
     5064                false,
     5065            ),
     5066        );
     5067    }
     5068
     5069    /**
     5070     * @ticket 58235
     5071     *
     5072     * @covers ::wp_maybe_add_fetchpriority_high_attr
     5073     */
     5074    public function test_wp_maybe_add_fetchpriority_high_attr_min_priority_filter() {
     5075        $attr = array(
     5076            'width'  => 50,
     5077            'height' => 50,
     5078        );
     5079
     5080        add_filter(
     5081            'wp_min_priority_img_pixels',
     5082            static function( $res ) {
     5083                return 2500; // 50*50=2500
     5084            }
     5085        );
     5086
     5087        // fetchpriority set to high as resolution is equal to (or greater than) 2500.
     5088        $this->assertSame(
     5089            array(
     5090                'fetchpriority' => 'high',
     5091            ),
     5092            wp_maybe_add_fetchpriority_high_attr( array(), 'img', $attr )
     5093        );
    43105094    }
    43115095
     
    44085192        $wp_the_query = $query;
    44095193    }
     5194
     5195    /**
     5196     * Returns an array with dimension attribute values eligible for a high priority image.
     5197     *
     5198     * @return array Associative array with 'width' and 'height' keys.
     5199     */
     5200    private function get_width_height_for_high_priority() {
     5201        /*
     5202         * The product of width * height must be >50000 to qualify for high priority image.
     5203         * 300 * 200 = 60000
     5204         */
     5205        return array(
     5206            'width'  => 300,
     5207            'height' => 200,
     5208        );
     5209    }
     5210
     5211    /**
     5212     * Returns an array with dimension attribute values ineligible for a high priority image.
     5213     *
     5214     * @return array Associative array with 'width' and 'height' keys.
     5215     */
     5216    private function get_insufficient_width_height_for_high_priority() {
     5217        /*
     5218         * The product of width * height must be >50000 to qualify for high priority image.
     5219         * 200 * 100 = 20000
     5220         */
     5221        return array(
     5222            'width'  => 200,
     5223            'height' => 100,
     5224        );
     5225    }
    44105226}
    44115227
Note: See TracChangeset for help on using the changeset viewer.