diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index e3a5fc8dab74..fb49f23efc60 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 4.8.0 + +* Adds `AndroidWebViewController.setMixedContentMode` to control how + mixed-content pages load. + ## 4.7.0 * Adds support to respond to recoverable SSL certificate errors. See `AndroidNavigationDelegate.setOnSSlAuthError`. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt index 2eb9c2ea414d..58fc0ed29242 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/AndroidWebkitLibrary.g.kt @@ -660,6 +660,7 @@ private class AndroidWebkitLibraryPigeonProxyApiBaseCodec( value is ConsoleMessageLevel || value is OverScrollMode || value is SslErrorType || + value is MixedContentMode || value == null) { super.writeValue(stream, value) return @@ -886,6 +887,32 @@ enum class SslErrorType(val raw: Int) { } } +/** + * Options for mixed content mode support. + * + * See https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_ALWAYS_ALLOW + */ +enum class MixedContentMode(val raw: Int) { + /** + * The WebView will allow a secure origin to load content from any other origin, even if that + * origin is insecure. + */ + ALWAYS_ALLOW(0), + /** + * The WebView will attempt to be compatible with the approach of a modern web browser with regard + * to mixed content. + */ + COMPATIBILITY_MODE(1), + /** The WebView will not allow a secure origin to load content from an insecure origin. */ + NEVER_ALLOW(2); + + companion object { + fun ofRaw(raw: Int): MixedContentMode? { + return values().firstOrNull { it.raw == raw } + } + } +} + private open class AndroidWebkitLibraryPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { @@ -901,6 +928,9 @@ private open class AndroidWebkitLibraryPigeonCodec : StandardMessageCodec() { 132.toByte() -> { return (readValue(buffer) as Long?)?.let { SslErrorType.ofRaw(it.toInt()) } } + 133.toByte() -> { + return (readValue(buffer) as Long?)?.let { MixedContentMode.ofRaw(it.toInt()) } + } else -> super.readValueOfType(type, buffer) } } @@ -923,6 +953,10 @@ private open class AndroidWebkitLibraryPigeonCodec : StandardMessageCodec() { stream.write(132) writeValue(stream, value.raw) } + is MixedContentMode -> { + stream.write(133) + writeValue(stream, value.raw) + } else -> super.writeValue(stream, value) } } @@ -2285,6 +2319,12 @@ abstract class PigeonApiWebSettings( /** Gets the WebView's user-agent string. */ abstract fun getUserAgentString(pigeon_instance: android.webkit.WebSettings): String + /** Configures the WebView's behavior when handling mixed content. */ + abstract fun setMixedContentMode( + pigeon_instance: android.webkit.WebSettings, + mode: MixedContentMode + ) + companion object { @Suppress("LocalVariableName") fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebSettings?) { @@ -2671,6 +2711,30 @@ abstract class PigeonApiWebSettings( channel.setMessageHandler(null) } } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebSettings.setMixedContentMode", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val pigeon_instanceArg = args[0] as android.webkit.WebSettings + val modeArg = args[1] as MixedContentMode + val wrapped: List = + try { + api.setMixedContentMode(pigeon_instanceArg, modeArg) + listOf(null) + } catch (exception: Throwable) { + AndroidWebkitLibraryPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsProxyApi.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsProxyApi.java index 43966249703a..5a36162b0466 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsProxyApi.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsProxyApi.java @@ -101,4 +101,20 @@ public void setTextZoom(@NonNull WebSettings pigeon_instance, long textZoom) { public String getUserAgentString(@NonNull WebSettings pigeon_instance) { return pigeon_instance.getUserAgentString(); } + + @Override + public void setMixedContentMode( + @NonNull WebSettings pigeon_instance, @NonNull MixedContentMode mode) { + switch (mode) { + case ALWAYS_ALLOW: + pigeon_instance.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + break; + case COMPATIBILITY_MODE: + pigeon_instance.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); + break; + case NEVER_ALLOW: + pigeon_instance.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW); + break; + } + } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java index 52ac10716158..1897025def4c 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java @@ -166,4 +166,14 @@ public void getUserAgentString() { assertEquals(value, api.getUserAgentString(instance)); } + + @Test + public void setMixedContentMode() { + final PigeonApiWebSettings api = new TestProxyApiRegistrar().getPigeonApiWebSettings(); + + final WebSettings instance = mock(WebSettings.class); + api.setMixedContentMode(instance, MixedContentMode.COMPATIBILITY_MODE); + + verify(instance).setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); + } } diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart index f985564749b3..2675271ce501 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webkit.g.dart @@ -570,6 +570,23 @@ enum SslErrorType { unknown, } +/// Options for mixed content mode support. +/// +/// See https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_ALWAYS_ALLOW +enum MixedContentMode { + /// The WebView will allow a secure origin to load content from any other + /// origin, even if that origin is insecure. + alwaysAllow, + + /// The WebView will attempt to be compatible with the approach of a modern + /// web browser with regard to mixed content. + compatibilityMode, + + /// The WebView will not allow a secure origin to load content from an + /// insecure origin. + neverAllow, +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -589,6 +606,9 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is SslErrorType) { buffer.putUint8(132); writeValue(buffer, value.index); + } else if (value is MixedContentMode) { + buffer.putUint8(133); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -609,6 +629,9 @@ class _PigeonCodec extends StandardMessageCodec { case 132: final int? value = readValue(buffer) as int?; return value == null ? null : SslErrorType.values[value]; + case 133: + final int? value = readValue(buffer) as int?; + return value == null ? null : MixedContentMode.values[value]; default: return super.readValueOfType(type, buffer); } @@ -2895,6 +2918,36 @@ class WebSettings extends PigeonInternalProxyApiBaseClass { } } + /// Configures the WebView's behavior when handling mixed content. + Future setMixedContentMode(MixedContentMode mode) async { + final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec = + _pigeonVar_codecWebSettings; + final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger; + const String pigeonVar_channelName = + 'dev.flutter.pigeon.webview_flutter_android.WebSettings.setMixedContentMode'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([this, mode]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + @override WebSettings pigeon_copy() { return WebSettings.pigeon_detached( 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 19a6607eb27c..d0ab423344ed 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 @@ -768,6 +768,19 @@ class AndroidWebViewController extends PlatformWebViewController { _ => throw UnsupportedError('Android does not support $mode.'), }; } + + /// Configures the WebView's behavior when handling mixed content. + Future setMixedContentMode(MixedContentMode mode) { + final android_webview.MixedContentMode androidMode = switch (mode) { + MixedContentMode.alwaysAllow => + android_webview.MixedContentMode.alwaysAllow, + MixedContentMode.compatibilityMode => + android_webview.MixedContentMode.compatibilityMode, + MixedContentMode.neverAllow => + android_webview.MixedContentMode.neverAllow, + }; + return _webView.settings.setMixedContentMode(androidMode); + } } /// Android implementation of [PlatformWebViewPermissionRequest]. @@ -866,6 +879,36 @@ enum FileSelectorMode { save, } +/// Mode for controlling mixed content handling. + +/// See [AndroidWebViewController.setMixedContentMode]. +enum MixedContentMode { + /// The WebView will allow a secure origin to load content from any other + /// origin, even if that origin is insecure. + /// + /// This is the least secure mode of operation, and where possible apps should + /// not set this mode. + alwaysAllow, + + /// The WebView will attempt to be compatible with the approach of a modern + /// web browser with regard to mixed content. + /// + /// The types of content are allowed or blocked may change release to release + /// of the underlying Android WebView, and are not explicitly defined. This + /// mode is intended to be used by apps that are not in control of the content + /// that they render but desire to operate in a reasonably secure environment. + compatibilityMode, + + /// The WebView will not allow a secure origin to load content from an + /// insecure origin. + /// + /// This is the preferred and most secure mode of operation, and apps are + /// strongly advised to use this mode. + /// + /// This is the default mode. + neverAllow, +} + /// Parameters received when the `WebView` should show a file selector. @immutable class FileSelectorParams { diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart index 1f9381478f28..e8041c59abe7 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart @@ -125,6 +125,23 @@ enum SslErrorType { unknown, } +/// Options for mixed content mode support. +/// +/// See https://developer.android.com/reference/android/webkit/WebSettings#MIXED_CONTENT_ALWAYS_ALLOW +enum MixedContentMode { + /// The WebView will allow a secure origin to load content from any other + /// origin, even if that origin is insecure. + alwaysAllow, + + /// The WebView will attempt to be compatible with the approach of a modern + /// web browser with regard to mixed content. + compatibilityMode, + + /// The WebView will not allow a secure origin to load content from an + /// insecure origin. + neverAllow, +} + /// Encompasses parameters to the `WebViewClient.shouldInterceptRequest` method. /// /// See https://developer.android.com/reference/android/webkit/WebResourceRequest. @@ -410,6 +427,9 @@ abstract class WebSettings { /// Gets the WebView's user-agent string. String getUserAgentString(); + + /// Configures the WebView's behavior when handling mixed content. + void setMixedContentMode(MixedContentMode mode); } /// A JavaScript interface for exposing Javascript callbacks to Dart. diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index da7a80f252f7..a227805457a0 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/packages/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: 4.7.0 +version: 4.8.0 environment: sdk: ^3.6.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 276b26bb87c2..14a9eba1cfaf 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 @@ -1662,6 +1662,21 @@ void main() { verify(mockSettings.setTextZoom(100)).called(1); }); + test('setMixedContentMode', () async { + final MockWebView mockWebView = MockWebView(); + final MockWebSettings mockSettings = MockWebSettings(); + final AndroidWebViewController controller = createControllerWithMocks( + mockWebView: mockWebView, + mockSettings: mockSettings, + ); + + await controller.setMixedContentMode(MixedContentMode.compatibilityMode); + + verify(mockSettings.setMixedContentMode( + android_webview.MixedContentMode.compatibilityMode, + )).called(1); + }); + test('setOverScrollMode', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart index edc0a7c19202..a786a9394c94 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart @@ -1007,6 +1007,17 @@ class MockAndroidWebViewController extends _i1.Mock returnValue: _i8.Future.value(), returnValueForMissingStub: _i8.Future.value(), ) as _i8.Future); + + @override + _i8.Future setMixedContentMode(_i7.MixedContentMode? mode) => + (super.noSuchMethod( + Invocation.method( + #setMixedContentMode, + [mode], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [AndroidWebViewProxy]. @@ -3001,6 +3012,17 @@ class MockWebSettings extends _i1.Mock implements _i2.WebSettings { )), ) as _i8.Future); + @override + _i8.Future setMixedContentMode(_i2.MixedContentMode? mode) => + (super.noSuchMethod( + Invocation.method( + #setMixedContentMode, + [mode], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override _i2.WebSettings pigeon_copy() => (super.noSuchMethod( Invocation.method( diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart index 3417d866c31c..b9e807b88adf 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart @@ -698,4 +698,15 @@ class MockAndroidWebViewController extends _i1.Mock returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); + + @override + _i5.Future setMixedContentMode(_i6.MixedContentMode? mode) => + (super.noSuchMethod( + Invocation.method( + #setMixedContentMode, + [mode], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart index a77cdec8cefd..d42530568090 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart @@ -401,6 +401,17 @@ class MockWebSettings extends _i1.Mock implements _i2.WebSettings { )), ) as _i4.Future); + @override + _i4.Future setMixedContentMode(_i2.MixedContentMode? mode) => + (super.noSuchMethod( + Invocation.method( + #setMixedContentMode, + [mode], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override _i2.WebSettings pigeon_copy() => (super.noSuchMethod( Invocation.method(