Skip to content

fix(android): keep other documents from replacing the editor - #840

Open
dcalhoun wants to merge 4 commits into
trunkfrom
fix/android-editor-document-replacement
Open

dcalhoun wants to merge 4 commits into
trunkfrom
fix/android-editor-document-replacement

Conversation

@dcalhoun

@dcalhoun dcalhoun commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Description

Close CMM-2460. Close #732.

Prevent other documents from replacing the editor, and stop URLs that can't be opened from crashing the host app.

Plugin scripts could replace the editor with an entirely different document, leaving editor controls that no longer work.

  • Block blob, data, about, and file URLs in the main frame; they still load in subframes
  • Enable multiple windows, using a hidden web view to learn each new window's URL: http(s) opens in the OS browser, and anything else is dropped. A scheme check alone can't cover this, because window.open() otherwise returns the editor's own window and document.write wipes it without a navigation
  • Reload the editor if another page still reaches the main frame (e.g., POST forms), notifying hosts via onEditorUnavailable. After 3 reloads, show the error view with a Reload Editor action instead, so a script that navigates on every load can't reload forever. Either way, report open dialogs closed so hosts don't keep navigation blocked
  • Destroy a new window's hidden web view when a script closes it or its renderer dies, so it can't defeat the editor's renderer recovery from fix(android): recover the editor when its renderer process dies #839
  • Catch failures when opening URLs externally: no matching app, an app refusing the URL, or an oversized URL

Trade-off: plugin previews or exports using window.open with a blob URL or document.write now do nothing visible. A recovery reload restores the host's latest persisted content, so edits since the last save can be lost.

Known limitation: a script that calls window.open() and synchronously writes a network iframe into the new window crashes the app. This reproduces in a bare WebView without GutenbergKit, so it appears to be a Chromium bug. On trunk, the same sequence wipes the editor instead.

Testing instructions

Tip

The Chrome inspector seems to often timeout when using GutenbergKit's local dev server with an emulator. Consider using a physical device and/or a local production build instead (make build).

  1. In the Android demo app, open a site's editor and tap Start.
  2. In desktop Chrome, open chrome://inspect and inspect the editor WebView.
  3. Run each snippet below in the console, re-inspecting after any reload, and confirm the result:
Snippet Expected
top.location.href = URL.createObjectURL(new Blob(['<h1>Not the editor</h1>'], { type: 'text/html' })) The editor stays loaded.
window.open(URL.createObjectURL(new Blob(['<h1>Popup</h1>'], { type: 'text/html' }))) The editor stays loaded.
window.open().document.write('<h1>Written</h1>') The editor stays loaded.
window.open('https://example.org') The browser opens; returning to the app shows the editor.
location.href = 'unknown-scheme://example' The app does not crash.
window.open('https://example.org/' + 'a'.repeat(1900000)) The app does not crash.
const f = document.createElement('form'); f.method = 'post'; f.action = 'https://example.com/'; document.body.append(f); f.submit() A spinner shows, then the editor reloads with its content.
const i = document.createElement('iframe'); i.src = URL.createObjectURL(new Blob(['<p>Subframe</p>'], { type: 'text/html' })); document.body.append(i) The iframe loads and the editor stays loaded.
  1. In a Paragraph block's settings, expand Advanced, tap the Learn more link in the HTML Anchor section, and confirm it opens in the browser.
  2. Run the POST form snippet again until the fourth submission since opening the editor, with a block's settings open for that last submission. Confirm The editor stopped working appears and the close button is enabled, then tap Reload Editor and confirm the editor returns.

Additional AI-generated details

A script in the editor could load another document into the editor's WebView, leaving editor controls that no longer work. This change:

  • Allows about, blob, data, and file URLs in subframes only.
  • Enables multiple windows and handles onCreateWindow with a hidden WebView that sends http(s) URLs to the browser and drops the rest.
  • Enables JavaScript in the hidden WebView, because window.close() never reaches onCloseWindow without it.
  • Catches RuntimeException when opening URLs externally. A 1.9 MB URL throws RuntimeException: Failure from system, caused by TransactionTooLargeException.
  • Reloads the editor when any other page starts in the main frame, notifying the host via onEditorUnavailable; reloadEditor() now loads the editor URL instead of webView.reload().
  • Limits those reloads to 3 until the user taps Reload Editor. The count doesn't reset after a successful load.
  • Handles onRenderProcessGone in the hidden WebView. Windows share the editor's renderer, and any WebView leaving it unhandled kills the app.
  • Lets only the user's reload re-arm the renderer recovery limit, not these automatic reloads.
  • Reports open dialogs closed when another page replaces the editor. A successful reload already did this when the editor mounted, but no editor mounts behind the error view, which left Close disabled and Back refused.

Verified on an emulator (steps 1–5 above):

  • On trunk, the blob snippets replace the editor and unknown-scheme:// crashes the app.
  • With multiple windows disabled, window.open() returns the editor's own window, so document.write wipes the editor without any navigation callback. Multiple-window support is required for that case.
  • On this branch, every snippet behaves as expected.

Known limitation, on one API 36 emulator and one WebView build, with each variant run once:

  • The crash is a native SIGSEGV about 100 ms after onCreateWindow, before any popup callback runs.
  • It still crashes with an attached, configured popup, and when the popup is handed over 1.5 s late.
  • It does not crash when the write is delayed by 2 s, or when onCreateWindow returns false, which loses the URL.

🤖 Generated with Claude Code

@dcalhoun dcalhoun added [Type] Bug An existing feature does not function as intended Android labels Oct 28, 2026
@wpmobilebot

wpmobilebot commented Oct 28, 2026 •

Copy link
Copy Markdown

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/840")

Built from 804158c

@dcalhoun
dcalhoun force-pushed the fix/android-editor-document-replacement branch from 8e6f324 to ee318cc Compare October 30, 2026 13:58
dcalhoun and others added 3 commits October 30, 2026 15:54
A script in the editor could load blob, data, or new-window documents into
the editor's frame, leaving the host with an editor that drops commands and
fails content reads. Local documents now load in subframes only, new windows
hand http(s) URLs to the browser, and any other page that still reaches the
main frame is replaced by the editor again. Opening a URL no app handles no
longer crashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A script that navigates on every load reloaded the editor forever, closed windows stayed alive until the view detached, and an oversized URL crashed the app when opening it externally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hidden new windows share the editor's renderer and left its death unhandled, which killed the app. Automatic reloads over a replacing page also re-armed the renderer recovery limit, which only the user's reload should do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dcalhoun
dcalhoun force-pushed the fix/android-editor-document-replacement branch from ad7dea6 to 0965959 Compare October 30, 2026 20:00
…he editor

No editor mounts behind the error view shown after repeated replacements, so nothing reported an open dialog closed. Hosts kept Close disabled and Back was refused, leaving Reload Editor as the only way out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dcalhoun
dcalhoun marked this pull request as ready for review October 30, 2026 20:09
@dcalhoun
dcalhoun requested a review from oguzkocer October 30, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Android [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android editor can be replaced by another document without recovering

2 participants