-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[webview_flutter] platform_interface: Use Dart objects for creationParams and webSettings. #1624
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,8 @@ import 'package:flutter/foundation.dart'; | |
| import 'package:flutter/gestures.dart'; | ||
| import 'package:flutter/widgets.dart'; | ||
|
|
||
| import 'webview_flutter.dart'; | ||
|
|
||
| /// Interface for talking to the webview's platform implementation. | ||
| /// | ||
| /// An instance implementing this interface is passed to the `onWebViewPlatformCreated` callback that is | ||
|
|
@@ -29,6 +31,15 @@ abstract class WebViewPlatform { | |
| "WebView loadUrl is not implemented on the current platform"); | ||
| } | ||
|
|
||
| /// Updates the webview settings. | ||
| /// | ||
| /// Any non null field in `settings` will be set as the new setting value. | ||
| /// All null fields in `settings` are ignored. | ||
| Future<void> updateSettings(WebSettings setting) { | ||
| throw UnimplementedError( | ||
| "WebView updateSettings is not implemented on the current platform"); | ||
| } | ||
|
|
||
| // As the PR currently focus about the wiring I've only moved loadUrl to the new way, so | ||
| // the discussion is more focused. | ||
| // In this temporary state WebViewController still uses a method channel directly for all other | ||
|
|
@@ -37,6 +48,68 @@ abstract class WebViewPlatform { | |
| int get id; | ||
| } | ||
|
|
||
| /// Settings for configuring a WebViewPlatform. | ||
| /// | ||
| /// Initial settings are passed as part of [CreationParams], settings updates are sent with | ||
| /// [WebViewPlatform#updateSettings]. | ||
| class WebSettings { | ||
| WebSettings({ | ||
| this.javascriptMode, | ||
| this.hasNavigationDelegate, | ||
| this.debuggingEnabled, | ||
| }); | ||
|
|
||
| /// The JavaScript execution mode to be used by the webview. | ||
| final JavascriptMode javascriptMode; | ||
|
|
||
| /// Whether the [WebView] has a [NavigationDelegate] set. | ||
| final bool hasNavigationDelegate; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit(personal opinion): useNavigationDelegate might be a better name in this case. |
||
|
|
||
| /// Whether to enable the platform's webview content debugging tools. | ||
| /// | ||
| /// See also: [WebView.debuggingEnabled]. | ||
| final bool debuggingEnabled; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: isDebuggingEnabled
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it makes sense to keep it consistent with the Note that as far as I know the Flutter style guide doesn't prefer an "is" prefix for flags. |
||
|
|
||
| @override | ||
| String toString() { | ||
| return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, debuggingEnabled: $debuggingEnabled)'; | ||
| } | ||
| } | ||
|
|
||
| /// Configuration to use when creating a new [WebViewPlatform]. | ||
| class CreationParams { | ||
| CreationParams( | ||
| {this.initialUrl, this.webSettings, this.javascriptChannelNames}); | ||
|
|
||
| /// The initialUrl to load in the webview. | ||
| /// | ||
| /// When null the webview will be created without loading any page. | ||
| final String initialUrl; | ||
|
|
||
| /// The initial [WebSettings] for the new webview. | ||
| /// | ||
| /// This can later be updated with [WebViewPlatform.updateSettings]. | ||
| final WebSettings webSettings; | ||
|
|
||
| /// The initial set of JavaScript channels that are configured for this webview. | ||
| /// | ||
| /// For each value in this set the platform's webview should make sure that a corresponding | ||
| /// property with a postMessage method is set on `window`. For example for a JavaScript channel | ||
| /// named `Foo` it should be possible for JavaScript code executing in the webview to do | ||
| /// | ||
| /// ```javascript | ||
| /// Foo.postMessage('hello'); | ||
| /// ``` | ||
| // TODO(amirh): describe what should happen when postMessage is called once that code is migrated | ||
| // to PlatformWebView. | ||
| final Set<String> javascriptChannelNames; | ||
|
|
||
| @override | ||
| String toString() { | ||
| return '$runtimeType(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames)'; | ||
| } | ||
| } | ||
|
|
||
| typedef WebViewPlatformCreatedCallback = void Function( | ||
| WebViewPlatform webViewPlatform); | ||
|
|
||
|
|
@@ -67,7 +140,7 @@ abstract class WebViewBuilder { | |
| // TODO(amirh): convert this to be the actual parameters. | ||
| // I'm starting without it as the PR is starting to become pretty big. | ||
| // I'll followup with the conversion PR. | ||
| Map<String, dynamic> creationParams, | ||
| CreationParams creationParams, | ||
| WebViewPlatformCreatedCallback onWebViewPlatformCreated, | ||
| Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,46 @@ class MethodChannelWebViewPlatform implements WebViewPlatform { | |
| }); | ||
| } | ||
|
|
||
| @override | ||
| Future<void> updateSettings(WebSettings settings) { | ||
| // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. | ||
| // https://github.com/flutter/flutter/issues/26431 | ||
| // ignore: strong_mode_implicit_dynamic_method | ||
| final Map<String, dynamic> updatesMap = _webSettingsToMap(settings); | ||
| if (updatesMap.isEmpty) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another option could be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's actually legit to send a We could potentially move the short-circuting logic to webview_flutter.dart, but that requires some ugliness or maintaining an |
||
| return null; | ||
| } | ||
| return _channel.invokeMethod('updateSettings', updatesMap); | ||
| } | ||
|
|
||
| static Map<String, dynamic> _webSettingsToMap(WebSettings settings) { | ||
| final Map<String, dynamic> map = <String, dynamic>{}; | ||
| void _addIfNonNull(String key, dynamic value) { | ||
| if (value == null) { | ||
| return; | ||
| } | ||
| map[key] = value; | ||
| } | ||
|
|
||
| _addIfNonNull('jsMode', settings.javascriptMode?.index); | ||
| _addIfNonNull('hasNavigationDelegate', settings.hasNavigationDelegate); | ||
| _addIfNonNull('debuggingEnabled', settings.debuggingEnabled); | ||
| return map; | ||
| } | ||
|
|
||
| /// Converts a [CreationParams] object to a map as expected by `platform_views` channel. | ||
| /// | ||
| /// This is used for the `creationParams` argument of the platform views created by | ||
| /// [AndroidWebViewBuilder] and [CupertinoWebViewBuilder]. | ||
| static Map<String, dynamic> creationParamsToMap( | ||
| CreationParams creationParams) { | ||
| return <String, dynamic>{ | ||
| 'initialUrl': creationParams.initialUrl, | ||
| 'settings': _webSettingsToMap(creationParams.webSettings), | ||
| 'javascriptChannelNames': creationParams.javascriptChannelNames.toList(), | ||
| }; | ||
| } | ||
|
|
||
| @override | ||
| int get id => _id; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -268,9 +268,9 @@ class _WebViewState extends State<WebView> { | |
| Widget build(BuildContext context) { | ||
| return WebView.platformBuilder.build( | ||
| context: context, | ||
| creationParams: _CreationParams.fromWidget(widget).toMap(), | ||
| onWebViewPlatformCreated: _onWebViewPlatformCreated, | ||
| gestureRecognizers: widget.gestureRecognizers, | ||
| creationParams: _creationParamsfromWidget(widget), | ||
| ); | ||
| } | ||
|
|
||
|
|
@@ -307,85 +307,57 @@ class _WebViewState extends State<WebView> { | |
| } | ||
| } | ||
|
|
||
| Set<String> _extractChannelNames(Set<JavascriptChannel> channels) { | ||
| final Set<String> channelNames = channels == null | ||
| // TODO(iskakaushik): Remove this when collection literals makes it to stable. | ||
| // ignore: prefer_collection_literals | ||
| ? Set<String>() | ||
| : channels.map((JavascriptChannel channel) => channel.name).toSet(); | ||
| return channelNames; | ||
| CreationParams _creationParamsfromWidget(WebView widget) { | ||
| return CreationParams( | ||
| initialUrl: widget.initialUrl, | ||
| webSettings: _webSettingsFromWidget(widget), | ||
| javascriptChannelNames: _extractChannelNames(widget.javascriptChannels), | ||
| ); | ||
| } | ||
|
|
||
| class _CreationParams { | ||
| _CreationParams( | ||
| {this.initialUrl, this.settings, this.javascriptChannelNames}); | ||
|
|
||
| static _CreationParams fromWidget(WebView widget) { | ||
| return _CreationParams( | ||
| initialUrl: widget.initialUrl, | ||
| settings: _WebSettings.fromWidget(widget), | ||
| javascriptChannelNames: | ||
| _extractChannelNames(widget.javascriptChannels).toList(), | ||
| ); | ||
| } | ||
|
|
||
| final String initialUrl; | ||
|
|
||
| final _WebSettings settings; | ||
|
|
||
| final List<String> javascriptChannelNames; | ||
|
|
||
| Map<String, dynamic> toMap() { | ||
| return <String, dynamic>{ | ||
| 'initialUrl': initialUrl, | ||
| 'settings': settings.toMap(), | ||
| 'javascriptChannelNames': javascriptChannelNames, | ||
| }; | ||
| } | ||
| WebSettings _webSettingsFromWidget(WebView widget) { | ||
| return WebSettings( | ||
| javascriptMode: widget.javascriptMode, | ||
| hasNavigationDelegate: widget.navigationDelegate != null, | ||
| debuggingEnabled: widget.debuggingEnabled, | ||
| ); | ||
| } | ||
|
|
||
| class _WebSettings { | ||
| _WebSettings({ | ||
| this.javascriptMode, | ||
| this.hasNavigationDelegate, | ||
| this.debuggingEnabled, | ||
| }); | ||
|
|
||
| static _WebSettings fromWidget(WebView widget) { | ||
| return _WebSettings( | ||
| javascriptMode: widget.javascriptMode, | ||
| hasNavigationDelegate: widget.navigationDelegate != null, | ||
| debuggingEnabled: widget.debuggingEnabled, | ||
| ); | ||
| // This method assumes that no fields in `currentValue` are null. | ||
| WebSettings _clearUnchangedWebSettings( | ||
| WebSettings currentValue, WebSettings newValue) { | ||
| assert(currentValue.javascriptMode != null); | ||
| assert(currentValue.hasNavigationDelegate != null); | ||
| assert(currentValue.debuggingEnabled != null); | ||
| assert(newValue.javascriptMode != null); | ||
| assert(newValue.hasNavigationDelegate != null); | ||
| assert(newValue.debuggingEnabled != null); | ||
| JavascriptMode javascriptMode; | ||
| bool hasNavigationDelegate; | ||
| bool debuggingEnabled; | ||
| if (currentValue.javascriptMode != newValue.javascriptMode) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if the newValue.javascriptMode is null and the currentValue.javascriptMode is non-null, we are setting the old value to null. Same as below. It seems to violate the comment here: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fields in I made it clear in the method comment and added assertions.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking when the fields in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After offline discussion with @amirh; we confirmed that the fields in both currentValue and newValue could never be null because they are both created by _webSettingsFromWidget which takes the setting from the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added more assertions here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I'm a bit confused about this entire method, what it's purpose? It looks to me that the return type will almost always be a copy of newValue unless the fields of each equal, in which case it will return a new WebSettings object with some null fields - is that really what you want?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the platform side, at least for the platforms we've been dealing with so far, all these "settings" are updated by imperative calls, e.g on Android we call setJavaScriptEnabled to update the "javascriptMode". The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, so you're nullfying all the unchanged fields on purpose because
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to |
||
| javascriptMode = newValue.javascriptMode; | ||
| } | ||
|
|
||
| final JavascriptMode javascriptMode; | ||
| final bool hasNavigationDelegate; | ||
| final bool debuggingEnabled; | ||
|
|
||
| Map<String, dynamic> toMap() { | ||
| return <String, dynamic>{ | ||
| 'jsMode': javascriptMode.index, | ||
| 'hasNavigationDelegate': hasNavigationDelegate, | ||
| 'debuggingEnabled': debuggingEnabled, | ||
| }; | ||
| if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { | ||
| hasNavigationDelegate = newValue.hasNavigationDelegate; | ||
| } | ||
| if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { | ||
| debuggingEnabled = newValue.debuggingEnabled; | ||
| } | ||
|
|
||
| Map<String, dynamic> updatesMap(_WebSettings newSettings) { | ||
| final Map<String, dynamic> updates = <String, dynamic>{}; | ||
| if (javascriptMode != newSettings.javascriptMode) { | ||
| updates['jsMode'] = newSettings.javascriptMode.index; | ||
| } | ||
| if (hasNavigationDelegate != newSettings.hasNavigationDelegate) { | ||
| updates['hasNavigationDelegate'] = newSettings.hasNavigationDelegate; | ||
| } | ||
|
|
||
| if (debuggingEnabled != newSettings.debuggingEnabled) { | ||
| updates['debuggingEnabled'] = newSettings.debuggingEnabled; | ||
| } | ||
| return WebSettings( | ||
| javascriptMode: javascriptMode, | ||
| hasNavigationDelegate: hasNavigationDelegate, | ||
| debuggingEnabled: debuggingEnabled); | ||
| } | ||
|
|
||
| return updates; | ||
| } | ||
| Set<String> _extractChannelNames(Set<JavascriptChannel> channels) { | ||
| final Set<String> channelNames = channels == null | ||
| // TODO(iskakaushik): Remove this when collection literals makes it to stable. | ||
| // ignore: prefer_collection_literals | ||
| ? Set<String>() | ||
| : channels.map((JavascriptChannel channel) => channel.name).toSet(); | ||
| return channelNames; | ||
| } | ||
|
|
||
| /// Controls a [WebView]. | ||
|
|
@@ -398,7 +370,7 @@ class WebViewController { | |
| this._platformInterface, | ||
| this._widget, | ||
| ) : _channel = MethodChannel('plugins.flutter.io/webview_$id') { | ||
| _settings = _WebSettings.fromWidget(_widget); | ||
| _settings = _webSettingsFromWidget(_widget); | ||
| _updateJavascriptChannelsFromSet(_widget.javascriptChannels); | ||
| _channel.setMethodCallHandler(_onMethodCall); | ||
| } | ||
|
|
@@ -407,7 +379,7 @@ class WebViewController { | |
|
|
||
| final WebViewPlatform _platformInterface; | ||
|
|
||
| _WebSettings _settings; | ||
| WebSettings _settings; | ||
|
|
||
| WebView _widget; | ||
|
|
||
|
|
@@ -549,20 +521,15 @@ class WebViewController { | |
|
|
||
| Future<void> _updateWidget(WebView widget) async { | ||
| _widget = widget; | ||
| await _updateSettings(_WebSettings.fromWidget(widget)); | ||
| await _updateSettings(_webSettingsFromWidget(widget)); | ||
| await _updateJavascriptChannels(widget.javascriptChannels); | ||
| } | ||
|
|
||
| Future<void> _updateSettings(_WebSettings setting) async { | ||
| final Map<String, dynamic> updateMap = _settings.updatesMap(setting); | ||
| if (updateMap == null || updateMap.isEmpty) { | ||
| return null; | ||
| } | ||
| _settings = setting; | ||
| // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter. | ||
| // https://github.com/flutter/flutter/issues/26431 | ||
| // ignore: strong_mode_implicit_dynamic_method | ||
| return _channel.invokeMethod('updateSettings', updateMap); | ||
| Future<void> _updateSettings(WebSettings newSettings) { | ||
| final WebSettings update = | ||
| _clearUnchangedWebSettings(_settings, newSettings); | ||
| _settings = newSettings; | ||
| return _platformInterface.updateSettings(update); | ||
| } | ||
|
|
||
| Future<void> _updateJavascriptChannels( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: dart doc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done