Prevent Unsaved Changes Data Loss on Desktop-Mode Iframe Window Close - #315
Merged
AllTerrainDeveloper merged 5 commits intoJul 8, 2026
Conversation
…t data loss. - When closing an iframe-based window (Post Editor, Block Editor, etc.), the shell now sends desktop-mode-bridge-beforeunload-query via postMessage and suspends close pending a response (with 500ms safety timeout). - Both the PHP chromeless-bridge and the standalone TS bridge handle the query by synthesizing a beforeunload event on window.onbeforeunload + dispatching a DOM beforeunload event. - If unsaved changes are detected, a wpd-confirm-dialog prompts the user before calling destroy().
…se flow. - Add _closePending flag to prevent duplicate beforeunload queries and orphaned safety timers when close() is called re-entrantly. - Add .catch() on dynamic wpdConfirm import so a module-load failure falls through to destroy() instead of leaving the window in a zombie state. - Guard response handler and safety timeout against _isDestroyed to prevent double-teardown races. - Use location.origin instead of '*' as postMessage targetOrigin. - Fix iframe-bridge-standalone.ts no-shadow lint, brace-style, and Event.returnValue type cast.
KarunyaChavan
marked this pull request as ready for review
June 24, 2026 17:16
Collaborator
|
This is an amazing work, we'll test it and release it! |
…es query, add test coverage + docs - close() now no-ops on a second call while a beforeunload query is already in flight, instead of falling through to an immediate destroy (a double-click on the close button could previously skip the confirm dialog and lose unsaved changes silently). - Add tests for the pre-close query flow (bridge-ready gating, safety timeout, native windows unaffected, the re-entrancy fix above) and for the beforeunload-response handler (confirm/cancel/fallback title/import-failure/already-destroyed/timeout cleanup). - Document the new desktop-mode-bridge-beforeunload-* message pair in bridge-protocol.md and clarify in javascript-reference.md that iframe windows have their own separate pre-close guard from the native-only before-close filter.
Collaborator
|
Thanks for this fix, really nice catch on the data-loss scenario! I went through it and added a bit on top:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR prevents data loss when users close desktop-mode iframe windows (such as the Post Editor or Block Editor) that contain unsaved changes.
It introduces a cross-frame beforeunload negotiation protocol between the desktop shell and embedded iframe applications, ensuring editors can participate in the same unsaved-changes workflow used during normal WordPress navigation. The implementation also addresses several race conditions and failure scenarios to avoid orphaned or undestroyable windows.
Closes #314
Why
WordPress editors commonly register
beforeunloadhandlers to warn users before navigating away with unsaved changes.In desktop mode, however, iframe-based editor windows bypassed this mechanism. Clicking the native window close button (
X) immediately destroyed the iframe, preventing the embedded application from executing itsbeforeunloadlogic and creating a data-loss path that did not exist in standard WordPress behavior.During implementation, several correctness issues were also identified:
close()calls could trigger duplicate beforeunload negotiations and orphan safety timers.postMessagecommunication used an unnecessarily permissive target origin ('*').Changes
Shell-side close negotiation
File:
src/window/index.tsdesktop-mode-bridge-beforeunload-querymessage sent to iframe windows before destruction._closePendingto prevent duplicate negotiations from re-entrantclose()calls._closePending: booleanstate tracking._isDestroyedbefore proceeding.postMessagetarget origin from'*'tolocation.origin.Response handling
File:
src/window/iframe-bridge.tsAdded handling for
desktop-mode-bridge-beforeunload-response.When
prevent === true:<wpd-confirm-dialog>.destroy().When
prevent === false:Added
_isDestroyedguards to ignore stale responses.Added
.catch()handling around dynamic imports.Resets
_closePendingwhen negotiation completes.Iframe bridge support
Files:
includes/render/chromeless-bridge.phpsrc/iframe-bridge-standalone.tsAdded support for handling
desktop-mode-bridge-beforeunload-queryby:window.onbeforeunloadhandler.beforeunloadevent.desktop-mode-bridge-beforeunload-response.TypeScript and lint fixes
File:
src/iframe-bridge-standalone.tsev→eto satisfyno-shadow.Event.returnValueto satisfy TypeScript compilation.How It Works
When a user clicks the close button on an iframe window:
The shell sends a
desktop-mode-bridge-beforeunload-querymessage to the iframe.The iframe synchronously executes its
beforeunloadlogic.The iframe responds indicating whether closing should be prevented.
If prevention is requested:
If prevention is not requested:
This mirrors the protection users already receive during standard WordPress navigation.
Reliability Improvements
Data-loss prevention
Editors embedded inside desktop-mode iframe windows can now participate in the standard WordPress unsaved-changes workflow, preventing accidental loss of content.
Race-condition protection
close()calls are ignored while a negotiation is already in progress.Zombie-state prevention
destroy()to ensure cleanup always completes.Result
Desktop-mode iframe windows now provide the same unsaved-changes protection as standard WordPress navigation while remaining resilient to race conditions, import failures, stale responses, and non-responsive iframes.