Skip to content
Open
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_android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.

## 4.13.0

* Adds support for configuring Web Authentication in `AndroidWebViewController` with `setWebAuthenticationSupport`.

## 4.12.0

* Adds support for retrieving cookies with `PlatformWebViewCookieManager.getCookies`.
Expand Down
28 changes: 28 additions & 0 deletions packages/webview_flutter/webview_flutter_android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,34 @@ Add intent filters to your AndroidManifest.xml to discover and invoke Android pa
</queries>
```

## Enable Web Authentication in WebView

WebAuthentication (WebAuthn) can be configured by calling
`AndroidWebViewController.setWebAuthenticationSupport` after checking
`AndroidWebViewController.isWebViewFeatureSupported`.

The WebAuthentication support level can be set to one of three values:
- **[WebAuthenticationSupport.none]**: Disables all WebAuthn requests
- **[WebAuthenticationSupport.forApp]**: Allows WebAuthn for the embedded application (default)
- **[WebAuthenticationSupport.forBrowser]**: Allows WebAuthn for any website (browser-like behavior)

<?code-excerpt "example/lib/readme_excerpts.dart (web_authentication_example)"?>
```dart
final bool webAuthenticationSupported = await androidController
.isWebViewFeatureSupported(WebViewFeatureType.webAuthentication);

if (webAuthenticationSupported) {
// Enable WebAuthn for the embedded app
await androidController.setWebAuthenticationSupport(
WebAuthenticationSupport.forApp,
);
// Or for browser-like behavior supporting any website:
// await androidController.setWebAuthenticationSupport(
// WebAuthenticationSupport.forBrowser,
// );
}
```

## Fullscreen Video

To display a video as fullscreen, an app must manually handle the notification that the current page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6826,6 +6826,8 @@ abstract class PigeonApiWebSettingsCompat(
) {
abstract fun setPaymentRequestEnabled(webSettings: android.webkit.WebSettings, enabled: Boolean)

abstract fun setWebAuthenticationSupport(webSettings: android.webkit.WebSettings, support: Long)

companion object {
@Suppress("LocalVariableName")
fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiWebSettingsCompat?) {
Expand Down Expand Up @@ -6854,6 +6856,30 @@ abstract class PigeonApiWebSettingsCompat(
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.WebSettingsCompat.setWebAuthenticationSupport",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val webSettingsArg = args[0] as android.webkit.WebSettings
val supportArg = args[1] as Long
val wrapped: List<Any?> =
try {
api.setWebAuthenticationSupport(webSettingsArg, supportArg)
listOf(null)
} catch (exception: Throwable) {
AndroidWebkitLibraryPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,28 @@ public WebSettingsCompatProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) {
public void setPaymentRequestEnabled(@NonNull WebSettings webSettings, boolean enabled) {
WebSettingsCompat.setPaymentRequestEnabled(webSettings, enabled);
}

/**
* This method should only be called if {@link WebViewFeatureProxyApi#isFeatureSupported(String)}
* with WEB_AUTHENTICATION returns true.
*
* <p>The {@code support} parameter is a {@code long} to accommodate Dart's integer type, but is
* safely converted to {@code int} for the underlying Android API call. {@link
* Math#toIntExact(long)} is used to verify the value fits in the {@code int} range and throw
* {@link ArithmeticException} if it overflows. This is safe because the valid support levels are
* constants (0, 1, 2) that well within the integer range.
*
* <p>Note: {@link Math#toIntExact(long)} requires API level 24 or higher. This is compatible with
* this plugin's minimum SDK version.
*
* @param webSettings the WebSettings instance
* @param support the WebAuthentication support level (0, 1, or 2)
* @throws ArithmeticException if {@code support} exceeds {@link Integer#MAX_VALUE}
*/
@SuppressLint("RequiresFeature")
@Override
public void setWebAuthenticationSupport(@NonNull WebSettings webSettings, long support) {
final int supportValue = Math.toIntExact(support);
Comment thread
creatorpiyush marked this conversation as resolved.
WebSettingsCompat.setWebAuthenticationSupport(webSettings, supportValue);
}
Comment thread
creatorpiyush marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.flutter.plugins.webviewflutter;

import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
Expand Down Expand Up @@ -36,4 +37,38 @@ public void setPaymentRequestEnabled() {
fail(e.toString());
}
}

@Test
public void setWebAuthenticationSupport() {
final PigeonApiWebSettingsCompat api =
new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat();

final WebSettings webSettings = mock(WebSettings.class);

try (MockedStatic<WebSettingsCompat> mockedStatic = mockStatic(WebSettingsCompat.class);
MockedStatic<WebViewFeature> mockedWebViewFeature = mockStatic(WebViewFeature.class)) {
mockedWebViewFeature
.when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.WEB_AUTHENTICATION))
.thenReturn(true);
api.setWebAuthenticationSupport(webSettings, 2L);
mockedStatic.verify(() -> WebSettingsCompat.setWebAuthenticationSupport(webSettings, 2));
}
}

@Test
public void setWebAuthenticationSupportWithLongOutsideIntRangeThrows() {
final PigeonApiWebSettingsCompat api =
new TestProxyApiRegistrar().getPigeonApiWebSettingsCompat();

final WebSettings webSettings = mock(WebSettings.class);

try (MockedStatic<WebViewFeature> mockedWebViewFeature = mockStatic(WebViewFeature.class)) {
mockedWebViewFeature
.when(() -> WebViewFeature.isFeatureSupported(WebViewFeature.WEB_AUTHENTICATION))
.thenReturn(true);
assertThrows(
ArithmeticException.class,
() -> api.setWebAuthenticationSupport(webSettings, Integer.MAX_VALUE + 1L));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ enum MenuOptions {
videoExample,
logExample,
basicAuthentication,
webAuthentication,
javaScriptAlert,
viewportMeta,
}
Expand Down Expand Up @@ -403,6 +404,8 @@ class SampleMenu extends StatelessWidget {
_onLogExample();
case MenuOptions.basicAuthentication:
_promptForUrl(context);
case MenuOptions.webAuthentication:
_onWebAuthenticationExample(context);
case MenuOptions.javaScriptAlert:
_onJavaScriptAlertExample(context);
case MenuOptions.viewportMeta:
Expand Down Expand Up @@ -475,6 +478,10 @@ class SampleMenu extends StatelessWidget {
value: MenuOptions.basicAuthentication,
child: Text('Basic Authentication Example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.webAuthentication,
child: Text('Web Authentication Example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.javaScriptAlert,
child: Text('JavaScript Alert Example'),
Expand Down Expand Up @@ -611,6 +618,36 @@ class SampleMenu extends StatelessWidget {
);
}

Future<void> _onWebAuthenticationExample(BuildContext context) async {
final androidController = webViewController as AndroidWebViewController;
final bool supported = await androidController.isWebViewFeatureSupported(
WebViewFeatureType.webAuthentication,
);

if (!supported) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Web Authentication is not supported on this device.',
),
),
);
}
return;
}

await androidController.setWebAuthenticationSupport(
WebAuthenticationSupport.forApp,
);

if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Web Authentication enabled.')),
);
}
}

Future<void> _onDoPostRequest() {
return webViewController.loadRequest(
LoadRequestParams(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,29 @@ Future<void> enablePaymentRequest() async {
// #enddocregion payment_request_example
}

/// Example function for README demonstration of Web Authentication support.
Future<void> enableWebAuthentication() async {
final controller = PlatformWebViewController(
AndroidWebViewControllerCreationParams(),
);
final androidController = controller as AndroidWebViewController;
// #docregion web_authentication_example
final bool webAuthenticationSupported = await androidController
.isWebViewFeatureSupported(WebViewFeatureType.webAuthentication);

if (webAuthenticationSupported) {
// Enable WebAuthn for the embedded app
await androidController.setWebAuthenticationSupport(
WebAuthenticationSupport.forApp,
);
// Or for browser-like behavior supporting any website:
// await androidController.setWebAuthenticationSupport(
// WebAuthenticationSupport.forBrowser,
// );
}
// #enddocregion web_authentication_example
}

/// Example function for README demonstration of geolocation permissions for
/// a use case where the content is always trusted (for example, it only shows
/// content from a domain controlled by the app developer) and geolocation
Expand Down
Loading