From 99e9df07534abc3ce39d155ab80e7806ba3c7759 Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Wed, 6 May 2026 12:04:05 +0530 Subject: [PATCH] Admin Bar: Prevent cache poisoning from device-specific classes --- src/wp-includes/class-wp-admin-bar.php | 7 ++-- tests/phpunit/tests/adminbar.php | 50 ++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-admin-bar.php b/src/wp-includes/class-wp-admin-bar.php index 9e7b54823b900..4fbda7d079ff4 100644 --- a/src/wp-includes/class-wp-admin-bar.php +++ b/src/wp-includes/class-wp-admin-bar.php @@ -463,9 +463,12 @@ final protected function _render( $root ) { /* * Add browser classes. * We have to do this here since admin bar shows on the front end. + * The 'mobile' class is only added for logged-in users to avoid cache poisoning: + * logged-out pages are commonly served from a full-page cache, so a device-specific + * class set by the first visitor would be served to all subsequent visitors. */ - $class = 'nojq nojs'; - if ( wp_is_mobile() ) { + $class = 'nojs'; + if ( is_user_logged_in() && wp_is_mobile() ) { $class .= ' mobile'; } diff --git a/tests/phpunit/tests/adminbar.php b/tests/phpunit/tests/adminbar.php index 27308bd82760e..303767d6ba056 100644 --- a/tests/phpunit/tests/adminbar.php +++ b/tests/phpunit/tests/adminbar.php @@ -822,6 +822,56 @@ public function test_proto_property_is_not_defined() { $this->assertFalse( isset( $admin_bar->proto ), 'WP_Admin_Bar::$proto should not be defined.' ); } + /** + * Tests that the 'mobile' class is not added to the admin bar for logged-out users on mobile, + * preventing cache poisoning when full-page caching is active. + * + * @ticket 28117 + * @covers WP_Admin_Bar::render + */ + public function test_admin_bar_does_not_add_mobile_class_for_logged_out_users() { + add_filter( 'wp_is_mobile', '__return_true' ); + + $admin_bar = new WP_Admin_Bar(); + $admin_bar_html = get_echo( array( $admin_bar, 'render' ) ); + + remove_filter( 'wp_is_mobile', '__return_true' ); + + $this->assertStringNotContainsString( 'class="nojs mobile"', $admin_bar_html, 'The "mobile" class should not be present for logged-out users.' ); + } + + /** + * Tests that the 'mobile' class is added to the admin bar for logged-in users on mobile. + * + * @ticket 28117 + * @covers WP_Admin_Bar::render + */ + public function test_admin_bar_adds_mobile_class_for_logged_in_users_on_mobile() { + wp_set_current_user( self::$admin_id ); + add_filter( 'wp_is_mobile', '__return_true' ); + + $admin_bar = new WP_Admin_Bar(); + $admin_bar_html = get_echo( array( $admin_bar, 'render' ) ); + + remove_filter( 'wp_is_mobile', '__return_true' ); + wp_set_current_user( 0 ); + + $this->assertStringContainsString( 'class="nojs mobile"', $admin_bar_html, 'The "mobile" class should be present for logged-in users on mobile.' ); + } + + /** + * Tests that the 'nojq' class is never present in the admin bar output. + * + * @ticket 28117 + * @covers WP_Admin_Bar::render + */ + public function test_admin_bar_does_not_output_nojq_class() { + $admin_bar = new WP_Admin_Bar(); + $admin_bar_html = get_echo( array( $admin_bar, 'render' ) ); + + $this->assertStringNotContainsString( 'nojq', $admin_bar_html, 'The "nojq" class should not be present in the admin bar output.' ); + } + /** * This test ensures that WP_Admin_Bar::$menu is declared as a "regular" class property. *