Remove extension WebView data during uninstall

This commit is contained in:
Levi committed 2026-09-14 10:11:53 +08:00
1 parent c47b735fc4
commit d642ae5018
25 files changed
+609 -19

No files matched your search

@@ -29,7 +29,7 @@ android {
compileSdk 34
defaultConfig {
minSdkVersion 19
minSdkVersion 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
@@ -49,7 +49,7 @@ android {
}
}
dependencies {
implementation 'androidx.webkit:webkit:1.12.0'
implementation 'androidx.webkit:webkit:1.13.0'
implementation 'androidx.browser:browser:1.8.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
@@ -1,6 +1,12 @@
package com.pichillilorenzo.flutter_inappwebview_android;
import android.net.Uri;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.webkit.Profile;
import androidx.webkit.WebStorageCompat;
import java.util.HashSet;
import java.util.Set;
import androidx.webkit.ProfileStore;
import androidx.webkit.ProxyConfig;
import androidx.webkit.ProxyController;
@@ -10,6 +16,76 @@ import io.flutter.plugin.common.MethodChannel;
// Internal host bridge: profile creation and proxy completion precede navigation.
final class GopeedProfiles {
private static SharedPreferences preferences;
private static final Set<String> cleared = new HashSet<>();
private static final String DELETIONS = "pendingDeletions";
static void initialize(Context context) {
if (preferences != null) return;
preferences = context.getSharedPreferences("gopeed_webview_profiles", Context.MODE_PRIVATE);
if (!WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE)) return;
for (String id : deletions()) {
try {
// Do this before getProfile/getOrCreateProfile load any profile.
ProfileStore.getInstance().deleteProfile(id);
setPending(id, false);
} catch (RuntimeException ignored) {
// Keep the durable entry and reject use until removal succeeds.
}
}
}
private static Set<String> deletions() {
return new HashSet<>(preferences.getStringSet(DELETIONS, new HashSet<>()));
}
private static void setPending(String id, boolean pending) {
Set<String> ids = deletions();
if (pending) ids.add(id); else ids.remove(id);
if (!preferences.edit().putStringSet(DELETIONS, ids).commit())
throw new IllegalStateException("Unable to persist WebView profile removal");
}
static void remove(MethodCall call, MethodChannel.Result result) {
if (!WebViewFeature.isFeatureSupported(WebViewFeature.MULTI_PROFILE)) {
result.error("UNAVAILABLE", "Android WebView profile removal is unavailable", null);
return;
}
String id = call.argument("gopeedProfileId");
try {
java.util.UUID.fromString(id);
setPending(id, true);
try {
ProfileStore.getInstance().deleteProfile(id);
setPending(id, false);
cleared.remove(id);
result.success(true);
return;
} catch (IllegalStateException loadedProfile) {
// Android forbids deleting a profile loaded in this process, even
// after every WebView has been destroyed. Clear all its data now;
// remove the empty profile at the next process start.
}
if (!WebViewFeature.isFeatureSupported(WebViewFeature.DELETE_BROWSING_DATA)) {
result.error("PROFILE_REMOVE_FAILED", "Restart Gopeed to finish removing WebView profile data", null);
return;
}
Profile profile = ProfileStore.getInstance().getProfile(id);
if (profile == null) {
setPending(id, false);
result.success(true);
return;
}
profile.getGeolocationPermissions().clearAll();
WebStorageCompat.deleteBrowsingData(profile.getWebStorage(), () -> {
cleared.add(id);
result.success(true);
});
} catch (RuntimeException e) {
result.error("PROFILE_REMOVE_FAILED", "Unable to remove WebView profile data", null);
}
}
private static String appliedProxy;
private static String pendingProxy;
private static final java.util.List<MethodChannel.Result> pending = new java.util.ArrayList<>();
@@ -26,6 +102,13 @@ final class GopeedProfiles {
Uri uri = Uri.parse(proxy);
if (!"http".equals(uri.getScheme()) || !"127.0.0.1".equals(uri.getHost()) || uri.getPort() <= 0)
throw new IllegalArgumentException("Invalid host proxy configuration");
if (deletions().contains(id)) {
if (!cleared.contains(id)) throw new IllegalStateException("Profile removal requires restart");
// Reinstallation may reuse the now-empty profile. Do not delete its
// new data on the next start.
setPending(id, false);
cleared.remove(id);
}
ProfileStore.getInstance().getOrCreateProfile(id);
if (proxy.equals(appliedProxy)) { result.success(true); return; }
if (pendingProxy != null) {
@@ -87,6 +87,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger, Activity activity, PlatformViewRegistry platformViewRegistry, FlutterView flutterView) {
this.applicationContext = applicationContext;
GopeedProfiles.initialize(applicationContext);
this.activity = activity;
this.messenger = messenger;
this.flutterView = flutterView;
@@ -49,6 +49,10 @@ public class MyCookieManager extends ChannelDelegateImpl {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (call.method.equals("gopeed.removeProfile")) {
GopeedProfiles.remove(call, result);
return;
}
if (call.method.equals("gopeed.prepareProfile")) {
GopeedProfiles.prepare(call, result);
return;