-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Add speculative loading support #7860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
felixarntz
wants to merge
38
commits into
WordPress:trunk
from
felixarntz:add/62503-speculative-loading
Closed
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
6a19089
Port speculative loading implementation to core, using filter instead…
felixarntz 061819d
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz 7448b4c
Fix WPCS violation.
felixarntz f65e1a5
Fix more WPCS issues.
felixarntz 694c1b1
Add missing translator comment.
felixarntz f38167c
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz cc66024
Use null instead of false to disable speculative loading.
felixarntz 1485faf
Fix WPCS.
felixarntz a78f8f5
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz 535c6f8
Exclude URLs with query parameters when pretty permalinks are enabled…
felixarntz a8c27e8
Disable speculative loading by default for logged-in users.
felixarntz a0d4dda
Exclude not only wp-login.php but any wp- PHP files from the WordPres…
felixarntz 4bb68cb
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz 70b6204
Use coversDefaultClass in tests.
felixarntz 8a55d2f
Make data provider methods static.
felixarntz cae7faa
Merge branch 'add/62503-speculative-loading' of github.com:felixarntz…
felixarntz da5fb19
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz 04d41c7
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz b267ae5
Allow providing additional speculation rules by amending speculation …
felixarntz aa50b92
Add tests for WP_Speculation_Rules class and fix bugs found by tests.
felixarntz 8c12bd3
Add support for the eagerness value immediate.
felixarntz 6827503
Fix prerender exclusions so that they also exclude links that opt out…
felixarntz 4d54a08
Fix WPCS errors.
felixarntz 82a6ef6
Make `WP_Speculation_Rules` final.
felixarntz d4c382d
Remove unnecessary to_array() method.
felixarntz b85f0b8
Make comment more future-proof.
felixarntz 066a1f7
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz 5093344
Merge branch 'add/62503-speculative-loading' of github.com:felixarntz…
felixarntz b587733
Avoid using we in inline comments and use encouraged syntax for multi…
felixarntz a9c52f8
Disallow use of immediate eagerness for document-level rules.
felixarntz a704a2b
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz b08592a
For sites without pretty permalinks, exclude URLs using any kind of n…
felixarntz 0a798ff
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz 349f4c0
Remove unnecessary parameter from wp_get_speculation_rules() and inst…
felixarntz 029324e
Move speculative loading validation functions to become static method…
felixarntz f290c26
Use static callbacks in tests.
felixarntz 57fd8c1
Merge branch 'trunk' into add/62503-speculative-loading
felixarntz b330805
Merge branch 'add/62503-speculative-loading' of github.com:felixarntz…
felixarntz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,293 @@ | ||
| <?php | ||
| /** | ||
| * Class 'WP_Speculation_Rules'. | ||
| * | ||
| * @package WordPress | ||
| * @subpackage Speculative Loading | ||
| * @since 6.8.0 | ||
| */ | ||
|
|
||
| /** | ||
| * Class representing a set of speculation rules. | ||
| * | ||
| * @since 6.8.0 | ||
| * @access private | ||
| */ | ||
| final class WP_Speculation_Rules implements JsonSerializable { | ||
|
|
||
| /** | ||
| * Stored rules, as a map of `$mode => $rules` pairs. | ||
| * | ||
| * Every `$rules` value is a map of `$id => $rule` pairs. | ||
| * | ||
| * @since 6.8.0 | ||
| * @var array<string, array<string, mixed>> | ||
| */ | ||
| private $rules_by_mode = array(); | ||
|
|
||
| /** | ||
| * The allowed speculation rules modes as a map, used for validation. | ||
| * | ||
| * @since 6.8.0 | ||
| * @var array<string, bool> | ||
| */ | ||
| private static $mode_allowlist = array( | ||
| 'prefetch' => true, | ||
| 'prerender' => true, | ||
| ); | ||
|
|
||
| /** | ||
| * The allowed speculation rules eagerness levels as a map, used for validation. | ||
| * | ||
| * @since 6.8.0 | ||
| * @var array<string, bool> | ||
| */ | ||
| private static $eagerness_allowlist = array( | ||
| 'immediate' => true, | ||
| 'eager' => true, | ||
| 'moderate' => true, | ||
| 'conservative' => true, | ||
| ); | ||
|
|
||
| /** | ||
| * The allowed speculation rules sources as a map, used for validation. | ||
| * | ||
| * @since 6.8.0 | ||
| * @var array<string, bool> | ||
| */ | ||
| private static $source_allowlist = array( | ||
| 'list' => true, | ||
| 'document' => true, | ||
| ); | ||
|
|
||
| /** | ||
| * Adds a speculation rule to the speculation rules to consider. | ||
| * | ||
| * @since 6.8.0 | ||
| * | ||
| * @param string $mode Speculative loading mode. Either 'prefetch' or 'prerender'. | ||
| * @param string $id Unique string identifier for the speculation rule. | ||
| * @param array<string, mixed> $rule Associative array of rule arguments. | ||
| * @return bool True on success, false if invalid parameters are provided. | ||
| */ | ||
| public function add_rule( string $mode, string $id, array $rule ): bool { | ||
| if ( ! self::is_valid_mode( $mode ) ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: %s: invalid mode value */ | ||
| __( 'The value "%s" is not a valid speculation rules mode.' ), | ||
| esc_html( $mode ) | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| if ( ! $this->is_valid_id( $id ) ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: %s: invalid ID value */ | ||
| __( 'The value "%s" is not a valid ID for a speculation rule.' ), | ||
| esc_html( $id ) | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| if ( $this->has_rule( $mode, $id ) ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: %s: invalid ID value */ | ||
| __( 'A speculation rule with ID "%s" already exists.' ), | ||
| esc_html( $id ) | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| /* | ||
| * Perform some basic speculation rule validation. | ||
| * Every rule must have either a 'where' key or a 'urls' key, but not both. | ||
| * The presence of a 'where' key implies a 'source' of 'document', while the presence of a 'urls' key implies | ||
| * a 'source' of 'list'. | ||
| */ | ||
| if ( | ||
| ( ! isset( $rule['where'] ) && ! isset( $rule['urls'] ) ) || | ||
| ( isset( $rule['where'] ) && isset( $rule['urls'] ) ) | ||
| ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: 1: allowed key, 2: alternative allowed key */ | ||
| __( 'A speculation rule must include either a "%1$s" key or a "%2$s" key, but not both.' ), | ||
| 'where', | ||
| 'urls' | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
| if ( isset( $rule['source'] ) ) { | ||
| if ( ! self::is_valid_source( $rule['source'] ) ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: %s: invalid source value */ | ||
| __( 'The value "%s" is not a valid source for a speculation rule.' ), | ||
| esc_html( $rule['source'] ) | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| if ( 'list' === $rule['source'] && isset( $rule['where'] ) ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: 1: source value, 2: forbidden key */ | ||
| __( 'A speculation rule of source "%1$s" must not include a "%2$s" key.' ), | ||
| 'list', | ||
| 'where' | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| if ( 'document' === $rule['source'] && isset( $rule['urls'] ) ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: 1: source value, 2: forbidden key */ | ||
| __( 'A speculation rule of source "%1$s" must not include a "%2$s" key.' ), | ||
| 'document', | ||
| 'urls' | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // If there is an 'eagerness' key specified, make sure it's valid. | ||
| if ( isset( $rule['eagerness'] ) ) { | ||
| if ( ! self::is_valid_eagerness( $rule['eagerness'] ) ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: %s: invalid eagerness value */ | ||
| __( 'The value "%s" is not a valid eagerness for a speculation rule.' ), | ||
| esc_html( $rule['eagerness'] ) | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
|
|
||
| if ( isset( $rule['where'] ) && 'immediate' === $rule['eagerness'] ) { | ||
| _doing_it_wrong( | ||
| __METHOD__, | ||
| sprintf( | ||
| /* translators: %s: forbidden eagerness value */ | ||
| __( 'The eagerness value "%s" is forbidden for document-level speculation rules.' ), | ||
| 'immediate' | ||
| ), | ||
| '6.8.0' | ||
| ); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| if ( ! isset( $this->rules_by_mode[ $mode ] ) ) { | ||
| $this->rules_by_mode[ $mode ] = array(); | ||
| } | ||
|
|
||
| $this->rules_by_mode[ $mode ][ $id ] = $rule; | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether a speculation rule for the given mode and ID already exists. | ||
| * | ||
| * @since 6.8.0 | ||
| * | ||
| * @param string $mode Speculative loading mode. Either 'prefetch' or 'prerender'. | ||
| * @param string $id Unique string identifier for the speculation rule. | ||
| * @return bool True if the rule already exists, false otherwise. | ||
| */ | ||
| public function has_rule( string $mode, string $id ): bool { | ||
| return isset( $this->rules_by_mode[ $mode ][ $id ] ); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the speculation rules data ready to be JSON-encoded. | ||
| * | ||
| * @since 6.8.0 | ||
| * | ||
| * @return array<string, array<string, mixed>> Speculation rules data. | ||
| */ | ||
| #[ReturnTypeWillChange] | ||
| public function jsonSerialize() { | ||
| // Strip the IDs for JSON output, since they are not relevant for the Speculation Rules API. | ||
| return array_map( | ||
| static function ( array $rules ) { | ||
| return array_values( $rules ); | ||
| }, | ||
| array_filter( $this->rules_by_mode ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether the given ID is valid. | ||
| * | ||
| * @since 6.8.0 | ||
| * | ||
| * @param string $id Unique string identifier for the speculation rule. | ||
| * @return bool True if the ID is valid, false otherwise. | ||
| */ | ||
| private function is_valid_id( string $id ): bool { | ||
| return (bool) preg_match( '/^[a-z][a-z0-9_-]+$/', $id ); | ||
| } | ||
felixarntz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Checks whether the given speculation rules mode is valid. | ||
| * | ||
| * @since 6.8.0 | ||
| * | ||
| * @param string $mode Speculation rules mode. | ||
| * @return bool True if valid, false otherwise. | ||
| */ | ||
| public static function is_valid_mode( string $mode ): bool { | ||
| return isset( self::$mode_allowlist[ $mode ] ); | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether the given speculation rules eagerness is valid. | ||
| * | ||
| * @since 6.8.0 | ||
| * | ||
| * @param string $eagerness Speculation rules eagerness. | ||
| * @return bool True if valid, false otherwise. | ||
| */ | ||
| public static function is_valid_eagerness( string $eagerness ): bool { | ||
| return isset( self::$eagerness_allowlist[ $eagerness ] ); | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether the given speculation rules source is valid. | ||
| * | ||
| * @since 6.8.0 | ||
| * | ||
| * @param string $source Speculation rules source. | ||
| * @return bool True if valid, false otherwise. | ||
| */ | ||
| public static function is_valid_source( string $source ): bool { | ||
| return isset( self::$source_allowlist[ $source ] ); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.