Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/webview_flutter/webview_flutter_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ void loadDataWithBaseUrl(

void loadUrl(Long instanceId, String url, Map<String, String> headers);

void postUrl(Long instanceId, String url, byte[] data);

String getUrl(Long instanceId);

Boolean canGoBack(Long instanceId);
Expand Down Expand Up @@ -407,6 +409,39 @@ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.postUrl", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
Map<String, Object> wrapped = new HashMap<>();
try {
ArrayList<Object> args = (ArrayList<Object>) 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<Object> channel =
new BasicMessageChannel<>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,12 @@ public void loadUrl(Long instanceId, String url, Map<String, String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -158,6 +159,7 @@ enum _MenuOptions {
navigationDelegate,
loadLocalFile,
loadHtmlString,
doPostRequest,
}

class _SampleMenu extends StatelessWidget {
Expand Down Expand Up @@ -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) => <PopupMenuItem<_MenuOptions>>[
Expand Down Expand Up @@ -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'),
),
],
);
},
Expand Down Expand Up @@ -330,6 +339,17 @@ class _SampleMenu extends StatelessWidget {
await controller.loadHtmlString(kExamplePage);
}

Future<void> _onDoPostRequest(
WebViewController controller, BuildContext context) async {
final WebViewRequest request = WebViewRequest(
uri: Uri.parse('https://httpbin.org/post'),
method: WebViewRequestMethod.post,
headers: <String, String>{'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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,11 @@ class WebViewController {
return _webViewPlatformController.loadUrl(url, headers);
}

/// Loads a page by making the specified request.
Future<void> 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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/foundation.dart';
import 'package:flutter/widgets.dart' show AndroidViewSurface;

Expand Down Expand Up @@ -166,6 +168,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<void> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,33 @@ class WebViewHostApi {
}
}

Future<void> postUrl(
int arg_instanceId, String arg_url, Uint8List arg_data) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.WebViewHostApi.postUrl', codec,
binaryMessenger: _binaryMessenger);
final Map<Object?, Object?>? replyMap =
await channel.send(<Object>[arg_instanceId, arg_url, arg_data])
as Map<Object?, Object?>?;
if (replyMap == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
details: null,
);
} else if (replyMap['error'] != null) {
final Map<Object?, Object?> error =
(replyMap['error'] as Map<Object?, Object?>?)!;
throw PlatformException(
code: (error['code'] as String?)!,
message: error['message'] as String?,
details: error['details'],
);
} else {
return;
}
}

Future<String> getUrl(int arg_instanceId) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.WebViewHostApi.getUrl', codec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -152,6 +154,15 @@ class WebViewHostApiImpl extends WebViewHostApi {
return loadUrl(instanceManager.getInstanceId(instance)!, url, headers);
}

/// Helper method to convert instances ids to objects.
Future<void> postUrlFromInstance(
WebView instance,
String url,
Uint8List data,
) {
return postUrl(instanceManager.getInstanceId(instance)!, url, data);
}

/// Helper method to convert instances ids to objects.
Future<String> getUrlFromInstance(WebView instance) {
return getUrl(instanceManager.getInstanceId(instance)!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -120,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;

Expand Down Expand Up @@ -148,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<void> loadHtmlString(String html, {String? baseUrl}) {
return webView.loadDataWithBaseUrl(
Expand All @@ -174,6 +184,46 @@ class WebViewAndroidPlatformController extends WebViewPlatformController {
return webView.loadUrl(url, headers ?? <String, String>{});
}

@override
Future<void> 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 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,
);
}
default:
throw UnimplementedError(
'This version of webview_android_widget currently has no implementation for HTTP method ${request.method.serialize()} in loadRequest.',
);
}
}

@override
Future<String?> currentUrl() => webView.getUrl();

Expand Down Expand Up @@ -282,6 +332,46 @@ class WebViewAndroidPlatformController extends WebViewPlatformController {

Future<void> _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<HTTPResponseWithUrl> postUrlAndFollowRedirects(Uri uri,
{Map<String, String>? headers,
Uint8List? body,
int redirections = 0}) async {
final http.Request req = http.Request('POST', uri)
..followRedirects = false
..bodyBytes = body ?? <int>[];
req.headers.addAll(headers ?? <String, String>{});
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) {
Expand Down Expand Up @@ -636,3 +726,17 @@ 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ abstract class WebViewHostApi {
Map<String, String> headers,
);

void postUrl(
int instanceId,
String url,
Uint8List data,
);

String getUrl(int instanceId);

bool canGoBack(int instanceId);
Expand Down
Loading