Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion flutter_inappwebview/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 6.2.0-beta.3

- Added `ContainerController` (`getAllContainerNames`, `hasContainer`, `deleteContainer`, `clearContainerData`) for enumerating, clearing and deleting named persistent storage containers. `clearContainerData` empties a container's cookies, DOM storage, IndexedDB, ServiceWorkers and HTTP cache without removing the container itself — works while WebViews are still bound, which `deleteContainer` cannot
- Added `containerId` property to `InAppWebViewSettings` for joining a named storage container at WebView construction (Android `androidx.webkit.Profile`, iOS 17+ / macOS 14+ `WKWebsiteDataStore(forIdentifier:)`, Linux `WebKitNetworkSession`)
- Added `proxySettings` property to `InAppWebViewSettings` for per-WebView proxy (iOS 17+ / macOS 14+ `WKWebsiteDataStore.proxyConfigurations`)
- Added Linux support
- Updated dependencies to the latest versions for all platform implementations:
- `flutter_inappwebview_platform_interface`: `^1.4.0-beta.2` -> `^1.4.0-beta.3`
Expand All @@ -8,7 +11,7 @@
- `flutter_inappwebview_macos`: `^1.2.0-beta.2` -> `^1.2.0-beta.3`
- `flutter_inappwebview_web`: `^1.2.0-beta.2` -> `^1.2.0-beta.3`
- `flutter_inappwebview_windows`: `^0.7.0-beta.2` -> `^0.7.0-beta.3`
- `flutter_inappwebview_linux`: `^0.1.0-beta.1`
- `flutter_inappwebview_linux`: `^0.1.0-beta.2`
- Added `InAppWebViewController.getFavicon` wrapper with `faviconImageFormat` support.
- Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197)
- Mapped `isClassSupported`, `isPropertySupported`, `isMethodSupported` platform interface static methods to the corresponding plugin classes such as `InAppWebViewController`, `InAppWebView`, `InAppBrowser`, etc., in order to check if a class, property, or method is supported by the platform at runtime
Expand All @@ -17,6 +20,9 @@
- Minimum Flutter SDK `>=3.32.0`

#### Platform Interface
- Added `PlatformContainerController` class with `getAllContainerNames`, `hasContainer`, `deleteContainer` methods
- Added `containerId` property to `InAppWebViewSettings` for joining a named storage container
- Added `proxySettings` property to `InAppWebViewSettings` for per-WebView proxy
- Updated `flutter_inappwebview_internal_annotations` dependency from `^1.2.0` to `^1.3.0`
- Added `isClassSupported`, `isPropertySupported`, `isMethodSupported` static methods for all main classes, such as `PlatformInAppWebViewController`, `InAppWebViewSettings`, `PlatformInAppBrowser`, etc., in order to check if a class, property, or method is supported by the platform at runtime
- Added `isSupported` method to all custom enum classes
Expand All @@ -28,6 +34,9 @@
- Deprecated `onReceivedIcon` in favor of `onFaviconChanged`

#### Android Platform
- Implemented `PlatformContainerController` (`getAllContainerNames`, `hasContainer`, `deleteContainer`) wrapping `androidx.webkit.ProfileStore`
- Implemented WebView container join via `InAppWebViewSettings.containerId`, bound through `WebViewCompat.setProfile` / `ProfileStore.getOrCreateProfile` before any session-bound op runs in `InAppWebView.prepare()` (requires `WebViewFeature.MULTI_PROFILE`, System WebView 110+)
- `MyCookieManager` now resolves the container's `CookieManager` via the new `webViewId` argument so cookie ops on a WebView in a container land in that container's jar
- Updated native dependencies:
- implementation from `'androidx.webkit:webkit:1.12.0'` to `'androidx.webkit:webkit:1.14.0'`
- implementation from `'androidx.browser:browser:1.8.0'` to `'androidx.browser:browser:1.9.0'`
Expand All @@ -48,6 +57,14 @@
- Merged "fix #2484, Remove not-empty assert for Cookie.value" [#2486](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2486) (thanks to [laishere](https://github.com/laishere))

#### macOS and iOS Platforms
- Implemented `PlatformContainerController` (`getAllContainerNames`, `hasContainer`, `deleteContainer`) on iOS 17+ / macOS 14+ via `WKWebsiteDataStore.fetchAllDataStoreIdentifiers` / `WKWebsiteDataStore.remove(forIdentifier:)`, with a `UserDefaults`-backed `containerId ↔ UUID` registry so listing returns original strings
- Implemented WebView container join via `InAppWebViewSettings.containerId` (iOS 17+ / macOS 14+), wiring `WKWebsiteDataStore(forIdentifier:)` in `preWKWebViewConfiguration(settings:)` with a SHA-256-derived stable UUID
- Implemented per-WebView proxy via `InAppWebViewSettings.proxySettings` (iOS 17+ / macOS 14+) attached to the WebView's data store
- `MyCookieManager` now resolves the container's `WKHTTPCookieStore` via the new `webViewId` argument so cookie ops on a WebView in a container land in that container's jar

#### Linux Platform
- Implemented `PlatformContainerController` (`getAllContainerNames`, `hasContainer`, `deleteContainer`) backed by `<XDG_DATA_HOME>/flutter_inappwebview/containers/` and `<XDG_CACHE_HOME>/flutter_inappwebview/containers/`
- Implemented WebView container join via `InAppWebViewSettings.containerId` using a process-wide cached `WebKitNetworkSession` with the container's data and cache directories, wired at `webkit_web_view_new` time. Multiple WebViews joining the same container share storage. Honored on WPE WebKit 2.40+
- Implemented `saveState`, `restoreState` InAppWebViewController methods
- Implemented `PlatformProxyController` class
- Add Swift Package Manager support [#2409](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2409)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
part of 'main.dart';

void containerIsolation() {
// containerId is honored on Android (System WebView 110+ via the
// `MULTI_PROFILE` feature), iOS 17+ and macOS 14+. The setting is
// serialized on every platform but only takes effect on those.
final shouldSkip = !InAppWebViewSettings.isPropertySupported(
InAppWebViewSettingsProperty.containerId,
);

// Two WebViews bound to the same profile must share storage; two
// WebViews bound to different profiles must not. We probe this with
// `localStorage` since it round-trips faster than HTTP cookies and
// doesn't depend on the test server.
skippableTestWidgets('containerId isolates storage between profiles', (
WidgetTester tester,
) async {
final url = TEST_CROSS_PLATFORM_URL_1;
final keyA = 'profile-isolation-a-${DateTime.now().millisecondsSinceEpoch}';
final keyB = 'profile-isolation-b-${DateTime.now().millisecondsSinceEpoch}';

Future<InAppWebViewController> launch(String? containerId, Key key) async {
final controllerCompleter = Completer<InAppWebViewController>();
final pageLoaded = Completer<void>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: key,
initialUrlRequest: URLRequest(url: url),
initialSettings: InAppWebViewSettings(
javaScriptEnabled: true,
containerId: containerId,
),
onWebViewCreated: controllerCompleter.complete,
onLoadStop: (_, _) => pageLoaded.complete(),
),
),
);
await tester.pumpAndSettle();
final controller = await controllerCompleter.future;
await pageLoaded.future;
return controller;
}

// Profile A: write a marker to localStorage.
final a1 = await launch('test-profile-a', GlobalKey());
await a1.evaluateJavascript(source: "localStorage.setItem('$keyA', 'A');");

// Profile B: write a different marker.
final b1 = await launch('test-profile-b', GlobalKey());
await b1.evaluateJavascript(source: "localStorage.setItem('$keyB', 'B');");

// Re-open profile A: marker A must still be there, marker B must not.
final a2 = await launch('test-profile-a', GlobalKey());
final aReadA = await a2.evaluateJavascript(
source: "localStorage.getItem('$keyA');",
);
final aReadB = await a2.evaluateJavascript(
source: "localStorage.getItem('$keyB');",
);
expect(aReadA, 'A', reason: 'profile A should retain its own marker');
expect(aReadB, isNull, reason: "profile A must not see profile B's data");
}, skip: shouldSkip);

// ProfileController surfaces the registered containerIds and lets the
// caller delete one. We register a probe profile via a HeadlessInAppWebView,
// assert listing/has return it, then delete and assert it's gone.
final controllerSkip = !ProfileController.isClassSupported();
skippableTestWidgets('ProfileController list/has/delete', (
WidgetTester tester,
) async {
final url = TEST_CROSS_PLATFORM_URL_1;
final probeId =
'profile-controller-${DateTime.now().millisecondsSinceEpoch}';

// Materialize the profile by loading a page in a HeadlessInAppWebView
// bound to it, then dispose the WebView (deleteProfile fails while
// a live WebView is using the profile on every supported platform).
final pageLoaded = Completer<void>();
final headless = HeadlessInAppWebView(
initialUrlRequest: URLRequest(url: url),
initialSettings: InAppWebViewSettings(containerId: probeId),
onLoadStop: (_, _) => pageLoaded.complete(),
);
await headless.run();
await pageLoaded.future;
// Force a write so the data store actually gets persisted to disk
// (Apple's `allDataStoreIdentifiers` only lists materialized stores).
await headless.webViewController?.evaluateJavascript(
source: "localStorage.setItem('probe', '1');",
);
await headless.dispose();

final controller = ProfileController.instance();
final names = await controller.getAllContainerNames();
expect(names, contains(probeId));
expect(await controller.hasContainer(probeId), isTrue);

expect(await controller.deleteContainer(probeId), isTrue);
expect(await controller.hasContainer(probeId), isFalse);
}, skip: controllerSkip);
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ part 'resize_webview.dart';
part 'web_archive.dart';
part 'set_custom_useragent.dart';
part 'set_get_settings.dart';
part 'container_isolation.dart';
part 'set_web_contents_debugging_enabled.dart';
part 'should_intercept_request.dart';
part 'should_override_url_loading.dart';
Expand Down
72 changes: 72 additions & 0 deletions flutter_inappwebview/lib/src/container_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';

///{@macro flutter_inappwebview_platform_interface.PlatformContainerController}
///
///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.supported_platforms}
class ContainerController {
///{@macro flutter_inappwebview_platform_interface.PlatformContainerController}
ContainerController()
: this.fromPlatformCreationParams(
const PlatformContainerControllerCreationParams(),
);

/// Constructs a [ContainerController] from creation params for a specific
/// platform.
ContainerController.fromPlatformCreationParams(
PlatformContainerControllerCreationParams params,
) : this.fromPlatform(PlatformContainerController(params));

/// Constructs a [ContainerController] from a specific platform
/// implementation.
ContainerController.fromPlatform(this.platform);

/// Implementation of [PlatformContainerController] for the current platform.
final PlatformContainerController platform;

static ContainerController? _instance;

///Gets the [ContainerController] shared instance.
static ContainerController instance() {
return _instance ??= ContainerController();
}

///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.getAllContainerNames}
///
///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.getAllContainerNames.supported_platforms}
Future<List<String>> getAllContainerNames() =>
platform.getAllContainerNames();

///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.hasProfile}
///
///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.hasProfile.supported_platforms}
Future<bool> hasContainer(String containerId) =>
platform.hasContainer(containerId);

///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.deleteProfile}
///
///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.deleteProfile.supported_platforms}
Future<bool> deleteContainer(String containerId) =>
platform.deleteContainer(containerId);

///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.clearContainerData}
///
///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.clearContainerData.supported_platforms}
Future<bool> clearContainerData(String containerId) =>
platform.clearContainerData(containerId);

///{@macro flutter_inappwebview_platform_interface.PlatformContainerControllerCreationParams.isClassSupported}
static bool isClassSupported({TargetPlatform? platform}) =>
PlatformContainerController.static().isClassSupported(platform: platform);

///{@macro flutter_inappwebview_platform_interface.PlatformContainerController.isMethodSupported}
static bool isMethodSupported(
PlatformContainerControllerMethod method, {
TargetPlatform? platform,
}) => PlatformContainerController.static().isMethodSupported(
method,
platform: platform,
);
}
1 change: 1 addition & 0 deletions flutter_inappwebview/lib/src/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export 'web_notification/main.dart';
export 'print_job/main.dart';
export 'find_interaction/main.dart';
export 'service_worker_controller.dart';
export 'container_controller.dart';
export 'proxy_controller.dart';
export 'webview_asset_loader.dart';
export 'tracing_controller.dart';
Expand Down
3 changes: 3 additions & 0 deletions flutter_inappwebview_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 1.2.0-beta.3

- Implemented `PlatformContainerController` (`getAllContainerNames`, `hasContainer`, `deleteContainer`, `clearContainerData`) wrapping `androidx.webkit.ProfileStore`; methods return empty/false when `WebViewFeature.MULTI_PROFILE` is unsupported. `clearContainerData` composes per-profile `CookieManager.removeAllCookies`, `WebStorage.deleteAllData` and `GeolocationPermissions.clearAll`; HTTP cache (per-WebView) and global `ServiceWorkerControllerCompat` are documented as not reached by this call
- Implemented per-WebView persistent profile partitioning via `InAppWebViewSettings.containerId`, bound through `WebViewCompat.setProfile` / `ProfileStore.getOrCreateProfile` before any session-bound op runs in `InAppWebView.prepare()` (requires `WebViewFeature.MULTI_PROFILE`, System WebView 110+; ignored otherwise)
- `MyCookieManager` now resolves the per-profile `CookieManager` via the new `webViewId` argument so cookie ops on a profile-bound WebView land in the profile's jar
- Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3
- Updated native dependencies:
- implementation from `'androidx.webkit:webkit:1.12.0'` to `'androidx.webkit:webkit:1.14.0'`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserManager;
import com.pichillilorenzo.flutter_inappwebview_android.print_job.PrintJobManager;
import com.pichillilorenzo.flutter_inappwebview_android.process_global_config.ProcessGlobalConfigManager;
import com.pichillilorenzo.flutter_inappwebview_android.container.ContainerManager;
import com.pichillilorenzo.flutter_inappwebview_android.proxy.ProxyManager;
import com.pichillilorenzo.flutter_inappwebview_android.service_worker.ServiceWorkerManager;
import com.pichillilorenzo.flutter_inappwebview_android.tracing.TracingControllerManager;
Expand Down Expand Up @@ -55,6 +56,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
public WebViewFeatureManager webViewFeatureManager;
@Nullable
public ProxyManager proxyManager;
public ContainerManager containerManager;
@Nullable
public PrintJobManager printJobManager;
@Nullable
Expand Down Expand Up @@ -111,6 +113,7 @@ private void onAttachedToEngine(Context applicationContext, BinaryMessenger mess
}
webViewFeatureManager = new WebViewFeatureManager(this);
proxyManager = new ProxyManager(this);
containerManager = new ContainerManager(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
printJobManager = new PrintJobManager(this);
}
Expand Down Expand Up @@ -168,6 +171,10 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
proxyManager.dispose();
proxyManager = null;
}
if (containerManager != null) {
containerManager.dispose();
containerManager = null;
}
if (printJobManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
printJobManager.dispose();
printJobManager = null;
Expand Down
Loading