Opened 20 hours ago
Last modified 8 hours ago
#65171 new defect (bug)
wp_check_post_lock_window filter values below 120s break post lock detection in backgrounded tabs
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | Awaiting Review | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Editor | Keywords: | has-patch |
| Focuses: | ui, javascript | Cc: |
Description
Summary
When wp_check_post_lock_window is filtered to a value lower than the maximum heartbeat interval (120s), post lock detection silently breaks for users editing in backgrounded tabs. A second editor opening the same post sees no "Currently being edited" modal, walks into the editor, and on the next heartbeat takes over the lock — silently overwriting any unsaved changes the first editor had typed.
Steps to reproduce
Add the following to a theme or plugin:
add_filter( 'wp_check_post_lock_window', function() { return 30; } );
- User A opens
/wp-admin/post.php?post=N&action=edit._edit_lockis set with the current timestamp. - User A switches to a different tab/window so the editor tab is backgrounded.
heartbeat.jsoverridesintervalto 120000ms. - After 30 seconds, the lock is considered expired by
wp_check_post_lock()(uses the filtered window). - User A's lock is not refreshed until 120s elapse (next backgrounded heartbeat).
- Between t=30s and t=120s, user B opens the same post.
wp_check_post_lock()returns false. No takeover modal appears. B walks into the editor. - B's first heartbeat claims the lock. A's next heartbeat receives
lock_errorand A is shown the takeover modal — losing any unsaved changes.
I reproduced this end-to-end by logging both heartbeat traffic and wp_check_post_lock_window calls. The 120s gap between A's heartbeats is exactly what heartbeat.js:512 produces; the lack of modal for B is exactly what post.php produces when the lock has aged past 30s.
Code paths
The defect is two pieces of core that have no shared awareness:
src/wp-admin/includes/post.php, wp_check_post_lock():
$time_window = apply_filters( 'wp_check_post_lock_window', 150 );
if ( $time && $time > time() - $time_window && get_current_user_id() !== $user ) {
return $user;
}
The filter has no documented minimum value.
src/js/_enqueues/wp/heartbeat.js, scheduleNextTick():
if ( ! settings.hasFocus ) {
interval = 120000; // 120 seconds. Post locks expire after 150 seconds.
}
The 120000 constant is hardcoded; the comment confirms the design implicitly assumes the lock window is always 150s. There is no mechanism for a customised lock window to reach the JS layer.
src/wp-includes/general-template.php, wp_heartbeat_settings(), does not expose the lock window — it only sets ajaxurl and nonce.
Change History (1)
This ticket was mentioned in PR #11732 on WordPress/wordpress-develop by @sukhendu2002.
8 hours ago
#1
- Keywords has-patch added; needs-patch removed
Trac ticket: https://core.trac.wordpress.org/ticket/65171