Rich text: synchronize the selection before events that consume it#80151
Conversation
The native selectionchange event is asynchronous and coalesced, so the record and the store selection can be one selection behind the DOM when an event that acts on them arrives, regardless of how the selection got there. Synchronize on capture of the events that consume the record, the store selection, or a value rendered from them: keydown, beforeinput, copy, cut and paste. A selection snapshot skips the work when the selection has already been processed, so the common path adds a few property comparisons per event. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Size Change: +157 B (0%) Total Size: 7.7 MB 📦 View Changed
|
The selectionchange path benefits from the same fast path: the event delivered after a capture synchronization, and coalesced duplicates, now cost a few property comparisons instead of recreating the record. The capture events subscribe handleSelectionChange directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The record's selection may be rewritten from (possibly stale) props on render without the DOM selection moving. A snapshot matching the DOM alone would then skip synchronization against a diverged record. Comparing the offsets the processing produced against the record re-processes in that case and keeps the fast path across benign renders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Flaky tests detected in 3aab38f. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/29215311386
|
What?
Update the rich text selection (record and store) right before any event that acts on it:
keydown,beforeinput,copy,cutandpaste.Why?
The browser fires
selectionchangeasynchronously, so when one of these events arrives first, handlers act on the previous selection: the caret ends up in the wrong place, and selection-dependent e2e tests flake. Fast input can always win that race, however the selection was moved (click, arrow key, split).Syncing at these events is enough: only a handful of events act on the selection, while there are countless ways to move it.
How?
The existing
selectionchangehandler is also subscribed on capture of the five events, so it runs before any other handler, with all its guards unchanged. A snapshot of the last processed selection keeps repeat calls down to a few property comparisons: real work only happens when the selection moved and itsselectionchangeevent hasn't been processed yet, which is exactly the race being closed. The snapshot also verifies the record still holds the processed offsets, since a render can rewrite the record without the selection moving, and it is cleared when the focus handler resets the record.Extracted from #79105, which uses the same mechanism for blocks whose caret moves between blocks without focus events.
Written with the help of Claude Code.
🤖 Generated with Claude Code