diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 24408d2b..924b998e 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -16,13 +16,19 @@
   <component name="ChangeListManager">
     <list default="true" id="9b41f7a2-a71e-4923-91fb-249d7815b3e7" name="Default" comment="">
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/CHANGELOG.md" beforeDir="false" afterPath="$PROJECT_DIR$/CHANGELOG.md" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/example/lib/main.dart" beforeDir="false" afterPath="$PROJECT_DIR$/example/lib/main.dart" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/ios/Classes/InAppBrowserOptions.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/InAppBrowserOptions.swift" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/ios/Classes/InAppBrowserWebViewController.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/InAppBrowserWebViewController.swift" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/ios/Classes/MyURLProtocol.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/MyURLProtocol.swift" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/ios/Classes/MyURLProtocol.swift" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/ios/Classes/NSURLProtocol+WKWebVIew.h" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/ios/Classes/NSURLProtocol+WKWebVIew.m" beforeDir="false" />
       <change beforePath="$PROJECT_DIR$/ios/Classes/SwiftFlutterPlugin.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/SwiftFlutterPlugin.swift" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" beforeDir="false" afterPath="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/pubspec.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pubspec.yaml" afterDir="false" />
     </list>
     <ignored path="$PROJECT_DIR$/.dart_tool/" />
     <ignored path="$PROJECT_DIR$/.idea/" />
@@ -42,8 +48,8 @@
       <file leaf-file-name="flutter_inappbrowser.dart" pinned="false" current-in-tab="false">
         <entry file="file://$PROJECT_DIR$/lib/flutter_inappbrowser.dart">
           <provider selected="true" editor-type-id="text-editor">
-            <state relative-caret-position="315">
-              <caret line="246" column="71" selection-start-line="246" selection-start-column="71" selection-end-line="246" selection-end-column="71" />
+            <state relative-caret-position="-386">
+              <caret line="366" column="187" selection-start-line="366" selection-start-column="187" selection-end-line="366" selection-end-column="187" />
               <folding>
                 <element signature="e#814#834#0" expanded="true" />
               </folding>
@@ -54,18 +60,18 @@
       <file leaf-file-name="build.gradle" pinned="false" current-in-tab="false">
         <entry file="file://$PROJECT_DIR$/android/build.gradle">
           <provider selected="true" editor-type-id="text-editor">
-            <state relative-caret-position="262">
+            <state relative-caret-position="462">
               <caret line="34" column="5" selection-start-line="34" selection-start-column="5" selection-end-line="34" selection-end-column="5" />
             </state>
           </provider>
         </entry>
       </file>
-      <file leaf-file-name="README.md" pinned="false" current-in-tab="false">
+      <file leaf-file-name="README.md" pinned="false" current-in-tab="true">
         <entry file="file://$PROJECT_DIR$/README.md">
           <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
             <state split_layout="SPLIT">
-              <first_editor relative-caret-position="2820">
-                <caret line="188" column="158" selection-start-line="188" selection-start-column="158" selection-end-line="188" selection-end-column="158" />
+              <first_editor relative-caret-position="280">
+                <caret line="592" lean-forward="true" selection-start-line="592" selection-end-line="592" />
               </first_editor>
               <second_editor>
                 <markdownNavigatorState />
@@ -74,11 +80,11 @@
           </provider>
         </entry>
       </file>
-      <file leaf-file-name="pubspec.yaml" pinned="false" current-in-tab="false">
-        <entry file="file://$PROJECT_DIR$/pubspec.yaml">
+      <file leaf-file-name="main.dart" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/example/lib/main.dart">
           <provider selected="true" editor-type-id="text-editor">
-            <state relative-caret-position="30">
-              <caret line="2" column="14" selection-start-line="2" selection-start-column="14" selection-end-line="2" selection-end-column="14" />
+            <state relative-caret-position="-10">
+              <caret line="99" lean-forward="true" selection-start-line="99" selection-end-line="99" />
             </state>
           </provider>
         </entry>
@@ -87,7 +93,9 @@
         <entry file="file://$PROJECT_DIR$/CHANGELOG.md">
           <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
             <state split_layout="SPLIT">
-              <first_editor />
+              <first_editor relative-caret-position="30">
+                <caret line="2" column="3" selection-start-line="2" selection-start-column="3" selection-end-line="2" selection-end-column="3" />
+              </first_editor>
               <second_editor>
                 <markdownNavigatorState />
               </second_editor>
@@ -95,15 +103,6 @@
           </provider>
         </entry>
       </file>
-      <file leaf-file-name="main.dart" pinned="false" current-in-tab="true">
-        <entry file="file://$PROJECT_DIR$/example/lib/main.dart">
-          <provider selected="true" editor-type-id="text-editor">
-            <state relative-caret-position="537">
-              <caret line="140" column="44" selection-start-line="140" selection-start-column="44" selection-end-line="140" selection-end-column="64" />
-            </state>
-          </provider>
-        </entry>
-      </file>
     </leaf>
   </component>
   <component name="FileTemplateManagerImpl">
@@ -180,19 +179,18 @@
         <option value="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowser.java" />
         <option value="$PROJECT_DIR$/ios/flutter_inappbrowser.podspec" />
         <option value="$PROJECT_DIR$/example/ios/Podfile" />
-        <option value="$PROJECT_DIR$/CHANGELOG.md" />
-        <option value="$PROJECT_DIR$/README.md" />
         <option value="$PROJECT_DIR$/android/build.gradle" />
-        <option value="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" />
         <option value="$PROJECT_DIR$/pubspec.yaml" />
         <option value="$PROJECT_DIR$/example/lib/main.dart" />
+        <option value="$PROJECT_DIR$/CHANGELOG.md" />
+        <option value="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" />
+        <option value="$PROJECT_DIR$/README.md" />
       </list>
     </option>
   </component>
-  <component name="ProjectFrameBounds">
-    <option name="x" value="235" />
+  <component name="ProjectFrameBounds" extendedState="6">
     <option name="y" value="23" />
-    <option name="width" value="1589" />
+    <option name="width" value="1582" />
     <option name="height" value="1027" />
   </component>
   <component name="ProjectLevelVcsManager" settingsEditedManually="true" />
@@ -396,7 +394,7 @@
     <servers />
   </component>
   <component name="ToolWindowManager">
-    <frame x="235" y="23" width="1589" height="1027" extended-state="0" />
+    <frame x="0" y="23" width="1920" height="1057" extended-state="6" />
     <editor active="true" />
     <layout>
       <window_info anchor="bottom" id="Android Profiler" order="7" show_stripe_button="false" />
@@ -408,15 +406,15 @@
       <window_info anchor="right" id="Capture Analysis" order="3" />
       <window_info anchor="bottom" id="Event Log" order="8" sideWeight="0.5035553" side_tool="true" weight="0.25689086" />
       <window_info anchor="bottom" id="Dart Analysis" order="14" weight="0.32855567" />
-      <window_info anchor="bottom" id="Run" order="2" sideWeight="0.49644473" weight="0.39801544" />
+      <window_info active="true" anchor="bottom" id="Run" order="2" sideWeight="0.49644473" visible="true" weight="0.20384204" />
       <window_info anchor="bottom" id="Version Control" order="9" />
-      <window_info active="true" anchor="bottom" id="Terminal" order="10" sideWeight="0.49644473" visible="true" weight="0.25689086" />
+      <window_info anchor="bottom" id="Terminal" order="10" sideWeight="0.49644473" weight="0.25689086" />
       <window_info anchor="right" id="Flutter Outline" order="3" weight="0.32922077" />
       <window_info anchor="bottom" id="Logcat" order="11" />
       <window_info id="Captures" order="2" weight="0.32936507" />
       <window_info id="Capture Tool" order="2" />
       <window_info id="Designer" order="2" />
-      <window_info content_ui="combo" id="Project" order="0" sideWeight="0.49855492" visible="true" weight="0.18293472" />
+      <window_info content_ui="combo" id="Project" order="0" sideWeight="0.49855492" visible="true" weight="0.15069222" />
       <window_info id="Structure" order="1" sideWeight="0.5014451" side_tool="true" weight="0.18293472" />
       <window_info anchor="right" id="Device File Explorer" order="3" side_tool="true" />
       <window_info anchor="right" id="Theme Preview" order="3" />
@@ -672,38 +670,6 @@
         </state>
       </provider>
     </entry>
-    <entry file="file://$PROJECT_DIR$/README.md">
-      <provider editor-type-id="text-editor">
-        <state relative-caret-position="247">
-          <caret line="19" column="15" selection-start-line="19" selection-start-column="3" selection-end-line="19" selection-end-column="15" />
-        </state>
-      </provider>
-      <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
-        <state split_layout="SPLIT">
-          <first_editor relative-caret-position="2820">
-            <caret line="188" column="158" selection-start-line="188" selection-start-column="158" selection-end-line="188" selection-end-column="158" />
-          </first_editor>
-          <second_editor>
-            <markdownNavigatorState />
-          </second_editor>
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/CHANGELOG.md">
-      <provider editor-type-id="text-editor">
-        <state relative-caret-position="30">
-          <caret line="2" column="69" lean-forward="true" selection-start-line="2" selection-start-column="2" selection-end-line="2" selection-end-column="73" />
-        </state>
-      </provider>
-      <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
-        <state split_layout="SPLIT">
-          <first_editor />
-          <second_editor>
-            <markdownNavigatorState />
-          </second_editor>
-        </state>
-      </provider>
-    </entry>
     <entry file="file://$PROJECT_DIR$/android/settings.gradle">
       <provider selected="true" editor-type-id="text-editor">
         <state>
@@ -728,13 +694,6 @@
         </state>
       </provider>
     </entry>
-    <entry file="file://$PROJECT_DIR$/android/build.gradle">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="262">
-          <caret line="34" column="5" selection-start-line="34" selection-start-column="5" selection-end-line="34" selection-end-column="5" />
-        </state>
-      </provider>
-    </entry>
     <entry file="file://$PROJECT_DIR$/pubspec.yaml">
       <provider selected="true" editor-type-id="text-editor">
         <state relative-caret-position="30">
@@ -742,20 +701,61 @@
         </state>
       </provider>
     </entry>
+    <entry file="file://$PROJECT_DIR$/example/lib/main.dart">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="-10">
+          <caret line="99" lean-forward="true" selection-start-line="99" selection-end-line="99" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/CHANGELOG.md">
+      <provider editor-type-id="text-editor">
+        <state relative-caret-position="30">
+          <caret line="2" column="69" lean-forward="true" selection-start-line="2" selection-start-column="2" selection-end-line="2" selection-end-column="73" />
+        </state>
+      </provider>
+      <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
+        <state split_layout="SPLIT">
+          <first_editor relative-caret-position="30">
+            <caret line="2" column="3" selection-start-line="2" selection-start-column="3" selection-end-line="2" selection-end-column="3" />
+          </first_editor>
+          <second_editor>
+            <markdownNavigatorState />
+          </second_editor>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/android/build.gradle">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="462">
+          <caret line="34" column="5" selection-start-line="34" selection-start-column="5" selection-end-line="34" selection-end-column="5" />
+        </state>
+      </provider>
+    </entry>
     <entry file="file://$PROJECT_DIR$/lib/flutter_inappbrowser.dart">
       <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="315">
-          <caret line="246" column="71" selection-start-line="246" selection-start-column="71" selection-end-line="246" selection-end-column="71" />
+        <state relative-caret-position="-386">
+          <caret line="366" column="187" selection-start-line="366" selection-start-column="187" selection-end-line="366" selection-end-column="187" />
           <folding>
             <element signature="e#814#834#0" expanded="true" />
           </folding>
         </state>
       </provider>
     </entry>
-    <entry file="file://$PROJECT_DIR$/example/lib/main.dart">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="537">
-          <caret line="140" column="44" selection-start-line="140" selection-start-column="44" selection-end-line="140" selection-end-column="64" />
+    <entry file="file://$PROJECT_DIR$/README.md">
+      <provider editor-type-id="text-editor">
+        <state relative-caret-position="247">
+          <caret line="19" column="15" selection-start-line="19" selection-start-column="3" selection-end-line="19" selection-end-column="15" />
+        </state>
+      </provider>
+      <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
+        <state split_layout="SPLIT">
+          <first_editor relative-caret-position="280">
+            <caret line="592" lean-forward="true" selection-start-line="592" selection-end-line="592" />
+          </first_editor>
+          <second_editor>
+            <markdownNavigatorState />
+          </second_editor>
         </state>
       </provider>
     </entry>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index db90f5b4..3189cc3b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,15 @@
+## 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
 
-- added InAppBrowser.onConsoleMessage() method to manage console messages
-- fixed InAppBrowser.injectScriptCode() method when there is not a return value
+- added `InAppBrowser.onConsoleMessage()` method to manage console messages
+- fixed `InAppBrowser.injectScriptCode()` method when there is not a return value
 
 ## 0.2.0
 
diff --git a/README.md b/README.md
index 94b1e01b..0023c24e 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,10 @@ class MyInAppBrowser extends InAppBrowser {
   @override
   Future onLoadStop(String url) async {
     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(await this.injectScriptCode("document.body.innerHTML"));
 
@@ -78,6 +82,11 @@ class MyInAppBrowser extends InAppBrowser {
     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
   void onConsoleMessage(ConsoleMessage consoleMessage) {
     print("""
@@ -105,6 +114,12 @@ class _MyAppState extends State<MyApp> {
   @override
   void 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
@@ -117,7 +132,8 @@ class _MyAppState extends State<MyApp> {
         body: new Center(
           child: new RaisedButton(onPressed: () {
             inAppBrowser.open("https://flutter.io/", options: {
-               "useShouldOverrideUrlLoading": true
+               "useShouldOverrideUrlLoading": true,
+               "useOnLoadResource": true
              });
           },
           child: Text("Open InAppBrowser")
@@ -134,12 +150,12 @@ class _MyAppState extends State<MyApp> {
 Opens a URL in a new InAppBrowser instance or the system browser.
 
 ```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.
 
-- `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.
 
@@ -153,6 +169,7 @@ Opens an `url` in a new `InAppBrowser` instance or the system browser.
 
   All platforms support:
   - __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`.
   - __userAgent___: Set the custom WebView's user-agent.
   - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`.
@@ -200,6 +217,7 @@ Example:
 ```dart
 inAppBrowser.open('https://flutter.io/', options: {
   "useShouldOverrideUrlLoading": true,
+  "useOnLoadResource": true,
   "clearCache": true,
   "disallowOverScroll": true,
   "domStorageEnabled": true,
@@ -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.
-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
   @override
   void shouldOverrideUrlLoading(String url) {
@@ -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
 
 Loads the given `url` with optional `headers` specified as a map from name to value.
@@ -370,7 +401,31 @@ Injects a CSS file into the `InAppBrowser` window. (Only available when the targ
 
 ```dart
 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
 Create a Class that extends the `ChromeSafariBrowser` Class in order to override the callbacks to manage the browser events. Example:
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java
index 13cd99f8..08101acb 100644
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java
@@ -5,6 +5,7 @@ public class InAppBrowserOptions extends Options {
     final static String LOG_TAG = "InAppBrowserOptions";
 
     public boolean useShouldOverrideUrlLoading = false;
+    public boolean useOnLoadResource = false;
     public boolean clearCache = false;
     public String userAgent = "";
     public boolean javaScriptEnabled = true;
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java
index 557a6ce8..1e4e44e9 100644
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java
@@ -31,6 +31,7 @@ public class InAppBrowserWebViewClient extends WebViewClient {
     protected static final String LOG_TAG = "IABWebViewClient";
     private WebViewActivity activity;
     Map<Integer, String> statusCodeMapping = new HashMap<Integer, String>();
+    long startPageTime = 0;
 
     public InAppBrowserWebViewClient(WebViewActivity activity) {
         super();
@@ -164,6 +165,8 @@ public class InAppBrowserWebViewClient extends WebViewClient {
     public void onPageStarted(WebView view, String url, Bitmap favicon) {
         super.onPageStarted(view, url, favicon);
 
+        startPageTime = System.currentTimeMillis();
+
         activity.isLoading = true;
 
         if (activity.searchView != null && !url.equals(activity.searchView.getQuery().toString())) {
@@ -195,7 +198,8 @@ public class InAppBrowserWebViewClient extends WebViewClient {
         view.requestFocus();
 
         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<>();
@@ -264,14 +268,24 @@ public class InAppBrowserWebViewClient extends WebViewClient {
     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
     @Override
     public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request){
+
+        if (!request.getMethod().toLowerCase().equals("get") || !activity.options.useOnLoadResource) {
+            return null;
+        }
+
         final String url = request.getUrl().toString();
 
-        Request mRequest = new Request.Builder().url(url).build();
-
         try {
-            long loadingTime = System.currentTimeMillis();
+            Request mRequest = new Request.Builder().url(url).build();
+
+            long startResourceTime = System.currentTimeMillis();
             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();
             if (reasonPhrase.equals("")) {
@@ -281,20 +295,20 @@ public class InAppBrowserWebViewClient extends WebViewClient {
 
             Map<String, String> headersResponse = new HashMap<String, String>();
             for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
-                String value = "";
+                StringBuilder value = new StringBuilder();
                 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>();
             for (Map.Entry<String, List<String>> entry : mRequest.headers().toMultimap().entrySet()) {
-                String value = "";
+                StringBuilder value = new StringBuilder();
                 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<>();
@@ -309,7 +323,8 @@ public class InAppBrowserWebViewClient extends WebViewClient {
             res.put("url", url);
             res.put("statusCode", response.code());
             res.put("headers", headersResponse);
-            res.put("loadingTime", loadingTime);
+            res.put("startTime", startTime);
+            res.put("duration", duration);
             res.put("data", dataBytes);
 
             req.put("url", url);
@@ -332,7 +347,11 @@ public class InAppBrowserWebViewClient extends WebViewClient {
         } catch (IOException e) {
             e.printStackTrace();
             Log.d(LOG_TAG, e.getMessage());
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.d(LOG_TAG, e.getMessage());
         }
+
         return null;
     }
 
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java
new file mode 100644
index 00000000..88117897
--- /dev/null
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java
@@ -0,0 +1,29 @@
+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);
+  }
+}
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java
index cafbaabd..97d859ad 100644
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import io.flutter.plugin.common.MethodChannel;
+import okhttp3.Cache;
 import okhttp3.OkHttpClient;
 
 public class WebViewActivity extends AppCompatActivity {
@@ -40,7 +41,7 @@ public class WebViewActivity extends AppCompatActivity {
     public boolean isHidden = false;
     OkHttpClient httpClient;
 
-    static final String jsConsoleLogScript = "(function() {\n"+
+    static final String consoleLogJS = "(function() {\n"+
 "   var oldLogs = {\n"+
 "       'log': console.log,\n"+
 "       'debug': console.debug,\n"+
@@ -89,14 +90,18 @@ public class WebViewActivity extends AppCompatActivity {
 
         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.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() {
 
+        webView.addJavascriptInterface(new JavaScriptBridgeInterface(this), JavaScriptBridgeInterface.name);
+
         inAppBrowserWebChromeClient = new InAppBrowserWebChromeClient(this);
         webView.setWebChromeClient(inAppBrowserWebChromeClient);
 
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 1ae29afc..899c64e1 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -13,8 +13,9 @@ class MyInAppBrowser extends InAppBrowser {
   Future onLoadStop(String url) async {
     print("\n\nStopped $url\n\n");
 
-//    // javascript error
-//    await this.injectScriptCode("console.log({'testJavaScriptError': 5}));");
+    await this.injectScriptCode("window.flutter_inappbrowser.callHandler('handlerTest', 1, 5,'string', {'key': 5}, [4,6,8]);");
+    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.warn('testWarn',null);");
@@ -73,20 +74,21 @@ class MyInAppBrowser extends InAppBrowser {
 
   @override
   void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
-    print(response.loadingTime.toString() + "ms " + response.url);
-    if (response.headers["content-length"] != null)
-      print(response.headers["content-length"] + " length");
+
+    print("Started at: " + response.startTime.toString() + "ms ---> duration: " + response.duration.toString() + "ms " + response.url);
+//    if (response.headers["content-length"] != null)
+//      print(response.headers["content-length"] + " length");
   }
 
   @override
   void onConsoleMessage(ConsoleMessage consoleMessage) {
-    print("""
-    console output: 
-      sourceURL: ${consoleMessage.sourceURL}
-      lineNumber: ${consoleMessage.lineNumber}
-      message: ${consoleMessage.message}
-      messageLevel: ${consoleMessage.messageLevel}
-    """);
+//    print("""
+//    console output:
+//      sourceURL: ${consoleMessage.sourceURL}
+//      lineNumber: ${consoleMessage.lineNumber}
+//      message: ${consoleMessage.message}
+//      messageLevel: ${consoleMessage.messageLevel}
+//    """);
   }
 }
 
@@ -112,9 +114,12 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser {
   }
 }
 
+// adding a webview fallback
 MyChromeSafariBrowser chromeSafariBrowser = new MyChromeSafariBrowser(inAppBrowserFallback);
 
-void main() => runApp(new MyApp());
+void main() {
+  runApp(new MyApp());
+}
 
 class MyApp extends StatefulWidget {
   @override
@@ -126,6 +131,15 @@ class _MyAppState extends State<MyApp> {
   @override
   void 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
@@ -139,6 +153,7 @@ class _MyAppState extends State<MyApp> {
           child: new RaisedButton(onPressed: () {
             //chromeSafariBrowser.open("https://flutter.io/");
             inAppBrowserFallback.open(url: "https://flutter.io/", options: {
+              //"useOnLoadResource": true,
               //"hidden": true,
               //"toolbarTopFixedTitle": "Fixed title",
               //"useShouldOverrideUrlLoading": true
diff --git a/ios/Classes/InAppBrowserOptions.swift b/ios/Classes/InAppBrowserOptions.swift
index 252e7376..121b9360 100644
--- a/ios/Classes/InAppBrowserOptions.swift
+++ b/ios/Classes/InAppBrowserOptions.swift
@@ -11,6 +11,7 @@ import Foundation
 public class InAppBrowserOptions: Options {
     
     var useShouldOverrideUrlLoading = false
+    var useOnLoadResource = false
     var clearCache = false
     var userAgent = ""
     var javaScriptEnabled = true
diff --git a/ios/Classes/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowserWebViewController.swift
index e41502fa..b9bc369e 100644
--- a/ios/Classes/InAppBrowserWebViewController.swift
+++ b/ios/Classes/InAppBrowserWebViewController.swift
@@ -15,7 +15,7 @@ typealias OlderClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, B
 typealias NewerClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void
 
 // the message needs to be concatenated with '' in order to have the same behavior like on Android
-let jsConsoleLog = """
+let consoleLogJS = """
 (function() {
     var oldLogs = {
         'consoleLog': console.log,
@@ -44,53 +44,91 @@ let jsConsoleLog = """
 })();
 """
 
-extension WKWebView{
-    
-    var keyboardDisplayRequiresUserAction: Bool? {
-        get {
-            return self.keyboardDisplayRequiresUserAction
-        }
-        set {
-            self.setKeyboardRequiresUserInteraction(newValue ?? true)
-        }
-    }
-    
-    func setKeyboardRequiresUserInteraction( _ value: Bool) {
-        
-        guard
-            let WKContentViewClass: AnyClass = NSClassFromString("WKContentView") else {
-                print("Cannot find the WKContentView class")
-                return
-        }
-        
-        let olderSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
-        let newerSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
-        
-        if let method = class_getInstanceMethod(WKContentViewClass, olderSelector) {
-            
-            let originalImp: IMP = method_getImplementation(method)
-            let original: OlderClosureType = unsafeBitCast(originalImp, to: OlderClosureType.self)
-            let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3) in
-                original(me, olderSelector, arg0, !value, arg2, arg3)
-            }
-            let imp: IMP = imp_implementationWithBlock(block)
-            method_setImplementation(method, imp)
-        }
-        
-        if let method = class_getInstanceMethod(WKContentViewClass, newerSelector) {
-            
-            let originalImp: IMP = method_getImplementation(method)
-            let original: NewerClosureType = unsafeBitCast(originalImp, to: NewerClosureType.self)
-            let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
-                original(me, newerSelector, arg0, !value, arg2, arg3, arg4)
-            }
-            let imp: IMP = imp_implementationWithBlock(block)
-            method_setImplementation(method, imp)
-        }
-        
-    }
-    
+let resourceObserverJS = """
+(function() {
+    var observer = new PerformanceObserver(function(list) {
+        list.getEntries().forEach(function(entry) {
+            window.webkit.messageHandlers['resourceLoaded'].postMessage(JSON.stringify(entry));
+        });
+    });
+    observer.observe({entryTypes: ['resource', 'mark', 'measure']});
+})();
+"""
+
+let JAVASCRIPT_BRIDGE_NAME = "flutter_inappbrowser"
+
+let javaScriptBridgeJS = """
+window.\(JAVASCRIPT_BRIDGE_NAME) = {};
+window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function(handlerName, ...args) {
+    window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': handlerName, 'args': JSON.stringify(args)} );
 }
+"""
+
+func currentTimeInMilliSeconds() -> Int {
+    let currentDate = Date()
+    let since1970 = currentDate.timeIntervalSince1970
+    return Int(since1970 * 1000)
+}
+
+func convertToDictionary(text: String) -> [String: Any]? {
+    if let data = text.data(using: .utf8) {
+        do {
+            return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
+        } catch {
+            print(error.localizedDescription)
+        }
+    }
+    return nil
+}
+
+
+//extension WKWebView{
+//
+//    var keyboardDisplayRequiresUserAction: Bool? {
+//        get {
+//            return self.keyboardDisplayRequiresUserAction
+//        }
+//        set {
+//            self.setKeyboardRequiresUserInteraction(newValue ?? true)
+//        }
+//    }
+//
+//    func setKeyboardRequiresUserInteraction( _ value: Bool) {
+//
+//        guard
+//            let WKContentViewClass: AnyClass = NSClassFromString("WKContentView") else {
+//                print("Cannot find the WKContentView class")
+//                return
+//        }
+//
+//        let olderSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
+//        let newerSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
+//
+//        if let method = class_getInstanceMethod(WKContentViewClass, olderSelector) {
+//
+//            let originalImp: IMP = method_getImplementation(method)
+//            let original: OlderClosureType = unsafeBitCast(originalImp, to: OlderClosureType.self)
+//            let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3) in
+//                original(me, olderSelector, arg0, !value, arg2, arg3)
+//            }
+//            let imp: IMP = imp_implementationWithBlock(block)
+//            method_setImplementation(method, imp)
+//        }
+//
+//        if let method = class_getInstanceMethod(WKContentViewClass, newerSelector) {
+//
+//            let originalImp: IMP = method_getImplementation(method)
+//            let original: NewerClosureType = unsafeBitCast(originalImp, to: NewerClosureType.self)
+//            let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
+//                original(me, newerSelector, arg0, !value, arg2, arg3, arg4)
+//            }
+//            let imp: IMP = imp_implementationWithBlock(block)
+//            method_setImplementation(method, imp)
+//        }
+//
+//    }
+//
+//}
 
 class WKWebView_IBWrapper: WKWebView {
     required convenience init?(coder: NSCoder) {
@@ -100,7 +138,7 @@ class WKWebView_IBWrapper: WKWebView {
     }
 }
 
-class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, UITextFieldDelegate, WKScriptMessageHandler, MyURLProtocolDelegate {
+class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, UITextFieldDelegate, WKScriptMessageHandler {
     @IBOutlet var webView: WKWebView_IBWrapper!
     @IBOutlet var closeButton: UIButton!
     @IBOutlet var reloadButton: UIBarButtonItem!
@@ -119,6 +157,8 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
     var initHeaders: [String: String]?
     var isHidden = false
     var uuid: String = ""
+    var WKNavigationMap: [String: [String: Any]] = [:]
+    var startPageTime = 0
     
     required init(coder aDecoder: NSCoder) {
         super.init(coder: aDecoder)!
@@ -132,7 +172,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        MyURLProtocol.wkWebViewDelegateMap[uuid] = self
+        //MyURLProtocol.wkWebViewDelegateMap[uuid] = self
         
         webView.uiDelegate = self
         webView.navigationDelegate = self
@@ -162,6 +202,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
         spinner.stopAnimating()
         
         loadUrl(url: self.currentURL!, headers: self.initHeaders)
+        
     }
     
     // Prevent crashes on closing windows
@@ -249,14 +290,25 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
         let jscriptWebkitTouchCallout = WKUserScript(source: "document.body.style.webkitTouchCallout='none';", injectionTime: .atDocumentEnd, forMainFrameOnly: true)
         self.webView.configuration.userContentController.addUserScript(jscriptWebkitTouchCallout)
         
-        let jsConsoleLogScript = WKUserScript(source: jsConsoleLog, injectionTime: .atDocumentStart, forMainFrameOnly: false)
-        self.webView.configuration.userContentController.addUserScript(jsConsoleLogScript)
+        
+        let consoleLogJSScript = WKUserScript(source: consoleLogJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+        self.webView.configuration.userContentController.addUserScript(consoleLogJSScript)
         self.webView.configuration.userContentController.add(self, name: "consoleLog")
         self.webView.configuration.userContentController.add(self, name: "consoleDebug")
         self.webView.configuration.userContentController.add(self, name: "consoleError")
         self.webView.configuration.userContentController.add(self, name: "consoleInfo")
         self.webView.configuration.userContentController.add(self, name: "consoleWarn")
         
+        let javaScriptBridgeJSScript = WKUserScript(source: javaScriptBridgeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+        self.webView.configuration.userContentController.addUserScript(javaScriptBridgeJSScript)
+        self.webView.configuration.userContentController.add(self, name: "callHandler")
+        
+        if (browserOptions?.useOnLoadResource)! {
+            let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+            self.webView.configuration.userContentController.addUserScript(resourceObserverJSScript)
+            self.webView.configuration.userContentController.add(self, name: "resourceLoaded")
+        }
+        
         if #available(iOS 10.0, *) {
             if (browserOptions?.mediaPlaybackRequiresUserGesture)! {
                 self.webView.configuration.mediaTypesRequiringUserActionForPlayback = .all
@@ -301,21 +353,6 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
             }
         }
         
-        // 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
-                    }
-                }
-            }
-        }
-        
         if (browserOptions?.clearCache)! {
             clearCache()
         }
@@ -480,18 +517,27 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
                  decidePolicyFor navigationAction: WKNavigationAction,
                  decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
         
-        let url = navigationAction.request.url
-        
-        if (url != nil && navigationAction.navigationType == .linkActivated && (browserOptions?.useShouldOverrideUrlLoading)!) {
-            navigationDelegate?.shouldOverrideUrlLoading(uuid: self.uuid, webView: webView, url: url!)
-            decisionHandler(.cancel)
-            return
+        if let url = navigationAction.request.url {
+            
+            if url.absoluteString != self.currentURL?.absoluteString && (browserOptions?.useOnLoadResource)! {
+                WKNavigationMap[url.absoluteString] = [
+                    "startTime": currentTimeInMilliSeconds(),
+                    "request": navigationAction.request
+                ]
+            }
+            
+            if navigationAction.navigationType == .linkActivated && (browserOptions?.useShouldOverrideUrlLoading)! {
+                navigationDelegate?.shouldOverrideUrlLoading(uuid: self.uuid, webView: webView, url: url)
+                decisionHandler(.cancel)
+                return
+            }
+            
+            if navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .backForward {
+                currentURL = url
+                updateUrlTextField(url: (url.absoluteString))
+            }
         }
         
-        if url != nil && (navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .backForward) {
-            currentURL = url
-            updateUrlTextField(url: (url?.absoluteString)!)
-        }
         
         decisionHandler(.allow)
     }
@@ -499,9 +545,18 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
     func webView(_ webView: WKWebView,
                  decidePolicyFor navigationResponse: WKNavigationResponse,
                  decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
-        //dump((navigationResponse.response as! HTTPURLResponse))
-        //print(navigationResponse.response.mimeType)
-        //print(navigationResponse.response.url)
+        
+        if (browserOptions?.useOnLoadResource)! {
+            if let url = navigationResponse.response.url {
+                if WKNavigationMap[url.absoluteString] != nil {
+                    let startResourceTime = (WKNavigationMap[url.absoluteString]!["startTime"] as! Int)
+                    let startTime = startResourceTime - startPageTime;
+                    let duration = currentTimeInMilliSeconds() - startResourceTime;
+                    self.didReceiveResourceResponse(navigationResponse.response, fromRequest: WKNavigationMap[url.absoluteString]!["request"] as? URLRequest, withData: Data(), startTime: startTime, duration: duration)
+                }
+            }
+        }
+        
         decisionHandler(.allow)
     }
 
@@ -540,6 +595,9 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
 //    }
     
     func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
+        
+        self.startPageTime = currentTimeInMilliSeconds()
+        
         // loading url, start spinner, update back/forward
         backButton.isEnabled = webView.canGoBack
         forwardButton.isEnabled = webView.canGoForward
@@ -552,8 +610,8 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
     }
     
     func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+        self.WKNavigationMap = [:]
         // update url, stop spinner, update back/forward
-        
         currentURL = webView.url
         updateUrlTextField(url: (currentURL?.absoluteString)!)
         backButton.isEnabled = webView.canGoBack
@@ -576,8 +634,8 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
         navigationDelegate?.onLoadError(uuid: self.uuid, webView: webView, error: error)
     }
     
-    func didReceiveResponse(_ response: URLResponse, fromRequest request: URLRequest?, withData data: Data, loadingTime time: Int) {
-        navigationDelegate?.onLoadResource(uuid: self.uuid, webView: webView, response: response, fromRequest: request, withData: data, loadingTime: time)
+    func didReceiveResourceResponse(_ response: URLResponse, fromRequest request: URLRequest?, withData data: Data, startTime: Int, duration: Int) {
+        navigationDelegate?.onLoadResource(uuid: self.uuid, webView: webView, response: response, fromRequest: request, withData: data, startTime: startTime, duration: duration)
     }
     
     func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
@@ -607,5 +665,37 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
             }
             navigationDelegate?.onConsoleMessage(uuid: self.uuid, sourceURL: "", lineNumber: 1, message: message.body as! String, messageLevel: messageLevel)
         }
+        else if message.name == "resourceLoaded" {
+            if let resource = convertToDictionary(text: message.body as! String) {
+                let url = URL(string: resource["name"] as! String)!
+                if !UIApplication.shared.canOpenURL(url) {
+                    return
+                }
+                let startTime = Int(resource["startTime"] as! Double)
+                let duration = Int(resource["duration"] as! Double)
+                var urlRequest = URLRequest(url: url)
+                urlRequest.allHTTPHeaderFields = [:]
+                let config = URLSessionConfiguration.default
+                let session = URLSession(configuration: config)
+                let task = session.dataTask(with: urlRequest) { (data, response, error) in
+                    if error != nil {
+                        print(error)
+                        return
+                    }
+                    var withData = data
+                    if withData == nil {
+                        withData = Data()
+                    }
+                    self.didReceiveResourceResponse(response!, fromRequest: urlRequest, withData: withData!, startTime: startTime, duration: duration)
+                }
+                task.resume()
+            }
+        }
+        else if message.name == "callHandler" {
+            let body = message.body as! [String: Any]
+            let handlerName = body["handlerName"] as! String
+            let args = body["args"] as! String
+            self.navigationDelegate?.onCallJsHandler(uuid: self.uuid, webView: webView, handlerName: handlerName, args: args)
+        }
     }
 }
diff --git a/ios/Classes/MyURLProtocol.swift b/ios/Classes/MyURLProtocol.swift
deleted file mode 100644
index c08ed8fe..00000000
--- a/ios/Classes/MyURLProtocol.swift
+++ /dev/null
@@ -1,149 +0,0 @@
-//
-//  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)
-}
diff --git a/ios/Classes/NSURLProtocol+WKWebVIew.h b/ios/Classes/NSURLProtocol+WKWebVIew.h
deleted file mode 100644
index d0dd71f3..00000000
--- a/ios/Classes/NSURLProtocol+WKWebVIew.h
+++ /dev/null
@@ -1,23 +0,0 @@
-//
-//  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
diff --git a/ios/Classes/NSURLProtocol+WKWebVIew.m b/ios/Classes/NSURLProtocol+WKWebVIew.m
deleted file mode 100644
index 176ded26..00000000
--- a/ios/Classes/NSURLProtocol+WKWebVIew.m
+++ /dev/null
@@ -1,54 +0,0 @@
-//
-//  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
diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift
index 4b782de2..bcfa0ec7 100644
--- a/ios/Classes/SwiftFlutterPlugin.swift
+++ b/ios/Classes/SwiftFlutterPlugin.swift
@@ -22,6 +22,19 @@ import Foundation
 import AVFoundation
 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_CONTROLLER_ID = "viewController"
 
@@ -46,9 +59,12 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
     }
     
     public static func register(with registrar: FlutterPluginRegistrar) {
-        URLProtocol.wk_registerScheme("http")
-        URLProtocol.wk_registerScheme("https")
-        URLProtocol.registerClass(MyURLProtocol.self)
+//        URLProtocol.wk_registerScheme("http")
+//        URLProtocol.wk_registerScheme("https")
+//        URLProtocol.registerClass(MyURLProtocol.self)
+
+        //URLCache.shared = CustomURLCache()
+        
         let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger())
         let instance = SwiftFlutterPlugin(with: registrar)
         registrar.addMethodCallDelegate(instance, channel: channel)
@@ -423,7 +439,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
                     
                     if error != nil {
                         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 {
@@ -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 {
             var headersResponse = (response as! HTTPURLResponse).allHeaderFields as! [String: String]
             headersResponse.lowercaseKeys()
@@ -506,7 +523,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
                     "url": response.url!.absoluteString,
                     "statusCode": (response as! HTTPURLResponse).statusCode,
                     "headers": headersResponse,
-                    "loadingTime": time,
+                    "startTime": startTime,
+                    "duration": duration,
                     "data": data
                 ],
                 "request": [
@@ -551,6 +569,10 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
         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) {
         if let safariViewController = self.safariViewControllers[uuid] {
             if #available(iOS 9.0, *) {
diff --git a/lib/flutter_inappbrowser.dart b/lib/flutter_inappbrowser.dart
index 8926d9b1..750f3885 100644
--- a/lib/flutter_inappbrowser.dart
+++ b/lib/flutter_inappbrowser.dart
@@ -22,11 +22,13 @@
 import 'dart:async';
 import 'dart:collection';
 import 'dart:typed_data';
+import 'dart:convert';
 
 import 'package:flutter/services.dart';
 import 'package:uuid/uuid.dart';
 
 typedef Future<dynamic> ListenerCallback(MethodCall call);
+typedef Future<void> JavaScriptHandlerCallback(List<dynamic> arguments);
 
 var _uuidGenerator = new Uuid();
 
@@ -35,6 +37,8 @@ enum ConsoleMessageLevel {
   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 {
 
   String url;
@@ -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 {
 
   String url;
   Map<String, String> headers;
   int statusCode;
-  int loadingTime;
+  int startTime;
+  int duration;
   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 {
 class InAppBrowser {
 
   String uuid;
+  Map<String, List<JavaScriptHandlerCallback>> javaScriptHandlersMap = HashMap<String, List<JavaScriptHandlerCallback>>();
 
   ///
   InAppBrowser () {
@@ -139,7 +147,8 @@ class InAppBrowser {
         Map<dynamic, dynamic> headersResponse = rawResponse["headers"];
         headersResponse = headersResponse.cast<String, String>();
         int statusCode = rawResponse["statusCode"];
-        int loadingTime = rawResponse["loadingTime"];
+        int startTime = rawResponse["startTime"];
+        int duration = rawResponse["duration"];
         Uint8List data = rawResponse["data"];
 
         String urlRequest = rawRequest["url"];
@@ -147,7 +156,7 @@ class InAppBrowser {
         headersRequest = headersResponse.cast<String, String>();
         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);
 
         onLoadResource(response, request);
@@ -165,13 +174,22 @@ class InAppBrowser {
         });
         onConsoleMessage(ConsoleMessage(sourceURL, lineNumber, message, messageLevel));
         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("");
   }
 
   ///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.
   ///
@@ -185,6 +203,7 @@ class InAppBrowser {
   ///
   ///  All platforms support:
   ///  - __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`.
   ///  - __userAgent___: Set the custom WebView's user-agent.
   ///  - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`.
@@ -342,6 +361,33 @@ class InAppBrowser {
     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].
   void onLoadStart(String url) {
 
@@ -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.
-  ///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) {
 
   }
 
-  ///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) {
 
   }