<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[56037] trunk: Media: Automatically add `fetchpriority="high"` to hero image to improve load time performance.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/56037">56037</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/56037","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>flixos90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-06-26 16:15:12 +0000 (Mon, 26 Jun 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Media: Automatically add `fetchpriority="high"` to hero image to improve load time performance.

This changeset adds support for the `fetchpriority` attribute, which is typically added to a single image in each HTML response with a value of "high". This enhances load time performance (also Largest Contentful Paint, or LCP) by telling the browser to prioritize this image for downloading even before the layout of the page has been computed. In lab tests, this has shown to improve LCP performance by ~10% on average.

Specifically, `fetchpriority="high"` is added to the first image that satisfies all of the following conditions:
* The image is not lazy-loaded, i.e. does not have `loading="lazy"`.
* The image does not already have a (conflicting) `fetchpriority` attribute.
* The size of of the image (i.e. width * height) is greater than 50,000 squarepixels.

While these heuristics are based on several field analyses, there will always be room for optimization. Sites can customize the squarepixel threshold using a new filter `wp_min_priority_img_pixels` which should return an integer for the value.

Since the logic for adding `fetchpriority="high"` is heavily intertwined with the logic for adding `loading="lazy"`, yet the features should work decoupled from each other, the majority of code changes in this changeset is refactoring of the existing lazy-loading logic to be reusable. For this purpose, a new function `wp_get_loading_optimization_attributes()` has been introduced which returns an associative array of performance-relevant attributes for a given HTML element. This function replaces `wp_get_loading_attr_default()`, which has been deprecated. As another result of that change, a new function `wp_img_tag_add_loading_optimization_attrs()` replaces the more specific `wp_img_tag_add_loading_attr()`, which has been deprecated as well.

See https://make.wordpress.org/core/2023/05/02/proposal-for-enhancing-lcp-image-performance-with-fetchpriority/ for the original proposal and additional context.

Props thekt12, joemcgill, spacedmonkey, mukesh27, costdev, 10upsimon.
Fixes <a href="https://core.trac.wordpress.org/ticket/58235">#58235</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesdeprecatedphp">trunk/src/wp-includes/deprecated.php</a></li>
<li><a href="#trunksrcwpincludesmediaphp">trunk/src/wp-includes/media.php</a></li>
<li><a href="#trunksrcwpincludespluggablephp">trunk/src/wp-includes/pluggable.php</a></li>
<li><a href="#trunktestsphpunittestsmediaphp">trunk/tests/phpunit/tests/media.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesdeprecatedphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/deprecated.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/deprecated.php      2023-06-26 15:48:23 UTC (rev 56036)
+++ trunk/src/wp-includes/deprecated.php        2023-06-26 16:15:12 UTC (rev 56037)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4658,3 +4658,149 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        wp_lazyload_comment_meta( $comment_ids );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * 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( '<img', '<img loading="' . esc_attr( $value ) . '"', $image );
+       }
+
+       return $image;
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesmediaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/media.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/media.php   2023-06-26 15:48:23 UTC (rev 56036)
+++ trunk/src/wp-includes/media.php     2023-06-26 16:15:12 UTC (rev 56037)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1058,13 +1058,19 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 * @param string $context The context. Default 'wp_get_attachment_image'.
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                $context = apply_filters( 'wp_get_attachment_image_context', 'wp_get_attachment_image' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $attr    = wp_parse_args( $attr, $default_attr );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Add `loading` attribute.
-               if ( wp_lazy_loading_enabled( 'img', $context ) ) {
-                       $default_attr['loading'] = wp_get_loading_attr_default( $context );
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $loading_attr              = $attr;
+               $loading_attr['width']     = $width;
+               $loading_attr['height']    = $height;
+               $loading_optimization_attr = wp_get_loading_optimization_attributes(
+                       'img',
+                       $loading_attr,
+                       $context
+               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $attr = wp_parse_args( $attr, $default_attr );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Add loading optimization attributes if not available.
+               $attr = array_merge( $attr, $loading_optimization_attr );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Omit the `decoding` attribute if the value is invalid according to the spec.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( empty( $attr['decoding'] ) || ! in_array( $attr['decoding'], array( 'async', 'sync', 'auto' ), true ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1073,10 +1079,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // If the default value of `lazy` for the `loading` attribute is overridden
</span><span class="cx" style="display: block; padding: 0 10px">                // to omit the attribute for this image, ensure it is not included.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( array_key_exists( 'loading', $attr ) && ! $attr['loading'] ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( isset( $attr['loading'] ) && ! $attr['loading'] ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         unset( $attr['loading'] );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // If the `fetchpriority` attribute is overridden and set to false or an empty string.
+               if ( isset( $attr['fetchpriority'] ) && ! $attr['fetchpriority'] ) {
+                       unset( $attr['fetchpriority'] );
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 // Generate 'srcset' and 'sizes' if not already present.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( empty( $attr['srcset'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $image_meta = wp_get_attachment_metadata( $attachment_id );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1780,7 +1791,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @see wp_img_tag_add_width_and_height_attr()
</span><span class="cx" style="display: block; padding: 0 10px">  * @see wp_img_tag_add_srcset_and_sizes_attr()
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @see wp_img_tag_add_loading_attr()
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @see wp_img_tag_add_loading_optimization_attrs()
</ins><span class="cx" style="display: block; padding: 0 10px">  * @see wp_iframe_tag_add_loading_attr()
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string $content The HTML content to be filtered.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1793,7 +1804,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $context = current_filter();
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $add_img_loading_attr    = wp_lazy_loading_enabled( 'img', $context );
</del><span class="cx" style="display: block; padding: 0 10px">         $add_iframe_loading_attr = wp_lazy_loading_enabled( 'iframe', $context );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! preg_match_all( '/<(img|iframe)\s[^>]+>/', $content, $matches, PREG_SET_ORDER ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1857,10 +1867,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id );
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // 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 );
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Add loading optimization attributes if applicable.
+                       $filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Add 'decoding=async' attribute unless a 'decoding' attribute is already present.
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! str_contains( $filtered_image, ' decoding=' ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1914,47 +1922,103 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Adds `loading` attribute to an `img` HTML tag.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Adds optimization attributes to an `img` HTML tag.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 5.5.0
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.3.0
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string $image   The HTML `img` tag where the attribute should be added.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string $context Additional context to pass to the filters.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @return string Converted `img` tag with `loading` attribute added.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @return string Converted `img` tag with optimization attributes added.
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-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 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+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;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // Images should have source and dimension attributes for the `loading` attribute to be added.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+        * 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.
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( ! str_contains( $image, ' src="' ) || ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return $image;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * 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 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Retained for backward compatibility.
+       $loading_attrs_enabled = wp_lazy_loading_enabled( 'img', $context );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( $value ) {
-               if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
-                       $value = 'lazy';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 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;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return str_replace( '<img', '<img loading="' . esc_attr( $value ) . '"', $image );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! empty( $optimization_attrs['loading'] ) ) {
+                       $image = str_replace( '<img', '<img loading="' . esc_attr( $optimization_attrs['loading'] ) . '"', $image );
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        if ( empty( $fetchpriority_val ) && ! empty( $optimization_attrs['fetchpriority'] ) ) {
+               $image = str_replace( '<img', '<img fetchpriority="' . esc_attr( $optimization_attrs['fetchpriority'] ) . '"', $image );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         return $image;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2103,7 +2167,22 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Get loading attribute value to use. This must occur before the conditional check below so that even iframes that
</span><span class="cx" style="display: block; padding: 0 10px">        // are ineligible for being lazy-loaded are considered.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $value = wp_get_loading_attr_default( $context );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $optimization_attrs = wp_get_loading_optimization_attributes(
+               'iframe',
+               array(
+                       /*
+                        * The concrete values for width and height are not important here for now
+                        * since fetchpriority is not yet supported for iframes.
+                        * TODO: Use WP_HTML_Tag_Processor to extract actual values once support is
+                        * added.
+                        */
+                       'width'   => 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
+       );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Iframes should have source and dimension attributes for the `loading` attribute to be added.
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! str_contains( $iframe, ' src="' ) || ! str_contains( $iframe, ' width="' ) || ! str_contains( $iframe, ' height="' ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2110,6 +2189,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return $iframe;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        $value = isset( $optimization_attrs['loading'] ) ? $optimization_attrs['loading'] : false;
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Filters the `loading` attribute value to add to an iframe. Default `lazy`.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5469,45 +5550,102 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Gets the default value to use for a `loading` attribute on an element.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Gets loading optimization attributes.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * This function should only be called for a tag and context if lazy-loading is generally enabled.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 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"
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * 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.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 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.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * 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.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.3.0
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 5.9.0
- *
</del><span class="cx" style="display: block; padding: 0 10px">  * @global WP_Query $wp_query WordPress Query object.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @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.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @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.
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function wp_get_loading_attr_default( $context ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         global $wp_query;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // Skip lazy-loading for the overall block template, as it is handled more granularly.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ /*
+        * 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`.
+        */
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( 'template' === $context ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return $loading_attrs;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // 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.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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.
+        */
</ins><span class="cx" style="display: block; padding: 0 10px">         $header_area = WP_TEMPLATE_PART_AREA_HEADER;
</span><span class="cx" style="display: block; padding: 0 10px">        if ( "template_part_{$header_area}" === $context ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return $postprocess( $loading_attrs, true );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Special handling for programmatically created image tags.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 /*
</span><span class="cx" style="display: block; padding: 0 10px">                 * Skip programmatically created images within post content as they need to be handled together with the other
</span><span class="cx" style="display: block; padding: 0 10px">                 * images within the post content.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5515,7 +5653,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 * post content image being lazy-loaded only because there are images elsewhere in the post content.
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                if ( doing_filter( 'the_content' ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        return false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return $postprocess( $loading_attrs, true );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Conditionally skip lazy-loading on images before the loop.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5529,7 +5667,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                         */
</span><span class="cx" style="display: block; padding: 0 10px">                        && did_action( 'get_header' ) && ! did_action( 'get_footer' )
</span><span class="cx" style="display: block; padding: 0 10px">                ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        return false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return $postprocess( $loading_attrs, true );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5540,23 +5678,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) {
</span><span class="cx" style="display: block; padding: 0 10px">                // Only elements within the main query loop have special handling.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        return 'lazy';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $loading_attrs['loading'] = 'lazy';
+                       return $postprocess( $loading_attrs, false );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Increase the counter since this is a main query content element.
</span><span class="cx" style="display: block; padding: 0 10px">                $content_media_count = wp_increase_content_media_count();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // If the count so far is below the threshold, `loading` attribute is omitted.
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( $content_media_count <= wp_omit_loading_attr_threshold() ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        return false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // The first largest image will still get `fetchpriority='high'`.
+                       return $postprocess( $loading_attrs, true );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-               // For elements after the threshold, lazy-load them as usual.
-               return 'lazy';
</del><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Lazy-load by default for any unknown context.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return 'lazy';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $loading_attrs['loading'] = 'lazy';
+       return $postprocess( $loading_attrs, false );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -5609,3 +5747,76 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        return $content_media_count;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * 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;
+}
</ins></span></pre></div>
<a id="trunksrcwpincludespluggablephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/pluggable.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/pluggable.php       2023-06-26 15:48:23 UTC (rev 56036)
+++ trunk/src/wp-includes/pluggable.php 2023-06-26 16:15:12 UTC (rev 56037)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2815,14 +2815,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'class'         => null,
</span><span class="cx" style="display: block; padding: 0 10px">                        'force_display' => false,
</span><span class="cx" style="display: block; padding: 0 10px">                        'loading'       => null,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'fetchpriority' => null,
</ins><span class="cx" style="display: block; padding: 0 10px">                         'extra_attr'    => '',
</span><span class="cx" style="display: block; padding: 0 10px">                        'decoding'      => 'async',
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( wp_lazy_loading_enabled( 'img', 'get_avatar' ) ) {
-                       $defaults['loading'] = wp_get_loading_attr_default( 'get_avatar' );
-               }
-
</del><span class="cx" style="display: block; padding: 0 10px">                 if ( empty( $args ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $args = array();
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2840,6 +2837,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $args['width'] = $args['size'];
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // 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 );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $id_or_email = get_comment( $id_or_email );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2892,7 +2894,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Add `loading` and `decoding` attributes.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Add `loading`, `fetchpriority` and `decoding` attributes.
</ins><span class="cx" style="display: block; padding: 0 10px">                 $extra_attr = $args['extra_attr'];
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( in_array( $args['loading'], array( 'lazy', 'eager' ), true )
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2915,6 +2917,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $extra_attr .= "decoding='{$args['decoding']}'";
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // 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']}'";
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $avatar = sprintf(
</span><span class="cx" style="display: block; padding: 0 10px">                        "<img alt='%s' src='%s' srcset='%s' class='%s' height='%d' width='%d' %s/>",
</span><span class="cx" style="display: block; padding: 0 10px">                        esc_attr( $args['alt'] ),
</span></span></pre></div>
<a id="trunktestsphpunittestsmediaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/media.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/media.php       2023-06-26 15:48:23 UTC (rev 56036)
+++ trunk/tests/phpunit/tests/media.php 2023-06-26 16:15:12 UTC (rev 56037)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -76,7 +76,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Ensures that the static content media count and related filter are reset between tests.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Ensures that the static content media count, fetchpriority element flag and related filter are reset between tests.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function set_up() {
</span><span class="cx" style="display: block; padding: 0 10px">                parent::set_up();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -83,6 +83,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->reset_content_media_count();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->reset_omit_loading_attr_filter();
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $this->reset_high_priority_element_flag();
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_img_caption_shortcode_added() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2289,7 +2290,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_srcset_sizes_wrong() {
</span><span class="cx" style="display: block; padding: 0 10px">                $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $img = wp_img_tag_add_loading_attr( $img, 'test' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Replace the src URL.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2304,7 +2305,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_srcset_sizes_with_preexisting_srcset() {
</span><span class="cx" style="display: block; padding: 0 10px">                // Generate HTML and add a dummy srcset attribute.
</span><span class="cx" style="display: block; padding: 0 10px">                $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $img = wp_img_tag_add_loading_attr( $img, 'test' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
</span><span class="cx" style="display: block; padding: 0 10px">                $img = preg_replace( '|<img ([^>]+) />|', '<img $1 ' . 'srcset="image2x.jpg 2x" />', $img );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2449,7 +2450,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Build HTML for the editor.
</span><span class="cx" style="display: block; padding: 0 10px">                $img          = get_image_tag( self::$large_id, '', '', '', 'medium' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $img          = wp_img_tag_add_loading_attr( $img, 'test' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $img          = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $img_https    = str_replace( 'http://', 'https://', $img );
</span><span class="cx" style="display: block; padding: 0 10px">                $img_relative = str_replace( 'http://', '//', $img );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2990,6 +2991,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 44427
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 50367
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 50756
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
</ins><span class="cx" style="display: block; padding: 0 10px">          * @requires function imagejpeg
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_loading_lazy() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3004,13 +3006,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $iframe                 = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
</span><span class="cx" style="display: block; padding: 0 10px">                $iframe_no_width_height = '<iframe src="https://www.example.com"></iframe>';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $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' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $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' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $lazy_iframe    = wp_iframe_tag_add_loading_attr( $iframe, 'test' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // The following should not be modified because there already is a 'loading' attribute.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $img_eager    = str_replace( ' />', ' loading="eager" />', $img );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $img_eager    = str_replace( ' />', ' loading="eager" fetchpriority="high" />', $img );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $iframe_eager = str_replace( '">', '" loading="eager">', $iframe );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $content = '
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3069,10 +3071,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 44427
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 50756
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_loading_lazy_opted_in() {
</span><span class="cx" style="display: block; padding: 0 10px">                $img         = get_image_tag( self::$large_id, '', '', '', 'medium' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $lazy_img    = wp_img_tag_add_loading_attr( $img, 'test' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $lazy_img    = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $lazy_img    = wp_img_tag_add_decoding_attr( $lazy_img, 'the_content' );
</span><span class="cx" style="display: block; padding: 0 10px">                $iframe      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
</span><span class="cx" style="display: block; padding: 0 10px">                $lazy_iframe = wp_iframe_tag_add_loading_attr( $iframe, 'test' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3127,6 +3130,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 44427
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 50367
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         *
+        * @expectedDeprecated wp_img_tag_add_loading_attr
+        * @expectedDeprecated wp_get_loading_attr_default
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_img_tag_add_loading_attr() {
</span><span class="cx" style="display: block; padding: 0 10px">                $img = '<img src="example.png" alt=" width="300" height="225" />';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3138,6 +3144,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 44427
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 50367
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         *
+        * @expectedDeprecated wp_img_tag_add_loading_attr
+        * @expectedDeprecated wp_get_loading_attr_default
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_img_tag_add_loading_attr_without_src() {
</span><span class="cx" style="display: block; padding: 0 10px">                $img = '<img alt=" width="300" height="225" />';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3149,6 +3158,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 44427
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 50367
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         *
+        * @expectedDeprecated wp_img_tag_add_loading_attr
+        * @expectedDeprecated wp_get_loading_attr_default
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_img_tag_add_loading_attr_with_single_quotes() {
</span><span class="cx" style="display: block; padding: 0 10px">                $img = "<img src='example.png' alt=' width='300' height='225' />";
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3289,6 +3301,93 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @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 );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * @ticket 57086
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @dataProvider data_wp_get_attachment_image_decoding_attr
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3564,6 +3663,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::wp_get_loading_attr_default
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @expectedDeprecated wp_get_loading_attr_default
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @dataProvider data_wp_get_loading_attr_default
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $context
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3587,8 +3688,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        // Set as main query.
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->set_main_query( $query );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // 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.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 /*
+                        * 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.
+                        */
</ins><span class="cx" style="display: block; padding: 0 10px">                         $this->assertSame( 'lazy', wp_get_loading_attr_default( 'wp_get_attachment_image' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Return `false` in the main query for first three element.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3617,8 +3720,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 53675
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_omit_loading_attr_threshold_filter() {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Using a smaller image here.
+               $attr = array(
+                       'width'  => 100,
+                       'height' => 100,
+               );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $query = $this->get_new_wp_query_for_published_post();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->set_main_query( $query );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3630,25 +3740,37 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // Due to the filter, now the first five elements should not be lazy-loaded, i.e. return `false`.
</span><span class="cx" style="display: block; padding: 0 10px">                        for ( $i = 0; $i < 5; $i++ ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $this->assertFalse( wp_get_loading_attr_default( 'the_content' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $this->assertEmpty(
+                                       wp_get_loading_optimization_attributes( 'img', $attr, 'the_content' ),
+                                       'Expected second image to not be lazy-loaded.'
+                               );
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // For following elements, lazy-load them again.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $this->assertSame( 'lazy', wp_get_loading_attr_default( 'the_content' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $this->assertSame(
+                               array( 'loading' => 'lazy' ),
+                               wp_get_loading_optimization_attributes( 'img', $attr, 'the_content' )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 53675
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
+        *
+        * @covers ::wp_filter_content_tags
+        * @covers ::wp_img_tag_add_loading_optimization_attrs
+        * @covers ::wp_get_loading_optimization_attributes
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        public function test_wp_filter_content_tags_with_wp_get_loading_attr_default() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function test_wp_filter_content_tags_with_loading_optimization_attrs() {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $img1         = get_image_tag( self::$large_id, '', '', '', 'large' );
</span><span class="cx" style="display: block; padding: 0 10px">                $iframe1      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
</span><span class="cx" style="display: block; padding: 0 10px">                $img2         = get_image_tag( self::$large_id, '', '', '', 'medium' );
</span><span class="cx" style="display: block; padding: 0 10px">                $img3         = get_image_tag( self::$large_id, '', '', '', 'thumbnail' );
</span><span class="cx" style="display: block; padding: 0 10px">                $iframe2      = '<iframe src="https://wordpress.org" width="640" height="360"></iframe>';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $lazy_img2    = wp_img_tag_add_loading_attr( $img2, 'the_content' );
-               $lazy_img3    = wp_img_tag_add_loading_attr( $img3, 'the_content' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $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' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $lazy_iframe2 = wp_iframe_tag_add_loading_attr( $iframe2, 'the_content' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Use a threshold of 2.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3656,7 +3778,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Following the threshold of 2, the first two content media elements should not be lazy-loaded.
</span><span class="cx" style="display: block; padding: 0 10px">                $content_unfiltered = $img1 . $iframe1 . $img2 . $img3 . $iframe2;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $content_expected   = $img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $content_expected   = $prio_img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2;
</ins><span class="cx" style="display: block; padding: 0 10px">                 $content_expected   = wp_img_tag_add_decoding_attr( $content_expected, 'the_content' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $query = $this->get_new_wp_query_for_published_post();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3705,6 +3827,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @expectedDeprecated wp_get_loading_attr_default
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @param string $context Context for the element for which the `loading` attribute value is requested.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_get_loading_attr_default_before_loop_if_not_main_query( $context ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3727,6 +3851,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @expectedDeprecated wp_get_loading_attr_default
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @param string $context Context for the element for which the `loading` attribute value is requested.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_get_loading_attr_default_before_loop_in_main_query_but_header_not_called( $context ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3748,6 +3874,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @expectedDeprecated wp_get_loading_attr_default
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @param string $context Context for the element for which the `loading` attribute value is requested.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_get_loading_attr_default_before_loop_if_main_query( $context ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3769,6 +3897,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @expectedDeprecated wp_get_loading_attr_default
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @param string $context Context for the element for which the `loading` attribute value is requested.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_get_loading_attr_default_after_loop( $context ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3794,6 +3924,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @expectedDeprecated wp_get_loading_attr_default
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @param string $context Context for the element for which the `loading` attribute value is requested.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_get_loading_attr_default_no_loop( $context ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3828,9 +3960,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 56930
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 58548
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::wp_filter_content_tags
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @covers ::wp_get_loading_attr_default
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @covers ::wp_img_tag_add_loading_optimization_attrs
+        * @covers ::wp_get_loading_optimization_attributes
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_does_not_lazy_load_first_image_in_block_theme() {
</span><span class="cx" style="display: block; padding: 0 10px">                global $_wp_current_template_content, $wp_query, $wp_the_query, $post;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3842,11 +3976,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $img1      = get_image_tag( self::$large_id, '', '', '', 'large' );
</span><span class="cx" style="display: block; padding: 0 10px">                $img2      = get_image_tag( self::$large_id, '', '', '', 'medium' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $lazy_img2 = wp_img_tag_add_loading_attr( $img2, 'the_content' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $prio_img1 = str_replace( ' src=', ' fetchpriority="high" src=', $img1 );
+               $lazy_img2 = wp_img_tag_add_loading_optimization_attrs( $img2, 'the_content' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Only the second image should be lazy-loaded.
</span><span class="cx" style="display: block; padding: 0 10px">                $post_content     = $img1 . $img2;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $expected_content = wpautop( $img1 . $lazy_img2 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $expected_content = wpautop( $prio_img1 . $lazy_img2 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Update the post to test with so that it has the above post content.
</span><span class="cx" style="display: block; padding: 0 10px">                wp_update_post(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3873,9 +4008,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 56930
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 58548
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::wp_filter_content_tags
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @covers ::wp_get_loading_attr_default
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @covers ::wp_img_tag_add_loading_optimization_attrs
+        * @covers ::wp_get_loading_optimization_attributes
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_does_not_lazy_load_first_featured_image_in_block_theme() {
</span><span class="cx" style="display: block; padding: 0 10px">                global $_wp_current_template_content, $wp_query, $wp_the_query, $post;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3893,13 +4030,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->force_omit_loading_attr_threshold( 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $content_img      = get_image_tag( self::$large_id, '', '', '', 'large' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $lazy_content_img = wp_img_tag_add_loading_attr( $content_img, 'the_content' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $lazy_content_img = wp_img_tag_add_loading_optimization_attrs( $content_img, 'the_content' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // The featured image should not be lazy-loaded as it is the first image.
</span><span class="cx" style="display: block; padding: 0 10px">                $featured_image_id = self::$large_id;
</span><span class="cx" style="display: block; padding: 0 10px">                update_post_meta( self::$post_ids['publish'], '_thumbnail_id', $featured_image_id );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $expected_featured_image = '<figure class="wp-block-post-featured-image">' . get_the_post_thumbnail( self::$post_ids['publish'], 'post-thumbnail', array( 'loading' => false ) ) . '</figure>';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $expected_featured_image = '<figure class="wp-block-post-featured-image">' . get_the_post_thumbnail(
+                       self::$post_ids['publish'],
+                       'post-thumbnail',
+                       array(
+                               'loading'       => false,
+                               'fetchpriority' => 'high',
+                       )
+               ) . '</figure>';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Reset high priority flag as the forced `fetchpriority="high"` above already modified it.
+               $this->reset_high_priority_element_flag();
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 // The post content image should be lazy-loaded since the featured image appears above.
</span><span class="cx" style="display: block; padding: 0 10px">                $post_content     = $content_img;
</span><span class="cx" style="display: block; padding: 0 10px">                $expected_content = wpautop( $lazy_content_img );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3912,7 +4059,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'post_content_filtered' => $post_content,
</span><span class="cx" style="display: block; padding: 0 10px">                        )
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del><span class="cx" style="display: block; padding: 0 10px">                 $wp_query     = new WP_Query( array( 'p' => self::$post_ids['publish'] ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $wp_the_query = $wp_query;
</span><span class="cx" style="display: block; padding: 0 10px">                $post         = get_post( self::$post_ids['publish'] );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3928,9 +4074,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * in a "Header" template part.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 56930
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::wp_filter_content_tags
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @covers ::wp_get_loading_attr_default
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @covers ::wp_img_tag_add_loading_optimization_attrs
+        * @covers ::wp_get_loading_optimization_attributes
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_does_not_lazy_load_images_in_header() {
</span><span class="cx" style="display: block; padding: 0 10px">                global $_wp_current_template_content;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3941,6 +4089,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Use a single image for each header and footer template parts.
</span><span class="cx" style="display: block; padding: 0 10px">                $header_img = get_image_tag( self::$large_id, '', '', '', 'large' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Since header_img is qualified candidate for LCP, fetchpriority high is applied to it.
+               $header_img = str_replace( '<img', '<img fetchpriority="high"', $header_img );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $footer_img = get_image_tag( self::$large_id, '', '', '', 'medium' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Create header and footer template parts.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3969,7 +4120,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Header image should not be lazy-loaded, footer image should be lazy-loaded.
</span><span class="cx" style="display: block; padding: 0 10px">                $expected_template_content  = '<header class="wp-block-template-part">' . $header_img . '</header>';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $expected_template_content .= '<footer class="wp-block-template-part">' . wp_img_tag_add_loading_attr( $footer_img, 'force-lazy' ) . '</footer>';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $expected_template_content .= '<footer class="wp-block-template-part">' . wp_img_tag_add_loading_optimization_attrs( $footer_img, 'force-lazy' ) . '</footer>';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $html = get_the_block_template_html();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( '<div class="wp-site-blocks">' . $expected_template_content . '</div>', $html );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3977,16 +4128,30 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 58089
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::wp_filter_content_tags
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @covers ::wp_get_loading_attr_default
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @covers ::wp_get_loading_optimization_attributes
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_does_not_lazy_load_special_images_within_the_content() {
</span><span class="cx" style="display: block; padding: 0 10px">                global $wp_query, $wp_the_query;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Force no lazy-loading on the image tag expected in the content.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $expected_content = wpautop( wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => false ) ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $expected_content = wpautop(
+                       wp_get_attachment_image(
+                               self::$large_id,
+                               'large',
+                               false,
+                               array(
+                                       'loading'       => false,
+                                       'fetchpriority' => 'high',
+                               )
+                       )
+               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Reset high priority flag as the forced `fetchpriority="high"` above already modified it.
+               $this->reset_high_priority_element_flag();
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 // Overwrite post content with an image.
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter(
</span><span class="cx" style="display: block; padding: 0 10px">                        'the_content',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4023,6 +4188,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::wp_get_loading_attr_default
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @expectedDeprecated wp_get_loading_attr_default
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @dataProvider data_special_contexts_for_the_content
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $context Context for the element for which the `loading` attribute value is requested.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4038,6 +4205,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::wp_get_loading_attr_default
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @expectedDeprecated wp_get_loading_attr_default
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @dataProvider data_special_contexts_for_the_content
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param string $context Context for the element for which the `loading` attribute value is requested.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4070,6 +4239,313 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Tests that wp_get_loading_attr_default() returns the expected loading attribute value.
+        *
+        * @ticket 53675
+        * @ticket 56930
+        * @ticket 58235
+        *
+        * @covers ::wp_get_loading_optimization_attributes
+        *
+        * @dataProvider data_wp_get_loading_attr_default
+        *
+        * @param string $context
+        */
+       public function test_wp_get_loading_optimization_attributes( $context ) {
+               $attr = $this->get_width_height_for_high_priority();
+
+               // Return 'lazy' by default.
+               $this->assertSame(
+                       array( 'loading' => 'lazy' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, 'test' )
+               );
+               $this->assertSame(
+                       array( 'loading' => 'lazy' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, 'wp_get_attachment_image' )
+               );
+
+               // Return 'lazy' if not in the loop or the main query.
+               $this->assertSame(
+                       array( 'loading' => 'lazy' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, $context )
+               );
+
+               $query = $this->get_new_wp_query_for_published_post();
+
+               while ( have_posts() ) {
+                       the_post();
+
+                       // Return 'lazy' if in the loop but not in the main query.
+                       $this->assertSame(
+                               array( 'loading' => 'lazy' ),
+                               wp_get_loading_optimization_attributes( 'img', $attr, $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.
+                        */
+                       $this->assertSame(
+                               array( 'loading' => 'lazy' ),
+                               wp_get_loading_optimization_attributes( 'img', $attr, 'wp_get_attachment_image' )
+                       );
+
+                       // First three element are not lazy loaded. However, first image is loaded with fetchpriority high.
+                       $this->assertSame(
+                               array( 'fetchpriority' => 'high' ),
+                               wp_get_loading_optimization_attributes( 'img', $attr, $context ),
+                               "Expected first image to not be lazy-loaded. First large image get's high fetchpriority."
+                       );
+                       $this->assertEmpty(
+                               wp_get_loading_optimization_attributes( 'img', $attr, $context ),
+                               'Expected second image to not be lazy-loaded.'
+                       );
+                       $this->assertEmpty(
+                               wp_get_loading_optimization_attributes( 'img', $attr, $context ),
+                               'Expected third image to not be lazy-loaded.'
+                       );
+
+                       // Return 'lazy' if in the loop and in the main query for any subsequent elements.
+                       $this->assertSame(
+                               array( 'loading' => 'lazy' ),
+                               wp_get_loading_optimization_attributes( 'img', $attr, $context )
+                       );
+
+                       // Yes, for all subsequent elements.
+                       $this->assertSame(
+                               array( 'loading' => 'lazy' ),
+                               wp_get_loading_optimization_attributes( 'img', $attr, $context )
+                       );
+               }
+       }
+
+       /**
+        * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute value before loop but after get_header if not main query.
+        *
+        * @ticket 58211
+        * @ticket 58235
+        *
+        * @covers ::wp_get_loading_optimization_attributes
+        *
+        * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
+        *
+        * @param string $context Context for the element for which the `loading` attribute value is requested.
+        */
+       public function test_wp_get_loading_optimization_attributes_before_loop_if_not_main_query( $context ) {
+               global $wp_query;
+
+               $wp_query = $this->get_new_wp_query_for_published_post();
+
+               do_action( 'get_header' );
+
+               $attr = $this->get_width_height_for_high_priority();
+
+               // Lazy if not main query.
+               $this->assertSame(
+                       array( 'loading' => 'lazy' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, $context )
+               );
+       }
+
+       /**
+        * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute value before loop but after get_header in main query but header was not called.
+        *
+        * @ticket 58211
+        * @ticket 58235
+        *
+        * @covers ::wp_get_loading_optimization_attributes
+        *
+        * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
+        *
+        * @param string $context Context for the element for which the `loading` attribute value is requested.
+        */
+       public function test_wp_get_loading_optimization_attributes_before_loop_in_main_query_but_header_not_called( $context ) {
+               global $wp_query;
+
+               $wp_query = $this->get_new_wp_query_for_published_post();
+               $this->set_main_query( $wp_query );
+
+               $attr = $this->get_width_height_for_high_priority();
+
+               // Lazy if header not called.
+               $this->assertSame(
+                       array( 'loading' => 'lazy' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, $context )
+               );
+       }
+
+       /**
+        * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute value before loop but after get_header for main query.
+        *
+        * @ticket 58211
+        * @ticket 58235
+        *
+        * @covers ::wp_get_loading_optimization_attributes
+        *
+        * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
+        *
+        * @param string $context Context for the element for which the `loading` attribute value is requested.
+        */
+       public function test_wp_get_loading_optimization_attributes_before_loop_if_main_query( $context ) {
+               global $wp_query;
+
+               $wp_query = $this->get_new_wp_query_for_published_post();
+               $this->set_main_query( $wp_query );
+               do_action( 'get_header' );
+
+               $attr = $this->get_width_height_for_high_priority();
+
+               // First image is loaded with high fetchpriority.
+               $this->assertSame(
+                       array( 'fetchpriority' => 'high' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, $context ),
+                       'Expected first image to not be lazy-loaded. First large image is loaded with high fetchpriority.'
+               );
+       }
+
+       /**
+        * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute value after get_header and after loop.
+        *
+        * @ticket 58211
+        * @ticket 58235
+        *
+        * @covers ::wp_get_loading_optimization_attributes
+        *
+        * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
+        *
+        * @param string $context Context for the element for which the `loading` attribute value is requested.
+        */
+       public function test_wp_get_loading_optimization_attributes_after_loop( $context ) {
+               global $wp_query;
+
+               $wp_query = $this->get_new_wp_query_for_published_post();
+               $this->set_main_query( $wp_query );
+
+               do_action( 'get_header' );
+
+               while ( have_posts() ) {
+                       the_post();
+               }
+
+               $attr = $this->get_width_height_for_high_priority();
+               $this->assertSame(
+                       array( 'loading' => 'lazy' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, $context )
+               );
+       }
+
+       /**
+        * Tests that wp_get_loading_optimization_attributes() returns the expected loading attribute if no loop.
+        *
+        * @ticket 58211
+        * @ticket 58235
+        *
+        * @covers ::wp_get_loading_optimization_attributes
+        *
+        * @dataProvider data_wp_get_loading_attr_default_before_and_no_loop
+        *
+        * @param string $context Context for the element for which the `loading` attribute value is requested.
+        */
+       public function test_wp_get_loading_optimization_attributes_no_loop( $context ) {
+               global $wp_query;
+
+               $wp_query = $this->get_new_wp_query_for_published_post();
+               $this->set_main_query( $wp_query );
+
+               // Ensure header and footer is called.
+               do_action( 'get_header' );
+               do_action( 'get_footer' );
+
+               $attr = $this->get_width_height_for_high_priority();
+
+               // Load lazy if the there is no loop and footer was called.
+               $this->assertSame(
+                       array( 'loading' => 'lazy' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, $context )
+               );
+       }
+
+       /**
+        * Tests that wp_get_loading_optimization_attributes() returns 'lazy' for special contexts when they're used outside of 'the_content' filter.
+        *
+        * @ticket 58089
+        * @ticket 58235
+        *
+        * @covers ::wp_get_loading_optimization_attributes
+        *
+        * @dataProvider data_special_contexts_for_the_content
+        *
+        * @param string $context Context for the element for which the `loading` attribute value is requested.
+        */
+       public function test_wp_get_loading_optimization_attributes_should_return_lazy_for_special_contexts_outside_of_the_content( $context ) {
+               $attr = $this->get_width_height_for_high_priority();
+               $this->assertSame(
+                       array( 'loading' => 'lazy' ),
+                       wp_get_loading_optimization_attributes( 'img', $attr, $context )
+               );
+       }
+
+       /**
+        * Tests that wp_get_loading_optimization_attributes() returns false for special contexts when they're used within 'the_content' filter.
+        *
+        * @ticket 58089
+        * @ticket 58235
+        *
+        * @covers ::wp_get_loading_optimization_attributes
+        *
+        * @dataProvider data_special_contexts_for_the_content
+        *
+        * @param string $context Context for the element for which the `loading` attribute value is requested.
+        */
+       public function test_wp_get_loading_optimization_attributes_should_return_false_for_special_contexts_within_the_content( $context ) {
+               remove_all_filters( 'the_content' );
+
+               $result = null;
+               add_filter(
+                       'the_content',
+                       function( $content ) use ( &$result, $context ) {
+                               $attr   = $this->get_width_height_for_high_priority();
+                               $result = wp_get_loading_optimization_attributes( 'img', $attr, $context );
+                               return $content;
+                       }
+               );
+               apply_filters( 'the_content', '' );
+
+               $this->assertSame(
+                       array( 'fetchpriority' => 'high' ),
+                       $result,
+                       'First large image is loaded with high fetchpriority.'
+               );
+       }
+
+       /**
+        * @ticket 44427
+        * @ticket 50367
+        * @ticket 58235
+        */
+       public function test_wp_img_tag_add_loading_optimization_attrs() {
+               $img = '<img src="example.png" alt=" width="300" height="225" />';
+               $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 = '<img alt="" width="300" height="225" />';
+               $img = wp_img_tag_add_loading_optimization_attrs( $img, 'test' );
+
+               $this->assertStringNotContainsString( ' loading=', $img );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Tests that the content media count is not affected by `the_excerpt()` calls for posts that contain images.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 56588
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4120,6 +4596,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * that featured image not being lazy-loaded, since the images in the post content aren't displayed in the excerpt.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 56588
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @ticket 58235
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @covers ::wp_trim_excerpt
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4131,6 +4608,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                 * then use a post that contains exactly 2 images.
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><span class="cx" style="display: block; padding: 0 10px">                $this->force_omit_loading_attr_threshold( 2 );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $post_content  = '<img src="example.jpg" width="800" height="600">';
</span><span class="cx" style="display: block; padding: 0 10px">                $post_content .= '<p>Some text.</p>';
</span><span class="cx" style="display: block; padding: 0 10px">                $post_content .= '<img src="example2.jpg" width="800" height="600">';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4144,8 +4622,18 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $featured_image_id = self::$large_id;
</span><span class="cx" style="display: block; padding: 0 10px">                update_post_meta( $post_id, '_thumbnail_id', $featured_image_id );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $expected_image_tag = get_the_post_thumbnail( $post_id, 'post-thumbnail', array( 'loading' => false ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $expected_image_tag = get_the_post_thumbnail(
+                       $post_id,
+                       'post-thumbnail',
+                       array(
+                               'loading'       => false,
+                               'fetchpriority' => 'high',
+                       )
+               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Reset high priority flag as the forced `fetchpriority="high"` above already modified it.
+               $this->reset_high_priority_element_flag();
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $wp_query     = new WP_Query( array( 'post__in' => array( $post_id ) ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $wp_the_query = $wp_query;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4180,6 +4668,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_omit_loading_attr_threshold', '__return_null', 100 );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        private function reset_high_priority_element_flag() {
+               wp_high_priority_element_flag( true );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Test that generated files with the `image_editor_output_format` applied use the correct
</span><span class="cx" style="display: block; padding: 0 10px">         * quality level based on their mime type.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4276,7 +4768,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 58212
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @covers ::wp_get_attachment_image()
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @covers ::wp_get_attachment_image
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_get_attachment_image_context_filter_default() {
</span><span class="cx" style="display: block; padding: 0 10px">                $last_context = '';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4291,7 +4783,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ticket 58212
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @covers ::wp_get_attachment_image()
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @covers ::wp_get_attachment_image
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_get_attachment_image_context_filter_value_is_passed_correctly() {
</span><span class="cx" style="display: block; padding: 0 10px">                $last_context = '';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4310,6 +4802,298 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * 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 )
+               );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Helper method to keep track of the last context returned by the 'wp_get_attachment_image_context' filter.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * The method parameter is passed by reference and therefore will always contain the last context value.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4407,6 +5191,38 @@
</span><span class="cx" style="display: block; padding: 0 10px">                global $wp_the_query;
</span><span class="cx" style="display: block; padding: 0 10px">                $wp_the_query = $query;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * 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,
+               );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre>
</div>
</div>

</body>
</html>