diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 36be77efddcd4..1626995290d24 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -4658,3 +4658,149 @@ function wp_queue_comments_for_comment_meta_lazyload( $comments ) { wp_lazyload_comment_meta( $comment_ids ); } + +/** + * Gets the default value to use for a `loading` attribute on an element. + * + * This function should only be called for a tag and context if lazy-loading is generally enabled. + * + * The function usually returns 'lazy', but uses certain heuristics to guess whether the current element is likely to + * appear above the fold, in which case it returns a boolean `false`, which will lead to the `loading` attribute being + * omitted on the element. The purpose of this refinement is to avoid lazy-loading elements that are within the initial + * viewport, which can have a negative performance impact. + * + * Under the hood, the function uses {@see wp_increase_content_media_count()} every time it is called for an element + * within the main content. If the element is the very first content element, the `loading` attribute will be omitted. + * This default threshold of 3 content elements to omit the `loading` attribute for can be customized using the + * {@see 'wp_omit_loading_attr_threshold'} filter. + * + * @since 5.9.0 + * @deprecated 6.3.0 Use wp_get_loading_optimization_attributes() instead. + * @see wp_get_loading_optimization_attributes() + * + * @global WP_Query $wp_query WordPress Query object. + * + * @param string $context Context for the element for which the `loading` attribute value is requested. + * @return string|bool The default `loading` attribute value. Either 'lazy', 'eager', or a boolean `false`, to indicate + * that the `loading` attribute should be skipped. + */ +function wp_get_loading_attr_default( $context ) { + _deprecated_function( __FUNCTION__, '6.3.0', 'wp_get_loading_optimization_attributes' ); + global $wp_query; + + // Skip lazy-loading for the overall block template, as it is handled more granularly. + if ( 'template' === $context ) { + return false; + } + + /* + * Do not lazy-load images in the header block template part, as they are likely above the fold. + * For classic themes, this is handled in the condition below using the 'get_header' action. + */ + $header_area = WP_TEMPLATE_PART_AREA_HEADER; + if ( "template_part_{$header_area}" === $context ) { + return false; + } + + // Special handling for programmatically created image tags. + if ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) { + /* + * Skip programmatically created images within post content as they need to be handled together with the other + * images within the post content. + * Without this clause, they would already be counted below which skews the number and can result in the first + * post content image being lazy-loaded only because there are images elsewhere in the post content. + */ + if ( doing_filter( 'the_content' ) ) { + return false; + } + + // Conditionally skip lazy-loading on images before the loop. + if ( + // Only apply for main query but before the loop. + $wp_query->before_loop && $wp_query->is_main_query() + /* + * Any image before the loop, but after the header has started should not be lazy-loaded, + * except when the footer has already started which can happen when the current template + * does not include any loop. + */ + && did_action( 'get_header' ) && ! did_action( 'get_footer' ) + ) { + return false; + } + } + + /* + * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded, + * as they are likely above the fold. + */ + if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) { + // Only elements within the main query loop have special handling. + if ( is_admin() || ! in_the_loop() || ! is_main_query() ) { + return 'lazy'; + } + + // Increase the counter since this is a main query content element. + $content_media_count = wp_increase_content_media_count(); + + // If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted. + if ( $content_media_count <= wp_omit_loading_attr_threshold() ) { + return false; + } + + // For elements after the threshold, lazy-load them as usual. + return 'lazy'; + } + + // Lazy-load by default for any unknown context. + return 'lazy'; +} + +/** + * Adds `loading` attribute to an `img` HTML tag. + * + * @since 5.5.0 + * @deprecated 6.3.0 Use wp_img_tag_add_loading_optimization_attrs() instead. + * @see wp_img_tag_add_loading_optimization_attrs() + * + * @param string $image The HTML `img` tag where the attribute should be added. + * @param string $context Additional context to pass to the filters. + * @return string Converted `img` tag with `loading` attribute added. + */ +function wp_img_tag_add_loading_attr( $image, $context ) { + _deprecated_function( __FUNCTION__, '6.3.0', 'wp_img_tag_add_loading_optimization_attrs' ); + /* + * Get loading attribute value to use. This must occur before the conditional check below so that even images that + * are ineligible for being lazy-loaded are considered. + */ + $value = wp_get_loading_attr_default( $context ); + + // Images should have source and dimension attributes for the `loading` attribute to be added. + if ( ! str_contains( $image, ' src="' ) || ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) { + return $image; + } + + /** + * Filters the `loading` attribute value to add to an image. Default `lazy`. + * + * Returning `false` or an empty string will not add the attribute. + * Returning `true` will add the default value. + * + * @since 5.5.0 + * + * @param string|bool $value The `loading` attribute value. Returning a falsey value will result in + * the attribute being omitted for the image. + * @param string $image The HTML `img` tag to be filtered. + * @param string $context Additional context about how the function was called or where the img tag is. + */ + $value = apply_filters( 'wp_img_tag_add_loading_attr', $value, $image, $context ); + + if ( $value ) { + if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) { + $value = 'lazy'; + } + + return str_replace( ']+>/', $content, $matches, PREG_SET_ORDER ) ) { @@ -1857,10 +1867,8 @@ function wp_filter_content_tags( $content, $context = null ) { $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id ); } - // Add 'loading' attribute if applicable. - if ( $add_img_loading_attr && ! str_contains( $filtered_image, ' loading=' ) ) { - $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context ); - } + // Add loading optimization attributes if applicable. + $filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context ); // Add 'decoding=async' attribute unless a 'decoding' attribute is already present. if ( ! str_contains( $filtered_image, ' decoding=' ) ) { @@ -1914,45 +1922,101 @@ function wp_filter_content_tags( $content, $context = null ) { } /** - * Adds `loading` attribute to an `img` HTML tag. + * Adds optimization attributes to an `img` HTML tag. * - * @since 5.5.0 + * @since 6.3.0 * * @param string $image The HTML `img` tag where the attribute should be added. * @param string $context Additional context to pass to the filters. - * @return string Converted `img` tag with `loading` attribute added. + * @return string Converted `img` tag with optimization attributes added. */ -function wp_img_tag_add_loading_attr( $image, $context ) { - // Get loading attribute value to use. This must occur before the conditional check below so that even images that - // are ineligible for being lazy-loaded are considered. - $value = wp_get_loading_attr_default( $context ); +function wp_img_tag_add_loading_optimization_attrs( $image, $context ) { + $width = preg_match( '/ width=["\']([0-9]+)["\']/', $image, $match_width ) ? (int) $match_width[1] : null; + $height = preg_match( '/ height=["\']([0-9]+)["\']/', $image, $match_height ) ? (int) $match_height[1] : null; + $loading_val = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading ) ? $match_loading[1] : null; + $fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority ) ? $match_fetchpriority[1] : null; + + /* + * Get loading optimization attributes to use. + * This must occur before the conditional check below so that even images + * that are ineligible for being lazy-loaded are considered. + */ + $optimization_attrs = wp_get_loading_optimization_attributes( + 'img', + array( + 'width' => $width, + 'height' => $height, + 'loading' => $loading_val, + 'fetchpriority' => $fetchpriority_val, + ), + $context + ); - // Images should have source and dimension attributes for the `loading` attribute to be added. + // Images should have source and dimension attributes for the loading optimization attributes to be added. if ( ! str_contains( $image, ' src="' ) || ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) { return $image; } - /** - * Filters the `loading` attribute value to add to an image. Default `lazy`. - * - * Returning `false` or an empty string will not add the attribute. - * Returning `true` will add the default value. - * - * @since 5.5.0 - * - * @param string|bool $value The `loading` attribute value. Returning a falsey value will result in - * the attribute being omitted for the image. - * @param string $image The HTML `img` tag to be filtered. - * @param string $context Additional context about how the function was called or where the img tag is. - */ - $value = apply_filters( 'wp_img_tag_add_loading_attr', $value, $image, $context ); + // Retained for backward compatibility. + $loading_attrs_enabled = wp_lazy_loading_enabled( 'img', $context ); - if ( $value ) { - if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) { - $value = 'lazy'; + if ( empty( $loading_val ) && $loading_attrs_enabled ) { + /** + * Filters the `loading` attribute value to add to an image. Default `lazy`. + * This filter is added in for backward compatibility. + * + * Returning `false` or an empty string will not add the attribute. + * Returning `true` will add the default value. + * `true` and `false` usage supported for backward compatibility. + * + * @since 5.5.0 + * + * @param string|bool $loading Current value for `loading` attribute for the image. + * @param string $image The HTML `img` tag to be filtered. + * @param string $context Additional context about how the function was called or where the img tag is. + */ + $filtered_loading_attr = apply_filters( + 'wp_img_tag_add_loading_attr', + isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false, + $image, + $context + ); + + // Validate the values after filtering. + if ( isset( $optimization_attrs['loading'] ) && ! $filtered_loading_attr ) { + // Unset `loading` attributes if `$filtered_loading_attr` is set to `false`. + unset( $optimization_attrs['loading'] ); + } elseif ( in_array( $filtered_loading_attr, array( 'lazy', 'eager' ), true ) ) { + /* + * If the filter changed the loading attribute to "lazy" when a fetchpriority attribute + * with value "high" is already present, trigger a warning since those two attribute + * values should be mutually exclusive. + * + * The same warning is present in `wp_get_loading_optimization_attributes()`, and here it + * is only intended for the specific scenario where the above filtered caused the problem. + */ + if ( isset( $optimization_attrs['fetchpriority'] ) && 'high' === $optimization_attrs['fetchpriority'] && + ( isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false ) !== $filtered_loading_attr && + 'lazy' === $filtered_loading_attr + ) { + _doing_it_wrong( + __FUNCTION__, + __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ), + '6.3.0' + ); + } + + // The filtered value will still be respected. + $optimization_attrs['loading'] = $filtered_loading_attr; } - return str_replace( ' str_contains( $iframe, ' width="' ) ? 100 : null, + 'height' => str_contains( $iframe, ' height="' ) ? 100 : null, + // This function is never called when a 'loading' attribute is already present. + 'loading' => null, + ), + $context + ); // Iframes should have source and dimension attributes for the `loading` attribute to be added. if ( ! str_contains( $iframe, ' src="' ) || ! str_contains( $iframe, ' width="' ) || ! str_contains( $iframe, ' height="' ) ) { return $iframe; } + $value = isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false; + /** * Filters the `loading` attribute value to add to an iframe. Default `lazy`. * @@ -5469,45 +5550,102 @@ function wp_get_webp_info( $filename ) { } /** - * Gets the default value to use for a `loading` attribute on an element. - * - * This function should only be called for a tag and context if lazy-loading is generally enabled. + * Gets loading optimization attributes. * - * The function usually returns 'lazy', but uses certain heuristics to guess whether the current element is likely to - * appear above the fold, in which case it returns a boolean `false`, which will lead to the `loading` attribute being - * omitted on the element. The purpose of this refinement is to avoid lazy-loading elements that are within the initial - * viewport, which can have a negative performance impact. + * This function returns an array of attributes that should be merged into the given attributes array to optimize + * loading performance. Potential attributes returned by this function are: + * - `loading` attribute with a value of "lazy" + * - `fetchpriority` attribute with a value of "high" * - * Under the hood, the function uses {@see wp_increase_content_media_count()} every time it is called for an element - * within the main content. If the element is the very first content element, the `loading` attribute will be omitted. - * This default threshold of 3 content elements to omit the `loading` attribute for can be customized using the - * {@see 'wp_omit_loading_attr_threshold'} filter. + * If any of these attributes are already present in the given attributes, they will not be modified. Note that no + * element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case + * both attributes are present with those values. * - * @since 5.9.0 + * @since 6.3.0 * * @global WP_Query $wp_query WordPress Query object. * - * @param string $context Context for the element for which the `loading` attribute value is requested. - * @return string|bool The default `loading` attribute value. Either 'lazy', 'eager', or a boolean `false`, to indicate - * that the `loading` attribute should be skipped. + * @param string $tag_name The tag name. + * @param array $attr Array of the attributes for the tag. + * @param string $context Context for the element for which the loading optimization attribute is requested. + * @return array Loading optimization attributes. */ -function wp_get_loading_attr_default( $context ) { +function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) { global $wp_query; - // Skip lazy-loading for the overall block template, as it is handled more granularly. + /* + * Closure for postprocessing logic. + * It is here to avoid duplicate logic in many places below, without having + * to introduce a very specific private global function. + */ + $postprocess = static function( $loading_attributes, $with_fetchpriority = false ) use ( $tag_name, $attr, $context ) { + // Potentially add `fetchpriority="high"`. + if ( $with_fetchpriority ) { + $loading_attributes = wp_maybe_add_fetchpriority_high_attr( $loading_attributes, $tag_name, $attr ); + } + // Potentially strip `loading="lazy"` if the feature is disabled. + if ( isset( $loading_attributes['loading'] ) && ! wp_lazy_loading_enabled( $tag_name, $context ) ) { + unset( $loading_attributes['loading'] ); + } + return $loading_attributes; + }; + + $loading_attrs = array(); + + /* + * Skip lazy-loading for the overall block template, as it is handled more granularly. + * The skip is also applicable for `fetchpriority`. + */ if ( 'template' === $context ) { - return false; + return $loading_attrs; + } + + // For now this function only supports images and iframes. + if ( 'img' !== $tag_name && 'iframe' !== $tag_name ) { + return $loading_attrs; + } + + // For any resources, width and height must be provided, to avoid layout shifts. + if ( ! isset( $attr['width'], $attr['height'] ) ) { + return $loading_attrs; + } + + if ( isset( $attr['loading'] ) ) { + /* + * While any `loading` value could be set in `$loading_attrs`, for + * consistency we only do it for `loading="lazy"` since that is the + * only possible value that WordPress core would apply on its own. + */ + if ( 'lazy' === $attr['loading'] ) { + $loading_attrs['loading'] = 'lazy'; + if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) { + _doing_it_wrong( + __FUNCTION__, + __( 'An image should not be lazy-loaded and marked as high priority at the same time.' ), + '6.3.0' + ); + } + } + + return $postprocess( $loading_attrs, true ); + } + + // An image with `fetchpriority="high"` cannot be assigned `loading="lazy"` at the same time. + if ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] ) { + return $postprocess( $loading_attrs, true ); } - // Do not lazy-load images in the header block template part, as they are likely above the fold. - // For classic themes, this is handled in the condition below using the 'get_header' action. + /* + * Do not lazy-load images in the header block template part, as they are likely above the fold. + * For classic themes, this is handled in the condition below using the 'get_header' action. + */ $header_area = WP_TEMPLATE_PART_AREA_HEADER; if ( "template_part_{$header_area}" === $context ) { - return false; + return $postprocess( $loading_attrs, true ); } // Special handling for programmatically created image tags. - if ( ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) ) { + if ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) { /* * Skip programmatically created images within post content as they need to be handled together with the other * images within the post content. @@ -5515,7 +5653,7 @@ function wp_get_loading_attr_default( $context ) { * post content image being lazy-loaded only because there are images elsewhere in the post content. */ if ( doing_filter( 'the_content' ) ) { - return false; + return $postprocess( $loading_attrs, true ); } // Conditionally skip lazy-loading on images before the loop. @@ -5529,7 +5667,7 @@ function wp_get_loading_attr_default( $context ) { */ && did_action( 'get_header' ) && ! did_action( 'get_footer' ) ) { - return false; + return $postprocess( $loading_attrs, true ); } } @@ -5540,23 +5678,23 @@ function wp_get_loading_attr_default( $context ) { if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) { // Only elements within the main query loop have special handling. if ( is_admin() || ! in_the_loop() || ! is_main_query() ) { - return 'lazy'; + $loading_attrs['loading'] = 'lazy'; + return $postprocess( $loading_attrs, false ); } // Increase the counter since this is a main query content element. $content_media_count = wp_increase_content_media_count(); - // If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted. + // If the count so far is below the threshold, `loading` attribute is omitted. if ( $content_media_count <= wp_omit_loading_attr_threshold() ) { - return false; + // The first largest image will still get `fetchpriority='high'`. + return $postprocess( $loading_attrs, true ); } - - // For elements after the threshold, lazy-load them as usual. - return 'lazy'; } // Lazy-load by default for any unknown context. - return 'lazy'; + $loading_attrs['loading'] = 'lazy'; + return $postprocess( $loading_attrs, false ); } /** @@ -5609,3 +5747,76 @@ function wp_increase_content_media_count( $amount = 1 ) { return $content_media_count; } + +/** + * Determines whether to add `fetchpriority='high'` to loading attributes. + * + * @since 6.3.0 + * @access private + * + * @param array $loading_attrs Array of the loading optimization attributes for the element. + * @param string $tag_name The tag name. + * @param array $attr Array of the attributes for the element. + * @return array Updated loading optimization attributes for the element. + */ +function wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr ) { + // For now, adding `fetchpriority="high"` is only supported for images. + if ( 'img' !== $tag_name ) { + return $loading_attrs; + } + + if ( isset( $attr['fetchpriority'] ) ) { + /* + * While any `fetchpriority` value could be set in `$loading_attrs`, + * for consistency we only do it for `fetchpriority="high"` since that + * is the only possible value that WordPress core would apply on its + * own. + */ + if ( 'high' === $attr['fetchpriority'] ) { + $loading_attrs['fetchpriority'] = 'high'; + wp_high_priority_element_flag( false ); + } + return $loading_attrs; + } + + // Lazy-loading and `fetchpriority="high"` are mutually exclusive. + if ( isset( $loading_attrs['loading'] ) && 'lazy' === $loading_attrs['loading'] ) { + return $loading_attrs; + } + + if ( ! wp_high_priority_element_flag() ) { + return $loading_attrs; + } + + /** + * Filters the minimum square-pixels threshold for an image to be eligible as the high-priority image. + * + * @since 6.3.0 + * + * @param int $threshold Minimum square-pixels threshold. Default 50000. + */ + $wp_min_priority_img_pixels = apply_filters( 'wp_min_priority_img_pixels', 50000 ); + if ( $wp_min_priority_img_pixels <= $attr['width'] * $attr['height'] ) { + $loading_attrs['fetchpriority'] = 'high'; + wp_high_priority_element_flag( false ); + } + return $loading_attrs; +} + +/** + * Accesses a flag that indicates if an element is a possible candidate for `fetchpriority='high'`. + * + * @since 6.3.0 + * @access private + * + * @param bool $value Optional. Used to change the static variable. Default null. + * @return bool Returns true if high-priority element was marked already, otherwise false. + */ +function wp_high_priority_element_flag( $value = null ) { + static $high_priority_element = true; + + if ( is_bool( $value ) ) { + $high_priority_element = $value; + } + return $high_priority_element; +} diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 73d7d20748188..45ee504fda6e7 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -2815,14 +2815,11 @@ function get_avatar( $id_or_email, $size = 96, $default_value = '', $alt = '', $ 'class' => null, 'force_display' => false, 'loading' => null, + 'fetchpriority' => null, 'extra_attr' => '', 'decoding' => 'async', ); - if ( wp_lazy_loading_enabled( 'img', 'get_avatar' ) ) { - $defaults['loading'] = wp_get_loading_attr_default( 'get_avatar' ); - } - if ( empty( $args ) ) { $args = array(); } @@ -2840,6 +2837,11 @@ function get_avatar( $id_or_email, $size = 96, $default_value = '', $alt = '', $ $args['width'] = $args['size']; } + // Update args with loading optimized attributes. + $loading_optimization_attr = wp_get_loading_optimization_attributes( 'img', $args, 'get_avatar' ); + + $args = array_merge( $args, $loading_optimization_attr ); + if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) { $id_or_email = get_comment( $id_or_email ); } @@ -2892,7 +2894,7 @@ function get_avatar( $id_or_email, $size = 96, $default_value = '', $alt = '', $ } } - // Add `loading` and `decoding` attributes. + // Add `loading`, `fetchpriority` and `decoding` attributes. $extra_attr = $args['extra_attr']; if ( in_array( $args['loading'], array( 'lazy', 'eager' ), true ) @@ -2915,6 +2917,17 @@ function get_avatar( $id_or_email, $size = 96, $default_value = '', $alt = '', $ $extra_attr .= "decoding='{$args['decoding']}'"; } + // Add support for `fetchpriority`. + if ( in_array( $args['fetchpriority'], array( 'high', 'low', 'auto' ), true ) + && ! preg_match( '/\bfetchpriority\s*=/', $extra_attr ) + ) { + if ( ! empty( $extra_attr ) ) { + $extra_attr .= ' '; + } + + $extra_attr .= "fetchpriority='{$args['fetchpriority']}'"; + } + $avatar = sprintf( "", esc_attr( $args['alt'] ), diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index 3c42f01290abc..ba3fe92fef7d4 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -76,13 +76,14 @@ public static function tear_down_after_class() { } /** - * Ensures that the static content media count and related filter are reset between tests. + * Ensures that the static content media count, fetchpriority element flag and related filter are reset between tests. */ public function set_up() { parent::set_up(); $this->reset_content_media_count(); $this->reset_omit_loading_attr_filter(); + $this->reset_high_priority_element_flag(); } public function test_img_caption_shortcode_added() { @@ -2289,7 +2290,7 @@ public function test_wp_filter_content_tags_srcset_sizes() { */ public function test_wp_filter_content_tags_srcset_sizes_wrong() { $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); - $img = wp_img_tag_add_loading_attr( $img, 'test' ); + $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' ); $img = wp_img_tag_add_decoding_attr( $img, 'the_content' ); // Replace the src URL. @@ -2304,7 +2305,7 @@ public function test_wp_filter_content_tags_srcset_sizes_wrong() { public function test_wp_filter_content_tags_srcset_sizes_with_preexisting_srcset() { // Generate HTML and add a dummy srcset attribute. $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); - $img = wp_img_tag_add_loading_attr( $img, 'test' ); + $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' ); $img = wp_img_tag_add_decoding_attr( $img, 'the_content' ); $img = preg_replace( '|]+) />|', '', $img ); @@ -2449,7 +2450,7 @@ public function test_wp_filter_content_tags_schemes() { // Build HTML for the editor. $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); - $img = wp_img_tag_add_loading_attr( $img, 'test' ); + $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' ); $img_https = str_replace( 'http://', 'https://', $img ); $img_relative = str_replace( 'http://', '//', $img ); @@ -2990,6 +2991,7 @@ public function test_wp_filter_content_tags_width_height() { * @ticket 44427 * @ticket 50367 * @ticket 50756 + * @ticket 58235 * @requires function imagejpeg */ public function test_wp_filter_content_tags_loading_lazy() { @@ -3004,13 +3006,13 @@ public function test_wp_filter_content_tags_loading_lazy() { $iframe = ''; $iframe_no_width_height = ''; - $lazy_img = wp_img_tag_add_loading_attr( $img, 'test' ); - $lazy_img_xhtml = wp_img_tag_add_loading_attr( $img_xhtml, 'test' ); - $lazy_img_html5 = wp_img_tag_add_loading_attr( $img_html5, 'test' ); + $lazy_img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' ); + $lazy_img_xhtml = wp_img_tag_add_loading_optimization_attrs( $img_xhtml, 'test' ); + $lazy_img_html5 = wp_img_tag_add_loading_optimization_attrs( $img_html5, 'test' ); $lazy_iframe = wp_iframe_tag_add_loading_attr( $iframe, 'test' ); // The following should not be modified because there already is a 'loading' attribute. - $img_eager = str_replace( ' />', ' loading="eager" />', $img ); + $img_eager = str_replace( ' />', ' loading="eager" fetchpriority="high" />', $img ); $iframe_eager = str_replace( '">', '" loading="eager">', $iframe ); $content = ' @@ -3069,10 +3071,11 @@ public function test_wp_filter_content_tags_loading_lazy() { /** * @ticket 44427 * @ticket 50756 + * @ticket 58235 */ public function test_wp_filter_content_tags_loading_lazy_opted_in() { $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); - $lazy_img = wp_img_tag_add_loading_attr( $img, 'test' ); + $lazy_img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' ); $lazy_img = wp_img_tag_add_decoding_attr( $lazy_img, 'the_content' ); $iframe = ''; $lazy_iframe = wp_iframe_tag_add_loading_attr( $iframe, 'test' ); @@ -3127,6 +3130,9 @@ public function test_wp_filter_content_tags_loading_lazy_opted_out() { /** * @ticket 44427 * @ticket 50367 + * + * @expectedDeprecated wp_img_tag_add_loading_attr + * @expectedDeprecated wp_get_loading_attr_default */ public function test_wp_img_tag_add_loading_attr() { $img = ''; @@ -3138,6 +3144,9 @@ public function test_wp_img_tag_add_loading_attr() { /** * @ticket 44427 * @ticket 50367 + * + * @expectedDeprecated wp_img_tag_add_loading_attr + * @expectedDeprecated wp_get_loading_attr_default */ public function test_wp_img_tag_add_loading_attr_without_src() { $img = ''; @@ -3149,6 +3158,9 @@ public function test_wp_img_tag_add_loading_attr_without_src() { /** * @ticket 44427 * @ticket 50367 + * + * @expectedDeprecated wp_img_tag_add_loading_attr + * @expectedDeprecated wp_get_loading_attr_default */ public function test_wp_img_tag_add_loading_attr_with_single_quotes() { $img = ""; @@ -3288,6 +3300,93 @@ public function test_wp_get_attachment_image_loading_opt_out_individual() { $this->assertStringNotContainsString( ' loading=', $img ); } + /** + * @ticket 58235 + * + * @covers ::wp_get_attachment_image + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_attachment_image_fetchpriority_not_present_by_default() { + $img = wp_get_attachment_image( self::$large_id ); + + $this->assertStringNotContainsString( ' fetchpriority="high"', $img ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_get_attachment_image + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_attachment_image_fetchpriority_high_when_not_lazy_loaded() { + $img = wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => false ) ); + + $this->assertStringContainsString( ' fetchpriority="high"', $img ); + } + + /** + * @ticket 58235 + * + * @dataProvider data_provider_fetchpriority_values + * + * @covers ::wp_get_attachment_image + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_attachment_image_fetchpriority_original_value_respected( $value ) { + $img = wp_get_attachment_image( + self::$large_id, + 'large', + false, + array( + 'loading' => false, + 'fetchpriority' => $value, + ) + ); + + $this->assertStringContainsString( ' fetchpriority="' . $value . '"', $img ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_provider_fetchpriority_values() { + return self::text_array_to_dataprovider( array( 'high', 'low', 'auto' ) ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_get_attachment_image + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_attachment_image_fetchpriority_stripped_when_false() { + $img = wp_get_attachment_image( + self::$large_id, + 'large', + false, + array( + 'loading' => false, + 'fetchpriority' => false, + ) + ); + + $this->assertStringNotContainsString( ' fetchpriority=', $img ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_get_attachment_image + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_attachment_image_fetchpriority_high_prevents_lazy_loading() { + $img = wp_get_attachment_image( self::$large_id, 'large', false, array( 'fetchpriority' => 'high' ) ); + + $this->assertStringNotContainsString( ' loading="lazy"', $img ); + } + /** * @ticket 57086 * @@ -3564,6 +3663,8 @@ public function data_attachment_permalinks_based_on_parent_status() { * * @covers ::wp_get_loading_attr_default * + * @expectedDeprecated wp_get_loading_attr_default + * * @dataProvider data_wp_get_loading_attr_default * * @param string $context @@ -3587,8 +3688,10 @@ public function test_wp_get_loading_attr_default( $context ) { // Set as main query. $this->set_main_query( $query ); - // For contexts other than for the main content, still return 'lazy' even in the loop - // and in the main query, and do not increase the content media count. + /* + * For contexts other than for the main content, still return 'lazy' even in the loop + * and in the main query, and do not increase the content media count. + */ $this->assertSame( 'lazy', wp_get_loading_attr_default( 'wp_get_attachment_image' ) ); // Return `false` in the main query for first three element. @@ -3617,8 +3720,15 @@ public function data_wp_get_loading_attr_default() { /** * @ticket 53675 + * @ticket 58235 */ public function test_wp_omit_loading_attr_threshold_filter() { + // Using a smaller image here. + $attr = array( + 'width' => 100, + 'height' => 100, + ); + $query = $this->get_new_wp_query_for_published_post(); $this->set_main_query( $query ); @@ -3630,25 +3740,37 @@ public function test_wp_omit_loading_attr_threshold_filter() { // Due to the filter, now the first five elements should not be lazy-loaded, i.e. return `false`. for ( $i = 0; $i < 5; $i++ ) { - $this->assertFalse( wp_get_loading_attr_default( 'the_content' ) ); + $this->assertEmpty( + wp_get_loading_optimization_attributes( 'img', $attr, 'the_content' ), + 'Expected second image to not be lazy-loaded.' + ); } // For following elements, lazy-load them again. - $this->assertSame( 'lazy', wp_get_loading_attr_default( 'the_content' ) ); + $this->assertSame( + array( 'loading' => 'lazy' ), + wp_get_loading_optimization_attributes( 'img', $attr, 'the_content' ) + ); } } /** * @ticket 53675 + * @ticket 58235 + * + * @covers ::wp_filter_content_tags + * @covers ::wp_img_tag_add_loading_optimization_attrs + * @covers ::wp_get_loading_optimization_attributes */ - public function test_wp_filter_content_tags_with_wp_get_loading_attr_default() { + public function test_wp_filter_content_tags_with_loading_optimization_attrs() { $img1 = get_image_tag( self::$large_id, '', '', '', 'large' ); $iframe1 = ''; $img2 = get_image_tag( self::$large_id, '', '', '', 'medium' ); $img3 = get_image_tag( self::$large_id, '', '', '', 'thumbnail' ); $iframe2 = ''; - $lazy_img2 = wp_img_tag_add_loading_attr( $img2, 'the_content' ); - $lazy_img3 = wp_img_tag_add_loading_attr( $img3, 'the_content' ); + $prio_img1 = str_replace( ' src=', ' fetchpriority="high" src=', $img1 ); + $lazy_img2 = wp_img_tag_add_loading_optimization_attrs( $img2, 'the_content' ); + $lazy_img3 = wp_img_tag_add_loading_optimization_attrs( $img3, 'the_content' ); $lazy_iframe2 = wp_iframe_tag_add_loading_attr( $iframe2, 'the_content' ); // Use a threshold of 2. @@ -3656,7 +3778,7 @@ public function test_wp_filter_content_tags_with_wp_get_loading_attr_default() { // Following the threshold of 2, the first two content media elements should not be lazy-loaded. $content_unfiltered = $img1 . $iframe1 . $img2 . $img3 . $iframe2; - $content_expected = $img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2; + $content_expected = $prio_img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2; $content_expected = wp_img_tag_add_decoding_attr( $content_expected, 'the_content' ); $query = $this->get_new_wp_query_for_published_post(); @@ -3705,6 +3827,8 @@ public function test_wp_omit_loading_attr_threshold() { * * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop * + * @expectedDeprecated wp_get_loading_attr_default + * * @param string $context Context for the element for which the `loading` attribute value is requested. */ public function test_wp_get_loading_attr_default_before_loop_if_not_main_query( $context ) { @@ -3727,6 +3851,8 @@ public function test_wp_get_loading_attr_default_before_loop_if_not_main_query( * * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop * + * @expectedDeprecated wp_get_loading_attr_default + * * @param string $context Context for the element for which the `loading` attribute value is requested. */ public function test_wp_get_loading_attr_default_before_loop_in_main_query_but_header_not_called( $context ) { @@ -3748,6 +3874,8 @@ public function test_wp_get_loading_attr_default_before_loop_in_main_query_but_h * * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop * + * @expectedDeprecated wp_get_loading_attr_default + * * @param string $context Context for the element for which the `loading` attribute value is requested. */ public function test_wp_get_loading_attr_default_before_loop_if_main_query( $context ) { @@ -3769,6 +3897,8 @@ public function test_wp_get_loading_attr_default_before_loop_if_main_query( $con * * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop * + * @expectedDeprecated wp_get_loading_attr_default + * * @param string $context Context for the element for which the `loading` attribute value is requested. */ public function test_wp_get_loading_attr_default_after_loop( $context ) { @@ -3794,6 +3924,8 @@ public function test_wp_get_loading_attr_default_after_loop( $context ) { * * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop * + * @expectedDeprecated wp_get_loading_attr_default + * * @param string $context Context for the element for which the `loading` attribute value is requested. */ public function test_wp_get_loading_attr_default_no_loop( $context ) { @@ -3828,9 +3960,11 @@ public function data_wp_get_loading_attr_default_before_and_no_loop() { * * @ticket 56930 * @ticket 58548 + * @ticket 58235 * * @covers ::wp_filter_content_tags - * @covers ::wp_get_loading_attr_default + * @covers ::wp_img_tag_add_loading_optimization_attrs + * @covers ::wp_get_loading_optimization_attributes */ public function test_wp_filter_content_tags_does_not_lazy_load_first_image_in_block_theme() { global $_wp_current_template_content, $wp_query, $wp_the_query, $post; @@ -3842,11 +3976,12 @@ public function test_wp_filter_content_tags_does_not_lazy_load_first_image_in_bl $img1 = get_image_tag( self::$large_id, '', '', '', 'large' ); $img2 = get_image_tag( self::$large_id, '', '', '', 'medium' ); - $lazy_img2 = wp_img_tag_add_loading_attr( $img2, 'the_content' ); + $prio_img1 = str_replace( ' src=', ' fetchpriority="high" src=', $img1 ); + $lazy_img2 = wp_img_tag_add_loading_optimization_attrs( $img2, 'the_content' ); // Only the second image should be lazy-loaded. $post_content = $img1 . $img2; - $expected_content = wpautop( $img1 . $lazy_img2 ); + $expected_content = wpautop( $prio_img1 . $lazy_img2 ); // Update the post to test with so that it has the above post content. wp_update_post( @@ -3873,9 +4008,11 @@ public function test_wp_filter_content_tags_does_not_lazy_load_first_image_in_bl * * @ticket 56930 * @ticket 58548 + * @ticket 58235 * * @covers ::wp_filter_content_tags - * @covers ::wp_get_loading_attr_default + * @covers ::wp_img_tag_add_loading_optimization_attrs + * @covers ::wp_get_loading_optimization_attributes */ public function test_wp_filter_content_tags_does_not_lazy_load_first_featured_image_in_block_theme() { global $_wp_current_template_content, $wp_query, $wp_the_query, $post; @@ -3893,12 +4030,22 @@ static function( $attr ) { $this->force_omit_loading_attr_threshold( 1 ); $content_img = get_image_tag( self::$large_id, '', '', '', 'large' ); - $lazy_content_img = wp_img_tag_add_loading_attr( $content_img, 'the_content' ); + $lazy_content_img = wp_img_tag_add_loading_optimization_attrs( $content_img, 'the_content' ); // The featured image should not be lazy-loaded as it is the first image. $featured_image_id = self::$large_id; update_post_meta( self::$post_ids['publish'], '_thumbnail_id', $featured_image_id ); - $expected_featured_image = ''; + $expected_featured_image = ''; + + // Reset high priority flag as the forced `fetchpriority="high"` above already modified it. + $this->reset_high_priority_element_flag(); // The post content image should be lazy-loaded since the featured image appears above. $post_content = $content_img; @@ -3912,7 +4059,6 @@ static function( $attr ) { 'post_content_filtered' => $post_content, ) ); - $wp_query = new WP_Query( array( 'p' => self::$post_ids['publish'] ) ); $wp_the_query = $wp_query; $post = get_post( self::$post_ids['publish'] ); @@ -3928,9 +4074,11 @@ static function( $attr ) { * in a "Header" template part. * * @ticket 56930 + * @ticket 58235 * * @covers ::wp_filter_content_tags - * @covers ::wp_get_loading_attr_default + * @covers ::wp_img_tag_add_loading_optimization_attrs + * @covers ::wp_get_loading_optimization_attributes */ public function test_wp_filter_content_tags_does_not_lazy_load_images_in_header() { global $_wp_current_template_content; @@ -3941,6 +4089,9 @@ public function test_wp_filter_content_tags_does_not_lazy_load_images_in_header( // Use a single image for each header and footer template parts. $header_img = get_image_tag( self::$large_id, '', '', '', 'large' ); + // Since header_img is qualified candidate for LCP, fetchpriority high is applied to it. + $header_img = str_replace( '' . $header_img . ''; - $expected_template_content .= ''; + $expected_template_content .= ''; $html = get_the_block_template_html(); $this->assertSame( '
Some text.
'; $post_content .= ''; @@ -4144,7 +4622,17 @@ public function test_the_excerpt_does_not_affect_omit_lazy_loading_logic() { $featured_image_id = self::$large_id; update_post_meta( $post_id, '_thumbnail_id', $featured_image_id ); - $expected_image_tag = get_the_post_thumbnail( $post_id, 'post-thumbnail', array( 'loading' => false ) ); + $expected_image_tag = get_the_post_thumbnail( + $post_id, + 'post-thumbnail', + array( + 'loading' => false, + 'fetchpriority' => 'high', + ) + ); + + // Reset high priority flag as the forced `fetchpriority="high"` above already modified it. + $this->reset_high_priority_element_flag(); $wp_query = new WP_Query( array( 'post__in' => array( $post_id ) ) ); $wp_the_query = $wp_query; @@ -4180,6 +4668,10 @@ private function reset_omit_loading_attr_filter() { remove_filter( 'wp_omit_loading_attr_threshold', '__return_null', 100 ); } + private function reset_high_priority_element_flag() { + wp_high_priority_element_flag( true ); + } + /** * Test that generated files with the `image_editor_output_format` applied use the correct * quality level based on their mime type. @@ -4276,7 +4768,7 @@ public function test_wp_generate_attachment_metadata_doesnt_generate_sizes_for_1 * * @ticket 58212 * - * @covers ::wp_get_attachment_image() + * @covers ::wp_get_attachment_image */ public function test_wp_get_attachment_image_context_filter_default() { $last_context = ''; @@ -4291,7 +4783,7 @@ public function test_wp_get_attachment_image_context_filter_default() { * * @ticket 58212 * - * @covers ::wp_get_attachment_image() + * @covers ::wp_get_attachment_image */ public function test_wp_get_attachment_image_context_filter_value_is_passed_correctly() { $last_context = ''; @@ -4309,6 +4801,298 @@ static function() { $this->assertSame( 'my_custom_context', $last_context ); } + /** + * Tests tag restriction for `wp_get_loading_optimization_attributes()`. + * + * @ticket 58235 + * + * @covers ::wp_get_loading_optimization_attributes + * + * @dataProvider data_wp_get_loading_optimization_attributes_min_required_attrs + * + * @param string $tag_name The tag name. + * @param string $attr Element attributes. + * @param array $expected Expected return value. + * @param string $message Message to display if the test fails. + */ + public function test_wp_get_loading_optimization_attributes_min_required_attrs( $tag_name, $attr, $expected, $message ) { + $context = 'the_post_thumbnail'; + $this->assertSame( wp_get_loading_optimization_attributes( $tag_name, $attr, $context ), $expected, $message ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_wp_get_loading_optimization_attributes_min_required_attrs() { + return array( + 'img_with_min_attrs' => array( + 'img', + array( + 'width' => 100, + 'height' => 100, + ), + array( 'loading' => 'lazy' ), + 'Expected default `loading="lazy"`.', + ), + 'img_without_height' => array( + 'img', + array( 'width' => 100 ), + array(), + 'Expected blank array as height is required.', + ), + 'img_without_width' => array( + 'img', + array( 'height' => 100 ), + array(), + 'Expected blank array as width is required.', + ), + ); + } + + /** + * Tests tag restriction for `wp_get_loading_optimization_attributes()`. + * + * @ticket 58235 + * + * @covers ::wp_get_loading_optimization_attributes + * + * @dataProvider data_wp_get_loading_optimization_attributes_check_allowed_tags + * + * @param string $tag_name The tag name. + * @param array $expected Expected return value. + * @param string $message Message to display if the test fails. + */ + public function test_wp_get_loading_optimization_attributes_check_allowed_tags( $tag_name, $expected, $message ) { + $attr = $this->get_width_height_for_high_priority(); + $context = 'the_post_thumbnail'; + $this->assertSame( wp_get_loading_optimization_attributes( $tag_name, $attr, $context ), $expected, $message ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_wp_get_loading_optimization_attributes_check_allowed_tags() { + return array( + 'img' => array( + 'img', + array( 'loading' => 'lazy' ), + 'Expected `loading="lazy"` for the img.', + ), + 'iframe' => array( + 'iframe', + array( + 'loading' => 'lazy', + ), + 'Expected `loading="lazy"` for the iframe.', + ), + 'video' => + array( + 'video', + array(), + 'Function should return empty array as video tag is not supported.', + ), + ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_loading_optimization_attributes_skip_for_block_template() { + $attr = $this->get_width_height_for_high_priority(); + + // Skip logic if context is `template`. + $this->assertSame( + array(), + wp_get_loading_optimization_attributes( 'img', $attr, 'template' ), + 'Skip logic and return blank array for block template.' + ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_loading_optimization_attributes_header_block_template() { + $attr = $this->get_width_height_for_high_priority(); + + // Skip logic if context is `template`. + $this->assertSame( + array( 'fetchpriority' => 'high' ), + wp_get_loading_optimization_attributes( 'img', $attr, 'template_part_' . WP_TEMPLATE_PART_AREA_HEADER ), + 'Images in the header block template part should not be lazy-loaded and first large image is set high fetchpriority.' + ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_get_loading_optimization_attributes + * @expectedIncorrectUsage wp_get_loading_optimization_attributes + */ + public function test_wp_get_loading_optimization_attributes_incorrect_loading_attrs() { + $attr = $this->get_width_height_for_high_priority(); + $attr['loading'] = 'lazy'; + $attr['fetchpriority'] = 'high'; + + $this->assertSame( + array( + 'loading' => 'lazy', + 'fetchpriority' => 'high', + ), + wp_get_loading_optimization_attributes( 'img', $attr, 'test' ), + 'This should return both lazy-loading and high fetchpriority, but with doing_it_wrong message.' + ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_loading_optimization_attributes_if_loading_attr_present() { + $attr = $this->get_width_height_for_high_priority(); + $attr['loading'] = 'eager'; + + // Check fetchpriority high logic if loading attribute is present. + $this->assertSame( + array( + 'fetchpriority' => 'high', + ), + wp_get_loading_optimization_attributes( 'img', $attr, 'test' ), + 'fetchpriority should be set to high.' + ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_get_loading_optimization_attributes + */ + public function test_wp_get_loading_optimization_attributes_low_res_image() { + $attr = array( + 'width' => 100, + 'height' => 100, + 'loading' => 'eager', + ); + + // fetchpriority not set as image is of lower resolution. + $this->assertSame( + array(), + wp_get_loading_optimization_attributes( 'img', $attr, 'test' ), + 'loading optimization attr array should be empty.' + ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_maybe_add_fetchpriority_high_attr + * + * @dataProvider data_wp_maybe_add_fetchpriority_high_attr + */ + public function test_wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr, $expected_fetchpriority ) { + $loading_attrs = wp_maybe_add_fetchpriority_high_attr( $loading_attrs, $tag_name, $attr ); + + if ( $expected_fetchpriority ) { + $this->assertArrayHasKey( 'fetchpriority', $loading_attrs, 'fetchpriority attribute should be present' ); + $this->assertSame( $expected_fetchpriority, $loading_attrs['fetchpriority'], 'fetchpriority attribute has incorrect value' ); + } else { + $this->assertArrayNotHasKey( 'fetchpriority', $loading_attrs, 'fetchpriority attribute should not be present' ); + } + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_wp_maybe_add_fetchpriority_high_attr() { + return array( + 'small image' => array( + array(), + 'img', + $this->get_insufficient_width_height_for_high_priority(), + false, + ), + 'large image' => array( + array(), + 'img', + $this->get_width_height_for_high_priority(), + 'high', + ), + 'image with loading=lazy' => array( + array( 'loading' => 'lazy' ), + 'img', + $this->get_width_height_for_high_priority(), + false, + ), + 'image with loading=eager' => array( + array( 'loading' => 'eager' ), + 'img', + $this->get_width_height_for_high_priority(), + 'high', + ), + 'image with fetchpriority=high' => array( + array(), + 'img', + array_merge( + $this->get_insufficient_width_height_for_high_priority(), + array( 'fetchpriority' => 'high' ) + ), + 'high', + ), + 'image with fetchpriority=low' => array( + array(), + 'img', + array_merge( + $this->get_insufficient_width_height_for_high_priority(), + array( 'fetchpriority' => 'low' ) + ), + false, + ), + 'non-image element' => array( + array(), + 'video', + $this->get_width_height_for_high_priority(), + false, + ), + ); + } + + /** + * @ticket 58235 + * + * @covers ::wp_maybe_add_fetchpriority_high_attr + */ + public function test_wp_maybe_add_fetchpriority_high_attr_min_priority_filter() { + $attr = array( + 'width' => 50, + 'height' => 50, + ); + + add_filter( + 'wp_min_priority_img_pixels', + static function( $res ) { + return 2500; // 50*50=2500 + } + ); + + // fetchpriority set to high as resolution is equal to (or greater than) 2500. + $this->assertSame( + array( + 'fetchpriority' => 'high', + ), + wp_maybe_add_fetchpriority_high_attr( array(), 'img', $attr ) + ); + } + /** * Helper method to keep track of the last context returned by the 'wp_get_attachment_image_context' filter. * @@ -4407,6 +5191,38 @@ public function set_main_query( $query ) { global $wp_the_query; $wp_the_query = $query; } + + /** + * Returns an array with dimension attribute values eligible for a high priority image. + * + * @return array Associative array with 'width' and 'height' keys. + */ + private function get_width_height_for_high_priority() { + /* + * The product of width * height must be >50000 to qualify for high priority image. + * 300 * 200 = 60000 + */ + return array( + 'width' => 300, + 'height' => 200, + ); + } + + /** + * Returns an array with dimension attribute values ineligible for a high priority image. + * + * @return array Associative array with 'width' and 'height' keys. + */ + private function get_insufficient_width_height_for_high_priority() { + /* + * The product of width * height must be >50000 to qualify for high priority image. + * 200 * 100 = 20000 + */ + return array( + 'width' => 200, + 'height' => 100, + ); + } } /**