Skip to content

Provide mechanism for host to change default Speculative Loading eagerness and mode#12514

Open
westonruter wants to merge 6 commits into
WordPress:trunkfrom
westonruter:trac-65624
Open

Provide mechanism for host to change default Speculative Loading eagerness and mode#12514
westonruter wants to merge 6 commits into
WordPress:trunkfrom
westonruter:trac-65624

Conversation

@westonruter

Copy link
Copy Markdown
Member

Provides a mechanism for hosting providers to change the default speculative loading mode and eagerness that the auto value resolves to, without having to ship an mu-plugin.

This is the first step proposed in comment:26 of Core-64066: give hosts an opt-in so they can enable a moderate default and report back on the resulting TTFB/LCP improvements and the increase in server load from wasted speculations. If that data is favorable, a later change can consider using the page cache and persistent object cache Site Health results as the heuristic for defaulting to moderate, with these overrides then serving as the opt-out.

Approach

Two overrides are introduced, each settable as either an environment variable or a constant:

  • WP_SPECULATIVE_LOADING_DEFAULT_MODEprefetch (default) or prerender.
  • WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESSconservative (default), moderate, or eager.

Resolution follows wp_get_environment_type(): the environment variable is read with getenv(), and a constant of the same name takes precedence over it. Supporting the constant means a host can set this from wp-config.php, which is how hosts already inject WP_CACHE and friends. An invalid or unrecognized value falls back to the Core default.

These only change what auto resolves to. A plugin that supplies an explicit mode or eagerness through the wp_speculation_rules_configuration filter still takes precedence over a host's default, so the Speculative Loading plugin continues to win.

An eagerness of immediate is deliberately not accepted, since WordPress does not permit it for the document-level rules it generates — WP_Speculation_Rules::add_rule() rejects such a rule, which would leave the site with no speculation rules at all.

Also widens WP_Speculation_Rules::is_valid_mode() and ::is_valid_eagerness() to accept mixed rather than string. Both are validation methods, so accepting an arbitrary value and returning false is the more useful contract; it also hardens the filter path, where a plugin returning e.g. an array for mode would previously have caused a TypeError.

Testing instructions

  1. Confirm the default output is unchanged: view the source of a front-end page as a logged-out user with pretty permalinks enabled, and observe the speculation rules script with "eagerness": "conservative" and a prefetch rule.
  2. Set the environment variable for your web server (or add define( 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS', 'moderate' ); to wp-config.php).
  3. Reload and observe the rules now use "eagerness": "moderate".
  4. Set the value to something invalid (e.g. bogus, or immediate) and confirm the rules fall back to conservative and that speculation rules are still output.
  5. With the override still set to moderate, add a filter returning an explicit 'eagerness' => 'conservative' and confirm the filter wins.

Unit tests cover the environment variable and constant handling, the precedence of the constant over the environment variable, invalid/empty values, the rejection of immediate, and that an explicit filter value still beats a host default:

npm run test:php -- --group=speculative-loading

Trac ticket: https://core.trac.wordpress.org/ticket/65624

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.8
Used for: Reviewing the initial implementation, which surfaced two defects that I have since fixed — the overrides were originally read from $_ENV, which is not populated under the variables_order value that PHP's shipped php.ini files use, so they were silently ignored on a stock install; and immediate was accepted as a default eagerness, which caused the main document-level rule to be rejected and left the page with no speculation rules. Also used to draft the accompanying unit tests. I have reviewed and take responsibility for all of the above.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

westonruter and others added 6 commits July 14, 2026 00:08
This fixes the last PHPStan level 10 error in the file:

> Parameter #1 $callback of function array_map expects (callable(mixed): mixed)|null, Closure(string): string given.
…erness

Fix two problems with the speculative loading default overrides:

- `$_ENV` is only populated when `variables_order` includes `E`, which is
  not the case for the `php.ini-production` and `php.ini-development` files
  shipped with PHP. The overrides were therefore silently ignored on a stock
  install even when the environment variable was in fact set. Read them with
  `getenv()` instead, and let a constant of the same name take precedence,
  consistent with how `wp_get_environment_type()` resolves
  `WP_ENVIRONMENT_TYPE`. This also gives hosts the `wp-config.php` route.

- `immediate` passed `is_valid_eagerness()` and so could be supplied as the
  default eagerness, bypassing the guard that keeps it out of the
  document-level rules that WordPress generates.
  `WP_Speculation_Rules::add_rule()` then rejected the main rule, leaving the
  site with no speculation rules at all.

Move the default resolution into `wp_get_speculation_rules_default_configuration()`
so that the overrides only change what `auto` resolves to. An explicit mode or
eagerness supplied via the `wp_speculation_rules_configuration` filter continues
to take precedence over a host default.

Add unit tests covering both, including the constant/environment variable
precedence and the rejection of `immediate`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@westonruter

Copy link
Copy Markdown
Member Author

cc @mukeshpanchal27 @b1ink0

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds host-level overrides for what speculative loading configuration auto resolves to (mode/eagerness), via environment variables or constants, and hardens validation against non-string filter input. This supports opt-in host experimentation with different defaults without requiring an mu-plugin.

Changes:

  • Introduces wp_get_speculation_rules_default_configuration() and wp_get_speculative_loading_override() to resolve Core defaults with optional host overrides.
  • Expands WP_Speculation_Rules::is_valid_mode() / ::is_valid_eagerness() to accept mixed and safely return false for invalid types.
  • Adds PHPUnit coverage for override behavior and precedence, and updates existing tests to account for overridden defaults.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/wp-includes/speculative-loading.php Adds default-resolution and override helpers; uses resolved defaults when sanitizing configuration; filters non-string exclude paths.
src/wp-includes/class-wp-speculation-rules.php Makes validation methods accept mixed to avoid TypeError from invalid filter returns.
tests/phpunit/tests/speculative-loading/wpGetSpeculationRulesDefaultConfiguration.php New unit tests for env/constant override resolution and precedence.
tests/phpunit/tests/speculative-loading/wpGetSpeculationRulesConfiguration.php Adds tests ensuring auto resolves to overridden defaults and filter precedence remains intact.
tests/phpunit/tests/speculative-loading/wpGetSpeculationRules.php Adds coverage ensuring overridden defaults affect rules output and immediate cannot be forced as default.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +169 to +175
// Fetch the value from a constant, which overrides the environment variable.
if ( defined( $name ) ) {
$has_constant = constant( $name );
if ( is_string( $has_constant ) ) {
$value = $has_constant;
}
}

Copy link
Copy Markdown
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 return null empty here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@mukeshpanchal27 I don't understand. How?

Comment on lines +13 to +20
class Tests_Speculative_Loading_wpGetSpeculationRulesDefaultConfiguration extends WP_UnitTestCase {

public function tear_down(): void {
putenv( 'WP_SPECULATIVE_LOADING_DEFAULT_MODE' );
putenv( 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS' );

parent::tear_down();
}
Comment on lines +21 to +25
public function tear_down(): void {
putenv( 'WP_SPECULATIVE_LOADING_DEFAULT_MODE' );
putenv( 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS' );

parent::tear_down();
Comment on lines +44 to +48
public function tear_down(): void {
putenv( 'WP_SPECULATIVE_LOADING_DEFAULT_MODE' );
putenv( 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS' );

parent::tear_down();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There wouldn't be any present. This seems overkill.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

But if disablePingsForEnvironment.php is doing it, I guess it wouldn't hurt.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants