From 1b2de86375aa317a7f75075b3610544c223ebc28 Mon Sep 17 00:00:00 2001
From: Lorenzo Pichilli <pichillilorenzo@gmail.com>
Date: Fri, 12 Jun 2020 04:04:41 +0200
Subject: [PATCH] added new webview methods, added supporZoom webview option on
 iOS, bug fixes, prepare new version 3.4.0

---
 CHANGELOG.md                                  |  15 +
 README.md                                     |  10 +-
 .../InAppWebView/FlutterWebView.java          |  20 +-
 .../InAppWebView/InAppWebView.java            |  45 +-
 .../InAppWebView/InAppWebViewClient.java      |   2 -
 .../InAppWebView/InAppWebViewOptions.java     |   4 +-
 example/.flutter-plugins-dependencies         |   2 +-
 .../ios/Flutter/flutter_export_environment.sh |   3 +-
 example/lib/in_app_webiew_example.screen.dart |   4 +-
 example/test_driver/.env.dart                 |   2 +-
 ios/Classes/FlutterWebViewController.swift    |  34 +-
 ios/Classes/InAppWebView.swift                | 119 ++++-
 ios/Classes/InAppWebViewOptions.swift         |   1 +
 lib/flutter_inappwebview.dart                 |   1 +
 lib/src/cookie_manager.dart                   |   2 +-
 lib/src/in_app_webview.dart                   |   2 +-
 lib/src/in_app_webview_controller.dart        | 130 +++++-
 lib/src/types.dart                            | 143 ++++++
 lib/src/util.dart                             | 425 ++++++++++++++++++
 lib/src/web_storage.dart                      | 136 ++++++
 lib/src/webview.dart                          |   2 +-
 lib/src/webview_options.dart                  |  22 +-
 pubspec.yaml                                  |   2 +-
 23 files changed, 1081 insertions(+), 45 deletions(-)
 create mode 100644 lib/src/util.dart
 create mode 100644 lib/src/web_storage.dart

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea7f746..2d1c41c 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+## 3.4.0
+
+- Added `requestFocusNodeHref`, `requestImageRef`, `getMetaTags`, `getMetaThemeColor` webview methods
+- Added `WebStorage`, `LocalStorage` and `SessionStorage` class to manage `window.localStorage` and `window.sessionStorage` JavaScript [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)
+- Added `supportZoom` webview option also on iOS
+- Fixed `zoomBy`, `setOptions` webview methods on Android
+- Fixed `databaseEnabled` android webview option default value to `true`
+
+### BREAKING CHANGES
+
+- `evaluateJavascript` webview method now returns `null` on iOS if the evaluated JavaScript source returns `null`
+- `getHtml` webview method now could return `null` if it was unable to get it.
+- Moved `supportZoom` webview option to cross-platform
+- `builtInZoomControls` android webview options changed default value to `true`
+
 ## 3.3.0+3
 
 - Updated Android build.gradle version and some androidx properties
diff --git a/README.md b/README.md
index c5af3b2..d0a6992 100755
--- a/README.md
+++ b/README.md
@@ -402,6 +402,10 @@ Screenshots:
 * `getHitTestResult`: Gets the hit result for hitting an HTML elements.
 * `clearFocus`: Clears the current focus. It will clear also, for example, the current text selection.
 * `setContextMenu(ContextMenu contextMenu)`: Sets or updates the WebView context menu to be used next time it will appear.
+* `requestFocusNodeHref`: Requests the anchor or image element URL at the last tapped point.
+* `requestImageRef`: Requests the URL of the image last touched by the user.
+* `getMetaTags`: Returns the list of `<meta>` tags of the current WebView.
+* `getMetaThemeColor`: Returns an instance of `Color` representing the `content` value of the `<meta name="theme-color" content="">` tag of the current WebView, if available, otherwise `null`.
 * `static getDefaultUserAgent`: Gets the default user agent.
 
 ##### `InAppWebViewController` Android-specific methods
@@ -504,6 +508,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
 * `disableVerticalScroll`: Set to `true` to disable vertical scroll. The default value is `false`.
 * `disableHorizontalScroll`: Set to `true` to disable horizontal scroll. The default value is `false`.
 * `disableContextMenu`: Set to `true` to disable context menu. The default value is `false`.
+* `supportZoom`: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
 
 ##### `InAppWebView` Android-specific options
 
@@ -511,9 +516,8 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
 * `useOnRenderProcessGone`: Set to `true` to be able to listen at the `androidOnRenderProcessGone` event. The default value is `false`.
 * `textZoom`: Sets the text zoom of the page in percent. The default value is `100`.
 * `clearSessionCache`: Set to `true` to have the session cookie cache cleared before the new window is opened.
-* `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`.
+* `builtInZoomControls`: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `true`.
 * `displayZoomControls`: Set to `true` if the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. The default value is `false`.
-* `supportZoom`: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
 * `databaseEnabled`: Set to `true` if you want the database storage API is enabled. The default value is `true`.
 * `domStorageEnabled`: Set to `true` if you want the DOM storage API is enabled. The default value is `true`.
 * `useWideViewPort`: Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport.
@@ -622,7 +626,7 @@ Event names that starts with `android` or `ios` are events platform-specific.
 * `androidOnPermissionRequest`: Event fired when the webview is requesting permission to access the specified resources and the permission currently isn't granted or denied (available only on Android).
 * `androidOnGeolocationPermissionsShowPrompt`: Event that notifies the host application that web content from the specified origin is attempting to use the Geolocation API, but no permission state is currently set for that origin (available only on Android).
 * `androidOnGeolocationPermissionsHidePrompt`: Notify the host application that a request for Geolocation permissions, made with a previous call to `androidOnGeolocationPermissionsShowPrompt` has been canceled (available only on Android).
-* `androidShouldInterceptRequest`: Notify the host application of a resource request and allow the application to return the data (available only on Android).
+* `androidShouldInterceptRequest`: Notify the host application of a resource request and allow the application to return the data (available only on Android). To use this event, the `useShouldInterceptRequest` option must be `true`.
 * `androidOnRenderProcessGone`: Event fired when the given WebView's render process has exited (available only on Android).
 * `androidOnRenderProcessResponsive`: Event called once when an unresponsive renderer currently associated with the WebView becomes responsive (available only on Android).
 * `androidOnRenderProcessUnresponsive`: Event called when the renderer currently associated with the WebView becomes unresponsive as a result of a long running blocking task such as the execution of JavaScript (available only on Android).
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java
index e9be1ad..609c7ec 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java
@@ -5,6 +5,7 @@ import android.hardware.display.DisplayManager;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.util.Log;
 import android.view.View;
 import android.webkit.ValueCallback;
@@ -22,7 +23,6 @@ import com.pichillilorenzo.flutter_inappwebview.Util;
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 import io.flutter.plugin.common.BinaryMessenger;
@@ -365,8 +365,8 @@ public class FlutterWebView implements PlatformView, MethodCallHandler  {
         break;
       case "zoomBy":
         if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-          float zoomFactor = (float) call.argument("zoomFactor");
-          webView.zoomBy(zoomFactor);
+          double zoomFactor = (double) call.argument("zoomFactor");
+          webView.zoomBy((float) zoomFactor);
           result.success(true);
         } else {
           result.success(false);
@@ -457,6 +457,20 @@ public class FlutterWebView implements PlatformView, MethodCallHandler  {
           result.success(false);
         }
         break;
+      case "requestFocusNodeHref":
+        if (webView != null) {
+          result.success(webView.requestFocusNodeHref());
+        } else {
+          result.success(false);
+        }
+        break;
+      case "requestImageRef":
+        if (webView != null) {
+          result.success(webView.requestImageRef());
+        } else {
+          result.success(false);
+        }
+        break;
       default:
         result.notImplemented();
     }
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java
index d00e114..e26386b 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java
@@ -6,8 +6,10 @@ import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.print.PrintAttributes;
 import android.print.PrintDocumentAdapter;
 import android.print.PrintManager;
@@ -84,6 +86,7 @@ final public class InAppWebView extends InputAwareWebView {
   public LinearLayout floatingContextMenu = null;
   public Map<String, Object> contextMenu = null;
   public Handler headlessHandler = new Handler(Looper.getMainLooper());
+  static Handler mHandler = new Handler();
 
   public Runnable checkScrollStoppedTask;
   public int initialPositionScrollStoppedTask;
@@ -1184,7 +1187,7 @@ final public class InAppWebView extends InputAwareWebView {
     }
 
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
-      if (newOptionsMap.get("mixedContentMode") != null && !options.mixedContentMode.equals(newOptions.mixedContentMode))
+      if (newOptionsMap.get("mixedContentMode") != null && (options.mixedContentMode == null || !options.mixedContentMode.equals(newOptions.mixedContentMode)))
         settings.setMixedContentMode(newOptions.mixedContentMode);
 
     if (newOptionsMap.get("supportMultipleWindows") != null && options.supportMultipleWindows != newOptions.supportMultipleWindows)
@@ -1213,7 +1216,7 @@ final public class InAppWebView extends InputAwareWebView {
     if (newOptionsMap.get("cacheEnabled") != null && options.cacheEnabled != newOptions.cacheEnabled)
       setCacheEnabled(newOptions.cacheEnabled);
 
-    if (newOptionsMap.get("appCachePath") != null && !options.appCachePath.equals(newOptions.appCachePath))
+    if (newOptionsMap.get("appCachePath") != null && (options.appCachePath == null || !options.appCachePath.equals(newOptions.appCachePath)))
       settings.setAppCachePath(newOptions.appCachePath);
 
     if (newOptionsMap.get("blockNetworkImage") != null && options.blockNetworkImage != newOptions.blockNetworkImage)
@@ -1237,8 +1240,9 @@ final public class InAppWebView extends InputAwareWebView {
     if (newOptionsMap.get("defaultTextEncodingName") != null && !options.defaultTextEncodingName.equals(newOptions.defaultTextEncodingName))
       settings.setDefaultTextEncodingName(newOptions.defaultTextEncodingName);
 
-    if (newOptionsMap.get("disabledActionModeMenuItems") != null && !options.disabledActionModeMenuItems.equals(newOptions.disabledActionModeMenuItems))
-      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+      if (newOptionsMap.get("disabledActionModeMenuItems") != null && (options.disabledActionModeMenuItems == null ||
+            !options.disabledActionModeMenuItems.equals(newOptions.disabledActionModeMenuItems)))
         settings.setDisabledActionModeMenuItems(newOptions.disabledActionModeMenuItems);
 
     if (newOptionsMap.get("fantasyFontFamily") != null && !options.fantasyFontFamily.equals(newOptions.fantasyFontFamily))
@@ -1318,7 +1322,8 @@ final public class InAppWebView extends InputAwareWebView {
         setLayerType(View.LAYER_TYPE_SOFTWARE, null);
     }
 
-    if (newOptionsMap.get("regexToCancelSubFramesLoading") != null && options.regexToCancelSubFramesLoading != newOptions.regexToCancelSubFramesLoading) {
+    if (newOptionsMap.get("regexToCancelSubFramesLoading") != null && (options.regexToCancelSubFramesLoading == null ||
+            !options.regexToCancelSubFramesLoading.equals(newOptions.regexToCancelSubFramesLoading))) {
       if (newOptions.regexToCancelSubFramesLoading == null)
         regexToCancelSubFramesLoadingCompiled = null;
       else
@@ -1338,13 +1343,15 @@ final public class InAppWebView extends InputAwareWebView {
     if (newOptionsMap.get("scrollBarStyle") != null && !options.scrollBarStyle.equals(newOptions.scrollBarStyle))
       setScrollBarStyle(newOptions.scrollBarStyle);
 
-    if (newOptionsMap.get("scrollBarDefaultDelayBeforeFade") != null && !options.scrollBarDefaultDelayBeforeFade.equals(newOptions.scrollBarDefaultDelayBeforeFade))
+    if (newOptionsMap.get("scrollBarDefaultDelayBeforeFade") != null && (options.scrollBarDefaultDelayBeforeFade == null ||
+            !options.scrollBarDefaultDelayBeforeFade.equals(newOptions.scrollBarDefaultDelayBeforeFade)))
       setScrollBarDefaultDelayBeforeFade(newOptions.scrollBarDefaultDelayBeforeFade);
 
     if (newOptionsMap.get("scrollbarFadingEnabled") != null && !options.scrollbarFadingEnabled.equals(newOptions.scrollbarFadingEnabled))
       setScrollbarFadingEnabled(newOptions.scrollbarFadingEnabled);
 
-    if (newOptionsMap.get("scrollBarFadeDuration") != null && !options.scrollBarFadeDuration.equals(newOptions.scrollBarFadeDuration))
+    if (newOptionsMap.get("scrollBarFadeDuration") != null && (options.scrollBarFadeDuration == null ||
+            !options.scrollBarFadeDuration.equals(newOptions.scrollBarFadeDuration)))
       setScrollBarFadeDuration(newOptions.scrollBarFadeDuration);
 
     if (newOptionsMap.get("verticalScrollbarPosition") != null && !options.verticalScrollbarPosition.equals(newOptions.verticalScrollbarPosition))
@@ -1802,6 +1809,30 @@ final public class InAppWebView extends InputAwareWebView {
     });
   }
 
+  public Map<String, Object> requestFocusNodeHref() {
+    Message msg = InAppWebView.mHandler.obtainMessage();
+    requestFocusNodeHref(msg);
+    Bundle bundle = msg.peekData();
+
+    Map<String, Object> obj = new HashMap<>();
+    obj.put("src", bundle.getString("src"));
+    obj.put("url", bundle.getString("url"));
+    obj.put("title", bundle.getString("title"));
+
+    return obj;
+  }
+
+  public Map<String, Object> requestImageRef() {
+    Message msg = InAppWebView.mHandler.obtainMessage();
+    requestImageRef(msg);
+    Bundle bundle = msg.peekData();
+
+    Map<String, Object> obj = new HashMap<>();
+    obj.put("url", bundle.getString("url"));
+
+    return obj;
+  }
+
   @Override
   public void dispose() {
     super.dispose();
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java
index 5b31a61..615929b 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java
@@ -465,8 +465,6 @@ public class InAppWebViewClient extends WebViewClient {
     }
     obj.put("message", message);
 
-    Log.d(LOG_TAG, obj.toString());
-
     channel.invokeMethod("onReceivedServerTrustAuthRequest", obj, new MethodChannel.Result() {
       @Override
       public void success(Object response) {
diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java
index aed179c..e6a8702 100755
--- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java
+++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java
@@ -43,12 +43,12 @@ public class InAppWebViewOptions implements Options<InAppWebView> {
   public Boolean disableVerticalScroll = false;
   public Boolean disableHorizontalScroll = false;
   public Boolean disableContextMenu = false;
+  public Boolean supportZoom = true;
 
   public Integer textZoom = 100;
   public Boolean clearSessionCache = false;
-  public Boolean builtInZoomControls = false;
+  public Boolean builtInZoomControls = true;
   public Boolean displayZoomControls = false;
-  public Boolean supportZoom = true;
   public Boolean databaseEnabled = false;
   public Boolean domStorageEnabled = true;
   public Boolean useWideViewPort = true;
diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies
index dced168..a5a27a9 100755
--- a/example/.flutter-plugins-dependencies
+++ b/example/.flutter-plugins-dependencies
@@ -1 +1 @@
-{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"android":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-03 01:35:21.255449","version":"1.17.1"}
\ No newline at end of file
+{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"android":[{"name":"e2e","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/e2e-0.2.4+4/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.9/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.0+hotfix.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-12 02:54:04.283438","version":"1.17.1"}
\ No newline at end of file
diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh
index 4882a9e..fb55de3 100755
--- a/example/ios/Flutter/flutter_export_environment.sh
+++ b/example/ios/Flutter/flutter_export_environment.sh
@@ -2,10 +2,11 @@
 # This is a generated file; do not edit or check into version control.
 export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter"
 export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
-export "FLUTTER_TARGET=lib/main.dart"
+export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart"
 export "FLUTTER_BUILD_DIR=build"
 export "SYMROOT=${SOURCE_ROOT}/../build/ios"
 export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
 export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios"
 export "FLUTTER_BUILD_NAME=1.0.0"
 export "FLUTTER_BUILD_NUMBER=1"
+export "TRACK_WIDGET_CREATION=true"
diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart
index 8a836c9..db32139 100755
--- a/example/lib/in_app_webiew_example.screen.dart
+++ b/example/lib/in_app_webiew_example.screen.dart
@@ -78,8 +78,8 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
                   BoxDecoration(border: Border.all(color: Colors.blueAccent)),
                   child: InAppWebView(
                     contextMenu: contextMenu,
-                    initialUrl: "https://github.com/flutter",
-                    // initialFile: "assets/index.html",
+                    // initialUrl: "https://github.com/flutter",
+                    initialFile: "assets/index.html",
                     initialHeaders: {},
                     initialOptions: InAppWebViewGroupOptions(
                       crossPlatform: InAppWebViewOptions(
diff --git a/example/test_driver/.env.dart b/example/test_driver/.env.dart
index bf49aee..672d39f 100755
--- a/example/test_driver/.env.dart
+++ b/example/test_driver/.env.dart
@@ -1 +1 @@
-final environment = {"NODE_SERVER_IP":"192.168.43.166"};
\ No newline at end of file
+final environment = {"NODE_SERVER_IP":"192.168.1.123"};
\ No newline at end of file
diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift
index 4bf95f3..6582cfd 100755
--- a/ios/Classes/FlutterWebViewController.swift
+++ b/ios/Classes/FlutterWebViewController.swift
@@ -203,7 +203,7 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
                     webView!.evaluateJavascript(source: source, result: result)
                 }
                 else {
-                    result("")
+                    result(nil)
                 }
                 break
             case "injectJavascriptFileFromUrl":
@@ -405,6 +405,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
                     webView!.getSelectedText { (value, error) in
                         if let err = error {
                             print(err.localizedDescription)
+                            result("")
+                            return
                         }
                         result(value)
                     }
@@ -418,6 +420,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
                     webView!.getHitTestResult { (value, error) in
                         if let err = error {
                             print(err.localizedDescription)
+                            result(nil)
+                            return
                         }
                         result(value)
                     }
@@ -443,6 +447,34 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
                     result(false)
                 }
                 break
+            case "requestFocusNodeHref":
+                if webView != nil {
+                    webView!.requestFocusNodeHref { (value, error) in
+                        if let err = error {
+                            print(err.localizedDescription)
+                            result(nil)
+                            return
+                        }
+                        result(value)
+                    }
+                } else {
+                    result(false)
+                }
+                break
+            case "requestImageRef":
+                if webView != nil {
+                    webView!.requestImageRef { (value, error) in
+                        if let err = error {
+                            print(err.localizedDescription)
+                            result(nil)
+                            return
+                        }
+                        result(value)
+                    }
+                } else {
+                    result(false)
+                }
+                break
             default:
                 result(FlutterMethodNotImplemented)
                 break
diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift
index c6463b6..97b3ebc 100755
--- a/ios/Classes/InAppWebView.swift
+++ b/ios/Classes/InAppWebView.swift
@@ -681,7 +681,6 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) {
         EDIT_TEXT_TYPE: 9
     };
     var element = document.elementFromPoint(x, y);
-    console.log(element);
     var data = {
         type: 0,
         extra: null
@@ -735,6 +734,63 @@ let getSelectedTextJS = """
 })();
 """
 
+let lastTouchedAnchorOrImageJS = """
+window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = null;
+window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null;
+(function() {
+    document.addEventListener('touchstart', function(event) {
+        var target = event.target;
+        while (target) {
+            if (target.tagName === 'IMG') {
+                var img = target;
+                window.flutter_inappwebview._lastImageTouched = {
+                    src: img.src
+                };
+                var parent = img.parentNode;
+                while (parent) {
+                    if (parent.tagName === 'A') {
+                        window.flutter_inappwebview._lastAnchorOrImageTouched = {
+                            title: parent.textContent,
+                            url: parent.href,
+                            src: img.src
+                        };
+                        break;
+                    }
+                    parent = parent.parentNode;
+                }
+                return;
+            } else if (target.tagName === 'A') {
+                var link = target;
+                var images = link.getElementsByTagName('img');
+                var img = (images.length > 0) ? images[0] : null;
+                var imgSrc = (img != null) ? img.src : null;
+                window.flutter_inappwebview._lastImageTouched = (img != null) ? {src: img.src} : window.flutter_inappwebview._lastImageTouched;
+                window.flutter_inappwebview._lastAnchorOrImageTouched = {
+                    title: link.textContent,
+                    url: link.href,
+                    src: imgSrc
+                };
+                return;
+            }
+            target = target.parentNode;
+        }
+    });
+})();
+"""
+
+let originalViewPortMetaTagContentJS = """
+window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = "";
+(function() {
+    var metaTagNodes = document.head.getElementsByTagName('meta');
+    for (var i = 0; i < metaTagNodes.length; i++) {
+        var metaTagNode = metaTagNodes[i];
+        if (metaTagNode.name === "viewport") {
+            window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content;
+        }
+    }
+})();
+"""
+
 var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:]
 
 public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate {
@@ -991,7 +1047,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
             }
         }
         
-        if (options?.enableViewportScale)! {
+        let originalViewPortMetaTagContentJSScript = WKUserScript(source: originalViewPortMetaTagContentJS, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
+        configuration.userContentController.addUserScript(originalViewPortMetaTagContentJSScript)
+        
+        if !(options?.supportZoom)! {
+            let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); document.getElementsByTagName('head')[0].appendChild(meta);"
+            let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
+            configuration.userContentController.addUserScript(userScript)
+        } else if (options?.enableViewportScale)! {
             let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
             let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
             configuration.userContentController.addUserScript(userScript)
@@ -1018,6 +1081,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
         let printJSScript = WKUserScript(source: printJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
         configuration.userContentController.addUserScript(printJSScript)
         
+        let lastTouchedAnchorOrImageJSScript = WKUserScript(source: lastTouchedAnchorOrImageJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+        configuration.userContentController.addUserScript(lastTouchedAnchorOrImageJSScript)
+        
         if (options?.useOnLoadResource)! {
             let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
             configuration.userContentController.addUserScript(resourceObserverJSScript)
@@ -1424,8 +1490,23 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
             }
         }
         
-        if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale && newOptions.enableViewportScale {
-            let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
+        if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale {
+            var jscript = ""
+            if (newOptions.enableViewportScale) {
+                jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
+            } else {
+                jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); document.getElementsByTagName('head')[0].appendChild(meta);"
+            }
+            evaluateJavaScript(jscript, completionHandler: nil)
+        }
+        
+        if newOptionsMap["supportZoom"] != nil && options?.supportZoom != newOptions.supportZoom {
+            var jscript = ""
+            if (newOptions.supportZoom) {
+                jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent); document.getElementsByTagName('head')[0].appendChild(meta);"
+            } else {
+                jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); document.getElementsByTagName('head')[0].appendChild(meta);"
+            }
             evaluateJavaScript(jscript, completionHandler: nil)
         }
         
@@ -1635,7 +1716,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
             }
             
             if value == nil {
-                result!("")
+                result!(nil)
                 return
             }
             
@@ -2742,6 +2823,34 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
         }
     }
     
+    public func requestFocusNodeHref(completionHandler: @escaping ([String: Any?]?, Error?) -> Void) {
+        if configuration.preferences.javaScriptEnabled {
+            // add some delay to make it sure _lastAnchorOrImageTouched is updated
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
+                self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched", completionHandler: {(value, error) in
+                    let lastAnchorOrImageTouched = value as? [String: Any?]
+                    completionHandler(lastAnchorOrImageTouched, error)
+                })
+            }
+        } else {
+            completionHandler(nil, nil)
+        }
+    }
+    
+    public func requestImageRef(completionHandler: @escaping ([String: Any?]?, Error?) -> Void) {
+        if configuration.preferences.javaScriptEnabled {
+            // add some delay to make it sure _lastImageTouched is updated
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
+                self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched", completionHandler: {(value, error) in
+                    let lastImageTouched = value as? [String: Any?]
+                    completionHandler(lastImageTouched, error)
+                })
+            }
+        } else {
+            completionHandler(nil, nil)
+        }
+    }
+    
     public func clearFocus() {
         self.scrollView.subviews.first?.resignFirstResponder()
     }
diff --git a/ios/Classes/InAppWebViewOptions.swift b/ios/Classes/InAppWebViewOptions.swift
index 00d92d4..38de832 100755
--- a/ios/Classes/InAppWebViewOptions.swift
+++ b/ios/Classes/InAppWebViewOptions.swift
@@ -34,6 +34,7 @@ public class InAppWebViewOptions: Options<InAppWebView> {
     var disableVerticalScroll = false
     var disableHorizontalScroll = false
     var disableContextMenu = false
+    var supportZoom = true
 
     var disallowOverScroll = false
     var enableViewportScale = false
diff --git a/lib/flutter_inappwebview.dart b/lib/flutter_inappwebview.dart
index b69a842..22a69b3 100755
--- a/lib/flutter_inappwebview.dart
+++ b/lib/flutter_inappwebview.dart
@@ -36,3 +36,4 @@ export 'src/content_blocker.dart';
 export 'src/http_auth_credentials_database.dart';
 export 'src/web_storage_manager.dart';
 export 'src/context_menu.dart';
+export 'src/web_storage.dart';
diff --git a/lib/src/cookie_manager.dart b/lib/src/cookie_manager.dart
index defd8c7..8db51de 100755
--- a/lib/src/cookie_manager.dart
+++ b/lib/src/cookie_manager.dart
@@ -41,7 +41,7 @@ class CookieManager {
       int expiresDate,
       int maxAge,
       bool isSecure}) async {
-    if (domain == null) domain = _getDomainName(url);
+    if (domain == null || domain.isEmpty) domain = _getDomainName(url);
 
     assert(url != null && url.isNotEmpty);
     assert(name != null && name.isNotEmpty);
diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart
index 59645a2..abe6921 100755
--- a/lib/src/in_app_webview.dart
+++ b/lib/src/in_app_webview.dart
@@ -13,7 +13,7 @@ import 'in_app_webview_controller.dart';
 
 ///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.
 class InAppWebView extends StatefulWidget implements WebView {
-  /// `gestureRecognizers` specifies which gestures should be consumed by the web view.
+  /// `gestureRecognizers` specifies which gestures should be consumed by the WebView.
   /// It is possible for other gesture recognizers to be competing with the web view on pointer
   /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle
   /// vertical drags. The web view will claim gestures that are recognized by any of the
diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart
index 10b3a43..9f40f5e 100644
--- a/lib/src/in_app_webview_controller.dart
+++ b/lib/src/in_app_webview_controller.dart
@@ -3,6 +3,7 @@ import 'dart:async';
 import 'dart:collection';
 import 'dart:typed_data';
 import 'dart:convert';
+import 'dart:core';
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
@@ -18,6 +19,8 @@ import 'webview_options.dart';
 import 'headless_in_app_webview.dart';
 import 'webview.dart';
 import 'in_app_webview.dart';
+import 'web_storage.dart';
+import 'util.dart';
 
 ///List of forbidden names for JavaScript handlers.
 const javaScriptHandlerForbiddenNames = [
@@ -56,6 +59,8 @@ class InAppWebViewController {
   ///iOS controller that contains only ios-specific methods
   IOSInAppWebViewController ios;
 
+  WebStorage webStorage;
+
   InAppWebViewController(dynamic id, WebView webview) {
     this._id = id;
     this._channel =
@@ -64,6 +69,7 @@ class InAppWebViewController {
     this._webview = webview;
     this.android = AndroidInAppWebViewController(this);
     this.ios = IOSInAppWebViewController(this);
+    this.webStorage = WebStorage(localStorage: LocalStorage(this), sessionStorage: SessionStorage(this));
   }
 
   InAppWebViewController.fromInAppBrowser(
@@ -789,8 +795,11 @@ class InAppWebViewController {
   ///If this doesn't work, it tries to get the content reading the file:
   ///- checking if it is an asset (`file:///`) or
   ///- downloading it using an `HttpClient` through the WebView's current url.
+  ///
+  ///Returns `null` if it was unable to get it.
   Future<String> getHtml() async {
-    var html = "";
+    String html;
+
     InAppWebViewGroupOptions options = await getOptions();
     if (options != null && options.crossPlatform.javaScriptEnabled == true) {
       html = await evaluateJavascript(
@@ -830,7 +839,7 @@ class InAppWebViewController {
     String manifestUrl;
 
     var html = await getHtml();
-    if (html.isEmpty) {
+    if (html != null && html.isEmpty) {
       return favicons;
     }
 
@@ -1503,6 +1512,121 @@ class InAppWebViewController {
     _inAppBrowser?.contextMenu = contextMenu;
   }
 
+  ///Requests the anchor or image element URL at the last tapped point.
+  ///
+  ///**NOTE**: On iOS it is implemented using JavaScript.
+  ///
+  ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#requestFocusNodeHref(android.os.Message)
+  Future<RequestFocusNodeHrefResult> requestFocusNodeHref() async {
+    Map<String, dynamic> args = <String, dynamic>{};
+    Map<dynamic, dynamic> result = await _channel.invokeMethod('requestFocusNodeHref', args);
+    return result != null ? RequestFocusNodeHrefResult(
+      url: result['url'],
+      title: result['title'],
+      src: result['src'],
+    ) : null;
+  }
+
+  ///Requests the URL of the image last touched by the user.
+  ///
+  ///**NOTE**: On iOS it is implemented using JavaScript.
+  ///
+  ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#requestImageRef(android.os.Message)
+  Future<RequestImageRefResult> requestImageRef() async {
+    Map<String, dynamic> args = <String, dynamic>{};
+    Map<dynamic, dynamic> result = await _channel.invokeMethod('requestImageRef', args);
+    return result != null ? RequestImageRefResult(
+      url: result['url'],
+    ) : null;
+  }
+
+  ///Returns the list of `<meta>` tags of the current WebView.
+  ///
+  ///**NOTE**: It is implemented using JavaScript.
+  Future<List<MetaTag>> getMetaTags() async {
+    List<MetaTag> metaTags = [];
+
+    List<Map<dynamic, dynamic>> metaTagList = (await evaluateJavascript(source: """
+(function() {
+  var metaTags = [];
+  var metaTagNodes = document.head.getElementsByTagName('meta');
+  for (var i = 0; i < metaTagNodes.length; i++) {
+    var metaTagNode = metaTagNodes[i];
+    
+    var otherAttributes = metaTagNode.getAttributeNames();
+    var nameIndex = otherAttributes.indexOf("name");
+    if (nameIndex !== -1) otherAttributes.splice(nameIndex, 1);
+    var contentIndex = otherAttributes.indexOf("content");
+    if (contentIndex !== -1) otherAttributes.splice(contentIndex, 1);
+    
+    var attrs = [];
+    for (var j = 0; j < otherAttributes.length; j++) {
+      var otherAttribute = otherAttributes[j];
+      attrs.push(
+        {
+          name: otherAttribute,
+          value: metaTagNode.getAttribute(otherAttribute)
+        }
+      );
+    }
+
+    metaTags.push(
+      {
+        name: metaTagNode.name,
+        content: metaTagNode.content,
+        attrs: attrs
+      }
+    );
+  }
+  return metaTags;
+})();
+    """))?.cast<Map<dynamic, dynamic>>();
+
+    if (metaTagList == null) {
+      return metaTags;
+    }
+
+    for (var metaTag in metaTagList) {
+      var attrs = <MetaTagAttribute>[];
+
+      for (var metaTagAttr in metaTag["attrs"]) {
+        attrs.add(
+          MetaTagAttribute(name: metaTagAttr["name"], value: metaTagAttr["value"])
+        );
+      }
+
+      metaTags.add(
+        MetaTag(name: metaTag["name"], content: metaTag["content"], attrs: attrs)
+      );
+    }
+
+    return metaTags;
+  }
+
+  ///Returns an instance of [Color] representing the `content` value of the
+  ///`<meta name="theme-color" content="">` tag of the current WebView, if available, otherwise `null`.
+  ///
+  ///**NOTE**: It is implemented using JavaScript.
+  Future<Color> getMetaThemeColor() async {
+    var metaTags = await getMetaTags();
+    MetaTag metaTagThemeColor;
+
+    for (var metaTag in metaTags) {
+      if (metaTag.name == "theme-color") {
+        metaTagThemeColor = metaTag;
+        break;
+      }
+    }
+
+    if (metaTagThemeColor == null) {
+      return null;
+    }
+
+    var colorValue = metaTagThemeColor.content;
+
+    return Util.convertColorFromStringRepresentation(colorValue);
+  }
+
   ///Gets the default user agent.
   ///
   ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebSettings#getDefaultUserAgent(android.content.Context)
@@ -1517,6 +1641,7 @@ class AndroidInAppWebViewController {
   InAppWebViewController _controller;
 
   AndroidInAppWebViewController(InAppWebViewController controller) {
+    assert(controller != null);
     this._controller = controller;
   }
 
@@ -1719,6 +1844,7 @@ class IOSInAppWebViewController {
   InAppWebViewController _controller;
 
   IOSInAppWebViewController(InAppWebViewController controller) {
+    assert(controller != null);
     this._controller = controller;
   }
 
diff --git a/lib/src/types.dart b/lib/src/types.dart
index f1e0a20..e6a2c92 100755
--- a/lib/src/types.dart
+++ b/lib/src/types.dart
@@ -3279,3 +3279,146 @@ class AndroidWebViewPackageInfo {
     return toMap().toString();
   }
 }
+
+///Class that represents the result used by the [InAppWebViewController.requestFocusNodeHref] method.
+class RequestFocusNodeHrefResult {
+  ///The anchor's href attribute.
+  String url;
+
+  ///The anchor's text.
+  String title;
+
+  ///The image's src attribute.
+  String src;
+
+  RequestFocusNodeHrefResult(
+      {this.url,
+        this.title,
+        this.src});
+
+  Map<String, dynamic> toMap() {
+    return {
+      "url": url,
+      "title": title,
+      "src": src
+    };
+  }
+
+  Map<String, dynamic> toJson() {
+    return this.toMap();
+  }
+
+  @override
+  String toString() {
+    return toMap().toString();
+  }
+}
+
+///Class that represents the result used by the [InAppWebViewController.requestImageRef] method.
+class RequestImageRefResult {
+  ///The image's url.
+  String url;
+
+  RequestImageRefResult({this.url});
+
+  Map<String, dynamic> toMap() {
+    return {
+      "url": url,
+    };
+  }
+
+  Map<String, dynamic> toJson() {
+    return this.toMap();
+  }
+
+  @override
+  String toString() {
+    return toMap().toString();
+  }
+}
+
+///Class that represents a `<meta>` HTML tag. It is used by the [InAppWebViewController.getMetaTags] method.
+class MetaTag {
+  ///The meta tag name value.
+  String name;
+  ///The meta tag content value.
+  String content;
+  ///The meta tag attributes list.
+  List<MetaTagAttribute> attrs;
+
+  MetaTag({this.name, this.content, this.attrs});
+
+  Map<String, dynamic> toMap() {
+    return {
+      "name": name,
+      "content": content,
+      "attrs": attrs
+    };
+  }
+
+  Map<String, dynamic> toJson() {
+    return this.toMap();
+  }
+
+  @override
+  String toString() {
+    return toMap().toString();
+  }
+}
+
+///Class that represents an attribute of a `<meta>` HTML tag. It is used by the [MetaTag] class.
+class MetaTagAttribute {
+  ///The attribute name.
+  String name;
+  ///The attribute value.
+  String value;
+
+  MetaTagAttribute({this.name, this.value});
+
+  Map<String, dynamic> toMap() {
+    return {
+      "name": name,
+      "value": value,
+    };
+  }
+
+  Map<String, dynamic> toJson() {
+    return this.toMap();
+  }
+
+  @override
+  String toString() {
+    return toMap().toString();
+  }
+}
+
+class WebStorageType {
+  final String _value;
+
+  const WebStorageType._internal(this._value);
+
+  static WebStorageType fromValue(String value) {
+    return ([
+      "localStorage",
+      "sessionStorage",
+    ].contains(value))
+        ? WebStorageType._internal(value)
+        : null;
+  }
+
+  String toValue() => _value;
+
+  @override
+  String toString() => _value;
+
+  static const LOCAL_STORAGE =
+  const WebStorageType._internal("localStorage");
+
+  static const SESSION_STORAGE =
+  const WebStorageType._internal("sessionStorage");
+
+  bool operator ==(value) => value == _value;
+
+  @override
+  int get hashCode => _value.hashCode;
+}
diff --git a/lib/src/util.dart b/lib/src/util.dart
new file mode 100644
index 0000000..1b058fa
--- /dev/null
+++ b/lib/src/util.dart
@@ -0,0 +1,425 @@
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+
+class Util {
+  static Color convertColorFromStringRepresentation(String colorValue) {
+    if (colorValue.startsWith("#")) {
+      return Util.getColorFromHex(colorValue);
+    } else if (colorValue.startsWith("rgb(")) {
+      return Util.getColorFromRgbString(colorValue);
+    } else if (colorValue.startsWith("rgba(")) {
+      return Util.getColorFromRgbaString(colorValue);
+    } else if (colorValue.startsWith("hls(")) {
+      return Util.getColorFromHlsString(colorValue);
+    } else if (colorValue.startsWith("hlsa(")) {
+      return Util.getColorFromHlsaString(colorValue);
+    } else {
+
+      /**
+        This part of the code is generated using the JavaScript code below on this link: https://drafts.csswg.org/css-color/#typedef-color
+
+
+        let code = 'switch(colorValue) {\n';
+        const table = document.querySelector('.named-color-table');
+        const colorNameCells = table.querySelectorAll('tr > th dfn');
+        const colorHexValueCells = table.querySelectorAll('tr > td:nth-child(4)');
+        for (let i = 0; i < colorNameCells.length; i++) {
+          const colorName = colorNameCells[i].textContent.trim();
+          const colorHexValue = colorHexValueCells[i].textContent.trim();
+          code += '  case "' + colorName + '":\n';
+          code += '     return Util.getColorFromHex("' + colorHexValue + '");\n';
+        }
+        code += '}';
+      */
+
+      switch(colorValue) {
+        case "aliceblue":
+          return Util.getColorFromHex("#f0f8ff");
+        case "antiquewhite":
+          return Util.getColorFromHex("#faebd7");
+        case "aqua":
+          return Util.getColorFromHex("#00ffff");
+        case "aquamarine":
+          return Util.getColorFromHex("#7fffd4");
+        case "azure":
+          return Util.getColorFromHex("#f0ffff");
+        case "beige":
+          return Util.getColorFromHex("#f5f5dc");
+        case "bisque":
+          return Util.getColorFromHex("#ffe4c4");
+        case "black":
+          return Util.getColorFromHex("#000000");
+        case "blanchedalmond":
+          return Util.getColorFromHex("#ffebcd");
+        case "blue":
+          return Util.getColorFromHex("#0000ff");
+        case "blueviolet":
+          return Util.getColorFromHex("#8a2be2");
+        case "brown":
+          return Util.getColorFromHex("#a52a2a");
+        case "burlywood":
+          return Util.getColorFromHex("#deb887");
+        case "cadetblue":
+          return Util.getColorFromHex("#5f9ea0");
+        case "chartreuse":
+          return Util.getColorFromHex("#7fff00");
+        case "chocolate":
+          return Util.getColorFromHex("#d2691e");
+        case "coral":
+          return Util.getColorFromHex("#ff7f50");
+        case "cornflowerblue":
+          return Util.getColorFromHex("#6495ed");
+        case "cornsilk":
+          return Util.getColorFromHex("#fff8dc");
+        case "crimson":
+          return Util.getColorFromHex("#dc143c");
+        case "cyan":
+          return Util.getColorFromHex("#00ffff");
+        case "darkblue":
+          return Util.getColorFromHex("#00008b");
+        case "darkcyan":
+          return Util.getColorFromHex("#008b8b");
+        case "darkgoldenrod":
+          return Util.getColorFromHex("#b8860b");
+        case "darkgray":
+          return Util.getColorFromHex("#a9a9a9");
+        case "darkgreen":
+          return Util.getColorFromHex("#006400");
+        case "darkgrey":
+          return Util.getColorFromHex("#a9a9a9");
+        case "darkkhaki":
+          return Util.getColorFromHex("#bdb76b");
+        case "darkmagenta":
+          return Util.getColorFromHex("#8b008b");
+        case "darkolivegreen":
+          return Util.getColorFromHex("#556b2f");
+        case "darkorange":
+          return Util.getColorFromHex("#ff8c00");
+        case "darkorchid":
+          return Util.getColorFromHex("#9932cc");
+        case "darkred":
+          return Util.getColorFromHex("#8b0000");
+        case "darksalmon":
+          return Util.getColorFromHex("#e9967a");
+        case "darkseagreen":
+          return Util.getColorFromHex("#8fbc8f");
+        case "darkslateblue":
+          return Util.getColorFromHex("#483d8b");
+        case "darkslategray":
+          return Util.getColorFromHex("#2f4f4f");
+        case "darkslategrey":
+          return Util.getColorFromHex("#2f4f4f");
+        case "darkturquoise":
+          return Util.getColorFromHex("#00ced1");
+        case "darkviolet":
+          return Util.getColorFromHex("#9400d3");
+        case "deeppink":
+          return Util.getColorFromHex("#ff1493");
+        case "deepskyblue":
+          return Util.getColorFromHex("#00bfff");
+        case "dimgray":
+          return Util.getColorFromHex("#696969");
+        case "dimgrey":
+          return Util.getColorFromHex("#696969");
+        case "dodgerblue":
+          return Util.getColorFromHex("#1e90ff");
+        case "firebrick":
+          return Util.getColorFromHex("#b22222");
+        case "floralwhite":
+          return Util.getColorFromHex("#fffaf0");
+        case "forestgreen":
+          return Util.getColorFromHex("#228b22");
+        case "fuchsia":
+          return Util.getColorFromHex("#ff00ff");
+        case "gainsboro":
+          return Util.getColorFromHex("#dcdcdc");
+        case "ghostwhite":
+          return Util.getColorFromHex("#f8f8ff");
+        case "gold":
+          return Util.getColorFromHex("#ffd700");
+        case "goldenrod":
+          return Util.getColorFromHex("#daa520");
+        case "gray":
+          return Util.getColorFromHex("#808080");
+        case "green":
+          return Util.getColorFromHex("#008000");
+        case "greenyellow":
+          return Util.getColorFromHex("#adff2f");
+        case "grey":
+          return Util.getColorFromHex("#808080");
+        case "honeydew":
+          return Util.getColorFromHex("#f0fff0");
+        case "hotpink":
+          return Util.getColorFromHex("#ff69b4");
+        case "indianred":
+          return Util.getColorFromHex("#cd5c5c");
+        case "indigo":
+          return Util.getColorFromHex("#4b0082");
+        case "ivory":
+          return Util.getColorFromHex("#fffff0");
+        case "khaki":
+          return Util.getColorFromHex("#f0e68c");
+        case "lavender":
+          return Util.getColorFromHex("#e6e6fa");
+        case "lavenderblush":
+          return Util.getColorFromHex("#fff0f5");
+        case "lawngreen":
+          return Util.getColorFromHex("#7cfc00");
+        case "lemonchiffon":
+          return Util.getColorFromHex("#fffacd");
+        case "lightblue":
+          return Util.getColorFromHex("#add8e6");
+        case "lightcoral":
+          return Util.getColorFromHex("#f08080");
+        case "lightcyan":
+          return Util.getColorFromHex("#e0ffff");
+        case "lightgoldenrodyellow":
+          return Util.getColorFromHex("#fafad2");
+        case "lightgray":
+          return Util.getColorFromHex("#d3d3d3");
+        case "lightgreen":
+          return Util.getColorFromHex("#90ee90");
+        case "lightgrey":
+          return Util.getColorFromHex("#d3d3d3");
+        case "lightpink":
+          return Util.getColorFromHex("#ffb6c1");
+        case "lightsalmon":
+          return Util.getColorFromHex("#ffa07a");
+        case "lightseagreen":
+          return Util.getColorFromHex("#20b2aa");
+        case "lightskyblue":
+          return Util.getColorFromHex("#87cefa");
+        case "lightslategray":
+          return Util.getColorFromHex("#778899");
+        case "lightslategrey":
+          return Util.getColorFromHex("#778899");
+        case "lightsteelblue":
+          return Util.getColorFromHex("#b0c4de");
+        case "lightyellow":
+          return Util.getColorFromHex("#ffffe0");
+        case "lime":
+          return Util.getColorFromHex("#00ff00");
+        case "limegreen":
+          return Util.getColorFromHex("#32cd32");
+        case "linen":
+          return Util.getColorFromHex("#faf0e6");
+        case "magenta":
+          return Util.getColorFromHex("#ff00ff");
+        case "maroon":
+          return Util.getColorFromHex("#800000");
+        case "mediumaquamarine":
+          return Util.getColorFromHex("#66cdaa");
+        case "mediumblue":
+          return Util.getColorFromHex("#0000cd");
+        case "mediumorchid":
+          return Util.getColorFromHex("#ba55d3");
+        case "mediumpurple":
+          return Util.getColorFromHex("#9370db");
+        case "mediumseagreen":
+          return Util.getColorFromHex("#3cb371");
+        case "mediumslateblue":
+          return Util.getColorFromHex("#7b68ee");
+        case "mediumspringgreen":
+          return Util.getColorFromHex("#00fa9a");
+        case "mediumturquoise":
+          return Util.getColorFromHex("#48d1cc");
+        case "mediumvioletred":
+          return Util.getColorFromHex("#c71585");
+        case "midnightblue":
+          return Util.getColorFromHex("#191970");
+        case "mintcream":
+          return Util.getColorFromHex("#f5fffa");
+        case "mistyrose":
+          return Util.getColorFromHex("#ffe4e1");
+        case "moccasin":
+          return Util.getColorFromHex("#ffe4b5");
+        case "navajowhite":
+          return Util.getColorFromHex("#ffdead");
+        case "navy":
+          return Util.getColorFromHex("#000080");
+        case "oldlace":
+          return Util.getColorFromHex("#fdf5e6");
+        case "olive":
+          return Util.getColorFromHex("#808000");
+        case "olivedrab":
+          return Util.getColorFromHex("#6b8e23");
+        case "orange":
+          return Util.getColorFromHex("#ffa500");
+        case "orangered":
+          return Util.getColorFromHex("#ff4500");
+        case "orchid":
+          return Util.getColorFromHex("#da70d6");
+        case "palegoldenrod":
+          return Util.getColorFromHex("#eee8aa");
+        case "palegreen":
+          return Util.getColorFromHex("#98fb98");
+        case "paleturquoise":
+          return Util.getColorFromHex("#afeeee");
+        case "palevioletred":
+          return Util.getColorFromHex("#db7093");
+        case "papayawhip":
+          return Util.getColorFromHex("#ffefd5");
+        case "peachpuff":
+          return Util.getColorFromHex("#ffdab9");
+        case "peru":
+          return Util.getColorFromHex("#cd853f");
+        case "pink":
+          return Util.getColorFromHex("#ffc0cb");
+        case "plum":
+          return Util.getColorFromHex("#dda0dd");
+        case "powderblue":
+          return Util.getColorFromHex("#b0e0e6");
+        case "purple":
+          return Util.getColorFromHex("#800080");
+        case "rebeccapurple":
+          return Util.getColorFromHex("#663399");
+        case "red":
+          return Util.getColorFromHex("#ff0000");
+        case "rosybrown":
+          return Util.getColorFromHex("#bc8f8f");
+        case "royalblue":
+          return Util.getColorFromHex("#4169e1");
+        case "saddlebrown":
+          return Util.getColorFromHex("#8b4513");
+        case "salmon":
+          return Util.getColorFromHex("#fa8072");
+        case "sandybrown":
+          return Util.getColorFromHex("#f4a460");
+        case "seagreen":
+          return Util.getColorFromHex("#2e8b57");
+        case "seashell":
+          return Util.getColorFromHex("#fff5ee");
+        case "sienna":
+          return Util.getColorFromHex("#a0522d");
+        case "silver":
+          return Util.getColorFromHex("#c0c0c0");
+        case "skyblue":
+          return Util.getColorFromHex("#87ceeb");
+        case "slateblue":
+          return Util.getColorFromHex("#6a5acd");
+        case "slategray":
+          return Util.getColorFromHex("#708090");
+        case "slategrey":
+          return Util.getColorFromHex("#708090");
+        case "snow":
+          return Util.getColorFromHex("#fffafa");
+        case "springgreen":
+          return Util.getColorFromHex("#00ff7f");
+        case "steelblue":
+          return Util.getColorFromHex("#4682b4");
+        case "tan":
+          return Util.getColorFromHex("#d2b48c");
+        case "teal":
+          return Util.getColorFromHex("#008080");
+        case "thistle":
+          return Util.getColorFromHex("#d8bfd8");
+        case "tomato":
+          return Util.getColorFromHex("#ff6347");
+        case "turquoise":
+          return Util.getColorFromHex("#40e0d0");
+        case "violet":
+          return Util.getColorFromHex("#ee82ee");
+        case "wheat":
+          return Util.getColorFromHex("#f5deb3");
+        case "white":
+          return Util.getColorFromHex("#ffffff");
+        case "whitesmoke":
+          return Util.getColorFromHex("#f5f5f5");
+        case "yellow":
+          return Util.getColorFromHex("#ffff00");
+        case "yellowgreen":
+          return Util.getColorFromHex("#9acd32");
+      }
+    }
+    return null;
+  }
+
+  static Color getColorFromHex(String hexString) {
+    hexString = hexString.trim();
+    if (hexString.length == 4) { // convert for example #f00 to #ff0000
+      hexString = "#" + (hexString[1] * 2) + (hexString[2] * 2) + (hexString[3] * 2);
+    }
+    final buffer = StringBuffer();
+    if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
+    buffer.write(hexString.replaceFirst('#', ''));
+    return Color(int.parse(buffer.toString(), radix: 16));
+  }
+
+  static Color getColorFromRgbString(String rgbString) {
+    rgbString = rgbString.trim();
+    var rgbValues = rgbString
+        .substring(4, rgbString.length - 1)
+        .split(",")
+        .map((rbgValue) => int.parse(rbgValue.trim()))
+        .toList();
+    return Color.fromRGBO(rgbValues[0], rgbValues[1], rgbValues[2], 1);
+  }
+
+  static Color getColorFromRgbaString(String rgbaString) {
+    rgbaString = rgbaString.trim();
+    var rgbaValues = rgbaString
+        .substring(5, rgbaString.length - 1)
+        .split(",")
+        .map((rbgValue) => rbgValue.trim())
+        .toList();
+    return Color.fromRGBO(int.parse(rgbaValues[0]), int.parse(rgbaValues[1]), int.parse(rgbaValues[2]), double.parse(rgbaValues[3]));
+  }
+
+  static Color getColorFromHlsString(String hlsString) {
+    hlsString = hlsString.trim();
+    var hlsValues = hlsString
+        .substring(4, hlsString.length - 1)
+        .split(",")
+        .map((rbgValue) => double.parse(rbgValue.trim()))
+        .toList();
+    var rgbValues = hslToRgb(hlsValues[0], hlsValues[1], hlsValues[2]);
+    return Color.fromRGBO(rgbValues[0], rgbValues[1], rgbValues[2], 1);
+  }
+
+  static Color getColorFromHlsaString(String hlsaString) {
+    hlsaString = hlsaString.trim();
+    var hlsaValues = hlsaString
+        .substring(5, hlsaString.length - 1)
+        .split(",")
+        .map((rbgValue) => double.parse(rbgValue.trim()))
+        .toList();
+    var rgbaValues = hslToRgb(hlsaValues[0], hlsaValues[1], hlsaValues[2]);
+    return Color.fromRGBO(rgbaValues[0], rgbaValues[1], rgbaValues[2], hlsaValues[3]);
+  }
+
+  static List<num> hslToRgb(double h, double s, double l){
+    double r, g, b;
+
+    if (s == 0) {
+      r = g = b = l; // achromatic
+    } else {
+      double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+      double p = 2 * l - q;
+      r = hueToRgb(p, q, h + 1/3);
+      g = hueToRgb(p, q, h);
+      b = hueToRgb(p, q, h - 1/3);
+    }
+    var rgb = [to255(r), to255(g), to255(b)];
+    return rgb;
+  }
+
+  static num to255(double v) {
+    return min(255, 256*v);
+  }
+
+  /// Helper method that converts hue to rgb
+  static double hueToRgb(double p, double q, double t) {
+    if (t < 0)
+      t += 1;
+    if (t > 1)
+      t -= 1;
+    if (t < 1/6)
+      return p + (q - p) * 6 * t;
+    if (t < 1/2)
+      return q;
+    if (t < 2/3)
+      return p + (q - p) * (2/3 - t) * 6;
+    return p;
+  }
+}
\ No newline at end of file
diff --git a/lib/src/web_storage.dart b/lib/src/web_storage.dart
new file mode 100644
index 0000000..495bac6
--- /dev/null
+++ b/lib/src/web_storage.dart
@@ -0,0 +1,136 @@
+import 'dart:convert';
+
+import 'package:flutter/foundation.dart';
+
+import 'in_app_webview_controller.dart';
+import 'types.dart';
+
+class WebStorage {
+  LocalStorage localStorage;
+  SessionStorage sessionStorage;
+
+  WebStorage({@required this.localStorage, @required this.sessionStorage});
+}
+
+class WebStorageItem {
+  String key;
+  dynamic value;
+
+  WebStorageItem({this.key, this.value});
+
+  Map<String, dynamic> toMap() {
+    return {
+      "key": key,
+      "value": value,
+    };
+  }
+
+  Map<String, dynamic> toJson() {
+    return this.toMap();
+  }
+
+  @override
+  String toString() {
+    return toMap().toString();
+  }
+}
+
+class Storage {
+  InAppWebViewController _controller;
+  WebStorageType webStorageType;
+
+  Storage(InAppWebViewController controller, this.webStorageType) {
+    assert(controller != null && this.webStorageType != null);
+    this._controller = controller;
+  }
+
+  Future<int> length() async {
+    var result = await _controller.evaluateJavascript(source: """
+    window.$webStorageType.length;
+    """);
+    return result != null ? int.parse(json.decode(result)) : null;
+  }
+
+  Future<void> setItem({@required String key, @required dynamic value}) async {
+    var encodedValue = json.encode(value);
+    await _controller.evaluateJavascript(source: """
+    window.$webStorageType.setItem("$key", ${ value is String ? encodedValue : "JSON.stringify($encodedValue)" });
+    """);
+  }
+
+  Future<dynamic> getItem({@required String key}) async {
+    var itemValue = await _controller.evaluateJavascript(source: """
+    window.$webStorageType.getItem("$key");
+    """);
+
+    if (itemValue == null) {
+      return null;
+    }
+
+    try {
+      return json.decode(itemValue);
+    } catch (e) {}
+
+    return itemValue;
+  }
+
+  Future<void> removeItem({@required String key}) async {
+    await _controller.evaluateJavascript(source: """
+    window.$webStorageType.removeItem("$key");
+    """);
+  }
+
+  Future<List<WebStorageItem>> getItems() async {
+
+    var webStorageItems = <WebStorageItem>[];
+
+    List<Map<dynamic, dynamic>> items = (await _controller.evaluateJavascript(source: """
+(function() {
+  var webStorageItems = [];
+  for(var i = 0; i < window.$webStorageType.length; i++){
+    var key = window.$webStorageType.key(i);
+    webStorageItems.push(
+      {
+        key: key,
+        value: window.$webStorageType.getItem(key)
+      }
+    );
+  }
+  return webStorageItems;
+})();
+    """)).cast<Map<dynamic, dynamic>>();
+
+    if (items == null) {
+      return webStorageItems;
+    }
+
+    for (var item in items) {
+      webStorageItems.add(
+          WebStorageItem(key: item["key"], value: item["value"])
+      );
+    }
+
+    return webStorageItems;
+  }
+
+  Future<void> clear() async {
+    await _controller.evaluateJavascript(source: """
+    window.$webStorageType.clear();
+    """);
+  }
+
+  Future<String> key({@required int index}) async {
+    var result = await _controller.evaluateJavascript(source: """
+    window.$webStorageType.key($index);
+    """);
+    return result != null ? json.decode(result) : null;
+  }
+}
+
+class LocalStorage extends Storage {
+  LocalStorage(InAppWebViewController controller) : super(controller, WebStorageType.LOCAL_STORAGE);
+}
+
+class SessionStorage extends Storage {
+  SessionStorage(InAppWebViewController controller) : super(controller, WebStorageType.SESSION_STORAGE);
+}
diff --git a/lib/src/webview.dart b/lib/src/webview.dart
index 135469a..228952c 100644
--- a/lib/src/webview.dart
+++ b/lib/src/webview.dart
@@ -380,7 +380,7 @@ abstract class WebView {
   ///
   ///[request] Object containing the details of the request.
   ///
-  ///**NOTE**: available only on Android.
+  ///**NOTE**: available only on Android. In order to be able to listen this event, you need to set [AndroidInAppWebViewOptions.useShouldInterceptRequest] option to `true`.
   ///
   ///**Official Android API**:
   ///- https://developer.android.com/reference/android/webkit/WebViewClient#shouldInterceptRequest(android.webkit.WebView,%20android.webkit.WebResourceRequest)
diff --git a/lib/src/webview_options.dart b/lib/src/webview_options.dart
index a9061c9..5b12660 100755
--- a/lib/src/webview_options.dart
+++ b/lib/src/webview_options.dart
@@ -159,6 +159,9 @@ class InAppWebViewOptions
   ///Set to `true` to disable context menu. The default value is `false`.
   bool disableContextMenu;
 
+  ///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
+  bool supportZoom;
+
   InAppWebViewOptions(
       {this.useShouldOverrideUrlLoading = false,
       this.useOnLoadResource = false,
@@ -183,7 +186,8 @@ class InAppWebViewOptions
       this.transparentBackground = false,
       this.disableVerticalScroll = false,
       this.disableHorizontalScroll = false,
-      this.disableContextMenu = false}) {
+      this.disableContextMenu = false,
+      this.supportZoom = true}) {
     if (this.minimumFontSize == null)
       this.minimumFontSize = Platform.isAndroid ? 8 : 0;
     assert(!this.resourceCustomSchemes.contains("http") &&
@@ -221,7 +225,8 @@ class InAppWebViewOptions
       "transparentBackground": transparentBackground,
       "disableVerticalScroll": disableVerticalScroll,
       "disableHorizontalScroll": disableHorizontalScroll,
-      "disableContextMenu": disableContextMenu
+      "disableContextMenu": disableContextMenu,
+      "supportZoom": supportZoom
     };
   }
 
@@ -266,6 +271,7 @@ class InAppWebViewOptions
     options.disableVerticalScroll = map["disableVerticalScroll"];
     options.disableHorizontalScroll = map["disableHorizontalScroll"];
     options.disableContextMenu = map["disableContextMenu"];
+    options.supportZoom = map["supportZoom"];
     return options;
   }
 
@@ -289,15 +295,12 @@ class AndroidInAppWebViewOptions
   ///Set to `true` to have the session cookie cache cleared before the new window is opened.
   bool clearSessionCache;
 
-  ///Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`.
+  ///Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `true`.
   bool builtInZoomControls;
 
   ///Set to `true` if the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. The default value is `false`.
   bool displayZoomControls;
 
-  ///Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
-  bool supportZoom;
-
   ///Set to `true` if you want the database storage API is enabled. The default value is `true`.
   bool databaseEnabled;
 
@@ -493,10 +496,9 @@ class AndroidInAppWebViewOptions
   AndroidInAppWebViewOptions({
     this.textZoom = 100,
     this.clearSessionCache = false,
-    this.builtInZoomControls = false,
+    this.builtInZoomControls = true,
     this.displayZoomControls = false,
-    this.supportZoom = true,
-    this.databaseEnabled = false,
+    this.databaseEnabled = true,
     this.domStorageEnabled = true,
     this.useWideViewPort = true,
     this.safeBrowsingEnabled = true,
@@ -553,7 +555,6 @@ class AndroidInAppWebViewOptions
       "clearSessionCache": clearSessionCache,
       "builtInZoomControls": builtInZoomControls,
       "displayZoomControls": displayZoomControls,
-      "supportZoom": supportZoom,
       "databaseEnabled": databaseEnabled,
       "domStorageEnabled": domStorageEnabled,
       "useWideViewPort": useWideViewPort,
@@ -610,7 +611,6 @@ class AndroidInAppWebViewOptions
     options.clearSessionCache = map["clearSessionCache"];
     options.builtInZoomControls = map["builtInZoomControls"];
     options.displayZoomControls = map["displayZoomControls"];
-    options.supportZoom = map["supportZoom"];
     options.databaseEnabled = map["databaseEnabled"];
     options.domStorageEnabled = map["domStorageEnabled"];
     options.useWideViewPort = map["useWideViewPort"];
diff --git a/pubspec.yaml b/pubspec.yaml
index 2840ded..218af4d 100755
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: flutter_inappwebview
 description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
-version: 3.3.0+3
+version: 3.4.0
 homepage: https://github.com/pichillilorenzo/flutter_inappwebview
 
 environment:
-- 
2.26.2