Skip to content

fix(android): recover the editor when its renderer process dies - #839

Merged
dcalhoun merged 7 commits into
trunkfrom
fix/android-render-process-gone
Oct 30, 2026
Merged

dcalhoun merged 7 commits into
trunkfrom
fix/android-render-process-gone

Conversation

@dcalhoun

@dcalhoun dcalhoun commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Description

Recover the editor when its renderer process dies.

Close #643. Close CMM-2414. Previously, the host app would crash as GutenbergKit did not handle this specific event for the web view, which could lead to content loss.

  • Implement a onRenderProcessGone callback to handle the event of the web view dying.
  • Add an isWebViewBlank instance variable to inform how the editor should recover from the web view dying
  • Upon process death, reset the view state and present the editor again

Testing instructions

To crash the editor's renderer without Chrome DevTools, forward the app's WebView debug socket. Re-run this after the app restarts; renderer crashes don't require it.

adb forward tcp:9222 localabstract:webview_devtools_remote_$(adb shell pidof com.example.gutenbergkit)

Then crash the renderer with this command (Node 22+):

node -e "fetch('http://localhost:9222/json').then((r) => r.json()).then(([page]) => { const ws = new WebSocket(page.webSocketDebuggerUrl); ws.onopen = () => { ws.send(JSON.stringify({ id: 1, method: 'Page.crash' })); setTimeout(() => process.exit(), 500); }; })"
  1. Run the Android demo app and open a post in the editor.
  2. Make an unsaved edit, e.g. add text to the title.
  3. Run the adb forward command, then crash the renderer. Verify the app stays open and the editor reloads with your edit.
  4. Once the editor reloads, open a block's settings and crash the renderer again. Verify "The editor stopped working" notice appears instead of another reload, and the close button is enabled.
  5. Tap Reload Editor. Verify the editor loads with your edit.
  6. Optional: repeat step 3 on trunk. The app closes.

AI-generated details

Closes #643.

Problem: GutenbergView never handled onRenderProcessGone, so a WebView renderer death (often out-of-memory) killed the host app. On trunk:

E chromium: Renderer process (14058) crash detected (code 5).
F chromium: [FATAL] Render process (14058)'s crash wasn't handled by all associated webviews, triggering application crash.
I ActivityManager: Process com.example.gutenbergkit (pid 13908) has died: fg TOP

Fix: Handle the callback and recover in place, matching iOS's reload on web content process termination.

  • Android can't reuse a WebView after its renderer dies, so the dead one is removed, destroyed, and replaced.
  • Pending getTitleAndContent reads fail with EditorNotReadyException, and the host gets onEditorUnavailable.
  • Dialogs the editor reported open are reported closed, as iOS does. The dead renderer can't unmount them to report it, so hosts would otherwise keep navigation disabled and Back would be refused on the crash notice.
  • If the editor had loaded, it loads again in the new WebView from LatestContentProvider, once.
  • If the renderer died before the editor loaded, or dies again after an automatic recovery, the existing crash notice offers a reload instead. onEditorLoaded fires on first paint, before images decode, so content that runs the renderer out of memory would otherwise reload forever. Tapping Reload Editor allows one more automatic recovery.

Testing: Unit tests cover each behavior. On an emulator, crash the editor's renderer with the adb forward and Page.crash commands from the testing instructions:

  1. With the editor loaded and an unsaved edit, crash the renderer. The app survives and the editor reloads with the edit.
  2. Crash again while the reloaded editor is still loading. The "The editor stopped working" notice appears.
  3. Tap Reload Editor. The editor loads with the edit.
  4. Crash once the editor has loaded and let it recover, then crash again after it reloads. The notice appears instead of a second automatic reload.
  5. Open a block's settings, then crash the renderer after an automatic recovery. On the crash notice, the close button is enabled and Back leaves the editor.

Not covered:

  • An app's WebViews share one renderer, so the app still dies if a host WebView alive at the time leaves onRenderProcessGone unhandled (WordPress-Android handles it nowhere). The GutenbergView KDoc now says so.
  • HtmlToBitmapRenderer's off-screen WebView also lacks the handler. It has no production callers yet.

🤖 Generated with Claude Code

https://claude.ai/code/session_017NCdQuj1TpMKFBGz2r4RHx

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

wpmobilebot commented Oct 27, 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/839")

Built from 8ad4c4f

dcalhoun and others added 6 commits October 30, 2026 08:09
Unhandled, a WebView renderer death (often out-of-memory) kills the host
app. Android can't reuse the WebView afterward, so replace it and load
the editor again, failing pending reads. A renderer that dies before the
editor loads shows the crash notice instead, avoiding a reload loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RenderProcessGoneDetail.didCrash() is API 26, above the library's
minSdk of 24, so Android Lint reported a NewApi error. Android only
calls the handler from API 26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NCdQuj1TpMKFBGz2r4RHx
The editor reports itself loaded on first paint, before images and
embeds finish rendering, so content that runs the renderer out of memory
after load reloaded forever. A second death after an automatic recovery
now shows the crash notice; reloading from it re-arms recovery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NCdQuj1TpMKFBGz2r4RHx
An app's WebViews share one renderer, so GutenbergView recovering alone
doesn't keep the app alive while a host WebView leaves the death
unhandled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NCdQuj1TpMKFBGz2r4RHx
The listener's KDoc named the error boundary as the only cause and promised a reload notice, but a renderer death now notifies it too and may reload automatically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NCdQuj1TpMKFBGz2r4RHx
A dead renderer can't unmount the editor's dialogs to report them closed, so hosts kept navigation disabled and Back was refused on the crash notice. This mirrors iOS, which reports them closed when its web content process terminates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NCdQuj1TpMKFBGz2r4RHx
@dcalhoun
dcalhoun force-pushed the fix/android-render-process-gone branch from 13f02e3 to 5555d6c Compare October 30, 2026 12:10
The @RequiresApi annotation already states the API level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017NCdQuj1TpMKFBGz2r4RHx
@dcalhoun
dcalhoun marked this pull request as ready for review October 30, 2026 12:35
@dcalhoun
dcalhoun requested a review from nbradbury October 30, 2026 12:35
@nbradbury

Copy link
Copy Markdown
Contributor

@dcalhoun This all looks good to me and passed the testing steps. I'll approve but leave Claude's findings for you to look at.

Severity Merge Location Issue Impact
High Must fix GutenbergView.kt:1259 Automatic recovery reloads the editor from LatestContentProvider without any user action, and falls back silently to the content the editor was opened with when no provider is set or it returns null. A host with no provider (or with unpersisted edits) loses the user's typing after a renderer death and shows no notice, so the user may keep editing and save over the lost work.
High Must fix GutenbergView.kt:1259 Recovery goes through loadEditor(), whose teardown (lines 777–785) runs WebStorage.deleteAllData(), clearCache(true) and removeAllCookies app-wide, then restores the cookies captured at construction. A shared renderer death that the host's own WebViews survive wipes their localStorage, HTTP cache and cookies, and puts back auth/nonce cookies that may have rotated or expired.

@nbradbury nbradbury left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All good :shipit:

@dcalhoun

Copy link
Copy Markdown
Member Author

I checked both findings, and I don't feel either blocks this PR:

  1. Silent reload from the latest content: expected. Hosts are expected to set a provider and manage pending content changes. The WordPress apps set a provider, so this loses at most the edits since the last save (a 1-second pause or 50 changes in WordPress-Android). trunk loses the same edits and crashes the app too, and iOS also reloads without a notice.
  2. App-wide wipe during recovery: the mechanism is real, but the wipe already runs each time the editor opens on trunk, and WordPress-Android can't reach it during recovery today. Without an onRenderProcessGone handler, the app dies if another WebView is alive. WordPress-Android's WebViews also re-establish their auth when they load, so I didn't find a flow it affects.

The second could be worth an improvement later, but there is no impacted flow today.

@dcalhoun
dcalhoun merged commit 3389e86 into trunk Oct 30, 2026
25 checks passed
@dcalhoun
dcalhoun deleted the fix/android-render-process-gone branch October 30, 2026 18:46
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: a WebView renderer crash kills the host app process

3 participants