diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 8f4826823c38..ec5557ef1b01 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.0 + +* Added `limitsNavigationsToAppBoundDomains` functionality for iOS 14.0+. + ## 2.3.0 * Add ability to enable/disable zoom functionality. diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md index de475ad9acd7..e3b055977a20 100644 --- a/packages/webview_flutter/webview_flutter/README.md +++ b/packages/webview_flutter/webview_flutter/README.md @@ -24,7 +24,7 @@ Here are some points to consider when choosing between the two: * *Hybrid composition* mode has a built-in keyboard support while *Virtual displays* mode has multiple [keyboard issues](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22) * *Hybrid composition* mode requires Android SDK 19+ while *Virtual displays* mode requires Android SDK 20+ -* *Hybrid composition* mode has [performance limitations](https://flutter.dev/docs/development/platform-integration/platform-views#performance) when working on Android versions prior to Android 10 while *Virtual displays* is performant on all supported Android versions +* *Hybrid composition* mode has [performance limitations](https://flutter.dev/docs/development/platform-integration/platform-views#performance) when working on Android versions prior to Android 10 while *Virtual displays* is performant on all supported Android versions | | Hybrid composition | Virtual displays | | --------------------------- | ------------------- | ---------------- | @@ -60,17 +60,17 @@ android { 2. Set `WebView.platform = SurfaceAndroidWebView();` in `initState()`. For example: - + ```dart import 'dart:io'; - + import 'package:webview_flutter/webview_flutter.dart'; class WebViewExample extends StatefulWidget { @override WebViewExampleState createState() => WebViewExampleState(); } - + class WebViewExampleState extends State { @override void initState() { @@ -92,3 +92,8 @@ android { To use Material Components when the user interacts with input elements in the WebView, follow the steps described in the [Enabling Material Components instructions](https://flutter.dev/docs/deployment/android#enabling-material-components). + +## iOS + +### Limits Navigations To App Bound Domains +To take advantage of in-app browsing on iOS 14.0 and iPadOS 14.0, App-Bound Domains can be enabled and setup using the following [guide](https://webkit.org/blog/10882/app-bound-domains/) diff --git a/packages/webview_flutter/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/webview_flutter/example/pubspec.yaml index 6b668eb96af3..d601dd77f65f 100644 --- a/packages/webview_flutter/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/example/pubspec.yaml @@ -27,6 +27,14 @@ dev_dependencies: sdk: flutter pedantic: ^1.10.0 +dependency_overrides: + webview_flutter_platform_interface: + path: ../../webview_flutter_platform_interface + webview_flutter_android: + path: ../../webview_flutter_android + webview_flutter_wkwebview: + path: ../../webview_flutter_wkwebview + flutter: uses-material-design: true assets: diff --git a/packages/webview_flutter/webview_flutter/lib/src/webview.dart b/packages/webview_flutter/webview_flutter/lib/src/webview.dart index 442999d0ff5c..f0ddc78c19e9 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/webview.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/webview.dart @@ -75,6 +75,8 @@ class WebView extends StatefulWidget { /// `onWebViewCreated` callback once the web view is created. /// /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. + /// + /// Once the WebView has been initialized, it is not possible to change `limitsNavigationsToAppBoundDomains` const WebView({ Key? key, this.onWebViewCreated, @@ -94,6 +96,7 @@ class WebView extends StatefulWidget { this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.allowsInlineMediaPlayback = false, + this.limitsNavigationsToAppBoundDomains = false, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), assert(allowsInlineMediaPlayback != null), @@ -212,6 +215,13 @@ class WebView extends StatefulWidget { /// By default `allowsInlineMediaPlayback` is false. final bool allowsInlineMediaPlayback; + /// Controls whether navigation is limited to app-bound domains on iOS + /// + /// This field is ignored on Android and on iOS before iOS 14.0 + /// + /// By default `limitsNavigationsToAppBoundDomains` is false + final bool limitsNavigationsToAppBoundDomains; + /// Invoked when a page starts loading. final PageStartedCallback? onPageStarted; @@ -354,6 +364,8 @@ CreationParams _creationParamsfromWidget(WebView widget) { javascriptChannelNames: _extractChannelNames(widget.javascriptChannels), userAgent: widget.userAgent, autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, + limitsNavigationsToAppBoundDomains: + widget.limitsNavigationsToAppBoundDomains, ); } diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index c7ac2e0200f9..cac3fabec98f 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.3.0 +version: 2.4.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -19,9 +19,9 @@ flutter: dependencies: flutter: sdk: flutter + webview_flutter_platform_interface: ^1.5.0 webview_flutter_android: ^2.2.0 - webview_flutter_platform_interface: ^1.2.0 - webview_flutter_wkwebview: ^2.2.0 + webview_flutter_wkwebview: ^2.4.0 dev_dependencies: flutter_driver: @@ -29,3 +29,11 @@ dev_dependencies: flutter_test: sdk: flutter pedantic: ^1.10.0 + +dependency_overrides: + webview_flutter_platform_interface: + path: ../webview_flutter_platform_interface + webview_flutter_android: + path: ../webview_flutter_android + webview_flutter_wkwebview: + path: ../webview_flutter_wkwebview diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 1e065a6a5b0b..c9e53ee4bc15 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -26,6 +26,10 @@ dev_dependencies: sdk: flutter pedantic: ^1.10.0 +dependency_overrides: + webview_flutter_platform_interface: + path: ../../webview_flutter_platform_interface + flutter: uses-material-design: true assets: diff --git a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md index 4c7434a86b41..5e1079b5d178 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.5.0 + +* Added `limitsNavigationsToAppBoundDomains` functionality to `CreationParams`. + ## 1.4.0 * Added `loadFile` and `loadHtml` interface methods. diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart index 8df9f4c62b33..451fa56b6a47 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/method_channel/webview_method_channel.dart @@ -257,6 +257,8 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { 'userAgent': creationParams.userAgent, 'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index, 'usesHybridComposition': usesHybridComposition, + 'limitsNavigationsToAppBoundDomains': + creationParams.limitsNavigationsToAppBoundDomains, }; } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart index f213e976ad84..09697b9cad7f 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/creation_params.dart @@ -20,6 +20,7 @@ class CreationParams { this.userAgent, this.autoMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, + this.limitsNavigationsToAppBoundDomains = false, }) : assert(autoMediaPlaybackPolicy != null); /// The initialUrl to load in the webview. @@ -53,8 +54,11 @@ class CreationParams { /// Which restrictions apply on automatic media playback. final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy; + /// This value is used to either enable or disable navigations to app-bound domains + final bool limitsNavigationsToAppBoundDomains; + @override String toString() { - return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent)'; + return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent, limitsNavigationsToAppBoundDomains: $limitsNavigationsToAppBoundDomains)'; } } diff --git a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml index 4a4746d8ab68..57ae9a06f056 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/plugins/tree/master/packages/webview_flut issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview_flutter%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.4.0 +version: 1.5.0 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart b/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart index 396013535aa9..ad3580d4e1eb 100644 --- a/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart +++ b/packages/webview_flutter/webview_flutter_platform_interface/test/src/method_channel/webview_method_channel_test.dart @@ -591,6 +591,32 @@ void main() { ); }); }); + + group('Tests limitsNavigationsToAppBoundDomains', () { + test('Make sure limitsNavigationsToAppBoundDomains defaults to false', () { + final value = MethodChannelWebViewPlatform.creationParamsToMap( + CreationParams( + webSettings: WebSettings( + userAgent: WebSetting.of(''), + ), + ), + ); + + expect(value['limitsNavigationsToAppBoundDomains'], false); + }); + test('Make sure limitsNavigationsToAppBoundDomains can be set to true', () { + final value = MethodChannelWebViewPlatform.creationParamsToMap( + CreationParams( + limitsNavigationsToAppBoundDomains: true, + webSettings: WebSettings( + userAgent: WebSetting.of(''), + ), + ), + ); + + expect(value['limitsNavigationsToAppBoundDomains'], true); + }); + }); } class MockWebViewPlatformCallbacksHandler extends Mock diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index c1b19fd22d3e..df76a0e4ff54 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.0 + +* Added `limitsNavigationsToAppBoundDomains` functionality. + ## 2.3.0 * Implemented new `loadRequest` method from platform interface. @@ -12,7 +16,7 @@ ## 2.0.14 -* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). +* Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). ## 2.0.13 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m index 61e43c104b57..7dd78686f5cc 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FLTWebViewTests.m @@ -267,41 +267,6 @@ - (void)testRunJavascriptReturningResultRunsStringWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } -- (void)testRunJavascriptReturningResultReturnsErrorResultForWKError { - // Setup - FLTWebViewController *controller = - [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) - viewIdentifier:1 - arguments:nil - binaryMessenger:self.mockBinaryMessenger]; - XCTestExpectation *resultExpectation = - [self expectationWithDescription:@"Should return error result over the method channel."]; - NSError *testError = [NSError errorWithDomain:@"" - code:5 - userInfo:@{NSLocalizedDescriptionKey : @"Test Error"}]; - FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class); - [OCMStub([mockView evaluateJavaScript:[OCMArg any] - completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) { - // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668 - __unsafe_unretained void (^evalResultHandler)(id, NSError *); - [invocation getArgument:&evalResultHandler atIndex:3]; - evalResultHandler(nil, testError); - }]; - controller.webView = mockView; - - // Run - [controller - onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascriptReturningResult" - arguments:@"Test JavaScript String"] - result:^(id _Nullable result) { - XCTAssertTrue([result class] == [FlutterError class]); - [resultExpectation fulfill]; - }]; - - // Verify - [self waitForExpectationsWithTimeout:30.0 handler:nil]; -} - - (void)testBuildNSURLRequestReturnsNilForNonDictionaryValue { // Setup FLTWebViewController *controller = @@ -494,4 +459,83 @@ - (void)testOnLoadRequestLoadsRequestWithSuccessResult { [self waitForExpectationsWithTimeout:30.0 handler:nil]; } +- (void)testRunJavascriptReturningResultReturnsErrorResultForWKError { + // Setup + FLTWebViewController *controller = + [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) + viewIdentifier:1 + arguments:nil + binaryMessenger:self.mockBinaryMessenger]; + XCTestExpectation *resultExpectation = + [self expectationWithDescription:@"Should return error result over the method channel."]; + NSError *testError = [NSError errorWithDomain:@"" + code:5 + userInfo:@{NSLocalizedDescriptionKey : @"Test Error"}]; + FLTWKWebView *mockView = OCMClassMock(FLTWKWebView.class); + [OCMStub([mockView evaluateJavaScript:[OCMArg any] + completionHandler:[OCMArg any]]) andDo:^(NSInvocation *invocation) { + // __unsafe_unretained: https://github.com/erikdoe/ocmock/issues/384#issuecomment-589376668 + __unsafe_unretained void (^evalResultHandler)(id, NSError *); + [invocation getArgument:&evalResultHandler atIndex:3]; + evalResultHandler(nil, testError); + }]; + controller.webView = mockView; + + // Run + [controller + onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"runJavascriptReturningResult" + arguments:@"Test JavaScript String"] + result:^(id _Nullable result) { + XCTAssertTrue([result class] == [FlutterError class]); + [resultExpectation fulfill]; + }]; + + // Verify + [self waitForExpectationsWithTimeout:30.0 handler:nil]; +} + +- (void)testLimitsNavigationsToAppBoundDomainsDefaultToFalse { + // Setup + FLTWebViewController *controller = + [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) + viewIdentifier:1 + arguments:nil + binaryMessenger:self.mockBinaryMessenger]; + XCTestExpectation *resultExpectation = + [self expectationWithDescription:@"Should set limitsNavigationsToAppBoundDomains to false"]; + + // Run + if (@available(iOS 14.0, *)) { + XCTAssertFalse(controller.webView.configuration.limitsNavigationsToAppBoundDomains); + } + + [resultExpectation fulfill]; + + // Verify + [self waitForExpectationsWithTimeout:30.0 handler:nil]; +} + +- (void)testLimitsNavigationsToAppBoundDomainsSetToTrue { + // Setup + NSDictionary *arguments = @{@"limitsNavigationsToAppBoundDomains" : @true}; + + FLTWebViewController *controller = + [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) + viewIdentifier:1 + arguments:arguments + binaryMessenger:self.mockBinaryMessenger]; + XCTestExpectation *resultExpectation = + [self expectationWithDescription:@"Should set limitsNavigationsToAppBoundDomains to true"]; + + // Run + if (@available(iOS 14.0, *)) { + XCTAssertTrue(controller.webView.configuration.limitsNavigationsToAppBoundDomains); + } + + [resultExpectation fulfill]; + + // Verify + [self waitForExpectationsWithTimeout:30.0 handler:nil]; +} + @end diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart index b2555cd831c6..2af9bb245f4e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart @@ -69,6 +69,7 @@ class WebView extends StatefulWidget { this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.allowsInlineMediaPlayback = false, + this.limitsNavigationsToAppBoundDomains = false, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), assert(allowsInlineMediaPlayback != null), @@ -157,6 +158,13 @@ class WebView extends StatefulWidget { /// By default `allowsInlineMediaPlayback` is false. final bool allowsInlineMediaPlayback; + /// Controls whether navigation is limited to app-bound domains on iOS + /// + /// This field is ignored on Android and on iOS before iOS 14.0 + /// + /// By default `limitsNavigationsToAppBoundDomains` is false + final bool limitsNavigationsToAppBoundDomains; + /// Invoked when a page starts loading. final PageStartedCallback? onPageStarted; @@ -278,6 +286,8 @@ class _WebViewState extends State { _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, + limitsNavigationsToAppBoundDomains: + widget.limitsNavigationsToAppBoundDomains, ), javascriptChannelRegistry: _javascriptChannelRegistry, ); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index 229da5e337a5..f77e289be2bb 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml @@ -26,6 +26,10 @@ dev_dependencies: sdk: flutter pedantic: ^1.10.0 +dependency_overrides: + webview_flutter_platform_interface: + path: ../../webview_flutter_platform_interface + flutter: uses-material-design: true assets: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m index b8355ad18183..6cb60d68bc67 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m @@ -95,6 +95,11 @@ - (instancetype)initWithFrame:(CGRect)frame [self updateAutoMediaPlaybackPolicy:args[@"autoMediaPlaybackPolicy"] inConfiguration:configuration]; + if (@available(iOS 14.0, *)) { + NSNumber* limitsNavigationsToAppBoundDomains = args[@"limitsNavigationsToAppBoundDomains"]; + configuration.limitsNavigationsToAppBoundDomains = limitsNavigationsToAppBoundDomains; + } + _webView = [[FLTWKWebView alloc] initWithFrame:frame configuration:configuration]; _navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:_channel]; _webView.UIDelegate = self; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index ff2b69fa8d20..f14a70f55e7f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/master/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: 2.3.0 +version: 2.4.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -18,11 +18,15 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: ^1.3.0 + webview_flutter_platform_interface: ^1.5.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter - pedantic: ^1.10.0 \ No newline at end of file + pedantic: ^1.10.0 + +dependency_overrides: + webview_flutter_platform_interface: + path: ../webview_flutter_platform_interface \ No newline at end of file