Skip to content

HTML API: Respect enqueued class updates when listing attribute names - #14496

Draft
sirreal wants to merge 1 commit into
WordPress:trunkfrom
sirreal:html-api/names-with-prefix-class-updates
Draft

sirreal wants to merge 1 commit into
WordPress:trunkfrom
sirreal:html-api/names-with-prefix-class-updates

Conversation

@sirreal

@sirreal sirreal commented Oct 19, 2026

Copy link
Copy Markdown
Member

get_attribute_names_with_prefix() can disagree with get_updated_html() about the class attribute when add_class() or remove_class() follows an enqueued set_attribute( 'class', ... ) or remove_attribute( 'class' ) on the same tag. This is in released WordPress since r62960 (7.1.0), which made the method read enqueued updates.

$p = new WP_HTML_Tag_Processor( '<div class="a">' );
$p->next_tag();
$p->remove_attribute( 'class' ); // Working input: $p->remove_class( 'a' );
$p->add_class( 'b' );
var_dump( $p->get_attribute_names_with_prefix( '' ), $p->get_updated_html() );
remove_class( 'a' ) remove_attribute( 'class' )
get_attribute_names_with_prefix( '' ) array( 'class' ) array()
get_updated_html() <div class="b"> <div class="b">

The reverse also happens: on <div>, set_attribute( 'class', 'a' ) then remove_class( 'a' ) reports class, and the output has no class attribute.

The method flushes class name updates only when they could add or remove the class attribute: a removal when the tag has the attribute, an addition when it does not. It decided whether the tag has the attribute from the input document alone. It now uses the enqueued class attribute update when one exists, which is the value class_name_updates_to_attributes_updates() starts from.

Every sequence of up to three calls to add_class(), remove_class(), remove_attribute( 'class' ) and set_attribute( 'class', ... ), on tags with no, an empty, a boolean, a single and a duplicated class attribute, was compared with a new Tag Processor's reading of get_updated_html(). On trunk 254 of 5,550 sequences disagree; with this change none do, and none of 55,550 sequences of up to four calls. get_attribute( 'class' ) agrees in every sequence on both. The new data provider test has a case for each kind of mismatch.

Trac ticket: https://core.trac.wordpress.org/ticket/65967

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Diagnosis, the sequence comparison, tests, implementation and description drafting.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

`get_attribute_names_with_prefix()` flushes enqueued `add_class()` and `remove_class()` calls only when they could add or remove the `class` attribute. It decided whether the tag has a `class` attribute from the input document alone and ignored an enqueued `set_attribute()` or `remove_attribute()` for `class`, so two kinds of sequence gave the wrong answer:

- The input has a `class` attribute, `remove_attribute( 'class' )` or `set_attribute( 'class', false )` enqueues its removal, and `add_class()` follows. The method did not flush, found the removal, and omitted `class`, while `get_updated_html()` produces a `class` attribute.
- The input has no `class` attribute, `set_attribute( 'class', ... )` enqueues one, and `remove_class()` removes every class in it. The method did not flush, found the addition, and reported `class`, while `get_updated_html()` produces no `class` attribute.

The method now uses the enqueued `class` attribute update, when one exists, to decide whether the attribute exists.

Every sequence of up to three calls to `add_class()`, `remove_class()`, `remove_attribute( 'class' )` and `set_attribute( 'class', ... )` was compared, on tags with no, an empty, a boolean, a single and a duplicated `class` attribute, against a new Tag Processor's reading of `get_updated_html()`. Before this change 254 of 5,550 sequences disagreed; after it, none do. `get_attribute( 'class' )` agreed in every sequence before and after.

Follow-up to [62960].

Props jonsurrell.
See #65967.
@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant