Skip to content

Commit

Permalink
Updated Android context menu workaround, updated iOS onCreateContextM…
Browse files Browse the repository at this point in the history
…enu event, Added Android keyboard workaround to hide the keyboard when clicking other HTML elements
  • Loading branch information
Lorenzo Pichilli committed May 21, 2020
1 parent 5943059 commit f569e36
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 38 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -29,7 +24,6 @@
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {" +
Expand Down Expand Up @@ -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'));";

Expand Down Expand Up @@ -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) {" +
Expand All @@ -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);
}
Expand Down Expand Up @@ -746,19 +795,19 @@ public void run() {
};

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);
}
}
});
Expand Down Expand Up @@ -1424,14 +1473,18 @@ public void printCurrentPage() {
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() {
Expand All @@ -1444,6 +1497,12 @@ public void onCreateContextMenu(ContextMenu menu) {
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<>();
Expand Down Expand Up @@ -1545,7 +1604,7 @@ public void onClick(View v) {
text.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
clearFocus();
// clearFocus();
hideContextMenu();

Map<String, Object> obj = new HashMap<>();
Expand Down Expand Up @@ -1588,8 +1647,8 @@ public void onGlobalLayout() {
if (hasBeenRemovedAndRebuilt) {
sendOnCreateContextMenuEvent();
}
if (selectedTextTask != null) {
selectedTextTask.run();
if (checkContextMenuShouldBeClosedTask != null) {
checkContextMenuShouldBeClosedTask.run();
}
}
actionMenu.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ else if (obj instanceof FlutterWebView)
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;
Expand Down
2 changes: 1 addition & 1 deletion example/.flutter-plugins-dependencies
Original file line number Diff line number Diff line change
@@ -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"}
2 changes: 1 addition & 1 deletion example/ios/Flutter/flutter_export_environment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 5 additions & 1 deletion example/lib/in_app_webiew_example.screen.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -78,7 +81,8 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true
debuggingEnabled: true,
disableContextMenu: true,
),
),
onWebViewCreated: (InAppWebViewController controller) {
Expand Down
4 changes: 2 additions & 2 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 11 additions & 3 deletions ios/Classes/InAppWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = []

Expand Down Expand Up @@ -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-") {
Expand Down Expand Up @@ -1083,8 +1093,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if (options?.clearCache)! {
clearCache()
}


}

@available(iOS 10.0, *)
Expand Down
3 changes: 2 additions & 1 deletion lib/src/in_app_webview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit f569e36

Please sign in to comment.