From f862ba67306adbaf6ec1eba2d1266eeb9bcfdab1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:50:19 +0000 Subject: [PATCH 1/8] feat(webview_flutter): Add Android-specific API for setMixedContentMode This commit introduces a new Android-specific API `setMixedContentMode` to the `webview_flutter` plugin. This allows developers to control how the WebView handles mixed content (HTTP content on an HTTPS page). Fixes https://github.com/flutter/flutter/issues/43595 --- .../webview_flutter_test.dart | 21 +++++++++++++ .../webviewflutter/WebSettingsProxyApi.java | 7 +++++ .../webviewflutter/WebSettingsTest.java | 13 ++++++++ .../lib/src/android_webview_controller.dart | 13 ++++++++ .../pigeons/android_webkit.dart | 3 ++ .../test/android_webview_controller_test.dart | 31 +++++++++++++++++++ 6 files changed, 88 insertions(+) diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart index 6307f311b19c..c085724c653b 100644 --- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -923,6 +923,27 @@ Future main() async { expect(nullItem, _webViewNull()); }, ); + + testWidgets('setMixedContentMode', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + await tester.pumpWidget( + MaterialApp( + home: WebView( + initialUrl: 'about:blank', + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller as AndroidWebViewController); + }, + ), + ), + ); + final AndroidWebViewController controller = await controllerCompleter.future; + // 0 is MIXED_CONTENT_ALWAYS_ALLOW + await controller.setMixedContentMode(0); + // TODO(bparrishMines): Verify that mixed content is allowed. + // This might require loading a page with mixed content and checking + // that the insecure content is loaded. + }); } // JavaScript `null` evaluate to different string values per platform. 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..dd63147a5868 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,11 @@ 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, long mode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + pigeon_instance.setMixedContentMode((int) mode); + } + } } 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..1f081c8bb871 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,17 @@ public void getUserAgentString() { assertEquals(value, api.getUserAgentString(instance)); } + + @Test + public void setMixedContentMode() { + final WebSettings mockWebSettings = mock(WebSettings.class); + final WebSettingsProxyApi api = rule.getWebSettingsProxyApi(); + + api.setMixedContentMode(mockWebSettings, 1L); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + verify(mockWebSettings).setMixedContentMode(1); + } else { + verify(mockWebSettings, never()).setMixedContentMode(anyInt()); + } + } } 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..56ab0e95331c 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. + /// + /// The [mode] parameter corresponds to Android's mixed content mode constants: + /// + /// * [WebSettings.MIXED_CONTENT_ALWAYS_ALLOW] + /// * [WebSettings.MIXED_CONTENT_NEVER_ALLOW] + /// * [WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE] + /// + /// This method is only available on Android API level 21 (Lollipop) and above. + Future setMixedContentMode(int mode) { + return _webView.settings.setMixedContentMode(mode); + } } /// Android implementation of [PlatformWebViewPermissionRequest]. 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..7dd1d1991a00 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart @@ -410,6 +410,9 @@ abstract class WebSettings { /// Gets the WebView's user-agent string. String getUserAgentString(); + + /// Configures the WebView's behavior when handling mixed content. + void setMixedContentMode(int mode); } /// A JavaScript interface for exposing Javascript callbacks to Dart. 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..27fa8b5c8d80 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 @@ -2152,6 +2152,37 @@ void main() { ); }); }); + + group('AndroidWebViewController', () { + test('setMixedContentMode', () async { + final PlatformWebViewControllerCreationParams creationParams = + PlatformWebViewControllerCreationParams(); + final AndroidWebViewController controller = AndroidWebViewController( + creationParams, + ); + + final MockAndroidWebViewProxy mockWebViewProxy = MockAndroidWebViewProxy(); + final android_webview.WebView mockWebView = android_webview.WebView.detached( + instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ); + final android_webview.WebSettings mockWebSettings = + android_webview.WebSettings.detached( + instanceManager: android_webview.PigeonInstanceManager( + onWeakReferenceRemoved: (_) {}, + ), + ); + + when(mockWebViewProxy.newWebView(onScrollChanged: anyNamed('onScrollChanged'))) + .thenReturn(mockWebView); + when(mockWebView.settings).thenReturn(mockWebSettings); + + await controller.setMixedContentMode(1); + + verify(mockWebSettings.setMixedContentMode(1)); + }); + }); } /// Creates a PigeonInstanceManager that doesn't make a call to Java when an From 0cb69c6c26f1207dfae6ba61752bd75101b19d2f Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 9 Jul 2025 13:59:21 -0400 Subject: [PATCH 2/8] Add an enum --- .../lib/src/android_webview_controller.dart | 31 ++++++++++++++++++- .../pigeons/android_webkit.dart | 22 ++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) 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 56ab0e95331c..8e5791d72c6f 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 @@ -778,7 +778,7 @@ class AndroidWebViewController extends PlatformWebViewController { /// * [WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE] /// /// This method is only available on Android API level 21 (Lollipop) and above. - Future setMixedContentMode(int mode) { + Future setMixedContentMode(MixedContentMode mode) { return _webView.settings.setMixedContentMode(mode); } } @@ -879,6 +879,35 @@ 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 7dd1d1991a00..561d7b39676b 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,26 @@ 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, + + /// The mode is not recognized by this wrapper. + unknown, +} + /// Encompasses parameters to the `WebViewClient.shouldInterceptRequest` method. /// /// See https://developer.android.com/reference/android/webkit/WebResourceRequest. @@ -412,7 +432,7 @@ abstract class WebSettings { String getUserAgentString(); /// Configures the WebView's behavior when handling mixed content. - void setMixedContentMode(int mode); + void setMixedContentMode(MixedContentMode mode); } /// A JavaScript interface for exposing Javascript callbacks to Dart. From 9022a769c2174a03a7dc21253b4615c4a1642ec9 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 9 Jul 2025 14:19:35 -0400 Subject: [PATCH 3/8] Wire things up, fix Java unit test --- .../webviewflutter/AndroidWebkitLibrary.g.kt | 70 +++++++++++++++++-- .../webviewflutter/WebSettingsProxyApi.java | 15 +++- .../webviewflutter/WebSettingsTest.java | 15 ++-- .../lib/src/android_webkit.g.dart | 59 +++++++++++++++- .../lib/src/android_webview_controller.dart | 18 ++--- .../pigeons/android_webkit.dart | 3 - .../test/android_webview_controller_test.dart | 9 ++- 7 files changed, 156 insertions(+), 33 deletions(-) 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..250df485c12e 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 @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -127,10 +127,6 @@ class AndroidWebkitLibraryPigeonInstanceManager( */ fun remove(identifier: Long): T? { logWarningIfFinalizationListenerHasStopped() - val instance: Any? = getInstance(identifier) - if (instance is WebViewProxyApi.WebViewPlatformView) { - instance.destroy() - } return strongInstances.remove(identifier) as T? } @@ -660,6 +656,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 +883,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 +924,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 +949,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 +2315,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 +2707,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 dd63147a5868..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 @@ -103,9 +103,18 @@ public String getUserAgentString(@NonNull WebSettings pigeon_instance) { } @Override - public void setMixedContentMode(@NonNull WebSettings pigeon_instance, long mode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - pigeon_instance.setMixedContentMode((int) mode); + 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 1f081c8bb871..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 @@ -169,14 +169,11 @@ public void getUserAgentString() { @Test public void setMixedContentMode() { - final WebSettings mockWebSettings = mock(WebSettings.class); - final WebSettingsProxyApi api = rule.getWebSettingsProxyApi(); - - api.setMixedContentMode(mockWebSettings, 1L); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - verify(mockWebSettings).setMixedContentMode(1); - } else { - verify(mockWebSettings, never()).setMixedContentMode(anyInt()); - } + 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..653c4e2cbfbb 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 @@ -1,11 +1,12 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.3.2), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers import 'dart:async'; +import 'dart:io' show Platform; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' @@ -125,6 +126,9 @@ class PigeonInstanceManager { late final void Function(int) onWeakReferenceRemoved; static PigeonInstanceManager _initInstance() { + if (Platform.environment['FLUTTER_TEST'] == 'true') { + return PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); + } WidgetsFlutterBinding.ensureInitialized(); final _PigeonInternalInstanceManagerApi api = _PigeonInternalInstanceManagerApi(); @@ -570,6 +574,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 +610,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 +633,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 +2922,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 8e5791d72c6f..d1646af1ecb0 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 @@ -770,16 +770,16 @@ class AndroidWebViewController extends PlatformWebViewController { } /// Configures the WebView's behavior when handling mixed content. - /// - /// The [mode] parameter corresponds to Android's mixed content mode constants: - /// - /// * [WebSettings.MIXED_CONTENT_ALWAYS_ALLOW] - /// * [WebSettings.MIXED_CONTENT_NEVER_ALLOW] - /// * [WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE] - /// - /// This method is only available on Android API level 21 (Lollipop) and above. Future setMixedContentMode(MixedContentMode mode) { - return _webView.settings.setMixedContentMode(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); } } 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 561d7b39676b..e8041c59abe7 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webkit.dart @@ -140,9 +140,6 @@ enum MixedContentMode { /// The WebView will not allow a secure origin to load content from an /// insecure origin. neverAllow, - - /// The mode is not recognized by this wrapper. - unknown, } /// Encompasses parameters to the `WebViewClient.shouldInterceptRequest` method. 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 27fa8b5c8d80..87fe1fa5acff 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 @@ -2161,8 +2161,10 @@ void main() { creationParams, ); - final MockAndroidWebViewProxy mockWebViewProxy = MockAndroidWebViewProxy(); - final android_webview.WebView mockWebView = android_webview.WebView.detached( + final MockAndroidWebViewProxy mockWebViewProxy = + MockAndroidWebViewProxy(); + final android_webview.WebView mockWebView = + android_webview.WebView.detached( instanceManager: android_webview.PigeonInstanceManager( onWeakReferenceRemoved: (_) {}, ), @@ -2174,7 +2176,8 @@ void main() { ), ); - when(mockWebViewProxy.newWebView(onScrollChanged: anyNamed('onScrollChanged'))) + when(mockWebViewProxy.newWebView( + onScrollChanged: anyNamed('onScrollChanged'))) .thenReturn(mockWebView); when(mockWebView.settings).thenReturn(mockWebSettings); From 6e7576a9b1555e3d50fd9c72f28374ce1f7996bf Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 9 Jul 2025 14:29:39 -0400 Subject: [PATCH 4/8] Revert integration test --- .../webview_flutter_test.dart | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart index c085724c653b..6307f311b19c 100644 --- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -923,27 +923,6 @@ Future main() async { expect(nullItem, _webViewNull()); }, ); - - testWidgets('setMixedContentMode', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - await tester.pumpWidget( - MaterialApp( - home: WebView( - initialUrl: 'about:blank', - onWebViewCreated: (WebViewController controller) { - controllerCompleter.complete(controller as AndroidWebViewController); - }, - ), - ), - ); - final AndroidWebViewController controller = await controllerCompleter.future; - // 0 is MIXED_CONTENT_ALWAYS_ALLOW - await controller.setMixedContentMode(0); - // TODO(bparrishMines): Verify that mixed content is allowed. - // This might require loading a page with mixed content and checking - // that the insecure content is loaded. - }); } // JavaScript `null` evaluate to different string values per platform. From ba8948d1b02993630c7b2ce1c7ff2a4635677038 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 9 Jul 2025 14:39:16 -0400 Subject: [PATCH 5/8] Update mocks, replace Dart unit test with a working one --- .../test/android_webview_controller_test.dart | 49 ++++++------------- ...android_webview_controller_test.mocks.dart | 22 +++++++++ ...oid_webview_cookie_manager_test.mocks.dart | 11 +++++ .../webview_android_widget_test.mocks.dart | 11 +++++ 4 files changed, 59 insertions(+), 34 deletions(-) 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 87fe1fa5acff..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( @@ -2152,40 +2167,6 @@ void main() { ); }); }); - - group('AndroidWebViewController', () { - test('setMixedContentMode', () async { - final PlatformWebViewControllerCreationParams creationParams = - PlatformWebViewControllerCreationParams(); - final AndroidWebViewController controller = AndroidWebViewController( - creationParams, - ); - - final MockAndroidWebViewProxy mockWebViewProxy = - MockAndroidWebViewProxy(); - final android_webview.WebView mockWebView = - android_webview.WebView.detached( - instanceManager: android_webview.PigeonInstanceManager( - onWeakReferenceRemoved: (_) {}, - ), - ); - final android_webview.WebSettings mockWebSettings = - android_webview.WebSettings.detached( - instanceManager: android_webview.PigeonInstanceManager( - onWeakReferenceRemoved: (_) {}, - ), - ); - - when(mockWebViewProxy.newWebView( - onScrollChanged: anyNamed('onScrollChanged'))) - .thenReturn(mockWebView); - when(mockWebView.settings).thenReturn(mockWebSettings); - - await controller.setMixedContentMode(1); - - verify(mockWebSettings.setMixedContentMode(1)); - }); - }); } /// Creates a PigeonInstanceManager that doesn't make a call to Java when an 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( From f49121596ec881f2baf083bef3effc0d1fa6a2bd Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 9 Jul 2025 14:56:38 -0400 Subject: [PATCH 6/8] Version bump --- .../webview_flutter/webview_flutter_android/CHANGELOG.md | 5 +++++ .../webview_flutter/webview_flutter_android/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) 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/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 From 973bc0841321ddc61435b944bb699889c2599865 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 9 Jul 2025 16:02:00 -0400 Subject: [PATCH 7/8] Re-add manual change, per package docs --- .../plugins/webviewflutter/AndroidWebkitLibrary.g.kt | 6 +++++- .../webview_flutter_android/lib/src/android_webkit.g.dart | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) 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 250df485c12e..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 @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.5.0), do not edit directly. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. // See also: https://pub.dev/packages/pigeon @file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") @@ -127,6 +127,10 @@ class AndroidWebkitLibraryPigeonInstanceManager( */ fun remove(identifier: Long): T? { logWarningIfFinalizationListenerHasStopped() + val instance: Any? = getInstance(identifier) + if (instance is WebViewProxyApi.WebViewPlatformView) { + instance.destroy() + } return strongInstances.remove(identifier) as T? } 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 653c4e2cbfbb..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 @@ -1,12 +1,11 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v25.5.0), do not edit directly. +// Autogenerated from Pigeon (v25.3.2), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers import 'dart:async'; -import 'dart:io' show Platform; import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; import 'package:flutter/foundation.dart' @@ -126,9 +125,6 @@ class PigeonInstanceManager { late final void Function(int) onWeakReferenceRemoved; static PigeonInstanceManager _initInstance() { - if (Platform.environment['FLUTTER_TEST'] == 'true') { - return PigeonInstanceManager(onWeakReferenceRemoved: (_) {}); - } WidgetsFlutterBinding.ensureInitialized(); final _PigeonInternalInstanceManagerApi api = _PigeonInternalInstanceManagerApi(); From db1040b243019fbc227b5d69e057be2ad4c5e9ec Mon Sep 17 00:00:00 2001 From: stuartmorgan-g Date: Tue, 15 Jul 2025 09:31:06 -0400 Subject: [PATCH 8/8] Fix doc comment style Co-authored-by: Maurice Parrish <10687576+bparrishMines@users.noreply.github.com> --- .../lib/src/android_webview_controller.dart | 1 + 1 file changed, 1 insertion(+) 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 d1646af1ecb0..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 @@ -880,6 +880,7 @@ enum FileSelectorMode { } /// Mode for controlling mixed content handling. + /// See [AndroidWebViewController.setMixedContentMode]. enum MixedContentMode { /// The WebView will allow a secure origin to load content from any other