Commit 64f6995d authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

added onLoadHttpError event, fixed InAppWebView handleMethod, updated example, updated driver tests

parent 2a1e0b82
This diff is collapsed.
......@@ -29,7 +29,7 @@
- Added `onReceivedServerTrustAuthRequest` and `onReceivedClientCertRequest` events to manage SSL requests
- Added `onFindResultReceived` event, `findAllAsync`, `findNext` and `clearMatches` methods
- Added `shouldInterceptAjaxRequest`, `onAjaxReadyStateChange`, `onAjaxProgress` and `shouldInterceptFetchRequest` events with `useShouldInterceptAjaxRequest` and `useShouldInterceptFetchRequest` webview options
- Added `onNavigationStateChange` event
- Added `onNavigationStateChange` and `onLoadHttpError` events
- Fun: added `getTRexRunnerHtml` and `getTRexRunnerCss` methods to get html (with javascript) and css to recreate the Chromium's t-rex runner game
### BREAKING CHANGES
......
......@@ -7,8 +7,6 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.util.AttributeSet;
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
......@@ -31,7 +29,6 @@ import com.pichillilorenzo.flutter_inappbrowser.Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
......@@ -52,7 +49,7 @@ final public class InAppWebView extends InputAwareWebView {
public FlutterWebView flutterWebView;
public int id;
public InAppWebViewClient inAppWebViewClient;
public InAppWebChromeClient inAppWebChromeClient;
public InAppWebViewChromeClient inAppWebViewChromeClient;
public InAppWebViewOptions options;
public boolean isLoading = false;
public OkHttpClient httpClient;
......@@ -538,8 +535,8 @@ final public class InAppWebView extends InputAwareWebView {
addJavascriptInterface(new JavaScriptBridgeInterface((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView), JavaScriptBridgeInterface.name);
inAppWebChromeClient = new InAppWebChromeClient((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView, this.registrar);
setWebChromeClient(inAppWebChromeClient);
inAppWebViewChromeClient = new InAppWebViewChromeClient((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView, this.registrar);
setWebChromeClient(inAppWebViewChromeClient);
inAppWebViewClient = new InAppWebViewClient((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView);
setWebViewClient(inAppWebViewClient);
......
......@@ -40,7 +40,7 @@ import io.flutter.plugin.common.PluginRegistry;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
public class InAppWebChromeClient extends WebChromeClient implements PluginRegistry.ActivityResultListener {
public class InAppWebViewChromeClient extends WebChromeClient implements PluginRegistry.ActivityResultListener {
protected static final String LOG_TAG = "IABWebChromeClient";
private PluginRegistry.Registrar registrar;
......@@ -56,7 +56,7 @@ public class InAppWebChromeClient extends WebChromeClient implements PluginRegis
private int mOriginalOrientation;
private int mOriginalSystemUiVisibility;
public InAppWebChromeClient(Object obj, PluginRegistry.Registrar registrar) {
public InAppWebViewChromeClient(Object obj, PluginRegistry.Registrar registrar) {
super();
this.registrar = registrar;
if (obj instanceof InAppBrowserActivity)
......
......@@ -237,6 +237,21 @@ public class InAppWebViewClient extends WebViewClient {
getChannel().invokeMethod("onLoadError", obj);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
if(request.isForMainFrame()) {
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("url", request.getUrl().toString());
obj.put("statusCode", errorResponse.getStatusCode());
obj.put("description", errorResponse.getReasonPhrase());
getChannel().invokeMethod("onLoadHttpError", obj);
}
}
/**
* On received http auth request.
*/
......@@ -297,7 +312,8 @@ public class InAppWebViewClient extends WebViewClient {
} else {
handler.cancel();
}
//handler.useHttpAuthUsernamePassword();
// used custom CredentialDatabase!
// handler.useHttpAuthUsernamePassword();
return;
case 0:
default:
......
......@@ -56,19 +56,19 @@ class _ChromeSafariBrowserExampleScreenState
ListTile(
title: Text('InAppBrowser'),
onTap: () {
Navigator.popAndPushNamed(context, '/InAppBrowser');
Navigator.pushReplacementNamed(context, '/InAppBrowser');
},
),
ListTile(
title: Text('ChromeSafariBrowser'),
onTap: () {
Navigator.popAndPushNamed(context, '/ChromeSafariBrowser');
Navigator.pushReplacementNamed(context, '/ChromeSafariBrowser');
},
),
ListTile(
title: Text('InAppWebView'),
onTap: () {
Navigator.popAndPushNamed(context, '/');
Navigator.pushReplacementNamed(context, '/');
},
),
],
......
......@@ -133,19 +133,19 @@ class _InAppBrowserExampleScreenState extends State<InAppBrowserExampleScreen> {
ListTile(
title: Text('InAppBrowser'),
onTap: () {
Navigator.popAndPushNamed(context, '/InAppBrowser');
Navigator.pushReplacementNamed(context, '/InAppBrowser');
},
),
ListTile(
title: Text('ChromeSafariBrowser'),
onTap: () {
Navigator.popAndPushNamed(context, '/ChromeSafariBrowser');
Navigator.pushReplacementNamed(context, '/ChromeSafariBrowser');
},
),
ListTile(
title: Text('InAppWebView'),
onTap: () {
Navigator.popAndPushNamed(context, '/');
Navigator.pushReplacementNamed(context, '/');
},
),
],
......
......@@ -83,22 +83,19 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
ListTile(
title: Text('InAppBrowser'),
onTap: () {
Navigator.popAndPushNamed(context, '/InAppBrowser');
dispose();
Navigator.pushReplacementNamed(context, '/InAppBrowser');
},
),
ListTile(
title: Text('ChromeSafariBrowser'),
onTap: () {
Navigator.popAndPushNamed(context, '/ChromeSafariBrowser');
dispose();
Navigator.pushReplacementNamed(context, '/ChromeSafariBrowser');
},
),
ListTile(
title: Text('InAppWebView'),
onTap: () {
Navigator.popAndPushNamed(context, '/');
dispose();
Navigator.pushReplacementNamed(context, '/');
},
),
],
......@@ -163,7 +160,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
databaseEnabled: true,
domStorageEnabled: true,
geolocationEnabled: true,
//safeBrowsingEnabled: true,
safeBrowsingEnabled: true,
//blockNetworkImage: true,
),
),
......@@ -241,6 +238,10 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
</html>
""");
},
onLoadHttpError: (InAppWebViewController controller, String url,
int statusCode, String description) async {
print("HTTP error $url: $statusCode, $description");
},
onProgressChanged:
(InAppWebViewController controller, int progress) {
setState(() {
......@@ -364,7 +365,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
onSafeBrowsingHit: (InAppWebViewController controller,
String url, SafeBrowsingThreat threatType) async {
SafeBrowsingResponseAction action =
SafeBrowsingResponseAction.BACK_TO_SAFETY;
SafeBrowsingResponseAction.SHOW_INTERSTITIAL;
return new SafeBrowsingResponse(report: true, action: action);
},
onReceivedHttpAuthRequest: (InAppWebViewController controller,
......@@ -376,7 +377,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
username: "USERNAME",
password: "PASSWORD",
action: HttpAuthResponseAction
.USE_SAVED_HTTP_AUTH_CREDENTIALS,
.PROCEED,
permanentPersistence: true);
},
onReceivedServerTrustAuthRequest:
......
......@@ -206,5 +206,29 @@ void main() {
}, timeout: new Timeout(new Duration(minutes: 5)));
test('InAppWebViewOnSafeBrowsingHitTest', () async {
await Future.delayed(const Duration(milliseconds: 2000));
final appBarTitle = find.byValueKey('AppBarTitle');
while((await driver.getText(appBarTitle)) == "InAppWebViewOnSafeBrowsingHitTest") {
await Future.delayed(const Duration(milliseconds: 1000));
}
String url = await driver.getText(appBarTitle);
expect(url, "chrome://safe-browsing/match?type=malware");
}, timeout: new Timeout(new Duration(minutes: 5)));
test('InAppWebViewOnReceivedHttpAuthRequestTest', () async {
await Future.delayed(const Duration(milliseconds: 2000));
final appBarTitle = find.byValueKey('AppBarTitle');
while((await driver.getText(appBarTitle)) == "InAppWebViewOnReceivedHttpAuthRequestTest") {
await Future.delayed(const Duration(milliseconds: 1000));
}
String title = await driver.getText(appBarTitle);
expect(title, "Authorized");
}, timeout: new Timeout(new Duration(minutes: 5)));
});
}
\ No newline at end of file
......@@ -14,7 +14,7 @@ class InAppWebViewInitialUrlTest extends WidgetTest {
}
class InAppWebViewInitialUrlTestState extends WidgetTestState {
String initialUrl = "https://flutter.dev/";
String appBarTitle = "InAppWebViewInitialUrlTest";
@override
......@@ -26,7 +26,7 @@ class InAppWebViewInitialUrlTestState extends WidgetTestState {
Expanded(
child: Container(
child: InAppWebView(
initialUrl: initialUrl,
initialUrl: "https://flutter.dev/",
initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
......
import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
import 'custom_widget_test.dart';
import 'main_test.dart';
import 'util_test.dart';
class InAppWebViewOnReceivedHttpAuthRequestTest extends WidgetTest {
final InAppWebViewOnReceivedHttpAuthRequestTestState state = InAppWebViewOnReceivedHttpAuthRequestTestState();
@override
InAppWebViewOnReceivedHttpAuthRequestTestState createState() => state;
}
class InAppWebViewOnReceivedHttpAuthRequestTestState extends WidgetTestState {
String appBarTitle = "InAppWebViewOnReceivedHttpAuthRequestTest";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(state: this, title: appBarTitle),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrl: "http://192.168.1.20:8081/",
initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
clearCache: true,
debuggingEnabled: true
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
},
onLoadStop: (InAppWebViewController controller, String url) async {
String h1Content = await controller.evaluateJavascript(source: "document.body.querySelector('h1').textContent");
setState(() {
appBarTitle = h1Content;
});
nextTest(context: context, state: this);
},
onReceivedHttpAuthRequest: (InAppWebViewController controller, HttpAuthChallenge challenge) async {
return new HttpAuthResponse(
username: "USERNAME",
password: "PASSWORD",
action: HttpAuthResponseAction.PROCEED,
permanentPersistence: true);
},
),
),
),
])
)
);
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
import 'custom_widget_test.dart';
import 'main_test.dart';
import 'util_test.dart';
class InAppWebViewOnSafeBrowsingHitTest extends WidgetTest {
final InAppWebViewOnSafeBrowsingHitTestState state = InAppWebViewOnSafeBrowsingHitTestState();
@override
InAppWebViewOnSafeBrowsingHitTestState createState() => state;
}
class InAppWebViewOnSafeBrowsingHitTestState extends WidgetTestState {
String appBarTitle = "InAppWebViewOnSafeBrowsingHitTest";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(state: this, title: appBarTitle),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrl: "chrome://safe-browsing/match?type=malware",
initialHeaders: {},
initialOptions: InAppWebViewWidgetOptions(
inAppWebViewOptions: InAppWebViewOptions(
// if I set javaScriptEnabled to true, it will crash!
javaScriptEnabled: false,
clearCache: true,
debuggingEnabled: true
),
androidInAppWebViewOptions: AndroidInAppWebViewOptions(
databaseEnabled: true,
domStorageEnabled: true,
safeBrowsingEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
if(Platform.isAndroid)
controller.startSafeBrowsing();
},
onLoadStart: (InAppWebViewController controller, String url) {
},
onLoadStop: (InAppWebViewController controller, String url) {
setState(() {
appBarTitle = url;
});
nextTest(context: context, state: this);
},
onSafeBrowsingHit: (InAppWebViewController controller, String url, SafeBrowsingThreat threatType) async {
return SafeBrowsingResponse(report: true, action: SafeBrowsingResponseAction.PROCEED);
},
),
),
),
])
)
);
}
}
......@@ -13,6 +13,8 @@ import 'in_app_webview_on_download_start_test.dart';
import 'in_app_webview_on_js_dialog_test.dart';
import 'in_app_webview_on_load_resource_custom_scheme_test.dart';
import 'in_app_webview_on_load_resource_test.dart';
import 'in_app_webview_on_received_http_auth_request_test.dart';
import 'in_app_webview_on_safe_browsing_hit_test.dart';
import 'in_app_webview_on_target_blank_test.dart';
import 'in_app_webview_should_override_url_loading_test.dart';
......@@ -32,6 +34,8 @@ Map<String, WidgetBuilder> buildRoutes({@required BuildContext context}) {
'/InAppWebViewOnDownloadStartTest': (context) => InAppWebViewOnDownloadStartTest(),
'/InAppWebViewOnTargetBlankTest': (context) => InAppWebViewOnTargetBlankTest(),
'/InAppWebViewOnJsDialogTest': (context) => InAppWebViewOnJsDialogTest(),
'/InAppWebViewOnSafeBrowsingHitTest': (context) => InAppWebViewOnSafeBrowsingHitTest(),
'/InAppWebViewOnReceivedHttpAuthRequestTest': (context) => InAppWebViewOnReceivedHttpAuthRequestTest(),
};
routes.forEach((k, v) => testRoutes.add(k));
return routes;
......
......@@ -1340,6 +1340,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
public func webView(_ webView: WKWebView,
decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if navigationResponse.isForMainFrame, let response = navigationResponse.response as? HTTPURLResponse {
if response.statusCode >= 400 {
onLoadHttpError(url: response.url!.absoluteString, statusCode: response.statusCode, description: "")
}
}
if (options?.useOnDownloadStart)! {
let mimeType = navigationResponse.response.mimeType
......@@ -1427,7 +1432,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
switch action {
case 0:
InAppWebView.credentialsProposed = []
completionHandler(.cancelAuthenticationChallenge, nil)
// used .performDefaultHandling to mantain consistency with Android
// because .cancelAuthenticationChallenge will call webView(_:didFail:withError:)
completionHandler(.performDefaultHandling, nil)
//completionHandler(.cancelAuthenticationChallenge, nil)
break
case 1:
let username = response["username"] as! String
......@@ -1853,6 +1861,16 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
public func onLoadHttpError(url: String, statusCode: Int, description: String) {
var arguments: [String: Any] = ["url": url, "statusCode": statusCode, "description": description]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onLoadHttpError", arguments: arguments)
}
}
public func onProgressChanged(progress: Int) {
var arguments: [String: Any] = ["progress": progress]
if IABController != nil {
......
......@@ -42,7 +42,9 @@ class ContentBlockerTriggerResourceType {
return (["document", "image", "style-sheet", "script", "font",
"media", "svg-document", "raw"].contains(value)) ? ContentBlockerTriggerResourceType._internal(value) : null;
}
toValue() => _value;
String toValue() => _value;
@override
String toString() => _value;
static const DOCUMENT = const ContentBlockerTriggerResourceType._internal('document');
static const IMAGE = const ContentBlockerTriggerResourceType._internal('image');
......@@ -67,7 +69,9 @@ class ContentBlockerTriggerLoadType {
static ContentBlockerTriggerLoadType fromValue(String value) {
return (["first-party", "third-party"].contains(value)) ? ContentBlockerTriggerLoadType._internal(value) : null;
}
toValue() => _value;
String toValue() => _value;
@override
String toString() => _value;
///FIRST_PARTY is triggered only if the resource has the same scheme, domain, and port as the main page resource.
static const FIRST_PARTY = const ContentBlockerTriggerLoadType._internal('first-party');
......@@ -187,7 +191,9 @@ class ContentBlockerActionType {
static ContentBlockerActionType fromValue(String value) {
return (["block", "css-display-none", "make-https"].contains(value)) ? ContentBlockerActionType._internal(value) : null;
}
toValue() => _value;
String toValue() => _value;
@override
String toString() => _value;
///Stops loading of the resource. If the resource was cached, the cache is ignored.
static const BLOCK = const ContentBlockerActionType._internal('block');
......
......@@ -301,6 +301,19 @@ class InAppBrowser {
}
///Event fires when the [InAppBrowser] main page receives an HTTP error.
///
///[url] represents the url of the main page that received the HTTP error.
///
///[statusCode] represents the status code of the response. HTTP errors have status codes >= 400.
///
///[description] represents the description of the HTTP error. On iOS, it is always an empty string.
///
///**NOTE**: available on Android 23+.
void onLoadHttpError(String url, int statusCode, String description) {
}
///Event fires when the current [progress] (range 0-100) of loading a page is changed.
void onProgressChanged(int progress) {
......
......@@ -35,6 +35,17 @@ class InAppWebView extends StatefulWidget {
///Event fires when the [InAppWebView] encounters an error loading an [url].
final void Function(InAppWebViewController controller, String url, int code, String message) onLoadError;
///Event fires when the [InAppWebView] main page receives an HTTP error.
///
///[url] represents the url of the main page that received the HTTP error.
///
///[statusCode] represents the status code of the response. HTTP errors have status codes >= 400.
///
///[description] represents the description of the HTTP error. On iOS, it is always an empty string.
///
///**NOTE**: available on Android 23+.
final void Function(InAppWebViewController controller, String url, int statusCode, String description) onLoadHttpError;
///Event fires when the current [progress] of loading a page is changed.
final void Function(InAppWebViewController controller, int progress) onProgressChanged;
......@@ -233,6 +244,7 @@ class InAppWebView extends StatefulWidget {
this.onLoadStart,
this.onLoadStop,
this.onLoadError,
this.onLoadHttpError,
this.onConsoleMessage,
this.onProgressChanged,
this.shouldOverrideUrlLoading,
......@@ -401,6 +413,15 @@ class InAppWebViewController {
else if (_inAppBrowser != null)
_inAppBrowser.onLoadError(url, code, message);
break;
case "onLoadHttpError":
String url = call.arguments["url"];
int statusCode = call.arguments["statusCode"];
String description = call.arguments["description"];
if (_widget != null && _widget.onLoadHttpError != null)
_widget.onLoadHttpError(this, url, statusCode, description);
else if (_inAppBrowser != null)
_inAppBrowser.onLoadHttpError(url, statusCode, description);
break;
case "onProgressChanged":
int progress = call.arguments["progress"];
if (_widget != null && _widget.onProgressChanged != null)
......@@ -499,7 +520,7 @@ class InAppWebViewController {
case "onSafeBrowsingHit":
String url = call.arguments["url"];
SafeBrowsingThreat threatType = SafeBrowsingThreat.fromValue(call.arguments["threatType"]);
if (_widget != null && _widget.onJsPrompt != null)
if (_widget != null && _widget.onSafeBrowsingHit != null)
return (await _widget.onSafeBrowsingHit(this, url, threatType))?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onSafeBrowsingHit(url, threatType))?.toMap();
......@@ -548,7 +569,7 @@ class InAppWebViewController {
int activeMatchOrdinal = call.arguments["activeMatchOrdinal"];
int numberOfMatches = call.arguments["numberOfMatches"];
bool isDoneCounting = call.arguments["isDoneCounting"];
if (_widget != null && _widget.onReceivedClientCertRequest != null)
if (_widget != null && _widget.onFindResultReceived != null)
_widget.onFindResultReceived(this, activeMatchOrdinal, numberOfMatches, isDoneCounting);
else if (_inAppBrowser != null)
_inAppBrowser.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting);
......
This diff is collapsed.
......@@ -64,10 +64,19 @@ https.createServer(options, appHttps).listen(4433)
appAuthBasic.use((req, res, next) => {
let user = auth(req)
if (user === undefined || user['name'] !== 'user 1' || user['pass'] !== 'password 1') {
if (user === undefined || user['name'] !== 'USERNAME' || user['pass'] !== 'PASSWORD') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
res.end('Unauthorized')
res.send(`
<html>
<head>
</head>
<body>
<h1>Unauthorized</h1>
</body>
</html>
`);
res.end()
} else {
next()
}
......@@ -80,7 +89,7 @@ appAuthBasic.get("/", (req, res) => {
<head>
</head>
<body>
<p>HELLO</p>
<h1>Authorized</h1>
</body>
</html>
`);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment