Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
953e220
Flutter implementation
LuisThein Jul 26, 2019
9478588
Added Android implementation + updated example
LuisThein Jul 26, 2019
45e915d
iOS implementation
LuisThein Jul 26, 2019
ae81a98
Added Widget Test
LuisThein Jul 26, 2019
de27c90
Added unit tests for User Agent
LuisThein Jul 26, 2019
cbcc808
Updated CHANGELOG.md
LuisThein Jul 27, 2019
4141cf2
Merge branch 'master' into feature/set_user_agent
LuisThein Jul 27, 2019
d54e0bc
Fixed format error in FlutterwebView.m
LuisThein Jul 27, 2019
86d44ca
Fixed another format error
LuisThein Jul 27, 2019
7959989
Merge branch 'master' into feature/set_user_agent
LuisThein Aug 2, 2019
7b26a41
Changed files according to pull request review
LuisThein Aug 5, 2019
7fed032
Minor changes
LuisThein Aug 5, 2019
c5cc6a7
Updated dartdoc for WebView.userAgent
LuisThein Aug 5, 2019
c99c8f2
Merged branch master into feature/set_user_agent
LuisThein Aug 8, 2019
c931307
Resolved nerge conflicts.
LuisThein Aug 10, 2019
7976151
Use a WebSetting<T> type to allow representing absence of a null value.
amirh Aug 14, 2019
4c0d2f3
review fixes
amirh Aug 14, 2019
de4d1df
Merge branch 'master' into feature/set_user_agent
amirh Aug 14, 2019
5be86ef
bump version
amirh Aug 14, 2019
5887b33
Merge branch 'master' into feature/set_user_agent
amirh Aug 14, 2019
bf8b74c
remove extra blank line from changelog
amirh Aug 14, 2019
ebe699d
Removed getUserAgent
LuisThein Aug 15, 2019
ee693fd
Merge branch 'feature/set_user_agent' of https://github.com/LuisThein…
LuisThein Aug 15, 2019
641537b
Fixed formatting issue
LuisThein Aug 15, 2019
db3cfb9
Fixed _getUserAgent in webview.dart
LuisThein Aug 15, 2019
974c16e
Reverted unrelated reformatting
LuisThein Aug 16, 2019
c13245a
Resolved merge conflicts.
LuisThein Aug 16, 2019
3282117
Fixed another formatting issue.
LuisThein Aug 16, 2019
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
6 changes: 6 additions & 0 deletions packages/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.3.11

* Add an optional `userAgent` property to set a custom User Agent.
Add `getUserAgent` to WebViewController.

## 0.3.10+5

* Add dependency on `androidx.annotation:annotation:1.0.0`.
Expand All @@ -6,6 +11,7 @@

* Add keyboard text to README.


## 0.3.10+3

* Don't log an unknown setting key error for 'debuggingEnabled' on iOS.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
String url = (String) params.get("initialUrl");
webView.loadUrl(url);
}

if (params.containsKey("userAgent")) {
String userAgent = (String) params.get("userAgent");
updateUserAgent(userAgent);
}
}

@Override
Expand Down Expand Up @@ -121,6 +126,9 @@ public void onMethodCall(MethodCall methodCall, Result result) {
case "clearCache":
clearCache(result);
break;
case "getUserAgent":
getUserAgent(methodCall, result);
break;
default:
result.notImplemented();
}
Expand Down Expand Up @@ -232,6 +240,9 @@ private void applySettings(Map<String, Object> settings) {

webView.setWebContentsDebuggingEnabled(debuggingEnabled);
break;
case "userAgent":
updateUserAgent((String) settings.get(key));
break;
default:
throw new IllegalArgumentException("Unknown WebView setting: " + key);
}
Expand All @@ -258,6 +269,15 @@ private void registerJavaScriptChannelNames(List<String> channelNames) {
}
}

private void getUserAgent(MethodCall methodCall, Result result) {
String userAgent = webView.getSettings().getUserAgentString();
result.success(userAgent);
}

private void updateUserAgent(String userAgent) {
webView.getSettings().setUserAgentString(userAgent);
}

@Override
public void dispose() {
methodChannel.setMethodCallHandler(null);
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 @@ -53,6 +53,7 @@ class _WebViewExampleState extends State<WebViewExample> {
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
userAgent: 'Flutter_Custom_Agent',
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
// ignore: prefer_collection_literals
javascriptChannels: <JavascriptChannel>[
Expand Down
43 changes: 43 additions & 0 deletions packages/webview_flutter/example/test_driver/webview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,49 @@ void main() {
await controller.evaluateJavascript('Echo.postMessage("hello");');
expect(messagesReceived, equals(<String>['hello']));
});

test('userAgent', () async {
final Completer<WebViewController> controllerCompleter1 =
Completer<WebViewController>();
await pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: GlobalKey(),
initialUrl: 'https://flutter.dev/',
javascriptMode: JavascriptMode.unrestricted,
userAgent: 'Custom_User_Agent1',
onWebViewCreated: (WebViewController controller) {
controllerCompleter1.complete(controller);
},
),
),
);
final WebViewController controller1 = await controllerCompleter1.future;
final String customUserAgent1 = await controller1.getUserAgent();
expect(customUserAgent1, 'Custom_User_Agent1');

// rebuilds a WebView with a different user agent.
final Completer<WebViewController> controllerCompleter2 =
Completer<WebViewController>();
await pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: GlobalKey(),
initialUrl: 'https://flutter.dev/',
javascriptMode: JavascriptMode.unrestricted,
userAgent: 'Custom_User_Agent2',
onWebViewCreated: (WebViewController controller) {
controllerCompleter2.complete(controller);
},
),
),
);
final WebViewController controller2 = await controllerCompleter2.future;
final String customUserAgent2 = await controller2.getUserAgent();
expect(customUserAgent2, 'Custom_User_Agent2');
});
}

Future<void> pumpWidget(Widget widget) {
Expand Down
30 changes: 30 additions & 0 deletions packages/webview_flutter/ios/Classes/FlutterWebView.m
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
[self onRemoveJavaScriptChannels:call result:result];
} else if ([[call method] isEqualToString:@"clearCache"]) {
[self clearCache:result];
} else if ([[call method] isEqualToString:@"getUserAgent"]) {
[self onGetUserAgent:call result:result];
} else {
result(FlutterMethodNotImplemented);
}
Expand Down Expand Up @@ -246,6 +248,9 @@ - (NSString*)applySettings:(NSDictionary<NSString*, id>*)settings {
_navigationDelegate.hasDartNavigationDelegate = [hasDartNavigationDelegate boolValue];
} else if ([key isEqualToString:@"debuggingEnabled"]) {
// no-op debugging is always enabled on iOS.
} else if ([key isEqualToString:@"userAgent"]) {
NSString* userAgent = settings[key];
[self updateUserAgent:[userAgent isEqual:[NSNull null]] ? nil : userAgent];
} else {
[unknownKeys addObject:key];
}
Expand Down Expand Up @@ -321,4 +326,29 @@ - (void)registerJavaScriptChannels:(NSSet*)channelNames
}
}

- (void)onGetUserAgent:(FlutterMethodCall*)call result:(FlutterResult)result {
[_webView
evaluateJavaScript:@"navigator.userAgent"
completionHandler:^(NSString* userAgent, NSError* error) {
if (error) {
result([FlutterError
errorWithCode:@"userAgent_failed"
message:@"Failed to get UserAgent"
details:[NSString stringWithFormat:
@"webview_flutter: failed evaluating JavaScript: %@",
[error localizedDescription]]]);
} else {
result(userAgent);
}
}];
}

- (void)updateUserAgent:(NSString*)userAgent {
if (@available(iOS 9.0, *)) {
[_webView setCustomUserAgent:userAgent];
} else {
NSLog(@"Updating UserAgent is not supported for Flutter WebViews prior to iOS 9.");
}
}

@end
25 changes: 22 additions & 3 deletions packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ abstract class WebViewPlatformController {
throw UnimplementedError(
"WebView removeJavascriptChannels is not implemented on the current platform");
}

Future<String> getUserAgent() {
throw UnimplementedError(
"WebView getUserAgent is not implemented on the current platform");
}
}

/// Settings for configuring a WebViewPlatform.
Expand All @@ -163,6 +168,7 @@ class WebSettings {
this.javascriptMode,
this.hasNavigationDelegate,
this.debuggingEnabled,
this.userAgent,
});

/// The JavaScript execution mode to be used by the webview.
Expand All @@ -176,16 +182,24 @@ class WebSettings {
/// See also: [WebView.debuggingEnabled].
final bool debuggingEnabled;

/// The User Agent used for all following requests
///
/// See also [WebView.userAgent]
final String userAgent;

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

/// Configuration to use when creating a new [WebViewPlatformController].
class CreationParams {
CreationParams(
{this.initialUrl, this.webSettings, this.javascriptChannelNames});
{this.initialUrl,
this.webSettings,
this.javascriptChannelNames,
this.userAgent});

/// The initialUrl to load in the webview.
///
Expand All @@ -210,9 +224,14 @@ class CreationParams {
// to PlatformWebView.
final Set<String> javascriptChannelNames;

/// The User Agent used for all following requests
///
/// When null, the default User Agent of the Android/iOS WebView is used
final String userAgent;

@override
String toString() {
return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames)';
return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent)';
}
}

Expand Down
7 changes: 7 additions & 0 deletions packages/webview_flutter/lib/src/webview_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
'removeJavascriptChannels', javascriptChannelNames.toList());
}

@override
Future<String> getUserAgent() {
return _channel.invokeMethod('getUserAgent');
}

/// Method channel mplementation for [WebViewPlatform.clearCookies].
static Future<bool> clearCookies() {
return _cookieManagerChannel
Expand All @@ -122,6 +127,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
_addIfNonNull('jsMode', settings.javascriptMode?.index);
_addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate);
_addIfNonNull('debuggingEnabled', settings.debuggingEnabled);
_addIfNonNull('userAgent', settings.userAgent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we rebuild a WebView that had an explicitly set userAgent and set the userAgent to null. IIUC the way this currently propagates will lead to preserving the previous user agent. I'd expect it to go back to the platform's default user agent.

We should add an test for this scenario as well (build with an explicitly set user agent, rebuild with user agent set to null)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I changed it so that the user agent can be set to null and the default platform user agent is used after a rebuild. Additionally I adapted the same logic for the FakePlatformWebView unit tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I think we should avoid updating the user agent each time the widget is rebuilt.
It seems like we're lacking the ability to express presence/absence of a setting that may be null. One way to do it would be to use some kind of an optional type.

I pushed 7976151 with a proposal on how to do that, let me know whether that makes sense to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is a nice way to handle nullable settings and I agree that updating the user agent every rebuild is unnecessary.

return map;
}

Expand All @@ -135,6 +141,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
'initialUrl': creationParams.initialUrl,
'settings': _webSettingsToMap(creationParams.webSettings),
'javascriptChannelNames': creationParams.javascriptChannelNames.toList(),
'userAgent': creationParams.userAgent,
};
}
}
29 changes: 27 additions & 2 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class WebView extends StatefulWidget {
this.gestureRecognizers,
this.onPageFinished,
this.debuggingEnabled = false,
this.userAgent,
}) : assert(javascriptMode != null),
super(key: key);

Expand Down Expand Up @@ -255,6 +256,18 @@ class WebView extends StatefulWidget {
/// By default `debuggingEnabled` is false.
final bool debuggingEnabled;

/// The User_Agent used for WebView requests.
///
/// By default `userAgent` is null.
/// If the `userAgent` is null, the default User Agent from the Android / iOS WebView is used.
/// WebView User Agent for Android (https://developer.chrome.com/multidevice/user-agent)
///
/// When the WebView is rebuild with a new User Agent, the page reloads and the request uses the new User Agent.
///
/// When [WebViewController.goBack] is called after changing the User Agent, the old User Agent is used.
/// Only after reloading the Page you went back to,the request uses the new User Agent.
final String userAgent;

@override
State<StatefulWidget> createState() => _WebViewState();
}
Expand Down Expand Up @@ -317,6 +330,7 @@ CreationParams _creationParamsfromWidget(WebView widget) {
initialUrl: widget.initialUrl,
webSettings: _webSettingsFromWidget(widget),
javascriptChannelNames: _extractChannelNames(widget.javascriptChannels),
userAgent: widget.userAgent,
);
}

Expand All @@ -325,10 +339,11 @@ WebSettings _webSettingsFromWidget(WebView widget) {
javascriptMode: widget.javascriptMode,
hasNavigationDelegate: widget.navigationDelegate != null,
debuggingEnabled: widget.debuggingEnabled,
userAgent: widget.userAgent,
);
}

// This method assumes that no fields in `currentValue` are null.
// This method assumes that no fields, except userAgent (see WebView.userAgent), in `currentValue` are null.
WebSettings _clearUnchangedWebSettings(
WebSettings currentValue, WebSettings newValue) {
assert(currentValue.javascriptMode != null);
Expand All @@ -340,6 +355,7 @@ WebSettings _clearUnchangedWebSettings(
JavascriptMode javascriptMode;
bool hasNavigationDelegate;
bool debuggingEnabled;
String userAgent;
if (currentValue.javascriptMode != newValue.javascriptMode) {
javascriptMode = newValue.javascriptMode;
}
Expand All @@ -349,11 +365,15 @@ WebSettings _clearUnchangedWebSettings(
if (currentValue.debuggingEnabled != newValue.debuggingEnabled) {
debuggingEnabled = newValue.debuggingEnabled;
}
if (currentValue.userAgent != newValue.userAgent) {
userAgent = newValue.userAgent;
}

return WebSettings(
javascriptMode: javascriptMode,
hasNavigationDelegate: hasNavigationDelegate,
debuggingEnabled: debuggingEnabled);
debuggingEnabled: debuggingEnabled,
userAgent: userAgent);
}

Set<String> _extractChannelNames(Set<JavascriptChannel> channels) {
Expand Down Expand Up @@ -568,6 +588,11 @@ class WebViewController {
// ignore: strong_mode_implicit_dynamic_method
return _webViewPlatformController.evaluateJavascript(javascriptString);
}

/// Returns the User Agent value that will be used for subsequent HTTP requests.
Future<String> getUserAgent() async {
return _webViewPlatformController.getUserAgent();
}
}

/// Manages cookies pertaining to all [WebView]s.
Expand Down
Loading