fix(WebView): properly recover when the renderer process is gone (refs #5823)#6793
fix(WebView): properly recover when the renderer process is gone (refs #5823)#6793jim-daf wants to merge 1 commit into
Conversation
…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.
There was a problem hiding this comment.
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 callsActivity.recreate()to rebuild a fresh instance. - Skips recovery work when the activity is already finishing or relaunching.
| 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 | ||
| } |
There was a problem hiding this comment.
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).
| 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 |
| if (!isFinishing && !isRelaunching) { | ||
| view?.let { dead -> | ||
| (dead.parent as? android.view.ViewGroup)?.removeView(dead) | ||
| dead.destroy() | ||
| } | ||
| recreate() | ||
| } |
There was a problem hiding this comment.
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.
|
I'm hesitant to accept this as a fix without being able to reproduce the issue to confirm. The existing log line 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 ... |
|
If it is triggered by OOM there is high change that it is going to crash again. I share the same feelings as @jpelgrom. |
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:
i.e. the Mali GPU driver dropped the renderer's GL context (the
EXT_robustnessGPU 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 viaWebViewClient.onRenderProcessGone(view, detail)withdetail.didCrash() == false(renderer killed by the system, not crashed by the page).Why the current code does not recover
WebViewActivityalready overridesonRenderProcessGone, but the body is:Per the
android.webkit.WebViewJavadoc, 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: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:
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.destroy()on it so its native resources are released.Activity.recreate()soonCreateruns again with a brand-new WebView, the presenter re-loads the last URL through the normal path, and the JS bridge is re-registered.relaunchApp()/ a back-press tear-down.trueso the hosting app process is not killed by the system when the renderer dies.Scope
app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt.onRenderProcessGonesimply does not fire there.