From 65d7692005193a9e9ac644acdaa933beea029117 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:10:25 +0200 Subject: [PATCH 01/14] HTML API: Document whitespace handling in `add_class()`. Note that the `class_name` argument to `WP_HTML_Tag_Processor::add_class()` and `WP_HTML_Processor::add_class()` is inserted verbatim into the `class` attribute, which is a whitespace-separated token list. Whitespace in the value therefore adds multiple classes; a single class whose name contains whitespace cannot be expressed. --- .../html-api/class-wp-html-processor.php | 17 ++++++++++++++++- .../html-api/class-wp-html-tag-processor.php | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 35d91fad3129c..05e03a3ccd87d 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5474,9 +5474,24 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { /** * Adds a new class name to the currently matched tag. * + * HTML class attributes are whitespace-separated token lists; a class + * name cannot contain whitespace. Whitespace in the value therefore + * splits it into multiple classes. HTML "ASCII whitespace" is U+0009 + * TAB, U+000A LF, U+000C FF, U+000D CR, and U+0020 SPACE. + * + * Examples: + * + * $p->add_class( 'wp-block' ); + * // Adds one class: "wp-block". + * + * $p->add_class( 'wp-block alignwide' ); + * // Adds two classes: "wp-block" and "alignwide", not a single + * // class named "wp-block alignwide". + * * @since 6.6.0 Subclassed for the HTML Processor. * - * @param string $class_name The class name to add. + * @param string $class_name The class name to add. Whitespace splits + * the value into multiple class names. * @return bool Whether the class was set to be added. */ public function add_class( $class_name ): bool { diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 77c1a471db5b1..7e739911bd025 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4537,9 +4537,24 @@ public function remove_attribute( $name ): bool { /** * Adds a new class name to the currently matched tag. * + * HTML class attributes are whitespace-separated token lists; a class + * name cannot contain whitespace. Whitespace in the value therefore + * splits it into multiple classes. HTML "ASCII whitespace" is U+0009 + * TAB, U+000A LF, U+000C FF, U+000D CR, and U+0020 SPACE. + * + * Examples: + * + * $p->add_class( 'wp-block' ); + * // Adds one class: "wp-block". + * + * $p->add_class( 'wp-block alignwide' ); + * // Adds two classes: "wp-block" and "alignwide", not a single + * // class named "wp-block alignwide". + * * @since 6.2.0 * - * @param string $class_name The class name to add. + * @param string $class_name The class name to add. Whitespace splits + * the value into multiple class names. * @return bool Whether the class was set to be added. */ public function add_class( $class_name ): bool { From 029e33db74a2c4222feaf7567876fc003bfdc8c8 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:29:00 +0200 Subject: [PATCH 02/14] HTML API: Trim `add_class()` whitespace note. Drop the inline enumeration of HTML ASCII whitespace code points and shorten the explanation; keep the worked single- and multi-class examples. --- src/wp-includes/html-api/class-wp-html-processor.php | 6 ++---- src/wp-includes/html-api/class-wp-html-tag-processor.php | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 05e03a3ccd87d..afd6b270dda96 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5474,10 +5474,8 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { /** * Adds a new class name to the currently matched tag. * - * HTML class attributes are whitespace-separated token lists; a class - * name cannot contain whitespace. Whitespace in the value therefore - * splits it into multiple classes. HTML "ASCII whitespace" is U+0009 - * TAB, U+000A LF, U+000C FF, U+000D CR, and U+0020 SPACE. + * Note that a value containing whitespace is added as *multiple* + * class names: a single class name cannot contain whitespace. * * Examples: * diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 7e739911bd025..575cacaba9261 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4537,10 +4537,8 @@ public function remove_attribute( $name ): bool { /** * Adds a new class name to the currently matched tag. * - * HTML class attributes are whitespace-separated token lists; a class - * name cannot contain whitespace. Whitespace in the value therefore - * splits it into multiple classes. HTML "ASCII whitespace" is U+0009 - * TAB, U+000A LF, U+000C FF, U+000D CR, and U+0020 SPACE. + * Note that a value containing whitespace is added as *multiple* + * class names: a single class name cannot contain whitespace. * * Examples: * From 1f855966367a5ddc2d2bde91a5933ee07a456a8f Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:30:34 +0200 Subject: [PATCH 03/14] HTML API: Name HTML ASCII whitespace in `add_class()` note. Identify the term and list the characters (tab, line feed, form feed, carriage return, space) so readers know exactly what splits a value into multiple class names. --- src/wp-includes/html-api/class-wp-html-processor.php | 2 ++ src/wp-includes/html-api/class-wp-html-tag-processor.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index afd6b270dda96..26c63af00b4d0 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5476,6 +5476,8 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { * * Note that a value containing whitespace is added as *multiple* * class names: a single class name cannot contain whitespace. + * HTML ASCII whitespace is tab, line feed, form feed, carriage + * return, and space. * * Examples: * diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 575cacaba9261..3330d916b3e0b 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4539,6 +4539,8 @@ public function remove_attribute( $name ): bool { * * Note that a value containing whitespace is added as *multiple* * class names: a single class name cannot contain whitespace. + * HTML ASCII whitespace is tab, line feed, form feed, carriage + * return, and space. * * Examples: * From d450ff98514c30c372ef5e3fe17697c9bd91f31f Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:31:25 +0200 Subject: [PATCH 04/14] HTML API: Quote the Infra spec for ASCII whitespace in `add_class()`. Replace the paraphrased whitespace list with a blockquote of the Infra Standard definition and add a `@see` link to the source. --- src/wp-includes/html-api/class-wp-html-processor.php | 7 +++++-- src/wp-includes/html-api/class-wp-html-tag-processor.php | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 26c63af00b4d0..9d215de2da66d 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5476,8 +5476,9 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { * * Note that a value containing whitespace is added as *multiple* * class names: a single class name cannot contain whitespace. - * HTML ASCII whitespace is tab, line feed, form feed, carriage - * return, and space. + * + * > ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, + * > or U+0020 SPACE. * * Examples: * @@ -5490,6 +5491,8 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { * * @since 6.6.0 Subclassed for the HTML Processor. * + * @see https://infra.spec.whatwg.org/#ascii-whitespace + * * @param string $class_name The class name to add. Whitespace splits * the value into multiple class names. * @return bool Whether the class was set to be added. diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 3330d916b3e0b..6bceee2871407 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4539,8 +4539,9 @@ public function remove_attribute( $name ): bool { * * Note that a value containing whitespace is added as *multiple* * class names: a single class name cannot contain whitespace. - * HTML ASCII whitespace is tab, line feed, form feed, carriage - * return, and space. + * + * > ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, + * > or U+0020 SPACE. * * Examples: * @@ -4553,6 +4554,8 @@ public function remove_attribute( $name ): bool { * * @since 6.2.0 * + * @see https://infra.spec.whatwg.org/#ascii-whitespace + * * @param string $class_name The class name to add. Whitespace splits * the value into multiple class names. * @return bool Whether the class was set to be added. From 34157b9d23031e93c9b8d38f7119f54c99a36b66 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:36:09 +0200 Subject: [PATCH 05/14] HTML API: Match `add_class()` docblocks across processors. Adopt the simpler note on the Tag Processor and mirror it on the HTML Processor so both `add_class()` docblocks read the same. --- src/wp-includes/html-api/class-wp-html-processor.php | 10 +++------- .../html-api/class-wp-html-tag-processor.php | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 9d215de2da66d..c08c49bd3687c 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5474,11 +5474,8 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { /** * Adds a new class name to the currently matched tag. * - * Note that a value containing whitespace is added as *multiple* - * class names: a single class name cannot contain whitespace. - * - * > ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, - * > or U+0020 SPACE. + * A `$class_name` value that contains ASCII whitespace separated strings + * will be added as is, resulting in multiple additional class names. * * Examples: * @@ -5486,8 +5483,7 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { * // Adds one class: "wp-block". * * $p->add_class( 'wp-block alignwide' ); - * // Adds two classes: "wp-block" and "alignwide", not a single - * // class named "wp-block alignwide". + * // Adds two classes: "wp-block" and "alignwide". * * @since 6.6.0 Subclassed for the HTML Processor. * diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 6bceee2871407..75192be82692b 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4537,11 +4537,8 @@ public function remove_attribute( $name ): bool { /** * Adds a new class name to the currently matched tag. * - * Note that a value containing whitespace is added as *multiple* - * class names: a single class name cannot contain whitespace. - * - * > ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, - * > or U+0020 SPACE. + * A `$class_name` value that contains ASCII whitespace separated strings + * will be added as is, resulting in multiple additional class names. * * Examples: * @@ -4549,8 +4546,7 @@ public function remove_attribute( $name ): bool { * // Adds one class: "wp-block". * * $p->add_class( 'wp-block alignwide' ); - * // Adds two classes: "wp-block" and "alignwide", not a single - * // class named "wp-block alignwide". + * // Adds two classes: "wp-block" and "alignwide". * * @since 6.2.0 * From a145ba5e71dddf1b19bc5de27a3ea5f5c57aea66 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:42:20 +0200 Subject: [PATCH 06/14] HTML API: Sharpen `add_class()` whitespace note. Rewrite the lead sentence so it parses on one read, and restore the explicit "not one class" cue on the multi-class example so the docblock directly contradicts the natural misreading. --- src/wp-includes/html-api/class-wp-html-processor.php | 7 ++++--- src/wp-includes/html-api/class-wp-html-tag-processor.php | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index c08c49bd3687c..50d05b2cecd3c 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5474,8 +5474,8 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { /** * Adds a new class name to the currently matched tag. * - * A `$class_name` value that contains ASCII whitespace separated strings - * will be added as is, resulting in multiple additional class names. + * If `$class_name` contains ASCII whitespace, each whitespace-separated + * substring is added as its own class name. * * Examples: * @@ -5483,7 +5483,8 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { * // Adds one class: "wp-block". * * $p->add_class( 'wp-block alignwide' ); - * // Adds two classes: "wp-block" and "alignwide". + * // Adds two classes: "wp-block" and "alignwide" + * // (not one class "wp-block alignwide"). * * @since 6.6.0 Subclassed for the HTML Processor. * diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 75192be82692b..e8f29afa8721a 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4537,8 +4537,8 @@ public function remove_attribute( $name ): bool { /** * Adds a new class name to the currently matched tag. * - * A `$class_name` value that contains ASCII whitespace separated strings - * will be added as is, resulting in multiple additional class names. + * If `$class_name` contains ASCII whitespace, each whitespace-separated + * substring is added as its own class name. * * Examples: * @@ -4546,7 +4546,8 @@ public function remove_attribute( $name ): bool { * // Adds one class: "wp-block". * * $p->add_class( 'wp-block alignwide' ); - * // Adds two classes: "wp-block" and "alignwide". + * // Adds two classes: "wp-block" and "alignwide" + * // (not one class "wp-block alignwide"). * * @since 6.2.0 * From 887575d5f58770c640b301702b016b1a03796aa0 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:44:49 +0200 Subject: [PATCH 07/14] HTML API: Reframe `add_class()` whitespace note around "verbatim". MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State directly that the value is added verbatim and may yield multiple class names in the element's class list — "may" because trailing or leading whitespace alone still produces a single class. --- src/wp-includes/html-api/class-wp-html-processor.php | 5 +++-- src/wp-includes/html-api/class-wp-html-tag-processor.php | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 50d05b2cecd3c..029edcced4e9b 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5474,8 +5474,9 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { /** * Adds a new class name to the currently matched tag. * - * If `$class_name` contains ASCII whitespace, each whitespace-separated - * substring is added as its own class name. + * A `$class_name` containing ASCII whitespace is added verbatim. This + * may result in multiple class names being added to the element's + * class list. * * Examples: * diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index e8f29afa8721a..97a8356fbb18e 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4537,8 +4537,9 @@ public function remove_attribute( $name ): bool { /** * Adds a new class name to the currently matched tag. * - * If `$class_name` contains ASCII whitespace, each whitespace-separated - * substring is added as its own class name. + * A `$class_name` containing ASCII whitespace is added verbatim. This + * may result in multiple class names being added to the element's + * class list. * * Examples: * From ee6d916542cacf954f3cb2fb74b3f6f55e7f688b Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:48:23 +0200 Subject: [PATCH 08/14] HTML API: Drop Infra `@see` and revert `@param` on `add_class()`. The body prose already names ASCII whitespace and explains the multi-class effect, so the `@see` link and expanded `@param` description are redundant. Restore the original `@param` text. --- src/wp-includes/html-api/class-wp-html-processor.php | 5 +---- src/wp-includes/html-api/class-wp-html-tag-processor.php | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 029edcced4e9b..da44d4b8853c0 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5489,10 +5489,7 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { * * @since 6.6.0 Subclassed for the HTML Processor. * - * @see https://infra.spec.whatwg.org/#ascii-whitespace - * - * @param string $class_name The class name to add. Whitespace splits - * the value into multiple class names. + * @param string $class_name The class name to add. * @return bool Whether the class was set to be added. */ public function add_class( $class_name ): bool { diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 97a8356fbb18e..ba29f4f000107 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4552,10 +4552,7 @@ public function remove_attribute( $name ): bool { * * @since 6.2.0 * - * @see https://infra.spec.whatwg.org/#ascii-whitespace - * - * @param string $class_name The class name to add. Whitespace splits - * the value into multiple class names. + * @param string $class_name The class name to add. * @return bool Whether the class was set to be added. */ public function add_class( $class_name ): bool { From 1e3527b6c80e17b25363c82c18cdb5eebef8876f Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:49:27 +0200 Subject: [PATCH 09/14] HTML API: Trim disambiguation parenthetical from `add_class()` example. --- src/wp-includes/html-api/class-wp-html-processor.php | 3 +-- src/wp-includes/html-api/class-wp-html-tag-processor.php | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index da44d4b8853c0..4319919005941 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5484,8 +5484,7 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { * // Adds one class: "wp-block". * * $p->add_class( 'wp-block alignwide' ); - * // Adds two classes: "wp-block" and "alignwide" - * // (not one class "wp-block alignwide"). + * // Adds two classes: "wp-block" and "alignwide". * * @since 6.6.0 Subclassed for the HTML Processor. * diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index ba29f4f000107..0a33be3dacdc5 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4547,8 +4547,7 @@ public function remove_attribute( $name ): bool { * // Adds one class: "wp-block". * * $p->add_class( 'wp-block alignwide' ); - * // Adds two classes: "wp-block" and "alignwide" - * // (not one class "wp-block alignwide"). + * // Adds two classes: "wp-block" and "alignwide". * * @since 6.2.0 * From e84c734866745282ec6b87629841e041064b75ee Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:51:17 +0200 Subject: [PATCH 10/14] =?UTF-8?q?HTML=20API:=20Correct=20`add=5Fclass()`?= =?UTF-8?q?=20note=20=E2=80=94=20whitespace=20is=20verbatim,=20not=20the?= =?UTF-8?q?=20value.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The class name itself is HTML-escaped when written into the attribute; only the whitespace passes through unchanged, which is what splits the value into multiple class names. --- src/wp-includes/html-api/class-wp-html-processor.php | 5 ++--- src/wp-includes/html-api/class-wp-html-tag-processor.php | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 4319919005941..c95ea56345131 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5474,9 +5474,8 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { /** * Adds a new class name to the currently matched tag. * - * A `$class_name` containing ASCII whitespace is added verbatim. This - * may result in multiple class names being added to the element's - * class list. + * Whitespace in `$class_name` is preserved verbatim. This may result + * in multiple class names being added to the element's class list. * * Examples: * diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 0a33be3dacdc5..d7a9203a43f86 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -4537,9 +4537,8 @@ public function remove_attribute( $name ): bool { /** * Adds a new class name to the currently matched tag. * - * A `$class_name` containing ASCII whitespace is added verbatim. This - * may result in multiple class names being added to the element's - * class list. + * Whitespace in `$class_name` is preserved verbatim. This may result + * in multiple class names being added to the element's class list. * * Examples: * From 2a842339beb0e1a7f8ad2555965c692a64c126f8 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:55:26 +0200 Subject: [PATCH 11/14] HTML API: Short-circuit `has_class()` and `remove_class()` on whitespace. A class name cannot contain whitespace; the HTML class attribute is a whitespace-separated token list. Pass-through whitespace in the `$wanted_class` / `$class_name` argument can therefore never match a real token, so reject it up front and return false. Document the new behavior in both the Tag Processor and HTML Processor docblocks. Behavior note: previously, `add_class('a b')` followed by `remove_class('a b')` was an accidental no-op because the REMOVE overwrote the ADD in the keyed updates map. With the short-circuit the REMOVE is rejected and the ADD survives, so the element gets classes "a" and "b" as the lone `add_class()` call would have produced. --- .../html-api/class-wp-html-processor.php | 8 ++++++++ .../html-api/class-wp-html-tag-processor.php | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index c95ea56345131..4271221c63792 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5497,7 +5497,11 @@ public function add_class( $class_name ): bool { /** * Removes a class name from the currently matched tag. * + * Whitespace in `$class_name` never matches: an HTML class name + * cannot contain whitespace, so the removal is short-circuited. + * * @since 6.6.0 Subclassed for the HTML Processor. + * @since 7.1.0 Returns false when `$class_name` contains ASCII whitespace. * * @param string $class_name The class name to remove. * @return bool Whether the class was set to be removed. @@ -5509,7 +5513,11 @@ public function remove_class( $class_name ): bool { /** * Returns if a matched tag contains the given ASCII case-insensitive class name. * + * Whitespace in `$wanted_class` never matches: an HTML class name + * cannot contain whitespace, so the lookup is short-circuited. + * * @since 6.6.0 Subclassed for the HTML Processor. + * @since 7.1.0 Returns false when `$wanted_class` contains ASCII whitespace. * * @todo When reconstructing active formatting elements with attributes, find a way * to indicate if the virtually-reconstructed formatting elements contain the diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index d7a9203a43f86..db5f879bca47a 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -1232,7 +1232,11 @@ public function class_list() { /** * Returns if a matched tag contains the given ASCII case-insensitive class name. * + * Whitespace in `$wanted_class` never matches: an HTML class name + * cannot contain whitespace, so the lookup is short-circuited. + * * @since 6.4.0 + * @since 7.1.0 Returns false when `$wanted_class` contains ASCII whitespace. * * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. * @return bool|null Whether the matched tag contains the given class name, or null if not matched. @@ -1242,6 +1246,10 @@ public function has_class( $wanted_class ): ?bool { return null; } + if ( false !== strpbrk( $wanted_class, " \t\f\r\n" ) ) { + return false; + } + $case_insensitive = self::QUIRKS_MODE === $this->compat_mode; $wanted_length = strlen( $wanted_class ); @@ -4590,7 +4598,11 @@ public function add_class( $class_name ): bool { /** * Removes a class name from the currently matched tag. * + * Whitespace in `$class_name` never matches: an HTML class name + * cannot contain whitespace, so the removal is short-circuited. + * * @since 6.2.0 + * @since 7.1.0 Returns false when `$class_name` contains ASCII whitespace. * * @param string $class_name The class name to remove. * @return bool Whether the class was set to be removed. @@ -4603,6 +4615,10 @@ public function remove_class( $class_name ): bool { return false; } + if ( false !== strpbrk( $class_name, " \t\f\r\n" ) ) { + return false; + } + if ( self::QUIRKS_MODE !== $this->compat_mode ) { $this->classname_updates[ $class_name ] = self::REMOVE_CLASS; return true; From c0b016827b1166d49db64720dd0278186359fb18 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 16:58:53 +0200 Subject: [PATCH 12/14] HTML API: Drop `has_class()` `@since` for the whitespace short-circuit. The short-circuit is purely a performance refinement of an already guaranteed result (no token can contain whitespace), not a meaningful behavior change worth versioning. Keep the inline docblock note. --- src/wp-includes/html-api/class-wp-html-processor.php | 1 - src/wp-includes/html-api/class-wp-html-tag-processor.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 4271221c63792..0096d191754b5 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5517,7 +5517,6 @@ public function remove_class( $class_name ): bool { * cannot contain whitespace, so the lookup is short-circuited. * * @since 6.6.0 Subclassed for the HTML Processor. - * @since 7.1.0 Returns false when `$wanted_class` contains ASCII whitespace. * * @todo When reconstructing active formatting elements with attributes, find a way * to indicate if the virtually-reconstructed formatting elements contain the diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index db5f879bca47a..1b4f88e6ddd13 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -1236,7 +1236,6 @@ public function class_list() { * cannot contain whitespace, so the lookup is short-circuited. * * @since 6.4.0 - * @since 7.1.0 Returns false when `$wanted_class` contains ASCII whitespace. * * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. * @return bool|null Whether the matched tag contains the given class name, or null if not matched. From 02ee5153153db5d3591f0ec6a1dae5e9b8de2c40 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 17:06:03 +0200 Subject: [PATCH 13/14] HTML API: Warn via `_doing_it_wrong()` on whitespace in class lookups. Add a `_doing_it_wrong()` call on the whitespace short-circuit in `has_class()` and `remove_class()` so developers passing a value that can never identify a class token see an actionable warning rather than a silent false. --- .../html-api/class-wp-html-tag-processor.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 1b4f88e6ddd13..f6ed6593779c3 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -1246,6 +1246,11 @@ public function has_class( $wanted_class ): ?bool { } if ( false !== strpbrk( $wanted_class, " \t\f\r\n" ) ) { + _doing_it_wrong( + __METHOD__, + __( 'A class name cannot contain ASCII whitespace.' ), + '7.1.0' + ); return false; } @@ -4615,6 +4620,11 @@ public function remove_class( $class_name ): bool { } if ( false !== strpbrk( $class_name, " \t\f\r\n" ) ) { + _doing_it_wrong( + __METHOD__, + __( 'A class name cannot contain ASCII whitespace.' ), + '7.1.0' + ); return false; } From 66dc5677c3a91dd1128baa3d63429269fac6918e Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 2 Jun 2026 17:10:22 +0200 Subject: [PATCH 14/14] HTML API: Drop whitespace prose from `has_class()` / `remove_class()` docblocks. The `_doing_it_wrong()` warning now communicates the constraint at the point of misuse, so the docblock note is redundant. --- src/wp-includes/html-api/class-wp-html-processor.php | 6 ------ src/wp-includes/html-api/class-wp-html-tag-processor.php | 6 ------ 2 files changed, 12 deletions(-) diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 0096d191754b5..244b95dc9c7c0 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -5497,9 +5497,6 @@ public function add_class( $class_name ): bool { /** * Removes a class name from the currently matched tag. * - * Whitespace in `$class_name` never matches: an HTML class name - * cannot contain whitespace, so the removal is short-circuited. - * * @since 6.6.0 Subclassed for the HTML Processor. * @since 7.1.0 Returns false when `$class_name` contains ASCII whitespace. * @@ -5513,9 +5510,6 @@ public function remove_class( $class_name ): bool { /** * Returns if a matched tag contains the given ASCII case-insensitive class name. * - * Whitespace in `$wanted_class` never matches: an HTML class name - * cannot contain whitespace, so the lookup is short-circuited. - * * @since 6.6.0 Subclassed for the HTML Processor. * * @todo When reconstructing active formatting elements with attributes, find a way diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index f6ed6593779c3..53a207ec04cda 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -1232,9 +1232,6 @@ public function class_list() { /** * Returns if a matched tag contains the given ASCII case-insensitive class name. * - * Whitespace in `$wanted_class` never matches: an HTML class name - * cannot contain whitespace, so the lookup is short-circuited. - * * @since 6.4.0 * * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. @@ -4602,9 +4599,6 @@ public function add_class( $class_name ): bool { /** * Removes a class name from the currently matched tag. * - * Whitespace in `$class_name` never matches: an HTML class name - * cannot contain whitespace, so the removal is short-circuited. - * * @since 6.2.0 * @since 7.1.0 Returns false when `$class_name` contains ASCII whitespace. *