Skip to content

Commit acfb32e

Browse files
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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"webview_session_large_bytes": {
3+
"description": "A persisted WebView session bundle exceeded the size threshold (256 KiB) on save. Used to monitor the size distribution of per-tab session state stored in Room. Stripped of appVersion and ATB at send time via PixelInterceptorPixelsRequiringDataCleaning.",
4+
"owners": ["marcosholgado"],
5+
"triggers": ["other"],
6+
"suffixes": ["form_factor"]
7+
}
8+
}

0 commit comments

Comments
 (0)