Skip to content

Persist webview session in storage rather than memory#8709

Merged
marcosholgado merged 17 commits into
developfrom
feature/marcos/webview_room_session
Jun 24, 2026
Merged

Persist webview session in storage rather than memory#8709
marcosholgado merged 17 commits into
developfrom
feature/marcos/webview_room_session

Conversation

@marcosholgado

@marcosholgado marcosholgado commented May 28, 2026

Copy link
Copy Markdown
Contributor

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

  • Install internal build.
  • Open a new tab and navigate duckduckgo.com → wikipedia.org → cnn.com. Verify the back button works inside the session.
  • Background the app, then force-stop it.
  • Relaunch the app. The active tab restores at cnn.com at the same scroll position when you closed the app.
  • Press back. Expected: WebView navigates to wikipedia.org. Press back again → duckduckgo.com.

Fire button still wipes saved sessions

  • 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

  • Open a tab, navigate two pages.
  • Close the tab from the tab switcher.
  • Restart the app. The closed tab is gone (no resurrection from stale Room rows).

Escape hatch

  • Go to settings -> General -> After Inactivity.
  • Select Always and New Tab Page.
  • Open a new tab and navigate duckduckgo.com → wikipedia.org → cnn.com
  • Close the app and open it again
  • You should see the NTP with the escape hatch
  • 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

  • Flip the webViewSessionPersistence subfeature OFF via internal config tooling.
  • Cold-restart the app. Repeat the first scenario.
  • Expected: legacy behavior back closes the tab after force-stop + relaunch (same as production today).

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.

Reviewed by Cursor Bugbot for commit b20510e. Bugbot is set up for automated code reviews on this repo. Configure here.

@marcosholgado marcosholgado force-pushed the feature/marcos/webview_room_session branch from 394553d to 36f8212 Compare May 28, 2026 17:47
@marcosholgado marcosholgado force-pushed the feature/marcos/webview_room_session branch from 36f8212 to f660778 Compare June 18, 2026 22:12
@marcosholgado marcosholgado marked this pull request as ready for review June 18, 2026 22:12

marcosholgado commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f660778. Configure here.

dao.upsert(WebViewSessionEntity(tabId, bytes, System.currentTimeMillis()))
logcat(INFO) { "Saved WebView session for $tabId (${bytes.size} bytes)" }
}.onFailure { t -> logcat(WARN) { "Failed to save session for $tabId: ${t.asLog()}" } }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Async session save races restore

Medium Severity

RoomWebViewSessionStorage.saveSession persists the marshalled bundle on a background coroutine, while onSaveInstanceState returns as soon as that work is scheduled. On configuration changes or other quick recreate paths, the new tab view can run restoreSession before the upsert finishes, so Room still holds an older or missing row. Restore then fails or loads stale history while the legacy in-memory path updated the LRU cache synchronously in the same call.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f660778. Configure here.

@anikiki anikiki left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good and works as expected! 🎉

@marcosholgado marcosholgado force-pushed the feature/marcos/webview_room_session branch from f660778 to b20510e Compare June 23, 2026 14:06
@marcosholgado marcosholgado merged commit acfb32e into develop Jun 24, 2026
16 checks passed
@marcosholgado marcosholgado deleted the feature/marcos/webview_room_session branch June 24, 2026 08:37
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