From a51f21cfce9d43f05d4ff8f7cb23b29b9a3708f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Ku=C5=9Bnierek?= <6025373+dkodr@users.noreply.github.com> Date: Fri, 11 Sep 2026 16:09:09 +0200 Subject: [PATCH 1/2] Keep the page across process death WebViewActivity saved no state at all, so when Android reclaimed the process the activity came back on the start URL with the scroll position, open view and form contents gone. Cookies survive on disk, so this was never a logout, just a reset. Save through WebViewCompat, which drops the oldest history entries to fit a byte budget instead of risking TransactionTooLargeException: the 1MB savedInstanceState limit is shared by the whole process, and web apps that are not sandboxed all live in the main one. Forward entries are dropped too, since there is no forward button. Only saves where the WebView reports SAVE_STATE. Falling back to the framework saveState would reintroduce exactly the crash the budget exists to avoid. --- app/build.gradle | 2 +- .../cylonid/nativealpha/WebViewActivity.java | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 27b5a6b1..7788cc63 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -129,7 +129,7 @@ dependencies { implementation 'androidx.navigation:navigation-ui:2.8.9' implementation 'com.google.code.gson:gson:2.11.0' implementation 'io.github.medyo:android-about-page:2.0.0' - implementation 'androidx.webkit:webkit:1.13.0' + implementation 'androidx.webkit:webkit:1.17.0' implementation 'com.jakewharton:process-phoenix:2.1.2' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.14.1' diff --git a/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java b/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java index ad2aa915..bf246e35 100644 --- a/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java +++ b/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java @@ -52,6 +52,7 @@ import androidx.core.app.ShareCompat; import androidx.core.content.ContextCompat; import androidx.webkit.WebSettingsCompat; +import androidx.webkit.WebViewCompat; import androidx.webkit.WebViewFeature; import com.cylonid.nativealpha.databinding.DialogHttpAuthBinding; @@ -119,6 +120,8 @@ public class WebViewActivity extends AppCompatActivity implements EasyPermission private AdblockProviderApiHelper adblockProviderApiHelper; private AdblockLifecycleHelper adblockLifecycleHelper; + private static final int MAX_SAVED_STATE_BYTES = 256 * 1024; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -135,14 +138,14 @@ protected void onCreate(Bundle savedInstanceState) { finish(); } else { if(webapp.isBiometricProtection()) { - new BiometricPromptHelper(WebViewActivity.this).showPrompt(() -> setupWebView(), () -> finish(), getString(R.string.bioprompt_restricted_webapp)); + new BiometricPromptHelper(WebViewActivity.this).showPrompt(() -> setupWebView(savedInstanceState), () -> finish(), getString(R.string.bioprompt_restricted_webapp)); } - setupWebView(); + setupWebView(savedInstanceState); } } @SuppressLint("ClickableViewAccessibility") - private void setupWebView() { + private void setupWebView(Bundle savedState) { String processName = Application.getProcessName(); String packageName = this.getPackageName(); @@ -246,7 +249,9 @@ private void setupWebView() { } CUSTOM_HEADERS = initCustomHeaders(webapp.isSendSavedataRequest()); - loadURL(wv, url); + if (savedState == null || wv.restoreState(savedState) == null) { + loadURL(wv, url); + } wv.setWebChromeClient(new CustomWebChromeClient()); wv.setOnLongClickListener(view -> { if(webapp.getAlwaysUseFallbackContextMenu()) return false; @@ -507,6 +512,14 @@ private void showWebViewPopupMenu() { mPopupMenu.show(); } + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + if (wv != null && WebViewFeature.isFeatureSupported(WebViewFeature.SAVE_STATE)) { + WebViewCompat.saveState(wv, outState, MAX_SAVED_STATE_BYTES, false); + } + } + @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); From 5c08ce277c9e830f77df5a4674c3485e02f10d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Ku=C5=9Bnierek?= <6025373+dkodr@users.noreply.github.com> Date: Fri, 11 Sep 2026 16:09:22 +0200 Subject: [PATCH 2/2] Enable the back-forward cache Going back re-ran the page load. With the back-forward cache the previous page is restored from memory, which is what makes in-app back feel instant rather than like a browser reload. --- .../main/java/com/cylonid/nativealpha/WebViewActivity.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java b/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java index bf246e35..c5bcd044 100644 --- a/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java +++ b/app/src/main/java/com/cylonid/nativealpha/WebViewActivity.java @@ -248,6 +248,10 @@ private void setupWebView(Bundle savedState) { wv.getSettings().setBuiltInZoomControls(true); } + if (WebViewFeature.isFeatureSupported(WebViewFeature.BACK_FORWARD_CACHE)) { + WebSettingsCompat.setBackForwardCacheEnabled(wv.getSettings(), true); + } + CUSTOM_HEADERS = initCustomHeaders(webapp.isSendSavedataRequest()); if (savedState == null || wv.restoreState(savedState) == null) { loadURL(wv, url);