Skip to content
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

Add support for Speculative Loading WP Core API, loading the plugin's own API implementation conditionally #1883

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 9 additions & 39 deletions plugins/speculation-rules/hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,18 @@
}
// @codeCoverageIgnoreEnd

/**
* Prints the speculation rules.
*
* For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored.
*
* @since 1.0.0
*/
function plsr_print_speculation_rules(): void {
Copy link
Member Author

Choose a reason for hiding this comment

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

Note for reviewers: This function was moved without any changes to the plugin-api.php file.

// Skip speculative loading for logged-in users.
if ( is_user_logged_in() ) {
return;
}

// Skip speculative loading for sites without pretty permalinks, unless explicitly enabled.
if ( ! (bool) get_option( 'permalink_structure' ) ) {
/**
* Filters whether speculative loading should be enabled even though the site does not use pretty permalinks.
*
* Since query parameters are commonly used by plugins for dynamic behavior that can change state, ideally any
* such URLs are excluded from speculative loading. If the site does not use pretty permalinks though, they are
* impossible to recognize. Therefore speculative loading is disabled by default for those sites.
*
* For site owners of sites without pretty permalinks that are certain their site is not using such a pattern,
* this filter can be used to still enable speculative loading at their own risk.
*
* @since 1.4.0
*
* @param bool $enabled Whether speculative loading is enabled even without pretty permalinks.
*/
$enabled = (bool) apply_filters( 'plsr_enabled_without_pretty_permalinks', false );
// Conditionally use either the WordPress Core API, or load the plugin's API implementation otherwise.
if ( function_exists( 'wp_get_speculation_rules_configuration' ) ) {
require_once __DIR__ . '/wp-core-api.php';

Check warning on line 17 in plugins/speculation-rules/hooks.php

View check run for this annotation

Codecov / codecov/patch

plugins/speculation-rules/hooks.php#L16-L17

Added lines #L16 - L17 were not covered by tests

if ( ! $enabled ) {
return;
}
}
add_filter( 'wp_speculation_rules_configuration', 'plsr_filter_speculation_rules_configuration' );
add_filter( 'wp_speculation_rules_href_exclude_paths', 'plsr_filter_speculation_rules_exclude_paths', 10, 2 );

Check warning on line 20 in plugins/speculation-rules/hooks.php

View check run for this annotation

Codecov / codecov/patch

plugins/speculation-rules/hooks.php#L19-L20

Added lines #L19 - L20 were not covered by tests
} else {
require_once __DIR__ . '/class-plsr-url-pattern-prefixer.php';
require_once __DIR__ . '/plugin-api.php';

Check warning on line 23 in plugins/speculation-rules/hooks.php

View check run for this annotation

Codecov / codecov/patch

plugins/speculation-rules/hooks.php#L22-L23

Added lines #L22 - L23 were not covered by tests

wp_print_inline_script_tag(
(string) wp_json_encode( plsr_get_speculation_rules() ),
array( 'type' => 'speculationrules' )
);
add_action( 'wp_footer', 'plsr_print_speculation_rules' );

Check warning on line 25 in plugins/speculation-rules/hooks.php

View check run for this annotation

Codecov / codecov/patch

plugins/speculation-rules/hooks.php#L25

Added line #L25 was not covered by tests
}
add_action( 'wp_footer', 'plsr_print_speculation_rules' );

/**
* Displays the HTML generator meta tag for the Speculative Loading plugin.
Expand Down
2 changes: 0 additions & 2 deletions plugins/speculation-rules/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ static function ( string $version ): void {
define( 'SPECULATION_RULES_VERSION', $version );
define( 'SPECULATION_RULES_MAIN_FILE', plugin_basename( __FILE__ ) );

require_once __DIR__ . '/class-plsr-url-pattern-prefixer.php';
require_once __DIR__ . '/helper.php';
require_once __DIR__ . '/hooks.php';
require_once __DIR__ . '/settings.php';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Helper functions used for Speculative Loading.
* Plugin API for Speculative Loading.
*
* @package speculation-rules
* @since 1.0.0
Expand Down Expand Up @@ -121,3 +121,45 @@ static function ( string $href_exclude_path ) use ( $prefixer ): string {

return array( $mode => $rules );
}

/**
* Prints the speculation rules.
*
* For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored.
*
* @since 1.0.0
*/
function plsr_print_speculation_rules(): void {
// Skip speculative loading for logged-in users.
if ( is_user_logged_in() ) {
return;
}

// Skip speculative loading for sites without pretty permalinks, unless explicitly enabled.
if ( ! (bool) get_option( 'permalink_structure' ) ) {
/**
* Filters whether speculative loading should be enabled even though the site does not use pretty permalinks.
*
* Since query parameters are commonly used by plugins for dynamic behavior that can change state, ideally any
* such URLs are excluded from speculative loading. If the site does not use pretty permalinks though, they are
* impossible to recognize. Therefore speculative loading is disabled by default for those sites.
*
* For site owners of sites without pretty permalinks that are certain their site is not using such a pattern,
* this filter can be used to still enable speculative loading at their own risk.
*
* @since 1.4.0
*
* @param bool $enabled Whether speculative loading is enabled even without pretty permalinks.
*/
$enabled = (bool) apply_filters( 'plsr_enabled_without_pretty_permalinks', false );

if ( ! $enabled ) {
return;
}
}

wp_print_inline_script_tag(
(string) wp_json_encode( plsr_get_speculation_rules() ),
array( 'type' => 'speculationrules' )
);
}
11 changes: 11 additions & 0 deletions plugins/speculation-rules/tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
/**
* Test Bootstrap for Speculative Loading.
*
* @package speculation-rules
*/

// Load conditionally loaded files unconditionally so that they can be tested.
require_once TESTS_PLUGIN_DIR . '/class-plsr-url-pattern-prefixer.php';
require_once TESTS_PLUGIN_DIR . '/plugin-api.php';
require_once TESTS_PLUGIN_DIR . '/wp-core-api.php';
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
<?php
/**
* Tests for speculation-rules helper file.
* Tests for speculation-rules plugin API.
*
* @package speculation-rules
*/

class Test_Speculation_Rules_Helper extends WP_UnitTestCase {
class Test_Speculation_Rules_Plugin_API extends WP_UnitTestCase {

/** @var array<string, mixed> */
private $original_wp_theme_features = array();

/**
* Runs the routine before each test is executed.
*/
public function set_up(): void {
parent::set_up();

$this->original_wp_theme_features = $GLOBALS['_wp_theme_features'];

add_filter(
'template_directory_uri',
static function () {
Expand All @@ -27,6 +33,10 @@ static function () {
);
}

public function tear_down(): void {
$GLOBALS['_wp_theme_features'] = $this->original_wp_theme_features;
parent::tear_down();
}

/**
* @covers ::plsr_get_speculation_rules
Expand Down Expand Up @@ -308,4 +318,59 @@ public function data_plsr_get_speculation_rules_with_eagerness(): array {
array( 'eager' ),
);
}

/**
* @covers ::plsr_print_speculation_rules
*/
public function test_plsr_print_speculation_rules_without_html5_support(): void {
$this->enable_pretty_permalinks();

$output = get_echo( 'plsr_print_speculation_rules' );
$this->assertStringContainsString( '<script type="speculationrules">', $output );

$json = str_replace( array( '<script type="speculationrules">', '</script>' ), '', $output );
$rules = json_decode( $json, true );
$this->assertIsArray( $rules );
$this->assertArrayHasKey( 'prerender', $rules );
}

/**
* @covers ::plsr_print_speculation_rules
*/
public function test_plsr_print_speculation_rules_without_pretty_permalinks(): void {
$this->disable_pretty_permalinks();

$output = get_echo( 'plsr_print_speculation_rules' );
$this->assertSame( '', $output );
}

/**
* @covers ::plsr_print_speculation_rules
*/
public function test_plsr_print_speculation_rules_without_pretty_permalinks_but_opted_in(): void {
$this->disable_pretty_permalinks();
add_filter( 'plsr_enabled_without_pretty_permalinks', '__return_true' );

$output = get_echo( 'plsr_print_speculation_rules' );
$this->assertStringContainsString( '<script type="speculationrules">', $output );
}

/**
* @covers ::plsr_print_speculation_rules
*/
public function test_plsr_print_speculation_rules_for_logged_in_user(): void {
wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
$this->enable_pretty_permalinks();

$output = get_echo( 'plsr_print_speculation_rules' );
$this->assertSame( '', $output );
}

private function enable_pretty_permalinks(): void {
update_option( 'permalink_structure', '/%year%/%monthnum%/%day%/%postname%/' );
}

private function disable_pretty_permalinks(): void {
update_option( 'permalink_structure', '' );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
/**
* Tests for speculation-rules WP Core API.
*
* @package speculation-rules
*/

class Test_Speculation_Rules_WP_Core_API extends WP_UnitTestCase {

public function test_plsr_filter_speculation_rules_configuration_with_regular(): void {
$this->assertSame(
array(
'mode' => 'prerender',
'eagerness' => 'moderate',
),
plsr_filter_speculation_rules_configuration(
array(
'mode' => 'prefetch',
'eagerness' => 'conservative',
)
)
);
}

public function test_plsr_filter_speculation_rules_configuration_with_invalid(): void {
$this->assertSame(
array(
'mode' => 'prerender',
'eagerness' => 'moderate',
),
plsr_filter_speculation_rules_configuration( 'neither-an-array-nor-null' )
);
}

public function test_plsr_filter_speculation_rules_configuration_with_null(): void {
$this->assertNull( plsr_filter_speculation_rules_configuration( null ) );
}

public function test_plsr_filter_speculation_rules_configuration_with_pretty_permalinks_filter(): void {
// Providing null while pretty permalinks are disabled should be respected.
$this->disable_pretty_permalinks();
$this->assertNull( plsr_filter_speculation_rules_configuration( null ) );

// Unless the filter to enable speculative loading despite that is set to true.
add_filter( 'plsr_enabled_without_pretty_permalinks', '__return_true' );
$this->assertSame(
array(
'mode' => 'prerender',
'eagerness' => 'moderate',
),
plsr_filter_speculation_rules_configuration( null )
);
}

public function test_plsr_filter_speculation_rules_exclude_paths_with_regular(): void {
$base_rules = array( '/membership-areas/*' );

$this->assertSame( $base_rules, plsr_filter_speculation_rules_exclude_paths( $base_rules, 'prefetch' ) );

add_filter(
'plsr_speculation_rules_href_exclude_paths',
static function ( $rules ) {
$rules[] = '/cart/*';
return $rules;
}
);

$this->assertSame(
array_merge( $base_rules, array( '/cart/*' ) ),
plsr_filter_speculation_rules_exclude_paths( $base_rules, 'prefetch' )
);
}

public function test_plsr_filter_speculation_rules_exclude_paths_with_invalid(): void {
$this->assertSame( array( '/personalized/*' ), plsr_filter_speculation_rules_exclude_paths( '/personalized/*', 'prefetch' ) );
}

private function disable_pretty_permalinks(): void {
update_option( 'permalink_structure', '' );
}
}
Loading