diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e066fbc..fc405027 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## 6.0.0-beta.24 +- Added InAppWebView keep alive feature +- Added `hasJavaScriptHandler`, `hasUserScript`, `hasWebMessageListener` InAppWebViewController methods +- `HeadlessInAppWebView.webViewController` could be `null` +- Removed `throwIfAlreadyOpened`, `throwIfNotOpened` InAppBrowser methods +- Removed `throwIfAlreadyOpened`, `throwIfNotOpened` ChromeSafariBrowser methods - Merged "fix #1389 #1315 contextMenu ios 13" [#1575](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1575) (thanks to [heralight](https://github.com/heralight)) - Merged "fix: remove ignored flutter_export_environment.sh" [#1593](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1593) (thanks to [Sunbreak](https://github.com/Sunbreak)) - Merged "Fix AndroidX migration URL in README.md" [#1529](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1529) (thanks to [cslee](https://github.com/cslee)) diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/FlutterWebViewFactory.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/FlutterWebViewFactory.java deleted file mode 100755 index da5d2b40..00000000 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/FlutterWebViewFactory.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.pichillilorenzo.flutter_inappwebview; - -import android.content.Context; - -import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebView; -import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebViewManager; -import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.FlutterWebView; -import com.pichillilorenzo.flutter_inappwebview.webview.PlatformWebView; -import com.pichillilorenzo.flutter_inappwebview.types.WebViewImplementation; - -import java.util.HashMap; - -import io.flutter.plugin.common.StandardMessageCodec; -import io.flutter.plugin.platform.PlatformView; -import io.flutter.plugin.platform.PlatformViewFactory; - -public class FlutterWebViewFactory extends PlatformViewFactory { - public static final String VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview"; - private final InAppWebViewFlutterPlugin plugin; - - public FlutterWebViewFactory(final InAppWebViewFlutterPlugin plugin) { - super(StandardMessageCodec.INSTANCE); - this.plugin = plugin; - } - - @Override - public PlatformView create(Context context, int id, Object args) { - HashMap params = (HashMap) args; - PlatformWebView flutterWebView = null; - - String headlessWebViewId = (String) params.get("headlessWebViewId"); - if (headlessWebViewId != null) { - HeadlessInAppWebView headlessInAppWebView = HeadlessInAppWebViewManager.webViews.get(headlessWebViewId); - if (headlessInAppWebView != null) { - flutterWebView = headlessInAppWebView.disposeAndGetFlutterWebView(); - } - } - - if (flutterWebView == null) { - WebViewImplementation implementation = WebViewImplementation.fromValue((Integer) params.get("implementation")); - switch (implementation) { - case NATIVE: - default: - flutterWebView = new FlutterWebView(plugin, context, id, params); - } - flutterWebView.makeInitialLoad(params); - } - - return flutterWebView; - } -} - diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java index 04ffb490..97b668b3 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java @@ -17,6 +17,8 @@ import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager; import com.pichillilorenzo.flutter_inappwebview.proxy.ProxyManager; import com.pichillilorenzo.flutter_inappwebview.service_worker.ServiceWorkerManager; import com.pichillilorenzo.flutter_inappwebview.tracing.TracingControllerManager; +import com.pichillilorenzo.flutter_inappwebview.webview.FlutterWebViewFactory; +import com.pichillilorenzo.flutter_inappwebview.webview.InAppWebViewManager; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; @@ -39,7 +41,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { @Nullable public ChromeSafariBrowserManager chromeSafariBrowserManager; @Nullable - public InAppWebViewStatic inAppWebViewStatic; + public InAppWebViewManager inAppWebViewManager; @Nullable public MyCookieManager myCookieManager; @Nullable @@ -56,11 +58,6 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { public PrintJobManager printJobManager; @Nullable public TracingControllerManager tracingControllerManager; - @Nullable - public static ValueCallback filePathCallbackLegacy; - @Nullable - public static ValueCallback filePathCallback; - public FlutterWebViewFactory flutterWebViewFactory; public Context applicationContext; public PluginRegistry.Registrar registrar; @@ -110,7 +107,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { FlutterWebViewFactory.VIEW_TYPE_ID, flutterWebViewFactory); platformUtil = new PlatformUtil(this); - inAppWebViewStatic = new InAppWebViewStatic(this); + inAppWebViewManager = new InAppWebViewManager(this); myCookieManager = new MyCookieManager(this); myWebStorage = new MyWebStorage(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { @@ -122,7 +119,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { webViewFeatureManager = new WebViewFeatureManager(this); proxyManager = new ProxyManager(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - printJobManager = new PrintJobManager(); + printJobManager = new PrintJobManager(this); } tracingControllerManager = new TracingControllerManager(this); } @@ -157,9 +154,9 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { credentialDatabaseHandler.dispose(); credentialDatabaseHandler = null; } - if (inAppWebViewStatic != null) { - inAppWebViewStatic.dispose(); - inAppWebViewStatic = null; + if (inAppWebViewManager != null) { + inAppWebViewManager.dispose(); + inAppWebViewManager = null; } if (serviceWorkerManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { serviceWorkerManager.dispose(); @@ -181,8 +178,6 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware { tracingControllerManager.dispose(); tracingControllerManager = null; } - filePathCallbackLegacy = null; - filePathCallback = null; } @Override diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyCookieManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyCookieManager.java index 991dba0b..4161d784 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyCookieManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyCookieManager.java @@ -393,6 +393,5 @@ public class MyCookieManager extends ChannelDelegateImpl { public void dispose() { super.dispose(); plugin = null; - cookieManager = null; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyWebStorage.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyWebStorage.java index ad3cc982..7e49bac8 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyWebStorage.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/MyWebStorage.java @@ -134,6 +134,5 @@ public class MyWebStorage extends ChannelDelegateImpl { public void dispose() { super.dispose(); plugin = null; - webStorageManager = null; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ActionBroadcastReceiver.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ActionBroadcastReceiver.java index 1b6ea859..abc5b31d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ActionBroadcastReceiver.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ActionBroadcastReceiver.java @@ -12,6 +12,7 @@ public class ActionBroadcastReceiver extends BroadcastReceiver { protected static final String LOG_TAG = "ActionBroadcastReceiver"; public static final String KEY_ACTION_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_ID"; public static final String KEY_ACTION_VIEW_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_VIEW_ID"; + public static final String KEY_ACTION_MANAGER_ID = "com.pichillilorenzo.flutter_inappwebview.ChromeCustomTabs.ACTION_MANAGER_ID"; public static final String KEY_URL_TITLE = "android.intent.extra.SUBJECT"; @Override @@ -21,19 +22,25 @@ public class ActionBroadcastReceiver extends BroadcastReceiver { if (url != null) { Bundle b = intent.getExtras(); String viewId = b.getString(KEY_ACTION_VIEW_ID); + String managerId = b.getString(KEY_ACTION_MANAGER_ID); - if (clickedId == -1) { - int id = b.getInt(KEY_ACTION_ID); - String title = b.getString(KEY_URL_TITLE); + if (managerId != null) { + ChromeSafariBrowserManager chromeSafariBrowserManager = ChromeSafariBrowserManager.shared.get(managerId); + if (chromeSafariBrowserManager != null) { + if (clickedId == -1) { + int id = b.getInt(KEY_ACTION_ID); + String title = b.getString(KEY_URL_TITLE); - ChromeCustomTabsActivity browser = ChromeSafariBrowserManager.browsers.get(viewId); - if (browser != null && browser.channelDelegate != null) { - browser.channelDelegate.onItemActionPerform(id, url, title); - } - } else { - ChromeCustomTabsActivity browser = ChromeSafariBrowserManager.browsers.get(viewId); - if (browser != null && browser.channelDelegate != null) { - browser.channelDelegate.onSecondaryItemActionPerform(browser.getResources().getResourceName(clickedId), url); + ChromeCustomTabsActivity browser = chromeSafariBrowserManager.browsers.get(viewId); + if (browser != null && browser.channelDelegate != null) { + browser.channelDelegate.onItemActionPerform(id, url, title); + } + } else { + ChromeCustomTabsActivity browser = chromeSafariBrowserManager.browsers.get(viewId); + if (browser != null && browser.channelDelegate != null) { + browser.channelDelegate.onSecondaryItemActionPerform(browser.getResources().getResourceName(clickedId), url); + } + } } } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsActivity.java index 7842a67b..84afb575 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeCustomTabsActivity.java @@ -82,7 +82,7 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable { manager = ChromeSafariBrowserManager.shared.get(managerId); if (manager == null || manager.plugin == null || manager.plugin.messenger == null) return; - ChromeSafariBrowserManager.browsers.put(id, this); + manager.browsers.put(id, this); MethodChannel channel = new MethodChannel(manager.plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id); channelDelegate = new ChromeCustomTabsChannelDelegate(this, channel); @@ -271,6 +271,7 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable { Bundle extras = new Bundle(); extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id); + extras.putString(ActionBroadcastReceiver.KEY_ACTION_MANAGER_ID, manager != null ? manager.id : null); broadcastIntent.putExtras(extras); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -355,6 +356,7 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable { Bundle extras = new Bundle(); extras.putInt(ActionBroadcastReceiver.KEY_ACTION_ID, actionSourceId); extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id); + extras.putString(ActionBroadcastReceiver.KEY_ACTION_MANAGER_ID, manager != null ? manager.id : null); actionIntent.putExtras(extras); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -374,8 +376,10 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable { channelDelegate.dispose(); channelDelegate = null; } - if (ChromeSafariBrowserManager.browsers.containsKey(id)) { - ChromeSafariBrowserManager.browsers.put(id, null); + if (manager != null) { + if (manager.browsers.containsKey(id)) { + manager.browsers.put(id, null); + } } manager = null; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeSafariBrowserManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeSafariBrowserManager.java index fa1d168f..1df565fb 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeSafariBrowserManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/chrome_custom_tabs/ChromeSafariBrowserManager.java @@ -31,7 +31,7 @@ public class ChromeSafariBrowserManager extends ChannelDelegateImpl { public InAppWebViewFlutterPlugin plugin; public String id; public static final Map shared = new HashMap<>(); - public static final Map browsers = new HashMap<>(); + public final Map browsers = new HashMap<>(); public ChromeSafariBrowserManager(final InAppWebViewFlutterPlugin plugin) { super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME)); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java index 6540a749..bbac9f45 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java @@ -1,5 +1,6 @@ package com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview; +import android.app.Activity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -130,17 +131,21 @@ public class HeadlessInAppWebView implements Disposable { channelDelegate.dispose(); channelDelegate = null; } - if (HeadlessInAppWebViewManager.webViews.containsKey(id)) { - HeadlessInAppWebViewManager.webViews.put(id, null); - } - if (plugin != null && plugin.activity != null) { - ViewGroup contentView = plugin.activity.findViewById(android.R.id.content); - if (contentView != null) { - ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); - if (mainView != null && flutterWebView != null) { - View view = flutterWebView.getView(); - if (view != null) { - mainView.removeView(flutterWebView.getView()); + if (plugin != null) { + HeadlessInAppWebViewManager headlessInAppWebViewManager = plugin.headlessInAppWebViewManager; + if (headlessInAppWebViewManager != null && headlessInAppWebViewManager.webViews.containsKey(id)) { + headlessInAppWebViewManager.webViews.put(id, null); + } + Activity activity = plugin.activity; + if (activity != null) { + ViewGroup contentView = plugin.activity.findViewById(android.R.id.content); + if (contentView != null) { + ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); + if (mainView != null && flutterWebView != null) { + View view = flutterWebView.getView(); + if (view != null) { + mainView.removeView(flutterWebView.getView()); + } } } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java index 1324c2ed..c5175950 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java @@ -42,7 +42,7 @@ public class HeadlessInAppWebViewManager extends ChannelDelegateImpl { protected static final String LOG_TAG = "HeadlessInAppWebViewManager"; public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"; - public static final Map webViews = new HashMap<>(); + public final Map webViews = new HashMap<>(); @Nullable public InAppWebViewFlutterPlugin plugin; @@ -76,7 +76,7 @@ public class HeadlessInAppWebViewManager extends ChannelDelegateImpl { } FlutterWebView flutterWebView = new FlutterWebView(plugin, context, id, params); HeadlessInAppWebView headlessInAppWebView = new HeadlessInAppWebView(plugin, id, flutterWebView); - HeadlessInAppWebViewManager.webViews.put(id, headlessInAppWebView); + webViews.put(id, headlessInAppWebView); headlessInAppWebView.prepare(params); headlessInAppWebView.onWebViewCreated(); @@ -93,5 +93,6 @@ public class HeadlessInAppWebViewManager extends ChannelDelegateImpl { } } webViews.clear(); + plugin = null; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserActivity.java index 17476d78..0828c02c 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserActivity.java @@ -141,10 +141,12 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow prepareView(); if (windowId != -1) { - Message resultMsg = InAppWebViewChromeClient.windowWebViewMessages.get(windowId); - if (resultMsg != null) { - ((WebView.WebViewTransport) resultMsg.obj).setWebView(webView); - resultMsg.sendToTarget(); + if (webView.plugin != null && webView.plugin.inAppWebViewManager != null) { + Message resultMsg = webView.plugin.inAppWebViewManager.windowWebViewMessages.get(windowId); + if (resultMsg != null) { + ((WebView.WebViewTransport) resultMsg.obj).setWebView(webView); + resultMsg.sendToTarget(); + } } } else { String initialFile = b.getString("initialFile"); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobController.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobController.java index 28acd07a..b5c6a521 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobController.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobController.java @@ -21,6 +21,8 @@ public class PrintJobController implements Disposable { @NonNull public String id; @Nullable + public InAppWebViewFlutterPlugin plugin; + @Nullable public PrintJobChannelDelegate channelDelegate; @Nullable public android.print.PrintJob job; @@ -30,6 +32,7 @@ public class PrintJobController implements Disposable { public PrintJobController(@NonNull String id, @NonNull android.print.PrintJob job, @Nullable PrintJobSettings settings, @NonNull InAppWebViewFlutterPlugin plugin) { this.id = id; + this.plugin = plugin; this.job = job; this.settings = settings; final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id); @@ -61,12 +64,16 @@ public class PrintJobController implements Disposable { channelDelegate.dispose(); channelDelegate = null; } - if (PrintJobManager.jobs.containsKey(id)) { - PrintJobManager.jobs.put(id, null); + if (plugin != null) { + PrintJobManager printJobManager = plugin.printJobManager; + if (printJobManager != null && printJobManager.jobs.containsKey(id)) { + printJobManager.jobs.put(id, null); + } } if (job != null) { job = null; } + plugin = null; } @Override @@ -75,12 +82,16 @@ public class PrintJobController implements Disposable { channelDelegate.dispose(); channelDelegate = null; } - if (PrintJobManager.jobs.containsKey(id)) { - PrintJobManager.jobs.put(id, null); + if (plugin != null) { + PrintJobManager printJobManager = plugin.printJobManager; + if (printJobManager != null && printJobManager.jobs.containsKey(id)) { + printJobManager.jobs.put(id, null); + } } if (job != null) { job.cancel(); job = null; } + plugin = null; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobManager.java index fa4dcee4..13efa7e2 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/print_job/PrintJobManager.java @@ -23,8 +23,11 @@ package com.pichillilorenzo.flutter_inappwebview.print_job; import android.os.Build; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.types.Disposable; import java.util.Collection; @@ -34,11 +37,13 @@ import java.util.Map; @RequiresApi(api = Build.VERSION_CODES.KITKAT) public class PrintJobManager implements Disposable { protected static final String LOG_TAG = "PrintJobManager"; - - public static final Map jobs = new HashMap<>(); + @Nullable + public InAppWebViewFlutterPlugin plugin; + public final Map jobs = new HashMap<>(); - public PrintJobManager() { + public PrintJobManager(@NonNull final InAppWebViewFlutterPlugin plugin) { super(); + this.plugin = plugin; } public void dispose() { @@ -49,5 +54,6 @@ public class PrintJobManager implements Disposable { } } jobs.clear(); + plugin = null; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxyManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxyManager.java index 6b3b4a00..16e609ae 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxyManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/proxy/ProxyManager.java @@ -124,21 +124,6 @@ public class ProxyManager extends ChannelDelegateImpl { @Override public void dispose() { super.dispose(); - if (proxyController != null) { - // Clears the proxy settings - proxyController.clearProxyOverride(new Executor() { - @Override - public void execute(Runnable command) { - - } - }, new Runnable() { - @Override - public void run() { - - } - }); - proxyController = null; - } plugin = null; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/service_worker/ServiceWorkerManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/service_worker/ServiceWorkerManager.java index 74a52800..567e593a 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/service_worker/ServiceWorkerManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/service_worker/ServiceWorkerManager.java @@ -99,10 +99,6 @@ public class ServiceWorkerManager implements Disposable { channelDelegate.dispose(); channelDelegate = null; } - if (serviceWorkerController != null) { - serviceWorkerController.setServiceWorkerClient(dummyServiceWorkerClientCompat()); - serviceWorkerController = null; - } plugin = null; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/tracing/TracingControllerManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/tracing/TracingControllerManager.java index 3f46efa3..80595c99 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/tracing/TracingControllerManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/tracing/TracingControllerManager.java @@ -57,7 +57,6 @@ public class TracingControllerManager implements Disposable { channelDelegate.dispose(); channelDelegate = null; } - tracingController = null; plugin = null; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/WebViewImplementation.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/WebViewImplementation.java deleted file mode 100644 index 81d37d26..00000000 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/WebViewImplementation.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.pichillilorenzo.flutter_inappwebview.types; - -public enum WebViewImplementation { - NATIVE(0); - - private final int value; - - private WebViewImplementation(int value) { - this.value = value; - } - - public boolean equalsValue(int otherValue) { - return value == otherValue; - } - - public static WebViewImplementation fromValue(int value) { - for( WebViewImplementation type : WebViewImplementation.values()) { - if(value == type.value) - return type; - } - throw new IllegalArgumentException("No enum constant: " + value); - } - - public int rawValue() { - return this.value; - } - - @Override - public String toString() { - return String.valueOf(this.value); - } -} \ No newline at end of file diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/FlutterWebViewFactory.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/FlutterWebViewFactory.java new file mode 100755 index 00000000..ec89937b --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/FlutterWebViewFactory.java @@ -0,0 +1,81 @@ +package com.pichillilorenzo.flutter_inappwebview.webview; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebView; +import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebViewManager; +import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.FlutterWebView; + +import java.util.HashMap; + +import io.flutter.plugin.common.StandardMessageCodec; +import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugin.platform.PlatformViewFactory; + +public class FlutterWebViewFactory extends PlatformViewFactory { + public static final String VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview"; + private final InAppWebViewFlutterPlugin plugin; + + public FlutterWebViewFactory(final InAppWebViewFlutterPlugin plugin) { + super(StandardMessageCodec.INSTANCE); + this.plugin = plugin; + } + + @Override + public PlatformView create(Context context, int id, Object args) { + HashMap params = (HashMap) args; + FlutterWebView flutterWebView = null; + Object viewId = id; + + String keepAliveId = (String) params.get("keepAliveId"); + String headlessWebViewId = (String) params.get("headlessWebViewId"); + + HeadlessInAppWebViewManager headlessInAppWebViewManager = plugin.headlessInAppWebViewManager; + if (headlessWebViewId != null && headlessInAppWebViewManager != null) { + HeadlessInAppWebView headlessInAppWebView = headlessInAppWebViewManager.webViews.get(headlessWebViewId); + if (headlessInAppWebView != null) { + flutterWebView = headlessInAppWebView.disposeAndGetFlutterWebView(); + if (flutterWebView != null) { + flutterWebView.keepAliveId = keepAliveId; + } + } + } + + InAppWebViewManager inAppWebViewManager = plugin.inAppWebViewManager; + if (keepAliveId != null && flutterWebView == null && inAppWebViewManager != null) { + flutterWebView = inAppWebViewManager.keepAliveWebViews.get(keepAliveId); + if (flutterWebView != null) { + // be sure to remove the view from the previous parent. + View view = flutterWebView.getView(); + if (view != null) { + ViewGroup parent = (ViewGroup) view.getParent(); + if (parent != null) { + parent.removeView(view); + } + } + } + } + + boolean shouldMakeInitialLoad = flutterWebView == null; + if (flutterWebView == null) { + if (keepAliveId != null) { + viewId = keepAliveId; + } + flutterWebView = new FlutterWebView(plugin, context, viewId, params); + } + + if (keepAliveId != null && inAppWebViewManager != null) { + inAppWebViewManager.keepAliveWebViews.put(keepAliveId, flutterWebView); + } + + if (shouldMakeInitialLoad) { + flutterWebView.makeInitialLoad(params); + } + + return flutterWebView; + } +} + diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewStatic.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/InAppWebViewManager.java similarity index 66% rename from android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewStatic.java rename to android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/InAppWebViewManager.java index aadb6d79..881f1c07 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewStatic.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/InAppWebViewManager.java @@ -1,9 +1,12 @@ -package com.pichillilorenzo.flutter_inappwebview; +package com.pichillilorenzo.flutter_inappwebview.webview; -import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageInfo; +import android.net.Uri; import android.os.Build; +import android.os.Message; +import android.view.View; +import android.view.ViewGroup; import android.webkit.ValueCallback; import android.webkit.WebSettings; import android.webkit.WebView; @@ -13,9 +16,12 @@ import androidx.annotation.Nullable; import androidx.webkit.WebViewCompat; import androidx.webkit.WebViewFeature; +import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebView; import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl; +import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.FlutterWebView; -import java.lang.reflect.Method; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -25,14 +31,19 @@ import java.util.Set; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -public class InAppWebViewStatic extends ChannelDelegateImpl { - protected static final String LOG_TAG = "InAppWebViewStatic"; - public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static"; +public class InAppWebViewManager extends ChannelDelegateImpl { + protected static final String LOG_TAG = "InAppWebViewManager"; + public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"; @Nullable public InAppWebViewFlutterPlugin plugin; - public InAppWebViewStatic(final InAppWebViewFlutterPlugin plugin) { + public final Map keepAliveWebViews = new HashMap<>(); + + public final Map windowWebViewMessages = new HashMap<>(); + public int windowAutoincrementId = 0; + + public InAppWebViewManager(final InAppWebViewFlutterPlugin plugin) { super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME)); this.plugin = plugin; } @@ -41,7 +52,11 @@ public class InAppWebViewStatic extends ChannelDelegateImpl { public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) { switch (call.method) { case "getDefaultUserAgent": - result.success(WebSettings.getDefaultUserAgent(plugin.applicationContext)); + if (plugin != null) { + result.success(WebSettings.getDefaultUserAgent(plugin.applicationContext)); + } else { + result.success(null); + } break; case "clearClientCertPreferences": if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -118,6 +133,13 @@ public class InAppWebViewStatic extends ChannelDelegateImpl { result.success(false); } break; + case "disposeKeepAlive": + final String keepAliveId = (String) call.argument("keepAliveId"); + if (keepAliveId != null) { + disposeKeepAlive(keepAliveId); + } + result.success(true); + break; default: result.notImplemented(); } @@ -133,9 +155,37 @@ public class InAppWebViewStatic extends ChannelDelegateImpl { return webViewPackageInfoMap; } + public void disposeKeepAlive(@NonNull String keepAliveId) { + FlutterWebView flutterWebView = keepAliveWebViews.get(keepAliveId); + if (flutterWebView != null) { + flutterWebView.keepAliveId = null; + // be sure to remove the view from the previous parent. + View view = flutterWebView.getView(); + if (view != null) { + ViewGroup parent = (ViewGroup) view.getParent(); + if (parent != null) { + parent.removeView(view); + } + } + flutterWebView.dispose(); + } + if (keepAliveWebViews.containsKey(keepAliveId)) { + keepAliveWebViews.put(keepAliveId, null); + } + } + @Override public void dispose() { super.dispose(); + Collection flutterWebViews = keepAliveWebViews.values(); + for (FlutterWebView flutterWebView : flutterWebViews) { + String keepAliveId = flutterWebView.keepAliveId; + if (keepAliveId != null) { + disposeKeepAlive(flutterWebView.keepAliveId); + } + } + keepAliveWebViews.clear(); + windowWebViewMessages.clear(); plugin = null; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/JavaScriptBridgeInterface.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/JavaScriptBridgeInterface.java index fd872fa1..bfe947f9 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/JavaScriptBridgeInterface.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/JavaScriptBridgeInterface.java @@ -76,9 +76,11 @@ public class JavaScriptBridgeInterface { @Override public void defaultBehaviour(@Nullable Boolean handledByClient) { - PrintJobController printJobController = PrintJobManager.jobs.get(printJobId); - if (printJobController != null) { - printJobController.disposeNoCancel(); + if (inAppWebView != null && inAppWebView.plugin != null && inAppWebView.plugin.printJobManager != null) { + PrintJobController printJobController = inAppWebView.plugin.printJobManager.jobs.get(printJobId); + if (printJobController != null) { + printJobController.disposeNoCancel(); + } } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/FlutterWebView.java index e22aefe4..dde66261 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/FlutterWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/FlutterWebView.java @@ -42,12 +42,16 @@ public class FlutterWebView implements PlatformWebView { public InAppWebView webView; @Nullable public PullToRefreshLayout pullToRefreshLayout; + @Nullable + public String keepAliveId; public FlutterWebView(final InAppWebViewFlutterPlugin plugin, final Context context, Object id, HashMap params) { DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); displayListenerProxy.onPreWebViewInitialization(displayManager); + + keepAliveId = (String) params.get("keepAliveId"); Map initialSettings = (Map) params.get("initialSettings"); Map contextMenu = (Map) params.get("contextMenu"); @@ -101,26 +105,28 @@ public class FlutterWebView implements PlatformWebView { final Map initialData = (Map) params.get("initialData"); if (windowId != null) { - Message resultMsg = InAppWebViewChromeClient.windowWebViewMessages.get(windowId); - if (resultMsg != null) { - ((WebView.WebViewTransport) resultMsg.obj).setWebView(webView); - resultMsg.sendToTarget(); - if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { - // for some reason, if a WebView is created using a window id, - // the initial plugin and user scripts injected - // with WebViewCompat.addDocumentStartJavaScript will not be added! - // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1455 - // - // Also, calling the prepareAndAddUserScripts method right after won't work, - // so use the View.post method here. - webView.post(new Runnable() { - @Override - public void run() { - if (webView != null) { - webView.prepareAndAddUserScripts(); + if (webView.plugin != null && webView.plugin.inAppWebViewManager != null) { + Message resultMsg = webView.plugin.inAppWebViewManager.windowWebViewMessages.get(windowId); + if (resultMsg != null) { + ((WebView.WebViewTransport) resultMsg.obj).setWebView(webView); + resultMsg.sendToTarget(); + if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + // for some reason, if a WebView is created using a window id, + // the initial plugin and user scripts injected + // with WebViewCompat.addDocumentStartJavaScript will not be added! + // https://github.com/pichillilorenzo/flutter_inappwebview/issues/1455 + // + // Also, calling the prepareAndAddUserScripts method right after won't work, + // so use the View.post method here. + webView.post(new Runnable() { + @Override + public void run() { + if (webView != null) { + webView.prepareAndAddUserScripts(); + } } - } - }); + }); + } } } } else { @@ -151,7 +157,7 @@ public class FlutterWebView implements PlatformWebView { @Override public void dispose() { - if (webView != null) { + if (keepAliveId == null && webView != null) { if (webView.channelDelegate != null) { webView.channelDelegate.dispose(); } @@ -183,7 +189,7 @@ public class FlutterWebView implements PlatformWebView { webView.destroy(); webView = null; } - + if (pullToRefreshLayout != null) { pullToRefreshLayout.dispose(); pullToRefreshLayout = null; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java index 344c5f1e..2097a96d 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebView.java @@ -1439,10 +1439,10 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie // Create a printCurrentPage job with name and adapter instance android.print.PrintJob job = printManager.print(jobName, printAdapter, builder.build()); - if (settings != null && settings.handledByClient) { + if (settings != null && settings.handledByClient && plugin.printJobManager != null) { String id = UUID.randomUUID().toString(); PrintJobController printJobController = new PrintJobController(id, job, settings, plugin); - PrintJobManager.jobs.put(printJobController.id, printJobController); + plugin.printJobManager.jobs.put(printJobController.id, printJobController); return id; } } else { @@ -2011,8 +2011,8 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie webViewAssetLoaderExt.dispose(); webViewAssetLoaderExt = null; } - if (windowId != null) { - InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); + if (windowId != null && plugin != null && plugin.inAppWebViewManager != null) { + plugin.inAppWebViewManager.windowWebViewMessages.remove(windowId); } mainLooperHandler.removeCallbacksAndMessages(null); mHandler.removeCallbacksAndMessages(null); diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewChromeClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewChromeClient.java index 6cb450a7..c974801e 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewChromeClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/in_app_webview/InAppWebViewChromeClient.java @@ -77,14 +77,10 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR protected static final String LOG_TAG = "IABWebChromeClient"; private InAppBrowserDelegate inAppBrowserDelegate; - public static Map windowWebViewMessages = new HashMap<>(); - private static int windowAutoincrementId = 0; private static final int PICKER = 1; private static final int PICKER_LEGACY = 3; final String DEFAULT_MIME_TYPES = "*/*"; - private static Uri videoOutputFileUri; - private static Uri imageOutputFileUri; protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER); @@ -115,6 +111,15 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR @Nullable public InAppWebView inAppWebView; + @Nullable + private ValueCallback filePathCallbackLegacy; + @Nullable + private ValueCallback filePathCallback; + @Nullable + private Uri videoOutputFileUri; + @Nullable + private Uri imageOutputFileUri; + public InAppWebViewChromeClient(@NonNull final InAppWebViewFlutterPlugin plugin, @NonNull InAppWebView inAppWebView, InAppBrowserDelegate inAppBrowserDelegate) { super(); @@ -610,8 +615,11 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR @Override public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, final Message resultMsg) { - windowAutoincrementId++; - final int windowId = windowAutoincrementId; + int windowId = 0; + if (plugin != null && plugin.inAppWebViewManager != null) { + plugin.inAppWebViewManager.windowAutoincrementId++; + windowId = plugin.inAppWebViewManager.windowAutoincrementId; + } WebView.HitTestResult result = view.getHitTestResult(); String url = result.getExtra(); @@ -639,9 +647,12 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR isDialog ); - windowWebViewMessages.put(windowId, resultMsg); + if (plugin != null && plugin.inAppWebViewManager != null) { + plugin.inAppWebViewManager.windowWebViewMessages.put(windowId, resultMsg); + } if (inAppWebView != null && inAppWebView.channelDelegate != null) { + int finalWindowId = windowId; inAppWebView.channelDelegate.onCreateWindow(createWindowAction, new WebViewChannelDelegate.CreateWindowCallback() { @Override public boolean nonNullSuccess(@NonNull Boolean handledByClient) { @@ -650,7 +661,9 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR @Override public void defaultBehaviour(@Nullable Boolean handledByClient) { - InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); + if (plugin != null && plugin.inAppWebViewManager != null) { + plugin.inAppWebViewManager.windowWebViewMessages.remove(finalWindowId); + } } @Override @@ -824,7 +837,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR @Override public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - if (InAppWebViewFlutterPlugin.filePathCallback == null && InAppWebViewFlutterPlugin.filePathCallbackLegacy == null) { + if (filePathCallback == null && filePathCallbackLegacy == null) { return true; } @@ -838,8 +851,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR results = getSelectedFiles(data, resultCode); } - if (InAppWebViewFlutterPlugin.filePathCallback != null) { - InAppWebViewFlutterPlugin.filePathCallback.onReceiveValue(results); + if (filePathCallback != null) { + filePathCallback.onReceiveValue(results); } break; @@ -848,13 +861,14 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR if (resultCode == RESULT_OK) { result = data != null ? data.getData() : getCapturedMediaFile(); } - - InAppWebViewFlutterPlugin.filePathCallbackLegacy.onReceiveValue(result); + if (filePathCallbackLegacy != null) { + filePathCallbackLegacy.onReceiveValue(result); + } break; } - InAppWebViewFlutterPlugin.filePathCallback = null; - InAppWebViewFlutterPlugin.filePathCallbackLegacy = null; + filePathCallback = null; + filePathCallbackLegacy = null; imageOutputFileUri = null; videoOutputFileUri = null; @@ -921,7 +935,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR } public void startPickerIntent(ValueCallback filePathCallback, String acceptType, @Nullable String capture) { - InAppWebViewFlutterPlugin.filePathCallbackLegacy = filePathCallback; + filePathCallbackLegacy = filePathCallback; boolean images = acceptsImages(acceptType); boolean video = acceptsVideo(acceptType); @@ -965,7 +979,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public boolean startPickerIntent(final ValueCallback callback, final String[] acceptTypes, final boolean allowMultiple, final boolean captureEnabled) { - InAppWebViewFlutterPlugin.filePathCallback = callback; + filePathCallback = callback; boolean images = acceptsImages(acceptTypes); boolean video = acceptsVideo(acceptTypes); @@ -1289,7 +1303,11 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR inAppBrowserDelegate.getActivityResultListeners().clear(); inAppBrowserDelegate = null; } - plugin = null; + filePathCallbackLegacy = null; + filePathCallback = null; + videoOutputFileUri = null; + imageOutputFileUri = null; inAppWebView = null; + plugin = null; } } diff --git a/example/integration_test/chrome_safari_browser/custom_menu_item.dart b/example/integration_test/chrome_safari_browser/custom_menu_item.dart index f596771d..d441f6a5 100644 --- a/example/integration_test/chrome_safari_browser/custom_menu_item.dart +++ b/example/integration_test/chrome_safari_browser/custom_menu_item.dart @@ -25,7 +25,7 @@ void customMenuItem() { expect(chromeSafariBrowser.isOpened(), true); expect(() async { await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); - }, throwsA(isInstanceOf())); + }, throwsException); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); diff --git a/example/integration_test/chrome_safari_browser/custom_tabs.dart b/example/integration_test/chrome_safari_browser/custom_tabs.dart index 635faac6..ca76f9b5 100644 --- a/example/integration_test/chrome_safari_browser/custom_tabs.dart +++ b/example/integration_test/chrome_safari_browser/custom_tabs.dart @@ -26,7 +26,7 @@ void customTabs() { expect(chromeSafariBrowser.isOpened(), true); expect(() async { await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); - }, throwsA(isInstanceOf())); + }, throwsException); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); @@ -45,7 +45,7 @@ void customTabs() { expect(chromeSafariBrowser.isOpened(), true); expect(() async { await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); - }, throwsA(isInstanceOf())); + }, throwsException); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); @@ -71,7 +71,7 @@ void customTabs() { expect(chromeSafariBrowser.isOpened(), true); expect(() async { await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); - }, throwsA(isInstanceOf())); + }, throwsException); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.updateActionButton( diff --git a/example/integration_test/chrome_safari_browser/open_and_close.dart b/example/integration_test/chrome_safari_browser/open_and_close.dart index 2c20c16e..96e99a19 100644 --- a/example/integration_test/chrome_safari_browser/open_and_close.dart +++ b/example/integration_test/chrome_safari_browser/open_and_close.dart @@ -50,7 +50,7 @@ void openAndClose() { expect(chromeSafariBrowser.isOpened(), true); expect(() async { await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); - }, throwsA(isInstanceOf())); + }, throwsException); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); diff --git a/example/integration_test/chrome_safari_browser/sf_safari_view_controller.dart b/example/integration_test/chrome_safari_browser/sf_safari_view_controller.dart index 992830dd..a3e19efd 100644 --- a/example/integration_test/chrome_safari_browser/sf_safari_view_controller.dart +++ b/example/integration_test/chrome_safari_browser/sf_safari_view_controller.dart @@ -22,7 +22,7 @@ void sfSafariViewController() { expect(chromeSafariBrowser.isOpened(), true); expect(() async { await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); - }, throwsA(isInstanceOf())); + }, throwsException); expect(await chromeSafariBrowser.firstPageLoaded.future, true); await chromeSafariBrowser.close(); diff --git a/example/integration_test/chrome_safari_browser/trusted_web_activity.dart b/example/integration_test/chrome_safari_browser/trusted_web_activity.dart index cc9d3dae..a7996dea 100644 --- a/example/integration_test/chrome_safari_browser/trusted_web_activity.dart +++ b/example/integration_test/chrome_safari_browser/trusted_web_activity.dart @@ -24,7 +24,7 @@ void trustedWebActivity() { expect(chromeSafariBrowser.isOpened(), true); expect(() async { await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); - }, throwsA(isInstanceOf())); + }, throwsException); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); @@ -44,7 +44,7 @@ void trustedWebActivity() { expect(chromeSafariBrowser.isOpened(), true); expect(() async { await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); - }, throwsA(isInstanceOf())); + }, throwsException); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await chromeSafariBrowser.close(); diff --git a/example/integration_test/in_app_browser/open_data_and_close.dart b/example/integration_test/in_app_browser/open_data_and_close.dart index c36589cc..85dbc3d6 100644 --- a/example/integration_test/in_app_browser/open_data_and_close.dart +++ b/example/integration_test/in_app_browser/open_data_and_close.dart @@ -19,7 +19,7 @@ void openDataAndClose() { expect(inAppBrowser.isOpened(), false); expect(() async { await inAppBrowser.show(); - }, throwsA(isInstanceOf())); + }, throwsException); await inAppBrowser.openData( data: """ @@ -46,7 +46,7 @@ void openDataAndClose() { expect(() async { await inAppBrowser.openUrlRequest( urlRequest: URLRequest(url: TEST_URL_1)); - }, throwsA(isInstanceOf())); + }, throwsException); await inAppBrowser.firstPageLoaded.future; var controller = inAppBrowser.webViewController; diff --git a/example/integration_test/in_app_browser/open_file_and_close.dart b/example/integration_test/in_app_browser/open_file_and_close.dart index 3a387b4e..11503383 100644 --- a/example/integration_test/in_app_browser/open_file_and_close.dart +++ b/example/integration_test/in_app_browser/open_file_and_close.dart @@ -19,7 +19,7 @@ void openFileAndClose() { expect(inAppBrowser.isOpened(), false); expect(() async { await inAppBrowser.show(); - }, throwsA(isInstanceOf())); + }, throwsException); await inAppBrowser.openFile( assetFilePath: "test_assets/in_app_webview_initial_file_test.html"); @@ -28,7 +28,7 @@ void openFileAndClose() { expect(() async { await inAppBrowser.openUrlRequest( urlRequest: URLRequest(url: TEST_URL_1)); - }, throwsA(isInstanceOf())); + }, throwsException); await inAppBrowser.firstPageLoaded.future; var controller = inAppBrowser.webViewController; diff --git a/example/integration_test/in_app_browser/open_url_and_close.dart b/example/integration_test/in_app_browser/open_url_and_close.dart index 57a3dd4d..efffc6e7 100644 --- a/example/integration_test/in_app_browser/open_url_and_close.dart +++ b/example/integration_test/in_app_browser/open_url_and_close.dart @@ -19,7 +19,7 @@ void openUrlAndClose() { expect(inAppBrowser.isOpened(), false); expect(() async { await inAppBrowser.show(); - }, throwsA(isInstanceOf())); + }, throwsException); await inAppBrowser.openUrlRequest(urlRequest: URLRequest(url: TEST_URL_1)); await inAppBrowser.browserCreated.future; @@ -27,7 +27,7 @@ void openUrlAndClose() { expect(() async { await inAppBrowser.openUrlRequest( urlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1)); - }, throwsA(isInstanceOf())); + }, throwsException); await inAppBrowser.firstPageLoaded.future; var controller = inAppBrowser.webViewController; diff --git a/example/lib/headless_in_app_webview.screen.dart b/example/lib/headless_in_app_webview.screen.dart index 1d260f38..6df4fd25 100755 --- a/example/lib/headless_in_app_webview.screen.dart +++ b/example/lib/headless_in_app_webview.screen.dart @@ -86,7 +86,7 @@ class _HeadlessInAppWebViewExampleScreenState child: ElevatedButton( onPressed: () async { if (headlessWebView?.isRunning() ?? false) { - await headlessWebView?.webViewController.evaluateJavascript( + await headlessWebView?.webViewController?.evaluateJavascript( source: """console.log('Here is the message!');"""); } else { ScaffoldMessenger.of(context).showSnackBar(SnackBar( diff --git a/example/lib/in_app_webiew_example.screen.dart b/example/lib/in_app_webiew_example.screen.dart index 2345c49c..6eff2d15 100755 --- a/example/lib/in_app_webiew_example.screen.dart +++ b/example/lib/in_app_webiew_example.screen.dart @@ -126,7 +126,6 @@ class _InAppWebViewExampleScreenState extends State { pullToRefreshController: pullToRefreshController, onWebViewCreated: (controller) async { webViewController = controller; - print(await controller.getUrl()); }, onLoadStart: (controller, url) async { setState(() { diff --git a/ios/Classes/FindInteraction/FindInteractionController.swift b/ios/Classes/FindInteraction/FindInteractionController.swift index 9eb93142..d3ebf169 100644 --- a/ios/Classes/FindInteraction/FindInteractionController.swift +++ b/ios/Classes/FindInteraction/FindInteractionController.swift @@ -11,6 +11,7 @@ import Flutter public class FindInteractionController : NSObject, Disposable { static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_"; + var plugin: SwiftFlutterPlugin? var webView: InAppWebView? var channelDelegate: FindInteractionChannelDelegate? var settings: FindInteractionSettings? @@ -48,13 +49,16 @@ public class FindInteractionController : NSObject, Disposable { } } - public init(registrar: FlutterPluginRegistrar, id: Any, webView: InAppWebView, settings: FindInteractionSettings?) { + public init(plugin: SwiftFlutterPlugin, id: Any, webView: InAppWebView, settings: FindInteractionSettings?) { super.init() + self.plugin = plugin self.webView = webView self.settings = settings - let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger()) - self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: registrar.messenger()) + self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) + } } public func prepare() { @@ -148,6 +152,7 @@ public class FindInteractionController : NSObject, Disposable { channelDelegate = nil webView = nil activeFindSession = nil + plugin = nil } deinit { diff --git a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift index 657cb151..3cb5262f 100644 --- a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift +++ b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift @@ -79,7 +79,7 @@ public class HeadlessInAppWebView : Disposable { public func dispose() { channelDelegate?.dispose() channelDelegate = nil - HeadlessInAppWebViewManager.webViews[id] = nil + plugin?.headlessInAppWebViewManager?.webViews[id] = nil flutterWebView = nil plugin = nil } diff --git a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index 020ffb2e..36d770c3 100644 --- a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -16,7 +16,7 @@ import AVFoundation public class HeadlessInAppWebViewManager: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview" var plugin: SwiftFlutterPlugin? - static var webViews: [String: HeadlessInAppWebView?] = [:] + var webViews: [String: HeadlessInAppWebView?] = [:] init(plugin: SwiftFlutterPlugin) { super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) @@ -40,15 +40,15 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { } public func run(id: String, params: [String: Any?]) { - guard let plugin = plugin, let registrar = plugin.registrar else { + guard let plugin = plugin else { return } - let flutterWebView = FlutterWebViewController(registrar: registrar, + let flutterWebView = FlutterWebViewController(plugin: plugin, withFrame: CGRect.zero, viewIdentifier: id, params: params as NSDictionary) let headlessInAppWebView = HeadlessInAppWebView(plugin: plugin, id: id, flutterWebView: flutterWebView) - HeadlessInAppWebViewManager.webViews[id] = headlessInAppWebView + webViews[id] = headlessInAppWebView headlessInAppWebView.prepare(params: params as NSDictionary) headlessInAppWebView.onWebViewCreated() @@ -57,11 +57,11 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { public override func dispose() { super.dispose() - let headlessWebViews = HeadlessInAppWebViewManager.webViews.values + let headlessWebViews = webViews.values headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in headlessWebView?.dispose() } - HeadlessInAppWebViewManager.webViews.removeAll() + webViews.removeAll() plugin = nil } diff --git a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift index b6789ed7..196291e7 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -42,7 +42,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega var isHidden = false public override func loadView() { - guard let registrar = plugin?.registrar else { + guard let plugin = plugin, let registrar = plugin.registrar else { return } @@ -55,13 +55,13 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings) - if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView webView!.contextMenu = contextMenu webView!.initialUserScripts = userScripts } else { webView = InAppWebView(id: nil, - registrar: nil, + plugin: nil, frame: .zero, configuration: preWebviewConfiguration, contextMenu: contextMenu, @@ -74,17 +74,18 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega webView.inAppBrowserDelegate = self webView.id = id + webView.plugin = plugin webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel) let pullToRefreshSettings = PullToRefreshSettings() let _ = pullToRefreshSettings.parse(settings: pullToRefreshInitialSettings) - let pullToRefreshControl = PullToRefreshControl(registrar: registrar, id: id, settings: pullToRefreshSettings) + let pullToRefreshControl = PullToRefreshControl(plugin: plugin, id: id, settings: pullToRefreshSettings) webView.pullToRefreshControl = pullToRefreshControl pullToRefreshControl.delegate = webView pullToRefreshControl.prepare() let findInteractionController = FindInteractionController( - registrar: registrar, + plugin: plugin, id: id, webView: webView, settings: nil) webView.findInteractionController = findInteractionController findInteractionController.prepare() @@ -132,7 +133,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } } - if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView?.load(webViewTransport.request) channelDelegate?.onBrowserCreated() } else { diff --git a/ios/Classes/InAppWebView/FlutterWebViewController.swift b/ios/Classes/InAppWebView/FlutterWebViewController.swift index 1c38d7e7..6fef9088 100755 --- a/ios/Classes/InAppWebView/FlutterWebViewController.swift +++ b/ios/Classes/InAppWebView/FlutterWebViewController.swift @@ -11,13 +11,16 @@ import WebKit public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable { var myView: UIView? + var keepAliveId: String? - init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) { + init(plugin: SwiftFlutterPlugin, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) { super.init() myView = UIView(frame: frame) myView!.clipsToBounds = true + keepAliveId = params["keepAliveId"] as? String + let initialSettings = params["initialSettings"] as! [String: Any?] let contextMenu = params["contextMenu"] as? [String: Any] let windowId = params["windowId"] as? Int64 @@ -37,18 +40,21 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable var webView: InAppWebView? - if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView webView!.id = viewId - let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), - binaryMessenger: registrar.messenger()) - webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) + webView!.plugin = plugin + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), + binaryMessenger: registrar.messenger()) + webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) + } webView!.frame = myView!.bounds webView!.contextMenu = contextMenu webView!.initialUserScripts = userScripts } else { webView = InAppWebView(id: viewId, - registrar: registrar, + plugin: plugin, frame: myView!.bounds, configuration: preWebviewConfiguration, contextMenu: contextMenu, @@ -57,13 +63,13 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable let pullToRefreshSettings = PullToRefreshSettings() let _ = pullToRefreshSettings.parse(settings: pullToRefreshInitialSettings) - let pullToRefreshControl = PullToRefreshControl(registrar: registrar, id: viewId, settings: pullToRefreshSettings) + let pullToRefreshControl = PullToRefreshControl(plugin: plugin, id: viewId, settings: pullToRefreshSettings) webView!.pullToRefreshControl = pullToRefreshControl pullToRefreshControl.delegate = webView! pullToRefreshControl.prepare() let findInteractionController = FindInteractionController( - registrar: registrar, + plugin: plugin, id: viewId, webView: webView!, settings: nil) webView!.findInteractionController = findInteractionController findInteractionController.prepare() @@ -132,7 +138,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable } load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData) } - else if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + else if let wId = windowId, let webViewTransport = webView.plugin?.inAppWebViewManager?.windowWebViews[wId] { webView.load(webViewTransport.request) } } @@ -180,10 +186,12 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable } public func dispose() { - if let webView = webView() { - webView.dispose() + if keepAliveId == nil { + if let webView = webView() { + webView.dispose() + } + myView = nil } - myView = nil } deinit { diff --git a/ios/Classes/InAppWebView/FlutterWebViewFactory.swift b/ios/Classes/InAppWebView/FlutterWebViewFactory.swift index b3685802..74a5b2bf 100755 --- a/ios/Classes/InAppWebView/FlutterWebViewFactory.swift +++ b/ios/Classes/InAppWebView/FlutterWebViewFactory.swift @@ -10,11 +10,11 @@ import Foundation public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { static let VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview" - private var registrar: FlutterPluginRegistrar? + private var plugin: SwiftFlutterPlugin - init(registrar: FlutterPluginRegistrar?) { + init(plugin: SwiftFlutterPlugin) { + self.plugin = plugin super.init() - self.registrar = registrar } public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { @@ -23,18 +23,48 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { let arguments = args as? NSDictionary + var flutterWebView: FlutterWebViewController? + var id: Any = viewId - if let headlessWebViewId = arguments?["headlessWebViewId"] as? String, - let headlessWebView = HeadlessInAppWebViewManager.webViews[headlessWebViewId], + let keepAliveId = arguments?["keepAliveId"] as? String + let headlessWebViewId = arguments?["headlessWebViewId"] as? String + + if let headlessWebViewId = headlessWebViewId, + let headlessWebView = plugin.headlessInAppWebViewManager?.webViews[headlessWebViewId], let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: frame) { - return platformView + flutterWebView = platformView + flutterWebView?.keepAliveId = keepAliveId } - let webviewController = FlutterWebViewController(registrar: registrar!, - withFrame: frame, - viewIdentifier: viewId, - params: arguments!) - webviewController.makeInitialLoad(params: arguments!) - return webviewController + if let keepAliveId = keepAliveId, + flutterWebView == nil, + let keepAliveWebView = plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] { + flutterWebView = keepAliveWebView + if let view = flutterWebView?.view() { + // remove from parent + view.removeFromSuperview() + } + } + + let shouldMakeInitialLoad = flutterWebView == nil + if flutterWebView == nil { + if let keepAliveId = keepAliveId { + id = keepAliveId + } + flutterWebView = FlutterWebViewController(plugin: plugin, + withFrame: frame, + viewIdentifier: id, + params: arguments!) + } + + if let keepAliveId = keepAliveId { + plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] = flutterWebView! + } + + if shouldMakeInitialLoad { + flutterWebView?.makeInitialLoad(params: arguments!) + } + + return flutterWebView! } } diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index ccbd7ead..97c20640 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -17,7 +17,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_" var id: Any? // viewId - var registrar: FlutterPluginRegistrar? + var plugin: SwiftFlutterPlugin? var windowId: Int64? var windowCreated = false var inAppBrowserDelegate: InAppBrowserDelegate? @@ -61,21 +61,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, var customIMPs: [IMP] = [] - static var windowWebViews: [Int64:WebViewTransport] = [:] - static var windowAutoincrementId: Int64 = 0; - var callAsyncJavaScriptBelowIOS14Results: [String:((Any?) -> Void)] = [:] var oldZoomScale = Float(1.0) - init(id: Any?, registrar: FlutterPluginRegistrar?, frame: CGRect, configuration: WKWebViewConfiguration, + init(id: Any?, plugin: SwiftFlutterPlugin?, frame: CGRect, configuration: WKWebViewConfiguration, contextMenu: [String: Any]?, userScripts: [UserScript] = []) { super.init(frame: frame, configuration: configuration) self.id = id - self.registrar = registrar - if let id = id, let registrar = registrar { + self.plugin = plugin + if let id = id, let registrar = plugin?.registrar { let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger()) + binaryMessenger: registrar.messenger()) self.channelDelegate = WebViewChannelDelegate(webView: self, channel: channel) } self.contextMenu = contextMenu @@ -940,8 +937,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } public func loadFile(assetFilePath: String) throws { - if let registrar = registrar { - let assetURL = try Util.getUrlAsset(registrar: registrar, assetFilePath: assetFilePath) + if let plugin = plugin { + let assetURL = try Util.getUrlAsset(plugin: plugin, assetFilePath: assetFilePath) let urlRequest = URLRequest(url: assetURL) loadUrl(urlRequest: urlRequest, allowingReadAccessTo: nil) } @@ -2090,8 +2087,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, var path: String = certificatePath do { - if let registrar = self.registrar { - path = try Util.getAbsPathAsset(registrar: registrar, assetFilePath: certificatePath) + if let plugin = self.plugin { + path = try Util.getAbsPathAsset(plugin: plugin, assetFilePath: certificatePath) } } catch {} @@ -2464,10 +2461,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { - InAppWebView.windowAutoincrementId += 1 - let windowId = InAppWebView.windowAutoincrementId + var windowId: Int64 = 0 + let inAppWebViewManager = plugin?.inAppWebViewManager + if let inAppWebViewManager = inAppWebViewManager { + inAppWebViewManager.windowAutoincrementId += 1 + windowId = inAppWebViewManager.windowAutoincrementId + } - let windowWebView = InAppWebView(id: nil, registrar: nil, frame: CGRect.zero, configuration: configuration, contextMenu: nil) + let windowWebView = InAppWebView(id: nil, plugin: nil, frame: CGRect.zero, configuration: configuration, contextMenu: nil) windowWebView.windowId = windowId let webViewTransport = WebViewTransport( @@ -2475,7 +2476,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, request: navigationAction.request ) - InAppWebView.windowWebViews[windowId] = webViewTransport + inAppWebViewManager?.windowWebViews[windowId] = webViewTransport windowWebView.stopLoading() let createWindowAction = CreateWindowAction(navigationAction: navigationAction, windowId: windowId, windowFeatures: windowFeatures, isDialog: nil) @@ -2485,8 +2486,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, return !handledByClient } callback.defaultBehaviour = { (handledByClient: Bool?) in - if InAppWebView.windowWebViews[windowId] != nil { - InAppWebView.windowWebViews.removeValue(forKey: windowId) + if inAppWebViewManager?.windowWebViews[windowId] != nil { + inAppWebViewManager?.windowWebViews.removeValue(forKey: windowId) } self.loadUrl(urlRequest: navigationAction.request, allowingReadAccessTo: nil) } @@ -2752,7 +2753,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let _windowId = body["_windowId"] as? Int64 var webView = self - if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) @@ -2769,7 +2770,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, return !handledByClient } callback.defaultBehaviour = { (handledByClient: Bool?) in - if let printJob = PrintJobManager.jobs[printJobId] { + if let printJob = self.plugin?.printJobManager?.jobs[printJobId] { printJob?.disposeNoDismiss() } } @@ -2787,7 +2788,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let _windowId = body["_windowId"] as? Int64 var webView = self - if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } @@ -2829,7 +2830,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { let _windowId = body["_windowId"] as? Int64 var webView = self - if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) @@ -2979,9 +2980,9 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } let animated = settings?.animated ?? true - if let id = printJobId, let registrar = registrar { - let printJob = PrintJobController(registrar: registrar, id: id, job: printController, settings: settings) - PrintJobManager.jobs[id] = printJob + if let id = printJobId, let plugin = plugin { + let printJob = PrintJobController(plugin: plugin, id: id, job: printController, settings: settings) + plugin.printJobManager?.jobs[id] = printJob printJob.present(animated: animated, completionHandler: completionHandler) } else { printController.present(animated: animated, completionHandler: completionHandler) @@ -3123,12 +3124,12 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } public func createWebMessageChannel(completionHandler: ((WebMessageChannel?) -> Void)? = nil) -> WebMessageChannel? { - guard let registrar = registrar else { + guard let plugin = plugin else { completionHandler?(nil) return nil } let id = NSUUID().uuidString - let webMessageChannel = WebMessageChannel(registrar: registrar, id: id) + let webMessageChannel = WebMessageChannel(plugin: plugin, id: id) webMessageChannel.initJsInstance(webView: self, completionHandler: completionHandler) webMessageChannels[id] = webMessageChannel @@ -3216,8 +3217,8 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if #available(iOS 11.0, *) { configuration.userContentController.removeAllContentRuleLists() } - } else if let wId = windowId, InAppWebView.windowWebViews[wId] != nil { - InAppWebView.windowWebViews.removeValue(forKey: wId) + } else if let wId = windowId, plugin?.inAppWebViewManager?.windowWebViews[wId] != nil { + plugin?.inAppWebViewManager?.windowWebViews.removeValue(forKey: wId) } configuration.userContentController.dispose(windowId: windowId) NotificationCenter.default.removeObserver(self) @@ -3245,7 +3246,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { SharedLastTouchPointTimestamp.removeValue(forKey: self) callAsyncJavaScriptBelowIOS14Results.removeAll() super.removeFromSuperview() - registrar = nil + plugin = nil } deinit { diff --git a/ios/Classes/InAppWebViewStatic.swift b/ios/Classes/InAppWebView/InAppWebViewManager.swift similarity index 65% rename from ios/Classes/InAppWebViewStatic.swift rename to ios/Classes/InAppWebView/InAppWebViewManager.swift index a14792c5..19d42f92 100755 --- a/ios/Classes/InAppWebViewStatic.swift +++ b/ios/Classes/InAppWebView/InAppWebViewManager.swift @@ -1,5 +1,5 @@ // -// InAppWebViewStatic.swift +// InAppWebViewManager.swift // flutter_inappwebview // // Created by Lorenzo Pichilli on 08/12/2019. @@ -8,14 +8,18 @@ import Foundation import WebKit -public class InAppWebViewStatic: ChannelDelegate { - static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static" +public class InAppWebViewManager: ChannelDelegate { + static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager" var plugin: SwiftFlutterPlugin? var webViewForUserAgent: WKWebView? var defaultUserAgent: String? + var keepAliveWebViews: [String:FlutterWebViewController?] = [:] + var windowWebViews: [Int64:WebViewTransport] = [:] + var windowAutoincrementId: Int64 = 0 + init(plugin: SwiftFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppWebViewStatic.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) + super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) self.plugin = plugin } @@ -36,6 +40,11 @@ public class InAppWebViewStatic: ChannelDelegate { result(false) } break + case "disposeKeepAlive": + let keepAliveId = arguments!["keepAliveId"] as! String + disposeKeepAlive(keepAliveId: keepAliveId) + result(true) + break default: result(FlutterMethodNotImplemented) break @@ -67,11 +76,27 @@ public class InAppWebViewStatic: ChannelDelegate { } } + public func disposeKeepAlive(keepAliveId: String) { + if let flutterWebView = keepAliveWebViews[keepAliveId] as? FlutterWebViewController { + flutterWebView.keepAliveId = nil + flutterWebView.dispose() + keepAliveWebViews[keepAliveId] = nil + } + } + public override func dispose() { super.dispose() - plugin = nil + let keepAliveWebViewValues = keepAliveWebViews.values + keepAliveWebViewValues.forEach {(keepAliveWebView: FlutterWebViewController?) in + if let keepAliveId = keepAliveWebView?.keepAliveId { + disposeKeepAlive(keepAliveId: keepAliveId) + } + } + keepAliveWebViews.removeAll() + windowWebViews.removeAll() webViewForUserAgent = nil defaultUserAgent = nil + plugin = nil } deinit { diff --git a/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift b/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift index dc89b45b..8110b940 100644 --- a/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift +++ b/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift @@ -10,18 +10,20 @@ import Foundation public class WebMessageChannel : FlutterMethodCallDelegate { static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" var id: String + var plugin: SwiftFlutterPlugin? var channelDelegate: WebMessageChannelChannelDelegate? weak var webView: InAppWebView? var ports: [WebMessagePort] = [] - var registrar: FlutterPluginRegistrar? - public init(registrar: FlutterPluginRegistrar, id: String) { + public init(plugin: SwiftFlutterPlugin, id: String) { self.id = id - self.registrar = registrar + self.plugin = plugin super.init() - let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger()) - self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: registrar.messenger()) + self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) + } self.ports = [ WebMessagePort(name: "port1", webMessageChannel: self), WebMessagePort(name: "port2", webMessageChannel: self) @@ -67,7 +69,7 @@ public class WebMessageChannel : FlutterMethodCallDelegate { })(); """) webView = nil - registrar = nil + plugin = nil } deinit { diff --git a/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift b/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift index c8853fbf..3b80930b 100644 --- a/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift +++ b/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift @@ -15,17 +15,19 @@ public class WebMessageListener : FlutterMethodCallDelegate { var allowedOriginRules: Set var channelDelegate: WebMessageListenerChannelDelegate? weak var webView: InAppWebView? - var registrar: FlutterPluginRegistrar? + var plugin: SwiftFlutterPlugin? - public init(registrar: FlutterPluginRegistrar, id: String, jsObjectName: String, allowedOriginRules: Set) { + public init(plugin: SwiftFlutterPlugin, id: String, jsObjectName: String, allowedOriginRules: Set) { self.id = id - self.registrar = registrar + self.plugin = plugin self.jsObjectName = jsObjectName self.allowedOriginRules = allowedOriginRules super.init() - let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, - binaryMessenger: registrar.messenger()) - self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, + binaryMessenger: registrar.messenger()) + self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) + } } public func assertOriginRulesValid() throws { @@ -116,12 +118,12 @@ public class WebMessageListener : FlutterMethodCallDelegate { } } - public static func fromMap(registrar: FlutterPluginRegistrar, map: [String:Any?]?) -> WebMessageListener? { + public static func fromMap(plugin: SwiftFlutterPlugin, map: [String:Any?]?) -> WebMessageListener? { guard let map = map else { return nil } return WebMessageListener( - registrar: registrar, + plugin: plugin, id: map["id"] as! String, jsObjectName: map["jsObjectName"] as! String, allowedOriginRules: Set(map["allowedOriginRules"] as! [String]) @@ -180,7 +182,7 @@ public class WebMessageListener : FlutterMethodCallDelegate { channelDelegate?.dispose() channelDelegate = nil webView = nil - registrar = nil + plugin = nil } deinit { diff --git a/ios/Classes/InAppWebView/WebViewChannelDelegate.swift b/ios/Classes/InAppWebView/WebViewChannelDelegate.swift index acdad64b..3e94be8d 100644 --- a/ios/Classes/InAppWebView/WebViewChannelDelegate.swift +++ b/ios/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -536,9 +536,9 @@ public class WebViewChannelDelegate : ChannelDelegate { } break case .addWebMessageListener: - if let webView = webView, let registrar = webView.registrar { + if let webView = webView, let plugin = webView.plugin { let webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?] - let webMessageListener = WebMessageListener.fromMap(registrar: registrar, map: webMessageListenerMap)! + let webMessageListener = WebMessageListener.fromMap(plugin: plugin, map: webMessageListenerMap)! do { try webView.addWebMessageListener(webMessageListener: webMessageListener) result(false) diff --git a/ios/Classes/PrintJob/PrintJobController.swift b/ios/Classes/PrintJob/PrintJobController.swift index 84a2886c..8ac450f5 100644 --- a/ios/Classes/PrintJob/PrintJobController.swift +++ b/ios/Classes/PrintJob/PrintJobController.swift @@ -18,7 +18,7 @@ public enum PrintJobState: Int { public class PrintJobController : NSObject, Disposable, UIPrintInteractionControllerDelegate { static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_" var id: String - var registrar: FlutterPluginRegistrar? + var plugin: SwiftFlutterPlugin? var job: UIPrintInteractionController? var settings: PrintJobSettings? var printFormatter: UIPrintFormatter? @@ -27,18 +27,20 @@ public class PrintJobController : NSObject, Disposable, UIPrintInteractionContro var state = PrintJobState.created var creationTime = Int64(Date().timeIntervalSince1970 * 1000) - public init(registrar: FlutterPluginRegistrar, id: String, job: UIPrintInteractionController? = nil, settings: PrintJobSettings? = nil) { + public init(plugin: SwiftFlutterPlugin, id: String, job: UIPrintInteractionController? = nil, settings: PrintJobSettings? = nil) { self.id = id - self.registrar = registrar + self.plugin = plugin super.init() self.job = job self.settings = settings self.printFormatter = job?.printFormatter self.printPageRenderer = job?.printPageRenderer self.job?.delegate = self - let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger()) - self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: registrar.messenger()) + self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) + } } public func printInteractionControllerWillStartJob(_ printInteractionController: UIPrintInteractionController) { @@ -82,7 +84,8 @@ public class PrintJobController : NSObject, Disposable, UIPrintInteractionContro printPageRenderer = nil job?.delegate = nil job = nil - PrintJobManager.jobs[id] = nil + plugin?.printJobManager?.jobs[id] = nil + plugin = nil } public func dispose() { @@ -93,7 +96,7 @@ public class PrintJobController : NSObject, Disposable, UIPrintInteractionContro job?.delegate = nil job?.dismiss(animated: false) job = nil - PrintJobManager.jobs[id] = nil - registrar = nil + plugin?.printJobManager?.jobs[id] = nil + plugin = nil } } diff --git a/ios/Classes/PrintJob/PrintJobManager.swift b/ios/Classes/PrintJob/PrintJobManager.swift index 7148027a..3a458c6e 100644 --- a/ios/Classes/PrintJob/PrintJobManager.swift +++ b/ios/Classes/PrintJob/PrintJobManager.swift @@ -8,18 +8,21 @@ import Foundation public class PrintJobManager: NSObject, Disposable { - static var jobs: [String: PrintJobController?] = [:] + var plugin: SwiftFlutterPlugin? + var jobs: [String: PrintJobController?] = [:] - public override init() { + public init(plugin: SwiftFlutterPlugin?) { super.init() + self.plugin = plugin } public func dispose() { - let jobs = PrintJobManager.jobs.values - jobs.forEach { (job: PrintJobController?) in + let jobValues = jobs.values + jobValues.forEach { (job: PrintJobController?) in job?.dispose() } - PrintJobManager.jobs.removeAll() + jobs.removeAll() + plugin = nil } deinit { diff --git a/ios/Classes/PullToRefresh/PullToRefreshControl.swift b/ios/Classes/PullToRefresh/PullToRefreshControl.swift index d73bc1e0..7d114475 100644 --- a/ios/Classes/PullToRefresh/PullToRefreshControl.swift +++ b/ios/Classes/PullToRefresh/PullToRefreshControl.swift @@ -10,17 +10,21 @@ import Flutter public class PullToRefreshControl : UIRefreshControl, Disposable { static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_"; + var plugin: SwiftFlutterPlugin? var channelDelegate: PullToRefreshChannelDelegate? var settings: PullToRefreshSettings? var shouldCallOnRefresh = false var delegate: PullToRefreshDelegate? - public init(registrar: FlutterPluginRegistrar, id: Any, settings: PullToRefreshSettings?) { + public init(plugin: SwiftFlutterPlugin, id: Any, settings: PullToRefreshSettings?) { super.init() + self.plugin = plugin self.settings = settings - let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger()) - self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: registrar.messenger()) + self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel) + } } required init?(coder: NSCoder) { @@ -59,6 +63,7 @@ public class PullToRefreshControl : UIRefreshControl, Disposable { channelDelegate = nil removeTarget(self, action: #selector(updateShouldCallOnRefresh), for: .valueChanged) delegate = nil + plugin = nil } deinit { diff --git a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift index a8af3ed1..3e4a9bc6 100755 --- a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift +++ b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift @@ -15,9 +15,8 @@ import SafariServices public class ChromeSafariBrowserManager: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_chromesafaribrowser" var plugin: SwiftFlutterPlugin? - static var browsers: [String: SafariViewController?] = [:] - @available(iOS 15.0, *) - static var prewarmingTokens: [String: SFSafariViewController.PrewarmingToken?] = [:] + var browsers: [String: SafariViewController?] = [:] + var prewarmingTokens: [String: Any?] = [:] init(plugin: SwiftFlutterPlugin) { super.init(channel: FlutterMethodChannel(name: ChromeSafariBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) @@ -61,7 +60,7 @@ public class ChromeSafariBrowserManager: ChannelDelegate { } let prewarmingToken = SFSafariViewController.prewarmConnections(to: URLs) let prewarmingTokenId = NSUUID().uuidString - ChromeSafariBrowserManager.prewarmingTokens[prewarmingTokenId] = prewarmingToken + prewarmingTokens[prewarmingTokenId] = prewarmingToken result([ "id": prewarmingTokenId ]) @@ -72,9 +71,9 @@ public class ChromeSafariBrowserManager: ChannelDelegate { if #available(iOS 15.0, *) { let prewarmingToken = arguments!["prewarmingToken"] as! [String:Any?] if let prewarmingTokenId = prewarmingToken["id"] as? String, - let prewarmingToken = ChromeSafariBrowserManager.prewarmingTokens[prewarmingTokenId] { + let prewarmingToken = prewarmingTokens[prewarmingTokenId] as? SFSafariViewController.PrewarmingToken? { prewarmingToken?.invalidate() - ChromeSafariBrowserManager.prewarmingTokens[prewarmingTokenId] = nil + prewarmingTokens[prewarmingTokenId] = nil } result(true) } else { @@ -115,7 +114,7 @@ public class ChromeSafariBrowserManager: ChannelDelegate { result(true) } - ChromeSafariBrowserManager.browsers[id] = safari + browsers[id] = safari } return } @@ -125,17 +124,20 @@ public class ChromeSafariBrowserManager: ChannelDelegate { public override func dispose() { super.dispose() - let browsers = ChromeSafariBrowserManager.browsers.values - browsers.forEach { (browser: SafariViewController?) in + let browserValues = browsers.values + browserValues.forEach { (browser: SafariViewController?) in browser?.close(result: nil) browser?.dispose() } - ChromeSafariBrowserManager.browsers.removeAll() + browsers.removeAll() if #available(iOS 15.0, *) { - ChromeSafariBrowserManager.prewarmingTokens.values.forEach { (prewarmingToken: SFSafariViewController.PrewarmingToken?) in - prewarmingToken?.invalidate() + let prewarmingTokensValues = prewarmingTokens.values + prewarmingTokensValues.forEach { (prewarmingToken: Any?) in + if let prewarmingToken = prewarmingToken as? SFSafariViewController.PrewarmingToken? { + prewarmingToken?.invalidate() + } } - ChromeSafariBrowserManager.prewarmingTokens.removeAll() + prewarmingTokens.removeAll() } plugin = nil } diff --git a/ios/Classes/SafariViewController/CustomUIActivity.swift b/ios/Classes/SafariViewController/CustomUIActivity.swift index 7d50e707..cd3ecfa0 100644 --- a/ios/Classes/SafariViewController/CustomUIActivity.swift +++ b/ios/Classes/SafariViewController/CustomUIActivity.swift @@ -8,6 +8,7 @@ import Foundation class CustomUIActivity : UIActivity { + var plugin: SwiftFlutterPlugin var viewId: String var id: Int64 var url: URL @@ -16,7 +17,8 @@ class CustomUIActivity : UIActivity { var label: String? var image: UIImage? - init(viewId: String, id: Int64, url: URL, title: String?, label: String?, type: UIActivity.ActivityType?, image: UIImage?) { + init(plugin: SwiftFlutterPlugin, viewId: String, id: Int64, url: URL, title: String?, label: String?, type: UIActivity.ActivityType?, image: UIImage?) { + self.plugin = plugin self.viewId = viewId self.id = id self.url = url @@ -47,7 +49,7 @@ class CustomUIActivity : UIActivity { } override func perform() { - let browser = ChromeSafariBrowserManager.browsers[viewId] + let browser = plugin.chromeSafariBrowserManager?.browsers[viewId] browser??.channelDelegate?.onItemActionPerform(id: id, url: url, title: title) } } diff --git a/ios/Classes/SafariViewController/SafariViewController.swift b/ios/Classes/SafariViewController/SafariViewController.swift index 675e7f99..980e983e 100755 --- a/ios/Classes/SafariViewController/SafariViewController.swift +++ b/ios/Classes/SafariViewController/SafariViewController.swift @@ -105,9 +105,14 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle } public func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] { + guard let plugin = plugin else { + return [] + } var uiActivities: [UIActivity] = [] menuItemList.forEach { (menuItem) in - let activity = CustomUIActivity(viewId: id, id: menuItem["id"] as! Int64, url: URL, title: title, label: menuItem["label"] as? String, type: nil, image: .fromMap(map: menuItem["image"] as? [String:Any?])) + let activity = CustomUIActivity(plugin: plugin, viewId: id, id: menuItem["id"] as! Int64, url: URL, + title: title, label: menuItem["label"] as? String, type: nil, + image: .fromMap(map: menuItem["image"] as? [String:Any?])) uiActivities.append(activity) } return uiActivities @@ -125,7 +130,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle channelDelegate?.dispose() channelDelegate = nil delegate = nil - ChromeSafariBrowserManager.browsers[id] = nil + plugin?.chromeSafariBrowserManager?.browsers[id] = nil plugin = nil } diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift index 4dc52a7b..22eaecdc 100755 --- a/ios/Classes/SwiftFlutterPlugin.swift +++ b/ios/Classes/SwiftFlutterPlugin.swift @@ -26,7 +26,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { var registrar: FlutterPluginRegistrar? var platformUtil: PlatformUtil? - var inAppWebViewStatic: InAppWebViewStatic? + var inAppWebViewManager: InAppWebViewManager? var myCookieManager: Any? var myWebStorageManager: Any? var credentialDatabase: CredentialDatabase? @@ -43,13 +43,13 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { super.init() self.registrar = registrar - registrar.register(FlutterWebViewFactory(registrar: registrar) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID) + registrar.register(FlutterWebViewFactory(plugin: self) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID) platformUtil = PlatformUtil(plugin: self) inAppBrowserManager = InAppBrowserManager(plugin: self) headlessInAppWebViewManager = HeadlessInAppWebViewManager(plugin: self) chromeSafariBrowserManager = ChromeSafariBrowserManager(plugin: self) - inAppWebViewStatic = InAppWebViewStatic(plugin: self) + inAppWebViewManager = InAppWebViewManager(plugin: self) credentialDatabase = CredentialDatabase(plugin: self) if #available(iOS 11.0, *) { myCookieManager = MyCookieManager(plugin: self) @@ -58,7 +58,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { myWebStorageManager = MyWebStorageManager(plugin: self) } webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self) - printJobManager = PrintJobManager() + printJobManager = PrintJobManager(plugin: self) } public static func register(with registrar: FlutterPluginRegistrar) { @@ -74,8 +74,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { headlessInAppWebViewManager = nil chromeSafariBrowserManager?.dispose() chromeSafariBrowserManager = nil - inAppWebViewStatic?.dispose() - inAppWebViewStatic = nil + inAppWebViewManager?.dispose() + inAppWebViewManager = nil credentialDatabase?.dispose() credentialDatabase = nil if #available(iOS 11.0, *) { diff --git a/ios/Classes/Util.swift b/ios/Classes/Util.swift index c3313306..799a4cae 100644 --- a/ios/Classes/Util.swift +++ b/ios/Classes/Util.swift @@ -11,17 +11,17 @@ import WebKit var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:] public class Util { - public static func getUrlAsset(registrar: FlutterPluginRegistrar, assetFilePath: String) throws -> URL { - let key = registrar.lookupKey(forAsset: assetFilePath) - guard let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { + public static func getUrlAsset(plugin: SwiftFlutterPlugin, assetFilePath: String) throws -> URL { + guard let key = plugin.registrar?.lookupKey(forAsset: assetFilePath), + let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } return assetURL } - public static func getAbsPathAsset(registrar: FlutterPluginRegistrar, assetFilePath: String) throws -> String { - let key = registrar.lookupKey(forAsset: assetFilePath) - guard let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { + public static func getAbsPathAsset(plugin: SwiftFlutterPlugin, assetFilePath: String) throws -> String { + guard let key = plugin.registrar?.lookupKey(forAsset: assetFilePath), + let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } return assetAbsPath diff --git a/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift b/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift index c38afc7c..281abbbf 100644 --- a/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift +++ b/ios/Classes/WebAuthenticationSession/WebAuthenticationSession.swift @@ -98,7 +98,7 @@ public class WebAuthenticationSession : NSObject, ASWebAuthenticationPresentatio channelDelegate?.dispose() channelDelegate = nil session = nil - WebAuthenticationSessionManager.sessions[id] = nil + plugin?.webAuthenticationSessionManager?.sessions[id] = nil plugin = nil } diff --git a/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift b/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift index 10536777..6ba8238d 100644 --- a/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift +++ b/ios/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift @@ -15,7 +15,7 @@ import SafariServices public class WebAuthenticationSessionManager: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webauthenticationsession" var plugin: SwiftFlutterPlugin? - static var sessions: [String: WebAuthenticationSession?] = [:] + var sessions: [String: WebAuthenticationSession?] = [:] init(plugin: SwiftFlutterPlugin) { super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) @@ -53,7 +53,7 @@ public class WebAuthenticationSessionManager: ChannelDelegate { let _ = initialSettings.parse(settings: settings) let session = WebAuthenticationSession(plugin: plugin, id: id, url: sessionUrl, callbackURLScheme: callbackURLScheme, settings: initialSettings) session.prepare() - WebAuthenticationSessionManager.sessions[id] = session + sessions[id] = session result(true) return } @@ -63,12 +63,12 @@ public class WebAuthenticationSessionManager: ChannelDelegate { public override func dispose() { super.dispose() - let sessions = WebAuthenticationSessionManager.sessions.values - sessions.forEach { (session: WebAuthenticationSession?) in + let sessionValues = sessions.values + sessionValues.forEach { (session: WebAuthenticationSession?) in session?.cancel() session?.dispose() } - WebAuthenticationSessionManager.sessions.removeAll() + sessions.removeAll() plugin = nil } diff --git a/lib/src/android/main.dart b/lib/src/android/main.dart index b9471411..210fcc42 100644 --- a/lib/src/android/main.dart +++ b/lib/src/android/main.dart @@ -1,6 +1,6 @@ export 'service_worker_controller.dart'; export 'webview_feature.dart' show WebViewFeature, AndroidWebViewFeature; -export 'proxy_controller.dart'; +export 'proxy_controller.dart' show ProxyController, ProxySettings; export 'webview_asset_loader.dart' show WebViewAssetLoader, diff --git a/lib/src/android/proxy_controller.dart b/lib/src/android/proxy_controller.dart index 50d3210a..988db110 100644 --- a/lib/src/android/proxy_controller.dart +++ b/lib/src/android/proxy_controller.dart @@ -1,9 +1,13 @@ import 'dart:async'; import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; +import '../types/proxy_rule.dart'; import 'webview_feature.dart'; import '../in_app_webview/webview.dart'; import '../types/main.dart'; +part 'proxy_controller.g.dart'; + ///Manages setting and clearing a process-specific override for the Android system-wide proxy settings that govern network requests made by [WebView]. /// ///[WebView] may make network requests in order to fetch content that is not otherwise read from the file system or provided directly by application code. @@ -78,7 +82,8 @@ class ProxyController { /// ///**Supported Platforms/Implementations**: ///- Android native WebView ([Official API - ProxyConfig](https://developer.android.com/reference/androidx/webkit/ProxyConfig)) -class ProxySettings { +@ExchangeableObject(copyMethod: true) +class ProxySettings_ { ///List of bypass rules. /// ///A bypass rule describes URLs that should skip proxy override settings and make a direct connection instead. These can be URLs or IP addresses. Wildcards are accepted. @@ -99,7 +104,7 @@ class ProxySettings { ///Port number is optional and defaults to `80` for `HTTP`, `443` for `HTTPS` and `1080` for `SOCKS`. /// ///The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2). - List proxyRules; + List proxyRules; ///Hostnames without a period in them (and that are not IP literals) will skip proxy settings and be connected to directly instead. Examples: `"abc"`, `"local"`, `"some-domain"`. /// @@ -128,7 +133,7 @@ class ProxySettings { ///**NOTE**: available only if [WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS] feature is supported. bool reverseBypassEnabled; - ProxySettings( + ProxySettings_( {this.bypassRules = const [], this.directs = const [], this.proxyRules = const [], @@ -136,40 +141,40 @@ class ProxySettings { this.removeImplicitRules, this.reverseBypassEnabled = false}); - Map toMap() { - return { - "bypassRules": bypassRules, - "directs": directs, - "proxyRules": proxyRules.map((e) => e.toMap()).toList(), - "bypassSimpleHostnames": bypassSimpleHostnames, - "removeImplicitRules": removeImplicitRules, - "reverseBypassEnabled": reverseBypassEnabled - }; - } - - static ProxySettings fromMap(Map map) { - var settings = ProxySettings(); - settings.bypassRules = map["bypassRules"]; - settings.directs = map["directs"]; - settings.proxyRules = (map["proxyRules"].cast>() - as List>) - .map((e) => ProxyRule.fromMap(e)) as List; - settings.bypassSimpleHostnames = map["bypassSimpleHostnames"]; - settings.removeImplicitRules = map["removeImplicitRules"]; - settings.reverseBypassEnabled = map["reverseBypassEnabled"]; - return settings; - } - - Map toJson() { - return this.toMap(); - } - - @override - String toString() { - return toMap().toString(); - } - - ProxySettings copy() { - return ProxySettings.fromMap(this.toMap()); - } + // Map toMap() { + // return { + // "bypassRules": bypassRules, + // "directs": directs, + // "proxyRules": proxyRules.map((e) => e.toMap()).toList(), + // "bypassSimpleHostnames": bypassSimpleHostnames, + // "removeImplicitRules": removeImplicitRules, + // "reverseBypassEnabled": reverseBypassEnabled + // }; + // } + // + // static ProxySettings fromMap(Map map) { + // var settings = ProxySettings(); + // settings.bypassRules = map["bypassRules"]; + // settings.directs = map["directs"]; + // settings.proxyRules = (map["proxyRules"].cast>() + // as List>) + // .map((e) => ProxyRule.fromMap(e)) as List; + // settings.bypassSimpleHostnames = map["bypassSimpleHostnames"]; + // settings.removeImplicitRules = map["removeImplicitRules"]; + // settings.reverseBypassEnabled = map["reverseBypassEnabled"]; + // return settings; + // } + // + // Map toJson() { + // return this.toMap(); + // } + // + // @override + // String toString() { + // return 'ProxySettings{bypassRules: $bypassRules, directs: $directs, proxyRules: $proxyRules, bypassSimpleHostnames: $bypassSimpleHostnames, removeImplicitRules: $removeImplicitRules, reverseBypassEnabled: $reverseBypassEnabled}'; + // } + // + // ProxySettings copy() { + // return ProxySettings.fromMap(this.toMap()); + // } } diff --git a/lib/src/android/proxy_controller.g.dart b/lib/src/android/proxy_controller.g.dart new file mode 100644 index 00000000..418215bf --- /dev/null +++ b/lib/src/android/proxy_controller.g.dart @@ -0,0 +1,114 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'proxy_controller.dart'; + +// ************************************************************************** +// ExchangeableObjectGenerator +// ************************************************************************** + +///Class that represents the settings used to configure the [ProxyController]. +/// +///**Supported Platforms/Implementations**: +///- Android native WebView ([Official API - ProxyConfig](https://developer.android.com/reference/androidx/webkit/ProxyConfig)) +class ProxySettings { + ///List of bypass rules. + /// + ///A bypass rule describes URLs that should skip proxy override settings and make a direct connection instead. These can be URLs or IP addresses. Wildcards are accepted. + ///For instance, the rule "*example.com" would mean that requests to "http://example.com" and "www.example.com" would not be directed to any proxy, + ///instead, would be made directly to the origin specified by the URL. + List bypassRules; + + ///Hostnames without a period in them (and that are not IP literals) will skip proxy settings and be connected to directly instead. Examples: `"abc"`, `"local"`, `"some-domain"`. + /// + ///Hostnames with a trailing dot are not considered simple by this definition. + bool? bypassSimpleHostnames; + + ///List of scheme filters. + /// + ///URLs that match these scheme filters are connected to directly instead of using a proxy server. + List directs; + + ///List of proxy rules to be used for all URLs. This method can be called multiple times to add multiple rules. Additional rules have decreasing precedence. + /// + ///Proxy is a string in the format `[scheme://]host[:port]`. + ///Scheme is optional, if present must be `HTTP`, `HTTPS` or [SOCKS](https://tools.ietf.org/html/rfc1928) and defaults to `HTTP`. + ///Host is one of an IPv6 literal with brackets, an IPv4 literal or one or more labels separated by a period. + ///Port number is optional and defaults to `80` for `HTTP`, `443` for `HTTPS` and `1080` for `SOCKS`. + /// + ///The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2). + List proxyRules; + + ///By default, certain hostnames implicitly bypass the proxy if they are link-local IPs, or localhost addresses. + ///For instance hostnames matching any of (non-exhaustive list): + ///- localhost + ///- *.localhost + ///- [::1] + ///- 127.0.0.1/8 + ///- 169.254/16 + ///- [FE80::]/10 + ///Set this to `true` to override the default behavior and force localhost and link-local URLs to be sent through the proxy. + bool? removeImplicitRules; + + ///Reverse the bypass list. + /// + ///The default value is `false`, in which case all URLs will use proxy settings except the ones in the bypass list, which will be connected to directly instead. + /// + ///If set to `true`, then only URLs in the bypass list will use these proxy settings, and all other URLs will be connected to directly. + /// + ///Use [bypassRules] to add bypass rules. + /// + ///**NOTE**: available only if [WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS] feature is supported. + bool reverseBypassEnabled; + ProxySettings( + {this.bypassRules = const [], + this.bypassSimpleHostnames, + this.directs = const [], + this.proxyRules = const [], + this.removeImplicitRules, + this.reverseBypassEnabled = false}); + + ///Gets a possible [ProxySettings] instance from a [Map] value. + static ProxySettings? fromMap(Map? map) { + if (map == null) { + return null; + } + final instance = ProxySettings( + bypassSimpleHostnames: map['bypassSimpleHostnames'], + removeImplicitRules: map['removeImplicitRules'], + ); + instance.bypassRules = + List.from(map['bypassRules']!.cast()); + instance.directs = List.from(map['directs']!.cast()); + instance.proxyRules = List.from(map['proxyRules'] + .map((e) => ProxyRule.fromMap(e?.cast())!)); + instance.reverseBypassEnabled = map['reverseBypassEnabled']; + return instance; + } + + ///Converts instance to a map. + Map toMap() { + return { + "bypassRules": bypassRules, + "bypassSimpleHostnames": bypassSimpleHostnames, + "directs": directs, + "proxyRules": proxyRules.map((e) => e.toMap()).toList(), + "removeImplicitRules": removeImplicitRules, + "reverseBypassEnabled": reverseBypassEnabled, + }; + } + + ///Converts instance to a map. + Map toJson() { + return toMap(); + } + + ///Returns a copy of ProxySettings. + ProxySettings copy() { + return ProxySettings.fromMap(toMap()) ?? ProxySettings(); + } + + @override + String toString() { + return 'ProxySettings{bypassRules: $bypassRules, bypassSimpleHostnames: $bypassSimpleHostnames, directs: $directs, proxyRules: $proxyRules, removeImplicitRules: $removeImplicitRules, reverseBypassEnabled: $reverseBypassEnabled}'; + } +} diff --git a/lib/src/chrome_safari_browser/chrome_safari_browser.dart b/lib/src/chrome_safari_browser/chrome_safari_browser.dart index 720fb58b..4d30e864 100755 --- a/lib/src/chrome_safari_browser/chrome_safari_browser.dart +++ b/lib/src/chrome_safari_browser/chrome_safari_browser.dart @@ -15,30 +15,6 @@ import '../debug_logging_settings.dart'; import '../web_uri.dart'; import 'chrome_safari_browser_settings.dart'; -class ChromeSafariBrowserAlreadyOpenedException implements Exception { - final dynamic message; - - ChromeSafariBrowserAlreadyOpenedException([this.message]); - - String toString() { - Object? message = this.message; - if (message == null) return "ChromeSafariBrowserAlreadyOpenedException"; - return "ChromeSafariBrowserAlreadyOpenedException: $message"; - } -} - -class ChromeSafariBrowserNotOpenedException implements Exception { - final dynamic message; - - ChromeSafariBrowserNotOpenedException([this.message]); - - String toString() { - Object? message = this.message; - if (message == null) return "ChromeSafariBrowserNotOpenedException"; - return "ChromeSafariBrowserNotOpenedException: $message"; - } -} - ///This class uses native [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android ///and [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS. /// @@ -60,7 +36,7 @@ class ChromeSafariBrowser { Map _menuItems = new HashMap(); ChromeSafariBrowserSecondaryToolbar? _secondaryToolbar; bool _isOpened = false; - late MethodChannel _channel; + MethodChannel? _channel; static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser'); @@ -68,7 +44,7 @@ class ChromeSafariBrowser { id = IdGenerator.generate(); this._channel = MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id'); - this._channel.setMethodCallHandler((call) async { + this._channel?.setMethodCallHandler((call) async { try { return await _handleMethod(call); } on Error catch (e) { @@ -79,6 +55,19 @@ class ChromeSafariBrowser { _isOpened = false; } + _init() { + this._channel = + MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id'); + this._channel?.setMethodCallHandler((call) async { + try { + return await _handleMethod(call); + } on Error catch (e) { + print(e); + print(e.stackTrace); + } + }); + } + _debugLog(String method, dynamic args) { debugLog( className: this.runtimeType.toString(), @@ -125,8 +114,9 @@ class ChromeSafariBrowser { onWillOpenInBrowser(); break; case "onClosed": + _isOpened = false; + _dispose(); onClosed(); - this._isOpened = false; break; case "onItemActionPerform": String url = call.arguments["url"]; @@ -208,6 +198,9 @@ class ChromeSafariBrowser { // ignore: deprecated_member_use_from_same_package ChromeSafariBrowserClassOptions? options, ChromeSafariBrowserSettings? settings}) async { + assert(!_isOpened, 'The browser is already opened.'); + _isOpened = true; + if (Util.isIOS) { assert(url != null, 'The specified URL must not be null on iOS.'); assert(['http', 'https'].contains(url!.scheme), @@ -216,9 +209,8 @@ class ChromeSafariBrowser { if (url != null) { assert(url.toString().isNotEmpty, 'The specified URL must not be empty.'); } - this.throwIsAlreadyOpened(message: url != null ? 'Cannot open $url!' : ''); - this._isOpened = true; + _init(); List> menuItemList = []; _menuItems.forEach((key, value) { @@ -269,7 +261,7 @@ class ChromeSafariBrowser { args.putIfAbsent('otherLikelyURLs', () => otherLikelyURLs?.map((e) => e.toString()).toList()); args.putIfAbsent('referrer', () => referrer?.toString()); - await _channel.invokeMethod("launchUrl", args); + await _channel?.invokeMethod("launchUrl", args); } ///Tells the browser of a likely future navigation to a URL. @@ -290,7 +282,7 @@ class ChromeSafariBrowser { args.putIfAbsent('url', () => url?.toString()); args.putIfAbsent('otherLikelyURLs', () => otherLikelyURLs?.map((e) => e.toString()).toList()); - return await _channel.invokeMethod("mayLaunchUrl", args); + return await _channel?.invokeMethod("mayLaunchUrl", args); } ///Requests to validate a relationship between the application and an origin. @@ -315,7 +307,7 @@ class ChromeSafariBrowser { Map args = {}; args.putIfAbsent('relation', () => relation.toNativeValue()); args.putIfAbsent('origin', () => origin.toString()); - return await _channel.invokeMethod("validateRelationship", args); + return await _channel?.invokeMethod("validateRelationship", args); } ///Closes the [ChromeSafariBrowser] instance. @@ -325,7 +317,7 @@ class ChromeSafariBrowser { ///- iOS Future close() async { Map args = {}; - await _channel.invokeMethod("close", args); + await _channel?.invokeMethod("close", args); } ///Set a custom action button. @@ -349,7 +341,7 @@ class ChromeSafariBrowser { Map args = {}; args.putIfAbsent('icon', () => icon); args.putIfAbsent('description', () => description); - await _channel.invokeMethod("updateActionButton", args); + await _channel?.invokeMethod("updateActionButton", args); _actionButton?.icon = icon; _actionButton?.description = description; } @@ -375,7 +367,7 @@ class ChromeSafariBrowser { ChromeSafariBrowserSecondaryToolbar secondaryToolbar) async { Map args = {}; args.putIfAbsent('secondaryToolbar', () => secondaryToolbar.toMap()); - await _channel.invokeMethod("updateSecondaryToolbar", args); + await _channel?.invokeMethod("updateSecondaryToolbar", args); this._secondaryToolbar = secondaryToolbar; } @@ -546,23 +538,13 @@ class ChromeSafariBrowser { ///- Android ///- iOS bool isOpened() { - return this._isOpened; + return _isOpened; } - void throwIsAlreadyOpened({String message = ''}) { - if (this.isOpened()) { - throw ChromeSafariBrowserAlreadyOpenedException([ - 'Error: ${(message.isEmpty) ? '' : message + ' '}The browser is already opened.' - ]); - } - } - - void throwIsNotOpened({String message = ''}) { - if (!this.isOpened()) { - throw ChromeSafariBrowserNotOpenedException([ - 'Error: ${(message.isEmpty) ? '' : message + ' '}The browser is not opened.' - ]); - } + ///Disposes the channel. + void _dispose() { + _channel?.setMethodCallHandler(null); + _channel = null; } } diff --git a/lib/src/cookie_manager.dart b/lib/src/cookie_manager.dart index d4f2ba39..fb89e734 100755 --- a/lib/src/cookie_manager.dart +++ b/lib/src/cookie_manager.dart @@ -275,7 +275,7 @@ class CookieManager { await pageLoaded.future; List documentCookies = (await headlessWebView.webViewController - .evaluateJavascript(source: 'document.cookie') as String) + !.evaluateJavascript(source: 'document.cookie') as String) .split(';') .map((documentCookie) => documentCookie.trim()) .toList(); diff --git a/lib/src/find_interaction/find_interaction_controller.dart b/lib/src/find_interaction/find_interaction_controller.dart index a28366a7..1c8bcd42 100644 --- a/lib/src/find_interaction/find_interaction_controller.dart +++ b/lib/src/find_interaction/find_interaction_controller.dart @@ -37,20 +37,6 @@ class FindInteractionController { FindInteractionController({this.onFindResultReceived}) {} - void initMethodChannel(dynamic id) { - this._channel = MethodChannel( - 'com.pichillilorenzo/flutter_inappwebview_find_interaction_$id'); - - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); - } - _debugLog(String method, dynamic args) { debugLog( className: this.runtimeType.toString(), @@ -217,4 +203,29 @@ class FindInteractionController { ?.cast(); return FindSession.fromMap(result); } + + ///Disposes the controller. + void dispose({bool isKeepAlive = false}) { + if (!isKeepAlive) { + _channel?.setMethodCallHandler(null); + } + _channel = null; + } } + +extension InternalFindInteractionController on FindInteractionController { + void init(dynamic id) { + this._channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_find_interaction_$id'); + + this._channel?.setMethodCallHandler((call) async { + if (_channel == null) return null; + try { + return await _handleMethod(call); + } on Error catch (e) { + print(e); + print(e.stackTrace); + } + }); + } +} \ No newline at end of file diff --git a/lib/src/find_interaction/main.dart b/lib/src/find_interaction/main.dart index b0a3a463..9862f9d7 100644 --- a/lib/src/find_interaction/main.dart +++ b/lib/src/find_interaction/main.dart @@ -1 +1 @@ -export 'find_interaction_controller.dart'; +export 'find_interaction_controller.dart' show FindInteractionController; diff --git a/lib/src/in_app_browser/in_app_browser.dart b/lib/src/in_app_browser/in_app_browser.dart index 30199275..a0d6b6f4 100755 --- a/lib/src/in_app_browser/in_app_browser.dart +++ b/lib/src/in_app_browser/in_app_browser.dart @@ -18,30 +18,7 @@ import '../print_job/main.dart'; import '../web_uri.dart'; import 'in_app_browser_settings.dart'; import '../debug_logging_settings.dart'; - -class InAppBrowserAlreadyOpenedException implements Exception { - final dynamic message; - - InAppBrowserAlreadyOpenedException([this.message]); - - String toString() { - Object? message = this.message; - if (message == null) return "InAppBrowserAlreadyOpenedException"; - return "InAppBrowserAlreadyOpenedException: $message"; - } -} - -class InAppBrowserNotOpenedException implements Exception { - final dynamic message; - - InAppBrowserNotOpenedException([this.message]); - - String toString() { - Object? message = this.message; - if (message == null) return "InAppBrowserNotOpenedException"; - return "InAppBrowserNotOpenedException: $message"; - } -} +import '../pull_to_refresh/pull_to_refresh_controller.dart'; ///This class uses the native WebView of the platform. ///The [webViewController] field can be used to access the [InAppWebViewController] API. @@ -70,11 +47,11 @@ class InAppBrowser { final UnmodifiableListView? initialUserScripts; bool _isOpened = false; - late MethodChannel _channel; + MethodChannel? _channel; static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser'); - late final InAppWebViewController _webViewController; + InAppWebViewController? _webViewController; ///WebView Controller that can be used to access the [InAppWebViewController] API. ///When [onExit] is fired, this will be `null` and cannot be used anymore. @@ -85,18 +62,16 @@ class InAppBrowser { ///The window id of a [CreateWindowAction.windowId]. final int? windowId; - ///Represents the WebView native implementation to be used. - ///The default value is [WebViewImplementation.NATIVE]. - final WebViewImplementation implementation; - InAppBrowser( {this.windowId, - this.initialUserScripts, - this.implementation = WebViewImplementation.NATIVE}) { + this.initialUserScripts}) { id = IdGenerator.generate(); + } + + _init() { this._channel = MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id'); - this._channel.setMethodCallHandler((call) async { + this._channel?.setMethodCallHandler((call) async { try { return await _handleMethod(call); } on Error catch (e) { @@ -104,9 +79,10 @@ class InAppBrowser { print(e.stackTrace); } }); - _isOpened = false; _webViewController = new InAppWebViewController.fromInAppBrowser( - this._channel, this, this.initialUserScripts); + this._channel!, this, this.initialUserScripts); + pullToRefreshController?.init(id); + findInteractionController?.init(id); } _debugLog(String method, dynamic args) { @@ -122,18 +98,16 @@ class InAppBrowser { switch (call.method) { case "onBrowserCreated": _debugLog(call.method, call.arguments); - this._isOpened = true; - this.pullToRefreshController?.initMethodChannel(id); - this.findInteractionController?.initMethodChannel(id); onBrowserCreated(); break; case "onExit": _debugLog(call.method, call.arguments); - this._isOpened = false; + _isOpened = false; + _dispose(); onExit(); break; default: - return _webViewController.handleMethod(call); + return _webViewController?.handleMethod(call); } } @@ -154,8 +128,10 @@ class InAppBrowser { // ignore: deprecated_member_use_from_same_package @Deprecated('Use settings instead') InAppBrowserClassOptions? options, InAppBrowserClassSettings? settings}) async { - this.throwIfAlreadyOpened(message: 'Cannot open $urlRequest!'); + assert(!_isOpened, 'The browser is already opened.'); + _isOpened = true; assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty); + _init(); var initialSettings = settings?.toMap() ?? options?.toMap() ?? @@ -173,7 +149,6 @@ class InAppBrowser { args.putIfAbsent('settings', () => initialSettings); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('windowId', () => windowId); - args.putIfAbsent('implementation', () => implementation.toNativeValue()); args.putIfAbsent('initialUserScripts', () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings); @@ -227,8 +202,10 @@ class InAppBrowser { // ignore: deprecated_member_use_from_same_package @Deprecated('Use settings instead') InAppBrowserClassOptions? options, InAppBrowserClassSettings? settings}) async { - this.throwIfAlreadyOpened(message: 'Cannot open $assetFilePath!'); + assert(!_isOpened, 'The browser is already opened.'); + _isOpened = true; assert(assetFilePath.isNotEmpty); + _init(); var initialSettings = settings?.toMap() ?? options?.toMap() ?? @@ -246,7 +223,6 @@ class InAppBrowser { args.putIfAbsent('settings', () => initialSettings); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('windowId', () => windowId); - args.putIfAbsent('implementation', () => implementation.toNativeValue()); args.putIfAbsent('initialUserScripts', () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings); @@ -279,7 +255,9 @@ class InAppBrowser { // ignore: deprecated_member_use_from_same_package @Deprecated('Use settings instead') InAppBrowserClassOptions? options, InAppBrowserClassSettings? settings}) async { - this.throwIfAlreadyOpened(message: 'Cannot open data!'); + assert(!_isOpened, 'The browser is already opened.'); + _isOpened = true; + _init(); var initialSettings = settings?.toMap() ?? options?.toMap() ?? @@ -302,7 +280,6 @@ class InAppBrowser { () => (historyUrl ?? androidHistoryUrl)?.toString() ?? "about:blank"); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('windowId', () => windowId); - args.putIfAbsent('implementation', () => implementation.toNativeValue()); args.putIfAbsent('initialUserScripts', () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings); @@ -317,6 +294,7 @@ class InAppBrowser { ///- MacOS static Future openWithSystemBrowser({required WebUri url}) async { assert(url.toString().isNotEmpty); + Map args = {}; args.putIfAbsent('url', () => url.toString()); return await _sharedChannel.invokeMethod('openWithSystemBrowser', args); @@ -329,9 +307,10 @@ class InAppBrowser { ///- iOS ///- MacOS Future show() async { - this.throwIfNotOpened(); + assert(_isOpened, 'The browser is not opened.'); + Map args = {}; - await _channel.invokeMethod('show', args); + await _channel?.invokeMethod('show', args); } ///Hides the [InAppBrowser] window. Calling this has no effect if the [InAppBrowser] was already hidden. @@ -341,9 +320,10 @@ class InAppBrowser { ///- iOS ///- MacOS Future hide() async { - this.throwIfNotOpened(); + assert(_isOpened, 'The browser is not opened.'); + Map args = {}; - await _channel.invokeMethod('hide', args); + await _channel?.invokeMethod('hide', args); } ///Closes the [InAppBrowser] window. @@ -353,9 +333,10 @@ class InAppBrowser { ///- iOS ///- MacOS Future close() async { - this.throwIfNotOpened(); + assert(_isOpened, 'The browser is not opened.'); + Map args = {}; - await _channel.invokeMethod('close', args); + await _channel?.invokeMethod('close', args); } ///Check if the Web View of the [InAppBrowser] instance is hidden. @@ -365,29 +346,30 @@ class InAppBrowser { ///- iOS ///- MacOS Future isHidden() async { - this.throwIfNotOpened(); + assert(_isOpened, 'The browser is not opened.'); + Map args = {}; - return await _channel.invokeMethod('isHidden', args); + return await _channel?.invokeMethod('isHidden', args); } ///Use [setSettings] instead. @Deprecated('Use setSettings instead') Future setOptions({required InAppBrowserClassOptions options}) async { - this.throwIfNotOpened(); + assert(_isOpened, 'The browser is not opened.'); Map args = {}; args.putIfAbsent('settings', () => options.toMap()); - await _channel.invokeMethod('setSettings', args); + await _channel?.invokeMethod('setSettings', args); } ///Use [getSettings] instead. @Deprecated('Use getSettings instead') Future getOptions() async { - this.throwIfNotOpened(); + assert(_isOpened, 'The browser is not opened.'); Map args = {}; Map? options = - await _channel.invokeMethod('getSettings', args); + await _channel?.invokeMethod('getSettings', args); if (options != null) { options = options.cast(); return InAppBrowserClassOptions.fromMap(options as Map); @@ -404,11 +386,11 @@ class InAppBrowser { ///- MacOS Future setSettings( {required InAppBrowserClassSettings settings}) async { - this.throwIfNotOpened(); + assert(_isOpened, 'The browser is not opened.'); Map args = {}; args.putIfAbsent('settings', () => settings.toMap()); - await _channel.invokeMethod('setSettings', args); + await _channel?.invokeMethod('setSettings', args); } ///Gets the current [InAppBrowser] settings. Returns `null` if it wasn't able to get them. @@ -418,11 +400,12 @@ class InAppBrowser { ///- iOS ///- MacOS Future getSettings() async { - this.throwIfNotOpened(); + assert(_isOpened, 'The browser is not opened.'); + Map args = {}; Map? settings = - await _channel.invokeMethod('getSettings', args); + await _channel?.invokeMethod('getSettings', args); if (settings != null) { settings = settings.cast(); return InAppBrowserClassSettings.fromMap( @@ -1321,19 +1304,15 @@ class InAppBrowser { ///- iOS void onContentSizeChanged(Size oldContentSize, Size newContentSize) {} - void throwIfAlreadyOpened({String message = ''}) { - if (this.isOpened()) { - throw InAppBrowserAlreadyOpenedException([ - 'Error: ${(message.isEmpty) ? '' : message + ' '}The browser is already opened.' - ]); - } - } - - void throwIfNotOpened({String message = ''}) { - if (!this.isOpened()) { - throw InAppBrowserNotOpenedException([ - 'Error: ${(message.isEmpty) ? '' : message + ' '}The browser is not opened.' - ]); - } + ///Disposes the channel and controllers. + void _dispose() { + _channel?.setMethodCallHandler(null); + _channel = null; + _webViewController?.dispose(); + _webViewController = null; + pullToRefreshController?.dispose(); + pullToRefreshController = null; + findInteractionController?.dispose(); + findInteractionController = null; } } diff --git a/lib/src/in_app_webview/_static_channel.dart b/lib/src/in_app_webview/_static_channel.dart index 07f94bf3..af2feaf1 100644 --- a/lib/src/in_app_webview/_static_channel.dart +++ b/lib/src/in_app_webview/_static_channel.dart @@ -1,4 +1,4 @@ import 'package:flutter/services.dart'; const IN_APP_WEBVIEW_STATIC_CHANNEL = - const MethodChannel('com.pichillilorenzo/flutter_inappwebview_static'); + const MethodChannel('com.pichillilorenzo/flutter_inappwebview_manager'); diff --git a/lib/src/in_app_webview/android/in_app_webview_controller.dart b/lib/src/in_app_webview/android/in_app_webview_controller.dart index 985ac143..4b7f58dc 100644 --- a/lib/src/in_app_webview/android/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/android/in_app_webview_controller.dart @@ -8,7 +8,7 @@ import '../in_app_webview_controller.dart'; ///Use [InAppWebViewController] instead. @Deprecated("Use InAppWebViewController instead") class AndroidInAppWebViewController { - late MethodChannel _channel; + MethodChannel? _channel; AndroidInAppWebViewController({required MethodChannel channel}) { this._channel = channel; @@ -18,28 +18,28 @@ class AndroidInAppWebViewController { @Deprecated("Use InAppWebViewController.startSafeBrowsing instead") Future startSafeBrowsing() async { Map args = {}; - return await _channel.invokeMethod('startSafeBrowsing', args); + return await _channel?.invokeMethod('startSafeBrowsing', args); } ///Use [InAppWebViewController.clearSslPreferences] instead. @Deprecated("Use InAppWebViewController.clearSslPreferences instead") Future clearSslPreferences() async { Map args = {}; - await _channel.invokeMethod('clearSslPreferences', args); + await _channel?.invokeMethod('clearSslPreferences', args); } ///Use [InAppWebViewController.pause] instead. @Deprecated("Use InAppWebViewController.pause instead") Future pause() async { Map args = {}; - await _channel.invokeMethod('pause', args); + await _channel?.invokeMethod('pause', args); } ///Use [InAppWebViewController.resume] instead. @Deprecated("Use InAppWebViewController.resume instead") Future resume() async { Map args = {}; - await _channel.invokeMethod('resume', args); + await _channel?.invokeMethod('resume', args); } ///Use [InAppWebViewController.pageDown] instead. @@ -47,7 +47,7 @@ class AndroidInAppWebViewController { Future pageDown({required bool bottom}) async { Map args = {}; args.putIfAbsent("bottom", () => bottom); - return await _channel.invokeMethod('pageDown', args); + return await _channel?.invokeMethod('pageDown', args); } ///Use [InAppWebViewController.pageUp] instead. @@ -55,28 +55,28 @@ class AndroidInAppWebViewController { Future pageUp({required bool top}) async { Map args = {}; args.putIfAbsent("top", () => top); - return await _channel.invokeMethod('pageUp', args); + return await _channel?.invokeMethod('pageUp', args); } ///Use [InAppWebViewController.zoomIn] instead. @Deprecated("Use InAppWebViewController.zoomIn instead") Future zoomIn() async { Map args = {}; - return await _channel.invokeMethod('zoomIn', args); + return await _channel?.invokeMethod('zoomIn', args); } ///Use [InAppWebViewController.zoomOut] instead. @Deprecated("Use InAppWebViewController.zoomOut instead") Future zoomOut() async { Map args = {}; - return await _channel.invokeMethod('zoomOut', args); + return await _channel?.invokeMethod('zoomOut', args); } ///Use [InAppWebViewController.clearHistory] instead. @Deprecated("Use InAppWebViewController.clearHistory instead") Future clearHistory() async { Map args = {}; - return await _channel.invokeMethod('clearHistory', args); + return await _channel?.invokeMethod('clearHistory', args); } ///Use [InAppWebViewController.clearClientCertPreferences] instead. @@ -120,7 +120,11 @@ class AndroidInAppWebViewController { @Deprecated('Use InAppWebViewController.getOriginalUrl instead') Future getOriginalUrl() async { Map args = {}; - String? url = await _channel.invokeMethod('getOriginalUrl', args); + String? url = await _channel?.invokeMethod('getOriginalUrl', args); return url != null ? Uri.tryParse(url) : null; } + + void dispose() { + _channel = null; + } } diff --git a/lib/src/in_app_webview/apple/in_app_webview_controller.dart b/lib/src/in_app_webview/apple/in_app_webview_controller.dart index 84056e08..0934606d 100644 --- a/lib/src/in_app_webview/apple/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/apple/in_app_webview_controller.dart @@ -9,7 +9,8 @@ import '../in_app_webview_controller.dart'; ///Use [InAppWebViewController] instead. @Deprecated("Use InAppWebViewController instead") class IOSInAppWebViewController { - late MethodChannel _channel; + MethodChannel? _channel; + IOSInAppWebViewController({required MethodChannel channel}) { this._channel = channel; } @@ -18,7 +19,7 @@ class IOSInAppWebViewController { @Deprecated("Use InAppWebViewController.reloadFromOrigin instead") Future reloadFromOrigin() async { Map args = {}; - await _channel.invokeMethod('reloadFromOrigin', args); + await _channel?.invokeMethod('reloadFromOrigin', args); } ///Use [InAppWebViewController.createPdf] instead. @@ -31,21 +32,21 @@ class IOSInAppWebViewController { Map args = {}; args.putIfAbsent('pdfConfiguration', () => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap()); - return await _channel.invokeMethod('createPdf', args); + return await _channel?.invokeMethod('createPdf', args); } ///Use [InAppWebViewController.createWebArchiveData] instead. @Deprecated("Use InAppWebViewController.createWebArchiveData instead") Future createWebArchiveData() async { Map args = {}; - return await _channel.invokeMethod('createWebArchiveData', args); + return await _channel?.invokeMethod('createWebArchiveData', args); } ///Use [InAppWebViewController.hasOnlySecureContent] instead. @Deprecated("Use InAppWebViewController.hasOnlySecureContent instead") Future hasOnlySecureContent() async { Map args = {}; - return await _channel.invokeMethod('hasOnlySecureContent', args); + return await _channel?.invokeMethod('hasOnlySecureContent', args); } ///Use [InAppWebViewController.handlesURLScheme] instead. @@ -53,4 +54,8 @@ class IOSInAppWebViewController { static Future handlesURLScheme(String urlScheme) async { return await InAppWebViewController.handlesURLScheme(urlScheme); } + + void dispose() { + _channel = null; + } } diff --git a/lib/src/in_app_webview/headless_in_app_webview.dart b/lib/src/in_app_webview/headless_in_app_webview.dart index 4718b11c..f7c53199 100644 --- a/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/lib/src/in_app_webview/headless_in_app_webview.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview/src/util.dart'; +import '../util.dart'; import '../context_menu.dart'; import '../find_interaction/find_interaction_controller.dart'; @@ -38,10 +38,12 @@ class HeadlessInAppWebView implements WebView, Disposable { static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview'); - late MethodChannel _channel; + MethodChannel? _channel; + + InAppWebViewController? _webViewController; ///WebView Controller that can be used to access the [InAppWebViewController] API. - late final InAppWebViewController webViewController; + InAppWebViewController? get webViewController => _webViewController; ///{@macro flutter_inappwebview.WebView.windowId} final int? windowId; @@ -74,7 +76,6 @@ class HeadlessInAppWebView implements WebView, Disposable { this.initialUserScripts, this.pullToRefreshController, this.findInteractionController, - this.implementation = WebViewImplementation.NATIVE, this.onWebViewCreated, this.onLoadStart, this.onLoadStop, @@ -180,10 +181,15 @@ class HeadlessInAppWebView implements WebView, Disposable { this.onMicrophoneCaptureStateChanged, this.onContentSizeChanged}) { id = IdGenerator.generate(); - webViewController = new InAppWebViewController(id, this); + } + + _init() { + _webViewController = InAppWebViewController(id, this); + pullToRefreshController?.init(id); + findInteractionController?.init(id); this._channel = MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview_$id'); - this._channel.setMethodCallHandler((call) async { + this._channel?.setMethodCallHandler((call) async { try { return await handleMethod(call); } on Error catch (e) { @@ -196,10 +202,8 @@ class HeadlessInAppWebView implements WebView, Disposable { Future handleMethod(MethodCall call) async { switch (call.method) { case "onWebViewCreated": - pullToRefreshController?.initMethodChannel(id); - findInteractionController?.initMethodChannel(id); - if (onWebViewCreated != null) { - onWebViewCreated!(webViewController); + if (onWebViewCreated != null && _webViewController != null) { + onWebViewCreated!(_webViewController!); } break; default: @@ -222,6 +226,7 @@ class HeadlessInAppWebView implements WebView, Disposable { return; } _started = true; + _init(); final initialSettings = this.initialSettings ?? InAppWebViewSettings(); _inferInitialSettings(initialSettings); @@ -249,7 +254,6 @@ class HeadlessInAppWebView implements WebView, Disposable { 'initialSettings': settingsMap, 'contextMenu': this.contextMenu?.toMap() ?? {}, 'windowId': this.windowId, - 'implementation': this.implementation.toNativeValue(), 'initialUserScripts': this.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], 'pullToRefreshSettings': pullToRefreshSettings, @@ -306,9 +310,15 @@ class HeadlessInAppWebView implements WebView, Disposable { return; } Map args = {}; - await _channel.invokeMethod('dispose', args); + await _channel?.invokeMethod('dispose', args); + _channel?.setMethodCallHandler(null); + _channel = null; _started = false; _running = false; + _webViewController?.dispose(); + _webViewController = null; + pullToRefreshController?.dispose(); + findInteractionController?.dispose(); } ///Indicates if the headless WebView is running or not. @@ -343,7 +353,7 @@ class HeadlessInAppWebView implements WebView, Disposable { Map args = {}; args.putIfAbsent('size', () => size.toMap()); - await _channel.invokeMethod('setSize', args); + await _channel?.invokeMethod('setSize', args); } ///Gets the current size in pixels of the WebView. @@ -362,7 +372,7 @@ class HeadlessInAppWebView implements WebView, Disposable { Map args = {}; Map sizeMap = - (await _channel.invokeMethod('getSize', args))?.cast(); + (await _channel?.invokeMethod('getSize', args))?.cast(); return MapSize.fromMap(sizeMap); } @@ -403,10 +413,6 @@ class HeadlessInAppWebView implements WebView, Disposable { @override final FindInteractionController? findInteractionController; - ///{@macro flutter_inappwebview.WebView.implementation} - @override - final WebViewImplementation implementation; - ///Use [onGeolocationPermissionsHidePrompt] instead. @override @Deprecated('Use onGeolocationPermissionsHidePrompt instead') diff --git a/lib/src/in_app_webview/in_app_webview.dart b/lib/src/in_app_webview/in_app_webview.dart index 217fb842..e7b2ae90 100755 --- a/lib/src/in_app_webview/in_app_webview.dart +++ b/lib/src/in_app_webview/in_app_webview.dart @@ -8,8 +8,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter_inappwebview/src/in_app_webview/headless_in_app_webview.dart'; -import 'package:flutter_inappwebview/src/util.dart'; +import 'headless_in_app_webview.dart'; +import '../util.dart'; import '../find_interaction/find_interaction_controller.dart'; import '../web/web_platform_manager.dart'; @@ -23,6 +23,8 @@ import 'webview.dart'; import 'in_app_webview_controller.dart'; import 'in_app_webview_settings.dart'; import '../pull_to_refresh/main.dart'; +import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import 'in_app_webview_keep_alive.dart'; ///{@template flutter_inappwebview.InAppWebView} ///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree. @@ -46,7 +48,7 @@ class InAppWebView extends StatefulWidget implements WebView { @override final int? windowId; - ///The [HeadlessInAppWebView] to use to initialize this widget + ///The [HeadlessInAppWebView] to use to initialize this widget. /// ///**Supported Platforms/Implementations**: ///- Android native WebView @@ -54,33 +56,46 @@ class InAppWebView extends StatefulWidget implements WebView { ///- Web final HeadlessInAppWebView? headlessWebView; + ///Used to keep alive this WebView. + ///Remember to dispose the [InAppWebViewKeepAlive] instance + ///using [InAppWebViewController.disposeKeepAlive]. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + final InAppWebViewKeepAlive? keepAlive; + ///{@macro flutter_inappwebview.InAppWebView} const InAppWebView({ Key? key, this.windowId, + this.keepAlive, this.initialUrlRequest, this.initialFile, this.initialData, - @Deprecated('Use initialSettings instead') this.initialOptions, + @Deprecated('Use initialSettings instead') + this.initialOptions, this.initialSettings, this.initialUserScripts, this.pullToRefreshController, this.findInteractionController, - this.implementation = WebViewImplementation.NATIVE, this.contextMenu, this.onWebViewCreated, this.onLoadStart, this.onLoadStop, - @Deprecated("Use onReceivedError instead") this.onLoadError, + @Deprecated("Use onReceivedError instead") + this.onLoadError, this.onReceivedError, - @Deprecated("Use onReceivedHttpError instead") this.onLoadHttpError, + @Deprecated("Use onReceivedHttpError instead") + this.onLoadHttpError, this.onReceivedHttpError, this.onConsoleMessage, this.onProgressChanged, this.shouldOverrideUrlLoading, this.onLoadResource, this.onScrollChanged, - @Deprecated('Use onDownloadStartRequest instead') this.onDownloadStart, + @Deprecated('Use onDownloadStartRequest instead') + this.onDownloadStart, this.onDownloadStartRequest, @Deprecated('Use onLoadResourceWithCustomScheme instead') this.onLoadResourceCustomScheme, @@ -100,7 +115,8 @@ class InAppWebView extends StatefulWidget implements WebView { this.onAjaxProgress, this.shouldInterceptFetchRequest, this.onUpdateVisitedHistory, - @Deprecated("Use onPrintRequest instead") this.onPrint, + @Deprecated("Use onPrintRequest instead") + this.onPrint, this.onPrintRequest, this.onLongPressHitTestResult, this.onEnterFullscreen, @@ -111,7 +127,8 @@ class InAppWebView extends StatefulWidget implements WebView { this.onWindowBlur, this.onOverScrolled, this.onZoomScaleChanged, - @Deprecated('Use onSafeBrowsingHit instead') this.androidOnSafeBrowsingHit, + @Deprecated('Use onSafeBrowsingHit instead') + this.androidOnSafeBrowsingHit, this.onSafeBrowsingHit, @Deprecated('Use onPermissionRequest instead') this.androidOnPermissionRequest, @@ -137,13 +154,16 @@ class InAppWebView extends StatefulWidget implements WebView { @Deprecated('Use onFormResubmission instead') this.androidOnFormResubmission, this.onFormResubmission, - @Deprecated('Use onZoomScaleChanged instead') this.androidOnScaleChanged, - @Deprecated('Use onReceivedIcon instead') this.androidOnReceivedIcon, + @Deprecated('Use onZoomScaleChanged instead') + this.androidOnScaleChanged, + @Deprecated('Use onReceivedIcon instead') + this.androidOnReceivedIcon, this.onReceivedIcon, @Deprecated('Use onReceivedTouchIconUrl instead') this.androidOnReceivedTouchIconUrl, this.onReceivedTouchIconUrl, - @Deprecated('Use onJsBeforeUnload instead') this.androidOnJsBeforeUnload, + @Deprecated('Use onJsBeforeUnload instead') + this.androidOnJsBeforeUnload, this.onJsBeforeUnload, @Deprecated('Use onReceivedLoginRequest instead') this.androidOnReceivedLoginRequest, @@ -222,10 +242,6 @@ class InAppWebView extends StatefulWidget implements WebView { @override final URLRequest? initialUrlRequest; - ///{@macro flutter_inappwebview.WebView.implementation} - @override - final WebViewImplementation implementation; - ///{@macro flutter_inappwebview.WebView.initialUserScripts} @override final UnmodifiableListView? initialUserScripts; @@ -704,6 +720,15 @@ class _InAppWebViewState extends State { widget.pullToRefreshController?.options.toMap() ?? PullToRefreshSettings(enabled: false).toMap(); + if ((widget.headlessWebView?.isRunning() ?? false) && + widget.keepAlive != null) { + final headlessId = widget.headlessWebView?.id; + if (headlessId != null) { + // force keep alive id to match headless webview id + widget.keepAlive?.id = headlessId; + } + } + if (Util.isWeb) { return HtmlElementView( viewType: 'com.pichillilorenzo/flutter_inappwebview', @@ -727,9 +752,7 @@ class _InAppWebViewState extends State { } else if (Util.isAndroid) { var useHybridComposition = (widget.initialSettings != null ? initialSettings.useHybridComposition - : - // ignore: deprecated_member_use_from_same_package - widget.initialOptions?.android.useHybridComposition) ?? + : widget.initialOptions?.android.useHybridComposition) ?? true; return PlatformViewLink( @@ -762,11 +785,11 @@ class _InAppWebViewState extends State { 'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false ? widget.headlessWebView?.id : null, - 'implementation': widget.implementation.toNativeValue(), 'initialUserScripts': widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], - 'pullToRefreshSettings': pullToRefreshSettings + 'pullToRefreshSettings': pullToRefreshSettings, + 'keepAliveId': widget.keepAlive?.id }, ) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) @@ -790,10 +813,10 @@ class _InAppWebViewState extends State { 'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false ? widget.headlessWebView?.id : null, - 'implementation': widget.implementation.toNativeValue(), 'initialUserScripts': widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], - 'pullToRefreshSettings': pullToRefreshSettings + 'pullToRefreshSettings': pullToRefreshSettings, + 'keepAliveId': widget.keepAlive?.id }, creationParamsCodec: const StandardMessageCodec(), ); @@ -810,13 +833,24 @@ class _InAppWebViewState extends State { @override void dispose() { dynamic viewId = _controller?.getViewId(); + debugLog( + className: "InAppWebView", + name: "WebView", + id: viewId?.toString(), + debugLoggingSettings: WebView.debugLoggingSettings, + method: "dispose", + args: []); if (viewId != null && kIsWeb && WebPlatformManager.webViews.containsKey(viewId)) { WebPlatformManager.webViews.remove(viewId); } - super.dispose(); + final isKeepAlive = widget.keepAlive != null; + _controller?.dispose(isKeepAlive: isKeepAlive); _controller = null; + widget.pullToRefreshController?.dispose(isKeepAlive: isKeepAlive); + widget.findInteractionController?.dispose(isKeepAlive: isKeepAlive); + super.dispose(); } AndroidViewController _createAndroidViewController({ @@ -845,21 +879,25 @@ class _InAppWebViewState extends State { } void _onPlatformViewCreated(int id) { - final viewId = (!kIsWeb && (widget.headlessWebView?.isRunning() ?? false)) - ? widget.headlessWebView?.id - : id; + dynamic viewId = id; + if (!kIsWeb) { + if (widget.headlessWebView?.isRunning() ?? false) { + viewId = widget.headlessWebView?.id; + } + viewId = widget.keepAlive?.id ?? viewId ?? id; + } widget.headlessWebView?.internalDispose(); _controller = InAppWebViewController(viewId, widget); - widget.pullToRefreshController?.initMethodChannel(viewId); - widget.findInteractionController?.initMethodChannel(viewId); + widget.pullToRefreshController?.init(viewId); + widget.findInteractionController?.init(viewId); + debugLog( + className: "InAppWebView", + name: "WebView", + id: viewId?.toString(), + debugLoggingSettings: WebView.debugLoggingSettings, + method: "onWebViewCreated", + args: []); if (widget.onWebViewCreated != null) { - debugLog( - className: "InAppWebView", - name: "WebView", - id: viewId?.toString(), - debugLoggingSettings: WebView.debugLoggingSettings, - method: "onWebViewCreated", - args: []); widget.onWebViewCreated!(_controller!); } } diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index 12ff13a7..95396694 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -27,6 +27,7 @@ import 'in_app_webview.dart'; import 'in_app_webview_settings.dart'; import 'webview.dart'; import '_static_channel.dart'; +import 'in_app_webview_keep_alive.dart'; import '../print_job/main.dart'; import '../find_interaction/main.dart'; @@ -52,17 +53,23 @@ final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ ///callback. Instead, if you are using an [InAppBrowser] instance, you can get it through the [InAppBrowser.webViewController] attribute. class InAppWebViewController { WebView? _webview; - late MethodChannel _channel; - static MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; - Map javaScriptHandlersMap = + MethodChannel? _channel; + static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; + + // properties to be saved and restored for keep alive feature + Map _javaScriptHandlersMap = HashMap(); - final Map> _userScripts = { + Map> _userScripts = { UserScriptInjectionTime.AT_DOCUMENT_START: [], UserScriptInjectionTime.AT_DOCUMENT_END: [] }; Set _webMessageListenerObjNames = Set(); Map _injectedScriptsFromURL = {}; + // static map that contains the properties to be saved and restored for keep alive feature + static final Map + _keepAliveMap = {}; + dynamic _id; InAppBrowser? _inAppBrowser; @@ -82,7 +89,8 @@ class InAppWebViewController { this._id = id; this._channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); - this._channel.setMethodCallHandler((call) async { + this._channel?.setMethodCallHandler((call) async { + if (_channel == null) return null; try { return await handleMethod(call); } on Error catch (e) { @@ -136,12 +144,31 @@ class InAppWebViewController { } void _init() { - // ignore: deprecated_member_use_from_same_package - this.android = AndroidInAppWebViewController(channel: _channel); - // ignore: deprecated_member_use_from_same_package - this.ios = IOSInAppWebViewController(channel: _channel); - this.webStorage = WebStorage( + android = AndroidInAppWebViewController(channel: _channel!); + ios = IOSInAppWebViewController(channel: _channel!); + webStorage = WebStorage( localStorage: LocalStorage(this), sessionStorage: SessionStorage(this)); + + if (_webview is InAppWebView) { + final keepAlive = (_webview as InAppWebView).keepAlive; + if (keepAlive != null) { + InAppWebViewControllerKeepAliveProps? props = _keepAliveMap[keepAlive]; + if (props == null) { + // save controller properties to restore it later + _keepAliveMap[keepAlive] = InAppWebViewControllerKeepAliveProps( + injectedScriptsFromURL: _injectedScriptsFromURL, + javaScriptHandlersMap: _javaScriptHandlersMap, + userScripts: _userScripts, + webMessageListenerObjNames: _webMessageListenerObjNames); + } else { + // restore controller properties + _injectedScriptsFromURL = props.injectedScriptsFromURL; + _javaScriptHandlersMap = props.javaScriptHandlersMap; + _userScripts = props.userScripts; + _webMessageListenerObjNames = props.webMessageListenerObjNames; + } + } + } } _debugLog(String method, dynamic args) { @@ -1374,10 +1401,10 @@ class InAppWebViewController { return null; } - if (javaScriptHandlersMap.containsKey(handlerName)) { + if (_javaScriptHandlersMap.containsKey(handlerName)) { // convert result to json try { - return jsonEncode(await javaScriptHandlersMap[handlerName]!(args)); + return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args)); } catch (error, stacktrace) { developer.log(error.toString() + '\n' + stacktrace.toString(), name: 'JavaScript Handler "$handlerName"'); @@ -1404,7 +1431,7 @@ class InAppWebViewController { ///- Web Future getUrl() async { Map args = {}; - String? url = await _channel.invokeMethod('getUrl', args); + String? url = await _channel?.invokeMethod('getUrl', args); return url != null ? WebUri(url) : null; } @@ -1419,7 +1446,7 @@ class InAppWebViewController { ///- Web Future getTitle() async { Map args = {}; - return await _channel.invokeMethod('getTitle', args); + return await _channel?.invokeMethod('getTitle', args); } ///Gets the progress for the current page. The progress value is between 0 and 100. @@ -1430,7 +1457,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.estimatedProgress](https://developer.apple.com/documentation/webkit/wkwebview/1415007-estimatedprogress)) Future getProgress() async { Map args = {}; - return await _channel.invokeMethod('getProgress', args); + return await _channel?.invokeMethod('getProgress', args); } ///Gets the content html of the page. It first tries to get the content through javascript. @@ -1693,7 +1720,7 @@ class InAppWebViewController { () => allowingReadAccessTo?.toString() ?? iosAllowingReadAccessTo?.toString()); - await _channel.invokeMethod('loadUrl', args); + await _channel?.invokeMethod('loadUrl', args); } ///Loads the given [url] with [postData] (x-www-form-urlencoded) using `POST` method into this WebView. @@ -1717,7 +1744,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('url', () => url.toString()); args.putIfAbsent('postData', () => postData); - await _channel.invokeMethod('postUrl', args); + await _channel?.invokeMethod('postUrl', args); } ///Loads the given [data] into this WebView, using [baseUrl] as the base URL for the content. @@ -1771,7 +1798,7 @@ class InAppWebViewController { () => allowingReadAccessTo?.toString() ?? iosAllowingReadAccessTo?.toString()); - await _channel.invokeMethod('loadData', args); + await _channel?.invokeMethod('loadData', args); } ///Loads the given [assetFilePath]. @@ -1813,7 +1840,7 @@ class InAppWebViewController { assert(assetFilePath.isNotEmpty); Map args = {}; args.putIfAbsent('assetFilePath', () => assetFilePath); - await _channel.invokeMethod('loadFile', args); + await _channel?.invokeMethod('loadFile', args); } ///Reloads the WebView. @@ -1827,7 +1854,7 @@ class InAppWebViewController { ///- Web ([Official API - Location.reload](https://developer.mozilla.org/en-US/docs/Web/API/Location/reload)) Future reload() async { Map args = {}; - await _channel.invokeMethod('reload', args); + await _channel?.invokeMethod('reload', args); } ///Goes back in the history of the WebView. @@ -1841,7 +1868,7 @@ class InAppWebViewController { ///- Web ([Official API - History.back](https://developer.mozilla.org/en-US/docs/Web/API/History/back)) Future goBack() async { Map args = {}; - await _channel.invokeMethod('goBack', args); + await _channel?.invokeMethod('goBack', args); } ///Returns a boolean value indicating whether the WebView can move backward. @@ -1852,7 +1879,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.canGoBack](https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback)) Future canGoBack() async { Map args = {}; - return await _channel.invokeMethod('canGoBack', args); + return await _channel?.invokeMethod('canGoBack', args); } ///Goes forward in the history of the WebView. @@ -1866,7 +1893,7 @@ class InAppWebViewController { ///- Web ([Official API - History.forward](https://developer.mozilla.org/en-US/docs/Web/API/History/forward)) Future goForward() async { Map args = {}; - await _channel.invokeMethod('goForward', args); + await _channel?.invokeMethod('goForward', args); } ///Returns a boolean value indicating whether the WebView can move forward. @@ -1877,7 +1904,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.canGoForward](https://developer.apple.com/documentation/webkit/wkwebview/1414962-cangoforward)) Future canGoForward() async { Map args = {}; - return await _channel.invokeMethod('canGoForward', args); + return await _channel?.invokeMethod('canGoForward', args); } ///Goes to the history item that is the number of steps away from the current item. Steps is negative if backward and positive if forward. @@ -1892,7 +1919,7 @@ class InAppWebViewController { Future goBackOrForward({required int steps}) async { Map args = {}; args.putIfAbsent('steps', () => steps); - await _channel.invokeMethod('goBackOrForward', args); + await _channel?.invokeMethod('goBackOrForward', args); } ///Returns a boolean value indicating whether the WebView can go back or forward the given number of steps. Steps is negative if backward and positive if forward. @@ -1904,7 +1931,7 @@ class InAppWebViewController { Future canGoBackOrForward({required int steps}) async { Map args = {}; args.putIfAbsent('steps', () => steps); - return await _channel.invokeMethod('canGoBackOrForward', args); + return await _channel?.invokeMethod('canGoBackOrForward', args); } ///Navigates to a [WebHistoryItem] from the back-forward [WebHistory.list] and sets it as the current item. @@ -1932,7 +1959,7 @@ class InAppWebViewController { ///- Web Future isLoading() async { Map args = {}; - return await _channel.invokeMethod('isLoading', args); + return await _channel?.invokeMethod('isLoading', args); } ///Stops the WebView from loading. @@ -1946,7 +1973,7 @@ class InAppWebViewController { ///- Web ([Official API - Window.stop](https://developer.mozilla.org/en-US/docs/Web/API/Window/stop)) Future stopLoading() async { Map args = {}; - await _channel.invokeMethod('stopLoading', args); + await _channel?.invokeMethod('stopLoading', args); } ///Evaluates JavaScript [source] code into the WebView and returns the result of the evaluation. @@ -1976,7 +2003,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('source', () => source); args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); - var data = await _channel.invokeMethod('evaluateJavascript', args); + var data = await _channel?.invokeMethod('evaluateJavascript', args); if (data != null && (Util.isAndroid || Util.isWeb)) { try { // try to json decode the data coming from JavaScript @@ -2015,7 +2042,7 @@ class InAppWebViewController { args.putIfAbsent('urlFile', () => urlFile.toString()); args.putIfAbsent( 'scriptHtmlTagAttributes', () => scriptHtmlTagAttributes?.toMap()); - await _channel.invokeMethod('injectJavascriptFileFromUrl', args); + await _channel?.invokeMethod('injectJavascriptFileFromUrl', args); } ///Evaluates the content of a JavaScript file into the WebView from the flutter assets directory. @@ -2055,7 +2082,7 @@ class InAppWebViewController { Future injectCSSCode({required String source}) async { Map args = {}; args.putIfAbsent('source', () => source); - await _channel.invokeMethod('injectCSSCode', args); + await _channel?.invokeMethod('injectCSSCode', args); } ///Injects an external CSS file into the WebView from a defined url. @@ -2082,7 +2109,7 @@ class InAppWebViewController { args.putIfAbsent('urlFile', () => urlFile.toString()); args.putIfAbsent( 'cssLinkHtmlTagAttributes', () => cssLinkHtmlTagAttributes?.toMap()); - await _channel.invokeMethod('injectCSSFileFromUrl', args); + await _channel?.invokeMethod('injectCSSFileFromUrl', args); } ///Injects a CSS file into the WebView from the flutter assets directory. @@ -2164,7 +2191,7 @@ class InAppWebViewController { required JavaScriptHandlerCallback callback}) { assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), '"$handlerName" is a forbidden name!'); - this.javaScriptHandlersMap[handlerName] = (callback); + this._javaScriptHandlersMap[handlerName] = (callback); } ///Removes a JavaScript message handler previously added with the [addJavaScriptHandler] associated to [handlerName] key. @@ -2177,7 +2204,17 @@ class InAppWebViewController { ///- MacOS JavaScriptHandlerCallback? removeJavaScriptHandler( {required String handlerName}) { - return this.javaScriptHandlersMap.remove(handlerName); + return this._javaScriptHandlersMap.remove(handlerName); + } + + ///Returns `true` if a JavaScript handler with [handlerName] already exists, otherwise `false`. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + bool hasJavaScriptHandler({required String handlerName}) { + return this._javaScriptHandlersMap.containsKey(handlerName); } ///Takes a screenshot of the WebView's visible viewport and returns a [Uint8List]. Returns `null` if it wasn't be able to take it. @@ -2197,7 +2234,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent( 'screenshotConfiguration', () => screenshotConfiguration?.toMap()); - return await _channel.invokeMethod('takeScreenshot', args); + return await _channel?.invokeMethod('takeScreenshot', args); } ///Use [setSettings] instead. @@ -2233,7 +2270,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('settings', () => settings.toMap()); - await _channel.invokeMethod('setSettings', args); + await _channel?.invokeMethod('setSettings', args); } ///Gets the current WebView settings. Returns `null` if it wasn't able to get them. @@ -2247,7 +2284,7 @@ class InAppWebViewController { Map args = {}; Map? settings = - await _channel.invokeMethod('getSettings', args); + await _channel?.invokeMethod('getSettings', args); if (settings != null) { settings = settings.cast(); return InAppWebViewSettings.fromMap(settings as Map); @@ -2268,7 +2305,7 @@ class InAppWebViewController { Future getCopyBackForwardList() async { Map args = {}; Map? result = - (await _channel.invokeMethod('getCopyBackForwardList', args)) + (await _channel?.invokeMethod('getCopyBackForwardList', args)) ?.cast(); return WebHistory.fromMap(result); } @@ -2281,7 +2318,7 @@ class InAppWebViewController { ///- MacOS Future clearCache() async { Map args = {}; - await _channel.invokeMethod('clearCache', args); + await _channel?.invokeMethod('clearCache', args); } ///Use [FindInteractionController.findAll] instead. @@ -2289,7 +2326,7 @@ class InAppWebViewController { Future findAllAsync({required String find}) async { Map args = {}; args.putIfAbsent('find', () => find); - await _channel.invokeMethod('findAll', args); + await _channel?.invokeMethod('findAll', args); } ///Use [FindInteractionController.findNext] instead. @@ -2297,14 +2334,14 @@ class InAppWebViewController { Future findNext({required bool forward}) async { Map args = {}; args.putIfAbsent('forward', () => forward); - await _channel.invokeMethod('findNext', args); + await _channel?.invokeMethod('findNext', args); } ///Use [FindInteractionController.clearMatches] instead. @Deprecated("Use FindInteractionController.clearMatches instead") Future clearMatches() async { Map args = {}; - await _channel.invokeMethod('clearMatches', args); + await _channel?.invokeMethod('clearMatches', args); } ///Use [tRexRunnerHtml] instead. @@ -2342,7 +2379,7 @@ class InAppWebViewController { args.putIfAbsent('x', () => x); args.putIfAbsent('y', () => y); args.putIfAbsent('animated', () => animated); - await _channel.invokeMethod('scrollTo', args); + await _channel?.invokeMethod('scrollTo', args); } ///Moves the scrolled position of the WebView. @@ -2368,7 +2405,7 @@ class InAppWebViewController { args.putIfAbsent('x', () => x); args.putIfAbsent('y', () => y); args.putIfAbsent('animated', () => animated); - await _channel.invokeMethod('scrollBy', args); + await _channel?.invokeMethod('scrollBy', args); } ///On Android native WebView, it pauses all layout, parsing, and JavaScript timers for all WebViews. @@ -2384,7 +2421,7 @@ class InAppWebViewController { ///- MacOS Future pauseTimers() async { Map args = {}; - await _channel.invokeMethod('pauseTimers', args); + await _channel?.invokeMethod('pauseTimers', args); } ///On Android, it resumes all layout, parsing, and JavaScript timers for all WebViews. This will resume dispatching all timers. @@ -2399,7 +2436,7 @@ class InAppWebViewController { ///- MacOS Future resumeTimers() async { Map args = {}; - await _channel.invokeMethod('resumeTimers', args); + await _channel?.invokeMethod('resumeTimers', args); } ///Prints the current page. @@ -2422,7 +2459,7 @@ class InAppWebViewController { {PrintJobSettings? settings}) async { Map args = {}; args.putIfAbsent("settings", () => settings?.toMap()); - String? jobId = await _channel.invokeMethod('printCurrentPage', args); + String? jobId = await _channel?.invokeMethod('printCurrentPage', args); if (jobId != null) { return PrintJobController(id: jobId); } @@ -2442,7 +2479,7 @@ class InAppWebViewController { ///- Web ([Official API - Document.documentElement.scrollHeight](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight)) Future getContentHeight() async { Map args = {}; - var height = await _channel.invokeMethod('getContentHeight', args); + var height = await _channel?.invokeMethod('getContentHeight', args); if (height == null || height == 0) { // try to use javascript var scrollHeight = await evaluateJavascript( @@ -2469,7 +2506,7 @@ class InAppWebViewController { ///- Web ([Official API - Document.documentElement.scrollWidth](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth)) Future getContentWidth() async { Map args = {}; - var height = await _channel.invokeMethod('getContentWidth', args); + var height = await _channel?.invokeMethod('getContentWidth', args); if (height == null || height == 0) { // try to use javascript var scrollHeight = await evaluateJavascript( @@ -2503,7 +2540,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('zoomFactor', () => zoomFactor); args.putIfAbsent('animated', () => iosAnimated ?? animated); - return await _channel.invokeMethod('zoomBy', args); + return await _channel?.invokeMethod('zoomBy', args); } ///Gets the URL that was originally requested for the current page. @@ -2519,7 +2556,7 @@ class InAppWebViewController { ///- Web Future getOriginalUrl() async { Map args = {}; - String? url = await _channel.invokeMethod('getOriginalUrl', args); + String? url = await _channel?.invokeMethod('getOriginalUrl', args); return url != null ? WebUri(url) : null; } @@ -2530,7 +2567,7 @@ class InAppWebViewController { ///- iOS ([Official API - UIScrollView.zoomScale](https://developer.apple.com/documentation/uikit/uiscrollview/1619419-zoomscale)) Future getZoomScale() async { Map args = {}; - return await _channel.invokeMethod('getZoomScale', args); + return await _channel?.invokeMethod('getZoomScale', args); } ///Use [getZoomScale] instead. @@ -2554,7 +2591,7 @@ class InAppWebViewController { ///- Web Future getSelectedText() async { Map args = {}; - return await _channel.invokeMethod('getSelectedText', args); + return await _channel?.invokeMethod('getSelectedText', args); } ///Gets the hit result for hitting an HTML elements. @@ -2567,7 +2604,7 @@ class InAppWebViewController { Future getHitTestResult() async { Map args = {}; Map? hitTestResultMap = - await _channel.invokeMethod('getHitTestResult', args); + await _channel?.invokeMethod('getHitTestResult', args); if (hitTestResultMap == null) { return null; @@ -2589,7 +2626,7 @@ class InAppWebViewController { ///- iOS ([Official API - UIResponder.resignFirstResponder](https://developer.apple.com/documentation/uikit/uiresponder/1621097-resignfirstresponder)) Future clearFocus() async { Map args = {}; - return await _channel.invokeMethod('clearFocus', args); + return await _channel?.invokeMethod('clearFocus', args); } ///Sets or updates the WebView context menu to be used next time it will appear. @@ -2600,7 +2637,7 @@ class InAppWebViewController { Future setContextMenu(ContextMenu? contextMenu) async { Map args = {}; args.putIfAbsent("contextMenu", () => contextMenu?.toMap()); - await _channel.invokeMethod('setContextMenu', args); + await _channel?.invokeMethod('setContextMenu', args); _inAppBrowser?.contextMenu = contextMenu; } @@ -2614,7 +2651,7 @@ class InAppWebViewController { Future requestFocusNodeHref() async { Map args = {}; Map? result = - await _channel.invokeMethod('requestFocusNodeHref', args); + await _channel?.invokeMethod('requestFocusNodeHref', args); return result != null ? RequestFocusNodeHrefResult( url: result['url'] != null ? WebUri(result['url']) : null, @@ -2634,7 +2671,7 @@ class InAppWebViewController { Future requestImageRef() async { Map args = {}; Map? result = - await _channel.invokeMethod('requestImageRef', args); + await _channel?.invokeMethod('requestImageRef', args); return result != null ? RequestImageRefResult( url: result['url'] != null ? WebUri(result['url']) : null, @@ -2730,7 +2767,7 @@ class InAppWebViewController { try { Map args = {}; themeColor = UtilColor.fromStringRepresentation( - await _channel.invokeMethod('getMetaThemeColor', args)); + await _channel?.invokeMethod('getMetaThemeColor', args)); return themeColor; } catch (e) { // not implemented @@ -2773,7 +2810,7 @@ class InAppWebViewController { ///- Web ([Official API - Window.scrollX](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX)) Future getScrollX() async { Map args = {}; - return await _channel.invokeMethod('getScrollX', args); + return await _channel?.invokeMethod('getScrollX', args); } ///Returns the scrolled top position of the current WebView. @@ -2789,7 +2826,7 @@ class InAppWebViewController { ///- Web ([Official API - Window.scrollY](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY)) Future getScrollY() async { Map args = {}; - return await _channel.invokeMethod('getScrollY', args); + return await _channel?.invokeMethod('getScrollY', args); } ///Gets the SSL certificate for the main top-level page or null if there is no certificate (the site is not secure). @@ -2801,7 +2838,7 @@ class InAppWebViewController { Future getCertificate() async { Map args = {}; Map? sslCertificateMap = - (await _channel.invokeMethod('getCertificate', args)) + (await _channel?.invokeMethod('getCertificate', args)) ?.cast(); return SslCertificate.fromMap(sslCertificateMap); } @@ -2824,7 +2861,7 @@ class InAppWebViewController { if (!(_userScripts[userScript.injectionTime]?.contains(userScript) ?? false)) { _userScripts[userScript.injectionTime]?.add(userScript); - await _channel.invokeMethod('addUserScript', args); + await _channel?.invokeMethod('addUserScript', args); } } @@ -2870,7 +2907,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('userScript', () => userScript.toMap()); args.putIfAbsent('index', () => index); - await _channel.invokeMethod('removeUserScript', args); + await _channel?.invokeMethod('removeUserScript', args); return true; } @@ -2907,7 +2944,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('groupName', () => groupName); - await _channel.invokeMethod('removeUserScriptsByGroupName', args); + await _channel?.invokeMethod('removeUserScriptsByGroupName', args); } ///Removes the [userScripts] from the webpage’s content. @@ -2947,7 +2984,18 @@ class InAppWebViewController { _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear(); Map args = {}; - await _channel.invokeMethod('removeAllUserScripts', args); + await _channel?.invokeMethod('removeAllUserScripts', args); + } + + ///Returns `true` if the [userScript] has been already added, otherwise `false`. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + bool hasUserScript({required UserScript userScript}) { + return _userScripts[userScript.injectionTime]?.contains(userScript) ?? + false; } ///Executes the specified string as an asynchronous JavaScript function. @@ -2991,7 +3039,7 @@ class InAppWebViewController { args.putIfAbsent('functionBody', () => functionBody); args.putIfAbsent('arguments', () => arguments); args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); - var data = await _channel.invokeMethod('callAsyncJavaScript', args); + var data = await _channel?.invokeMethod('callAsyncJavaScript', args); if (data == null) { return null; } @@ -3034,7 +3082,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent("filePath", () => filePath); args.putIfAbsent("autoname", () => autoname); - return await _channel.invokeMethod('saveWebArchive', args); + return await _channel?.invokeMethod('saveWebArchive', args); } ///Indicates whether the webpage context is capable of using features that require [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). @@ -3051,7 +3099,7 @@ class InAppWebViewController { ///- Web ([Official API - Window.isSecureContext](https://developer.mozilla.org/en-US/docs/Web/API/Window/isSecureContext)) Future isSecureContext() async { Map args = {}; - return await _channel.invokeMethod('isSecureContext', args); + return await _channel?.invokeMethod('isSecureContext', args); } ///Creates a message channel to communicate with JavaScript and returns the message channel with ports that represent the endpoints of this message channel. @@ -3074,7 +3122,7 @@ class InAppWebViewController { Future createWebMessageChannel() async { Map args = {}; Map? result = - (await _channel.invokeMethod('createWebMessageChannel', args)) + (await _channel?.invokeMethod('createWebMessageChannel', args)) ?.cast(); return WebMessageChannel.fromMap(result); } @@ -3102,7 +3150,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('message', () => message.toMap()); args.putIfAbsent('targetOrigin', () => targetOrigin.toString()); - await _channel.invokeMethod('postWebMessage', args); + await _channel?.invokeMethod('postWebMessage', args); } ///Adds a [WebMessageListener] to the WebView and injects a JavaScript object into each frame that the [WebMessageListener] will listen on. @@ -3278,7 +3326,18 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('webMessageListener', () => webMessageListener.toMap()); - await _channel.invokeMethod('addWebMessageListener', args); + await _channel?.invokeMethod('addWebMessageListener', args); + } + + ///Returns `true` if the [webMessageListener] has been already added, otherwise `false`. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + ///- MacOS + bool hasWebMessageListener(WebMessageListener webMessageListener) { + return _webMessageListenerObjNames + .contains(webMessageListener.jsObjectName); } ///Returns `true` if the webpage can scroll vertically, otherwise `false`. @@ -3294,7 +3353,7 @@ class InAppWebViewController { ///- Web Future canScrollVertically() async { Map args = {}; - return await _channel.invokeMethod('canScrollVertically', args); + return await _channel?.invokeMethod('canScrollVertically', args); } ///Returns `true` if the webpage can scroll horizontally, otherwise `false`. @@ -3310,7 +3369,7 @@ class InAppWebViewController { ///- Web Future canScrollHorizontally() async { Map args = {}; - return await _channel.invokeMethod('canScrollHorizontally', args); + return await _channel?.invokeMethod('canScrollHorizontally', args); } ///Starts Safe Browsing initialization. @@ -3327,7 +3386,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.startSafeBrowsing](https://developer.android.com/reference/android/webkit/WebView#startSafeBrowsing(android.content.Context,%20android.webkit.ValueCallback%3Cjava.lang.Boolean%3E))) Future startSafeBrowsing() async { Map args = {}; - return await _channel.invokeMethod('startSafeBrowsing', args); + return await _channel?.invokeMethod('startSafeBrowsing', args); } ///Clears the SSL preferences table stored in response to proceeding with SSL certificate errors. @@ -3336,7 +3395,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.clearSslPreferences](https://developer.android.com/reference/android/webkit/WebView#clearSslPreferences())) Future clearSslPreferences() async { Map args = {}; - await _channel.invokeMethod('clearSslPreferences', args); + await _channel?.invokeMethod('clearSslPreferences', args); } ///Does a best-effort attempt to pause any processing that can be paused safely, such as animations and geolocation. Note that this call does not pause JavaScript. @@ -3346,7 +3405,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.onPause](https://developer.android.com/reference/android/webkit/WebView#onPause())) Future pause() async { Map args = {}; - await _channel.invokeMethod('pause', args); + await _channel?.invokeMethod('pause', args); } ///Resumes a WebView after a previous call to [pause]. @@ -3355,7 +3414,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.onResume](https://developer.android.com/reference/android/webkit/WebView#onResume())) Future resume() async { Map args = {}; - await _channel.invokeMethod('resume', args); + await _channel?.invokeMethod('resume', args); } ///Scrolls the contents of this WebView down by half the page size. @@ -3368,7 +3427,7 @@ class InAppWebViewController { Future pageDown({required bool bottom}) async { Map args = {}; args.putIfAbsent("bottom", () => bottom); - return await _channel.invokeMethod('pageDown', args); + return await _channel?.invokeMethod('pageDown', args); } ///Scrolls the contents of this WebView up by half the view size. @@ -3381,7 +3440,7 @@ class InAppWebViewController { Future pageUp({required bool top}) async { Map args = {}; args.putIfAbsent("top", () => top); - return await _channel.invokeMethod('pageUp', args); + return await _channel?.invokeMethod('pageUp', args); } ///Performs zoom in in this WebView. @@ -3391,7 +3450,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.zoomIn](https://developer.android.com/reference/android/webkit/WebView#zoomIn())) Future zoomIn() async { Map args = {}; - return await _channel.invokeMethod('zoomIn', args); + return await _channel?.invokeMethod('zoomIn', args); } ///Performs zoom out in this WebView. @@ -3401,7 +3460,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.zoomOut](https://developer.android.com/reference/android/webkit/WebView#zoomOut())) Future zoomOut() async { Map args = {}; - return await _channel.invokeMethod('zoomOut', args); + return await _channel?.invokeMethod('zoomOut', args); } ///Clears the internal back/forward list. @@ -3410,7 +3469,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.clearHistory](https://developer.android.com/reference/android/webkit/WebView#clearHistory())) Future clearHistory() async { Map args = {}; - return await _channel.invokeMethod('clearHistory', args); + return await _channel?.invokeMethod('clearHistory', args); } ///Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible. @@ -3420,7 +3479,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.reloadFromOrigin](https://developer.apple.com/documentation/webkit/wkwebview/1414956-reloadfromorigin)) Future reloadFromOrigin() async { Map args = {}; - await _channel.invokeMethod('reloadFromOrigin', args); + await _channel?.invokeMethod('reloadFromOrigin', args); } ///Generates PDF data from the web view’s contents asynchronously. @@ -3443,7 +3502,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('pdfConfiguration', () => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap()); - return await _channel.invokeMethod('createPdf', args); + return await _channel?.invokeMethod('createPdf', args); } ///Creates a web archive of the web view’s current contents asynchronously. @@ -3458,7 +3517,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.createWebArchiveData](https://developer.apple.com/documentation/webkit/wkwebview/3650491-createwebarchivedata)) Future createWebArchiveData() async { Map args = {}; - return await _channel.invokeMethod('createWebArchiveData', args); + return await _channel?.invokeMethod('createWebArchiveData', args); } ///A Boolean value indicating whether all resources on the page have been loaded over securely encrypted connections. @@ -3468,7 +3527,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.hasOnlySecureContent](https://developer.apple.com/documentation/webkit/wkwebview/1415002-hasonlysecurecontent)) Future hasOnlySecureContent() async { Map args = {}; - return await _channel.invokeMethod('hasOnlySecureContent', args); + return await _channel?.invokeMethod('hasOnlySecureContent', args); } ///Pauses playback of all media in the web view. @@ -3482,7 +3541,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.pauseAllMediaPlayback](https://developer.apple.com/documentation/webkit/wkwebview/3752240-pauseallmediaplayback)). Future pauseAllMediaPlayback() async { Map args = {}; - return await _channel.invokeMethod('pauseAllMediaPlayback', args); + return await _channel?.invokeMethod('pauseAllMediaPlayback', args); } ///Changes whether the webpage is suspending playback of all media in the page. @@ -3500,7 +3559,7 @@ class InAppWebViewController { Future setAllMediaPlaybackSuspended({required bool suspended}) async { Map args = {}; args.putIfAbsent("suspended", () => suspended); - return await _channel.invokeMethod('setAllMediaPlaybackSuspended', args); + return await _channel?.invokeMethod('setAllMediaPlaybackSuspended', args); } ///Closes all media the web view is presenting, including picture-in-picture video and fullscreen video. @@ -3514,7 +3573,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.closeAllMediaPresentations](https://developer.apple.com/documentation/webkit/wkwebview/3752235-closeallmediapresentations)). Future closeAllMediaPresentations() async { Map args = {}; - return await _channel.invokeMethod('closeAllMediaPresentations', args); + return await _channel?.invokeMethod('closeAllMediaPresentations', args); } ///Requests the playback status of media in the web view. @@ -3531,7 +3590,7 @@ class InAppWebViewController { Future requestMediaPlaybackState() async { Map args = {}; return MediaPlaybackState.fromNativeValue( - await _channel.invokeMethod('requestMediaPlaybackState', args)); + await _channel?.invokeMethod('requestMediaPlaybackState', args)); } ///Returns `true` if the [WebView] is in fullscreen mode, otherwise `false`. @@ -3542,7 +3601,7 @@ class InAppWebViewController { ///- MacOS Future isInFullscreen() async { Map args = {}; - return await _channel.invokeMethod('isInFullscreen', args); + return await _channel?.invokeMethod('isInFullscreen', args); } ///Returns a [MediaCaptureState] that indicates whether the webpage is using the camera to capture images or video. @@ -3557,7 +3616,7 @@ class InAppWebViewController { Future getCameraCaptureState() async { Map args = {}; return MediaCaptureState.fromNativeValue( - await _channel.invokeMethod('getCameraCaptureState', args)); + await _channel?.invokeMethod('getCameraCaptureState', args)); } ///Changes whether the webpage is using the camera to capture images or video. @@ -3572,7 +3631,7 @@ class InAppWebViewController { Future setCameraCaptureState({required MediaCaptureState state}) async { Map args = {}; args.putIfAbsent('state', () => state.toNativeValue()); - await _channel.invokeMethod('setCameraCaptureState', args); + await _channel?.invokeMethod('setCameraCaptureState', args); } ///Returns a [MediaCaptureState] that indicates whether the webpage is using the microphone to capture audio. @@ -3587,7 +3646,7 @@ class InAppWebViewController { Future getMicrophoneCaptureState() async { Map args = {}; return MediaCaptureState.fromNativeValue( - await _channel.invokeMethod('getMicrophoneCaptureState', args)); + await _channel?.invokeMethod('getMicrophoneCaptureState', args)); } ///Changes whether the webpage is using the microphone to capture audio. @@ -3603,7 +3662,7 @@ class InAppWebViewController { {required MediaCaptureState state}) async { Map args = {}; args.putIfAbsent('state', () => state.toNativeValue()); - await _channel.invokeMethod('setMicrophoneCaptureState', args); + await _channel?.invokeMethod('setMicrophoneCaptureState', args); } ///Loads the web content from the data you provide as if the data were the response to the request. @@ -3639,7 +3698,7 @@ class InAppWebViewController { args.putIfAbsent('urlRequest', () => urlRequest.toMap()); args.putIfAbsent('data', () => data); args.putIfAbsent('urlResponse', () => urlResponse?.toMap()); - await _channel.invokeMethod('loadSimulatedRequest', args); + await _channel?.invokeMethod('loadSimulatedRequest', args); } ///Returns the iframe `id` attribute used on the Web platform. @@ -3648,7 +3707,7 @@ class InAppWebViewController { ///- Web Future getIFrameId() async { Map args = {}; - return await _channel.invokeMethod('getIFrameId', args); + return await _channel?.invokeMethod('getIFrameId', args); } ///Gets the default user agent. @@ -3812,6 +3871,19 @@ class InAppWebViewController { return await _staticChannel.invokeMethod('handlesURLScheme', args); } + ///Disposes the WebView that is using the [keepAlive] instance + ///for the keep alive feature. + /// + ///**Supported Platforms/Implementations**: + ///- Android native WebView + ///- iOS + static Future disposeKeepAlive(InAppWebViewKeepAlive keepAlive) async { + Map args = {}; + args.putIfAbsent('keepAliveId', () => keepAlive.id); + await _staticChannel.invokeMethod('disposeKeepAlive', args); + _keepAliveMap[keepAlive] = null; + } + ///Gets the html (with javascript) of the Chromium's t-rex runner game. Used in combination with [tRexRunnerCss]. /// ///**Supported Platforms/Implementations**: @@ -3831,7 +3903,7 @@ class InAppWebViewController { 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css'); ///Used internally. - MethodChannel getChannel() { + MethodChannel? getChannel() { return _channel; } @@ -3839,4 +3911,23 @@ class InAppWebViewController { dynamic getViewId() { return _id; } + + ///Disposes the controller. + void dispose({bool isKeepAlive = false}) { + if (!isKeepAlive) { + _channel?.setMethodCallHandler(null); + } + android.dispose(); + ios.dispose(); + _channel = null; + _webview = null; + _inAppBrowser = null; + webStorage.dispose(); + if (!isKeepAlive) { + _javaScriptHandlersMap.clear(); + _userScripts.clear(); + _webMessageListenerObjNames.clear(); + _injectedScriptsFromURL.clear(); + } + } } diff --git a/lib/src/in_app_webview/in_app_webview_keep_alive.dart b/lib/src/in_app_webview/in_app_webview_keep_alive.dart new file mode 100644 index 00000000..b5405c84 --- /dev/null +++ b/lib/src/in_app_webview/in_app_webview_keep_alive.dart @@ -0,0 +1,33 @@ +import '../types/main.dart'; +import '../util.dart'; +import 'in_app_webview.dart'; +import 'in_app_webview_controller.dart'; + +///Class used to keep alive a [InAppWebView]. +class InAppWebViewKeepAlive { + String _id = IdGenerator.generate(); +} + +///Used internally +extension InternalInAppWebViewKeepAlive on InAppWebViewKeepAlive { + set id(String id) { + _id = id; + } + + String get id => _id; +} + +///Used internally to save and restore [InAppWebViewController] properties +///for the keep alive feature. +class InAppWebViewControllerKeepAliveProps { + Map javaScriptHandlersMap; + Map> userScripts; + Set webMessageListenerObjNames; + Map injectedScriptsFromURL; + + InAppWebViewControllerKeepAliveProps( + {required this.javaScriptHandlersMap, + required this.userScripts, + required this.webMessageListenerObjNames, + required this.injectedScriptsFromURL}); +} \ No newline at end of file diff --git a/lib/src/in_app_webview/main.dart b/lib/src/in_app_webview/main.dart index 37afa89f..f202e1f3 100644 --- a/lib/src/in_app_webview/main.dart +++ b/lib/src/in_app_webview/main.dart @@ -11,3 +11,4 @@ export 'headless_in_app_webview.dart' hide InternalHeadlessInAppWebView; export 'android/main.dart'; export 'apple/main.dart'; export '../find_interaction/find_interaction_controller.dart'; +export 'in_app_webview_keep_alive.dart' show InAppWebViewKeepAlive; diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index 40c404fa..0fa1ca48 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -1191,12 +1191,6 @@ abstract class WebView { ///{@endtemplate} final FindInteractionController? findInteractionController; - ///{@template flutter_inappwebview.WebView.implementation} - ///Represents the WebView native implementation to be used. - ///The default value is [WebViewImplementation.NATIVE]. - ///{@endtemplate} - final WebViewImplementation implementation; - ///{@macro flutter_inappwebview.WebView} WebView( {this.windowId, @@ -1314,6 +1308,5 @@ abstract class WebView { this.contextMenu, this.initialUserScripts, this.pullToRefreshController, - this.findInteractionController, - this.implementation = WebViewImplementation.NATIVE}); + this.findInteractionController}); } diff --git a/lib/src/print_job/print_job_controller.dart b/lib/src/print_job/print_job_controller.dart index 6047d0ca..44ece029 100644 --- a/lib/src/print_job/print_job_controller.dart +++ b/lib/src/print_job/print_job_controller.dart @@ -17,7 +17,7 @@ class PrintJobController implements Disposable { ///Print job ID. final String id; - late MethodChannel _channel; + MethodChannel? _channel; ///A completion handler used to handle the conclusion of the print job (for instance, to reset state) and to handle any errors encountered in printing. /// @@ -29,7 +29,7 @@ class PrintJobController implements Disposable { PrintJobController({required this.id}) { this._channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_$id'); - this._channel.setMethodCallHandler((call) async { + this._channel?.setMethodCallHandler((call) async { try { return await _handleMethod(call); } on Error catch (e) { @@ -60,7 +60,7 @@ class PrintJobController implements Disposable { ///- Android native WebView ([Official API - PrintJob.cancel](https://developer.android.com/reference/android/print/PrintJob#cancel())) Future cancel() async { Map args = {}; - await _channel.invokeMethod('cancel', args); + await _channel?.invokeMethod('cancel', args); } ///Restarts this print job. @@ -70,7 +70,7 @@ class PrintJobController implements Disposable { ///- Android native WebView ([Official API - PrintJob.restart](https://developer.android.com/reference/android/print/PrintJob#restart())) Future restart() async { Map args = {}; - await _channel.invokeMethod('restart', args); + await _channel?.invokeMethod('restart', args); } ///Dismisses the printing-options sheet or popover. @@ -85,7 +85,7 @@ class PrintJobController implements Disposable { Future dismiss({bool animated: true}) async { Map args = {}; args.putIfAbsent("animated", () => animated); - await _channel.invokeMethod('dismiss', args); + await _channel?.invokeMethod('dismiss', args); } ///Gets the [PrintJobInfo] that describes this job. @@ -101,7 +101,7 @@ class PrintJobController implements Disposable { Future getInfo() async { Map args = {}; Map? infoMap = - (await _channel.invokeMethod('getInfo', args))?.cast(); + (await _channel?.invokeMethod('getInfo', args))?.cast(); return PrintJobInfo.fromMap(infoMap); } @@ -114,6 +114,8 @@ class PrintJobController implements Disposable { @override Future dispose() async { Map args = {}; - await _channel.invokeMethod('dispose', args); + await _channel?.invokeMethod('dispose', args); + _channel?.setMethodCallHandler(null); + _channel = null; } } diff --git a/lib/src/pull_to_refresh/main.dart b/lib/src/pull_to_refresh/main.dart index 34d036cb..ae4ef16a 100644 --- a/lib/src/pull_to_refresh/main.dart +++ b/lib/src/pull_to_refresh/main.dart @@ -1,3 +1,4 @@ -export 'pull_to_refresh_controller.dart'; +export 'pull_to_refresh_controller.dart' + show PullToRefreshController; export 'pull_to_refresh_settings.dart' show PullToRefreshSettings, PullToRefreshOptions; diff --git a/lib/src/pull_to_refresh/pull_to_refresh_controller.dart b/lib/src/pull_to_refresh/pull_to_refresh_controller.dart index f521d535..a91cf575 100644 --- a/lib/src/pull_to_refresh/pull_to_refresh_controller.dart +++ b/lib/src/pull_to_refresh/pull_to_refresh_controller.dart @@ -44,19 +44,6 @@ class PullToRefreshController { this.settings = settings ?? PullToRefreshSettings(); } - void initMethodChannel(dynamic id) { - this._channel = MethodChannel( - 'com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id'); - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); - } - _debugLog(String method, dynamic args) { debugLog( className: this.runtimeType.toString(), @@ -228,4 +215,28 @@ class PullToRefreshController { args.putIfAbsent('attributedTitle', () => attributedTitle.toMap()); await _channel?.invokeMethod('setStyledTitle', args); } + + ///Disposes the controller. + void dispose({bool isKeepAlive = false}) { + if (!isKeepAlive) { + _channel?.setMethodCallHandler(null); + } + _channel = null; + } } + +extension InternalPullToRefreshController on PullToRefreshController { + void init(dynamic id) { + this._channel = MethodChannel( + 'com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id'); + this._channel?.setMethodCallHandler((call) async { + if (_channel == null) return null; + try { + return await _handleMethod(call); + } on Error catch (e) { + print(e); + print(e.stackTrace); + } + }); + } +} \ No newline at end of file diff --git a/lib/src/types/main.dart b/lib/src/types/main.dart index d6fd748a..fbd30d34 100644 --- a/lib/src/types/main.dart +++ b/lib/src/types/main.dart @@ -198,7 +198,6 @@ export 'web_storage_type.dart' show WebStorageType; export 'website_data_record.dart' show WebsiteDataRecord, IOSWKWebsiteDataRecord; export 'website_data_type.dart' show WebsiteDataType, IOSWKWebsiteDataType; -export 'webview_implementation.dart' show WebViewImplementation; export 'webview_package_info.dart' show WebViewPackageInfo, AndroidWebViewPackageInfo; export 'webview_render_process_action.dart' show WebViewRenderProcessAction; diff --git a/lib/src/types/webview_implementation.dart b/lib/src/types/webview_implementation.dart deleted file mode 100644 index 91d4fb6e..00000000 --- a/lib/src/types/webview_implementation.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; - -import '../in_app_webview/webview.dart'; - -part 'webview_implementation.g.dart'; - -///Class that represents the [WebView] native implementation to be used. -@ExchangeableEnum() -class WebViewImplementation_ { - // ignore: unused_field - final int _value; - const WebViewImplementation_._internal(this._value); - - ///Default native implementation, such as `WKWebView` for iOS and `android.webkit.WebView` for Android. - static const NATIVE = const WebViewImplementation_._internal(0); -} diff --git a/lib/src/types/webview_implementation.g.dart b/lib/src/types/webview_implementation.g.dart deleted file mode 100644 index f61a6bb3..00000000 --- a/lib/src/types/webview_implementation.g.dart +++ /dev/null @@ -1,73 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'webview_implementation.dart'; - -// ************************************************************************** -// ExchangeableEnumGenerator -// ************************************************************************** - -///Class that represents the [WebView] native implementation to be used. -class WebViewImplementation { - final int _value; - final int _nativeValue; - const WebViewImplementation._internal(this._value, this._nativeValue); -// ignore: unused_element - factory WebViewImplementation._internalMultiPlatform( - int value, Function nativeValue) => - WebViewImplementation._internal(value, nativeValue()); - - ///Default native implementation, such as `WKWebView` for iOS and `android.webkit.WebView` for Android. - static const NATIVE = WebViewImplementation._internal(0, 0); - - ///Set of all values of [WebViewImplementation]. - static final Set values = [ - WebViewImplementation.NATIVE, - ].toSet(); - - ///Gets a possible [WebViewImplementation] instance from [int] value. - static WebViewImplementation? fromValue(int? value) { - if (value != null) { - try { - return WebViewImplementation.values - .firstWhere((element) => element.toValue() == value); - } catch (e) { - return null; - } - } - return null; - } - - ///Gets a possible [WebViewImplementation] instance from a native value. - static WebViewImplementation? fromNativeValue(int? value) { - if (value != null) { - try { - return WebViewImplementation.values - .firstWhere((element) => element.toNativeValue() == value); - } catch (e) { - return null; - } - } - return null; - } - - ///Gets [int] value. - int toValue() => _value; - - ///Gets [int] native value. - int toNativeValue() => _nativeValue; - - @override - int get hashCode => _value.hashCode; - - @override - bool operator ==(value) => value == _value; - - @override - String toString() { - switch (_value) { - case 0: - return 'NATIVE'; - } - return _value.toString(); - } -} diff --git a/lib/src/web_authentication_session/web_authenticate_session.dart b/lib/src/web_authentication_session/web_authenticate_session.dart index 79665ca7..6f377fb7 100755 --- a/lib/src/web_authentication_session/web_authenticate_session.dart +++ b/lib/src/web_authentication_session/web_authenticate_session.dart @@ -56,7 +56,7 @@ class WebAuthenticationSession implements Disposable { ///A completion handler the session calls when it completes successfully, or when the user cancels the session. WebAuthenticationSessionCompletionHandler onComplete; - late MethodChannel _channel; + MethodChannel? _channel; static const MethodChannel _sharedChannel = const MethodChannel( 'com.pichillilorenzo/flutter_webauthenticationsession'); @@ -104,7 +104,7 @@ class WebAuthenticationSession implements Disposable { initialSettings ?? WebAuthenticationSessionSettings(); this._channel = MethodChannel( 'com.pichillilorenzo/flutter_webauthenticationsession_$id'); - this._channel.setMethodCallHandler((call) async { + this._channel?.setMethodCallHandler((call) async { try { return await _handleMethod(call); } on Error catch (e) { @@ -147,10 +147,10 @@ class WebAuthenticationSession implements Disposable { ///- iOS ([Official API - ASWebAuthenticationSession.canStart](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3516277-canstart)) Future canStart() async { Map args = {}; - return await _channel.invokeMethod('canStart', args); + return await _channel?.invokeMethod('canStart', args); } - ///Starts a web authentication session. + ///Starts the web authentication session. /// ///Returns a boolean value indicating whether the web authentication session started successfully. /// @@ -161,10 +161,10 @@ class WebAuthenticationSession implements Disposable { ///- iOS ([Official API - ASWebAuthenticationSession.start](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990953-start)) Future start() async { Map args = {}; - return await _channel.invokeMethod('start', args); + return await _channel?.invokeMethod('start', args); } - ///Cancels a web authentication session. + ///Cancels the web authentication session. /// ///If the session has already presented a view with the authentication webpage, calling this method dismisses that view. ///Calling [cancel] on an already canceled session has no effect. @@ -173,17 +173,19 @@ class WebAuthenticationSession implements Disposable { ///- iOS ([Official API - ASWebAuthenticationSession.cancel](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990951-cancel)) Future cancel() async { Map args = {}; - await _channel.invokeMethod("cancel", args); + await _channel?.invokeMethod("cancel", args); } - ///Disposes a web authentication session. + ///Disposes the web authentication session. /// ///**Supported Platforms/Implementations**: ///- iOS @override Future dispose() async { Map args = {}; - await _channel.invokeMethod("dispose", args); + await _channel?.invokeMethod("dispose", args); + _channel?.setMethodCallHandler(null); + _channel = null; } ///Returns `true` if [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) diff --git a/lib/src/web_message/web_message_channel.dart b/lib/src/web_message/web_message_channel.dart index 40656658..7ca3751f 100644 --- a/lib/src/web_message/web_message_channel.dart +++ b/lib/src/web_message/web_message_channel.dart @@ -18,13 +18,13 @@ class WebMessageChannel { ///The second [WebMessagePort] object of the channel. final WebMessagePort port2; - late MethodChannel _channel; + MethodChannel? _channel; WebMessageChannel( {required this.id, required this.port1, required this.port2}) { this._channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_web_message_channel_$id'); - this._channel.setMethodCallHandler((call) async { + this._channel?.setMethodCallHandler((call) async { try { return await _handleMethod(call); } on Error catch (e) { @@ -97,7 +97,7 @@ class WebMessagePort { Map args = {}; args.putIfAbsent('index', () => this._index); await _webMessageChannel._channel - .invokeMethod('setWebMessageCallback', args); + ?.invokeMethod('setWebMessageCallback', args); this._onMessage = onMessage; } @@ -106,14 +106,14 @@ class WebMessagePort { Map args = {}; args.putIfAbsent('index', () => this._index); args.putIfAbsent('message', () => message.toMap()); - await _webMessageChannel._channel.invokeMethod('postMessage', args); + await _webMessageChannel._channel?.invokeMethod('postMessage', args); } ///Close the message port and free any resources associated with it. Future close() async { Map args = {}; args.putIfAbsent('index', () => this._index); - await _webMessageChannel._channel.invokeMethod('close', args); + await _webMessageChannel._channel?.invokeMethod('close', args); } Map toMap() { diff --git a/lib/src/web_message/web_message_listener.dart b/lib/src/web_message/web_message_listener.dart index 9d13bc75..03c7ebfa 100644 --- a/lib/src/web_message/web_message_listener.dart +++ b/lib/src/web_message/web_message_listener.dart @@ -34,7 +34,7 @@ class WebMessageListener { ///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat.WebMessageListener#onPostMessage(android.webkit.WebView,%20androidx.webkit.WebMessageCompat,%20android.net.Uri,%20boolean,%20androidx.webkit.JavaScriptReplyProxy) OnPostMessageCallback? onPostMessage; - late MethodChannel _channel; + MethodChannel? _channel; WebMessageListener( {required this.jsObjectName, @@ -47,7 +47,7 @@ class WebMessageListener { "allowedOriginRules cannot contain empty strings"); this._channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${id}_$jsObjectName'); - this._channel.setMethodCallHandler((call) async { + this._channel?.setMethodCallHandler((call) async { try { return await _handleMethod(call); } on Error catch (e) { @@ -114,6 +114,6 @@ class JavaScriptReplyProxy { Future postMessage(String message) async { Map args = {}; args.putIfAbsent('message', () => message); - await _webMessageListener._channel.invokeMethod('postMessage', args); + await _webMessageListener._channel?.invokeMethod('postMessage', args); } } diff --git a/lib/src/web_storage/web_storage.dart b/lib/src/web_storage/web_storage.dart index 5dcbace3..56d1121c 100644 --- a/lib/src/web_storage/web_storage.dart +++ b/lib/src/web_storage/web_storage.dart @@ -19,6 +19,12 @@ class WebStorage { SessionStorage sessionStorage; WebStorage({required this.localStorage, required this.sessionStorage}); + + ///Disposes the web storage. + void dispose() { + localStorage.dispose(); + sessionStorage.dispose(); + } } ///Class that represents a single web storage item of the JavaScript `window.sessionStorage` and `window.localStorage` objects. @@ -51,7 +57,7 @@ class WebStorageItem { ///Class that provides methods to manage the JavaScript [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) object. ///It is used by [LocalStorage] and [SessionStorage]. class Storage { - late InAppWebViewController _controller; + InAppWebViewController? _controller; ///The web storage type: `window.sessionStorage` or `window.localStorage`. WebStorageType webStorageType; @@ -69,7 +75,7 @@ class Storage { ///- iOS ///- Web Future length() async { - var result = await _controller.evaluateJavascript(source: """ + var result = await _controller?.evaluateJavascript(source: """ window.$webStorageType.length; """); return result != null ? int.parse(json.decode(result)) : null; @@ -85,7 +91,7 @@ class Storage { ///- Web Future setItem({required String key, required dynamic value}) async { var encodedValue = json.encode(value); - await _controller.evaluateJavascript(source: """ + await _controller?.evaluateJavascript(source: """ window.$webStorageType.setItem("$key", ${value is String ? encodedValue : "JSON.stringify($encodedValue)"}); """); } @@ -99,7 +105,7 @@ class Storage { ///- iOS ///- Web Future getItem({required String key}) async { - var itemValue = await _controller.evaluateJavascript(source: """ + var itemValue = await _controller?.evaluateJavascript(source: """ window.$webStorageType.getItem("$key"); """); @@ -123,7 +129,7 @@ class Storage { ///- iOS ///- Web Future removeItem({required String key}) async { - await _controller.evaluateJavascript(source: """ + await _controller?.evaluateJavascript(source: """ window.$webStorageType.removeItem("$key"); """); } @@ -140,7 +146,7 @@ class Storage { var webStorageItems = []; List>? items = - (await _controller.evaluateJavascript(source: """ + (await _controller?.evaluateJavascript(source: """ (function() { var webStorageItems = []; for(var i = 0; i < window.$webStorageType.length; i++){ @@ -177,7 +183,7 @@ class Storage { ///- iOS ///- Web Future clear() async { - await _controller.evaluateJavascript(source: """ + await _controller?.evaluateJavascript(source: """ window.$webStorageType.clear(); """); } @@ -192,11 +198,16 @@ class Storage { ///- iOS ///- Web Future key({required int index}) async { - var result = await _controller.evaluateJavascript(source: """ + var result = await _controller?.evaluateJavascript(source: """ window.$webStorageType.key($index); """); return result != null ? json.decode(result) : null; } + + ///Disposes the storage. + void dispose() { + _controller = null; + } } ///Class that provides methods to manage the JavaScript `window.localStorage` object. diff --git a/macos/Classes/FindInteraction/FindInteractionController.swift b/macos/Classes/FindInteraction/FindInteractionController.swift index d1973db5..2b5f7c97 100644 --- a/macos/Classes/FindInteraction/FindInteractionController.swift +++ b/macos/Classes/FindInteraction/FindInteractionController.swift @@ -11,6 +11,7 @@ import FlutterMacOS public class FindInteractionController : NSObject, Disposable { static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_"; + var plugin: InAppWebViewFlutterPlugin? var webView: InAppWebView? var channelDelegate: FindInteractionChannelDelegate? var settings: FindInteractionSettings? @@ -18,13 +19,16 @@ public class FindInteractionController : NSObject, Disposable { var searchText: String? var activeFindSession: FindSession? - public init(registrar: FlutterPluginRegistrar, id: Any, webView: InAppWebView, settings: FindInteractionSettings?) { + public init(plugin: InAppWebViewFlutterPlugin, id: Any, webView: InAppWebView, settings: FindInteractionSettings?) { super.init() + self.plugin = plugin self.webView = webView self.settings = settings - let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), - binaryMessenger: registrar.messenger) - self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: registrar.messenger) + self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) + } } public func prepare() { @@ -87,6 +91,7 @@ public class FindInteractionController : NSObject, Disposable { channelDelegate = nil webView = nil activeFindSession = nil + plugin = nil } deinit { diff --git a/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift b/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift index 8e947423..ee109a64 100644 --- a/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift +++ b/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift @@ -81,7 +81,7 @@ public class HeadlessInAppWebView : Disposable { public func dispose() { channelDelegate?.dispose() channelDelegate = nil - HeadlessInAppWebViewManager.webViews[id] = nil + plugin?.headlessInAppWebViewManager?.webViews[id] = nil if let view = flutterWebView?.view() { view.superview?.removeFromSuperview() } diff --git a/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index f2d14cbc..29bed518 100644 --- a/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/macos/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -16,7 +16,7 @@ import AVFoundation public class HeadlessInAppWebViewManager: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview" var plugin: InAppWebViewFlutterPlugin? - static var webViews: [String: HeadlessInAppWebView?] = [:] + var webViews: [String: HeadlessInAppWebView?] = [:] init(plugin: InAppWebViewFlutterPlugin) { super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) @@ -40,15 +40,15 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { } public func run(id: String, params: [String: Any?]) { - guard let plugin = plugin, let registrar = plugin.registrar else { + guard let plugin = plugin else { return } - let flutterWebView = FlutterWebViewController(registrar: registrar, + let flutterWebView = FlutterWebViewController(plugin: plugin, withFrame: CGRect.zero, viewIdentifier: id, params: params as NSDictionary) let headlessInAppWebView = HeadlessInAppWebView(plugin: plugin, id: id, flutterWebView: flutterWebView) - HeadlessInAppWebViewManager.webViews[id] = headlessInAppWebView + webViews[id] = headlessInAppWebView headlessInAppWebView.prepare(params: params as NSDictionary) headlessInAppWebView.onWebViewCreated() @@ -57,11 +57,11 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { public override func dispose() { super.dispose() - let headlessWebViews = HeadlessInAppWebViewManager.webViews.values + let headlessWebViews = webViews.values headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in headlessWebView?.dispose() } - HeadlessInAppWebViewManager.webViews.removeAll() + webViews.removeAll() plugin = nil } diff --git a/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift index 8df02349..582e02b4 100755 --- a/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/macos/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -33,7 +33,7 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega var isHidden = false public override func loadView() { - guard let registrar = plugin?.registrar else { + guard let plugin = plugin, let registrar = plugin.registrar else { return } @@ -46,12 +46,12 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega } let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings) - if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView webView!.initialUserScripts = userScripts } else { webView = InAppWebView(id: nil, - registrar: nil, + plugin: nil, frame: .zero, configuration: preWebviewConfiguration, userScripts: userScripts) @@ -63,10 +63,11 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega webView.inAppBrowserDelegate = self webView.id = id + webView.plugin = plugin webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel) let findInteractionController = FindInteractionController( - registrar: registrar, + plugin: plugin, id: id, webView: webView, settings: nil) webView.findInteractionController = findInteractionController findInteractionController.prepare() @@ -99,7 +100,7 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true progressBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true - if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView?.load(webViewTransport.request) channelDelegate?.onBrowserCreated() } else { diff --git a/macos/Classes/InAppWebView/FlutterWebViewController.swift b/macos/Classes/InAppWebView/FlutterWebViewController.swift index 1e11f30c..fd6d03d9 100755 --- a/macos/Classes/InAppWebView/FlutterWebViewController.swift +++ b/macos/Classes/InAppWebView/FlutterWebViewController.swift @@ -12,12 +12,15 @@ import FlutterMacOS public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Disposable { var myView: NSView? + var keepAliveId: String? - init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) { + init(plugin: InAppWebViewFlutterPlugin, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) { super.init() myView = NSView(frame: frame) + keepAliveId = params["keepAliveId"] as? String + let initialSettings = params["initialSettings"] as! [String: Any?] let windowId = params["windowId"] as? Int64 let initialUserScripts = params["initialUserScripts"] as? [[String: Any]] @@ -35,24 +38,27 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos var webView: InAppWebView? - if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView webView!.id = viewId - let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), - binaryMessenger: registrar.messenger) - webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) + webView!.plugin = plugin + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), + binaryMessenger: registrar.messenger) + webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) + } webView!.frame = myView!.bounds webView!.initialUserScripts = userScripts } else { webView = InAppWebView(id: viewId, - registrar: registrar, + plugin: plugin, frame: myView!.bounds, configuration: preWebviewConfiguration, userScripts: userScripts) } let findInteractionController = FindInteractionController( - registrar: registrar, + plugin: plugin, id: viewId, webView: webView!, settings: nil) webView!.findInteractionController = findInteractionController findInteractionController.prepare() @@ -121,7 +127,7 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos } load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData) } - else if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + else if let wId = windowId, let webViewTransport = webView.plugin?.inAppWebViewManager?.windowWebViews[wId] { webView.load(webViewTransport.request) } } @@ -169,10 +175,12 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos } public func dispose() { - if let webView = webView() { - webView.dispose() + if keepAliveId == nil { + if let webView = webView() { + webView.dispose() + } + myView = nil } - myView = nil } deinit { diff --git a/macos/Classes/InAppWebView/FlutterWebViewFactory.swift b/macos/Classes/InAppWebView/FlutterWebViewFactory.swift index b37fb9a7..5fe11183 100755 --- a/macos/Classes/InAppWebView/FlutterWebViewFactory.swift +++ b/macos/Classes/InAppWebView/FlutterWebViewFactory.swift @@ -12,11 +12,11 @@ import Foundation public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { static let VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview" - private var registrar: FlutterPluginRegistrar? + private var plugin: InAppWebViewFlutterPlugin - init(registrar: FlutterPluginRegistrar?) { + init(plugin: InAppWebViewFlutterPlugin) { + self.plugin = plugin super.init() - self.registrar = registrar } public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? { @@ -25,18 +25,48 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { public func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView { let arguments = args as? NSDictionary + var flutterWebView: FlutterWebViewController? + var id: Any = viewId - if let headlessWebViewId = arguments?["headlessWebViewId"] as? String, - let headlessWebView = HeadlessInAppWebViewManager.webViews[headlessWebViewId], + let keepAliveId = arguments?["keepAliveId"] as? String + let headlessWebViewId = arguments?["headlessWebViewId"] as? String + + if let headlessWebViewId = headlessWebViewId, + let headlessWebView = plugin.headlessInAppWebViewManager?.webViews[headlessWebViewId], let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: .zero) { - return platformView.view() + flutterWebView = platformView + flutterWebView?.keepAliveId = keepAliveId } - let webviewController = FlutterWebViewController(registrar: registrar!, - withFrame: .zero, - viewIdentifier: viewId, - params: arguments!) - webviewController.makeInitialLoad(params: arguments!) - return webviewController.view() + if let keepAliveId = keepAliveId, + flutterWebView == nil, + let keepAliveWebView = plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] { + flutterWebView = keepAliveWebView + if let view = flutterWebView?.view() { + // remove from parent + view.removeFromSuperview() + } + } + + let shouldMakeInitialLoad = flutterWebView == nil + if flutterWebView == nil { + if let keepAliveId = keepAliveId { + id = keepAliveId + } + flutterWebView = FlutterWebViewController(plugin: plugin, + withFrame: .zero, + viewIdentifier: id, + params: arguments!) + } + + if let keepAliveId = keepAliveId { + plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] = flutterWebView! + } + + if shouldMakeInitialLoad { + flutterWebView?.makeInitialLoad(params: arguments!) + } + + return flutterWebView!.view() } } diff --git a/macos/Classes/InAppWebView/InAppWebView.swift b/macos/Classes/InAppWebView/InAppWebView.swift index 010f9c34..e5575dd5 100755 --- a/macos/Classes/InAppWebView/InAppWebView.swift +++ b/macos/Classes/InAppWebView/InAppWebView.swift @@ -16,7 +16,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_" var id: Any? // viewId - var registrar: FlutterPluginRegistrar? + var plugin: InAppWebViewFlutterPlugin? var windowId: Int64? var windowCreated = false var inAppBrowserDelegate: InAppBrowserDelegate? @@ -43,19 +43,16 @@ public class InAppWebView: WKWebView, WKUIDelegate, var customIMPs: [IMP] = [] - static var windowWebViews: [Int64:WebViewTransport] = [:] - static var windowAutoincrementId: Int64 = 0; - var callAsyncJavaScriptBelowIOS14Results: [String:((Any?) -> Void)] = [:] var currentOpenPanel: NSOpenPanel? - init(id: Any?, registrar: FlutterPluginRegistrar?, frame: CGRect, configuration: WKWebViewConfiguration, + init(id: Any?, plugin: InAppWebViewFlutterPlugin?, frame: CGRect, configuration: WKWebViewConfiguration, userScripts: [UserScript] = []) { super.init(frame: frame, configuration: configuration) self.id = id - self.registrar = registrar - if let id = id, let registrar = registrar { + self.plugin = plugin + if let id = id, let registrar = plugin?.registrar { let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), binaryMessenger: registrar.messenger) self.channelDelegate = WebViewChannelDelegate(webView: self, channel: channel) @@ -1788,10 +1785,14 @@ public class InAppWebView: WKWebView, WKUIDelegate, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { - InAppWebView.windowAutoincrementId += 1 - let windowId = InAppWebView.windowAutoincrementId + var windowId: Int64 = 0 + let inAppWebViewManager = plugin?.inAppWebViewManager + if let inAppWebViewManager = inAppWebViewManager { + inAppWebViewManager.windowAutoincrementId += 1 + windowId = inAppWebViewManager.windowAutoincrementId + } - let windowWebView = InAppWebView(id: nil, registrar: nil, frame: CGRect.zero, configuration: configuration) + let windowWebView = InAppWebView(id: nil, plugin: nil, frame: CGRect.zero, configuration: configuration) windowWebView.windowId = windowId let webViewTransport = WebViewTransport( @@ -1799,7 +1800,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, request: navigationAction.request ) - InAppWebView.windowWebViews[windowId] = webViewTransport + inAppWebViewManager?.windowWebViews[windowId] = webViewTransport windowWebView.stopLoading() let createWindowAction = CreateWindowAction(navigationAction: navigationAction, windowId: windowId, windowFeatures: windowFeatures, isDialog: nil) @@ -1809,8 +1810,8 @@ public class InAppWebView: WKWebView, WKUIDelegate, return !handledByClient } callback.defaultBehaviour = { (handledByClient: Bool?) in - if InAppWebView.windowWebViews[windowId] != nil { - InAppWebView.windowWebViews.removeValue(forKey: windowId) + if inAppWebViewManager?.windowWebViews[windowId] != nil { + inAppWebViewManager?.windowWebViews.removeValue(forKey: windowId) } self.loadUrl(urlRequest: navigationAction.request, allowingReadAccessTo: nil) } @@ -2066,7 +2067,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, let _windowId = body["_windowId"] as? Int64 var webView = self - if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) @@ -2083,7 +2084,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, return !handledByClient } callback.defaultBehaviour = { (handledByClient: Bool?) in - if let printJob = PrintJobManager.jobs[printJobId] { + if let printJob = self.plugin?.printJobManager?.jobs[printJobId] { printJob?.disposeNoDismiss() } } @@ -2101,7 +2102,7 @@ public class InAppWebView: WKWebView, WKUIDelegate, let _windowId = body["_windowId"] as? Int64 var webView = self - if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } @@ -2143,7 +2144,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { let _windowId = body["_windowId"] as? Int64 var webView = self - if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) @@ -2155,7 +2156,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { let _windowId = body["_windowId"] as? Int64 var webView = self - if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { + if let wId = _windowId, let webViewTransport = plugin?.inAppWebViewManager?.windowWebViews[wId] { webView = webViewTransport.webView } webView.channelDelegate?.onScrollChanged(x: x, y: y) @@ -2358,9 +2359,9 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } } - if let id = printJobId, let registrar = registrar { - let printJob = PrintJobController(registrar: registrar, id: id, job: printOperation, settings: settings) - PrintJobManager.jobs[id] = printJob + if let id = printJobId, let plugin = plugin { + let printJob = PrintJobController(plugin: plugin, id: id, job: printOperation, settings: settings) + plugin.printJobManager?.jobs[id] = printJob printJob.present(parentWindow: window, completionHandler: completionHandler) } else if let window = window { printJobCompletionHandler = completionHandler @@ -2458,12 +2459,12 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } public func createWebMessageChannel(completionHandler: ((WebMessageChannel?) -> Void)? = nil) -> WebMessageChannel? { - guard let registrar = registrar else { + guard let plugin = plugin else { completionHandler?(nil) return nil } let id = NSUUID().uuidString - let webMessageChannel = WebMessageChannel(registrar: registrar, id: id) + let webMessageChannel = WebMessageChannel(plugin: plugin, id: id) webMessageChannel.initJsInstance(webView: self, completionHandler: completionHandler) webMessageChannels[id] = webMessageChannel @@ -2567,8 +2568,8 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if #available(macOS 10.13, *) { configuration.userContentController.removeAllContentRuleLists() } - } else if let wId = windowId, InAppWebView.windowWebViews[wId] != nil { - InAppWebView.windowWebViews.removeValue(forKey: wId) + } else if let wId = windowId, plugin?.inAppWebViewManager?.windowWebViews[wId] != nil { + plugin?.inAppWebViewManager?.windowWebViews.removeValue(forKey: wId) } configuration.userContentController.dispose(windowId: windowId) NotificationCenter.default.removeObserver(self) @@ -2582,7 +2583,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { isPausedTimersCompletionHandler = nil callAsyncJavaScriptBelowIOS14Results.removeAll() super.removeFromSuperview() - registrar = nil + plugin = nil } deinit { diff --git a/macos/Classes/InAppWebViewStatic.swift b/macos/Classes/InAppWebView/InAppWebViewManager.swift similarity index 65% rename from macos/Classes/InAppWebViewStatic.swift rename to macos/Classes/InAppWebView/InAppWebViewManager.swift index b8dc8c40..19486446 100755 --- a/macos/Classes/InAppWebViewStatic.swift +++ b/macos/Classes/InAppWebView/InAppWebViewManager.swift @@ -1,5 +1,5 @@ // -// InAppWebViewStatic.swift +// InAppWebViewManager.swift // flutter_inappwebview // // Created by Lorenzo Pichilli on 08/12/2019. @@ -9,14 +9,18 @@ import Foundation import WebKit import FlutterMacOS -public class InAppWebViewStatic: ChannelDelegate { - static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static" +public class InAppWebViewManager: ChannelDelegate { + static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager" var plugin: InAppWebViewFlutterPlugin? var webViewForUserAgent: WKWebView? var defaultUserAgent: String? + var keepAliveWebViews: [String:FlutterWebViewController?] = [:] + var windowWebViews: [Int64:WebViewTransport] = [:] + var windowAutoincrementId: Int64 = 0 + init(plugin: InAppWebViewFlutterPlugin) { - super.init(channel: FlutterMethodChannel(name: InAppWebViewStatic.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) + super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) self.plugin = plugin } @@ -37,6 +41,11 @@ public class InAppWebViewStatic: ChannelDelegate { result(false) } break + case "disposeKeepAlive": + let keepAliveId = arguments!["keepAliveId"] as! String + disposeKeepAlive(keepAliveId: keepAliveId) + result(true) + break default: result(FlutterMethodNotImplemented) break @@ -68,11 +77,27 @@ public class InAppWebViewStatic: ChannelDelegate { } } + public func disposeKeepAlive(keepAliveId: String) { + if let flutterWebView = keepAliveWebViews[keepAliveId] as? FlutterWebViewController { + flutterWebView.keepAliveId = nil + flutterWebView.dispose() + keepAliveWebViews[keepAliveId] = nil + } + } + public override func dispose() { super.dispose() - plugin = nil + let keepAliveWebViewValues = keepAliveWebViews.values + keepAliveWebViewValues.forEach {(keepAliveWebView: FlutterWebViewController?) in + if let keepAliveId = keepAliveWebView?.keepAliveId { + disposeKeepAlive(keepAliveId: keepAliveId) + } + } + keepAliveWebViews.removeAll() + windowWebViews.removeAll() webViewForUserAgent = nil defaultUserAgent = nil + plugin = nil } deinit { diff --git a/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift b/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift index ef7639f2..cb0e7be9 100644 --- a/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift +++ b/macos/Classes/InAppWebView/WebMessage/WebMessageChannel.swift @@ -11,18 +11,20 @@ import FlutterMacOS public class WebMessageChannel : FlutterMethodCallDelegate { static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" var id: String + var plugin: InAppWebViewFlutterPlugin? var channelDelegate: WebMessageChannelChannelDelegate? weak var webView: InAppWebView? var ports: [WebMessagePort] = [] - var registrar: FlutterPluginRegistrar? - public init(registrar: FlutterPluginRegistrar, id: String) { + public init(plugin: InAppWebViewFlutterPlugin, id: String) { self.id = id - self.registrar = registrar + self.plugin = plugin super.init() - let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger) - self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: registrar.messenger) + self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) + } self.ports = [ WebMessagePort(name: "port1", webMessageChannel: self), WebMessagePort(name: "port2", webMessageChannel: self) @@ -68,7 +70,7 @@ public class WebMessageChannel : FlutterMethodCallDelegate { })(); """) webView = nil - registrar = nil + plugin = nil } deinit { diff --git a/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift b/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift index 544481f7..bd1c0860 100644 --- a/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift +++ b/macos/Classes/InAppWebView/WebMessage/WebMessageListener.swift @@ -16,17 +16,19 @@ public class WebMessageListener : FlutterMethodCallDelegate { var allowedOriginRules: Set var channelDelegate: WebMessageListenerChannelDelegate? weak var webView: InAppWebView? - var registrar: FlutterPluginRegistrar? + var plugin: InAppWebViewFlutterPlugin? - public init(registrar: FlutterPluginRegistrar, id: String, jsObjectName: String, allowedOriginRules: Set) { + public init(plugin: InAppWebViewFlutterPlugin, id: String, jsObjectName: String, allowedOriginRules: Set) { self.id = id - self.registrar = registrar + self.plugin = plugin self.jsObjectName = jsObjectName self.allowedOriginRules = allowedOriginRules super.init() - let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, - binaryMessenger: registrar.messenger) - self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, + binaryMessenger: registrar.messenger) + self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) + } } public func assertOriginRulesValid() throws { @@ -117,12 +119,12 @@ public class WebMessageListener : FlutterMethodCallDelegate { } } - public static func fromMap(registrar: FlutterPluginRegistrar, map: [String:Any?]?) -> WebMessageListener? { + public static func fromMap(plugin: InAppWebViewFlutterPlugin, map: [String:Any?]?) -> WebMessageListener? { guard let map = map else { return nil } return WebMessageListener( - registrar: registrar, + plugin: plugin, id: map["id"] as! String, jsObjectName: map["jsObjectName"] as! String, allowedOriginRules: Set(map["allowedOriginRules"] as! [String]) @@ -181,7 +183,7 @@ public class WebMessageListener : FlutterMethodCallDelegate { channelDelegate?.dispose() channelDelegate = nil webView = nil - registrar = nil + plugin = nil } deinit { diff --git a/macos/Classes/InAppWebView/WebViewChannelDelegate.swift b/macos/Classes/InAppWebView/WebViewChannelDelegate.swift index f4693c10..e90154c2 100644 --- a/macos/Classes/InAppWebView/WebViewChannelDelegate.swift +++ b/macos/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -507,9 +507,9 @@ public class WebViewChannelDelegate : ChannelDelegate { } break case .addWebMessageListener: - if let webView = webView, let registrar = webView.registrar { + if let webView = webView, let plugin = webView.plugin { let webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?] - let webMessageListener = WebMessageListener.fromMap(registrar: registrar, map: webMessageListenerMap)! + let webMessageListener = WebMessageListener.fromMap(plugin: plugin, map: webMessageListenerMap)! do { try webView.addWebMessageListener(webMessageListener: webMessageListener) result(false) diff --git a/macos/Classes/InAppWebViewFlutterPlugin.swift b/macos/Classes/InAppWebViewFlutterPlugin.swift index 4e8bca9e..530efdd7 100644 --- a/macos/Classes/InAppWebViewFlutterPlugin.swift +++ b/macos/Classes/InAppWebViewFlutterPlugin.swift @@ -27,14 +27,14 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { var registrar: FlutterPluginRegistrar? var platformUtil: PlatformUtil? - var inAppWebViewStatic: InAppWebViewStatic? + var inAppWebViewManager: InAppWebViewManager? var myCookieManager: Any? var myWebStorageManager: MyWebStorageManager? var credentialDatabase: CredentialDatabase? var inAppBrowserManager: InAppBrowserManager? var headlessInAppWebViewManager: HeadlessInAppWebViewManager? var webAuthenticationSessionManager: WebAuthenticationSessionManager? -// var printJobManager: PrintJobManager? + var printJobManager: PrintJobManager? var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var safariViewControllers: [String: Any?] = [:] @@ -42,19 +42,19 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { public init(with registrar: FlutterPluginRegistrar) { super.init() self.registrar = registrar - registrar.register(FlutterWebViewFactory(registrar: registrar) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID) + registrar.register(FlutterWebViewFactory(plugin: self) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID) platformUtil = PlatformUtil(plugin: self) inAppBrowserManager = InAppBrowserManager(plugin: self) headlessInAppWebViewManager = HeadlessInAppWebViewManager(plugin: self) - inAppWebViewStatic = InAppWebViewStatic(plugin: self) + inAppWebViewManager = InAppWebViewManager(plugin: self) credentialDatabase = CredentialDatabase(plugin: self) if #available(macOS 10.13, *) { myCookieManager = MyCookieManager(plugin: self) } myWebStorageManager = MyWebStorageManager(plugin: self) webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self) -// printJobManager = PrintJobManager() + printJobManager = PrintJobManager(plugin: self) } public static func register(with registrar: FlutterPluginRegistrar) { @@ -68,8 +68,8 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { inAppBrowserManager = nil headlessInAppWebViewManager?.dispose() headlessInAppWebViewManager = nil - inAppWebViewStatic?.dispose() - inAppWebViewStatic = nil + inAppWebViewManager?.dispose() + inAppWebViewManager = nil credentialDatabase?.dispose() credentialDatabase = nil if #available(macOS 10.13, *) { @@ -80,7 +80,7 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin { myWebStorageManager = nil webAuthenticationSessionManager?.dispose() webAuthenticationSessionManager = nil -// printJobManager?.dispose() -// printJobManager = nil + printJobManager?.dispose() + printJobManager = nil } } diff --git a/macos/Classes/PrintJob/PrintJobController.swift b/macos/Classes/PrintJob/PrintJobController.swift index a1780d9c..67e18c22 100644 --- a/macos/Classes/PrintJob/PrintJobController.swift +++ b/macos/Classes/PrintJob/PrintJobController.swift @@ -18,7 +18,7 @@ public enum PrintJobState: Int { public class PrintJobController : NSObject, Disposable { static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_" var id: String - var registrar: FlutterPluginRegistrar? + var plugin: InAppWebViewFlutterPlugin? var job: NSPrintOperation? var settings: PrintJobSettings? var channelDelegate: PrintJobChannelDelegate? @@ -30,15 +30,17 @@ public class PrintJobController : NSObject, Disposable { _ success: Bool, _ contextInfo: UnsafeMutableRawPointer?) -> Void - public init(registrar: FlutterPluginRegistrar, id: String, job: NSPrintOperation? = nil, settings: PrintJobSettings? = nil) { + public init(plugin: InAppWebViewFlutterPlugin, id: String, job: NSPrintOperation? = nil, settings: PrintJobSettings? = nil) { self.id = id - self.registrar = registrar + self.plugin = plugin super.init() self.job = job self.settings = settings - let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, - binaryMessenger: registrar.messenger) - self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) + if let registrar = plugin.registrar { + let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: registrar.messenger) + self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) + } } public func present(parentWindow: NSWindow? = nil, completionHandler: PrintJobController.CompletionHandler? = nil) { @@ -76,7 +78,8 @@ public class PrintJobController : NSObject, Disposable { channelDelegate = nil completionHandler = nil job = nil - PrintJobManager.jobs[id] = nil + plugin?.printJobManager?.jobs[id] = nil + plugin = nil } public func dispose() { @@ -84,7 +87,7 @@ public class PrintJobController : NSObject, Disposable { channelDelegate = nil completionHandler = nil job = nil - PrintJobManager.jobs[id] = nil - registrar = nil + plugin?.printJobManager?.jobs[id] = nil + plugin = nil } } diff --git a/macos/Classes/PrintJob/PrintJobManager.swift b/macos/Classes/PrintJob/PrintJobManager.swift index 7148027a..cf86b1e4 100644 --- a/macos/Classes/PrintJob/PrintJobManager.swift +++ b/macos/Classes/PrintJob/PrintJobManager.swift @@ -8,18 +8,21 @@ import Foundation public class PrintJobManager: NSObject, Disposable { - static var jobs: [String: PrintJobController?] = [:] + var plugin: InAppWebViewFlutterPlugin? + var jobs: [String: PrintJobController?] = [:] - public override init() { + public init(plugin: InAppWebViewFlutterPlugin?) { super.init() + self.plugin = plugin } public func dispose() { - let jobs = PrintJobManager.jobs.values - jobs.forEach { (job: PrintJobController?) in + let jobValues = jobs.values + jobValues.forEach { (job: PrintJobController?) in job?.dispose() } - PrintJobManager.jobs.removeAll() + jobs.removeAll() + plugin = nil } deinit { diff --git a/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift b/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift index d48ec8ee..a477d42d 100644 --- a/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift +++ b/macos/Classes/WebAuthenticationSession/WebAuthenticationSession.swift @@ -91,7 +91,7 @@ public class WebAuthenticationSession : NSObject, ASWebAuthenticationPresentatio channelDelegate?.dispose() channelDelegate = nil session = nil - WebAuthenticationSessionManager.sessions[id] = nil + plugin?.webAuthenticationSessionManager?.sessions[id] = nil plugin = nil } diff --git a/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift b/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift index 8267434b..102438ff 100644 --- a/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift +++ b/macos/Classes/WebAuthenticationSession/WebAuthenticationSessionManager.swift @@ -15,7 +15,7 @@ import SafariServices public class WebAuthenticationSessionManager: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webauthenticationsession" var plugin: InAppWebViewFlutterPlugin? - static var sessions: [String: WebAuthenticationSession?] = [:] + var sessions: [String: WebAuthenticationSession?] = [:] init(plugin: InAppWebViewFlutterPlugin) { super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) @@ -53,7 +53,7 @@ public class WebAuthenticationSessionManager: ChannelDelegate { let _ = initialSettings.parse(settings: settings) let session = WebAuthenticationSession(plugin: plugin, id: id, url: sessionUrl, callbackURLScheme: callbackURLScheme, settings: initialSettings) session.prepare() - WebAuthenticationSessionManager.sessions[id] = session + sessions[id] = session result(true) return } @@ -63,12 +63,12 @@ public class WebAuthenticationSessionManager: ChannelDelegate { public override func dispose() { super.dispose() - let sessions = WebAuthenticationSessionManager.sessions.values - sessions.forEach { (session: WebAuthenticationSession?) in + let sessionValues = sessions.values + sessionValues.forEach { (session: WebAuthenticationSession?) in session?.cancel() session?.dispose() } - WebAuthenticationSessionManager.sessions.removeAll() + sessions.removeAll() plugin = nil }