[webview_flutter_wkwebview][webview_flutter_android] Fixes bug where the `WebView`s could not be released (#6996)
* fix and test non disposing webview
* combine boolean
* version bump
* more pumpAndSettles
* add more pumpAndSettles
* update int tests
* android test
* fix version bump and unchange compile files
* make _currentNavigationDelegate nullable
* quick fix
* accidental letter
* small tests fixes
* missing hashtag
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index e1786d6..1a490b5 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 3.2.3
+
+* Fixes bug that prevented the web view from being garbage collected.
+* Fixes bug causing a `LateInitializationError` when a `PlatformNavigationDelegate` is not provided.
+
## 3.2.2
* Updates example code for `use_build_context_synchronously` lint.
diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
index 3f62053..af144e5 100644
--- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart
@@ -17,6 +17,8 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
+import 'package:webview_flutter_android/src/android_webview.dart' as android;
+import 'package:webview_flutter_android/src/android_webview_api_impls.dart';
import 'package:webview_flutter_android/src/instance_manager.dart';
import 'package:webview_flutter_android/src/weak_reference_utils.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';
@@ -58,15 +60,13 @@
)
..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl)));
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageFinished.future;
@@ -96,6 +96,79 @@
expect(gcIdentifier, 0);
}, timeout: const Timeout(Duration(seconds: 10)));
+ testWidgets(
+ 'WebView is released by garbage collection',
+ (WidgetTester tester) async {
+ final Completer<void> webViewGCCompleter = Completer<void>();
+
+ late final InstanceManager instanceManager;
+ instanceManager =
+ InstanceManager(onWeakReferenceRemoved: (int identifier) {
+ final Copyable instance =
+ instanceManager.getInstanceWithWeakReference(identifier)!;
+ if (instance is android.WebView && !webViewGCCompleter.isCompleted) {
+ webViewGCCompleter.complete();
+ }
+ });
+
+ android.WebView.api = WebViewHostApiImpl(
+ instanceManager: instanceManager,
+ );
+ android.WebSettings.api = WebSettingsHostApiImpl(
+ instanceManager: instanceManager,
+ );
+ android.WebChromeClient.api = WebChromeClientHostApiImpl(
+ instanceManager: instanceManager,
+ );
+
+ await tester.pumpWidget(
+ Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ AndroidWebViewWidgetCreationParams(
+ instanceManager: instanceManager,
+ controller: PlatformWebViewController(
+ const PlatformWebViewControllerCreationParams(),
+ ),
+ ),
+ ).build(context);
+ },
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ await tester.pumpWidget(
+ Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ AndroidWebViewWidgetCreationParams(
+ instanceManager: instanceManager,
+ controller: PlatformWebViewController(
+ const PlatformWebViewControllerCreationParams(),
+ ),
+ ),
+ ).build(context);
+ },
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ // Force garbage collection.
+ await IntegrationTestWidgetsFlutterBinding.instance
+ .watchPerformance(() async {
+ await tester.pumpAndSettle();
+ });
+
+ await tester.pumpAndSettle();
+ await expectLater(webViewGCCompleter.future, completes);
+
+ android.WebView.api = WebViewHostApiImpl();
+ android.WebSettings.api = WebSettingsHostApiImpl();
+ android.WebChromeClient.api = WebChromeClientHostApiImpl();
+ },
+ timeout: const Timeout(Duration(seconds: 10)),
+ );
+
testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async {
final Completer<void> pageFinished = Completer<void>();
@@ -110,15 +183,13 @@
)
..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl)));
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageFinished.future;
@@ -151,15 +222,13 @@
),
);
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageLoads.stream.firstWhere((String url) => url == headersUrl);
@@ -195,15 +264,13 @@
'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+',
);
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageFinished.future;
@@ -258,15 +325,13 @@
..setUserAgent('Custom_User_Agent1')
..loadRequest(LoadRequestParams(uri: Uri.parse('about:blank')));
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageFinished.future;
@@ -335,15 +400,13 @@
),
);
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageLoaded.future;
@@ -369,15 +432,13 @@
),
);
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageLoaded.future;
@@ -491,15 +552,13 @@
),
);
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageLoaded.future;
@@ -525,15 +584,13 @@
),
);
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageLoaded.future;
@@ -573,15 +630,13 @@
),
);
- await tester.pumpWidget(
- Builder(
- builder: (BuildContext context) {
- return PlatformWebViewWidget(
- PlatformWebViewWidgetCreationParams(controller: controller),
- ).build(context);
- },
- ),
- );
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
await pageLoaded.future;
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
index 6f4af3e..fd287a5 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
@@ -96,18 +96,20 @@
);
late final android_webview.WebChromeClient _webChromeClient =
- withWeakReferenceTo(this,
- (WeakReference<AndroidWebViewController> weakReference) {
- return _androidWebViewParams.androidWebViewProxy
- .createAndroidWebChromeClient(
- onProgressChanged: (android_webview.WebView webView, int progress) {
- if (weakReference.target?._currentNavigationDelegate._onProgress !=
+ _androidWebViewParams.androidWebViewProxy.createAndroidWebChromeClient(
+ onProgressChanged: withWeakReferenceTo(this,
+ (WeakReference<AndroidWebViewController> weakReference) {
+ return (android_webview.WebView webView, int progress) {
+ if (weakReference.target?._currentNavigationDelegate?._onProgress !=
null) {
weakReference
- .target!._currentNavigationDelegate._onProgress!(progress);
+ .target!._currentNavigationDelegate!._onProgress!(progress);
}
- },
- onShowFileChooser: (android_webview.WebView webView,
+ };
+ }),
+ onShowFileChooser: withWeakReferenceTo(this,
+ (WeakReference<AndroidWebViewController> weakReference) {
+ return (android_webview.WebView webView,
android_webview.FileChooserParams params) async {
if (weakReference.target?._onShowFileSelectorCallback != null) {
return weakReference.target!._onShowFileSelectorCallback!(
@@ -115,9 +117,9 @@
);
}
return <String>[];
- },
- );
- });
+ };
+ }),
+ );
/// The native [android_webview.FlutterAssetManager] allows managing assets.
late final android_webview.FlutterAssetManager _flutterAssetManager =
@@ -126,10 +128,7 @@
final Map<String, AndroidJavaScriptChannelParams> _javaScriptChannelParams =
<String, AndroidJavaScriptChannelParams>{};
- // The keeps a reference to the current NavigationDelegate so that the
- // callback methods remain reachable.
- // ignore: unused_field
- late AndroidNavigationDelegate _currentNavigationDelegate;
+ AndroidNavigationDelegate? _currentNavigationDelegate;
Future<List<String>> Function(FileSelectorParams)?
_onShowFileSelectorCallback;
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 81255df..30ea5d7 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin that provides a WebView widget on Android.
repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 3.2.2
+version: 3.2.3
environment:
sdk: ">=2.17.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
index 80fa192..03e71ec 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
@@ -539,6 +539,19 @@
expect(callbackProgress, 42);
});
+ test('onProgress does not cause LateInitializationError', () {
+ // ignore: unused_local_variable
+ final AndroidWebViewController controller = createControllerWithMocks(
+ createWebChromeClient: CapturingWebChromeClient.new,
+ );
+
+ // Should not cause LateInitializationError
+ CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!(
+ android_webview.WebView.detached(),
+ 42,
+ );
+ });
+
test('setOnShowFileSelector', () async {
late final Future<List<String>> Function(
android_webview.WebView webView,
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
index a9cb87f..f79058c 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.0.4
+
+* Fixes bug that prevented the web view from being garbage collected.
+
## 3.0.3
* Updates example code for `use_build_context_synchronously` lint.
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
index 946f27b..16411b8 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart
@@ -21,6 +21,7 @@
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart';
import 'package:webview_flutter_wkwebview/src/common/weak_reference_utils.dart';
+import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
Future<void> main() async {
@@ -47,7 +48,7 @@
final String headersUrl = '$prefixUrl/headers';
testWidgets(
- 'withWeakRefenceTo allows encapsulating class to be garbage collected',
+ 'withWeakReferenceTo allows encapsulating class to be garbage collected',
(WidgetTester tester) async {
final Completer<int> gcCompleter = Completer<int>();
final InstanceManager instanceManager = InstanceManager(
@@ -68,21 +69,102 @@
expect(gcIdentifier, 0);
}, timeout: const Timeout(Duration(seconds: 10)));
+ testWidgets(
+ 'WKWebView is released by garbage collection',
+ (WidgetTester tester) async {
+ final Completer<void> webViewGCCompleter = Completer<void>();
+
+ late final InstanceManager instanceManager;
+ instanceManager =
+ InstanceManager(onWeakReferenceRemoved: (int identifier) {
+ final Copyable instance =
+ instanceManager.getInstanceWithWeakReference(identifier)!;
+ if (instance is WKWebView && !webViewGCCompleter.isCompleted) {
+ webViewGCCompleter.complete();
+ }
+ });
+
+ await tester.pumpWidget(
+ Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ WebKitWebViewWidgetCreationParams(
+ instanceManager: instanceManager,
+ controller: PlatformWebViewController(
+ WebKitWebViewControllerCreationParams(
+ instanceManager: instanceManager,
+ ),
+ ),
+ ),
+ ).build(context);
+ },
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ await tester.pumpWidget(Container());
+
+ // Force garbage collection.
+ await IntegrationTestWidgetsFlutterBinding.instance
+ .watchPerformance(() async {
+ await tester.pumpAndSettle();
+ });
+
+ await expectLater(webViewGCCompleter.future, completes);
+ },
+ timeout: const Timeout(Duration(seconds: 10)),
+ );
+
testWidgets('loadRequest', (WidgetTester tester) async {
+ final Completer<void> pageFinished = Completer<void>();
+
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
- );
- controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl)));
+ )
+ ..setPlatformNavigationDelegate(
+ PlatformNavigationDelegate(
+ const PlatformNavigationDelegateCreationParams(),
+ )..setOnPageFinished((_) => pageFinished.complete()),
+ )
+ ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl)));
+
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
+ await pageFinished.future;
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, primaryUrl);
});
testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async {
+ final Completer<void> pageFinished = Completer<void>();
+
final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
- );
- controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl)));
+ )
+ ..setJavaScriptMode(JavaScriptMode.unrestricted)
+ ..setPlatformNavigationDelegate(
+ PlatformNavigationDelegate(
+ const PlatformNavigationDelegateCreationParams(),
+ )..setOnPageFinished((_) => pageFinished.complete()),
+ )
+ ..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl)));
+
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
+ await pageFinished.future;
await expectLater(
controller.runJavaScriptReturningResult('1 + 1'),
@@ -113,6 +195,14 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageLoads.stream.firstWhere((String url) => url == headersUrl);
final String content = await controller.runJavaScriptReturningResult(
@@ -147,6 +237,14 @@
'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+',
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageFinished.future;
await controller.runJavaScript('Echo.postMessage("hello");');
@@ -184,6 +282,14 @@
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setUserAgent('Custom_User_Agent1');
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
final String customUserAgent2 = await _getUserAgent(controller);
expect(customUserAgent2, 'Custom_User_Agent1');
});
@@ -250,6 +356,14 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageLoaded.future;
bool isPaused =
@@ -274,6 +388,14 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageLoaded.future;
isPaused =
@@ -447,6 +569,14 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageLoaded.future;
bool isPaused =
@@ -471,6 +601,14 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageLoaded.future;
isPaused =
@@ -509,6 +647,14 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageLoaded.future;
// On at least iOS, it does not appear to be guaranteed that the native
@@ -565,6 +711,14 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageLoaded.future;
await tester.pumpAndSettle(const Duration(seconds: 3));
@@ -599,8 +753,7 @@
'${base64Encode(const Utf8Encoder().convert(blankPage))}';
testWidgets('can allow requests', (WidgetTester tester) async {
- final StreamController<String> pageLoads =
- StreamController<String>.broadcast();
+ Completer<void> pageLoaded = Completer<void>();
final PlatformWebViewController controller = PlatformWebViewController(
WebKitWebViewControllerCreationParams(),
@@ -610,7 +763,7 @@
WebKitNavigationDelegate(
const WebKitNavigationDelegateCreationParams(),
)
- ..setOnPageFinished((String url) => pageLoads.add(url))
+ ..setOnPageFinished((_) => pageLoaded.complete())
..setOnNavigationRequest((NavigationRequest navigationRequest) {
return (navigationRequest.url.contains('youtube.com'))
? NavigationDecision.prevent
@@ -621,10 +774,20 @@
LoadRequestParams(uri: Uri.parse(blankPageEncoded)),
);
- await pageLoads.stream.first; // Wait for initial page load.
- await controller.runJavaScript('location.href = "$secondaryUrl"');
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
- await pageLoads.stream.first; // Wait for the next page load.
+ await pageLoaded.future; // Wait for initial page load.
+
+ pageLoaded = Completer<void>();
+ await controller.runJavaScript('location.href = "$secondaryUrl"');
+ await pageLoaded.future;
+
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, secondaryUrl);
});
@@ -633,7 +796,7 @@
final Completer<WebResourceError> errorCompleter =
Completer<WebResourceError>();
- PlatformWebViewController(
+ final PlatformWebViewController controller = PlatformWebViewController(
WebKitWebViewControllerCreationParams(),
)
..setJavaScriptMode(JavaScriptMode.unrestricted)
@@ -648,6 +811,14 @@
LoadRequestParams(uri: Uri.parse('https://www.notawebsite..com')),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
final WebResourceError error = await errorCompleter.future;
expect(error, isNotNull);
@@ -660,7 +831,7 @@
Completer<WebResourceError>();
final Completer<void> pageFinishCompleter = Completer<void>();
- PlatformWebViewController(
+ final PlatformWebViewController controller = PlatformWebViewController(
WebKitWebViewControllerCreationParams(),
)
..setJavaScriptMode(JavaScriptMode.unrestricted)
@@ -681,6 +852,14 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
expect(errorCompleter.future, doesNotComplete);
await pageFinishCompleter.future;
});
@@ -706,7 +885,7 @@
Completer<WebResourceError>();
final Completer<void> pageFinishCompleter = Completer<void>();
- PlatformWebViewController(
+ final PlatformWebViewController controller = PlatformWebViewController(
WebKitWebViewControllerCreationParams(),
)
..setJavaScriptMode(JavaScriptMode.unrestricted)
@@ -727,14 +906,21 @@
),
);
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
expect(errorCompleter.future, doesNotComplete);
await pageFinishCompleter.future;
},
);
testWidgets('can block requests', (WidgetTester tester) async {
- final StreamController<String> pageLoads =
- StreamController<String>.broadcast();
+ Completer<void> pageLoaded = Completer<void>();
final PlatformWebViewController controller = PlatformWebViewController(
WebKitWebViewControllerCreationParams(),
@@ -744,7 +930,7 @@
WebKitNavigationDelegate(
const WebKitNavigationDelegateCreationParams(),
)
- ..setOnPageFinished((String url) => pageLoads.add(url))
+ ..setOnPageFinished((_) => pageLoaded.complete())
..setOnNavigationRequest((NavigationRequest navigationRequest) {
return (navigationRequest.url.contains('youtube.com'))
? NavigationDecision.prevent
@@ -753,22 +939,31 @@
)
..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded)));
- await pageLoads.stream.first; // Wait for initial page load.
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
+ await pageLoaded.future; // Wait for initial page load.
+
+ pageLoaded = Completer<void>();
await controller
.runJavaScript('location.href = "https://www.youtube.com/"');
// There should never be any second page load, since our new URL is
// blocked. Still wait for a potential page change for some time in order
// to give the test a chance to fail.
- await pageLoads.stream.first
+ await pageLoaded.future
.timeout(const Duration(milliseconds: 500), onTimeout: () => '');
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, isNot(contains('youtube.com')));
});
testWidgets('supports asynchronous decisions', (WidgetTester tester) async {
- final StreamController<String> pageLoads =
- StreamController<String>.broadcast();
+ Completer<void> pageLoaded = Completer<void>();
final PlatformWebViewController controller = PlatformWebViewController(
WebKitWebViewControllerCreationParams(),
@@ -778,7 +973,7 @@
WebKitNavigationDelegate(
const WebKitNavigationDelegateCreationParams(),
)
- ..setOnPageFinished((String url) => pageLoads.add(url))
+ ..setOnPageFinished((_) => pageLoaded.complete())
..setOnNavigationRequest(
(NavigationRequest navigationRequest) async {
NavigationDecision decision = NavigationDecision.prevent;
@@ -790,10 +985,20 @@
)
..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded)));
- await pageLoads.stream.first; // Wait for initial page load.
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
+ await pageLoaded.future; // Wait for initial page load.
+
+ pageLoaded = Completer<void>();
await controller.runJavaScript('location.href = "$secondaryUrl"');
- await pageLoads.stream.first; // Wait for second page to load.
+ await pageLoaded.future; // Wait for second page to load.
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, secondaryUrl);
});
@@ -807,6 +1012,14 @@
..setAllowsBackForwardNavigationGestures(true)
..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl)));
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, primaryUrl);
});
@@ -824,6 +1037,15 @@
)..setOnPageFinished((_) => pageLoaded.complete()));
await controller.runJavaScript('window.open("$primaryUrl", "_blank")');
+
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
await pageLoaded.future;
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, primaryUrl);
@@ -843,6 +1065,14 @@
)..setOnPageFinished((_) => pageLoaded.complete()))
..loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl)));
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
expect(controller.currentUrl(), completion(primaryUrl));
await pageLoaded.future;
pageLoaded = Completer<void>();
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart
index 2cdc7e2..3e8d679 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'common/instance_manager.dart';
import 'foundation/foundation.dart';
import 'web_kit/web_kit.dart';
@@ -39,10 +40,13 @@
Map<NSKeyValueChangeKey, Object?> change,
)?
observeValue,
+ InstanceManager? instanceManager,
}) createWebView;
/// Constructs a [WKWebViewConfiguration].
- final WKWebViewConfiguration Function() createWebViewConfiguration;
+ final WKWebViewConfiguration Function({
+ InstanceManager? instanceManager,
+ }) createWebViewConfiguration;
/// Constructs a [WKScriptMessageHandler].
final WKScriptMessageHandler Function({
@@ -72,7 +76,7 @@
void Function(WKWebView webView)? webViewWebContentProcessDidTerminate,
}) createNavigationDelegate;
- /// Contructs a [WKUIDelegate].
+ /// Constructs a [WKUIDelegate].
final WKUIDelegate Function({
void Function(
WKWebView webView,
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
index dc90906..02b5b73 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart
@@ -49,7 +49,12 @@
PlaybackMediaTypes.video,
},
this.allowsInlineMediaPlayback = false,
- }) : _configuration = webKitProxy.createWebViewConfiguration() {
+ @visibleForTesting InstanceManager? instanceManager,
+ }) : _instanceManager = instanceManager ?? NSObject.globalInstanceManager {
+ _configuration = webKitProxy.createWebViewConfiguration(
+ instanceManager: _instanceManager,
+ );
+
if (mediaTypesRequiringUserAction.isEmpty) {
_configuration.setMediaTypesRequiringUserActionForPlayback(
<WKAudiovisualMediaType>{WKAudiovisualMediaType.none},
@@ -79,13 +84,15 @@
PlaybackMediaTypes.video,
},
bool allowsInlineMediaPlayback = false,
+ @visibleForTesting InstanceManager? instanceManager,
}) : this(
webKitProxy: webKitProxy,
mediaTypesRequiringUserAction: mediaTypesRequiringUserAction,
allowsInlineMediaPlayback: allowsInlineMediaPlayback,
+ instanceManager: instanceManager,
);
- final WKWebViewConfiguration _configuration;
+ late final WKWebViewConfiguration _configuration;
/// Media types that require a user gesture to begin playing.
///
@@ -102,6 +109,10 @@
/// native library.
@visibleForTesting
final WebKitProxy webKitProxy;
+
+ // Maintains instances used to communicate with the native objects they
+ // represent.
+ final InstanceManager _instanceManager;
}
/// An implementation of [PlatformWebViewController] with the WebKit api.
@@ -122,12 +133,12 @@
}
/// The WebKit WebView being controlled.
- late final WKWebView _webView = withWeakRefenceTo(this, (
- WeakReference<WebKitWebViewController> weakReference,
- ) {
- return _webKitParams.webKitProxy.createWebView(
- _webKitParams._configuration,
- observeValue: (
+ late final WKWebView _webView = _webKitParams.webKitProxy.createWebView(
+ _webKitParams._configuration,
+ observeValue: withWeakRefenceTo(this, (
+ WeakReference<WebKitWebViewController> weakReference,
+ ) {
+ return (
String keyPath,
NSObject object,
Map<NSKeyValueChangeKey, Object?> change,
@@ -139,9 +150,10 @@
change[NSKeyValueChangeKey.newValue]! as double;
progressCallback((progress * 100).round());
}
- },
- );
- });
+ };
+ }),
+ instanceManager: _webKitParams._instanceManager,
+ );
final Map<String, WebKitJavaScriptChannelParams> _javaScriptChannelParams =
<String, WebKitJavaScriptChannelParams>{};
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
index c41bce1..15e8ac5 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_wkwebview
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 3.0.3
+version: 3.0.4
environment:
sdk: ">=2.17.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
index fc06db2..0360c13 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart
@@ -13,6 +13,7 @@
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
+import 'package:webview_flutter_wkwebview/src/common/instance_manager.dart';
import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart';
import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart';
import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart';
@@ -49,6 +50,7 @@
})?
createMockWebView,
MockWKWebViewConfiguration? mockWebViewConfiguration,
+ InstanceManager? instanceManager,
}) {
final MockWKWebViewConfiguration nonNullMockWebViewConfiguration =
mockWebViewConfiguration ?? MockWKWebViewConfiguration();
@@ -57,7 +59,9 @@
final PlatformWebViewControllerCreationParams controllerCreationParams =
WebKitWebViewControllerCreationParams(
webKitProxy: WebKitProxy(
- createWebViewConfiguration: () => nonNullMockWebViewConfiguration,
+ createWebViewConfiguration: ({InstanceManager? instanceManager}) {
+ return nonNullMockWebViewConfiguration;
+ },
createWebView: (
_, {
void Function(
@@ -66,6 +70,7 @@
Map<NSKeyValueChangeKey, Object?> change,
)?
observeValue,
+ InstanceManager? instanceManager,
}) {
nonNullMockWebView = createMockWebView == null
? MockWKWebView()
@@ -104,7 +109,9 @@
WebKitWebViewControllerCreationParams(
webKitProxy: WebKitProxy(
- createWebViewConfiguration: () => mockConfiguration,
+ createWebViewConfiguration: ({InstanceManager? instanceManager}) {
+ return mockConfiguration;
+ },
),
allowsInlineMediaPlayback: true,
);
@@ -120,7 +127,9 @@
WebKitWebViewControllerCreationParams(
webKitProxy: WebKitProxy(
- createWebViewConfiguration: () => mockConfiguration,
+ createWebViewConfiguration: ({InstanceManager? instanceManager}) {
+ return mockConfiguration;
+ },
),
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{
PlaybackMediaTypes.video,
@@ -143,7 +152,9 @@
WebKitWebViewControllerCreationParams(
webKitProxy: WebKitProxy(
- createWebViewConfiguration: () => mockConfiguration,
+ createWebViewConfiguration: ({InstanceManager? instanceManager}) {
+ return mockConfiguration;
+ },
),
);
@@ -164,7 +175,9 @@
WebKitWebViewControllerCreationParams(
webKitProxy: WebKitProxy(
- createWebViewConfiguration: () => mockConfiguration,
+ createWebViewConfiguration: ({InstanceManager? instanceManager}) {
+ return mockConfiguration;
+ },
),
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart
index 2e0d6e3..2a6434b 100644
--- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart
@@ -19,7 +19,7 @@
group('WebKitWebViewWidget', () {
testWidgets('build', (WidgetTester tester) async {
- final InstanceManager instanceManager = InstanceManager(
+ final InstanceManager testInstanceManager = InstanceManager(
onWeakReferenceRemoved: (_) {},
);
@@ -34,14 +34,17 @@
Map<NSKeyValueChangeKey, Object?> change,
)?
observeValue,
+ InstanceManager? instanceManager,
}) {
final WKWebView webView = WKWebView.detached(
- instanceManager: instanceManager,
+ instanceManager: testInstanceManager,
);
- instanceManager.addDartCreatedInstance(webView);
+ testInstanceManager.addDartCreatedInstance(webView);
return webView;
},
- createWebViewConfiguration: () => MockWKWebViewConfiguration(),
+ createWebViewConfiguration: ({InstanceManager? instanceManager}) {
+ return MockWKWebViewConfiguration();
+ },
),
),
);
@@ -50,7 +53,7 @@
WebKitWebViewWidgetCreationParams(
key: const Key('keyValue'),
controller: controller,
- instanceManager: instanceManager,
+ instanceManager: testInstanceManager,
),
);