Make WordPress Core

Changeset 55825


Ignore:
Timestamp:
05/18/2023 07:53:37 PM (3 years 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.