Skip to content

[webview_flutter_android][webview_flutter_wkwebview] Adds support for getUserAgent for webview_flutter platform implementations #4927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 29, 2023
Merged
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.12.0

* Adds support for `PlatformWebViewController.getUserAgent`.

## 3.11.0

* Adds support to register a callback to receive JavaScript console messages. See `AndroidWebViewController.onConsoleMessage`.
Expand Down Expand Up @@ -27,7 +31,7 @@
## 3.9.2

* Fixes bug where `PlatformWebViewWidget` doesn't rebuild when the controller or PlatformView
implementation flag changes.
implementation flag changes.

## 3.9.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1790,6 +1790,9 @@ public interface WebSettingsHostApi {

void setTextZoom(@NonNull Long instanceId, @NonNull Long textZoom);

@NonNull
String getUserAgentString(@NonNull Long instanceId);

/** The codec used by WebSettingsHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
Expand Down Expand Up @@ -2179,6 +2182,33 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable WebSetting
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.WebSettingsHostApi.getUserAgentString",
getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
Number instanceIdArg = (Number) args.get(0);
try {
String output =
api.getUserAgentString(
(instanceIdArg == null) ? null : instanceIdArg.longValue());
wrapped.add(0, output);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,11 @@ public void setTextZoom(@NonNull Long instanceId, @NonNull Long textZoom) {
final WebSettings webSettings = Objects.requireNonNull(instanceManager.getInstance(instanceId));
webSettings.setTextZoom(textZoom.intValue());
}

@NonNull
@Override
public String getUserAgentString(@NonNull Long instanceId) {
final WebSettings webSettings = Objects.requireNonNull(instanceManager.getInstance(instanceId));
return webSettings.getUserAgentString();
}
}
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.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -117,4 +118,11 @@ public void setTextZoom() {
testHostApiImpl.setTextZoom(0L, 100L);
verify(mockWebSettings).setTextZoom(100);
}

@Test
public void getUserAgentString() {
final String userAgent = "str";
when(mockWebSettings.getUserAgentString()).thenReturn(userAgent);
assertEquals(testHostApiImpl.getUserAgentString(0L), userAgent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ Future<void> main() async {

await pageFinished.future;

final String customUserAgent = await _getUserAgent(controller);
final String? customUserAgent = await controller.getUserAgent();
expect(customUserAgent, 'Custom_User_Agent1');
});

Expand Down Expand Up @@ -1353,19 +1353,6 @@ Future<void> main() async {
});
}

/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
Future<String> _getUserAgent(PlatformWebViewController controller) async {
return _runJavaScriptReturningResult(controller, 'navigator.userAgent;');
}

Future<String> _runJavaScriptReturningResult(
PlatformWebViewController controller,
String js,
) async {
return jsonDecode(await controller.runJavaScriptReturningResult(js) as String)
as String;
}

class ResizableWebView extends StatefulWidget {
const ResizableWebView({
super.key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,11 @@ class WebSettings extends JavaObject {
return api.setSetTextZoomFromInstance(this, textZoom);
}

/// Gets the WebView's user-agent string.
Future<String> getUserAgentString() {
return api.getUserAgentStringFromInstance(this);
}

@override
WebSettings copy() {
return WebSettings.detached(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,34 @@ class WebSettingsHostApi {
return;
}
}

Future<String> getUserAgentString(int arg_instanceId) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.webview_flutter_android.WebSettingsHostApi.getUserAgentString',
codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_instanceId]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as String?)!;
}
}
}

class JavaScriptChannelHostApi {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,11 @@ class WebSettingsHostApiImpl extends WebSettingsHostApi {
enabled,
);
}

/// Helper method to convert instances ids to objects.
Future<String> getUserAgentStringFromInstance(WebSettings instance) {
return getUserAgentString(instanceManager.getIdentifier(instance)!);
}
}

/// Host api implementation for [JavaScriptChannel].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,9 @@ class AndroidWebViewController extends PlatformWebViewController {
return _webChromeClient.setSynchronousReturnValueForOnConsoleMessage(
_onConsoleLogCallback != null);
}

@override
Future<String?> getUserAgent() => _webView.settings.getUserAgentString();
}

/// Android implementation of [PlatformWebViewPermissionRequest].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ abstract class WebSettingsHostApi {
void setAllowFileAccess(int instanceId, bool enabled);

void setTextZoom(int instanceId, int textZoom);

String getUserAgentString(int instanceId);
}

@HostApi(dartHostTestHandler: 'TestJavaScriptChannelHostApi')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: 3.11.0
version: 3.12.0

environment:
sdk: ">=2.19.0 <4.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,20 @@ void main() {
verify(mockWebView.settings).called(1);
verify(mockSettings.setUserAgentString('Test Framework')).called(1);
});

test('getUserAgent', () async {
final MockWebSettings mockSettings = MockWebSettings();
final AndroidWebViewController controller = createControllerWithMocks(
mockSettings: mockSettings,
);

const String userAgent = 'str';

when(mockSettings.getUserAgentString())
.thenAnswer((_) => Future<String>.value(userAgent));

expect(await controller.getUserAgent(), userAgent);
});
});

test('setMediaPlaybackRequiresUserGesture', () async {
Expand Down
Loading