Make WordPress Core

Changeset 55825


Ignore:
Timestamp:
05/18/2023 07:53:37 PM (19 months ago)
Author:
flixos90
Message:

Media: Prevent special images within post content to skew image counts and cause lazy-loading bugs.

In order to skip lazy-loading the first few images on a page, as of WordPress 5.9 there has been logic to count images that are eligible based on certain criteria. One of those groups are images that appear within the content of a post.

This changeset fixes a bug where images created via get_the_post_thumbnail() or wp_get_attachment_image() that are injected into the post content would skew the count and therefore result in all images to be lazy-loaded, potentially hurting load time performance. This is relevant for example when those functions are called in server-side rendered blocks, or any other filter callbacks hooked into the_content.

Props flixos90, antpb, joedolson, spacedmonkey, mukesh27, thekt12, costdev, jrf.
Fixes #58089.
See #53675.

Location:
trunk
Files:
2 edited

Legend:

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

    r55821 r55825  
    55085508
    55095509    /*
     5510     * Skip programmatically created images within post content as they need to be handled together with the other
     5511     * images within the post content.
     5512     * Without this clause, they would already be counted below which skews the number and can result in the first
     5513     * post content image being lazy-loaded only because there are images elsewhere in the post content.
     5514     */
     5515    if ( ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) && doing_filter( 'the_content' ) ) {
     5516        return false;
     5517    }
     5518
     5519    /*
    55105520     * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
    55115521     * as they are likely above the fold.
  • trunk/tests/phpunit/tests/media.php

    r55822 r55825  
    38543854    }
    38553855
     3856    /**
     3857     * @ticket 58089
     3858     *
     3859     * @covers ::wp_filter_content_tags
     3860     * @covers ::wp_get_loading_attr_default
     3861     */
     3862    public function test_wp_filter_content_tags_does_not_lazy_load_special_images_within_the_content() {
     3863        global $wp_query, $wp_the_query;
     3864
     3865        // Force no lazy-loading on the image tag expected in the content.
     3866        $expected_content = wpautop( wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => false ) ) );
     3867
     3868        // Overwrite post content with an image.
     3869        add_filter(
     3870            'the_content',
     3871            static function() {
     3872                // Replace content with an image tag, i.e. the 'wp_get_attachment_image' context is used while running 'the_content' filter.
     3873                return wp_get_attachment_image( self::$large_id, 'large', false );
     3874            },
     3875            9 // Run before wp_filter_content_tags().
     3876        );
     3877
     3878        /*
     3879         * We have to run a main query loop so that the first 'the_content' context image is not
     3880         * lazy-loaded.
     3881         * Without the fix from 58089, the image would still be lazy-loaded since the check for the
     3882         * separately invoked 'wp_get_attachment_image' context would lead to that.
     3883         */
     3884        $wp_query     = new WP_Query( array( 'post__in' => array( self::$post_ids['publish'] ) ) );
     3885        $wp_the_query = $wp_query;
     3886        $this->reset_content_media_count();
     3887        $this->reset_omit_loading_attr_filter();
     3888        $content = '';
     3889        while ( have_posts() ) {
     3890            the_post();
     3891            $content = get_echo( 'the_content' );
     3892        }
     3893
     3894        // Ensure that parsed content has the image without lazy-loading.
     3895        $this->assertSame( $expected_content, $content );
     3896    }
     3897
     3898    /**
     3899     * Tests that wp_get_loading_attr_default() returns 'lazy' for special contexts when they're used outside of 'the_content' filter.
     3900     *
     3901     * @ticket 58089
     3902     *
     3903     * @covers ::wp_get_loading_attr_default
     3904     *
     3905     * @dataProvider data_special_contexts_for_the_content
     3906     *
     3907     * @param string $context Context for the element for which the `loading` attribute value is requested.
     3908     */
     3909    public function test_wp_get_loading_attr_default_should_return_lazy_for_special_contexts_outside_of_the_content( $context ) {
     3910        $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) );
     3911    }
     3912
     3913    /**
     3914     * Tests that wp_get_loading_attr_default() returns false for special contexts when they're used within 'the_content' filter.
     3915     *
     3916     * @ticket 58089
     3917     *
     3918     * @covers ::wp_get_loading_attr_default
     3919     *
     3920     * @dataProvider data_special_contexts_for_the_content
     3921     *
     3922     * @param string $context Context for the element for which the `loading` attribute value is requested.
     3923     */
     3924    public function test_wp_get_loading_attr_default_should_return_false_for_special_contexts_within_the_content( $context ) {
     3925        remove_all_filters( 'the_content' );
     3926
     3927        $result = null;
     3928        add_filter(
     3929            'the_content',
     3930            function( $content ) use ( &$result, $context ) {
     3931                $result = wp_get_loading_attr_default( $context );
     3932                return $content;
     3933            }
     3934        );
     3935        apply_filters( 'the_content', '' );
     3936        $this->assertFalse( $result );
     3937    }
     3938
     3939    /**
     3940     * Data provider.
     3941     *
     3942     * @return array[]
     3943     */
     3944    public function data_special_contexts_for_the_content() {
     3945        return array(
     3946            'the_post_thumbnail'      => array( 'context' => 'the_post_thumbnail' ),
     3947            'wp_get_attachment_image' => array( 'context' => 'wp_get_attachment_image' ),
     3948        );
     3949    }
     3950
    38563951    private function reset_content_media_count() {
    38573952        // Get current value without increasing.
Note: See TracChangeset for help on using the changeset viewer.