Skip to content

fix(WebView): properly recover when the renderer process is gone (refs #5823)#6793

Draft
jim-daf wants to merge 1 commit into
home-assistant:mainfrom
jim-daf:fix/issue-5823-renderer-gone-recovery
Draft

fix(WebView): properly recover when the renderer process is gone (refs #5823)#6793
jim-daf wants to merge 1 commit into
home-assistant:mainfrom
jim-daf:fix/issue-5823-renderer-gone-recovery

Conversation

@jim-daf
Copy link
Copy Markdown
Contributor

@jim-daf jim-daf commented Apr 30, 2026

Recover the WebView when the renderer process is gone (refs #5823)

Problem

Issue #5823 reports that line-style dashboard cards (graph card, apex-charts-card, mini-graph-card) render as permanently greyed-out blank surfaces on a Samsung Note Pro 12.2 (Mali-T628 GPU) running LineageOS 17.1 / Android 10 with WebView Dev 142.0.7418.5. Cards become functional again only when the user enters dashboard edit mode (which forces a re-layout) and revert to greyed-out afterwards.

The chromium log excerpt the reporter attached shows the smoking gun:

SharedContextState context lost via EXT_robustness
RasterDecoderImpl: Context lost during MakeCurrent
GLES-MALI <pid> [tgid] : OoM error

i.e. the Mali GPU driver dropped the renderer's GL context (the EXT_robustness GPU robustness extension is the standard way Chromium learns its GL context died) and the renderer subsequently OOM'd. On API ≥ 26 Android then signals this to the embedder via WebViewClient.onRenderProcessGone(view, detail) with detail.didCrash() == false (renderer killed by the system, not crashed by the page).

Why the current code does not recover

WebViewActivity already overrides onRenderProcessGone, but the body is:

Timber.e("onRenderProcessGone: webView crashed")
view?.let {
    reload()
    lifecycleScope.launch {
        webViewAddJavascriptInterface()
    }
}
return true

Per the android.webkit.WebView Javadoc, once the WebView's render process is gone the WebView instance is permanently unusable and any subsequent method call on it is a no-op:

The given WebView can't be used after this method is called. Any further calls on the > WebView will result in undefined behavior.

So the existing reload() (which calls reload on the activity's same WebView field) silently does nothing, and the JS bridge re-registration also does nothing. The user is left looking at the greyed-out dashboard until they manually kill and relaunch the app — exactly the symptom #5823 describes.

Fix

Implement the recovery pattern the framework documentation recommends:

  1. Read RenderProcessGoneDetail.didCrash() (API ≥ 26) so the log line tells us whether the renderer crashed (e.g. JS heap OOM) or was killed by the system (e.g. GPU context loss + low memory) — useful triage information for future reports.
  2. Detach the dead WebView from its parent and call destroy() on it so its native resources are released.
  3. Call Activity.recreate() so onCreate runs again with a brand-new WebView, the presenter re-loads the last URL through the normal path, and the JS bridge is re-registered.
  4. Skip steps 2-3 if the activity is already finishing or relaunching, to avoid racing with relaunchApp() / a back-press tear-down.
  5. Continue to return true so the hosting app process is not killed by the system when the renderer dies.

Scope

  • Single file change: app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt.
  • No new dependencies, no new strings, no preference / settings UI surface.
  • Behaviour is unchanged on healthy devices onRenderProcessGone simply does not fire there.
  • On affected devices the user sees a brief activity recreation (~ the same as a screen rotation) instead of staying stuck on greyed-out cards.

…e-assistant#5823)

Refs home-assistant#5823

Issue home-assistant#5823 reports that line-style dashboard cards (graph card,
apex-charts-card, mini-graph-card) appear permanently greyed-out
on a Samsung Note Pro 12.2 running LineageOS 17.1 (Android 10) /
WebView Dev 142.0.7418.5. The attached chromium logs show the
Mali GPU dropping the WebView's GL context via EXT_robustness and
an out-of-memory in the renderer:

    SharedContextState context lost via EXT_robustness
    RasterDecoderImpl: Context lost during MakeCurrent
    GLES-MALI <pid> [tgid] : OoM error

When this happens Android delivers WebViewClient.onRenderProcessGone
to us. The current handler logs and then calls reload() — but per
the android.webkit.WebView contract a WebView whose render process
is gone is unusable and any further call on it is a no-op. The
user is left staring at the broken dashboard until they kill the
app.

Detach + destroy the dead WebView and call Activity.recreate() so
onCreate runs again with a fresh WebView and the last URL is
re-loaded through the normal path. This is the recovery pattern
the framework documentation recommends. Returning true keeps the
hosting app process alive across the renderer crash.
@jim-daf jim-daf marked this pull request as ready for review April 30, 2026 14:11
Copilot AI review requested due to automatic review settings April 30, 2026 14:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates WebViewActivity to correctly recover when the WebView renderer process is lost by tearing down the dead WebView and recreating the activity (refs #5823), instead of attempting to reuse an unusable WebView instance.

Changes:

  • Detects whether the renderer “crashed” vs was killed (didCrash) and improves the diagnostic log message.
  • Detaches and destroys the dead WebView, then calls Activity.recreate() to rebuild a fresh instance.
  • Skips recovery work when the activity is already finishing or relaunching.

Comment on lines +584 to +589
val didCrash = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
handler?.didCrash() == true
} else {
// On API < 26 didCrash() is unavailable; treat as a crash so we still recover.
true
}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

The API < 26 branch/comment is misleading: framework WebViewClient.onRenderProcessGone(...) is only invoked on API ≥ 26, so this branch won’t run in practice. Consider simplifying to val didCrash = handler?.didCrash() == true and updating the comment accordingly (or rewording it as a defensive fallback for unexpected/null detail rather than an API-level behavior).

Suggested change
val didCrash = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
handler?.didCrash() == true
} else {
// On API < 26 didCrash() is unavailable; treat as a crash so we still recover.
true
}
val didCrash = handler?.didCrash() == true

Copilot uses AI. Check for mistakes.
Comment on lines +604 to 610
if (!isFinishing && !isRelaunching) {
view?.let { dead ->
(dead.parent as? android.view.ViewGroup)?.removeView(dead)
dead.destroy()
}
recreate()
}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

Calling recreate() unconditionally on every onRenderProcessGone can lead to a fast recreation loop if the renderer keeps getting killed immediately (e.g., persistent GPU/driver failure), which can degrade UX and battery and potentially make the app unusable. Consider adding a simple guard/backoff (e.g., only recreate once per activity instance, or rate-limit recreations using a timestamp in savedInstanceState / a field) and fall back to finishing the activity or showing an error state if it repeats.

Copilot uses AI. Check for mistakes.
@jpelgrom
Copy link
Copy Markdown
Member

jpelgrom commented May 4, 2026

I'm hesitant to accept this as a fix without being able to reproduce the issue to confirm. The existing log line onRenderProcessGone: webView crashed does not show up in their log, suggesting this code path won't be triggered. In the linked issue the page remains interactive and the user can switch between normal and edit mode. It is only this graph card on the screen that decides to not work in normal/view mode only. Switching is specifically mentioned so it is not the entire renderer that's broken.

The current behavior with 'only' reloading instead of recreating as suggested in the docs was added after user feedback - #3787.

Wouldn't this change potentially result in a recreate loop? If this is on the start page they crash and will get recreated every time which reloads the page and crash and ...

@TimoPtr
Copy link
Copy Markdown
Member

TimoPtr commented May 4, 2026

If it is triggered by OOM there is high change that it is going to crash again. I share the same feelings as @jpelgrom.

@TimoPtr TimoPtr marked this pull request as draft May 5, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants