Skip to content

Guard against detection logic from negatively impacting INP #894

Closed
@westonruter

Description

@westonruter

Feature Description

As discussed in #878 (comment), the Image Loading Optimization module (see #869) has detection logic to discover the LCP image and other images which are in the initial viewport. This logic is currently running at the load event after which it immediately sends the data to the REST API endpoint:

// Wait until the resources on the page have fully loaded.
await new Promise( ( resolve ) => {
if ( doc.readyState === 'complete' ) {
resolve();
} else {
win.addEventListener( 'load', resolve, { once: true } );
}
} );
// Stop observing.
disconnectIntersectionObserver();
if ( isDebug ) {
log( 'Detection is stopping.' );
}
/** @type {URLMetrics} */
const urlMetrics = {
url: win.location.href,
slug: urlMetricsSlug,
nonce: urlMetricsNonce,
viewport: {
width: win.innerWidth,
height: win.innerHeight,
},
elements: [],
};
const lcpMetric = lcpMetricCandidates.at( -1 );
for ( const elementIntersection of elementIntersections ) {
const breadcrumbs = breadcrumbedElementsMap.get(
elementIntersection.target
);
if ( ! breadcrumbs ) {
if ( isDebug ) {
error( 'Unable to look up breadcrumbs for element' );
}
continue;
}
const isLCP =
elementIntersection.target === lcpMetric?.entries[ 0 ]?.element;
/** @type {ElementMetrics} */
const elementMetrics = {
isLCP,
isLCPCandidate: !! lcpMetricCandidates.find(
( lcpMetricCandidate ) =>
lcpMetricCandidate.entries[ 0 ]?.element ===
elementIntersection.target
),
breadcrumbs,
intersectionRatio: elementIntersection.intersectionRatio,
intersectionRect: elementIntersection.intersectionRect,
boundingClientRect: elementIntersection.boundingClientRect,
};
urlMetrics.elements.push( elementMetrics );
}
if ( isDebug ) {
log( 'URL metrics:', urlMetrics );
}
// TODO: Wait until idle? Yield to main?
try {
const response = await fetch( restApiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': restApiNonce,
},
body: JSON.stringify( urlMetrics ),
} );
if ( response.status === 200 ) {
setStorageLock( getCurrentTime() );
}
if ( isDebug ) {
const body = await response.json();
if ( response.status === 200 ) {
log( 'Response:', body );
} else {
error( 'Failure:', body );
}
}
} catch ( err ) {
if ( isDebug ) {
error( err );
}
}

However, given that the metrics collection is a low priority task which is intended to improve the user experience, we must guard against it from possibly impacting a visitor in a negative way when URL metrics need to gathered. For this reason there is the following todo:

// TODO: Wait until idle? Yield to main?

We should leverage requestIdleCallback() and/or scheduler.yield() (or else setTimeout)--whatever is available--to make sure that the detection task is broken up into small tasks that do not contribute to a long task.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions