WordPress Version: 6.4
/**
* Gets loading optimization attributes.
*
* 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"
* - `decoding` attribute with a value of "async"
*
* If any of these attributes are already present in the given attributes, they will not be modified. Note that no
* element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case
* both attributes are present with those values.
*
* @since 6.3.0
*
* @global WP_Query $wp_query WordPress Query object.
*
* @param string $tag_name The tag name.
* @param array $attr Array of the attributes for the tag.
* @param string $context Context for the element for which the loading optimization attribute is requested.
* @return array Loading optimization attributes.
*/
function wp_get_loading_optimization_attributes($tag_name, $attr, $context)
{
global $wp_query;
/**
* Filters whether to short-circuit loading optimization attributes.
*
* Returning an array from the filter will effectively short-circuit the loading of optimization attributes,
* returning that value instead.
*
* @since 6.4.0
*
* @param array|false $loading_attrs False by default, or array of loading optimization attributes to short-circuit.
* @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.
*/
$loading_attrs = apply_filters('pre_wp_get_loading_optimization_attributes', false, $tag_name, $attr, $context);
if (is_array($loading_attrs)) {
return $loading_attrs;
}
$loading_attrs = array();
/*
* Skip lazy-loading for the overall block template, as it is handled more granularly.
* The skip is also applicable for `fetchpriority`.
*/
if ('template' === $context) {
/** This filter is documented in wp-includes/media.php */
return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
}
// For now this function only supports images and iframes.
if ('img' !== $tag_name && 'iframe' !== $tag_name) {
/** This filter is documented in wp-includes/media.php */
return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
}
/*
* Skip programmatically created images within content blobs as they need to be handled together with the other
* images within the post content or widget content.
* Without this clause, they would already be considered within their own context which skews the image count and
* can result in the first post content image being lazy-loaded or an image further down the page being marked as a
* high priority.
*/
if ('the_content' !== $context && doing_filter('the_content') || 'widget_text_content' !== $context && doing_filter('widget_text_content') || 'widget_block_content' !== $context && doing_filter('widget_block_content')) {
/** This filter is documented in wp-includes/media.php */
return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
}
/*
* Add `decoding` with a value of "async" for every image unless it has a
* conflicting `decoding` attribute already present.
*/
if ('img' === $tag_name) {
if (isset($attr['decoding'])) {
$loading_attrs['decoding'] = $attr['decoding'];
} else {
$loading_attrs['decoding'] = 'async';
}
}
// For any resources, width and height must be provided, to avoid layout shifts.
if (!isset($attr['width'], $attr['height'])) {
/** This filter is documented in wp-includes/media.php */
return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
}
/*
* The key function logic starts here.
*/
$maybe_in_viewport = null;
$increase_count = false;
$maybe_increase_count = false;
// Logic to handle a `loading` attribute that is already provided.
if (isset($attr['loading'])) {
/*
* Interpret "lazy" as not in viewport. Any other value can be
* interpreted as in viewport (realistically only "eager" or `false`
* to force-omit the attribute are other potential values).
*/
if ('lazy' === $attr['loading']) {
$maybe_in_viewport = false;
} else {
$maybe_in_viewport = true;
}
}
// Logic to handle a `fetchpriority` attribute that is already provided.
if (isset($attr['fetchpriority']) && 'high' === $attr['fetchpriority']) {
/*
* If the image was already determined to not be in the viewport (e.g.
* from an already provided `loading` attribute), trigger a warning.
* Otherwise, the value can be interpreted as in viewport, since only
* the most important in-viewport image should have `fetchpriority` set
* to "high".
*/
if (false === $maybe_in_viewport) {
_doing_it_wrong(__FUNCTION__, __('An image should not be lazy-loaded and marked as high priority at the same time.'), '6.3.0');
/*
* Set `fetchpriority` here for backward-compatibility as we should
* not override what a developer decided, even though it seems
* incorrect.
*/
$loading_attrs['fetchpriority'] = 'high';
} else {
$maybe_in_viewport = true;
}
}
if (null === $maybe_in_viewport) {
$header_enforced_contexts = array('template_part_' . WP_TEMPLATE_PART_AREA_HEADER => true, 'get_header_image_tag' => true);
/**
* Filters the header-specific contexts.
*
* @since 6.4.0
*
* @param array $default_header_enforced_contexts Map of contexts for which elements should be considered
* in the header of the page, as $context => $enabled
* pairs. The $enabled should always be true.
*/
$header_enforced_contexts = apply_filters('wp_loading_optimization_force_header_contexts', $header_enforced_contexts);
// Consider elements with these header-specific contexts to be in viewport.
if (isset($header_enforced_contexts[$context])) {
$maybe_in_viewport = true;
$maybe_increase_count = true;
} elseif (!is_admin() && in_the_loop() && is_main_query()) {
/*
* Get the content media count, since this is a main query
* content element. This is accomplished by "increasing"
* the count by zero, as the only way to get the count is
* to call this function.
* The actual count increase happens further below, based
* on the `$increase_count` flag set here.
*/
$content_media_count = wp_increase_content_media_count(0);
$increase_count = true;
// If the count so far is below the threshold, `loading` attribute is omitted.
if ($content_media_count < wp_omit_loading_attr_threshold()) {
$maybe_in_viewport = true;
} else {
$maybe_in_viewport = false;
}
} elseif ($wp_query->before_loop && $wp_query->is_main_query() && did_action('get_header') && !did_action('get_footer')) {
$maybe_in_viewport = true;
$maybe_increase_count = true;
}
}
/*
* If the element is in the viewport (`true`), potentially add
* `fetchpriority` with a value of "high". Otherwise, i.e. if the element
* is not not in the viewport (`false`) or it is unknown (`null`), add
* `loading` with a value of "lazy".
*/
if ($maybe_in_viewport) {
$loading_attrs = wp_maybe_add_fetchpriority_high_attr($loading_attrs, $tag_name, $attr);
} else if (wp_lazy_loading_enabled($tag_name, $context)) {
$loading_attrs['loading'] = 'lazy';
}
/*
* If flag was set based on contextual logic above, increase the content
* media count, either unconditionally, or based on whether the image size
* is larger than the threshold.
*/
if ($increase_count) {
wp_increase_content_media_count();
} elseif ($maybe_increase_count) {
/** This filter is documented in wp-includes/media.php */
$wp_min_priority_img_pixels = apply_filters('wp_min_priority_img_pixels', 50000);
if ($wp_min_priority_img_pixels <= $attr['width'] * $attr['height']) {
wp_increase_content_media_count();
}
}
/**
* Filters the loading optimization attributes.
*
* @since 6.4.0
*
* @param array $loading_attrs The loading optimization attributes.
* @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 apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
}