Skip to content

Commit e7cab2c

Browse files
authored
Merge pull request #1883 from WordPress/add/speculation-rules-core-api-support
Add support for Speculative Loading WP Core API, loading the plugin's own API implementation conditionally
2 parents 8050a1b + ef87d7a commit e7cab2c

File tree

8 files changed

+300
-113
lines changed

8 files changed

+300
-113
lines changed

plugins/speculation-rules/hooks.php

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,48 +12,18 @@
1212
}
1313
// @codeCoverageIgnoreEnd
1414

15-
/**
16-
* Prints the speculation rules.
17-
*
18-
* For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored.
19-
*
20-
* @since 1.0.0
21-
*/
22-
function plsr_print_speculation_rules(): void {
23-
// Skip speculative loading for logged-in users.
24-
if ( is_user_logged_in() ) {
25-
return;
26-
}
27-
28-
// Skip speculative loading for sites without pretty permalinks, unless explicitly enabled.
29-
if ( ! (bool) get_option( 'permalink_structure' ) ) {
30-
/**
31-
* Filters whether speculative loading should be enabled even though the site does not use pretty permalinks.
32-
*
33-
* Since query parameters are commonly used by plugins for dynamic behavior that can change state, ideally any
34-
* such URLs are excluded from speculative loading. If the site does not use pretty permalinks though, they are
35-
* impossible to recognize. Therefore speculative loading is disabled by default for those sites.
36-
*
37-
* For site owners of sites without pretty permalinks that are certain their site is not using such a pattern,
38-
* this filter can be used to still enable speculative loading at their own risk.
39-
*
40-
* @since 1.4.0
41-
*
42-
* @param bool $enabled Whether speculative loading is enabled even without pretty permalinks.
43-
*/
44-
$enabled = (bool) apply_filters( 'plsr_enabled_without_pretty_permalinks', false );
15+
// Conditionally use either the WordPress Core API, or load the plugin's API implementation otherwise.
16+
if ( function_exists( 'wp_get_speculation_rules_configuration' ) ) {
17+
require_once __DIR__ . '/wp-core-api.php';
4518

46-
if ( ! $enabled ) {
47-
return;
48-
}
49-
}
19+
add_filter( 'wp_speculation_rules_configuration', 'plsr_filter_speculation_rules_configuration' );
20+
add_filter( 'wp_speculation_rules_href_exclude_paths', 'plsr_filter_speculation_rules_exclude_paths', 10, 2 );
21+
} else {
22+
require_once __DIR__ . '/class-plsr-url-pattern-prefixer.php';
23+
require_once __DIR__ . '/plugin-api.php';
5024

51-
wp_print_inline_script_tag(
52-
(string) wp_json_encode( plsr_get_speculation_rules() ),
53-
array( 'type' => 'speculationrules' )
54-
);
25+
add_action( 'wp_footer', 'plsr_print_speculation_rules' );
5526
}
56-
add_action( 'wp_footer', 'plsr_print_speculation_rules' );
5727

5828
/**
5929
* Displays the HTML generator meta tag for the Speculative Loading plugin.

plugins/speculation-rules/load.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ static function ( string $version ): void {
7777
define( 'SPECULATION_RULES_VERSION', $version );
7878
define( 'SPECULATION_RULES_MAIN_FILE', plugin_basename( __FILE__ ) );
7979

80-
require_once __DIR__ . '/class-plsr-url-pattern-prefixer.php';
81-
require_once __DIR__ . '/helper.php';
8280
require_once __DIR__ . '/hooks.php';
8381
require_once __DIR__ . '/settings.php';
8482
}

plugins/speculation-rules/helper.php renamed to plugins/speculation-rules/plugin-api.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/**
3-
* Helper functions used for Speculative Loading.
3+
* Plugin API for Speculative Loading.
44
*
55
* @package speculation-rules
66
* @since 1.0.0
@@ -121,3 +121,45 @@ static function ( string $href_exclude_path ) use ( $prefixer ): string {
121121

122122
return array( $mode => $rules );
123123
}
124+
125+
/**
126+
* Prints the speculation rules.
127+
*
128+
* For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored.
129+
*
130+
* @since 1.0.0
131+
*/
132+
function plsr_print_speculation_rules(): void {
133+
// Skip speculative loading for logged-in users.
134+
if ( is_user_logged_in() ) {
135+
return;
136+
}
137+
138+
// Skip speculative loading for sites without pretty permalinks, unless explicitly enabled.
139+
if ( ! (bool) get_option( 'permalink_structure' ) ) {
140+
/**
141+
* Filters whether speculative loading should be enabled even though the site does not use pretty permalinks.
142+
*
143+
* Since query parameters are commonly used by plugins for dynamic behavior that can change state, ideally any
144+
* such URLs are excluded from speculative loading. If the site does not use pretty permalinks though, they are
145+
* impossible to recognize. Therefore speculative loading is disabled by default for those sites.
146+
*
147+
* For site owners of sites without pretty permalinks that are certain their site is not using such a pattern,
148+
* this filter can be used to still enable speculative loading at their own risk.
149+
*
150+
* @since 1.4.0
151+
*
152+
* @param bool $enabled Whether speculative loading is enabled even without pretty permalinks.
153+
*/
154+
$enabled = (bool) apply_filters( 'plsr_enabled_without_pretty_permalinks', false );
155+
156+
if ( ! $enabled ) {
157+
return;
158+
}
159+
}
160+
161+
wp_print_inline_script_tag(
162+
(string) wp_json_encode( plsr_get_speculation_rules() ),
163+
array( 'type' => 'speculationrules' )
164+
);
165+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
/**
3+
* Test Bootstrap for Speculative Loading.
4+
*
5+
* @package speculation-rules
6+
*/
7+
8+
// Load conditionally loaded files unconditionally so that they can be tested.
9+
require_once TESTS_PLUGIN_DIR . '/class-plsr-url-pattern-prefixer.php';
10+
require_once TESTS_PLUGIN_DIR . '/plugin-api.php';
11+
require_once TESTS_PLUGIN_DIR . '/wp-core-api.php';

plugins/speculation-rules/tests/test-speculation-rules-helper.php renamed to plugins/speculation-rules/tests/test-speculation-rules-plugin-api.php

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
<?php
22
/**
3-
* Tests for speculation-rules helper file.
3+
* Tests for speculation-rules plugin API.
44
*
55
* @package speculation-rules
66
*/
77

8-
class Test_Speculation_Rules_Helper extends WP_UnitTestCase {
8+
class Test_Speculation_Rules_Plugin_API extends WP_UnitTestCase {
9+
10+
/** @var array<string, mixed> */
11+
private $original_wp_theme_features = array();
12+
913
/**
1014
* Runs the routine before each test is executed.
1115
*/
1216
public function set_up(): void {
1317
parent::set_up();
1418

19+
$this->original_wp_theme_features = $GLOBALS['_wp_theme_features'];
20+
1521
add_filter(
1622
'template_directory_uri',
1723
static function () {
@@ -27,6 +33,10 @@ static function () {
2733
);
2834
}
2935

36+
public function tear_down(): void {
37+
$GLOBALS['_wp_theme_features'] = $this->original_wp_theme_features;
38+
parent::tear_down();
39+
}
3040

3141
/**
3242
* @covers ::plsr_get_speculation_rules
@@ -308,4 +318,59 @@ public function data_plsr_get_speculation_rules_with_eagerness(): array {
308318
array( 'eager' ),
309319
);
310320
}
321+
322+
/**
323+
* @covers ::plsr_print_speculation_rules
324+
*/
325+
public function test_plsr_print_speculation_rules_without_html5_support(): void {
326+
$this->enable_pretty_permalinks();
327+
328+
$output = get_echo( 'plsr_print_speculation_rules' );
329+
$this->assertStringContainsString( '<script type="speculationrules">', $output );
330+
331+
$json = str_replace( array( '<script type="speculationrules">', '</script>' ), '', $output );
332+
$rules = json_decode( $json, true );
333+
$this->assertIsArray( $rules );
334+
$this->assertArrayHasKey( 'prerender', $rules );
335+
}
336+
337+
/**
338+
* @covers ::plsr_print_speculation_rules
339+
*/
340+
public function test_plsr_print_speculation_rules_without_pretty_permalinks(): void {
341+
$this->disable_pretty_permalinks();
342+
343+
$output = get_echo( 'plsr_print_speculation_rules' );
344+
$this->assertSame( '', $output );
345+
}
346+
347+
/**
348+
* @covers ::plsr_print_speculation_rules
349+
*/
350+
public function test_plsr_print_speculation_rules_without_pretty_permalinks_but_opted_in(): void {
351+
$this->disable_pretty_permalinks();
352+
add_filter( 'plsr_enabled_without_pretty_permalinks', '__return_true' );
353+
354+
$output = get_echo( 'plsr_print_speculation_rules' );
355+
$this->assertStringContainsString( '<script type="speculationrules">', $output );
356+
}
357+
358+
/**
359+
* @covers ::plsr_print_speculation_rules
360+
*/
361+
public function test_plsr_print_speculation_rules_for_logged_in_user(): void {
362+
wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
363+
$this->enable_pretty_permalinks();
364+
365+
$output = get_echo( 'plsr_print_speculation_rules' );
366+
$this->assertSame( '', $output );
367+
}
368+
369+
private function enable_pretty_permalinks(): void {
370+
update_option( 'permalink_structure', '/%year%/%monthnum%/%day%/%postname%/' );
371+
}
372+
373+
private function disable_pretty_permalinks(): void {
374+
update_option( 'permalink_structure', '' );
375+
}
311376
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
/**
3+
* Tests for speculation-rules WP Core API.
4+
*
5+
* @package speculation-rules
6+
*/
7+
8+
class Test_Speculation_Rules_WP_Core_API extends WP_UnitTestCase {
9+
10+
/**
11+
* @covers ::plsr_filter_speculation_rules_configuration
12+
*/
13+
public function test_plsr_filter_speculation_rules_configuration_with_regular(): void {
14+
$this->assertSame(
15+
array(
16+
'mode' => 'prerender',
17+
'eagerness' => 'moderate',
18+
),
19+
plsr_filter_speculation_rules_configuration(
20+
array(
21+
'mode' => 'prefetch',
22+
'eagerness' => 'conservative',
23+
)
24+
)
25+
);
26+
}
27+
28+
/**
29+
* @covers ::plsr_filter_speculation_rules_configuration
30+
*/
31+
public function test_plsr_filter_speculation_rules_configuration_with_invalid(): void {
32+
$this->assertSame(
33+
array(
34+
'mode' => 'prerender',
35+
'eagerness' => 'moderate',
36+
),
37+
plsr_filter_speculation_rules_configuration( 'neither-an-array-nor-null' )
38+
);
39+
}
40+
41+
/**
42+
* @covers ::plsr_filter_speculation_rules_configuration
43+
*/
44+
public function test_plsr_filter_speculation_rules_configuration_with_null(): void {
45+
$this->assertNull( plsr_filter_speculation_rules_configuration( null ) );
46+
}
47+
48+
/**
49+
* @covers ::plsr_filter_speculation_rules_configuration
50+
*/
51+
public function test_plsr_filter_speculation_rules_configuration_with_pretty_permalinks_filter(): void {
52+
// Providing null while pretty permalinks are disabled should be respected.
53+
$this->disable_pretty_permalinks();
54+
$this->assertNull( plsr_filter_speculation_rules_configuration( null ) );
55+
56+
// Unless the filter to enable speculative loading despite that is set to true.
57+
add_filter( 'plsr_enabled_without_pretty_permalinks', '__return_true' );
58+
$this->assertSame(
59+
array(
60+
'mode' => 'prerender',
61+
'eagerness' => 'moderate',
62+
),
63+
plsr_filter_speculation_rules_configuration( null )
64+
);
65+
}
66+
67+
/**
68+
* @covers ::plsr_filter_speculation_rules_exclude_paths
69+
*/
70+
public function test_plsr_filter_speculation_rules_exclude_paths_with_regular(): void {
71+
$base_rules = array( '/membership-areas/*' );
72+
73+
$this->assertSame( $base_rules, plsr_filter_speculation_rules_exclude_paths( $base_rules, 'prefetch' ) );
74+
75+
add_filter(
76+
'plsr_speculation_rules_href_exclude_paths',
77+
static function ( $rules ) {
78+
$rules[] = '/cart/*';
79+
return $rules;
80+
}
81+
);
82+
83+
$this->assertSame(
84+
array_merge( $base_rules, array( '/cart/*' ) ),
85+
plsr_filter_speculation_rules_exclude_paths( $base_rules, 'prefetch' )
86+
);
87+
}
88+
89+
/**
90+
* @covers ::plsr_filter_speculation_rules_exclude_paths
91+
*/
92+
public function test_plsr_filter_speculation_rules_exclude_paths_with_invalid(): void {
93+
$this->assertSame( array( '/personalized/*' ), plsr_filter_speculation_rules_exclude_paths( '/personalized/*', 'prefetch' ) );
94+
}
95+
96+
private function disable_pretty_permalinks(): void {
97+
update_option( 'permalink_structure', '' );
98+
}
99+
}

0 commit comments

Comments
 (0)