Skip to content

Custom CSS: Decode HTML entities in nested selectors#75654

Closed
glendaviesnz wants to merge 1 commit intotrunkfrom
custom-css-nested-selector-fix
Closed

Custom CSS: Decode HTML entities in nested selectors#75654
glendaviesnz wants to merge 1 commit intotrunkfrom
custom-css-nested-selector-fix

Conversation

@glendaviesnz
Copy link
Copy Markdown
Contributor

Summary

  • Block serialisation may encode & as & in the stored custom CSS attribute, breaking nested CSS selectors like &:hover and & .child
  • Adds html_entity_decode() before CSS processing so process_blocks_custom_css() handles nested selectors correctly
  • Adds a unit test verifying that encoded ampersands in nested selectors are decoded and produce valid output CSS

Test plan

  • Verify blocks with custom CSS using nested selectors (&:hover, & .child-class) render correctly on the front end
  • Verify plain custom CSS (no nested selectors) continues to work as before
  • Verify CSS containing HTML markup is still rejected
  • CI PHP unit tests pass

Block serialisation may encode `&` as `&` in the stored CSS
attribute, which breaks nested CSS selectors (e.g. `&:hover`).
This decodes HTML entities before processing so that
`process_blocks_custom_css()` handles nested selectors correctly.
@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 18, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: glendaviesnz <glendaviesnz@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@glendaviesnz glendaviesnz self-assigned this Feb 18, 2026
@glendaviesnz glendaviesnz added the [Type] Bug An existing feature does not function as intended label Feb 18, 2026
@github-actions
Copy link
Copy Markdown

Flaky tests detected in 9fca8aa.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/22124473321
📝 Reported issues:

@ramonjd
Copy link
Copy Markdown
Member

ramonjd commented Feb 18, 2026

I've tested with a bunch of selectors, e.g

&::before { content: "test"; }
&::after { display: block; }
&::placeholder { color: gray; }
&:focus { outline: 2px solid blue; }
&:active { opacity: 0.8; }
&:focus-visible { outline: 3px solid; }
&:not(.disabled) { cursor: pointer; }
&:is(h1, h2, h3) { font-weight: bold; }
&:has(> p) { display: flex; color:yellow;}
&:nth-child(1n) { background: #eee; }
&.wp-block-group { background: blue; }    /* appended class */
&[class=*] {color:red; }     /* attribute selector */
&[class*="wp-block-group"] { color: red; }

And things look fine in the editor and frontend.

The frontend handles the following well:

&::before { content: "&copy;"; }
Screenshot 2026-02-18 at 2 53 27 pm

The editor not so much. I don't think this is strictly related to this change.

I might be thinking to far into this, but also wondering if we need to support the following:

&::before { content: "&amp;copy;"; }
Screenshot 2026-02-18 at 2 57 53 pm

I don't want to distract, this is an improvement on the is state.

@ramonjd ramonjd added the CSS Styling Related to editor and front end styles, CSS-specific issues. label Feb 18, 2026
Copy link
Copy Markdown
Member

@ramonjd ramonjd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix @glendaviesnz

I think this change is fine in itself to address the bug, but I would like a confidence check from folks who have been thinking about string sanitization. E.g., WordPress/wordpress-develop#10641

It might not make Beta 1, but we can flag it for backporting to a RC

// Decode HTML entities (e.g. &amp; → &) that may be introduced
// during block serialisation so that nested selectors using &
// are processed correctly by process_blocks_custom_css().
$custom_css = html_entity_decode( $custom_css, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if a surgical str_replace( '&amp;', '&', $custom_css ) would be more minimal and do the same job.

Not a huge deal I think.

Soft ping to @dmsnell and @sirreal for confidence check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it looks like the defaul encoding is actuall \u0026 so it seems like it is something in our CSS sanitisation pipeline that may be causing this, so we may be able to apply a local fix for this, so switching to draft until I have clarified that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pinging me on this. I'm not sure what the resolution was, but it seems like this change was not needed after all?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it seems like this change was not needed after all?

Seems like it, and thanks for getting back to us. Did you resolve it in the end @glendaviesnz?

}

// Validate CSS doesn't contain HTML markup (same validation as global styles REST API).
if ( preg_match( '#</?\w+#', $custom_css ) ) {
Copy link
Copy Markdown
Member

@ramonjd ramonjd Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix now causes &lt;script&gt; to be decoded and then rejected by this HTML check. Maybe this behavior is worth asserting explicitly. If the order of operations were ever accidentally swapped, this test would catch it.

public function test_custom_css_rejects_html_entity_encoded_tags() {
    // Before this fix, &lt;script&gt; would bypass the HTML check
    // because no literal '<' existed. Now decode runs first.
    $parsed_block = array(
        'blockName' => 'test/custom-css-encoded-amp',
        'attrs'     => array( 'style' => array( 'css' => '&lt;script&gt;alert(1)&lt;/script&gt;' ) ),
    );
    $result = gutenberg_render_custom_css_support_styles( $parsed_block );
    $this->assertArrayNotHasKey( 'className', $result['attrs'],
        'HTML-entity-encoded script tags must be rejected after decoding.' );
}

@glendaviesnz glendaviesnz marked this pull request as draft February 18, 2026 04:24
@glendaviesnz
Copy link
Copy Markdown
Contributor Author

Exploring a local fix for this it my be caused by a custom sanitization pipeline - so switched to draft while i clarify that

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

Labels

CSS Styling Related to editor and front end styles, CSS-specific issues. [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants