Commit f8d212b06a for wordpress.org

commit f8d212b06ab5a1e3e0cd80e4e3bb0a71c362df1f
Author: jonsurrell <jonsurrell@git.wordpress.org>
Date:   Thu Oct 15 07:56:50 2026 +0000

    Editor: Cache the theme.json data merged from all origins.

    `WP_Theme_JSON_Resolver::get_merged_data()` merged all origins on every call, including once for each rendered block with an `is-style-*` class. Cache the result until a block is registered or `wp_clean_theme_json_cache()` is called, and remove the redundant cache in `wp_get_global_settings()`. Theme support changes made after the first merge require `wp_clean_theme_json_cache()`.

    Backports https://github.com/WordPress/gutenberg/pull/81979.

    Developed in: https://github.com/WordPress/wordpress-develop/pull/14071

    Props jonsurrell, peterwilsoncc, isabel_brison, ramonopoly.
    Fixes #66280. See #62249.

    Built from https://develop.svn.wordpress.org/trunk@64354


    git-svn-id: http://core.svn.wordpress.org/trunk@63504 1a063a9b-81f0-0310-95a4-ce76da25c4cd

diff --git a/wp-includes/block-supports/layout.php b/wp-includes/block-supports/layout.php
index 0162ae8d5a..dc3b56c017 100644
--- a/wp-includes/block-supports/layout.php
+++ b/wp-includes/block-supports/layout.php
@@ -1022,8 +1022,6 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false
  * @return string Filtered block content.
  */
 function wp_render_layout_support_flag( $block_content, $block ) {
-	static $global_styles = null;
-
 	$block_type            = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
 	$block_supports_layout = block_has_support( $block_type, 'layout', false ) || block_has_support( $block_type, '__experimentalLayout', false );
 	$style_attr            = $block['attrs']['style'] ?? array();
@@ -1241,10 +1239,8 @@ function wp_render_layout_support_flag( $block_content, $block ) {

 		// Get default blockGap value from global styles for use in layouts like grid.
 		// Check style variation first, then block-specific styles, then fall back to root styles.
-		$block_name = $block['blockName'] ?? '';
-		if ( null === $global_styles ) {
-			$global_styles = wp_get_global_styles();
-		}
+		$block_name    = $block['blockName'] ?? '';
+		$global_styles = wp_get_global_styles();

 		// Check if the block has an active style variation with a blockGap value.
 		// Only check the registry if the className contains a variation class to avoid unnecessary lookups.
diff --git a/wp-includes/class-wp-theme-json-resolver.php b/wp-includes/class-wp-theme-json-resolver.php
index 05eedbcd40..c8129e1ebc 100644
--- a/wp-includes/class-wp-theme-json-resolver.php
+++ b/wp-includes/class-wp-theme-json-resolver.php
@@ -24,6 +24,7 @@ class WP_Theme_JSON_Resolver {
 	 * Container for keep track of registered blocks.
 	 *
 	 * @since 6.1.0
+	 * @since 7.2.0 Added the `merged` entry.
 	 * @var array
 	 */
 	protected static $blocks_cache = array(
@@ -31,8 +32,17 @@ class WP_Theme_JSON_Resolver {
 		'blocks' => array(),
 		'theme'  => array(),
 		'user'   => array(),
+		'merged' => array(),
 	);

+	/**
+	 * Merged data keyed by origin.
+	 *
+	 * @since 7.2.0
+	 * @var array
+	 */
+	protected static $merged = array();
+
 	/**
 	 * Container for data coming from core.
 	 *
@@ -192,9 +202,11 @@ class WP_Theme_JSON_Resolver {
 	 * Checks whether the registered blocks were already processed for this origin.
 	 *
 	 * @since 6.1.0
+	 * @since 7.2.0 Added the 'merged' origin.
 	 *
 	 * @param string $origin Data source for which to cache the blocks.
-	 *                       Valid values are 'core', 'blocks', 'theme', and 'user'.
+	 *                       Valid values are 'core', 'blocks', 'theme', 'user',
+	 *                       and 'merged'.
 	 * @return bool True on success, false otherwise.
 	 */
 	protected static function has_same_registered_blocks( $origin ) {
@@ -638,6 +650,7 @@ class WP_Theme_JSON_Resolver {
 	 *              added the `$origin` parameter.
 	 * @since 6.1.0 Added block data and generation of spacingSizes array.
 	 * @since 6.2.0 Changed ' $origin' parameter values to 'default', 'blocks', 'theme' or 'custom'.
+	 * @since 7.2.0 Caches the merged data per origin for the request and returns a copy of it.
 	 *
 	 * @param string $origin Optional. To what level should we merge data: 'default', 'blocks', 'theme' or 'custom'.
 	 *                       'custom' is used as default value as well as fallback value if the origin is unknown.
@@ -648,25 +661,39 @@ class WP_Theme_JSON_Resolver {
 			_deprecated_argument( __FUNCTION__, '5.9.0' );
 		}

-		$result = new WP_Theme_JSON();
-		$result->merge( static::get_core_data() );
-		if ( 'default' === $origin ) {
-			return $result;
+		if ( ! in_array( $origin, array( 'default', 'blocks', 'theme', 'custom' ), true ) ) {
+			$origin = 'custom';
 		}

-		$result->merge( static::get_block_data() );
-		if ( 'blocks' === $origin ) {
-			return $result;
+		/*
+		 * Discard the merged data when blocks were registered after the last merge.
+		 *
+		 * This check detects new blocks, not theme support changes. Call
+		 * clean_cached_data() after changing theme supports to refresh the merged data.
+		 */
+		if ( ! static::has_same_registered_blocks( 'merged' ) ) {
+			static::$merged = array();
+		}
+		if ( isset( static::$merged[ $origin ] ) ) {
+			return clone static::$merged[ $origin ];
 		}

-		$result->merge( static::get_theme_data() );
-		if ( 'theme' === $origin ) {
-			return $result;
+		$result = new WP_Theme_JSON();
+		$result->merge( static::get_core_data() );
+		if ( 'default' !== $origin ) {
+			$result->merge( static::get_block_data() );
+		}
+		if ( 'theme' === $origin || 'custom' === $origin ) {
+			$result->merge( static::get_theme_data() );
+		}
+		if ( 'custom' === $origin ) {
+			$result->merge( static::get_user_data() );
 		}

-		$result->merge( static::get_user_data() );
+		static::$merged[ $origin ] = $result;

-		return $result;
+		// Return a clone so callers can modify the result without changing the cache.
+		return clone $result;
 	}

 	/**
@@ -733,16 +760,13 @@ class WP_Theme_JSON_Resolver {
 	 *              and `$i18n_schema` variables to reset.
 	 * @since 6.1.0 Added the `$blocks` and `$blocks_cache` variables
 	 *              to reset.
+	 * @since 7.2.0 Added the `$merged` variable to reset.
 	 */
 	public static function clean_cached_data() {
 		static::$core                     = null;
 		static::$blocks                   = null;
-		static::$blocks_cache             = array(
-			'core'   => array(),
-			'blocks' => array(),
-			'theme'  => array(),
-			'user'   => array(),
-		);
+		static::$blocks_cache             = array_fill_keys( array_keys( static::$blocks_cache ), array() );
+		static::$merged                   = array();
 		static::$theme                    = null;
 		static::$user                     = null;
 		static::$user_custom_post_type_id = null;
diff --git a/wp-includes/global-styles-and-settings.php b/wp-includes/global-styles-and-settings.php
index 2315eef1ad..8c9affa79a 100644
--- a/wp-includes/global-styles-and-settings.php
+++ b/wp-includes/global-styles-and-settings.php
@@ -9,6 +9,7 @@
  * Gets the settings resulting of merging core, theme, and user data.
  *
  * @since 5.9.0
+ * @since 7.2.0 Removed the per-request cache; WP_Theme_JSON_Resolver::get_merged_data() caches the merged data.
  *
  * @param array $path    Path to the specific setting to retrieve. Optional.
  *                       If empty, will return all settings.
@@ -32,53 +33,13 @@ function wp_get_global_settings( $path = array(), $context = array() ) {
 		$path = $new_path;
 	}

-	/*
-	 * This is the default value when no origin is provided or when it is 'all'.
-	 *
-	 * The $origin is used as part of the cache key. Changes here need to account
-	 * for clearing the cache appropriately.
-	 */
+	// This is the default value when no origin is provided or when it is 'all'.
 	$origin = 'custom';
 	if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) {
 		$origin = 'theme';
 	}

-	/*
-	 * By using the 'theme_json' group, this data is marked to be non-persistent across requests.
-	 * See `wp_cache_add_non_persistent_groups` in src/wp-includes/load.php and other places.
-	 *
-	 * The rationale for this is to make sure derived data from theme.json
-	 * is always fresh from the potential modifications done via hooks
-	 * that can use dynamic data (modify the stylesheet depending on some option,
-	 * settings depending on user permissions, etc.).
-	 * See some of the existing hooks to modify theme.json behavior:
-	 * https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/
-	 *
-	 * A different alternative considered was to invalidate the cache upon certain
-	 * events such as options add/update/delete, user meta, etc.
-	 * It was judged not enough, hence this approach.
-	 * See https://github.com/WordPress/gutenberg/pull/45372
-	 */
-	$cache_group = 'theme_json';
-	$cache_key   = 'wp_get_global_settings_' . $origin;
-
-	/*
-	 * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme
-	 * developer's workflow.
-	 */
-	$can_use_cached = ! wp_is_development_mode( 'theme' );
-
-	$settings = false;
-	if ( $can_use_cached ) {
-		$settings = wp_cache_get( $cache_key, $cache_group );
-	}
-
-	if ( false === $settings ) {
-		$settings = WP_Theme_JSON_Resolver::get_merged_data( $origin )->get_settings();
-		if ( $can_use_cached ) {
-			wp_cache_set( $cache_key, $settings, $cache_group );
-		}
-	}
+	$settings = WP_Theme_JSON_Resolver::get_merged_data( $origin )->get_settings();

 	return _wp_array_get( $settings, $path, $settings );
 }
@@ -452,8 +413,6 @@ function wp_theme_has_theme_json() {
 function wp_clean_theme_json_cache() {
 	wp_cache_delete( 'wp_get_global_stylesheet', 'theme_json' );
 	wp_cache_delete( 'wp_get_global_styles_svg_filters', 'theme_json' );
-	wp_cache_delete( 'wp_get_global_settings_custom', 'theme_json' );
-	wp_cache_delete( 'wp_get_global_settings_theme', 'theme_json' );
 	wp_cache_delete( 'wp_get_global_styles_custom_css', 'theme_json' );
 	wp_cache_delete( 'wp_get_theme_data_template_parts', 'theme_json' );
 	WP_Theme_JSON_Resolver::clean_cached_data();
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 261f66d56d..5163cc6b44 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-alpha-64353';
+$wp_version = '7.2-alpha-64354';

 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.