Skip to content

Destroy LilicoWebView when tabs and WebViewActivity tear down#2995

Open
jim-daf wants to merge 1 commit into
onflow:devfrom
jim-daf:fix/webview-lifecycle-destroy
Open

Destroy LilicoWebView when tabs and WebViewActivity tear down#2995
jim-daf wants to merge 1 commit into
onflow:devfrom
jim-daf:fix/webview-lifecycle-destroy

Conversation

@jim-daf
Copy link
Copy Markdown

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

Background

BrowserTabs.kt keeps a process-wide mutableListOf<BrowserTab>() where each tab owns a LilicoWebView. Two things happen to that list as the user uses the in-app dApp browser:

  1. popBrowserTab removes the tab entry and detaches the WebView from its parent container, but it never calls destroy() on the WebView.
  2. clearBrowserTabs (invoked from releaseBrowser when the user leaves the browser, and from a few other places) just runs tabs.clear() and does not destroy the WebViews either.

WebViewActivity has the same gap. The activity holds a LilicoWebView through the inflated layout for its full lifetime and never calls destroy on onDestroy.

A WebView is mostly a thin Java handle on top of a chromium renderer process, a JS heap, GPU buffers, and a pile of native parcel buffers. None of that is freed until destroy() is called. Until then the GC cannot reclaim the wrapper either, because the platform keeps internal references back into it. So every dApp the user opens leaks a renderer worth of memory, and the leak persists across the user backing out of the browser, switching wallets, or closing tabs.

Why this matches the open OOM reports

Several open issues report OutOfMemoryError from very different call sites: ExploreFragment, FclSignMessageDialog, EVMSignMessageDialog, generic 16 to 64 byte allocation failures inside Parcel.nativeCreate, RxJava OnErrorNotImplementedException wrappers around OOM, and so on. The common thread is that none of those allocation sites are large by themselves, they only fail when the process heap is already saturated. A leaking WebView per browser session is a very plausible cause of that saturation, and the fact that the failures cluster on devices with a long browser usage session is consistent with that pattern.

This PR is not a claim that every one of those reports is fully explained by this single leak, but stopping the leak removes the most obvious systemic source of pressure and should reduce the rate noticeably.

Related reports that should benefit:

Changes

  • Added a private LilicoWebView.destroyWebView() extension in BrowserTabs.kt. It runs the standard cleanup recommended by the Android team inside runCatching: stop loading, drop the webview callback, navigate to about:blank, clear history, detach from any parent, drop child views, then destroy.
  • Called destroyWebView() from popBrowserTab after the existing removeWebView call.
  • Called destroyWebView() for every tab inside clearBrowserTabs before tabs.clear() runs.
  • Overrode onDestroy in WebViewActivity to apply the same cleanup, so the standalone activity stops leaking too.

The browser keeps a list of LilicoWebView instances in BrowserTabs.kt and never calls destroy on them. popBrowserTab only removes the view from its parent and clearBrowserTabs only empties the list. WebViewActivity has the same gap, the WebView is held by the layout for the lifetime of the activity and is dropped on the floor when the user leaves.

The chromium-side resources behind a WebView (renderer process, GPU buffers, JS heap, parcel buffers) stay live until destroy is called, so each navigation that creates or rotates a tab leaks memory. The OOM cluster of crash reports against ExploreFragment and the dialogs that hover above the browser are consistent with that leak. The same root cause shows up in the RxJava OutOfMemory wrappers and in the various 16 to 64 byte allocation failures reported from random Parcel and SystemSensor sites.

Changes:
- Add a private LilicoWebView.destroyWebView extension that runs the standard cleanup sequence (stopLoading, clear callback, about:blank, clearHistory, detach, destroy) inside runCatching.
- Call destroyWebView from popBrowserTab after the view is removed from the container.
- Call destroyWebView for every tab inside clearBrowserTabs before the list is emptied.
- Override onDestroy in WebViewActivity to apply the same cleanup.
@jim-daf jim-daf marked this pull request as ready for review April 22, 2026 20:14
@jim-daf jim-daf requested a review from a team as a code owner April 22, 2026 20:14
Copilot AI review requested due to automatic review settings April 22, 2026 20:14
Copy link
Copy Markdown

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

This PR addresses WebView-backed memory leaks in the in-app dApp browser by ensuring LilicoWebView instances are explicitly torn down when tabs are closed/cleared and when WebViewActivity is destroyed.

Changes:

  • Added a LilicoWebView.destroyWebView() teardown helper in BrowserTabs.kt and invoked it when popping tabs and clearing all tabs.
  • Updated clearBrowserTabs() to destroy each tab’s WebView before clearing the global tab list.
  • Added onDestroy() cleanup in WebViewActivity to explicitly destroy its LilicoWebView.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
app/src/main/java/com/flowfoundation/wallet/page/common/WebViewActivity.kt Adds onDestroy() cleanup steps to stop and destroy the activity-owned LilicoWebView.
app/src/main/java/com/flowfoundation/wallet/page/browser/tools/BrowserTabs.kt Introduces a WebView teardown helper and applies it to tab pop/clear paths to reduce persistent WebView memory usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +119 to +123
loadUrl("about:blank")
clearHistory()
removeFromParent()
removeAllViews()
destroy()
Comment on lines +117 to +120
stopLoading()
setWebViewCallback(null)
loadUrl("about:blank")
clearHistory()
// chromium-side resources (renderer process, GPU buffers, JS heap) stay
// alive until the GC happens to collect the wrapper, which is a major
// contributor to the OOM reports filed against ExploreFragment.
tabs.forEach { runCatching { it.webView.destroyWebView() }.onFailure { loge(it) } }
Comment on lines +32 to +35
web.stopLoading()
web.setWebViewCallback(null)
web.loadUrl("about:blank")
web.clearHistory()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants