Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 47 additions & 6 deletions src/wp-admin/includes/class-wp-site-health.php
Original file line number Diff line number Diff line change
Expand Up @@ -2447,6 +2447,7 @@ public function get_test_page_cache() {
$description = '<p>' . __( 'Page cache enhances the speed and performance of your site by saving and serving static pages instead of calling for a page every time a user visits.' ) . '</p>';
$description .= '<p>' . __( 'Page cache is detected by looking for an active page cache plugin as well as making three requests to the homepage and looking for one or more of the following HTTP client caching response headers:' ) . '</p>';
$description .= '<code>' . implode( '</code>, <code>', array_keys( $this->get_page_cache_headers() ) ) . '.</code>';
$description .= '<p>' . $this->get_speculative_loading_cache_description() . '</p>';

$result = array(
'badge' => array(
Expand Down Expand Up @@ -2580,8 +2581,9 @@ public function get_test_persistent_object_cache() {
),
'label' => __( 'A persistent object cache is being used' ),
'description' => sprintf(
'<p>%s</p>',
__( 'A persistent object cache makes your site&#8217;s database more efficient, resulting in faster load times because WordPress can retrieve your site&#8217;s content and settings much more quickly.' )
'<p>%s</p><p>%s</p>',
__( 'A persistent object cache makes your site&#8217;s database more efficient, resulting in faster load times because WordPress can retrieve your site&#8217;s content and settings much more quickly.' ),
$this->get_speculative_loading_cache_description()
),
'actions' => sprintf(
'<p><a href="%s" target="_blank">%s<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>',
Expand Down Expand Up @@ -2647,6 +2649,32 @@ public function get_test_persistent_object_cache() {
return $result;
}

/**
* Gets the description of speculative loading used in the page cache and persistent object cache tests.
*
* @since 6.9.0
*
* @see self::get_test_persistent_object_cache()
* @see self::get_test_page_cache()
* @see wp_get_speculation_rules_configuration()
*
* @return string Description.
*/
private function get_speculative_loading_cache_description() {
return sprintf(
/* translators: 1: Link to the Speculative Loading dev note. 2: Additional link attributes. 3: Accessibility text. */
__( 'When a page cache is detected and a persistent object cache is enabled, <a href="%1$s" %2$s>Speculative Loading%3$s</a> will accelerate cross-site navigations by using a default <code>eagerness</code> of <code>moderate</code> instead of <code>conservative</code>.' ),
/* translators: Localized Speculative Loading dev note, if one exists. */
esc_url( __( 'https://make.wordpress.org/core/2025/03/06/speculative-loading-in-6-8/' ) ),
'target="_blank"',
sprintf(
'<span class="screen-reader-text"> %s</span><span aria-hidden="true" class="dashicons dashicons-external"></span>',
/* translators: Hidden accessibility text. */
__( '(opens in a new tab)' )
)
);
}

/**
* Calculates total amount of autoloaded data.
*
Expand Down Expand Up @@ -3603,9 +3631,10 @@ public function get_page_cache_headers(): array {
* @return WP_Error|array {
* Page cache detection details or else error information.
*
* @type bool $advanced_cache_present Whether a page cache plugin is present.
* @type array[] $page_caching_response_headers Sets of client caching headers for the responses.
* @type float[] $response_timing Response timings.
* @type bool $advanced_cache_present Whether a page cache plugin is present.
* @type array[] $page_caching_response_headers Sets of client caching headers for the responses.
* @type float[] $response_timing Response timings.
* @type string[] $caching_response_headers List of response headers detected which indicate page caching.
* }
*/
private function check_for_page_caching() {
Expand All @@ -3628,6 +3657,7 @@ private function check_for_page_caching() {
$page_caching_response_headers = array();
$response_timing = array();

$all_seen_headers = array();
for ( $i = 1; $i <= 3; $i++ ) {
$start_time = microtime( true );
$http_response = wp_remote_get( home_url( '/' ), compact( 'sslverify', 'headers' ) );
Expand All @@ -3653,14 +3683,15 @@ private function check_for_page_caching() {
$header_values = (array) $header_values;
if ( empty( $callback ) || ( is_callable( $callback ) && count( array_filter( $header_values, $callback ) ) > 0 ) ) {
$response_headers[ $header ] = $header_values;
$all_seen_headers[] = $header;
}
}

$page_caching_response_headers[] = $response_headers;
$response_timing[] = ( $end_time - $start_time ) * 1000;
}

return array(
$result = array(
'advanced_cache_present' => (
file_exists( WP_CONTENT_DIR . '/advanced-cache.php' )
&&
Expand All @@ -3671,7 +3702,17 @@ private function check_for_page_caching() {
),
'page_caching_response_headers' => $page_caching_response_headers,
'response_timing' => $response_timing,
'caching_response_headers' => array_unique( $all_seen_headers ),
);

/*
* Store the results in a non-expiring (autoloaded) transient so that Speculative Loading can use this to change
* the default mode from conservative to moderate when page caching is enabled.
* See wp_get_speculation_rules_configuration().
*/
set_transient( 'health_check_page_cache_detail', $result );

return $result;
}

/**
Expand Down
22 changes: 22 additions & 0 deletions src/wp-includes/speculative-loading.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ function wp_get_speculation_rules_configuration(): ?array {
// Sanitize the configuration and replace 'auto' with current defaults.
$default_mode = 'prefetch';
$default_eagerness = 'conservative';

// Default to moderate eagerness on production when page caching is detected and a persistent object cache is used.
if ( 'production' === wp_get_environment_type() && wp_using_ext_object_cache() ) {
$page_cache_detail = get_transient( 'health_check_page_cache_detail' );
if (
is_array( $page_cache_detail ) &&
(
(
isset( $page_cache_detail['advanced_cache_present'] ) &&
$page_cache_detail['advanced_cache_present']
) ||
(
isset( $page_cache_detail['caching_response_headers'] ) &&
is_array( $page_cache_detail['caching_response_headers'] ) &&
count( $page_cache_detail['caching_response_headers'] ) > 0
)
)
) {
$default_eagerness = 'moderate';
}
}

if ( ! is_array( $config ) ) {
return array(
'mode' => $default_mode,
Expand Down
8 changes: 8 additions & 0 deletions tests/phpunit/includes/abstract-testcase.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ public function set_up() {
if ( $wp_rewrite->permalink_structure ) {
$this->set_permalink_structure( '' );
}

/*
* Site Health stores page cache probe results in this transient. When a persistent object cache is used
* (common in CI), it would otherwise change the default resolved eagerness for unrelated tests.
*
* @ticket 64066
*/
delete_transient( 'health_check_page_cache_detail' );
}

$this->start_transaction();
Expand Down
76 changes: 76 additions & 0 deletions tests/phpunit/tests/admin/wpSiteHealth.php
Original file line number Diff line number Diff line change
Expand Up @@ -707,4 +707,80 @@ public function test_get_test_opcode_cache_result_by_environment() {
$this->assertStringContainsString( __( 'Enabling this cache can significantly improve the performance of your site.' ), $result['description'] );
}
}

/**
* @ticket 64066
*
* @covers ::check_for_page_caching
*/
public function test_check_for_page_caching_stores_health_check_page_cache_detail_transient() {
delete_transient( 'health_check_page_cache_detail' );

$pre_http_response = function () {
return array(
'headers' => array( 'age' => '100' ),
'response' => array(
'code' => 200,
'message' => 'OK',
),
);
};
add_filter( 'pre_http_request', $pre_http_response, 10 );

$this->instance->get_test_page_cache();

$detail = get_transient( 'health_check_page_cache_detail' );

remove_filter( 'pre_http_request', $pre_http_response, 10 );

$this->assertIsArray( $detail );
$this->assertArrayHasKey( 'caching_response_headers', $detail );
$this->assertContains( 'age', $detail['caching_response_headers'] );
}

/**
* @ticket 64066
*
* @covers ::get_test_page_cache
*/
public function test_get_test_page_cache_description_includes_speculative_loading_note() {
delete_transient( 'health_check_page_cache_detail' );

$pre_http_response = function () {
return array(
'headers' => array( 'age' => '100' ),
'response' => array(
'code' => 200,
'message' => 'OK',
),
);
};
add_filter( 'pre_http_request', $pre_http_response, 10 );

$result = $this->instance->get_test_page_cache();

remove_filter( 'pre_http_request', $pre_http_response, 10 );

$this->assertStringContainsString( 'Speculative Loading', $result['description'] );
$this->assertStringContainsString( 'moderate', $result['description'] );
$this->assertStringContainsString( 'conservative', $result['description'] );
}

/**
* @ticket 64066
*
* @covers ::get_test_persistent_object_cache
*/
public function test_get_test_persistent_object_cache_includes_speculative_loading_note_when_ext_object_cache_enabled() {
$initial = wp_using_ext_object_cache();
wp_using_ext_object_cache( true );

$result = $this->instance->get_test_persistent_object_cache();

wp_using_ext_object_cache( $initial );

$this->assertStringContainsString( 'Speculative Loading', $result['description'] );
$this->assertStringContainsString( 'moderate', $result['description'] );
$this->assertStringContainsString( 'conservative', $result['description'] );
}
}
Loading
Loading