From b95b98b536551a38b27c893ffa55842911e7f6b5 Mon Sep 17 00:00:00 2001 From: Sukhendu Sekhar Guria Date: Wed, 6 May 2026 15:26:30 +0530 Subject: [PATCH] Heartbeat: Expose post lock window to JS to prevent background tab race --- src/js/_enqueues/wp/heartbeat.js | 23 ++++++++++++++++++++--- src/wp-admin/includes/misc.php | 5 ++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/js/_enqueues/wp/heartbeat.js b/src/js/_enqueues/wp/heartbeat.js index 65635177d9f66..a286a9ecee826 100644 --- a/src/js/_enqueues/wp/heartbeat.js +++ b/src/js/_enqueues/wp/heartbeat.js @@ -100,7 +100,12 @@ // Timer that keeps track of how long needs to be waited before connecting to // the server again. - beatTimer: 0 + beatTimer: 0, + + // Post lock window in milliseconds. Synced from PHP via heartbeat_settings. + // Used to calculate the background tab heartbeat interval. + // Default matches the wp_check_post_lock_window default of 150 seconds. + postLockWindow: 150000 }; /** @@ -112,7 +117,7 @@ * @return {void} */ function initialize() { - var options, hidden, visibilityState, visibilitychange; + var options, hidden, visibilityState, visibilitychange, postLockWindow; if ( typeof window.pagenow === 'string' ) { settings.screenId = window.pagenow; @@ -172,6 +177,14 @@ if ( options.suspension === 'disable' ) { settings.suspendEnabled = false; } + + if ( options.post_lock_window ) { + postLockWindow = parseInt( options.post_lock_window, 10 ); + + if ( postLockWindow > 0 ) { + settings.postLockWindow = postLockWindow * 1000; + } + } } // Convert to milliseconds. @@ -509,7 +522,11 @@ } if ( ! settings.hasFocus ) { - interval = 120000; // 120 seconds. Post locks expire after 150 seconds. + // Fire 30 seconds before the post lock window expires so backgrounded + // tabs always refresh the lock in time. For the default 150s window this + // equals 120s, preserving existing behaviour. Floored at 5s for very + // short custom windows. + interval = Math.max( 5000, settings.postLockWindow - 30000 ); } else if ( settings.countdown > 0 && settings.tempInterval ) { interval = settings.tempInterval; settings.countdown--; diff --git a/src/wp-admin/includes/misc.php b/src/wp-admin/includes/misc.php index 3724684ffd428..1b223143f5947 100644 --- a/src/wp-admin/includes/misc.php +++ b/src/wp-admin/includes/misc.php @@ -1334,7 +1334,7 @@ function wp_refresh_heartbeat_nonces( $response ) { } /** - * Disables suspension of Heartbeat on the Add/Edit Post screens. + * Disables suspension of Heartbeat and sets post lock data on the Add/Edit Post screens. * * @since 3.8.0 * @@ -1348,6 +1348,9 @@ function wp_heartbeat_set_suspension( $settings ) { if ( 'post.php' === $pagenow || 'post-new.php' === $pagenow ) { $settings['suspension'] = 'disable'; + + /** This filter is documented in wp-admin/includes/ajax-actions.php */ + $settings['post_lock_window'] = apply_filters( 'wp_check_post_lock_window', 150 ); } return $settings;