From 9b205628053d684a0a3ad7bc82b8d521f4975bb5 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Fri, 19 May 2023 14:29:13 -0700 Subject: [PATCH 01/14] Implement basic module to experiment with speculationrules for prerendering. --- .../js-and-css/speculation-rules/helper.php | 51 +++++++++++++++++++ .../js-and-css/speculation-rules/hooks.php | 44 ++++++++++++++++ modules/js-and-css/speculation-rules/load.php | 19 +++++++ 3 files changed, 114 insertions(+) create mode 100644 modules/js-and-css/speculation-rules/helper.php create mode 100644 modules/js-and-css/speculation-rules/hooks.php create mode 100644 modules/js-and-css/speculation-rules/load.php diff --git a/modules/js-and-css/speculation-rules/helper.php b/modules/js-and-css/speculation-rules/helper.php new file mode 100644 index 0000000000..505ea30312 --- /dev/null +++ b/modules/js-and-css/speculation-rules/helper.php @@ -0,0 +1,51 @@ + 'document', + 'where' => array( + 'and' => array( + // Prerender any URLs within the same site. + array( + 'href_matches' => '/*\\?*', + 'relative_to' => 'document', + ), + // Except for WP login and admin URLs. + array( + 'not' => array( + 'href_matches' => array( + '/wp-login.php\\?*#*', + '/wp-admin/*\\?*#*', + ), + ), + ), + // And except for any links marked with a class to not prerender. + array( + 'not' => array( + 'selector_matches' => '.no-prerender', + ), + ), + ), + ), + 'eagerness' => 'moderate', + ), + ); + + return array( 'prerender' => $prerender_rules ); +} diff --git a/modules/js-and-css/speculation-rules/hooks.php b/modules/js-and-css/speculation-rules/hooks.php new file mode 100644 index 0000000000..474cb7f5fb --- /dev/null +++ b/modules/js-and-css/speculation-rules/hooks.php @@ -0,0 +1,44 @@ + Date: Mon, 22 May 2023 13:51:01 -0700 Subject: [PATCH 02/14] Include proper backslashes in rules source. --- modules/js-and-css/speculation-rules/helper.php | 6 +++--- modules/js-and-css/speculation-rules/hooks.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/js-and-css/speculation-rules/helper.php b/modules/js-and-css/speculation-rules/helper.php index 505ea30312..3e59af4006 100644 --- a/modules/js-and-css/speculation-rules/helper.php +++ b/modules/js-and-css/speculation-rules/helper.php @@ -23,15 +23,15 @@ function plsr_get_speculation_rules() { 'and' => array( // Prerender any URLs within the same site. array( - 'href_matches' => '/*\\?*', + 'href_matches' => '/*\\\\?*', 'relative_to' => 'document', ), // Except for WP login and admin URLs. array( 'not' => array( 'href_matches' => array( - '/wp-login.php\\?*#*', - '/wp-admin/*\\?*#*', + '/wp-login.php\\\\?*#*', + '/wp-admin/*\\\\?*#*', ), ), ), diff --git a/modules/js-and-css/speculation-rules/hooks.php b/modules/js-and-css/speculation-rules/hooks.php index 474cb7f5fb..afb953f9d0 100644 --- a/modules/js-and-css/speculation-rules/hooks.php +++ b/modules/js-and-css/speculation-rules/hooks.php @@ -37,7 +37,7 @@ function plsr_print_speculation_rules() { wp_print_inline_script_tag( sprintf( $script, - str_replace( '\\', '\\\\', wp_json_encode( $rules ) ) + wp_json_encode( $rules ) ) ); } From 286e50a35c980cef5bc4850be075de123a4ab6b3 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Mon, 22 May 2023 16:25:34 -0700 Subject: [PATCH 03/14] Add hook to make it easy to use origin trial. --- .../js-and-css/speculation-rules/hooks.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/modules/js-and-css/speculation-rules/hooks.php b/modules/js-and-css/speculation-rules/hooks.php index afb953f9d0..a89ae709d4 100644 --- a/modules/js-and-css/speculation-rules/hooks.php +++ b/modules/js-and-css/speculation-rules/hooks.php @@ -42,3 +42,25 @@ function plsr_print_speculation_rules() { ); } add_action( 'wp_footer', 'plsr_print_speculation_rules' ); + +/** + * Prints the tag to opt in to the Chrome origin trial if the token constant is defined. + * + * After opting in to the origin trial via https://github.com/WICG/nav-speculation/blob/main/chrome-2023q1-experiment-overview.md, + * please set your token in a `PLSR_ORIGIN_TRIAL_TOKEN` constant, e.g. in `wp-config.php`. + * + * This function is here temporarily and will eventually be removed. + * + * @since n.e.x.t + * @access private + * @ignore + */ +function plsr_print_origin_trial_optin() { + if ( ! defined( 'PLSR_ORIGIN_TRIAL_TOKEN' ) || ! PLSR_ORIGIN_TRIAL_TOKEN ) { + return; + } + ?> + + Date: Mon, 13 Nov 2023 12:44:50 -0800 Subject: [PATCH 04/14] Print speculationrules script tag directly without using JS. --- .../js-and-css/speculation-rules/helper.php | 6 ++--- .../js-and-css/speculation-rules/hooks.php | 25 +++---------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/modules/js-and-css/speculation-rules/helper.php b/modules/js-and-css/speculation-rules/helper.php index 3e59af4006..505ea30312 100644 --- a/modules/js-and-css/speculation-rules/helper.php +++ b/modules/js-and-css/speculation-rules/helper.php @@ -23,15 +23,15 @@ function plsr_get_speculation_rules() { 'and' => array( // Prerender any URLs within the same site. array( - 'href_matches' => '/*\\\\?*', + 'href_matches' => '/*\\?*', 'relative_to' => 'document', ), // Except for WP login and admin URLs. array( 'not' => array( 'href_matches' => array( - '/wp-login.php\\\\?*#*', - '/wp-admin/*\\\\?*#*', + '/wp-login.php\\?*#*', + '/wp-admin/*\\?*#*', ), ), ), diff --git a/modules/js-and-css/speculation-rules/hooks.php b/modules/js-and-css/speculation-rules/hooks.php index a89ae709d4..5f77704220 100644 --- a/modules/js-and-css/speculation-rules/hooks.php +++ b/modules/js-and-css/speculation-rules/hooks.php @@ -7,9 +7,9 @@ */ /** - * Prints the speculation rules in a cross-browser compatible way. + * Prints the speculation rules. * - * For browsers that do not support speculation rules yet, the rules will not be loaded. + * For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored. * * @since n.e.x.t */ @@ -19,26 +19,9 @@ function plsr_print_speculation_rules() { return; } - $script = << 'speculationrules' ) ); } add_action( 'wp_footer', 'plsr_print_speculation_rules' ); From 25fc9da1b305ba9d848f8f75bc7e8cdb9540f7ef Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Mon, 13 Nov 2023 13:06:41 -0800 Subject: [PATCH 05/14] Introduce filter for which paths to exclude for speculative prerendering. --- .../js-and-css/speculation-rules/helper.php | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/modules/js-and-css/speculation-rules/helper.php b/modules/js-and-css/speculation-rules/helper.php index 505ea30312..2b02125b5e 100644 --- a/modules/js-and-css/speculation-rules/helper.php +++ b/modules/js-and-css/speculation-rules/helper.php @@ -16,6 +16,40 @@ * @return array Associative array of speculation rules by type. */ function plsr_get_speculation_rules() { + $base_href_exclude_paths = array( + '/wp-login.php', + '/wp-admin/*', + ); + $href_exclude_paths = $base_href_exclude_paths; + + /** + * Filters the paths for which speculative prerendering should be disabled. + * + * All paths should start in a forward slash, relative to the root document. The `*` can be used as a wildcard. + * By default, the array includes `/wp-login.php` and `/wp-admin/*`. + * + * @since n.e.x.t + * + * @param array $href_exclude_paths Paths to disable speculative prerendering for. + */ + $href_exclude_paths = (array) apply_filters( 'plsr_speculation_rules_href_exclude_paths', $href_exclude_paths ); + + // Ensure that there are no duplicates and that the base paths cannot be removed. + $href_exclude_paths = array_map( + static function ( $exclude_path ) { + if ( ! str_starts_with( $exclude_path, '/' ) ) { + $exclude_path = '/' . $exclude_path; + } + return $exclude_path . '\\?*#*'; + }, + array_unique( + array_merge( + $base_href_exclude_paths, + $href_exclude_paths + ) + ) + ); + $prerender_rules = array( array( 'source' => 'document', @@ -29,10 +63,7 @@ function plsr_get_speculation_rules() { // Except for WP login and admin URLs. array( 'not' => array( - 'href_matches' => array( - '/wp-login.php\\?*#*', - '/wp-admin/*\\?*#*', - ), + 'href_matches' => $href_exclude_paths, ), ), // And except for any links marked with a class to not prerender. From ce1c4696a2a571fdd59035c41519c0350c7eda51 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Mon, 13 Nov 2023 13:30:13 -0800 Subject: [PATCH 06/14] Add CODEOWNERS entry. --- .github/CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d2939e8d47..b8ae808fdb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -53,3 +53,8 @@ /modules/images/dominant-color-images @pbearne @spacedmonkey /tests/modules/images/dominant-color-images @pbearne @spacedmonkey /tests/testdata/modules/images/dominant-color-images @pbearne @spacedmonkey + +# Module: Speculation Rules +/modules/js-and-css/speculation-rules @felixarntz +/tests/modules/js-and-css/speculation-rules @felixarntz +/tests/testdata/modules/js-and-css/speculation-rules @felixarntz From f32401f680f44f628007d67e189c7401f61027b7 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Mon, 13 Nov 2023 13:30:45 -0800 Subject: [PATCH 07/14] Add initial test coverage for Speculation Rules. --- .../speculation-rules-helper-test.php | 57 +++++++++++++++++++ .../speculation-rules-test.php | 20 +++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/modules/js-and-css/speculation-rules/speculation-rules-helper-test.php create mode 100644 tests/modules/js-and-css/speculation-rules/speculation-rules-test.php diff --git a/tests/modules/js-and-css/speculation-rules/speculation-rules-helper-test.php b/tests/modules/js-and-css/speculation-rules/speculation-rules-helper-test.php new file mode 100644 index 0000000000..7889835985 --- /dev/null +++ b/tests/modules/js-and-css/speculation-rules/speculation-rules-helper-test.php @@ -0,0 +1,57 @@ +assertIsArray( $rules ); + $this->assertArrayHasKey( 'prerender', $rules ); + $this->assertIsArray( $rules['prerender'] ); + foreach ( $rules['prerender'] as $entry ) { + $this->assertIsArray( $entry ); + $this->assertArrayHasKey( 'source', $entry ); + $this->assertTrue( in_array( $entry['source'], array( 'list', 'document' ), true ) ); + } + } + + public function test_plsr_get_speculation_rules_href_exclude_paths() { + $rules = plsr_get_speculation_rules(); + $href_exclude_paths = $rules['prerender'][0]['where']['and'][1]['not']['href_matches']; + + $this->assertSameSets( + array( + '/wp-login.php\\?*#*', + '/wp-admin/*\\?*#*', + ), + $href_exclude_paths + ); + + // Add filter that attempts to replace base exclude paths with a custom path to exclude. + add_filter( + 'plsr_speculation_rules_href_exclude_paths', + static function () { + return array( 'custom-file.php' ); + } + ); + + $rules = plsr_get_speculation_rules(); + $href_exclude_paths = $rules['prerender'][0]['where']['and'][1]['not']['href_matches']; + + // Ensure the base exclude paths are still present and that the custom path was formatted correctly. + $this->assertSameSets( + array( + '/wp-login.php\\?*#*', + '/wp-admin/*\\?*#*', + '/custom-file.php\\?*#*' + ), + $href_exclude_paths + ); + } +} diff --git a/tests/modules/js-and-css/speculation-rules/speculation-rules-test.php b/tests/modules/js-and-css/speculation-rules/speculation-rules-test.php new file mode 100644 index 0000000000..8696e4b26f --- /dev/null +++ b/tests/modules/js-and-css/speculation-rules/speculation-rules-test.php @@ -0,0 +1,20 @@ +assertStringContainsString( '