Skip to content

Commit 5da134c

Browse files
authored
Merge branch 'trunk' into update/od-initialization-timing
2 parents 5d12ef7 + da320f3 commit 5da134c

File tree

12 files changed

+566
-113
lines changed

12 files changed

+566
-113
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
},
1818
"suggest": {
1919
"ext-imagick": "Required to use Modern Image Format's Dominant_Color_Image_Editor_Imagick class",
20-
"ext-gd": "Required to use Modern Image Format's Dominant_Color_Image_Editor_GD class"
20+
"ext-gd": "Required to use Modern Image Format's Dominant_Color_Image_Editor_GD class",
21+
"ext-zlib": "Required for compression of URL Metric data submitted to the REST API for storage in Optimization Detective"
2122
},
2223
"require-dev": {
2324
"phpcompatibility/php-compatibility": "^9.3",

composer.lock

Lines changed: 26 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/optimization-detective/detect.js

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// noinspection JSUnusedGlobalSymbols
2+
13
/**
24
* @typedef {import("web-vitals").LCPMetric} LCPMetric
35
* @typedef {import("web-vitals").LCPMetricWithAttribution} LCPMetricWithAttribution
@@ -214,7 +216,7 @@ function getCurrentTime() {
214216
/**
215217
* Recursively freezes an object to prevent mutation.
216218
*
217-
* @param {Object} obj Object to recursively freeze.
219+
* @param {Object} obj - Object to recursively freeze.
218220
*/
219221
function recursiveFreeze( obj ) {
220222
for ( const prop of Object.getOwnPropertyNames( obj ) ) {
@@ -293,7 +295,7 @@ const reservedElementPropertyKeys = new Set( [
293295
/**
294296
* Gets element data.
295297
*
296-
* @param {string} xpath XPath.
298+
* @param {string} xpath - XPath.
297299
* @return {ElementData|null} Element data, or null if no element for the XPath exists.
298300
*/
299301
function getElementData( xpath ) {
@@ -309,8 +311,8 @@ function getElementData( xpath ) {
309311
/**
310312
* Extends element data.
311313
*
312-
* @param {string} xpath XPath.
313-
* @param {ExtendedElementData} properties Properties.
314+
* @param {string} xpath - XPath.
315+
* @param {ExtendedElementData} properties - Properties.
314316
*/
315317
function extendElementData( xpath, properties ) {
316318
if ( ! elementsByXPath.has( xpath ) ) {
@@ -327,6 +329,23 @@ function extendElementData( xpath, properties ) {
327329
Object.assign( elementData, properties );
328330
}
329331

332+
/**
333+
* Compresses a (JSON) string using CompressionStream API.
334+
*
335+
* @param {string} jsonString - JSON string to compress.
336+
* @return {Promise<Blob>} Compressed data.
337+
*/
338+
async function compress( jsonString ) {
339+
const encodedData = new TextEncoder().encode( jsonString );
340+
const compressedDataStream = new Blob( [ encodedData ] )
341+
.stream()
342+
.pipeThrough( new CompressionStream( 'gzip' ) );
343+
const compressedDataBuffer = await new Response(
344+
compressedDataStream
345+
).arrayBuffer();
346+
return new Blob( [ compressedDataBuffer ], { type: 'application/gzip' } );
347+
}
348+
330349
/**
331350
* @typedef {{timestamp: number, creationDate: Date}} UrlMetricDebugData
332351
* @typedef {{groups: Array<{url_metrics: Array<UrlMetricDebugData>}>}} CollectionDebugData
@@ -335,23 +354,25 @@ function extendElementData( xpath, properties ) {
335354
/**
336355
* Detects the LCP element, loaded images, client viewport and store for future optimizations.
337356
*
338-
* @param {Object} args Args.
339-
* @param {string[]} args.extensionModuleUrls URLs for extension script modules to import.
340-
* @param {number} args.minViewportAspectRatio Minimum aspect ratio allowed for the viewport.
341-
* @param {number} args.maxViewportAspectRatio Maximum aspect ratio allowed for the viewport.
342-
* @param {boolean} args.isDebug Whether to show debug messages.
343-
* @param {string} args.restApiEndpoint URL for where to send the detection data.
344-
* @param {string} [args.restApiNonce] Nonce for the REST API when the user is logged-in.
345-
* @param {string} args.currentETag Current ETag.
346-
* @param {string} args.currentUrl Current URL.
347-
* @param {string} args.urlMetricSlug Slug for URL Metric.
348-
* @param {number|null} args.cachePurgePostId Cache purge post ID.
349-
* @param {string} args.urlMetricHMAC HMAC for URL Metric storage.
350-
* @param {URLMetricGroupStatus[]} args.urlMetricGroupStatuses URL Metric group statuses.
351-
* @param {number} args.storageLockTTL The TTL (in seconds) for the URL Metric storage lock.
352-
* @param {number} args.freshnessTTL The freshness age (TTL) for a given URL Metric.
353-
* @param {string} args.webVitalsLibrarySrc The URL for the web-vitals library.
354-
* @param {CollectionDebugData} [args.urlMetricGroupCollection] URL Metric group collection, when in debug mode.
357+
* @param {Object} args - Args.
358+
* @param {string[]} args.extensionModuleUrls - URLs for extension script modules to import.
359+
* @param {number} args.minViewportAspectRatio - Minimum aspect ratio allowed for the viewport.
360+
* @param {number} args.maxViewportAspectRatio - Maximum aspect ratio allowed for the viewport.
361+
* @param {boolean} args.isDebug - Whether to show debug messages.
362+
* @param {string} args.restApiEndpoint - URL for where to send the detection data.
363+
* @param {string} [args.restApiNonce] - Nonce for the REST API when the user is logged-in.
364+
* @param {boolean} args.gzdecodeAvailable - Whether application/gzip can be sent to the REST API.
365+
* @param {number} args.maxUrlMetricSize - Maximum size of the URL Metric to send.
366+
* @param {string} args.currentETag - Current ETag.
367+
* @param {string} args.currentUrl - Current URL.
368+
* @param {string} args.urlMetricSlug - Slug for URL Metric.
369+
* @param {number|null} args.cachePurgePostId - Cache purge post ID.
370+
* @param {string} args.urlMetricHMAC - HMAC for URL Metric storage.
371+
* @param {URLMetricGroupStatus[]} args.urlMetricGroupStatuses - URL Metric group statuses.
372+
* @param {number} args.storageLockTTL - The TTL (in seconds) for the URL Metric storage lock.
373+
* @param {number} args.freshnessTTL - The freshness age (TTL) for a given URL Metric.
374+
* @param {string} args.webVitalsLibrarySrc - The URL for the web-vitals library.
375+
* @param {CollectionDebugData} [args.urlMetricGroupCollection] - URL Metric group collection, when in debug mode.
355376
*/
356377
export default async function detect( {
357378
minViewportAspectRatio,
@@ -360,6 +381,8 @@ export default async function detect( {
360381
extensionModuleUrls,
361382
restApiEndpoint,
362383
restApiNonce,
384+
gzdecodeAvailable,
385+
maxUrlMetricSize,
363386
currentETag,
364387
currentUrl,
365388
urlMetricSlug,
@@ -670,9 +693,7 @@ export default async function detect( {
670693
for ( const elementIntersection of elementIntersections ) {
671694
const xpath = breadcrumbedElementsMap.get( elementIntersection.target );
672695
if ( ! xpath ) {
673-
if ( isDebug ) {
674-
error( 'Unable to look up XPath for element' );
675-
}
696+
warn( 'Unable to look up XPath for element' );
676697
continue;
677698
}
678699

@@ -795,26 +816,34 @@ export default async function detect( {
795816
const maxBodyLengthKiB = 64;
796817
const maxBodyLengthBytes = maxBodyLengthKiB * 1024;
797818

798-
// TODO: Consider adding replacer to reduce precision on numbers in DOMRect to reduce payload size.
799819
const jsonBody = JSON.stringify( urlMetric );
820+
if ( jsonBody.length > maxUrlMetricSize ) {
821+
error(
822+
`URL Metric is ${ jsonBody.length.toLocaleString() } bytes, exceeding the maximum size of ${ maxUrlMetricSize.toLocaleString() } bytes:`,
823+
urlMetric
824+
);
825+
return;
826+
}
827+
828+
const payloadBlob = gzdecodeAvailable
829+
? await compress( jsonBody )
830+
: new Blob( [ jsonBody ], { type: 'application/json' } );
800831
const percentOfBudget =
801-
( jsonBody.length / ( maxBodyLengthKiB * 1000 ) ) * 100;
832+
( payloadBlob.size / ( maxBodyLengthKiB * 1000 ) ) * 100;
802833

803834
/*
804835
* According to the fetch() spec:
805836
* "If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error."
806837
* This is what browsers also implement for navigator.sendBeacon(). Therefore, if the size of the JSON is greater
807838
* than the maximum, we should avoid even trying to send it.
808839
*/
809-
if ( jsonBody.length > maxBodyLengthBytes ) {
810-
if ( isDebug ) {
811-
error(
812-
`Unable to send URL Metric because it is ${ jsonBody.length.toLocaleString() } bytes, ${ Math.round(
813-
percentOfBudget
814-
) }% of ${ maxBodyLengthKiB } KiB limit:`,
815-
urlMetric
816-
);
817-
}
840+
if ( payloadBlob.size > maxBodyLengthBytes ) {
841+
error(
842+
`Unable to send URL Metric because it is ${ payloadBlob.size.toLocaleString() } bytes, ${ Math.round(
843+
percentOfBudget
844+
) }% of ${ maxBodyLengthKiB } KiB limit:`,
845+
urlMetric
846+
);
818847
return;
819848
}
820849

@@ -830,7 +859,7 @@ export default async function detect( {
830859
);
831860
}
832861

833-
const message = `Sending URL Metric (${ jsonBody.length.toLocaleString() } bytes, ${ Math.round(
862+
const message = `Sending URL Metric (${ payloadBlob.size.toLocaleString() } bytes, ${ Math.round(
834863
percentOfBudget
835864
) }% of ${ maxBodyLengthKiB } KiB limit):`;
836865

@@ -854,12 +883,7 @@ export default async function detect( {
854883
);
855884
}
856885
url.searchParams.set( 'hmac', urlMetricHMAC );
857-
navigator.sendBeacon(
858-
url,
859-
new Blob( [ jsonBody ], {
860-
type: 'application/json',
861-
} )
862-
);
886+
navigator.sendBeacon( url, payloadBlob );
863887

864888
// Clean up.
865889
breadcrumbedElementsMap.clear();

plugins/optimization-detective/detection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ static function ( OD_URL_Metric_Group $group ): array {
140140
'storageLockTTL' => OD_Storage_Lock::get_ttl(),
141141
'freshnessTTL' => od_get_url_metric_freshness_ttl(),
142142
'webVitalsLibrarySrc' => $web_vitals_lib_src,
143+
'gzdecodeAvailable' => function_exists( 'gzdecode' ),
144+
'maxUrlMetricSize' => od_get_maximum_url_metric_size(),
143145
);
144146
if ( is_user_logged_in() ) {
145147
$detect_args['restApiNonce'] = wp_create_nonce( 'wp_rest' );

plugins/optimization-detective/hooks.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@
2424
add_filter( 'site_status_tests', 'od_add_rest_api_availability_test' );
2525
add_action( 'admin_init', 'od_maybe_run_rest_api_health_check' );
2626
add_action( 'after_plugin_row_meta', 'od_render_rest_api_health_check_admin_notice_in_plugin_row', 30 );
27+
add_filter( 'rest_pre_dispatch', 'od_decompress_rest_request_body', 10, 3 );
2728
// @codeCoverageIgnoreEnd

0 commit comments

Comments
 (0)