Skip to content

Commit f569e36

Browse files
author
Lorenzo Pichilli
committed
Updated Android context menu workaround, updated iOS onCreateContextMenu event, Added Android keyboard workaround to hide the keyboard when clicking other HTML elements
1 parent 5943059 commit f569e36

13 files changed

+134
-38
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 3.3.0
2+
3+
- Updated Android context menu workaround
4+
- Calling `onCreateContextMenu` event on iOS also when the context menu is disabled in order to have the same effect as Android
5+
- Added Android keyboard workaround to hide the keyboard when clicking other HTML elements, losing the focus on the previous input
6+
17
## 3.2.0
28

39
- Added `ContextMenu` and `ContextMenuItem` classes [#235](https://github.com/pichillilorenzo/flutter_inappwebview/issues/235)

android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/FlutterWebView.java

-6
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
package com.pichillilorenzo.flutter_inappwebview.InAppWebView;
22

3-
import android.app.Activity;
43
import android.content.Context;
54
import android.hardware.display.DisplayManager;
65
import android.os.Build;
76
import android.os.Handler;
87
import android.os.Looper;
98
import android.util.Log;
10-
import android.view.ContextThemeWrapper;
119
import android.view.View;
1210
import android.webkit.WebChromeClient;
1311
import android.webkit.WebSettings;
1412
import android.webkit.WebView;
1513
import android.webkit.WebViewClient;
1614

17-
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.DisplayListenerProxy;
18-
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView;
19-
import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebViewOptions;
2015
import com.pichillilorenzo.flutter_inappwebview.Shared;
2116
import com.pichillilorenzo.flutter_inappwebview.Util;
2217

@@ -29,7 +24,6 @@
2924
import io.flutter.plugin.common.BinaryMessenger;
3025
import io.flutter.plugin.common.MethodCall;
3126
import io.flutter.plugin.common.MethodChannel;
32-
import io.flutter.plugin.common.PluginRegistry.Registrar;
3327
import io.flutter.plugin.platform.PlatformView;
3428

3529
import static io.flutter.plugin.common.MethodChannel.MethodCallHandler;

android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java

+81-22
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,17 @@ final public class InAppWebView extends InputAwareWebView {
7676
int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB
7777
public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler();
7878
public Pattern regexToCancelSubFramesLoadingCompiled;
79-
private GestureDetector gestureDetector = null;
80-
private MotionEvent motionEvent = null;
81-
private LinearLayout floatingContextMenu = null;
79+
public GestureDetector gestureDetector = null;
80+
public LinearLayout floatingContextMenu = null;
8281
public HashMap<String, Object> contextMenu = null;
8382
public Handler headlessHandler = new Handler(Looper.getMainLooper());
8483

85-
private Runnable checkScrollStoppedTask;
86-
private int initialPositionScrollStoppedTask;
87-
private int newCheckScrollStoppedTask = 100;
84+
public Runnable checkScrollStoppedTask;
85+
public int initialPositionScrollStoppedTask;
86+
public int newCheckScrollStoppedTask = 100; // ms
8887

89-
private Runnable selectedTextTask;
90-
private int newCheckSelectedTextTask = 100;
88+
public Runnable checkContextMenuShouldBeClosedTask;
89+
public int newCheckContextMenuShouldBeClosedTaskTask = 100; // ms
9190

9291
static final String consoleLogJS = "(function(console) {" +
9392
" var oldLogs = {" +
@@ -117,7 +116,7 @@ final public class InAppWebView extends InputAwareWebView {
117116

118117
static final String printJS = "window.print = function() {" +
119118
" window." + JavaScriptBridgeInterface.name + ".callHandler('onPrint', window.location.href);" +
120-
"}";
119+
"};";
121120

122121
static final String platformReadyJS = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));";
123122

@@ -531,6 +530,14 @@ final public class InAppWebView extends InputAwareWebView {
531530
" };" +
532531
"})(window.fetch);";
533532

533+
static final String isActiveElementInputEditableJS =
534+
"var activeEl = document.activeElement;" +
535+
"var nodeName = (activeEl != null) ? activeEl.nodeName.toLowerCase() : '';" +
536+
"var isActiveElementInputEditable = activeEl != null && " +
537+
"(activeEl.nodeType == 1 && (nodeName == 'textarea' || (nodeName == 'input' && /^(?:text|email|number|search|tel|url|password)$/i.test(activeEl.type != null ? activeEl.type : 'text')))) && " +
538+
"!activeEl.disabled && !activeEl.readOnly;" +
539+
"var isActiveElementEditable = isActiveElementInputEditable || (activeEl != null && activeEl.isContentEditable) || document.designMode === 'on';";
540+
534541
static final String getSelectedTextJS = "(function(){" +
535542
" var txt;" +
536543
" if (window.getSelection) {" +
@@ -543,6 +550,48 @@ final public class InAppWebView extends InputAwareWebView {
543550
" return txt;" +
544551
"})();";
545552

553+
// android Workaround to hide context menu when selected text is empty
554+
// and the document active element is not an input element.
555+
static final String checkContextMenuShouldBeHiddenJS = "(function(){" +
556+
" var txt;" +
557+
" if (window.getSelection) {" +
558+
" txt = window.getSelection().toString();" +
559+
" } else if (window.document.getSelection) {" +
560+
" txt = window.document.getSelection().toString();" +
561+
" } else if (window.document.selection) {" +
562+
" txt = window.document.selection.createRange().text;" +
563+
" }" +
564+
isActiveElementInputEditableJS +
565+
" return txt === '' && !isActiveElementEditable;" +
566+
"})();";
567+
568+
// android Workaround to hide context menu when user emit a keydown event
569+
static final String checkGlobalKeyDownEventToHideContextMenuJS = "(function(){" +
570+
" document.addEventListener('keydown', function(e) {" +
571+
" window." + JavaScriptBridgeInterface.name + "._hideContextMenu();" +
572+
" });" +
573+
"})();";
574+
575+
// android Workaround to hide the Keyboard when the user click outside
576+
// on something not focusable such as input or a textarea.
577+
static final String androidKeyboardWorkaroundFocusoutEventJS = "(function(){" +
578+
" var isFocusin = false;" +
579+
" document.addEventListener('focusin', function(e) {" +
580+
" var nodeName = e.target.nodeName.toLowerCase();" +
581+
" var isInputButton = nodeName === 'input' && e.target.type != null && e.target.type === 'button';" +
582+
" isFocusin = (['a', 'area', 'button', 'details', 'iframe', 'select', 'summary'].indexOf(nodeName) >= 0 || isInputButton) ? false : true;" +
583+
" });" +
584+
" document.addEventListener('focusout', function(e) {" +
585+
" isFocusin = false;" +
586+
" setTimeout(function() {" +
587+
isActiveElementInputEditableJS +
588+
" if (!isFocusin && !isActiveElementEditable) {" +
589+
" window." + JavaScriptBridgeInterface.name + ".callHandler('androidKeyboardWorkaroundFocusoutEvent');" +
590+
" }" +
591+
" }, 300);" +
592+
" });" +
593+
"})();";
594+
546595
public InAppWebView(Context context) {
547596
super(context);
548597
}
@@ -746,19 +795,19 @@ public void run() {
746795
};
747796

748797
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
749-
selectedTextTask = new Runnable() {
798+
checkContextMenuShouldBeClosedTask = new Runnable() {
750799
@Override
751800
public void run() {
752801
if (floatingContextMenu != null) {
753-
getSelectedText(new ValueCallback<String>() {
802+
evaluateJavascript(checkContextMenuShouldBeHiddenJS, new ValueCallback<String>() {
754803
@Override
755804
public void onReceiveValue(String value) {
756-
if (value == null || value.length() == 0) {
805+
if (value == null || value.equals("true")) {
757806
if (floatingContextMenu != null) {
758807
hideContextMenu();
759808
}
760809
} else {
761-
headlessHandler.postDelayed(selectedTextTask, newCheckSelectedTextTask);
810+
headlessHandler.postDelayed(checkContextMenuShouldBeClosedTask, newCheckContextMenuShouldBeClosedTaskTask);
762811
}
763812
}
764813
});
@@ -1424,14 +1473,18 @@ public void printCurrentPage() {
14241473
PrintManager printManager = (PrintManager) Shared.activity.getApplicationContext()
14251474
.getSystemService(Context.PRINT_SERVICE);
14261475

1427-
String jobName = getTitle() + " Document";
1476+
if (printManager != null) {
1477+
String jobName = getTitle() + " Document";
14281478

1429-
// Get a printCurrentPage adapter instance
1430-
PrintDocumentAdapter printAdapter = createPrintDocumentAdapter(jobName);
1479+
// Get a printCurrentPage adapter instance
1480+
PrintDocumentAdapter printAdapter = createPrintDocumentAdapter(jobName);
14311481

1432-
// Create a printCurrentPage job with name and adapter instance
1433-
printManager.print(jobName, printAdapter,
1434-
new PrintAttributes.Builder().build());
1482+
// Create a printCurrentPage job with name and adapter instance
1483+
printManager.print(jobName, printAdapter,
1484+
new PrintAttributes.Builder().build());
1485+
} else {
1486+
Log.e(LOG_TAG, "No PrintManager available");
1487+
}
14351488
}
14361489

14371490
public Float getUpdatedScale() {
@@ -1444,6 +1497,12 @@ public void onCreateContextMenu(ContextMenu menu) {
14441497
sendOnCreateContextMenuEvent();
14451498
}
14461499

1500+
@Override
1501+
public boolean onCheckIsTextEditor() {
1502+
Log.d(LOG_TAG, "onCheckIsTextEditor");
1503+
return super.onCheckIsTextEditor();
1504+
}
1505+
14471506
private void sendOnCreateContextMenuEvent() {
14481507
HitTestResult hitTestResult = getHitTestResult();
14491508
Map<String, Object> hitTestResultMap = new HashMap<>();
@@ -1545,7 +1604,7 @@ public void onClick(View v) {
15451604
text.setOnClickListener(new OnClickListener() {
15461605
@Override
15471606
public void onClick(View v) {
1548-
clearFocus();
1607+
// clearFocus();
15491608
hideContextMenu();
15501609

15511610
Map<String, Object> obj = new HashMap<>();
@@ -1588,8 +1647,8 @@ public void onGlobalLayout() {
15881647
if (hasBeenRemovedAndRebuilt) {
15891648
sendOnCreateContextMenuEvent();
15901649
}
1591-
if (selectedTextTask != null) {
1592-
selectedTextTask.run();
1650+
if (checkContextMenuShouldBeClosedTask != null) {
1651+
checkContextMenuShouldBeClosedTask.run();
15931652
}
15941653
}
15951654
actionMenu.clear();

android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java

+4
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) {
179179
if (webView.options.useOnLoadResource) {
180180
js += InAppWebView.resourceObserverJS.replaceAll("[\r\n]+", "");
181181
}
182+
js += InAppWebView.checkGlobalKeyDownEventToHideContextMenuJS.replaceAll("[\r\n]+", "");
183+
if (flutterWebView != null) {
184+
js += InAppWebView.androidKeyboardWorkaroundFocusoutEventJS.replaceAll("[\r\n]+", "");
185+
}
182186
js += InAppWebView.printJS.replaceAll("[\r\n]+", "");
183187

184188
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java

+15
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ else if (obj instanceof FlutterWebView)
4444
this.channel = (this.inAppBrowserActivity != null) ? this.inAppBrowserActivity.channel : this.flutterWebView.channel;
4545
}
4646

47+
@JavascriptInterface
48+
public void _hideContextMenu() {
49+
final InAppWebView webView = (inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView;
50+
51+
final Handler handler = new Handler(Looper.getMainLooper());
52+
handler.post(new Runnable() {
53+
@Override
54+
public void run() {
55+
if (webView != null && webView.floatingContextMenu != null) {
56+
webView.hideContextMenu();
57+
}
58+
}
59+
});
60+
}
61+
4762
@JavascriptInterface
4863
public void _callHandler(final String handlerName, final String _callHandlerID, final String args) {
4964
final InAppWebView webView = (inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView;

example/.flutter-plugins-dependencies

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +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"}
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/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"}

example/ios/Flutter/flutter_export_environment.sh

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

example/lib/in_app_webiew_example.screen.dart

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import 'dart:io';
22

3+
import 'package:flutter/foundation.dart';
4+
import 'package:flutter/gestures.dart';
35
import 'package:flutter/material.dart';
6+
import 'package:flutter/services.dart';
47
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
58

69
import 'main.dart';
@@ -78,7 +81,8 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
7881
initialHeaders: {},
7982
initialOptions: InAppWebViewGroupOptions(
8083
crossPlatform: InAppWebViewOptions(
81-
debuggingEnabled: true
84+
debuggingEnabled: true,
85+
disableContextMenu: true,
8286
),
8387
),
8488
onWebViewCreated: (InAppWebViewController controller) {

example/pubspec.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ dependencies:
2424
path_provider: ^1.4.0
2525
permission_handler: ^3.3.0
2626
connectivity: ^0.4.5+6
27-
flutter_inappwebview:
28-
path: ../
27+
flutter_inappwebview: ^3.2.0
28+
#path: ../
2929

3030
dev_dependencies:
3131
flutter_driver:

ios/Classes/InAppWebView.swift

+11-3
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
763763
var lastTouchPointTimestamp = Int64(Date().timeIntervalSince1970 * 1000)
764764

765765
var contextMenuIsShowing = false
766+
// flag used for the workaround to trigger onCreateContextMenu event as the same on Android
767+
var onCreateContextMenuEventTriggeredWhenMenuDisabled = false
766768

767769
var customIMPs: [IMP] = []
768770

@@ -887,10 +889,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
887889

888890
return super.hitTest(point, with: event)
889891
}
890-
892+
891893
public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
892894
if let _ = sender as? UIMenuController {
893895
if self.options?.disableContextMenu == true {
896+
if !onCreateContextMenuEventTriggeredWhenMenuDisabled {
897+
// workaround to trigger onCreateContextMenu event as the same on Android
898+
self.onCreateContextMenu()
899+
onCreateContextMenuEventTriggeredWhenMenuDisabled = true
900+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
901+
self.onCreateContextMenuEventTriggeredWhenMenuDisabled = false
902+
}
903+
}
894904
return false
895905
}
896906
if contextMenuIsShowing, !action.description.starts(with: "onContextMenuActionItemClicked-") {
@@ -1083,8 +1093,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
10831093
if (options?.clearCache)! {
10841094
clearCache()
10851095
}
1086-
1087-
10881096
}
10891097

10901098
@available(iOS 10.0, *)

lib/src/in_app_webview.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const javaScriptHandlerForbiddenNames = [
1818
"onAjaxReadyStateChange",
1919
"onAjaxProgress",
2020
"shouldInterceptFetchRequest",
21-
"onPrint"
21+
"onPrint",
22+
"androidKeyboardWorkaroundFocusoutEvent"
2223
];
2324

2425
///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.

0 commit comments

Comments
 (0)