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 all 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
4 changes: 4 additions & 0 deletions packages/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.13

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

## 0.3.12+1

* Temporarily revert getTitle (doing this as a patch bump shortly after publishing).
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 @@ -241,6 +245,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 +281,10 @@ private void registerJavaScriptChannelNames(List<String> channelNames) {
}
}

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

@Override
public void dispose() {
methodChannel.setMethodCallHandler(null);
Expand Down
97 changes: 97 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 _getUserAgent(controller1);
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 _getUserAgent(controller1);
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 _getUserAgent(controller);
// 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 _getUserAgent(controller);
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 _getUserAgent(controller);
expect(customUserAgent2, defaultPlatformUserAgent);
});

group('Media playback policy', () {
String audioTestBase64;
setUpAll(() async {
Expand Down Expand Up @@ -384,3 +472,12 @@ String _webviewBool(bool value) {
}
return value ? 'true' : 'false';
}

/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
Future<String> _getUserAgent(WebViewController controller) async {
if (defaultTargetPlatform == TargetPlatform.iOS) {
return await controller.evaluateJavascript('navigator.userAgent;');
}
return jsonDecode(
await controller.evaluateJavascript('navigator.userAgent;'));
}
11 changes: 11 additions & 0 deletions packages/webview_flutter/ios/Classes/FlutterWebView.m
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,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 @@ -347,4 +350,12 @@ - (void)registerJavaScriptChannels:(NSSet*)channelNames
}
}

- (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
72 changes: 69 additions & 3 deletions packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,66 @@ abstract class WebViewPlatformController {
}
}

/// 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 +226,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 +250,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 +278,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
9 changes: 9 additions & 0 deletions packages/webview_flutter/lib/src/webview_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,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 +143,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
25 changes: 25 additions & 0 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class WebView extends StatefulWidget {
this.gestureRecognizers,
this.onPageFinished,
this.debuggingEnabled = false,
this.userAgent,
this.initialMediaPlaybackPolicy =
AutoMediaPlaybackPolicy.require_user_action_for_all_media_types,
}) : assert(javascriptMode != null),
Expand Down Expand Up @@ -277,6 +278,20 @@ class WebView extends StatefulWidget {
/// By default `debuggingEnabled` is false.
final bool debuggingEnabled;

/// The value used for the HTTP User-Agent: request header.
///
/// When null the platform's webview default is used for the User-Agent header.
///
/// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent.
///
/// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded.
///
/// This field is ignored on iOS versions prior to 9 as the platform does not support a custom
/// user agent.
///
/// By default `userAgent` is null.
final String userAgent;

/// Which restrictions apply on automatic media playback.
///
/// This initial value is applied to the platform's webview upon creation. Any following
Expand Down Expand Up @@ -347,6 +362,7 @@ CreationParams _creationParamsfromWidget(WebView widget) {
initialUrl: widget.initialUrl,
webSettings: _webSettingsFromWidget(widget),
javascriptChannelNames: _extractChannelNames(widget.javascriptChannels),
userAgent: widget.userAgent,
autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy,
);
}
Expand All @@ -356,6 +372,7 @@ WebSettings _webSettingsFromWidget(WebView widget) {
javascriptMode: widget.javascriptMode,
hasNavigationDelegate: widget.navigationDelegate != null,
debuggingEnabled: widget.debuggingEnabled,
userAgent: WebSetting<String>.of(widget.userAgent),
);
}

Expand All @@ -365,12 +382,16 @@ WebSettings _clearUnchangedWebSettings(
assert(currentValue.javascriptMode != null);
assert(currentValue.hasNavigationDelegate != null);
assert(currentValue.debuggingEnabled != null);
assert(currentValue.userAgent.isPresent);
assert(newValue.javascriptMode != null);
assert(newValue.hasNavigationDelegate != null);
assert(newValue.debuggingEnabled != null);
assert(newValue.userAgent.isPresent);

JavascriptMode javascriptMode;
bool hasNavigationDelegate;
bool debuggingEnabled;
WebSetting<String> userAgent = WebSetting<String>.absent();
if (currentValue.javascriptMode != newValue.javascriptMode) {
javascriptMode = newValue.javascriptMode;
}
Expand All @@ -380,11 +401,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,
userAgent: userAgent,
);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
version: 0.3.12+1
version: 0.3.13
author: Flutter Team <flutter-dev@googlegroups.com>
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter

Expand Down
Loading