From 1bbac03dc9fa156058c71e9171890672985e31c4 Mon Sep 17 00:00:00 2001 From: Philip Peterson Date: Sun, 25 Oct 2020 20:31:56 -0700 Subject: [PATCH 1/3] Support for WebView loading data directly via loadData and loadDataBase64. Android fallback for the latter using data URI. --- .../webviewflutter/FlutterWebView.java | 32 +++++++ .../ios/Classes/FlutterWebView.m | 94 +++++++++++++++++++ .../lib/platform_interface.dart | 35 +++++++ .../lib/src/webview_method_channel.dart | 29 ++++++ .../webview_flutter/lib/webview_flutter.dart | 39 ++++++++ 5 files changed, 229 insertions(+) diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java index bfb79a39e8ba..44c7b9b45b17 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java @@ -28,6 +28,7 @@ import java.util.Map; public class FlutterWebView implements PlatformView, MethodCallHandler { + private static final String TAG = "FlutterWebView"; private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames"; private final InputAwareWebView webView; private final MethodChannel methodChannel; @@ -174,6 +175,12 @@ public void onMethodCall(MethodCall methodCall, Result result) { case "loadUrl": loadUrl(methodCall, result); break; + case "loadData": + loadData(methodCall, result); + break; + case "loadDataBase64": + loadDataBase64(methodCall, result); + break; case "updateSettings": updateSettings(methodCall, result); break; @@ -239,6 +246,31 @@ private void loadUrl(MethodCall methodCall, Result result) { result.success(null); } + @SuppressWarnings("unchecked") + private void loadData(MethodCall methodCall, Result result) { + Map request = (Map) methodCall.arguments; + String baseUrl = (String) request.get("baseUrl"); + String data = (String) request.get("data"); + String mimeType = (String) request.get("mimeType"); + + final String encoding = "UTF-8"; + webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, null); + result.success(null); + } + + @SuppressWarnings("unchecked") + private void loadDataBase64(MethodCall methodCall, Result result) { + Map request = (Map) methodCall.arguments; + String data = (String) request.get("data"); + String mimeType = (String) request.get("mimeType"); + + // Theoretically, we should be able to use WebView#loadDataWithBaseURL here, but + // support for base64 seems to be broken. + String url = "data:" + mimeType + ";base64," + data; + webView.loadUrl(url); + result.success(null); + } + private void canGoBack(Result result) { result.success(webView.canGoBack()); } diff --git a/packages/webview_flutter/ios/Classes/FlutterWebView.m b/packages/webview_flutter/ios/Classes/FlutterWebView.m index 969e010913f3..e0530288c4d8 100644 --- a/packages/webview_flutter/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/ios/Classes/FlutterWebView.m @@ -128,6 +128,10 @@ - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [self onUpdateSettings:call result:result]; } else if ([[call method] isEqualToString:@"loadUrl"]) { [self onLoadUrl:call result:result]; + } else if ([[call method] isEqualToString:@"loadData"]) { + [self onLoadData:call result:result]; + } else if ([[call method] isEqualToString:@"loadDataBase64"]) { + [self onLoadDataBase64:call result:result]; } else if ([[call method] isEqualToString:@"canGoBack"]) { [self onCanGoBack:call result:result]; } else if ([[call method] isEqualToString:@"canGoForward"]) { @@ -183,6 +187,96 @@ - (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result { } } +- (void)onLoadData:(FlutterMethodCall*)call result:(FlutterResult)result { + NSDictionary* arguments = [call arguments]; + if (!arguments) { + result([FlutterError errorWithCode:@"loadData_failed" + message:@"No arguments passed to method" + details:nil]); + return; + } + + NSString* const encoding = @"UTF-8"; + NSString* mimeType = arguments[@"mimeType"]; + NSString* baseUrl = arguments[@"baseUrl"]; + NSString* dataStr = arguments[@"data"]; + + if (![mimeType isKindOfClass:[NSString class]]) { + result([FlutterError errorWithCode:@"loadData_failed" + message:@"Argument type of mimeType is not string" + details:nil]); + return; + } + + if (![baseUrl isKindOfClass:[NSString class]]) { + result([FlutterError errorWithCode:@"loadData_failed" + message:@"Argument type of baseUrl is not string" + details:nil]); + return; + } + + NSURL* nsUrl = [NSURL URLWithString:baseUrl]; + if (!nsUrl) { + result([FlutterError errorWithCode:@"loadData_failed" + message:@"Argument type of baseUrl is not a valid URL" + details:[NSString stringWithFormat:@"URL was: '%@'", baseUrl]]); + return; + } + + if (![dataStr isKindOfClass:[NSString class]]) { + result([FlutterError errorWithCode:@"loadData_failed" + message:@"Argument type of data is not string" + details:nil]); + return; + } + + NSData* data = [dataStr dataUsingEncoding:NSUTF8StringEncoding]; + if (@available(iOS 9.0, *)) { + [_webView loadData:data MIMEType:mimeType characterEncodingName:encoding baseURL:nsUrl]; + } else { + NSLog(@"Setting data is not supported for Flutter WebViews prior to iOS 9."); + } + + result(nil); +} + +- (void)onLoadDataBase64:(FlutterMethodCall*)call result:(FlutterResult)result { + NSDictionary* arguments = [call arguments]; + if (!arguments) { + result([FlutterError errorWithCode:@"loadDataBase64_failed" + message:@"No arguments passed to method" + details:nil]); + return; + } + + NSString* const encoding = @"UTF-8"; + NSString* mimeType = arguments[@"mimeType"]; + NSString* dataStr = arguments[@"data"]; + + if (![mimeType isKindOfClass:[NSString class]]) { + result([FlutterError errorWithCode:@"loadDataBase64_failed" + message:@"Argument type of mimeType is not string" + details:nil]); + return; + } + + if (![dataStr isKindOfClass:[NSString class]]) { + result([FlutterError errorWithCode:@"loadDataBase64_failed" + message:@"Argument type of data is not string" + details:nil]); + return; + } + + NSData* data = [[NSData alloc] initWithBase64EncodedString:dataStr options:0]; + if (@available(iOS 9.0, *)) { + [_webView loadData:data MIMEType:mimeType characterEncodingName:encoding baseURL:nil]; + } else { + NSLog(@"Setting data is not supported for Flutter WebViews prior to iOS 9."); + } + + result(nil); +} + - (void)onCanGoBack:(FlutterMethodCall*)call result:(FlutterResult)result { BOOL canGoBack = [_webView canGoBack]; result([NSNumber numberWithBool:canGoBack]); diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 6c991b14a76e..f864666bf6ba 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -182,6 +182,41 @@ abstract class WebViewPlatformController { "WebView loadUrl is not implemented on the current platform"); } + /// Loads the specified content data into the WebView. + /// + /// The string `data` will be interpreted as a UTF-8 string. + /// + /// `baseUrl` is the apparent URL which the page was loaded at, used to resolve + /// relative paths. + /// + /// Throws an ArgumentError if `baseUrl` is not a valid URL string. + Future loadData( + String data, + String baseUrl, + String mimeType, + ) { + throw UnimplementedError( + "WebView loadData is not implemented on the current platform"); + } + + /// Loads the specified content data into the WebView. + /// + /// The string `data` will be interpreted as a Base64 encoded string. Note + /// that any trailing padding = or == characters MUST be present for full device + /// compatibility. + /// + /// This function has no `baseUrl` argument because of a limitation of the Android + /// WebView API. + /// + /// Throws an ArgumentError if `baseUrl` is not a valid URL string. + Future loadDataBase64( + String data, + String mimeType, + ) { + throw UnimplementedError( + "WebView loadDataBase64 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. diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart index 348b225bb257..72f1dab7b622 100644 --- a/packages/webview_flutter/lib/src/webview_method_channel.dart +++ b/packages/webview_flutter/lib/src/webview_method_channel.dart @@ -80,6 +80,35 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { }); } + @override + Future loadData( + String data, + String baseUrl, + String mimeType, + ) async { + assert(data != null); + assert(baseUrl != null); + assert(mimeType != null); + return _channel.invokeMethod('loadData', { + 'data': data, + 'baseUrl': baseUrl, + 'mimeType': mimeType, + }); + } + + @override + Future loadDataBase64( + String data, + String mimeType, + ) async { + assert(data != null); + assert(mimeType != null); + return _channel.invokeMethod('loadDataBase64', { + 'data': data, + 'mimeType': mimeType, + }); + } + @override Future currentUrl() => _channel.invokeMethod('currentUrl'); diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index 5e2bffd6539d..6c5608252e4f 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -609,6 +609,45 @@ class WebViewController { return _webViewPlatformController.loadUrl(url, headers); } + /// Loads the specified content data into the WebView. + /// + /// The string `data` will be interpreted as a UTF-8 string. + /// + /// `baseUrl` is the apparent URL which the page was loaded at, used to resolve + /// relative paths. + /// + /// Throws an ArgumentError if `baseUrl` is not a valid URL string. + Future loadData( + String data, { + @required String baseUrl, + @required String mimeType, + }) async { + assert(data != null); + assert(baseUrl != null); + assert(mimeType != null); + _validateUrlString(baseUrl); + return _webViewPlatformController.loadData(data, baseUrl, mimeType); + } + + /// Loads the specified content data into the WebView. + /// + /// The string `data` will be interpreted as a Base64 encoded string. Note + /// that any trailing padding = or == characters MUST be present for full device + /// compatibility. + /// + /// This function has no `baseUrl` argument because of a limitation of the Android + /// WebView API. + /// + /// Throws an ArgumentError if `baseUrl` is not a valid URL string. + Future loadDataBase64( + String data, { + @required String mimeType, + }) async { + assert(data != null); + assert(mimeType != null); + return _webViewPlatformController.loadDataBase64(data, mimeType); + } + /// Accessor to the current URL that the WebView is displaying. /// /// If [WebView.initialUrl] was never specified, returns `null`. From 694d2d7ff6db1a85ad6e9a74e821a0cdd30a59db Mon Sep 17 00:00:00 2001 From: Philip Peterson Date: Tue, 27 Oct 2020 21:13:40 -0700 Subject: [PATCH 2/3] Fix minor issues --- .../java/io/flutter/plugins/webviewflutter/FlutterWebView.java | 3 +-- packages/webview_flutter/lib/platform_interface.dart | 2 +- packages/webview_flutter/lib/webview_flutter.dart | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java index 44c7b9b45b17..159f64d0ee41 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java @@ -28,7 +28,6 @@ import java.util.Map; public class FlutterWebView implements PlatformView, MethodCallHandler { - private static final String TAG = "FlutterWebView"; private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames"; private final InputAwareWebView webView; private final MethodChannel methodChannel; @@ -266,7 +265,7 @@ private void loadDataBase64(MethodCall methodCall, Result result) { // Theoretically, we should be able to use WebView#loadDataWithBaseURL here, but // support for base64 seems to be broken. - String url = "data:" + mimeType + ";base64," + data; + final String url = String.format("data:%s;base64,%s", mimeType, data); webView.loadUrl(url); result.success(null); } diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index f864666bf6ba..1a16b543d063 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -205,7 +205,7 @@ abstract class WebViewPlatformController { /// that any trailing padding = or == characters MUST be present for full device /// compatibility. /// - /// This function has no `baseUrl` argument because of a limitation of the Android + /// This method has no `baseUrl` argument because of a limitation of the Android /// WebView API. /// /// Throws an ArgumentError if `baseUrl` is not a valid URL string. diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index 6c5608252e4f..d69e5e20e65d 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -635,7 +635,7 @@ class WebViewController { /// that any trailing padding = or == characters MUST be present for full device /// compatibility. /// - /// This function has no `baseUrl` argument because of a limitation of the Android + /// This method has no `baseUrl` argument because of a limitation of the Android /// WebView API. /// /// Throws an ArgumentError if `baseUrl` is not a valid URL string. From 4e0135dcc86a4404fe1e10852a8f385c9920184c Mon Sep 17 00:00:00 2001 From: Philip Peterson Date: Sat, 7 Nov 2020 20:33:02 -0800 Subject: [PATCH 3/3] Make baseURL required --- .../plugins/webviewflutter/FlutterWebView.java | 2 ++ .../ios/Classes/FlutterWebView.m | 18 +++++++++++++++++- .../lib/platform_interface.dart | 4 ++-- .../lib/src/webview_method_channel.dart | 3 +++ .../webview_flutter/lib/webview_flutter.dart | 6 +++--- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java index 159f64d0ee41..db65a8116b44 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java @@ -262,6 +262,8 @@ private void loadDataBase64(MethodCall methodCall, Result result) { Map request = (Map) methodCall.arguments; String data = (String) request.get("data"); String mimeType = (String) request.get("mimeType"); + + // We do not use baseUrl, because Android has no way to set it in this context. // Theoretically, we should be able to use WebView#loadDataWithBaseURL here, but // support for base64 seems to be broken. diff --git a/packages/webview_flutter/ios/Classes/FlutterWebView.m b/packages/webview_flutter/ios/Classes/FlutterWebView.m index e0530288c4d8..17cd74b628d2 100644 --- a/packages/webview_flutter/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/ios/Classes/FlutterWebView.m @@ -252,6 +252,7 @@ - (void)onLoadDataBase64:(FlutterMethodCall*)call result:(FlutterResult)result { NSString* const encoding = @"UTF-8"; NSString* mimeType = arguments[@"mimeType"]; NSString* dataStr = arguments[@"data"]; + NSString* baseUrl = arguments[@"baseUrl"]; if (![mimeType isKindOfClass:[NSString class]]) { result([FlutterError errorWithCode:@"loadDataBase64_failed" @@ -267,9 +268,24 @@ - (void)onLoadDataBase64:(FlutterMethodCall*)call result:(FlutterResult)result { return; } + if (![baseUrl isKindOfClass:[NSString class]]) { + result([FlutterError errorWithCode:@"loadDataBase64_failed" + message:@"Argument type of baseUrl is not string" + details:nil]); + return; + } + + NSURL* nsUrl = [NSURL URLWithString:baseUrl]; + if (!nsUrl) { + result([FlutterError errorWithCode:@"loadData_failed" + message:@"Argument type of baseUrl is not a valid URL" + details:[NSString stringWithFormat:@"URL was: '%@'", baseUrl]]); + return; + } + NSData* data = [[NSData alloc] initWithBase64EncodedString:dataStr options:0]; if (@available(iOS 9.0, *)) { - [_webView loadData:data MIMEType:mimeType characterEncodingName:encoding baseURL:nil]; + [_webView loadData:data MIMEType:mimeType characterEncodingName:encoding baseURL:nsUrl]; } else { NSLog(@"Setting data is not supported for Flutter WebViews prior to iOS 9."); } diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 1a16b543d063..53193ad10a6f 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -205,13 +205,13 @@ abstract class WebViewPlatformController { /// that any trailing padding = or == characters MUST be present for full device /// compatibility. /// - /// This method has no `baseUrl` argument because of a limitation of the Android - /// WebView API. + /// The `baseUrl` argument is required but has no effect on Android. /// /// Throws an ArgumentError if `baseUrl` is not a valid URL string. Future loadDataBase64( String data, String mimeType, + String baseUrl, ) { throw UnimplementedError( "WebView loadDataBase64 is not implemented on the current platform"); diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart index 72f1dab7b622..776d78433f9b 100644 --- a/packages/webview_flutter/lib/src/webview_method_channel.dart +++ b/packages/webview_flutter/lib/src/webview_method_channel.dart @@ -100,12 +100,15 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { Future loadDataBase64( String data, String mimeType, + String baseUrl, ) async { assert(data != null); assert(mimeType != null); + assert(baseUrl != null); return _channel.invokeMethod('loadDataBase64', { 'data': data, 'mimeType': mimeType, + 'baseUrl': baseUrl, }); } diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index d69e5e20e65d..f1782643cb0f 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -635,17 +635,17 @@ class WebViewController { /// that any trailing padding = or == characters MUST be present for full device /// compatibility. /// - /// This method has no `baseUrl` argument because of a limitation of the Android - /// WebView API. + /// The `baseUrl` argument is required but has no effect on Android. /// /// Throws an ArgumentError if `baseUrl` is not a valid URL string. Future loadDataBase64( String data, { @required String mimeType, + @required String baseUrl, }) async { assert(data != null); assert(mimeType != null); - return _webViewPlatformController.loadDataBase64(data, mimeType); + return _webViewPlatformController.loadDataBase64(data, mimeType, baseUrl); } /// Accessor to the current URL that the WebView is displaying.