Skip to content

Commit

Permalink
Editor: Avoid running certain logic around theme.json parsing unnec…
Browse files Browse the repository at this point in the history
…essarily for classic themes.

Here's what it does:
* Do not load and parse `theme-i18n.json` schema if the theme does not have a `theme.json` file.
* Fix the variable caching layer around the theme's `theme.json` parsing so that a parent's theme `theme.json` is cached as well.
* Do not run a `WP_Query` for global styles for a user when the theme does not have a `theme.json`.

In a basic WordPress setup, this changeset improves `wp_head` execution time for classic themes by 10%, and overall response time for both block themes and classic themes by 4%. This may seem like a small win, but 4% reduced overall response time is actually quite a bit for one change, and it is worth mentioning that this is just one of several other little performance tweaks which are being worked on to improve performance of `theme.json` parsing further.

Props flixos90, manuilov, oandregal, peterwilsoncc, spacedmonkey.
Fixes #56945.


git-svn-id: https://develop.svn.wordpress.org/trunk@54799 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
felixarntz committed Nov 10, 2022
1 parent 1ab43ac commit 8368eef
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 43 deletions.
54 changes: 37 additions & 17 deletions src/wp-includes/class-wp-theme-json-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,13 @@ public static function get_theme_data( $deprecated = array(), $options = array()
$options = wp_parse_args( $options, array( 'with_supports' => true ) );

if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) {
$theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) );
$theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) );
$theme_json_file = static::get_file_path_from_theme( 'theme.json' );
if ( '' !== $theme_json_file ) {
$theme_json_data = static::read_json_file( $theme_json_file );
$theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) );
} else {
$theme_json_data = array();
}

/**
* Filters the data provided by the theme for global styles and settings.
Expand All @@ -259,20 +264,23 @@ public static function get_theme_data( $deprecated = array(), $options = array()
$theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $theme_json_data, 'theme' ) );
$theme_json_data = $theme_json->get_data();
static::$theme = new WP_Theme_JSON( $theme_json_data );
}

if ( wp_get_theme()->parent() ) {
// Get parent theme.json.
$parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) );
$parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) );
$parent_theme = new WP_Theme_JSON( $parent_theme_json_data );

/*
* Merge the child theme.json into the parent theme.json.
* The child theme takes precedence over the parent.
*/
$parent_theme->merge( static::$theme );
static::$theme = $parent_theme;
if ( wp_get_theme()->parent() ) {
// Get parent theme.json.
$parent_theme_json_file = static::get_file_path_from_theme( 'theme.json', true );
if ( '' !== $parent_theme_json_file ) {
$parent_theme_json_data = static::read_json_file( $parent_theme_json_file );
$parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) );
$parent_theme = new WP_Theme_JSON( $parent_theme_json_data );

/*
* Merge the child theme.json into the parent theme.json.
* The child theme takes precedence over the parent.
*/
$parent_theme->merge( static::$theme );
static::$theme = $parent_theme;
}
}
}

if ( ! $options['with_supports'] ) {
Expand Down Expand Up @@ -403,6 +411,18 @@ public static function get_user_data_from_wp_global_styles( $theme, $create_post
if ( ! $theme instanceof WP_Theme ) {
$theme = wp_get_theme();
}

/*
* Bail early if the theme does not support a theme.json.
*
* Since WP_Theme_JSON_Resolver::theme_has_support() only supports the active
* theme, the extra condition for whether $theme is the active theme is
* present here.
*/
if ( $theme->get_stylesheet() === get_stylesheet() && ! static::theme_has_support() ) {
return array();
}

$user_cpt = array();
$post_type_filter = 'wp_global_styles';
$stylesheet = $theme->get_stylesheet();
Expand Down Expand Up @@ -582,8 +602,8 @@ public static function get_user_global_styles_post_id() {
public static function theme_has_support() {
if ( ! isset( static::$theme_has_support ) ) {
static::$theme_has_support = (
is_readable( static::get_file_path_from_theme( 'theme.json' ) ) ||
is_readable( static::get_file_path_from_theme( 'theme.json', true ) )
static::get_file_path_from_theme( 'theme.json' ) !== '' ||
static::get_file_path_from_theme( 'theme.json', true ) !== ''
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/wp-includes/class-wp-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -2939,7 +2939,8 @@ public function get_data() {
public function set_spacing_sizes() {
$spacing_scale = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'spacingScale' ), array() );

if ( ! is_numeric( $spacing_scale['steps'] )
if ( ! isset( $spacing_scale['steps'] )
|| ! is_numeric( $spacing_scale['steps'] )
|| ! isset( $spacing_scale['mediumStep'] )
|| ! isset( $spacing_scale['unit'] )
|| ! isset( $spacing_scale['operator'] )
Expand Down
98 changes: 73 additions & 25 deletions tests/phpunit/tests/theme/wpThemeJsonResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,6 @@ class Tests_Theme_wpThemeJsonResolver extends WP_UnitTestCase {
*/
private $orig_theme_dir;

/**
* Queries.
*
* @var array
*/
private $queries = array();

/**
* WP_Theme_JSON_Resolver::$blocks_cache property.
*
Expand Down Expand Up @@ -105,7 +98,7 @@ public function set_up() {
add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) );
add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) );
add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) );
$this->queries = array();

// Clear caches.
wp_clean_themes_cache();
unset( $GLOBALS['wp_themes'] );
Expand All @@ -129,13 +122,6 @@ public function filter_set_locale_to_polish() {
return 'pl_PL';
}

function filter_db_query( $query ) {
if ( preg_match( '#post_type = \'wp_global_styles\'#', $query ) ) {
$this->queries[] = $query;
}
return $query;
}
/**
* @ticket 52991
* @ticket 54336
Expand Down Expand Up @@ -634,58 +620,91 @@ function test_merges_child_theme_json_into_parent_theme_json() {
* @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles
*/
function test_get_user_data_from_wp_global_styles_does_not_use_uncached_queries() {
// Switch to a theme that does have support.
switch_theme( 'block-theme' );
wp_set_current_user( self::$administrator_id );
$theme = wp_get_theme();
WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
add_filter( 'query', array( $this, 'filter_db_query' ) );
$query_count = count( $this->queries );
$global_styles_query_count = 0;
add_filter(
'query',
function( $query ) use ( &$global_styles_query_count ) {
if ( preg_match( '#post_type = \'wp_global_styles\'#', $query ) ) {
$global_styles_query_count++;
}
return $query;
}
);
for ( $i = 0; $i < 3; $i++ ) {
WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
WP_Theme_JSON_Resolver::clean_cached_data();
}
$query_count = count( $this->queries ) - $query_count;
$this->assertSame( 0, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' );
$this->assertSame( 0, $global_styles_query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' );
$user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
$this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' );
$user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true );
$this->assertNotEmpty( $user_cpt, 'User CPT is expected not to be empty.' );
$query_count = count( $this->queries );
$global_styles_query_count = 0;
for ( $i = 0; $i < 3; $i ++ ) {
$new_user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
WP_Theme_JSON_Resolver::clean_cached_data();
$this->assertSameSets( $user_cpt, $new_user_cpt, "User CPTs do not match on run {$i}." );
}
$query_count = count( $this->queries ) - $query_count;
$this->assertSame( 1, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type after creation.' );
$this->assertSame( 1, $global_styles_query_count, 'Unexpected SQL queries detected for the wp_global_style post type after creation.' );
}
/**
* @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles
*/
function test_get_user_data_from_wp_global_styles_does_not_use_uncached_queries_for_logged_out_users() {
// Switch to a theme that does have support.
switch_theme( 'block-theme' );
$theme = wp_get_theme();
WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
add_filter( 'query', array( $this, 'filter_db_query' ) );
$query_count = count( $this->queries );
$query_count = get_num_queries();
for ( $i = 0; $i < 3; $i++ ) {
WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
WP_Theme_JSON_Resolver::clean_cached_data();
}
$query_count = count( $this->queries ) - $query_count;
$query_count = get_num_queries() - $query_count;
$this->assertSame( 0, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' );

$user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
$this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' );
}

/**
* @ticket 56945
* @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles
*/
function test_get_user_data_from_wp_global_styles_does_not_run_for_theme_without_support() {
// The 'default' theme does not support theme.json.
switch_theme( 'default' );
wp_set_current_user( self::$administrator_id );
$theme = wp_get_theme();

$start_queries = get_num_queries();

// When theme.json is not supported, the method should not run a query and always return an empty result.
$user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
$this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' );
$this->assertSame( 0, get_num_queries() - $start_queries, 'Unexpected SQL query detected for theme without theme.json support.' );

$user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true );
$this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' );
$this->assertSame( 0, get_num_queries() - $start_queries, 'Unexpected SQL query detected for theme without theme.json support.' );
}

/**
* @ticket 55392
* @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles
*/
function test_get_user_data_from_wp_global_styles_does_exist() {
// Switch to a theme that does have support.
switch_theme( 'block-theme' );
$theme = wp_get_theme();
$post1 = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true );
$this->assertIsArray( $post1 );
Expand All @@ -701,6 +720,8 @@ function test_get_user_data_from_wp_global_styles_does_exist() {
* @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles
*/
function test_get_user_data_from_wp_global_styles_create_post() {
// Switch to a theme that does have support.
switch_theme( 'block-theme' );
$theme = wp_get_theme( 'testing' );
$post1 = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
$this->assertIsArray( $post1 );
Expand All @@ -718,6 +739,8 @@ function test_get_user_data_from_wp_global_styles_create_post() {
* @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles
*/
function test_get_user_data_from_wp_global_styles_filter_state() {
// Switch to a theme that does have support.
switch_theme( 'block-theme' );
$theme = wp_get_theme( 'foo' );
$post1 = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true, array( 'publish' ) );
$this->assertIsArray( $post1 );
Expand Down Expand Up @@ -749,4 +772,29 @@ function test_get_theme_data_theme_supports_overrides_theme_json() {
$line_height = $current_settings['typography']['lineHeight'];
$this->assertTrue( $line_height, 'lineHeight setting after add_theme_support() should be true.' );
}

/**
* @ticket 56945
* @covers WP_Theme_JSON_Resolver::get_theme_data
*/
function test_get_theme_data_does_not_parse_theme_json_if_not_present() {
// The 'default' theme does not support theme.json.
switch_theme( 'default' );

$theme_json_resolver = new WP_Theme_JSON_Resolver();

// Force-unset $i18n_schema property to "unload" translation schema.
$property = new ReflectionProperty( $theme_json_resolver, 'i18n_schema' );
$property->setAccessible( true );
$property->setValue( null );

// A completely empty theme.json data set still has the 'version' key when parsed.
$empty_theme_json = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA );

// Call using 'with_supports' set to false, so that the method only considers theme.json.
$theme_data = $theme_json_resolver->get_theme_data( array(), array( 'with_supports' => false ) );
$this->assertInstanceOf( 'WP_Theme_JSON', $theme_data, 'Theme data should be an instance of WP_Theme_JSON.' );
$this->assertSame( $empty_theme_json, $theme_data->get_raw_data(), 'Theme data should be empty without theme support.' );
$this->assertNull( $property->getValue(), 'Theme i18n schema should not have been loaded without theme support.' );
}
}

0 comments on commit 8368eef

Please sign in to comment.