From f72b8c8489674eece7f3afa003b12a3774aaf8cb Mon Sep 17 00:00:00 2001 From: Igor Rozum Date: Wed, 16 Sep 2026 19:21:39 -0400 Subject: [PATCH 1/5] Tests: Add coverage for wp-exclude-emoji when the excluded node itself is mutated after load --- tests/qunit/wp-includes/js/emoji.html | 31 ++++++++++++++++ tests/qunit/wp-includes/js/emoji.js | 52 +++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 tests/qunit/wp-includes/js/emoji.html create mode 100644 tests/qunit/wp-includes/js/emoji.js diff --git a/tests/qunit/wp-includes/js/emoji.html b/tests/qunit/wp-includes/js/emoji.html new file mode 100644 index 0000000000000..049a6468c2f5a --- /dev/null +++ b/tests/qunit/wp-includes/js/emoji.html @@ -0,0 +1,31 @@ + + + + wp.emoji QUnit Test Suite + + + + + + + +
+
+ + + + + + + diff --git a/tests/qunit/wp-includes/js/emoji.js b/tests/qunit/wp-includes/js/emoji.js new file mode 100644 index 0000000000000..597071079ee8c --- /dev/null +++ b/tests/qunit/wp-includes/js/emoji.js @@ -0,0 +1,52 @@ +( function( QUnit ) { + var FLAG = '🇰🇷'; // Flag: South Korea. + + QUnit.module( 'wp.emoji' ); + + function assertNotParsed( assert, container, done ) { + window.setTimeout( function() { + assert.strictEqual( container.querySelector( 'img.emoji' ), null, 'the excluded element should not contain an emoji image' ); + container.remove(); + done(); + }, 50 ); + } + + QUnit.test( 'does not parse an excluded element appended whole after load', function( assert ) { + var done = assert.async(), + span = document.createElement( 'span' ); + + span.className = 'wp-exclude-emoji'; + span.textContent = FLAG; + document.body.appendChild( span ); + + assertNotParsed( assert, span, done ); + } ); + + QUnit.test( 'does not parse an excluded element whose text is replaced after load', function( assert ) { + var done = assert.async(), + span = document.createElement( 'span' ); + + span.className = 'wp-exclude-emoji'; + document.body.appendChild( span ); + + window.setTimeout( function() { + span.textContent = FLAG; + assertNotParsed( assert, span, done ); + }, 50 ); + } ); + + QUnit.test( 'does not parse text appended inside an already-excluded ancestor', function( assert ) { + var done = assert.async(), + wrapper = document.createElement( 'span' ), + child = document.createElement( 'em' ); + + wrapper.className = 'wp-exclude-emoji'; + wrapper.appendChild( child ); + document.body.appendChild( wrapper ); + + window.setTimeout( function() { + child.textContent = FLAG; + assertNotParsed( assert, wrapper, done ); + }, 50 ); + } ); +} )( window.QUnit ); From 91b6d8b69a1887b4a4e24d9f8514271bc7878c4f Mon Sep 17 00:00:00 2001 From: Igor Rozum Date: Wed, 16 Sep 2026 19:21:42 -0400 Subject: [PATCH 2/5] Emoji: Check the mutated node itself, not just its descendants, for the wp-exclude-emoji class --- src/js/_enqueues/wp/emoji.js | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/js/_enqueues/wp/emoji.js b/src/js/_enqueues/wp/emoji.js index 274868f52d859..6eb0d81f23f76 100644 --- a/src/js/_enqueues/wp/emoji.js +++ b/src/js/_enqueues/wp/emoji.js @@ -147,6 +147,10 @@ node = node.parentNode; } + if ( isExcludedFromParsing( node ) ) { + continue; + } + if ( test( node.textContent ) ) { parse( node ); } @@ -161,6 +165,39 @@ parse( document.body ); } + /** + * Checks whether a node, or one of its ancestors, is excluded from + * emoji parsing. + * + * The doNotParse() callback passed to Twemoji only excludes descendants + * of the node it's asked to parse, so a node that is itself excluded + * (or nested inside an excluded ancestor) has to be checked here, + * before it's ever handed to parse(). + * + * @since 7.2.0 + * @private + * + * @param {Node} node The node to check. + * + * @return {boolean} Whether the node or an ancestor has the + * wp-exclude-emoji class. + */ + function isExcludedFromParsing( node ) { + while ( node ) { + if ( + node.className && + typeof node.className === 'string' && + node.className.indexOf( 'wp-exclude-emoji' ) !== -1 + ) { + return true; + } + + node = node.parentNode; + } + + return false; + } + /** * Tests if a text string contains emoji characters. * From b4fdebc907b7240222e8d71b66a5400bbfea1997 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 11 Nov 2026 14:42:36 +1100 Subject: [PATCH 3/5] Update docblock type to HTMLElement. --- src/js/_enqueues/wp/emoji.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/_enqueues/wp/emoji.js b/src/js/_enqueues/wp/emoji.js index fcb7b2b9bdbd8..2ecc9438242a7 100644 --- a/src/js/_enqueues/wp/emoji.js +++ b/src/js/_enqueues/wp/emoji.js @@ -139,7 +139,7 @@ * @since 7.2.0 * @private * - * @param {Node} node The node to check. + * @param {HTMLElement} node The node to check. * * @return {boolean} Whether the node or an ancestor has the * wp-exclude-emoji class. From f3ee13cd1dce3858fde0b0651173bdfc3d575387 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 11 Nov 2026 14:47:16 +1100 Subject: [PATCH 4/5] Fix CS --- src/js/_enqueues/wp/emoji.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/js/_enqueues/wp/emoji.js b/src/js/_enqueues/wp/emoji.js index 2ecc9438242a7..8bb2e88ef19f6 100644 --- a/src/js/_enqueues/wp/emoji.js +++ b/src/js/_enqueues/wp/emoji.js @@ -111,9 +111,7 @@ const node = addedNode instanceof Text ? addedNode.parentElement : addedNode; if ( - node instanceof HTMLElement - && test( node.textContent ) - && ! isExcludedFromParsing( node ) + node instanceof HTMLElement && test( node.textContent ) && ! isExcludedFromParsing( node ) ) { parse( node ); } From cf5e964757cb652a50afdbfad70b9539fc04ba7b Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Wed, 11 Nov 2026 14:52:55 +1100 Subject: [PATCH 5/5] Fix type issues. --- src/js/_enqueues/wp/emoji.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/_enqueues/wp/emoji.js b/src/js/_enqueues/wp/emoji.js index 8bb2e88ef19f6..9ec1a788a95b0 100644 --- a/src/js/_enqueues/wp/emoji.js +++ b/src/js/_enqueues/wp/emoji.js @@ -137,7 +137,7 @@ * @since 7.2.0 * @private * - * @param {HTMLElement} node The node to check. + * @param {HTMLElement|null} node The node to check. * * @return {boolean} Whether the node or an ancestor has the * wp-exclude-emoji class. @@ -152,7 +152,7 @@ return true; } - node = node.parentNode; + node = node.parentElement; } return false;