Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
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
4 changes: 4 additions & 0 deletions packages/webview_flutter/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.4.0

* Added `limitsNavigationsToAppBoundDomains` functionality for iOS 14.0+.

## 2.3.0

* Add ability to enable/disable zoom functionality.
Expand Down
13 changes: 9 additions & 4 deletions packages/webview_flutter/webview_flutter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| --------------------------- | ------------------- | ---------------- |
Expand Down Expand Up @@ -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<WebViewExample> {
@override
void initState() {
Expand All @@ -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/)
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 12 additions & 0 deletions packages/webview_flutter/webview_flutter/lib/src/webview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -94,6 +96,7 @@ class WebView extends StatefulWidget {
this.initialMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
this.allowsInlineMediaPlayback = false,
this.limitsNavigationsToAppBoundDomains = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding platform-specific arguments to this list is not a direction I want to take if we can possibly avoid it; it could get out of hand very quickly.

@bparrishMines @mvanbeusekom You're both more familiar than I am with the overall API of this plugin; do either of you have thoughts on how to introduce platform-specific extensions here?

I'm wondering if it would make sense to add a creation parameters class that's created by a factory, implemented by the implementing packages, so that they could return subclasses of the settings object that have extensions. Then clients could do platform-specific casting to access settings like this one, similar to the IAP extensions.

Copy link
Contributor

@bparrishMines bparrishMines Nov 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the long term, I believe that yes we could use casting to add cleaner support for very specific platform features like this. For example, the API would look something like:

class MyWidget extends StatelessElement {
  MyWidget(StatelessWidget widget) : super(widget);

  Future<void> additionalIosSetup(IosWebViewController controller) {
    controller.configuration.setLimitsNavigationsToAppBoundDomains(false);
  }

  Future<void> additionalAndroidSetup(AndroidWebViewController controller) {
    ...
  }

  @override
  Widget build() {
    return WebView(
      onWebViewCreated: (WebViewController controller) {
        if (Platform.isIOS) {
          additionalIosSetup(controller as IosWebViewController);
        } else if (Platform.isAndroid) {
          additionalAndroidSetup(controller as AndroidWebViewController);
        }
      },
    );
  }
}

However, this can only be done after the iOS platform implementation is transitioned to pigeon and we update the platform interface. After #4503 lands, this should be possible for Android. So we may want to hold off on adding this feature until iOS is also transitioned.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I'm missing something with your example @bparrishMines, but limitsNavigationsToAppBoundDomains is not a setting that can be changed after the WebView has been initialized. It needs to be configured during the initialize process of the FLTWebViewController object. And if I understand it correctly onWebViewCreated is called after both the WebView Configuration and the WebView is done initializing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I hadn't realized it was part of initialization. Then, I agree with @stuartmorgan that we could expose a CreationParams class that each platform can add additional fields too. A solution that could look something like this in the platform interface:

class CreationParams {
  factory CreationParams() {
    return WebView.platform.createCreationParams();
  }
}

abstract class WebViewPlatform {
.
.
.
  void createCreationParams() {
    throw UnimplementedError();
  }
}

@stuartmorgan Is this what you were considering?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks pretty much like I was thinking (except that we'd probably want the platform interface class to return the base class rather than throw unimplemented, so that only platforms that want extra parameters need to implement it).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stuartmorgan is this something I should implement, or should I wait for it to be implemented before adding my changes? Also, how would this look in an app where I might target multiple platforms and want toggle options for multiple platforms?

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g Nov 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not something there's an existing plan to implement, so you should go ahead and implement it. I would start with trying it as part of this PR; we could do it as a separate PR that lands first, but it's probably useful to build (and review) in conjunction with this use as a test case to make sure it's working as we want it to end to end. (If it turns out to be more complex than anticipated for some reason, it could be split out later.)

Also, how would this look in an app where I might target multiple platforms and want toggle options for multiple platforms?

Basically like the example shown above, but with the settings object being cast, not the controller.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you should go ahead and implement it

Oh, but as @bparrishMines mentioned, it should wait a couple of weeks since the restructuring that's happening in the webview implementation will probably interact with this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stuartmorgan Sounds good, is there an active issue or PR for the iOS transition to pigeon that I can follow to know when this change is landed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There wasn't, but really should be. I filed flutter/flutter#93732 (Android is in progress now and nearing completion, iOS will follow after).

}) : assert(javascriptMode != null),
assert(initialMediaPlaybackPolicy != null),
assert(allowsInlineMediaPlayback != null),
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -354,6 +364,8 @@ CreationParams _creationParamsfromWidget(WebView widget) {
javascriptChannelNames: _extractChannelNames(widget.javascriptChannels),
userAgent: widget.userAgent,
autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
limitsNavigationsToAppBoundDomains:
widget.limitsNavigationsToAppBoundDomains,
);
}

Expand Down
14 changes: 11 additions & 3 deletions packages/webview_flutter/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -19,13 +19,21 @@ 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:
sdk: flutter
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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.5.0

* Added `limitsNavigationsToAppBoundDomains` functionality to `CreationParams`.

## 1.4.0

* Added `loadFile` and `loadHtml` interface methods.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
'userAgent': creationParams.userAgent,
'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index,
'usesHybridComposition': usesHybridComposition,
'limitsNavigationsToAppBoundDomains':
creationParams.limitsNavigationsToAppBoundDomains,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String?>.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<String?>.of(''),
),
),
);

expect(value['limitsNavigationsToAppBoundDomains'], true);
});
});
}

class MockWebViewPlatformCallbacksHandler extends Mock
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.4.0

* Added `limitsNavigationsToAppBoundDomains` functionality.

## 2.3.0

* Implemented new `loadRequest` method from platform interface.
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -278,6 +286,8 @@ class _WebViewState extends State<WebView> {
_javascriptChannelRegistry.channels.keys.toSet(),
autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
userAgent: widget.userAgent,
limitsNavigationsToAppBoundDomains:
widget.limitsNavigationsToAppBoundDomains,
),
javascriptChannelRegistry: _javascriptChannelRegistry,
);
Expand Down
Loading