From 6bc4fa806c81a0a6f7d1806107bc10d653473201 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 1 Dec 2021 17:20:49 +0100 Subject: [PATCH 01/11] Add android implementations for loadRequest. --- .../GeneratedAndroidWebView.java | 35 ++++++++ .../webviewflutter/WebViewHostApiImpl.java | 6 ++ .../example/lib/main.dart | 20 +++++ .../example/lib/web_view.dart | 5 ++ .../lib/src/android_webview.dart | 10 +++ .../lib/src/android_webview.pigeon.dart | 31 ++++++- .../lib/src/android_webview_api_impls.dart | 11 +++ .../lib/webview_android_widget.dart | 86 ++++++++++++++++++- .../pigeons/android_webview.dart | 6 ++ .../webview_flutter_android/pubspec.yaml | 1 + .../test/android_webview.pigeon.dart | 28 +++++- 11 files changed, 233 insertions(+), 6 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index beb2d7105f22..bb612f05588d 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -185,6 +185,8 @@ void loadDataWithBaseUrl( void loadUrl(Long instanceId, String url, Map headers); + void postUrl(Long instanceId, String url, byte[] data); + String getUrl(Long instanceId); Boolean canGoBack(Long instanceId); @@ -407,6 +409,39 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.postUrl", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + if (instanceIdArg == null) { + throw new NullPointerException("instanceIdArg unexpectedly null."); + } + String urlArg = (String) args.get(1); + if (urlArg == null) { + throw new NullPointerException("urlArg unexpectedly null."); + } + byte[] dataArg = (byte[]) args.get(2); + if (dataArg == null) { + throw new NullPointerException("dataArg unexpectedly null."); + } + api.postUrl(instanceIdArg.longValue(), urlArg, dataArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java index b557e9889704..e6ee7c74d339 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java @@ -382,6 +382,12 @@ public void loadUrl(Long instanceId, String url, Map headers) { webView.loadUrl(url, headers); } + @Override + public void postUrl(Long instanceId, String url, byte[] data) { + final WebView webView = (WebView) instanceManager.getInstance(instanceId); + webView.postUrl(url, data); + } + @Override public String getUrl(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index bc43c16d4498..c95a438c7b99 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; @@ -158,6 +159,7 @@ enum _MenuOptions { navigationDelegate, loadLocalFile, loadHtmlString, + doPostRequest, } class _SampleMenu extends StatelessWidget { @@ -201,6 +203,9 @@ class _SampleMenu extends StatelessWidget { case _MenuOptions.loadHtmlString: _onLoadHtmlStringExample(controller.data!, context); break; + case _MenuOptions.doPostRequest: + _onDoPostRequest(controller.data!, context); + break; } }, itemBuilder: (BuildContext context) => >[ @@ -241,6 +246,10 @@ class _SampleMenu extends StatelessWidget { value: _MenuOptions.loadLocalFile, child: Text('Load local file'), ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.doPostRequest, + child: Text('Post Request'), + ), ], ); }, @@ -330,6 +339,17 @@ class _SampleMenu extends StatelessWidget { await controller.loadHtmlString(kExamplePage); } + Future _onDoPostRequest( + WebViewController controller, BuildContext context) async { + final WebViewRequest request = WebViewRequest( + uri: Uri.parse('https://httpbin.org/post'), + method: WebViewRequestMethod.post, + headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, + body: Uint8List.fromList('Test Body'.codeUnits), + ); + await controller.loadRequest(request); + } + Widget _getCookieList(String cookies) { if (cookies == null || cookies == '""') { return Container(); diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart index 654abbd960cd..295e4de7a312 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/web_view.dart @@ -402,6 +402,11 @@ class WebViewController { return _webViewPlatformController.loadUrl(url, headers); } + /// Loads a page by making the specified request. + Future loadRequest(WebViewRequest request) async { + return _webViewPlatformController.loadRequest(request); + } + /// Accessor to the current URL that the WebView is displaying. /// /// If [WebView.initialUrl] was never specified, returns `null`. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index 928cdb3099f1..6fc175eb25b0 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ffi'; +import 'dart:typed_data'; + import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart' show AndroidViewSurface; @@ -166,6 +169,13 @@ class WebView { return api.loadUrlFromInstance(this, url, headers); } + /// Loads the URL with postData using "POST" method into this WebView. + /// + /// If url is not a network URL, it will be loaded with [loadUrl] instead, ignoring the postData param. + Future postUrl(String url, Uint8List data) { + return api.postUrlFromInstance(this, url, data); + } + /// Gets the URL for the current page. /// /// This is not always the same as the URL passed to diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index 9bf2de6d169b..5e603b58f6af 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart @@ -1,7 +1,3 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name @@ -220,6 +216,33 @@ class WebViewHostApi { } } + Future postUrl( + int arg_instanceId, String arg_url, Uint8List arg_data) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.postUrl', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_instanceId, arg_url, arg_data]) + as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } + Future getUrl(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getUrl', codec, diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart index ece05b56c182..89a245304560 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; + import 'package:flutter/services.dart'; import 'android_webview.dart'; @@ -152,6 +154,15 @@ class WebViewHostApiImpl extends WebViewHostApi { return loadUrl(instanceManager.getInstanceId(instance)!, url, headers); } + /// Helper method to convert instances ids to objects. + Future postUrlFromInstance( + WebView instance, + String url, + Uint8List data, + ) { + return postUrl(instanceManager.getInstanceId(instance)!, url, data); + } + /// Helper method to convert instances ids to objects. Future getUrlFromInstance(WebView instance) { return getUrl(instanceManager.getInstanceId(instance)!); diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index c264bc1b9e6e..afdf9b07c4cd 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -3,9 +3,11 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/widgets.dart'; - +import 'package:http/http.dart' as http; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'src/android_webview.dart' as android_webview; @@ -174,6 +176,46 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { return webView.loadUrl(url, headers ?? {}); } + @override + Future loadRequest( + WebViewRequest request, + ) async { + switch (request.method) { + case WebViewRequestMethod.get: + return loadUrl(request.uri.toString(), request.headers); + case WebViewRequestMethod.post: + // If the request requires no additional headers, postUrl can be used directly. + if (request.headers.isEmpty) { + return webView.postUrl( + request.uri.toString(), request.body ?? Uint8List(0)); + } + // Otherwise, the request has to be made manually. + else { + final _HTTPResponseWithFinalUrl responseWithUrl = + await _postUrlAndFollowRedirects( + request.uri, + headers: request.headers, + body: request.body ?? Uint8List(0), + ); + final http.Response response = responseWithUrl.response; + + final String baseUrl = responseWithUrl.finalUrl; + final String mimeType = + response.headers['content-type'] ?? 'text/html'; + + return webView.loadDataWithBaseUrl( + data: response.body, + baseUrl: baseUrl, + mimeType: mimeType, + ); + } + default: + throw UnimplementedError( + 'This version of webview_android_widget currently has no implementation for HTTP method ${request.method.serialize()} in loadRequest.', + ); + } + } + @override Future currentUrl() => webView.getUrl(); @@ -282,6 +324,41 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { Future _dispose() => webView.release(); + // As the http package does currently not expose the resulting url when + // automatically following redirects. Because of this, redirects have to be + // followed manually so that the final url can be tracked. Once this + // functionality has been implemented in the http package, + // this method should be removed. + // https://github.com/dart-lang/http/issues/556 + // https://github.com/dart-lang/http/issues/293 + Future<_HTTPResponseWithFinalUrl> _postUrlAndFollowRedirects(Uri uri, + {Map? headers, + Uint8List? body, + int redirections = 0}) async { + final http.Request req = http.Request('POST', uri) + ..followRedirects = false + ..bodyBytes = body ?? []; + req.headers.addAll(headers ?? {}); + final http.Client baseClient = http.Client(); + final http.Response response = + await http.Response.fromStream(await baseClient.send(req)); + + // If it's a redirection, follow it + if (response.statusCode >= 300 && + response.statusCode < 400 && + response.headers.containsKey('location')) { + // Maximum of 20 redirections (Default in Chrome & Firefox). + if (redirections >= 20) { + throw const HttpException('Maximum amount of redirections reached.'); + } + final Uri redirectUri = Uri.parse(response.headers['location']!); + return _postUrlAndFollowRedirects(redirectUri, + headers: headers, body: body, redirections: redirections + 1); + } + + return _HTTPResponseWithFinalUrl(response, uri.toString()); + } + void _setCreationParams(CreationParams creationParams) { final WebSettings? webSettings = creationParams.webSettings; if (webSettings != null) { @@ -636,3 +713,10 @@ class WebViewProxy { return android_webview.WebView.setWebContentsDebuggingEnabled(true); } } + +class _HTTPResponseWithFinalUrl { + _HTTPResponseWithFinalUrl(this.response, this.finalUrl); + + final http.Response response; + final String finalUrl; +} diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index 963216329237..1b9f9f47b5e8 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -46,6 +46,12 @@ abstract class WebViewHostApi { Map headers, ); + void postUrl( + int instanceId, + String url, + Uint8List data, + ); + String getUrl(int instanceId); bool canGoBack(int instanceId); diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 8889469920df..e78edbc49d41 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -19,6 +19,7 @@ flutter: dependencies: flutter: sdk: flutter + http: ^0.13.4 webview_flutter_platform_interface: ^1.5.2 dev_dependencies: diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart index 45988a0ae441..6d28837a2590 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart @@ -4,7 +4,7 @@ // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, avoid_relative_lib_imports, unnecessary_parenthesis +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import // @dart = 2.12 import 'dart:async'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; @@ -27,6 +27,7 @@ abstract class TestWebViewHostApi { void loadDataWithBaseUrl(int instanceId, String baseUrl, String data, String mimeType, String encoding, String historyUrl); void loadUrl(int instanceId, String url, Map headers); + void postUrl(int instanceId, String url, Uint8List data); String getUrl(int instanceId); bool canGoBack(int instanceId); bool canGoForward(int instanceId); @@ -179,6 +180,31 @@ abstract class TestWebViewHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewHostApi.postUrl', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null int.'); + final String? arg_url = (args[1] as String?); + assert(arg_url != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null String.'); + final Uint8List? arg_data = (args[2] as Uint8List?); + assert(arg_data != null, + 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null Uint8List.'); + api.postUrl(arg_instanceId!, arg_url!, arg_data!); + return {}; + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getUrl', codec, From 25578626d318aaf89e697b52141effcac00d8508 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 1 Dec 2021 17:21:58 +0100 Subject: [PATCH 02/11] Update changelog and pubspec. --- packages/webview_flutter/webview_flutter_android/CHANGELOG.md | 4 ++++ packages/webview_flutter/webview_flutter_android/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 479364ff14a7..f208da919b40 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.5.0 + +* Adds support for the `loadRequest` method from the platform interface. + ## 2.4.0 * Adds support for Android's `WebView.loadData` and `WebView.loadDataWithBaseUrl` methods and implements the `loadFile` and `loadHtmlString` methods from the platform interface. diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index e78edbc49d41..608f8ef3d41f 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.4.0 +version: 2.5.0 environment: sdk: ">=2.14.0 <3.0.0" From 986217bae26ad055abd739abe424cab940606156 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 1 Dec 2021 17:29:55 +0100 Subject: [PATCH 03/11] Fix comment. --- .../webview_flutter_android/lib/webview_android_widget.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index afdf9b07c4cd..b484badaa51f 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -324,11 +324,11 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { Future _dispose() => webView.release(); - // As the http package does currently not expose the resulting url when + // The http package currently does not expose the resulting url when // automatically following redirects. Because of this, redirects have to be // followed manually so that the final url can be tracked. Once this - // functionality has been implemented in the http package, - // this method should be removed. + // functionality has been implemented in the http package, this method + // should be removed. // https://github.com/dart-lang/http/issues/556 // https://github.com/dart-lang/http/issues/293 Future<_HTTPResponseWithFinalUrl> _postUrlAndFollowRedirects(Uri uri, From 493ad5dcc99fd0111af3e5444edb1f9bfaa92bef Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Wed, 1 Dec 2021 17:31:31 +0100 Subject: [PATCH 04/11] Fix comment. --- .../webview_flutter_android/lib/webview_android_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index b484badaa51f..bd9b0263c5b1 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -343,7 +343,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { final http.Response response = await http.Response.fromStream(await baseClient.send(req)); - // If it's a redirection, follow it + // If it's a redirection, follow it. if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.containsKey('location')) { From ab49b54dc2958621c634181bf4e3567db4d2faac Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 2 Dec 2021 14:41:24 +0100 Subject: [PATCH 05/11] Add tests --- .../plugins/webviewflutter/WebViewTest.java | 6 + .../lib/webview_android_widget.dart | 44 ++- .../test/android_webview_test.mocks.dart | 104 +++---- .../test/webview_android_widget_test.dart | 227 ++++++++++++++++ .../webview_android_widget_test.mocks.dart | 253 ++++++++++++------ 5 files changed, 496 insertions(+), 138 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java index dc58b9b100ff..2312b764342f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java @@ -208,6 +208,12 @@ public void loadUrl() { verify(mockWebView).loadUrl("https://www.google.com", new HashMap<>()); } + @Test + public void postUrl() { + testHostApiImpl.postUrl(0L, "https://www.google.com", new byte[] {0x01, 0x02}); + verify(mockWebView).postUrl("https://www.google.com", new byte[] {0x01, 0x02}); + } + @Test public void getUrl() { when(mockWebView.getUrl()).thenReturn("https://www.google.com"); diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index bd9b0263c5b1..2898bcb5de4c 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -122,6 +122,8 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { late WebViewAndroidWebViewClient _webViewClient; + http.Client _httpClient = http.Client(); + /// Represents the WebView maintained by platform code. late final android_webview.WebView webView; @@ -150,6 +152,12 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @visibleForTesting WebViewAndroidWebViewClient get webViewClient => _webViewClient; + /// Sets the HTTP client used for making requests on the dart side. + @visibleForTesting + set httpClient(http.Client httpClient) { + _httpClient = httpClient; + } + @override Future loadHtmlString(String html, {String? baseUrl}) { return webView.loadDataWithBaseUrl( @@ -191,15 +199,15 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } // Otherwise, the request has to be made manually. else { - final _HTTPResponseWithFinalUrl responseWithUrl = - await _postUrlAndFollowRedirects( + final HTTPResponseWithUrl responseWithUrl = + await postUrlAndFollowRedirects( request.uri, headers: request.headers, body: request.body ?? Uint8List(0), ); final http.Response response = responseWithUrl.response; - final String baseUrl = responseWithUrl.finalUrl; + final String baseUrl = responseWithUrl.url; final String mimeType = response.headers['content-type'] ?? 'text/html'; @@ -331,7 +339,12 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { // should be removed. // https://github.com/dart-lang/http/issues/556 // https://github.com/dart-lang/http/issues/293 - Future<_HTTPResponseWithFinalUrl> _postUrlAndFollowRedirects(Uri uri, + /// Makes a POST request and automatically follows any redirections + /// while keeping track of the new location. Returns the final response + /// together with the url of the final redirection. + /// This method is only publicly visible for testing purposes. + @visibleForTesting + Future postUrlAndFollowRedirects(Uri uri, {Map? headers, Uint8List? body, int redirections = 0}) async { @@ -339,24 +352,24 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { ..followRedirects = false ..bodyBytes = body ?? []; req.headers.addAll(headers ?? {}); - final http.Client baseClient = http.Client(); final http.Response response = - await http.Response.fromStream(await baseClient.send(req)); + await http.Response.fromStream(await _httpClient.send(req)); // If it's a redirection, follow it. if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.containsKey('location')) { + redirections++; // Maximum of 20 redirections (Default in Chrome & Firefox). if (redirections >= 20) { throw const HttpException('Maximum amount of redirections reached.'); } final Uri redirectUri = Uri.parse(response.headers['location']!); - return _postUrlAndFollowRedirects(redirectUri, - headers: headers, body: body, redirections: redirections + 1); + return postUrlAndFollowRedirects(redirectUri, + headers: headers, body: body, redirections: redirections); } - return _HTTPResponseWithFinalUrl(response, uri.toString()); + return HTTPResponseWithUrl(response, uri.toString()); } void _setCreationParams(CreationParams creationParams) { @@ -714,9 +727,16 @@ class WebViewProxy { } } -class _HTTPResponseWithFinalUrl { - _HTTPResponseWithFinalUrl(this.response, this.finalUrl); +/// Wrapper class for bundling a http response with the url it came from. +/// This class is only publicly visible for testing purposes. +@visibleForTesting +class HTTPResponseWithUrl { + /// Constructs a [HTTPResponseWithUrl]. + HTTPResponseWithUrl(this.response, this.url); + /// The response object. final http.Response response; - final String finalUrl; + + /// The url the response came from. + final String url; } diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index 90cbf2c4fefe..a54b5df204d6 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -2,7 +2,8 @@ // in webview_flutter_android/test/android_webview_test.dart. // Do not manually edit this file. -import 'dart:async' as _i4; +import 'dart:async' as _i5; +import 'dart:typed_data' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; @@ -240,6 +241,10 @@ class MockTestWebViewHostApi extends _i1.Mock Invocation.method(#loadUrl, [instanceId, url, headers]), returnValueForMissingStub: null); @override + void postUrl(int? instanceId, String? url, _i4.Uint8List? data) => + super.noSuchMethod(Invocation.method(#postUrl, [instanceId, url, data]), + returnValueForMissingStub: null); + @override String getUrl(int? instanceId) => (super.noSuchMethod(Invocation.method(#getUrl, [instanceId]), returnValue: '') as String); @@ -269,12 +274,12 @@ class MockTestWebViewHostApi extends _i1.Mock Invocation.method(#clearCache, [instanceId, includeDiskFiles]), returnValueForMissingStub: null); @override - _i4.Future evaluateJavascript( + _i5.Future evaluateJavascript( int? instanceId, String? javascriptString) => (super.noSuchMethod( Invocation.method( #evaluateJavascript, [instanceId, javascriptString]), - returnValue: Future.value('')) as _i4.Future); + returnValue: Future.value('')) as _i5.Future); @override String getTitle(int? instanceId) => (super.noSuchMethod(Invocation.method(#getTitle, [instanceId]), @@ -368,15 +373,15 @@ class MockWebView extends _i1.Mock implements _i2.WebView { (super.noSuchMethod(Invocation.getter(#settings), returnValue: _FakeWebSettings_0()) as _i2.WebSettings); @override - _i4.Future loadData( + _i5.Future loadData( {String? data, String? mimeType, String? encoding}) => (super.noSuchMethod( Invocation.method(#loadData, [], {#data: data, #mimeType: mimeType, #encoding: encoding}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future loadDataWithBaseUrl( + _i5.Future loadDataWithBaseUrl( {String? baseUrl, String? data, String? mimeType, @@ -391,104 +396,109 @@ class MockWebView extends _i1.Mock implements _i2.WebView { #historyUrl: historyUrl }), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future loadUrl(String? url, Map? headers) => + _i5.Future loadUrl(String? url, Map? headers) => (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future postUrl(String? url, _i4.Uint8List? data) => + (super.noSuchMethod(Invocation.method(#postUrl, [url, data]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future getUrl() => + _i5.Future getUrl() => (super.noSuchMethod(Invocation.method(#getUrl, []), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i4.Future canGoBack() => + _i5.Future canGoBack() => (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: Future.value(false)) as _i4.Future); + returnValue: Future.value(false)) as _i5.Future); @override - _i4.Future canGoForward() => + _i5.Future canGoForward() => (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: Future.value(false)) as _i4.Future); + returnValue: Future.value(false)) as _i5.Future); @override - _i4.Future goBack() => + _i5.Future goBack() => (super.noSuchMethod(Invocation.method(#goBack, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future goForward() => + _i5.Future goForward() => (super.noSuchMethod(Invocation.method(#goForward, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future reload() => + _i5.Future reload() => (super.noSuchMethod(Invocation.method(#reload, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future clearCache(bool? includeDiskFiles) => + _i5.Future clearCache(bool? includeDiskFiles) => (super.noSuchMethod(Invocation.method(#clearCache, [includeDiskFiles]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future evaluateJavascript(String? javascriptString) => (super + _i5.Future evaluateJavascript(String? javascriptString) => (super .noSuchMethod(Invocation.method(#evaluateJavascript, [javascriptString]), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i4.Future getTitle() => + _i5.Future getTitle() => (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i4.Future scrollTo(int? x, int? y) => + _i5.Future scrollTo(int? x, int? y) => (super.noSuchMethod(Invocation.method(#scrollTo, [x, y]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future scrollBy(int? x, int? y) => + _i5.Future scrollBy(int? x, int? y) => (super.noSuchMethod(Invocation.method(#scrollBy, [x, y]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future getScrollX() => + _i5.Future getScrollX() => (super.noSuchMethod(Invocation.method(#getScrollX, []), - returnValue: Future.value(0)) as _i4.Future); + returnValue: Future.value(0)) as _i5.Future); @override - _i4.Future getScrollY() => + _i5.Future getScrollY() => (super.noSuchMethod(Invocation.method(#getScrollY, []), - returnValue: Future.value(0)) as _i4.Future); + returnValue: Future.value(0)) as _i5.Future); @override - _i4.Future setWebViewClient(_i2.WebViewClient? webViewClient) => + _i5.Future setWebViewClient(_i2.WebViewClient? webViewClient) => (super.noSuchMethod(Invocation.method(#setWebViewClient, [webViewClient]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future addJavaScriptChannel( + _i5.Future addJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method(#addJavaScriptChannel, [javaScriptChannel]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future removeJavaScriptChannel( + _i5.Future removeJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method(#removeJavaScriptChannel, [javaScriptChannel]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setDownloadListener(_i2.DownloadListener? listener) => + _i5.Future setDownloadListener(_i2.DownloadListener? listener) => (super.noSuchMethod(Invocation.method(#setDownloadListener, [listener]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setWebChromeClient(_i2.WebChromeClient? client) => + _i5.Future setWebChromeClient(_i2.WebChromeClient? client) => (super.noSuchMethod(Invocation.method(#setWebChromeClient, [client]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future release() => + _i5.Future release() => (super.noSuchMethod(Invocation.method(#release, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override String toString() => super.toString(); } diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index 460cb54bd393..a953b1492a78 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart @@ -3,9 +3,12 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart' @@ -25,6 +28,7 @@ import 'webview_android_widget_test.mocks.dart'; JavascriptChannelRegistry, WebViewPlatformCallbacksHandler, WebViewProxy, + http.Client ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -42,6 +46,7 @@ void main() { late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; late WebViewAndroidPlatformController testController; + late MockClient mockHttpClient; setUp(() { mockWebView = MockWebView(); @@ -55,6 +60,7 @@ void main() { mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); + mockHttpClient = MockClient(); }); // Builds a AndroidWebViewWidget with default parameters. @@ -79,6 +85,7 @@ void main() { webViewProxy: mockWebViewProxy, onBuildWidget: (WebViewAndroidPlatformController controller) { testController = controller; + testController.httpClient = mockHttpClient; return Container(); }, )); @@ -342,6 +349,208 @@ void main() { )); }); + group('postUrlAndFollowRedirects', () { + http.StreamedResponse _buildStreamedResponse( + Map data) => + http.StreamedResponse( + Stream>.value( + (data['body'] as String? ?? '').codeUnits), + data['status'] as int, + headers: (data['headers'] ?? {}) + as Map, + ); + http.Request _buildRequest(Map data) { + final http.Request req = http.Request( + 'POST', + Uri.parse(data['url'] as String), + ) + ..followRedirects = false + ..body = data['body'] as String? ?? ''; + req.headers.addAll( + data['headers'] as Map? ?? {}); + return req; + } + + Future withXRedirects(int redirects) async { + assert(redirects >= 1); + // Setup + final List requests = >[ + for (int i = 1; i <= redirects + 1; i++) + { + 'url': 'https://origin-$i', + 'headers': {'a': 'header'} + }, + ].map(_buildRequest).toList(); + final List responses = >[ + for (int i = 2; i <= redirects + 1; i++) + { + 'status': 300, + 'headers': {'location': 'https://origin-$i'} + }, + { + 'body': 'Response Data', + 'status': 200, + 'headers': {'content-type': 'text/html'} + } + ].map(_buildStreamedResponse).toList(); + final List requestLog = []; + when(mockHttpClient.send(any)) + .thenAnswer((Invocation invocation) async { + requestLog.add(invocation.positionalArguments[0] as http.Request); + return responses.removeAt(0); + }); + + // Run + final HTTPResponseWithUrl responseWithUrl = await testController + .postUrlAndFollowRedirects(Uri.parse(requests[0].url.toString()), + headers: requests[0].headers, body: requests[0].bodyBytes); + + // Verify + expect(requestLog.length, equals(requests.length)); + for (int i = 0; i < requestLog.length; i++) { + expect(requestLog[i], _EqualsHttpRequest(requests[i])); + } + expect( + responseWithUrl.url, equals('https://origin-${redirects + 1}')); + expect(responseWithUrl.response.body, equals('Response Data')); + expect(responseWithUrl.response.statusCode, equals(200)); + expect(responseWithUrl.response.headers, + equals({'content-type': 'text/html'})); + } + + testWidgets('Succeeds without redirect', (WidgetTester tester) async { + // Setup + await buildWidget(tester); + when(mockHttpClient.send(any)).thenAnswer( + (_) async => http.StreamedResponse( + Stream>.value('Response Data'.codeUnits), + 200, + headers: {'content-type': 'text/html'}, + ), + ); + // Run + final HTTPResponseWithUrl responseWithUrl = await testController + .postUrlAndFollowRedirects(Uri.parse('https://www.google.com'), + headers: {'a': 'header'}, + body: Uint8List.fromList('Test Body'.codeUnits)); + // Verify + final http.Request expectedRequest = + http.Request('POST', Uri.parse('https://www.google.com')) + ..followRedirects = false + ..bodyBytes = Uint8List.fromList('Test Body'.codeUnits); + expectedRequest.headers.addAll({'a': 'header'}); + verify(mockHttpClient + .send(argThat(_EqualsHttpRequest(expectedRequest)))); + expect(responseWithUrl.url, equals('https://www.google.com')); + expect(responseWithUrl.response.body, equals('Response Data')); + expect(responseWithUrl.response.statusCode, equals(200)); + expect(responseWithUrl.response.headers, + equals({'content-type': 'text/html'})); + }); + + testWidgets('Succeeds with 1 redirect', (WidgetTester tester) async { + await buildWidget(tester); + withXRedirects(1); + }); + + testWidgets('Succeeds with 2 redirects', (WidgetTester tester) async { + await buildWidget(tester); + withXRedirects(2); + }); + + testWidgets('Fails after 20 redirects', (WidgetTester tester) async { + await buildWidget(tester); + expect(() => withXRedirects(20), + throwsA(const TypeMatcher())); + }); + }); + + group('loadRequest', () { + testWidgets('GET without headers', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.get, + )); + + verify(mockWebView.loadUrl( + 'https://www.google.com', + {}, + )); + }); + + testWidgets('GET with headers', (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.get, + headers: {'a': 'header'}, + )); + + verify(mockWebView.loadUrl( + 'https://www.google.com', + {'a': 'header'}, + )); + }); + + testWidgets('POST without headers or body', + (WidgetTester tester) async { + await buildWidget(tester); + + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.post, + )); + + verify(mockWebView.postUrl( + 'https://www.google.com', + Uint8List(0), + )); + }); + + testWidgets('POST without headers with body', + (WidgetTester tester) async { + await buildWidget(tester); + + final Uint8List body = Uint8List.fromList('Test Body'.codeUnits); + + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.post, + body: body)); + + verify(mockWebView.postUrl( + 'https://www.google.com', + body, + )); + }); + + testWidgets('POST with headers and body', (WidgetTester tester) async { + // Setup + await buildWidget(tester); + when(mockHttpClient.send(any)).thenAnswer((_) async => + http.StreamedResponse( + Stream>.value('Response Data'.codeUnits), 200, + headers: {'content-type': 'text/html'})); + final Uint8List body = Uint8List.fromList('Test Body'.codeUnits); + final Map headers = {'a': 'header'}; + // Run + await testController.loadRequest(WebViewRequest( + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.post, + body: body, + headers: headers, + )); + // Verify + verify(mockWebView.loadDataWithBaseUrl( + data: 'Response Data', + baseUrl: 'https://www.google.com', + mimeType: 'text/html')); + }); + }); + testWidgets('currentUrl', (WidgetTester tester) async { await buildWidget(tester); @@ -617,3 +826,21 @@ void main() { }); }); } + +class _EqualsHttpRequest extends Matcher { + const _EqualsHttpRequest(this._expected); + + final http.Request _expected; + + @override + bool matches( + covariant http.Request actual, Map matchState) { + return equals(_expected.url).matches(actual.url, matchState) && + equals(_expected.headers).matches(actual.headers, matchState) && + equals(_expected.method).matches(actual.method, matchState) && + equals(_expected.bodyBytes).matches(actual.bodyBytes, matchState); + } + + @override + Description describe(Description description) => description.add('matches'); +} diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index 0582049acf6d..7c4601e81526 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart @@ -2,11 +2,14 @@ // in webview_flutter_android/test/webview_android_widget_test.dart. // Do not manually edit this file. -import 'dart:async' as _i4; +import 'dart:async' as _i5; +import 'dart:convert' as _i8; +import 'dart:typed_data' as _i6; +import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; -import 'package:webview_flutter_android/webview_android_widget.dart' as _i5; +import 'package:webview_flutter_android/webview_android_widget.dart' as _i7; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' as _i3; @@ -26,6 +29,11 @@ class _FakeJavascriptChannelRegistry_1 extends _i1.Fake class _FakeWebView_2 extends _i1.Fake implements _i2.WebView {} +class _FakeResponse_3 extends _i1.Fake implements _i4.Response {} + +class _FakeStreamedResponse_4 extends _i1.Fake implements _i4.StreamedResponse { +} + /// A class which mocks [WebSettings]. /// /// See the documentation for Mockito's code generation for more information. @@ -35,62 +43,62 @@ class MockWebSettings extends _i1.Mock implements _i2.WebSettings { } @override - _i4.Future setDomStorageEnabled(bool? flag) => + _i5.Future setDomStorageEnabled(bool? flag) => (super.noSuchMethod(Invocation.method(#setDomStorageEnabled, [flag]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => + _i5.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => (super.noSuchMethod( Invocation.method(#setJavaScriptCanOpenWindowsAutomatically, [flag]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setSupportMultipleWindows(bool? support) => (super + _i5.Future setSupportMultipleWindows(bool? support) => (super .noSuchMethod(Invocation.method(#setSupportMultipleWindows, [support]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setJavaScriptEnabled(bool? flag) => + _i5.Future setJavaScriptEnabled(bool? flag) => (super.noSuchMethod(Invocation.method(#setJavaScriptEnabled, [flag]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setUserAgentString(String? userAgentString) => (super + _i5.Future setUserAgentString(String? userAgentString) => (super .noSuchMethod(Invocation.method(#setUserAgentString, [userAgentString]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setMediaPlaybackRequiresUserGesture(bool? require) => + _i5.Future setMediaPlaybackRequiresUserGesture(bool? require) => (super.noSuchMethod( Invocation.method(#setMediaPlaybackRequiresUserGesture, [require]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setSupportZoom(bool? support) => + _i5.Future setSupportZoom(bool? support) => (super.noSuchMethod(Invocation.method(#setSupportZoom, [support]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setLoadWithOverviewMode(bool? overview) => (super + _i5.Future setLoadWithOverviewMode(bool? overview) => (super .noSuchMethod(Invocation.method(#setLoadWithOverviewMode, [overview]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setUseWideViewPort(bool? use) => + _i5.Future setUseWideViewPort(bool? use) => (super.noSuchMethod(Invocation.method(#setUseWideViewPort, [use]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setDisplayZoomControls(bool? enabled) => + _i5.Future setDisplayZoomControls(bool? enabled) => (super.noSuchMethod(Invocation.method(#setDisplayZoomControls, [enabled]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setBuiltInZoomControls(bool? enabled) => + _i5.Future setBuiltInZoomControls(bool? enabled) => (super.noSuchMethod(Invocation.method(#setBuiltInZoomControls, [enabled]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override String toString() => super.toString(); } @@ -112,15 +120,15 @@ class MockWebView extends _i1.Mock implements _i2.WebView { (super.noSuchMethod(Invocation.getter(#settings), returnValue: _FakeWebSettings_0()) as _i2.WebSettings); @override - _i4.Future loadData( + _i5.Future loadData( {String? data, String? mimeType, String? encoding}) => (super.noSuchMethod( Invocation.method(#loadData, [], {#data: data, #mimeType: mimeType, #encoding: encoding}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future loadDataWithBaseUrl( + _i5.Future loadDataWithBaseUrl( {String? baseUrl, String? data, String? mimeType, @@ -135,104 +143,109 @@ class MockWebView extends _i1.Mock implements _i2.WebView { #historyUrl: historyUrl }), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future loadUrl(String? url, Map? headers) => + _i5.Future loadUrl(String? url, Map? headers) => (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future getUrl() => + _i5.Future postUrl(String? url, _i6.Uint8List? data) => + (super.noSuchMethod(Invocation.method(#postUrl, [url, data]), + returnValue: Future.value(), + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + _i5.Future getUrl() => (super.noSuchMethod(Invocation.method(#getUrl, []), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i4.Future canGoBack() => + _i5.Future canGoBack() => (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: Future.value(false)) as _i4.Future); + returnValue: Future.value(false)) as _i5.Future); @override - _i4.Future canGoForward() => + _i5.Future canGoForward() => (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: Future.value(false)) as _i4.Future); + returnValue: Future.value(false)) as _i5.Future); @override - _i4.Future goBack() => + _i5.Future goBack() => (super.noSuchMethod(Invocation.method(#goBack, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future goForward() => + _i5.Future goForward() => (super.noSuchMethod(Invocation.method(#goForward, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future reload() => + _i5.Future reload() => (super.noSuchMethod(Invocation.method(#reload, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future clearCache(bool? includeDiskFiles) => + _i5.Future clearCache(bool? includeDiskFiles) => (super.noSuchMethod(Invocation.method(#clearCache, [includeDiskFiles]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future evaluateJavascript(String? javascriptString) => (super + _i5.Future evaluateJavascript(String? javascriptString) => (super .noSuchMethod(Invocation.method(#evaluateJavascript, [javascriptString]), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i4.Future getTitle() => + _i5.Future getTitle() => (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: Future.value()) as _i4.Future); + returnValue: Future.value()) as _i5.Future); @override - _i4.Future scrollTo(int? x, int? y) => + _i5.Future scrollTo(int? x, int? y) => (super.noSuchMethod(Invocation.method(#scrollTo, [x, y]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future scrollBy(int? x, int? y) => + _i5.Future scrollBy(int? x, int? y) => (super.noSuchMethod(Invocation.method(#scrollBy, [x, y]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future getScrollX() => + _i5.Future getScrollX() => (super.noSuchMethod(Invocation.method(#getScrollX, []), - returnValue: Future.value(0)) as _i4.Future); + returnValue: Future.value(0)) as _i5.Future); @override - _i4.Future getScrollY() => + _i5.Future getScrollY() => (super.noSuchMethod(Invocation.method(#getScrollY, []), - returnValue: Future.value(0)) as _i4.Future); + returnValue: Future.value(0)) as _i5.Future); @override - _i4.Future setWebViewClient(_i2.WebViewClient? webViewClient) => + _i5.Future setWebViewClient(_i2.WebViewClient? webViewClient) => (super.noSuchMethod(Invocation.method(#setWebViewClient, [webViewClient]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future addJavaScriptChannel( + _i5.Future addJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method(#addJavaScriptChannel, [javaScriptChannel]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future removeJavaScriptChannel( + _i5.Future removeJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method(#removeJavaScriptChannel, [javaScriptChannel]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setDownloadListener(_i2.DownloadListener? listener) => + _i5.Future setDownloadListener(_i2.DownloadListener? listener) => (super.noSuchMethod(Invocation.method(#setDownloadListener, [listener]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future setWebChromeClient(_i2.WebChromeClient? client) => + _i5.Future setWebChromeClient(_i2.WebChromeClient? client) => (super.noSuchMethod(Invocation.method(#setWebChromeClient, [client]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override - _i4.Future release() => + _i5.Future release() => (super.noSuchMethod(Invocation.method(#release, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); @override String toString() => super.toString(); } @@ -241,16 +254,16 @@ class MockWebView extends _i1.Mock implements _i2.WebView { /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidDownloadListener extends _i1.Mock - implements _i5.WebViewAndroidDownloadListener { + implements _i7.WebViewAndroidDownloadListener { MockWebViewAndroidDownloadListener() { _i1.throwOnMissingStub(this); } @override - _i4.Future Function(String, Map?) get loadUrl => + _i5.Future Function(String, Map?) get loadUrl => (super.noSuchMethod(Invocation.getter(#loadUrl), returnValue: (String url, Map? headers) => - Future.value()) as _i4.Future Function( + Future.value()) as _i5.Future Function( String, Map?)); @override void onDownloadStart(String? url, String? userAgent, @@ -267,7 +280,7 @@ class MockWebViewAndroidDownloadListener extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidJavaScriptChannel extends _i1.Mock - implements _i5.WebViewAndroidJavaScriptChannel { + implements _i7.WebViewAndroidJavaScriptChannel { MockWebViewAndroidJavaScriptChannel() { _i1.throwOnMissingStub(this); } @@ -293,7 +306,7 @@ class MockWebViewAndroidJavaScriptChannel extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidWebChromeClient extends _i1.Mock - implements _i5.WebViewAndroidWebChromeClient { + implements _i7.WebViewAndroidWebChromeClient { MockWebViewAndroidWebChromeClient() { _i1.throwOnMissingStub(this); } @@ -310,7 +323,7 @@ class MockWebViewAndroidWebChromeClient extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidWebViewClient extends _i1.Mock - implements _i5.WebViewAndroidWebViewClient { + implements _i7.WebViewAndroidWebViewClient { MockWebViewAndroidWebViewClient() { _i1.throwOnMissingStub(this); } @@ -414,11 +427,11 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock } @override - _i4.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => + _i5.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => (super.noSuchMethod( Invocation.method(#onNavigationRequest, [], {#url: url, #isForMainFrame: isForMainFrame}), - returnValue: Future.value(false)) as _i4.FutureOr); + returnValue: Future.value(false)) as _i5.FutureOr); @override void onPageStarted(String? url) => super.noSuchMethod(Invocation.method(#onPageStarted, [url]), @@ -442,7 +455,7 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock /// A class which mocks [WebViewProxy]. /// /// See the documentation for Mockito's code generation for more information. -class MockWebViewProxy extends _i1.Mock implements _i5.WebViewProxy { +class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy { MockWebViewProxy() { _i1.throwOnMissingStub(this); } @@ -454,11 +467,93 @@ class MockWebViewProxy extends _i1.Mock implements _i5.WebViewProxy { {#useHybridComposition: useHybridComposition}), returnValue: _FakeWebView_2()) as _i2.WebView); @override - _i4.Future setWebContentsDebuggingEnabled(bool? enabled) => + _i5.Future setWebContentsDebuggingEnabled(bool? enabled) => (super.noSuchMethod( Invocation.method(#setWebContentsDebuggingEnabled, [enabled]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); + returnValueForMissingStub: Future.value()) as _i5.Future); + @override + String toString() => super.toString(); +} + +/// A class which mocks [Client]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockClient extends _i1.Mock implements _i4.Client { + MockClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.Future<_i4.Response> head(Uri? url, {Map? headers}) => + (super.noSuchMethod(Invocation.method(#head, [url], {#headers: headers}), + returnValue: Future<_i4.Response>.value(_FakeResponse_3())) + as _i5.Future<_i4.Response>); + @override + _i5.Future<_i4.Response> get(Uri? url, {Map? headers}) => + (super.noSuchMethod(Invocation.method(#get, [url], {#headers: headers}), + returnValue: Future<_i4.Response>.value(_FakeResponse_3())) + as _i5.Future<_i4.Response>); + @override + _i5.Future<_i4.Response> post(Uri? url, + {Map? headers, + Object? body, + _i8.Encoding? encoding}) => + (super.noSuchMethod( + Invocation.method(#post, [url], + {#headers: headers, #body: body, #encoding: encoding}), + returnValue: Future<_i4.Response>.value(_FakeResponse_3())) + as _i5.Future<_i4.Response>); + @override + _i5.Future<_i4.Response> put(Uri? url, + {Map? headers, + Object? body, + _i8.Encoding? encoding}) => + (super.noSuchMethod( + Invocation.method(#put, [url], + {#headers: headers, #body: body, #encoding: encoding}), + returnValue: Future<_i4.Response>.value(_FakeResponse_3())) + as _i5.Future<_i4.Response>); + @override + _i5.Future<_i4.Response> patch(Uri? url, + {Map? headers, + Object? body, + _i8.Encoding? encoding}) => + (super.noSuchMethod( + Invocation.method(#patch, [url], + {#headers: headers, #body: body, #encoding: encoding}), + returnValue: Future<_i4.Response>.value(_FakeResponse_3())) + as _i5.Future<_i4.Response>); + @override + _i5.Future<_i4.Response> delete(Uri? url, + {Map? headers, + Object? body, + _i8.Encoding? encoding}) => + (super.noSuchMethod( + Invocation.method(#delete, [url], + {#headers: headers, #body: body, #encoding: encoding}), + returnValue: Future<_i4.Response>.value(_FakeResponse_3())) + as _i5.Future<_i4.Response>); + @override + _i5.Future read(Uri? url, {Map? headers}) => + (super.noSuchMethod(Invocation.method(#read, [url], {#headers: headers}), + returnValue: Future.value('')) as _i5.Future); + @override + _i5.Future<_i6.Uint8List> readBytes(Uri? url, + {Map? headers}) => + (super.noSuchMethod( + Invocation.method(#readBytes, [url], {#headers: headers}), + returnValue: Future<_i6.Uint8List>.value(_i6.Uint8List(0))) + as _i5.Future<_i6.Uint8List>); + @override + _i5.Future<_i4.StreamedResponse> send(_i4.BaseRequest? request) => + (super.noSuchMethod(Invocation.method(#send, [request]), + returnValue: + Future<_i4.StreamedResponse>.value(_FakeStreamedResponse_4())) + as _i5.Future<_i4.StreamedResponse>); + @override + void close() => super.noSuchMethod(Invocation.method(#close, []), + returnValueForMissingStub: null); @override String toString() => super.toString(); } From 8ca591a01e9c5c2736d18d1128f96814896e9d2b Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 2 Dec 2021 14:45:54 +0100 Subject: [PATCH 06/11] Add back removed license headers --- .../lib/src/android_webview.pigeon.dart | 4 ++++ .../test/android_webview_test.mocks.dart | 4 ++++ .../test/webview_android_widget_test.mocks.dart | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart index 5e603b58f6af..a172c6b315a6 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + // Autogenerated from Pigeon (v1.0.9), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index a54b5df204d6..1d9871f5b908 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter_android/test/android_webview_test.dart. // Do not manually edit this file. diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index 7c4601e81526..cf1b41219d0f 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + // Mocks generated by Mockito 5.0.16 from annotations // in webview_flutter_android/test/webview_android_widget_test.dart. // Do not manually edit this file. From 78f9968c01969fb8ea0af89da99c17fe16e7a2ef Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 2 Dec 2021 14:54:53 +0100 Subject: [PATCH 07/11] Fix analysis error --- .../webview_flutter_android/lib/src/android_webview.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index 6fc175eb25b0..a7e6e1bc8370 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ffi'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; From 7e0a3853d9a9f7691906bea4163a5390626e819c Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 6 Dec 2021 09:56:18 +0100 Subject: [PATCH 08/11] Remove workaround for supporting custom headers when making post requests on Android. --- .../lib/webview_android_widget.dart | 109 ++------ .../webview_flutter_android/pubspec.yaml | 1 - .../test/webview_android_widget_test.dart | 176 +----------- .../webview_android_widget_test.mocks.dart | 253 ++++++------------ 4 files changed, 115 insertions(+), 424 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index 2898bcb5de4c..f450a7f4d075 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -3,11 +3,9 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/widgets.dart'; -import 'package:http/http.dart' as http; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'src/android_webview.dart' as android_webview; @@ -122,8 +120,6 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { late WebViewAndroidWebViewClient _webViewClient; - http.Client _httpClient = http.Client(); - /// Represents the WebView maintained by platform code. late final android_webview.WebView webView; @@ -152,12 +148,6 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { @visibleForTesting WebViewAndroidWebViewClient get webViewClient => _webViewClient; - /// Sets the HTTP client used for making requests on the dart side. - @visibleForTesting - set httpClient(http.Client httpClient) { - _httpClient = httpClient; - } - @override Future loadHtmlString(String html, {String? baseUrl}) { return webView.loadDataWithBaseUrl( @@ -185,38 +175,31 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } @override + /// Makes a specific HTTP request ands loads the response in the webview. + /// + /// [WebViewRequest.method] must be one of the supported HTTP methods + /// in [WebViewRequestMethod]. + /// + /// If [WebViewRequest.headers] is not empty, its key-value pairs will be + /// added as the headers for the request. + /// + /// If [WebViewRequest.body] is not null, it will be added as the body + /// for the request. + /// + /// Throws an ArgumentError if [WebViewRequest.uri] has empty scheme. + /// + /// Android only: + /// When making a POST request, headers are ignored. As a workaround, make + /// the request manually and load the response data using [loadHTMLString]. Future loadRequest( WebViewRequest request, ) async { switch (request.method) { case WebViewRequestMethod.get: - return loadUrl(request.uri.toString(), request.headers); + return webView.loadUrl(request.uri.toString(), request.headers); case WebViewRequestMethod.post: - // If the request requires no additional headers, postUrl can be used directly. - if (request.headers.isEmpty) { - return webView.postUrl( - request.uri.toString(), request.body ?? Uint8List(0)); - } - // Otherwise, the request has to be made manually. - else { - final HTTPResponseWithUrl responseWithUrl = - await postUrlAndFollowRedirects( - request.uri, - headers: request.headers, - body: request.body ?? Uint8List(0), - ); - final http.Response response = responseWithUrl.response; - - final String baseUrl = responseWithUrl.url; - final String mimeType = - response.headers['content-type'] ?? 'text/html'; - - return webView.loadDataWithBaseUrl( - data: response.body, - baseUrl: baseUrl, - mimeType: mimeType, - ); - } + return webView.postUrl( + request.uri.toString(), request.body ?? Uint8List(0)); default: throw UnimplementedError( 'This version of webview_android_widget currently has no implementation for HTTP method ${request.method.serialize()} in loadRequest.', @@ -332,46 +315,6 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { Future _dispose() => webView.release(); - // The http package currently does not expose the resulting url when - // automatically following redirects. Because of this, redirects have to be - // followed manually so that the final url can be tracked. Once this - // functionality has been implemented in the http package, this method - // should be removed. - // https://github.com/dart-lang/http/issues/556 - // https://github.com/dart-lang/http/issues/293 - /// Makes a POST request and automatically follows any redirections - /// while keeping track of the new location. Returns the final response - /// together with the url of the final redirection. - /// This method is only publicly visible for testing purposes. - @visibleForTesting - Future postUrlAndFollowRedirects(Uri uri, - {Map? headers, - Uint8List? body, - int redirections = 0}) async { - final http.Request req = http.Request('POST', uri) - ..followRedirects = false - ..bodyBytes = body ?? []; - req.headers.addAll(headers ?? {}); - final http.Response response = - await http.Response.fromStream(await _httpClient.send(req)); - - // If it's a redirection, follow it. - if (response.statusCode >= 300 && - response.statusCode < 400 && - response.headers.containsKey('location')) { - redirections++; - // Maximum of 20 redirections (Default in Chrome & Firefox). - if (redirections >= 20) { - throw const HttpException('Maximum amount of redirections reached.'); - } - final Uri redirectUri = Uri.parse(response.headers['location']!); - return postUrlAndFollowRedirects(redirectUri, - headers: headers, body: body, redirections: redirections); - } - - return HTTPResponseWithUrl(response, uri.toString()); - } - void _setCreationParams(CreationParams creationParams) { final WebSettings? webSettings = creationParams.webSettings; if (webSettings != null) { @@ -726,17 +669,3 @@ class WebViewProxy { return android_webview.WebView.setWebContentsDebuggingEnabled(true); } } - -/// Wrapper class for bundling a http response with the url it came from. -/// This class is only publicly visible for testing purposes. -@visibleForTesting -class HTTPResponseWithUrl { - /// Constructs a [HTTPResponseWithUrl]. - HTTPResponseWithUrl(this.response, this.url); - - /// The response object. - final http.Response response; - - /// The url the response came from. - final String url; -} diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 608f8ef3d41f..5f7dad457996 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -19,7 +19,6 @@ flutter: dependencies: flutter: sdk: flutter - http: ^0.13.4 webview_flutter_platform_interface: ^1.5.2 dev_dependencies: diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index a953b1492a78..2b3291028f3b 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart @@ -3,12 +3,10 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:http/http.dart' as http; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart' @@ -28,7 +26,6 @@ import 'webview_android_widget_test.mocks.dart'; JavascriptChannelRegistry, WebViewPlatformCallbacksHandler, WebViewProxy, - http.Client ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -46,7 +43,6 @@ void main() { late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; late WebViewAndroidPlatformController testController; - late MockClient mockHttpClient; setUp(() { mockWebView = MockWebView(); @@ -60,7 +56,6 @@ void main() { mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); - mockHttpClient = MockClient(); }); // Builds a AndroidWebViewWidget with default parameters. @@ -85,7 +80,6 @@ void main() { webViewProxy: mockWebViewProxy, onBuildWidget: (WebViewAndroidPlatformController controller) { testController = controller; - testController.httpClient = mockHttpClient; return Container(); }, )); @@ -349,122 +343,6 @@ void main() { )); }); - group('postUrlAndFollowRedirects', () { - http.StreamedResponse _buildStreamedResponse( - Map data) => - http.StreamedResponse( - Stream>.value( - (data['body'] as String? ?? '').codeUnits), - data['status'] as int, - headers: (data['headers'] ?? {}) - as Map, - ); - http.Request _buildRequest(Map data) { - final http.Request req = http.Request( - 'POST', - Uri.parse(data['url'] as String), - ) - ..followRedirects = false - ..body = data['body'] as String? ?? ''; - req.headers.addAll( - data['headers'] as Map? ?? {}); - return req; - } - - Future withXRedirects(int redirects) async { - assert(redirects >= 1); - // Setup - final List requests = >[ - for (int i = 1; i <= redirects + 1; i++) - { - 'url': 'https://origin-$i', - 'headers': {'a': 'header'} - }, - ].map(_buildRequest).toList(); - final List responses = >[ - for (int i = 2; i <= redirects + 1; i++) - { - 'status': 300, - 'headers': {'location': 'https://origin-$i'} - }, - { - 'body': 'Response Data', - 'status': 200, - 'headers': {'content-type': 'text/html'} - } - ].map(_buildStreamedResponse).toList(); - final List requestLog = []; - when(mockHttpClient.send(any)) - .thenAnswer((Invocation invocation) async { - requestLog.add(invocation.positionalArguments[0] as http.Request); - return responses.removeAt(0); - }); - - // Run - final HTTPResponseWithUrl responseWithUrl = await testController - .postUrlAndFollowRedirects(Uri.parse(requests[0].url.toString()), - headers: requests[0].headers, body: requests[0].bodyBytes); - - // Verify - expect(requestLog.length, equals(requests.length)); - for (int i = 0; i < requestLog.length; i++) { - expect(requestLog[i], _EqualsHttpRequest(requests[i])); - } - expect( - responseWithUrl.url, equals('https://origin-${redirects + 1}')); - expect(responseWithUrl.response.body, equals('Response Data')); - expect(responseWithUrl.response.statusCode, equals(200)); - expect(responseWithUrl.response.headers, - equals({'content-type': 'text/html'})); - } - - testWidgets('Succeeds without redirect', (WidgetTester tester) async { - // Setup - await buildWidget(tester); - when(mockHttpClient.send(any)).thenAnswer( - (_) async => http.StreamedResponse( - Stream>.value('Response Data'.codeUnits), - 200, - headers: {'content-type': 'text/html'}, - ), - ); - // Run - final HTTPResponseWithUrl responseWithUrl = await testController - .postUrlAndFollowRedirects(Uri.parse('https://www.google.com'), - headers: {'a': 'header'}, - body: Uint8List.fromList('Test Body'.codeUnits)); - // Verify - final http.Request expectedRequest = - http.Request('POST', Uri.parse('https://www.google.com')) - ..followRedirects = false - ..bodyBytes = Uint8List.fromList('Test Body'.codeUnits); - expectedRequest.headers.addAll({'a': 'header'}); - verify(mockHttpClient - .send(argThat(_EqualsHttpRequest(expectedRequest)))); - expect(responseWithUrl.url, equals('https://www.google.com')); - expect(responseWithUrl.response.body, equals('Response Data')); - expect(responseWithUrl.response.statusCode, equals(200)); - expect(responseWithUrl.response.headers, - equals({'content-type': 'text/html'})); - }); - - testWidgets('Succeeds with 1 redirect', (WidgetTester tester) async { - await buildWidget(tester); - withXRedirects(1); - }); - - testWidgets('Succeeds with 2 redirects', (WidgetTester tester) async { - await buildWidget(tester); - withXRedirects(2); - }); - - testWidgets('Fails after 20 redirects', (WidgetTester tester) async { - await buildWidget(tester); - expect(() => withXRedirects(20), - throwsA(const TypeMatcher())); - }); - }); - group('loadRequest', () { testWidgets('GET without headers', (WidgetTester tester) async { await buildWidget(tester); @@ -495,8 +373,7 @@ void main() { )); }); - testWidgets('POST without headers or body', - (WidgetTester tester) async { + testWidgets('POST without body', (WidgetTester tester) async { await buildWidget(tester); await testController.loadRequest(WebViewRequest( @@ -510,8 +387,7 @@ void main() { )); }); - testWidgets('POST without headers with body', - (WidgetTester tester) async { + testWidgets('POST with body', (WidgetTester tester) async { await buildWidget(tester); final Uint8List body = Uint8List.fromList('Test Body'.codeUnits); @@ -527,27 +403,21 @@ void main() { )); }); - testWidgets('POST with headers and body', (WidgetTester tester) async { - // Setup + testWidgets('POST ignores headers', (WidgetTester tester) async { await buildWidget(tester); - when(mockHttpClient.send(any)).thenAnswer((_) async => - http.StreamedResponse( - Stream>.value('Response Data'.codeUnits), 200, - headers: {'content-type': 'text/html'})); + final Uint8List body = Uint8List.fromList('Test Body'.codeUnits); - final Map headers = {'a': 'header'}; - // Run + await testController.loadRequest(WebViewRequest( - uri: Uri.parse('https://www.google.com'), - method: WebViewRequestMethod.post, - body: body, - headers: headers, + uri: Uri.parse('https://www.google.com'), + method: WebViewRequestMethod.post, + headers: {'foo': 'bar'}, + body: body)); + + verify(mockWebView.postUrl( + 'https://www.google.com', + body, )); - // Verify - verify(mockWebView.loadDataWithBaseUrl( - data: 'Response Data', - baseUrl: 'https://www.google.com', - mimeType: 'text/html')); }); }); @@ -825,22 +695,4 @@ void main() { }); }); }); -} - -class _EqualsHttpRequest extends Matcher { - const _EqualsHttpRequest(this._expected); - - final http.Request _expected; - - @override - bool matches( - covariant http.Request actual, Map matchState) { - return equals(_expected.url).matches(actual.url, matchState) && - equals(_expected.headers).matches(actual.headers, matchState) && - equals(_expected.method).matches(actual.method, matchState) && - equals(_expected.bodyBytes).matches(actual.bodyBytes, matchState); - } - - @override - Description describe(Description description) => description.add('matches'); -} +} \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart index cf1b41219d0f..1e98ac785082 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart @@ -6,14 +6,12 @@ // in webview_flutter_android/test/webview_android_widget_test.dart. // Do not manually edit this file. -import 'dart:async' as _i5; -import 'dart:convert' as _i8; -import 'dart:typed_data' as _i6; +import 'dart:async' as _i4; +import 'dart:typed_data' as _i5; -import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; -import 'package:webview_flutter_android/webview_android_widget.dart' as _i7; +import 'package:webview_flutter_android/webview_android_widget.dart' as _i6; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' as _i3; @@ -33,11 +31,6 @@ class _FakeJavascriptChannelRegistry_1 extends _i1.Fake class _FakeWebView_2 extends _i1.Fake implements _i2.WebView {} -class _FakeResponse_3 extends _i1.Fake implements _i4.Response {} - -class _FakeStreamedResponse_4 extends _i1.Fake implements _i4.StreamedResponse { -} - /// A class which mocks [WebSettings]. /// /// See the documentation for Mockito's code generation for more information. @@ -47,62 +40,62 @@ class MockWebSettings extends _i1.Mock implements _i2.WebSettings { } @override - _i5.Future setDomStorageEnabled(bool? flag) => + _i4.Future setDomStorageEnabled(bool? flag) => (super.noSuchMethod(Invocation.method(#setDomStorageEnabled, [flag]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => + _i4.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => (super.noSuchMethod( Invocation.method(#setJavaScriptCanOpenWindowsAutomatically, [flag]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setSupportMultipleWindows(bool? support) => (super + _i4.Future setSupportMultipleWindows(bool? support) => (super .noSuchMethod(Invocation.method(#setSupportMultipleWindows, [support]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setJavaScriptEnabled(bool? flag) => + _i4.Future setJavaScriptEnabled(bool? flag) => (super.noSuchMethod(Invocation.method(#setJavaScriptEnabled, [flag]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setUserAgentString(String? userAgentString) => (super + _i4.Future setUserAgentString(String? userAgentString) => (super .noSuchMethod(Invocation.method(#setUserAgentString, [userAgentString]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setMediaPlaybackRequiresUserGesture(bool? require) => + _i4.Future setMediaPlaybackRequiresUserGesture(bool? require) => (super.noSuchMethod( Invocation.method(#setMediaPlaybackRequiresUserGesture, [require]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setSupportZoom(bool? support) => + _i4.Future setSupportZoom(bool? support) => (super.noSuchMethod(Invocation.method(#setSupportZoom, [support]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setLoadWithOverviewMode(bool? overview) => (super + _i4.Future setLoadWithOverviewMode(bool? overview) => (super .noSuchMethod(Invocation.method(#setLoadWithOverviewMode, [overview]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setUseWideViewPort(bool? use) => + _i4.Future setUseWideViewPort(bool? use) => (super.noSuchMethod(Invocation.method(#setUseWideViewPort, [use]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setDisplayZoomControls(bool? enabled) => + _i4.Future setDisplayZoomControls(bool? enabled) => (super.noSuchMethod(Invocation.method(#setDisplayZoomControls, [enabled]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setBuiltInZoomControls(bool? enabled) => + _i4.Future setBuiltInZoomControls(bool? enabled) => (super.noSuchMethod(Invocation.method(#setBuiltInZoomControls, [enabled]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override String toString() => super.toString(); } @@ -124,15 +117,15 @@ class MockWebView extends _i1.Mock implements _i2.WebView { (super.noSuchMethod(Invocation.getter(#settings), returnValue: _FakeWebSettings_0()) as _i2.WebSettings); @override - _i5.Future loadData( + _i4.Future loadData( {String? data, String? mimeType, String? encoding}) => (super.noSuchMethod( Invocation.method(#loadData, [], {#data: data, #mimeType: mimeType, #encoding: encoding}), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future loadDataWithBaseUrl( + _i4.Future loadDataWithBaseUrl( {String? baseUrl, String? data, String? mimeType, @@ -147,109 +140,109 @@ class MockWebView extends _i1.Mock implements _i2.WebView { #historyUrl: historyUrl }), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future loadUrl(String? url, Map? headers) => + _i4.Future loadUrl(String? url, Map? headers) => (super.noSuchMethod(Invocation.method(#loadUrl, [url, headers]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future postUrl(String? url, _i6.Uint8List? data) => + _i4.Future postUrl(String? url, _i5.Uint8List? data) => (super.noSuchMethod(Invocation.method(#postUrl, [url, data]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future getUrl() => + _i4.Future getUrl() => (super.noSuchMethod(Invocation.method(#getUrl, []), - returnValue: Future.value()) as _i5.Future); + returnValue: Future.value()) as _i4.Future); @override - _i5.Future canGoBack() => + _i4.Future canGoBack() => (super.noSuchMethod(Invocation.method(#canGoBack, []), - returnValue: Future.value(false)) as _i5.Future); + returnValue: Future.value(false)) as _i4.Future); @override - _i5.Future canGoForward() => + _i4.Future canGoForward() => (super.noSuchMethod(Invocation.method(#canGoForward, []), - returnValue: Future.value(false)) as _i5.Future); + returnValue: Future.value(false)) as _i4.Future); @override - _i5.Future goBack() => + _i4.Future goBack() => (super.noSuchMethod(Invocation.method(#goBack, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future goForward() => + _i4.Future goForward() => (super.noSuchMethod(Invocation.method(#goForward, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future reload() => + _i4.Future reload() => (super.noSuchMethod(Invocation.method(#reload, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future clearCache(bool? includeDiskFiles) => + _i4.Future clearCache(bool? includeDiskFiles) => (super.noSuchMethod(Invocation.method(#clearCache, [includeDiskFiles]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future evaluateJavascript(String? javascriptString) => (super + _i4.Future evaluateJavascript(String? javascriptString) => (super .noSuchMethod(Invocation.method(#evaluateJavascript, [javascriptString]), - returnValue: Future.value()) as _i5.Future); + returnValue: Future.value()) as _i4.Future); @override - _i5.Future getTitle() => + _i4.Future getTitle() => (super.noSuchMethod(Invocation.method(#getTitle, []), - returnValue: Future.value()) as _i5.Future); + returnValue: Future.value()) as _i4.Future); @override - _i5.Future scrollTo(int? x, int? y) => + _i4.Future scrollTo(int? x, int? y) => (super.noSuchMethod(Invocation.method(#scrollTo, [x, y]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future scrollBy(int? x, int? y) => + _i4.Future scrollBy(int? x, int? y) => (super.noSuchMethod(Invocation.method(#scrollBy, [x, y]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future getScrollX() => + _i4.Future getScrollX() => (super.noSuchMethod(Invocation.method(#getScrollX, []), - returnValue: Future.value(0)) as _i5.Future); + returnValue: Future.value(0)) as _i4.Future); @override - _i5.Future getScrollY() => + _i4.Future getScrollY() => (super.noSuchMethod(Invocation.method(#getScrollY, []), - returnValue: Future.value(0)) as _i5.Future); + returnValue: Future.value(0)) as _i4.Future); @override - _i5.Future setWebViewClient(_i2.WebViewClient? webViewClient) => + _i4.Future setWebViewClient(_i2.WebViewClient? webViewClient) => (super.noSuchMethod(Invocation.method(#setWebViewClient, [webViewClient]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future addJavaScriptChannel( + _i4.Future addJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method(#addJavaScriptChannel, [javaScriptChannel]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future removeJavaScriptChannel( + _i4.Future removeJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method(#removeJavaScriptChannel, [javaScriptChannel]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setDownloadListener(_i2.DownloadListener? listener) => + _i4.Future setDownloadListener(_i2.DownloadListener? listener) => (super.noSuchMethod(Invocation.method(#setDownloadListener, [listener]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future setWebChromeClient(_i2.WebChromeClient? client) => + _i4.Future setWebChromeClient(_i2.WebChromeClient? client) => (super.noSuchMethod(Invocation.method(#setWebChromeClient, [client]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override - _i5.Future release() => + _i4.Future release() => (super.noSuchMethod(Invocation.method(#release, []), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); + returnValueForMissingStub: Future.value()) as _i4.Future); @override String toString() => super.toString(); } @@ -258,16 +251,16 @@ class MockWebView extends _i1.Mock implements _i2.WebView { /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidDownloadListener extends _i1.Mock - implements _i7.WebViewAndroidDownloadListener { + implements _i6.WebViewAndroidDownloadListener { MockWebViewAndroidDownloadListener() { _i1.throwOnMissingStub(this); } @override - _i5.Future Function(String, Map?) get loadUrl => + _i4.Future Function(String, Map?) get loadUrl => (super.noSuchMethod(Invocation.getter(#loadUrl), returnValue: (String url, Map? headers) => - Future.value()) as _i5.Future Function( + Future.value()) as _i4.Future Function( String, Map?)); @override void onDownloadStart(String? url, String? userAgent, @@ -284,7 +277,7 @@ class MockWebViewAndroidDownloadListener extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidJavaScriptChannel extends _i1.Mock - implements _i7.WebViewAndroidJavaScriptChannel { + implements _i6.WebViewAndroidJavaScriptChannel { MockWebViewAndroidJavaScriptChannel() { _i1.throwOnMissingStub(this); } @@ -310,7 +303,7 @@ class MockWebViewAndroidJavaScriptChannel extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidWebChromeClient extends _i1.Mock - implements _i7.WebViewAndroidWebChromeClient { + implements _i6.WebViewAndroidWebChromeClient { MockWebViewAndroidWebChromeClient() { _i1.throwOnMissingStub(this); } @@ -327,7 +320,7 @@ class MockWebViewAndroidWebChromeClient extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidWebViewClient extends _i1.Mock - implements _i7.WebViewAndroidWebViewClient { + implements _i6.WebViewAndroidWebViewClient { MockWebViewAndroidWebViewClient() { _i1.throwOnMissingStub(this); } @@ -431,11 +424,11 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock } @override - _i5.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => + _i4.FutureOr onNavigationRequest({String? url, bool? isForMainFrame}) => (super.noSuchMethod( Invocation.method(#onNavigationRequest, [], {#url: url, #isForMainFrame: isForMainFrame}), - returnValue: Future.value(false)) as _i5.FutureOr); + returnValue: Future.value(false)) as _i4.FutureOr); @override void onPageStarted(String? url) => super.noSuchMethod(Invocation.method(#onPageStarted, [url]), @@ -459,7 +452,7 @@ class MockWebViewPlatformCallbacksHandler extends _i1.Mock /// A class which mocks [WebViewProxy]. /// /// See the documentation for Mockito's code generation for more information. -class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy { +class MockWebViewProxy extends _i1.Mock implements _i6.WebViewProxy { MockWebViewProxy() { _i1.throwOnMissingStub(this); } @@ -471,93 +464,11 @@ class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy { {#useHybridComposition: useHybridComposition}), returnValue: _FakeWebView_2()) as _i2.WebView); @override - _i5.Future setWebContentsDebuggingEnabled(bool? enabled) => + _i4.Future setWebContentsDebuggingEnabled(bool? enabled) => (super.noSuchMethod( Invocation.method(#setWebContentsDebuggingEnabled, [enabled]), returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - String toString() => super.toString(); -} - -/// A class which mocks [Client]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockClient extends _i1.Mock implements _i4.Client { - MockClient() { - _i1.throwOnMissingStub(this); - } - - @override - _i5.Future<_i4.Response> head(Uri? url, {Map? headers}) => - (super.noSuchMethod(Invocation.method(#head, [url], {#headers: headers}), - returnValue: Future<_i4.Response>.value(_FakeResponse_3())) - as _i5.Future<_i4.Response>); - @override - _i5.Future<_i4.Response> get(Uri? url, {Map? headers}) => - (super.noSuchMethod(Invocation.method(#get, [url], {#headers: headers}), - returnValue: Future<_i4.Response>.value(_FakeResponse_3())) - as _i5.Future<_i4.Response>); - @override - _i5.Future<_i4.Response> post(Uri? url, - {Map? headers, - Object? body, - _i8.Encoding? encoding}) => - (super.noSuchMethod( - Invocation.method(#post, [url], - {#headers: headers, #body: body, #encoding: encoding}), - returnValue: Future<_i4.Response>.value(_FakeResponse_3())) - as _i5.Future<_i4.Response>); - @override - _i5.Future<_i4.Response> put(Uri? url, - {Map? headers, - Object? body, - _i8.Encoding? encoding}) => - (super.noSuchMethod( - Invocation.method(#put, [url], - {#headers: headers, #body: body, #encoding: encoding}), - returnValue: Future<_i4.Response>.value(_FakeResponse_3())) - as _i5.Future<_i4.Response>); - @override - _i5.Future<_i4.Response> patch(Uri? url, - {Map? headers, - Object? body, - _i8.Encoding? encoding}) => - (super.noSuchMethod( - Invocation.method(#patch, [url], - {#headers: headers, #body: body, #encoding: encoding}), - returnValue: Future<_i4.Response>.value(_FakeResponse_3())) - as _i5.Future<_i4.Response>); - @override - _i5.Future<_i4.Response> delete(Uri? url, - {Map? headers, - Object? body, - _i8.Encoding? encoding}) => - (super.noSuchMethod( - Invocation.method(#delete, [url], - {#headers: headers, #body: body, #encoding: encoding}), - returnValue: Future<_i4.Response>.value(_FakeResponse_3())) - as _i5.Future<_i4.Response>); - @override - _i5.Future read(Uri? url, {Map? headers}) => - (super.noSuchMethod(Invocation.method(#read, [url], {#headers: headers}), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future<_i6.Uint8List> readBytes(Uri? url, - {Map? headers}) => - (super.noSuchMethod( - Invocation.method(#readBytes, [url], {#headers: headers}), - returnValue: Future<_i6.Uint8List>.value(_i6.Uint8List(0))) - as _i5.Future<_i6.Uint8List>); - @override - _i5.Future<_i4.StreamedResponse> send(_i4.BaseRequest? request) => - (super.noSuchMethod(Invocation.method(#send, [request]), - returnValue: - Future<_i4.StreamedResponse>.value(_FakeStreamedResponse_4())) - as _i5.Future<_i4.StreamedResponse>); - @override - void close() => super.noSuchMethod(Invocation.method(#close, []), - returnValueForMissingStub: null); + returnValueForMissingStub: Future.value()) as _i4.Future); @override String toString() => super.toString(); } From 61101166699fbfac7e7436c8dbc54afff852c432 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 6 Dec 2021 10:06:57 +0100 Subject: [PATCH 09/11] Enforce uri scheme --- .../lib/webview_android_widget.dart | 4 ++++ .../test/webview_android_widget_test.dart | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index f450a7f4d075..dad3e13f787e 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -175,6 +175,7 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { } @override + /// Makes a specific HTTP request ands loads the response in the webview. /// /// [WebViewRequest.method] must be one of the supported HTTP methods @@ -194,6 +195,9 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { Future loadRequest( WebViewRequest request, ) async { + if (!request.uri.hasScheme) { + throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); + } switch (request.method) { case WebViewRequestMethod.get: return webView.loadUrl(request.uri.toString(), request.headers); diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index 2b3291028f3b..a6b7e0772a4d 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart @@ -344,6 +344,20 @@ void main() { }); group('loadRequest', () { + testWidgets('Throws ArgumentError for empty scheme', + (WidgetTester tester) async { + await buildWidget(tester); + + expect( + () async => await testController.loadRequest( + WebViewRequest( + uri: Uri.parse('www.google.com'), + method: WebViewRequestMethod.get, + ), + ), + throwsA(const TypeMatcher())); + }); + testWidgets('GET without headers', (WidgetTester tester) async { await buildWidget(tester); @@ -695,4 +709,4 @@ void main() { }); }); }); -} \ No newline at end of file +} From f02ea2abdd42a045f2dfa0b01894cb8a4541aa52 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Mon, 6 Dec 2021 19:16:34 +0100 Subject: [PATCH 10/11] Processed PR feedback. --- .../example/lib/main.dart | 1 - .../lib/webview_android_widget.dart | 17 +---------------- .../test/webview_android_widget_test.dart | 17 ----------------- 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index c95a438c7b99..d3bd3153d165 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -344,7 +344,6 @@ class _SampleMenu extends StatelessWidget { final WebViewRequest request = WebViewRequest( uri: Uri.parse('https://httpbin.org/post'), method: WebViewRequestMethod.post, - headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, body: Uint8List.fromList('Test Body'.codeUnits), ); await controller.loadRequest(request); diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart index dad3e13f787e..b98098178e48 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart @@ -174,24 +174,9 @@ class WebViewAndroidPlatformController extends WebViewPlatformController { return webView.loadUrl(url, headers ?? {}); } - @override - - /// Makes a specific HTTP request ands loads the response in the webview. - /// - /// [WebViewRequest.method] must be one of the supported HTTP methods - /// in [WebViewRequestMethod]. - /// - /// If [WebViewRequest.headers] is not empty, its key-value pairs will be - /// added as the headers for the request. - /// - /// If [WebViewRequest.body] is not null, it will be added as the body - /// for the request. - /// - /// Throws an ArgumentError if [WebViewRequest.uri] has empty scheme. - /// - /// Android only: /// When making a POST request, headers are ignored. As a workaround, make /// the request manually and load the response data using [loadHTMLString]. + @override Future loadRequest( WebViewRequest request, ) async { diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart index a6b7e0772a4d..80895c592338 100644 --- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart @@ -416,23 +416,6 @@ void main() { body, )); }); - - testWidgets('POST ignores headers', (WidgetTester tester) async { - await buildWidget(tester); - - final Uint8List body = Uint8List.fromList('Test Body'.codeUnits); - - await testController.loadRequest(WebViewRequest( - uri: Uri.parse('https://www.google.com'), - method: WebViewRequestMethod.post, - headers: {'foo': 'bar'}, - body: body)); - - verify(mockWebView.postUrl( - 'https://www.google.com', - body, - )); - }); }); testWidgets('currentUrl', (WidgetTester tester) async { From 2c1a9a80b9005b869f9f1662ee5df116e11e5d82 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Tue, 7 Dec 2021 10:07:55 +0100 Subject: [PATCH 11/11] Trigger build