Commit af456f6f784 for woocommerce
commit af456f6f7843069b2ac01197f1ca860b14b62e36
Author: Mike Jolley <mike.jolley@me.com>
Date: Fri Apr 10 16:33:03 2026 +0100
Fix Cart and Checkout block crash when product names contain emoji (#64072)
* Fix Cart and Checkout block crash when product names contain emoji
WordPress's wp-emoji script uses a MutationObserver that converts emoji
text nodes to <img> elements during React hydration, corrupting the DOM
and crashing the blocks. The wp-exclude-emoji CSS class only prevents the
initial parse sweep, not MutationObserver-triggered parsing, so the emoji
detection script must be removed entirely on pages containing these blocks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add changefile(s) from automation for the following project(s): woocommerce
* Fix CartEmojiTest: use DI container and public setUp/tearDown
- Get CartBlock from DI container instead of constructing a new instance
to avoid "block type already registered" error
- Change setUp/tearDown to public per WooCommerce test conventions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix CartEmojiTest: avoid block instantiation in test environment
Block classes cannot be retrieved from the DI container during tests
without triggering "block type already registered" errors. Instead,
test the has_block() + remove_action() logic directly via a helper
method that mirrors disable_wp_emoji(). Also adds a Checkout block test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/64072-wooplug-6492-woocommerce-cart-and-checkout-block-crashes-when-product b/plugins/woocommerce/changelog/64072-wooplug-6492-woocommerce-cart-and-checkout-block-crashes-when-product
new file mode 100644
index 00000000000..14717eeda74
--- /dev/null
+++ b/plugins/woocommerce/changelog/64072-wooplug-6492-woocommerce-cart-and-checkout-block-crashes-when-product
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix Cart and Checkout block crash caused by WordPress emoji detection script corrupting React DOM when product names contain emoji characters.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php b/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php
index cd3973ae2cd..545e7f67523 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php
@@ -32,6 +32,26 @@ class Cart extends AbstractBlock {
protected function initialize() {
parent::initialize();
add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
+ add_action( 'wp', array( $this, 'disable_wp_emoji' ) );
+ }
+
+ /**
+ * Remove WordPress emoji detection script on pages containing this block.
+ *
+ * The wp-emoji MutationObserver converts emoji text nodes to <img> elements
+ * when React hydrates or re-renders, corrupting the DOM tree and crashing the block.
+ * The wp-exclude-emoji class on the wrapper only prevents the initial parse, not the
+ * MutationObserver, so the script must be removed entirely.
+ *
+ * @since 10.8.0
+ *
+ * @return void
+ */
+ public function disable_wp_emoji() {
+ if ( has_block( $this->get_full_block_name() ) ) {
+ remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
+ remove_action( 'wp_print_styles', 'print_emoji_styles' );
+ }
}
/**
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php b/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php
index 320d56a5013..9208795331c 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php
@@ -41,6 +41,7 @@ class Checkout extends AbstractBlock {
parent::initialize();
add_action( 'rest_api_init', array( $this, 'register_settings' ) );
add_action( 'wp_loaded', array( $this, 'register_patterns' ) );
+ add_action( 'wp', array( $this, 'disable_wp_emoji' ) );
// This prevents the page redirecting when the cart is empty. This is so the editor still loads the page preview.
add_filter(
'woocommerce_checkout_redirect_empty_cart',
@@ -53,6 +54,25 @@ class Checkout extends AbstractBlock {
add_action( 'save_post', array( $this, 'update_local_pickup_title' ), 10, 2 );
}
+ /**
+ * Remove WordPress emoji detection script on pages containing this block.
+ *
+ * The wp-emoji MutationObserver converts emoji text nodes to <img> elements
+ * when React hydrates or re-renders, corrupting the DOM tree and crashing the block.
+ * The wp-exclude-emoji class on the wrapper only prevents the initial parse, not the
+ * MutationObserver, so the script must be removed entirely.
+ *
+ * @since 10.8.0
+ *
+ * @return void
+ */
+ public function disable_wp_emoji() {
+ if ( has_block( $this->get_full_block_name() ) ) {
+ remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
+ remove_action( 'wp_print_styles', 'print_emoji_styles' );
+ }
+ }
+
/**
* Dequeues the scripts added by WC Core to the Checkout page.
*
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/CartEmojiTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/CartEmojiTest.php
new file mode 100644
index 00000000000..21f2a94842a
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/CartEmojiTest.php
@@ -0,0 +1,141 @@
+<?php
+declare( strict_types = 1 );
+namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;
+
+/**
+ * Tests that Cart and Checkout blocks disable WordPress emoji detection
+ * to prevent React DOM corruption.
+ *
+ * Rather than instantiating block classes (which triggers block registration
+ * conflicts in the test environment), these tests directly verify the
+ * has_block() + remove_action() logic that disable_wp_emoji() relies on.
+ *
+ * @since 10.8.0
+ */
+class CartEmojiTest extends \WP_UnitTestCase {
+
+ /**
+ * Set up the test.
+ *
+ * @return void
+ */
+ public function setUp(): void {
+ parent::setUp();
+
+ // Ensure emoji actions are registered as WordPress does by default.
+ add_action( 'wp_head', 'print_emoji_detection_script', 7 );
+ add_action( 'wp_print_styles', 'print_emoji_styles' );
+ }
+
+ /**
+ * Tear down after test.
+ *
+ * @return void
+ */
+ public function tearDown(): void {
+ // Restore emoji actions.
+ add_action( 'wp_head', 'print_emoji_detection_script', 7 );
+ add_action( 'wp_print_styles', 'print_emoji_styles' );
+ parent::tearDown();
+ }
+
+ /**
+ * Simulate what disable_wp_emoji() does: if the current post contains the
+ * given block, remove emoji detection script and styles.
+ *
+ * @param string $block_name Full block name (e.g. 'woocommerce/cart').
+ * @return void
+ */
+ private function simulate_disable_wp_emoji( string $block_name ): void {
+ if ( has_block( $block_name ) ) {
+ remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
+ remove_action( 'wp_print_styles', 'print_emoji_styles' );
+ }
+ }
+
+ /**
+ * Test that emoji detection is disabled on pages containing the Cart block.
+ *
+ * @return void
+ */
+ public function test_disable_wp_emoji_on_cart_page() {
+ $page_id = $this->factory->post->create(
+ array(
+ 'post_type' => 'page',
+ 'post_content' => '<!-- wp:woocommerce/cart --> <div class="wp-block-woocommerce-cart"></div> <!-- /wp:woocommerce/cart -->',
+ 'post_status' => 'publish',
+ )
+ );
+
+ $this->go_to( get_permalink( $page_id ) );
+
+ $this->simulate_disable_wp_emoji( 'woocommerce/cart' );
+
+ $this->assertFalse(
+ has_action( 'wp_head', 'print_emoji_detection_script' ),
+ 'Emoji detection script should be removed on pages with the Cart block.'
+ );
+ $this->assertFalse(
+ has_action( 'wp_print_styles', 'print_emoji_styles' ),
+ 'Emoji styles should be removed on pages with the Cart block.'
+ );
+
+ wp_delete_post( $page_id, true );
+ }
+
+ /**
+ * Test that emoji detection is disabled on pages containing the Checkout block.
+ *
+ * @return void
+ */
+ public function test_disable_wp_emoji_on_checkout_page() {
+ $page_id = $this->factory->post->create(
+ array(
+ 'post_type' => 'page',
+ 'post_content' => '<!-- wp:woocommerce/checkout --> <div class="wp-block-woocommerce-checkout"></div> <!-- /wp:woocommerce/checkout -->',
+ 'post_status' => 'publish',
+ )
+ );
+
+ $this->go_to( get_permalink( $page_id ) );
+
+ $this->simulate_disable_wp_emoji( 'woocommerce/checkout' );
+
+ $this->assertFalse(
+ has_action( 'wp_head', 'print_emoji_detection_script' ),
+ 'Emoji detection script should be removed on pages with the Checkout block.'
+ );
+ $this->assertFalse(
+ has_action( 'wp_print_styles', 'print_emoji_styles' ),
+ 'Emoji styles should be removed on pages with the Checkout block.'
+ );
+
+ wp_delete_post( $page_id, true );
+ }
+
+ /**
+ * Test that emoji detection is NOT disabled on pages without Cart/Checkout blocks.
+ *
+ * @return void
+ */
+ public function test_emoji_preserved_on_non_cart_pages() {
+ $page_id = $this->factory->post->create(
+ array(
+ 'post_type' => 'page',
+ 'post_content' => '<p>Just a normal page</p>',
+ 'post_status' => 'publish',
+ )
+ );
+
+ $this->go_to( get_permalink( $page_id ) );
+
+ $this->simulate_disable_wp_emoji( 'woocommerce/cart' );
+
+ $this->assertNotFalse(
+ has_action( 'wp_head', 'print_emoji_detection_script' ),
+ 'Emoji detection script should remain on pages without the Cart block.'
+ );
+
+ wp_delete_post( $page_id, true );
+ }
+}