From 299042f828950f06e744774144510719476e1db9 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 2 Nov 2019 19:58:01 +0100 Subject: [PATCH] added getHtml method, updated android config, remove block on long press webview android, fixed other issues --- .idea/workspace.xml | 383 +++++---- CHANGELOG.md | 2 + android/build.gradle | 35 +- android/gradle.properties | 3 - android/src/main/AndroidManifest.xml | 3 - .../ChromeCustomTabsActivity.java | 8 +- .../ContentBlocker/ContentBlockerHandler.java | 13 +- .../CredentialDatabaseHandler.java | 4 + .../flutter_inappbrowser/FlutterWebView.java | 14 +- .../flutter_inappbrowser/InAppBrowser.java | 739 +++++++++++++++++ .../InAppBrowserActivity.java | 29 +- .../InAppBrowserFlutterPlugin.java | 771 +----------------- .../InAppBrowserOptions.java | 2 +- .../InAppWebView/InAppWebChromeClient.java | 2 +- .../InAppWebView/InAppWebView.java | 7 +- .../InAppWebView/InAppWebViewClient.java | 4 +- .../InAppWebView/InAppWebViewOptions.java | 2 +- .../InAppWebView/InputAwareWebView.java | 38 +- .../JavaScriptBridgeInterface.java | 4 +- .../flutter_inappbrowser/MyCookieManager.java | 3 + .../flutter_inappbrowser/Util.java | 56 ++ example/android/gradle.properties | 5 +- example/assets/favicon.ico | Bin 0 -> 17694 bytes example/assets/index.html | 5 + .../ios/Flutter/flutter_export_environment.sh | 2 +- example/lib/inline_example.screen.dart | 8 +- example/pubspec.yaml | 1 + ios/Classes/FlutterWebViewController.swift | 16 - ios/Classes/InAppWebView.swift | 20 - ios/flutter_inappbrowser.podspec | 2 +- lib/src/in_app_browser.dart | 3 +- lib/src/in_app_webview.dart | 242 +++--- lib/src/webview_options.dart | 1 + .../index.js | 18 +- pubspec.yaml | 5 +- 35 files changed, 1363 insertions(+), 1087 deletions(-) delete mode 100644 android/gradle.properties create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowser.java create mode 100644 example/assets/favicon.ico diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 57f6868a..697c2180 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -15,23 +15,41 @@ + + + + + + + + + + - + + + + + + + + + + - - - - + - + + + @@ -54,17 +72,26 @@ - - + + - + + + + + + + + + + - - + + @@ -73,22 +100,40 @@ - + - - + + - + + + + + + + + + + + + + + + + + + + - - + + @@ -108,20 +153,6 @@ - a - as - IABWebViewClient - clearCache - Auth - onReceivedHttpAuthRequestCallback - Protection - cast - clear - HttpAuthResponse - ClientCertResponse - onReceivedServerTrustAuthRequest - ServerTrustAuthResponse - certific CER ServerTrustChallenge cONTENTMODE @@ -138,6 +169,20 @@ on findAll getFave + Uri + != "an + _channel + getHtml + url + getOptions + gestureR + _dispose + dispose + Long + custom + scheme + useOnLoadResource + useShouldOverrideUrlLoading activity.getPreferences(0) @@ -175,7 +220,6 @@ - + @@ -310,13 +376,6 @@ - - - - - - - @@ -324,6 +383,13 @@ + + + + + + + - - - + + @@ -455,7 +520,7 @@ - + @@ -491,29 +556,6 @@ - - - - - - - - - - - - - - - - - - - - - - - @@ -688,27 +730,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -722,13 +743,6 @@ - - - - - - - @@ -785,30 +799,10 @@ - + - - - - - - - - - - - - - - - - - - - - - - + + @@ -822,26 +816,97 @@ - + - - + + - + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 44e0366f..e18b5148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Added `HttpAuthCredentialDatabase` class - Added `onReceivedServerTrustAuthRequest` and `onReceivedClientCertRequest` events to manage SSL requests - Added `onFindResultReceived` event, `findAllAsync`, `findNext` and `clearMatches` methods +- Added `getHtml` method ### BREAKING CHANGES - Deleted `WebResourceRequest` class @@ -34,6 +35,7 @@ - Updated `onLoadResource` event - Updated `CookieManager` class - WebView options are now available with the new corresponding classes: `InAppWebViewOptions`, `AndroidInAppWebViewOptions`, `iOSInAppWebViewOptions`, `InAppBrowserOptions`, `AndroidInAppBrowserOptions`, `iOSInAppBrowserOptions`, `AndroidChromeCustomTabsOptions` and `iOSSafariOptions` +- Renamed `getFavicon` to `getFavicons`, now it returns a list of all favicons (`List`) found ## 1.2.1 diff --git a/android/build.gradle b/android/build.gradle index e53aab08..ee6a0047 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -33,11 +33,34 @@ android { lintOptions { disable 'InvalidPackage' } + dependencies { + implementation 'androidx.webkit:webkit:1.0.0' + implementation 'androidx.browser:browser:1.0.0' + implementation 'androidx.appcompat:appcompat:1.0.0' + implementation 'com.squareup.okhttp3:mockwebserver:3.11.0' + } } -dependencies { - implementation 'androidx.webkit:webkit:1.0.0' - implementation 'androidx.browser:browser:1.0.0' - implementation 'androidx.appcompat:appcompat:1.0.0' - implementation 'com.squareup.okhttp3:mockwebserver:3.11.0' -} +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + } + } + } +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 53ae0ae4..00000000 --- a/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true -org.gradle.jvmargs=-Xmx1536M diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index aaa8e933..2a8cbcc8 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,9 +1,6 @@ - - - diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ChromeCustomTabs/ChromeCustomTabsActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ChromeCustomTabs/ChromeCustomTabsActivity.java index 426d380f..09ad29be 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ChromeCustomTabs/ChromeCustomTabsActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ChromeCustomTabs/ChromeCustomTabsActivity.java @@ -36,7 +36,7 @@ public class ChromeCustomTabsActivity extends Activity { options = new ChromeCustomTabsOptions(); options.parse((HashMap) b.getSerializable("options")); - InAppBrowserFlutterPlugin.instance.chromeCustomTabsActivities.put(uuid, this); + InAppBrowserFlutterPlugin.inAppBrowser.chromeCustomTabsActivities.put(uuid, this); customTabActivityHelper = new CustomTabActivityHelper(); builder = new CustomTabsIntent.Builder(); @@ -49,8 +49,8 @@ public class ChromeCustomTabsActivity extends Activity { Map obj = new HashMap<>(); obj.put("uuid", uuid); - InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onChromeSafariBrowserOpened", obj); - InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onChromeSafariBrowserLoaded", obj); + InAppBrowserFlutterPlugin.inAppBrowser.channel.invokeMethod("onChromeSafariBrowserOpened", obj); + InAppBrowserFlutterPlugin.inAppBrowser.channel.invokeMethod("onChromeSafariBrowserLoaded", obj); } private void prepareCustomTabs() { @@ -86,7 +86,7 @@ public class ChromeCustomTabsActivity extends Activity { finish(); Map obj = new HashMap<>(); obj.put("uuid", uuid); - InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onChromeSafariBrowserClosed", obj); + InAppBrowserFlutterPlugin.inAppBrowser.channel.invokeMethod("onChromeSafariBrowserClosed", obj); } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerHandler.java index 71848cfd..03440094 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerHandler.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/ContentBlocker/ContentBlockerHandler.java @@ -7,18 +7,16 @@ import android.util.Log; import android.webkit.WebResourceResponse; import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebView; +import com.pichillilorenzo.flutter_inappbrowser.Util; import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.regex.Matcher; @@ -163,8 +161,7 @@ public class ContentBlockerHandler { Response response = null; try { - - response = webView.httpClient.newCall(mRequest).execute(); + response = Util.getUnsafeOkHttpClient().newCall(mRequest).execute(); byte[] dataBytes = response.body().bytes(); InputStream dataStream = new ByteArrayInputStream(dataBytes); @@ -195,7 +192,7 @@ public class ContentBlockerHandler { } public WebResourceResponse checkUrl(final InAppWebView webView, String url) throws URISyntaxException, InterruptedException, MalformedURLException { - ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromUrl(webView, url); + ContentBlockerTriggerResourceType responseResourceType = getResourceTypeFromUrl(url); return checkUrl(webView, url, responseResourceType); } @@ -204,7 +201,7 @@ public class ContentBlockerHandler { return checkUrl(webView, url, responseResourceType); } - public ContentBlockerTriggerResourceType getResourceTypeFromUrl(InAppWebView webView, String url) { + public ContentBlockerTriggerResourceType getResourceTypeFromUrl(String url) { ContentBlockerTriggerResourceType responseResourceType = ContentBlockerTriggerResourceType.RAW; if (url.startsWith("http://") || url.startsWith("https://")) { @@ -212,7 +209,7 @@ public class ContentBlockerHandler { Request mRequest = new Request.Builder().url(url).head().build(); Response response = null; try { - response = webView.httpClient.newCall(mRequest).execute(); + response = Util.getUnsafeOkHttpClient().newCall(mRequest).execute(); if (response.header("content-type") != null) { String[] contentTypeSplitted = response.header("content-type").split(";"); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/CredentialDatabaseHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/CredentialDatabaseHandler.java index 3a7f981c..45fc541d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/CredentialDatabaseHandler.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/CredentialDatabaseHandler.java @@ -116,4 +116,8 @@ public class CredentialDatabaseHandler implements MethodChannel.MethodCallHandle } } + public void dispose() { + channel.setMethodCallHandler(null); + } + } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java index c291cea5..03ce8346 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/FlutterWebView.java @@ -289,10 +289,6 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { result.success(false); } break; - case "dispose": - dispose(); - result.success(true); - break; default: result.notImplemented(); } @@ -300,10 +296,12 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { @Override public void dispose() { + channel.setMethodCallHandler(null); if (webView != null) { webView.setWebChromeClient(new WebChromeClient()); webView.setWebViewClient(new WebViewClient() { public void onPageFinished(WebView view, String url) { + webView.dispose(); webView.destroy(); webView = null; } @@ -324,4 +322,12 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { webView.unlockInputConnection(); } + public void onFlutterViewAttached(View flutterView) { + webView.setContainerView(flutterView); + } + + public void onFlutterViewDetached() { + webView.setContainerView(null); + } + } \ No newline at end of file diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowser.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowser.java new file mode 100644 index 00000000..27f3fa65 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowser.java @@ -0,0 +1,739 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package com.pichillilorenzo.flutter_inappbrowser; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Build; +import android.os.Parcelable; +import android.provider.Browser; +import android.net.Uri; +import android.os.Bundle; +import android.webkit.MimeTypeMap; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.util.Log; + +import com.pichillilorenzo.flutter_inappbrowser.ChromeCustomTabs.ChromeCustomTabsActivity; +import com.pichillilorenzo.flutter_inappbrowser.ChromeCustomTabs.CustomTabActivityHelper; + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry.Registrar; + +/** + * InAppBrowser + */ +public class InAppBrowser implements MethodChannel.MethodCallHandler { + + public Registrar registrar; + public MethodChannel channel; + public Map webViewActivities = new HashMap<>(); + public Map chromeCustomTabsActivities = new HashMap<>(); + + protected static final String LOG_TAG = "IABFlutterPlugin"; + + public InAppBrowser(Registrar r) { + registrar = r; + channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser"); + channel.setMethodCallHandler(this); + } + + @Override + public void onMethodCall(final MethodCall call, final Result result) { + String source; + String urlFile; + final Activity activity = registrar.activity(); + final String uuid = (String) call.argument("uuid"); + + switch (call.method) { + case "open": + boolean isData = (boolean) call.argument("isData"); + if (!isData) { + final String url_final = (String) call.argument("url"); + + final boolean useChromeSafariBrowser = (boolean) call.argument("useChromeSafariBrowser"); + + final Map headers = (Map) call.argument("headers"); + + Log.d(LOG_TAG, "use Chrome Custom Tabs = " + useChromeSafariBrowser); + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + + if (useChromeSafariBrowser) { + + final String uuidFallback = (String) call.argument("uuidFallback"); + + final HashMap options = (HashMap) call.argument("options"); + + final HashMap optionsFallback = (HashMap) call.argument("optionsFallback"); + + open(activity, uuid, uuidFallback, url_final, options, headers, true, optionsFallback, result); + } else { + + String url = url_final; + + final HashMap options = (HashMap) call.argument("options"); + + final boolean isLocalFile = (boolean) call.argument("isLocalFile"); + final boolean openWithSystemBrowser = (boolean) call.argument("openWithSystemBrowser"); + + if (isLocalFile) { + // check if the asset file exists + try { + url = Util.getUrlAsset(registrar, url); + } catch (IOException e) { + e.printStackTrace(); + result.error(LOG_TAG, url + " asset file cannot be found!", e); + return; + } + } + // SYSTEM + if (openWithSystemBrowser) { + Log.d(LOG_TAG, "in system"); + openExternal(activity, url, result); + } else { + //Load the dialer + if (url.startsWith(WebView.SCHEME_TEL)) { + try { + Log.d(LOG_TAG, "loading in dialer"); + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(url)); + activity.startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + Log.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); + } + } + // load in InAppBrowser + else { + Log.d(LOG_TAG, "loading in InAppBrowser"); + open(activity, uuid, null, url, options, headers, false, null, result); + } + } + } + } + }); + } + else { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + HashMap options = (HashMap) call.argument("options"); + String data = (String) call.argument("data"); + String mimeType = (String) call.argument("mimeType"); + String encoding = (String) call.argument("encoding"); + String baseUrl = (String) call.argument("baseUrl"); + openData(activity, uuid, options, data, mimeType, encoding, baseUrl); + result.success(true); + } + }); + } + break; + case "getUrl": + result.success(getUrl(uuid)); + break; + case "getTitle": + result.success(getTitle(uuid)); + break; + case "getProgress": + result.success(getProgress(uuid)); + break; + case "loadUrl": + loadUrl(uuid, (String) call.argument("url"), (Map) call.argument("headers"), result); + break; + case "postUrl": + postUrl(uuid, (String) call.argument("url"), (byte[]) call.argument("postData"), result); + break; + case "loadData": + { + String data = (String) call.argument("data"); + String mimeType = (String) call.argument("mimeType"); + String encoding = (String) call.argument("encoding"); + String baseUrl = (String) call.argument("baseUrl"); + loadData(uuid, data, mimeType, encoding, baseUrl, result); + } + break; + case "loadFile": + loadFile(uuid, (String) call.argument("url"), (Map) call.argument("headers"), result); + break; + case "close": + close(activity, uuid, result); + break; + case "injectScriptCode": + source = (String) call.argument("source"); + injectScriptCode(uuid, source, result); + break; + case "injectScriptFile": + urlFile = (String) call.argument("urlFile"); + injectScriptFile(uuid, urlFile); + result.success(true); + break; + case "injectStyleCode": + source = (String) call.argument("source"); + injectStyleCode(uuid, source); + result.success(true); + break; + case "injectStyleFile": + urlFile = (String) call.argument("urlFile"); + injectStyleFile(uuid, urlFile); + result.success(true); + break; + case "show": + show(uuid); + result.success(true); + break; + case "hide": + hide(uuid); + result.success(true); + break; + case "reload": + reload(uuid); + result.success(true); + break; + case "goBack": + goBack(uuid); + result.success(true); + break; + case "canGoBack": + result.success(canGoBack(uuid)); + break; + case "goForward": + goForward(uuid); + result.success(true); + break; + case "canGoForward": + result.success(canGoForward(uuid)); + break; + case "goBackOrForward": + goBackOrForward(uuid, (Integer) call.argument("steps")); + result.success(true); + break; + case "canGoBackOrForward": + result.success(canGoBackOrForward(uuid, (Integer) call.argument("steps"))); + break; + case "stopLoading": + stopLoading(uuid); + result.success(true); + break; + case "isLoading": + result.success(isLoading(uuid)); + break; + case "isHidden": + result.success(isHidden(uuid)); + break; + case "takeScreenshot": + result.success(takeScreenshot(uuid)); + break; + case "setOptions": + { + String optionsType = (String) call.argument("optionsType"); + switch (optionsType){ + case "InAppBrowserOptions": + InAppBrowserOptions inAppBrowserOptions = new InAppBrowserOptions(); + HashMap inAppBrowserOptionsMap = (HashMap) call.argument("options"); + inAppBrowserOptions.parse(inAppBrowserOptionsMap); + setOptions(uuid, inAppBrowserOptions, inAppBrowserOptionsMap); + break; + default: + result.error(LOG_TAG, "Options " + optionsType + " not available.", null); + } + } + result.success(true); + break; + case "getOptions": + result.success(getOptions(uuid)); + break; + case "getCopyBackForwardList": + result.success(getCopyBackForwardList(uuid)); + break; + case "startSafeBrowsing": + startSafeBrowsing(uuid, result); + break; + case "setSafeBrowsingWhitelist": + setSafeBrowsingWhitelist(uuid, (List) call.argument("hosts"), result); + break; + case "clearCache": + clearCache(uuid); + result.success(true); + break; + case "clearSslPreferences": + clearSslPreferences(uuid); + result.success(true); + break; + case "clearClientCertPreferences": + clearClientCertPreferences(uuid, result); + break; + case "findAllAsync": + String find = (String) call.argument("find"); + findAllAsync(uuid, find); + result.success(true); + break; + case "findNext": + Boolean forward = (Boolean) call.argument("forward"); + findNext(uuid, forward, result); + break; + case "clearMatches": + clearMatches(uuid, result); + break; + default: + result.notImplemented(); + } + + } + + private void injectScriptCode(String uuid, String source, final Result result) { + final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) { + inAppBrowserActivity.injectScriptCode(source, result); + } else { + Log.d(LOG_TAG, "webView is null"); + } + } + + private void injectScriptFile(String uuid, String urlFile) { + final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) { + inAppBrowserActivity.injectScriptFile(urlFile); + } + } + + private void injectStyleCode(String uuid, String source) { + final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) { + inAppBrowserActivity.injectStyleCode(source); + } + } + + private void injectStyleFile(String uuid, String urlFile) { + final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) { + inAppBrowserActivity.injectStyleFile(urlFile); + } + } + + public static String getMimeType(String url) { + String type = null; + String extension = MimeTypeMap.getFileExtensionFromUrl(url); + if (extension != null) { + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + return type; + } + + /** + * Display a new browser with the specified URL. + * + * @param url the url to load. + * @param result + * @return "" if ok, or error message. + */ + public void openExternal(Activity activity, String url, Result result) { + try { + Intent intent; + intent = new Intent(Intent.ACTION_VIEW); + // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent". + // Adding the MIME type to http: URLs causes them to not be handled by the downloader. + Uri uri = Uri.parse(url); + if ("file".equals(uri.getScheme())) { + intent.setDataAndType(uri, getMimeType(url)); + } else { + intent.setData(uri); + } + intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName()); + // CB-10795: Avoid circular loops by preventing it from opening in the current app + this.openExternalExcludeCurrentApp(activity, intent); + result.success(true); + // not catching FileUriExposedException explicitly because buildtools<24 doesn't know about it + } catch (java.lang.RuntimeException e) { + Log.d(LOG_TAG, url + " cannot be opened: " + e.toString()); + result.error(LOG_TAG, url + " cannot be opened!", null); + } + } + + /** + * Opens the intent, providing a chooser that excludes the current app to avoid + * circular loops. + */ + private void openExternalExcludeCurrentApp(Activity activity, Intent intent) { + String currentPackage = activity.getPackageName(); + boolean hasCurrentPackage = false; + PackageManager pm = activity.getPackageManager(); + List activities = pm.queryIntentActivities(intent, 0); + ArrayList targetIntents = new ArrayList(); + for (ResolveInfo ri : activities) { + if (!currentPackage.equals(ri.activityInfo.packageName)) { + Intent targetIntent = (Intent) intent.clone(); + targetIntent.setPackage(ri.activityInfo.packageName); + targetIntents.add(targetIntent); + } else { + hasCurrentPackage = true; + } + } + // If the current app package isn't a target for this URL, then use + // the normal launch behavior + if (!hasCurrentPackage || targetIntents.size() == 0) { + activity.startActivity(intent); + } + // If there's only one possible intent, launch it directly + else if (targetIntents.size() == 1) { + activity.startActivity(targetIntents.get(0)); + } + // Otherwise, show a custom chooser without the current app listed + else if (targetIntents.size() > 0) { + Intent chooser = Intent.createChooser(targetIntents.remove(targetIntents.size() - 1), null); + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[]{})); + activity.startActivity(chooser); + } + } + + public void open(Activity activity, String uuid, String uuidFallback, String url, HashMap options, Map headers, boolean useChromeSafariBrowser, HashMap optionsFallback, Result result) { + + Intent intent = null; + Bundle extras = new Bundle(); + extras.putString("fromActivity", activity.getClass().getName()); + extras.putString("url", url); + extras.putBoolean("isData", false); + extras.putString("uuid", uuid); + extras.putSerializable("options", options); + extras.putSerializable("headers", (Serializable) headers); + + if (useChromeSafariBrowser && CustomTabActivityHelper.isAvailable(activity)) { + intent = new Intent(activity, ChromeCustomTabsActivity.class); + } + // check for webview fallback + else if (useChromeSafariBrowser && !CustomTabActivityHelper.isAvailable(activity) && !uuidFallback.isEmpty()) { + Log.d(LOG_TAG, "WebView fallback declared."); + // overwrite with extras fallback parameters + extras.putString("uuid", uuidFallback); + if (optionsFallback != null) + extras.putSerializable("options", optionsFallback); + else + extras.putSerializable("options", (new InAppBrowserOptions()).getHashMap()); + extras.putSerializable("headers", (Serializable) headers); + intent = new Intent(activity, InAppBrowserActivity.class); + } + // native webview + else if (!useChromeSafariBrowser) { + intent = new Intent(activity, InAppBrowserActivity.class); + } + else { + Log.d(LOG_TAG, "No WebView fallback declared."); + } + + if (intent != null) { + intent.putExtras(extras); + activity.startActivity(intent); + result.success(true); + return; + } + + result.error(LOG_TAG, "No WebView fallback declared.", null); + } + + public void openData(Activity activity, String uuid, HashMap options, String data, String mimeType, String encoding, String baseUrl) { + Intent intent = new Intent(activity, InAppBrowserActivity.class); + Bundle extras = new Bundle(); + + extras.putBoolean("isData", true); + extras.putString("uuid", uuid); + extras.putSerializable("options", options); + extras.putString("data", data); + extras.putString("mimeType", mimeType); + extras.putString("encoding", encoding); + extras.putString("baseUrl", baseUrl); + + intent.putExtras(extras); + activity.startActivity(intent); + } + + private String getUrl(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.getUrl(); + return null; + } + + private String getTitle(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.getWebViewTitle(); + return null; + } + + private Integer getProgress(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.getProgress(); + return null; + } + + public void loadUrl(String uuid, String url, Map headers, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) { + if (headers != null) + inAppBrowserActivity.loadUrl(url, headers, result); + else + inAppBrowserActivity.loadUrl(url, result); + } + } + + public void postUrl(String uuid, String url, byte[] postData, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.postUrl(url, postData, result); + } + + public void loadData(String uuid, String data, String mimeType, String encoding, String baseUrl, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.loadData(data, mimeType, encoding, baseUrl, result); + } + + public void loadFile(String uuid, String url, Map headers, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) { + if (headers != null) + inAppBrowserActivity.loadFile(url, headers, result); + else + inAppBrowserActivity.loadFile(url, result); + } + } + + public void show(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.show(); + } + + public void hide(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.hide(); + } + + public void reload(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.reload(); + } + + public boolean isLoading(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.isLoading(); + return false; + } + + public boolean isHidden(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.isHidden; + return false; + } + + public void stopLoading(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.stopLoading(); + } + + public void goBack(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.goBack(); + } + + public boolean canGoBack(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.canGoBack(); + return false; + } + + public void goForward(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.goForward(); + } + + public boolean canGoForward(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.canGoForward(); + return false; + } + + public void goBackOrForward(String uuid, int steps) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.goBackOrForward(steps); + } + + public boolean canGoBackOrForward(String uuid, int steps) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.canGoBackOrForward(steps); + return false; + } + + public void close(Activity activity, final String uuid, final Result result) { + final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + + Map obj = new HashMap<>(); + obj.put("uuid", uuid); + channel.invokeMethod("onExit", obj); + + // The JS protects against multiple calls, so this should happen only when + // close() is called by other native code. + if (inAppBrowserActivity == null) { + if (result != null) { + result.success(true); + } + return; + } + + inAppBrowserActivity.webView.setWebViewClient(new WebViewClient() { + // NB: wait for about:blank before dismissing + public void onPageFinished(WebView view, String url) { + inAppBrowserActivity.close(); + } + }); + // NB: From SDK 19: "If you call methods on WebView from any thread + // other than your app's UI thread, it can cause unexpected results." + // http://developer.android.com/guide/webapps/migrating.html#Threads + inAppBrowserActivity.webView.loadUrl("about:blank"); + if (result != null) { + result.success(true); + } + } + }); + } + else if (result != null) { + result.success(true); + } + } + + public byte[] takeScreenshot(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.takeScreenshot(); + return null; + } + + public void setOptions(String uuid, InAppBrowserOptions options, HashMap optionsMap) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.setOptions(options, optionsMap); + } + + public HashMap getOptions(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.getOptions(); + return null; + } + + public HashMap getCopyBackForwardList(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + return inAppBrowserActivity.getCopyBackForwardList(); + return null; + } + + public void startSafeBrowsing(String uuid, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.startSafeBrowsing(result); + result.success(false); + } + + public void setSafeBrowsingWhitelist(String uuid, List hosts, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.setSafeBrowsingWhitelist(hosts, result); + result.success(false); + } + + public void clearCache(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.clearCache(); + } + + public void clearSslPreferences(String uuid) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.clearSslPreferences(); + } + + public void clearClientCertPreferences(String uuid, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.clearClientCertPreferences(result); + result.success(false); + } + + public void findAllAsync(String uuid, String find) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.findAllAsync(find); + } + + public void findNext(String uuid, Boolean forward, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.findNext(forward, result); + result.success(false); + } + + public void clearMatches(String uuid, Result result) { + InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); + if (inAppBrowserActivity != null) + inAppBrowserActivity.clearMatches(result); + result.success(false); + } + + public void dispose() { + channel.setMethodCallHandler(null); + for ( InAppBrowserActivity activity : webViewActivities.values()) { + activity.dispose(); + } + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java index 87b112f1..1494aaf2 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserActivity.java @@ -1,6 +1,5 @@ package com.pichillilorenzo.flutter_inappbrowser; -import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -18,6 +17,9 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.webkit.WebViewClient; import android.widget.ProgressBar; import android.widget.SearchView; @@ -26,13 +28,10 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebViewOptions import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import io.flutter.app.FlutterActivity; -import io.flutter.app.FlutterApplication; import io.flutter.plugin.common.MethodChannel; public class InAppBrowserActivity extends AppCompatActivity { @@ -71,7 +70,7 @@ public class InAppBrowserActivity extends AppCompatActivity { webViewOptions.parse(optionsMap); webView.options = webViewOptions; - InAppBrowserFlutterPlugin.instance.webViewActivities.put(uuid, this); + InAppBrowserFlutterPlugin.inAppBrowser.webViewActivities.put(uuid, this); actionBar = getSupportActionBar(); @@ -93,7 +92,7 @@ public class InAppBrowserActivity extends AppCompatActivity { Map obj = new HashMap<>(); obj.put("uuid", uuid); - InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onBrowserCreated", obj); + InAppBrowserFlutterPlugin.inAppBrowser.channel.invokeMethod("onBrowserCreated", obj); } @@ -257,7 +256,7 @@ public class InAppBrowserActivity extends AppCompatActivity { if (canGoBack()) goBack(); else if (options.closeOnCannotGoBack) - InAppBrowserFlutterPlugin.instance.close(this, uuid, null); + InAppBrowserFlutterPlugin.inAppBrowser.close(this, uuid, null); return true; } return super.onKeyDown(keyCode, event); @@ -356,7 +355,7 @@ public class InAppBrowserActivity extends AppCompatActivity { } public void closeButtonClicked(MenuItem item) { - InAppBrowserFlutterPlugin.instance.close(this, uuid, null); + InAppBrowserFlutterPlugin.inAppBrowser.close(this, uuid, null); } public byte[] takeScreenshot() { @@ -522,4 +521,18 @@ public class InAppBrowserActivity extends AppCompatActivity { else result.success(false); } + + public void dispose() { + if (webView != null) { + webView.setWebChromeClient(new WebChromeClient()); + webView.setWebViewClient(new WebViewClient() { + public void onPageFinished(WebView view, String url) { + webView.dispose(); + webView.destroy(); + webView = null; + } + }); + webView.loadUrl("about:blank"); + } + } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java index 9eb4e24e..a4247e05 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java @@ -1,754 +1,65 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - package com.pichillilorenzo.flutter_inappbrowser; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.os.Build; -import android.os.Parcelable; -import android.provider.Browser; -import android.net.Uri; -import android.os.Bundle; -import android.webkit.MimeTypeMap; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.util.Log; -import com.pichillilorenzo.flutter_inappbrowser.ChromeCustomTabs.ChromeCustomTabsActivity; -import com.pichillilorenzo.flutter_inappbrowser.ChromeCustomTabs.CustomTabActivityHelper; - -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.common.PluginRegistry; +import io.flutter.embedding.engine.plugins.FlutterPlugin; -/** - * InAppBrowserFlutterPlugin - */ -public class InAppBrowserFlutterPlugin implements MethodCallHandler { - - public static InAppBrowserFlutterPlugin instance; - public Registrar registrar; +public class InAppBrowserFlutterPlugin implements FlutterPlugin { + public PluginRegistry.Registrar registrar; public MethodChannel channel; - public Map webViewActivities = new HashMap<>(); - public Map chromeCustomTabsActivities = new HashMap<>(); - protected static final String LOG_TAG = "IABFlutterPlugin"; + protected static final String LOG_TAG = "InAppBrowserFlutterPlugin"; - public InAppBrowserFlutterPlugin(Registrar r) { - registrar = r; - channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser"); - channel.setMethodCallHandler(this); - } + public static InAppBrowser inAppBrowser; + public static MyCookieManager myCookieManager; + public static CredentialDatabaseHandler credentialDatabaseHandler; - /** - * Plugin registration. - */ - public static void registerWith(Registrar registrar) { - Activity activity = registrar.activity(); - // registrar.activity() may return null because of Flutter's background execution feature - // described here: https://medium.com/flutter-io/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124 - if (activity != null) { - instance = new InAppBrowserFlutterPlugin(registrar); + public InAppBrowserFlutterPlugin() {} - new MyCookieManager(registrar); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - new CredentialDatabaseHandler(registrar); - } + public static void registerWith(PluginRegistry.Registrar registrar) { + inAppBrowser = new InAppBrowser(registrar); - registrar - .platformViewRegistry() - .registerViewFactory( - "com.pichillilorenzo/flutter_inappwebview", new FlutterWebViewFactory(registrar, registrar.view())); + registrar + .platformViewRegistry() + .registerViewFactory( + "com.pichillilorenzo/flutter_inappwebview", new FlutterWebViewFactory(registrar, registrar.view())); + new MyCookieManager(registrar); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + new CredentialDatabaseHandler(registrar); } } @Override - public void onMethodCall(final MethodCall call, final Result result) { - String source; - String urlFile; - final Activity activity = registrar.activity(); - final String uuid = (String) call.argument("uuid"); - - switch (call.method) { - case "open": - boolean isData = (boolean) call.argument("isData"); - if (!isData) { - final String url_final = (String) call.argument("url"); - - final boolean useChromeSafariBrowser = (boolean) call.argument("useChromeSafariBrowser"); - - final Map headers = (Map) call.argument("headers"); - - Log.d(LOG_TAG, "use Chrome Custom Tabs = " + useChromeSafariBrowser); - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - - if (useChromeSafariBrowser) { - - final String uuidFallback = (String) call.argument("uuidFallback"); - - final HashMap options = (HashMap) call.argument("options"); - - final HashMap optionsFallback = (HashMap) call.argument("optionsFallback"); - - open(activity, uuid, uuidFallback, url_final, options, headers, true, optionsFallback, result); - } else { - - String url = url_final; - - final HashMap options = (HashMap) call.argument("options"); - - final boolean isLocalFile = (boolean) call.argument("isLocalFile"); - final boolean openWithSystemBrowser = (boolean) call.argument("openWithSystemBrowser"); - - if (isLocalFile) { - // check if the asset file exists - try { - url = Util.getUrlAsset(registrar, url); - } catch (IOException e) { - e.printStackTrace(); - result.error(LOG_TAG, url + " asset file cannot be found!", e); - return; - } - } - // SYSTEM - if (openWithSystemBrowser) { - Log.d(LOG_TAG, "in system"); - openExternal(activity, url, result); - } else { - //Load the dialer - if (url.startsWith(WebView.SCHEME_TEL)) { - try { - Log.d(LOG_TAG, "loading in dialer"); - Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(url)); - activity.startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - Log.e(LOG_TAG, "Error dialing " + url + ": " + e.toString()); - } - } - // load in InAppBrowserFlutterPlugin - else { - Log.d(LOG_TAG, "loading in InAppBrowserFlutterPlugin"); - open(activity, uuid, null, url, options, headers, false, null, result); - } - } - } - } - }); - } - else { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - HashMap options = (HashMap) call.argument("options"); - String data = (String) call.argument("data"); - String mimeType = (String) call.argument("mimeType"); - String encoding = (String) call.argument("encoding"); - String baseUrl = (String) call.argument("baseUrl"); - openData(activity, uuid, options, data, mimeType, encoding, baseUrl); - result.success(true); - } - }); - } - break; - case "getUrl": - result.success(getUrl(uuid)); - break; - case "getTitle": - result.success(getTitle(uuid)); - break; - case "getProgress": - result.success(getProgress(uuid)); - break; - case "loadUrl": - loadUrl(uuid, (String) call.argument("url"), (Map) call.argument("headers"), result); - break; - case "postUrl": - postUrl(uuid, (String) call.argument("url"), (byte[]) call.argument("postData"), result); - break; - case "loadData": - { - String data = (String) call.argument("data"); - String mimeType = (String) call.argument("mimeType"); - String encoding = (String) call.argument("encoding"); - String baseUrl = (String) call.argument("baseUrl"); - loadData(uuid, data, mimeType, encoding, baseUrl, result); - } - break; - case "loadFile": - loadFile(uuid, (String) call.argument("url"), (Map) call.argument("headers"), result); - break; - case "close": - close(activity, uuid, result); - break; - case "injectScriptCode": - source = (String) call.argument("source"); - injectScriptCode(uuid, source, result); - break; - case "injectScriptFile": - urlFile = (String) call.argument("urlFile"); - injectScriptFile(uuid, urlFile); - result.success(true); - break; - case "injectStyleCode": - source = (String) call.argument("source"); - injectStyleCode(uuid, source); - result.success(true); - break; - case "injectStyleFile": - urlFile = (String) call.argument("urlFile"); - injectStyleFile(uuid, urlFile); - result.success(true); - break; - case "show": - show(uuid); - result.success(true); - break; - case "hide": - hide(uuid); - result.success(true); - break; - case "reload": - reload(uuid); - result.success(true); - break; - case "goBack": - goBack(uuid); - result.success(true); - break; - case "canGoBack": - result.success(canGoBack(uuid)); - break; - case "goForward": - goForward(uuid); - result.success(true); - break; - case "canGoForward": - result.success(canGoForward(uuid)); - break; - case "goBackOrForward": - goBackOrForward(uuid, (Integer) call.argument("steps")); - result.success(true); - break; - case "canGoBackOrForward": - result.success(canGoBackOrForward(uuid, (Integer) call.argument("steps"))); - break; - case "stopLoading": - stopLoading(uuid); - result.success(true); - break; - case "isLoading": - result.success(isLoading(uuid)); - break; - case "isHidden": - result.success(isHidden(uuid)); - break; - case "takeScreenshot": - result.success(takeScreenshot(uuid)); - break; - case "setOptions": - { - String optionsType = (String) call.argument("optionsType"); - switch (optionsType){ - case "InAppBrowserOptions": - InAppBrowserOptions inAppBrowserOptions = new InAppBrowserOptions(); - HashMap inAppBrowserOptionsMap = (HashMap) call.argument("options"); - inAppBrowserOptions.parse(inAppBrowserOptionsMap); - setOptions(uuid, inAppBrowserOptions, inAppBrowserOptionsMap); - break; - default: - result.error(LOG_TAG, "Options " + optionsType + " not available.", null); - } - } - result.success(true); - break; - case "getOptions": - result.success(getOptions(uuid)); - break; - case "getCopyBackForwardList": - result.success(getCopyBackForwardList(uuid)); - break; - case "startSafeBrowsing": - startSafeBrowsing(uuid, result); - break; - case "setSafeBrowsingWhitelist": - setSafeBrowsingWhitelist(uuid, (List) call.argument("hosts"), result); - break; - case "clearCache": - clearCache(uuid); - result.success(true); - break; - case "clearSslPreferences": - clearSslPreferences(uuid); - result.success(true); - break; - case "clearClientCertPreferences": - clearClientCertPreferences(uuid, result); - break; - case "findAllAsync": - String find = (String) call.argument("find"); - findAllAsync(uuid, find); - result.success(true); - break; - case "findNext": - Boolean forward = (Boolean) call.argument("forward"); - findNext(uuid, forward, result); - break; - case "clearMatches": - clearMatches(uuid, result); - break; - default: - result.notImplemented(); - } - - } - - private void injectScriptCode(String uuid, String source, final Result result) { - final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) { - inAppBrowserActivity.injectScriptCode(source, result); - } else { - Log.d(LOG_TAG, "webView is null"); + public void onAttachedToEngine(FlutterPluginBinding binding) { + //BinaryMessenger messenger = binding.getFlutterEngine().getDartExecutor(); + inAppBrowser = new InAppBrowser(registrar); + binding + .getFlutterEngine() + .getPlatformViewsController() + .getRegistry() + .registerViewFactory( + "com.pichillilorenzo/flutter_inappwebview", new FlutterWebViewFactory(registrar,null)); + myCookieManager = new MyCookieManager(registrar); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + credentialDatabaseHandler = new CredentialDatabaseHandler(registrar); } } - private void injectScriptFile(String uuid, String urlFile) { - final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) { - inAppBrowserActivity.injectScriptFile(urlFile); + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + if (inAppBrowser != null) { + inAppBrowser.dispose(); + inAppBrowser = null; } - } - - private void injectStyleCode(String uuid, String source) { - final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) { - inAppBrowserActivity.injectStyleCode(source); + if (myCookieManager != null) { + myCookieManager.dispose(); + myCookieManager = null; } - } - - private void injectStyleFile(String uuid, String urlFile) { - final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) { - inAppBrowserActivity.injectStyleFile(urlFile); + if (credentialDatabaseHandler != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + credentialDatabaseHandler.dispose(); + credentialDatabaseHandler = null; } } - - public static String getMimeType(String url) { - String type = null; - String extension = MimeTypeMap.getFileExtensionFromUrl(url); - if (extension != null) { - type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - } - return type; - } - - /** - * Display a new browser with the specified URL. - * - * @param url the url to load. - * @param result - * @return "" if ok, or error message. - */ - public void openExternal(Activity activity, String url, Result result) { - try { - Intent intent; - intent = new Intent(Intent.ACTION_VIEW); - // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent". - // Adding the MIME type to http: URLs causes them to not be handled by the downloader. - Uri uri = Uri.parse(url); - if ("file".equals(uri.getScheme())) { - intent.setDataAndType(uri, getMimeType(url)); - } else { - intent.setData(uri); - } - intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName()); - // CB-10795: Avoid circular loops by preventing it from opening in the current app - this.openExternalExcludeCurrentApp(activity, intent); - result.success(true); - // not catching FileUriExposedException explicitly because buildtools<24 doesn't know about it - } catch (java.lang.RuntimeException e) { - Log.d(LOG_TAG, url + " cannot be opened: " + e.toString()); - result.error(LOG_TAG, url + " cannot be opened!", null); - } - } - - /** - * Opens the intent, providing a chooser that excludes the current app to avoid - * circular loops. - */ - private void openExternalExcludeCurrentApp(Activity activity, Intent intent) { - String currentPackage = activity.getPackageName(); - boolean hasCurrentPackage = false; - PackageManager pm = activity.getPackageManager(); - List activities = pm.queryIntentActivities(intent, 0); - ArrayList targetIntents = new ArrayList(); - for (ResolveInfo ri : activities) { - if (!currentPackage.equals(ri.activityInfo.packageName)) { - Intent targetIntent = (Intent) intent.clone(); - targetIntent.setPackage(ri.activityInfo.packageName); - targetIntents.add(targetIntent); - } else { - hasCurrentPackage = true; - } - } - // If the current app package isn't a target for this URL, then use - // the normal launch behavior - if (!hasCurrentPackage || targetIntents.size() == 0) { - activity.startActivity(intent); - } - // If there's only one possible intent, launch it directly - else if (targetIntents.size() == 1) { - activity.startActivity(targetIntents.get(0)); - } - // Otherwise, show a custom chooser without the current app listed - else if (targetIntents.size() > 0) { - Intent chooser = Intent.createChooser(targetIntents.remove(targetIntents.size() - 1), null); - chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[]{})); - activity.startActivity(chooser); - } - } - - public void open(Activity activity, String uuid, String uuidFallback, String url, HashMap options, Map headers, boolean useChromeSafariBrowser, HashMap optionsFallback, Result result) { - - Intent intent = null; - Bundle extras = new Bundle(); - extras.putString("fromActivity", activity.getClass().getName()); - extras.putString("url", url); - extras.putBoolean("isData", false); - extras.putString("uuid", uuid); - extras.putSerializable("options", options); - extras.putSerializable("headers", (Serializable) headers); - - if (useChromeSafariBrowser && CustomTabActivityHelper.isAvailable(activity)) { - intent = new Intent(activity, ChromeCustomTabsActivity.class); - } - // check for webview fallback - else if (useChromeSafariBrowser && !CustomTabActivityHelper.isAvailable(activity) && !uuidFallback.isEmpty()) { - Log.d(LOG_TAG, "WebView fallback declared."); - // overwrite with extras fallback parameters - extras.putString("uuid", uuidFallback); - if (optionsFallback != null) - extras.putSerializable("options", optionsFallback); - else - extras.putSerializable("options", (new InAppBrowserOptions()).getHashMap()); - extras.putSerializable("headers", (Serializable) headers); - intent = new Intent(activity, InAppBrowserActivity.class); - } - // native webview - else if (!useChromeSafariBrowser) { - intent = new Intent(activity, InAppBrowserActivity.class); - } - else { - Log.d(LOG_TAG, "No WebView fallback declared."); - } - - if (intent != null) { - intent.putExtras(extras); - activity.startActivity(intent); - result.success(true); - return; - } - - result.error(LOG_TAG, "No WebView fallback declared.", null); - } - - public void openData(Activity activity, String uuid, HashMap options, String data, String mimeType, String encoding, String baseUrl) { - Intent intent = new Intent(activity, InAppBrowserActivity.class); - Bundle extras = new Bundle(); - - extras.putBoolean("isData", true); - extras.putString("uuid", uuid); - extras.putSerializable("options", options); - extras.putString("data", data); - extras.putString("mimeType", mimeType); - extras.putString("encoding", encoding); - extras.putString("baseUrl", baseUrl); - - intent.putExtras(extras); - activity.startActivity(intent); - } - - private String getUrl(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.getUrl(); - return null; - } - - private String getTitle(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.getWebViewTitle(); - return null; - } - - private Integer getProgress(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.getProgress(); - return null; - } - - public void loadUrl(String uuid, String url, Map headers, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) { - if (headers != null) - inAppBrowserActivity.loadUrl(url, headers, result); - else - inAppBrowserActivity.loadUrl(url, result); - } - } - - public void postUrl(String uuid, String url, byte[] postData, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.postUrl(url, postData, result); - } - - public void loadData(String uuid, String data, String mimeType, String encoding, String baseUrl, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.loadData(data, mimeType, encoding, baseUrl, result); - } - - public void loadFile(String uuid, String url, Map headers, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) { - if (headers != null) - inAppBrowserActivity.loadFile(url, headers, result); - else - inAppBrowserActivity.loadFile(url, result); - } - } - - public void show(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.show(); - } - - public void hide(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.hide(); - } - - public void reload(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.reload(); - } - - public boolean isLoading(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.isLoading(); - return false; - } - - public boolean isHidden(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.isHidden; - return false; - } - - public void stopLoading(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.stopLoading(); - } - - public void goBack(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.goBack(); - } - - public boolean canGoBack(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.canGoBack(); - return false; - } - - public void goForward(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.goForward(); - } - - public boolean canGoForward(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.canGoForward(); - return false; - } - - public void goBackOrForward(String uuid, int steps) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.goBackOrForward(steps); - } - - public boolean canGoBackOrForward(String uuid, int steps) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.canGoBackOrForward(steps); - return false; - } - - public void close(Activity activity, final String uuid, final Result result) { - final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - - Map obj = new HashMap<>(); - obj.put("uuid", uuid); - channel.invokeMethod("onExit", obj); - - // The JS protects against multiple calls, so this should happen only when - // close() is called by other native code. - if (inAppBrowserActivity == null) { - if (result != null) { - result.success(true); - } - return; - } - - inAppBrowserActivity.webView.setWebViewClient(new WebViewClient() { - // NB: wait for about:blank before dismissing - public void onPageFinished(WebView view, String url) { - inAppBrowserActivity.close(); - } - }); - // NB: From SDK 19: "If you call methods on WebView from any thread - // other than your app's UI thread, it can cause unexpected results." - // http://developer.android.com/guide/webapps/migrating.html#Threads - inAppBrowserActivity.webView.loadUrl("about:blank"); - if (result != null) { - result.success(true); - } - } - }); - } - else if (result != null) { - result.success(true); - } - } - - public byte[] takeScreenshot(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.takeScreenshot(); - return null; - } - - public void setOptions(String uuid, InAppBrowserOptions options, HashMap optionsMap) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.setOptions(options, optionsMap); - } - - public HashMap getOptions(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.getOptions(); - return null; - } - - public HashMap getCopyBackForwardList(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - return inAppBrowserActivity.getCopyBackForwardList(); - return null; - } - - public void startSafeBrowsing(String uuid, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.startSafeBrowsing(result); - result.success(false); - } - - public void setSafeBrowsingWhitelist(String uuid, List hosts, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.setSafeBrowsingWhitelist(hosts, result); - result.success(false); - } - - public void clearCache(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.clearCache(); - } - - public void clearSslPreferences(String uuid) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.clearSslPreferences(); - } - - public void clearClientCertPreferences(String uuid, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.clearClientCertPreferences(result); - result.success(false); - } - - public void findAllAsync(String uuid, String find) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.findAllAsync(find); - } - - public void findNext(String uuid, Boolean forward, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.findNext(forward, result); - result.success(false); - } - - public void clearMatches(String uuid, Result result) { - InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid); - if (inAppBrowserActivity != null) - inAppBrowserActivity.clearMatches(result); - result.success(false); - } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java index 58f81d56..fad45c5d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java @@ -2,7 +2,7 @@ package com.pichillilorenzo.flutter_inappbrowser; public class InAppBrowserOptions extends Options { - static final String LOG_TAG = "InAppBrowserOptions"; + public static final String LOG_TAG = "InAppBrowserOptions"; public boolean hidden = false; public boolean toolbarTop = true; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebChromeClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebChromeClient.java index 79073cac..86a8f75f 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebChromeClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebChromeClient.java @@ -538,6 +538,6 @@ public class InAppWebChromeClient extends WebChromeClient { } private MethodChannel getChannel() { - return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel; + return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.inAppBrowser.channel : flutterWebView.channel; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java index 3fa73093..33d2bcfa 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebView.java @@ -686,7 +686,7 @@ final public class InAppWebView extends InputAwareWebView { } private MethodChannel getChannel() { - return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel; + return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.inAppBrowser.channel : flutterWebView.channel; } public void startSafeBrowsing(final MethodChannel.Result result) { @@ -745,6 +745,11 @@ final public class InAppWebView extends InputAwareWebView { webSettings.setBuiltInZoomControls(enabled); } + @Override + public void dispose() { + super.dispose(); + } + @Override public void destroy() { super.destroy(); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java index 093fcb58..7a2053b9 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewClient.java @@ -224,7 +224,7 @@ public class InAppWebViewClient extends WebViewClient { previousAuthRequestFailureCount = 0; credentialsProposed = null; - // CB-10395 InAppBrowserFlutterPlugin's WebView not storing cookies reliable to local device storage + // CB-10395 InAppBrowser's WebView not storing cookies reliable to local device storage if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().flush(); } else { @@ -667,7 +667,7 @@ public class InAppWebViewClient extends WebViewClient { } private MethodChannel getChannel() { - return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel; + return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.inAppBrowser.channel : flutterWebView.channel; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewOptions.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewOptions.java index 116d5e3a..ae2e2042 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewOptions.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InAppWebViewOptions.java @@ -10,7 +10,7 @@ import java.util.Map; public class InAppWebViewOptions extends Options { - static final String LOG_TAG = "InAppWebViewOptions"; + public static final String LOG_TAG = "InAppWebViewOptions"; public boolean useShouldOverrideUrlLoading = false; public boolean useOnLoadResource = false; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InputAwareWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InputAwareWebView.java index 7c6754d7..7e36b36d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InputAwareWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppWebView/InputAwareWebView.java @@ -4,6 +4,7 @@ import static android.content.Context.INPUT_METHOD_SERVICE; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; @@ -15,8 +16,8 @@ import android.webkit.WebView; * https://github.com/flutter/plugins/blob/master/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java */ public class InputAwareWebView extends WebView { + private static final String LOG_TAG = "InputAwareWebView"; public View containerView; - private View threadedInputConnectionProxyView; private ThreadedInputConnectionProxyAdapterView proxyAdapterView; @@ -40,6 +41,19 @@ public class InputAwareWebView extends WebView { this.containerView = null; } + public void setContainerView(View containerView) { + this.containerView = containerView; + + if (proxyAdapterView == null) { + return; + } + + Log.w(LOG_TAG, "The containerView has changed while the proxyAdapterView exists."); + if (containerView != null) { + setInputConnectionTarget(proxyAdapterView); + } + } + /** * Set our proxy adapter view to use its cached input connection instead of creating new ones. * @@ -82,8 +96,6 @@ public class InputAwareWebView extends WebView { */ @Override public boolean checkInputConnectionProxy(final View view) { - if (containerView == null) - return super.checkInputConnectionProxy(view); // Check to see if the view param is WebView's ThreadedInputConnectionProxyView. View previousProxy = threadedInputConnectionProxyView; threadedInputConnectionProxyView = view; @@ -91,6 +103,12 @@ public class InputAwareWebView extends WebView { // This isn't a new ThreadedInputConnectionProxyView. Ignore it. return super.checkInputConnectionProxy(view); } + if (containerView == null) { + Log.e( + LOG_TAG, + "Can't create a proxy view because there's no container view. Text input may not work."); + return super.checkInputConnectionProxy(view); + } // We've never seen this before, so we make the assumption that this is WebView's // ThreadedInputConnectionProxyView. We are making the assumption that the only view that could @@ -115,8 +133,7 @@ public class InputAwareWebView extends WebView { @Override public void clearFocus() { super.clearFocus(); - if (containerView != null) - resetInputConnection(); + resetInputConnection(); } /** @@ -131,6 +148,10 @@ public class InputAwareWebView extends WebView { // No need to reset the InputConnection to the default thread if we've never changed it. return; } + if (containerView == null) { + Log.e(LOG_TAG, "Can't reset the input connection to the container view because there is none."); + return; + } setInputConnectionTarget(/*targetView=*/ containerView); } @@ -143,6 +164,13 @@ public class InputAwareWebView extends WebView { * InputConnections should be created on. */ private void setInputConnectionTarget(final View targetView) { + if (containerView == null) { + Log.e( + LOG_TAG, + "Can't set the input connection target because there is no containerView to use as a handler."); + return; + } + targetView.requestFocus(); containerView.post( new Runnable() { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java index d62e0bae..5fb96a30 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java @@ -11,8 +11,6 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebView; import org.json.JSONException; import org.json.JSONObject; -import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.util.HashMap; import java.util.Map; @@ -121,6 +119,6 @@ public class JavaScriptBridgeInterface { } private MethodChannel getChannel() { - return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel; + return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.inAppBrowser.channel : flutterWebView.channel; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/MyCookieManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/MyCookieManager.java index 1a3e4266..4c011e66 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/MyCookieManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/MyCookieManager.java @@ -226,4 +226,7 @@ public class MyCookieManager implements MethodChannel.MethodCallHandler { return sdf.format(new Date(timestamp)); } + public void dispose() { + channel.setMethodCallHandler(null); + } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Util.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Util.java index 4d3a4e13..d2c0111b 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Util.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Util.java @@ -13,14 +13,24 @@ import java.security.Key; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; +import okhttp3.OkHttpClient; public class Util { @@ -148,4 +158,50 @@ public class Util { this.certificates = certificates; } } + + public static OkHttpClient getUnsafeOkHttpClient() { + try { + // Create a trust manager that does not validate certificate chains + final TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } + } + }; + + // Install the all-trusting trust manager + final SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + // Create an ssl socket factory with our all-trusting manager + final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]); + builder.hostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + + OkHttpClient okHttpClient = builder + .connectTimeout(15, TimeUnit.SECONDS) + .writeTimeout(15, TimeUnit.SECONDS) + .readTimeout(15, TimeUnit.SECONDS) + .build(); + return okHttpClient; + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 53ae0ae4..41672498 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,4 @@ -android.enableJetifier=true -android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true \ No newline at end of file diff --git a/example/assets/favicon.ico b/example/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e6adc1c6e75b4301b09e4b74c76d940fd01babc6 GIT binary patch literal 17694 zcmeHPd2m!k8t<7LfQo{IK#&{Pg@hoAM?rubAe1PS0=d8gtG3pxwY60%Sc@fSN-?-7 z1PPdvD}wH_C09ZM${`6MgoH~$L_}o|6htJ3a7>Qg-`DT;OwXJ5lF4L--9Jp#N56UR zef|CV>+WB7f87tpY%GKgAI^~WXKD8^mc$tA-(UMZxGQ73aj#1k{dq8B=N@+dJ!Q1} z?~*vi1|tt-$!0+)-$4KI4T(X#JT{f@i(AO|#b>lzD&k(@6%)b(jhpi4oY{c;dC1QY z?kD+?myL@IG=3d>8s8UN>nGf@{1*a^KY-Y&3<1R-VC(_Ke-p3=4F65S9uWK*_5_Q* z8V{Rokyd%?0A@2z%3>htit%#?&_L9nIZk41W6}#(cEj&1IvxWA~^_do%h? zeSK1B^r9z1hMUQoQ*Q0yP2mW8{q~`0^`()u*Gi+fy9~we_J}dRD1N1Q2(MVsy*W1} z=yG=R?9)F5*R~J!qP-}1(MPVD ziCdYpvbbXp;`!8WT4_5i`QM2>*;bpEWaHKEc!S>puUQ%Bo?Z^(fx05;cLlqT_*Lvw zMh*W!_@CO3dEE;vW;j(}n>^RBNAmp(x}(h)uzOJl#`a^x?!>>iCHy9SC*0suQxVTJ zzN{-I?BRT6YOt%#NZe{_!9gm1!d{Tfo(Ct@%Kp(hegpQFa4Y4Zv7)ZXg}opp2wZ95 z{}$Sjia$Szr5drfjNcpVhIeR;0}s>{M>+j2vCGuiciZ6qRWsN?VEtV6yS%0`qT00m zcw%$47vnSS_qFZltJG%R?7>&g2oXM)xMgaJ-=Yqr|Iu##2)vCFvzq25+gr!))@~$r z_x7*(ov@?56Lz$DA$r?s5B~gQd#;LEy*9@$G0IfMO83;?N}GY-SItzgQyC2S>9=Ve znDk-kf8bxJVpgwP#ovnWiFZYN7uZ92f$ICV)Im%5Q&|EqUs7?)>$c-}`Ch|56m6y_ z{2r>cn*iWnJ&SeBPqO_(VpdaM@jH1&VDBKXi*_FX{4{^dOJD|ggcDFKBG-Jh_MrK@z-)o7wP%)X-UsZm4lo|P z0Q}7we)sjG#E$rkbKTS$Myuz6z%J_GDHegb^HC#yGwc$B6-KN3QqMAV&3RgFGt%#B ziaK~v#ec1oVoMflZP`}!)Mv~xpLSxWa;!>WeSrUqgNQ#|-$83WJuXJ^8EGdFsUNV* zdH~11QoVQpzp^b7F}-f+r+Pdu5V$E%KVqjcyf=*vJo4^6XDTxy#Cj<|nbsFl5H|q# z_(f8*^;qGoPJF`)cT}&Nb*snNBz@Y}^vLqzzdMpO?9Asm_wwTqNANTFv*54Jh^imgA;k_j5V^XW0Evj{ghnK9=L)&1~-~MsKb~Kd*>;jaS5d zg?`?s^z}Mk5x;@&kMrBUs5f|geFyu&zvUINe*peW$lLfmh3}YftFQU}s`sl0{Otki zNc|yk5PzOC!+EB7wE2yYV(%n&8{T^#^N+g#rCoo>V-)5IUm!m^2hs+KJx_hMJ;r%i zd<<-!Y@1yt#_xIgIGh&r?XR7-$xlAp=apvVwmJ{vEU&R{zUPM~-rd&HH|%~O2IpE% z(zz2&#vt`ZPzKnj8GA6UZ?<9ssO8M*5PQ9%#A^sV2wKVc1#j-SM>sb_v zS<{)5NM8A7coWt~KjkFZ&*Qww1rl6e%$gpu#>{?YM&Fp)tx+f2d!~fe#`_n9Ar|De z26V0mS0n~)GTP6Y%3^lS?|JgdHi`kaus2TeVGq&Y7iT_(AnrC~dDo%+)j&V(4P$3EjSXc!v`kz5zpK0Zr|35eZ@8~&^~5m2JHnZd$3j*OzfllnZDYuD;}!ZehPc2 zsb1JOv_q=@++=n?_8I>rG34TGPs{#C`JMIMldpztk3B@I@|)P#&WPBTdTrlX-|)F^ z+iUxt{@+aeqx;}IVaRQz{b}_r_uRETNRK@*tMVJ{XHO5xBm3#;%#L$?dBQ&Nk6Zh9 z?aPV&E!(-e|Dax5**5qdwuWX62T)Dcz9BLUX6H4Lj$B3w{&hH;ti&vBYRk{x-GirEQ5LYZ{xlC%xwtZ4V6R zO)-Acez9fVl)she?Y*^fOWTzGc_iypwW$Br18?;Lw`iOu_R8t3h1Y_92|g}-wj*CR ztCL4tQ|*~uAI`e(f#W&DioPklzy7;*_w#Rsj6mwWe7AlCKf7*3@#%FByR~yOduI3e zxyiTis&S8F%~OQ9ZoPGE*ED8JvEX8U>`|X=t9W<~aFoYAj+jjm)_(O~u78U8fw*?t z_7R8QZJXlQ7rO<%sB`%vwh2Dk9JID|uBG(#T*D_r z`Jp9uHLrMr)q?vQvJ-+S zGyR`Yxw9s-T|4J?r?XtTu5PllG@eMEbM~GYY#T|rH>v9A^rM~JC>hHuehckN%p20e}IXfxnQ;DPWIjXaBf2I47ft_ z%zD8ywiW?xP`^Kv-9zCBvkb?2D|>UWvF`MWNjY0!AWR)udi))3EZo!#TQ8P90C zeRPh}<1ESc#=@E6To&~0NAkflTV{thN_{G;R$k(nzI>VNv#vFg(9cP-RTj()(RgNd zIJnuxGn(DLCR`!# z|Cc<%X78K}|Lur&G!W0gSHu;jJdL` zEx}Q^eqYvmP40*gx~^F=Xx{mCzY*mXJd+#@o(a);CWL2Cw>vhbb>?fH>FCr|d4}>i zUHBk)Cgu|6WxW35F}${Pv;)#uTM}~vdyV|!)=_S{5`!DhTz;&p;Fk-R7u&_tt%G99qSc{&M^O?+vETZ(KuA6JO z(D$2atXt4N4IOV4Zr0UBr-8xEuX~t70vGY<)ux7^*Vwb5vx$_>2n&`rDyrT ze1~j1l$O2GbAHudd+~M8bp)4m;A@}tl)_mZfh)*Em-K7ixhL#YLG-KVHazh4g-s7} zAsdk@a(4ZF`8971bhAsk>L0Jc8K_E}jo6>?6~=3ft3sNvj?d+1#4#yUOpQCQ;Glo{tH-7L~{TD literal 0 HcmV?d00001 diff --git a/example/assets/index.html b/example/assets/index.html index 93a71e2e..8b81f09b 100644 --- a/example/assets/index.html +++ b/example/assets/index.html @@ -8,6 +8,7 @@ +
@@ -26,6 +27,10 @@

Inline WebView

flutter logo

Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.

+

placeholder 100x50

diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh index 7626a9ca..42466e06 100755 --- a/example/ios/Flutter/flutter_export_environment.sh +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -5,6 +5,6 @@ export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappbro export "FLUTTER_TARGET=lib/main.dart" export "FLUTTER_BUILD_DIR=build" export "SYMROOT=${SOURCE_ROOT}/../build/ios" -export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios-release" +export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" diff --git a/example/lib/inline_example.screen.dart b/example/lib/inline_example.screen.dart index f5567ac4..dcd4cf32 100755 --- a/example/lib/inline_example.screen.dart +++ b/example/lib/inline_example.screen.dart @@ -83,11 +83,11 @@ class _InlineExampleScreenState extends State { BoxDecoration(border: Border.all(color: Colors.blueAccent)), child: InAppWebView( //initialUrl: "https://www.youtube.com/embed/M7lc1UVf-VE?playsinline=1", - initialUrl: "https://github.com", + //initialUrl: "https://github.com", //initialUrl: "chrome://safe-browsing/match?type=malware", //initialUrl: "http://192.168.1.20:8081/", //initialUrl: "https://192.168.1.20:4433/", - //initialFile: "assets/index.html", + initialFile: "assets/index.html", initialHeaders: {}, initialOptions: [ InAppWebViewOptions( @@ -143,8 +143,8 @@ class _InlineExampleScreenState extends State { controller.clearSslPreferences(); controller.clearClientCertPreferences(); } - //controller.findAllAsync("a"); - controller.getFavicon(); + //controller.findAllAsync("flutter"); + print(await controller.getFavicons()); }, onLoadError: (InAppWebViewController controller, String url, int code, String message) async { print("error $url: $code, $message"); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c74f3797..ab86227d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -48,6 +48,7 @@ flutter: - assets/page-2.html - assets/css/ - assets/images/ + - assets/favicon.ico # To add assets to your application, add an assets section, like this: # assets: diff --git a/ios/Classes/FlutterWebViewController.swift b/ios/Classes/FlutterWebViewController.swift index 1588095e..9202af9d 100755 --- a/ios/Classes/FlutterWebViewController.swift +++ b/ios/Classes/FlutterWebViewController.swift @@ -301,25 +301,9 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { } result(true) break - case "dispose": - dispose() - result(true) - break default: result(FlutterMethodNotImplemented) break } } - - public func dispose() { - if webView != nil { - webView!.IABController = nil - webView!.IAWController = nil - webView!.uiDelegate = nil - webView!.navigationDelegate = nil - webView!.scrollView.delegate = nil - webView!.stopLoading() - webView = nil - } - } } diff --git a/ios/Classes/InAppWebView.swift b/ios/Classes/InAppWebView.swift index dda0b4d0..601e15e5 100755 --- a/ios/Classes/InAppWebView.swift +++ b/ios/Classes/InAppWebView.swift @@ -238,7 +238,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi var IAWController: FlutterWebViewController? var options: InAppWebViewOptions? var currentURL: URL? - var WKNavigationMap: [String: [String: Any]] = [:] var startPageTime: Int64 = 0 static var credentialsProposed: [URLCredential] = [] @@ -786,13 +785,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi let app = UIApplication.shared if let url = navigationAction.request.url { - if url.absoluteString != url.absoluteString && (options?.useOnLoadResource)! { - WKNavigationMap[url.absoluteString] = [ - "startTime": currentTimeInMilliSeconds(), - "request": navigationAction.request - ] - } - // Handle target="_blank" if navigationAction.targetFrame == nil && (options?.useOnTargetBlank)! { onTargetBlank(url: url) @@ -834,17 +826,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { -// if (options?.useOnLoadResource)! { -// if let url = navigationResponse.response.url { -// if WKNavigationMap[url.absoluteString] != nil { -// let startResourceTime: Int64 = (WKNavigationMap[url.absoluteString]!["startTime"] as! Int64) -// let startTime: Int64 = startResourceTime - startPageTime; -// let duration: Int64 = currentTimeInMilliSeconds() - startResourceTime; -// onLoadResource(response: navigationResponse.response, fromRequest: WKNavigationMap[url.absoluteString]!["request"] as? URLRequest, withData: Data(), startTime: startTime, duration: duration) -// } -// } -// } - if (options?.useOnDownloadStart)! { let mimeType = navigationResponse.response.mimeType if let url = navigationResponse.response.url { @@ -875,7 +856,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - self.WKNavigationMap = [:] currentURL = url InAppWebView.credentialsProposed = [] onLoadStop(url: (currentURL?.absoluteString)!) diff --git a/ios/flutter_inappbrowser.podspec b/ios/flutter_inappbrowser.podspec index 08581b88..0c88c439 100755 --- a/ios/flutter_inappbrowser.podspec +++ b/ios/flutter_inappbrowser.podspec @@ -17,7 +17,7 @@ A new Flutter plugin. s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.ios.deployment_target = '8.0' + s.platform = '8.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } s.swift_version = '5.0' end diff --git a/lib/src/in_app_browser.dart b/lib/src/in_app_browser.dart index 35d16e36..61025078 100644 --- a/lib/src/in_app_browser.dart +++ b/lib/src/in_app_browser.dart @@ -268,7 +268,8 @@ class InAppBrowser { args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('optionsType', () => "InAppBrowserOptions"); Map options = await ChannelManager.channel.invokeMethod('getOptions', args); - options = options.cast(); + if (options != null) + options = options.cast(); return options; } diff --git a/lib/src/in_app_webview.dart b/lib/src/in_app_webview.dart index ab9c7941..6eeaa6b9 100755 --- a/lib/src/in_app_webview.dart +++ b/lib/src/in_app_webview.dart @@ -10,9 +10,10 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/gestures.dart'; +import 'package:html/parser.dart' show parse; + import 'types.dart'; import 'in_app_browser.dart'; -import 'channel_manager.dart'; import 'webview_options.dart'; /* @@ -255,15 +256,6 @@ class _InAppWebViewState extends State { InAppWebViewController _controller; - @override - void dispose() { - super.dispose(); - if (_controller != null) { - _controller._dispose(); - _controller = null; - } - } - @override Widget build(BuildContext context) { Map initialOptions = {}; @@ -273,7 +265,22 @@ class _InAppWebViewState extends State { }); if (defaultTargetPlatform == TargetPlatform.android) { - return GestureDetector( + return AndroidView( + viewType: 'com.pichillilorenzo/flutter_inappwebview', + onPlatformViewCreated: _onPlatformViewCreated, + gestureRecognizers: widget.gestureRecognizers, + layoutDirection: TextDirection.rtl, + creationParams: { + 'initialUrl': widget.initialUrl, + 'initialFile': widget.initialFile, + 'initialData': widget.initialData?.toMap(), + 'initialHeaders': widget.initialHeaders, + 'initialOptions': initialOptions + }, + creationParamsCodec: const StandardMessageCodec(), + ); + // onLongPress issue: https://github.com/flutter/plugins/blob/f31d16a6ca0c4bd6849cff925a00b6823973696b/packages/webview_flutter/lib/src/webview_android.dart#L31 + /*return GestureDetector( onLongPress: () {}, excludeFromSemantics: true, child: AndroidView( @@ -290,7 +297,7 @@ class _InAppWebViewState extends State { }, creationParamsCodec: const StandardMessageCodec(), ), - ); + );*/ } else if (defaultTargetPlatform == TargetPlatform.iOS) { return UiKitView( viewType: 'com.pichillilorenzo/flutter_inappwebview', @@ -593,105 +600,146 @@ class InAppWebViewController { return await _channel.invokeMethod('getProgress', args); } - ///Gets the favicon for the current page. - Future> getFavicon() async { - List favicons = []; - HttpClient client = new HttpClient(); - var url = Uri.parse(await getUrl()); - - var htmlRequest = await client.getUrl(url); - var html = await (await htmlRequest.close()).transform(Utf8Decoder()).join(); - /// TODO: parse HTML instead of using javascript code - - try { - List faviconsJs = json.decode(await injectScriptCode(""" -function flutter_inappbrowser_ger_favicons() { - var favicons = []; - var links = document.getElementsByTagName('link'); - for (var i = 0; i < links.length; i++) { - var link = links[i]; - if (link.rel.indexOf("icon") >= 0) { - favicons.push({ - rel: link.rel, - href: link.href, - sizes: link.sizes - }); - } + ///Gets the content html of the page. It first tries to get the content through javascript. + ///If this doesn't work, it tries to get the content reading the file: + ///- checking if it is an asset (`file:///`) or + ///- downloading it using an `HttpClient` through the WebView's current url. + Future getHtml() async { + var html = ""; + Map options = await getOptions(); + if (options != null && options["javaScriptEnabled"] == true) { + html = await injectScriptCode("window.document.getElementsByTagName('html')[0].outerHTML;"); + if (html.isNotEmpty) + return html; } - return favicons; -} -flutter_inappbrowser_ger_favicons(); - """)); - for(Map favicon in faviconsJs) { - String sizes = favicon["sizes"]; - if (sizes != null && sizes != "any") { - List sizesSplitted = sizes.split(" "); - for (String size in sizesSplitted) { - int width = int.parse(size.split("x")[0]); - int height = int.parse(size.split("x")[1]); - favicons.add(Favicon(url: favicon["href"], rel: favicon["rel"], width: width, height: height)); - } - } else { - favicons.add(Favicon(url: favicon["href"], rel: favicon["rel"], width: null, height: null)); - } + var webviewUrl = await getUrl(); + if (webviewUrl.startsWith("file:///")) { + var assetPathSplitted = webviewUrl.split("/flutter_assets/"); + var assetPath = assetPathSplitted[assetPathSplitted.length - 1]; + var bytes = await rootBundle.load(assetPath); + html = utf8.decode(bytes.buffer.asUint8List()); + } + else { + HttpClient client = new HttpClient(); + var url = Uri.parse(webviewUrl); + try { + var htmlRequest = await client.getUrl(url); + html = await (await htmlRequest.close()).transform(Utf8Decoder()).join(); + } catch (e) { + print(e); } - } catch (e) {} + } + return html; + } + ///Gets the list of all favicons for the current page. + Future> getFavicons() async { + List favicons = []; - var completer = new Completer>(); - var faviconData = new List(); + HttpClient client = new HttpClient(); + var webviewUrl = await getUrl(); + var url = (webviewUrl.startsWith("file:///")) ? Uri.file(webviewUrl) : Uri.parse(webviewUrl); + String manifestUrl; + var html = await getHtml(); + if (html.isEmpty) { + return favicons; + } + + var assetPathBase; + + if (webviewUrl.startsWith("file:///")) { + var assetPathSplitted = webviewUrl.split("/flutter_assets/"); + assetPathBase = assetPathSplitted[0] + "/flutter_assets/"; + } + + // get all link html elements + var document = parse(html); + var links = document.getElementsByTagName('link'); + for (var link in links) { + var attributes = link.attributes; + if (attributes["rel"] == "manifest") { + manifestUrl = attributes["href"]; + if (!_isUrlAbsolute(manifestUrl)) { + if (manifestUrl.startsWith("/")) { + manifestUrl = manifestUrl.substring(1); + } + manifestUrl = ((assetPathBase == null) ? url.scheme + "://" + url.host + "/" : assetPathBase) + manifestUrl; + } + continue; + } + if (!attributes["rel"].contains("icon")) { + continue; + } + favicons.addAll(_createFavicons(url, assetPathBase, attributes["href"], attributes["rel"], attributes["sizes"], false)); + } + + // try to get /favicon.ico try { var faviconUrl = url.scheme + "://" + url.host + "/favicon.ico"; await client.headUrl(Uri.parse(faviconUrl)); favicons.add(Favicon(url: faviconUrl, rel: "shortcut icon")); - } catch(e) {} - - var manifestRequest; - try { - var manifestJsonUrl = url.scheme + "://" + url.host + "/manifest.json"; - manifestRequest = await client.getUrl(Uri.parse(manifestJsonUrl)); } catch(e) { - /// TODO: find manifest throught rel="manifest" + print("/favicon.ico file not found: " + e.toString()); } - if (manifestRequest) { - Map manifest = json.decode(await (await manifestRequest.close()).transform(Utf8Decoder()).join()); + // try to get the manifest file + HttpClientRequest manifestRequest; + HttpClientResponse manifestResponse; + bool manifestFound = false; + if (manifestUrl == null) { + manifestUrl = url.scheme + "://" + url.host + "/manifest.json"; + } + try { + manifestRequest = await client.getUrl(Uri.parse(manifestUrl)); + manifestResponse = await manifestRequest.close(); + manifestFound = manifestResponse.statusCode == 200 && manifestResponse.headers.contentType?.mimeType == "application/json"; + } catch(e) { + print("Manifest file not found: " + e.toString()); + } + + if (manifestFound) { + Map manifest = json.decode(await manifestResponse.transform(Utf8Decoder()).join()); if (manifest.containsKey("icons")) { for(Map icon in manifest["icons"]) { - String url = icon["src"]; - List urlSplitted = url.split("/"); - String sizes = icon["sizes"]; - String rel = (sizes != null) ? urlSplitted[urlSplitted.length - 1].replaceFirst("-" + sizes, "").split(" ")[0].split(".")[0] : null; - if (sizes != null && sizes != "any") { - List sizesSplitted = sizes.split(" "); - for (String size in sizesSplitted) { - int width = int.parse(size.split("x")[0]); - int height = int.parse(size.split("x")[1]); - favicons.add(Favicon(url: url, rel: rel, width: width, height: height)); - } - } else { - favicons.add(Favicon(url: url, rel: rel, width: null, height: null)); - } + favicons.addAll(_createFavicons(url, assetPathBase, icon["src"], icon["rel"], icon["sizes"], true)); } } } - //print(favicons); + return favicons; + } - // solution found here: https://stackoverflow.com/a/15750809/4637638 - var googleFaviconUrl = Uri.parse("https://plus.google.com/_/favicon?domain_url=" + url.scheme + "://" + url.host); - client.getUrl(googleFaviconUrl).then((HttpClientRequest request) { - return request.close(); - }).then((HttpClientResponse response) { - response.listen((List data) { - faviconData = data; - }, onDone: () => completer.complete(faviconData)); - }).catchError((error) { - completer.completeError(error); - }); - return completer.future; + bool _isUrlAbsolute(String url) { + return url.startsWith("http://") || url.startsWith("https://"); + } + + List _createFavicons(Uri url, String assetPathBase, String urlIcon, String rel, String sizes, bool isManifest) { + List favicons = []; + + List urlSplitted = urlIcon.split("/"); + if (!_isUrlAbsolute(urlIcon)) { + if (urlIcon.startsWith("/")) { + urlIcon = urlIcon.substring(1); + } + urlIcon = ((assetPathBase == null) ? url.scheme + "://" + url.host + "/" : assetPathBase) + urlIcon; + } + if (isManifest) { + rel = (sizes != null) ? urlSplitted[urlSplitted.length - 1].replaceFirst("-" + sizes, "").split(" ")[0].split(".")[0] : null; + } + if (sizes != null && sizes.isNotEmpty && sizes != "any") { + List sizesSplitted = sizes.split(" "); + for (String size in sizesSplitted) { + int width = int.parse(size.split("x")[0]); + int height = int.parse(size.split("x")[1]); + favicons.add(Favicon(url: urlIcon, rel: rel, width: width, height: height)); + } + } else { + favicons.add(Favicon(url: urlIcon, rel: rel, width: null, height: null)); + } + + return favicons; } ///Loads the given [url] with optional [headers] specified as a map from name to value. @@ -1001,7 +1049,6 @@ flutter_inappbrowser_ger_favicons(); args.putIfAbsent('uuid', () => _inAppBrowserUuid); } args.putIfAbsent('options', () => options); - args.putIfAbsent('optionsType', () => "InAppBrowserOptions"); await _channel.invokeMethod('setOptions', args); } @@ -1012,9 +1059,9 @@ flutter_inappbrowser_ger_favicons(); _inAppBrowser.throwIsNotOpened(); args.putIfAbsent('uuid', () => _inAppBrowserUuid); } - args.putIfAbsent('optionsType', () => "InAppBrowserOptions"); - Map options = await ChannelManager.channel.invokeMethod('getOptions', args); - options = options.cast(); + Map options = await _channel.invokeMethod('getOptions', args); + if (options != null) + options = options.cast(); return options; } @@ -1183,9 +1230,4 @@ flutter_inappbrowser_ger_favicons(); } await _channel.invokeMethod('clearMatches', args); } - - ///Dispose/Destroy the WebView. - Future _dispose() async { - await _channel.invokeMethod('dispose'); - } } diff --git a/lib/src/webview_options.dart b/lib/src/webview_options.dart index f88a836f..518b7f55 100644 --- a/lib/src/webview_options.dart +++ b/lib/src/webview_options.dart @@ -43,6 +43,7 @@ class InAppWebViewOptions implements WebViewOptions, BrowserOptions, AndroidOpti this.resourceCustomSchemes = const [], this.contentBlockers = const [], this.preferredContentMode = InAppWebViewUserPreferredContentMode.RECOMMENDED}) { if (this.minimumFontSize == null) this.minimumFontSize = Platform.isAndroid ? 8 : 0; + assert(!this.resourceCustomSchemes.contains("http") && !this.resourceCustomSchemes.contains("https")); } @override diff --git a/nodejs_server_test_auth_basic_and_ssl/index.js b/nodejs_server_test_auth_basic_and_ssl/index.js index d52404db..a6004761 100644 --- a/nodejs_server_test_auth_basic_and_ssl/index.js +++ b/nodejs_server_test_auth_basic_and_ssl/index.js @@ -23,7 +23,16 @@ appHttps.get('/', (req, res) => { // `localhost`. if (req.client.authorized) { - res.send(`Hello ${cert.subject.CN}, your certificate was issued by ${cert.issuer.CN}!`) + res.send(` + + + + + +

Hello ${cert.subject.CN}, your certificate was issued by ${cert.issuer.CN}!

+ + + `) // They can still provide a certificate which is not accepted by us. Unfortunately, the `cert` object will be an empty // object instead of `null` if there is no certificate at all, so we have to check for a known field rather than // truthiness. @@ -34,6 +43,13 @@ appHttps.get('/', (req, res) => { } else { res.status(401).send(`Sorry, but you need to provide a client certificate to continue.`) } + res.end() +}) + +appHttps.get('/fakeResource', (req, res) => { + res.set("Content-Type", "text/javascript") + res.send(`alert("HI");`) + res.end() }) // Let's create our HTTPS server and we're ready to go. diff --git a/pubspec.yaml b/pubspec.yaml index 3923f656..efe4d795 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,14 +5,15 @@ author: Lorenzo Pichilli homepage: https://github.com/pichillilorenzo/flutter_inappbrowser environment: - sdk: ">=2.0.0-dev <3.0.0" - flutter: ">=0.10.1 <2.0.0" + sdk: ">=2.0.0-dev.68.0 <3.0.0" + flutter: ">=1.9.1+hotfix.5 <2.0.0" dependencies: flutter: sdk: flutter uuid: ^2.0.0 mime: ^0.9.6+2 + html: ^0.14.0+3 # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec