Commit b4e3e73b authored by pichillilorenzo's avatar pichillilorenzo

updated onLoadResource method, InAppBrowser.open url parameter has the default value: 'about:blank'

parent d696ed1e
This diff is collapsed.
package com.pichillilorenzo.flutter_inappbrowser; package com.pichillilorenzo.flutter_inappbrowser;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.net.http.SslError; import android.net.http.SslError;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.CookieSyncManager; import android.webkit.CookieSyncManager;
import android.webkit.HttpAuthHandler; import android.webkit.HttpAuthHandler;
import android.webkit.MimeTypeMap;
import android.webkit.SslErrorHandler; import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest; import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse; import android.webkit.WebResourceResponse;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
...@@ -278,28 +269,55 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -278,28 +269,55 @@ public class InAppBrowserWebViewClient extends WebViewClient {
Request mRequest = new Request.Builder().url(url).build(); Request mRequest = new Request.Builder().url(url).build();
try { try {
long loadingTime = System.currentTimeMillis();
Response response = activity.httpClient.newCall(mRequest).execute(); Response response = activity.httpClient.newCall(mRequest).execute();
loadingTime = System.currentTimeMillis() - loadingTime;
String reasonPhrase = response.message(); String reasonPhrase = response.message();
if (reasonPhrase.equals("")) { if (reasonPhrase.equals("")) {
reasonPhrase = statusCodeMapping.get(response.code()); reasonPhrase = statusCodeMapping.get(response.code());
Log.d(LOG_TAG, reasonPhrase);
} }
reasonPhrase = (reasonPhrase.equals("") || reasonPhrase == null) ? "OK" : reasonPhrase; reasonPhrase = (reasonPhrase.equals("") || reasonPhrase == null) ? "OK" : reasonPhrase;
Map<String, String> headers = new HashMap<String, String>(); Map<String, String> headersResponse = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) { for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
String value = ""; String value = "";
for (String val: entry.getValue()) { for (String val: entry.getValue()) {
value += (value == "") ? val : "; " + val; value += (value == "") ? val : "; " + val;
} }
headers.put(entry.getKey().toLowerCase(), value); headersResponse.put(entry.getKey().toLowerCase(), value);
}
Map<String, String> headersRequest = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : mRequest.headers().toMultimap().entrySet()) {
String value = "";
for (String val: entry.getValue()) {
value += (value == "") ? val : "; " + val;
}
headersRequest.put(entry.getKey().toLowerCase(), value);
} }
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
Map<String, Object> res = new HashMap<>();
Map<String, Object> req = new HashMap<>();
obj.put("uuid", activity.uuid); obj.put("uuid", activity.uuid);
obj.put("url", url);
obj.put("statusCode", response.code()); byte[] dataBytes = response.body().bytes();
obj.put("headers", headers); InputStream dataStream = new ByteArrayInputStream(dataBytes);
res.put("url", url);
res.put("statusCode", response.code());
res.put("headers", headersResponse);
res.put("loadingTime", loadingTime);
res.put("data", dataBytes);
req.put("url", url);
req.put("headers", headersRequest);
req.put("method", mRequest.method());
obj.put("response", res);
obj.put("request", req);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadResource", obj); InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadResource", obj);
...@@ -308,8 +326,8 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -308,8 +326,8 @@ public class InAppBrowserWebViewClient extends WebViewClient {
response.header("content-encoding"), response.header("content-encoding"),
response.code(), response.code(),
reasonPhrase, reasonPhrase,
headers, headersResponse,
response.body().byteStream() dataStream
); );
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
......
...@@ -72,10 +72,10 @@ class MyInAppBrowser extends InAppBrowser { ...@@ -72,10 +72,10 @@ class MyInAppBrowser extends InAppBrowser {
} }
@override @override
void onLoadResource(String url, int statusCode, Map<String, String> headers) { void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
print("\n\n resource: $url\n\n"); print(response.loadingTime.toString() + "ms " + response.url);
print(statusCode); if (response.headers["content-length"] != null)
print(headers); print(response.headers["content-length"] + " length");
} }
@override @override
...@@ -138,7 +138,7 @@ class _MyAppState extends State<MyApp> { ...@@ -138,7 +138,7 @@ class _MyAppState extends State<MyApp> {
body: new Center( body: new Center(
child: new RaisedButton(onPressed: () { child: new RaisedButton(onPressed: () {
//chromeSafariBrowser.open("https://flutter.io/"); //chromeSafariBrowser.open("https://flutter.io/");
inAppBrowserFallback.open("https://flutter.io/", options: { inAppBrowserFallback.open(url: "https://flutter.io/", options: {
//"hidden": true, //"hidden": true,
//"toolbarTopFixedTitle": "Fixed title", //"toolbarTopFixedTitle": "Fixed title",
//"useShouldOverrideUrlLoading": true //"useShouldOverrideUrlLoading": true
......
...@@ -131,13 +131,8 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio ...@@ -131,13 +131,8 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
URLProtocol.wk_registerScheme("http") MyURLProtocol.wkWebViewDelegateMap[uuid] = self
URLProtocol.wk_registerScheme("https")
MyURLProtocol.URLProtocolDelegate = self
URLProtocol.registerClass(MyURLProtocol.self)
webView.uiDelegate = self webView.uiDelegate = self
webView.navigationDelegate = self webView.navigationDelegate = self
...@@ -303,8 +298,21 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio ...@@ -303,8 +298,21 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
if ((browserOptions?.userAgent)! != "") { if ((browserOptions?.userAgent)! != "") {
if #available(iOS 9.0, *) { if #available(iOS 9.0, *) {
self.webView.customUserAgent = (browserOptions?.userAgent)! self.webView.customUserAgent = (browserOptions?.userAgent)!
} else { }
// Fallback on earlier versions }
// set uuid in the User-Agent in order to know which webview is making internal requests and
// to send the onLoadResource event to the correct webview
if #available(iOS 9.0, *) {
if (self.webView.customUserAgent != nil) {
self.webView.customUserAgent = self.webView.customUserAgent! + " WKWebView/" + self.uuid
}
else {
self.webView.evaluateJavaScript("navigator.userAgent") { [weak webView] (result, error) in
if let webView = self.webView, let userAgent = result as? String {
webView.customUserAgent = userAgent + " WKWebView/" + self.uuid
}
}
} }
} }
...@@ -491,7 +499,6 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio ...@@ -491,7 +499,6 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
func webView(_ webView: WKWebView, func webView(_ webView: WKWebView,
decidePolicyFor navigationResponse: WKNavigationResponse, decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
//dump((navigationResponse.response as! HTTPURLResponse)) //dump((navigationResponse.response as! HTTPURLResponse))
//print(navigationResponse.response.mimeType) //print(navigationResponse.response.mimeType)
//print(navigationResponse.response.url) //print(navigationResponse.response.url)
...@@ -569,8 +576,8 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio ...@@ -569,8 +576,8 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
navigationDelegate?.onLoadError(uuid: self.uuid, webView: webView, error: error) navigationDelegate?.onLoadError(uuid: self.uuid, webView: webView, error: error)
} }
func didReceiveResponse(_ response: URLResponse, from request: URLRequest?) { func didReceiveResponse(_ response: URLResponse, fromRequest request: URLRequest?, withData data: Data, loadingTime time: Int) {
navigationDelegate?.onLoadResource(uuid: self.uuid, webView: webView, response: response) navigationDelegate?.onLoadResource(uuid: self.uuid, webView: webView, response: response, fromRequest: request, withData: data, loadingTime: time)
} }
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
......
...@@ -6,29 +6,47 @@ ...@@ -6,29 +6,47 @@
// //
import Foundation import Foundation
import WebKit
func currentTimeInMilliSeconds() -> Int {
let currentDate = Date()
let since1970 = currentDate.timeIntervalSince1970
return Int(since1970 * 1000)
}
class MyURLProtocol: URLProtocol { class MyURLProtocol: URLProtocol {
struct Constants { // struct Constants {
static let RequestHandledKey = "URLProtocolRequestHandled" // static let RequestHandledKey = "URLProtocolRequestHandled"
} // }
var wkWebViewUuid: String?
var session: URLSession? var session: URLSession?
var sessionTask: URLSessionDataTask? var sessionTask: URLSessionDataTask?
static var URLProtocolDelegate: MyURLProtocolDelegate? var response: URLResponse?
var data: Data?
static var wkWebViewDelegateMap: [String: MyURLProtocolDelegate] = [:]
var loadingTime: Int = 0
override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
super.init(request: request, cachedResponse: cachedResponse, client: client) super.init(request: request, cachedResponse: cachedResponse, client: client)
if session == nil { self.wkWebViewUuid = MyURLProtocol.getUuid(request)
if session == nil && self.wkWebViewUuid != nil {
session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
} }
} }
override class func canInit(with request: URLRequest) -> Bool { override class func canInit(with request: URLRequest) -> Bool {
if MyURLProtocol.property(forKey: Constants.RequestHandledKey, in: request) != nil {
if getUuid(request) == nil {
return false return false
} }
// if MyURLProtocol.property(forKey: Constants.RequestHandledKey, in: request) != nil {
// return false
// }
return true return true
} }
...@@ -38,25 +56,54 @@ class MyURLProtocol: URLProtocol { ...@@ -38,25 +56,54 @@ class MyURLProtocol: URLProtocol {
override func startLoading() { override func startLoading() {
let newRequest = ((request as NSURLRequest).mutableCopy() as? NSMutableURLRequest)! let newRequest = ((request as NSURLRequest).mutableCopy() as? NSMutableURLRequest)!
MyURLProtocol.setProperty(true, forKey: Constants.RequestHandledKey, in: newRequest) loadingTime = currentTimeInMilliSeconds()
//MyURLProtocol.setProperty(true, forKey: Constants.RequestHandledKey, in: newRequest)
sessionTask = session?.dataTask(with: newRequest as URLRequest) sessionTask = session?.dataTask(with: newRequest as URLRequest)
sessionTask?.resume() sessionTask?.resume()
} }
override func stopLoading() { override func stopLoading() {
if let uuid = self.wkWebViewUuid {
if MyURLProtocol.wkWebViewDelegateMap[uuid] != nil && self.response != nil {
loadingTime = currentTimeInMilliSeconds() - loadingTime
if self.data == nil {
self.data = Data()
}
MyURLProtocol.wkWebViewDelegateMap[uuid]!.didReceiveResponse(self.response!, fromRequest: request, withData: self.data!, loadingTime: loadingTime)
}
}
sessionTask?.cancel() sessionTask?.cancel()
} }
class func getUuid(_ request: URLRequest?) -> String? {
let userAgent: String? = request?.allHTTPHeaderFields?["User-Agent"]
var uuid: String? = nil
if userAgent != nil {
if userAgent!.contains("WKWebView/") {
let userAgentSplitted = userAgent!.split(separator: " ")
uuid = String(userAgentSplitted[userAgentSplitted.count-1]).replacingOccurrences(of: "WKWebView/", with: "")
}
}
return uuid
}
} }
extension MyURLProtocol: URLSessionDataDelegate { extension MyURLProtocol: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if self.data == nil {
self.data = data
}
else {
self.data!.append(data)
}
client?.urlProtocol(self, didLoad: data) client?.urlProtocol(self, didLoad: data)
} }
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
let policy = URLCache.StoragePolicy(rawValue: request.cachePolicy.rawValue) ?? .notAllowed let policy = URLCache.StoragePolicy(rawValue: request.cachePolicy.rawValue) ?? .notAllowed
self.response = response
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: policy) client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: policy)
MyURLProtocol.URLProtocolDelegate?.didReceiveResponse(response, from: dataTask.currentRequest)
completionHandler(.allow) completionHandler(.allow)
} }
...@@ -98,5 +145,5 @@ extension MyURLProtocol: URLSessionDataDelegate { ...@@ -98,5 +145,5 @@ extension MyURLProtocol: URLSessionDataDelegate {
} }
protocol MyURLProtocolDelegate { protocol MyURLProtocolDelegate {
func didReceiveResponse(_ response: URLResponse, from request: URLRequest?) func didReceiveResponse(_ response: URLResponse, fromRequest request: URLRequest?, withData data: Data, loadingTime time: Int)
} }
...@@ -46,6 +46,9 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -46,6 +46,9 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
} }
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
URLProtocol.wk_registerScheme("http")
URLProtocol.wk_registerScheme("https")
URLProtocol.registerClass(MyURLProtocol.self)
let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger())
let instance = SwiftFlutterPlugin(with: registrar) let instance = SwiftFlutterPlugin(with: registrar)
registrar.addMethodCallDelegate(instance, channel: channel) registrar.addMethodCallDelegate(instance, channel: channel)
...@@ -489,16 +492,28 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -489,16 +492,28 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
} }
} }
func onLoadResource(uuid: String, webView: WKWebView, response: URLResponse) { func onLoadResource(uuid: String, webView: WKWebView, response: URLResponse, fromRequest request: URLRequest?, withData data: Data, loadingTime time: Int) {
if self.webViewControllers[uuid] != nil { if self.webViewControllers[uuid] != nil {
var headers = (response as! HTTPURLResponse).allHeaderFields as! [String: String] var headersResponse = (response as! HTTPURLResponse).allHeaderFields as! [String: String]
headers.lowercaseKeys() headersResponse.lowercaseKeys()
var headersRequest = request!.allHTTPHeaderFields! as [String: String]
headersRequest.lowercaseKeys()
let arguments: [String : Any] = [ let arguments: [String : Any] = [
"uuid": uuid, "uuid": uuid,
"url": response.url?.absoluteString ?? "", "response": [
"statusCode": (response as! HTTPURLResponse).statusCode, "url": response.url!.absoluteString,
"headers": headers "statusCode": (response as! HTTPURLResponse).statusCode,
"headers": headersResponse,
"loadingTime": time,
"data": data
],
"request": [
"url": request!.url!.absoluteString,
"headers": headersRequest,
"method": request!.httpMethod!
]
] ]
channel.invokeMethod("onLoadResource", arguments: arguments) channel.invokeMethod("onLoadResource", arguments: arguments)
} }
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
...@@ -34,6 +35,28 @@ enum ConsoleMessageLevel { ...@@ -34,6 +35,28 @@ enum ConsoleMessageLevel {
DEBUG, ERROR, LOG, TIP, WARNING DEBUG, ERROR, LOG, TIP, WARNING
} }
class WebResourceRequest {
String url;
Map<String, String> headers;
String method;
WebResourceRequest(this.url, this.headers, this.method);
}
class WebResourceResponse {
String url;
Map<String, String> headers;
int statusCode;
int loadingTime;
Uint8List data;
WebResourceResponse(this.url, this.headers, this.statusCode, this.loadingTime, this.data);
}
///Public class representing a JavaScript console message from WebCore. ///Public class representing a JavaScript console message from WebCore.
///This could be a issued by a call to one of the console logging functions (e.g. console.log('...')) or a JavaScript error on the page. ///This could be a issued by a call to one of the console logging functions (e.g. console.log('...')) or a JavaScript error on the page.
/// ///
...@@ -107,10 +130,27 @@ class InAppBrowser { ...@@ -107,10 +130,27 @@ class InAppBrowser {
shouldOverrideUrlLoading(url); shouldOverrideUrlLoading(url);
break; break;
case "onLoadResource": case "onLoadResource":
String url = call.arguments["url"]; Map<dynamic, dynamic> rawResponse = call.arguments["response"];
int statusCode = call.arguments["statusCode"]; rawResponse = rawResponse.cast<String, dynamic>();
Map<dynamic, dynamic> headers = call.arguments["headers"]; Map<dynamic, dynamic> rawRequest = call.arguments["request"];
onLoadResource(url, statusCode, headers.cast<String, String>()); rawRequest = rawRequest.cast<String, dynamic>();
String urlResponse = rawResponse["url"];
Map<dynamic, dynamic> headersResponse = rawResponse["headers"];
headersResponse = headersResponse.cast<String, String>();
int statusCode = rawResponse["statusCode"];
int loadingTime = rawResponse["loadingTime"];
Uint8List data = rawResponse["data"];
String urlRequest = rawRequest["url"];
Map<dynamic, dynamic> headersRequest = rawRequest["headers"];
headersRequest = headersResponse.cast<String, String>();
String method = rawRequest["method"];
var response = new WebResourceResponse(urlResponse, headersResponse, statusCode, loadingTime, data);
var request = new WebResourceRequest(urlRequest, headersRequest, method);
onLoadResource(response, request);
break; break;
case "onConsoleMessage": case "onConsoleMessage":
String sourceURL = call.arguments["sourceURL"]; String sourceURL = call.arguments["sourceURL"];
...@@ -187,7 +227,7 @@ class InAppBrowser { ...@@ -187,7 +227,7 @@ class InAppBrowser {
/// - __allowsInlineMediaPlayback__: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. For this to work, add the `webkit-playsinline` attribute to any `<video>` elements. The default value is `false`. /// - __allowsInlineMediaPlayback__: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. For this to work, add the `webkit-playsinline` attribute to any `<video>` elements. The default value is `false`.
/// - __allowsPictureInPictureMediaPlayback__: Set to `true` to allow HTML5 videos play picture-in-picture. The default value is `true`. /// - __allowsPictureInPictureMediaPlayback__: Set to `true` to allow HTML5 videos play picture-in-picture. The default value is `true`.
/// - __spinner__: Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`. /// - __spinner__: Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`.
Future<void> open(String url, {Map<String, String> headers = const {}, String target = "_self", Map<String, dynamic> options = const {}}) async { Future<void> open({String url = "about:blank", Map<String, String> headers = const {}, String target = "_self", Map<String, dynamic> options = const {}}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => url); args.putIfAbsent('url', () => url);
...@@ -328,8 +368,8 @@ class InAppBrowser { ...@@ -328,8 +368,8 @@ class InAppBrowser {
} }
///Event fires when the [InAppBrowser] webview will load the resource specified by the given [url]. ///Event fires when the [InAppBrowser] webview will load the resource specified by the given [WebResourceRequest].
void onLoadResource(String url, int statusCode, Map<String, String> headers) { void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
} }
......
name: flutter_inappbrowser name: flutter_inappbrowser
description: A Flutter plugin that allows you to open an in-app browser window. (inspired by the popular cordova-plugin-inappbrowser). description: A Flutter plugin that allows you to open an in-app browser window. (inspired by the popular cordova-plugin-inappbrowser).
version: 0.2.1 version: 0.3.0
author: Lorenzo Pichilli <pichillilorenzo@gmail.com> author: Lorenzo Pichilli <pichillilorenzo@gmail.com>
homepage: https://github.com/pichillilorenzo/flutter_inappbrowser homepage: https://github.com/pichillilorenzo/flutter_inappbrowser
......
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