1
+ // noinspection JSUnusedGlobalSymbols
2
+
1
3
/**
2
4
* @typedef {import("web-vitals").LCPMetric } LCPMetric
3
5
* @typedef {import("web-vitals").LCPMetricWithAttribution } LCPMetricWithAttribution
@@ -214,7 +216,7 @@ function getCurrentTime() {
214
216
/**
215
217
* Recursively freezes an object to prevent mutation.
216
218
*
217
- * @param {Object } obj Object to recursively freeze.
219
+ * @param {Object } obj - Object to recursively freeze.
218
220
*/
219
221
function recursiveFreeze ( obj ) {
220
222
for ( const prop of Object . getOwnPropertyNames ( obj ) ) {
@@ -293,7 +295,7 @@ const reservedElementPropertyKeys = new Set( [
293
295
/**
294
296
* Gets element data.
295
297
*
296
- * @param {string } xpath XPath.
298
+ * @param {string } xpath - XPath.
297
299
* @return {ElementData|null } Element data, or null if no element for the XPath exists.
298
300
*/
299
301
function getElementData ( xpath ) {
@@ -309,8 +311,8 @@ function getElementData( xpath ) {
309
311
/**
310
312
* Extends element data.
311
313
*
312
- * @param {string } xpath XPath.
313
- * @param {ExtendedElementData } properties Properties.
314
+ * @param {string } xpath - XPath.
315
+ * @param {ExtendedElementData } properties - Properties.
314
316
*/
315
317
function extendElementData ( xpath , properties ) {
316
318
if ( ! elementsByXPath . has ( xpath ) ) {
@@ -327,6 +329,23 @@ function extendElementData( xpath, properties ) {
327
329
Object . assign ( elementData , properties ) ;
328
330
}
329
331
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
+
330
349
/**
331
350
* @typedef {{timestamp: number, creationDate: Date} } UrlMetricDebugData
332
351
* @typedef {{groups: Array<{url_metrics: Array<UrlMetricDebugData>}>} } CollectionDebugData
@@ -335,23 +354,25 @@ function extendElementData( xpath, properties ) {
335
354
/**
336
355
* Detects the LCP element, loaded images, client viewport and store for future optimizations.
337
356
*
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.
355
376
*/
356
377
export default async function detect ( {
357
378
minViewportAspectRatio,
@@ -360,6 +381,8 @@ export default async function detect( {
360
381
extensionModuleUrls,
361
382
restApiEndpoint,
362
383
restApiNonce,
384
+ gzdecodeAvailable,
385
+ maxUrlMetricSize,
363
386
currentETag,
364
387
currentUrl,
365
388
urlMetricSlug,
@@ -670,9 +693,7 @@ export default async function detect( {
670
693
for ( const elementIntersection of elementIntersections ) {
671
694
const xpath = breadcrumbedElementsMap . get ( elementIntersection . target ) ;
672
695
if ( ! xpath ) {
673
- if ( isDebug ) {
674
- error ( 'Unable to look up XPath for element' ) ;
675
- }
696
+ warn ( 'Unable to look up XPath for element' ) ;
676
697
continue ;
677
698
}
678
699
@@ -795,26 +816,34 @@ export default async function detect( {
795
816
const maxBodyLengthKiB = 64 ;
796
817
const maxBodyLengthBytes = maxBodyLengthKiB * 1024 ;
797
818
798
- // TODO: Consider adding replacer to reduce precision on numbers in DOMRect to reduce payload size.
799
819
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' } ) ;
800
831
const percentOfBudget =
801
- ( jsonBody . length / ( maxBodyLengthKiB * 1000 ) ) * 100 ;
832
+ ( payloadBlob . size / ( maxBodyLengthKiB * 1000 ) ) * 100 ;
802
833
803
834
/*
804
835
* According to the fetch() spec:
805
836
* "If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error."
806
837
* This is what browsers also implement for navigator.sendBeacon(). Therefore, if the size of the JSON is greater
807
838
* than the maximum, we should avoid even trying to send it.
808
839
*/
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
+ ) ;
818
847
return ;
819
848
}
820
849
@@ -830,7 +859,7 @@ export default async function detect( {
830
859
) ;
831
860
}
832
861
833
- const message = `Sending URL Metric (${ jsonBody . length . toLocaleString ( ) } bytes, ${ Math . round (
862
+ const message = `Sending URL Metric (${ payloadBlob . size . toLocaleString ( ) } bytes, ${ Math . round (
834
863
percentOfBudget
835
864
) } % of ${ maxBodyLengthKiB } KiB limit):`;
836
865
@@ -854,12 +883,7 @@ export default async function detect( {
854
883
) ;
855
884
}
856
885
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 ) ;
863
887
864
888
// Clean up.
865
889
breadcrumbedElementsMap . clear ( ) ;
0 commit comments