PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.4.1
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.4.1
1.4.1 1.4.0 1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 All 35 releases
← All changes | includes/modules/Fonts/FontsModule.php +58 -1 1.3.7 → 1.4.1 View file →
@@ -44,9 +44,9 @@
44 44 'font_display_swap' => array(
45 45 'type' => 'bool',
46 46 'default' => true,
47 47 'label' => __( 'Show text while fonts load', 'xspeed' ),
48 - 'description' => __( 'Google Fonts text shows at once in a standard font, then switches when the web font arrives.', 'xspeed' ),
48 + 'description' => __( 'Text in Google Fonts, and in fonts added to WordPress itself, shows at once in a standard font, then switches when the web font arrives.', 'xspeed' ),
49 49 ),
50 50 'preload_fonts' => array(
51 51 'type' => 'list',
52 52 'default' => array(),
@@ -94,8 +94,27 @@
94 94 $opts = $this->get_settings();
95 95
96 96 if ( ! empty( $opts['font_display_swap'] ) ) {
97 97 add_filter( 'style_loader_tag', array( __CLASS__, 'inject_display_swap' ), 10, 2 );
98 +
99 + // Fonts added through the Font Library or theme.json are printed
100 + // by core with font-display: fallback, and core offers no filter
101 + // for it. Fallback hides the text for up to 100ms while the font
102 + // loads; when that text is the LCP, the hero paints after the
103 + // font instead of at first paint. Measured on a live text hero:
104 + // LCP landed ~85ms after FCP with fallback and on FCP with swap,
105 + // CLS unchanged, and PageSpeed mobile moved between 81 and 90
106 + // depending on which side of that window the font fell. So core's
107 + // own printer runs with swap as the default. Only an exact
108 + // priority-50 hook is replaced: anything that already moved or
109 + // removed it is left alone.
110 + if ( function_exists( 'wp_print_font_faces' )
111 + && class_exists( '\WP_Font_Face_Resolver' )
112 + && 50 === has_action( 'wp_head', 'wp_print_font_faces' )
113 + ) {
114 + remove_action( 'wp_head', 'wp_print_font_faces', 50 );
115 + add_action( 'wp_head', array( __CLASS__, 'print_font_faces_swap' ), 50 );
116 + }
98 117 }
99 118
100 119 if ( ! empty( $opts['preload_fonts'] ) ) {
101 120 add_action(
@@ -160,8 +179,46 @@
160 179 $separator = ( false === strpos( $href_decoded, '?' ) ) ? '?' : '&';
161 180 $new_href = $href . $separator . 'display=swap';
162 181
163 182 return str_replace( $href, $new_href, $tag );
183 + }
184 +
185 + /**
186 + * Print core's font faces as wp_print_font_faces() does, with swap as
187 + * the font-display default.
188 + */
189 + public static function print_font_faces_swap(): void {
190 + $fonts = \WP_Font_Face_Resolver::get_fonts_from_theme_json();
191 + if ( empty( $fonts ) ) {
192 + return;
193 + }
194 + // WordPress 6.4+. Only hooked when the function exists (see
195 + // boot_on_init()), called by name so a 6.0 floor stays compatible.
196 + call_user_func( 'wp_print_font_faces', self::default_display_swap( $fonts ) );
197 + }
198 +
199 + /**
200 + * Give every font face without its own font-display a swap one.
201 + *
202 + * The resolver only sets font-display when a theme.json fontFace
203 + * declares fontDisplay, so a face that has one was chosen on purpose
204 + * and keeps it. Public + static so the test suite can drive it.
205 + *
206 + * @param array<int|string,mixed> $fonts Font families, each a list of faces.
207 + * @return array<int|string,mixed>
208 + */
209 + public static function default_display_swap( array $fonts ): array {
210 + foreach ( $fonts as $family => $faces ) {
211 + if ( ! is_array( $faces ) ) {
212 + continue;
213 + }
214 + foreach ( $faces as $i => $face ) {
215 + if ( is_array( $face ) && ! isset( $face['font-display'] ) ) {
216 + $fonts[ $family ][ $i ]['font-display'] = 'swap';
217 + }
218 + }
219 + }
220 + return $fonts;
164 221 }
165 222
166 223 /**
167 224 * Render the preload <link> markup for a list of font URLs.