Commit acfb32e
authored
Persist webview session in storage rather than memory (#8709)
Task/Issue URL:
https://app.asana.com/1/137249556945/project/715106103902962/task/1215107805550918?focus=true
### Description
Persist each tab's WebView back/forward history to Room so back
navigation works after the OS kills the app. Today the WebView session
bundle is held only in a 10 MiB in-memory `LruCache`, so
after process death the back button on a restored tab has nothing to
walk back through and closes the tab instead. This PR replaces the
in-memory cache with a Room-backed implementation,
gates the swap behind a kill-switch feature flag, and adds observability
for unusually-large bundles.
**What changes:**
- New `webview_sessions` Room table (FK to `tabs(tabId)` with `ON DELETE
CASCADE`), DAO, and `AppDatabase` migration 61 → 62.
- `RoomWebViewSessionStorage` marshals `WebView.saveState(Bundle)` to
bytes and persists per-tab. On restore, it unmarshals and calls
`WebView.restoreState`. Bundle-level errors are caught
and treated as a restore miss (defensive against WebView version skew).
- `WebViewSessionStorageProxy` is the bound `WebViewSessionStorage`
implementation. It delegates to either `RoomWebViewSessionStorage` (new
behaviour) or the resurrected
`InMemoryWebViewSessionStorage` (legacy LruCache), based on the new
`webViewSessionPersistence` toggle under `AndroidBrowserConfigFeature`
(default ON). The toggle is read once via `by lazy`
so the routing decision is stable for the process lifetime — kill-switch
flips take effect on next launch.
- `WebViewSessionStorage.restoreSession` and `deleteAllSessions` are now
`suspend`. `BrowserTabViewModel.restoreWebViewState` becomes `suspend`
and is launched from the fragment's
`onViewStateRestored` via `viewLifecycleOwner.lifecycleScope`.
- Cold-start navigation is consolidated: `onViewReady` no longer
auto-navigates to `TabEntity.url`; `restoreWebViewState` is the single
navigation entry point on first load (restore from Room
→ success, otherwise fall back to omnibar text or `TabEntity.url`). This
avoids a double-navigation race that produced duplicate history entries
after restore.
- New `m_webview_session_large_bytes_count` pixel fires when a
marshalled bundle exceeds 256 KiB, so we can monitor the size
distribution before deciding whether to cap or compress.
- Tests added for the DAO (instrumentation, including CASCADE on tab
delete), `RoomWebViewSessionStorage` (round-trip, defensive paths,
pixel-firing), `WebViewSessionStorageProxy` (routing
per flag state), and `BrowserTabViewModel`'s new cold-start fallback
path.
### Steps to test this PR
_Cold-start back navigation_
- [x] Install internal build.
- [x] Open a new tab and navigate duckduckgo.com → wikipedia.org →
cnn.com. Verify the back button works inside the session.
- [x] Background the app, then force-stop it.
- [x] Relaunch the app. The active tab restores at cnn.com at the same
scroll position when you closed the app.
- [x] Press back. **Expected:** WebView navigates to wikipedia.org.
Press back again → duckduckgo.com.
_Fire button still wipes saved sessions_
- [x] After the above flow, press the Fire button and confirm data is
deleted from the `webview_sessions` table inside the app DB.
_Tab close path_
- [x] Open a tab, navigate two pages.
- [x] Close the tab from the tab switcher.
- [x] Restart the app. The closed tab is gone (no resurrection from
stale Room rows).
_Escape hatch_
- [x] Go to settings -> General -> After Inactivity.
- [x] Select Always and New Tab Page.
- [x] Open a new tab and navigate duckduckgo.com → wikipedia.org →
cnn.com
- [x] Close the app and open it again
- [x] You should see the NTP with the escape hatch
- [x] Click on the escape hatch and the website should reopen on the
same scroll position and you should be able to go back to wikipedia and
then to duckduckgo.com
_Kill-switch rollback_
- [x] Flip the `webViewSessionPersistence` subfeature OFF via internal
config tooling.
- [x] Cold-restart the app. Repeat the first scenario.
- [x] **Expected:** legacy behavior back closes the tab after force-stop
+ relaunch (same as production today).
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches core tab restore/navigation after process death and adds a DB
migration; incorrect restore timing or fallback could cause double-loads
or lost back stack, though a feature flag allows rollback to in-memory
behavior.
>
> **Overview**
> Per-tab **WebView back/forward history** can now survive process death
by persisting `WebView.saveState` bundles in a new **`webview_sessions`
Room table** (DB v62, FK to `tabs` with cascade delete), via
**`RoomWebViewSessionStorage`**.
>
> **`WebViewSessionStorageProxy`** is the injected implementation: it
picks **Room vs legacy in-memory LRU** from the new
**`webViewSessionPersistence`** remote toggle (stable for the process
via `lazy`). The manual **`BrowserModule` in-memory binding is
removed**; restore/delete-all APIs become **`suspend`**, and
**`restoreWebViewState`** runs from **`onViewStateRestored`** in a
lifecycle coroutine.
>
> Cold-start navigation is **deduplicated**: **`onViewReady` no longer
auto-loads the tab URL**; restore is the single entry point, with
fallback to omnibar text or **`TabEntity.url`**. A
**`webview_session_large_bytes`** pixel (256 KiB threshold) plus param
cleaning was added for bundle-size monitoring.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b20510e. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent 9589604 commit acfb32e
19 files changed
Lines changed: 1895 additions & 26 deletions
File tree
- PixelDefinitions/pixels/definitions
- app
- schemas/com.duckduckgo.app.global.db.AppDatabase
- src
- main/java/com/duckduckgo/app
- browser
- di
- session
- di
- global
- api
- db
- pixels
- remoteconfig
- test/java/com/duckduckgo/app/browser
- session
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
0 commit comments