From e807a7cbe09024183f6fba464e75281493d3424b Mon Sep 17 00:00:00 2001
From: Felix Arntz <flixos90@git.wordpress.org>
Date: Thu, 27 Feb 2025 22:12:10 +0000
Subject: [PATCH] General: Allow speculative loading opt-out CSS classes to be
 applied on parent element, e.g. at the block level.

Follow-up to [59837].

Props flixos90, westonruter.
Fixes #63032.
See #62503.


git-svn-id: https://develop.svn.wordpress.org/trunk@59881 602fd350-edb4-49c9-b593-d223f7449a82
---
 src/wp-includes/speculative-loading.php                   | 8 ++++----
 .../tests/speculative-loading/wpGetSpeculationRules.php   | 6 +++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/wp-includes/speculative-loading.php b/src/wp-includes/speculative-loading.php
index ee9d4b6381..2a06bc4aea 100644
--- a/src/wp-includes/speculative-loading.php
+++ b/src/wp-includes/speculative-loading.php
@@ -190,19 +190,19 @@ function wp_get_speculation_rules(): ?WP_Speculation_Rules {
 				'selector_matches' => 'a[rel~="nofollow"]',
 			),
 		),
-		// Also exclude links that are explicitly marked to opt out.
+		// Also exclude links that are explicitly marked to opt out, either directly or via a parent element.
 		array(
 			'not' => array(
-				'selector_matches' => ".no-{$mode}",
+				'selector_matches' => ".no-{$mode}, .no-{$mode} a",
 			),
 		),
 	);
 
-	// If using 'prerender', also exclude links that opt-out of 'prefetch' because it's part of 'prerender'.
+	// If using 'prerender', also exclude links that opt out of 'prefetch' because it's part of 'prerender'.
 	if ( 'prerender' === $mode ) {
 		$main_rule_conditions[] = array(
 			'not' => array(
-				'selector_matches' => '.no-prefetch',
+				'selector_matches' => '.no-prefetch, .no-prefetch a',
 			),
 		);
 	}
diff --git a/tests/phpunit/tests/speculative-loading/wpGetSpeculationRules.php b/tests/phpunit/tests/speculative-loading/wpGetSpeculationRules.php
index d285974070..a7b5594bdf 100644
--- a/tests/phpunit/tests/speculative-loading/wpGetSpeculationRules.php
+++ b/tests/phpunit/tests/speculative-loading/wpGetSpeculationRules.php
@@ -139,7 +139,7 @@ class Tests_Speculative_Loading_wpGetSpeculationRules extends WP_UnitTestCase {
 		$this->assertCount( 4, $rules['prefetch'][0]['where']['and'] );
 		$this->assertArrayHasKey( 'not', $rules['prefetch'][0]['where']['and'][3] );
 		$this->assertArrayHasKey( 'selector_matches', $rules['prefetch'][0]['where']['and'][3]['not'] );
-		$this->assertSame( '.no-prefetch', $rules['prefetch'][0]['where']['and'][3]['not']['selector_matches'] );
+		$this->assertSame( '.no-prefetch, .no-prefetch a', $rules['prefetch'][0]['where']['and'][3]['not']['selector_matches'] );
 	}
 
 	/**
@@ -164,10 +164,10 @@ class Tests_Speculative_Loading_wpGetSpeculationRules extends WP_UnitTestCase {
 		$this->assertCount( 5, $rules['prerender'][0]['where']['and'] );
 		$this->assertArrayHasKey( 'not', $rules['prerender'][0]['where']['and'][3] );
 		$this->assertArrayHasKey( 'selector_matches', $rules['prerender'][0]['where']['and'][3]['not'] );
-		$this->assertSame( '.no-prerender', $rules['prerender'][0]['where']['and'][3]['not']['selector_matches'] );
+		$this->assertSame( '.no-prerender, .no-prerender a', $rules['prerender'][0]['where']['and'][3]['not']['selector_matches'] );
 		$this->assertArrayHasKey( 'not', $rules['prerender'][0]['where']['and'][4] );
 		$this->assertArrayHasKey( 'selector_matches', $rules['prerender'][0]['where']['and'][4]['not'] );
-		$this->assertSame( '.no-prefetch', $rules['prerender'][0]['where']['and'][4]['not']['selector_matches'] );
+		$this->assertSame( '.no-prefetch, .no-prefetch a', $rules['prerender'][0]['where']['and'][4]['not']['selector_matches'] );
 	}
 
 	/**