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/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.0.3

* Add `mixedContentMode` option for Android WebView.

## 2.0.2

* Fixes bug where text fields are hidden behind the keyboard
Expand Down
4 changes: 4 additions & 0 deletions packages/webview_flutter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,7 @@ 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).

### Android `mixedContentMode`

1. Android `mixedContentMode` requires `usesCleartextTraffic` in `AndroidManifest.xml`;
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
Expand Down Expand Up @@ -371,6 +372,14 @@ private void applySettings(Map<String, Object> settings) {
Integer mode = (Integer) settings.get(key);
if (mode != null) updateJsMode(mode);
break;
case "mixedContentMode":
{
Object value = settings.get(key);
if (value != null) {
updateMixedContentMode((Integer) value);
}
break;
}
case "hasNavigationDelegate":
final boolean hasNavigationDelegate = (boolean) settings.get(key);

Expand Down Expand Up @@ -416,6 +425,24 @@ private void updateJsMode(int mode) {
}
}

private void updateMixedContentMode(int mode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (mode) {
case 0:
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
break;
case 1:
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
break;
case 2:
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
break;
default:
throw new IllegalArgumentException("Trying to set unknown mixed content mode: " + mode);
}
}
}

private void updateAutoMediaPlaybackPolicy(int mode) {
// This is the index of the AutoMediaPlaybackPolicy enum, index 1 is always_allow, for all
// other values we require a user gesture.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
<application
android:icon="@mipmap/ic_launcher"
android:label="webview_flutter_example"
android:name="io.flutter.app.FlutterApplication">
android:name="io.flutter.app.FlutterApplication"
android:usesCleartextTraffic="true">
<meta-data
android:name="flutterEmbedding"
android:value="2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,48 @@ void main() {
}, skip: true /* https://github.com/flutter/flutter/issues/72572 */);
});

// NOTE: Requires `usesCleartextTraffic` in `AndroidManifest.xml`
testWidgets('set mixedContentMode', (WidgetTester tester) async {
// Only for Android
if (!Platform.isAndroid) return;

void test(bool allow) async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
final Completer<void> loadCompleter = Completer<void>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: GlobalKey(),
initialUrl:
'https://www.mixedcontentexamples.com/Test/NonSecureImage',
javascriptMode: JavascriptMode.unrestricted,
mixedContentMode: allow
? MixedContentMode.compatibilityMode
: MixedContentMode.neverAllow,
onWebViewCreated: (WebViewController controller) {
controllerCompleter.complete(controller);
},
onPageFinished: (_) => loadCompleter.complete(null),
),
),
);
final WebViewController controller = await controllerCompleter.future;
await loadCompleter.future;
final height = int.tryParse(await controller.evaluateJavascript(
'document.getElementsByTagName("img")[0].height'));
if (allow) {
expect(height, 398);
} else {
expect(height != 398, true);
}
}

await test(false);
await test(true);
});

group('Audio playback policy', () {
String audioTestBase64;
setUpAll(() async {
Expand Down
1 change: 1 addition & 0 deletions packages/webview_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class _WebViewExampleState extends State<WebViewExample> {
return WebView(
initialUrl: 'https://flutter.dev',
javascriptMode: JavascriptMode.unrestricted,
mixedContentMode: MixedContentMode.compatibilityMode,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
Expand Down
8 changes: 7 additions & 1 deletion packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ class WebSettings {
this.hasNavigationDelegate,
this.hasProgressTracking,
this.debuggingEnabled,
this.mixedContentMode,
this.gestureNavigationEnabled,
this.allowsInlineMediaPlayback,
required this.userAgent,
Expand Down Expand Up @@ -429,14 +430,19 @@ class WebSettings {
/// See also [WebView.userAgent].
final WebSetting<String?> userAgent;

/// The mixed content mode to be used by the webview.
///
/// See also: [WebView.mixedContentMode]
final MixedContentMode? mixedContentMode;

/// Whether to allow swipe based navigation in iOS.
///
/// See also: [WebView.gestureNavigationEnabled]
final bool? gestureNavigationEnabled;

@override
String toString() {
return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, hasProgressTracking: $hasProgressTracking, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent, allowsInlineMediaPlayback: $allowsInlineMediaPlayback)';
return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, hasProgressTracking: $hasProgressTracking, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent, allowsInlineMediaPlayback: $allowsInlineMediaPlayback, mixedContentMode: $mixedContentMode)';
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
_addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate);
_addIfNonNull('hasProgressTracking', settings.hasProgressTracking);
_addIfNonNull('debuggingEnabled', settings.debuggingEnabled);
_addIfNonNull('mixedContentMode', settings.mixedContentMode?.index);
_addIfNonNull(
'gestureNavigationEnabled', settings.gestureNavigationEnabled);
_addIfNonNull(
Expand Down
27 changes: 27 additions & 0 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,23 @@ class SurfaceAndroidWebView extends AndroidWebView {
}
}

/// Describes the state of mixed content(e.g. http content in https pages) support in a given web view.
///
/// Only works in Android.
///
/// See also https://developer.android.com/reference/android/webkit/WebSettings.html#MIXED_CONTENT_ALWAYS_ALLOW
/// for these 3 options.
enum MixedContentMode {
/// See also Android `MIXED_CONTENT_ALWAYS_ALLOW` option
alwaysAllow,

/// See also Android `MIXED_CONTENT_NEVER_ALLOW` option
neverAllow,

/// See also Android `MIXED_CONTENT_COMPATIBILITY_MODE` option
compatibilityMode,
}

/// Decides how to handle a specific navigation request.
///
/// The returned [NavigationDecision] determines how the navigation described by
Expand Down Expand Up @@ -228,6 +245,7 @@ class WebView extends StatefulWidget {
this.debuggingEnabled = false,
this.gestureNavigationEnabled = false,
this.userAgent,
this.mixedContentMode,
this.initialMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
this.allowsInlineMediaPlayback = false,
Expand Down Expand Up @@ -407,6 +425,14 @@ class WebView extends StatefulWidget {
/// By default `userAgent` is null.
final String? userAgent;

/// Controls whether mixed content is allowed to load.
///
/// This only works on Android.
/// For iOS, you can change the settings in info.plist.
///
/// By default `mixedContentMode` is null, and Android would use its own default.
final MixedContentMode? mixedContentMode;

/// Which restrictions apply on automatic media playback.
///
/// This initial value is applied to the platform's webview upon creation. Any following
Expand Down Expand Up @@ -488,6 +514,7 @@ WebSettings _webSettingsFromWidget(WebView widget) {
hasNavigationDelegate: widget.navigationDelegate != null,
hasProgressTracking: widget.onProgress != null,
debuggingEnabled: widget.debuggingEnabled,
mixedContentMode: widget.mixedContentMode,
gestureNavigationEnabled: widget.gestureNavigationEnabled,
allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback,
userAgent: WebSetting<String?>.of(widget.userAgent),
Expand Down
2 changes: 1 addition & 1 deletion packages/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
version: 2.0.2
version: 2.0.3

environment:
sdk: ">=2.12.0-259.9.beta <3.0.0"
Expand Down
27 changes: 27 additions & 0 deletions packages/webview_flutter/test/webview_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,31 @@ void main() {
});
});

group('mixedContentMode', () {
testWidgets('set mixedContentMode', (WidgetTester tester) async {
await tester.pumpWidget(const WebView(
mixedContentMode: MixedContentMode.alwaysAllow,
));

final FakePlatformWebView? platformWebView =
fakePlatformViewsController.lastCreatedView;

expect(platformWebView?.mixedContentMode,
MixedContentMode.alwaysAllow.index);
});

testWidgets('defaults to null', (WidgetTester tester) async {
await tester.pumpWidget(const WebView());

final FakePlatformWebView? platformWebView =
fakePlatformViewsController.lastCreatedView;

expect(platformWebView?.mixedContentMode, null);
});

// no need to change
});

group('Custom platform implementation', () {
setUpAll(() {
WebView.platform = MyWebViewPlatform();
Expand Down Expand Up @@ -939,6 +964,7 @@ class FakePlatformWebView {
hasNavigationDelegate =
params['settings']['hasNavigationDelegate'] ?? false;
debuggingEnabled = params['settings']['debuggingEnabled'];
mixedContentMode = params['settings']['mixedContentMode'];
userAgent = params['settings']['userAgent'];
channel = MethodChannel(
'plugins.flutter.io/webview_$id', const StandardMethodCodec());
Expand All @@ -958,6 +984,7 @@ class FakePlatformWebView {

bool? hasNavigationDelegate;
bool? debuggingEnabled;
int? mixedContentMode;
String? userAgent;

Future<dynamic> onMethodCall(MethodCall call) {
Expand Down