diff --git a/CHANGELOG.md b/CHANGELOG.md index e591f41f..4cc4fbe9 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,24 @@ +## 4.0.0 + +- Updated `onCreateWindow`, `onJsAlert`, `onJsConfirm`, `onJsPrompt` webview events +- Added `onCloseWindow`, `onTitleChanged`, `onWindowFocus`, `onWindowBlur` webview events +- Added `androidOnRequestFocus`, `androidOnReceivedIcon`, `androidOnReceivedTouchIconUrl`, `androidOnJsBeforeUnload`, `androidOnReceivedLoginRequest` Android-specific webview events +- Fixed "SFSafariViewController doesn't open like a native iOS modal" [#403](https://github.com/pichillilorenzo/flutter_inappwebview/issues/403) + +### BREAKING CHANGES + +- Updated `onCreateWindow`, `onJsAlert`, `onJsConfirm`, `onJsPrompt` webview event +- Renamed `OnCreateWindowRequest` class to `CreateWindowRequest` + ## 3.4.0+2 -- Revert default `InAppWebView.gestureRecognizers` value to null on Android +- Reverted default `InAppWebView.gestureRecognizers` value to null on Android ## 3.4.0+1 -- Update README.md -- Update missing docs -- Fix pub.dev Health suggestions and Analysis suggestions +- Updated README.md +- Updated missing docs +- Fixed pub.dev Health suggestions and Analysis suggestions ## 3.4.0 diff --git a/README.md b/README.md index 5caa7172..d8bde937 100755 --- a/README.md +++ b/README.md @@ -628,6 +628,7 @@ Event names that starts with `android` or `ios` are events platform-specific. * `onDownloadStart`: Event fired when InAppWebView recognizes a downloadable file (to use this event, the `useOnDownloadStart` option must be `true`). To download the file, you can use the [flutter_downloader](https://pub.dev/packages/flutter_downloader) plugin. * `onLoadResourceCustomScheme`: Event fired 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`. * `onCreateWindow`: Event fired when the InAppWebView requests the host application to create a new window, for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side. +* `onCloseWindow`: Event fired when the host application should close the given WebView and remove it from the view system if necessary. * `onJsAlert`: Event fired when javascript calls the `alert()` method to display an alert dialog. * `onJsConfirm`: Event fired when javascript calls the `confirm()` method to display a confirm dialog. * `onJsPrompt`: Event fired when javascript calls the `prompt()` method to display a prompt dialog. @@ -644,6 +645,9 @@ Event names that starts with `android` or `ios` are events platform-specific. * `onEnterFullscreen`: Event fired when the current page has entered full screen mode. * `onExitFullscreen`: Event fired when the current page has exited full screen mode. * `onPageCommitVisible`: Called when the web view begins to receive web content. +* `onTitleChanged`: Event fired when a change in the document title occurred. +* `onWindowFocus`: Event fired when the JavaScript `window` object of the WebView has received focus. This is the result of the `focus` JavaScript event applied to the `window` object. +* `onWindowBlur`: Event fired when the JavaScript `window` object of the WebView has lost focus. This is the result of the `blur` JavaScript event applied to the `window` object. * `androidOnSafeBrowsingHit`: Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing (available only on Android). * `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). @@ -654,6 +658,11 @@ Event names that starts with `android` or `ios` are events platform-specific. * `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). * `androidOnFormResubmission`: As the host application if the browser should resend data as the requested page was a result of a POST. The default is to not resend the data (available only on Android). * `androidOnScaleChanged`: Event fired when the scale applied to the WebView has changed (available only on Android). +* `androidOnRequestFocus`: Event fired when there is a request to display and focus for this WebView (available only on Android). +* `androidOnReceivedIcon`: Event fired when there is new favicon for the current page (available only on Android). +* `androidOnReceivedTouchIconUrl`: Event fired when there is an url for an apple-touch-icon (available only on Android). +* `androidOnJsBeforeUnload`: Event fired when the client should display a dialog to confirm navigation away from the current page. This is the result of the `onbeforeunload` javascript event (available only on Android). +* `androidOnReceivedLoginRequest`: Event fired when a request to automatically log in the user has been processed (available only on Android). * `iosOnWebContentProcessDidTerminate`: Invoked when the web view's web content process is terminated (available only on iOS). * `iosOnDidReceiveServerRedirectForProvisionalNavigation`: Called when a web view receives a server redirect (available only on iOS). diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ChromeSafariBrowserManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ChromeSafariBrowserManager.java index b2ebde9e..30e5cd24 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ChromeSafariBrowserManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/ChromeSafariBrowserManager.java @@ -45,7 +45,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl Map headersFallback = (Map) call.argument("headersFallback"); HashMap optionsFallback = (HashMap) call.argument("optionsFallback"); HashMap contextMenuFallback = (HashMap) call.argument("contextMenuFallback"); - open(activity, uuid, url, options, menuItemList, uuidFallback, headersFallback, optionsFallback, contextMenuFallback, result); + Integer windowIdFallback = (Integer) call.argument("windowIdFallback"); + open(activity, uuid, url, options, menuItemList, uuidFallback, headersFallback, optionsFallback, contextMenuFallback, windowIdFallback, result); } break; default: @@ -54,7 +55,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl } public void open(Activity activity, String uuid, String url, HashMap options, List> menuItemList, String uuidFallback, - Map headersFallback, HashMap optionsFallback, HashMap contextMenuFallback, MethodChannel.Result result) { + Map headersFallback, HashMap optionsFallback, HashMap contextMenuFallback, Integer windowIdFallback, + MethodChannel.Result result) { Intent intent = null; Bundle extras = new Bundle(); @@ -68,6 +70,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl extras.putSerializable("headers", (Serializable) headersFallback); extras.putSerializable("contextMenu", (Serializable) contextMenuFallback); + extras.putInt("windowId", windowIdFallback != null ? windowIdFallback : -1); + if (CustomTabActivityHelper.isAvailable(activity)) { intent = new Intent(activity, ChromeCustomTabsActivity.class); } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java index 996e995f..81a4fe18 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowser/InAppBrowserActivity.java @@ -5,12 +5,14 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.os.Bundle; +import android.os.Message; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; @@ -25,6 +27,7 @@ import androidx.webkit.WebViewCompat; import androidx.webkit.WebViewFeature; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView; +import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewChromeClient; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions; import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.Shared; @@ -41,6 +44,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha static final String LOG_TAG = "InAppBrowserActivity"; public MethodChannel channel; + public Integer windowId; public String uuid; public InAppWebView webView; public ActionBar actionBar; @@ -63,6 +67,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha Bundle b = getIntent().getExtras(); uuid = b.getString("uuid"); + windowId = b.getInt("windowId"); channel = new MethodChannel(Shared.messenger, "com.pichillilorenzo/flutter_inappbrowser_" + uuid); channel.setMethodCallHandler(this); @@ -70,6 +75,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha setContentView(R.layout.activity_web_view); webView = findViewById(R.id.webView); + webView.windowId = windowId; webView.inAppBrowserActivity = this; webView.channel = channel; @@ -90,19 +96,27 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha prepareView(); - Boolean isData = b.getBoolean("isData"); - if (!isData) { - headers = (HashMap) b.getSerializable("headers"); - String url = b.getString("url"); - webView.loadUrl(url, headers); - } - else { - String data = b.getString("data"); - String mimeType = b.getString("mimeType"); - String encoding = b.getString("encoding"); - String baseUrl = b.getString("baseUrl"); - String historyUrl = b.getString("historyUrl"); - webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + if (windowId != -1) { + Message resultMsg = InAppWebViewChromeClient.windowWebViewMessages.get(windowId); + if (resultMsg != null) { + ((WebView.WebViewTransport) resultMsg.obj).setWebView(webView); + resultMsg.sendToTarget(); + } + } else { + Boolean isData = b.getBoolean("isData"); + if (!isData) { + headers = (HashMap) b.getSerializable("headers"); + String url = b.getString("url"); + webView.loadUrl(url, headers); + } + else { + String data = b.getString("data"); + String mimeType = b.getString("mimeType"); + String encoding = b.getString("encoding"); + String baseUrl = b.getString("baseUrl"); + String historyUrl = b.getString("historyUrl"); + webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } } Map obj = new HashMap<>(); @@ -565,30 +579,14 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha } public void close(final MethodChannel.Result result) { - runOnUiThread(new Runnable() { - @Override - public void run() { + Map obj = new HashMap<>(); + channel.invokeMethod("onExit", obj); - Map obj = new HashMap<>(); - channel.invokeMethod("onExit", obj); - - webView.setWebViewClient(new WebViewClient() { - // NB: wait for about:blank before dismissing - public void onPageFinished(WebView view, String url) { - hide(); - finish(); - } - }); - // NB: From SDK 19: "If you call methods on WebView from any thread - // other than your app's UI thread, it can cause unexpected results." - // http://developer.android.com/guide/webapps/migrating.html#Threads - webView.loadUrl("about:blank"); - if (result != null) { - result.success(true); - } - } - }); + dispose(); + if (result != null) { + result.success(true); + } } public void reload() { @@ -984,6 +982,8 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha if (Shared.activityPluginBinding != null) { Shared.activityPluginBinding.removeActivityResultListener(webView.inAppWebViewChromeClient); } + ViewGroup vg = (ViewGroup) (webView.getParent()); + vg.removeView(webView); webView.setWebChromeClient(new WebChromeClient()); webView.setWebViewClient(new WebViewClient() { public void onPageFinished(WebView view, String url) { @@ -993,6 +993,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha } }); webView.loadUrl("about:blank"); + finish(); } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserManager.java index 1d56a7dd..26bfca7e 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppBrowserManager.java @@ -71,7 +71,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { HashMap options = (HashMap) call.argument("options"); Map headers = (Map) call.argument("headers"); HashMap contextMenu = (HashMap) call.argument("contextMenu"); - openUrl(activity, uuid, url, options, headers, contextMenu); + Integer windowId = (Integer) call.argument("windowId"); + openUrl(activity, uuid, url, options, headers, contextMenu, windowId); } result.success(true); break; @@ -88,7 +89,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { HashMap options = (HashMap) call.argument("options"); Map headers = (Map) call.argument("headers"); HashMap contextMenu = (HashMap) call.argument("contextMenu"); - openUrl(activity, uuid, url, options, headers, contextMenu); + Integer windowId = (Integer) call.argument("windowId"); + openUrl(activity, uuid, url, options, headers, contextMenu, windowId); } result.success(true); break; @@ -101,7 +103,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { String baseUrl = (String) call.argument("baseUrl"); String historyUrl = (String) call.argument("historyUrl"); HashMap contextMenu = (HashMap) call.argument("contextMenu"); - openData(activity, uuid, options, data, mimeType, encoding, baseUrl, historyUrl, contextMenu); + Integer windowId = (Integer) call.argument("windowId"); + openData(activity, uuid, options, data, mimeType, encoding, baseUrl, historyUrl, contextMenu, windowId); } result.success(true); break; @@ -192,7 +195,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { } } - public void openUrl(Activity activity, String uuid, String url, HashMap options, Map headers, HashMap contextMenu) { + public void openUrl(Activity activity, String uuid, String url, HashMap options, Map headers, + HashMap contextMenu, Integer windowId) { Bundle extras = new Bundle(); extras.putString("fromActivity", activity.getClass().getName()); extras.putString("url", url); @@ -201,10 +205,12 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { extras.putSerializable("options", options); extras.putSerializable("headers", (Serializable) headers); extras.putSerializable("contextMenu", (Serializable) contextMenu); + extras.putInt("windowId", windowId != null ? windowId : -1); startInAppBrowserActivity(activity, extras); } - public void openData(Activity activity, String uuid, HashMap options, String data, String mimeType, String encoding, String baseUrl, String historyUrl, HashMap contextMenu) { + public void openData(Activity activity, String uuid, HashMap options, String data, String mimeType, String encoding, + String baseUrl, String historyUrl, HashMap contextMenu, Integer windowId) { Bundle extras = new Bundle(); extras.putBoolean("isData", true); extras.putString("uuid", uuid); @@ -215,6 +221,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { extras.putString("baseUrl", baseUrl); extras.putString("historyUrl", historyUrl); extras.putSerializable("contextMenu", (Serializable) contextMenu); + extras.putInt("windowId", windowId != null ? windowId : -1); startInAppBrowserActivity(activity, extras); } 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 052ab6f3..59e1e3fa 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 @@ -1,11 +1,11 @@ package com.pichillilorenzo.flutter_inappwebview.InAppWebView; import android.content.Context; -import android.content.MutableContextWrapper; 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; @@ -25,6 +25,7 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; +import io.flutter.embedding.android.FlutterView; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -54,6 +55,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { final Map initialHeaders = (Map) params.get("initialHeaders"); Map initialOptions = (Map) params.get("initialOptions"); Map contextMenu = (Map) params.get("contextMenu"); + Integer windowId = (Integer) params.get("windowId"); InAppWebViewOptions options = new InAppWebViewOptions(); options.parse(initialOptions); @@ -70,7 +72,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { // displayListenerProxy.onPostWebViewInitialization(displayManager); // mMutableContext.setBaseContext(context); - webView = new InAppWebView(Shared.activity, this, id, options, contextMenu, containerView); + webView = new InAppWebView(Shared.activity, this, id, windowId, options, contextMenu, containerView); displayListenerProxy.onPostWebViewInitialization(displayManager); // fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/182 @@ -89,33 +91,41 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { webView.prepare(); - if (initialFile != null) { - try { - initialUrl = Util.getUrlAsset(initialFile); - } catch (IOException e) { - e.printStackTrace(); - Log.e(LOG_TAG, initialFile + " asset file cannot be found!", e); - return; + if (windowId != null) { + Message resultMsg = InAppWebViewChromeClient.windowWebViewMessages.get(windowId); + if (resultMsg != null) { + ((WebView.WebViewTransport) resultMsg.obj).setWebView(webView); + resultMsg.sendToTarget(); } - } - - final String finalInitialUrl = initialUrl; - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - if (initialData != null) { - String data = initialData.get("data"); - String mimeType = initialData.get("mimeType"); - String encoding = initialData.get("encoding"); - String baseUrl = initialData.get("baseUrl"); - String historyUrl = initialData.get("historyUrl"); - webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } else { + if (initialFile != null) { + try { + initialUrl = Util.getUrlAsset(initialFile); + } catch (IOException e) { + e.printStackTrace(); + Log.e(LOG_TAG, initialFile + " asset file cannot be found!", e); + return; } - else - webView.loadUrl(finalInitialUrl, initialHeaders); } - }); + + final String finalInitialUrl = initialUrl; + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + if (initialData != null) { + String data = initialData.get("data"); + String mimeType = initialData.get("mimeType"); + String encoding = initialData.get("encoding"); + String baseUrl = initialData.get("baseUrl"); + String historyUrl = initialData.get("historyUrl"); + webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } + else + webView.loadUrl(finalInitialUrl, initialHeaders); + } + }); + } if (containerView == null && id instanceof String) { Map obj = new HashMap<>(); @@ -513,6 +523,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { } webView.setWebChromeClient(new WebChromeClient()); webView.setWebViewClient(new WebViewClient() { + @Override public void onPageFinished(WebView view, String url) { webView.dispose(); webView.destroy(); 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 c8eb0ad7..d985344a 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 @@ -35,9 +35,11 @@ import android.webkit.CookieManager; import android.webkit.DownloadListener; import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; +import android.webkit.WebChromeClient; import android.webkit.WebHistoryItem; import android.webkit.WebSettings; import android.webkit.WebStorage; +import android.webkit.WebViewClient; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import android.widget.TextView; @@ -46,6 +48,7 @@ import androidx.annotation.Keep; import androidx.annotation.RequiresApi; import androidx.webkit.WebViewCompat; import androidx.webkit.WebViewFeature; +import androidx.webkit.WebViewRenderProcessClient; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlocker; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerAction; @@ -84,6 +87,7 @@ final public class InAppWebView extends InputAwareWebView { public FlutterWebView flutterWebView; public MethodChannel channel; public Object id; + public Integer windowId; public InAppWebViewClient inAppWebViewClient; public InAppWebViewChromeClient inAppWebViewChromeClient; public InAppWebViewRenderProcessClient inAppWebViewRenderProcessClient; @@ -108,6 +112,13 @@ final public class InAppWebView extends InputAwareWebView { public Runnable checkContextMenuShouldBeClosedTask; public int newCheckContextMenuShouldBeClosedTaskTask = 100; // ms + static final String scriptsWrapperJS = "(function(){" + + " if (window." + JavaScriptBridgeInterface.name + "._scriptsLoaded == null) {" + + " $PLACEHOLDER_VALUE" + + " window." + JavaScriptBridgeInterface.name + "._scriptsLoaded = true;" + + " }" + + "})();"; + static final String consoleLogJS = "(function(console) {" + " var oldLogs = {" + " 'log': console.log," + @@ -138,7 +149,12 @@ final public class InAppWebView extends InputAwareWebView { " window." + JavaScriptBridgeInterface.name + ".callHandler('onPrint', window.location.href);" + "};"; - static final String platformReadyJS = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));"; + static final String platformReadyJS = "(function() {" + + " if (window." + JavaScriptBridgeInterface.name + "._platformReady == null) {" + + " window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));" + + " window." + JavaScriptBridgeInterface.name + "._platformReady = true;" + + " }" + + "})();"; static final String variableForOnLoadResourceJS = "window._flutter_inappwebview_useOnLoadResource"; static final String enableVariableForOnLoadResourceJS = variableForOnLoadResourceJS + " = $PLACEHOLDER_VALUE;"; @@ -592,6 +608,18 @@ final public class InAppWebView extends InputAwareWebView { " });" + "})();"; + static final String onWindowFocusEventJS = "(function(){" + + " window.addEventListener('focus', function(e) {" + + " window." + JavaScriptBridgeInterface.name + ".callHandler('onWindowFocus');" + + " });" + + "})();"; + + static final String onWindowBlurEventJS = "(function(){" + + " window.addEventListener('blur', function(e) {" + + " window." + JavaScriptBridgeInterface.name + ".callHandler('onWindowBlur');" + + " });" + + "})();"; + public InAppWebView(Context context) { super(context); } @@ -604,7 +632,7 @@ final public class InAppWebView extends InputAwareWebView { super(context, attrs, defaultStyle); } - public InAppWebView(Context context, Object obj, Object id, InAppWebViewOptions options, Map contextMenu, View containerView) { + public InAppWebView(Context context, Object obj, Object id, Integer windowId, InAppWebViewOptions options, Map contextMenu, View containerView) { super(context, containerView); if (obj instanceof InAppBrowserActivity) this.inAppBrowserActivity = (InAppBrowserActivity) obj; @@ -612,6 +640,7 @@ final public class InAppWebView extends InputAwareWebView { this.flutterWebView = (FlutterWebView) obj; this.channel = (this.inAppBrowserActivity != null) ? this.inAppBrowserActivity.channel : this.flutterWebView.channel; this.id = id; + this.windowId = windowId; this.options = options; this.contextMenu = contextMenu; Shared.activity.registerForContextMenu(this); @@ -1949,12 +1978,22 @@ final public class InAppWebView extends InputAwareWebView { @Override public void dispose() { + if (windowId != null && InAppWebViewChromeClient.windowWebViewMessages.containsKey(windowId)) { + InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); + } + headlessHandler.removeCallbacksAndMessages(null); + mHandler.removeCallbacksAndMessages(null); + removeJavascriptInterface(JavaScriptBridgeInterface.name); + removeAllViews(); + if (checkContextMenuShouldBeClosedTask != null) + removeCallbacks(checkContextMenuShouldBeClosedTask); + if (checkScrollStoppedTask != null) + removeCallbacks(checkScrollStoppedTask); super.dispose(); } @Override public void destroy() { - headlessHandler.removeCallbacksAndMessages(null); super.destroy(); } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java index 8181d34d..62ac5087 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewChromeClient.java @@ -3,7 +3,6 @@ package com.pichillilorenzo.flutter_inappwebview.InAppWebView; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; @@ -30,11 +29,11 @@ import android.webkit.PermissionRequest; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; @@ -45,6 +44,7 @@ import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.Shared; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -64,6 +64,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR private FlutterWebView flutterWebView; private InAppBrowserActivity inAppBrowserActivity; public MethodChannel channel; + public static Map windowWebViewMessages = new HashMap<>(); + private static int windowAutoincrementId = 0; private static final String fileProviderAuthorityExtension = "flutter_inappwebview.fileprovider"; @@ -132,7 +134,6 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR this.mCustomViewCallback.onCustomViewHidden(); this.mCustomViewCallback = null; activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - Map obj = new HashMap<>(); if (inAppBrowserActivity != null) obj.put("uuid", inAppBrowserActivity.uuid); @@ -175,7 +176,9 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR Map obj = new HashMap<>(); if (inAppBrowserActivity != null) obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("url", url); obj.put("message", message); + obj.put("iosIsMainFrame", null); channel.invokeMethod("onJsAlert", obj, new MethodChannel.Result() { @Override @@ -260,7 +263,9 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR Map obj = new HashMap<>(); if (inAppBrowserActivity != null) obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("url", url); obj.put("message", message); + obj.put("iosIsMainFrame", null); channel.invokeMethod("onJsConfirm", obj, new MethodChannel.Result() { @Override @@ -358,8 +363,10 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR Map obj = new HashMap<>(); if (inAppBrowserActivity != null) obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("url", url); obj.put("message", message); obj.put("defaultValue", defaultValue); + obj.put("iosIsMainFrame", null); channel.invokeMethod("onJsPrompt", obj, new MethodChannel.Result() { @Override @@ -472,47 +479,172 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR alertDialog.show(); } + @Override + public boolean onJsBeforeUnload(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("url", url); + obj.put("message", message); + obj.put("iosIsMainFrame", null); + + channel.invokeMethod("onJsBeforeUnload", obj, new MethodChannel.Result() { + @Override + public void success(Object response) { + String responseMessage = null; + String confirmButtonTitle = null; + String cancelButtonTitle = null; + + if (response != null) { + Map responseMap = (Map) response; + responseMessage = (String) responseMap.get("message"); + confirmButtonTitle = (String) responseMap.get("confirmButtonTitle"); + cancelButtonTitle = (String) responseMap.get("cancelButtonTitle"); + Boolean handledByClient = (Boolean) responseMap.get("handledByClient"); + if (handledByClient != null && handledByClient) { + Integer action = (Integer) responseMap.get("action"); + action = action != null ? action : 1; + switch (action) { + case 0: + result.confirm(); + break; + case 1: + default: + result.cancel(); + } + return; + } + } + + createBeforeUnloadDialog(view, message, result, responseMessage, confirmButtonTitle, cancelButtonTitle); + } + + @Override + public void error(String s, String s1, Object o) { + Log.e(LOG_TAG, s + ", " + s1); + result.cancel(); + } + + @Override + public void notImplemented() { + createConfirmDialog(view, message, result, null, null, null); + } + }); + + return true; + } + + public void createBeforeUnloadDialog(WebView view, String message, final JsResult result, String responseMessage, String confirmButtonTitle, String cancelButtonTitle) { + 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(); + } + }; + + Activity activity = inAppBrowserActivity != null ? inAppBrowserActivity : Shared.activity; + + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity, 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 boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, final Message resultMsg) { + windowAutoincrementId++; + final int windowId = windowAutoincrementId; + + WebView.HitTestResult result = view.getHitTestResult(); + String url = result.getExtra(); + final Map obj = new HashMap<>(); if (inAppBrowserActivity != null) obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("url", url); + obj.put("windowId", windowId); obj.put("androidIsDialog", isDialog); obj.put("androidIsUserGesture", isUserGesture); obj.put("iosWKNavigationType", null); + obj.put("iosIsForMainFrame", null); - WebView.HitTestResult result = view.getHitTestResult(); - String data = result.getExtra(); + windowWebViewMessages.put(windowId, resultMsg); - if (data == null) { - // to get the URL, create a temp weview - final WebView tempWebView = new WebView(view.getContext()); - // disable javascript - tempWebView.getSettings().setJavaScriptEnabled(false); - tempWebView.setWebViewClient(new WebViewClient(){ - @Override - public void onPageStarted(WebView v, String url, Bitmap favicon) { - super.onPageStarted(v, url, favicon); - - obj.put("url", url); - channel.invokeMethod("onCreateWindow", obj); - - // stop webview loading - v.stopLoading(); - - // this will throw the error "Application attempted to call on a destroyed AwAutofillManager" that will kill the webview. - // that's ok. - v.destroy(); + channel.invokeMethod("onCreateWindow", obj, new MethodChannel.Result() { + @Override + public void success(@Nullable Object result) { + if (result == null && InAppWebViewChromeClient.windowWebViewMessages.containsKey(windowId)) { + InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); } - }); - ((WebView.WebViewTransport) resultMsg.obj).setWebView(tempWebView); - resultMsg.sendToTarget(); - return true; - } + } - obj.put("url", data); - channel.invokeMethod("onCreateWindow", obj); - return false; + @Override + public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + if (InAppWebViewChromeClient.windowWebViewMessages.containsKey(windowId)) { + InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); + } + } + + @Override + public void notImplemented() { + if (InAppWebViewChromeClient.windowWebViewMessages.containsKey(windowId)) { + InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); + } + } + }); + + return true; + } + + @Override + public void onCloseWindow(WebView window) { + final Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + + channel.invokeMethod("onCloseWindow", obj); + + super.onCloseWindow(window); + } + + @Override + public void onRequestFocus(WebView view) { + final Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + + channel.invokeMethod("onRequestFocus", obj); + + super.onCloseWindow(view); } @Override @@ -588,13 +720,53 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); - if (inAppBrowserActivity != null && inAppBrowserActivity.actionBar != null && inAppBrowserActivity.options.toolbarTopFixedTitle.isEmpty()) + if (inAppBrowserActivity != null && inAppBrowserActivity.actionBar != null && inAppBrowserActivity.options.toolbarTopFixedTitle.isEmpty()) { inAppBrowserActivity.actionBar.setTitle(title); + } + + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("title", title); + channel.invokeMethod("onTitleChanged", obj); } @Override public void onReceivedIcon(WebView view, Bitmap icon) { super.onReceivedIcon(view, icon); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + icon.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); + try { + byteArrayOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + String errorMessage = e.getMessage(); + if (errorMessage != null) { + Log.e(LOG_TAG, errorMessage); + } + } + icon.recycle(); + + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("icon", byteArrayOutputStream.toByteArray()); + channel.invokeMethod("onReceivedIcon", obj); + } + + @Override + public void onReceivedTouchIconUrl(WebView view, + String url, + boolean precomposed) { + super.onReceivedTouchIconUrl(view, url, precomposed); + + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("url", url); + obj.put("precomposed", precomposed); + channel.invokeMethod("onReceivedTouchIconUrl", obj); } protected ViewGroup getRootView() { 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 b26670dc..b8155298 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 @@ -2,10 +2,8 @@ package com.pichillilorenzo.flutter_inappwebview.InAppWebView; import android.annotation.TargetApi; import android.graphics.Bitmap; -import android.net.http.SslCertificate; import android.net.http.SslError; import android.os.Build; -import android.os.Bundle; import android.os.Message; import android.util.Log; import android.view.KeyEvent; @@ -29,20 +27,13 @@ import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.Credential; import com.pichillilorenzo.flutter_inappwebview.CredentialDatabase.CredentialDatabase; import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity; import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface; -import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Util; import java.io.ByteArrayInputStream; -import java.io.File; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -185,8 +176,14 @@ public class InAppWebViewClient extends WebViewClient { js += InAppWebView.resourceObserverJS.replaceAll("[\r\n]+", ""); } js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS.replaceAll("[\r\n]+", ""); + js += InAppWebView.onWindowFocusEventJS.replaceAll("[\r\n]+", ""); + js += InAppWebView.onWindowBlurEventJS.replaceAll("[\r\n]+", ""); js += InAppWebView.printJS.replaceAll("[\r\n]+", ""); + js = InAppWebView.scriptsWrapperJS + .replace("$PLACEHOLDER_VALUE", js) + .replaceAll("[\r\n]+", ""); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(js, (ValueCallback) null); } else { @@ -817,6 +814,18 @@ public class InAppWebViewClient extends WebViewClient { return super.onRenderProcessGone(view, detail); } + @Override + public void onReceivedLoginRequest(WebView view, String realm, String account, String args) { + Map obj = new HashMap<>(); + if (inAppBrowserActivity != null) + obj.put("uuid", inAppBrowserActivity.uuid); + obj.put("realm", realm); + obj.put("account", account); + obj.put("args", args); + + channel.invokeMethod("onReceivedLoginRequest", obj); + } + @Override public void onUnhandledKeyEvent(WebView view, KeyEvent event) { diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index 4177fcd3..092a5a51 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.10/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1/","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.10/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1/","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":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+1/","dependencies":[]}],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-23 10:38:00.775824","version":"1.17.4"} \ 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.10/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1/","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.10/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.0.1/","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":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+1/","dependencies":[]}],"windows":[],"web":[]},"dependencyGraph":[{"name":"e2e","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-06-29 16:24:39.876196","version":"1.17.4"} \ No newline at end of file diff --git a/example/test_driver/in_app_webview_on_create_window_test.dart b/example/test_driver/in_app_webview_on_create_window_test.dart index 24c05c82..af5c54a8 100755 --- a/example/test_driver/in_app_webview_on_create_window_test.dart +++ b/example/test_driver/in_app_webview_on_create_window_test.dart @@ -48,8 +48,9 @@ class InAppWebViewOnCreateWindowTestState extends WidgetTestState { }); } }, - onCreateWindow: (InAppWebViewController controller, OnCreateWindowRequest onCreateWindowRequest) { - controller.loadUrl(url: onCreateWindowRequest.url); + onCreateWindow: (InAppWebViewController controller, CreateWindowRequest createWindowRequest) async { + controller.loadUrl(url: createWindowRequest.url); + return null; }, ), ), diff --git a/example/test_driver/in_app_webview_on_js_dialog_test.dart b/example/test_driver/in_app_webview_on_js_dialog_test.dart index 317aaad8..65813ef2 100755 --- a/example/test_driver/in_app_webview_on_js_dialog_test.dart +++ b/example/test_driver/in_app_webview_on_js_dialog_test.dart @@ -66,24 +66,23 @@ class InAppWebViewOnJsDialogTestState extends WidgetTestState { }); }, onJsAlert: - (InAppWebViewController controller, String message) async { + (InAppWebViewController controller, JsAlertRequest jsAlertRequest) async { JsAlertResponseAction action = - await createAlertDialog(context, message); + await createAlertDialog(context, jsAlertRequest.message); return JsAlertResponse( handledByClient: true, action: action); }, onJsConfirm: - (InAppWebViewController controller, String message) async { + (InAppWebViewController controller, JsConfirmRequest jsConfirmRequest) async { JsConfirmResponseAction action = - await createConfirmDialog(context, message); + await createConfirmDialog(context, jsConfirmRequest.message); return JsConfirmResponse( handledByClient: true, action: action); }, - onJsPrompt: (InAppWebViewController controller, String message, - String defaultValue) async { - _textFieldController.text = defaultValue; + onJsPrompt: (InAppWebViewController controller, JsPromptRequest jsPromptRequest) async { + _textFieldController.text = jsPromptRequest.defaultValue; JsPromptResponseAction action = - await createPromptDialog(context, message); + await createPromptDialog(context, jsPromptRequest.message); return JsPromptResponse( handledByClient: true, action: action, diff --git a/ios/Classes/ChromeSafariBrowserManager.swift b/ios/Classes/ChromeSafariBrowserManager.swift index 114bc35a..9d5bdf36 100755 --- a/ios/Classes/ChromeSafariBrowserManager.swift +++ b/ios/Classes/ChromeSafariBrowserManager.swift @@ -16,9 +16,6 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { static var registrar: FlutterPluginRegistrar? static var channel: FlutterMethodChannel? - var tmpWindow: UIWindow? - private var previousStatusBarStyle = -1 - public static func register(with registrar: FlutterPluginRegistrar) { } @@ -43,7 +40,10 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { let headersFallback = arguments!["headersFallback"] as? [String: String] let optionsFallback = arguments!["optionsFallback"] as? [String: Any?] let contextMenuFallback = arguments!["contextMenuFallback"] as? [String: Any] - open(uuid: uuid, url: url, options: options, menuItemList: menuItemList, uuidFallback: uuidFallback, headersFallback: headersFallback, optionsFallback: optionsFallback, contextMenuFallback: contextMenuFallback, result: result) + let windowIdFallback = arguments!["windowIdFallback"] as? Int64 + open(uuid: uuid, url: url, options: options, menuItemList: menuItemList, uuidFallback: uuidFallback, + headersFallback: headersFallback, optionsFallback: optionsFallback, contextMenuFallback: contextMenuFallback, + windowIdFallback: windowIdFallback, result: result) break default: result(FlutterMethodNotImplemented) @@ -51,51 +51,40 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { } } - public func open(uuid: String, url: String, options: [String: Any?], menuItemList: [[String: Any]], uuidFallback: String?, headersFallback: [String: String]?, optionsFallback: [String: Any?]?, contextMenuFallback: [String: Any]?, result: @escaping FlutterResult) { + public func open(uuid: String, url: String, options: [String: Any?], menuItemList: [[String: Any]], uuidFallback: String?, + headersFallback: [String: String]?, optionsFallback: [String: Any?]?, contextMenuFallback: [String: Any]?, + windowIdFallback: Int64?, result: @escaping FlutterResult) { let absoluteUrl = URL(string: url)!.absoluteURL - if self.previousStatusBarStyle == -1 { - self.previousStatusBarStyle = UIApplication.shared.statusBarStyle.rawValue - } - - if !(self.tmpWindow != nil) { - let frame: CGRect = UIScreen.main.bounds - self.tmpWindow = UIWindow(frame: frame) - } - - let tmpController = UIViewController() - let baseWindowLevel = UIApplication.shared.keyWindow?.windowLevel - self.tmpWindow!.rootViewController = tmpController - self.tmpWindow!.windowLevel = UIWindow.Level(baseWindowLevel!.rawValue + 1.0) - self.tmpWindow!.makeKeyAndVisible() - if #available(iOS 9.0, *) { - let safariOptions = SafariBrowserOptions() - let _ = safariOptions.parse(options: options) - let safari: SafariViewController - - if #available(iOS 11.0, *) { - let config = SFSafariViewController.Configuration() - config.entersReaderIfAvailable = safariOptions.entersReaderIfAvailable - config.barCollapsingEnabled = safariOptions.barCollapsingEnabled + if let flutterViewController = UIApplication.shared.delegate?.window.unsafelyUnwrapped?.rootViewController as? FlutterViewController { + let safariOptions = SafariBrowserOptions() + let _ = safariOptions.parse(options: options) - safari = SafariViewController(url: absoluteUrl, configuration: config) - } else { - // Fallback on earlier versions - safari = SafariViewController(url: absoluteUrl) - } - - safari.uuid = uuid - safari.menuItemList = menuItemList - safari.prepareMethodChannel() - safari.delegate = safari - safari.tmpWindow = tmpWindow - safari.safariOptions = safariOptions - safari.prepareSafariBrowser() - - tmpController.present(safari, animated: true) { - result(true) + let safari: SafariViewController + + if #available(iOS 11.0, *) { + let config = SFSafariViewController.Configuration() + config.entersReaderIfAvailable = safariOptions.entersReaderIfAvailable + config.barCollapsingEnabled = safariOptions.barCollapsingEnabled + + safari = SafariViewController(url: absoluteUrl, configuration: config) + } else { + // Fallback on earlier versions + safari = SafariViewController(url: absoluteUrl) + } + + safari.uuid = uuid + safari.menuItemList = menuItemList + safari.prepareMethodChannel() + safari.delegate = safari + safari.safariOptions = safariOptions + safari.prepareSafariBrowser() + + flutterViewController.present(safari, animated: true) { + result(true) + } } return } @@ -106,7 +95,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { return } - SwiftFlutterPlugin.instance!.inAppBrowserManager!.openUrl(uuid: uuidFallback!, url: url, options: optionsFallback ?? [:], headers: headersFallback ?? [:], contextMenu: contextMenuFallback ?? [:]) + SwiftFlutterPlugin.instance!.inAppBrowserManager!.openUrl(uuid: uuidFallback!, url: url, options: optionsFallback ?? [:], headers: headersFallback ?? [:], contextMenu: contextMenuFallback ?? [:], windowId: windowIdFallback) } } } diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift index bb483f6c..da31cc37 100755 --- a/ios/Classes/FlutterWebViewController.swift +++ b/ios/Classes/FlutterWebViewController.swift @@ -39,47 +39,62 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor let initialHeaders = args["initialHeaders"] as? [String: String] let initialOptions = args["initialOptions"] as! [String: Any?] let contextMenu = args["contextMenu"] as? [String: Any] + let windowId = args["windowId"] as? Int64 let options = InAppWebViewOptions() let _ = options.parse(options: initialOptions) let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: options) + + if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + webView = webViewTransport.webView + webView!.frame = myView!.bounds + webView!.IABController = nil + webView!.contextMenu = contextMenu + webView!.channel = channel! + } else { + webView = InAppWebView(frame: myView!.bounds, configuration: preWebviewConfiguration, IABController: nil, contextMenu: contextMenu, channel: channel!) + } - webView = InAppWebView(frame: myView!.bounds, configuration: preWebviewConfiguration, IABController: nil, contextMenu: contextMenu, channel: channel!) webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] myView!.autoresizesSubviews = true myView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] myView!.addSubview(webView!) - + webView!.options = options webView!.prepare() + + if windowId == nil { + if #available(iOS 11.0, *) { + self.webView!.configuration.userContentController.removeAllContentRuleLists() + if let contentBlockers = webView!.options?.contentBlockers, contentBlockers.count > 0 { + do { + let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) + let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) + WKContentRuleListStore.default().compileContentRuleList( + forIdentifier: "ContentBlockingRules", + encodedContentRuleList: blockRules) { (contentRuleList, error) in - if #available(iOS 11.0, *) { - self.webView!.configuration.userContentController.removeAllContentRuleLists() - if let contentBlockers = webView!.options?.contentBlockers, contentBlockers.count > 0 { - do { - let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) - let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) - WKContentRuleListStore.default().compileContentRuleList( - forIdentifier: "ContentBlockingRules", - encodedContentRuleList: blockRules) { (contentRuleList, error) in + if let error = error { + print(error.localizedDescription) + return + } - if let error = error { - print(error.localizedDescription) - return - } + let configuration = self.webView!.configuration + configuration.userContentController.add(contentRuleList!) - let configuration = self.webView!.configuration - configuration.userContentController.add(contentRuleList!) - - self.load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) + self.load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) + } + return + } catch { + print(error.localizedDescription) } - return - } catch { - print(error.localizedDescription) } } + load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) + } + else if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + webView!.load(webViewTransport.request) } - load(initialUrl: initialUrl, initialFile: initialFile, initialData: initialData, initialHeaders: initialHeaders) if (frame.isEmpty && viewId is String) { /// Note: The WKWebView behaves very unreliable when rendering offscreen diff --git a/ios/Classes/InAppBrowserManager.swift b/ios/Classes/InAppBrowserManager.swift index 77fd11a6..27178e11 100755 --- a/ios/Classes/InAppBrowserManager.swift +++ b/ios/Classes/InAppBrowserManager.swift @@ -18,7 +18,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { static var registrar: FlutterPluginRegistrar? static var channel: FlutterMethodChannel? - var tmpWindow: UIWindow? + // var tmpWindow: UIWindow? private var previousStatusBarStyle = -1 public static func register(with registrar: FlutterPluginRegistrar) { @@ -42,7 +42,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { let options = arguments!["options"] as! [String: Any?] let headers = arguments!["headers"] as! [String: String] let contextMenu = arguments!["contextMenu"] as! [String: Any] - openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu) + let windowId = arguments!["windowId"] as? Int64 + openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu, windowId: windowId) result(true) break case "openFile": @@ -59,7 +60,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { let options = arguments!["options"] as! [String: Any?] let headers = arguments!["headers"] as! [String: String] let contextMenu = arguments!["contextMenu"] as! [String: Any] - openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu) + let windowId = arguments!["windowId"] as? Int64 + openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu, windowId: windowId) result(true) break case "openData": @@ -70,7 +72,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { let encoding = arguments!["encoding"] as! String let baseUrl = arguments!["baseUrl"] as! String let contextMenu = arguments!["contextMenu"] as! [String: Any] - openData(uuid: uuid, options: options, data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, contextMenu: contextMenu) + let windowId = arguments!["windowId"] as? Int64 + openData(uuid: uuid, options: options, data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, contextMenu: contextMenu, windowId: windowId) result(true) break case "openWithSystemBrowser": @@ -88,16 +91,14 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { self.previousStatusBarStyle = UIApplication.shared.statusBarStyle.rawValue } - if !(self.tmpWindow != nil) { - let frame: CGRect = UIScreen.main.bounds - self.tmpWindow = UIWindow(frame: frame) - } + let frame: CGRect = UIScreen.main.bounds + let tmpWindow = UIWindow(frame: frame) let tmpController = UIViewController() let baseWindowLevel = UIApplication.shared.keyWindow?.windowLevel - self.tmpWindow!.rootViewController = tmpController - self.tmpWindow!.windowLevel = UIWindow.Level(baseWindowLevel!.rawValue + 1.0) - self.tmpWindow!.makeKeyAndVisible() + tmpWindow.rootViewController = tmpController + tmpWindow.windowLevel = UIWindow.Level(baseWindowLevel!.rawValue + 1.0) + tmpWindow.makeKeyAndVisible() let browserOptions = InAppBrowserOptions() let _ = browserOptions.parse(options: options) @@ -107,6 +108,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { let storyboard = UIStoryboard(name: WEBVIEW_STORYBOARD, bundle: Bundle(for: InAppWebViewFlutterPlugin.self)) let webViewController = storyboard.instantiateViewController(withIdentifier: WEBVIEW_STORYBOARD_CONTROLLER_ID) as! InAppBrowserWebViewController + webViewController.tmpWindow = tmpWindow webViewController.browserOptions = browserOptions webViewController.webViewOptions = webViewOptions webViewController.isHidden = browserOptions.hidden @@ -115,57 +117,60 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { return webViewController } - public func openUrl(uuid: String, url: String, options: [String: Any?], headers: [String: String], contextMenu: [String: Any]) { + public func openUrl(uuid: String, url: String, options: [String: Any?], headers: [String: String], + contextMenu: [String: Any], windowId: Int64?) { let absoluteUrl = URL(string: url)!.absoluteURL let webViewController = prepareInAppBrowserWebViewController(options: options) webViewController.uuid = uuid webViewController.prepareMethodChannel() - webViewController.tmpWindow = tmpWindow webViewController.initURL = absoluteUrl webViewController.initHeaders = headers webViewController.contextMenu = contextMenu + webViewController.windowId = windowId if webViewController.isHidden { webViewController.view.isHidden = true - tmpWindow!.rootViewController!.present(webViewController, animated: false, completion: {() -> Void in + webViewController.tmpWindow!.rootViewController!.present(webViewController, animated: false, completion: {() -> Void in }) webViewController.presentingViewController?.dismiss(animated: false, completion: {() -> Void in - self.tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) + webViewController.tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) UIApplication.shared.delegate?.window??.makeKeyAndVisible() }) } else { - tmpWindow!.rootViewController!.present(webViewController, animated: true, completion: {() -> Void in + webViewController.tmpWindow!.rootViewController!.present(webViewController, animated: true, completion: {() -> Void in }) } } - public func openData(uuid: String, options: [String: Any?], data: String, mimeType: String, encoding: String, baseUrl: String, contextMenu: [String: Any]) { + public func openData(uuid: String, options: [String: Any?], data: String, mimeType: String, encoding: String, + baseUrl: String, contextMenu: [String: Any], windowId: Int64?) { let webViewController = prepareInAppBrowserWebViewController(options: options) webViewController.uuid = uuid - webViewController.tmpWindow = tmpWindow + webViewController.prepareMethodChannel() webViewController.initData = data webViewController.initMimeType = mimeType webViewController.initEncoding = encoding webViewController.initBaseUrl = baseUrl webViewController.contextMenu = contextMenu + webViewController.windowId = windowId if webViewController.isHidden { webViewController.view.isHidden = true - tmpWindow!.rootViewController!.present(webViewController, animated: false, completion: {() -> Void in + webViewController.tmpWindow!.rootViewController!.present(webViewController, animated: false, completion: {() -> Void in webViewController.webView.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl) }) webViewController.presentingViewController?.dismiss(animated: false, completion: {() -> Void in - self.tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) + webViewController.tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) UIApplication.shared.delegate?.window??.makeKeyAndVisible() }) } else { - tmpWindow!.rootViewController!.present(webViewController, animated: true, completion: {() -> Void in + webViewController.tmpWindow!.rootViewController!.present(webViewController, animated: true, completion: {() -> Void in webViewController.webView.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl) }) } diff --git a/ios/Classes/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowserWebViewController.swift index 3fd99234..92b49754 100755 --- a/ios/Classes/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowserWebViewController.swift @@ -14,17 +14,16 @@ import AVFoundation typealias OlderClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Any?) -> Void typealias NewerClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void -public class InAppWebView_IBWrapper: InAppWebView { - required init(coder: NSCoder) { - let config = WKWebViewConfiguration() - super.init(frame: .zero, configuration: config, IABController: nil, contextMenu: nil, channel: nil) +public class InAppWebView_IBWrapper: UIView { + required init?(coder: NSCoder) { + super.init(coder: coder) self.translatesAutoresizingMaskIntoConstraints = false } } public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIScrollViewDelegate, WKUIDelegate, UITextFieldDelegate { - @IBOutlet var containerWebView: UIView! + @IBOutlet var containerWebView: InAppWebView_IBWrapper! @IBOutlet var closeButton: UIButton! @IBOutlet var reloadButton: UIBarButtonItem! @IBOutlet var backButton: UIBarButtonItem! @@ -43,6 +42,7 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS @IBOutlet var webView_TopFullScreenConstraint: NSLayoutConstraint! var uuid: String = "" + var windowId: Int64? var webView: InAppWebView! var channel: FlutterMethodChannel? var initURL: URL? @@ -385,42 +385,58 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS public override func viewWillAppear(_ animated: Bool) { if !viewPrepared { + print(containerWebView) let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: webViewOptions) - self.webView = InAppWebView(frame: .zero, configuration: preWebviewConfiguration, IABController: self, contextMenu: contextMenu, channel: channel!) + if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + self.webView = webViewTransport.webView + self.webView.IABController = self + self.webView.contextMenu = contextMenu + self.webView.channel = channel! + } else { + self.webView = InAppWebView(frame: .zero, + configuration: preWebviewConfiguration, + IABController: self, + contextMenu: contextMenu, + channel: channel!) + } self.containerWebView.addSubview(self.webView) prepareConstraints() prepareWebView() - if #available(iOS 11.0, *) { - if let contentBlockers = webView.options?.contentBlockers, contentBlockers.count > 0 { - do { - let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) - let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) - WKContentRuleListStore.default().compileContentRuleList( - forIdentifier: "ContentBlockingRules", - encodedContentRuleList: blockRules) { (contentRuleList, error) in - - if let error = error { - print(error.localizedDescription) - return - } - - let configuration = self.webView!.configuration - configuration.userContentController.add(contentRuleList!) - - self.initLoad(initURL: self.initURL, initData: self.initData, initMimeType: self.initMimeType, initEncoding: self.initEncoding, initBaseUrl: self.initBaseUrl, initHeaders: self.initHeaders) - - self.onBrowserCreated() + if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + self.webView.load(webViewTransport.request) + } else { + if #available(iOS 11.0, *) { + if let contentBlockers = webView.options?.contentBlockers, contentBlockers.count > 0 { + do { + let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) + let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) + WKContentRuleListStore.default().compileContentRuleList( + forIdentifier: "ContentBlockingRules", + encodedContentRuleList: blockRules) { (contentRuleList, error) in + + if let error = error { + print(error.localizedDescription) + return + } + + let configuration = self.webView!.configuration + configuration.userContentController.add(contentRuleList!) + + self.initLoad(initURL: self.initURL, initData: self.initData, initMimeType: self.initMimeType, initEncoding: self.initEncoding, initBaseUrl: self.initBaseUrl, initHeaders: self.initHeaders) + + self.onBrowserCreated() + } + return + } catch { + print(error.localizedDescription) } - return - } catch { - print(error.localizedDescription) } } + + initLoad(initURL: initURL, initData: initData, initMimeType: initMimeType, initEncoding: initEncoding, initBaseUrl: initBaseUrl, initHeaders: initHeaders) } - initLoad(initURL: initURL, initData: initData, initMimeType: initMimeType, initEncoding: initEncoding, initBaseUrl: initBaseUrl, initHeaders: initHeaders) - onBrowserCreated() } viewPrepared = true diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index 39188156..eb4c47f2 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -791,10 +791,37 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = ""; })(); """ +let onWindowFocusEventJS = """ +(function(){ + window.addEventListener('focus', function(e) { + window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowFocus'); + }); +})(); +""" + +let onWindowBlurEventJS = """ +(function(){ + window.addEventListener('blur', function(e) { + window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowBlur'); + }); +})(); +""" + var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:] +public class WebViewTransport: NSObject { + var webView: InAppWebView + var request: URLRequest + + init(webView: InAppWebView, request: URLRequest) { + self.webView = webView + self.request = request + } +} + public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate { + var windowId: Int64? var IABController: InAppBrowserWebViewController? var channel: FlutterMethodChannel? var options: InAppWebViewOptions? @@ -826,6 +853,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi var customIMPs: [IMP] = [] + static var windowWebViews: [Int64:WebViewTransport] = [:] + static var windowAutoincrementId: Int64 = 0; + init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, contextMenu: [String: Any]?, channel: FlutterMethodChannel?) { super.init(frame: frame, configuration: configuration) self.channel = channel @@ -1000,6 +1030,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi forKeyPath: #keyPath(WKWebView.url), options: [.new, .old], context: nil) + + addObserver(self, + forKeyPath: #keyPath(WKWebView.title), + options: [.new, .old], + context: nil) NotificationCenter.default.addObserver( self, @@ -1026,9 +1061,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi name: UIWindow.didBecomeHiddenNotification, object: window) - configuration.userContentController = WKUserContentController() - configuration.preferences = WKPreferences() - if let options = options { if options.transparentBackground { isOpaque = false @@ -1050,6 +1082,55 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } } + if #available(iOS 11.0, *) { + accessibilityIgnoresInvertColors = options.accessibilityIgnoresInvertColors + scrollView.contentInsetAdjustmentBehavior = + UIScrollView.ContentInsetAdjustmentBehavior.init(rawValue: options.contentInsetAdjustmentBehavior)! + } + + allowsBackForwardNavigationGestures = options.allowsBackForwardNavigationGestures + if #available(iOS 9.0, *) { + allowsLinkPreview = options.allowsLinkPreview + if !options.userAgent.isEmpty { + customUserAgent = options.userAgent + } + } + + if #available(iOS 13.0, *) { + scrollView.automaticallyAdjustsScrollIndicatorInsets = options.automaticallyAdjustsScrollIndicatorInsets + } + + scrollView.showsVerticalScrollIndicator = !options.disableVerticalScroll + scrollView.showsHorizontalScrollIndicator = !options.disableHorizontalScroll + scrollView.showsVerticalScrollIndicator = options.verticalScrollBarEnabled + scrollView.showsHorizontalScrollIndicator = options.horizontalScrollBarEnabled + + scrollView.decelerationRate = InAppWebView.getDecelerationRate(type: options.decelerationRate) + scrollView.alwaysBounceVertical = options.alwaysBounceVertical + scrollView.alwaysBounceHorizontal = options.alwaysBounceHorizontal + scrollView.scrollsToTop = options.scrollsToTop + scrollView.isPagingEnabled = options.isPagingEnabled + scrollView.maximumZoomScale = CGFloat(options.maximumZoomScale) + scrollView.minimumZoomScale = CGFloat(options.minimumZoomScale) + + // options.debuggingEnabled is always enabled for iOS, + // there isn't any option to set about it such as on Android. + + if options.clearCache { + clearCache() + } + } + + if windowId != nil { + // the new created window webview has the same WKWebViewConfiguration variable reference + return + } + + configuration.userContentController = WKUserContentController() + configuration.preferences = WKPreferences() + + if let options = options { + let originalViewPortMetaTagContentJSScript = WKUserScript(source: originalViewPortMetaTagContentJS, injectionTime: .atDocumentEnd, forMainFrameOnly: true) configuration.userContentController.addUserScript(originalViewPortMetaTagContentJSScript) @@ -1096,6 +1177,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi configuration.userContentController.addUserScript(findTextHighlightJSScript) configuration.userContentController.add(self, name: "onFindResultReceived") + let onWindowFocusEventJSScript = WKUserScript(source: onWindowFocusEventJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) + configuration.userContentController.addUserScript(onWindowFocusEventJSScript) + + let onWindowBlurEventJSScript = WKUserScript(source: onWindowBlurEventJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) + configuration.userContentController.addUserScript(onWindowBlurEventJSScript) + if options.useShouldInterceptAjaxRequest { let interceptAjaxRequestsJSScript = WKUserScript(source: interceptAjaxRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false) configuration.userContentController.addUserScript(interceptAjaxRequestsJSScript) @@ -1106,23 +1193,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi configuration.userContentController.addUserScript(interceptFetchRequestsJSScript) } - if #available(iOS 11.0, *) { - accessibilityIgnoresInvertColors = options.accessibilityIgnoresInvertColors - scrollView.contentInsetAdjustmentBehavior = - UIScrollView.ContentInsetAdjustmentBehavior.init(rawValue: options.contentInsetAdjustmentBehavior)! - } - - allowsBackForwardNavigationGestures = options.allowsBackForwardNavigationGestures if #available(iOS 9.0, *) { - allowsLinkPreview = options.allowsLinkPreview configuration.allowsAirPlayForMediaPlayback = options.allowsAirPlayForMediaPlayback configuration.allowsPictureInPictureMediaPlayback = options.allowsPictureInPictureMediaPlayback if !options.applicationNameForUserAgent.isEmpty { configuration.applicationNameForUserAgent = options.applicationNameForUserAgent } - if !options.userAgent.isEmpty { - customUserAgent = options.userAgent - } } configuration.preferences.javaScriptCanOpenWindowsAutomatically = options.javaScriptCanOpenWindowsAutomatically @@ -1132,26 +1208,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if #available(iOS 13.0, *) { configuration.preferences.isFraudulentWebsiteWarningEnabled = options.isFraudulentWebsiteWarningEnabled configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: options.preferredContentMode)! - scrollView.automaticallyAdjustsScrollIndicatorInsets = options.automaticallyAdjustsScrollIndicatorInsets - } - - scrollView.showsVerticalScrollIndicator = !options.disableVerticalScroll - scrollView.showsHorizontalScrollIndicator = !options.disableHorizontalScroll - scrollView.showsVerticalScrollIndicator = options.verticalScrollBarEnabled - scrollView.showsHorizontalScrollIndicator = options.horizontalScrollBarEnabled - - scrollView.decelerationRate = InAppWebView.getDecelerationRate(type: options.decelerationRate) - scrollView.alwaysBounceVertical = options.alwaysBounceVertical - scrollView.alwaysBounceHorizontal = options.alwaysBounceHorizontal - scrollView.scrollsToTop = options.scrollsToTop - scrollView.isPagingEnabled = options.isPagingEnabled - scrollView.maximumZoomScale = CGFloat(options.maximumZoomScale) - scrollView.minimumZoomScale = CGFloat(options.minimumZoomScale) - - // options.debuggingEnabled is always enabled for iOS. - - if options.clearCache { - clearCache() } } } @@ -1348,8 +1404,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi onProgressChanged(progress: progress) } else if keyPath == #keyPath(WKWebView.url) && change?[NSKeyValueChangeKey.newKey] is URL { let newUrl = change?[NSKeyValueChangeKey.newKey] as? URL - onUpdateVisitedHistory(url: newUrl!.absoluteString) - } + onUpdateVisitedHistory(url: newUrl?.absoluteString) + } else if keyPath == #keyPath(WKWebView.title) && change?[NSKeyValueChangeKey.newKey] is String { + let newTitle = change?[NSKeyValueChangeKey.newKey] as? String + onTitleChanged(title: newTitle) + } replaceGestureHandlerIfNeeded() } @@ -1831,7 +1890,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi if navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .backForward { currentURL = url if IABController != nil { - IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!) + IABController!.updateUrlTextField(url: currentURL?.absoluteString ?? "") } } } @@ -1841,7 +1900,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { if navigationResponse.isForMainFrame, let response = navigationResponse.response as? HTTPURLResponse { if response.statusCode >= 400 { - onLoadHttpError(url: response.url!.absoluteString, statusCode: response.statusCode, description: "") + onLoadHttpError(url: response.url?.absoluteString, statusCode: response.statusCode, description: "") } } @@ -1863,7 +1922,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi self.x509CertificateData = nil self.startPageTime = currentTimeInMilliSeconds() - onLoadStart(url: (currentURL?.absoluteString)!) + + onLoadStart(url: url?.absoluteString) if IABController != nil { // loading url, start spinner, update back/forward @@ -1880,10 +1940,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi currentURL = url InAppWebView.credentialsProposed = [] evaluateJavaScript(platformReadyJS, completionHandler: nil) - onLoadStop(url: (currentURL?.absoluteString)!) + onLoadStop(url: url?.absoluteString) if IABController != nil { - IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!) + IABController!.updateUrlTextField(url: currentURL?.absoluteString ?? "") IABController!.backButton.isEnabled = canGoBack IABController!.forwardButton.isEnabled = canGoForward IABController!.spinner.stopAnimating() @@ -1899,7 +1959,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { InAppWebView.credentialsProposed = [] - onLoadError(url: (currentURL?.absoluteString)!, error: error) + onLoadError(url: url?.absoluteString, error: error) if IABController != nil { IABController!.backButton.isEnabled = canGoBack @@ -2138,7 +2198,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi return } - onJsAlert(message: message, result: {(result) -> Void in + onJsAlert(frame: frame, message: message, result: {(result) -> Void in if result is FlutterError { print((result as! FlutterError).message ?? "") } @@ -2195,8 +2255,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { - - onJsConfirm(message: message, result: {(result) -> Void in + + onJsConfirm(frame: frame, message: message, result: {(result) -> Void in if result is FlutterError { print((result as! FlutterError).message ?? "") } @@ -2268,7 +2328,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { - onJsPrompt(message: message, defaultValue: defaultValue, result: {(result) -> Void in + onJsPrompt(frame: frame, message: message, defaultValue: defaultValue, result: {(result) -> Void in if result is FlutterError { print((result as! FlutterError).message ?? "") } @@ -2341,8 +2401,56 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { - onCreateWindow(url: navigationAction.request.url!, navigationType: navigationAction.navigationType) - return nil + InAppWebView.windowAutoincrementId += 1 + let windowId = InAppWebView.windowAutoincrementId + + let windowWebView = InAppWebView(frame: CGRect.zero, configuration: configuration, IABController: nil, contextMenu: nil, channel: nil) + windowWebView.windowId = windowId + + let webViewTransport = WebViewTransport( + webView: windowWebView, + request: navigationAction.request + ) + + InAppWebView.windowWebViews[windowId] = webViewTransport + windowWebView.stopLoading() + + let arguments: [String: Any?] = [ + "url": navigationAction.request.url?.absoluteString, + "windowId": windowId, + "androidIsDialog": nil, + "androidIsUserGesture": nil, + "iosWKNavigationType": navigationAction.navigationType.rawValue, + "iosIsForMainFrame": navigationAction.targetFrame?.isMainFrame ?? false + ] + channel?.invokeMethod("onCreateWindow", arguments: arguments, result: { (result) -> Void in + if result is FlutterError { + print((result as! FlutterError).message ?? "") + if InAppWebView.windowWebViews[windowId] != nil { + InAppWebView.windowWebViews.removeValue(forKey: windowId) + } + return + } + else if (result as? NSObject) == FlutterMethodNotImplemented { + self.updateUrlTextFieldForIABController(navigationAction: navigationAction) + if InAppWebView.windowWebViews[windowId] != nil { + InAppWebView.windowWebViews.removeValue(forKey: windowId) + } + return + } + else { + if result == nil, InAppWebView.windowWebViews[windowId] != nil { + InAppWebView.windowWebViews.removeValue(forKey: windowId) + } + } + }) + + return windowWebView + } + + public func webViewDidClose(_ webView: WKWebView) { + let arguments: [String: Any?] = [:] + channel?.invokeMethod("onCloseWindow", arguments: arguments) } public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { @@ -2461,23 +2569,23 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi // //onContextMenuWillPresentForElement(linkURL: elementInfo.linkURL?.absoluteString) // } - public func onLoadStart(url: String) { - let arguments: [String: Any] = ["url": url] + public func onLoadStart(url: String?) { + let arguments: [String: Any?] = ["url": url] channel?.invokeMethod("onLoadStart", arguments: arguments) } - public func onLoadStop(url: String) { - let arguments: [String: Any] = ["url": url] + public func onLoadStop(url: String?) { + let arguments: [String: Any?] = ["url": url] channel?.invokeMethod("onLoadStop", arguments: arguments) } - public func onLoadError(url: String, error: Error) { - let arguments: [String: Any] = ["url": url, "code": error._code, "message": error.localizedDescription] + public func onLoadError(url: String?, error: Error) { + let arguments: [String: Any?] = ["url": url, "code": error._code, "message": error.localizedDescription] channel?.invokeMethod("onLoadError", arguments: arguments) } - public func onLoadHttpError(url: String, statusCode: Int, description: String) { - let arguments: [String: Any] = ["url": url, "statusCode": statusCode, "description": description] + public func onLoadHttpError(url: String?, statusCode: Int, description: String) { + let arguments: [String: Any?] = ["url": url, "statusCode": statusCode, "description": description] channel?.invokeMethod("onLoadHttpError", arguments: arguments) } @@ -2523,16 +2631,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi channel?.invokeMethod("shouldOverrideUrlLoading", arguments: arguments, result: result) } - public func onCreateWindow(url: URL, navigationType: WKNavigationType) { - let arguments: [String: Any?] = [ - "url": url.absoluteString, - "androidIsDialog": nil, - "androidIsUserGesture": nil, - "iosWKNavigationType": navigationType.rawValue - ] - channel?.invokeMethod("onCreateWindow", arguments: arguments) - } - public func onReceivedHttpAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) { let arguments: [String: Any?] = [ "host": challenge.protectionSpace.host, @@ -2613,18 +2711,31 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi channel?.invokeMethod("onReceivedClientCertRequest", arguments: arguments, result: result) } - public func onJsAlert(message: String, result: FlutterResult?) { - let arguments: [String: Any] = ["message": message] + public func onJsAlert(frame: WKFrameInfo, message: String, result: FlutterResult?) { + let arguments: [String: Any?] = [ + "url": frame.request.url?.absoluteString, + "message": message, + "iosIsMainFrame": frame.isMainFrame + ] channel?.invokeMethod("onJsAlert", arguments: arguments, result: result) } - public func onJsConfirm(message: String, result: FlutterResult?) { - let arguments: [String: Any] = ["message": message] + public func onJsConfirm(frame: WKFrameInfo, message: String, result: FlutterResult?) { + let arguments: [String: Any?] = [ + "url": frame.request.url?.absoluteString, + "message": message, + "iosIsMainFrame": frame.isMainFrame + ] channel?.invokeMethod("onJsConfirm", arguments: arguments, result: result) } - public func onJsPrompt(message: String, defaultValue: String?, result: FlutterResult?) { - let arguments: [String: Any] = ["message": message, "defaultValue": defaultValue as Any] + public func onJsPrompt(frame: WKFrameInfo, message: String, defaultValue: String?, result: FlutterResult?) { + let arguments: [String: Any?] = [ + "url": frame.request.url?.absoluteString, + "message": message, + "defaultValue": defaultValue as Any, + "iosIsMainFrame": frame.isMainFrame + ] channel?.invokeMethod("onJsPrompt", arguments: arguments, result: result) } @@ -2633,7 +2744,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi channel?.invokeMethod("onConsoleMessage", arguments: arguments) } - public func onUpdateVisitedHistory(url: String) { + public func onUpdateVisitedHistory(url: String?) { let arguments: [String: Any?] = [ "url": url, "androidIsReload": nil @@ -2641,6 +2752,13 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi channel?.invokeMethod("onUpdateVisitedHistory", arguments: arguments) } + public func onTitleChanged(title: String?) { + let arguments: [String: Any?] = [ + "title": title + ] + channel?.invokeMethod("onTitleChanged", arguments: arguments) + } + public func onLongPressHitTestResult(hitTestResult: [String: Any?]) { let arguments: [String: Any?] = [ "hitTestResult": hitTestResult @@ -2929,19 +3047,22 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi completionHandler() } stopLoading() - configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog") - configuration.userContentController.removeScriptMessageHandler(forName: "consoleDebug") - configuration.userContentController.removeScriptMessageHandler(forName: "consoleError") - configuration.userContentController.removeScriptMessageHandler(forName: "consoleInfo") - configuration.userContentController.removeScriptMessageHandler(forName: "consoleWarn") - configuration.userContentController.removeScriptMessageHandler(forName: "callHandler") - configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived") - configuration.userContentController.removeAllUserScripts() + if windowId == nil { + configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog") + configuration.userContentController.removeScriptMessageHandler(forName: "consoleDebug") + configuration.userContentController.removeScriptMessageHandler(forName: "consoleError") + configuration.userContentController.removeScriptMessageHandler(forName: "consoleInfo") + configuration.userContentController.removeScriptMessageHandler(forName: "consoleWarn") + configuration.userContentController.removeScriptMessageHandler(forName: "callHandler") + configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived") + configuration.userContentController.removeAllUserScripts() + if #available(iOS 11.0, *) { + configuration.userContentController.removeAllContentRuleLists() + } + } removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) removeObserver(self, forKeyPath: #keyPath(WKWebView.url)) - if #available(iOS 11.0, *) { - configuration.userContentController.removeAllContentRuleLists() - } + removeObserver(self, forKeyPath: #keyPath(WKWebView.title)) NotificationCenter.default.removeObserver(self) for imp in customIMPs { imp_removeBlock(imp) @@ -2956,6 +3077,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi isPausedTimersCompletionHandler = nil channel = nil SharedLastTouchPointTimestamp.removeValue(forKey: self) + if let wId = windowId, InAppWebView.windowWebViews[wId] != nil { + InAppWebView.windowWebViews.removeValue(forKey: wId) + } super.removeFromSuperview() } diff --git a/ios/Classes/SafariViewController.swift b/ios/Classes/SafariViewController.swift index aec12e3c..b77256bc 100755 --- a/ios/Classes/SafariViewController.swift +++ b/ios/Classes/SafariViewController.swift @@ -12,7 +12,6 @@ import SafariServices public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafariViewControllerDelegate { var channel: FlutterMethodChannel? - var tmpWindow: UIWindow? var safariOptions: SafariBrowserOptions? var uuid: String = "" var menuItemList: [[String: Any]] = [] @@ -48,6 +47,13 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa onChromeSafariBrowserOpened() } + public override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.onChromeSafariBrowserClosed() + self.dispose() + } + + func prepareSafariBrowser() { if #available(iOS 11.0, *) { self.dismissButtonStyle = SFSafariViewController.DismissButtonStyle(rawValue: (safariOptions?.dismissButtonStyle)!)! @@ -69,11 +75,8 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa func close(result: FlutterResult?) { dismiss(animated: true) + // wait for the animation DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400), execute: {() -> Void in - self.tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) - UIApplication.shared.delegate?.window??.makeKeyAndVisible() - self.onChromeSafariBrowserClosed() - self.dispose() if result != nil { result!(true) } diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift index 3d3b4c98..3117e8cb 100755 --- a/ios/Classes/SwiftFlutterPlugin.swift +++ b/ios/Classes/SwiftFlutterPlugin.swift @@ -37,9 +37,6 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var safariViewControllers: [String: Any?] = [:] - var tmpWindow: UIWindow? - private var previousStatusBarStyle = -1 - public init(with registrar: FlutterPluginRegistrar) { super.init() diff --git a/lib/src/headless_in_app_webview.dart b/lib/src/headless_in_app_webview.dart index 57dff8a6..36633128 100644 --- a/lib/src/headless_in_app_webview.dart +++ b/lib/src/headless_in_app_webview.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:flutter/services.dart'; import 'context_menu.dart'; @@ -18,8 +20,12 @@ class HeadlessInAppWebView implements WebView { ///WebView Controller that can be used to access the [InAppWebViewController] API. InAppWebViewController webViewController; + ///The window id of a [CreateWindowRequest.windowId]. + final int windowId; + HeadlessInAppWebView( - {this.onWebViewCreated, + {this.windowId, + this.onWebViewCreated, this.onLoadStart, this.onLoadStop, this.onLoadError, @@ -32,6 +38,7 @@ class HeadlessInAppWebView implements WebView { this.onDownloadStart, this.onLoadResourceCustomScheme, this.onCreateWindow, + this.onCloseWindow, this.onJsAlert, this.onJsConfirm, this.onJsPrompt, @@ -49,6 +56,9 @@ class HeadlessInAppWebView implements WebView { this.onEnterFullscreen, this.onExitFullscreen, this.onPageCommitVisible, + this.onTitleChanged, + this.onWindowFocus, + this.onWindowBlur, this.androidOnSafeBrowsingHit, this.androidOnPermissionRequest, this.androidOnGeolocationPermissionsShowPrompt, @@ -59,6 +69,11 @@ class HeadlessInAppWebView implements WebView { this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, this.androidOnScaleChanged, + this.androidOnRequestFocus, + this.androidOnReceivedIcon, + this.androidOnReceivedTouchIconUrl, + this.androidOnJsBeforeUnload, + this.androidOnReceivedLoginRequest, this.iosOnWebContentProcessDidTerminate, this.iosOnDidReceiveServerRedirectForProvisionalNavigation, this.initialUrl, @@ -114,7 +129,7 @@ class HeadlessInAppWebView implements WebView { } @override - final Future Function(InAppWebViewController controller) + final void Function(InAppWebViewController controller) androidOnGeolocationPermissionsHidePrompt; @override @@ -151,15 +166,19 @@ class HeadlessInAppWebView implements WebView { final String initialUrl; @override - final Future Function(InAppWebViewController controller, String url) + final void Function(InAppWebViewController controller, String url) onPageCommitVisible; @override - final Future Function(InAppWebViewController controller) + final void Function(InAppWebViewController controller, String title) + onTitleChanged; + + @override + final void Function(InAppWebViewController controller) iosOnDidReceiveServerRedirectForProvisionalNavigation; @override - final Future Function(InAppWebViewController controller) + final void Function(InAppWebViewController controller) iosOnWebContentProcessDidTerminate; @override @@ -178,8 +197,24 @@ class HeadlessInAppWebView implements WebView { onConsoleMessage; @override - final void Function(InAppWebViewController controller, - OnCreateWindowRequest onCreateWindowRequest) onCreateWindow; + final Future Function(InAppWebViewController controller, + CreateWindowRequest onCreateWindowRequest) onCreateWindow; + + @override + final void Function(InAppWebViewController controller) + onCloseWindow; + + @override + final void Function(InAppWebViewController controller) + onWindowFocus; + + @override + final void Function(InAppWebViewController controller) + onWindowBlur; + + @override + final void Function(InAppWebViewController controller) + androidOnRequestFocus; @override final void Function(InAppWebViewController controller, String url) @@ -191,15 +226,15 @@ class HeadlessInAppWebView implements WebView { @override final Future Function( - InAppWebViewController controller, String message) onJsAlert; + InAppWebViewController controller, JsAlertRequest jsAlertRequest) onJsAlert; @override final Future Function( - InAppWebViewController controller, String message) onJsConfirm; + InAppWebViewController controller, JsConfirmRequest jsConfirmRequest) onJsConfirm; @override final Future Function(InAppWebViewController controller, - String message, String defaultValue) onJsPrompt; + JsPromptRequest jsPromptRequest) onJsPrompt; @override final void Function(InAppWebViewController controller, String url, int code, @@ -302,7 +337,7 @@ class HeadlessInAppWebView implements WebView { androidOnRenderProcessResponsive; @override - final Future Function( + final void Function( InAppWebViewController controller, RenderProcessGoneDetail detail) androidOnRenderProcessGone; @@ -311,7 +346,24 @@ class HeadlessInAppWebView implements WebView { InAppWebViewController controller, String url) androidOnFormResubmission; @override - final Future Function( + final void Function( InAppWebViewController controller, double oldScale, double newScale) androidOnScaleChanged; + + @override + final void Function(InAppWebViewController controller, Uint8List icon) + androidOnReceivedIcon; + + @override + final void Function(InAppWebViewController controller, String url, bool precomposed) + androidOnReceivedTouchIconUrl; + + @override + final Future Function( + InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest) + androidOnJsBeforeUnload; + + @override + final void Function(InAppWebViewController controller, LoginRequest loginRequest) + androidOnReceivedLoginRequest; } diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart index f5ceac33..de1937e4 100755 --- a/lib/src/in_app_browser.dart +++ b/lib/src/in_app_browser.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'dart:collection'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'context_menu.dart'; import 'in_app_webview_controller.dart'; import 'webview_options.dart'; @@ -28,8 +30,11 @@ class InAppBrowser { /// WebView Controller that can be used to access the [InAppWebViewController] API. InAppWebViewController webViewController; + ///The window id of a [CreateWindowRequest.windowId]. + final int windowId; + /// - InAppBrowser() { + InAppBrowser({this.windowId}) { uuid = uuidGenerator.v4(); this._channel = MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$uuid'); @@ -74,6 +79,7 @@ class InAppBrowser { args.putIfAbsent('headers', () => headers); args.putIfAbsent('options', () => options?.toMap() ?? {}); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); + args.putIfAbsent('windowId', () => windowId); await _sharedChannel.invokeMethod('openUrl', args); } @@ -123,6 +129,7 @@ class InAppBrowser { args.putIfAbsent('headers', () => headers); args.putIfAbsent('options', () => options?.toMap() ?? {}); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); + args.putIfAbsent('windowId', () => windowId); await _sharedChannel.invokeMethod('openFile', args); } @@ -153,6 +160,7 @@ class InAppBrowser { args.putIfAbsent('baseUrl', () => baseUrl); args.putIfAbsent('historyUrl', () => androidHistoryUrl); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); + args.putIfAbsent('windowId', () => windowId); await _sharedChannel.invokeMethod('openData', args); } @@ -231,18 +239,21 @@ class InAppBrowser { ///Event fired when the [InAppBrowser] starts to load an [url]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview void onLoadStart(String url) {} ///Event fired when the [InAppBrowser] finishes loading an [url]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onPageFinished(android.webkit.WebView,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview void onLoadStop(String url) {} ///Event fired when the [InAppBrowser] encounters an error loading an [url]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedError(android.webkit.WebView,%20int,%20java.lang.String,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455623-webview void onLoadError(String url, int code, String message) {} @@ -257,6 +268,7 @@ class InAppBrowser { ///**NOTE**: available on Android 23+. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpError(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20android.webkit.WebResourceResponse) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview void onLoadHttpError(String url, int statusCode, String description) {} @@ -283,6 +295,7 @@ class InAppBrowser { ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldOverrideUrlLoading] option to `true`. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455641-webview // ignore: missing_return Future shouldOverrideUrlLoading( @@ -300,6 +313,7 @@ class InAppBrowser { ///[y] represents the current vertical scroll origin in pixels. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#onScrollChanged(int,%20int,%20int,%20int) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619392-scrollviewdidscroll void onScrollChanged(int x, int y) {} @@ -310,6 +324,7 @@ class InAppBrowser { ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useOnDownloadStart] option to `true`. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#setDownloadListener(android.webkit.DownloadListener) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview void onDownloadStart(String url) {} @@ -326,51 +341,91 @@ class InAppBrowser { ///Event fired when the [InAppBrowser] webview requests the host application to create a new window, ///for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side. + ///The return type should be an [InAppBrowser] instance, a [HeadlessInAppWebView] instance or `null`. If it returns `null`, then nothing will happen. + ///If it returns an [InAppBrowser] instance, the new [InAppBrowser] will be immediately opened. + ///If it returns a [HeadlessInAppWebView] instance, the [HeadlessInAppWebView.run] method will be immediately called. + ///Remember to use the [CreateWindowRequest.windowId] to create the new WebView instance. /// - ///[onCreateWindowRequest] represents the request. + ///[createWindowRequest] represents the request. /// ///**NOTE**: on Android you need to set [AndroidInAppWebViewOptions.supportMultipleWindows] option to `true`. /// + ///**NOTE**: on iOS, setting these initial options: [InAppWebViewOptions.supportZoom], [InAppWebViewOptions.useOnLoadResource], [InAppWebViewOptions.useShouldInterceptAjaxRequest], + ///[InAppWebViewOptions.useShouldInterceptFetchRequest], [InAppWebViewOptions.applicationNameForUserAgent], [InAppWebViewOptions.javaScriptCanOpenWindowsAutomatically], + ///[InAppWebViewOptions.javaScriptEnabled], [InAppWebViewOptions.minimumFontSize], [InAppWebViewOptions.preferredContentMode], [InAppWebViewOptions.incognito], + ///[InAppWebViewOptions.cacheEnabled], [InAppWebViewOptions.mediaPlaybackRequiresUserGesture], + ///[InAppWebViewOptions.resourceCustomSchemes], [IOSInAppWebViewOptions.sharedCookiesEnabled], + ///[IOSInAppWebViewOptions.enableViewportScale], [IOSInAppWebViewOptions.allowsAirPlayForMediaPlayback], + ///[IOSInAppWebViewOptions.allowsPictureInPictureMediaPlayback], [IOSInAppWebViewOptions.isFraudulentWebsiteWarningEnabled], + ///[IOSInAppWebViewOptions.allowsInlineMediaPlayback], [IOSInAppWebViewOptions.suppressesIncrementalRendering], [IOSInAppWebViewOptions.selectionGranularity], + ///[IOSInAppWebViewOptions.ignoresViewportScaleLimits], + ///will have no effect due to a `WKWebView` limitation when creating a new window WebView: it's impossible to return a new `WKWebView` + ///with a different `WKWebViewConfiguration` instance (see https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview). + ///So, these options will be inherited from the caller WebView. + ///Also, note that calling [InAppWebViewController.setOptions] method using the controller of the new created WebView, + ///it will update also the WebView options of the caller WebView. + /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onCreateWindow(android.webkit.WebView,%20boolean,%20boolean,%20android.os.Message) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview - void onCreateWindow(OnCreateWindowRequest onCreateWindowRequest) {} + // ignore: missing_return + Future onCreateWindow(CreateWindowRequest createWindowRequest) {} + + ///Event fired when the host application should close the given WebView and remove it from the view system if necessary. + ///At this point, WebCore has stopped any loading in this window and has removed any cross-scripting ability in javascript. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onCloseWindow(android.webkit.WebView) + /// + ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1537390-webviewdidclose + void onCloseWindow() {} + + ///Event fired when the JavaScript `window` object of the WebView has received focus. + ///This is the result of the `focus` javascript event applied to the `window` object. + void onWindowFocus() {} + + ///Event fired when the JavaScript `window` object of the WebView has lost focus. + ///This is the result of the `blur` javascript event applied to the `window` object. + void onWindowBlur() {} ///Event fired 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. + ///[jsAlertRequest] contains the message to be displayed in the alert dialog and the of the page requesting the dialog. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onJsAlert(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1537406-webview // ignore: missing_return - Future onJsAlert(String message) {} + Future onJsAlert(JsAlertRequest jsAlertRequest) {} ///Event fired 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. + ///[jsConfirmRequest] contains the message to be displayed in the confirm dialog and the of the page requesting the dialog. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onJsConfirm(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1536489-webview // ignore: missing_return - Future onJsConfirm(String message) {} + Future onJsConfirm(JsConfirmRequest jsConfirmRequest) {} ///Event fired 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. + ///[jsPromptRequest] contains the message to be displayed in the prompt dialog, the default value displayed in the prompt dialog, and the of the page requesting the dialog. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onJsPrompt(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20android.webkit.JsPromptResult) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1538086-webview // ignore: missing_return - Future onJsPrompt(String message, String defaultValue) {} + Future onJsPrompt(JsPromptRequest jsPromptRequest) {} ///Event fired when the WebView received an HTTP authentication request. The default behavior is to cancel the request. /// ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [HttpAuthChallenge]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpAuthRequest(android.webkit.WebView,%20android.webkit.HttpAuthHandler,%20java.lang.String,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview // ignore: missing_return Future onReceivedHttpAuthRequest( @@ -382,6 +437,7 @@ class InAppBrowser { ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ServerTrustChallenge]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%20android.webkit.SslErrorHandler,%20android.net.http.SslError) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview // ignore: missing_return Future onReceivedServerTrustAuthRequest( @@ -395,6 +451,7 @@ class InAppBrowser { ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ClientCertChallenge]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedClientCertRequest(android.webkit.WebView,%20android.webkit.ClientCertRequest) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview // ignore: missing_return Future onReceivedClientCertRequest( @@ -473,18 +530,21 @@ class InAppBrowser { ///[hitTestResult] represents the hit result for hitting an HTML elements. /// ///**Official Android API**: https://developer.android.com/reference/android/view/View#setOnLongClickListener(android.view.View.OnLongClickListener) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer void onLongPressHitTestResult(InAppWebViewHitTestResult hitTestResult) {} ///Event fired when the current page has entered full screen mode. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onShowCustomView(android.view.View,%20android.webkit.WebChromeClient.CustomViewCallback) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiwindow/1621621-didbecomevisiblenotification void onEnterFullscreen() {} ///Event fired when the current page has exited full screen mode. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onHideCustomView() + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiwindow/1621617-didbecomehiddennotification void onExitFullscreen() {} @@ -496,9 +556,18 @@ class InAppBrowser { ///[url] represents the URL corresponding to the page navigation that triggered this callback. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onPageCommitVisible(android.webkit.WebView,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455635-webview void onPageCommitVisible(String url) {} + ///Event fired when a change in the document title occurred. + /// + ///[title] represents the string containing the new title of the document. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedTitle(android.webkit.WebView,%20java.lang.String) + void onTitleChanged(String title) {} + + ///Event fired when the WebView notifies that a loading URL has been flagged by Safe Browsing. ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible. /// @@ -636,6 +705,60 @@ class InAppBrowser { ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) void androidOnScaleChanged(double oldScale, double newScale) {} + ///Event fired when there is a request to display and focus for this WebView. + ///This may happen due to another WebView opening a link in this WebView and requesting that this WebView be displayed. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onRequestFocus(android.webkit.WebView) + void androidOnRequestFocus() {} + + ///Event fired when there is new favicon for the current page. + /// + ///[icon] represents the favicon for the current page. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedIcon(android.webkit.WebView,%20android.graphics.Bitmap) + void androidOnReceivedIcon(Uint8List icon) {} + + ///Event fired when there is an url for an apple-touch-icon. + /// + ///[url] represents the icon url. + /// + ///[precomposed] is `true` if the url is for a precomposed touch icon. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedTouchIconUrl(android.webkit.WebView,%20java.lang.String,%20boolean) + void androidOnReceivedTouchIconUrl(String url, bool precomposed) {} + + ///Event fired when the client should display a dialog to confirm navigation away from the current page. + ///This is the result of the `onbeforeunload` javascript event. + ///If [JsBeforeUnloadResponse.handledByClient] is `true`, WebView will assume that the client will handle the confirm dialog. + ///If [JsBeforeUnloadResponse.handledByClient] is `false`, a default value of `true` will be returned to javascript to accept navigation away from the current page. + ///The default behavior is to return `false`. + ///Setting the [JsBeforeUnloadResponse.action] to [JsBeforeUnloadResponseAction.CONFIRM] will navigate away from the current page, + ///[JsBeforeUnloadResponseAction.CANCEL] will cancel the navigation. + /// + ///[jsBeforeUnloadRequest] contains the message to be displayed in the alert dialog and the of the page requesting the dialog. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onJsBeforeUnload(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult) + // ignore: missing_return + Future androidOnJsBeforeUnload( + JsBeforeUnloadRequest jsBeforeUnloadRequest) {} + + ///Event fired when a request to automatically log in the user has been processed. + /// + ///[loginRequest] contains the realm, account and args of the login request. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedLoginRequest(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20java.lang.String) + void androidOnReceivedLoginRequest(LoginRequest loginRequest) {} + ///Invoked when the web view's web content process is terminated. /// ///**NOTE**: available only on iOS. diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart index abe69210..8b1ee228 100755 --- a/lib/src/in_app_webview.dart +++ b/lib/src/in_app_webview.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -22,8 +23,12 @@ class InAppWebView extends StatefulWidget implements WebView { /// were not claimed by any other gesture recognizer. final Set> gestureRecognizers; + ///The window id of a [CreateWindowRequest.windowId]. + final int windowId; + const InAppWebView({ Key key, + this.windowId, this.initialUrl = "about:blank", this.initialFile, this.initialData, @@ -43,6 +48,7 @@ class InAppWebView extends StatefulWidget implements WebView { this.onDownloadStart, this.onLoadResourceCustomScheme, this.onCreateWindow, + this.onCloseWindow, this.onJsAlert, this.onJsConfirm, this.onJsPrompt, @@ -60,6 +66,9 @@ class InAppWebView extends StatefulWidget implements WebView { this.onEnterFullscreen, this.onExitFullscreen, this.onPageCommitVisible, + this.onTitleChanged, + this.onWindowFocus, + this.onWindowBlur, this.androidOnSafeBrowsingHit, this.androidOnPermissionRequest, this.androidOnGeolocationPermissionsShowPrompt, @@ -70,6 +79,11 @@ class InAppWebView extends StatefulWidget implements WebView { this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, this.androidOnScaleChanged, + this.androidOnRequestFocus, + this.androidOnReceivedIcon, + this.androidOnReceivedTouchIconUrl, + this.androidOnJsBeforeUnload, + this.androidOnReceivedLoginRequest, this.iosOnWebContentProcessDidTerminate, this.iosOnDidReceiveServerRedirectForProvisionalNavigation, this.gestureRecognizers, @@ -79,7 +93,7 @@ class InAppWebView extends StatefulWidget implements WebView { _InAppWebViewState createState() => _InAppWebViewState(); @override - final Future Function(InAppWebViewController controller) + final void Function(InAppWebViewController controller) androidOnGeolocationPermissionsHidePrompt; @override @@ -116,15 +130,19 @@ class InAppWebView extends StatefulWidget implements WebView { final ContextMenu contextMenu; @override - final Future Function(InAppWebViewController controller, String url) + final void Function(InAppWebViewController controller, String url) onPageCommitVisible; @override - final Future Function(InAppWebViewController controller) + final void Function(InAppWebViewController controller, String title) + onTitleChanged; + + @override + final void Function(InAppWebViewController controller) iosOnDidReceiveServerRedirectForProvisionalNavigation; @override - final Future Function(InAppWebViewController controller) + final void Function(InAppWebViewController controller) iosOnWebContentProcessDidTerminate; @override @@ -143,8 +161,32 @@ class InAppWebView extends StatefulWidget implements WebView { onConsoleMessage; @override - final void Function(InAppWebViewController controller, - OnCreateWindowRequest onCreateWindowRequest) onCreateWindow; + final Future Function(InAppWebViewController controller, + CreateWindowRequest onCreateWindowRequest) onCreateWindow; + + @override + final void Function(InAppWebViewController controller) + onCloseWindow; + + @override + final void Function(InAppWebViewController controller) + onWindowFocus; + + @override + final void Function(InAppWebViewController controller) + onWindowBlur; + + @override + final void Function(InAppWebViewController controller) + androidOnRequestFocus; + + @override + final void Function(InAppWebViewController controller, Uint8List icon) + androidOnReceivedIcon; + + @override + final void Function(InAppWebViewController controller, String url, bool precomposed) + androidOnReceivedTouchIconUrl; @override final void Function(InAppWebViewController controller, String url) @@ -156,15 +198,15 @@ class InAppWebView extends StatefulWidget implements WebView { @override final Future Function( - InAppWebViewController controller, String message) onJsAlert; + InAppWebViewController controller, JsAlertRequest jsAlertRequest) onJsAlert; @override final Future Function( - InAppWebViewController controller, String message) onJsConfirm; + InAppWebViewController controller, JsConfirmRequest jsConfirmRequest) onJsConfirm; @override final Future Function(InAppWebViewController controller, - String message, String defaultValue) onJsPrompt; + JsPromptRequest jsPromptRequest) onJsPrompt; @override final void Function(InAppWebViewController controller, String url, int code, @@ -267,7 +309,7 @@ class InAppWebView extends StatefulWidget implements WebView { androidOnRenderProcessResponsive; @override - final Future Function( + final void Function( InAppWebViewController controller, RenderProcessGoneDetail detail) androidOnRenderProcessGone; @@ -276,9 +318,18 @@ class InAppWebView extends StatefulWidget implements WebView { InAppWebViewController controller, String url) androidOnFormResubmission; @override - final Future Function( + final void Function( InAppWebViewController controller, double oldScale, double newScale) androidOnScaleChanged; + + @override + final Future Function( + InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest) + androidOnJsBeforeUnload; + + @override + final void Function(InAppWebViewController controller, LoginRequest loginRequest) + androidOnReceivedLoginRequest; } class _InAppWebViewState extends State { @@ -298,7 +349,8 @@ class _InAppWebViewState extends State { 'initialData': widget.initialData?.toMap(), 'initialHeaders': widget.initialHeaders, 'initialOptions': widget.initialOptions?.toMap() ?? {}, - 'contextMenu': widget.contextMenu?.toMap() ?? {} + 'contextMenu': widget.contextMenu?.toMap() ?? {}, + 'windowId': widget.windowId }, creationParamsCodec: const StandardMessageCodec(), ); @@ -332,7 +384,8 @@ class _InAppWebViewState extends State { 'initialData': widget.initialData?.toMap(), 'initialHeaders': widget.initialHeaders, 'initialOptions': widget.initialOptions?.toMap() ?? {}, - 'contextMenu': widget.contextMenu?.toMap() ?? {} + 'contextMenu': widget.contextMenu?.toMap() ?? {}, + 'windowId': widget.windowId }, creationParamsCodec: const StandardMessageCodec(), ); diff --git a/lib/src/in_app_webview_controller.dart b/lib/src/in_app_webview_controller.dart index 3c3bea85..9f46c02d 100644 --- a/lib/src/in_app_webview_controller.dart +++ b/lib/src/in_app_webview_controller.dart @@ -30,7 +30,8 @@ const javaScriptHandlerForbiddenNames = [ "onAjaxProgress", "shouldInterceptFetchRequest", "onPrint", - "androidKeyboardWorkaroundFocusoutEvent" + "onWindowFocus", + "onWindowBlur", ]; ///Controls a WebView, such as an [InAppWebView] widget instance, a [HeadlessInAppWebView] instance or [InAppBrowser] WebView instance. @@ -206,21 +207,74 @@ class InAppWebViewController { break; case "onCreateWindow": String url = call.arguments["url"]; + int windowId = call.arguments["windowId"]; bool androidIsDialog = call.arguments["androidIsDialog"]; bool androidIsUserGesture = call.arguments["androidIsUserGesture"]; int iosWKNavigationType = call.arguments["iosWKNavigationType"]; + bool iosIsForMainFrame = call.arguments["iosIsForMainFrame"]; - OnCreateWindowRequest onCreateWindowRequest = OnCreateWindowRequest( + CreateWindowRequest createWindowRequest = CreateWindowRequest( url: url, + windowId: windowId, androidIsDialog: androidIsDialog, androidIsUserGesture: androidIsUserGesture, iosWKNavigationType: - IOSWKNavigationType.fromValue(iosWKNavigationType)); + IOSWKNavigationType.fromValue(iosWKNavigationType), + iosIsForMainFrame: iosIsForMainFrame); + + WebView webView; + dynamic inAppBrowserWindow; if (_webview != null && _webview.onCreateWindow != null) - _webview.onCreateWindow(this, onCreateWindowRequest); + webView = await _webview.onCreateWindow(this, createWindowRequest); + else if (_inAppBrowser != null) { + inAppBrowserWindow = await _inAppBrowser.onCreateWindow(createWindowRequest); + assert( + inAppBrowserWindow is InAppBrowser || inAppBrowserWindow is HeadlessInAppWebView, + "InAppBrowser.onCreateWindow should return an \"InAppBrowser\" instance or a \"HeadlessInAppWebView\" instance." + ); + } + + int webViewWindowId; + + if (webView != null) { + webViewWindowId = webView.windowId; + assert(webViewWindowId != + null, "If you are returning a WebView, then WebView.windowId should be not null. To set the " + + "WebView.windowId property, you should use the CreateWindowRequest.windowId property."); + if (webView is HeadlessInAppWebView) { + webView.run(); + } + } else if (inAppBrowserWindow != null) { + if (inAppBrowserWindow is InAppBrowser) { + webViewWindowId = inAppBrowserWindow.windowId; + assert(webViewWindowId != + null, "If you are returning an InAppBrowser, then InAppBrowser.windowId should be not null. To set the " + + "InAppBrowser.windowId property, you should use the CreateWindowRequest.windowId property."); + inAppBrowserWindow.openUrl(url: "about:blank"); + } + else if (inAppBrowserWindow is HeadlessInAppWebView) { + webViewWindowId = inAppBrowserWindow.windowId; + assert(webViewWindowId != + null, "If you are returning a HeadlessInAppWebView, then HeadlessInAppWebView.windowId should be not null. To set the " + + "HeadlessInAppWebView.windowId property, you should use the CreateWindowRequest.windowId property."); + inAppBrowserWindow.run(); + } + } + + return webViewWindowId; + case "onCloseWindow": + if (_webview != null && _webview.onCloseWindow != null) + _webview.onCloseWindow(this); else if (_inAppBrowser != null) - _inAppBrowser.onCreateWindow(onCreateWindowRequest); + _inAppBrowser.onCloseWindow(); + break; + case "onTitleChanged": + String title = call.arguments["title"]; + if (_webview != null && _webview.onTitleChanged != null) + _webview.onTitleChanged(this, title); + else if (_inAppBrowser != null) + _inAppBrowser.onTitleChanged(title); break; case "onGeolocationPermissionsShowPrompt": String origin = call.arguments["origin"]; @@ -312,30 +366,98 @@ class InAppWebViewController { else if (_inAppBrowser != null) _inAppBrowser.androidOnScaleChanged(oldScale, newScale); break; - case "onJsAlert": - String message = call.arguments["message"]; - if (_webview != null && _webview.onJsAlert != null) - return (await _webview.onJsAlert(this, message))?.toMap(); + case "onRequestFocus": + if (_webview != null && _webview.androidOnRequestFocus != null) + _webview.androidOnRequestFocus(this); else if (_inAppBrowser != null) - return (await _inAppBrowser.onJsAlert(message))?.toMap(); + _inAppBrowser.androidOnRequestFocus(); + break; + case "onReceivedIcon": + Uint8List icon = Uint8List.fromList(call.arguments["icon"].cast()); + + if (_webview != null && _webview.androidOnReceivedIcon != null) + _webview.androidOnReceivedIcon(this, icon); + else if (_inAppBrowser != null) + _inAppBrowser.androidOnReceivedIcon(icon); + break; + case "onReceivedTouchIconUrl": + String url = call.arguments["url"]; + bool precomposed = call.arguments["precomposed"]; + if (_webview != null && _webview.androidOnReceivedTouchIconUrl != null) + _webview.androidOnReceivedTouchIconUrl(this, url, precomposed); + else if (_inAppBrowser != null) + _inAppBrowser.androidOnReceivedTouchIconUrl(url, precomposed); + break; + case "onJsAlert": + String url = call.arguments["url"]; + String message = call.arguments["message"]; + bool iosIsMainFrame = call.arguments["iosIsMainFrame"]; + + JsAlertRequest jsAlertRequest = JsAlertRequest( + url: url, + message: message, + iosIsMainFrame: iosIsMainFrame + ); + + if (_webview != null && _webview.onJsAlert != null) + return (await _webview.onJsAlert(this, jsAlertRequest))?.toMap(); + else if (_inAppBrowser != null) + return (await _inAppBrowser.onJsAlert(jsAlertRequest))?.toMap(); break; case "onJsConfirm": + String url = call.arguments["url"]; String message = call.arguments["message"]; + bool iosIsMainFrame = call.arguments["iosIsMainFrame"]; + + JsConfirmRequest jsConfirmRequest = JsConfirmRequest( + url: url, + message: message, + iosIsMainFrame: iosIsMainFrame + ); + if (_webview != null && _webview.onJsConfirm != null) - return (await _webview.onJsConfirm(this, message))?.toMap(); + return (await _webview.onJsConfirm(this, jsConfirmRequest))?.toMap(); else if (_inAppBrowser != null) - return (await _inAppBrowser.onJsConfirm(message))?.toMap(); + return (await _inAppBrowser.onJsConfirm(jsConfirmRequest))?.toMap(); break; case "onJsPrompt": + String url = call.arguments["url"]; String message = call.arguments["message"]; String defaultValue = call.arguments["defaultValue"]; + bool iosIsMainFrame = call.arguments["iosIsMainFrame"]; + + JsPromptRequest jsPromptRequest = JsPromptRequest( + url: url, + message: message, + defaultValue: defaultValue, + iosIsMainFrame: iosIsMainFrame + ); + if (_webview != null && _webview.onJsPrompt != null) - return (await _webview.onJsPrompt(this, message, defaultValue)) + return (await _webview.onJsPrompt(this, jsPromptRequest)) ?.toMap(); else if (_inAppBrowser != null) - return (await _inAppBrowser.onJsPrompt(message, defaultValue)) + return (await _inAppBrowser.onJsPrompt(jsPromptRequest)) ?.toMap(); break; + case "onJsBeforeUnload": + String url = call.arguments["url"]; + String message = call.arguments["message"]; + bool iosIsMainFrame = call.arguments["iosIsMainFrame"]; + + JsBeforeUnloadRequest jsBeforeUnloadRequest = JsBeforeUnloadRequest( + url: url, + message: message, + iosIsMainFrame: iosIsMainFrame + ); + + print(jsBeforeUnloadRequest); + + if (_webview != null && _webview.androidOnJsBeforeUnload != null) + return (await _webview.androidOnJsBeforeUnload(this, jsBeforeUnloadRequest))?.toMap(); + else if (_inAppBrowser != null) + return (await _inAppBrowser.androidOnJsBeforeUnload(jsBeforeUnloadRequest))?.toMap(); + break; case "onSafeBrowsingHit": String url = call.arguments["url"]; SafeBrowsingThreat threatType = @@ -348,6 +470,22 @@ class InAppWebViewController { return (await _inAppBrowser.androidOnSafeBrowsingHit(url, threatType)) ?.toMap(); break; + case "onReceivedLoginRequest": + String realm = call.arguments["realm"]; + String account = call.arguments["account"]; + String args = call.arguments["args"]; + + LoginRequest loginRequest = LoginRequest( + realm: realm, + account: account, + args: args + ); + + if (_webview != null && _webview.androidOnReceivedLoginRequest != null) + _webview.androidOnReceivedLoginRequest(this, loginRequest); + else if (_inAppBrowser != null) + _inAppBrowser.androidOnReceivedLoginRequest(loginRequest); + break; case "onReceivedHttpAuthRequest": String host = call.arguments["host"]; String protocol = call.arguments["protocol"]; @@ -801,6 +939,18 @@ class InAppWebViewController { _webview.onPrint(this, url); else if (_inAppBrowser != null) _inAppBrowser.onPrint(url); return null; + case "onWindowFocus": + if (_webview != null && _webview.onWindowFocus != null) + _webview.onWindowFocus(this); + else if (_inAppBrowser != null) + _inAppBrowser.onWindowFocus(); + return null; + case "onWindowBlur": + if (_webview != null && _webview.onWindowBlur != null) + _webview.onWindowBlur(this); + else if (_inAppBrowser != null) + _inAppBrowser.onWindowBlur(); + return null; } if (javaScriptHandlersMap.containsKey(handlerName)) { @@ -822,6 +972,7 @@ class InAppWebViewController { ///This is not always the same as the URL passed to [WebView.onLoadStart] because although the load for that URL has begun, the current page may not have changed. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#getUrl() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1415005-url Future getUrl() async { Map args = {}; @@ -831,6 +982,7 @@ class InAppWebViewController { ///Gets the title for the current page. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#getTitle() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1415015-title Future getTitle() async { Map args = {}; @@ -840,6 +992,7 @@ class InAppWebViewController { ///Gets the progress for the current page. The progress value is between 0 and 100. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#getProgress() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1415007-estimatedprogress Future getProgress() async { Map args = {}; @@ -1039,6 +1192,7 @@ class InAppWebViewController { ///Loads the given [url] with optional [headers] specified as a map from name to value. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#loadUrl(java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414954-load Future loadUrl( {@required String url, Map headers = const {}}) async { @@ -1071,6 +1225,7 @@ class InAppWebViewController { ///The [androidHistoryUrl] parameter is the URL to use as the history entry. The default value is `about:blank`. If non-null, this must be a valid URL. This parameter is used only on Android. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#loadDataWithBaseURL(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String) + /// ///**Official iOS API**: ///- https://developer.apple.com/documentation/webkit/wkwebview/1415004-loadhtmlstring ///- https://developer.apple.com/documentation/webkit/wkwebview/1415011-load @@ -1132,6 +1287,7 @@ class InAppWebViewController { ///Reloads the WebView. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#reload() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414969-reload Future reload() async { Map args = {}; @@ -1141,6 +1297,7 @@ class InAppWebViewController { ///Goes back in the history of the WebView. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#goBack() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414952-goback Future goBack() async { Map args = {}; @@ -1150,6 +1307,7 @@ class InAppWebViewController { ///Returns a boolean value indicating whether the WebView can move backward. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#canGoBack() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback Future canGoBack() async { Map args = {}; @@ -1159,6 +1317,7 @@ class InAppWebViewController { ///Goes forward in the history of the WebView. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#goForward() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414993-goforward Future goForward() async { Map args = {}; @@ -1168,6 +1327,7 @@ class InAppWebViewController { ///Returns a boolean value indicating whether the WebView can move forward. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#canGoForward() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414962-cangoforward Future canGoForward() async { Map args = {}; @@ -1177,6 +1337,7 @@ class InAppWebViewController { ///Goes to the history item that is the number of steps away from the current item. Steps is negative if backward and positive if forward. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#goBackOrForward(int) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414991-go Future goBackOrForward({@required int steps}) async { assert(steps != null); @@ -1211,6 +1372,7 @@ class InAppWebViewController { ///Stops the WebView from loading. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#stopLoading() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414981-stoploading Future stopLoading() async { Map args = {}; @@ -1225,6 +1387,7 @@ class InAppWebViewController { ///where you know the page is ready "enough". /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#evaluateJavascript(java.lang.String,%20android.webkit.ValueCallback%3Cjava.lang.String%3E) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1415017-evaluatejavascript Future evaluateJavascript({@required String source}) async { Map args = {}; @@ -1396,6 +1559,7 @@ class InAppWebViewController { ///The object returned from this method will not be updated to reflect any new state. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#copyBackForwardList() + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkwebview/1414977-backforwardlist Future getCopyBackForwardList() async { Map args = {}; @@ -1488,6 +1652,7 @@ class InAppWebViewController { ///[animated] `true` to animate the scroll transition, `false` to make the scoll transition immediate. /// ///**Official Android API**: https://developer.android.com/reference/android/view/View#scrollTo(int,%20int) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619400-setcontentoffset Future scrollTo( {@required int x, @required int y, bool animated = false}) async { @@ -1508,6 +1673,7 @@ class InAppWebViewController { ///[animated] `true` to animate the scroll transition, `false` to make the scoll transition immediate. /// ///**Official Android API**: https://developer.android.com/reference/android/view/View#scrollBy(int,%20int) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619400-setcontentoffset Future scrollBy( {@required int x, @required int y, bool animated = false}) async { @@ -1545,6 +1711,7 @@ class InAppWebViewController { ///**NOTE**: available on Android 21+. /// ///**Official Android API**: https://developer.android.com/reference/android/print/PrintManager + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiprintinteractioncontroller Future printCurrentPage() async { Map args = {}; @@ -1554,6 +1721,7 @@ class InAppWebViewController { ///Gets the height of the HTML content. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#getContentHeight() + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619399-contentsize Future getContentHeight() async { Map args = {}; @@ -1567,6 +1735,7 @@ class InAppWebViewController { ///**NOTE**: available on Android 21+. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#zoomBy(float) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619412-setzoomscale Future zoomBy(double zoomFactor) async { assert(!Platform.isAndroid || @@ -1582,6 +1751,7 @@ class InAppWebViewController { ///**Official Android API**: ///- https://developer.android.com/reference/android/util/DisplayMetrics#density ///- https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619419-zoomscale Future getScale() async { Map args = {}; @@ -1616,6 +1786,7 @@ class InAppWebViewController { ///Clears the current focus. It will clear also, for example, the current text selection. /// ///**Official Android API**: https://developer.android.com/reference/android/view/ViewGroup#clearFocus() + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiresponder/1621097-resignfirstresponder Future clearFocus() async { Map args = {}; @@ -1753,6 +1924,7 @@ class InAppWebViewController { ///Returns the scrolled left position of the current WebView. /// ///**Official Android API**: https://developer.android.com/reference/android/view/View#getScrollX() + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619404-contentoffset Future getScrollX() async { Map args = {}; @@ -1762,6 +1934,7 @@ class InAppWebViewController { ///Returns the scrolled top position of the current WebView. /// ///**Official Android API**: https://developer.android.com/reference/android/view/View#getScrollY() + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619404-contentoffset Future getScrollY() async { Map args = {}; diff --git a/lib/src/types.dart b/lib/src/types.dart index aa895db8..46446eae 100755 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -439,6 +439,37 @@ class GeolocationPermissionShowPromptResponse { } } +///Class that represents the request of the [WebView.onJsAlert] event. +class JsAlertRequest { + ///The url of the page requesting the dialog. + String url; + + ///Message to be displayed in the window. + String message; + + ///Indicates whether the request was made for the main frame. Available only on iOS. + bool iosIsMainFrame; + + JsAlertRequest({ + this.url, + this.message, + this.iosIsMainFrame + }); + + Map toMap() { + return {"url": url, "message": message, "iosIsMainFrame": iosIsMainFrame}; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} + ///Class used by [JsAlertResponse] class. class JsAlertResponseAction { final int _value; @@ -494,6 +525,37 @@ class JsAlertResponse { } } +///Class that represents the request of the [WebView.onJsConfirm] event. +class JsConfirmRequest { + ///The url of the page requesting the dialog. + String url; + + ///Message to be displayed in the window. + String message; + + ///Indicates whether the request was made for the main frame. Available only on iOS. + bool iosIsMainFrame; + + JsConfirmRequest({ + this.url, + this.message, + this.iosIsMainFrame + }); + + Map toMap() { + return {"url": url, "message": message, "iosIsMainFrame": iosIsMainFrame}; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} + ///Class used by [JsConfirmResponse] class. class JsConfirmResponseAction { final int _value; @@ -555,6 +617,41 @@ class JsConfirmResponse { } } +///Class that represents the request of the [WebView.onJsPrompt] event. +class JsPromptRequest { + ///The url of the page requesting the dialog. + String url; + + ///Message to be displayed in the window. + String message; + + ///The default value displayed in the prompt dialog. + String defaultValue; + + ///Indicates whether the request was made for the main frame. Available only on iOS. + bool iosIsMainFrame; + + JsPromptRequest({ + this.url, + this.message, + this.defaultValue, + this.iosIsMainFrame + }); + + Map toMap() { + return {"url": url, "message": message, "defaultValue": defaultValue, "iosIsMainFrame": iosIsMainFrame}; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} + ///Class used by [JsPromptResponse] class. class JsPromptResponseAction { final int _value; @@ -626,6 +723,98 @@ class JsPromptResponse { } } +///Class that represents the request of the [WebView.androidOnJsBeforeUnload] event. +class JsBeforeUnloadRequest { + ///The url of the page requesting the dialog. + String url; + + ///Message to be displayed in the window. + String message; + + ///Indicates whether the request was made for the main frame. Available only on iOS. + bool iosIsMainFrame; + + JsBeforeUnloadRequest({ + this.url, + this.message, + this.iosIsMainFrame + }); + + Map toMap() { + return {"url": url, "message": message, "iosIsMainFrame": iosIsMainFrame}; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} + +///Class used by [JsBeforeUnloadResponse] class. +class JsBeforeUnloadResponseAction { + final int _value; + + const JsBeforeUnloadResponseAction._internal(this._value); + + int toValue() => _value; + + static const CONFIRM = const JsBeforeUnloadResponseAction._internal(0); + static const CANCEL = const JsBeforeUnloadResponseAction._internal(1); + + bool operator ==(value) => value == _value; + + @override + int get hashCode => _value.hashCode; +} + +///Class that represents the response used by the [WebView.androidOnJsBeforeUnload] event to control a JavaScript alert dialog. +class JsBeforeUnloadResponse { + ///Message to be displayed in the window. + String message; + + ///Title of the confirm button. + String confirmButtonTitle; + + ///Title of the cancel button. + String cancelButtonTitle; + + ///Whether the client will handle the alert dialog. + bool handledByClient; + + ///Action used to confirm that the user hit confirm button. + JsBeforeUnloadResponseAction action; + + JsBeforeUnloadResponse( + {this.message = "", + this.handledByClient = false, + this.confirmButtonTitle = "", + this.cancelButtonTitle = "", + this.action = JsBeforeUnloadResponseAction.CONFIRM}); + + Map toMap() { + return { + "message": message, + "confirmButtonTitle": confirmButtonTitle, + "cancelButtonTitle": cancelButtonTitle, + "handledByClient": handledByClient, + "action": action?.toValue() + }; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} + ///Class that represents the reason the resource was caught by Safe Browsing. class SafeBrowsingThreat { final int _value; @@ -2954,10 +3143,15 @@ class ShouldOverrideUrlLoadingRequest { } ///Class that represents the navigation request used by the [WebView.onCreateWindow] event. -class OnCreateWindowRequest { - ///Represents the url of the navigation request. +class CreateWindowRequest { + ///The URL of the request. + /// + ///**NOTE**: On Android, if the window has been created using JavaScript, this will be `null`. String url; + ///The window id. Used by [WebView] to create a new WebView. + int windowId; + ///Indicates if the new window should be a dialog, rather than a full-size window. Available only on Android. bool androidIsDialog; @@ -2967,18 +3161,24 @@ class OnCreateWindowRequest { ///The type of action triggering the navigation. Available only on iOS. IOSWKNavigationType iosWKNavigationType; - OnCreateWindowRequest( + ///Whether the request was made in order to fetch the main frame's document. Available only on iOS. + bool iosIsForMainFrame; + + CreateWindowRequest( {this.url, + this.windowId, this.androidIsDialog, this.androidIsUserGesture, - this.iosWKNavigationType}); + this.iosWKNavigationType, + this.iosIsForMainFrame}); Map toMap() { return { - "url": url, "androidIsDialog": androidIsDialog, "androidIsUserGesture": androidIsUserGesture, - "iosWKNavigationType": iosWKNavigationType?.toValue() + "iosWKNavigationType": iosWKNavigationType?.toValue(), + "iosUrl": url, + "windowId": windowId }; } @@ -4163,3 +4363,39 @@ class SslCertificateDName { return toMap().toString(); } } + +///Class used by [WebView.androidOnReceivedLoginRequest] event. +class LoginRequest { + ///The account realm used to look up accounts. + String realm; + + ///An optional account. If not `null`, the account should be checked against accounts on the device. + ///If it is a valid account, it should be used to log in the user. This value may be `null`. + String account; + + ///Authenticator specific arguments used to log in the user. + String args; + + LoginRequest({ + this.realm, + this.account, + this.args + }); + + Map toMap() { + return { + "realm": realm, + "account": account, + "args": args + }; + } + + Map toJson() { + return this.toMap(); + } + + @override + String toString() { + return toMap().toString(); + } +} \ No newline at end of file diff --git a/lib/src/webview.dart b/lib/src/webview.dart index 19228c1b..50ecd1b5 100644 --- a/lib/src/webview.dart +++ b/lib/src/webview.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'context_menu.dart'; import 'types.dart'; @@ -7,12 +9,16 @@ import 'headless_in_app_webview.dart'; ///Abstract class that represents a WebView. Used by [WebView] and [HeadlessInAppWebView]. abstract class WebView { + ///The window id of a [CreateWindowRequest.windowId]. + final int windowId; + ///Event fired when the [WebView] is created. final void Function(InAppWebViewController controller) onWebViewCreated; ///Event fired when the [WebView] starts to load an [url]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455621-webview final void Function(InAppWebViewController controller, String url) onLoadStart; @@ -20,12 +26,14 @@ abstract class WebView { ///Event fired when the [WebView] finishes loading an [url]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onPageFinished(android.webkit.WebView,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview final void Function(InAppWebViewController controller, String url) onLoadStop; ///Event fired when the [WebView] encounters an error loading an [url]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedError(android.webkit.WebView,%20int,%20java.lang.String,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455623-webview final void Function(InAppWebViewController controller, String url, int code, String message) onLoadError; @@ -41,6 +49,7 @@ abstract class WebView { ///**NOTE**: available on Android 23+. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpError(android.webkit.WebView,%20android.webkit.WebResourceRequest,%20android.webkit.WebResourceResponse) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview final void Function(InAppWebViewController controller, String url, int statusCode, String description) onLoadHttpError; @@ -71,6 +80,7 @@ abstract class WebView { ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useShouldOverrideUrlLoading] option to `true`. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455641-webview final Future Function( InAppWebViewController controller, @@ -91,6 +101,7 @@ abstract class WebView { ///[y] represents the current vertical scroll origin in pixels. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#onScrollChanged(int,%20int,%20int,%20int) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619392-scrollviewdidscroll final void Function(InAppWebViewController controller, int x, int y) onScrollChanged; @@ -103,6 +114,7 @@ abstract class WebView { ///**NOTE**: In order to be able to listen this event, you need to set [InAppWebViewOptions.useOnDownloadStart] option to `true`. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#setDownloadListener(android.webkit.DownloadListener) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455643-webview final void Function(InAppWebViewController controller, String url) onDownloadStart; @@ -120,53 +132,91 @@ abstract class WebView { ///Event fired when the [WebView] requests the host application to create a new window, ///for example when trying to open a link with `target="_blank"` or when `window.open()` is called by JavaScript side. + ///The return value should be a [WebView] instance or `null`. If it returns `null`, then nothing will happen. + ///If it returns an [InAppWebView] instance, when it will be added to the widget tree, it will load the request. + ///If it returns a [HeadlessInAppWebView] instance, the [HeadlessInAppWebView.run] method will be immediately called. + ///Remember to use the [CreateWindowRequest.windowId] to create the new WebView instance. /// - ///[onCreateWindowRequest] represents the request. + ///[createWindowRequest] represents the request. /// ///**NOTE**: on Android you need to set [AndroidInAppWebViewOptions.supportMultipleWindows] option to `true`. /// + ///**NOTE**: on iOS, setting these initial options: [InAppWebViewOptions.supportZoom], [InAppWebViewOptions.useOnLoadResource], [InAppWebViewOptions.useShouldInterceptAjaxRequest], + ///[InAppWebViewOptions.useShouldInterceptFetchRequest], [InAppWebViewOptions.applicationNameForUserAgent], [InAppWebViewOptions.javaScriptCanOpenWindowsAutomatically], + ///[InAppWebViewOptions.javaScriptEnabled], [InAppWebViewOptions.minimumFontSize], [InAppWebViewOptions.preferredContentMode], [InAppWebViewOptions.incognito], + ///[InAppWebViewOptions.cacheEnabled], [InAppWebViewOptions.mediaPlaybackRequiresUserGesture], + ///[InAppWebViewOptions.resourceCustomSchemes], [IOSInAppWebViewOptions.sharedCookiesEnabled], + ///[IOSInAppWebViewOptions.enableViewportScale], [IOSInAppWebViewOptions.allowsAirPlayForMediaPlayback], + ///[IOSInAppWebViewOptions.allowsPictureInPictureMediaPlayback], [IOSInAppWebViewOptions.isFraudulentWebsiteWarningEnabled], + ///[IOSInAppWebViewOptions.allowsInlineMediaPlayback], [IOSInAppWebViewOptions.suppressesIncrementalRendering], [IOSInAppWebViewOptions.selectionGranularity], + ///[IOSInAppWebViewOptions.ignoresViewportScaleLimits], + ///will have no effect due to a `WKWebView` limitation when creating a new window WebView: it's impossible to return a new `WKWebView` + ///with a different `WKWebViewConfiguration` instance (see https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview). + ///So, these options will be inherited from the caller WebView. + ///Also, note that calling [InAppWebViewController.setOptions] method using the controller of the new created WebView, + ///it will update also the WebView options of the caller WebView. + /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onCreateWindow(android.webkit.WebView,%20boolean,%20boolean,%20android.os.Message) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1536907-webview - final void Function(InAppWebViewController controller, - OnCreateWindowRequest onCreateWindowRequest) onCreateWindow; + final Future Function(InAppWebViewController controller, + CreateWindowRequest createWindowRequest) onCreateWindow; + + ///Event fired when the host application should close the given WebView and remove it from the view system if necessary. + ///At this point, WebCore has stopped any loading in this window and has removed any cross-scripting ability in javascript. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onCloseWindow(android.webkit.WebView) + /// + ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1537390-webviewdidclose + final void Function(InAppWebViewController controller) onCloseWindow; + + ///Event fired when the JavaScript `window` object of the WebView has received focus. + ///This is the result of the `focus` JavaScript event applied to the `window` object. + final void Function(InAppWebViewController controller) onWindowFocus; + + ///Event fired when the JavaScript `window` object of the WebView has lost focus. + ///This is the result of the `blur` JavaScript event applied to the `window` object. + final void Function(InAppWebViewController controller) onWindowBlur; ///Event fired 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. + ///[jsAlertRequest] contains the message to be displayed in the alert dialog and the of the page requesting the dialog. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onJsAlert(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1537406-webview final Future Function( - InAppWebViewController controller, String message) onJsAlert; + InAppWebViewController controller, JsAlertRequest jsAlertRequest) onJsAlert; ///Event fired 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. + ///[jsConfirmRequest] contains the message to be displayed in the confirm dialog and the of the page requesting the dialog. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onJsConfirm(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1536489-webview final Future Function( - InAppWebViewController controller, String message) onJsConfirm; + InAppWebViewController controller, JsConfirmRequest jsConfirmRequest) onJsConfirm; ///Event fired 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. + ///[jsPromptRequest] contains the message to be displayed in the prompt dialog, the default value displayed in the prompt dialog, and the of the page requesting the dialog. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onJsPrompt(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20android.webkit.JsPromptResult) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wkuidelegate/1538086-webview final Future Function(InAppWebViewController controller, - String message, String defaultValue) onJsPrompt; + JsPromptRequest jsPromptRequest) onJsPrompt; ///Event fired when the WebView received an HTTP authentication request. The default behavior is to cancel the request. /// ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [HttpAuthChallenge]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedHttpAuthRequest(android.webkit.WebView,%20android.webkit.HttpAuthHandler,%20java.lang.String,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview final Future Function( InAppWebViewController controller, HttpAuthChallenge challenge) @@ -178,6 +228,7 @@ abstract class WebView { ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ServerTrustChallenge]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedSslError(android.webkit.WebView,%20android.webkit.SslErrorHandler,%20android.net.http.SslError) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview final Future Function( InAppWebViewController controller, ServerTrustChallenge challenge) @@ -191,6 +242,7 @@ abstract class WebView { ///[challenge] contains data about host, port, protocol, realm, etc. as specified in the [ClientCertChallenge]. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedClientCertRequest(android.webkit.WebView,%20android.webkit.ClientCertRequest) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455638-webview final Future Function( InAppWebViewController controller, ClientCertChallenge challenge) @@ -291,6 +343,7 @@ abstract class WebView { ///[hitTestResult] represents the hit result for hitting an HTML elements. /// ///**Official Android API**: https://developer.android.com/reference/android/view/View#setOnLongClickListener(android.view.View.OnLongClickListener) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer final void Function(InAppWebViewController controller, InAppWebViewHitTestResult hitTestResult) onLongPressHitTestResult; @@ -298,12 +351,14 @@ abstract class WebView { ///Event fired when the current page has entered full screen mode. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onShowCustomView(android.view.View,%20android.webkit.WebChromeClient.CustomViewCallback) + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiwindow/1621621-didbecomevisiblenotification final void Function(InAppWebViewController controller) onEnterFullscreen; ///Event fired when the current page has exited full screen mode. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onHideCustomView() + /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiwindow/1621617-didbecomehiddennotification final void Function(InAppWebViewController controller) onExitFullscreen; @@ -315,10 +370,19 @@ abstract class WebView { ///[url] represents the URL corresponding to the page navigation that triggered this callback. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onPageCommitVisible(android.webkit.WebView,%20java.lang.String) + /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455635-webview - final Future Function(InAppWebViewController controller, String url) + final void Function(InAppWebViewController controller, String url) onPageCommitVisible; + ///Event fired when a change in the document title occurred. + /// + ///[title] represents the string containing the new title of the document. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedTitle(android.webkit.WebView,%20java.lang.String) + final void Function(InAppWebViewController controller, String title) + onTitleChanged; + ///Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing. ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible. /// @@ -435,7 +499,7 @@ abstract class WebView { ///**NOTE**: available only on Android 26+. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onRenderProcessGone(android.webkit.WebView,%20android.webkit.RenderProcessGoneDetail) - final Future Function( + final void Function( InAppWebViewController controller, RenderProcessGoneDetail detail) androidOnRenderProcessGone; @@ -456,16 +520,69 @@ abstract class WebView { ///**NOTE**: available only on Android. /// ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) - final Future Function( + final void Function( InAppWebViewController controller, double oldScale, double newScale) androidOnScaleChanged; + ///Event fired when there is a request to display and focus for this WebView. + ///This may happen due to another WebView opening a link in this WebView and requesting that this WebView be displayed. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onRequestFocus(android.webkit.WebView) + final void Function(InAppWebViewController controller) androidOnRequestFocus; + + ///Event fired when there is new favicon for the current page. + /// + ///[icon] represents the favicon for the current page. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedIcon(android.webkit.WebView,%20android.graphics.Bitmap) + final void Function(InAppWebViewController controller, Uint8List icon) androidOnReceivedIcon; + + ///Event fired when there is an url for an apple-touch-icon. + /// + ///[url] represents the icon url. + /// + ///[precomposed] is `true` if the url is for a precomposed touch icon. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onReceivedTouchIconUrl(android.webkit.WebView,%20java.lang.String,%20boolean) + final void Function(InAppWebViewController controller, String url, bool precomposed) androidOnReceivedTouchIconUrl; + + ///Event fired when the client should display a dialog to confirm navigation away from the current page. + ///This is the result of the `onbeforeunload` javascript event. + ///If [JsBeforeUnloadResponse.handledByClient] is `true`, WebView will assume that the client will handle the confirm dialog. + ///If [JsBeforeUnloadResponse.handledByClient] is `false`, a default value of `true` will be returned to javascript to accept navigation away from the current page. + ///The default behavior is to return `false`. + ///Setting the [JsBeforeUnloadResponse.action] to [JsBeforeUnloadResponseAction.CONFIRM] will navigate away from the current page, + ///[JsBeforeUnloadResponseAction.CANCEL] will cancel the navigation. + /// + ///[jsBeforeUnloadRequest] contains the message to be displayed in the alert dialog and the of the page requesting the dialog. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebChromeClient#onJsBeforeUnload(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20android.webkit.JsResult) + final Future Function( + InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest) androidOnJsBeforeUnload; + + ///Event fired when a request to automatically log in the user has been processed. + /// + ///[loginRequest] contains the realm, account and args of the login request. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onReceivedLoginRequest(android.webkit.WebView,%20java.lang.String,%20java.lang.String,%20java.lang.String) + final void Function(InAppWebViewController controller, LoginRequest loginRequest) androidOnReceivedLoginRequest; + ///Invoked when the web view's web content process is terminated. /// ///**NOTE**: available only on iOS. /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455639-webviewwebcontentprocessdidtermi - final Future Function(InAppWebViewController controller) + final void Function(InAppWebViewController controller) iosOnWebContentProcessDidTerminate; ///Called when a web view receives a server redirect. @@ -473,7 +590,7 @@ abstract class WebView { ///**NOTE**: available only on iOS. /// ///**Official iOS API**: https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455627-webview - final Future Function(InAppWebViewController controller) + final void Function(InAppWebViewController controller) iosOnDidReceiveServerRedirectForProvisionalNavigation; ///Initial url that will be loaded. @@ -495,7 +612,8 @@ abstract class WebView { final ContextMenu contextMenu; WebView( - {this.onWebViewCreated, + {this.windowId, + this.onWebViewCreated, this.onLoadStart, this.onLoadStop, this.onLoadError, @@ -508,6 +626,7 @@ abstract class WebView { this.onDownloadStart, this.onLoadResourceCustomScheme, this.onCreateWindow, + this.onCloseWindow, this.onJsAlert, this.onJsConfirm, this.onJsPrompt, @@ -525,6 +644,9 @@ abstract class WebView { this.onEnterFullscreen, this.onExitFullscreen, this.onPageCommitVisible, + this.onTitleChanged, + this.onWindowFocus, + this.onWindowBlur, this.androidOnSafeBrowsingHit, this.androidOnPermissionRequest, this.androidOnGeolocationPermissionsShowPrompt, @@ -535,6 +657,11 @@ abstract class WebView { this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, this.androidOnScaleChanged, + this.androidOnRequestFocus, + this.androidOnReceivedIcon, + this.androidOnReceivedTouchIconUrl, + this.androidOnJsBeforeUnload, + this.androidOnReceivedLoginRequest, this.iosOnWebContentProcessDidTerminate, this.iosOnDidReceiveServerRedirectForProvisionalNavigation, this.initialUrl, diff --git a/pubspec.yaml b/pubspec.yaml index e19364a7..c88a5141 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.4.0+2 +version: 4.0.0 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: