[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> speculative-loading.php (source)

   1  <?php
   2  /**
   3   * Speculative loading functions.
   4   *
   5   * @package WordPress
   6   * @subpackage Speculative Loading
   7   * @since 6.8.0
   8   */
   9  
  10  /**
  11   * Returns the speculation rules configuration.
  12   *
  13   * @since 6.8.0
  14   * @since 7.1.0 The `WP_SPECULATIVE_LOADING_DEFAULT_MODE` and `WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS` constants and
  15   *              environment variables can now specify the default mode and eagerness, respectively.
  16   *
  17   * @see wp_get_speculation_rules_default_configuration()
  18   *
  19   * @return array<string, string>|null Associative array with 'mode' and 'eagerness' keys, or null if speculative
  20   *                                    loading is disabled.
  21   * @phpstan-return array{
  22   *     mode: 'prefetch'|'prerender',
  23   *     eagerness: 'conservative'|'moderate'|'eager',
  24   * }|null
  25   */
  26  function wp_get_speculation_rules_configuration(): ?array {
  27      // By default, speculative loading is only enabled for sites with pretty permalinks when no user is logged in.
  28      if ( ! is_user_logged_in() && get_option( 'permalink_structure' ) ) {
  29          $config = array(
  30              'mode'      => 'auto',
  31              'eagerness' => 'auto',
  32          );
  33      } else {
  34          $config = null;
  35      }
  36  
  37      /**
  38       * Filters the way that speculation rules are configured.
  39       *
  40       * The Speculation Rules API is a web API that allows to automatically prefetch or prerender certain URLs on the
  41       * page, which can lead to near-instant page load times. This is also referred to as speculative loading.
  42       *
  43       * There are two aspects to the configuration:
  44       * * The "mode" (whether to "prefetch" or "prerender" URLs).
  45       * * The "eagerness" (whether to speculatively load URLs in an "eager", "moderate", or "conservative" way).
  46       *
  47       * By default, the speculation rules configuration is decided by WordPress Core ("auto"). This filter can be used
  48       * to force a certain configuration, which could for instance load URLs more or less eagerly.
  49       *
  50       * For logged-in users or for sites that are not configured to use pretty permalinks, the default value is `null`,
  51       * indicating that speculative loading is entirely disabled.
  52       *
  53       * @since 6.8.0
  54       * @see https://developer.chrome.com/docs/web-platform/prerender-pages
  55       *
  56       * @param array<string, string>|null $config Associative array with 'mode' and 'eagerness' keys, or `null`. The
  57       *                                           default value for both of the keys is 'auto'. Other possible values
  58       *                                           for 'mode' are 'prefetch' and 'prerender'. Other possible values for
  59       *                                           'eagerness' are 'eager', 'moderate', and 'conservative'. The value
  60       *                                           `null` is used to disable speculative loading entirely.
  61       */
  62      $config = apply_filters( 'wp_speculation_rules_configuration', $config );
  63  
  64      // Allow the value `null` to indicate that speculative loading is disabled.
  65      if ( null === $config ) {
  66          return null;
  67      }
  68  
  69      // Sanitize the configuration and replace 'auto' with current defaults.
  70      $defaults          = wp_get_speculation_rules_default_configuration();
  71      $default_mode      = $defaults['mode'];
  72      $default_eagerness = $defaults['eagerness'];
  73  
  74      if ( ! is_array( $config ) ) {
  75          return array(
  76              'mode'      => $default_mode,
  77              'eagerness' => $default_eagerness,
  78          );
  79      }
  80      if (
  81          ! isset( $config['mode'] ) ||
  82          'auto' === $config['mode'] ||
  83          ! WP_Speculation_Rules::is_valid_mode( $config['mode'] )
  84      ) {
  85          $config['mode'] = $default_mode;
  86      }
  87      if (
  88          ! isset( $config['eagerness'] ) ||
  89          'auto' === $config['eagerness'] ||
  90          ! WP_Speculation_Rules::is_valid_eagerness( $config['eagerness'] ) ||
  91          // 'immediate' is a valid eagerness, but for safety WordPress does not allow it for document-level rules.
  92          'immediate' === $config['eagerness']
  93      ) {
  94          $config['eagerness'] = $default_eagerness;
  95      }
  96  
  97      return array(
  98          'mode'      => $config['mode'],
  99          'eagerness' => $config['eagerness'],
 100      );
 101  }
 102  
 103  /**
 104   * Returns the default speculation rules configuration that the value 'auto' resolves to.
 105   *
 106   * WordPress Core defaults to a mode of 'prefetch' and an eagerness of 'conservative'. Hosting providers can override
 107   * either default by way of the `WP_SPECULATIVE_LOADING_DEFAULT_MODE` and `WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS`
 108   * constants or environment variables. This only changes what the 'auto' value resolves to, so a plugin which supplies
 109   * an explicit mode or eagerness via the {@see 'wp_speculation_rules_configuration'} filter continues to take
 110   * precedence.
 111   *
 112   * Note that an eagerness of 'immediate' is not permitted as a default, since WordPress does not allow it for the
 113   * document-level rules that it generates.
 114   *
 115   * @since 7.1.0
 116   * @access private
 117   *
 118   * @return array<string, string> Associative array with 'mode' and 'eagerness' keys.
 119   * @phpstan-return array{
 120   *     mode: 'prefetch'|'prerender',
 121   *     eagerness: 'conservative'|'moderate'|'eager',
 122   * }
 123   */
 124  function wp_get_speculation_rules_default_configuration(): array {
 125      $default_mode = 'prefetch';
 126      $mode         = wp_get_speculative_loading_override( 'WP_SPECULATIVE_LOADING_DEFAULT_MODE' );
 127      if ( WP_Speculation_Rules::is_valid_mode( $mode ) ) {
 128          $default_mode = $mode;
 129      }
 130  
 131      $default_eagerness = 'conservative';
 132      $eagerness         = wp_get_speculative_loading_override( 'WP_SPECULATIVE_LOADING_DEFAULT_EAGERNESS' );
 133      if (
 134          WP_Speculation_Rules::is_valid_eagerness( $eagerness ) &&
 135          // 'immediate' is a valid eagerness, but for safety WordPress does not allow it for document-level rules.
 136          'immediate' !== $eagerness
 137      ) {
 138          $default_eagerness = $eagerness;
 139      }
 140  
 141      return array(
 142          'mode'      => $default_mode,
 143          'eagerness' => $default_eagerness,
 144      );
 145  }
 146  
 147  /**
 148   * Returns the value of a speculative loading override, as supplied by a constant or an environment variable.
 149   *
 150   * The constant takes precedence over the environment variable, consistent with {@see wp_get_environment_type()}.
 151   *
 152   * @since 7.1.0
 153   * @access private
 154   *
 155   * @param string $name Name of the constant and environment variable to look up.
 156   * @return string|null The override value, or null if neither is set.
 157   */
 158  function wp_get_speculative_loading_override( string $name ): ?string {
 159      $value = null;
 160  
 161      // Check if the environment variable has been set, if `getenv` is available on the system.
 162      if ( function_exists( 'getenv' ) ) {
 163          $has_env = getenv( $name );
 164          if ( false !== $has_env ) {
 165              $value = $has_env;
 166          }
 167      }
 168  
 169      // Fetch the value from a constant, which overrides the environment variable.
 170      if ( defined( $name ) ) {
 171          $has_constant = constant( $name );
 172          if ( is_string( $has_constant ) ) {
 173              $value = $has_constant;
 174          }
 175      }
 176  
 177      return $value;
 178  }
 179  
 180  /**
 181   * Returns the full speculation rules data based on the configuration.
 182   *
 183   * Plugins with features that rely on frontend URLs to exclude from prefetching or prerendering should use the
 184   * {@see 'wp_speculation_rules_href_exclude_paths'} filter to ensure those URL patterns are excluded.
 185   *
 186   * Additional speculation rules other than the default rule from WordPress Core can be provided by using the
 187   * {@see 'wp_load_speculation_rules'} action and amending the passed WP_Speculation_Rules object.
 188   *
 189   * @since 6.8.0
 190   * @access private
 191   *
 192   * @return WP_Speculation_Rules|null Object representing the speculation rules to use, or null if speculative loading
 193   *                                   is disabled in the current context.
 194   */
 195  function wp_get_speculation_rules(): ?WP_Speculation_Rules {
 196      $configuration = wp_get_speculation_rules_configuration();
 197      if ( null === $configuration ) {
 198          return null;
 199      }
 200  
 201      $mode      = $configuration['mode'];
 202      $eagerness = $configuration['eagerness'];
 203  
 204      $prefixer = new WP_URL_Pattern_Prefixer();
 205  
 206      $base_href_exclude_paths = array(
 207          $prefixer->prefix_path_pattern( '/wp-*.php', 'site' ),
 208          $prefixer->prefix_path_pattern( '/wp-admin/*', 'site' ),
 209          $prefixer->prefix_path_pattern( '/*', 'uploads' ),
 210          $prefixer->prefix_path_pattern( '/*', 'content' ),
 211          $prefixer->prefix_path_pattern( '/*', 'plugins' ),
 212          $prefixer->prefix_path_pattern( '/*', 'template' ),
 213          $prefixer->prefix_path_pattern( '/*', 'stylesheet' ),
 214      );
 215  
 216      /*
 217       * If pretty permalinks are enabled, exclude any URLs with query parameters.
 218       * Otherwise, exclude specifically the URLs with a `_wpnonce` query parameter or any other query parameter
 219       * containing the word `nonce`.
 220       */
 221      if ( get_option( 'permalink_structure' ) ) {
 222          $base_href_exclude_paths[] = $prefixer->prefix_path_pattern( '/*\\?(.+)', 'home' );
 223      } else {
 224          $base_href_exclude_paths[] = $prefixer->prefix_path_pattern( '/*\\?*(^|&)*nonce*=*', 'home' );
 225      }
 226  
 227      /**
 228       * Filters the paths for which speculative loading should be disabled.
 229       *
 230       * All paths should start in a forward slash, relative to the root document. The `*` can be used as a wildcard.
 231       * If the WordPress site is in a subdirectory, the exclude paths will automatically be prefixed as necessary.
 232       *
 233       * Note that WordPress always excludes certain path patterns such as `/wp-login.php` and `/wp-admin/*`, and those
 234       * cannot be modified using the filter.
 235       *
 236       * @since 6.8.0
 237       *
 238       * @param string[] $href_exclude_paths Additional path patterns to disable speculative loading for.
 239       * @param string   $mode               Mode used to apply speculative loading. Either 'prefetch' or 'prerender'.
 240       */
 241      $href_exclude_paths = (array) apply_filters( 'wp_speculation_rules_href_exclude_paths', array(), $mode );
 242      $href_exclude_paths = array_filter( $href_exclude_paths, 'is_string' );
 243  
 244      // Ensure that:
 245      // 1. There are no duplicates.
 246      // 2. The base paths cannot be removed.
 247      // 3. The array has sequential keys (i.e. array_is_list()).
 248      $href_exclude_paths = array_values(
 249          array_unique(
 250              array_merge(
 251                  $base_href_exclude_paths,
 252                  array_map(
 253                      static function ( string $href_exclude_path ) use ( $prefixer ): string {
 254                          return $prefixer->prefix_path_pattern( $href_exclude_path );
 255                      },
 256                      $href_exclude_paths
 257                  )
 258              )
 259          )
 260      );
 261  
 262      $speculation_rules = new WP_Speculation_Rules();
 263  
 264      $main_rule_conditions = array(
 265          // Include any URLs within the same site.
 266          array(
 267              'href_matches' => $prefixer->prefix_path_pattern( '/*' ),
 268          ),
 269          // Except for excluded paths.
 270          array(
 271              'not' => array(
 272                  'href_matches' => $href_exclude_paths,
 273              ),
 274          ),
 275          // Also exclude rel=nofollow links, as certain plugins use that on their links that perform an action.
 276          array(
 277              'not' => array(
 278                  'selector_matches' => 'a[rel~="nofollow"]',
 279              ),
 280          ),
 281          // Also exclude links that are explicitly marked to opt out, either directly or via a parent element.
 282          array(
 283              'not' => array(
 284                  'selector_matches' => ".no-{$mode}, .no-{$mode} a",
 285              ),
 286          ),
 287      );
 288  
 289      // If using 'prerender', also exclude links that opt out of 'prefetch' because it's part of 'prerender'.
 290      if ( 'prerender' === $mode ) {
 291          $main_rule_conditions[] = array(
 292              'not' => array(
 293                  'selector_matches' => '.no-prefetch, .no-prefetch a',
 294              ),
 295          );
 296      }
 297  
 298      $speculation_rules->add_rule(
 299          $mode,
 300          'main',
 301          array(
 302              'source'    => 'document',
 303              'where'     => array(
 304                  'and' => $main_rule_conditions,
 305              ),
 306              'eagerness' => $eagerness,
 307          )
 308      );
 309  
 310      /**
 311       * Fires when speculation rules data is loaded, allowing to amend the rules.
 312       *
 313       * @since 6.8.0
 314       *
 315       * @param WP_Speculation_Rules $speculation_rules Object representing the speculation rules to use.
 316       */
 317      do_action( 'wp_load_speculation_rules', $speculation_rules );
 318  
 319      return $speculation_rules;
 320  }
 321  
 322  /**
 323   * Prints the speculation rules.
 324   *
 325   * For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored.
 326   *
 327   * @since 6.8.0
 328   * @access private
 329   */
 330  function wp_print_speculation_rules(): void {
 331      $speculation_rules = wp_get_speculation_rules();
 332      if ( null === $speculation_rules ) {
 333          return;
 334      }
 335  
 336      wp_print_inline_script_tag(
 337          (string) wp_json_encode(
 338              $speculation_rules,
 339              JSON_HEX_TAG | JSON_UNESCAPED_SLASHES
 340          ),
 341          array( 'type' => 'speculationrules' )
 342      );
 343  }


Generated : Wed Jul 29 08:20:18 2026 Cross-referenced by PHPXref