Updated Android context menu workaround, updated iOS onCreateContextMenu event, Added Android keyboard workaround to hide the keyboard when clicking other HTML elements

This commit is contained in:
Lorenzo Pichilli 2020-05-21 23:02:08 +02:00
parent 5943059b1d
commit f569e369f4
13 changed files with 134 additions and 38 deletions

View File

@ -1,3 +1,9 @@
## 3.3.0
- Updated Android context menu workaround
- Calling `onCreateContextMenu` event on iOS also when the context menu is disabled in order to have the same effect as Android
- Added Android keyboard workaround to hide the keyboard when clicking other HTML elements, losing the focus on the previous input
## 3.2.0
- Added `ContextMenu` and `ContextMenuItem` classes [#235](https://github.com/pichillilorenzo/flutter_inappwebview/issues/235)

View File

@ -1,22 +1,17 @@
package com.pichillilorenzo.flutter_inappwebview.InAppWebView;
import android.app.Activity;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.DisplayListenerProxy;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView;
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.Util;
@ -29,7 +24,6 @@ import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.platform.PlatformView;
import static io.flutter.plugin.common.MethodChannel.MethodCallHandler;

View File

@ -76,18 +76,17 @@ final public class InAppWebView extends InputAwareWebView {
int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB
public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler();
public Pattern regexToCancelSubFramesLoadingCompiled;
private GestureDetector gestureDetector = null;
private MotionEvent motionEvent = null;
private LinearLayout floatingContextMenu = null;
public GestureDetector gestureDetector = null;
public LinearLayout floatingContextMenu = null;
public HashMap<String, Object> contextMenu = null;
public Handler headlessHandler = new Handler(Looper.getMainLooper());
private Runnable checkScrollStoppedTask;
private int initialPositionScrollStoppedTask;
private int newCheckScrollStoppedTask = 100;
public Runnable checkScrollStoppedTask;
public int initialPositionScrollStoppedTask;
public int newCheckScrollStoppedTask = 100; // ms
private Runnable selectedTextTask;
private int newCheckSelectedTextTask = 100;
public Runnable checkContextMenuShouldBeClosedTask;
public int newCheckContextMenuShouldBeClosedTaskTask = 100; // ms
static final String consoleLogJS = "(function(console) {" +
" var oldLogs = {" +
@ -117,7 +116,7 @@ final public class InAppWebView extends InputAwareWebView {
static final String printJS = "window.print = function() {" +
" window." + JavaScriptBridgeInterface.name + ".callHandler('onPrint', window.location.href);" +
"}";
"};";
static final String platformReadyJS = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));";
@ -531,6 +530,14 @@ final public class InAppWebView extends InputAwareWebView {
" };" +
"})(window.fetch);";
static final String isActiveElementInputEditableJS =
"var activeEl = document.activeElement;" +
"var nodeName = (activeEl != null) ? activeEl.nodeName.toLowerCase() : '';" +
"var isActiveElementInputEditable = activeEl != null && " +
"(activeEl.nodeType == 1 && (nodeName == 'textarea' || (nodeName == 'input' && /^(?:text|email|number|search|tel|url|password)$/i.test(activeEl.type != null ? activeEl.type : 'text')))) && " +
"!activeEl.disabled && !activeEl.readOnly;" +
"var isActiveElementEditable = isActiveElementInputEditable || (activeEl != null && activeEl.isContentEditable) || document.designMode === 'on';";
static final String getSelectedTextJS = "(function(){" +
" var txt;" +
" if (window.getSelection) {" +
@ -543,6 +550,48 @@ final public class InAppWebView extends InputAwareWebView {
" return txt;" +
"})();";
// android Workaround to hide context menu when selected text is empty
// and the document active element is not an input element.
static final String checkContextMenuShouldBeHiddenJS = "(function(){" +
" var txt;" +
" if (window.getSelection) {" +
" txt = window.getSelection().toString();" +
" } else if (window.document.getSelection) {" +
" txt = window.document.getSelection().toString();" +
" } else if (window.document.selection) {" +
" txt = window.document.selection.createRange().text;" +
" }" +
isActiveElementInputEditableJS +
" return txt === '' && !isActiveElementEditable;" +
"})();";
// android Workaround to hide context menu when user emit a keydown event
static final String checkGlobalKeyDownEventToHideContextMenuJS = "(function(){" +
" document.addEventListener('keydown', function(e) {" +
" window." + JavaScriptBridgeInterface.name + "._hideContextMenu();" +
" });" +
"})();";
// android Workaround to hide the Keyboard when the user click outside
// on something not focusable such as input or a textarea.
static final String androidKeyboardWorkaroundFocusoutEventJS = "(function(){" +
" var isFocusin = false;" +
" document.addEventListener('focusin', function(e) {" +
" var nodeName = e.target.nodeName.toLowerCase();" +
" var isInputButton = nodeName === 'input' && e.target.type != null && e.target.type === 'button';" +
" isFocusin = (['a', 'area', 'button', 'details', 'iframe', 'select', 'summary'].indexOf(nodeName) >= 0 || isInputButton) ? false : true;" +
" });" +
" document.addEventListener('focusout', function(e) {" +
" isFocusin = false;" +
" setTimeout(function() {" +
isActiveElementInputEditableJS +
" if (!isFocusin && !isActiveElementEditable) {" +
" window." + JavaScriptBridgeInterface.name + ".callHandler('androidKeyboardWorkaroundFocusoutEvent');" +
" }" +
" }, 300);" +
" });" +
"})();";
public InAppWebView(Context context) {
super(context);
}
@ -746,19 +795,19 @@ final public class InAppWebView extends InputAwareWebView {
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
selectedTextTask = new Runnable() {
checkContextMenuShouldBeClosedTask = new Runnable() {
@Override
public void run() {
if (floatingContextMenu != null) {
getSelectedText(new ValueCallback<String>() {
evaluateJavascript(checkContextMenuShouldBeHiddenJS, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
if (value == null || value.length() == 0) {
if (value == null || value.equals("true")) {
if (floatingContextMenu != null) {
hideContextMenu();
}
} else {
headlessHandler.postDelayed(selectedTextTask, newCheckSelectedTextTask);
headlessHandler.postDelayed(checkContextMenuShouldBeClosedTask, newCheckContextMenuShouldBeClosedTaskTask);
}
}
});
@ -1424,14 +1473,18 @@ final public class InAppWebView extends InputAwareWebView {
PrintManager printManager = (PrintManager) Shared.activity.getApplicationContext()
.getSystemService(Context.PRINT_SERVICE);
String jobName = getTitle() + " Document";
if (printManager != null) {
String jobName = getTitle() + " Document";
// Get a printCurrentPage adapter instance
PrintDocumentAdapter printAdapter = createPrintDocumentAdapter(jobName);
// Get a printCurrentPage adapter instance
PrintDocumentAdapter printAdapter = createPrintDocumentAdapter(jobName);
// Create a printCurrentPage job with name and adapter instance
printManager.print(jobName, printAdapter,
new PrintAttributes.Builder().build());
// Create a printCurrentPage job with name and adapter instance
printManager.print(jobName, printAdapter,
new PrintAttributes.Builder().build());
} else {
Log.e(LOG_TAG, "No PrintManager available");
}
}
public Float getUpdatedScale() {
@ -1444,6 +1497,12 @@ final public class InAppWebView extends InputAwareWebView {
sendOnCreateContextMenuEvent();
}
@Override
public boolean onCheckIsTextEditor() {
Log.d(LOG_TAG, "onCheckIsTextEditor");
return super.onCheckIsTextEditor();
}
private void sendOnCreateContextMenuEvent() {
HitTestResult hitTestResult = getHitTestResult();
Map<String, Object> hitTestResultMap = new HashMap<>();
@ -1545,7 +1604,7 @@ final public class InAppWebView extends InputAwareWebView {
text.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
clearFocus();
// clearFocus();
hideContextMenu();
Map<String, Object> obj = new HashMap<>();
@ -1588,8 +1647,8 @@ final public class InAppWebView extends InputAwareWebView {
if (hasBeenRemovedAndRebuilt) {
sendOnCreateContextMenuEvent();
}
if (selectedTextTask != null) {
selectedTextTask.run();
if (checkContextMenuShouldBeClosedTask != null) {
checkContextMenuShouldBeClosedTask.run();
}
}
actionMenu.clear();

View File

@ -179,6 +179,10 @@ public class InAppWebViewClient extends WebViewClient {
if (webView.options.useOnLoadResource) {
js += InAppWebView.resourceObserverJS.replaceAll("[\r\n]+", "");
}
js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS.replaceAll("[\r\n]+", "");
if (flutterWebView != null) {
js += InAppWebView.androidKeyboardWorkaroundFocusoutEventJS.replaceAll("[\r\n]+", "");
}
js += InAppWebView.printJS.replaceAll("[\r\n]+", "");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

View File

@ -44,6 +44,21 @@ public class JavaScriptBridgeInterface {
this.channel = (this.inAppBrowserActivity != null) ? this.inAppBrowserActivity.channel : this.flutterWebView.channel;
}
@JavascriptInterface
public void _hideContextMenu() {
final InAppWebView webView = (inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView;
final Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
if (webView != null && webView.floatingContextMenu != null) {
webView.hideContextMenu();
}
}
});
}
@JavascriptInterface
public void _callHandler(final String handlerName, final String _callHandlerID, final String args) {
final InAppWebView webView = (inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView;

View File

@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","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.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"android":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","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.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"macos":[{"name":"connectivity_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_macos-0.1.0+3/","dependencies":[]},{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+2/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"connectivity","dependencies":["connectivity_macos"]},{"name":"connectivity_macos","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-05-21 03:31:36.578209","version":"1.17.0"}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","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/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_inappwebview-3.2.0/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"android":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","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/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_inappwebview-3.2.0/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"macos":[{"name":"connectivity_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_macos-0.1.0+3/","dependencies":[]},{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+2/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"connectivity","dependencies":["connectivity_macos"]},{"name":"connectivity_macos","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-05-21 22:50:56.107907","version":"1.17.0"}

View File

@ -2,7 +2,7 @@
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/test_driver/app.dart"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"

View File

@ -1,6 +1,9 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'main.dart';
@ -78,7 +81,8 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true
debuggingEnabled: true,
disableContextMenu: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {

View File

@ -24,8 +24,8 @@ dependencies:
path_provider: ^1.4.0
permission_handler: ^3.3.0
connectivity: ^0.4.5+6
flutter_inappwebview:
path: ../
flutter_inappwebview: ^3.2.0
#path: ../
dev_dependencies:
flutter_driver:

View File

@ -763,6 +763,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
var lastTouchPointTimestamp = Int64(Date().timeIntervalSince1970 * 1000)
var contextMenuIsShowing = false
// flag used for the workaround to trigger onCreateContextMenu event as the same on Android
var onCreateContextMenuEventTriggeredWhenMenuDisabled = false
var customIMPs: [IMP] = []
@ -887,10 +889,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
return super.hitTest(point, with: event)
}
public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if let _ = sender as? UIMenuController {
if self.options?.disableContextMenu == true {
if !onCreateContextMenuEventTriggeredWhenMenuDisabled {
// workaround to trigger onCreateContextMenu event as the same on Android
self.onCreateContextMenu()
onCreateContextMenuEventTriggeredWhenMenuDisabled = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.onCreateContextMenuEventTriggeredWhenMenuDisabled = false
}
}
return false
}
if contextMenuIsShowing, !action.description.starts(with: "onContextMenuActionItemClicked-") {
@ -1083,8 +1093,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if (options?.clearCache)! {
clearCache()
}
}
@available(iOS 10.0, *)

View File

@ -18,7 +18,8 @@ const javaScriptHandlerForbiddenNames = [
"onAjaxReadyStateChange",
"onAjaxProgress",
"shouldInterceptFetchRequest",
"onPrint"
"onPrint",
"androidKeyboardWorkaroundFocusoutEvent"
];
///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.

View File

@ -439,6 +439,11 @@ class InAppWebViewController {
List<dynamic> args = jsonDecode(call.arguments["args"]);
switch (handlerName) {
case "androidKeyboardWorkaroundFocusoutEvent":
// android Workaround to hide the Keyboard when the user click outside
// on something not focusable such as input or a textarea.
SystemChannels.textInput.invokeMethod("TextInput.hide");
break;
case "onLoadResource":
Map<dynamic, dynamic> argMap = args[0];
String initiatorType = argMap["initiatorType"];

View File

@ -1,6 +1,6 @@
name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview or open an in-app browser window.
version: 3.2.0
version: 3.3.0
homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment: