Make WordPress Core

Changeset 56347


Ignore:
Timestamp:
08/02/2023 05:56:16 PM (12 months ago)
Author:
flixos90
Message:

Media: Simplify logic in wp_get_loading_optimization_attributes().

While the wp_get_loading_optimization_attributes() function was only recently introduced in 6.3, its code was mostly ported over from the now deprecated wp_get_loading_attr_default() function introduced in 5.5.

That function started out in a simple way, but over time was expanded with more and more conditionals on when to avoid lazy-loading, which ended up making the logic extremely complex and hard to follow.

This changeset refactors the logic to simplify it, in a way that allows to follow it more sequentially, and without making any functional changes, ensuring that the extensive existing unit test coverage still passes. This will facilitate future enhancements to the function to be less error-prone and make it more accessible to new contributors.

Props flixos90, joemcgill.
Fixes #58891.

Location:
trunk
Files:
2 edited

Legend:

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

    r56283 r56347  
    56055605    global $wp_query;
    56065606
    5607     /*
    5608      * Closure for postprocessing logic.
    5609      * It is here to avoid duplicate logic in many places below, without having
    5610      * to introduce a very specific private global function.
    5611      */
    5612     $postprocess = static function( $loading_attributes, $with_fetchpriority = false ) use ( $tag_name, $attr, $context ) {
    5613         // Potentially add `fetchpriority="high"`.
    5614         if ( $with_fetchpriority ) {
    5615             $loading_attributes = wp_maybe_add_fetchpriority_high_attr( $loading_attributes, $tag_name, $attr );
    5616         }
    5617         // Potentially strip `loading="lazy"` if the feature is disabled.
    5618         if ( isset( $loading_attributes['loading'] ) && ! wp_lazy_loading_enabled( $tag_name, $context ) ) {
    5619             unset( $loading_attributes['loading'] );
    5620         }
    5621         return $loading_attributes;
    5622     };
    5623 
    5624     // Closure to increase media count for images with certain minimum threshold, mostly used for header images.
    5625     $maybe_increase_content_media_count = static function() use ( $attr ) {
    5626         /** This filter is documented in wp-admin/includes/media.php */
    5627         $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );
    5628         // Images with a certain minimum size in the header of the page are also counted towards the threshold.
    5629         if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
    5630             wp_increase_content_media_count();
    5631         }
    5632     };
    5633 
    56345607    $loading_attrs = array();
    56355608
     
    56525625    }
    56535626
     5627    /*
     5628     * Skip programmatically created images within post content as they need to be handled together with the other
     5629     * images within the post content.
     5630     * Without this clause, they would already be considered within their own context which skews the image count and
     5631     * can result in the first post content image being lazy-loaded or an image further down the page being marked as a
     5632     * high priority.
     5633     */
     5634    switch ( $context ) {
     5635        case 'the_post_thumbnail':
     5636        case 'wp_get_attachment_image':
     5637        case 'widget_media_image':
     5638            if ( doing_filter( 'the_content' ) ) {
     5639                return $loading_attrs;
     5640            }
     5641    }
     5642
     5643    /*
     5644     * The key function logic starts here.
     5645     */
     5646    $maybe_in_viewport    = null;
     5647    $increase_count       = false;
     5648    $maybe_increase_count = false;
     5649
     5650    // Logic to handle a `loading` attribute that is already provided.
    56545651    if ( isset( $attr['loading'] ) ) {
    56555652        /*
    5656          * While any `loading` value could be set in `$loading_attrs`, for
    5657          * consistency we only do it for `loading="lazy"` since that is the
    5658          * only possible value that WordPress core would apply on its own.
     5653         * Interpret "lazy" as not in viewport. Any other value can be
     5654         * interpreted as in viewport (realistically only "eager" or `false`
     5655         * to force-omit the attribute are other potential values).
    56595656         */
    56605657        if ( 'lazy' === $attr['loading'] ) {
     5658            $maybe_in_viewport = false;
     5659        } else {
     5660            $maybe_in_viewport = true;
     5661        }
     5662    }
     5663
     5664    // Logic to handle a `fetchpriority` attribute that is already provided.
     5665    if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) {
     5666        /*
     5667         * If the image was already determined to not be in the viewport (e.g.
     5668         * from an already provided `loading` attribute), trigger a warning.
     5669         * Otherwise, the value can be interpreted as in viewport, since only
     5670         * the most important in-viewport image should have `fetchpriority` set
     5671         * to "high".
     5672         */
     5673        if ( false === $maybe_in_viewport ) {
     5674            _doing_it_wrong(
     5675                __FUNCTION__,
     5676                __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ),
     5677                '6.3.0'
     5678            );
     5679            /*
     5680             * Set `fetchpriority` here for backward-compatibility as we should
     5681             * not override what a developer decided, even though it seems
     5682             * incorrect.
     5683             */
     5684            $loading_attrs['fetchpriority'] = 'high';
     5685        } else {
     5686            $maybe_in_viewport = true;
     5687        }
     5688    }
     5689
     5690    if ( null === $maybe_in_viewport ) {
     5691        switch ( $context ) {
     5692            // Consider elements with these header-specific contexts to be in viewport.
     5693            case 'template_part_' . WP_TEMPLATE_PART_AREA_HEADER:
     5694            case 'get_header_image_tag':
     5695                $maybe_in_viewport    = true;
     5696                $maybe_increase_count = true;
     5697                break;
     5698            // Count main content elements and detect whether in viewport.
     5699            case 'the_content':
     5700            case 'the_post_thumbnail':
     5701            case 'do_shortcode':
     5702                // Only elements within the main query loop have special handling.
     5703                if ( ! is_admin() && in_the_loop() && is_main_query() ) {
     5704                    /*
     5705                     * Get the content media count, since this is a main query
     5706                     * content element. This is accomplished by "increasing"
     5707                     * the count by zero, as the only way to get the count is
     5708                     * to call this function.
     5709                     * The actual count increase happens further below, based
     5710                     * on the `$increase_count` flag set here.
     5711                     */
     5712                    $content_media_count = wp_increase_content_media_count( 0 );
     5713                    $increase_count      = true;
     5714
     5715                    // If the count so far is below the threshold, `loading` attribute is omitted.
     5716                    if ( $content_media_count < wp_omit_loading_attr_threshold() ) {
     5717                        $maybe_in_viewport = true;
     5718                    } else {
     5719                        $maybe_in_viewport = false;
     5720                    }
     5721                }
     5722                /*
     5723                 * For the 'the_post_thumbnail' context, the following case
     5724                 * clause needs to be considered as well, therefore skip the
     5725                 * break statement here if the viewport has not been
     5726                 * determined.
     5727                 */
     5728                if ( 'the_post_thumbnail' !== $context || null !== $maybe_in_viewport ) {
     5729                    break;
     5730                }
     5731            // phpcs:ignore Generic.WhiteSpace.ScopeIndent.Incorrect
     5732            // Consider elements before the loop as being in viewport.
     5733            case 'wp_get_attachment_image':
     5734            case 'widget_media_image':
     5735                if (
     5736                    // Only apply for main query but before the loop.
     5737                    $wp_query->before_loop && $wp_query->is_main_query()
     5738                    /*
     5739                     * Any image before the loop, but after the header has started should not be lazy-loaded,
     5740                     * except when the footer has already started which can happen when the current template
     5741                     * does not include any loop.
     5742                     */
     5743                    && did_action( 'get_header' ) && ! did_action( 'get_footer' )
     5744                ) {
     5745                    $maybe_in_viewport    = true;
     5746                    $maybe_increase_count = true;
     5747                }
     5748                break;
     5749        }
     5750    }
     5751
     5752    /*
     5753     * If the element is in the viewport (`true`), potentially add
     5754     * `fetchpriority` with a value of "high". Otherwise, i.e. if the element
     5755     * is not not in the viewport (`false`) or it is unknown (`null`), add
     5756     * `loading` with a value of "lazy".
     5757     */
     5758    if ( $maybe_in_viewport ) {
     5759        $loading_attrs = wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr );
     5760    } else {
     5761        // Only add `loading="lazy"` if the feature is enabled.
     5762        if ( wp_lazy_loading_enabled( $tag_name, $context ) ) {
    56615763            $loading_attrs['loading'] = 'lazy';
    5662             if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) {
    5663                 _doing_it_wrong(
    5664                     __FUNCTION__,
    5665                     __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ),
    5666                     '6.3.0'
    5667                 );
    5668             }
    5669         }
    5670 
    5671         return $postprocess( $loading_attrs, true );
    5672     }
    5673 
    5674     // An image with `fetchpriority="high"` cannot be assigned `loading="lazy"` at the same time.
    5675     if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) {
    5676         return $postprocess( $loading_attrs, true );
     5764        }
    56775765    }
    56785766
    56795767    /*
    5680      * Do not lazy-load images in the header block template part, as they are likely above the fold.
    5681      * For classic themes, this is handled in the condition below using the 'get_header' action.
    5682      */
    5683     $header_area = WP_TEMPLATE_PART_AREA_HEADER;
    5684     if ( "template_part_{$header_area}" === $context ) {
    5685         // Increase media count if there are images in header above a certian minimum size threshold.
    5686         $maybe_increase_content_media_count();
    5687         return $postprocess( $loading_attrs, true );
    5688     }
    5689 
    5690     // The custom header image is always expected to be in the header.
    5691     if ( 'get_header_image_tag' === $context ) {
    5692         // Increase media count if there are images in header above a certian minimum size threshold.
    5693         $maybe_increase_content_media_count();
    5694         return $postprocess( $loading_attrs, true );
    5695     }
    5696 
    5697     // Special handling for programmatically created image tags.
    5698     if ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context || 'widget_media_image' === $context ) {
    5699         /*
    5700          * Skip programmatically created images within post content as they need to be handled together with the other
    5701          * images within the post content.
    5702          * Without this clause, they would already be considered below which skews the image count and can result in
    5703          * the first post content image being lazy-loaded or an image further down the page being marked as a high
    5704          * priority.
    5705          */
    5706         if ( doing_filter( 'the_content' ) ) {
    5707             return $loading_attrs;
    5708         }
    5709 
    5710         // Conditionally skip lazy-loading on images before the loop.
    5711         if (
    5712             // Only apply for main query but before the loop.
    5713             $wp_query->before_loop && $wp_query->is_main_query()
    5714             /*
    5715              * Any image before the loop, but after the header has started should not be lazy-loaded,
    5716              * except when the footer has already started which can happen when the current template
    5717              * does not include any loop.
    5718              */
    5719             && did_action( 'get_header' ) && ! did_action( 'get_footer' )
    5720         ) {
    5721             // Increase media count if there are images in header above a certian minimum size threshold.
    5722             $maybe_increase_content_media_count();
    5723             return $postprocess( $loading_attrs, true );
    5724         }
    5725     }
    5726 
    5727     /*
    5728      * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
    5729      * as they are likely above the fold. Shortcodes are processed after content images, so if
    5730      * thresholds haven't already been met, apply the same logic to those as well.
    5731      */
    5732     if ( 'the_content' === $context || 'the_post_thumbnail' === $context || 'do_shortcode' === $context ) {
    5733         // Only elements within the main query loop have special handling.
    5734         if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
    5735             $loading_attrs['loading'] = 'lazy';
    5736             return $postprocess( $loading_attrs, false );
    5737         }
    5738 
    5739         // Increase the counter since this is a main query content element.
    5740         $content_media_count = wp_increase_content_media_count();
    5741 
    5742         // If the count so far is below the threshold, `loading` attribute is omitted.
    5743         if ( $content_media_count <= wp_omit_loading_attr_threshold() ) {
    5744             // The first largest image will still get `fetchpriority='high'`.
    5745             return $postprocess( $loading_attrs, true );
    5746         }
    5747     }
    5748 
    5749     // Lazy-load by default for any unknown context.
    5750     $loading_attrs['loading'] = 'lazy';
    5751     return $postprocess( $loading_attrs, false );
     5768     * If flag was set based on contextual logic above, increase the content
     5769     * media count, either unconditionally, or based on whether the image size
     5770     * is larger than the threshold.
     5771     */
     5772    if ( $increase_count ) {
     5773        wp_increase_content_media_count();
     5774    } elseif ( $maybe_increase_count ) {
     5775        /** This filter is documented in wp-admin/includes/media.php */
     5776        $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 );
     5777
     5778        if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) {
     5779            wp_increase_content_media_count();
     5780        }
     5781    }
     5782
     5783    return $loading_attrs;
    57525784}
    57535785
  • trunk/tests/phpunit/tests/media.php

    r56214 r56347  
    49574957        $attr['fetchpriority'] = 'high';
    49584958
    4959         $this->assertSame(
     4959        $this->assertEqualSetsWithIndex(
    49604960            array(
    49614961                'loading'       => 'lazy',
Note: See TracChangeset for help on using the changeset viewer.