diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php
index 36be77efdd..1626995290 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;
- // Images should have source and dimension attributes for the `loading` attribute to be added.
+ /*
+ * 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 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.
+ * Gets loading optimization attributes.
*
- * This function should only be called for a tag and context if lazy-loading is generally enabled.
+ * 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"
*
- * 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.
+ * 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.
*
- * 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
+ * @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;
}
- // 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.
+ // 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.
+ */
$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 73d7d20748..45ee504fda 100644
--- a/src/wp-includes/pluggable.php
+++ b/src/wp-includes/pluggable.php
@@ -2815,14 +2815,11 @@ if ( ! function_exists( 'get_avatar' ) ) :
'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 @@ if ( ! function_exists( 'get_avatar' ) ) :
$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 @@ if ( ! function_exists( 'get_avatar' ) ) :
}
}
- // 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 @@ if ( ! function_exists( 'get_avatar' ) ) :
$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 3c42f01290..ba3fe92fef 100644
--- a/tests/phpunit/tests/media.php
+++ b/tests/phpunit/tests/media.php
@@ -76,13 +76,14 @@ CAP;
}
/**
- * 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 @@ EOF;
*/
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 @@ EOF;
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 @@ EOF;
// 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 @@ EOF;
* @ticket 44427
* @ticket 50367
* @ticket 50756
+ * @ticket 58235
* @requires function imagejpeg
*/
public function test_wp_filter_content_tags_loading_lazy() {
@@ -3004,13 +3006,13 @@ EOF;
$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 @@ EOF;
/**
* @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 @@ EOF;
/**
* @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 @@ EOF;
/**
* @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 @@ EOF;
/**
* @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 @@ EOF;
$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 @@ EOF;
*
* @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 @@ EOF;
// 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 @@ EOF;
/**
* @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 @@ EOF;
// 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 @@ EOF;
// 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 @@ EOF;
*
* @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 @@ EOF;
*
* @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 @@ EOF;
*
* @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 @@ EOF;
*
* @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 @@ EOF;
*
* @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 @@ EOF;
*
* @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 @@ EOF;
$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 @@ EOF;
*
* @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 @@ EOF;
$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 = '' . $header_img . '';
- $expected_template_content .= '';
+ $expected_template_content .= '';
$html = get_the_block_template_html();
$this->assertSame( '
';
+ $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
+
+ $this->assertStringContainsString( ' loading="lazy"', $img );
+ }
+
+ /**
+ * @ticket 44427
+ * @ticket 50367
+ * @ticket 58235
+ */
+ public function test_wp_img_tag_add_loading_optimization_attrs_without_src() {
+ $img = '
';
$post_content .= 'Some text.
'; $post_content .= '
';
@@ -4144,7 +4622,17 @@ EOF;
$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 @@ EOF;
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 @@ EOF;
*
* @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 @@ EOF;
*
* @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 @@ EOF;
$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 @@ EOF;
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,
+ );
+ }
}
/**