Skip to content

Commit

Permalink
Merge pull request #1697 from WordPress/add/external-bg-preload
Browse files Browse the repository at this point in the history
Preload image URLs for LCP elements with external background images
  • Loading branch information
westonruter authored Dec 11, 2024
2 parents 9661a3c + 02c6673 commit b1bf1ae
Show file tree
Hide file tree
Showing 17 changed files with 1,090 additions and 34 deletions.
2 changes: 1 addition & 1 deletion plugins/embed-optimizer/detect.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const loadedElementContentRects = new Map();
* @type {InitializeCallback}
* @param {InitializeArgs} args Args.
*/
export function initialize( { isDebug } ) {
export async function initialize( { isDebug } ) {
/** @type NodeListOf<HTMLDivElement> */
const embedWrappers = document.querySelectorAll(
'.wp-block-embed > .wp-block-embed__wrapper[data-od-xpath]'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
/**
* Tag visitor that optimizes elements with background-image styles.
*
* @phpstan-type LcpElementExternalBackgroundImage array{
* url: non-empty-string,
* tag: non-empty-string,
* id: string|null,
* class: string|null,
* }
*
* @since 0.1.0
* @access private
*/
Expand All @@ -35,6 +42,14 @@ final class Image_Prioritizer_Background_Image_Styled_Tag_Visitor extends Image_
*/
private $added_lazy_assets = false;

/**
* Tuples of URL Metric group and the common LCP element external background image.
*
* @since n.e.x.t
* @var array<array{OD_URL_Metric_Group, LcpElementExternalBackgroundImage}>
*/
private $group_common_lcp_element_external_background_images;

/**
* Visits a tag.
*
Expand Down Expand Up @@ -65,33 +80,128 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool {
}

if ( is_null( $background_image_url ) ) {
$this->maybe_preload_external_lcp_background_image( $context );
return false;
}

$xpath = $processor->get_xpath();

// If this element is the LCP (for a breakpoint group), add a preload link for it.
foreach ( $context->url_metric_group_collection->get_groups_by_lcp_element( $xpath ) as $group ) {
$link_attributes = array(
'rel' => 'preload',
'fetchpriority' => 'high',
'as' => 'image',
'href' => $background_image_url,
'media' => 'screen',
);

$context->link_collection->add_link(
$link_attributes,
$group->get_minimum_viewport_width(),
$group->get_maximum_viewport_width()
);
$this->add_image_preload_link( $context->link_collection, $group, $background_image_url );
}

$this->lazy_load_bg_images( $context );

return true;
}

/**
* Gets the common LCP element external background image for a URL Metric group.
*
* @since n.e.x.t
*
* @param OD_URL_Metric_Group $group Group.
* @return LcpElementExternalBackgroundImage|null
*/
private function get_common_lcp_element_external_background_image( OD_URL_Metric_Group $group ): ?array {

// If the group is not fully populated, we don't have enough URL Metrics to reliably know whether the background image is consistent across page loads.
// This is intentionally not using $group->is_complete() because we still will use stale URL Metrics in the calculation.
if ( $group->count() !== $group->get_sample_size() ) {
return null;
}

$previous_lcp_element_external_background_image = null;
foreach ( $group as $url_metric ) {
/**
* Stored data.
*
* @var LcpElementExternalBackgroundImage|null $lcp_element_external_background_image
*/
$lcp_element_external_background_image = $url_metric->get( 'lcpElementExternalBackgroundImage' );
if ( ! is_array( $lcp_element_external_background_image ) ) {
return null;
}
if ( null !== $previous_lcp_element_external_background_image && $previous_lcp_element_external_background_image !== $lcp_element_external_background_image ) {
return null;
}
$previous_lcp_element_external_background_image = $lcp_element_external_background_image;
}

return $previous_lcp_element_external_background_image;
}

/**
* Maybe preloads external background image.
*
* @since n.e.x.t
*
* @param OD_Tag_Visitor_Context $context Context.
*/
private function maybe_preload_external_lcp_background_image( OD_Tag_Visitor_Context $context ): void {
// Gather the tuples of URL Metric group and the common LCP element external background image.
// Note the groups of URL Metrics do not change across invocations, we just need to compute this once for all.
if ( ! is_array( $this->group_common_lcp_element_external_background_images ) ) {
$this->group_common_lcp_element_external_background_images = array();
foreach ( $context->url_metric_group_collection as $group ) {
$common = $this->get_common_lcp_element_external_background_image( $group );
if ( is_array( $common ) ) {
$this->group_common_lcp_element_external_background_images[] = array( $group, $common );
}
}
}

// There are no common LCP background images, so abort.
if ( count( $this->group_common_lcp_element_external_background_images ) === 0 ) {
return;
}

$processor = $context->processor;
$tag_name = strtoupper( (string) $processor->get_tag() );
foreach ( array_keys( $this->group_common_lcp_element_external_background_images ) as $i ) {
list( $group, $common ) = $this->group_common_lcp_element_external_background_images[ $i ];
if (
// Note that the browser may send a lower-case tag name in the case of XHTML or embedded SVG/MathML, but
// the HTML Tag Processor is currently normalizing to all upper-case. The HTML Processor on the other
// hand may return the expected case.
strtoupper( $common['tag'] ) === $tag_name
&&
$processor->get_attribute( 'id' ) === $common['id'] // May be checking equality with null.
&&
$processor->get_attribute( 'class' ) === $common['class'] // May be checking equality with null.
) {
$this->add_image_preload_link( $context->link_collection, $group, $common['url'] );

// Now that the preload link has been added, eliminate the entry to stop looking for it while iterating over the rest of the document.
unset( $this->group_common_lcp_element_external_background_images[ $i ] );
}
}
}

/**
* Adds an image preload link for the group.
*
* @since n.e.x.t
*
* @param OD_Link_Collection $link_collection Link collection.
* @param OD_URL_Metric_Group $group URL Metric group.
* @param non-empty-string $url Image URL.
*/
private function add_image_preload_link( OD_Link_Collection $link_collection, OD_URL_Metric_Group $group, string $url ): void {
$link_collection->add_link(
array(
'rel' => 'preload',
'fetchpriority' => 'high',
'as' => 'image',
'href' => $url,
'media' => 'screen',
),
$group->get_minimum_viewport_width(),
$group->get_maximum_viewport_width()
);
}

/**
* Optimizes an element with a background image based on whether it is displayed in any initial viewport.
*
Expand Down
Loading

0 comments on commit b1bf1ae

Please sign in to comment.