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 19 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.12

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

## 0.3.11+3

* Apply the display listeners workaround that was shipped in 0.3.11+1 on
Expand Down Expand Up @@ -26,6 +31,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 @@ -60,6 +60,10 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
}

updateAutoMediaPlaybackPolicy((Integer) params.get("autoMediaPlaybackPolicy"));
if (params.containsKey("userAgent")) {
String userAgent = (String) params.get("userAgent");
updateUserAgent(userAgent);
}
if (params.containsKey("initialUrl")) {
String url = (String) params.get("initialUrl");
webView.loadUrl(url);
Expand Down Expand Up @@ -130,6 +134,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 @@ -241,6 +248,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 Down Expand Up @@ -274,6 +284,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
88 changes: 88 additions & 0 deletions packages/webview_flutter/example/test_driver/webview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,94 @@ void main() {
await resizeCompleter.future;
});

test('set custom userAgent', () async {
final Completer<WebViewController> controllerCompleter1 =
Completer<WebViewController>();
final GlobalKey _globalKey = GlobalKey();
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');
// rebuild the WebView with a different user agent.
await pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: _globalKey,
initialUrl: 'https://flutter.dev/',
javascriptMode: JavascriptMode.unrestricted,
userAgent: 'Custom_User_Agent2',
),
),
);

final String customUserAgent2 = await controller1.getUserAgent();
expect(customUserAgent2, 'Custom_User_Agent2');
});

test('use default platform userAgent after webView is rebuilt', () async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
final GlobalKey _globalKey = GlobalKey();
// Build the webView with no user agent to get the default platform user agent.
await pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: _globalKey,
initialUrl: 'https://flutter.dev/',
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController controller) {
controllerCompleter.complete(controller);
},
),
),
);
final WebViewController controller = await controllerCompleter.future;
final String defaultPlatformUserAgent = await controller.getUserAgent();
// rebuild the WebView with a custom user agent.
await pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: _globalKey,
initialUrl: 'https://flutter.dev/',
javascriptMode: JavascriptMode.unrestricted,
userAgent: 'Custom_User_Agent',
),
),
);
final String customUserAgent = await controller.getUserAgent();
expect(customUserAgent, 'Custom_User_Agent');
// rebuilds the WebView with no user agent.
await pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: WebView(
key: _globalKey,
initialUrl: 'https://flutter.dev/',
javascriptMode: JavascriptMode.unrestricted,
),
),
);

final String customUserAgent2 = await controller.getUserAgent();
expect(customUserAgent2, defaultPlatformUserAgent);
});

group('Media playback policy', () {
String audioTestBase64;
setUpAll(() async {
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 @@ -118,6 +118,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 @@ -250,6 +252,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 @@ -349,4 +354,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
78 changes: 75 additions & 3 deletions packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,74 @@ abstract class WebViewPlatformController {
throw UnimplementedError(
"WebView removeJavascriptChannels is not implemented on the current platform");
}

/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
Future<String> getUserAgent() {
throw UnimplementedError(
"WebView getUserAgent is not implemented on the current platform");
}
}

/// A single setting for configuring a WebViewPlatform which may be absent.
class WebSetting<T> {
/// Constructs an absent setting instance.
///
/// The [isPresent] field for the instance will be false.
///
/// Accessing [value] for an absent instance will throw.
WebSetting.absent()
: _value = null,
isPresent = false;

/// Constructs a setting of the given `value`.
///
/// The [isPresent] field for the instance will be true.
WebSetting.of(T value)
: _value = value,
isPresent = true;

final T _value;

/// The setting's value.
///
/// Throws if [WebSetting.isPresent] is false.
T get value {
if (!isPresent) {
throw StateError('Cannot access a value of an absent WebSetting');
}
assert(isPresent);
return _value;
}

/// True when this web setting instance contains a value.
///
/// When false the [WebSetting.value] getter throws.
final bool isPresent;

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
final WebSetting<T> typedOther = other;
return typedOther.isPresent == isPresent && typedOther._value == _value;
}

@override
int get hashCode => hashValues(_value, isPresent);
}

/// Settings for configuring a WebViewPlatform.
///
/// Initial settings are passed as part of [CreationParams], settings updates are sent with
/// [WebViewPlatform#updateSettings].
///
/// The `userAgent` parameter must not be null.
class WebSettings {
WebSettings({
this.javascriptMode,
this.hasNavigationDelegate,
this.debuggingEnabled,
});
@required this.userAgent,
}) : assert(userAgent != null);

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

/// The value used for the HTTP `User-Agent:` request header.
///
/// If [userAgent.value] is null the platform's default user agent should be used.
///
/// An absent value ([userAgent.isPresent] is false) represents no change to this setting from the
/// last time it was set.
///
/// See also [WebView.userAgent].
final WebSetting<String> userAgent;

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

Expand All @@ -190,6 +256,7 @@ class CreationParams {
this.initialUrl,
this.webSettings,
this.javascriptChannelNames,
this.userAgent,
this.autoMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
}) : assert(autoMediaPlaybackPolicy != null);
Expand Down Expand Up @@ -217,12 +284,17 @@ class CreationParams {
// to PlatformWebView.
final Set<String> javascriptChannelNames;

/// The value used for the HTTP User-Agent: request header.
///
/// When null the platform's webview default is used for the User-Agent header.
final String userAgent;

/// Which restrictions apply on automatic media playback.
final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy;

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

Expand Down
14 changes: 14 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 implementation for [WebViewPlatform.clearCookies].
static Future<bool> clearCookies() {
return _cookieManagerChannel
Expand All @@ -119,9 +124,17 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
map[key] = value;
}

void _addSettingIfPresent<T>(String key, WebSetting<T> setting) {
if (!setting.isPresent) {
return;
}
map[key] = setting.value;
}

_addIfNonNull('jsMode', settings.javascriptMode?.index);
_addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate);
_addIfNonNull('debuggingEnabled', settings.debuggingEnabled);
_addSettingIfPresent('userAgent', settings.userAgent);
return map;
}

Expand All @@ -135,6 +148,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
'initialUrl': creationParams.initialUrl,
'settings': _webSettingsToMap(creationParams.webSettings),
'javascriptChannelNames': creationParams.javascriptChannelNames.toList(),
'userAgent': creationParams.userAgent,
'autoMediaPlaybackPolicy': creationParams.autoMediaPlaybackPolicy.index,
};
}
Expand Down
Loading