From ab0a5cc5b5a46af35398b8b9aa0b40b56739dc1a Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Tue, 29 Oct 2019 03:03:50 +0100 Subject: [PATCH] Added , (available only for Android), Added , and events to manage javascript popup dialogs only for Android now, added support for javascript dialogs popups for iOS --- .idea/workspace.xml | 321 ++++++++---------- CHANGELOG.md | 2 + android/src/main/AndroidManifest.xml | 1 - .../ContentBlocker/ContentBlockerHandler.java | 3 +- .../ContentBlockerTriggerResourceType.java | 1 + .../flutter_inappbrowser/FlutterWebView.java | 16 +- .../InAppBrowserActivity.java | 14 + .../InAppBrowserFlutterPlugin.java | 19 ++ .../InAppWebView/InAppWebChromeClient.java | 273 +++++++++++++++ .../InAppWebView/InAppWebView.java | 33 +- .../InAppWebView/InAppWebViewClient.java | 9 +- android/src/main/res/values/styles.xml | 4 + .../android/app/src/main/AndroidManifest.xml | 6 +- .../flutterwebviewexample/MainActivity.java | 2 +- example/assets/index.html | 9 +- example/lib/inline_example.screen.dart | 103 +++++- ios/Classes/FlutterWebViewController.swift | 1 + ios/Classes/InAppWebView.swift | 57 ++++ lib/src/in_app_browser.dart | 36 +- lib/src/in_app_webview.dart | 119 ++++++- lib/src/types.dart | 96 +++++- 21 files changed, 908 insertions(+), 217 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index dc97bd14..4244e9f6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -15,39 +15,27 @@ - - - - + + + + + - - - + + - - - - - - - - - - - - @@ -67,11 +55,11 @@ - + - - + + @@ -85,23 +73,11 @@ - - - - - - - - - - - - - - + + @@ -109,20 +85,11 @@ - - - - - - - - - - - + + @@ -131,10 +98,13 @@ - + - - + + + + + @@ -142,17 +112,17 @@ - - + + - + - - + + @@ -163,7 +133,7 @@ - + @@ -184,21 +154,6 @@ - options) - WebViewOptions - websiteDataStore - CacheMode - toStr - cacheMode - disabledActionModeMenuItems - AndroidInAppWebViewModeMenuItem - fantasyFontFamily - AndroidInAppWebViewLayoutAlgorithm - loadWithOverviewMode - AndroidInAppWebViewCacheMode - mixedContentMode - disable - appCachePath List<Con appCacheEnabled List<Content @@ -214,6 +169,21 @@ defaultWebpagePreferences contentBlockers preferredContentMode + useOnLoadResource + safe + InAppWebViewController + safeBrowsingEnabled + only + ///**NOTE**: available only for Android. + _inAppBrowser + _channel + uuid_ + javaScriptHandlersMap + onJsAlertResponseCallback + _jsResultCallbackMap + onJs + onJsAlert + defaultValue activity.getPreferences(0) @@ -222,6 +192,7 @@ flutter_inappbrowser throwIsNotOpened ChannelManager + Function $PROJECT_DIR$/example/android @@ -271,23 +242,24 @@ - + + @@ -539,11 +487,11 @@ - + - - + + @@ -553,7 +501,7 @@ - + @@ -561,7 +509,7 @@ - + @@ -626,7 +574,6 @@ - @@ -825,16 +772,6 @@ - - - - - - - - - - @@ -849,26 +786,16 @@ - + - - - - - - - - - - - - + + - + @@ -878,22 +805,32 @@ - - + + - + - - + + + + + + + + + + + + - - + + @@ -907,20 +844,40 @@ - + - - + + + + + + + + + - + - + - - + + + + + + + + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 288ee685..5c61d852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - Added new Android WebView options: `allowContentAccess`, `allowFileAccess`, `allowFileAccessFromFileURLs`, `allowUniversalAccessFromFileURLs`, `appCacheEnabled`, `appCachePath`, `blockNetworkImage`, `blockNetworkLoads`, `cacheMode`, `cursiveFontFamily`, `defaultFixedFontSize`, `defaultFontSize`, `defaultTextEncodingName`, `disabledActionModeMenuItems`, `fantasyFontFamily`, `fixedFontFamily`, `forceDark`, `geolocationEnabled`, `layoutAlgorithm`, `loadWithOverviewMode`, `loadsImagesAutomatically`, `minimumLogicalFontSize`, `needInitialFocus`, `offscreenPreRaster`, `sansSerifFontFamily`, `serifFontFamily`, `standardFontFamily` - Added new iOS WebView options: `applicationNameForUserAgent`, `isFraudulentWebsiteWarningEnabled`, `selectionGranularity`, `dataDetectorTypes`, `preferredContentMode` - Added `onGeolocationPermissionsShowPrompt` event and `GeolocationPermissionShowPromptResponse` class (available only for Android) +- Added `startSafeBrowsing`, `setSafeBrowsingWhitelist` and `getSafeBrowsingPrivacyPolicyUrl` methods (available only for Android) +- Added `onJsAlert`, `onJsConfirm` and `onJsPrompt` events to manage javascript popup dialogs ### BREAKING CHANGES - Deleted `WebResourceRequest` class diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 3245b439..aaa8e933 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -3,7 +3,6 @@ package="com.pichillilorenzo.flutter_inappbrowser"> - diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerHandler.java index ef45be8e..ea0b2071 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerHandler.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerHandler.java @@ -192,12 +192,11 @@ public class ContentBlockerHandler { return checkUrl(webView, url, responseResourceType); } - public ContentBlockerTriggerResourceType getResourceTypeFromUrl(InAppWebView webView, String url) { ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW; - // make an HTTP "HEAD" request to the server for that URL. This will not return the full content of the URL. if (url.startsWith("http://") || url.startsWith("https://")) { + // make an HTTP "HEAD" request to the server for that URL. This will not return the full content of the URL. Request mRequest = new Request.Builder().url(url).head().build(); Response response = null; try { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerTriggerResourceType.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerTriggerResourceType.java index c96192e6..26407e61 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerTriggerResourceType.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerTriggerResourceType.java @@ -8,6 +8,7 @@ public enum ContentBlockerTriggerResourceType { FONT ("font"), SVG_DOCUMENT ("svg-document"), MEDIA ("media"), + POPUP ("popup"), RAW ("raw"); private final String value; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java index 294034aa..ab666854 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java @@ -195,7 +195,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { if (webView != null) webView.takeScreenshot(result); else - result.error(LOG_TAG, "webView is null", null); + result.success(null); break; case "setOptions": if (webView != null) { @@ -212,6 +212,20 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { case "getCopyBackForwardList": result.success((webView != null) ? webView.getCopyBackForwardList() : null); break; + case "startSafeBrowsing": + if (webView != null) + webView.startSafeBrowsing(result); + else + result.success(false); + break; + case "setSafeBrowsingWhitelist": + if (webView != null) { + List hosts = (List) call.argument("hosts"); + webView.setSafeBrowsingWhitelist(hosts, result); + } + else + result.success(false); + break; case "dispose": dispose(); result.success(true); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java index 6ccf486c..ce5284e0 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java @@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import io.flutter.app.FlutterActivity; @@ -460,4 +461,17 @@ public class InAppBrowserActivity extends AppCompatActivity { return null; } + public void startSafeBrowsing(MethodChannel.Result result) { + if (webView != null) + webView.startSafeBrowsing(result); + else + result.success(false); + } + + public void setSafeBrowsingWhitelist(List hosts, MethodChannel.Result result) { + if (webView != null) + webView.setSafeBrowsingWhitelist(hosts, result); + else + result.success(false); + } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java index ae65c085..ea08456c 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java @@ -299,6 +299,12 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { case "getCopyBackForwardList": result.success(getCopyBackForwardList(uuid)); break; + case "startSafeBrowsing": + startSafeBrowsing(uuid, result); + break; + case "setSafeBrowsingWhitelist": + setSafeBrowsingWhitelist(uuid, (List) call.argument("hosts"), result); + break; default: result.notImplemented(); } @@ -668,4 +674,17 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { return null; } + public void startSafeBrowsing(String uuid, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.startSafeBrowsing(result); + result.success(false); + } + + public void setSafeBrowsingWhitelist(String uuid, List hosts, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.setSafeBrowsingWhitelist(hosts, result); + result.success(false); + } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebChromeClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebChromeClient.java index 0181a369..0df49d0d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebChromeClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebChromeClient.java @@ -2,25 +2,37 @@ package com.pichillilorenzo.flutter_inappbrowser.InAppWebView; import android.Manifest; import android.app.Activity; +import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.net.Uri; import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.text.Html; import android.util.Log; import android.view.View; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; +import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import androidx.appcompat.app.AlertDialog; import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin; +import com.pichillilorenzo.flutter_inappbrowser.R; import com.pichillilorenzo.flutter_inappbrowser.RequestPermissionHandler; +import com.pichillilorenzo.flutter_inappbrowser.Util; import java.util.HashMap; import java.util.Map; @@ -89,6 +101,267 @@ public class InAppWebChromeClient extends WebChromeClient { decorView.setSystemUiVisibility(3846 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); } + @Override + public boolean onJsAlert(final WebView view, String url, final String message, + final JsResult result) { + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("message", message); + + getChannel().invokeMethod("onJsAlert", obj, new MethodChannel.Result() { + @Override + public void success(Object response) { + Map responseMap = (Map) response; + String responseMessage = (String) responseMap.get("message"); + String confirmButtonTitle = (String) responseMap.get("confirmButtonTitle"); + boolean handledByClient = (boolean) responseMap.get("handledByClient"); + if (handledByClient) { + Integer action = (Integer) responseMap.get("action"); + action = action != null ? action : 1; + switch (action) { + case 0: + result.confirm(); + break; + case 1: + default: + result.cancel(); + } + } else { + String alertMessage = (responseMessage != null && !responseMessage.isEmpty()) ? responseMessage : message; + Log.d(LOG_TAG, alertMessage); + DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + result.confirm(); + dialog.dismiss(); + } + }; + + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(view.getContext(), R.style.Theme_AppCompat_Dialog_Alert); + alertDialogBuilder.setMessage(alertMessage); + if (confirmButtonTitle != null && !confirmButtonTitle.isEmpty()) { + alertDialogBuilder.setPositiveButton(confirmButtonTitle, clickListener); + } else { + alertDialogBuilder.setPositiveButton(android.R.string.ok, clickListener); + } + + alertDialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + result.cancel(); + dialog.dismiss(); + } + }); + + AlertDialog alertDialog = alertDialogBuilder.create(); + alertDialog.show(); + } + } + + @Override + public void error(String s, String s1, Object o) { + Log.e(LOG_TAG, s + ", " + s1); + } + + @Override + public void notImplemented() { + + } + }); + + return true; + } + + @Override + public boolean onJsConfirm(final WebView view, String url, final String message, + final JsResult result) { + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("message", message); + + getChannel().invokeMethod("onJsConfirm", obj, new MethodChannel.Result() { + @Override + public void success(Object response) { + Map responseMap = (Map) response; + String responseMessage = (String) responseMap.get("message"); + String confirmButtonTitle = (String) responseMap.get("confirmButtonTitle"); + String cancelButtonTitle = (String) responseMap.get("cancelButtonTitle"); + boolean handledByClient = (boolean) responseMap.get("handledByClient"); + if (handledByClient) { + Integer action = (Integer) responseMap.get("action"); + action = action != null ? action : 1; + switch (action) { + case 0: + result.confirm(); + break; + case 1: + default: + result.cancel(); + } + } else { + String alertMessage = (responseMessage != null && !responseMessage.isEmpty()) ? responseMessage : message; + DialogInterface.OnClickListener confirmClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + result.confirm(); + dialog.dismiss(); + } + }; + DialogInterface.OnClickListener cancelClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + result.cancel(); + dialog.dismiss(); + } + }; + + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(view.getContext(), R.style.Theme_AppCompat_Dialog_Alert); + alertDialogBuilder.setMessage(alertMessage); + if (confirmButtonTitle != null && !confirmButtonTitle.isEmpty()) { + alertDialogBuilder.setPositiveButton(confirmButtonTitle, confirmClickListener); + } else { + alertDialogBuilder.setPositiveButton(android.R.string.ok, confirmClickListener); + } + if (cancelButtonTitle != null && !cancelButtonTitle.isEmpty()) { + alertDialogBuilder.setNegativeButton(cancelButtonTitle, cancelClickListener); + } else { + alertDialogBuilder.setNegativeButton(android.R.string.cancel, cancelClickListener); + } + + alertDialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + result.cancel(); + dialog.dismiss(); + } + }); + + AlertDialog alertDialog = alertDialogBuilder.create(); + alertDialog.show(); + } + } + + @Override + public void error(String s, String s1, Object o) { + Log.e(LOG_TAG, s + ", " + s1); + } + + @Override + public void notImplemented() { + + } + }); + + return true; + } + + @Override + public boolean onJsPrompt(final WebView view, String url, final String message, + final String defaultValue, final JsPromptResult result) { + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("message", message); + obj.put("defaultValue", defaultValue); + + getChannel().invokeMethod("onJsPrompt", obj, new MethodChannel.Result() { + @Override + public void success(Object response) { + Map responseMap = (Map) response; + String responseMessage = (String) responseMap.get("message"); + String responseDefaultValue = (String) responseMap.get("defaultValue"); + String confirmButtonTitle = (String) responseMap.get("confirmButtonTitle"); + String cancelButtonTitle = (String) responseMap.get("cancelButtonTitle"); + final String value = (String) responseMap.get("value"); + boolean handledByClient = (boolean) responseMap.get("handledByClient"); + if (handledByClient) { + Integer action = (Integer) responseMap.get("action"); + action = action != null ? action : 1; + switch (action) { + case 0: + if (value != null) + result.confirm(value); + else + result.confirm(); + break; + case 1: + default: + result.cancel(); + } + } else { + FrameLayout layout = new FrameLayout(view.getContext()); + + final EditText input = new EditText(view.getContext()); + input.setMaxLines(1); + input.setText((responseDefaultValue != null && !responseDefaultValue.isEmpty()) ? responseDefaultValue : defaultValue); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT); + input.setLayoutParams(lp); + + layout.setPaddingRelative(45,15,45,0); + layout.addView(input); + + String alertMessage = (responseMessage != null && !responseMessage.isEmpty()) ? responseMessage : message; + DialogInterface.OnClickListener confirmClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String text = input.getText().toString(); + result.confirm(value != null ? value : text); + dialog.dismiss(); + } + }; + DialogInterface.OnClickListener cancelClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + result.cancel(); + dialog.dismiss(); + } + }; + + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(view.getContext(), R.style.Theme_AppCompat_Dialog_Alert); + alertDialogBuilder.setMessage(alertMessage); + if (confirmButtonTitle != null && !confirmButtonTitle.isEmpty()) { + alertDialogBuilder.setPositiveButton(confirmButtonTitle, confirmClickListener); + } else { + alertDialogBuilder.setPositiveButton(android.R.string.ok, confirmClickListener); + } + if (cancelButtonTitle != null && !cancelButtonTitle.isEmpty()) { + alertDialogBuilder.setNegativeButton(cancelButtonTitle, cancelClickListener); + } else { + alertDialogBuilder.setNegativeButton(android.R.string.cancel, cancelClickListener); + } + + alertDialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + result.cancel(); + dialog.dismiss(); + } + }); + + AlertDialog alertDialog = alertDialogBuilder.create(); + alertDialog.setView(layout); + alertDialog.show(); + } + } + + @Override + public void error(String s, String s1, Object o) { + Log.e(LOG_TAG, s + ", " + s1); + } + + @Override + public void notImplemented() { + + } + }); + + return true; + } + @Override public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, android.os.Message resultMsg) { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java index f6c62d6a..a3d4216a 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java @@ -12,6 +12,7 @@ import android.util.JsonToken; import android.util.Log; import android.webkit.CookieManager; import android.webkit.DownloadListener; +import android.webkit.JsResult; import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; import android.webkit.WebHistoryItem; @@ -53,6 +54,7 @@ public class InAppWebView extends WebView { public InAppWebViewOptions options; public boolean isLoading = false; public OkHttpClient httpClient; + public float scale = getResources().getDisplayMetrics().density; int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler(); @@ -123,7 +125,7 @@ public class InAppWebView extends WebView { public void prepare() { - final Activity activity = (inAppBrowserActivity != null) ? inAppBrowserActivity : registrar.activity().getParent(); + final Activity activity = (inAppBrowserActivity != null) ? inAppBrowserActivity : registrar.activity(); boolean isFromInAppBrowserActivity = inAppBrowserActivity != null; @@ -322,7 +324,6 @@ public class InAppWebView extends WebView { post(new Runnable() { @Override public void run() { - float scale = getResources().getDisplayMetrics().density; // getScale(); int height = (int) (getContentHeight() * scale + 0.5); Bitmap b = Bitmap.createBitmap( getWidth(), @@ -644,7 +645,6 @@ public class InAppWebView extends WebView { int oldt) { super.onScrollChanged(l, t, oldl, oldt); - float scale = getResources().getDisplayMetrics().density; int x = (int) (l/scale); int y = (int) (t/scale); @@ -660,6 +660,33 @@ public class InAppWebView extends WebView { return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel; } + public void startSafeBrowsing(final MethodChannel.Result result) { + Activity activity = (inAppBrowserActivity != null) ? inAppBrowserActivity : registrar.activity(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + startSafeBrowsing(activity.getApplicationContext(), new ValueCallback() { + @Override + public void onReceiveValue(Boolean value) { + result.success(value); + } + }); + } else { + result.success(false); + } + } + + public void setSafeBrowsingWhitelist(List hosts, final MethodChannel.Result result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setSafeBrowsingWhitelist(hosts, new ValueCallback() { + @Override + public void onReceiveValue(Boolean value) { + result.success(value); + } + }); + } else { + result.success(false); + } + } + class DownloadStartListener implements DownloadListener { @Override public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java index 24566dab..c4d7c6a2 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java @@ -19,7 +19,6 @@ import android.webkit.WebViewClient; import androidx.annotation.RequiresApi; -import com.pichillilorenzo.flutter_inappbrowser.ContentBlocker.ContentBlocker; import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin; @@ -27,8 +26,6 @@ import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappbrowser.Util; import java.io.ByteArrayInputStream; -import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; @@ -303,6 +300,12 @@ public class InAppWebViewClient extends WebViewClient { super.onReceivedHttpAuthRequest(view, handler, host, realm); } + @Override + public void onScaleChanged(WebView view, float oldScale, float newScale) { + final InAppWebView webView = (InAppWebView) view; + webView.scale = newScale; + } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml index 69344ed7..13b55bde 100644 --- a/android/src/main/res/values/styles.xml +++ b/android/src/main/res/values/styles.xml @@ -1,5 +1,9 @@ + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 40659558..7196121f 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.pichillilorenzo.flutterwebviewexample"> diff --git a/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/MainActivity.java b/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/MainActivity.java index f264bfa7..3b12e7e9 100644 --- a/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/MainActivity.java +++ b/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/MainActivity.java @@ -1,4 +1,4 @@ -package com.pichillilorenzo.flutter_inappbrowserexample; +package com.pichillilorenzo.flutterwebviewexample; import android.os.Bundle; import io.flutter.app.FlutterActivity; diff --git a/example/assets/index.html b/example/assets/index.html index e00af807..69ff2616 100644 --- a/example/assets/index.html +++ b/example/assets/index.html @@ -55,8 +55,13 @@ }); }); $(document).ready(function() { - console.log("jQuery ready"); + alert("Alert Popup"); + console.log(confirm("Press a button!")); + console.log(prompt("Please enter your name", "Harry Potter")); + + console.log("jQuery ready"); +/* if ("geolocation" in navigator) { console.log("Geolocation API enabled"); navigator.geolocation.getCurrentPosition(function(position) { @@ -64,7 +69,7 @@ }); } else { console.log("No geolocation API"); - } + }*/ }); diff --git a/example/lib/inline_example.screen.dart b/example/lib/inline_example.screen.dart index 26854a32..5594961a 100644 --- a/example/lib/inline_example.screen.dart +++ b/example/lib/inline_example.screen.dart @@ -32,6 +32,8 @@ class _InlineExampleScreenState extends State { String url = ""; double progress = 0; + TextEditingController _textFieldController = TextEditingController(); + @override void initState() { super.initState(); @@ -171,18 +173,18 @@ class _InlineExampleScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - title: new Text("Permission Geolocation API"), - content: new Text("Can we use Geolocation API?"), + title: Text("Permission Geolocation API"), + content: Text("Can we use Geolocation API?"), actions: [ - new FlatButton( - child: new Text("Close"), + FlatButton( + child: Text("Close"), onPressed: () { response = new GeolocationPermissionShowPromptResponse(origin, false, false); Navigator.of(context).pop(); }, ), - new FlatButton( - child: new Text("Accept"), + FlatButton( + child: Text("Accept"), onPressed: () { response = new GeolocationPermissionShowPromptResponse(origin, true, true); Navigator.of(context).pop(); @@ -194,7 +196,94 @@ class _InlineExampleScreenState extends State { ); return response; - } + }, + onJsAlert: (InAppWebViewController controller, String message) async { + JsAlertResponseAction action; + + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text(message), + actions: [ + FlatButton( + child: Text("Ok"), + onPressed: () { + action = JsAlertResponseAction.CONFIRM; + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + + return new JsAlertResponse(handledByClient: true, action: action); + }, + onJsConfirm: (InAppWebViewController controller, String message) async { + JsConfirmResponseAction action; + + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: Text(message), + actions: [ + FlatButton( + child: Text("Cancel"), + onPressed: () { + action = JsConfirmResponseAction.CANCEL; + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text("Ok"), + onPressed: () { + action = JsConfirmResponseAction.CONFIRM; + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + + return new JsConfirmResponse(handledByClient: true, action: action); + }, + onJsPrompt: (InAppWebViewController controller, String message, String defaultValue) async { + JsPromptResponseAction action; + _textFieldController.text = defaultValue; + + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(message), + content: TextField( + controller: _textFieldController, + ), + actions: [ + FlatButton( + child: Text("Cancel"), + onPressed: () { + action = JsPromptResponseAction.CANCEL; + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text("Ok"), + onPressed: () { + action = JsPromptResponseAction.CONFIRM; + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + + return new JsPromptResponse(handledByClient: true, action: action, value: _textFieldController.text); + }, ), ), ), diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift index 5e090a73..444f9e88 100755 --- a/ios/Classes/FlutterWebViewController.swift +++ b/ios/Classes/FlutterWebViewController.swift @@ -44,6 +44,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { do { let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) + print(blockRules) WKContentRuleListStore.default().compileContentRuleList( forIdentifier: "ContentBlockingRules", encodedContentRuleList: blockRules) { (contentRuleList, error) in diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index 36b9db2b..7d11ed43 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -745,6 +745,63 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } } + public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, + initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { + + let alertController = UIAlertController(title: message, message: nil, + preferredStyle: UIAlertController.Style.alert); + + alertController.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default) { + _ in completionHandler()} + ); + + let presentingViewController = ((IABController != nil) ? IABController! : window!.rootViewController!) + presentingViewController.present(alertController, animated: true, completion: {}) + } + + public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, + completionHandler: @escaping (Bool) -> Void) { + + let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) + + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in + completionHandler(true) + })) + + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in + completionHandler(false) + })) + + let presentingViewController = ((IABController != nil) ? IABController! : window!.rootViewController!) + presentingViewController.present(alertController, animated: true, completion: nil) + } + + + public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, + completionHandler: @escaping (String?) -> Void) { + + let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .alert) + + alertController.addTextField { (textField) in + textField.text = defaultText + } + + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in + if let text = alertController.textFields?.first?.text { + completionHandler(text) + } else { + completionHandler(defaultText) + } + })) + + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in + completionHandler(nil) + })) + + let presentingViewController = ((IABController != nil) ? IABController! : window!.rootViewController!) + presentingViewController.present(alertController, animated: true, completion: nil) + } + public func scrollViewDidScroll(_ scrollView: UIScrollView) { if navigationDelegate != nil { let x = Int(scrollView.contentOffset.x / scrollView.contentScaleFactor) diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart index 930e728a..e94952c6 100644 --- a/lib/src/in_app_browser.dart +++ b/lib/src/in_app_browser.dart @@ -327,39 +327,72 @@ class InAppBrowser { } ///Event fires when the [InAppBrowser] webview scrolls. + /// ///[x] represents the current horizontal scroll origin in pixels. + /// ///[y] represents the current vertical scroll origin in pixels. void onScrollChanged(int x, int y) { } ///Event fires when [InAppBrowser] recognizes and starts a downloadable file. + /// ///[url] represents the url of the file. void onDownloadStart(String url) { } ///Event fires when the [InAppBrowser] webview finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a [CustomSchemeResponse] to load a specific resource encoded to `base64`. + /// ///[scheme] represents the scheme of the url. + /// ///[url] represents the url of the request. Future onLoadResourceCustomScheme(String scheme, String url) { } ///Event fires when the [InAppBrowser] webview tries to open a link with `target="_blank"`. + /// ///[url] represents the url of the link. void onTargetBlank(String url) { } + ///Event fires when javascript calls the `alert()` method to display an alert dialog. + ///If [JsAlertResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog. + /// + ///[message] represents the message to be displayed in the alert dialog. + Future onJsAlert(String message) { + + } + + ///Event fires when javascript calls the `confirm()` method to display a confirm dialog. + ///If [JsConfirmResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog. + /// + ///[message] represents the message to be displayed in the alert dialog. + Future onJsConfirm(String message) { + + } + + ///Event fires when javascript calls the `prompt()` method to display a prompt dialog. + ///If [JsPromptResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog. + /// + ///[message] represents the message to be displayed in the alert dialog. + ///[defaultValue] represents the default value displayed in the prompt dialog. + Future onJsPrompt(String message, String defaultValue) { + + } + ///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. ///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https. ///On non-secure origins geolocation requests are automatically denied. + /// ///[origin] represents the origin of the web content attempting to use the Geolocation API. + /// ///**NOTE**: available only for Android. Future onGeolocationPermissionsShowPrompt (String origin) { -} + } void throwIsAlreadyOpened({String message = ''}) { if (this.isOpened()) { @@ -372,4 +405,5 @@ class InAppBrowser { throw Exception(['Error: ${ (message.isEmpty) ? '' : message + ' '}The browser is not opened.']); } } + } diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart index e1cc5404..b190d5bf 100644 --- a/lib/src/in_app_webview.dart +++ b/lib/src/in_app_webview.dart @@ -15,6 +15,9 @@ import 'in_app_browser.dart'; import 'channel_manager.dart'; import 'webview_options.dart'; +/* +* TODO: injectFileFromAssets, injectJavaScriptBeforeLoad +*/ ///Initial [data] as a content for an [InAppWebView] instance, using [baseUrl] as the base URL for it. ///The [mimeType] property specifies the format of the data. @@ -107,34 +110,61 @@ class InAppWebView extends StatefulWidget { /// ///**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/assets` encoding could be empty. + ///**NOTE only for Android**: to be able to listen this event, you need also the enable javascript. final onWebViewLoadResourceCallback onLoadResource; ///Event fires when the [InAppWebView] scrolls. + /// ///[x] represents the current horizontal scroll origin in pixels. + /// ///[y] represents the current vertical scroll origin in pixels. final onWebViewScrollChangedCallback onScrollChanged; ///Event fires when [InAppWebView] recognizes and starts a downloadable file. + /// ///[url] represents the url of the file. final onDownloadStartCallback onDownloadStart; ///Event fires when the [InAppWebView] finds the `custom-scheme` while loading a resource. Here you can handle the url request and return a [CustomSchemeResponse] to load a specific resource encoded to `base64`. + /// ///[scheme] represents the scheme of the url. + /// ///[url] represents the url of the request. final onLoadResourceCustomSchemeCallback onLoadResourceCustomScheme; ///Event fires when the [InAppWebView] tries to open a link with `target="_blank"`. + /// ///[url] represents the url of the link. final onTargetBlankCallback onTargetBlank; ///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. ///Note that for applications targeting Android N and later SDKs (API level > `Build.VERSION_CODES.M`) this method is only called for requests originating from secure origins such as https. ///On non-secure origins geolocation requests are automatically denied. + /// ///[origin] represents the origin of the web content attempting to use the Geolocation API. + /// ///**NOTE**: available only for Android. final onGeolocationPermissionsShowPromptCallback onGeolocationPermissionsShowPrompt; + ///Event fires when javascript calls the `alert()` method to display an alert dialog. + ///If [JsAlertResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog. + /// + ///[message] represents the message to be displayed in the alert dialog. + final onJsAlertCallback onJsAlert; + + ///Event fires when javascript calls the `confirm()` method to display a confirm dialog. + ///If [JsConfirmResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog. + /// + ///[message] represents the message to be displayed in the alert dialog. + final onJsConfirmCallback onJsConfirm; + + ///Event fires when javascript calls the `prompt()` method to display a prompt dialog. + ///If [JsPromptResponse.handledByClient] is `true`, the webview will assume that the client will handle the dialog. + /// + ///[message] represents the message to be displayed in the alert dialog. + ///[defaultValue] represents the default value displayed in the prompt dialog. + final onJsPromptCallback onJsPrompt; + ///Initial url that will be loaded. final String initialUrl; ///Initial asset file that will be loaded. See [InAppWebView.loadFile()] for explanation. @@ -174,6 +204,9 @@ class InAppWebView extends StatefulWidget { this.onLoadResourceCustomScheme, this.onTargetBlank, this.onGeolocationPermissionsShowPrompt, + this.onJsAlert, + this.onJsConfirm, + this.onJsPrompt, this.gestureRecognizers, }) : super(key: key); @@ -269,16 +302,16 @@ class InAppWebViewController { InAppWebViewController(int id, InAppWebView widget) { - _id = id; - _channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); - _channel.setMethodCallHandler(handleMethod); - _widget = widget; + this._id = id; + this._channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); + this._channel.setMethodCallHandler(handleMethod); + this._widget = widget; } InAppWebViewController.fromInAppBrowser(String uuid, MethodChannel channel, InAppBrowser inAppBrowser) { - _inAppBrowserUuid = uuid; - _channel = channel; - _inAppBrowser = inAppBrowser; + this._inAppBrowserUuid = uuid; + this._channel = channel; + this._inAppBrowser = inAppBrowser; } Future handleMethod(MethodCall call) async { @@ -399,6 +432,28 @@ class InAppWebViewController { else if (_inAppBrowser != null) return (await _inAppBrowser.onGeolocationPermissionsShowPrompt(origin)).toMap(); break; + case "onJsAlert": + String message = call.arguments["message"]; + if (_widget != null && _widget.onJsAlert != null) + return (await _widget.onJsAlert(this, message)).toMap(); + else if (_inAppBrowser != null) + return (await _inAppBrowser.onJsAlert(message)).toMap(); + break; + case "onJsConfirm": + String message = call.arguments["message"]; + if (_widget != null && _widget.onJsConfirm != null) + return (await _widget.onJsConfirm(this, message)).toMap(); + else if (_inAppBrowser != null) + return (await _inAppBrowser.onJsConfirm(message)).toMap(); + break; + case "onJsPrompt": + String message = call.arguments["message"]; + String defaultValue = call.arguments["defaultValue"]; + if (_widget != null && _widget.onJsPrompt != null) + return (await _widget.onJsPrompt(this, message, defaultValue)).toMap(); + else if (_inAppBrowser != null) + return (await _inAppBrowser.onJsPrompt(message, defaultValue)).toMap(); + break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; // decode args to json @@ -818,9 +873,55 @@ class InAppWebViewController { } return WebHistory(historyList, currentIndex); } + + ///Starts Safe Browsing initialization. + /// + ///URL loads are not guaranteed to be protected by Safe Browsing until after the this method returns true. + ///Safe Browsing is not fully supported on all devices. For those devices this method will returns false. + /// + ///This should not be called if Safe Browsing has been disabled by manifest tag + ///or [AndroidInAppWebViewOptions.safeBrowsingEnabled]. This prepares resources used for Safe Browsing. + /// + ///**NOTE**: available only for Android. + Future startSafeBrowsing() async { + Map args = {}; + if (_inAppBrowserUuid != null && _inAppBrowser != null) { + _inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _inAppBrowserUuid); + } + return await _channel.invokeMethod('startSafeBrowsing', args); + } + + ///Sets the list of hosts (domain names/IP addresses) that are exempt from SafeBrowsing checks. The list is global for all the WebViews. + /// + /// Each rule should take one of these: + ///| Rule | Example | Matches Subdomain | + ///| -- | -- | -- | + ///| HOSTNAME | example.com | Yes | + ///| .HOSTNAME | .example.com | No | + ///| IPV4_LITERAL | 192.168.1.1 | No | + ///| IPV6_LITERAL_WITH_BRACKETS | [10:20:30:40:50:60:70:80] | No | + /// + ///All other rules, including wildcards, are invalid. The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2). + /// + ///[hosts] represents the list of hosts. This value must never be null. + /// + ///**NOTE**: available only for Android. + Future setSafeBrowsingWhitelist(List hosts) async { + assert(hosts != null); + Map args = {}; + if (_inAppBrowserUuid != null && _inAppBrowser != null) { + _inAppBrowser.throwIsNotOpened(); + args.putIfAbsent('uuid', () => _inAppBrowserUuid); + } + args.putIfAbsent('hosts', () => hosts); + return await _channel.invokeMethod('setSafeBrowsingWhitelist', args); + } + + ///Dispose/Destroy the WebView. Future _dispose() async { await _channel.invokeMethod('dispose'); } -} \ No newline at end of file +} diff --git a/lib/src/types.dart b/lib/src/types.dart index 3ce4bc78..e482d8f8 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -1,6 +1,6 @@ import 'package:uuid/uuid.dart'; import 'package:flutter/services.dart'; -import 'in_app_webview.dart' show InAppWebViewController; +import 'in_app_webview.dart'; var uuidGenerator = new Uuid(); @@ -128,6 +128,95 @@ class GeolocationPermissionShowPromptResponse { } } + +class JsAlertResponseAction { + final int _value; + const JsAlertResponseAction._internal(this._value); + toValue() => _value; + + static const CONFIRM = const JsAlertResponseAction._internal(0); +} + +class JsAlertResponse { + String message; + String confirmButtonTitle; + bool handledByClient; + JsAlertResponseAction action; + + JsAlertResponse({this.message = "", this.handledByClient = false, this.confirmButtonTitle = "", this.action = JsAlertResponseAction.CONFIRM}); + + Map toMap() { + return { + "message": message, + "confirmButtonTitle": confirmButtonTitle, + "handledByClient": handledByClient, + "action": action?.toValue() + }; + } +} + +class JsConfirmResponseAction { + final int _value; + const JsConfirmResponseAction._internal(this._value); + toValue() => _value; + + static const CONFIRM = const JsConfirmResponseAction._internal(0); + static const CANCEL = const JsConfirmResponseAction._internal(1); +} + +class JsConfirmResponse { + String message; + String confirmButtonTitle; + String cancelButtonTitle; + bool handledByClient; + JsConfirmResponseAction action; + + JsConfirmResponse({this.message = "", this.handledByClient = false, this.confirmButtonTitle = "", this.cancelButtonTitle = "", this.action = JsConfirmResponseAction.CANCEL}); + + Map toMap() { + return { + "message": message, + "confirmButtonTitle": confirmButtonTitle, + "cancelButtonTitle": cancelButtonTitle, + "handledByClient": handledByClient, + "action": action?.toValue() + }; + } +} + +class JsPromptResponseAction { + final int _value; + const JsPromptResponseAction._internal(this._value); + toValue() => _value; + + static const CONFIRM = const JsPromptResponseAction._internal(0); + static const CANCEL = const JsPromptResponseAction._internal(1); +} + +class JsPromptResponse { + String message; + String defaultValue; + String confirmButtonTitle; + String cancelButtonTitle; + bool handledByClient; + String value; + JsPromptResponseAction action; + + JsPromptResponse({this.message = "", this.defaultValue = "", this.handledByClient = false, this.confirmButtonTitle = "", this.cancelButtonTitle = "", this.value, this.action = JsPromptResponseAction.CANCEL}); + + Map toMap() { + return { + "message": message, + "defaultValue": defaultValue, + "confirmButtonTitle": confirmButtonTitle, + "cancelButtonTitle": cancelButtonTitle, + "handledByClient": handledByClient, + "value": value, + "action": action?.toValue() + }; + } +} + typedef onWebViewCreatedCallback = void Function(InAppWebViewController controller); typedef onWebViewLoadStartCallback = void Function(InAppWebViewController controller, String url); typedef onWebViewLoadStopCallback = void Function(InAppWebViewController controller, String url); @@ -140,4 +229,7 @@ typedef onWebViewScrollChangedCallback = void Function(InAppWebViewController co typedef onDownloadStartCallback = void Function(InAppWebViewController controller, String url); typedef onLoadResourceCustomSchemeCallback = Future Function(InAppWebViewController controller, String scheme, String url); typedef onTargetBlankCallback = void Function(InAppWebViewController controller, String url); -typedef onGeolocationPermissionsShowPromptCallback = Future Function(InAppWebViewController controller, String origin); \ No newline at end of file +typedef onGeolocationPermissionsShowPromptCallback = Future Function(InAppWebViewController controller, String origin); +typedef onJsAlertCallback = Future Function(InAppWebViewController controller, String message); +typedef onJsConfirmCallback = Future Function(InAppWebViewController controller, String message); +typedef onJsPromptCallback = Future Function(InAppWebViewController controller, String message, String defaultValue); \ No newline at end of file