PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.6
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.6
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 1.1.3 1.1.4 1.1.5 All 32 releases
← All changes | includes/modules/Lazy/LazyModule.php +55 -5 1.3.0 → 1.3.6 View file →
@@ -76,8 +76,14 @@
76 76 'default' => true,
77 77 'label' => __( 'Lazy-load HTML5 Videos', 'xspeed' ),
78 78 'description' => __( 'Set preload="none" on self-hosted <video> tags, overriding a player\'s own preload="auto"/"metadata". Autoplaying videos are left alone — they need their bytes regardless. Browsers do not yet support loading="lazy" on video; preload="none" is the closest equivalent.', 'xspeed' ),
79 79 ),
80 + 'lazy_background_images' => array(
81 + 'type' => 'bool',
82 + 'default' => false,
83 + 'label' => __( 'Lazy-load Background Images', 'xspeed' ),
84 + 'description' => __( 'Hold back background images set in an element\'s inline style (a Cover or Group block, a page-builder section) until the element is near the screen. Uses a small script; visitors without JavaScript get every background as usual. The first N backgrounds on the page load straight away, like images. Backgrounds set in a stylesheet are not affected.', 'xspeed' ),
85 + ),
80 86 'eager_first_n' => array(
81 87 'type' => 'int',
82 88 'default' => 1,
83 89 'min' => 0,
@@ -82,9 +88,9 @@
82 88 'default' => 1,
83 89 'min' => 0,
84 90 'max' => 10,
85 91 'label' => __( 'Eager-load First N Images', 'xspeed' ),
86 - 'description' => __( 'How many images at the top of the post get loading="eager". 1 is usually right (the LCP hero image). 0 to lazy-load everything.', 'xspeed' ),
92 + 'description' => __( 'How many images at the top of the post get loading="eager". 1 is usually right (the LCP hero image). 0 to lazy-load everything. Lazy-loaded background images use the same number.', 'xspeed' ),
87 93 ),
88 94 'add_missing_dimensions' => array(
89 95 'type' => 'bool',
90 96 'default' => true,
@@ -155,8 +161,9 @@
155 161 $any_enabled = ! empty( $opts['lazy_images'] )
156 162 || ! empty( $opts['lazy_iframes'] )
157 163 || ! empty( $opts['lazy_videos'] )
158 164 || ! empty( $opts['video_facade'] )
165 + || ! empty( $opts['lazy_background_images'] )
159 166 || ! empty( $opts['add_missing_dimensions'] );
160 167 if ( ! $any_enabled ) {
161 168 return;
162 169 }
@@ -185,10 +192,23 @@
185 192 // renders isn't known until the content filter has run — long
186 193 // after wp_head — and a layout rule that arrives in the footer
187 194 // fixes the gap only after the visitor has already seen it.
188 195 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_facade_style' ) );
196 + // Embeds that never touch the HTML: a builder widget builds its
197 + // YouTube iframe from script, so the buffer pass has nothing to
198 + // rewrite. Head, priority 1, for the same reason as the autoplay
199 + // restorer below — it must be listening before the widget's
200 + // script sets a src and commits the fetch.
201 + add_action( 'wp_head', array( $this, 'print_observer_script' ), 1 );
189 202 }
190 203
204 + // Head, because the rule that holds a background back has to apply
205 + // before first paint, or the browser has already requested it.
206 + if ( ! empty( $opts['lazy_background_images'] ) ) {
207 + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_background_style' ) );
208 + add_action( 'wp_head', array( $this, 'print_background_script' ), 1 );
209 + }
210 +
191 211 // Same conditional-footer treatment for the autoplay restorer: it is
192 212 // only printed on a response that actually deferred one.
193 213 if ( ! empty( $opts['lazy_videos'] ) ) {
194 214 /*
@@ -221,18 +241,48 @@
221 241 wp_enqueue_style( 'xspeed-video-facade' );
222 242 wp_add_inline_style( 'xspeed-video-facade', \XSpeed\Video_Facade::facade_style() );
223 243 }
224 244
245 + public function enqueue_background_style(): void {
246 + wp_register_style( 'xspeed-lazy-bg', false, array(), XSPEED_VERSION );
247 + wp_enqueue_style( 'xspeed-lazy-bg' );
248 + wp_add_inline_style( 'xspeed-lazy-bg', Lazy_Loader::background_style() );
249 + }
250 +
225 251 /**
252 + * data-xs-nodelay: Delay JS would otherwise hold this back until the
253 + * first interaction, and every held background would stay blank.
254 + */
255 + public function print_background_script(): void {
256 + wp_print_inline_script_tag(
257 + Lazy_Loader::background_script(),
258 + array(
259 + 'id' => 'xspeed-lazy-bg',
260 + 'data-xs-nodelay' => true,
261 + )
262 + );
263 + }
264 +
265 + /**
226 266 * Emit the click-to-play handler inline. Inline (not enqueued) because
227 267 * it is ~400 bytes — a separate request would cost more than the code.
228 268 */
229 269 public function print_facade_script(): void {
230 - if ( ! Lazy_Loader::facade_used() ) {
231 - return;
232 - }
270 + // No longer gated on facade_used(): the observer script can build a
271 + // facade for a JS-injected embed on a page where the server pass
272 + // rendered none, and a facade without its click handler is a play
273 + // button that plays nothing. ~400 bytes on facade-less pages is the
274 + // cost of never shipping that.
275 + wp_print_inline_script_tag( \XSpeed\Video_Facade::facade_script(), array( 'id' => 'xspeed-video-facade' ) );
276 + }
233 277
234 - wp_print_inline_script_tag( \XSpeed\Video_Facade::facade_script(), array( 'id' => 'xspeed-video-facade' ) );
278 + /**
279 + * Emit the interceptor for JS-injected embeds (see
280 + * Video_Facade::observer_script() for the mechanism and why the
281 + * footer is too late).
282 + */
283 + public function print_observer_script(): void {
284 + wp_print_inline_script_tag( \XSpeed\Video_Facade::observer_script(), array( 'id' => 'xspeed-video-facade-observer' ) );
235 285 }
236 286
237 287 /**
238 288 * Emit the viewport restorer for deferred AUTOPLAY videos.