mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
fix(browser): a rebuilt tab resumes the page it showed
Two ways a browser tab came back on its start URL instead of where the user was, both seen on the emulator: - Every re-created session (after a renderer or `:napplet` death, or a Tor toggle) opened `startUrl`, although the controller tracks `lastUrl`. New sessions now resume `lastUrl`. Only the user's Retry starts over at `startUrl`, which is what it is for. - The memory trim Android sends about a minute into the background (TRIM_MEMORY_BACKGROUND) runs rebuildAll(), which built new controllers and lost the page, history and desktop/zoom/Tor choices of every pinned site. rebuildAll(keepPages = true) now hands each browser tab's page and settings to its rebuilt controller. The theme rebuild keeps them too; an account switch never does, so the next account can't open the previous one's pages. Verified on the emulator with a pinned tab moved off its start URL (P1, desktop mode, start URL P2): - am send-trim-memory BACKGROUND: the WebView was destroyed and rebuilt, and the tab came back on P1 in desktop mode (before: P2, mobile). - Force-stopping the WebView provider (renderer and `:napplet` died): it recovered to P1 in desktop mode (before: P2). - Tor toggle: stays on P1 (before: the start URL). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6962514721
commit
38526fe810
@@ -248,7 +248,7 @@ class Amethyst : Application() {
|
||||
// memory stays freed until the user comes back.
|
||||
val pressure = level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
|
||||
if (pressure && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
EmbeddedTabHost.rebuildAll()
|
||||
EmbeddedTabHost.rebuildAll(keepPages = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
-2
@@ -166,6 +166,24 @@ class EmbeddedWebAppController(
|
||||
var lastCanGoForward = false
|
||||
private set
|
||||
|
||||
/** A tab's page and per-tab settings, carried to the controller that replaces this one on a rebuild. */
|
||||
class PageSnapshot(
|
||||
val url: String?,
|
||||
val useTor: Boolean,
|
||||
val textZoom: Int,
|
||||
val desktopSite: Boolean,
|
||||
)
|
||||
|
||||
fun snapshot() = PageSnapshot(lastUrl, useTor, textZoom, desktopSite)
|
||||
|
||||
/** Takes over a torn-down predecessor's page and settings; call before [bind], which creates the session. */
|
||||
fun restore(snapshot: PageSnapshot) {
|
||||
lastUrl = snapshot.url
|
||||
useTor = snapshot.useTor
|
||||
textZoom = snapshot.textZoom
|
||||
desktopSite = snapshot.desktopSite
|
||||
}
|
||||
|
||||
/** The user's per-tab settings as last set, for a screen coming back to this tab. */
|
||||
val isTorOn: Boolean get() = useTor
|
||||
|
||||
@@ -462,6 +480,17 @@ class EmbeddedWebAppController(
|
||||
publishLoadStatus(EmbeddedLoadStatus(isLoading = true))
|
||||
}
|
||||
|
||||
// Set by the user's Retry: the next session starts over at [startUrl]. Every other re-creation (a crashed
|
||||
// renderer, a `:napplet` restart, a memory-trim rebuild) resumes the page the user was on.
|
||||
private var restartAtStart = false
|
||||
|
||||
/** Where a new session opens: the page on screen before it was lost, else the tab's own [startUrl]. */
|
||||
private fun sessionUrl(): String {
|
||||
val resume = lastUrl?.takeUnless { restartAtStart || it.isBlankPage() }
|
||||
restartAtStart = false
|
||||
return resume ?: startUrl
|
||||
}
|
||||
|
||||
private fun sendCreateSession() {
|
||||
awaitingReady = true
|
||||
uiDisplayed = false
|
||||
@@ -471,7 +500,7 @@ class EmbeddedWebAppController(
|
||||
data =
|
||||
Bundle().apply {
|
||||
putString(NappletBrowserContract.KEY_SESSION_ID, sessionId)
|
||||
putString(NappletBrowserContract.KEY_URL, startUrl)
|
||||
putString(NappletBrowserContract.KEY_URL, sessionUrl())
|
||||
putInt(NappletBrowserContract.KEY_PROXY_PORT, proxyPort())
|
||||
putBoolean(NappletBrowserContract.KEY_USE_TOR, useTor)
|
||||
putInt(NappletBrowserContract.KEY_BG_COLOR, backgroundColor)
|
||||
@@ -683,7 +712,12 @@ class EmbeddedWebAppController(
|
||||
recovery.clearPending()
|
||||
showRecovering()
|
||||
// A surface that never opened has nothing to navigate: only a new session can paint it.
|
||||
if (sessionDead || (sandboxedSdkView != null && !uiDisplayed)) rearmSession() else navigate(startUrl)
|
||||
if (sessionDead || (sandboxedSdkView != null && !uiDisplayed)) {
|
||||
restartAtStart = true
|
||||
rearmSession()
|
||||
} else {
|
||||
navigate(startUrl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLoadState(
|
||||
|
||||
+4
-1
@@ -73,7 +73,10 @@ object EmbeddedTabFactory {
|
||||
if (nightMask == Configuration.UI_MODE_NIGHT_YES) "DARK" else "LIGHT"
|
||||
}
|
||||
}
|
||||
EmbeddedWebAppController(context.applicationContext, ::currentTorPort, initialUseTor, backgroundColor, theme).also { it.bind(url) }
|
||||
EmbeddedWebAppController(context.applicationContext, ::currentTorPort, initialUseTor, backgroundColor, theme).also {
|
||||
EmbeddedTabHost.takePageSnapshot(webAppId(url))?.let(it::restore)
|
||||
it.bind(url)
|
||||
}
|
||||
} as EmbeddedWebAppController
|
||||
|
||||
/**
|
||||
|
||||
+19
-3
@@ -33,6 +33,7 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.vitorpamplona.amethyst.napplethost.NappletHostContract
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.EmbeddedWebAppController
|
||||
|
||||
/**
|
||||
* Process-level holder of **warm embedded sessions** — the persistent-surface-layer half of keep-warm.
|
||||
@@ -178,7 +179,7 @@ object EmbeddedTabHost {
|
||||
val previous = builtDark
|
||||
builtDark = dark
|
||||
// The first report only records what the sessions (built from the same preference) already use.
|
||||
if (previous != null && previous != dark) rebuildAll()
|
||||
if (previous != null && previous != dark) rebuildAll(keepPages = true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,16 +333,31 @@ object EmbeddedTabHost {
|
||||
* screen and the preloader re-acquire freshly built sessions. This keeps [activeId], so the visible tab
|
||||
* re-activates the instant its screen re-acquires — the user just sees the current tab reload, not a
|
||||
* blanked-out surface.
|
||||
*
|
||||
* [keepPages]: each browser tab's rebuilt controller resumes the page it showed, with the user's Tor, zoom
|
||||
* and desktop choices ([EmbeddedWebAppController.PageSnapshot]). Android sends the memory trim routinely,
|
||||
* about a minute into the background, so without this every pinned site came back on its start URL. Never
|
||||
* across an account switch: the next account must not open the previous one's pages.
|
||||
*/
|
||||
fun rebuildAll() {
|
||||
fun rebuildAll(keepPages: Boolean) {
|
||||
// Every page is about to be rebuilt from scratch, so no field survives to restore a keyboard onto.
|
||||
keyboardUpOnLeave.clear()
|
||||
val copy = warm.toList()
|
||||
warm.clear()
|
||||
pageSnapshots.clear()
|
||||
if (keepPages) {
|
||||
copy.forEach { w -> (w.controller as? EmbeddedWebAppController)?.let { pageSnapshots[w.id] = it.snapshot() } }
|
||||
}
|
||||
copy.forEach { it.controller.teardown() }
|
||||
rebuildEpoch += 1
|
||||
}
|
||||
|
||||
// Page state carried from a torn-down browser tab to its rebuilt controller (see [rebuildAll]).
|
||||
private val pageSnapshots = mutableMapOf<String, EmbeddedWebAppController.PageSnapshot>()
|
||||
|
||||
/** The page state [rebuildAll] saved for tab [id], handed over once. */
|
||||
fun takePageSnapshot(id: String): EmbeddedWebAppController.PageSnapshot? = pageSnapshots.remove(id)
|
||||
|
||||
/**
|
||||
* Account the warm sessions were built for, as the opaque WebView storage-profile name (null while
|
||||
* logged out). Kept HERE, next to the sessions it describes, rather than in a composable's `remember`:
|
||||
@@ -372,6 +388,6 @@ object EmbeddedTabHost {
|
||||
builtForProfile = profileName
|
||||
// Seeding on the first call (app start) must not bump the epoch: nothing is stale yet, and a
|
||||
// needless bump would restart the preload sweep that is just getting going.
|
||||
if (!isFirstCall) rebuildAll()
|
||||
if (!isFirstCall) rebuildAll(keepPages = false)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user