Commit 88d88bd1 authored by pichillilorenzo's avatar pichillilorenzo

fixed onLoadResource, added InAppBrowser.addJavaScriptHandler() and...

fixed onLoadResource, added InAppBrowser.addJavaScriptHandler() and InAppBrowser.removeJavaScriptHandler() methods, v0.3.0
parent b4e3e73b
This diff is collapsed.
## 0.3.0
- fixed WebView.storyboard to deployment target 8.0
- added `InAppBrowser.onLoadResource()` method. The event fires when the InAppBrowser webview loads a resource
- added `InAppBrowser.addJavaScriptHandler()` and `InAppBrowser.removeJavaScriptHandler()` methods to add/remove javascript message handlers
- removed `keyboardDisplayRequiresUserAction` from iOS available options
- now the `url` parameter of `InAppBrowser.open()` is optional. The default value is `about:blank`
## 0.2.1 ## 0.2.1
- added InAppBrowser.onConsoleMessage() method to manage console messages - added `InAppBrowser.onConsoleMessage()` method to manage console messages
- fixed InAppBrowser.injectScriptCode() method when there is not a return value - fixed `InAppBrowser.injectScriptCode()` method when there is not a return value
## 0.2.0 ## 0.2.0
......
...@@ -39,6 +39,10 @@ class MyInAppBrowser extends InAppBrowser { ...@@ -39,6 +39,10 @@ class MyInAppBrowser extends InAppBrowser {
@override @override
Future onLoadStop(String url) async { Future onLoadStop(String url) async {
print("\n\nStopped $url\n\n"); print("\n\nStopped $url\n\n");
// call a javascript message handler
await this.injectScriptCode("window.flutter_inappbrowser.callHandler('handlerNameTest', 1, 5,'string', {'key': 5}, [4,6,8]);");
// print body html // print body html
print(await this.injectScriptCode("document.body.innerHTML")); print(await this.injectScriptCode("document.body.innerHTML"));
...@@ -78,6 +82,11 @@ class MyInAppBrowser extends InAppBrowser { ...@@ -78,6 +82,11 @@ class MyInAppBrowser extends InAppBrowser {
this.loadUrl(url); this.loadUrl(url);
} }
@override
void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
print("Started at: " + response.startTime.toString() + "ms ---> duration: " + response.duration.toString() + "ms " + response.url);
}
@override @override
void onConsoleMessage(ConsoleMessage consoleMessage) { void onConsoleMessage(ConsoleMessage consoleMessage) {
print(""" print("""
...@@ -105,6 +114,12 @@ class _MyAppState extends State<MyApp> { ...@@ -105,6 +114,12 @@ class _MyAppState extends State<MyApp> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// listen for post messages coming from the JavaScript side
int indexTest = inAppBrowserFallback.addJavaScriptHandler("handlerNameTest", (arguments) async {
print("handlerNameTest arguments");
print(arguments); // it prints: [1, 5, string, {key: 5}, [4, 6, 8]]
});
} }
@override @override
...@@ -117,7 +132,8 @@ class _MyAppState extends State<MyApp> { ...@@ -117,7 +132,8 @@ class _MyAppState extends State<MyApp> {
body: new Center( body: new Center(
child: new RaisedButton(onPressed: () { child: new RaisedButton(onPressed: () {
inAppBrowser.open("https://flutter.io/", options: { inAppBrowser.open("https://flutter.io/", options: {
"useShouldOverrideUrlLoading": true "useShouldOverrideUrlLoading": true,
"useOnLoadResource": true
}); });
}, },
child: Text("Open InAppBrowser") child: Text("Open InAppBrowser")
...@@ -134,12 +150,12 @@ class _MyAppState extends State<MyApp> { ...@@ -134,12 +150,12 @@ class _MyAppState extends State<MyApp> {
Opens a URL in a new InAppBrowser instance or the system browser. Opens a URL in a new InAppBrowser instance or the system browser.
```dart ```dart
inAppBrowser.open(String url, {Map<String, String> headers = const {}, String target = "_self", Map<String, dynamic> options = const {}}); inAppBrowser.open({String url = "about:blank", Map<String, String> headers = const {}, String target = "_self", Map<String, dynamic> options = const {}});
``` ```
Opens an `url` in a new `InAppBrowser` instance or the system browser. Opens an `url` in a new `InAppBrowser` instance or the system browser.
- `url`: The `url` to load. Call `encodeUriComponent()` on this if the `url` contains Unicode characters. - `url`: The `url` to load. Call `encodeUriComponent()` on this if the `url` contains Unicode characters. The default value is `about:blank`.
- `headers`: The additional headers to be used in the HTTP request for this URL, specified as a map from name to value. - `headers`: The additional headers to be used in the HTTP request for this URL, specified as a map from name to value.
...@@ -153,6 +169,7 @@ Opens an `url` in a new `InAppBrowser` instance or the system browser. ...@@ -153,6 +169,7 @@ Opens an `url` in a new `InAppBrowser` instance or the system browser.
All platforms support: All platforms support:
- __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the `shouldOverrideUrlLoading` event. The default value is `false`. - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the `shouldOverrideUrlLoading` event. The default value is `false`.
- __useOnLoadResource__: Set to `true` to be able to listen at the `onLoadResource()` event. The default value is `false`.
- __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`. - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`.
- __userAgent___: Set the custom WebView's user-agent. - __userAgent___: Set the custom WebView's user-agent.
- __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`. - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`.
...@@ -200,6 +217,7 @@ Example: ...@@ -200,6 +217,7 @@ Example:
```dart ```dart
inAppBrowser.open('https://flutter.io/', options: { inAppBrowser.open('https://flutter.io/', options: {
"useShouldOverrideUrlLoading": true, "useShouldOverrideUrlLoading": true,
"useOnLoadResource": true,
"clearCache": true, "clearCache": true,
"disallowOverScroll": true, "disallowOverScroll": true,
"domStorageEnabled": true, "domStorageEnabled": true,
...@@ -252,7 +270,8 @@ Event fires when the `InAppBrowser` webview receives a `ConsoleMessage`. ...@@ -252,7 +270,8 @@ Event fires when the `InAppBrowser` webview receives a `ConsoleMessage`.
``` ```
Give the host application a chance to take control when a URL is about to be loaded in the current WebView. Give the host application a chance to take control when a URL is about to be loaded in the current WebView.
In order to be able to listen this event, you need to set `useShouldOverrideUrlLoading` option to `true`.
**NOTE**: In order to be able to listen this event, you need to set `useShouldOverrideUrlLoading` option to `true`.
```dart ```dart
@override @override
void shouldOverrideUrlLoading(String url) { void shouldOverrideUrlLoading(String url) {
...@@ -260,6 +279,18 @@ In order to be able to listen this event, you need to set `useShouldOverrideUrlL ...@@ -260,6 +279,18 @@ In order to be able to listen this event, you need to set `useShouldOverrideUrlL
} }
``` ```
Event fires when the `InAppBrowser` webview loads a resource.
**NOTE**: In order to be able to listen this event, you need to set `useOnLoadResource` option to `true`.
**NOTE only for iOS**: In some cases, the `response.data` of a `response` with `text/html` encoding could be empty.
```dart
@override
void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
}
```
#### Future\<void\> InAppBrowser.loadUrl #### Future\<void\> InAppBrowser.loadUrl
Loads the given `url` with optional `headers` specified as a map from name to value. Loads the given `url` with optional `headers` specified as a map from name to value.
...@@ -372,6 +403,30 @@ Injects a CSS file into the `InAppBrowser` window. (Only available when the targ ...@@ -372,6 +403,30 @@ Injects a CSS file into the `InAppBrowser` window. (Only available when the targ
inAppBrowser.injectStyleFile(String urlFile); inAppBrowser.injectStyleFile(String urlFile);
``` ```
#### int InAppBrowser.addJavaScriptHandler
Adds/Appends a JavaScript message handler `callback` (`JavaScriptHandlerCallback`) that listen to post messages sent from JavaScript by the handler with name `handlerName`.
Returns the position `index` of the handler that can be used to remove it with the `removeJavaScriptHandler()` method.
The Android implementation uses [addJavascriptInterface](https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object,%20java.lang.String)).
The iOS implementation uses [addScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-addscriptmessagehandler?language=objc)
The JavaScript function that can be used to call the handler is `window.flutter_inappbrowser.callHandler(handlerName <String>, ...args);`, where `args` are [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
The `args` will be stringified automatically using `JSON.stringify(args)` method and then they will be decoded on the Dart side.
```dart
inAppBrowser.addJavaScriptHandler(String handlerName, JavaScriptHandlerCallback callback);
```
#### bool InAppBrowser.removeJavaScriptHandler
Removes a JavaScript message handler previously added with the `addJavaScriptHandler()` method in the `handlerName` list by its position `index`.
Returns `true` if the callback is removed, otherwise `false`.
```dart
inAppBrowser.removeJavaScriptHandler(String handlerName, int index);
```
### `ChromeSafariBrowser` class ### `ChromeSafariBrowser` class
Create a Class that extends the `ChromeSafariBrowser` Class in order to override the callbacks to manage the browser events. Example: Create a Class that extends the `ChromeSafariBrowser` Class in order to override the callbacks to manage the browser events. Example:
```dart ```dart
......
...@@ -5,6 +5,7 @@ public class InAppBrowserOptions extends Options { ...@@ -5,6 +5,7 @@ public class InAppBrowserOptions extends Options {
final static String LOG_TAG = "InAppBrowserOptions"; final static String LOG_TAG = "InAppBrowserOptions";
public boolean useShouldOverrideUrlLoading = false; public boolean useShouldOverrideUrlLoading = false;
public boolean useOnLoadResource = false;
public boolean clearCache = false; public boolean clearCache = false;
public String userAgent = ""; public String userAgent = "";
public boolean javaScriptEnabled = true; public boolean javaScriptEnabled = true;
......
...@@ -31,6 +31,7 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -31,6 +31,7 @@ public class InAppBrowserWebViewClient extends WebViewClient {
protected static final String LOG_TAG = "IABWebViewClient"; protected static final String LOG_TAG = "IABWebViewClient";
private WebViewActivity activity; private WebViewActivity activity;
Map<Integer, String> statusCodeMapping = new HashMap<Integer, String>(); Map<Integer, String> statusCodeMapping = new HashMap<Integer, String>();
long startPageTime = 0;
public InAppBrowserWebViewClient(WebViewActivity activity) { public InAppBrowserWebViewClient(WebViewActivity activity) {
super(); super();
...@@ -164,6 +165,8 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -164,6 +165,8 @@ public class InAppBrowserWebViewClient extends WebViewClient {
public void onPageStarted(WebView view, String url, Bitmap favicon) { public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon); super.onPageStarted(view, url, favicon);
startPageTime = System.currentTimeMillis();
activity.isLoading = true; activity.isLoading = true;
if (activity.searchView != null && !url.equals(activity.searchView.getQuery().toString())) { if (activity.searchView != null && !url.equals(activity.searchView.getQuery().toString())) {
...@@ -195,7 +198,8 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -195,7 +198,8 @@ public class InAppBrowserWebViewClient extends WebViewClient {
view.requestFocus(); view.requestFocus();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
view.evaluateJavascript(activity.jsConsoleLogScript, null); view.evaluateJavascript(WebViewActivity.consoleLogJS, null);
view.evaluateJavascript(JavaScriptBridgeInterface.flutterInAppBroserJSClass, null);
} }
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
...@@ -264,14 +268,24 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -264,14 +268,24 @@ public class InAppBrowserWebViewClient extends WebViewClient {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override @Override
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request){ public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request){
if (!request.getMethod().toLowerCase().equals("get") || !activity.options.useOnLoadResource) {
return null;
}
final String url = request.getUrl().toString(); final String url = request.getUrl().toString();
try {
Request mRequest = new Request.Builder().url(url).build(); Request mRequest = new Request.Builder().url(url).build();
try { long startResourceTime = System.currentTimeMillis();
long loadingTime = System.currentTimeMillis();
Response response = activity.httpClient.newCall(mRequest).execute(); Response response = activity.httpClient.newCall(mRequest).execute();
loadingTime = System.currentTimeMillis() - loadingTime; long startTime = startResourceTime - startPageTime;
long duration = System.currentTimeMillis() - startResourceTime;
if (response.cacheResponse() != null) {
duration = 0;
}
String reasonPhrase = response.message(); String reasonPhrase = response.message();
if (reasonPhrase.equals("")) { if (reasonPhrase.equals("")) {
...@@ -281,20 +295,20 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -281,20 +295,20 @@ public class InAppBrowserWebViewClient extends WebViewClient {
Map<String, String> headersResponse = 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 = ""; StringBuilder value = new StringBuilder();
for (String val: entry.getValue()) { for (String val: entry.getValue()) {
value += (value == "") ? val : "; " + val; value.append( (value.toString().isEmpty()) ? val : "; " + val );
} }
headersResponse.put(entry.getKey().toLowerCase(), value); headersResponse.put(entry.getKey().toLowerCase(), value.toString());
} }
Map<String, String> headersRequest = new HashMap<String, String>(); Map<String, String> headersRequest = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : mRequest.headers().toMultimap().entrySet()) { for (Map.Entry<String, List<String>> entry : mRequest.headers().toMultimap().entrySet()) {
String value = ""; StringBuilder value = new StringBuilder();
for (String val: entry.getValue()) { for (String val: entry.getValue()) {
value += (value == "") ? val : "; " + val; value.append( (value.toString().isEmpty()) ? val : "; " + val );
} }
headersRequest.put(entry.getKey().toLowerCase(), value); headersRequest.put(entry.getKey().toLowerCase(), value.toString());
} }
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
...@@ -309,7 +323,8 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -309,7 +323,8 @@ public class InAppBrowserWebViewClient extends WebViewClient {
res.put("url", url); res.put("url", url);
res.put("statusCode", response.code()); res.put("statusCode", response.code());
res.put("headers", headersResponse); res.put("headers", headersResponse);
res.put("loadingTime", loadingTime); res.put("startTime", startTime);
res.put("duration", duration);
res.put("data", dataBytes); res.put("data", dataBytes);
req.put("url", url); req.put("url", url);
...@@ -332,7 +347,11 @@ public class InAppBrowserWebViewClient extends WebViewClient { ...@@ -332,7 +347,11 @@ public class InAppBrowserWebViewClient extends WebViewClient {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
Log.d(LOG_TAG, e.getMessage()); Log.d(LOG_TAG, e.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.d(LOG_TAG, e.getMessage());
} }
return null; return null;
} }
......
package com.pichillilorenzo.flutter_inappbrowser;
import android.webkit.JavascriptInterface;
import java.util.HashMap;
import java.util.Map;
public class JavaScriptBridgeInterface {
private static final String LOG_TAG = "JSBridgeInterface";
static final String name = "flutter_inappbrowser";
WebViewActivity activity;
static final String flutterInAppBroserJSClass = "window." + name + ".callHandler = function(handlerName, ...args) {\n" +
"window." + name + "._callHandler(handlerName, JSON.stringify(args));\n" +
"}\n";
JavaScriptBridgeInterface(WebViewActivity a) {
activity = a;
}
@JavascriptInterface
public void _callHandler(String handlerName, String args) {
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("handlerName", handlerName);
obj.put("args", args);
InAppBrowserFlutterPlugin.channel.invokeMethod("onCallJsHandler", obj);
}
}
...@@ -23,6 +23,7 @@ import java.util.HashMap; ...@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
import okhttp3.Cache;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
public class WebViewActivity extends AppCompatActivity { public class WebViewActivity extends AppCompatActivity {
...@@ -40,7 +41,7 @@ public class WebViewActivity extends AppCompatActivity { ...@@ -40,7 +41,7 @@ public class WebViewActivity extends AppCompatActivity {
public boolean isHidden = false; public boolean isHidden = false;
OkHttpClient httpClient; OkHttpClient httpClient;
static final String jsConsoleLogScript = "(function() {\n"+ static final String consoleLogJS = "(function() {\n"+
" var oldLogs = {\n"+ " var oldLogs = {\n"+
" 'log': console.log,\n"+ " 'log': console.log,\n"+
" 'debug': console.debug,\n"+ " 'debug': console.debug,\n"+
...@@ -89,14 +90,18 @@ public class WebViewActivity extends AppCompatActivity { ...@@ -89,14 +90,18 @@ public class WebViewActivity extends AppCompatActivity {
prepareWebView(); prepareWebView();
httpClient = new OkHttpClient(); int cacheSize = 10 * 1024 * 1024; // 10MB
httpClient = new OkHttpClient().newBuilder().cache(new Cache(getApplicationContext().getCacheDir(), cacheSize)).build();
webView.loadUrl(url, headers); webView.loadUrl(url, headers);
//webView.loadData("<!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> <title>Document</title> </head> <body> ciao <img src=\"https://via.placeholder.com/350x150\" /> <img src=\"./images/test\" alt=\"not found\" /></body> </html>", "text/html", "utf8");
} }
private void prepareWebView() { private void prepareWebView() {
webView.addJavascriptInterface(new JavaScriptBridgeInterface(this), JavaScriptBridgeInterface.name);
inAppBrowserWebChromeClient = new InAppBrowserWebChromeClient(this); inAppBrowserWebChromeClient = new InAppBrowserWebChromeClient(this);
webView.setWebChromeClient(inAppBrowserWebChromeClient); webView.setWebChromeClient(inAppBrowserWebChromeClient);
......
...@@ -13,8 +13,9 @@ class MyInAppBrowser extends InAppBrowser { ...@@ -13,8 +13,9 @@ class MyInAppBrowser extends InAppBrowser {
Future onLoadStop(String url) async { Future onLoadStop(String url) async {
print("\n\nStopped $url\n\n"); print("\n\nStopped $url\n\n");
// // javascript error await this.injectScriptCode("window.flutter_inappbrowser.callHandler('handlerTest', 1, 5,'string', {'key': 5}, [4,6,8]);");
// await this.injectScriptCode("console.log({'testJavaScriptError': 5}));"); await this.injectScriptCode("window.flutter_inappbrowser.callHandler('handlerTest2', false, null, undefined);");
await this.injectScriptCode("setTimeout(function(){window.flutter_inappbrowser.callHandler('handlerTest', 'anotherString');}, 1000);");
// //
// await this.injectScriptCode("console.log({'testObject': 5});"); // await this.injectScriptCode("console.log({'testObject': 5});");
// await this.injectScriptCode("console.warn('testWarn',null);"); // await this.injectScriptCode("console.warn('testWarn',null);");
...@@ -73,20 +74,21 @@ class MyInAppBrowser extends InAppBrowser { ...@@ -73,20 +74,21 @@ class MyInAppBrowser extends InAppBrowser {
@override @override
void onLoadResource(WebResourceResponse response, WebResourceRequest request) { void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
print(response.loadingTime.toString() + "ms " + response.url);
if (response.headers["content-length"] != null) print("Started at: " + response.startTime.toString() + "ms ---> duration: " + response.duration.toString() + "ms " + response.url);
print(response.headers["content-length"] + " length"); // if (response.headers["content-length"] != null)
// print(response.headers["content-length"] + " length");
} }
@override @override
void onConsoleMessage(ConsoleMessage consoleMessage) { void onConsoleMessage(ConsoleMessage consoleMessage) {
print(""" // print("""
console output: // console output:
sourceURL: ${consoleMessage.sourceURL} // sourceURL: ${consoleMessage.sourceURL}
lineNumber: ${consoleMessage.lineNumber} // lineNumber: ${consoleMessage.lineNumber}
message: ${consoleMessage.message} // message: ${consoleMessage.message}
messageLevel: ${consoleMessage.messageLevel} // messageLevel: ${consoleMessage.messageLevel}
"""); // """);
} }
} }
...@@ -112,9 +114,12 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser { ...@@ -112,9 +114,12 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser {
} }
} }
// adding a webview fallback
MyChromeSafariBrowser chromeSafariBrowser = new MyChromeSafariBrowser(inAppBrowserFallback); MyChromeSafariBrowser chromeSafariBrowser = new MyChromeSafariBrowser(inAppBrowserFallback);
void main() => runApp(new MyApp()); void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@override @override
...@@ -126,6 +131,15 @@ class _MyAppState extends State<MyApp> { ...@@ -126,6 +131,15 @@ class _MyAppState extends State<MyApp> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
int indexTest = inAppBrowserFallback.addJavaScriptHandler("handlerTest", (arguments) async {
print("handlerTest arguments");
print(arguments);
});
int indexTest2 = inAppBrowserFallback.addJavaScriptHandler("test2", (arguments) async {
print("handlerTest2 arguments");
print(arguments);
inAppBrowserFallback.removeJavaScriptHandler("test", indexTest);
});
} }
@override @override
...@@ -139,6 +153,7 @@ class _MyAppState extends State<MyApp> { ...@@ -139,6 +153,7 @@ class _MyAppState extends State<MyApp> {
child: new RaisedButton(onPressed: () { child: new RaisedButton(onPressed: () {
//chromeSafariBrowser.open("https://flutter.io/"); //chromeSafariBrowser.open("https://flutter.io/");
inAppBrowserFallback.open(url: "https://flutter.io/", options: { inAppBrowserFallback.open(url: "https://flutter.io/", options: {
//"useOnLoadResource": true,
//"hidden": true, //"hidden": true,
//"toolbarTopFixedTitle": "Fixed title", //"toolbarTopFixedTitle": "Fixed title",
//"useShouldOverrideUrlLoading": true //"useShouldOverrideUrlLoading": true
......
...@@ -11,6 +11,7 @@ import Foundation ...@@ -11,6 +11,7 @@ import Foundation
public class InAppBrowserOptions: Options { public class InAppBrowserOptions: Options {
var useShouldOverrideUrlLoading = false var useShouldOverrideUrlLoading = false
var useOnLoadResource = false
var clearCache = false var clearCache = false
var userAgent = "" var userAgent = ""
var javaScriptEnabled = true var javaScriptEnabled = true
......
//
// MyURLProtocol.swift
// Pods
//
// Created by Lorenzo on 12/10/18.
//
import Foundation
import WebKit
func currentTimeInMilliSeconds() -> Int {
let currentDate = Date()
let since1970 = currentDate.timeIntervalSince1970
return Int(since1970 * 1000)
}
class MyURLProtocol: URLProtocol {
// struct Constants {
// static let RequestHandledKey = "URLProtocolRequestHandled"
// }
var wkWebViewUuid: String?
var session: URLSession?
var sessionTask: URLSessionDataTask?
var response: URLResponse?
var data: Data?
static var wkWebViewDelegateMap: [String: MyURLProtocolDelegate] = [:]
var loadingTime: Int = 0
override init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
super.init(request: request, cachedResponse: cachedResponse, client: client)
self.wkWebViewUuid = MyURLProtocol.getUuid(request)
if session == nil && self.wkWebViewUuid != nil {
session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
}
}
override class func canInit(with request: URLRequest) -> Bool {
if getUuid(request) == nil {
return false
}
// if MyURLProtocol.property(forKey: Constants.RequestHandledKey, in: request) != nil {
// return false
// }
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
let newRequest = ((request as NSURLRequest).mutableCopy() as? NSMutableURLRequest)!
loadingTime = currentTimeInMilliSeconds()
//MyURLProtocol.setProperty(true, forKey: Constants.RequestHandledKey, in: newRequest)
sessionTask = session?.dataTask(with: newRequest as URLRequest)
sessionTask?.resume()
}
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()
}
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 {
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)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
let policy = URLCache.StoragePolicy(rawValue: request.cachePolicy.rawValue) ?? .notAllowed
self.response = response
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: policy)
completionHandler(.allow)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
client?.urlProtocol(self, didFailWithError: error)
} else {
client?.urlProtocolDidFinishLoading(self)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response)
completionHandler(request)
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
guard let error = error else { return }
client?.urlProtocol(self, didFailWithError: error)
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let protectionSpace = challenge.protectionSpace
let sender = challenge.sender
if protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
if let serverTrust = protectionSpace.serverTrust {
let credential = URLCredential(trust: serverTrust)
sender?.use(credential, for: challenge)
completionHandler(.useCredential, credential)
return
}
}
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
client?.urlProtocolDidFinishLoading(self)
}
}
protocol MyURLProtocolDelegate {
func didReceiveResponse(_ response: URLResponse, fromRequest request: URLRequest?, withData data: Data, loadingTime time: Int)
}
//
// NSURLProtocol+WKWebVIew.h
// Pods
//
// Created by Lorenzo on 11/10/18.
//
#ifndef NSURLProtocol_WKWebVIew_h
#define NSURLProtocol_WKWebVIew_h
#endif /* NSURLProtocol_WKWebVIew_h */
#import <Foundation/Foundation.h>
@interface NSURLProtocol (WKWebVIew)
+ (void)wk_registerScheme:(NSString*)scheme;
+ (void)wk_unregisterScheme:(NSString*)scheme;
@end
//
// NSURLProtocol+WKWebVIew.m
// Pods
//
// Created by Lorenzo on 11/10/18.
//
#import <Foundation/Foundation.h>
#import "NSURLProtocol+WKWebVIew.h"
#import <WebKit/WebKit.h>
//FOUNDATION_STATIC_INLINE 属于属于runtime范畴,你的.m文件需要频繁调用一个函数,可以用static inline来声明。从SDWebImage从get到的。
FOUNDATION_STATIC_INLINE Class ContextControllerClass() {
static Class cls;
if (!cls) {
cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class];
}
return cls;
}
FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() {
return NSSelectorFromString(@"registerSchemeForCustomProtocol:");
}
FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() {
return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:");
}
@implementation NSURLProtocol (WebKitSupport)
+ (void)wk_registerScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = RegisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
+ (void)wk_unregisterScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = UnregisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
@end
...@@ -22,6 +22,19 @@ import Foundation ...@@ -22,6 +22,19 @@ import Foundation
import AVFoundation import AVFoundation
import SafariServices import SafariServices
//class CustomURLCache: URLCache {
// override func cachedResponse(for request: URLRequest) -> CachedURLResponse? {
// dump(request.url)
// return super.cachedResponse(for: request)
// }
//
// override func getCachedResponse(for dataTask: URLSessionDataTask,
// completionHandler: @escaping (CachedURLResponse?) -> Void) {
// dump(dataTask.response)
// super.getCachedResponse(for: dataTask, completionHandler: completionHandler)
// }
//}
let WEBVIEW_STORYBOARD = "WebView" let WEBVIEW_STORYBOARD = "WebView"
let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController" let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController"
...@@ -46,9 +59,12 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -46,9 +59,12 @@ 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("http")
URLProtocol.wk_registerScheme("https") // URLProtocol.wk_registerScheme("https")
URLProtocol.registerClass(MyURLProtocol.self) // URLProtocol.registerClass(MyURLProtocol.self)
//URLCache.shared = CustomURLCache()
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)
...@@ -423,7 +439,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -423,7 +439,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
if error != nil { if error != nil {
let userInfo = (error! as NSError).userInfo let userInfo = (error! as NSError).userInfo
self.onConsoleMessage(uuid: uuid, sourceURL: (userInfo["WKJavaScriptExceptionSourceURL"] as! URL).absoluteString, lineNumber: userInfo["WKJavaScriptExceptionLineNumber"] as! Int, message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: "ERROR") dump(userInfo)
self.onConsoleMessage(uuid: uuid, sourceURL: (userInfo["WKJavaScriptExceptionSourceURL"] as? URL)?.absoluteString ?? "", lineNumber: userInfo["WKJavaScriptExceptionLineNumber"] as! Int, message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: "ERROR")
} }
if value == nil { if value == nil {
...@@ -492,7 +509,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -492,7 +509,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
} }
} }
func onLoadResource(uuid: String, webView: WKWebView, response: URLResponse, fromRequest request: URLRequest?, withData data: Data, loadingTime time: Int) { func onLoadResource(uuid: String, webView: WKWebView, response: URLResponse, fromRequest request: URLRequest?, withData data: Data, startTime: Int, duration: Int) {
if self.webViewControllers[uuid] != nil { if self.webViewControllers[uuid] != nil {
var headersResponse = (response as! HTTPURLResponse).allHeaderFields as! [String: String] var headersResponse = (response as! HTTPURLResponse).allHeaderFields as! [String: String]
headersResponse.lowercaseKeys() headersResponse.lowercaseKeys()
...@@ -506,7 +523,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -506,7 +523,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
"url": response.url!.absoluteString, "url": response.url!.absoluteString,
"statusCode": (response as! HTTPURLResponse).statusCode, "statusCode": (response as! HTTPURLResponse).statusCode,
"headers": headersResponse, "headers": headersResponse,
"loadingTime": time, "startTime": startTime,
"duration": duration,
"data": data "data": data
], ],
"request": [ "request": [
...@@ -551,6 +569,10 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -551,6 +569,10 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
channel.invokeMethod("onChromeSafariBrowserClosed", arguments: ["uuid": uuid]) channel.invokeMethod("onChromeSafariBrowserClosed", arguments: ["uuid": uuid])
} }
func onCallJsHandler(uuid: String, webView: WKWebView, handlerName: String, args: String) {
channel.invokeMethod("onCallJsHandler", arguments: ["uuid": uuid, "handlerName": handlerName, "args": args])
}
func safariExit(uuid: String) { func safariExit(uuid: String) {
if let safariViewController = self.safariViewControllers[uuid] { if let safariViewController = self.safariViewControllers[uuid] {
if #available(iOS 9.0, *) { if #available(iOS 9.0, *) {
......
...@@ -22,11 +22,13 @@ ...@@ -22,11 +22,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:convert';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
typedef Future<dynamic> ListenerCallback(MethodCall call); typedef Future<dynamic> ListenerCallback(MethodCall call);
typedef Future<void> JavaScriptHandlerCallback(List<dynamic> arguments);
var _uuidGenerator = new Uuid(); var _uuidGenerator = new Uuid();
...@@ -35,6 +37,8 @@ enum ConsoleMessageLevel { ...@@ -35,6 +37,8 @@ enum ConsoleMessageLevel {
DEBUG, ERROR, LOG, TIP, WARNING DEBUG, ERROR, LOG, TIP, WARNING
} }
///Public class representing a resource request of the [InAppBrowser] WebView.
///It is used by the method [InAppBrowser.onLoadResource()].
class WebResourceRequest { class WebResourceRequest {
String url; String url;
...@@ -45,15 +49,18 @@ class WebResourceRequest { ...@@ -45,15 +49,18 @@ class WebResourceRequest {
} }
///Public class representing a resource response of the [InAppBrowser] WebView.
///It is used by the method [InAppBrowser.onLoadResource()].
class WebResourceResponse { class WebResourceResponse {
String url; String url;
Map<String, String> headers; Map<String, String> headers;
int statusCode; int statusCode;
int loadingTime; int startTime;
int duration;
Uint8List data; Uint8List data;
WebResourceResponse(this.url, this.headers, this.statusCode, this.loadingTime, this.data); WebResourceResponse(this.url, this.headers, this.statusCode, this.startTime, this.duration, this.data);
} }
...@@ -99,6 +106,7 @@ class _ChannelManager { ...@@ -99,6 +106,7 @@ class _ChannelManager {
class InAppBrowser { class InAppBrowser {
String uuid; String uuid;
Map<String, List<JavaScriptHandlerCallback>> javaScriptHandlersMap = HashMap<String, List<JavaScriptHandlerCallback>>();
/// ///
InAppBrowser () { InAppBrowser () {
...@@ -139,7 +147,8 @@ class InAppBrowser { ...@@ -139,7 +147,8 @@ class InAppBrowser {
Map<dynamic, dynamic> headersResponse = rawResponse["headers"]; Map<dynamic, dynamic> headersResponse = rawResponse["headers"];
headersResponse = headersResponse.cast<String, String>(); headersResponse = headersResponse.cast<String, String>();
int statusCode = rawResponse["statusCode"]; int statusCode = rawResponse["statusCode"];
int loadingTime = rawResponse["loadingTime"]; int startTime = rawResponse["startTime"];
int duration = rawResponse["duration"];
Uint8List data = rawResponse["data"]; Uint8List data = rawResponse["data"];
String urlRequest = rawRequest["url"]; String urlRequest = rawRequest["url"];
...@@ -147,7 +156,7 @@ class InAppBrowser { ...@@ -147,7 +156,7 @@ class InAppBrowser {
headersRequest = headersResponse.cast<String, String>(); headersRequest = headersResponse.cast<String, String>();
String method = rawRequest["method"]; String method = rawRequest["method"];
var response = new WebResourceResponse(urlResponse, headersResponse, statusCode, loadingTime, data); var response = new WebResourceResponse(urlResponse, headersResponse, statusCode, startTime, duration, data);
var request = new WebResourceRequest(urlRequest, headersRequest, method); var request = new WebResourceRequest(urlRequest, headersRequest, method);
onLoadResource(response, request); onLoadResource(response, request);
...@@ -165,13 +174,22 @@ class InAppBrowser { ...@@ -165,13 +174,22 @@ class InAppBrowser {
}); });
onConsoleMessage(ConsoleMessage(sourceURL, lineNumber, message, messageLevel)); onConsoleMessage(ConsoleMessage(sourceURL, lineNumber, message, messageLevel));
break; break;
case "onCallJsHandler":
String handlerName = call.arguments["handlerName"];
List<dynamic> args = jsonDecode(call.arguments["args"]);
if (javaScriptHandlersMap.containsKey(handlerName)) {
for (var handler in javaScriptHandlersMap[handlerName]) {
handler(args);
}
}
break;
} }
return new Future.value(""); return new Future.value("");
} }
///Opens an [url] in a new [InAppBrowser] instance or the system browser. ///Opens an [url] in a new [InAppBrowser] instance or the system browser.
/// ///
///- [url]: The [url] to load. Call [encodeUriComponent()] on this if the [url] contains Unicode characters. ///- [url]: The [url] to load. Call [encodeUriComponent()] on this if the [url] contains Unicode characters. The default value is `about:blank`.
/// ///
///- [headers]: The additional headers to be used in the HTTP request for this URL, specified as a map from name to value. ///- [headers]: The additional headers to be used in the HTTP request for this URL, specified as a map from name to value.
/// ///
...@@ -185,6 +203,7 @@ class InAppBrowser { ...@@ -185,6 +203,7 @@ class InAppBrowser {
/// ///
/// All platforms support: /// All platforms support:
/// - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the [shouldOverrideUrlLoading()] event. The default value is `false`. /// - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the [shouldOverrideUrlLoading()] event. The default value is `false`.
/// - __useOnLoadResource__: Set to `true` to be able to listen at the [onLoadResource()] event. The default value is `false`.
/// - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`. /// - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`.
/// - __userAgent___: Set the custom WebView's user-agent. /// - __userAgent___: Set the custom WebView's user-agent.
/// - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`. /// - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`.
...@@ -342,6 +361,33 @@ class InAppBrowser { ...@@ -342,6 +361,33 @@ class InAppBrowser {
return await _ChannelManager.channel.invokeMethod('injectStyleFile', args); return await _ChannelManager.channel.invokeMethod('injectStyleFile', args);
} }
///Adds/Appends a JavaScript message handler [callback] ([JavaScriptHandlerCallback]) that listen to post messages sent from JavaScript by the handler with name [handlerName].
///Returns the position `index` of the handler that can be used to remove it with the [removeJavaScriptHandler()] method.
///
///The Android implementation uses [addJavascriptInterface](https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object,%20java.lang.String)).
///The iOS implementation uses [addScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-addscriptmessagehandler?language=objc)
///
///The JavaScript function that can be used to call the handler is `window.flutter_inappbrowser.callHandler(handlerName <String>, ...args);`, where `args` are [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
///The `args` will be stringified automatically using `JSON.stringify(args)` method and then they will be decoded on the Dart side.
int addJavaScriptHandler(String handlerName, JavaScriptHandlerCallback callback) {
this.javaScriptHandlersMap.putIfAbsent(handlerName, () => List<JavaScriptHandlerCallback>());
this.javaScriptHandlersMap[handlerName].add(callback);
return this.javaScriptHandlersMap[handlerName].indexOf(callback);
}
///Removes a JavaScript message handler previously added with the [addJavaScriptHandler()] method in the [handlerName] list by its position [index].
///Returns `true` if the callback is removed, otherwise `false`.
bool removeJavaScriptHandler(String handlerName, int index) {
try {
this.javaScriptHandlersMap[handlerName].removeAt(index);
return true;
}
on RangeError catch(e) {
print(e);
}
return false;
}
///Event fires when the [InAppBrowser] starts to load an [url]. ///Event fires when the [InAppBrowser] starts to load an [url].
void onLoadStart(String url) { void onLoadStart(String url) {
...@@ -363,12 +409,14 @@ class InAppBrowser { ...@@ -363,12 +409,14 @@ class InAppBrowser {
} }
///Give the host application a chance to take control when a URL is about to be loaded in the current WebView. ///Give the host application a chance to take control when a URL is about to be loaded in the current WebView.
///In order to be able to listen this event, you need to set `useShouldOverrideUrlLoading` option to `true`. ///**NOTE**: In order to be able to listen this event, you need to set `useShouldOverrideUrlLoading` option to `true`.
void shouldOverrideUrlLoading(String url) { void shouldOverrideUrlLoading(String url) {
} }
///Event fires when the [InAppBrowser] webview will load the resource specified by the given [WebResourceRequest]. ///Event fires when the [InAppBrowser] webview loads a resource.
///**NOTE**: In order to be able to listen this event, you need to set `useOnLoadResource` option to `true`.
///**NOTE only for iOS**: In some cases, the [response.data] of a [response] with `text/html` encoding could be empty.
void onLoadResource(WebResourceResponse response, WebResourceRequest request) { void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
} }
......
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