From 42edc13be23c2ed0ff960cd6f5cb9dc8c0e83598 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 22:19:06 +0000 Subject: [PATCH] feat(napplets): back gesture pages back in the WebView, then exits to Amethyst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sandbox host now routes the back gesture into the applet/site's own history first: an OnBackPressedCallback calls webView.goBack() while there's history to pop (in-page links, iframe navigations, and history.pushState all count), and only disables itself — letting system back return to Amethyst — once the WebView is at its first page. The callback's enabled state tracks canGoBack() via doUpdateVisitedHistory / onPageFinished. Works with predictive back. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde --- .../napplethost/NappletHostActivity.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt index 81a0ff9852..b0fd6fdf4f 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt @@ -49,6 +49,7 @@ import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast import androidx.activity.ComponentActivity +import androidx.activity.OnBackPressedCallback import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat @@ -132,6 +133,25 @@ class NappletHostActivity : ComponentActivity() { // Set once the WebView has begun loading the shell, so a retry doesn't reload it. private var started = false + // Back goes "page back" inside the applet's WebView history first; only when it can't go back + // further does this disable itself and let the system back exit the sandbox to Amethyst. + private val backCallback = + object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + if (this@NappletHostActivity::webView.isInitialized && webView.canGoBack()) { + webView.goBack() + } else { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + } + } + + /** Keep the in-WebView back gesture enabled exactly while the applet has history to pop. */ + private fun syncBackState() { + if (this::webView.isInitialized) backCallback.isEnabled = webView.canGoBack() + } + private val brokerConnection = object : ServiceConnection { override fun onServiceConnected( @@ -190,6 +210,9 @@ class NappletHostActivity : ComponentActivity() { // class reference, so this sandbox module needs no dependency on :amethyst. bindService(Intent().setClassName(this, NappletHostContract.BROKER_SERVICE_CLASS), brokerConnection, BIND_AUTO_CREATE) + // Route the back gesture into the WebView's history first (see backCallback). + onBackPressedDispatcher.addCallback(this, backCallback) + // Persistent trusted chrome (anti-phishing bar the applet can't draw over) over a content // frame that shows a loading screen → the applet's WebView, or an "unavailable" screen. val root = @@ -359,6 +382,23 @@ class NappletHostActivity : ComponentActivity() { request: WebResourceRequest, ): WebResourceResponse? = contentServer.serve(request) + // The applet's in-page / in-iframe navigations (links + history.pushState) change the + // WebView's back/forward list; keep the back gesture enabled exactly while it can pop. + override fun doUpdateVisitedHistory( + view: WebView, + url: String, + isReload: Boolean, + ) { + syncBackState() + } + + override fun onPageFinished( + view: WebView, + url: String, + ) { + syncBackState() + } + override fun shouldOverrideUrlLoading( view: WebView, request: WebResourceRequest,