Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4153951
Add string compression using CompressionStream API for URL Metric pay…
b1ink0 Mar 4, 2025
5347657
Add decompression for REST API request body in URL Metrics endpoint
b1ink0 Mar 4, 2025
29fff3c
Fix failing test
b1ink0 Mar 5, 2025
36bc475
Remove redundant comment and improve doc comment
b1ink0 Mar 6, 2025
a4632d1
Keep XPath for matching the required pattern
b1ink0 Mar 6, 2025
14409a6
Remove `isDebug` conditions and improve JSDoc comments for consistency
b1ink0 Mar 6, 2025
2f81ec0
Add REST API request body decompression and related tests
b1ink0 Mar 6, 2025
90aca14
Add test for `rest_pre_dispatch` hook and cover annotations
b1ink0 Mar 6, 2025
eeb066d
Merge branch 'trunk' into add/url-metrics-compression
b1ink0 Mar 6, 2025
8b84ed3
Suppress unused default export error
westonruter Mar 10, 2025
c59c3ed
Add ext-zlib to composer.json
westonruter Mar 10, 2025
130277a
Switch ext-zlip from require to suggest
westonruter Mar 10, 2025
cbfba12
Run composer update
westonruter Mar 10, 2025
0ca808f
Fix misspelling
westonruter Mar 10, 2025
e1d4b1e
Keep timestamps for URL Metrics current in fixture
westonruter Mar 10, 2025
af1d1dc
Compress URL metrics only when `gzdecode` is available
b1ink0 Mar 12, 2025
97c7836
Merge branch 'trunk' into add/url-metrics-compression
b1ink0 Mar 12, 2025
82fe8e9
Always pass `gzdecodeAvailable` to client
b1ink0 Mar 12, 2025
1984db0
Add max URL metrics size constraints
b1ink0 Mar 13, 2025
5f0be29
Add tests for max URL metrics size filter in REST API
b1ink0 Mar 13, 2025
f9d7fe1
Ensure `od_get_max_url_metric_size` returns a valid positive value wi…
b1ink0 Mar 13, 2025
2a11bf5
Improve DOC comment for filter
b1ink0 Mar 13, 2025
ba383c6
Merge branch 'trunk' of https://github.com/WordPress/performance into…
westonruter Mar 14, 2025
746a410
Use maximum instead of max in PHP; add test coverage
westonruter Mar 14, 2025
d41da3c
Move od_get_maximum_url_metric_size to storage/data.php
westonruter Mar 14, 2025
ad6d264
Fix passing filter name to _doing_it_wrong()
westonruter Mar 14, 2025
c0e3291
Rename string to jsonString to avoid potential type confusion
westonruter Mar 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 52 additions & 42 deletions plugins/optimization-detective/detect.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// noinspection JSUnusedGlobalSymbols
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this because in PhpStorm because otherwise there is a warning:

image

The reason the IDE doesn't know the export is used is because it is used in PHP and via a dynamic export. See also https://stackoverflow.com/questions/54687009/export-default-unused-in-webstorm

This noinspection annotation is used in Gutenberg once as well: https://github.com/WordPress/gutenberg/blob/8b88ada2fd675509bf0c39a55c23a75cc67987cb/packages/components/src/mobile/link-settings/test/edit.native.js#L1


/**
* @typedef {import("web-vitals").LCPMetric} LCPMetric
* @typedef {import("web-vitals").LCPMetricWithAttribution} LCPMetricWithAttribution
Expand Down Expand Up @@ -197,7 +199,7 @@ function getCurrentTime() {
/**
* Recursively freezes an object to prevent mutation.
*
* @param {Object} obj Object to recursively freeze.
* @param {Object} obj - Object to recursively freeze.
*/
function recursiveFreeze( obj ) {
for ( const prop of Object.getOwnPropertyNames( obj ) ) {
Expand Down Expand Up @@ -276,7 +278,7 @@ const reservedElementPropertyKeys = new Set( [
/**
* Gets element data.
*
* @param {string} xpath XPath.
* @param {string} xpath - XPath.
* @return {ElementData|null} Element data, or null if no element for the XPath exists.
*/
function getElementData( xpath ) {
Expand All @@ -292,8 +294,8 @@ function getElementData( xpath ) {
/**
* Extends element data.
*
* @param {string} xpath XPath.
* @param {ExtendedElementData} properties Properties.
* @param {string} xpath - XPath.
* @param {ExtendedElementData} properties - Properties.
*/
function extendElementData( xpath, properties ) {
if ( ! elementsByXPath.has( xpath ) ) {
Expand All @@ -310,6 +312,23 @@ function extendElementData( xpath, properties ) {
Object.assign( elementData, properties );
}

/**
* Compresses a string using CompressionStream API.
*
* @param {string} string - String to compress.
* @return {Promise<Blob>} Compressed data.
*/
async function compress( string ) {
const encodedData = new TextEncoder().encode( string );
const compressedDataStream = new Blob( [ encodedData ] )
.stream()
.pipeThrough( new CompressionStream( 'gzip' ) );
const compressedDataBuffer = await new Response(
compressedDataStream
).arrayBuffer();
return new Blob( [ compressedDataBuffer ], { type: 'application/gzip' } );
}

/**
* @typedef {{timestamp: number, creationDate: Date}} UrlMetricDebugData
* @typedef {{groups: Array<{url_metrics: Array<UrlMetricDebugData>}>}} CollectionDebugData
Expand All @@ -318,23 +337,23 @@ function extendElementData( xpath, properties ) {
/**
* Detects the LCP element, loaded images, client viewport and store for future optimizations.
*
* @param {Object} args Args.
* @param {string[]} args.extensionModuleUrls URLs for extension script modules to import.
* @param {number} args.minViewportAspectRatio Minimum aspect ratio allowed for the viewport.
* @param {number} args.maxViewportAspectRatio Maximum aspect ratio allowed for the viewport.
* @param {boolean} args.isDebug Whether to show debug messages.
* @param {string} args.restApiEndpoint URL for where to send the detection data.
* @param {string} [args.restApiNonce] Nonce for the REST API when the user is logged-in.
* @param {string} args.currentETag Current ETag.
* @param {string} args.currentUrl Current URL.
* @param {string} args.urlMetricSlug Slug for URL Metric.
* @param {number|null} args.cachePurgePostId Cache purge post ID.
* @param {string} args.urlMetricHMAC HMAC for URL Metric storage.
* @param {URLMetricGroupStatus[]} args.urlMetricGroupStatuses URL Metric group statuses.
* @param {number} args.storageLockTTL The TTL (in seconds) for the URL Metric storage lock.
* @param {number} args.freshnessTTL The freshness age (TTL) for a given URL Metric.
* @param {string} args.webVitalsLibrarySrc The URL for the web-vitals library.
* @param {CollectionDebugData} [args.urlMetricGroupCollection] URL Metric group collection, when in debug mode.
* @param {Object} args - Args.
* @param {string[]} args.extensionModuleUrls - URLs for extension script modules to import.
* @param {number} args.minViewportAspectRatio - Minimum aspect ratio allowed for the viewport.
* @param {number} args.maxViewportAspectRatio - Maximum aspect ratio allowed for the viewport.
* @param {boolean} args.isDebug - Whether to show debug messages.
* @param {string} args.restApiEndpoint - URL for where to send the detection data.
* @param {string} [args.restApiNonce] - Nonce for the REST API when the user is logged-in.
* @param {string} args.currentETag - Current ETag.
* @param {string} args.currentUrl - Current URL.
* @param {string} args.urlMetricSlug - Slug for URL Metric.
* @param {number|null} args.cachePurgePostId - Cache purge post ID.
* @param {string} args.urlMetricHMAC - HMAC for URL Metric storage.
* @param {URLMetricGroupStatus[]} args.urlMetricGroupStatuses - URL Metric group statuses.
* @param {number} args.storageLockTTL - The TTL (in seconds) for the URL Metric storage lock.
* @param {number} args.freshnessTTL - The freshness age (TTL) for a given URL Metric.
* @param {string} args.webVitalsLibrarySrc - The URL for the web-vitals library.
* @param {CollectionDebugData} [args.urlMetricGroupCollection] - URL Metric group collection, when in debug mode.
*/
export default async function detect( {
minViewportAspectRatio,
Expand Down Expand Up @@ -648,9 +667,7 @@ export default async function detect( {
for ( const elementIntersection of elementIntersections ) {
const xpath = breadcrumbedElementsMap.get( elementIntersection.target );
if ( ! xpath ) {
if ( isDebug ) {
error( 'Unable to look up XPath for element' );
}
warn( 'Unable to look up XPath for element' );
continue;
}

Expand Down Expand Up @@ -773,26 +790,24 @@ export default async function detect( {
const maxBodyLengthKiB = 64;
const maxBodyLengthBytes = maxBodyLengthKiB * 1024;

// TODO: Consider adding replacer to reduce precision on numbers in DOMRect to reduce payload size.
const jsonBody = JSON.stringify( urlMetric );
const compressedJsonBody = await compress( jsonBody );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per above, this should be conditional based on gzdecodeAvailable.

const percentOfBudget =
( jsonBody.length / ( maxBodyLengthKiB * 1000 ) ) * 100;
( compressedJsonBody.size / ( maxBodyLengthKiB * 1000 ) ) * 100;

/*
* According to the fetch() spec:
* "If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error."
* This is what browsers also implement for navigator.sendBeacon(). Therefore, if the size of the JSON is greater
* than the maximum, we should avoid even trying to send it.
*/
if ( jsonBody.length > maxBodyLengthBytes ) {
if ( isDebug ) {
error(
`Unable to send URL Metric because it is ${ jsonBody.length.toLocaleString() } bytes, ${ Math.round(
percentOfBudget
) }% of ${ maxBodyLengthKiB } KiB limit:`,
urlMetric
);
}
if ( compressedJsonBody.size > maxBodyLengthBytes ) {
error(
`Unable to send URL Metric because it is ${ compressedJsonBody.size.toLocaleString() } bytes, ${ Math.round(
percentOfBudget
) }% of ${ maxBodyLengthKiB } KiB limit:`,
urlMetric
);
return;
}

Expand All @@ -806,7 +821,7 @@ export default async function detect( {
String( getCurrentTime() )
);

const message = `Sending URL Metric (${ jsonBody.length.toLocaleString() } bytes, ${ Math.round(
const message = `Sending URL Metric (${ compressedJsonBody.size.toLocaleString() } bytes, ${ Math.round(
percentOfBudget
) }% of ${ maxBodyLengthKiB } KiB limit):`;

Expand All @@ -830,12 +845,7 @@ export default async function detect( {
);
}
url.searchParams.set( 'hmac', urlMetricHMAC );
navigator.sendBeacon(
url,
new Blob( [ jsonBody ], {
type: 'application/json',
} )
);
navigator.sendBeacon( url, compressedJsonBody );

// Clean up.
breadcrumbedElementsMap.clear();
Expand Down
1 change: 1 addition & 0 deletions plugins/optimization-detective/hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
add_filter( 'site_status_tests', 'od_add_rest_api_availability_test' );
add_action( 'admin_init', 'od_maybe_run_rest_api_health_check' );
add_action( 'after_plugin_row_meta', 'od_render_rest_api_health_check_admin_notice_in_plugin_row', 30 );
add_filter( 'rest_pre_dispatch', 'od_decompress_rest_request_body', 10, 3 );
// @codeCoverageIgnoreEnd
83 changes: 62 additions & 21 deletions plugins/optimization-detective/storage/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,27 +218,6 @@ function od_handle_rest_request( WP_REST_Request $request ) {
);
}

/*
* The limit for data sent via navigator.sendBeacon() is 64 KiB. This limit is checked in detect.js so that the
* request will not even be attempted if the payload is too large. This server-side restriction is added as a
* safeguard against clients sending possibly malicious payloads much larger than 64 KiB which should never be
* getting sent.
*/
$max_size = 64 * 1024;
$content_length = strlen( (string) wp_json_encode( $url_metric ) );
if ( $content_length > $max_size ) {
return new WP_Error(
'rest_content_too_large',
sprintf(
/* translators: 1: the size of the payload, 2: the maximum allowed payload size */
__( 'JSON payload size is %1$s bytes which is larger than the maximum allowed size of %2$s bytes.', 'optimization-detective' ),
number_format_i18n( $content_length ),
number_format_i18n( $max_size )
),
array( 'status' => 413 )
);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this check from od_handle_rest_request as we can compress the URL metrics. However, in cases where gzdecode is unavailable, should we reintroduce this check in od_handle_rest_request, conditioned upon the availability of gzdecode?

Additionally, if a malicious user sends data with application/json content type and gzdecode is available, there is currently no check enforcing an upper limit on the size of the URL metrics.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, good points!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a limit of 1MB for URL metrics? I am not sure about the what should be the limit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder as well, when gzdecode() is available, what happens if I try (maliciously) submitting a URL Metric which has 1,000,000 instances of the same element? Given that compression will likely be very effective in this case with the duplication, will it result in something extremely large being submitted when decompressed? If so, should we have some fallback size constraint for a decompressed URL Metric?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I think we were thinking along the same lines here and commented at the same time.)

Yes, let's maybe introduce a new function that returns the maximum byte size for a URL Metric serialized to JSON. The function's return value can be filtered, but defaulting to MB_IN_BYTES seems good to me. This should perhaps get exported to the client so that the pre-compressed JSON size can be checked to see if it is too big so the client will skip sending it in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought—should we compress the URL metrics before storing them in the database? Would using gzdecode cause performance issues, since we'll need to decompress the data on every request to the optimized page? Compressing the metrics could allow us to store larger URL metric data, but I'm not sure if any real-world scenario would generate more than 1 MB of URL metrics.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to compress in the DB, no. That would be an over-optimization and could negatively impact performance at runtime with having to decompress. Storage is cheap. What is expensive is network bandwidth, especially with the limitations of the beacon size. So that's what we want to optimize for.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried submitting a URL Metric with various numbers of copies of the first element completely populating the elements. I was able to get up to 18,000 identical elements being copied before going over the 64 KiB limit with compression. Before this URL Metric was compressed, it was a total of 10,584,362 bytes (10.6 MB). So 1,800 elements is roughly 1 MB. This seems like a reasonable limit to impose.

Copy link
Member

@westonruter westonruter Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried doing something silly and visit every single tag:

add_action( 'od_register_tag_visitors', function ( OD_Tag_Visitor_Registry $registry ) {
	$registry->register( 'all', '__return_true' );
} );

On a post with 1,000 Image blocks, this resulted in 2,198 elements in the URL Metric. The uncompressed size of the URL Metric is 1,068,353 bytes (about 1 MB), with the compressed size being 33,047 bytes (52% of 64 KiB limit). So indeed, I think 1 MB is probably far larger than any URL Metric will naturally be, so it's a safe upper bound for what should be allowed.

try {
$url_metric_group->add_url_metric( $url_metric );
} catch ( InvalidArgumentException $e ) {
Expand Down Expand Up @@ -346,3 +325,65 @@ function od_trigger_page_cache_invalidation( int $cache_purge_post_id ): void {
/** This action is documented in wp-includes/post.php. */
do_action( 'save_post', $post->ID, $post, /* $update */ true );
}

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just had a thought that perhaps an Accept response header should be added for this endpoint. This apparently isn't needed because I don't see Accept: application/json being served anywhere now in REST API responses. Just something to think about.

* Decompresses the REST API request body for the URL Metrics endpoint.
*
* @since n.e.x.t
* @access private
*
* @phpstan-param WP_REST_Request<array<string, mixed>> $request
*
* @param mixed $result Response to replace the requested version with. Can be anything a normal endpoint can return, or null to not hijack the request.
* @param WP_REST_Server $server Server instance.
* @param WP_REST_Request $request Request used to generate the response.
* @return mixed Response to replace the requested version with.
*/
function od_decompress_rest_request_body( $result, WP_REST_Server $server, WP_REST_Request $request ) {
if (
$request->get_route() === '/' . OD_REST_API_NAMESPACE . OD_URL_METRICS_ROUTE &&
'application/gzip' === $request->get_header( 'Content-Type' )
Copy link
Member

@westonruter westonruter Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'application/gzip' === $request->get_header( 'Content-Type' )
'application/gzip' === $request->get_header( 'Content-Type' ) &&
function_exists( 'gzdecode' )

) {
$compressed_body = $request->get_body();

/*
* The limit for data sent via navigator.sendBeacon() is 64 KiB. This limit is checked in detect.js so that the
* request will not even be attempted if the payload is too large. This server-side restriction is added as a
* safeguard against clients sending possibly malicious payloads much larger than 64 KiB which should never be
* getting sent.
*/
$max_size = 64 * 1024; // 64 KB
$content_length = strlen( $compressed_body );
if ( $content_length > $max_size ) {
return new WP_Error(
'rest_content_too_large',
sprintf(
/* translators: 1: the size of the payload, 2: the maximum allowed payload size */
__( 'Compressed JSON payload size is %1$s bytes which is larger than the maximum allowed size of %2$s bytes.', 'optimization-detective' ),
number_format_i18n( $content_length ),
number_format_i18n( $max_size )
),
array( 'status' => 413 )
);
}

try {
$decompressed_body = gzdecode( $compressed_body );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at this in PhpStorm and I noticed an error:

image
So then I added it to require in c59c3ed, but then I wondered what core does with this function. I found this:

https://github.com/WordPress/wordpress-develop/blob/d5a3a140ee0e459acedd3c182274a2b14adc5bdb/src/wp-includes/class-wp-http-encoding.php#L72-L78

Note that core checks if the function exists. I think what we'll have to do is do the same, and that od_get_detection_script() we'll need to add a new param which is exported to the client like gzdecodeAvailable which contains the value of function_exists( 'gzdecode' ). This can then be used to conditionally compress the URL Metric data before sending it to the REST API.

We should use the function in the same way it is being used in core, perhaps even with the error suppression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added logic for handling gzdecode() availability in af1d1dc

} catch ( Exception $e ) {
$decompressed_body = false;
}

if ( false === $decompressed_body ) {
return new WP_Error(
'rest_invalid_payload',
__( 'Unable to decompress the gzip payload.', 'optimization-detective' ),
array( 'status' => 400 )
);
}

// Update the request so later handlers see the decompressed JSON.
$request->set_body( $decompressed_body );
$request->set_header( 'Content-Type', 'application/json' );
}
return $result;
}
52 changes: 44 additions & 8 deletions plugins/optimization-detective/tests/storage/test-rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ static function ( array $properties ) use ( $property_name ): array {
* @covers ::od_register_endpoint
* @covers ::od_handle_rest_request
* @covers ::od_trigger_page_cache_invalidation
* @covers ::od_decompress_rest_request_body
* @covers OD_Strict_URL_Metric::set_additional_properties_to_false
* @covers OD_URL_Metric_Store_Request_Context::__construct
* @covers OD_URL_Metric_Store_Request_Context::__get
Expand Down Expand Up @@ -349,16 +350,14 @@ public function data_provider_invalid_params(): array {
'params' => array_merge(
$valid_params,
array(
// Repeat the elements until the JSON will surpass 64 KiB.
'elements' => array_fill(
0,
200,
// Fill the JSON with more than 64KB of data.
'elements' => array(
array_merge(
$valid_element,
array(
'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::DIV]',
'xpath' => sprintf( '/HTML/BODY/DIV[@id=\'%s\']/*[1][self::DIV]', bin2hex( random_bytes( 65000 ) ) ),
)
)
),
),
)
),
Expand Down Expand Up @@ -458,6 +457,7 @@ public function data_provider_invalid_params(): array {
*
* @covers ::od_register_endpoint
* @covers ::od_handle_rest_request
* @covers ::od_decompress_rest_request_body
* @covers OD_Strict_URL_Metric::set_additional_properties_to_false
*
* @dataProvider data_provider_invalid_params
Expand Down Expand Up @@ -590,6 +590,23 @@ public function test_rest_request_non_array_json_body(): void {
$this->assertSame( 0, did_action( 'od_url_metric_stored' ) );
}


/**
* Test invalid compressed JSON body.
*
* @covers ::od_register_endpoint
* @covers ::od_handle_rest_request
* @covers ::od_decompress_rest_request_body
*/
public function test_rest_request_invalid_compressed_json_body(): void {
$request = $this->create_request( $this->get_valid_params() );
$request->set_body( 'Invalid compressed JSON body' );
$response = rest_get_server()->dispatch( $request );
$this->assertSame( 400, $response->get_status(), 'Response: ' . wp_json_encode( $response ) );
$this->assertSame( 'rest_invalid_payload', $response->get_data()['code'], 'Response: ' . wp_json_encode( $response ) );
$this->assertSame( 0, did_action( 'od_url_metric_stored' ) );
}

/**
* Test timestamp ignored.
*
Expand Down Expand Up @@ -856,6 +873,25 @@ public function test_od_trigger_page_cache_invalidation_invalid_post_id(): void
$this->assertSame( $before_save_post_count, did_action( 'save_post' ) );
}

/**
* Test that the request is modified by od_decompress_rest_request_body().
*
* @covers ::od_register_endpoint
* @covers ::od_handle_rest_request
* @covers ::od_decompress_rest_request_body
*/
public function test_od_decompress_rest_request_body_modifies_request(): void {
$params = $this->get_valid_params();
$request = $this->create_request( $this->get_valid_params() );
unset( $params['hmac'], $params['slug'], $params['current_etag'], $params['cache_purge_post_id'] );
$json_data = wp_json_encode( $params );
$result = od_decompress_rest_request_body( null, rest_get_server(), $request );

$this->assertNotWPError( $result );
$this->assertEquals( $json_data, $request->get_body() );
$this->assertEquals( 'application/json', $request->get_header( 'Content-Type' ) );
}

/**
* Populate URL Metrics.
*
Expand Down Expand Up @@ -938,11 +974,11 @@ private function create_request( array $params ): WP_REST_Request {
* @var WP_REST_Request<array<string, mixed>> $request
*/
$request = new WP_REST_Request( 'POST', self::ROUTE );
$request->set_header( 'Content-Type', 'application/json' );
$request->set_header( 'Content-Type', 'application/gzip' );
$request->set_query_params( wp_array_slice_assoc( $params, array( 'hmac', 'current_etag', 'slug', 'cache_purge_post_id' ) ) );
$request->set_header( 'Origin', home_url() );
unset( $params['hmac'], $params['slug'], $params['current_etag'], $params['cache_purge_post_id'] );
$request->set_body( wp_json_encode( $params ) );
$request->set_body( gzencode( wp_json_encode( $params ) ) );
return $request;
}
}
1 change: 1 addition & 0 deletions plugins/optimization-detective/tests/test-hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ public function test_hooks_added(): void {
$this->assertEquals( 10, has_filter( 'site_status_tests', 'od_add_rest_api_availability_test' ) );
$this->assertEquals( 10, has_action( 'admin_init', 'od_maybe_run_rest_api_health_check' ) );
$this->assertEquals( 30, has_action( 'after_plugin_row_meta', 'od_render_rest_api_health_check_admin_notice_in_plugin_row' ) );
$this->assertEquals( 10, has_filter( 'rest_pre_dispatch', 'od_decompress_rest_request_body' ) );
}
}
Loading