Commit f569e369 authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

Updated Android context menu workaround, updated iOS onCreateContextMenu...

Updated Android context menu workaround, updated iOS onCreateContextMenu event, Added Android keyboard workaround to hide the keyboard when clicking other HTML elements
parent 5943059b
## 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)
......
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;
......
......@@ -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();
......
......@@ -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) {
......
......@@ -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;
......
{"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"}
\ No newline at end of file
{"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"}
\ No newline at end of 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"
......
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) {
......
......@@ -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:
......
......@@ -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, *)
......
......@@ -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.
......
......@@ -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"];
......
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:
......
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