diff --git a/.idea/workspace.xml b/.idea/workspace.xml index e6f8abf6..2613c168 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -15,8 +15,20 @@ + + - + + + + + + + + + + + @@ -36,11 +48,11 @@ - + - - + + @@ -51,8 +63,8 @@ - - + + @@ -63,8 +75,8 @@ - - + + @@ -75,8 +87,8 @@ - - + + @@ -87,7 +99,7 @@ - + @@ -99,8 +111,8 @@ - - + + @@ -108,15 +120,11 @@ - + - - - - - - + + @@ -124,8 +132,8 @@ - - + + @@ -160,7 +168,6 @@ ontARGET onCreateWindow javaScriptHandlerForbiddenNames - supportMultipleWindows ajaxRequest onTarget AjaxR @@ -172,6 +179,7 @@ fromValue shouldOv shouldOverrideUrlLoading + supportMultipleWindows activity.getPreferences(0) @@ -237,18 +245,18 @@ - + @@ -486,7 +494,7 @@ - + @@ -497,7 +505,7 @@ - + @@ -505,7 +513,7 @@ - + @@ -804,16 +812,6 @@ - - - - - - - - - - @@ -835,36 +833,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -875,37 +843,40 @@ - + - - - - - - - - - + + - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + @@ -914,11 +885,44 @@ - - + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 216e4d25..8c8c1087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ - Added `regexToCancelSubFramesLoading` webview option for Android to cancel subframe requests on `shouldOverrideUrlLoading` event based on a Regular Expression - Updated default value for `domStorageEnabled` option to `true` for Android - Fix for Android `InAppBrowser` for some controller methods not exposed. +- Merge "Fixes null error when calling getOptions for InAppBrowser class" [#214](https://github.com/pichillilorenzo/flutter_inappwebview/pull/214) (thanks to [panndoraBoo](https://github.com/panndoraBoo)) +- Added `enableDropDownWorkaroud` webview option for Android to enable a temporary workaround for html dropdowns (issue [#182](https://github.com/pichillilorenzo/flutter_inappwebview/issues/182)) ### BREAKING CHANGES diff --git a/README.md b/README.md index 94f39454..bad4b2e7 100644 --- a/README.md +++ b/README.md @@ -409,6 +409,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly: * `thirdPartyCookiesEnabled`: Boolean value to enable third party cookies in the WebView. * `hardwareAcceleration`: Boolean value to enable Hardware Acceleration in the WebView. * `supportMultipleWindows`: Sets whether the WebView whether supports multiple windows. +* `regexToCancelSubFramesLoading`: Regular expression used by `shouldOverrideUrlLoading` event to cancel navigation for frames that are not the main frame. If the url request of a subframe matches the regular expression, then the request of that subframe is canceled. ##### `InAppWebView` iOS-specific options diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java index 2a549429..d85ee51c 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebView.java @@ -2,6 +2,7 @@ package com.pichillilorenzo.flutter_inappwebview.InAppWebView; import android.app.Activity; import android.content.Context; +import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -20,8 +21,13 @@ import android.webkit.WebBackForwardList; import android.webkit.WebHistoryItem; import android.webkit.WebSettings; import android.webkit.WebStorage; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.ListView; import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AlertDialog; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlocker; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerAction; @@ -31,6 +37,7 @@ import com.pichillilorenzo.flutter_inappwebview.FlutterWebView; import com.pichillilorenzo.flutter_inappwebview.InAppBrowserActivity; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface; +import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.Util; import java.io.ByteArrayOutputStream; @@ -507,6 +514,128 @@ final public class InAppWebView extends InputAwareWebView { " };" + "})(window.fetch);"; + // Android API 19+ + static final String dropDownWorkaroundJS = "(function() {" + + " function getIndexSelectValues(select) {" + + " var result = [];" + + " var options = select && select.options;" + + " for (var i = 0, iLen=options.length; i < iLen; i++) {" + + " var opt = options[i];" + + " if (opt.selected) {" + + " result.push(i);" + + " }" + + " }" + + " return result;" + + " }" + + " function setMultipleValues(select, values) {" + + " var options = select && select.options;" + + " for (var i = 0, iLen=options.length; i < iLen; i++) {" + + " var opt = options[i];" + + " opt.selected = values.indexOf(opt.value) >= 0;" + + " }" + + " }" + + " function addDivWrapper(selectElement) {" + + " var divElement = document.createElement('div');" + + " divElement.flutterInAppWebViewSelect = selectElement;" + + " divElement.class = 'flutterInAppWebViewSelect';" + + " divElement.style.position = 'absolute';" + + " divElement.style.zIndex = 99999999;" + + " divElement.style.backgroundColor = 'transparent';" + + " selectElement.flutterInAppWebViewDivWrapper = divElement;" + + " updateBoundingClientRectDivWrapper(selectElement);" + + " var mutationObserver = new MutationObserver(function(mutations) {" + + " mutations.forEach(function(mutation) {" + + " updateBoundingClientRectDivWrapper(selectElement);" + + " });" + + " });" + + " mutationObserver.observe(selectElement, {" + + " attributes: true," + + " attributeFilter: ['style']" + + " });" + + " selectElement.mutationObserver = mutationObserver;" + + " var clickEventListener = function(event) {" + + " var self = this;" + + " event.preventDefault();" + + " this.flutterInAppWebViewSelect.focus();" + + " var options = [];" + + " var optionElements = this.flutterInAppWebViewSelect.querySelectorAll('option');" + + " for (var i = 0; i < optionElements.length; i++) {" + + " var optionElement = optionElements[i];" + + " options.push({" + + " key: optionElement.textContent," + + " value: optionElement.value" + + " });" + + " }" + + " var isMultiple = !!this.flutterInAppWebViewSelect.multiple;" + + " window." + JavaScriptBridgeInterface.name + ".callHandler('flutterInAppWebViewDropDownWorkaroud', getIndexSelectValues(this.flutterInAppWebViewSelect), isMultiple, options).then(function(result) {" + + " if (result != null && result.values != null) {" + + " if (!isMultiple) {" + + " if (result.values.length > 0) {" + + " self.flutterInAppWebViewSelect.value = result.values[0];" + + " }" + + " } else {" + + " setMultipleValues(self.flutterInAppWebViewSelect, result.values);" + + " }" + + " }" + + " self.flutterInAppWebViewSelect.blur();" + + " });" + + " };" + + " divElement.addEventListener('click', clickEventListener);" + + " divWithEventListeners.push({" + + " divElement: divElement," + + " clickEvent: clickEventListener" + + " });" + + " document.body.appendChild(divElement);" + + " }" + + " function removeDivWrapper(selectElement) {" + + " if (selectElement.flutterInAppWebViewDivWrapper) {" + + " divWithEventListeners.splice(divWithEventListeners.indexOf(selectElement.flutterInAppWebViewDivWrapper), 1);" + + " document.body.removeChild(selectElement.flutterInAppWebViewDivWrapper);" + + " }" + + " }" + + " function updateBoundingClientRectDivWrapper(selectElement) {" + + " selectElement.flutterInAppWebViewDivWrapper.style.width = selectElement.getBoundingClientRect().width + 'px';" + + " selectElement.flutterInAppWebViewDivWrapper.style.height = selectElement.getBoundingClientRect().height + 'px';" + + " selectElement.flutterInAppWebViewDivWrapper.style.top = selectElement.getBoundingClientRect().y + 'px';" + + " selectElement.flutterInAppWebViewDivWrapper.style.left = selectElement.getBoundingClientRect().x + 'px';" + + " }" + + " var selectElements = document.querySelectorAll('select');" + + " var divWithEventListeners = [];" + + " for(var selectElement of selectElements) {" + + " addDivWrapper(selectElement);" + + " }" + + " window.addEventListener('resize', function(event) {" + + " for(var divWithEventListener of divWithEventListeners) {" + + " var divElement = divWithEventListener.divElement;" + + " var selectElement = divElement.flutterInAppWebViewSelect;" + + " divElement.style.width = selectElement.getBoundingClientRect().width + 'px';" + + " divElement.style.height = selectElement.getBoundingClientRect().height + 'px';" + + " divElement.style.top = selectElement.getBoundingClientRect().y + 'px';" + + " divElement.style.left = selectElement.getBoundingClientRect().x + 'px';" + + " }" + + " });" + + " var mutationObserver = new MutationObserver(function(mutations) {" + + " mutations.forEach(function(mutation) {" + + " for(var nodeElement of mutation.addedNodes) {" + + " if (nodeElement.tagName == 'SELECT') {" + + " addDivWrapper(nodeElement);" + + " }" + + " }" + + " for(var nodeElement of mutation.removedNodes) {" + + " if (nodeElement.tagName == 'SELECT') {" + + " removeDivWrapper(nodeElement);" + + " if (nodeElement.mutationObserver) {" + + " nodeElement.mutationObserver.disconnect();" + + " }" + + " }" + + " }" + + " });" + + " });" + + " mutationObserver.observe(document.body, {" + + " childList: true" + + " });" + + "})();"; + public InAppWebView(Context context) { super(context); @@ -1341,6 +1470,72 @@ final public class InAppWebView extends InputAwareWebView { new PrintAttributes.Builder().build()); } + public void showDropDownWorkaroud(final List selectedValues, final List> values, final boolean isMultiSelect, final DropDownWorkaroudCallback callback) { + FrameLayout layout = new FrameLayout(getContext()); + + final List listViewValues = new ArrayList(); + for(List value : values) { + listViewValues.add(value.get(0)); + } + + ListView listView = new ListView(registrar.activeContext()); + ArrayAdapter spinnerArrayAdapter = new ArrayAdapter(registrar.activeContext(), (!isMultiSelect) ? android.R.layout.simple_list_item_1 : android.R.layout.simple_list_item_multiple_choice, listViewValues); + spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_list_item_multiple_choice); + listView.setAdapter(spinnerArrayAdapter); + + + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(registrar.activeContext(), R.style.Theme_AppCompat_Dialog_Alert); + final AlertDialog alertDialog = alertDialogBuilder.create(); + + final List result = new ArrayList<>(); + + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + String value = values.get(position).get(1); + if (!isMultiSelect) { + result.add(value); + //callback.result(result); + alertDialog.dismiss(); + } else { + if (!result.contains(value)) { + result.add(value); + } else { + result.remove(value); + } + } + } + }); + + if (isMultiSelect) { + listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + listView.setItemsCanFocus(false); + + for(Integer selectedValueIndex : selectedValues) { + listView.setItemChecked(selectedValueIndex, true); + String value = values.get(selectedValueIndex).get(1); + result.add(value); + } + } + + alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + callback.result(result); + } + }); + + layout.addView(listView); + alertDialog.setView(layout); + alertDialog.show(); + } + + public static class DropDownWorkaroudCallback { + public void result(List value) { + + } + } + @Override public void dispose() { super.dispose(); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java index ac62cc52..8f5fb573 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewClient.java @@ -1,6 +1,5 @@ package com.pichillilorenzo.flutter_inappwebview.InAppWebView; -import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.net.http.SslCertificate; @@ -227,6 +226,10 @@ public class InAppWebViewClient extends WebViewClient { String js = InAppWebView.platformReadyJS.replaceAll("[\r\n]+", ""); + if (webView.options.dropDownWorkaroudEnabled) { + js += InAppWebView.dropDownWorkaroundJS.replaceAll("[\r\n]+", ""); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(js, (ValueCallback) null); } else { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java index a819f5d7..92224154 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebView/InAppWebViewOptions.java @@ -83,6 +83,7 @@ public class InAppWebViewOptions extends Options { public Boolean hardwareAcceleration = true; public Boolean supportMultipleWindows = false; public String regexToCancelSubFramesLoading; + public Boolean dropDownWorkaroudEnabled = false; @Override public Object onParse(Map.Entry pair) { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java index d67d1f57..76ed3336 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/JavaScriptBridgeInterface.java @@ -9,7 +9,13 @@ import android.webkit.ValueCallback; import com.pichillilorenzo.flutter_inappwebview.InAppWebView.InAppWebView; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import io.flutter.plugin.common.MethodChannel; @@ -251,7 +257,7 @@ public class JavaScriptBridgeInterface { } @JavascriptInterface - public void _callHandler(final String handlerName, final String _callHandlerID, String args) { + public void _callHandler(final String handlerName, final String _callHandlerID, final String args) { final InAppWebView webView = (inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView; final Map obj = new HashMap<>(); @@ -266,6 +272,53 @@ public class JavaScriptBridgeInterface { handler.post(new Runnable() { @Override public void run() { + + // workaround for https://github.com/pichillilorenzo/flutter_inappwebview/issues/182 + if (handlerName.equals("flutterInAppWebViewDropDownWorkaroud")) { + try { + JSONArray jsonArray = new JSONArray(args); + + List selectedValues = new ArrayList<>(); + JSONArray jsonSelectedValues = jsonArray.getJSONArray(0); + for(int i = 0; i < jsonSelectedValues.length(); i++) { + Integer selectedValue = jsonSelectedValues.getInt(i); + selectedValues.add(selectedValue); + } + + boolean isMultiSelect = jsonArray.getBoolean(1); + + List> values = new ArrayList<>(); + JSONArray options = jsonArray.getJSONArray(2); + + Log.d(LOG_TAG, options.toString()); + for(int i = 0; i < options.length(); i++) { + JSONObject option = options.getJSONObject(i); + + List value = new ArrayList<>(); + value.add(option.getString("key")); + value.add(option.getString("value")); + + values.add(value); + } + + webView.showDropDownWorkaroud(selectedValues, values, isMultiSelect, new InAppWebView.DropDownWorkaroudCallback() { + @Override + public void result(List values) { + String value = "{values: " + (new JSONArray(values)) + "}"; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript("if(window." + name + "[" + _callHandlerID + "] != null) {window." + name + "[" + _callHandlerID + "](" + value + "); delete window." + name + "[" + _callHandlerID + "];}", (ValueCallback) null); + } + else { + webView.loadUrl("javascript:if(window." + name + "[" + _callHandlerID + "] != null) {window." + name + "[" + _callHandlerID + "](" + value + "); delete window." + name + "[" + _callHandlerID + "];}"); + } + } + }); + } catch (JSONException e) { + e.printStackTrace(); + } + return; + } + if (handlerName.equals("onPrint") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webView.printCurrentPage(); } diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart index 8e5f5058..a25caf97 100755 --- a/lib/src/in_app_webview.dart +++ b/lib/src/in_app_webview.dart @@ -24,6 +24,7 @@ const javaScriptHandlerForbiddenNames = [ "onAjaxProgress", "shouldInterceptFetchRequest", "onPrint", + "flutterInAppWebViewDropDownWorkaroud" ]; ///InAppWebView Widget class. diff --git a/lib/src/webview_options.dart b/lib/src/webview_options.dart index 879af0a1..12a03308 100644 --- a/lib/src/webview_options.dart +++ b/lib/src/webview_options.dart @@ -404,9 +404,17 @@ class AndroidInAppWebViewOptions ///If set to `true`, [onCreateWindow] event must be implemented by the host application. The default value is `false`. bool supportMultipleWindows; - ///Regular expression used by [shouldOverrideUrlLoading] event to cancel navigation for frames that are not the main frame. + ///Regular expression used by [shouldOverrideUrlLoading] event to cancel navigation requests for frames that are not the main frame. + ///If the url request of a subframe matches the regular expression, then the request of that subframe is canceled. String regexToCancelSubFramesLoading; + ///Enable a temporary workaround for html dropdowns (`