Skip to content

Script Loader: Add an API for filtering inline style attributes - #13250

Open
Sukhendu2002 wants to merge 32 commits into
WordPress:trunkfrom
Sukhendu2002:ticket/51325-inline-style-attributes
Open

Sukhendu2002 wants to merge 32 commits into
WordPress:trunkfrom
Sukhendu2002:ticket/51325-inline-style-attributes

Conversation

@Sukhendu2002

@Sukhendu2002 Sukhendu2002 commented Aug 24, 2026 •

Copy link
Copy Markdown

What this PR does

Adds a structured API for generating and filtering inline <style> tags, mirroring the existing inline script tag API:

  • Adds wp_get_inline_style_tag( string $data, array $attributes = array() ).
  • Adds wp_print_inline_style_tag().
  • Adds the wp_inline_style_attributes filter, which receives the attributes and the CSS.
  • Uses WP_HTML_Tag_Processor for safe attribute and CSS handling, including escaping of a closing </style> in the CSS.
  • Documents $attributes as array<string, string|bool>, matching wp_get_inline_script_tag().

This allows plugins to add attributes such as CSP nonces without modifying complete HTML strings. The script portion of the original ticket is already covered by wp_inline_script_attributes, so this PR focuses on the remaining inline style functionality.

Core STYLE tags routed through the API

All <style> tags constructed by core now go through the new API, so that they all receive the filtered attributes. This is especially important in the admin, where a CSP is most valuable.

  • WP_Styles output for normal, no-source, and concatenated inline styles, plus the hoisted extra block library styles and block support styles in script-loader.php.
  • Theme features: custom background, custom CSS, and custom logo header styles.
  • Font faces printed by WP_Font_Face.
  • Recent Comments widget styles.
  • Default gallery shortcode styles.
  • Legacy Widget block previews (block render and REST preview iframe).
  • Default wp_die() handler, with a fallback to a literal <style> when it is called before script-loader.php and the HTML API are loaded.
  • Interim login, Multisite signup and activation pages.
  • Admin: plugin information banner, site icon preview in General Settings.
  • Customizer: preview loading style, highlighted widget style, site icon control template, and the site icon crop preview media template.
  • Deprecated functions: emoji and embed styles, admin bar styles, skip link styles, the auto-sizes contain-intrinsic-size fix, and graceful_fail().
  • Theme compat header.

Literal CSS is passed as a <<<'CSS' nowdoc (or a <<<CSS heredoc when interpolating), as is already done for <<<'JS' with inline scripts, so IDEs apply CSS syntax highlighting.

URLs in url() are escaped late with esc_url_raw() rather than esc_url(), since HTML character references are not decoded in STYLE elements, and are wrapped in double quotes, since esc_url_raw() strips double quotes but not single quotes or parentheses.

Intentionally not changed

  • Bundled themes and Hello Dolly, which support older WordPress versions.
  • src/wp-includes/build/pages/*, which is generated from Gutenberg.
  • The personal data export file in privacy-tools.php, since it is a static HTML file where a per-request nonce does not belong.

Testing

New tests in tests/phpunit/tests/dependencies/wpInlineStyleTag.php, tests/phpunit/tests/dependencies/styles.php, and tests/phpunit/tests/template.php use assertEqualHTML() and WP_HTML_Tag_Processor to verify the output, including that the core inline styles have the filtered attributes.

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

Use of AI Tools

AI assistance: Yes
Tool(s): OpenAI Codex
Model(s): GPT-5.6
Used for: Initial code skeleton, test suggestions and code review; final implementation and tests were reviewed and edited by me.

Additional commits were made with Claude Code (Claude Opus 5.5) at the direction of @westonruter, to make the tests more robust with assertEqualHTML() and WP_HTML_Tag_Processor, to route the remaining core <style> tags through the new API, and to address code review findings. The changes were reviewed by @westonruter.


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.

🤖 Generated with Claude Code

@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.

@Sukhendu2002
Sukhendu2002 marked this pull request as ready for review August 24, 2026 12:43
@github-actions

github-actions Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

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.

Core Committers: Use this line as a base for the props when committing in SVN:

Props sukhendu2002, westonruter.

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

Comment thread tests/phpunit/tests/dependencies/wpInlineStyleTag.php Outdated
Comment thread tests/phpunit/tests/dependencies/wpInlineStyleTag.php Outdated
Comment thread src/wp-includes/script-loader.php
Comment thread src/wp-includes/script-loader.php
Comment thread src/wp-includes/script-loader.php
Comment thread src/wp-includes/script-loader.php Outdated
Comment thread src/wp-includes/script-loader.php Outdated
Sukhendu2002 and others added 19 commits October 7, 2026 13:57
Replace substring and exact-string assertions with assertEqualHTML()
for fully deterministic output, so attribute order, quoting style and
self-closing syntax no longer cause spurious failures.

Use WP_HTML_Tag_Processor where full-markup comparison is a poor fit:
the core concatenation test (whose load-styles.php URL is noisy) and
the closing-tag escaping test, which now checks that the CSS cannot
break out of the STYLE element rather than pinning the exact escape
sequence.

Also verify the attributes passed to the wp_inline_style_attributes
filter during normal style output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Widen the documented attribute value type from string|true to
string|bool for wp_get_inline_style_tag(), wp_print_inline_style_tag()
and the wp_inline_style_attributes filter. This matches
wp_get_script_tag() and wp_get_inline_script_tag(), and reflects that
a false value omits the attribute, as the filter docblock already
describes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When wp_hoist_late_printed_styles() splits CSS added via
wp_add_inline_style( 'wp-block-library', ... ) into a separate
wp-block-library-inline-css-extra STYLE tag, build that tag with
wp_get_inline_style_tag() so it receives the attributes from the
wp_inline_style_attributes filter. Previously the original tag had a
filtered nonce but the split-off tag did not, so a nonce-based Content
Security Policy would block the CSS after hoisting.

Add a hoisting test data set which filters in a nonce, and loosen the
global styles placeholder pattern so that it still matches the
placeholder STYLE tag when it has additional attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In WP_Styles::do_item(), the inline style for a handle is already
obtained up front via print_inline_style( $handle, false ). When not
concatenating, calling print_inline_style( $handle ) again re-read the
after data and rebuilt the sourceURL comment. Instead, append the inline
style tag to the LINK tag once, and then print or buffer the result,
which also removes the duplicated wp_get_inline_style_tag() call between
the concat and non-concat branches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the remaining hand-built front-end STYLE tags via
wp_print_inline_style_tag() so that they also receive attributes from
the wp_inline_style_attributes filter, such as a nonce for a Content
Security Policy:

* Custom background (including the empty Customizer preview tag).
* Additional CSS from the Customizer.
* Custom logo header text hiding.
* Block support styles.
* Font faces.
* Recent Comments widget.

Add a data-provider test covering each STYLE tag printed outside of
WP_Styles, and extend the hoisting test data set with a filtered nonce
to assert that every STYLE tag in the page has it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The core inline style data sets add and remove theme support, which is
not restored automatically between tests. The leaked custom-background
support caused WP_Test_REST_Themes_Controller to fail with an undefined
$custom_background global when run after these tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the error page styles with wp_print_inline_style_tag() so that
they receive attributes from the wp_inline_style_attributes filter. Since
wp_die() may be called before script-loader.php and the HTML API are
loaded, fall back to a literal STYLE tag in that case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the transparent background style for the interim login modal with
wp_print_inline_style_tag() so that it receives filtered attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the styles for wp-signup.php and wp-activate.php with
wp_print_inline_style_tag() so that they receive filtered attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the banner background styles in the plugin information modal
with wp_print_inline_style_tag() so that they receive filtered
attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the site icon URL custom property with wp_print_inline_style_tag()
so that it receives filtered attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the Customizer preview loading styles, the highlighted widget
styles and the site icon control template styles with
wp_print_inline_style_tag() so that they receive filtered attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the site icon URL custom property in the tmpl-site-icon-preview-crop
template with wp_print_inline_style_tag() so that it receives filtered
attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build the gallery shortcode's default STYLE tag with
wp_get_inline_style_tag() so that it receives filtered attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the reset styles in the Legacy Widget block preview and the REST
API widget preview iframe with wp_print_inline_style_tag() so that they
receive filtered attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
westonruter and others added 11 commits October 10, 2026 15:44
Print the STYLE tags in print_embed_styles(), print_emoji_styles(),
wp_admin_bar_header(), _admin_bar_bump_cb(),
the_block_template_skip_link(), wp_print_auto_sizes_contain_css_fix()
and graceful_fail() with the inline style tag API so that they receive
filtered attributes when still called by plugins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Print the Kubrick background style in the theme compatibility header
template with wp_print_inline_style_tag() so that it receives filtered
attributes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass the CSS for wp_admin_bar_header() and
wp_print_auto_sizes_contain_css_fix() as a CSS nowdoc, like the other
inline styles, so that IDEs apply CSS syntax highlighting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pass the CSS as a CSS nowdoc, like the other inline styles, so that IDEs
apply CSS syntax highlighting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HTML character references are not decoded in STYLE elements, so the
&WordPress#38; produced by esc_url() for a URL with multiple query parameters
would end up in the CSS literally. Escape with esc_url_raw() instead,
and wrap the URLs in double quotes: esc_url_raw() strips double quotes
but not single quotes or parentheses, so an unquoted url() token could
otherwise be terminated by the URL. Name the variables to make clear
they are already escaped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HTML character references are not decoded in STYLE elements, so escape
the URL with esc_url_raw() instead of esc_url(). Wrap it in double
quotes, since esc_url_raw() strips double quotes but not single quotes,
which could otherwise terminate the CSS string. Name the variable to
make clear it is already escaped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HTML character references are not decoded in STYLE elements, so obtain
the raw stylesheet directory URL and text direction rather than their
display-filtered values, and escape the resulting URL with
esc_url_raw(). Pass the CSS as a heredoc for syntax highlighting, and
name the variable to make clear it is already escaped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Match the quoting used for url() values in the other inline styles,
including the site icon crop preview template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Call esc_url_raw() as a variable function inside the heredoc
interpolation, so that the URLs are escaped at the point of output
rather than in separate variables beforehand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Getting the raw stylesheet directory URL skipped the bloginfo_url filter,
which plugins use to rewrite asset URLs, for example to a CDN. The
display context applies no escaping to URLs, so esc_url_raw() remains
correct.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The template is a sprintf() format, so a '%' in an attribute value added
via the wp_inline_style_attributes filter would be treated as a
conversion specification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants