-
Notifications
You must be signed in to change notification settings - Fork 139
Description
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:
performance/modules/images/image-loading-optimization/detection/detect.js
Lines 342 to 433 in 03f3252
| // 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.