Commit fed99ec0 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

added debuggingEnabled option, fixed InputConnection error on Android

parent ab3b5c39
This diff is collapsed.
......@@ -13,17 +13,19 @@
- Added `onLoadResourceCustomScheme` event and `resourceCustomSchemes` option to set custom schemes that WebView must handle to load resources
- Added `onTargetBlank` event and `useOnTargetBlank` option to manage links with `target="_blank"`
- Added `ContentBlocker`, `ContentBlockerTrigger` and `ContentBlockerAction` classes and the `contentBlockers` option that allows to define a set of rules to use to block content in the WebView
- Added new WebView option `minimumFontSize`
- Added new WebView options: `minimumFontSize`, `debuggingEnabled`
- Added new Android WebView options: `allowContentAccess`, `allowFileAccess`, `allowFileAccessFromFileURLs`, `allowUniversalAccessFromFileURLs`, `appCacheEnabled`, `appCachePath`, `blockNetworkImage`, `blockNetworkLoads`, `cacheMode`, `cursiveFontFamily`, `defaultFixedFontSize`, `defaultFontSize`, `defaultTextEncodingName`, `disabledActionModeMenuItems`, `fantasyFontFamily`, `fixedFontFamily`, `forceDark`, `geolocationEnabled`, `layoutAlgorithm`, `loadWithOverviewMode`, `loadsImagesAutomatically`, `minimumLogicalFontSize`, `needInitialFocus`, `offscreenPreRaster`, `sansSerifFontFamily`, `serifFontFamily`, `standardFontFamily`
- Added new iOS WebView options: `applicationNameForUserAgent`, `isFraudulentWebsiteWarningEnabled`, `selectionGranularity`, `dataDetectorTypes`, `preferredContentMode`
- Added `onGeolocationPermissionsShowPrompt` event and `GeolocationPermissionShowPromptResponse` class (available only for Android)
- Added `startSafeBrowsing`, `setSafeBrowsingWhitelist` and `getSafeBrowsingPrivacyPolicyUrl` methods (available only for Android)
- Added `onSafeBrowsingHit` event (available only for Android)
- Added `onJsAlert`, `onJsConfirm` and `onJsPrompt` events to manage javascript popup dialogs
- Fixed `InputConnection` error on Android
### BREAKING CHANGES
- Deleted `WebResourceRequest` class
- Updated `WebResourceResponse` class
- Updated `ConsoleMessageLevel` class
- Updated `onLoadResource` event
- WebView options are now available with the new corresponding classes: `InAppWebViewOptions`, `AndroidInAppWebViewOptions`, `iOSInAppWebViewOptions`, `InAppBrowserOptions`, `AndroidInAppBrowserOptions`, `iOSInAppBrowserOptions`, `AndroidChromeCustomTabsOptions` and `iOSChromeCustomTabsOptions`
......
......@@ -3,21 +3,26 @@ package com.pichillilorenzo.flutter_inappbrowser;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.DisplayListenerProxy;
import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebView;
import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebViewOptions;
import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InputAwareWebView;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import static io.flutter.plugin.common.MethodChannel.MethodCallHandler;
......@@ -31,14 +36,19 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
public final Activity activity;
public InAppWebView webView;
public MethodChannel channel;
public final MethodChannel channel;
public final Registrar registrar;
public FlutterWebView(Registrar registrar, int id, HashMap<String, Object> params) {
public FlutterWebView(Registrar registrar, int id, HashMap<String, Object> params, View containerView) {
this.registrar = registrar;
this.activity = registrar.activity();
DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy();
DisplayManager displayManager =
(DisplayManager) this.registrar.context().getSystemService(Context.DISPLAY_SERVICE);
displayListenerProxy.onPreWebViewInitialization(displayManager);
String initialUrl = (String) params.get("initialUrl");
String initialFile = (String) params.get("initialFile");
Map<String, String> initialData = (Map<String, String>) params.get("initialData");
......@@ -48,7 +58,9 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
InAppWebViewOptions options = new InAppWebViewOptions();
options.parse(initialOptions);
webView = new InAppWebView(registrar, this, id, options);
webView = new InAppWebView(registrar, this, id, options, containerView);
displayListenerProxy.onPostWebViewInitialization(displayManager);
webView.prepare();
channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappwebview_" + id);
......@@ -257,9 +269,15 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
}
@Override
public void onInputConnectionLocked() {}
public void onInputConnectionLocked() {
if (webView.inAppBrowserActivity == null)
webView.lockInputConnection();
}
@Override
public void onInputConnectionUnlocked() {}
public void onInputConnectionUnlocked() {
if (webView.inAppBrowserActivity == null)
webView.unlockInputConnection();
}
}
\ No newline at end of file
package com.pichillilorenzo.flutter_inappbrowser;
import android.content.Context;
import android.view.View;
import java.util.HashMap;
......@@ -11,16 +12,18 @@ import io.flutter.plugin.platform.PlatformViewFactory;
public class FlutterWebViewFactory extends PlatformViewFactory {
private final Registrar registrar;
private final View containerView;
public FlutterWebViewFactory(Registrar registrar) {
public FlutterWebViewFactory(Registrar registrar, View containerView) {
super(StandardMessageCodec.INSTANCE);
this.registrar = registrar;
this.containerView = containerView;
}
@Override
public PlatformView create(Context context, int id, Object args) {
HashMap<String, Object> params = (HashMap<String, Object>) args;
return new FlutterWebView(registrar, id, params);
return new FlutterWebView(registrar, id, params, containerView);
}
}
......@@ -86,7 +86,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
registrar
.platformViewRegistry()
.registerViewFactory(
"com.pichillilorenzo/flutter_inappwebview", new FlutterWebViewFactory(registrar));
"com.pichillilorenzo/flutter_inappwebview", new FlutterWebViewFactory(registrar, registrar.view()));
}
}
......
package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import static android.hardware.display.DisplayManager.DisplayListener;
import android.annotation.TargetApi;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.ArrayList;
/**
* Works around an Android WebView bug by filtering some DisplayListener invocations.
* https://github.com/flutter/plugins/blob/master/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public
class DisplayListenerProxy {
private static final String TAG = "DisplayListenerProxy";
private ArrayList<DisplayListener> listenersBeforeWebView;
/** Should be called prior to the webview's initialization. */
public void onPreWebViewInitialization(DisplayManager displayManager) {
listenersBeforeWebView = yoinkDisplayListeners(displayManager);
}
/** Should be called after the webview's initialization. */
public void onPostWebViewInitialization(final DisplayManager displayManager) {
final ArrayList<DisplayListener> webViewListeners = yoinkDisplayListeners(displayManager);
// We recorded the list of listeners prior to initializing webview, any new listeners we see
// after initializing the webview are listeners added by the webview.
webViewListeners.removeAll(listenersBeforeWebView);
if (webViewListeners.isEmpty()) {
// The Android WebView registers a single display listener per process (even if there
// are multiple WebView instances) so this list is expected to be non-empty only the
// first time a webview is initialized.
// Note that in an add2app scenario if the application had instantiated a non Flutter
// WebView prior to instantiating the Flutter WebView we are not able to get a reference
// to the WebView's display listener and can't work around the bug.
//
// This means that webview resizes in add2app Flutter apps with a non Flutter WebView
// running on a system with a webview prior to 58.0.3029.125 may crash (the Android's
// behavior seems to be racy so it doesn't always happen).
return;
}
for (DisplayListener webViewListener : webViewListeners) {
// Note that while DisplayManager.unregisterDisplayListener throws when given an
// unregistered listener, this isn't an issue as the WebView code never calls
// unregisterDisplayListener.
displayManager.unregisterDisplayListener(webViewListener);
// We never explicitly unregister this listener as the webview's listener is never
// unregistered (it's released when the process is terminated).
displayManager.registerDisplayListener(
new DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
for (DisplayListener webViewListener : webViewListeners) {
webViewListener.onDisplayAdded(displayId);
}
}
@Override
public void onDisplayRemoved(int displayId) {
for (DisplayListener webViewListener : webViewListeners) {
webViewListener.onDisplayRemoved(displayId);
}
}
@Override
public void onDisplayChanged(int displayId) {
if (displayManager.getDisplay(displayId) == null) {
return;
}
for (DisplayListener webViewListener : webViewListeners) {
webViewListener.onDisplayChanged(displayId);
}
}
},
null);
}
}
@SuppressWarnings({"unchecked", "PrivateApi"})
private static ArrayList<DisplayListener> yoinkDisplayListeners(DisplayManager displayManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// We cannot use reflection on Android P, but it shouldn't matter as it shipped
// with WebView 66.0.3359.158 and the WebView version the bug this code is working around was
// fixed in 61.0.3116.0.
return new ArrayList<>();
}
try {
Field displayManagerGlobalField = DisplayManager.class.getDeclaredField("mGlobal");
displayManagerGlobalField.setAccessible(true);
Object displayManagerGlobal = displayManagerGlobalField.get(displayManager);
Field displayListenersField =
displayManagerGlobal.getClass().getDeclaredField("mDisplayListeners");
displayListenersField.setAccessible(true);
ArrayList<Object> delegates =
(ArrayList<Object>) displayListenersField.get(displayManagerGlobal);
Field listenerField = null;
ArrayList<DisplayManager.DisplayListener> listeners = new ArrayList<>();
for (Object delegate : delegates) {
if (listenerField == null) {
listenerField = delegate.getClass().getField("mListener");
listenerField.setAccessible(true);
}
DisplayManager.DisplayListener listener =
(DisplayManager.DisplayListener) listenerField.get(delegate);
listeners.add(listener);
}
return listeners;
} catch (NoSuchFieldException | IllegalAccessException e) {
Log.w(TAG, "Could not extract WebView's display listeners. " + e);
return new ArrayList<>();
}
}
}
\ No newline at end of file
......@@ -10,9 +10,9 @@ import android.util.AttributeSet;
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.Log;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
......@@ -41,7 +41,7 @@ import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import okhttp3.OkHttpClient;
public class InAppWebView extends WebView {
final public class InAppWebView extends InputAwareWebView {
static final String LOG_TAG = "InAppWebView";
......@@ -107,8 +107,8 @@ public class InAppWebView extends WebView {
super(context, attrs, defaultStyle);
}
public InAppWebView(PluginRegistry.Registrar registrar, Object obj, int id, InAppWebViewOptions options) {
super(registrar.activeContext());
public InAppWebView(PluginRegistry.Registrar registrar, Object obj, int id, InAppWebViewOptions options, View containerView) {
super(registrar.activeContext(), containerView);
this.registrar = registrar;
if (obj instanceof InAppBrowserActivity)
this.inAppBrowserActivity = (InAppBrowserActivity) obj;
......@@ -146,6 +146,9 @@ public class InAppWebView extends WebView {
WebSettings settings = getSettings();
settings.setJavaScriptEnabled(options.javaScriptEnabled);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
setWebContentsDebuggingEnabled(options.debuggingEnabled);
}
settings.setJavaScriptCanOpenWindowsAutomatically(options.javaScriptCanOpenWindowsAutomatically);
settings.setBuiltInZoomControls(options.builtInZoomControls);
settings.setDisplayZoomControls(options.displayZoomControls);
......
......@@ -17,6 +17,7 @@ import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebViewDatabase;
import androidx.annotation.RequiresApi;
......@@ -296,9 +297,56 @@ public class InAppWebViewClient extends WebViewClient {
* On received http auth request.
*/
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
// By default handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, final String realm) {
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("host", host);
obj.put("realm", realm);
getChannel().invokeMethod("onReceivedHttpAuthRequest", obj, new MethodChannel.Result() {
@Override
public void success(Object response) {
if (response != null) {
Map<String, Object> responseMap = (Map<String, Object>) response;
Integer action = (Integer) responseMap.get("action");
Log.d(LOG_TAG, "\n\naction: " + action);
if (action != null) {
switch (action) {
case 0:
handler.cancel();
return;
case 1:
String username = (String) responseMap.get("username");
String password = (String) responseMap.get("password");
Boolean permanentPersistence = (Boolean) responseMap.get("permanentPersistence");
if (permanentPersistence != null && permanentPersistence && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WebViewDatabase.getInstance(view.getContext()).setHttpAuthUsernamePassword(host, realm, username, password);
}
handler.proceed(username, password);
return;
case 2:
handler.useHttpAuthUsernamePassword();
return;
}
}
}
handler.cancel();
}
@Override
public void error(String s, String s1, Object o) {
Log.e(LOG_TAG, s + ", " + s1);
}
@Override
public void notImplemented() {
handler.cancel();
}
});
}
@Override
......@@ -324,9 +372,6 @@ public class InAppWebViewClient extends WebViewClient {
Boolean report = (Boolean) responseMap.get("report");
Integer action = (Integer) responseMap.get("action");
Log.d(LOG_TAG, "\n\nreport: " + report);
Log.d(LOG_TAG, "\n\naction: " + action);
report = report != null ? report : true;
if (action != null) {
......
......@@ -19,6 +19,7 @@ public class InAppWebViewOptions extends Options {
public boolean clearCache = false;
public String userAgent = "";
public boolean javaScriptEnabled = true;
public boolean debuggingEnabled = false;
public boolean javaScriptCanOpenWindowsAutomatically = false;
public boolean mediaPlaybackRequiresUserGesture = true;
public Integer textZoom = 100;
......
package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import static android.content.Context.INPUT_METHOD_SERVICE;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView;
/**
* A WebView subclass that mirrors the same implementation hacks that the system WebView does in
* order to correctly create an InputConnection.
*
* https://github.com/flutter/plugins/blob/master/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java
*/
public class InputAwareWebView extends WebView {
public View containerView;
private View threadedInputConnectionProxyView;
private ThreadedInputConnectionProxyAdapterView proxyAdapterView;
public InputAwareWebView(Context context, View containerView) {
super(context);
this.containerView = containerView;
}
public InputAwareWebView(Context context, AttributeSet attrs) {
super(context, attrs);
this.containerView = null;
}
public InputAwareWebView(Context context) {
super(context);
this.containerView = null;
}
public InputAwareWebView(Context context, AttributeSet attrs, int defaultStyle) {
super(context, attrs, defaultStyle);
this.containerView = null;
}
/**
* Set our proxy adapter view to use its cached input connection instead of creating new ones.
*
* <p>This is used to avoid losing our input connection when the virtual display is resized.
*/
public void lockInputConnection() {
if (proxyAdapterView == null) {
return;
}
proxyAdapterView.setLocked(true);
}
/** Sets the proxy adapter view back to its default behavior. */
public void unlockInputConnection() {
if (proxyAdapterView == null) {
return;
}
proxyAdapterView.setLocked(false);
}
/** Restore the original InputConnection, if needed. */
void dispose() {
resetInputConnection();
}
/**
* Creates an InputConnection from the IME thread when needed.
*
* <p>We only need to create a {@link ThreadedInputConnectionProxyAdapterView} and create an
* InputConnectionProxy on the IME thread when WebView is doing the same thing. So we rely on the
* system calling this method for WebView's proxy view in order to know when we need to create our
* own.
*
* <p>This method would normally be called for any View that used the InputMethodManager. We rely
* on flutter/engine filtering the calls we receive down to the ones in our hierarchy and the
* system WebView in order to know whether or not the system WebView expects an InputConnection on
* the IME thread.
*/
@Override
public boolean checkInputConnectionProxy(final View view) {
if (containerView == null)
return super.checkInputConnectionProxy(view);
// Check to see if the view param is WebView's ThreadedInputConnectionProxyView.
View previousProxy = threadedInputConnectionProxyView;
threadedInputConnectionProxyView = view;
if (previousProxy == view) {
// This isn't a new ThreadedInputConnectionProxyView. Ignore it.
return super.checkInputConnectionProxy(view);
}
// We've never seen this before, so we make the assumption that this is WebView's
// ThreadedInputConnectionProxyView. We are making the assumption that the only view that could
// possibly be interacting with the IMM here is WebView's ThreadedInputConnectionProxyView.
proxyAdapterView =
new ThreadedInputConnectionProxyAdapterView(
/*containerView=*/ containerView,
/*targetView=*/ view,
/*imeHandler=*/ view.getHandler());
setInputConnectionTarget(/*targetView=*/ proxyAdapterView);
return super.checkInputConnectionProxy(view);
}
/**
* Ensure that input creation happens back on {@link #containerView}'s thread once this view no
* longer has focus.
*
* <p>The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's
* thread for all connections. We undo it here so users will be able to go back to typing in
* Flutter UIs as expected.
*/
@Override
public void clearFocus() {
super.clearFocus();
if (containerView != null)
resetInputConnection();
}
/**
* Ensure that input creation happens back on {@link #containerView}.
*
* <p>The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's
* thread for all connections. We undo it here so users will be able to go back to typing in
* Flutter UIs as expected.
*/
private void resetInputConnection() {
if (proxyAdapterView == null) {
// No need to reset the InputConnection to the default thread if we've never changed it.
return;
}
setInputConnectionTarget(/*targetView=*/ containerView);
}
/**
* This is the crucial trick that gets the InputConnection creation to happen on the correct
* thread pre Android N.
* https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java?l=169&rcl=f0698ee3e4483fad5b0c34159276f71cfaf81f3a
*
* <p>{@code targetView} should have a {@link View#getHandler} method with the thread that future
* InputConnections should be created on.
*/
private void setInputConnectionTarget(final View targetView) {
targetView.requestFocus();
containerView.post(
new Runnable() {
@Override
public void run() {
InputMethodManager imm =
(InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE);
// This is a hack to make InputMethodManager believe that the target view now has focus.
// As a result, InputMethodManager will think that targetView is focused, and will call
// getHandler() of the view when creating input connection.
// Step 1: Set targetView as InputMethodManager#mNextServedView. This does not affect
// the real window focus.
targetView.onWindowFocusChanged(true);
// Step 2: Have InputMethodManager focus in on targetView. As a result, IMM will call
// onCreateInputConnection() on targetView on the same thread as
// targetView.getHandler(). It will also call subsequent InputConnection methods on this
// thread. This is the IME thread in cases where targetView is our proxyAdapterView.
imm.isActive(containerView);
}
});
}
}
\ No newline at end of file
package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import android.os.Handler;
import android.os.IBinder;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
/**
* A fake View only exposed to InputMethodManager.
*
* https://github.com/flutter/plugins/blob/master/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/ThreadedInputConnectionProxyAdapterView.java
*/
final class ThreadedInputConnectionProxyAdapterView extends View {
final Handler imeHandler;
final IBinder windowToken;
final View containerView;
final View rootView;
final View targetView;
private boolean triggerDelayed = true;
private boolean isLocked = false;
private InputConnection cachedConnection;
ThreadedInputConnectionProxyAdapterView(View containerView, View targetView, Handler imeHandler) {
super(containerView.getContext());
this.imeHandler = imeHandler;
this.containerView = containerView;
this.targetView = targetView;
windowToken = containerView.getWindowToken();
rootView = containerView.getRootView();
setFocusable(true);
setFocusableInTouchMode(true);
setVisibility(VISIBLE);
}
/** Returns whether or not this is currently asynchronously acquiring an input connection. */
boolean isTriggerDelayed() {
return triggerDelayed;
}
/** Sets whether or not this should use its previously cached input connection. */
void setLocked(boolean locked) {
isLocked = locked;
}
/**
* This is expected to be called on the IME thread. See the setup required for this in {@link
* InputAwareWebView#checkInputConnectionProxy(View)}.
*
* <p>Delegates to ThreadedInputConnectionProxyView to get WebView's input connection.
*/
@Override
public InputConnection onCreateInputConnection(final EditorInfo outAttrs) {
triggerDelayed = false;
InputConnection inputConnection =
(isLocked) ? cachedConnection : targetView.onCreateInputConnection(outAttrs);
triggerDelayed = true;
cachedConnection = inputConnection;
return inputConnection;
}
@Override
public boolean checkInputConnectionProxy(View view) {
return true;
}
@Override
public boolean hasWindowFocus() {
// None of our views here correctly report they have window focus because of how we're embedding
// the platform view inside of a virtual display.
return true;
}
@Override
public View getRootView() {
return rootView;
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override
public boolean isFocused() {
return true;
}
@Override
public IBinder getWindowToken() {
return windowToken;
}
@Override
public Handler getHandler() {
return imeHandler;
}
}
\ No newline at end of file
......@@ -70,6 +70,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
initialHeaders: {},
initialOptions: [
InAppWebViewOptions(
clearCache: true,
useShouldOverrideUrlLoading: true,
useOnTargetBlank: true,
//useOnLoadResource: true,
......
......@@ -202,7 +202,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
scrollView.showsVerticalScrollIndicator = (options?.verticalScrollBarEnabled)!
scrollView.showsHorizontalScrollIndicator = (options?.horizontalScrollBarEnabled)!
// options.debuggingEnabled is always enabled for iOS.
if (options?.clearCache)! {
clearCache()
}
......@@ -745,6 +748,54 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let host = challenge.protectionSpace.host
let realm = challenge.protectionSpace.realm
onReceivedHttpAuthRequest(host: host, realm: realm, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
completionHandler(.performDefaultHandling, nil)
}
else {
//WKWebsiteDataStore.default()
//URLCredentialStorage()
var response: [String: Any]
if let r = result {
response = r as! [String: Any]
var action = response["action"] as? Int
action = action != nil ? action : 0;
switch action {
case 0:
completionHandler(.cancelAuthenticationChallenge, nil)
break
case 1:
let username = response["username"] as! String
let password = response["password"] as! String
let permanentPersistence = response["permanentPersistence"] as? Bool ?? false
let persistence = (permanentPersistence) ? URLCredential.Persistence.permanent : URLCredential.Persistence.forSession
let credential = URLCredential(user: username, password: password, persistence: persistence)
completionHandler(.useCredential, credential)
break
case 2:
if let credential = challenge.proposedCredential {
completionHandler(.useCredential, credential)
}
else {
completionHandler(.performDefaultHandling, nil)
}
break
default:
completionHandler(.performDefaultHandling, nil)
}
return;
}
completionHandler(.performDefaultHandling, nil)
}
})
}
fileprivate func createAlertDialog(message: String?, responseMessage: String?, confirmButtonTitle: String?, completionHandler: @escaping () -> Void) {
let title = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
......@@ -765,7 +816,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsAlert(message: message, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
completionHandler()
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createAlertDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, completionHandler: completionHandler)
......@@ -824,7 +874,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsConfirm(message: message, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
completionHandler(false)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createConfirmDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, completionHandler: completionHandler)
......@@ -898,7 +947,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
onJsPrompt(message: message, defaultValue: defaultValue, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
completionHandler(nil)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, value: nil, completionHandler: completionHandler)
......@@ -1053,6 +1101,16 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
public func onReceivedHttpAuthRequest(host: String, realm: String?, result: FlutterResult?) {
var arguments: [String: Any] = ["host": host, "realm": realm as Any]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onReceivedHttpAuthRequest", arguments: arguments, result: result)
}
}
public func onJsAlert(message: String, result: FlutterResult?) {
var arguments: [String: Any] = ["message": message]
if IABController != nil {
......
......@@ -18,6 +18,7 @@ public class InAppWebViewOptions: Options {
var clearCache = false
var userAgent = ""
var javaScriptEnabled = true
var debuggingEnabled = true
var javaScriptCanOpenWindowsAutomatically = false
var mediaPlaybackRequiresUserGesture = true
var verticalScrollBarEnabled = true
......@@ -25,7 +26,7 @@ public class InAppWebViewOptions: Options {
var resourceCustomSchemes: [String] = []
var contentBlockers: [[String: [String : Any]]] = []
var minimumFontSize = 0;
var disallowOverScroll = false
var enableViewportScale = false
//var keyboardDisplayRequiresUserAction = true
......
......@@ -406,6 +406,15 @@ class InAppBrowser {
}
///Event fires when a WebView received an HTTP authentication request. The default behavior is to cancel the request.
///
///[host] represents the host requiring authentication.
///
///[realm] represents the realm for which authentication is required
Future<HttpAuthResponse> onReceivedHttpAuthRequest(String url, String realm) {
}
void throwIsAlreadyOpened({String message = ''}) {
if (this.isOpened()) {
throw Exception(['Error: ${ (message.isEmpty) ? '' : message + ' '}The browser is already opened.']);
......
......@@ -176,6 +176,13 @@ class InAppWebView extends StatefulWidget {
///**NOTE**: available only for Android.
final onSafeBrowsingHitCallback onSafeBrowsingHit;
///Event fires when a WebView received an HTTP authentication request. The default behavior is to cancel the request.
///
///[host] represents the host requiring authentication.
///
///[realm] represents the realm for which authentication is required
final onReceivedHttpAuthRequestCallback onReceivedHttpAuthRequest;
///Initial url that will be loaded.
final String initialUrl;
///Initial asset file that will be loaded. See [InAppWebView.loadFile()] for explanation.
......@@ -219,6 +226,7 @@ class InAppWebView extends StatefulWidget {
this.onJsConfirm,
this.onJsPrompt,
this.onSafeBrowsingHit,
this.onReceivedHttpAuthRequest,
this.gestureRecognizers,
}) : super(key: key);
......@@ -468,6 +476,14 @@ class InAppWebViewController {
else if (_inAppBrowser != null)
return (await _inAppBrowser.onSafeBrowsingHit(url, threatType))?.toMap();
break;
case "onReceivedHttpAuthRequest":
String host = call.arguments["host"];
String realm = call.arguments["realm"];
if (_widget != null && _widget.onReceivedHttpAuthRequest != null)
return (await _widget.onReceivedHttpAuthRequest(this, host, realm))?.toMap();
else if (_inAppBrowser != null)
return (await _inAppBrowser.onReceivedHttpAuthRequest(host, realm))?.toMap();
break;
case "onCallJsHandler":
String handlerName = call.arguments["handlerName"];
// decode args to json
......
......@@ -270,6 +270,34 @@ class SafeBrowsingResponse {
}
}
class HttpAuthResponseAction {
final int _value;
const HttpAuthResponseAction._internal(this._value);
toValue() => _value;
static const CANCEL = const HttpAuthResponseAction._internal(0);
static const PROCEED = const HttpAuthResponseAction._internal(1);
static const USE_HTTP_AUTH_USERNAME_PASSWORD = const HttpAuthResponseAction._internal(2);
}
class HttpAuthResponse {
String username;
String password;
bool permanentPersistence;
HttpAuthResponseAction action;
HttpAuthResponse({this.username = "", this.password = "", this.permanentPersistence = false, this.action = HttpAuthResponseAction.CANCEL});
Map<String, dynamic> toMap() {
return {
"username": username,
"password": password,
"permanentPersistence": permanentPersistence,
"action": action?.toValue()
};
}
}
typedef onWebViewCreatedCallback = void Function(InAppWebViewController controller);
typedef onWebViewLoadStartCallback = void Function(InAppWebViewController controller, String url);
typedef onWebViewLoadStopCallback = void Function(InAppWebViewController controller, String url);
......@@ -286,4 +314,5 @@ typedef onGeolocationPermissionsShowPromptCallback = Future<GeolocationPermissio
typedef onJsAlertCallback = Future<JsAlertResponse> Function(InAppWebViewController controller, String message);
typedef onJsConfirmCallback = Future<JsConfirmResponse> Function(InAppWebViewController controller, String message);
typedef onJsPromptCallback = Future<JsPromptResponse> Function(InAppWebViewController controller, String message, String defaultValue);
typedef onSafeBrowsingHitCallback = Future<SafeBrowsingResponse> Function(InAppWebViewController controller, String url, SafeBrowsingThreat threatType);
\ No newline at end of file
typedef onSafeBrowsingHitCallback = Future<SafeBrowsingResponse> Function(InAppWebViewController controller, String url, SafeBrowsingThreat threatType);
typedef onReceivedHttpAuthRequestCallback = Future<HttpAuthResponse> Function(InAppWebViewController controller, String url, String realm);
\ No newline at end of file
......@@ -22,6 +22,7 @@ class InAppWebViewOptions implements WebViewOptions, BrowserOptions {
bool clearCache;
String userAgent;
bool javaScriptEnabled;
bool debuggingEnabled;
bool javaScriptCanOpenWindowsAutomatically;
bool mediaPlaybackRequiresUserGesture;
int textZoom;
......@@ -32,7 +33,7 @@ class InAppWebViewOptions implements WebViewOptions, BrowserOptions {
List<ContentBlocker> contentBlockers;
InAppWebViewOptions({this.useShouldOverrideUrlLoading = false, this.useOnLoadResource = false, this.useOnDownloadStart = false, this.useOnTargetBlank = false,
this.clearCache = false, this.userAgent = "", this.javaScriptEnabled = true, this.javaScriptCanOpenWindowsAutomatically = false,
this.clearCache = false, this.userAgent = "", this.javaScriptEnabled = true, this.debuggingEnabled = false, this.javaScriptCanOpenWindowsAutomatically = false,
this.mediaPlaybackRequiresUserGesture = true, this.textZoom = 100, this.minimumFontSize, this.verticalScrollBarEnabled = true, this.horizontalScrollBarEnabled = true,
this.resourceCustomSchemes = const [], this.contentBlockers = const []}) {
if (this.minimumFontSize == null)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment