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

This commit is contained in:
Lorenzo Pichilli 2023-05-19 00:45:12 +02:00
parent b6725128a8
commit 5a113f6e8a
97 changed files with 1448 additions and 989 deletions

View File

@ -1,5 +1,10 @@
## 6.0.0-beta.24 ## 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 #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: 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)) - Merged "Fix AndroidX migration URL in README.md" [#1529](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1529) (thanks to [cslee](https://github.com/cslee))

View File

@ -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<String, Object> params = (HashMap<String, Object>) 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;
}
}

View File

@ -17,6 +17,8 @@ import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager;
import com.pichillilorenzo.flutter_inappwebview.proxy.ProxyManager; import com.pichillilorenzo.flutter_inappwebview.proxy.ProxyManager;
import com.pichillilorenzo.flutter_inappwebview.service_worker.ServiceWorkerManager; import com.pichillilorenzo.flutter_inappwebview.service_worker.ServiceWorkerManager;
import com.pichillilorenzo.flutter_inappwebview.tracing.TracingControllerManager; 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.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
@ -39,7 +41,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
@Nullable @Nullable
public ChromeSafariBrowserManager chromeSafariBrowserManager; public ChromeSafariBrowserManager chromeSafariBrowserManager;
@Nullable @Nullable
public InAppWebViewStatic inAppWebViewStatic; public InAppWebViewManager inAppWebViewManager;
@Nullable @Nullable
public MyCookieManager myCookieManager; public MyCookieManager myCookieManager;
@Nullable @Nullable
@ -56,11 +58,6 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
public PrintJobManager printJobManager; public PrintJobManager printJobManager;
@Nullable @Nullable
public TracingControllerManager tracingControllerManager; public TracingControllerManager tracingControllerManager;
@Nullable
public static ValueCallback<Uri> filePathCallbackLegacy;
@Nullable
public static ValueCallback<Uri[]> filePathCallback;
public FlutterWebViewFactory flutterWebViewFactory; public FlutterWebViewFactory flutterWebViewFactory;
public Context applicationContext; public Context applicationContext;
public PluginRegistry.Registrar registrar; public PluginRegistry.Registrar registrar;
@ -110,7 +107,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
FlutterWebViewFactory.VIEW_TYPE_ID, flutterWebViewFactory); FlutterWebViewFactory.VIEW_TYPE_ID, flutterWebViewFactory);
platformUtil = new PlatformUtil(this); platformUtil = new PlatformUtil(this);
inAppWebViewStatic = new InAppWebViewStatic(this); inAppWebViewManager = new InAppWebViewManager(this);
myCookieManager = new MyCookieManager(this); myCookieManager = new MyCookieManager(this);
myWebStorage = new MyWebStorage(this); myWebStorage = new MyWebStorage(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@ -122,7 +119,7 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
webViewFeatureManager = new WebViewFeatureManager(this); webViewFeatureManager = new WebViewFeatureManager(this);
proxyManager = new ProxyManager(this); proxyManager = new ProxyManager(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
printJobManager = new PrintJobManager(); printJobManager = new PrintJobManager(this);
} }
tracingControllerManager = new TracingControllerManager(this); tracingControllerManager = new TracingControllerManager(this);
} }
@ -157,9 +154,9 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
credentialDatabaseHandler.dispose(); credentialDatabaseHandler.dispose();
credentialDatabaseHandler = null; credentialDatabaseHandler = null;
} }
if (inAppWebViewStatic != null) { if (inAppWebViewManager != null) {
inAppWebViewStatic.dispose(); inAppWebViewManager.dispose();
inAppWebViewStatic = null; inAppWebViewManager = null;
} }
if (serviceWorkerManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (serviceWorkerManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
serviceWorkerManager.dispose(); serviceWorkerManager.dispose();
@ -181,8 +178,6 @@ public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
tracingControllerManager.dispose(); tracingControllerManager.dispose();
tracingControllerManager = null; tracingControllerManager = null;
} }
filePathCallbackLegacy = null;
filePathCallback = null;
} }
@Override @Override

View File

@ -393,6 +393,5 @@ public class MyCookieManager extends ChannelDelegateImpl {
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
plugin = null; plugin = null;
cookieManager = null;
} }
} }

View File

@ -134,6 +134,5 @@ public class MyWebStorage extends ChannelDelegateImpl {
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
plugin = null; plugin = null;
webStorageManager = null;
} }
} }

View File

@ -12,6 +12,7 @@ public class ActionBroadcastReceiver extends BroadcastReceiver {
protected static final String LOG_TAG = "ActionBroadcastReceiver"; 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_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_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"; public static final String KEY_URL_TITLE = "android.intent.extra.SUBJECT";
@Override @Override
@ -21,19 +22,25 @@ public class ActionBroadcastReceiver extends BroadcastReceiver {
if (url != null) { if (url != null) {
Bundle b = intent.getExtras(); Bundle b = intent.getExtras();
String viewId = b.getString(KEY_ACTION_VIEW_ID); String viewId = b.getString(KEY_ACTION_VIEW_ID);
String managerId = b.getString(KEY_ACTION_MANAGER_ID);
if (clickedId == -1) { if (managerId != null) {
int id = b.getInt(KEY_ACTION_ID); ChromeSafariBrowserManager chromeSafariBrowserManager = ChromeSafariBrowserManager.shared.get(managerId);
String title = b.getString(KEY_URL_TITLE); 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); ChromeCustomTabsActivity browser = chromeSafariBrowserManager.browsers.get(viewId);
if (browser != null && browser.channelDelegate != null) { if (browser != null && browser.channelDelegate != null) {
browser.channelDelegate.onItemActionPerform(id, url, title); browser.channelDelegate.onItemActionPerform(id, url, title);
} }
} else { } else {
ChromeCustomTabsActivity browser = ChromeSafariBrowserManager.browsers.get(viewId); ChromeCustomTabsActivity browser = chromeSafariBrowserManager.browsers.get(viewId);
if (browser != null && browser.channelDelegate != null) { if (browser != null && browser.channelDelegate != null) {
browser.channelDelegate.onSecondaryItemActionPerform(browser.getResources().getResourceName(clickedId), url); browser.channelDelegate.onSecondaryItemActionPerform(browser.getResources().getResourceName(clickedId), url);
}
}
} }
} }
} }

View File

@ -82,7 +82,7 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
manager = ChromeSafariBrowserManager.shared.get(managerId); manager = ChromeSafariBrowserManager.shared.get(managerId);
if (manager == null || manager.plugin == null || manager.plugin.messenger == null) return; 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); MethodChannel channel = new MethodChannel(manager.plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
channelDelegate = new ChromeCustomTabsChannelDelegate(this, channel); channelDelegate = new ChromeCustomTabsChannelDelegate(this, channel);
@ -271,6 +271,7 @@ public class ChromeCustomTabsActivity extends Activity implements Disposable {
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id); extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id);
extras.putString(ActionBroadcastReceiver.KEY_ACTION_MANAGER_ID, manager != null ? manager.id : null);
broadcastIntent.putExtras(extras); broadcastIntent.putExtras(extras);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 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(); Bundle extras = new Bundle();
extras.putInt(ActionBroadcastReceiver.KEY_ACTION_ID, actionSourceId); extras.putInt(ActionBroadcastReceiver.KEY_ACTION_ID, actionSourceId);
extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id); extras.putString(ActionBroadcastReceiver.KEY_ACTION_VIEW_ID, id);
extras.putString(ActionBroadcastReceiver.KEY_ACTION_MANAGER_ID, manager != null ? manager.id : null);
actionIntent.putExtras(extras); actionIntent.putExtras(extras);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 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.dispose();
channelDelegate = null; channelDelegate = null;
} }
if (ChromeSafariBrowserManager.browsers.containsKey(id)) { if (manager != null) {
ChromeSafariBrowserManager.browsers.put(id, null); if (manager.browsers.containsKey(id)) {
manager.browsers.put(id, null);
}
} }
manager = null; manager = null;
} }

View File

@ -31,7 +31,7 @@ public class ChromeSafariBrowserManager extends ChannelDelegateImpl {
public InAppWebViewFlutterPlugin plugin; public InAppWebViewFlutterPlugin plugin;
public String id; public String id;
public static final Map<String, ChromeSafariBrowserManager> shared = new HashMap<>(); public static final Map<String, ChromeSafariBrowserManager> shared = new HashMap<>();
public static final Map<String, ChromeCustomTabsActivity> browsers = new HashMap<>(); public final Map<String, ChromeCustomTabsActivity> browsers = new HashMap<>();
public ChromeSafariBrowserManager(final InAppWebViewFlutterPlugin plugin) { public ChromeSafariBrowserManager(final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME)); super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));

View File

@ -1,5 +1,6 @@
package com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview; package com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview;
import android.app.Activity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -130,17 +131,21 @@ public class HeadlessInAppWebView implements Disposable {
channelDelegate.dispose(); channelDelegate.dispose();
channelDelegate = null; channelDelegate = null;
} }
if (HeadlessInAppWebViewManager.webViews.containsKey(id)) { if (plugin != null) {
HeadlessInAppWebViewManager.webViews.put(id, null); HeadlessInAppWebViewManager headlessInAppWebViewManager = plugin.headlessInAppWebViewManager;
} if (headlessInAppWebViewManager != null && headlessInAppWebViewManager.webViews.containsKey(id)) {
if (plugin != null && plugin.activity != null) { headlessInAppWebViewManager.webViews.put(id, null);
ViewGroup contentView = plugin.activity.findViewById(android.R.id.content); }
if (contentView != null) { Activity activity = plugin.activity;
ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); if (activity != null) {
if (mainView != null && flutterWebView != null) { ViewGroup contentView = plugin.activity.findViewById(android.R.id.content);
View view = flutterWebView.getView(); if (contentView != null) {
if (view != null) { ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0);
mainView.removeView(flutterWebView.getView()); if (mainView != null && flutterWebView != null) {
View view = flutterWebView.getView();
if (view != null) {
mainView.removeView(flutterWebView.getView());
}
} }
} }
} }

View File

@ -42,7 +42,7 @@ public class HeadlessInAppWebViewManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "HeadlessInAppWebViewManager"; protected static final String LOG_TAG = "HeadlessInAppWebViewManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"; public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview";
public static final Map<String, HeadlessInAppWebView> webViews = new HashMap<>(); public final Map<String, HeadlessInAppWebView> webViews = new HashMap<>();
@Nullable @Nullable
public InAppWebViewFlutterPlugin plugin; public InAppWebViewFlutterPlugin plugin;
@ -76,7 +76,7 @@ public class HeadlessInAppWebViewManager extends ChannelDelegateImpl {
} }
FlutterWebView flutterWebView = new FlutterWebView(plugin, context, id, params); FlutterWebView flutterWebView = new FlutterWebView(plugin, context, id, params);
HeadlessInAppWebView headlessInAppWebView = new HeadlessInAppWebView(plugin, id, flutterWebView); HeadlessInAppWebView headlessInAppWebView = new HeadlessInAppWebView(plugin, id, flutterWebView);
HeadlessInAppWebViewManager.webViews.put(id, headlessInAppWebView); webViews.put(id, headlessInAppWebView);
headlessInAppWebView.prepare(params); headlessInAppWebView.prepare(params);
headlessInAppWebView.onWebViewCreated(); headlessInAppWebView.onWebViewCreated();
@ -93,5 +93,6 @@ public class HeadlessInAppWebViewManager extends ChannelDelegateImpl {
} }
} }
webViews.clear(); webViews.clear();
plugin = null;
} }
} }

View File

@ -141,10 +141,12 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow
prepareView(); prepareView();
if (windowId != -1) { if (windowId != -1) {
Message resultMsg = InAppWebViewChromeClient.windowWebViewMessages.get(windowId); if (webView.plugin != null && webView.plugin.inAppWebViewManager != null) {
if (resultMsg != null) { Message resultMsg = webView.plugin.inAppWebViewManager.windowWebViewMessages.get(windowId);
((WebView.WebViewTransport) resultMsg.obj).setWebView(webView); if (resultMsg != null) {
resultMsg.sendToTarget(); ((WebView.WebViewTransport) resultMsg.obj).setWebView(webView);
resultMsg.sendToTarget();
}
} }
} else { } else {
String initialFile = b.getString("initialFile"); String initialFile = b.getString("initialFile");

View File

@ -21,6 +21,8 @@ public class PrintJobController implements Disposable {
@NonNull @NonNull
public String id; public String id;
@Nullable @Nullable
public InAppWebViewFlutterPlugin plugin;
@Nullable
public PrintJobChannelDelegate channelDelegate; public PrintJobChannelDelegate channelDelegate;
@Nullable @Nullable
public android.print.PrintJob job; public android.print.PrintJob job;
@ -30,6 +32,7 @@ public class PrintJobController implements Disposable {
public PrintJobController(@NonNull String id, @NonNull android.print.PrintJob job, public PrintJobController(@NonNull String id, @NonNull android.print.PrintJob job,
@Nullable PrintJobSettings settings, @NonNull InAppWebViewFlutterPlugin plugin) { @Nullable PrintJobSettings settings, @NonNull InAppWebViewFlutterPlugin plugin) {
this.id = id; this.id = id;
this.plugin = plugin;
this.job = job; this.job = job;
this.settings = settings; this.settings = settings;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id); final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
@ -61,12 +64,16 @@ public class PrintJobController implements Disposable {
channelDelegate.dispose(); channelDelegate.dispose();
channelDelegate = null; channelDelegate = null;
} }
if (PrintJobManager.jobs.containsKey(id)) { if (plugin != null) {
PrintJobManager.jobs.put(id, null); PrintJobManager printJobManager = plugin.printJobManager;
if (printJobManager != null && printJobManager.jobs.containsKey(id)) {
printJobManager.jobs.put(id, null);
}
} }
if (job != null) { if (job != null) {
job = null; job = null;
} }
plugin = null;
} }
@Override @Override
@ -75,12 +82,16 @@ public class PrintJobController implements Disposable {
channelDelegate.dispose(); channelDelegate.dispose();
channelDelegate = null; channelDelegate = null;
} }
if (PrintJobManager.jobs.containsKey(id)) { if (plugin != null) {
PrintJobManager.jobs.put(id, null); PrintJobManager printJobManager = plugin.printJobManager;
if (printJobManager != null && printJobManager.jobs.containsKey(id)) {
printJobManager.jobs.put(id, null);
}
} }
if (job != null) { if (job != null) {
job.cancel(); job.cancel();
job = null; job = null;
} }
plugin = null;
} }
} }

View File

@ -23,8 +23,11 @@ package com.pichillilorenzo.flutter_inappwebview.print_job;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable; import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import java.util.Collection; import java.util.Collection;
@ -34,11 +37,13 @@ import java.util.Map;
@RequiresApi(api = Build.VERSION_CODES.KITKAT) @RequiresApi(api = Build.VERSION_CODES.KITKAT)
public class PrintJobManager implements Disposable { public class PrintJobManager implements Disposable {
protected static final String LOG_TAG = "PrintJobManager"; protected static final String LOG_TAG = "PrintJobManager";
@Nullable
public static final Map<String, PrintJobController> jobs = new HashMap<>(); public InAppWebViewFlutterPlugin plugin;
public final Map<String, PrintJobController> jobs = new HashMap<>();
public PrintJobManager() { public PrintJobManager(@NonNull final InAppWebViewFlutterPlugin plugin) {
super(); super();
this.plugin = plugin;
} }
public void dispose() { public void dispose() {
@ -49,5 +54,6 @@ public class PrintJobManager implements Disposable {
} }
} }
jobs.clear(); jobs.clear();
plugin = null;
} }
} }

View File

@ -124,21 +124,6 @@ public class ProxyManager extends ChannelDelegateImpl {
@Override @Override
public void dispose() { public void dispose() {
super.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; plugin = null;
} }
} }

View File

@ -99,10 +99,6 @@ public class ServiceWorkerManager implements Disposable {
channelDelegate.dispose(); channelDelegate.dispose();
channelDelegate = null; channelDelegate = null;
} }
if (serviceWorkerController != null) {
serviceWorkerController.setServiceWorkerClient(dummyServiceWorkerClientCompat());
serviceWorkerController = null;
}
plugin = null; plugin = null;
} }

View File

@ -57,7 +57,6 @@ public class TracingControllerManager implements Disposable {
channelDelegate.dispose(); channelDelegate.dispose();
channelDelegate = null; channelDelegate = null;
} }
tracingController = null;
plugin = null; plugin = null;
} }
} }

View File

@ -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);
}
}

View File

@ -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<String, Object> params = (HashMap<String, Object>) 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;
}
}

View File

@ -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.Context;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
@ -13,9 +16,12 @@ import androidx.annotation.Nullable;
import androidx.webkit.WebViewCompat; import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature; 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.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.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -25,14 +31,19 @@ import java.util.Set;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
public class InAppWebViewStatic extends ChannelDelegateImpl { public class InAppWebViewManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "InAppWebViewStatic"; protected static final String LOG_TAG = "InAppWebViewManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static"; public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager";
@Nullable @Nullable
public InAppWebViewFlutterPlugin plugin; public InAppWebViewFlutterPlugin plugin;
public InAppWebViewStatic(final InAppWebViewFlutterPlugin plugin) { public final Map<String, FlutterWebView> keepAliveWebViews = new HashMap<>();
public final Map<Integer, Message> windowWebViewMessages = new HashMap<>();
public int windowAutoincrementId = 0;
public InAppWebViewManager(final InAppWebViewFlutterPlugin plugin) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME)); super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));
this.plugin = plugin; this.plugin = plugin;
} }
@ -41,7 +52,11 @@ public class InAppWebViewStatic extends ChannelDelegateImpl {
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) { public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) { switch (call.method) {
case "getDefaultUserAgent": case "getDefaultUserAgent":
result.success(WebSettings.getDefaultUserAgent(plugin.applicationContext)); if (plugin != null) {
result.success(WebSettings.getDefaultUserAgent(plugin.applicationContext));
} else {
result.success(null);
}
break; break;
case "clearClientCertPreferences": case "clearClientCertPreferences":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@ -118,6 +133,13 @@ public class InAppWebViewStatic extends ChannelDelegateImpl {
result.success(false); result.success(false);
} }
break; break;
case "disposeKeepAlive":
final String keepAliveId = (String) call.argument("keepAliveId");
if (keepAliveId != null) {
disposeKeepAlive(keepAliveId);
}
result.success(true);
break;
default: default:
result.notImplemented(); result.notImplemented();
} }
@ -133,9 +155,37 @@ public class InAppWebViewStatic extends ChannelDelegateImpl {
return webViewPackageInfoMap; 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 @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
Collection<FlutterWebView> flutterWebViews = keepAliveWebViews.values();
for (FlutterWebView flutterWebView : flutterWebViews) {
String keepAliveId = flutterWebView.keepAliveId;
if (keepAliveId != null) {
disposeKeepAlive(flutterWebView.keepAliveId);
}
}
keepAliveWebViews.clear();
windowWebViewMessages.clear();
plugin = null; plugin = null;
} }
} }

View File

@ -76,9 +76,11 @@ public class JavaScriptBridgeInterface {
@Override @Override
public void defaultBehaviour(@Nullable Boolean handledByClient) { public void defaultBehaviour(@Nullable Boolean handledByClient) {
PrintJobController printJobController = PrintJobManager.jobs.get(printJobId); if (inAppWebView != null && inAppWebView.plugin != null && inAppWebView.plugin.printJobManager != null) {
if (printJobController != null) { PrintJobController printJobController = inAppWebView.plugin.printJobManager.jobs.get(printJobId);
printJobController.disposeNoCancel(); if (printJobController != null) {
printJobController.disposeNoCancel();
}
} }
} }

View File

@ -42,12 +42,16 @@ public class FlutterWebView implements PlatformWebView {
public InAppWebView webView; public InAppWebView webView;
@Nullable @Nullable
public PullToRefreshLayout pullToRefreshLayout; public PullToRefreshLayout pullToRefreshLayout;
@Nullable
public String keepAliveId;
public FlutterWebView(final InAppWebViewFlutterPlugin plugin, final Context context, Object id, public FlutterWebView(final InAppWebViewFlutterPlugin plugin, final Context context, Object id,
HashMap<String, Object> params) { HashMap<String, Object> params) {
DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy();
DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
displayListenerProxy.onPreWebViewInitialization(displayManager); displayListenerProxy.onPreWebViewInitialization(displayManager);
keepAliveId = (String) params.get("keepAliveId");
Map<String, Object> initialSettings = (Map<String, Object>) params.get("initialSettings"); Map<String, Object> initialSettings = (Map<String, Object>) params.get("initialSettings");
Map<String, Object> contextMenu = (Map<String, Object>) params.get("contextMenu"); Map<String, Object> contextMenu = (Map<String, Object>) params.get("contextMenu");
@ -101,26 +105,28 @@ public class FlutterWebView implements PlatformWebView {
final Map<String, String> initialData = (Map<String, String>) params.get("initialData"); final Map<String, String> initialData = (Map<String, String>) params.get("initialData");
if (windowId != null) { if (windowId != null) {
Message resultMsg = InAppWebViewChromeClient.windowWebViewMessages.get(windowId); if (webView.plugin != null && webView.plugin.inAppWebViewManager != null) {
if (resultMsg != null) { Message resultMsg = webView.plugin.inAppWebViewManager.windowWebViewMessages.get(windowId);
((WebView.WebViewTransport) resultMsg.obj).setWebView(webView); if (resultMsg != null) {
resultMsg.sendToTarget(); ((WebView.WebViewTransport) resultMsg.obj).setWebView(webView);
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { resultMsg.sendToTarget();
// for some reason, if a WebView is created using a window id, if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
// the initial plugin and user scripts injected // for some reason, if a WebView is created using a window id,
// with WebViewCompat.addDocumentStartJavaScript will not be added! // the initial plugin and user scripts injected
// https://github.com/pichillilorenzo/flutter_inappwebview/issues/1455 // 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. // Also, calling the prepareAndAddUserScripts method right after won't work,
webView.post(new Runnable() { // so use the View.post method here.
@Override webView.post(new Runnable() {
public void run() { @Override
if (webView != null) { public void run() {
webView.prepareAndAddUserScripts(); if (webView != null) {
webView.prepareAndAddUserScripts();
}
} }
} });
}); }
} }
} }
} else { } else {
@ -151,7 +157,7 @@ public class FlutterWebView implements PlatformWebView {
@Override @Override
public void dispose() { public void dispose() {
if (webView != null) { if (keepAliveId == null && webView != null) {
if (webView.channelDelegate != null) { if (webView.channelDelegate != null) {
webView.channelDelegate.dispose(); webView.channelDelegate.dispose();
} }
@ -183,7 +189,7 @@ public class FlutterWebView implements PlatformWebView {
webView.destroy(); webView.destroy();
webView = null; webView = null;
} }
if (pullToRefreshLayout != null) { if (pullToRefreshLayout != null) {
pullToRefreshLayout.dispose(); pullToRefreshLayout.dispose();
pullToRefreshLayout = null; pullToRefreshLayout = null;

View File

@ -1439,10 +1439,10 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
// Create a printCurrentPage job with name and adapter instance // Create a printCurrentPage job with name and adapter instance
android.print.PrintJob job = printManager.print(jobName, printAdapter, builder.build()); 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(); String id = UUID.randomUUID().toString();
PrintJobController printJobController = new PrintJobController(id, job, settings, plugin); PrintJobController printJobController = new PrintJobController(id, job, settings, plugin);
PrintJobManager.jobs.put(printJobController.id, printJobController); plugin.printJobManager.jobs.put(printJobController.id, printJobController);
return id; return id;
} }
} else { } else {
@ -2011,8 +2011,8 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
webViewAssetLoaderExt.dispose(); webViewAssetLoaderExt.dispose();
webViewAssetLoaderExt = null; webViewAssetLoaderExt = null;
} }
if (windowId != null) { if (windowId != null && plugin != null && plugin.inAppWebViewManager != null) {
InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); plugin.inAppWebViewManager.windowWebViewMessages.remove(windowId);
} }
mainLooperHandler.removeCallbacksAndMessages(null); mainLooperHandler.removeCallbacksAndMessages(null);
mHandler.removeCallbacksAndMessages(null); mHandler.removeCallbacksAndMessages(null);

View File

@ -77,14 +77,10 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
protected static final String LOG_TAG = "IABWebChromeClient"; protected static final String LOG_TAG = "IABWebChromeClient";
private InAppBrowserDelegate inAppBrowserDelegate; private InAppBrowserDelegate inAppBrowserDelegate;
public static Map<Integer, Message> windowWebViewMessages = new HashMap<>();
private static int windowAutoincrementId = 0;
private static final int PICKER = 1; private static final int PICKER = 1;
private static final int PICKER_LEGACY = 3; private static final int PICKER_LEGACY = 3;
final String DEFAULT_MIME_TYPES = "*/*"; final String DEFAULT_MIME_TYPES = "*/*";
private static Uri videoOutputFileUri;
private static Uri imageOutputFileUri;
protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams( protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER); ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);
@ -115,6 +111,15 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
@Nullable @Nullable
public InAppWebView inAppWebView; public InAppWebView inAppWebView;
@Nullable
private ValueCallback<Uri> filePathCallbackLegacy;
@Nullable
private ValueCallback<Uri[]> filePathCallback;
@Nullable
private Uri videoOutputFileUri;
@Nullable
private Uri imageOutputFileUri;
public InAppWebViewChromeClient(@NonNull final InAppWebViewFlutterPlugin plugin, public InAppWebViewChromeClient(@NonNull final InAppWebViewFlutterPlugin plugin,
@NonNull InAppWebView inAppWebView, InAppBrowserDelegate inAppBrowserDelegate) { @NonNull InAppWebView inAppWebView, InAppBrowserDelegate inAppBrowserDelegate) {
super(); super();
@ -610,8 +615,11 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
@Override @Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, final Message resultMsg) { public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, final Message resultMsg) {
windowAutoincrementId++; int windowId = 0;
final int windowId = windowAutoincrementId; if (plugin != null && plugin.inAppWebViewManager != null) {
plugin.inAppWebViewManager.windowAutoincrementId++;
windowId = plugin.inAppWebViewManager.windowAutoincrementId;
}
WebView.HitTestResult result = view.getHitTestResult(); WebView.HitTestResult result = view.getHitTestResult();
String url = result.getExtra(); String url = result.getExtra();
@ -639,9 +647,12 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
isDialog isDialog
); );
windowWebViewMessages.put(windowId, resultMsg); if (plugin != null && plugin.inAppWebViewManager != null) {
plugin.inAppWebViewManager.windowWebViewMessages.put(windowId, resultMsg);
}
if (inAppWebView != null && inAppWebView.channelDelegate != null) { if (inAppWebView != null && inAppWebView.channelDelegate != null) {
int finalWindowId = windowId;
inAppWebView.channelDelegate.onCreateWindow(createWindowAction, new WebViewChannelDelegate.CreateWindowCallback() { inAppWebView.channelDelegate.onCreateWindow(createWindowAction, new WebViewChannelDelegate.CreateWindowCallback() {
@Override @Override
public boolean nonNullSuccess(@NonNull Boolean handledByClient) { public boolean nonNullSuccess(@NonNull Boolean handledByClient) {
@ -650,7 +661,9 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
@Override @Override
public void defaultBehaviour(@Nullable Boolean handledByClient) { public void defaultBehaviour(@Nullable Boolean handledByClient) {
InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); if (plugin != null && plugin.inAppWebViewManager != null) {
plugin.inAppWebViewManager.windowWebViewMessages.remove(finalWindowId);
}
} }
@Override @Override
@ -824,7 +837,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
@Override @Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) { public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (InAppWebViewFlutterPlugin.filePathCallback == null && InAppWebViewFlutterPlugin.filePathCallbackLegacy == null) { if (filePathCallback == null && filePathCallbackLegacy == null) {
return true; return true;
} }
@ -838,8 +851,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
results = getSelectedFiles(data, resultCode); results = getSelectedFiles(data, resultCode);
} }
if (InAppWebViewFlutterPlugin.filePathCallback != null) { if (filePathCallback != null) {
InAppWebViewFlutterPlugin.filePathCallback.onReceiveValue(results); filePathCallback.onReceiveValue(results);
} }
break; break;
@ -848,13 +861,14 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
result = data != null ? data.getData() : getCapturedMediaFile(); result = data != null ? data.getData() : getCapturedMediaFile();
} }
if (filePathCallbackLegacy != null) {
InAppWebViewFlutterPlugin.filePathCallbackLegacy.onReceiveValue(result); filePathCallbackLegacy.onReceiveValue(result);
}
break; break;
} }
InAppWebViewFlutterPlugin.filePathCallback = null; filePathCallback = null;
InAppWebViewFlutterPlugin.filePathCallbackLegacy = null; filePathCallbackLegacy = null;
imageOutputFileUri = null; imageOutputFileUri = null;
videoOutputFileUri = null; videoOutputFileUri = null;
@ -921,7 +935,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
} }
public void startPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType, @Nullable String capture) { public void startPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType, @Nullable String capture) {
InAppWebViewFlutterPlugin.filePathCallbackLegacy = filePathCallback; filePathCallbackLegacy = filePathCallback;
boolean images = acceptsImages(acceptType); boolean images = acceptsImages(acceptType);
boolean video = acceptsVideo(acceptType); boolean video = acceptsVideo(acceptType);
@ -965,7 +979,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean startPickerIntent(final ValueCallback<Uri[]> callback, final String[] acceptTypes, public boolean startPickerIntent(final ValueCallback<Uri[]> callback, final String[] acceptTypes,
final boolean allowMultiple, final boolean captureEnabled) { final boolean allowMultiple, final boolean captureEnabled) {
InAppWebViewFlutterPlugin.filePathCallback = callback; filePathCallback = callback;
boolean images = acceptsImages(acceptTypes); boolean images = acceptsImages(acceptTypes);
boolean video = acceptsVideo(acceptTypes); boolean video = acceptsVideo(acceptTypes);
@ -1289,7 +1303,11 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
inAppBrowserDelegate.getActivityResultListeners().clear(); inAppBrowserDelegate.getActivityResultListeners().clear();
inAppBrowserDelegate = null; inAppBrowserDelegate = null;
} }
plugin = null; filePathCallbackLegacy = null;
filePathCallback = null;
videoOutputFileUri = null;
imageOutputFileUri = null;
inAppWebView = null; inAppWebView = null;
plugin = null;
} }
} }

View File

@ -25,7 +25,7 @@ void customMenuItem() {
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsException);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close(); await chromeSafariBrowser.close();

View File

@ -26,7 +26,7 @@ void customTabs() {
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsException);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close(); await chromeSafariBrowser.close();
@ -45,7 +45,7 @@ void customTabs() {
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsException);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close(); await chromeSafariBrowser.close();
@ -71,7 +71,7 @@ void customTabs() {
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsException);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.updateActionButton( await chromeSafariBrowser.updateActionButton(

View File

@ -50,7 +50,7 @@ void openAndClose() {
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsException);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close(); await chromeSafariBrowser.close();

View File

@ -22,7 +22,7 @@ void sfSafariViewController() {
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsException);
expect(await chromeSafariBrowser.firstPageLoaded.future, true); expect(await chromeSafariBrowser.firstPageLoaded.future, true);
await chromeSafariBrowser.close(); await chromeSafariBrowser.close();

View File

@ -24,7 +24,7 @@ void trustedWebActivity() {
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsException);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close(); await chromeSafariBrowser.close();
@ -44,7 +44,7 @@ void trustedWebActivity() {
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1); await chromeSafariBrowser.open(url: TEST_CROSS_PLATFORM_URL_1);
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsException);
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);
await chromeSafariBrowser.close(); await chromeSafariBrowser.close();

View File

@ -19,7 +19,7 @@ void openDataAndClose() {
expect(inAppBrowser.isOpened(), false); expect(inAppBrowser.isOpened(), false);
expect(() async { expect(() async {
await inAppBrowser.show(); await inAppBrowser.show();
}, throwsA(isInstanceOf<InAppBrowserNotOpenedException>())); }, throwsException);
await inAppBrowser.openData( await inAppBrowser.openData(
data: """ data: """
@ -46,7 +46,7 @@ void openDataAndClose() {
expect(() async { expect(() async {
await inAppBrowser.openUrlRequest( await inAppBrowser.openUrlRequest(
urlRequest: URLRequest(url: TEST_URL_1)); urlRequest: URLRequest(url: TEST_URL_1));
}, throwsA(isInstanceOf<InAppBrowserAlreadyOpenedException>())); }, throwsException);
await inAppBrowser.firstPageLoaded.future; await inAppBrowser.firstPageLoaded.future;
var controller = inAppBrowser.webViewController; var controller = inAppBrowser.webViewController;

View File

@ -19,7 +19,7 @@ void openFileAndClose() {
expect(inAppBrowser.isOpened(), false); expect(inAppBrowser.isOpened(), false);
expect(() async { expect(() async {
await inAppBrowser.show(); await inAppBrowser.show();
}, throwsA(isInstanceOf<InAppBrowserNotOpenedException>())); }, throwsException);
await inAppBrowser.openFile( await inAppBrowser.openFile(
assetFilePath: "test_assets/in_app_webview_initial_file_test.html"); assetFilePath: "test_assets/in_app_webview_initial_file_test.html");
@ -28,7 +28,7 @@ void openFileAndClose() {
expect(() async { expect(() async {
await inAppBrowser.openUrlRequest( await inAppBrowser.openUrlRequest(
urlRequest: URLRequest(url: TEST_URL_1)); urlRequest: URLRequest(url: TEST_URL_1));
}, throwsA(isInstanceOf<InAppBrowserAlreadyOpenedException>())); }, throwsException);
await inAppBrowser.firstPageLoaded.future; await inAppBrowser.firstPageLoaded.future;
var controller = inAppBrowser.webViewController; var controller = inAppBrowser.webViewController;

View File

@ -19,7 +19,7 @@ void openUrlAndClose() {
expect(inAppBrowser.isOpened(), false); expect(inAppBrowser.isOpened(), false);
expect(() async { expect(() async {
await inAppBrowser.show(); await inAppBrowser.show();
}, throwsA(isInstanceOf<InAppBrowserNotOpenedException>())); }, throwsException);
await inAppBrowser.openUrlRequest(urlRequest: URLRequest(url: TEST_URL_1)); await inAppBrowser.openUrlRequest(urlRequest: URLRequest(url: TEST_URL_1));
await inAppBrowser.browserCreated.future; await inAppBrowser.browserCreated.future;
@ -27,7 +27,7 @@ void openUrlAndClose() {
expect(() async { expect(() async {
await inAppBrowser.openUrlRequest( await inAppBrowser.openUrlRequest(
urlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1)); urlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1));
}, throwsA(isInstanceOf<InAppBrowserAlreadyOpenedException>())); }, throwsException);
await inAppBrowser.firstPageLoaded.future; await inAppBrowser.firstPageLoaded.future;
var controller = inAppBrowser.webViewController; var controller = inAppBrowser.webViewController;

View File

@ -86,7 +86,7 @@ class _HeadlessInAppWebViewExampleScreenState
child: ElevatedButton( child: ElevatedButton(
onPressed: () async { onPressed: () async {
if (headlessWebView?.isRunning() ?? false) { if (headlessWebView?.isRunning() ?? false) {
await headlessWebView?.webViewController.evaluateJavascript( await headlessWebView?.webViewController?.evaluateJavascript(
source: """console.log('Here is the message!');"""); source: """console.log('Here is the message!');""");
} else { } else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(

View File

@ -126,7 +126,6 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
pullToRefreshController: pullToRefreshController, pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) async { onWebViewCreated: (controller) async {
webViewController = controller; webViewController = controller;
print(await controller.getUrl());
}, },
onLoadStart: (controller, url) async { onLoadStart: (controller, url) async {
setState(() { setState(() {

View File

@ -11,6 +11,7 @@ import Flutter
public class FindInteractionController : NSObject, Disposable { public class FindInteractionController : NSObject, Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_"; static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_";
var plugin: SwiftFlutterPlugin?
var webView: InAppWebView? var webView: InAppWebView?
var channelDelegate: FindInteractionChannelDelegate? var channelDelegate: FindInteractionChannelDelegate?
var settings: FindInteractionSettings? 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() super.init()
self.plugin = plugin
self.webView = webView self.webView = webView
self.settings = settings self.settings = settings
let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id),
self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) binaryMessenger: registrar.messenger())
self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel)
}
} }
public func prepare() { public func prepare() {
@ -148,6 +152,7 @@ public class FindInteractionController : NSObject, Disposable {
channelDelegate = nil channelDelegate = nil
webView = nil webView = nil
activeFindSession = nil activeFindSession = nil
plugin = nil
} }
deinit { deinit {

View File

@ -79,7 +79,7 @@ public class HeadlessInAppWebView : Disposable {
public func dispose() { public func dispose() {
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil
HeadlessInAppWebViewManager.webViews[id] = nil plugin?.headlessInAppWebViewManager?.webViews[id] = nil
flutterWebView = nil flutterWebView = nil
plugin = nil plugin = nil
} }

View File

@ -16,7 +16,7 @@ import AVFoundation
public class HeadlessInAppWebViewManager: ChannelDelegate { public class HeadlessInAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview" static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"
var plugin: SwiftFlutterPlugin? var plugin: SwiftFlutterPlugin?
static var webViews: [String: HeadlessInAppWebView?] = [:] var webViews: [String: HeadlessInAppWebView?] = [:]
init(plugin: SwiftFlutterPlugin) { init(plugin: SwiftFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) 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?]) { public func run(id: String, params: [String: Any?]) {
guard let plugin = plugin, let registrar = plugin.registrar else { guard let plugin = plugin else {
return return
} }
let flutterWebView = FlutterWebViewController(registrar: registrar, let flutterWebView = FlutterWebViewController(plugin: plugin,
withFrame: CGRect.zero, withFrame: CGRect.zero,
viewIdentifier: id, viewIdentifier: id,
params: params as NSDictionary) params: params as NSDictionary)
let headlessInAppWebView = HeadlessInAppWebView(plugin: plugin, id: id, flutterWebView: flutterWebView) let headlessInAppWebView = HeadlessInAppWebView(plugin: plugin, id: id, flutterWebView: flutterWebView)
HeadlessInAppWebViewManager.webViews[id] = headlessInAppWebView webViews[id] = headlessInAppWebView
headlessInAppWebView.prepare(params: params as NSDictionary) headlessInAppWebView.prepare(params: params as NSDictionary)
headlessInAppWebView.onWebViewCreated() headlessInAppWebView.onWebViewCreated()
@ -57,11 +57,11 @@ public class HeadlessInAppWebViewManager: ChannelDelegate {
public override func dispose() { public override func dispose() {
super.dispose() super.dispose()
let headlessWebViews = HeadlessInAppWebViewManager.webViews.values let headlessWebViews = webViews.values
headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in
headlessWebView?.dispose() headlessWebView?.dispose()
} }
HeadlessInAppWebViewManager.webViews.removeAll() webViews.removeAll()
plugin = nil plugin = nil
} }

View File

@ -42,7 +42,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
var isHidden = false var isHidden = false
public override func loadView() { public override func loadView() {
guard let registrar = plugin?.registrar else { guard let plugin = plugin, let registrar = plugin.registrar else {
return return
} }
@ -55,13 +55,13 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
} }
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings) 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 = webViewTransport.webView
webView!.contextMenu = contextMenu webView!.contextMenu = contextMenu
webView!.initialUserScripts = userScripts webView!.initialUserScripts = userScripts
} else { } else {
webView = InAppWebView(id: nil, webView = InAppWebView(id: nil,
registrar: nil, plugin: nil,
frame: .zero, frame: .zero,
configuration: preWebviewConfiguration, configuration: preWebviewConfiguration,
contextMenu: contextMenu, contextMenu: contextMenu,
@ -74,17 +74,18 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
webView.inAppBrowserDelegate = self webView.inAppBrowserDelegate = self
webView.id = id webView.id = id
webView.plugin = plugin
webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel) webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel)
let pullToRefreshSettings = PullToRefreshSettings() let pullToRefreshSettings = PullToRefreshSettings()
let _ = pullToRefreshSettings.parse(settings: pullToRefreshInitialSettings) 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 webView.pullToRefreshControl = pullToRefreshControl
pullToRefreshControl.delegate = webView pullToRefreshControl.delegate = webView
pullToRefreshControl.prepare() pullToRefreshControl.prepare()
let findInteractionController = FindInteractionController( let findInteractionController = FindInteractionController(
registrar: registrar, plugin: plugin,
id: id, webView: webView, settings: nil) id: id, webView: webView, settings: nil)
webView.findInteractionController = findInteractionController webView.findInteractionController = findInteractionController
findInteractionController.prepare() 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) webView?.load(webViewTransport.request)
channelDelegate?.onBrowserCreated() channelDelegate?.onBrowserCreated()
} else { } else {

View File

@ -11,13 +11,16 @@ import WebKit
public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable { public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable {
var myView: UIView? 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() super.init()
myView = UIView(frame: frame) myView = UIView(frame: frame)
myView!.clipsToBounds = true myView!.clipsToBounds = true
keepAliveId = params["keepAliveId"] as? String
let initialSettings = params["initialSettings"] as! [String: Any?] let initialSettings = params["initialSettings"] as! [String: Any?]
let contextMenu = params["contextMenu"] as? [String: Any] let contextMenu = params["contextMenu"] as? [String: Any]
let windowId = params["windowId"] as? Int64 let windowId = params["windowId"] as? Int64
@ -37,18 +40,21 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable
var webView: InAppWebView? 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 = webViewTransport.webView
webView!.id = viewId webView!.id = viewId
let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), webView!.plugin = plugin
binaryMessenger: registrar.messenger()) if let registrar = plugin.registrar {
webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) 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!.frame = myView!.bounds
webView!.contextMenu = contextMenu webView!.contextMenu = contextMenu
webView!.initialUserScripts = userScripts webView!.initialUserScripts = userScripts
} else { } else {
webView = InAppWebView(id: viewId, webView = InAppWebView(id: viewId,
registrar: registrar, plugin: plugin,
frame: myView!.bounds, frame: myView!.bounds,
configuration: preWebviewConfiguration, configuration: preWebviewConfiguration,
contextMenu: contextMenu, contextMenu: contextMenu,
@ -57,13 +63,13 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable
let pullToRefreshSettings = PullToRefreshSettings() let pullToRefreshSettings = PullToRefreshSettings()
let _ = pullToRefreshSettings.parse(settings: pullToRefreshInitialSettings) 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 webView!.pullToRefreshControl = pullToRefreshControl
pullToRefreshControl.delegate = webView! pullToRefreshControl.delegate = webView!
pullToRefreshControl.prepare() pullToRefreshControl.prepare()
let findInteractionController = FindInteractionController( let findInteractionController = FindInteractionController(
registrar: registrar, plugin: plugin,
id: viewId, webView: webView!, settings: nil) id: viewId, webView: webView!, settings: nil)
webView!.findInteractionController = findInteractionController webView!.findInteractionController = findInteractionController
findInteractionController.prepare() findInteractionController.prepare()
@ -132,7 +138,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable
} }
load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData) 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) webView.load(webViewTransport.request)
} }
} }
@ -180,10 +186,12 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable
} }
public func dispose() { public func dispose() {
if let webView = webView() { if keepAliveId == nil {
webView.dispose() if let webView = webView() {
webView.dispose()
}
myView = nil
} }
myView = nil
} }
deinit { deinit {

View File

@ -10,11 +10,11 @@ import Foundation
public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
static let VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview" 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() super.init()
self.registrar = registrar
} }
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { 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 { public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
let arguments = args as? NSDictionary let arguments = args as? NSDictionary
var flutterWebView: FlutterWebViewController?
var id: Any = viewId
if let headlessWebViewId = arguments?["headlessWebViewId"] as? String, let keepAliveId = arguments?["keepAliveId"] as? String
let headlessWebView = HeadlessInAppWebViewManager.webViews[headlessWebViewId], let headlessWebViewId = arguments?["headlessWebViewId"] as? String
if let headlessWebViewId = headlessWebViewId,
let headlessWebView = plugin.headlessInAppWebViewManager?.webViews[headlessWebViewId],
let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: frame) { let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: frame) {
return platformView flutterWebView = platformView
flutterWebView?.keepAliveId = keepAliveId
} }
let webviewController = FlutterWebViewController(registrar: registrar!, if let keepAliveId = keepAliveId,
withFrame: frame, flutterWebView == nil,
viewIdentifier: viewId, let keepAliveWebView = plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] {
params: arguments!) flutterWebView = keepAliveWebView
webviewController.makeInitialLoad(params: arguments!) if let view = flutterWebView?.view() {
return webviewController // 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!
} }
} }

View File

@ -17,7 +17,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_" static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_"
var id: Any? // viewId var id: Any? // viewId
var registrar: FlutterPluginRegistrar? var plugin: SwiftFlutterPlugin?
var windowId: Int64? var windowId: Int64?
var windowCreated = false var windowCreated = false
var inAppBrowserDelegate: InAppBrowserDelegate? var inAppBrowserDelegate: InAppBrowserDelegate?
@ -61,21 +61,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
var customIMPs: [IMP] = [] var customIMPs: [IMP] = []
static var windowWebViews: [Int64:WebViewTransport] = [:]
static var windowAutoincrementId: Int64 = 0;
var callAsyncJavaScriptBelowIOS14Results: [String:((Any?) -> Void)] = [:] var callAsyncJavaScriptBelowIOS14Results: [String:((Any?) -> Void)] = [:]
var oldZoomScale = Float(1.0) 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] = []) { contextMenu: [String: Any]?, userScripts: [UserScript] = []) {
super.init(frame: frame, configuration: configuration) super.init(frame: frame, configuration: configuration)
self.id = id self.id = id
self.registrar = registrar self.plugin = plugin
if let id = id, let registrar = registrar { if let id = id, let registrar = plugin?.registrar {
let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), 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.channelDelegate = WebViewChannelDelegate(webView: self, channel: channel)
} }
self.contextMenu = contextMenu self.contextMenu = contextMenu
@ -940,8 +937,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
} }
public func loadFile(assetFilePath: String) throws { public func loadFile(assetFilePath: String) throws {
if let registrar = registrar { if let plugin = plugin {
let assetURL = try Util.getUrlAsset(registrar: registrar, assetFilePath: assetFilePath) let assetURL = try Util.getUrlAsset(plugin: plugin, assetFilePath: assetFilePath)
let urlRequest = URLRequest(url: assetURL) let urlRequest = URLRequest(url: assetURL)
loadUrl(urlRequest: urlRequest, allowingReadAccessTo: nil) loadUrl(urlRequest: urlRequest, allowingReadAccessTo: nil)
} }
@ -2090,8 +2087,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
var path: String = certificatePath var path: String = certificatePath
do { do {
if let registrar = self.registrar { if let plugin = self.plugin {
path = try Util.getAbsPathAsset(registrar: registrar, assetFilePath: certificatePath) path = try Util.getAbsPathAsset(plugin: plugin, assetFilePath: certificatePath)
} }
} catch {} } catch {}
@ -2464,10 +2461,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
createWebViewWith configuration: WKWebViewConfiguration, createWebViewWith configuration: WKWebViewConfiguration,
for navigationAction: WKNavigationAction, for navigationAction: WKNavigationAction,
windowFeatures: WKWindowFeatures) -> WKWebView? { windowFeatures: WKWindowFeatures) -> WKWebView? {
InAppWebView.windowAutoincrementId += 1 var windowId: Int64 = 0
let windowId = InAppWebView.windowAutoincrementId 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 windowWebView.windowId = windowId
let webViewTransport = WebViewTransport( let webViewTransport = WebViewTransport(
@ -2475,7 +2476,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
request: navigationAction.request request: navigationAction.request
) )
InAppWebView.windowWebViews[windowId] = webViewTransport inAppWebViewManager?.windowWebViews[windowId] = webViewTransport
windowWebView.stopLoading() windowWebView.stopLoading()
let createWindowAction = CreateWindowAction(navigationAction: navigationAction, windowId: windowId, windowFeatures: windowFeatures, isDialog: nil) let createWindowAction = CreateWindowAction(navigationAction: navigationAction, windowId: windowId, windowFeatures: windowFeatures, isDialog: nil)
@ -2485,8 +2486,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return !handledByClient return !handledByClient
} }
callback.defaultBehaviour = { (handledByClient: Bool?) in callback.defaultBehaviour = { (handledByClient: Bool?) in
if InAppWebView.windowWebViews[windowId] != nil { if inAppWebViewManager?.windowWebViews[windowId] != nil {
InAppWebView.windowWebViews.removeValue(forKey: windowId) inAppWebViewManager?.windowWebViews.removeValue(forKey: windowId)
} }
self.loadUrl(urlRequest: navigationAction.request, allowingReadAccessTo: nil) self.loadUrl(urlRequest: navigationAction.request, allowingReadAccessTo: nil)
} }
@ -2752,7 +2753,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let _windowId = body["_windowId"] as? Int64 let _windowId = body["_windowId"] as? Int64
var webView = self 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 = webViewTransport.webView
} }
webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel)
@ -2769,7 +2770,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return !handledByClient return !handledByClient
} }
callback.defaultBehaviour = { (handledByClient: Bool?) in callback.defaultBehaviour = { (handledByClient: Bool?) in
if let printJob = PrintJobManager.jobs[printJobId] { if let printJob = self.plugin?.printJobManager?.jobs[printJobId] {
printJob?.disposeNoDismiss() printJob?.disposeNoDismiss()
} }
} }
@ -2787,7 +2788,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let _windowId = body["_windowId"] as? Int64 let _windowId = body["_windowId"] as? Int64
var webView = self 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 = webViewTransport.webView
} }
@ -2829,7 +2830,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
let _windowId = body["_windowId"] as? Int64 let _windowId = body["_windowId"] as? Int64
var webView = self 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 = webViewTransport.webView
} }
webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) 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 let animated = settings?.animated ?? true
if let id = printJobId, let registrar = registrar { if let id = printJobId, let plugin = plugin {
let printJob = PrintJobController(registrar: registrar, id: id, job: printController, settings: settings) let printJob = PrintJobController(plugin: plugin, id: id, job: printController, settings: settings)
PrintJobManager.jobs[id] = printJob plugin.printJobManager?.jobs[id] = printJob
printJob.present(animated: animated, completionHandler: completionHandler) printJob.present(animated: animated, completionHandler: completionHandler)
} else { } else {
printController.present(animated: animated, completionHandler: completionHandler) 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? { public func createWebMessageChannel(completionHandler: ((WebMessageChannel?) -> Void)? = nil) -> WebMessageChannel? {
guard let registrar = registrar else { guard let plugin = plugin else {
completionHandler?(nil) completionHandler?(nil)
return nil return nil
} }
let id = NSUUID().uuidString let id = NSUUID().uuidString
let webMessageChannel = WebMessageChannel(registrar: registrar, id: id) let webMessageChannel = WebMessageChannel(plugin: plugin, id: id)
webMessageChannel.initJsInstance(webView: self, completionHandler: completionHandler) webMessageChannel.initJsInstance(webView: self, completionHandler: completionHandler)
webMessageChannels[id] = webMessageChannel webMessageChannels[id] = webMessageChannel
@ -3216,8 +3217,8 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
configuration.userContentController.removeAllContentRuleLists() configuration.userContentController.removeAllContentRuleLists()
} }
} else if let wId = windowId, InAppWebView.windowWebViews[wId] != nil { } else if let wId = windowId, plugin?.inAppWebViewManager?.windowWebViews[wId] != nil {
InAppWebView.windowWebViews.removeValue(forKey: wId) plugin?.inAppWebViewManager?.windowWebViews.removeValue(forKey: wId)
} }
configuration.userContentController.dispose(windowId: windowId) configuration.userContentController.dispose(windowId: windowId)
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
@ -3245,7 +3246,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
SharedLastTouchPointTimestamp.removeValue(forKey: self) SharedLastTouchPointTimestamp.removeValue(forKey: self)
callAsyncJavaScriptBelowIOS14Results.removeAll() callAsyncJavaScriptBelowIOS14Results.removeAll()
super.removeFromSuperview() super.removeFromSuperview()
registrar = nil plugin = nil
} }
deinit { deinit {

View File

@ -1,5 +1,5 @@
// //
// InAppWebViewStatic.swift // InAppWebViewManager.swift
// flutter_inappwebview // flutter_inappwebview
// //
// Created by Lorenzo Pichilli on 08/12/2019. // Created by Lorenzo Pichilli on 08/12/2019.
@ -8,14 +8,18 @@
import Foundation import Foundation
import WebKit import WebKit
public class InAppWebViewStatic: ChannelDelegate { public class InAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static" static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"
var plugin: SwiftFlutterPlugin? var plugin: SwiftFlutterPlugin?
var webViewForUserAgent: WKWebView? var webViewForUserAgent: WKWebView?
var defaultUserAgent: String? var defaultUserAgent: String?
var keepAliveWebViews: [String:FlutterWebViewController?] = [:]
var windowWebViews: [Int64:WebViewTransport] = [:]
var windowAutoincrementId: Int64 = 0
init(plugin: SwiftFlutterPlugin) { 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 self.plugin = plugin
} }
@ -36,6 +40,11 @@ public class InAppWebViewStatic: ChannelDelegate {
result(false) result(false)
} }
break break
case "disposeKeepAlive":
let keepAliveId = arguments!["keepAliveId"] as! String
disposeKeepAlive(keepAliveId: keepAliveId)
result(true)
break
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
break 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() { public override func dispose() {
super.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 webViewForUserAgent = nil
defaultUserAgent = nil defaultUserAgent = nil
plugin = nil
} }
deinit { deinit {

View File

@ -10,18 +10,20 @@ import Foundation
public class WebMessageChannel : FlutterMethodCallDelegate { public class WebMessageChannel : FlutterMethodCallDelegate {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_"
var id: String var id: String
var plugin: SwiftFlutterPlugin?
var channelDelegate: WebMessageChannelChannelDelegate? var channelDelegate: WebMessageChannelChannelDelegate?
weak var webView: InAppWebView? weak var webView: InAppWebView?
var ports: [WebMessagePort] = [] var ports: [WebMessagePort] = []
var registrar: FlutterPluginRegistrar?
public init(registrar: FlutterPluginRegistrar, id: String) { public init(plugin: SwiftFlutterPlugin, id: String) {
self.id = id self.id = id
self.registrar = registrar self.plugin = plugin
super.init() super.init()
let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id,
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) binaryMessenger: registrar.messenger())
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel)
}
self.ports = [ self.ports = [
WebMessagePort(name: "port1", webMessageChannel: self), WebMessagePort(name: "port1", webMessageChannel: self),
WebMessagePort(name: "port2", webMessageChannel: self) WebMessagePort(name: "port2", webMessageChannel: self)
@ -67,7 +69,7 @@ public class WebMessageChannel : FlutterMethodCallDelegate {
})(); })();
""") """)
webView = nil webView = nil
registrar = nil plugin = nil
} }
deinit { deinit {

View File

@ -15,17 +15,19 @@ public class WebMessageListener : FlutterMethodCallDelegate {
var allowedOriginRules: Set<String> var allowedOriginRules: Set<String>
var channelDelegate: WebMessageListenerChannelDelegate? var channelDelegate: WebMessageListenerChannelDelegate?
weak var webView: InAppWebView? weak var webView: InAppWebView?
var registrar: FlutterPluginRegistrar? var plugin: SwiftFlutterPlugin?
public init(registrar: FlutterPluginRegistrar, id: String, jsObjectName: String, allowedOriginRules: Set<String>) { public init(plugin: SwiftFlutterPlugin, id: String, jsObjectName: String, allowedOriginRules: Set<String>) {
self.id = id self.id = id
self.registrar = registrar self.plugin = plugin
self.jsObjectName = jsObjectName self.jsObjectName = jsObjectName
self.allowedOriginRules = allowedOriginRules self.allowedOriginRules = allowedOriginRules
super.init() super.init()
let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName,
self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) binaryMessenger: registrar.messenger())
self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel)
}
} }
public func assertOriginRulesValid() throws { 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 { guard let map = map else {
return nil return nil
} }
return WebMessageListener( return WebMessageListener(
registrar: registrar, plugin: plugin,
id: map["id"] as! String, id: map["id"] as! String,
jsObjectName: map["jsObjectName"] as! String, jsObjectName: map["jsObjectName"] as! String,
allowedOriginRules: Set(map["allowedOriginRules"] as! [String]) allowedOriginRules: Set(map["allowedOriginRules"] as! [String])
@ -180,7 +182,7 @@ public class WebMessageListener : FlutterMethodCallDelegate {
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil
webView = nil webView = nil
registrar = nil plugin = nil
} }
deinit { deinit {

View File

@ -536,9 +536,9 @@ public class WebViewChannelDelegate : ChannelDelegate {
} }
break break
case .addWebMessageListener: 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 webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?]
let webMessageListener = WebMessageListener.fromMap(registrar: registrar, map: webMessageListenerMap)! let webMessageListener = WebMessageListener.fromMap(plugin: plugin, map: webMessageListenerMap)!
do { do {
try webView.addWebMessageListener(webMessageListener: webMessageListener) try webView.addWebMessageListener(webMessageListener: webMessageListener)
result(false) result(false)

View File

@ -18,7 +18,7 @@ public enum PrintJobState: Int {
public class PrintJobController : NSObject, Disposable, UIPrintInteractionControllerDelegate { public class PrintJobController : NSObject, Disposable, UIPrintInteractionControllerDelegate {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_" static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_"
var id: String var id: String
var registrar: FlutterPluginRegistrar? var plugin: SwiftFlutterPlugin?
var job: UIPrintInteractionController? var job: UIPrintInteractionController?
var settings: PrintJobSettings? var settings: PrintJobSettings?
var printFormatter: UIPrintFormatter? var printFormatter: UIPrintFormatter?
@ -27,18 +27,20 @@ public class PrintJobController : NSObject, Disposable, UIPrintInteractionContro
var state = PrintJobState.created var state = PrintJobState.created
var creationTime = Int64(Date().timeIntervalSince1970 * 1000) 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.id = id
self.registrar = registrar self.plugin = plugin
super.init() super.init()
self.job = job self.job = job
self.settings = settings self.settings = settings
self.printFormatter = job?.printFormatter self.printFormatter = job?.printFormatter
self.printPageRenderer = job?.printPageRenderer self.printPageRenderer = job?.printPageRenderer
self.job?.delegate = self self.job?.delegate = self
let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id,
self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) binaryMessenger: registrar.messenger())
self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel)
}
} }
public func printInteractionControllerWillStartJob(_ printInteractionController: UIPrintInteractionController) { public func printInteractionControllerWillStartJob(_ printInteractionController: UIPrintInteractionController) {
@ -82,7 +84,8 @@ public class PrintJobController : NSObject, Disposable, UIPrintInteractionContro
printPageRenderer = nil printPageRenderer = nil
job?.delegate = nil job?.delegate = nil
job = nil job = nil
PrintJobManager.jobs[id] = nil plugin?.printJobManager?.jobs[id] = nil
plugin = nil
} }
public func dispose() { public func dispose() {
@ -93,7 +96,7 @@ public class PrintJobController : NSObject, Disposable, UIPrintInteractionContro
job?.delegate = nil job?.delegate = nil
job?.dismiss(animated: false) job?.dismiss(animated: false)
job = nil job = nil
PrintJobManager.jobs[id] = nil plugin?.printJobManager?.jobs[id] = nil
registrar = nil plugin = nil
} }
} }

View File

@ -8,18 +8,21 @@
import Foundation import Foundation
public class PrintJobManager: NSObject, Disposable { 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() super.init()
self.plugin = plugin
} }
public func dispose() { public func dispose() {
let jobs = PrintJobManager.jobs.values let jobValues = jobs.values
jobs.forEach { (job: PrintJobController?) in jobValues.forEach { (job: PrintJobController?) in
job?.dispose() job?.dispose()
} }
PrintJobManager.jobs.removeAll() jobs.removeAll()
plugin = nil
} }
deinit { deinit {

View File

@ -10,17 +10,21 @@ import Flutter
public class PullToRefreshControl : UIRefreshControl, Disposable { public class PullToRefreshControl : UIRefreshControl, Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_"; static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_";
var plugin: SwiftFlutterPlugin?
var channelDelegate: PullToRefreshChannelDelegate? var channelDelegate: PullToRefreshChannelDelegate?
var settings: PullToRefreshSettings? var settings: PullToRefreshSettings?
var shouldCallOnRefresh = false var shouldCallOnRefresh = false
var delegate: PullToRefreshDelegate? var delegate: PullToRefreshDelegate?
public init(registrar: FlutterPluginRegistrar, id: Any, settings: PullToRefreshSettings?) { public init(plugin: SwiftFlutterPlugin, id: Any, settings: PullToRefreshSettings?) {
super.init() super.init()
self.plugin = plugin
self.settings = settings self.settings = settings
let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id),
self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel) binaryMessenger: registrar.messenger())
self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel)
}
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -59,6 +63,7 @@ public class PullToRefreshControl : UIRefreshControl, Disposable {
channelDelegate = nil channelDelegate = nil
removeTarget(self, action: #selector(updateShouldCallOnRefresh), for: .valueChanged) removeTarget(self, action: #selector(updateShouldCallOnRefresh), for: .valueChanged)
delegate = nil delegate = nil
plugin = nil
} }
deinit { deinit {

View File

@ -15,9 +15,8 @@ import SafariServices
public class ChromeSafariBrowserManager: ChannelDelegate { public class ChromeSafariBrowserManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_chromesafaribrowser" static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_chromesafaribrowser"
var plugin: SwiftFlutterPlugin? var plugin: SwiftFlutterPlugin?
static var browsers: [String: SafariViewController?] = [:] var browsers: [String: SafariViewController?] = [:]
@available(iOS 15.0, *) var prewarmingTokens: [String: Any?] = [:]
static var prewarmingTokens: [String: SFSafariViewController.PrewarmingToken?] = [:]
init(plugin: SwiftFlutterPlugin) { init(plugin: SwiftFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: ChromeSafariBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) 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 prewarmingToken = SFSafariViewController.prewarmConnections(to: URLs)
let prewarmingTokenId = NSUUID().uuidString let prewarmingTokenId = NSUUID().uuidString
ChromeSafariBrowserManager.prewarmingTokens[prewarmingTokenId] = prewarmingToken prewarmingTokens[prewarmingTokenId] = prewarmingToken
result([ result([
"id": prewarmingTokenId "id": prewarmingTokenId
]) ])
@ -72,9 +71,9 @@ public class ChromeSafariBrowserManager: ChannelDelegate {
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
let prewarmingToken = arguments!["prewarmingToken"] as! [String:Any?] let prewarmingToken = arguments!["prewarmingToken"] as! [String:Any?]
if let prewarmingTokenId = prewarmingToken["id"] as? String, if let prewarmingTokenId = prewarmingToken["id"] as? String,
let prewarmingToken = ChromeSafariBrowserManager.prewarmingTokens[prewarmingTokenId] { let prewarmingToken = prewarmingTokens[prewarmingTokenId] as? SFSafariViewController.PrewarmingToken? {
prewarmingToken?.invalidate() prewarmingToken?.invalidate()
ChromeSafariBrowserManager.prewarmingTokens[prewarmingTokenId] = nil prewarmingTokens[prewarmingTokenId] = nil
} }
result(true) result(true)
} else { } else {
@ -115,7 +114,7 @@ public class ChromeSafariBrowserManager: ChannelDelegate {
result(true) result(true)
} }
ChromeSafariBrowserManager.browsers[id] = safari browsers[id] = safari
} }
return return
} }
@ -125,17 +124,20 @@ public class ChromeSafariBrowserManager: ChannelDelegate {
public override func dispose() { public override func dispose() {
super.dispose() super.dispose()
let browsers = ChromeSafariBrowserManager.browsers.values let browserValues = browsers.values
browsers.forEach { (browser: SafariViewController?) in browserValues.forEach { (browser: SafariViewController?) in
browser?.close(result: nil) browser?.close(result: nil)
browser?.dispose() browser?.dispose()
} }
ChromeSafariBrowserManager.browsers.removeAll() browsers.removeAll()
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
ChromeSafariBrowserManager.prewarmingTokens.values.forEach { (prewarmingToken: SFSafariViewController.PrewarmingToken?) in let prewarmingTokensValues = prewarmingTokens.values
prewarmingToken?.invalidate() prewarmingTokensValues.forEach { (prewarmingToken: Any?) in
if let prewarmingToken = prewarmingToken as? SFSafariViewController.PrewarmingToken? {
prewarmingToken?.invalidate()
}
} }
ChromeSafariBrowserManager.prewarmingTokens.removeAll() prewarmingTokens.removeAll()
} }
plugin = nil plugin = nil
} }

View File

@ -8,6 +8,7 @@
import Foundation import Foundation
class CustomUIActivity : UIActivity { class CustomUIActivity : UIActivity {
var plugin: SwiftFlutterPlugin
var viewId: String var viewId: String
var id: Int64 var id: Int64
var url: URL var url: URL
@ -16,7 +17,8 @@ class CustomUIActivity : UIActivity {
var label: String? var label: String?
var image: UIImage? 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.viewId = viewId
self.id = id self.id = id
self.url = url self.url = url
@ -47,7 +49,7 @@ class CustomUIActivity : UIActivity {
} }
override func perform() { override func perform() {
let browser = ChromeSafariBrowserManager.browsers[viewId] let browser = plugin.chromeSafariBrowserManager?.browsers[viewId]
browser??.channelDelegate?.onItemActionPerform(id: id, url: url, title: title) browser??.channelDelegate?.onItemActionPerform(id: id, url: url, title: title)
} }
} }

View File

@ -105,9 +105,14 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
} }
public func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] { public func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] {
guard let plugin = plugin else {
return []
}
var uiActivities: [UIActivity] = [] var uiActivities: [UIActivity] = []
menuItemList.forEach { (menuItem) in 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) uiActivities.append(activity)
} }
return uiActivities return uiActivities
@ -125,7 +130,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil
delegate = nil delegate = nil
ChromeSafariBrowserManager.browsers[id] = nil plugin?.chromeSafariBrowserManager?.browsers[id] = nil
plugin = nil plugin = nil
} }

View File

@ -26,7 +26,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
var registrar: FlutterPluginRegistrar? var registrar: FlutterPluginRegistrar?
var platformUtil: PlatformUtil? var platformUtil: PlatformUtil?
var inAppWebViewStatic: InAppWebViewStatic? var inAppWebViewManager: InAppWebViewManager?
var myCookieManager: Any? var myCookieManager: Any?
var myWebStorageManager: Any? var myWebStorageManager: Any?
var credentialDatabase: CredentialDatabase? var credentialDatabase: CredentialDatabase?
@ -43,13 +43,13 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
super.init() super.init()
self.registrar = registrar 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) platformUtil = PlatformUtil(plugin: self)
inAppBrowserManager = InAppBrowserManager(plugin: self) inAppBrowserManager = InAppBrowserManager(plugin: self)
headlessInAppWebViewManager = HeadlessInAppWebViewManager(plugin: self) headlessInAppWebViewManager = HeadlessInAppWebViewManager(plugin: self)
chromeSafariBrowserManager = ChromeSafariBrowserManager(plugin: self) chromeSafariBrowserManager = ChromeSafariBrowserManager(plugin: self)
inAppWebViewStatic = InAppWebViewStatic(plugin: self) inAppWebViewManager = InAppWebViewManager(plugin: self)
credentialDatabase = CredentialDatabase(plugin: self) credentialDatabase = CredentialDatabase(plugin: self)
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
myCookieManager = MyCookieManager(plugin: self) myCookieManager = MyCookieManager(plugin: self)
@ -58,7 +58,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
myWebStorageManager = MyWebStorageManager(plugin: self) myWebStorageManager = MyWebStorageManager(plugin: self)
} }
webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self) webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self)
printJobManager = PrintJobManager() printJobManager = PrintJobManager(plugin: self)
} }
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
@ -74,8 +74,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
headlessInAppWebViewManager = nil headlessInAppWebViewManager = nil
chromeSafariBrowserManager?.dispose() chromeSafariBrowserManager?.dispose()
chromeSafariBrowserManager = nil chromeSafariBrowserManager = nil
inAppWebViewStatic?.dispose() inAppWebViewManager?.dispose()
inAppWebViewStatic = nil inAppWebViewManager = nil
credentialDatabase?.dispose() credentialDatabase?.dispose()
credentialDatabase = nil credentialDatabase = nil
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {

View File

@ -11,17 +11,17 @@ import WebKit
var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:] var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:]
public class Util { public class Util {
public static func getUrlAsset(registrar: FlutterPluginRegistrar, assetFilePath: String) throws -> URL { public static func getUrlAsset(plugin: SwiftFlutterPlugin, assetFilePath: String) throws -> URL {
let key = registrar.lookupKey(forAsset: assetFilePath) guard let key = plugin.registrar?.lookupKey(forAsset: assetFilePath),
guard let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else {
throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0)
} }
return assetURL return assetURL
} }
public static func getAbsPathAsset(registrar: FlutterPluginRegistrar, assetFilePath: String) throws -> String { public static func getAbsPathAsset(plugin: SwiftFlutterPlugin, assetFilePath: String) throws -> String {
let key = registrar.lookupKey(forAsset: assetFilePath) guard let key = plugin.registrar?.lookupKey(forAsset: assetFilePath),
guard let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else {
throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0)
} }
return assetAbsPath return assetAbsPath

View File

@ -98,7 +98,7 @@ public class WebAuthenticationSession : NSObject, ASWebAuthenticationPresentatio
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil
session = nil session = nil
WebAuthenticationSessionManager.sessions[id] = nil plugin?.webAuthenticationSessionManager?.sessions[id] = nil
plugin = nil plugin = nil
} }

View File

@ -15,7 +15,7 @@ import SafariServices
public class WebAuthenticationSessionManager: ChannelDelegate { public class WebAuthenticationSessionManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webauthenticationsession" static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webauthenticationsession"
var plugin: SwiftFlutterPlugin? var plugin: SwiftFlutterPlugin?
static var sessions: [String: WebAuthenticationSession?] = [:] var sessions: [String: WebAuthenticationSession?] = [:]
init(plugin: SwiftFlutterPlugin) { init(plugin: SwiftFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger())) 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 _ = initialSettings.parse(settings: settings)
let session = WebAuthenticationSession(plugin: plugin, id: id, url: sessionUrl, callbackURLScheme: callbackURLScheme, settings: initialSettings) let session = WebAuthenticationSession(plugin: plugin, id: id, url: sessionUrl, callbackURLScheme: callbackURLScheme, settings: initialSettings)
session.prepare() session.prepare()
WebAuthenticationSessionManager.sessions[id] = session sessions[id] = session
result(true) result(true)
return return
} }
@ -63,12 +63,12 @@ public class WebAuthenticationSessionManager: ChannelDelegate {
public override func dispose() { public override func dispose() {
super.dispose() super.dispose()
let sessions = WebAuthenticationSessionManager.sessions.values let sessionValues = sessions.values
sessions.forEach { (session: WebAuthenticationSession?) in sessionValues.forEach { (session: WebAuthenticationSession?) in
session?.cancel() session?.cancel()
session?.dispose() session?.dispose()
} }
WebAuthenticationSessionManager.sessions.removeAll() sessions.removeAll()
plugin = nil plugin = nil
} }

View File

@ -1,6 +1,6 @@
export 'service_worker_controller.dart'; export 'service_worker_controller.dart';
export 'webview_feature.dart' show WebViewFeature, AndroidWebViewFeature; export 'webview_feature.dart' show WebViewFeature, AndroidWebViewFeature;
export 'proxy_controller.dart'; export 'proxy_controller.dart' show ProxyController, ProxySettings;
export 'webview_asset_loader.dart' export 'webview_asset_loader.dart'
show show
WebViewAssetLoader, WebViewAssetLoader,

View File

@ -1,9 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; 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 'webview_feature.dart';
import '../in_app_webview/webview.dart'; import '../in_app_webview/webview.dart';
import '../types/main.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]. ///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. ///[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**: ///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - ProxyConfig](https://developer.android.com/reference/androidx/webkit/ProxyConfig)) ///- 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. ///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. ///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`. ///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). ///The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2).
List<ProxyRule> proxyRules; List<ProxyRule_> 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"`. ///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. ///**NOTE**: available only if [WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS] feature is supported.
bool reverseBypassEnabled; bool reverseBypassEnabled;
ProxySettings( ProxySettings_(
{this.bypassRules = const [], {this.bypassRules = const [],
this.directs = const [], this.directs = const [],
this.proxyRules = const [], this.proxyRules = const [],
@ -136,40 +141,40 @@ class ProxySettings {
this.removeImplicitRules, this.removeImplicitRules,
this.reverseBypassEnabled = false}); this.reverseBypassEnabled = false});
Map<String, dynamic> toMap() { // Map<String, dynamic> toMap() {
return { // return {
"bypassRules": bypassRules, // "bypassRules": bypassRules,
"directs": directs, // "directs": directs,
"proxyRules": proxyRules.map((e) => e.toMap()).toList(), // "proxyRules": proxyRules.map((e) => e.toMap()).toList(),
"bypassSimpleHostnames": bypassSimpleHostnames, // "bypassSimpleHostnames": bypassSimpleHostnames,
"removeImplicitRules": removeImplicitRules, // "removeImplicitRules": removeImplicitRules,
"reverseBypassEnabled": reverseBypassEnabled // "reverseBypassEnabled": reverseBypassEnabled
}; // };
} // }
//
static ProxySettings fromMap(Map<String, dynamic> map) { // static ProxySettings fromMap(Map<String, dynamic> map) {
var settings = ProxySettings(); // var settings = ProxySettings();
settings.bypassRules = map["bypassRules"]; // settings.bypassRules = map["bypassRules"];
settings.directs = map["directs"]; // settings.directs = map["directs"];
settings.proxyRules = (map["proxyRules"].cast<Map<String, dynamic>>() // settings.proxyRules = (map["proxyRules"].cast<Map<String, dynamic>>()
as List<Map<String, dynamic>>) // as List<Map<String, dynamic>>)
.map((e) => ProxyRule.fromMap(e)) as List<ProxyRule>; // .map((e) => ProxyRule.fromMap(e)) as List<ProxyRule>;
settings.bypassSimpleHostnames = map["bypassSimpleHostnames"]; // settings.bypassSimpleHostnames = map["bypassSimpleHostnames"];
settings.removeImplicitRules = map["removeImplicitRules"]; // settings.removeImplicitRules = map["removeImplicitRules"];
settings.reverseBypassEnabled = map["reverseBypassEnabled"]; // settings.reverseBypassEnabled = map["reverseBypassEnabled"];
return settings; // return settings;
} // }
//
Map<String, dynamic> toJson() { // Map<String, dynamic> toJson() {
return this.toMap(); // return this.toMap();
} // }
//
@override // @override
String toString() { // String toString() {
return toMap().toString(); // return 'ProxySettings{bypassRules: $bypassRules, directs: $directs, proxyRules: $proxyRules, bypassSimpleHostnames: $bypassSimpleHostnames, removeImplicitRules: $removeImplicitRules, reverseBypassEnabled: $reverseBypassEnabled}';
} // }
//
ProxySettings copy() { // ProxySettings copy() {
return ProxySettings.fromMap(this.toMap()); // return ProxySettings.fromMap(this.toMap());
} // }
} }

View File

@ -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<String> 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<String> 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<ProxyRule> 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<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = ProxySettings(
bypassSimpleHostnames: map['bypassSimpleHostnames'],
removeImplicitRules: map['removeImplicitRules'],
);
instance.bypassRules =
List<String>.from(map['bypassRules']!.cast<String>());
instance.directs = List<String>.from(map['directs']!.cast<String>());
instance.proxyRules = List<ProxyRule>.from(map['proxyRules']
.map((e) => ProxyRule.fromMap(e?.cast<String, dynamic>())!));
instance.reverseBypassEnabled = map['reverseBypassEnabled'];
return instance;
}
///Converts instance to a map.
Map<String, dynamic> 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<String, dynamic> 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}';
}
}

View File

@ -15,30 +15,6 @@ import '../debug_logging_settings.dart';
import '../web_uri.dart'; import '../web_uri.dart';
import 'chrome_safari_browser_settings.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 ///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. ///and [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
/// ///
@ -60,7 +36,7 @@ class ChromeSafariBrowser {
Map<int, ChromeSafariBrowserMenuItem> _menuItems = new HashMap(); Map<int, ChromeSafariBrowserMenuItem> _menuItems = new HashMap();
ChromeSafariBrowserSecondaryToolbar? _secondaryToolbar; ChromeSafariBrowserSecondaryToolbar? _secondaryToolbar;
bool _isOpened = false; bool _isOpened = false;
late MethodChannel _channel; MethodChannel? _channel;
static const MethodChannel _sharedChannel = static const MethodChannel _sharedChannel =
const MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser'); const MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser');
@ -68,7 +44,7 @@ class ChromeSafariBrowser {
id = IdGenerator.generate(); id = IdGenerator.generate();
this._channel = this._channel =
MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id'); MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id');
this._channel.setMethodCallHandler((call) async { this._channel?.setMethodCallHandler((call) async {
try { try {
return await _handleMethod(call); return await _handleMethod(call);
} on Error catch (e) { } on Error catch (e) {
@ -79,6 +55,19 @@ class ChromeSafariBrowser {
_isOpened = false; _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(String method, dynamic args) {
debugLog( debugLog(
className: this.runtimeType.toString(), className: this.runtimeType.toString(),
@ -125,8 +114,9 @@ class ChromeSafariBrowser {
onWillOpenInBrowser(); onWillOpenInBrowser();
break; break;
case "onClosed": case "onClosed":
_isOpened = false;
_dispose();
onClosed(); onClosed();
this._isOpened = false;
break; break;
case "onItemActionPerform": case "onItemActionPerform":
String url = call.arguments["url"]; String url = call.arguments["url"];
@ -208,6 +198,9 @@ class ChromeSafariBrowser {
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package
ChromeSafariBrowserClassOptions? options, ChromeSafariBrowserClassOptions? options,
ChromeSafariBrowserSettings? settings}) async { ChromeSafariBrowserSettings? settings}) async {
assert(!_isOpened, 'The browser is already opened.');
_isOpened = true;
if (Util.isIOS) { if (Util.isIOS) {
assert(url != null, 'The specified URL must not be null on iOS.'); assert(url != null, 'The specified URL must not be null on iOS.');
assert(['http', 'https'].contains(url!.scheme), assert(['http', 'https'].contains(url!.scheme),
@ -216,9 +209,8 @@ class ChromeSafariBrowser {
if (url != null) { if (url != null) {
assert(url.toString().isNotEmpty, 'The specified URL must not be empty.'); assert(url.toString().isNotEmpty, 'The specified URL must not be empty.');
} }
this.throwIsAlreadyOpened(message: url != null ? 'Cannot open $url!' : '');
this._isOpened = true; _init();
List<Map<String, dynamic>> menuItemList = []; List<Map<String, dynamic>> menuItemList = [];
_menuItems.forEach((key, value) { _menuItems.forEach((key, value) {
@ -269,7 +261,7 @@ class ChromeSafariBrowser {
args.putIfAbsent('otherLikelyURLs', args.putIfAbsent('otherLikelyURLs',
() => otherLikelyURLs?.map((e) => e.toString()).toList()); () => otherLikelyURLs?.map((e) => e.toString()).toList());
args.putIfAbsent('referrer', () => referrer?.toString()); 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. ///Tells the browser of a likely future navigation to a URL.
@ -290,7 +282,7 @@ class ChromeSafariBrowser {
args.putIfAbsent('url', () => url?.toString()); args.putIfAbsent('url', () => url?.toString());
args.putIfAbsent('otherLikelyURLs', args.putIfAbsent('otherLikelyURLs',
() => otherLikelyURLs?.map((e) => e.toString()).toList()); () => 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. ///Requests to validate a relationship between the application and an origin.
@ -315,7 +307,7 @@ class ChromeSafariBrowser {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('relation', () => relation.toNativeValue()); args.putIfAbsent('relation', () => relation.toNativeValue());
args.putIfAbsent('origin', () => origin.toString()); args.putIfAbsent('origin', () => origin.toString());
return await _channel.invokeMethod("validateRelationship", args); return await _channel?.invokeMethod("validateRelationship", args);
} }
///Closes the [ChromeSafariBrowser] instance. ///Closes the [ChromeSafariBrowser] instance.
@ -325,7 +317,7 @@ class ChromeSafariBrowser {
///- iOS ///- iOS
Future<void> close() async { Future<void> close() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod("close", args); await _channel?.invokeMethod("close", args);
} }
///Set a custom action button. ///Set a custom action button.
@ -349,7 +341,7 @@ class ChromeSafariBrowser {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('icon', () => icon); args.putIfAbsent('icon', () => icon);
args.putIfAbsent('description', () => description); args.putIfAbsent('description', () => description);
await _channel.invokeMethod("updateActionButton", args); await _channel?.invokeMethod("updateActionButton", args);
_actionButton?.icon = icon; _actionButton?.icon = icon;
_actionButton?.description = description; _actionButton?.description = description;
} }
@ -375,7 +367,7 @@ class ChromeSafariBrowser {
ChromeSafariBrowserSecondaryToolbar secondaryToolbar) async { ChromeSafariBrowserSecondaryToolbar secondaryToolbar) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('secondaryToolbar', () => secondaryToolbar.toMap()); args.putIfAbsent('secondaryToolbar', () => secondaryToolbar.toMap());
await _channel.invokeMethod("updateSecondaryToolbar", args); await _channel?.invokeMethod("updateSecondaryToolbar", args);
this._secondaryToolbar = secondaryToolbar; this._secondaryToolbar = secondaryToolbar;
} }
@ -546,23 +538,13 @@ class ChromeSafariBrowser {
///- Android ///- Android
///- iOS ///- iOS
bool isOpened() { bool isOpened() {
return this._isOpened; return _isOpened;
} }
void throwIsAlreadyOpened({String message = ''}) { ///Disposes the channel.
if (this.isOpened()) { void _dispose() {
throw ChromeSafariBrowserAlreadyOpenedException([ _channel?.setMethodCallHandler(null);
'Error: ${(message.isEmpty) ? '' : message + ' '}The browser is already opened.' _channel = null;
]);
}
}
void throwIsNotOpened({String message = ''}) {
if (!this.isOpened()) {
throw ChromeSafariBrowserNotOpenedException([
'Error: ${(message.isEmpty) ? '' : message + ' '}The browser is not opened.'
]);
}
} }
} }

View File

@ -275,7 +275,7 @@ class CookieManager {
await pageLoaded.future; await pageLoaded.future;
List<String> documentCookies = (await headlessWebView.webViewController List<String> documentCookies = (await headlessWebView.webViewController
.evaluateJavascript(source: 'document.cookie') as String) !.evaluateJavascript(source: 'document.cookie') as String)
.split(';') .split(';')
.map((documentCookie) => documentCookie.trim()) .map((documentCookie) => documentCookie.trim())
.toList(); .toList();

View File

@ -37,20 +37,6 @@ class FindInteractionController {
FindInteractionController({this.onFindResultReceived}) {} 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(String method, dynamic args) {
debugLog( debugLog(
className: this.runtimeType.toString(), className: this.runtimeType.toString(),
@ -217,4 +203,29 @@ class FindInteractionController {
?.cast<String, dynamic>(); ?.cast<String, dynamic>();
return FindSession.fromMap(result); 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);
}
});
}
}

View File

@ -1 +1 @@
export 'find_interaction_controller.dart'; export 'find_interaction_controller.dart' show FindInteractionController;

View File

@ -18,30 +18,7 @@ import '../print_job/main.dart';
import '../web_uri.dart'; import '../web_uri.dart';
import 'in_app_browser_settings.dart'; import 'in_app_browser_settings.dart';
import '../debug_logging_settings.dart'; import '../debug_logging_settings.dart';
import '../pull_to_refresh/pull_to_refresh_controller.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";
}
}
///This class uses the native WebView of the platform. ///This class uses the native WebView of the platform.
///The [webViewController] field can be used to access the [InAppWebViewController] API. ///The [webViewController] field can be used to access the [InAppWebViewController] API.
@ -70,11 +47,11 @@ class InAppBrowser {
final UnmodifiableListView<UserScript>? initialUserScripts; final UnmodifiableListView<UserScript>? initialUserScripts;
bool _isOpened = false; bool _isOpened = false;
late MethodChannel _channel; MethodChannel? _channel;
static const MethodChannel _sharedChannel = static const MethodChannel _sharedChannel =
const MethodChannel('com.pichillilorenzo/flutter_inappbrowser'); const MethodChannel('com.pichillilorenzo/flutter_inappbrowser');
late final InAppWebViewController _webViewController; InAppWebViewController? _webViewController;
///WebView Controller that can be used to access the [InAppWebViewController] API. ///WebView Controller that can be used to access the [InAppWebViewController] API.
///When [onExit] is fired, this will be `null` and cannot be used anymore. ///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]. ///The window id of a [CreateWindowAction.windowId].
final int? windowId; final int? windowId;
///Represents the WebView native implementation to be used.
///The default value is [WebViewImplementation.NATIVE].
final WebViewImplementation implementation;
InAppBrowser( InAppBrowser(
{this.windowId, {this.windowId,
this.initialUserScripts, this.initialUserScripts}) {
this.implementation = WebViewImplementation.NATIVE}) {
id = IdGenerator.generate(); id = IdGenerator.generate();
}
_init() {
this._channel = this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id'); MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id');
this._channel.setMethodCallHandler((call) async { this._channel?.setMethodCallHandler((call) async {
try { try {
return await _handleMethod(call); return await _handleMethod(call);
} on Error catch (e) { } on Error catch (e) {
@ -104,9 +79,10 @@ class InAppBrowser {
print(e.stackTrace); print(e.stackTrace);
} }
}); });
_isOpened = false;
_webViewController = new InAppWebViewController.fromInAppBrowser( _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) { _debugLog(String method, dynamic args) {
@ -122,18 +98,16 @@ class InAppBrowser {
switch (call.method) { switch (call.method) {
case "onBrowserCreated": case "onBrowserCreated":
_debugLog(call.method, call.arguments); _debugLog(call.method, call.arguments);
this._isOpened = true;
this.pullToRefreshController?.initMethodChannel(id);
this.findInteractionController?.initMethodChannel(id);
onBrowserCreated(); onBrowserCreated();
break; break;
case "onExit": case "onExit":
_debugLog(call.method, call.arguments); _debugLog(call.method, call.arguments);
this._isOpened = false; _isOpened = false;
_dispose();
onExit(); onExit();
break; break;
default: default:
return _webViewController.handleMethod(call); return _webViewController?.handleMethod(call);
} }
} }
@ -154,8 +128,10 @@ class InAppBrowser {
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package
@Deprecated('Use settings instead') InAppBrowserClassOptions? options, @Deprecated('Use settings instead') InAppBrowserClassOptions? options,
InAppBrowserClassSettings? settings}) async { 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); assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty);
_init();
var initialSettings = settings?.toMap() ?? var initialSettings = settings?.toMap() ??
options?.toMap() ?? options?.toMap() ??
@ -173,7 +149,6 @@ class InAppBrowser {
args.putIfAbsent('settings', () => initialSettings); args.putIfAbsent('settings', () => initialSettings);
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
args.putIfAbsent('windowId', () => windowId); args.putIfAbsent('windowId', () => windowId);
args.putIfAbsent('implementation', () => implementation.toNativeValue());
args.putIfAbsent('initialUserScripts', args.putIfAbsent('initialUserScripts',
() => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []);
args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings); args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings);
@ -227,8 +202,10 @@ class InAppBrowser {
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package
@Deprecated('Use settings instead') InAppBrowserClassOptions? options, @Deprecated('Use settings instead') InAppBrowserClassOptions? options,
InAppBrowserClassSettings? settings}) async { InAppBrowserClassSettings? settings}) async {
this.throwIfAlreadyOpened(message: 'Cannot open $assetFilePath!'); assert(!_isOpened, 'The browser is already opened.');
_isOpened = true;
assert(assetFilePath.isNotEmpty); assert(assetFilePath.isNotEmpty);
_init();
var initialSettings = settings?.toMap() ?? var initialSettings = settings?.toMap() ??
options?.toMap() ?? options?.toMap() ??
@ -246,7 +223,6 @@ class InAppBrowser {
args.putIfAbsent('settings', () => initialSettings); args.putIfAbsent('settings', () => initialSettings);
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
args.putIfAbsent('windowId', () => windowId); args.putIfAbsent('windowId', () => windowId);
args.putIfAbsent('implementation', () => implementation.toNativeValue());
args.putIfAbsent('initialUserScripts', args.putIfAbsent('initialUserScripts',
() => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []);
args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings); args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings);
@ -279,7 +255,9 @@ class InAppBrowser {
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package
@Deprecated('Use settings instead') InAppBrowserClassOptions? options, @Deprecated('Use settings instead') InAppBrowserClassOptions? options,
InAppBrowserClassSettings? settings}) async { InAppBrowserClassSettings? settings}) async {
this.throwIfAlreadyOpened(message: 'Cannot open data!'); assert(!_isOpened, 'The browser is already opened.');
_isOpened = true;
_init();
var initialSettings = settings?.toMap() ?? var initialSettings = settings?.toMap() ??
options?.toMap() ?? options?.toMap() ??
@ -302,7 +280,6 @@ class InAppBrowser {
() => (historyUrl ?? androidHistoryUrl)?.toString() ?? "about:blank"); () => (historyUrl ?? androidHistoryUrl)?.toString() ?? "about:blank");
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {}); args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
args.putIfAbsent('windowId', () => windowId); args.putIfAbsent('windowId', () => windowId);
args.putIfAbsent('implementation', () => implementation.toNativeValue());
args.putIfAbsent('initialUserScripts', args.putIfAbsent('initialUserScripts',
() => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []);
args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings); args.putIfAbsent('pullToRefreshSettings', () => pullToRefreshSettings);
@ -317,6 +294,7 @@ class InAppBrowser {
///- MacOS ///- MacOS
static Future<void> openWithSystemBrowser({required WebUri url}) async { static Future<void> openWithSystemBrowser({required WebUri url}) async {
assert(url.toString().isNotEmpty); assert(url.toString().isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString()); args.putIfAbsent('url', () => url.toString());
return await _sharedChannel.invokeMethod('openWithSystemBrowser', args); return await _sharedChannel.invokeMethod('openWithSystemBrowser', args);
@ -329,9 +307,10 @@ class InAppBrowser {
///- iOS ///- iOS
///- MacOS ///- MacOS
Future<void> show() async { Future<void> show() async {
this.throwIfNotOpened(); assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///Hides the [InAppBrowser] window. Calling this has no effect if the [InAppBrowser] was already hidden.
@ -341,9 +320,10 @@ class InAppBrowser {
///- iOS ///- iOS
///- MacOS ///- MacOS
Future<void> hide() async { Future<void> hide() async {
this.throwIfNotOpened(); assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('hide', args); await _channel?.invokeMethod('hide', args);
} }
///Closes the [InAppBrowser] window. ///Closes the [InAppBrowser] window.
@ -353,9 +333,10 @@ class InAppBrowser {
///- iOS ///- iOS
///- MacOS ///- MacOS
Future<void> close() async { Future<void> close() async {
this.throwIfNotOpened(); assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('close', args); await _channel?.invokeMethod('close', args);
} }
///Check if the Web View of the [InAppBrowser] instance is hidden. ///Check if the Web View of the [InAppBrowser] instance is hidden.
@ -365,29 +346,30 @@ class InAppBrowser {
///- iOS ///- iOS
///- MacOS ///- MacOS
Future<bool> isHidden() async { Future<bool> isHidden() async {
this.throwIfNotOpened(); assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('isHidden', args); return await _channel?.invokeMethod('isHidden', args);
} }
///Use [setSettings] instead. ///Use [setSettings] instead.
@Deprecated('Use setSettings instead') @Deprecated('Use setSettings instead')
Future<void> setOptions({required InAppBrowserClassOptions options}) async { Future<void> setOptions({required InAppBrowserClassOptions options}) async {
this.throwIfNotOpened(); assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('settings', () => options.toMap()); args.putIfAbsent('settings', () => options.toMap());
await _channel.invokeMethod('setSettings', args); await _channel?.invokeMethod('setSettings', args);
} }
///Use [getSettings] instead. ///Use [getSettings] instead.
@Deprecated('Use getSettings instead') @Deprecated('Use getSettings instead')
Future<InAppBrowserClassOptions?> getOptions() async { Future<InAppBrowserClassOptions?> getOptions() async {
this.throwIfNotOpened(); assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? options = Map<dynamic, dynamic>? options =
await _channel.invokeMethod('getSettings', args); await _channel?.invokeMethod('getSettings', args);
if (options != null) { if (options != null) {
options = options.cast<String, dynamic>(); options = options.cast<String, dynamic>();
return InAppBrowserClassOptions.fromMap(options as Map<String, dynamic>); return InAppBrowserClassOptions.fromMap(options as Map<String, dynamic>);
@ -404,11 +386,11 @@ class InAppBrowser {
///- MacOS ///- MacOS
Future<void> setSettings( Future<void> setSettings(
{required InAppBrowserClassSettings settings}) async { {required InAppBrowserClassSettings settings}) async {
this.throwIfNotOpened(); assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('settings', () => settings.toMap()); 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. ///Gets the current [InAppBrowser] settings. Returns `null` if it wasn't able to get them.
@ -418,11 +400,12 @@ class InAppBrowser {
///- iOS ///- iOS
///- MacOS ///- MacOS
Future<InAppBrowserClassSettings?> getSettings() async { Future<InAppBrowserClassSettings?> getSettings() async {
this.throwIfNotOpened(); assert(_isOpened, 'The browser is not opened.');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? settings = Map<dynamic, dynamic>? settings =
await _channel.invokeMethod('getSettings', args); await _channel?.invokeMethod('getSettings', args);
if (settings != null) { if (settings != null) {
settings = settings.cast<String, dynamic>(); settings = settings.cast<String, dynamic>();
return InAppBrowserClassSettings.fromMap( return InAppBrowserClassSettings.fromMap(
@ -1321,19 +1304,15 @@ class InAppBrowser {
///- iOS ///- iOS
void onContentSizeChanged(Size oldContentSize, Size newContentSize) {} void onContentSizeChanged(Size oldContentSize, Size newContentSize) {}
void throwIfAlreadyOpened({String message = ''}) { ///Disposes the channel and controllers.
if (this.isOpened()) { void _dispose() {
throw InAppBrowserAlreadyOpenedException([ _channel?.setMethodCallHandler(null);
'Error: ${(message.isEmpty) ? '' : message + ' '}The browser is already opened.' _channel = null;
]); _webViewController?.dispose();
} _webViewController = null;
} pullToRefreshController?.dispose();
pullToRefreshController = null;
void throwIfNotOpened({String message = ''}) { findInteractionController?.dispose();
if (!this.isOpened()) { findInteractionController = null;
throw InAppBrowserNotOpenedException([
'Error: ${(message.isEmpty) ? '' : message + ' '}The browser is not opened.'
]);
}
} }
} }

View File

@ -1,4 +1,4 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
const IN_APP_WEBVIEW_STATIC_CHANNEL = const IN_APP_WEBVIEW_STATIC_CHANNEL =
const MethodChannel('com.pichillilorenzo/flutter_inappwebview_static'); const MethodChannel('com.pichillilorenzo/flutter_inappwebview_manager');

View File

@ -8,7 +8,7 @@ import '../in_app_webview_controller.dart';
///Use [InAppWebViewController] instead. ///Use [InAppWebViewController] instead.
@Deprecated("Use InAppWebViewController instead") @Deprecated("Use InAppWebViewController instead")
class AndroidInAppWebViewController { class AndroidInAppWebViewController {
late MethodChannel _channel; MethodChannel? _channel;
AndroidInAppWebViewController({required MethodChannel channel}) { AndroidInAppWebViewController({required MethodChannel channel}) {
this._channel = channel; this._channel = channel;
@ -18,28 +18,28 @@ class AndroidInAppWebViewController {
@Deprecated("Use InAppWebViewController.startSafeBrowsing instead") @Deprecated("Use InAppWebViewController.startSafeBrowsing instead")
Future<bool> startSafeBrowsing() async { Future<bool> startSafeBrowsing() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('startSafeBrowsing', args); return await _channel?.invokeMethod('startSafeBrowsing', args);
} }
///Use [InAppWebViewController.clearSslPreferences] instead. ///Use [InAppWebViewController.clearSslPreferences] instead.
@Deprecated("Use InAppWebViewController.clearSslPreferences instead") @Deprecated("Use InAppWebViewController.clearSslPreferences instead")
Future<void> clearSslPreferences() async { Future<void> clearSslPreferences() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearSslPreferences', args); await _channel?.invokeMethod('clearSslPreferences', args);
} }
///Use [InAppWebViewController.pause] instead. ///Use [InAppWebViewController.pause] instead.
@Deprecated("Use InAppWebViewController.pause instead") @Deprecated("Use InAppWebViewController.pause instead")
Future<void> pause() async { Future<void> pause() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('pause', args); await _channel?.invokeMethod('pause', args);
} }
///Use [InAppWebViewController.resume] instead. ///Use [InAppWebViewController.resume] instead.
@Deprecated("Use InAppWebViewController.resume instead") @Deprecated("Use InAppWebViewController.resume instead")
Future<void> resume() async { Future<void> resume() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('resume', args); await _channel?.invokeMethod('resume', args);
} }
///Use [InAppWebViewController.pageDown] instead. ///Use [InAppWebViewController.pageDown] instead.
@ -47,7 +47,7 @@ class AndroidInAppWebViewController {
Future<bool> pageDown({required bool bottom}) async { Future<bool> pageDown({required bool bottom}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("bottom", () => bottom); args.putIfAbsent("bottom", () => bottom);
return await _channel.invokeMethod('pageDown', args); return await _channel?.invokeMethod('pageDown', args);
} }
///Use [InAppWebViewController.pageUp] instead. ///Use [InAppWebViewController.pageUp] instead.
@ -55,28 +55,28 @@ class AndroidInAppWebViewController {
Future<bool> pageUp({required bool top}) async { Future<bool> pageUp({required bool top}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("top", () => top); args.putIfAbsent("top", () => top);
return await _channel.invokeMethod('pageUp', args); return await _channel?.invokeMethod('pageUp', args);
} }
///Use [InAppWebViewController.zoomIn] instead. ///Use [InAppWebViewController.zoomIn] instead.
@Deprecated("Use InAppWebViewController.zoomIn instead") @Deprecated("Use InAppWebViewController.zoomIn instead")
Future<bool> zoomIn() async { Future<bool> zoomIn() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('zoomIn', args); return await _channel?.invokeMethod('zoomIn', args);
} }
///Use [InAppWebViewController.zoomOut] instead. ///Use [InAppWebViewController.zoomOut] instead.
@Deprecated("Use InAppWebViewController.zoomOut instead") @Deprecated("Use InAppWebViewController.zoomOut instead")
Future<bool> zoomOut() async { Future<bool> zoomOut() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('zoomOut', args); return await _channel?.invokeMethod('zoomOut', args);
} }
///Use [InAppWebViewController.clearHistory] instead. ///Use [InAppWebViewController.clearHistory] instead.
@Deprecated("Use InAppWebViewController.clearHistory instead") @Deprecated("Use InAppWebViewController.clearHistory instead")
Future<void> clearHistory() async { Future<void> clearHistory() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('clearHistory', args); return await _channel?.invokeMethod('clearHistory', args);
} }
///Use [InAppWebViewController.clearClientCertPreferences] instead. ///Use [InAppWebViewController.clearClientCertPreferences] instead.
@ -120,7 +120,11 @@ class AndroidInAppWebViewController {
@Deprecated('Use InAppWebViewController.getOriginalUrl instead') @Deprecated('Use InAppWebViewController.getOriginalUrl instead')
Future<Uri?> getOriginalUrl() async { Future<Uri?> getOriginalUrl() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
String? url = await _channel.invokeMethod('getOriginalUrl', args); String? url = await _channel?.invokeMethod('getOriginalUrl', args);
return url != null ? Uri.tryParse(url) : null; return url != null ? Uri.tryParse(url) : null;
} }
void dispose() {
_channel = null;
}
} }

View File

@ -9,7 +9,8 @@ import '../in_app_webview_controller.dart';
///Use [InAppWebViewController] instead. ///Use [InAppWebViewController] instead.
@Deprecated("Use InAppWebViewController instead") @Deprecated("Use InAppWebViewController instead")
class IOSInAppWebViewController { class IOSInAppWebViewController {
late MethodChannel _channel; MethodChannel? _channel;
IOSInAppWebViewController({required MethodChannel channel}) { IOSInAppWebViewController({required MethodChannel channel}) {
this._channel = channel; this._channel = channel;
} }
@ -18,7 +19,7 @@ class IOSInAppWebViewController {
@Deprecated("Use InAppWebViewController.reloadFromOrigin instead") @Deprecated("Use InAppWebViewController.reloadFromOrigin instead")
Future<void> reloadFromOrigin() async { Future<void> reloadFromOrigin() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('reloadFromOrigin', args); await _channel?.invokeMethod('reloadFromOrigin', args);
} }
///Use [InAppWebViewController.createPdf] instead. ///Use [InAppWebViewController.createPdf] instead.
@ -31,21 +32,21 @@ class IOSInAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('pdfConfiguration', args.putIfAbsent('pdfConfiguration',
() => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap()); () => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap());
return await _channel.invokeMethod('createPdf', args); return await _channel?.invokeMethod('createPdf', args);
} }
///Use [InAppWebViewController.createWebArchiveData] instead. ///Use [InAppWebViewController.createWebArchiveData] instead.
@Deprecated("Use InAppWebViewController.createWebArchiveData instead") @Deprecated("Use InAppWebViewController.createWebArchiveData instead")
Future<Uint8List?> createWebArchiveData() async { Future<Uint8List?> createWebArchiveData() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('createWebArchiveData', args); return await _channel?.invokeMethod('createWebArchiveData', args);
} }
///Use [InAppWebViewController.hasOnlySecureContent] instead. ///Use [InAppWebViewController.hasOnlySecureContent] instead.
@Deprecated("Use InAppWebViewController.hasOnlySecureContent instead") @Deprecated("Use InAppWebViewController.hasOnlySecureContent instead")
Future<bool> hasOnlySecureContent() async { Future<bool> hasOnlySecureContent() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('hasOnlySecureContent', args); return await _channel?.invokeMethod('hasOnlySecureContent', args);
} }
///Use [InAppWebViewController.handlesURLScheme] instead. ///Use [InAppWebViewController.handlesURLScheme] instead.
@ -53,4 +54,8 @@ class IOSInAppWebViewController {
static Future<bool> handlesURLScheme(String urlScheme) async { static Future<bool> handlesURLScheme(String urlScheme) async {
return await InAppWebViewController.handlesURLScheme(urlScheme); return await InAppWebViewController.handlesURLScheme(urlScheme);
} }
void dispose() {
_channel = null;
}
} }

View File

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/src/util.dart'; import '../util.dart';
import '../context_menu.dart'; import '../context_menu.dart';
import '../find_interaction/find_interaction_controller.dart'; import '../find_interaction/find_interaction_controller.dart';
@ -38,10 +38,12 @@ class HeadlessInAppWebView implements WebView, Disposable {
static const MethodChannel _sharedChannel = static const MethodChannel _sharedChannel =
const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview'); 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. ///WebView Controller that can be used to access the [InAppWebViewController] API.
late final InAppWebViewController webViewController; InAppWebViewController? get webViewController => _webViewController;
///{@macro flutter_inappwebview.WebView.windowId} ///{@macro flutter_inappwebview.WebView.windowId}
final int? windowId; final int? windowId;
@ -74,7 +76,6 @@ class HeadlessInAppWebView implements WebView, Disposable {
this.initialUserScripts, this.initialUserScripts,
this.pullToRefreshController, this.pullToRefreshController,
this.findInteractionController, this.findInteractionController,
this.implementation = WebViewImplementation.NATIVE,
this.onWebViewCreated, this.onWebViewCreated,
this.onLoadStart, this.onLoadStart,
this.onLoadStop, this.onLoadStop,
@ -180,10 +181,15 @@ class HeadlessInAppWebView implements WebView, Disposable {
this.onMicrophoneCaptureStateChanged, this.onMicrophoneCaptureStateChanged,
this.onContentSizeChanged}) { this.onContentSizeChanged}) {
id = IdGenerator.generate(); id = IdGenerator.generate();
webViewController = new InAppWebViewController(id, this); }
_init() {
_webViewController = InAppWebViewController(id, this);
pullToRefreshController?.init(id);
findInteractionController?.init(id);
this._channel = this._channel =
MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview_$id'); MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview_$id');
this._channel.setMethodCallHandler((call) async { this._channel?.setMethodCallHandler((call) async {
try { try {
return await handleMethod(call); return await handleMethod(call);
} on Error catch (e) { } on Error catch (e) {
@ -196,10 +202,8 @@ class HeadlessInAppWebView implements WebView, Disposable {
Future<dynamic> handleMethod(MethodCall call) async { Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) { switch (call.method) {
case "onWebViewCreated": case "onWebViewCreated":
pullToRefreshController?.initMethodChannel(id); if (onWebViewCreated != null && _webViewController != null) {
findInteractionController?.initMethodChannel(id); onWebViewCreated!(_webViewController!);
if (onWebViewCreated != null) {
onWebViewCreated!(webViewController);
} }
break; break;
default: default:
@ -222,6 +226,7 @@ class HeadlessInAppWebView implements WebView, Disposable {
return; return;
} }
_started = true; _started = true;
_init();
final initialSettings = this.initialSettings ?? InAppWebViewSettings(); final initialSettings = this.initialSettings ?? InAppWebViewSettings();
_inferInitialSettings(initialSettings); _inferInitialSettings(initialSettings);
@ -249,7 +254,6 @@ class HeadlessInAppWebView implements WebView, Disposable {
'initialSettings': settingsMap, 'initialSettings': settingsMap,
'contextMenu': this.contextMenu?.toMap() ?? {}, 'contextMenu': this.contextMenu?.toMap() ?? {},
'windowId': this.windowId, 'windowId': this.windowId,
'implementation': this.implementation.toNativeValue(),
'initialUserScripts': 'initialUserScripts':
this.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], this.initialUserScripts?.map((e) => e.toMap()).toList() ?? [],
'pullToRefreshSettings': pullToRefreshSettings, 'pullToRefreshSettings': pullToRefreshSettings,
@ -306,9 +310,15 @@ class HeadlessInAppWebView implements WebView, Disposable {
return; return;
} }
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('dispose', args); await _channel?.invokeMethod('dispose', args);
_channel?.setMethodCallHandler(null);
_channel = null;
_started = false; _started = false;
_running = false; _running = false;
_webViewController?.dispose();
_webViewController = null;
pullToRefreshController?.dispose();
findInteractionController?.dispose();
} }
///Indicates if the headless WebView is running or not. ///Indicates if the headless WebView is running or not.
@ -343,7 +353,7 @@ class HeadlessInAppWebView implements WebView, Disposable {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('size', () => size.toMap()); args.putIfAbsent('size', () => size.toMap());
await _channel.invokeMethod('setSize', args); await _channel?.invokeMethod('setSize', args);
} }
///Gets the current size in pixels of the WebView. ///Gets the current size in pixels of the WebView.
@ -362,7 +372,7 @@ class HeadlessInAppWebView implements WebView, Disposable {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic> sizeMap = Map<String, dynamic> sizeMap =
(await _channel.invokeMethod('getSize', args))?.cast<String, dynamic>(); (await _channel?.invokeMethod('getSize', args))?.cast<String, dynamic>();
return MapSize.fromMap(sizeMap); return MapSize.fromMap(sizeMap);
} }
@ -403,10 +413,6 @@ class HeadlessInAppWebView implements WebView, Disposable {
@override @override
final FindInteractionController? findInteractionController; final FindInteractionController? findInteractionController;
///{@macro flutter_inappwebview.WebView.implementation}
@override
final WebViewImplementation implementation;
///Use [onGeolocationPermissionsHidePrompt] instead. ///Use [onGeolocationPermissionsHidePrompt] instead.
@override @override
@Deprecated('Use onGeolocationPermissionsHidePrompt instead') @Deprecated('Use onGeolocationPermissionsHidePrompt instead')

View File

@ -8,8 +8,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter_inappwebview/src/in_app_webview/headless_in_app_webview.dart'; import 'headless_in_app_webview.dart';
import 'package:flutter_inappwebview/src/util.dart'; import '../util.dart';
import '../find_interaction/find_interaction_controller.dart'; import '../find_interaction/find_interaction_controller.dart';
import '../web/web_platform_manager.dart'; import '../web/web_platform_manager.dart';
@ -23,6 +23,8 @@ import 'webview.dart';
import 'in_app_webview_controller.dart'; import 'in_app_webview_controller.dart';
import 'in_app_webview_settings.dart'; import 'in_app_webview_settings.dart';
import '../pull_to_refresh/main.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} ///{@template flutter_inappwebview.InAppWebView}
///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree. ///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 @override
final int? windowId; final int? windowId;
///The [HeadlessInAppWebView] to use to initialize this widget ///The [HeadlessInAppWebView] to use to initialize this widget.
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- Android native WebView ///- Android native WebView
@ -54,33 +56,46 @@ class InAppWebView extends StatefulWidget implements WebView {
///- Web ///- Web
final HeadlessInAppWebView? headlessWebView; 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} ///{@macro flutter_inappwebview.InAppWebView}
const InAppWebView({ const InAppWebView({
Key? key, Key? key,
this.windowId, this.windowId,
this.keepAlive,
this.initialUrlRequest, this.initialUrlRequest,
this.initialFile, this.initialFile,
this.initialData, this.initialData,
@Deprecated('Use initialSettings instead') this.initialOptions, @Deprecated('Use initialSettings instead')
this.initialOptions,
this.initialSettings, this.initialSettings,
this.initialUserScripts, this.initialUserScripts,
this.pullToRefreshController, this.pullToRefreshController,
this.findInteractionController, this.findInteractionController,
this.implementation = WebViewImplementation.NATIVE,
this.contextMenu, this.contextMenu,
this.onWebViewCreated, this.onWebViewCreated,
this.onLoadStart, this.onLoadStart,
this.onLoadStop, this.onLoadStop,
@Deprecated("Use onReceivedError instead") this.onLoadError, @Deprecated("Use onReceivedError instead")
this.onLoadError,
this.onReceivedError, this.onReceivedError,
@Deprecated("Use onReceivedHttpError instead") this.onLoadHttpError, @Deprecated("Use onReceivedHttpError instead")
this.onLoadHttpError,
this.onReceivedHttpError, this.onReceivedHttpError,
this.onConsoleMessage, this.onConsoleMessage,
this.onProgressChanged, this.onProgressChanged,
this.shouldOverrideUrlLoading, this.shouldOverrideUrlLoading,
this.onLoadResource, this.onLoadResource,
this.onScrollChanged, this.onScrollChanged,
@Deprecated('Use onDownloadStartRequest instead') this.onDownloadStart, @Deprecated('Use onDownloadStartRequest instead')
this.onDownloadStart,
this.onDownloadStartRequest, this.onDownloadStartRequest,
@Deprecated('Use onLoadResourceWithCustomScheme instead') @Deprecated('Use onLoadResourceWithCustomScheme instead')
this.onLoadResourceCustomScheme, this.onLoadResourceCustomScheme,
@ -100,7 +115,8 @@ class InAppWebView extends StatefulWidget implements WebView {
this.onAjaxProgress, this.onAjaxProgress,
this.shouldInterceptFetchRequest, this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory, this.onUpdateVisitedHistory,
@Deprecated("Use onPrintRequest instead") this.onPrint, @Deprecated("Use onPrintRequest instead")
this.onPrint,
this.onPrintRequest, this.onPrintRequest,
this.onLongPressHitTestResult, this.onLongPressHitTestResult,
this.onEnterFullscreen, this.onEnterFullscreen,
@ -111,7 +127,8 @@ class InAppWebView extends StatefulWidget implements WebView {
this.onWindowBlur, this.onWindowBlur,
this.onOverScrolled, this.onOverScrolled,
this.onZoomScaleChanged, this.onZoomScaleChanged,
@Deprecated('Use onSafeBrowsingHit instead') this.androidOnSafeBrowsingHit, @Deprecated('Use onSafeBrowsingHit instead')
this.androidOnSafeBrowsingHit,
this.onSafeBrowsingHit, this.onSafeBrowsingHit,
@Deprecated('Use onPermissionRequest instead') @Deprecated('Use onPermissionRequest instead')
this.androidOnPermissionRequest, this.androidOnPermissionRequest,
@ -137,13 +154,16 @@ class InAppWebView extends StatefulWidget implements WebView {
@Deprecated('Use onFormResubmission instead') @Deprecated('Use onFormResubmission instead')
this.androidOnFormResubmission, this.androidOnFormResubmission,
this.onFormResubmission, this.onFormResubmission,
@Deprecated('Use onZoomScaleChanged instead') this.androidOnScaleChanged, @Deprecated('Use onZoomScaleChanged instead')
@Deprecated('Use onReceivedIcon instead') this.androidOnReceivedIcon, this.androidOnScaleChanged,
@Deprecated('Use onReceivedIcon instead')
this.androidOnReceivedIcon,
this.onReceivedIcon, this.onReceivedIcon,
@Deprecated('Use onReceivedTouchIconUrl instead') @Deprecated('Use onReceivedTouchIconUrl instead')
this.androidOnReceivedTouchIconUrl, this.androidOnReceivedTouchIconUrl,
this.onReceivedTouchIconUrl, this.onReceivedTouchIconUrl,
@Deprecated('Use onJsBeforeUnload instead') this.androidOnJsBeforeUnload, @Deprecated('Use onJsBeforeUnload instead')
this.androidOnJsBeforeUnload,
this.onJsBeforeUnload, this.onJsBeforeUnload,
@Deprecated('Use onReceivedLoginRequest instead') @Deprecated('Use onReceivedLoginRequest instead')
this.androidOnReceivedLoginRequest, this.androidOnReceivedLoginRequest,
@ -222,10 +242,6 @@ class InAppWebView extends StatefulWidget implements WebView {
@override @override
final URLRequest? initialUrlRequest; final URLRequest? initialUrlRequest;
///{@macro flutter_inappwebview.WebView.implementation}
@override
final WebViewImplementation implementation;
///{@macro flutter_inappwebview.WebView.initialUserScripts} ///{@macro flutter_inappwebview.WebView.initialUserScripts}
@override @override
final UnmodifiableListView<UserScript>? initialUserScripts; final UnmodifiableListView<UserScript>? initialUserScripts;
@ -704,6 +720,15 @@ class _InAppWebViewState extends State<InAppWebView> {
widget.pullToRefreshController?.options.toMap() ?? widget.pullToRefreshController?.options.toMap() ??
PullToRefreshSettings(enabled: false).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) { if (Util.isWeb) {
return HtmlElementView( return HtmlElementView(
viewType: 'com.pichillilorenzo/flutter_inappwebview', viewType: 'com.pichillilorenzo/flutter_inappwebview',
@ -727,9 +752,7 @@ class _InAppWebViewState extends State<InAppWebView> {
} else if (Util.isAndroid) { } else if (Util.isAndroid) {
var useHybridComposition = (widget.initialSettings != null var useHybridComposition = (widget.initialSettings != null
? initialSettings.useHybridComposition ? initialSettings.useHybridComposition
: : widget.initialOptions?.android.useHybridComposition) ??
// ignore: deprecated_member_use_from_same_package
widget.initialOptions?.android.useHybridComposition) ??
true; true;
return PlatformViewLink( return PlatformViewLink(
@ -762,11 +785,11 @@ class _InAppWebViewState extends State<InAppWebView> {
'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false 'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false
? widget.headlessWebView?.id ? widget.headlessWebView?.id
: null, : null,
'implementation': widget.implementation.toNativeValue(),
'initialUserScripts': 'initialUserScripts':
widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? widget.initialUserScripts?.map((e) => e.toMap()).toList() ??
[], [],
'pullToRefreshSettings': pullToRefreshSettings 'pullToRefreshSettings': pullToRefreshSettings,
'keepAliveId': widget.keepAlive?.id
}, },
) )
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
@ -790,10 +813,10 @@ class _InAppWebViewState extends State<InAppWebView> {
'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false 'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false
? widget.headlessWebView?.id ? widget.headlessWebView?.id
: null, : null,
'implementation': widget.implementation.toNativeValue(),
'initialUserScripts': 'initialUserScripts':
widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [],
'pullToRefreshSettings': pullToRefreshSettings 'pullToRefreshSettings': pullToRefreshSettings,
'keepAliveId': widget.keepAlive?.id
}, },
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
); );
@ -810,13 +833,24 @@ class _InAppWebViewState extends State<InAppWebView> {
@override @override
void dispose() { void dispose() {
dynamic viewId = _controller?.getViewId(); dynamic viewId = _controller?.getViewId();
debugLog(
className: "InAppWebView",
name: "WebView",
id: viewId?.toString(),
debugLoggingSettings: WebView.debugLoggingSettings,
method: "dispose",
args: []);
if (viewId != null && if (viewId != null &&
kIsWeb && kIsWeb &&
WebPlatformManager.webViews.containsKey(viewId)) { WebPlatformManager.webViews.containsKey(viewId)) {
WebPlatformManager.webViews.remove(viewId); WebPlatformManager.webViews.remove(viewId);
} }
super.dispose(); final isKeepAlive = widget.keepAlive != null;
_controller?.dispose(isKeepAlive: isKeepAlive);
_controller = null; _controller = null;
widget.pullToRefreshController?.dispose(isKeepAlive: isKeepAlive);
widget.findInteractionController?.dispose(isKeepAlive: isKeepAlive);
super.dispose();
} }
AndroidViewController _createAndroidViewController({ AndroidViewController _createAndroidViewController({
@ -845,21 +879,25 @@ class _InAppWebViewState extends State<InAppWebView> {
} }
void _onPlatformViewCreated(int id) { void _onPlatformViewCreated(int id) {
final viewId = (!kIsWeb && (widget.headlessWebView?.isRunning() ?? false)) dynamic viewId = id;
? widget.headlessWebView?.id if (!kIsWeb) {
: id; if (widget.headlessWebView?.isRunning() ?? false) {
viewId = widget.headlessWebView?.id;
}
viewId = widget.keepAlive?.id ?? viewId ?? id;
}
widget.headlessWebView?.internalDispose(); widget.headlessWebView?.internalDispose();
_controller = InAppWebViewController(viewId, widget); _controller = InAppWebViewController(viewId, widget);
widget.pullToRefreshController?.initMethodChannel(viewId); widget.pullToRefreshController?.init(viewId);
widget.findInteractionController?.initMethodChannel(viewId); widget.findInteractionController?.init(viewId);
debugLog(
className: "InAppWebView",
name: "WebView",
id: viewId?.toString(),
debugLoggingSettings: WebView.debugLoggingSettings,
method: "onWebViewCreated",
args: []);
if (widget.onWebViewCreated != null) { if (widget.onWebViewCreated != null) {
debugLog(
className: "InAppWebView",
name: "WebView",
id: viewId?.toString(),
debugLoggingSettings: WebView.debugLoggingSettings,
method: "onWebViewCreated",
args: []);
widget.onWebViewCreated!(_controller!); widget.onWebViewCreated!(_controller!);
} }
} }

View File

@ -27,6 +27,7 @@ import 'in_app_webview.dart';
import 'in_app_webview_settings.dart'; import 'in_app_webview_settings.dart';
import 'webview.dart'; import 'webview.dart';
import '_static_channel.dart'; import '_static_channel.dart';
import 'in_app_webview_keep_alive.dart';
import '../print_job/main.dart'; import '../print_job/main.dart';
import '../find_interaction/main.dart'; import '../find_interaction/main.dart';
@ -52,17 +53,23 @@ final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView<String>([
///callback. Instead, if you are using an [InAppBrowser] instance, you can get it through the [InAppBrowser.webViewController] attribute. ///callback. Instead, if you are using an [InAppBrowser] instance, you can get it through the [InAppBrowser.webViewController] attribute.
class InAppWebViewController { class InAppWebViewController {
WebView? _webview; WebView? _webview;
late MethodChannel _channel; MethodChannel? _channel;
static MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL;
Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap =
// properties to be saved and restored for keep alive feature
Map<String, JavaScriptHandlerCallback> _javaScriptHandlersMap =
HashMap<String, JavaScriptHandlerCallback>(); HashMap<String, JavaScriptHandlerCallback>();
final Map<UserScriptInjectionTime, List<UserScript>> _userScripts = { Map<UserScriptInjectionTime, List<UserScript>> _userScripts = {
UserScriptInjectionTime.AT_DOCUMENT_START: <UserScript>[], UserScriptInjectionTime.AT_DOCUMENT_START: <UserScript>[],
UserScriptInjectionTime.AT_DOCUMENT_END: <UserScript>[] UserScriptInjectionTime.AT_DOCUMENT_END: <UserScript>[]
}; };
Set<String> _webMessageListenerObjNames = Set(); Set<String> _webMessageListenerObjNames = Set();
Map<String, ScriptHtmlTagAttributes> _injectedScriptsFromURL = {}; Map<String, ScriptHtmlTagAttributes> _injectedScriptsFromURL = {};
// static map that contains the properties to be saved and restored for keep alive feature
static final Map<InAppWebViewKeepAlive, InAppWebViewControllerKeepAliveProps?>
_keepAliveMap = {};
dynamic _id; dynamic _id;
InAppBrowser? _inAppBrowser; InAppBrowser? _inAppBrowser;
@ -82,7 +89,8 @@ class InAppWebViewController {
this._id = id; this._id = id;
this._channel = this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id');
this._channel.setMethodCallHandler((call) async { this._channel?.setMethodCallHandler((call) async {
if (_channel == null) return null;
try { try {
return await handleMethod(call); return await handleMethod(call);
} on Error catch (e) { } on Error catch (e) {
@ -136,12 +144,31 @@ class InAppWebViewController {
} }
void _init() { void _init() {
// ignore: deprecated_member_use_from_same_package android = AndroidInAppWebViewController(channel: _channel!);
this.android = AndroidInAppWebViewController(channel: _channel); ios = IOSInAppWebViewController(channel: _channel!);
// ignore: deprecated_member_use_from_same_package webStorage = WebStorage(
this.ios = IOSInAppWebViewController(channel: _channel);
this.webStorage = WebStorage(
localStorage: LocalStorage(this), sessionStorage: SessionStorage(this)); 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) { _debugLog(String method, dynamic args) {
@ -1374,10 +1401,10 @@ class InAppWebViewController {
return null; return null;
} }
if (javaScriptHandlersMap.containsKey(handlerName)) { if (_javaScriptHandlersMap.containsKey(handlerName)) {
// convert result to json // convert result to json
try { try {
return jsonEncode(await javaScriptHandlersMap[handlerName]!(args)); return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args));
} catch (error, stacktrace) { } catch (error, stacktrace) {
developer.log(error.toString() + '\n' + stacktrace.toString(), developer.log(error.toString() + '\n' + stacktrace.toString(),
name: 'JavaScript Handler "$handlerName"'); name: 'JavaScript Handler "$handlerName"');
@ -1404,7 +1431,7 @@ class InAppWebViewController {
///- Web ///- Web
Future<WebUri?> getUrl() async { Future<WebUri?> getUrl() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
String? url = await _channel.invokeMethod('getUrl', args); String? url = await _channel?.invokeMethod('getUrl', args);
return url != null ? WebUri(url) : null; return url != null ? WebUri(url) : null;
} }
@ -1419,7 +1446,7 @@ class InAppWebViewController {
///- Web ///- Web
Future<String?> getTitle() async { Future<String?> getTitle() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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)) ///- MacOS ([Official API - WKWebView.estimatedProgress](https://developer.apple.com/documentation/webkit/wkwebview/1415007-estimatedprogress))
Future<int?> getProgress() async { Future<int?> getProgress() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///Gets the content html of the page. It first tries to get the content through javascript.
@ -1693,7 +1720,7 @@ class InAppWebViewController {
() => () =>
allowingReadAccessTo?.toString() ?? allowingReadAccessTo?.toString() ??
iosAllowingReadAccessTo?.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. ///Loads the given [url] with [postData] (x-www-form-urlencoded) using `POST` method into this WebView.
@ -1717,7 +1744,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString()); args.putIfAbsent('url', () => url.toString());
args.putIfAbsent('postData', () => postData); 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. ///Loads the given [data] into this WebView, using [baseUrl] as the base URL for the content.
@ -1771,7 +1798,7 @@ class InAppWebViewController {
() => () =>
allowingReadAccessTo?.toString() ?? allowingReadAccessTo?.toString() ??
iosAllowingReadAccessTo?.toString()); iosAllowingReadAccessTo?.toString());
await _channel.invokeMethod('loadData', args); await _channel?.invokeMethod('loadData', args);
} }
///Loads the given [assetFilePath]. ///Loads the given [assetFilePath].
@ -1813,7 +1840,7 @@ class InAppWebViewController {
assert(assetFilePath.isNotEmpty); assert(assetFilePath.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('assetFilePath', () => assetFilePath); args.putIfAbsent('assetFilePath', () => assetFilePath);
await _channel.invokeMethod('loadFile', args); await _channel?.invokeMethod('loadFile', args);
} }
///Reloads the WebView. ///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)) ///- Web ([Official API - Location.reload](https://developer.mozilla.org/en-US/docs/Web/API/Location/reload))
Future<void> reload() async { Future<void> reload() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('reload', args); await _channel?.invokeMethod('reload', args);
} }
///Goes back in the history of the WebView. ///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)) ///- Web ([Official API - History.back](https://developer.mozilla.org/en-US/docs/Web/API/History/back))
Future<void> goBack() async { Future<void> goBack() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('goBack', args); await _channel?.invokeMethod('goBack', args);
} }
///Returns a boolean value indicating whether the WebView can move backward. ///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)) ///- MacOS ([Official API - WKWebView.canGoBack](https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback))
Future<bool> canGoBack() async { Future<bool> canGoBack() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('canGoBack', args); return await _channel?.invokeMethod('canGoBack', args);
} }
///Goes forward in the history of the WebView. ///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)) ///- Web ([Official API - History.forward](https://developer.mozilla.org/en-US/docs/Web/API/History/forward))
Future<void> goForward() async { Future<void> goForward() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('goForward', args); await _channel?.invokeMethod('goForward', args);
} }
///Returns a boolean value indicating whether the WebView can move forward. ///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)) ///- MacOS ([Official API - WKWebView.canGoForward](https://developer.apple.com/documentation/webkit/wkwebview/1414962-cangoforward))
Future<bool> canGoForward() async { Future<bool> canGoForward() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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<void> goBackOrForward({required int steps}) async { Future<void> goBackOrForward({required int steps}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('steps', () => steps); 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. ///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<bool> canGoBackOrForward({required int steps}) async { Future<bool> canGoBackOrForward({required int steps}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('steps', () => steps); 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. ///Navigates to a [WebHistoryItem] from the back-forward [WebHistory.list] and sets it as the current item.
@ -1932,7 +1959,7 @@ class InAppWebViewController {
///- Web ///- Web
Future<bool> isLoading() async { Future<bool> isLoading() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('isLoading', args); return await _channel?.invokeMethod('isLoading', args);
} }
///Stops the WebView from loading. ///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)) ///- Web ([Official API - Window.stop](https://developer.mozilla.org/en-US/docs/Web/API/Window/stop))
Future<void> stopLoading() async { Future<void> stopLoading() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('stopLoading', args); await _channel?.invokeMethod('stopLoading', args);
} }
///Evaluates JavaScript [source] code into the WebView and returns the result of the evaluation. ///Evaluates JavaScript [source] code into the WebView and returns the result of the evaluation.
@ -1976,7 +2003,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source); args.putIfAbsent('source', () => source);
args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); 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)) { if (data != null && (Util.isAndroid || Util.isWeb)) {
try { try {
// try to json decode the data coming from JavaScript // try to json decode the data coming from JavaScript
@ -2015,7 +2042,7 @@ class InAppWebViewController {
args.putIfAbsent('urlFile', () => urlFile.toString()); args.putIfAbsent('urlFile', () => urlFile.toString());
args.putIfAbsent( args.putIfAbsent(
'scriptHtmlTagAttributes', () => scriptHtmlTagAttributes?.toMap()); '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. ///Evaluates the content of a JavaScript file into the WebView from the flutter assets directory.
@ -2055,7 +2082,7 @@ class InAppWebViewController {
Future<void> injectCSSCode({required String source}) async { Future<void> injectCSSCode({required String source}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source); 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. ///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('urlFile', () => urlFile.toString());
args.putIfAbsent( args.putIfAbsent(
'cssLinkHtmlTagAttributes', () => cssLinkHtmlTagAttributes?.toMap()); '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. ///Injects a CSS file into the WebView from the flutter assets directory.
@ -2164,7 +2191,7 @@ class InAppWebViewController {
required JavaScriptHandlerCallback callback}) { required JavaScriptHandlerCallback callback}) {
assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName), assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName),
'"$handlerName" is a forbidden name!'); '"$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. ///Removes a JavaScript message handler previously added with the [addJavaScriptHandler] associated to [handlerName] key.
@ -2177,7 +2204,17 @@ class InAppWebViewController {
///- MacOS ///- MacOS
JavaScriptHandlerCallback? removeJavaScriptHandler( JavaScriptHandlerCallback? removeJavaScriptHandler(
{required String handlerName}) { {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. ///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<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent( args.putIfAbsent(
'screenshotConfiguration', () => screenshotConfiguration?.toMap()); 'screenshotConfiguration', () => screenshotConfiguration?.toMap());
return await _channel.invokeMethod('takeScreenshot', args); return await _channel?.invokeMethod('takeScreenshot', args);
} }
///Use [setSettings] instead. ///Use [setSettings] instead.
@ -2233,7 +2270,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('settings', () => settings.toMap()); 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. ///Gets the current WebView settings. Returns `null` if it wasn't able to get them.
@ -2247,7 +2284,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? settings = Map<dynamic, dynamic>? settings =
await _channel.invokeMethod('getSettings', args); await _channel?.invokeMethod('getSettings', args);
if (settings != null) { if (settings != null) {
settings = settings.cast<String, dynamic>(); settings = settings.cast<String, dynamic>();
return InAppWebViewSettings.fromMap(settings as Map<String, dynamic>); return InAppWebViewSettings.fromMap(settings as Map<String, dynamic>);
@ -2268,7 +2305,7 @@ class InAppWebViewController {
Future<WebHistory?> getCopyBackForwardList() async { Future<WebHistory?> getCopyBackForwardList() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? result = Map<String, dynamic>? result =
(await _channel.invokeMethod('getCopyBackForwardList', args)) (await _channel?.invokeMethod('getCopyBackForwardList', args))
?.cast<String, dynamic>(); ?.cast<String, dynamic>();
return WebHistory.fromMap(result); return WebHistory.fromMap(result);
} }
@ -2281,7 +2318,7 @@ class InAppWebViewController {
///- MacOS ///- MacOS
Future<void> clearCache() async { Future<void> clearCache() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearCache', args); await _channel?.invokeMethod('clearCache', args);
} }
///Use [FindInteractionController.findAll] instead. ///Use [FindInteractionController.findAll] instead.
@ -2289,7 +2326,7 @@ class InAppWebViewController {
Future<void> findAllAsync({required String find}) async { Future<void> findAllAsync({required String find}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('find', () => find); args.putIfAbsent('find', () => find);
await _channel.invokeMethod('findAll', args); await _channel?.invokeMethod('findAll', args);
} }
///Use [FindInteractionController.findNext] instead. ///Use [FindInteractionController.findNext] instead.
@ -2297,14 +2334,14 @@ class InAppWebViewController {
Future<void> findNext({required bool forward}) async { Future<void> findNext({required bool forward}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('forward', () => forward); args.putIfAbsent('forward', () => forward);
await _channel.invokeMethod('findNext', args); await _channel?.invokeMethod('findNext', args);
} }
///Use [FindInteractionController.clearMatches] instead. ///Use [FindInteractionController.clearMatches] instead.
@Deprecated("Use FindInteractionController.clearMatches instead") @Deprecated("Use FindInteractionController.clearMatches instead")
Future<void> clearMatches() async { Future<void> clearMatches() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearMatches', args); await _channel?.invokeMethod('clearMatches', args);
} }
///Use [tRexRunnerHtml] instead. ///Use [tRexRunnerHtml] instead.
@ -2342,7 +2379,7 @@ class InAppWebViewController {
args.putIfAbsent('x', () => x); args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y); args.putIfAbsent('y', () => y);
args.putIfAbsent('animated', () => animated); args.putIfAbsent('animated', () => animated);
await _channel.invokeMethod('scrollTo', args); await _channel?.invokeMethod('scrollTo', args);
} }
///Moves the scrolled position of the WebView. ///Moves the scrolled position of the WebView.
@ -2368,7 +2405,7 @@ class InAppWebViewController {
args.putIfAbsent('x', () => x); args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y); args.putIfAbsent('y', () => y);
args.putIfAbsent('animated', () => animated); 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. ///On Android native WebView, it pauses all layout, parsing, and JavaScript timers for all WebViews.
@ -2384,7 +2421,7 @@ class InAppWebViewController {
///- MacOS ///- MacOS
Future<void> pauseTimers() async { Future<void> pauseTimers() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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 ///- MacOS
Future<void> resumeTimers() async { Future<void> resumeTimers() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('resumeTimers', args); await _channel?.invokeMethod('resumeTimers', args);
} }
///Prints the current page. ///Prints the current page.
@ -2422,7 +2459,7 @@ class InAppWebViewController {
{PrintJobSettings? settings}) async { {PrintJobSettings? settings}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("settings", () => settings?.toMap()); args.putIfAbsent("settings", () => settings?.toMap());
String? jobId = await _channel.invokeMethod('printCurrentPage', args); String? jobId = await _channel?.invokeMethod('printCurrentPage', args);
if (jobId != null) { if (jobId != null) {
return PrintJobController(id: jobId); 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)) ///- Web ([Official API - Document.documentElement.scrollHeight](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight))
Future<int?> getContentHeight() async { Future<int?> getContentHeight() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
var height = await _channel.invokeMethod('getContentHeight', args); var height = await _channel?.invokeMethod('getContentHeight', args);
if (height == null || height == 0) { if (height == null || height == 0) {
// try to use javascript // try to use javascript
var scrollHeight = await evaluateJavascript( 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)) ///- Web ([Official API - Document.documentElement.scrollWidth](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth))
Future<int?> getContentWidth() async { Future<int?> getContentWidth() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
var height = await _channel.invokeMethod('getContentWidth', args); var height = await _channel?.invokeMethod('getContentWidth', args);
if (height == null || height == 0) { if (height == null || height == 0) {
// try to use javascript // try to use javascript
var scrollHeight = await evaluateJavascript( var scrollHeight = await evaluateJavascript(
@ -2503,7 +2540,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('zoomFactor', () => zoomFactor); args.putIfAbsent('zoomFactor', () => zoomFactor);
args.putIfAbsent('animated', () => iosAnimated ?? animated); 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. ///Gets the URL that was originally requested for the current page.
@ -2519,7 +2556,7 @@ class InAppWebViewController {
///- Web ///- Web
Future<WebUri?> getOriginalUrl() async { Future<WebUri?> getOriginalUrl() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
String? url = await _channel.invokeMethod('getOriginalUrl', args); String? url = await _channel?.invokeMethod('getOriginalUrl', args);
return url != null ? WebUri(url) : null; 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)) ///- iOS ([Official API - UIScrollView.zoomScale](https://developer.apple.com/documentation/uikit/uiscrollview/1619419-zoomscale))
Future<double?> getZoomScale() async { Future<double?> getZoomScale() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getZoomScale', args); return await _channel?.invokeMethod('getZoomScale', args);
} }
///Use [getZoomScale] instead. ///Use [getZoomScale] instead.
@ -2554,7 +2591,7 @@ class InAppWebViewController {
///- Web ///- Web
Future<String?> getSelectedText() async { Future<String?> getSelectedText() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getSelectedText', args); return await _channel?.invokeMethod('getSelectedText', args);
} }
///Gets the hit result for hitting an HTML elements. ///Gets the hit result for hitting an HTML elements.
@ -2567,7 +2604,7 @@ class InAppWebViewController {
Future<InAppWebViewHitTestResult?> getHitTestResult() async { Future<InAppWebViewHitTestResult?> getHitTestResult() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? hitTestResultMap = Map<dynamic, dynamic>? hitTestResultMap =
await _channel.invokeMethod('getHitTestResult', args); await _channel?.invokeMethod('getHitTestResult', args);
if (hitTestResultMap == null) { if (hitTestResultMap == null) {
return null; return null;
@ -2589,7 +2626,7 @@ class InAppWebViewController {
///- iOS ([Official API - UIResponder.resignFirstResponder](https://developer.apple.com/documentation/uikit/uiresponder/1621097-resignfirstresponder)) ///- iOS ([Official API - UIResponder.resignFirstResponder](https://developer.apple.com/documentation/uikit/uiresponder/1621097-resignfirstresponder))
Future<void> clearFocus() async { Future<void> clearFocus() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///Sets or updates the WebView context menu to be used next time it will appear.
@ -2600,7 +2637,7 @@ class InAppWebViewController {
Future<void> setContextMenu(ContextMenu? contextMenu) async { Future<void> setContextMenu(ContextMenu? contextMenu) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("contextMenu", () => contextMenu?.toMap()); args.putIfAbsent("contextMenu", () => contextMenu?.toMap());
await _channel.invokeMethod('setContextMenu', args); await _channel?.invokeMethod('setContextMenu', args);
_inAppBrowser?.contextMenu = contextMenu; _inAppBrowser?.contextMenu = contextMenu;
} }
@ -2614,7 +2651,7 @@ class InAppWebViewController {
Future<RequestFocusNodeHrefResult?> requestFocusNodeHref() async { Future<RequestFocusNodeHrefResult?> requestFocusNodeHref() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? result = Map<dynamic, dynamic>? result =
await _channel.invokeMethod('requestFocusNodeHref', args); await _channel?.invokeMethod('requestFocusNodeHref', args);
return result != null return result != null
? RequestFocusNodeHrefResult( ? RequestFocusNodeHrefResult(
url: result['url'] != null ? WebUri(result['url']) : null, url: result['url'] != null ? WebUri(result['url']) : null,
@ -2634,7 +2671,7 @@ class InAppWebViewController {
Future<RequestImageRefResult?> requestImageRef() async { Future<RequestImageRefResult?> requestImageRef() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? result = Map<dynamic, dynamic>? result =
await _channel.invokeMethod('requestImageRef', args); await _channel?.invokeMethod('requestImageRef', args);
return result != null return result != null
? RequestImageRefResult( ? RequestImageRefResult(
url: result['url'] != null ? WebUri(result['url']) : null, url: result['url'] != null ? WebUri(result['url']) : null,
@ -2730,7 +2767,7 @@ class InAppWebViewController {
try { try {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
themeColor = UtilColor.fromStringRepresentation( themeColor = UtilColor.fromStringRepresentation(
await _channel.invokeMethod('getMetaThemeColor', args)); await _channel?.invokeMethod('getMetaThemeColor', args));
return themeColor; return themeColor;
} catch (e) { } catch (e) {
// not implemented // not implemented
@ -2773,7 +2810,7 @@ class InAppWebViewController {
///- Web ([Official API - Window.scrollX](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX)) ///- Web ([Official API - Window.scrollX](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX))
Future<int?> getScrollX() async { Future<int?> getScrollX() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getScrollX', args); return await _channel?.invokeMethod('getScrollX', args);
} }
///Returns the scrolled top position of the current WebView. ///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)) ///- Web ([Official API - Window.scrollY](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY))
Future<int?> getScrollY() async { Future<int?> getScrollY() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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). ///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<SslCertificate?> getCertificate() async { Future<SslCertificate?> getCertificate() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? sslCertificateMap = Map<String, dynamic>? sslCertificateMap =
(await _channel.invokeMethod('getCertificate', args)) (await _channel?.invokeMethod('getCertificate', args))
?.cast<String, dynamic>(); ?.cast<String, dynamic>();
return SslCertificate.fromMap(sslCertificateMap); return SslCertificate.fromMap(sslCertificateMap);
} }
@ -2824,7 +2861,7 @@ class InAppWebViewController {
if (!(_userScripts[userScript.injectionTime]?.contains(userScript) ?? if (!(_userScripts[userScript.injectionTime]?.contains(userScript) ??
false)) { false)) {
_userScripts[userScript.injectionTime]?.add(userScript); _userScripts[userScript.injectionTime]?.add(userScript);
await _channel.invokeMethod('addUserScript', args); await _channel?.invokeMethod('addUserScript', args);
} }
} }
@ -2870,7 +2907,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('userScript', () => userScript.toMap()); args.putIfAbsent('userScript', () => userScript.toMap());
args.putIfAbsent('index', () => index); args.putIfAbsent('index', () => index);
await _channel.invokeMethod('removeUserScript', args); await _channel?.invokeMethod('removeUserScript', args);
return true; return true;
} }
@ -2907,7 +2944,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('groupName', () => groupName); args.putIfAbsent('groupName', () => groupName);
await _channel.invokeMethod('removeUserScriptsByGroupName', args); await _channel?.invokeMethod('removeUserScriptsByGroupName', args);
} }
///Removes the [userScripts] from the webpages content. ///Removes the [userScripts] from the webpages content.
@ -2947,7 +2984,18 @@ class InAppWebViewController {
_userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear(); _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///Executes the specified string as an asynchronous JavaScript function.
@ -2991,7 +3039,7 @@ class InAppWebViewController {
args.putIfAbsent('functionBody', () => functionBody); args.putIfAbsent('functionBody', () => functionBody);
args.putIfAbsent('arguments', () => arguments); args.putIfAbsent('arguments', () => arguments);
args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); args.putIfAbsent('contentWorld', () => contentWorld?.toMap());
var data = await _channel.invokeMethod('callAsyncJavaScript', args); var data = await _channel?.invokeMethod('callAsyncJavaScript', args);
if (data == null) { if (data == null) {
return null; return null;
} }
@ -3034,7 +3082,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("filePath", () => filePath); args.putIfAbsent("filePath", () => filePath);
args.putIfAbsent("autoname", () => autoname); 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). ///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)) ///- Web ([Official API - Window.isSecureContext](https://developer.mozilla.org/en-US/docs/Web/API/Window/isSecureContext))
Future<bool> isSecureContext() async { Future<bool> isSecureContext() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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<WebMessageChannel?> createWebMessageChannel() async { Future<WebMessageChannel?> createWebMessageChannel() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? result = Map<String, dynamic>? result =
(await _channel.invokeMethod('createWebMessageChannel', args)) (await _channel?.invokeMethod('createWebMessageChannel', args))
?.cast<String, dynamic>(); ?.cast<String, dynamic>();
return WebMessageChannel.fromMap(result); return WebMessageChannel.fromMap(result);
} }
@ -3102,7 +3150,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('message', () => message.toMap()); args.putIfAbsent('message', () => message.toMap());
args.putIfAbsent('targetOrigin', () => targetOrigin.toString()); 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. ///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<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('webMessageListener', () => webMessageListener.toMap()); 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`. ///Returns `true` if the webpage can scroll vertically, otherwise `false`.
@ -3294,7 +3353,7 @@ class InAppWebViewController {
///- Web ///- Web
Future<bool> canScrollVertically() async { Future<bool> canScrollVertically() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('canScrollVertically', args); return await _channel?.invokeMethod('canScrollVertically', args);
} }
///Returns `true` if the webpage can scroll horizontally, otherwise `false`. ///Returns `true` if the webpage can scroll horizontally, otherwise `false`.
@ -3310,7 +3369,7 @@ class InAppWebViewController {
///- Web ///- Web
Future<bool> canScrollHorizontally() async { Future<bool> canScrollHorizontally() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('canScrollHorizontally', args); return await _channel?.invokeMethod('canScrollHorizontally', args);
} }
///Starts Safe Browsing initialization. ///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))) ///- 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<bool> startSafeBrowsing() async { Future<bool> startSafeBrowsing() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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())) ///- Android native WebView ([Official API - WebView.clearSslPreferences](https://developer.android.com/reference/android/webkit/WebView#clearSslPreferences()))
Future<void> clearSslPreferences() async { Future<void> clearSslPreferences() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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())) ///- Android native WebView ([Official API - WebView.onPause](https://developer.android.com/reference/android/webkit/WebView#onPause()))
Future<void> pause() async { Future<void> pause() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('pause', args); await _channel?.invokeMethod('pause', args);
} }
///Resumes a WebView after a previous call to [pause]. ///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())) ///- Android native WebView ([Official API - WebView.onResume](https://developer.android.com/reference/android/webkit/WebView#onResume()))
Future<void> resume() async { Future<void> resume() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('resume', args); await _channel?.invokeMethod('resume', args);
} }
///Scrolls the contents of this WebView down by half the page size. ///Scrolls the contents of this WebView down by half the page size.
@ -3368,7 +3427,7 @@ class InAppWebViewController {
Future<bool> pageDown({required bool bottom}) async { Future<bool> pageDown({required bool bottom}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("bottom", () => bottom); 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. ///Scrolls the contents of this WebView up by half the view size.
@ -3381,7 +3440,7 @@ class InAppWebViewController {
Future<bool> pageUp({required bool top}) async { Future<bool> pageUp({required bool top}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("top", () => top); args.putIfAbsent("top", () => top);
return await _channel.invokeMethod('pageUp', args); return await _channel?.invokeMethod('pageUp', args);
} }
///Performs zoom in in this WebView. ///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())) ///- Android native WebView ([Official API - WebView.zoomIn](https://developer.android.com/reference/android/webkit/WebView#zoomIn()))
Future<bool> zoomIn() async { Future<bool> zoomIn() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('zoomIn', args); return await _channel?.invokeMethod('zoomIn', args);
} }
///Performs zoom out in this WebView. ///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())) ///- Android native WebView ([Official API - WebView.zoomOut](https://developer.android.com/reference/android/webkit/WebView#zoomOut()))
Future<bool> zoomOut() async { Future<bool> zoomOut() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('zoomOut', args); return await _channel?.invokeMethod('zoomOut', args);
} }
///Clears the internal back/forward list. ///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())) ///- Android native WebView ([Official API - WebView.clearHistory](https://developer.android.com/reference/android/webkit/WebView#clearHistory()))
Future<void> clearHistory() async { Future<void> clearHistory() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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)) ///- MacOS ([Official API - WKWebView.reloadFromOrigin](https://developer.apple.com/documentation/webkit/wkwebview/1414956-reloadfromorigin))
Future<void> reloadFromOrigin() async { Future<void> reloadFromOrigin() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('reloadFromOrigin', args); await _channel?.invokeMethod('reloadFromOrigin', args);
} }
///Generates PDF data from the web views contents asynchronously. ///Generates PDF data from the web views contents asynchronously.
@ -3443,7 +3502,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('pdfConfiguration', args.putIfAbsent('pdfConfiguration',
() => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap()); () => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap());
return await _channel.invokeMethod('createPdf', args); return await _channel?.invokeMethod('createPdf', args);
} }
///Creates a web archive of the web views current contents asynchronously. ///Creates a web archive of the web views current contents asynchronously.
@ -3458,7 +3517,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.createWebArchiveData](https://developer.apple.com/documentation/webkit/wkwebview/3650491-createwebarchivedata)) ///- MacOS ([Official API - WKWebView.createWebArchiveData](https://developer.apple.com/documentation/webkit/wkwebview/3650491-createwebarchivedata))
Future<Uint8List?> createWebArchiveData() async { Future<Uint8List?> createWebArchiveData() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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)) ///- MacOS ([Official API - WKWebView.hasOnlySecureContent](https://developer.apple.com/documentation/webkit/wkwebview/1415002-hasonlysecurecontent))
Future<bool> hasOnlySecureContent() async { Future<bool> hasOnlySecureContent() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('hasOnlySecureContent', args); return await _channel?.invokeMethod('hasOnlySecureContent', args);
} }
///Pauses playback of all media in the web view. ///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)). ///- MacOS ([Official API - WKWebView.pauseAllMediaPlayback](https://developer.apple.com/documentation/webkit/wkwebview/3752240-pauseallmediaplayback)).
Future<void> pauseAllMediaPlayback() async { Future<void> pauseAllMediaPlayback() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///Changes whether the webpage is suspending playback of all media in the page.
@ -3500,7 +3559,7 @@ class InAppWebViewController {
Future<void> setAllMediaPlaybackSuspended({required bool suspended}) async { Future<void> setAllMediaPlaybackSuspended({required bool suspended}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("suspended", () => suspended); 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. ///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)). ///- MacOS ([Official API - WKWebView.closeAllMediaPresentations](https://developer.apple.com/documentation/webkit/wkwebview/3752235-closeallmediapresentations)).
Future<void> closeAllMediaPresentations() async { Future<void> closeAllMediaPresentations() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('closeAllMediaPresentations', args); return await _channel?.invokeMethod('closeAllMediaPresentations', args);
} }
///Requests the playback status of media in the web view. ///Requests the playback status of media in the web view.
@ -3531,7 +3590,7 @@ class InAppWebViewController {
Future<MediaPlaybackState?> requestMediaPlaybackState() async { Future<MediaPlaybackState?> requestMediaPlaybackState() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return MediaPlaybackState.fromNativeValue( return MediaPlaybackState.fromNativeValue(
await _channel.invokeMethod('requestMediaPlaybackState', args)); await _channel?.invokeMethod('requestMediaPlaybackState', args));
} }
///Returns `true` if the [WebView] is in fullscreen mode, otherwise `false`. ///Returns `true` if the [WebView] is in fullscreen mode, otherwise `false`.
@ -3542,7 +3601,7 @@ class InAppWebViewController {
///- MacOS ///- MacOS
Future<bool> isInFullscreen() async { Future<bool> isInFullscreen() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///Returns a [MediaCaptureState] that indicates whether the webpage is using the camera to capture images or video.
@ -3557,7 +3616,7 @@ class InAppWebViewController {
Future<MediaCaptureState?> getCameraCaptureState() async { Future<MediaCaptureState?> getCameraCaptureState() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return MediaCaptureState.fromNativeValue( 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. ///Changes whether the webpage is using the camera to capture images or video.
@ -3572,7 +3631,7 @@ class InAppWebViewController {
Future<void> setCameraCaptureState({required MediaCaptureState state}) async { Future<void> setCameraCaptureState({required MediaCaptureState state}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('state', () => state.toNativeValue()); 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. ///Returns a [MediaCaptureState] that indicates whether the webpage is using the microphone to capture audio.
@ -3587,7 +3646,7 @@ class InAppWebViewController {
Future<MediaCaptureState?> getMicrophoneCaptureState() async { Future<MediaCaptureState?> getMicrophoneCaptureState() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return MediaCaptureState.fromNativeValue( return MediaCaptureState.fromNativeValue(
await _channel.invokeMethod('getMicrophoneCaptureState', args)); await _channel?.invokeMethod('getMicrophoneCaptureState', args));
} }
///Changes whether the webpage is using the microphone to capture audio. ///Changes whether the webpage is using the microphone to capture audio.
@ -3603,7 +3662,7 @@ class InAppWebViewController {
{required MediaCaptureState state}) async { {required MediaCaptureState state}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('state', () => state.toNativeValue()); 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. ///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('urlRequest', () => urlRequest.toMap());
args.putIfAbsent('data', () => data); args.putIfAbsent('data', () => data);
args.putIfAbsent('urlResponse', () => urlResponse?.toMap()); 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. ///Returns the iframe `id` attribute used on the Web platform.
@ -3648,7 +3707,7 @@ class InAppWebViewController {
///- Web ///- Web
Future<String?> getIFrameId() async { Future<String?> getIFrameId() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getIFrameId', args); return await _channel?.invokeMethod('getIFrameId', args);
} }
///Gets the default user agent. ///Gets the default user agent.
@ -3812,6 +3871,19 @@ class InAppWebViewController {
return await _staticChannel.invokeMethod('handlesURLScheme', args); 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<void> disposeKeepAlive(InAppWebViewKeepAlive keepAlive) async {
Map<String, dynamic> args = <String, dynamic>{};
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]. ///Gets the html (with javascript) of the Chromium's t-rex runner game. Used in combination with [tRexRunnerCss].
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
@ -3831,7 +3903,7 @@ class InAppWebViewController {
'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css'); 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css');
///Used internally. ///Used internally.
MethodChannel getChannel() { MethodChannel? getChannel() {
return _channel; return _channel;
} }
@ -3839,4 +3911,23 @@ class InAppWebViewController {
dynamic getViewId() { dynamic getViewId() {
return _id; 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();
}
}
} }

View File

@ -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<String, JavaScriptHandlerCallback> javaScriptHandlersMap;
Map<UserScriptInjectionTime, List<UserScript>> userScripts;
Set<String> webMessageListenerObjNames;
Map<String, ScriptHtmlTagAttributes> injectedScriptsFromURL;
InAppWebViewControllerKeepAliveProps(
{required this.javaScriptHandlersMap,
required this.userScripts,
required this.webMessageListenerObjNames,
required this.injectedScriptsFromURL});
}

View File

@ -11,3 +11,4 @@ export 'headless_in_app_webview.dart' hide InternalHeadlessInAppWebView;
export 'android/main.dart'; export 'android/main.dart';
export 'apple/main.dart'; export 'apple/main.dart';
export '../find_interaction/find_interaction_controller.dart'; export '../find_interaction/find_interaction_controller.dart';
export 'in_app_webview_keep_alive.dart' show InAppWebViewKeepAlive;

View File

@ -1191,12 +1191,6 @@ abstract class WebView {
///{@endtemplate} ///{@endtemplate}
final FindInteractionController? findInteractionController; 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} ///{@macro flutter_inappwebview.WebView}
WebView( WebView(
{this.windowId, {this.windowId,
@ -1314,6 +1308,5 @@ abstract class WebView {
this.contextMenu, this.contextMenu,
this.initialUserScripts, this.initialUserScripts,
this.pullToRefreshController, this.pullToRefreshController,
this.findInteractionController, this.findInteractionController});
this.implementation = WebViewImplementation.NATIVE});
} }

View File

@ -17,7 +17,7 @@ class PrintJobController implements Disposable {
///Print job ID. ///Print job ID.
final String 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. ///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}) { PrintJobController({required this.id}) {
this._channel = MethodChannel( this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_$id'); 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_$id');
this._channel.setMethodCallHandler((call) async { this._channel?.setMethodCallHandler((call) async {
try { try {
return await _handleMethod(call); return await _handleMethod(call);
} on Error catch (e) { } 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())) ///- Android native WebView ([Official API - PrintJob.cancel](https://developer.android.com/reference/android/print/PrintJob#cancel()))
Future<void> cancel() async { Future<void> cancel() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('cancel', args); await _channel?.invokeMethod('cancel', args);
} }
///Restarts this print job. ///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())) ///- Android native WebView ([Official API - PrintJob.restart](https://developer.android.com/reference/android/print/PrintJob#restart()))
Future<void> restart() async { Future<void> restart() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('restart', args); await _channel?.invokeMethod('restart', args);
} }
///Dismisses the printing-options sheet or popover. ///Dismisses the printing-options sheet or popover.
@ -85,7 +85,7 @@ class PrintJobController implements Disposable {
Future<void> dismiss({bool animated: true}) async { Future<void> dismiss({bool animated: true}) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("animated", () => animated); args.putIfAbsent("animated", () => animated);
await _channel.invokeMethod('dismiss', args); await _channel?.invokeMethod('dismiss', args);
} }
///Gets the [PrintJobInfo] that describes this job. ///Gets the [PrintJobInfo] that describes this job.
@ -101,7 +101,7 @@ class PrintJobController implements Disposable {
Future<PrintJobInfo?> getInfo() async { Future<PrintJobInfo?> getInfo() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? infoMap = Map<String, dynamic>? infoMap =
(await _channel.invokeMethod('getInfo', args))?.cast<String, dynamic>(); (await _channel?.invokeMethod('getInfo', args))?.cast<String, dynamic>();
return PrintJobInfo.fromMap(infoMap); return PrintJobInfo.fromMap(infoMap);
} }
@ -114,6 +114,8 @@ class PrintJobController implements Disposable {
@override @override
Future<void> dispose() async { Future<void> dispose() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('dispose', args); await _channel?.invokeMethod('dispose', args);
_channel?.setMethodCallHandler(null);
_channel = null;
} }
} }

View File

@ -1,3 +1,4 @@
export 'pull_to_refresh_controller.dart'; export 'pull_to_refresh_controller.dart'
show PullToRefreshController;
export 'pull_to_refresh_settings.dart' export 'pull_to_refresh_settings.dart'
show PullToRefreshSettings, PullToRefreshOptions; show PullToRefreshSettings, PullToRefreshOptions;

View File

@ -44,19 +44,6 @@ class PullToRefreshController {
this.settings = settings ?? PullToRefreshSettings(); 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(String method, dynamic args) {
debugLog( debugLog(
className: this.runtimeType.toString(), className: this.runtimeType.toString(),
@ -228,4 +215,28 @@ class PullToRefreshController {
args.putIfAbsent('attributedTitle', () => attributedTitle.toMap()); args.putIfAbsent('attributedTitle', () => attributedTitle.toMap());
await _channel?.invokeMethod('setStyledTitle', args); 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);
}
});
}
}

View File

@ -198,7 +198,6 @@ export 'web_storage_type.dart' show WebStorageType;
export 'website_data_record.dart' export 'website_data_record.dart'
show WebsiteDataRecord, IOSWKWebsiteDataRecord; show WebsiteDataRecord, IOSWKWebsiteDataRecord;
export 'website_data_type.dart' show WebsiteDataType, IOSWKWebsiteDataType; export 'website_data_type.dart' show WebsiteDataType, IOSWKWebsiteDataType;
export 'webview_implementation.dart' show WebViewImplementation;
export 'webview_package_info.dart' export 'webview_package_info.dart'
show WebViewPackageInfo, AndroidWebViewPackageInfo; show WebViewPackageInfo, AndroidWebViewPackageInfo;
export 'webview_render_process_action.dart' show WebViewRenderProcessAction; export 'webview_render_process_action.dart' show WebViewRenderProcessAction;

View File

@ -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);
}

View File

@ -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<WebViewImplementation> 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();
}
}

View File

@ -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. ///A completion handler the session calls when it completes successfully, or when the user cancels the session.
WebAuthenticationSessionCompletionHandler onComplete; WebAuthenticationSessionCompletionHandler onComplete;
late MethodChannel _channel; MethodChannel? _channel;
static const MethodChannel _sharedChannel = const MethodChannel( static const MethodChannel _sharedChannel = const MethodChannel(
'com.pichillilorenzo/flutter_webauthenticationsession'); 'com.pichillilorenzo/flutter_webauthenticationsession');
@ -104,7 +104,7 @@ class WebAuthenticationSession implements Disposable {
initialSettings ?? WebAuthenticationSessionSettings(); initialSettings ?? WebAuthenticationSessionSettings();
this._channel = MethodChannel( this._channel = MethodChannel(
'com.pichillilorenzo/flutter_webauthenticationsession_$id'); 'com.pichillilorenzo/flutter_webauthenticationsession_$id');
this._channel.setMethodCallHandler((call) async { this._channel?.setMethodCallHandler((call) async {
try { try {
return await _handleMethod(call); return await _handleMethod(call);
} on Error catch (e) { } 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)) ///- iOS ([Official API - ASWebAuthenticationSession.canStart](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3516277-canstart))
Future<bool> canStart() async { Future<bool> canStart() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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)) ///- iOS ([Official API - ASWebAuthenticationSession.start](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990953-start))
Future<bool> start() async { Future<bool> start() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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. ///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. ///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)) ///- iOS ([Official API - ASWebAuthenticationSession.cancel](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990951-cancel))
Future<void> cancel() async { Future<void> cancel() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod("cancel", args); await _channel?.invokeMethod("cancel", args);
} }
///Disposes a web authentication session. ///Disposes the web authentication session.
/// ///
///**Supported Platforms/Implementations**: ///**Supported Platforms/Implementations**:
///- iOS ///- iOS
@override @override
Future<void> dispose() async { Future<void> dispose() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
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) ///Returns `true` if [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession)

View File

@ -18,13 +18,13 @@ class WebMessageChannel {
///The second [WebMessagePort] object of the channel. ///The second [WebMessagePort] object of the channel.
final WebMessagePort port2; final WebMessagePort port2;
late MethodChannel _channel; MethodChannel? _channel;
WebMessageChannel( WebMessageChannel(
{required this.id, required this.port1, required this.port2}) { {required this.id, required this.port1, required this.port2}) {
this._channel = MethodChannel( this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_channel_$id'); 'com.pichillilorenzo/flutter_inappwebview_web_message_channel_$id');
this._channel.setMethodCallHandler((call) async { this._channel?.setMethodCallHandler((call) async {
try { try {
return await _handleMethod(call); return await _handleMethod(call);
} on Error catch (e) { } on Error catch (e) {
@ -97,7 +97,7 @@ class WebMessagePort {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index); args.putIfAbsent('index', () => this._index);
await _webMessageChannel._channel await _webMessageChannel._channel
.invokeMethod('setWebMessageCallback', args); ?.invokeMethod('setWebMessageCallback', args);
this._onMessage = onMessage; this._onMessage = onMessage;
} }
@ -106,14 +106,14 @@ class WebMessagePort {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index); args.putIfAbsent('index', () => this._index);
args.putIfAbsent('message', () => message.toMap()); 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. ///Close the message port and free any resources associated with it.
Future<void> close() async { Future<void> close() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index); args.putIfAbsent('index', () => this._index);
await _webMessageChannel._channel.invokeMethod('close', args); await _webMessageChannel._channel?.invokeMethod('close', args);
} }
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {

View File

@ -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) ///**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; OnPostMessageCallback? onPostMessage;
late MethodChannel _channel; MethodChannel? _channel;
WebMessageListener( WebMessageListener(
{required this.jsObjectName, {required this.jsObjectName,
@ -47,7 +47,7 @@ class WebMessageListener {
"allowedOriginRules cannot contain empty strings"); "allowedOriginRules cannot contain empty strings");
this._channel = MethodChannel( this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${id}_$jsObjectName'); 'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${id}_$jsObjectName');
this._channel.setMethodCallHandler((call) async { this._channel?.setMethodCallHandler((call) async {
try { try {
return await _handleMethod(call); return await _handleMethod(call);
} on Error catch (e) { } on Error catch (e) {
@ -114,6 +114,6 @@ class JavaScriptReplyProxy {
Future<void> postMessage(String message) async { Future<void> postMessage(String message) async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('message', () => message); args.putIfAbsent('message', () => message);
await _webMessageListener._channel.invokeMethod('postMessage', args); await _webMessageListener._channel?.invokeMethod('postMessage', args);
} }
} }

View File

@ -19,6 +19,12 @@ class WebStorage {
SessionStorage sessionStorage; SessionStorage sessionStorage;
WebStorage({required this.localStorage, required this.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. ///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. ///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]. ///It is used by [LocalStorage] and [SessionStorage].
class Storage { class Storage {
late InAppWebViewController _controller; InAppWebViewController? _controller;
///The web storage type: `window.sessionStorage` or `window.localStorage`. ///The web storage type: `window.sessionStorage` or `window.localStorage`.
WebStorageType webStorageType; WebStorageType webStorageType;
@ -69,7 +75,7 @@ class Storage {
///- iOS ///- iOS
///- Web ///- Web
Future<int?> length() async { Future<int?> length() async {
var result = await _controller.evaluateJavascript(source: """ var result = await _controller?.evaluateJavascript(source: """
window.$webStorageType.length; window.$webStorageType.length;
"""); """);
return result != null ? int.parse(json.decode(result)) : null; return result != null ? int.parse(json.decode(result)) : null;
@ -85,7 +91,7 @@ class Storage {
///- Web ///- Web
Future<void> setItem({required String key, required dynamic value}) async { Future<void> setItem({required String key, required dynamic value}) async {
var encodedValue = json.encode(value); var encodedValue = json.encode(value);
await _controller.evaluateJavascript(source: """ await _controller?.evaluateJavascript(source: """
window.$webStorageType.setItem("$key", ${value is String ? encodedValue : "JSON.stringify($encodedValue)"}); window.$webStorageType.setItem("$key", ${value is String ? encodedValue : "JSON.stringify($encodedValue)"});
"""); """);
} }
@ -99,7 +105,7 @@ class Storage {
///- iOS ///- iOS
///- Web ///- Web
Future<dynamic> getItem({required String key}) async { Future<dynamic> getItem({required String key}) async {
var itemValue = await _controller.evaluateJavascript(source: """ var itemValue = await _controller?.evaluateJavascript(source: """
window.$webStorageType.getItem("$key"); window.$webStorageType.getItem("$key");
"""); """);
@ -123,7 +129,7 @@ class Storage {
///- iOS ///- iOS
///- Web ///- Web
Future<void> removeItem({required String key}) async { Future<void> removeItem({required String key}) async {
await _controller.evaluateJavascript(source: """ await _controller?.evaluateJavascript(source: """
window.$webStorageType.removeItem("$key"); window.$webStorageType.removeItem("$key");
"""); """);
} }
@ -140,7 +146,7 @@ class Storage {
var webStorageItems = <WebStorageItem>[]; var webStorageItems = <WebStorageItem>[];
List<Map<dynamic, dynamic>>? items = List<Map<dynamic, dynamic>>? items =
(await _controller.evaluateJavascript(source: """ (await _controller?.evaluateJavascript(source: """
(function() { (function() {
var webStorageItems = []; var webStorageItems = [];
for(var i = 0; i < window.$webStorageType.length; i++){ for(var i = 0; i < window.$webStorageType.length; i++){
@ -177,7 +183,7 @@ class Storage {
///- iOS ///- iOS
///- Web ///- Web
Future<void> clear() async { Future<void> clear() async {
await _controller.evaluateJavascript(source: """ await _controller?.evaluateJavascript(source: """
window.$webStorageType.clear(); window.$webStorageType.clear();
"""); """);
} }
@ -192,11 +198,16 @@ class Storage {
///- iOS ///- iOS
///- Web ///- Web
Future<String> key({required int index}) async { Future<String> key({required int index}) async {
var result = await _controller.evaluateJavascript(source: """ var result = await _controller?.evaluateJavascript(source: """
window.$webStorageType.key($index); window.$webStorageType.key($index);
"""); """);
return result != null ? json.decode(result) : null; 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. ///Class that provides methods to manage the JavaScript `window.localStorage` object.

View File

@ -11,6 +11,7 @@ import FlutterMacOS
public class FindInteractionController : NSObject, Disposable { public class FindInteractionController : NSObject, Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_"; static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_";
var plugin: InAppWebViewFlutterPlugin?
var webView: InAppWebView? var webView: InAppWebView?
var channelDelegate: FindInteractionChannelDelegate? var channelDelegate: FindInteractionChannelDelegate?
var settings: FindInteractionSettings? var settings: FindInteractionSettings?
@ -18,13 +19,16 @@ public class FindInteractionController : NSObject, Disposable {
var searchText: String? var searchText: String?
var activeFindSession: FindSession? 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() super.init()
self.plugin = plugin
self.webView = webView self.webView = webView
self.settings = settings self.settings = settings
let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger) let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id),
self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel) binaryMessenger: registrar.messenger)
self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel)
}
} }
public func prepare() { public func prepare() {
@ -87,6 +91,7 @@ public class FindInteractionController : NSObject, Disposable {
channelDelegate = nil channelDelegate = nil
webView = nil webView = nil
activeFindSession = nil activeFindSession = nil
plugin = nil
} }
deinit { deinit {

View File

@ -81,7 +81,7 @@ public class HeadlessInAppWebView : Disposable {
public func dispose() { public func dispose() {
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil
HeadlessInAppWebViewManager.webViews[id] = nil plugin?.headlessInAppWebViewManager?.webViews[id] = nil
if let view = flutterWebView?.view() { if let view = flutterWebView?.view() {
view.superview?.removeFromSuperview() view.superview?.removeFromSuperview()
} }

View File

@ -16,7 +16,7 @@ import AVFoundation
public class HeadlessInAppWebViewManager: ChannelDelegate { public class HeadlessInAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview" static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"
var plugin: InAppWebViewFlutterPlugin? var plugin: InAppWebViewFlutterPlugin?
static var webViews: [String: HeadlessInAppWebView?] = [:] var webViews: [String: HeadlessInAppWebView?] = [:]
init(plugin: InAppWebViewFlutterPlugin) { init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) 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?]) { public func run(id: String, params: [String: Any?]) {
guard let plugin = plugin, let registrar = plugin.registrar else { guard let plugin = plugin else {
return return
} }
let flutterWebView = FlutterWebViewController(registrar: registrar, let flutterWebView = FlutterWebViewController(plugin: plugin,
withFrame: CGRect.zero, withFrame: CGRect.zero,
viewIdentifier: id, viewIdentifier: id,
params: params as NSDictionary) params: params as NSDictionary)
let headlessInAppWebView = HeadlessInAppWebView(plugin: plugin, id: id, flutterWebView: flutterWebView) let headlessInAppWebView = HeadlessInAppWebView(plugin: plugin, id: id, flutterWebView: flutterWebView)
HeadlessInAppWebViewManager.webViews[id] = headlessInAppWebView webViews[id] = headlessInAppWebView
headlessInAppWebView.prepare(params: params as NSDictionary) headlessInAppWebView.prepare(params: params as NSDictionary)
headlessInAppWebView.onWebViewCreated() headlessInAppWebView.onWebViewCreated()
@ -57,11 +57,11 @@ public class HeadlessInAppWebViewManager: ChannelDelegate {
public override func dispose() { public override func dispose() {
super.dispose() super.dispose()
let headlessWebViews = HeadlessInAppWebViewManager.webViews.values let headlessWebViews = webViews.values
headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in
headlessWebView?.dispose() headlessWebView?.dispose()
} }
HeadlessInAppWebViewManager.webViews.removeAll() webViews.removeAll()
plugin = nil plugin = nil
} }

View File

@ -33,7 +33,7 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega
var isHidden = false var isHidden = false
public override func loadView() { public override func loadView() {
guard let registrar = plugin?.registrar else { guard let plugin = plugin, let registrar = plugin.registrar else {
return return
} }
@ -46,12 +46,12 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega
} }
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings) 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 = webViewTransport.webView
webView!.initialUserScripts = userScripts webView!.initialUserScripts = userScripts
} else { } else {
webView = InAppWebView(id: nil, webView = InAppWebView(id: nil,
registrar: nil, plugin: nil,
frame: .zero, frame: .zero,
configuration: preWebviewConfiguration, configuration: preWebviewConfiguration,
userScripts: userScripts) userScripts: userScripts)
@ -63,10 +63,11 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega
webView.inAppBrowserDelegate = self webView.inAppBrowserDelegate = self
webView.id = id webView.id = id
webView.plugin = plugin
webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel) webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel)
let findInteractionController = FindInteractionController( let findInteractionController = FindInteractionController(
registrar: registrar, plugin: plugin,
id: id, webView: webView, settings: nil) id: id, webView: webView, settings: nil)
webView.findInteractionController = findInteractionController webView.findInteractionController = findInteractionController
findInteractionController.prepare() findInteractionController.prepare()
@ -99,7 +100,7 @@ public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelega
progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
progressBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, 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) webView?.load(webViewTransport.request)
channelDelegate?.onBrowserCreated() channelDelegate?.onBrowserCreated()
} else { } else {

View File

@ -12,12 +12,15 @@ import FlutterMacOS
public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Disposable { public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Disposable {
var myView: NSView? 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() super.init()
myView = NSView(frame: frame) myView = NSView(frame: frame)
keepAliveId = params["keepAliveId"] as? String
let initialSettings = params["initialSettings"] as! [String: Any?] let initialSettings = params["initialSettings"] as! [String: Any?]
let windowId = params["windowId"] as? Int64 let windowId = params["windowId"] as? Int64
let initialUserScripts = params["initialUserScripts"] as? [[String: Any]] let initialUserScripts = params["initialUserScripts"] as? [[String: Any]]
@ -35,24 +38,27 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos
var webView: InAppWebView? 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 = webViewTransport.webView
webView!.id = viewId webView!.id = viewId
let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), webView!.plugin = plugin
binaryMessenger: registrar.messenger) if let registrar = plugin.registrar {
webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) 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!.frame = myView!.bounds
webView!.initialUserScripts = userScripts webView!.initialUserScripts = userScripts
} else { } else {
webView = InAppWebView(id: viewId, webView = InAppWebView(id: viewId,
registrar: registrar, plugin: plugin,
frame: myView!.bounds, frame: myView!.bounds,
configuration: preWebviewConfiguration, configuration: preWebviewConfiguration,
userScripts: userScripts) userScripts: userScripts)
} }
let findInteractionController = FindInteractionController( let findInteractionController = FindInteractionController(
registrar: registrar, plugin: plugin,
id: viewId, webView: webView!, settings: nil) id: viewId, webView: webView!, settings: nil)
webView!.findInteractionController = findInteractionController webView!.findInteractionController = findInteractionController
findInteractionController.prepare() findInteractionController.prepare()
@ -121,7 +127,7 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos
} }
load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData) 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) webView.load(webViewTransport.request)
} }
} }
@ -169,10 +175,12 @@ public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Dispos
} }
public func dispose() { public func dispose() {
if let webView = webView() { if keepAliveId == nil {
webView.dispose() if let webView = webView() {
webView.dispose()
}
myView = nil
} }
myView = nil
} }
deinit { deinit {

View File

@ -12,11 +12,11 @@ import Foundation
public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
static let VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview" 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() super.init()
self.registrar = registrar
} }
public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? { public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {
@ -25,18 +25,48 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
public func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView { public func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView {
let arguments = args as? NSDictionary let arguments = args as? NSDictionary
var flutterWebView: FlutterWebViewController?
var id: Any = viewId
if let headlessWebViewId = arguments?["headlessWebViewId"] as? String, let keepAliveId = arguments?["keepAliveId"] as? String
let headlessWebView = HeadlessInAppWebViewManager.webViews[headlessWebViewId], let headlessWebViewId = arguments?["headlessWebViewId"] as? String
if let headlessWebViewId = headlessWebViewId,
let headlessWebView = plugin.headlessInAppWebViewManager?.webViews[headlessWebViewId],
let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: .zero) { let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: .zero) {
return platformView.view() flutterWebView = platformView
flutterWebView?.keepAliveId = keepAliveId
} }
let webviewController = FlutterWebViewController(registrar: registrar!, if let keepAliveId = keepAliveId,
withFrame: .zero, flutterWebView == nil,
viewIdentifier: viewId, let keepAliveWebView = plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] {
params: arguments!) flutterWebView = keepAliveWebView
webviewController.makeInitialLoad(params: arguments!) if let view = flutterWebView?.view() {
return webviewController.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()
} }
} }

View File

@ -16,7 +16,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_" static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_"
var id: Any? // viewId var id: Any? // viewId
var registrar: FlutterPluginRegistrar? var plugin: InAppWebViewFlutterPlugin?
var windowId: Int64? var windowId: Int64?
var windowCreated = false var windowCreated = false
var inAppBrowserDelegate: InAppBrowserDelegate? var inAppBrowserDelegate: InAppBrowserDelegate?
@ -43,19 +43,16 @@ public class InAppWebView: WKWebView, WKUIDelegate,
var customIMPs: [IMP] = [] var customIMPs: [IMP] = []
static var windowWebViews: [Int64:WebViewTransport] = [:]
static var windowAutoincrementId: Int64 = 0;
var callAsyncJavaScriptBelowIOS14Results: [String:((Any?) -> Void)] = [:] var callAsyncJavaScriptBelowIOS14Results: [String:((Any?) -> Void)] = [:]
var currentOpenPanel: NSOpenPanel? var currentOpenPanel: NSOpenPanel?
init(id: Any?, registrar: FlutterPluginRegistrar?, frame: CGRect, configuration: WKWebViewConfiguration, init(id: Any?, plugin: InAppWebViewFlutterPlugin?, frame: CGRect, configuration: WKWebViewConfiguration,
userScripts: [UserScript] = []) { userScripts: [UserScript] = []) {
super.init(frame: frame, configuration: configuration) super.init(frame: frame, configuration: configuration)
self.id = id self.id = id
self.registrar = registrar self.plugin = plugin
if let id = id, let registrar = registrar { if let id = id, let registrar = plugin?.registrar {
let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), 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.channelDelegate = WebViewChannelDelegate(webView: self, channel: channel)
@ -1788,10 +1785,14 @@ public class InAppWebView: WKWebView, WKUIDelegate,
createWebViewWith configuration: WKWebViewConfiguration, createWebViewWith configuration: WKWebViewConfiguration,
for navigationAction: WKNavigationAction, for navigationAction: WKNavigationAction,
windowFeatures: WKWindowFeatures) -> WKWebView? { windowFeatures: WKWindowFeatures) -> WKWebView? {
InAppWebView.windowAutoincrementId += 1 var windowId: Int64 = 0
let windowId = InAppWebView.windowAutoincrementId 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 windowWebView.windowId = windowId
let webViewTransport = WebViewTransport( let webViewTransport = WebViewTransport(
@ -1799,7 +1800,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
request: navigationAction.request request: navigationAction.request
) )
InAppWebView.windowWebViews[windowId] = webViewTransport inAppWebViewManager?.windowWebViews[windowId] = webViewTransport
windowWebView.stopLoading() windowWebView.stopLoading()
let createWindowAction = CreateWindowAction(navigationAction: navigationAction, windowId: windowId, windowFeatures: windowFeatures, isDialog: nil) let createWindowAction = CreateWindowAction(navigationAction: navigationAction, windowId: windowId, windowFeatures: windowFeatures, isDialog: nil)
@ -1809,8 +1810,8 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return !handledByClient return !handledByClient
} }
callback.defaultBehaviour = { (handledByClient: Bool?) in callback.defaultBehaviour = { (handledByClient: Bool?) in
if InAppWebView.windowWebViews[windowId] != nil { if inAppWebViewManager?.windowWebViews[windowId] != nil {
InAppWebView.windowWebViews.removeValue(forKey: windowId) inAppWebViewManager?.windowWebViews.removeValue(forKey: windowId)
} }
self.loadUrl(urlRequest: navigationAction.request, allowingReadAccessTo: nil) self.loadUrl(urlRequest: navigationAction.request, allowingReadAccessTo: nil)
} }
@ -2066,7 +2067,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
let _windowId = body["_windowId"] as? Int64 let _windowId = body["_windowId"] as? Int64
var webView = self 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 = webViewTransport.webView
} }
webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel)
@ -2083,7 +2084,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return !handledByClient return !handledByClient
} }
callback.defaultBehaviour = { (handledByClient: Bool?) in callback.defaultBehaviour = { (handledByClient: Bool?) in
if let printJob = PrintJobManager.jobs[printJobId] { if let printJob = self.plugin?.printJobManager?.jobs[printJobId] {
printJob?.disposeNoDismiss() printJob?.disposeNoDismiss()
} }
} }
@ -2101,7 +2102,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
let _windowId = body["_windowId"] as? Int64 let _windowId = body["_windowId"] as? Int64
var webView = self 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 = webViewTransport.webView
} }
@ -2143,7 +2144,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
let _windowId = body["_windowId"] as? Int64 let _windowId = body["_windowId"] as? Int64
var webView = self 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 = webViewTransport.webView
} }
webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) 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 let _windowId = body["_windowId"] as? Int64
var webView = self 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 = webViewTransport.webView
} }
webView.channelDelegate?.onScrollChanged(x: x, y: y) 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 { if let id = printJobId, let plugin = plugin {
let printJob = PrintJobController(registrar: registrar, id: id, job: printOperation, settings: settings) let printJob = PrintJobController(plugin: plugin, id: id, job: printOperation, settings: settings)
PrintJobManager.jobs[id] = printJob plugin.printJobManager?.jobs[id] = printJob
printJob.present(parentWindow: window, completionHandler: completionHandler) printJob.present(parentWindow: window, completionHandler: completionHandler)
} else if let window = window { } else if let window = window {
printJobCompletionHandler = completionHandler printJobCompletionHandler = completionHandler
@ -2458,12 +2459,12 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
} }
public func createWebMessageChannel(completionHandler: ((WebMessageChannel?) -> Void)? = nil) -> WebMessageChannel? { public func createWebMessageChannel(completionHandler: ((WebMessageChannel?) -> Void)? = nil) -> WebMessageChannel? {
guard let registrar = registrar else { guard let plugin = plugin else {
completionHandler?(nil) completionHandler?(nil)
return nil return nil
} }
let id = NSUUID().uuidString let id = NSUUID().uuidString
let webMessageChannel = WebMessageChannel(registrar: registrar, id: id) let webMessageChannel = WebMessageChannel(plugin: plugin, id: id)
webMessageChannel.initJsInstance(webView: self, completionHandler: completionHandler) webMessageChannel.initJsInstance(webView: self, completionHandler: completionHandler)
webMessageChannels[id] = webMessageChannel webMessageChannels[id] = webMessageChannel
@ -2567,8 +2568,8 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
if #available(macOS 10.13, *) { if #available(macOS 10.13, *) {
configuration.userContentController.removeAllContentRuleLists() configuration.userContentController.removeAllContentRuleLists()
} }
} else if let wId = windowId, InAppWebView.windowWebViews[wId] != nil { } else if let wId = windowId, plugin?.inAppWebViewManager?.windowWebViews[wId] != nil {
InAppWebView.windowWebViews.removeValue(forKey: wId) plugin?.inAppWebViewManager?.windowWebViews.removeValue(forKey: wId)
} }
configuration.userContentController.dispose(windowId: windowId) configuration.userContentController.dispose(windowId: windowId)
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
@ -2582,7 +2583,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
isPausedTimersCompletionHandler = nil isPausedTimersCompletionHandler = nil
callAsyncJavaScriptBelowIOS14Results.removeAll() callAsyncJavaScriptBelowIOS14Results.removeAll()
super.removeFromSuperview() super.removeFromSuperview()
registrar = nil plugin = nil
} }
deinit { deinit {

View File

@ -1,5 +1,5 @@
// //
// InAppWebViewStatic.swift // InAppWebViewManager.swift
// flutter_inappwebview // flutter_inappwebview
// //
// Created by Lorenzo Pichilli on 08/12/2019. // Created by Lorenzo Pichilli on 08/12/2019.
@ -9,14 +9,18 @@ import Foundation
import WebKit import WebKit
import FlutterMacOS import FlutterMacOS
public class InAppWebViewStatic: ChannelDelegate { public class InAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static" static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"
var plugin: InAppWebViewFlutterPlugin? var plugin: InAppWebViewFlutterPlugin?
var webViewForUserAgent: WKWebView? var webViewForUserAgent: WKWebView?
var defaultUserAgent: String? var defaultUserAgent: String?
var keepAliveWebViews: [String:FlutterWebViewController?] = [:]
var windowWebViews: [Int64:WebViewTransport] = [:]
var windowAutoincrementId: Int64 = 0
init(plugin: InAppWebViewFlutterPlugin) { 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 self.plugin = plugin
} }
@ -37,6 +41,11 @@ public class InAppWebViewStatic: ChannelDelegate {
result(false) result(false)
} }
break break
case "disposeKeepAlive":
let keepAliveId = arguments!["keepAliveId"] as! String
disposeKeepAlive(keepAliveId: keepAliveId)
result(true)
break
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
break 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() { public override func dispose() {
super.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 webViewForUserAgent = nil
defaultUserAgent = nil defaultUserAgent = nil
plugin = nil
} }
deinit { deinit {

View File

@ -11,18 +11,20 @@ import FlutterMacOS
public class WebMessageChannel : FlutterMethodCallDelegate { public class WebMessageChannel : FlutterMethodCallDelegate {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_"
var id: String var id: String
var plugin: InAppWebViewFlutterPlugin?
var channelDelegate: WebMessageChannelChannelDelegate? var channelDelegate: WebMessageChannelChannelDelegate?
weak var webView: InAppWebView? weak var webView: InAppWebView?
var ports: [WebMessagePort] = [] var ports: [WebMessagePort] = []
var registrar: FlutterPluginRegistrar?
public init(registrar: FlutterPluginRegistrar, id: String) { public init(plugin: InAppWebViewFlutterPlugin, id: String) {
self.id = id self.id = id
self.registrar = registrar self.plugin = plugin
super.init() super.init()
let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger) let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id,
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) binaryMessenger: registrar.messenger)
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel)
}
self.ports = [ self.ports = [
WebMessagePort(name: "port1", webMessageChannel: self), WebMessagePort(name: "port1", webMessageChannel: self),
WebMessagePort(name: "port2", webMessageChannel: self) WebMessagePort(name: "port2", webMessageChannel: self)
@ -68,7 +70,7 @@ public class WebMessageChannel : FlutterMethodCallDelegate {
})(); })();
""") """)
webView = nil webView = nil
registrar = nil plugin = nil
} }
deinit { deinit {

View File

@ -16,17 +16,19 @@ public class WebMessageListener : FlutterMethodCallDelegate {
var allowedOriginRules: Set<String> var allowedOriginRules: Set<String>
var channelDelegate: WebMessageListenerChannelDelegate? var channelDelegate: WebMessageListenerChannelDelegate?
weak var webView: InAppWebView? weak var webView: InAppWebView?
var registrar: FlutterPluginRegistrar? var plugin: InAppWebViewFlutterPlugin?
public init(registrar: FlutterPluginRegistrar, id: String, jsObjectName: String, allowedOriginRules: Set<String>) { public init(plugin: InAppWebViewFlutterPlugin, id: String, jsObjectName: String, allowedOriginRules: Set<String>) {
self.id = id self.id = id
self.registrar = registrar self.plugin = plugin
self.jsObjectName = jsObjectName self.jsObjectName = jsObjectName
self.allowedOriginRules = allowedOriginRules self.allowedOriginRules = allowedOriginRules
super.init() super.init()
let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName, if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger) let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName,
self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) binaryMessenger: registrar.messenger)
self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel)
}
} }
public func assertOriginRulesValid() throws { 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 { guard let map = map else {
return nil return nil
} }
return WebMessageListener( return WebMessageListener(
registrar: registrar, plugin: plugin,
id: map["id"] as! String, id: map["id"] as! String,
jsObjectName: map["jsObjectName"] as! String, jsObjectName: map["jsObjectName"] as! String,
allowedOriginRules: Set(map["allowedOriginRules"] as! [String]) allowedOriginRules: Set(map["allowedOriginRules"] as! [String])
@ -181,7 +183,7 @@ public class WebMessageListener : FlutterMethodCallDelegate {
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil
webView = nil webView = nil
registrar = nil plugin = nil
} }
deinit { deinit {

View File

@ -507,9 +507,9 @@ public class WebViewChannelDelegate : ChannelDelegate {
} }
break break
case .addWebMessageListener: 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 webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?]
let webMessageListener = WebMessageListener.fromMap(registrar: registrar, map: webMessageListenerMap)! let webMessageListener = WebMessageListener.fromMap(plugin: plugin, map: webMessageListenerMap)!
do { do {
try webView.addWebMessageListener(webMessageListener: webMessageListener) try webView.addWebMessageListener(webMessageListener: webMessageListener)
result(false) result(false)

View File

@ -27,14 +27,14 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin {
var registrar: FlutterPluginRegistrar? var registrar: FlutterPluginRegistrar?
var platformUtil: PlatformUtil? var platformUtil: PlatformUtil?
var inAppWebViewStatic: InAppWebViewStatic? var inAppWebViewManager: InAppWebViewManager?
var myCookieManager: Any? var myCookieManager: Any?
var myWebStorageManager: MyWebStorageManager? var myWebStorageManager: MyWebStorageManager?
var credentialDatabase: CredentialDatabase? var credentialDatabase: CredentialDatabase?
var inAppBrowserManager: InAppBrowserManager? var inAppBrowserManager: InAppBrowserManager?
var headlessInAppWebViewManager: HeadlessInAppWebViewManager? var headlessInAppWebViewManager: HeadlessInAppWebViewManager?
var webAuthenticationSessionManager: WebAuthenticationSessionManager? var webAuthenticationSessionManager: WebAuthenticationSessionManager?
// var printJobManager: PrintJobManager? var printJobManager: PrintJobManager?
var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var webViewControllers: [String: InAppBrowserWebViewController?] = [:]
var safariViewControllers: [String: Any?] = [:] var safariViewControllers: [String: Any?] = [:]
@ -42,19 +42,19 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin {
public init(with registrar: FlutterPluginRegistrar) { public init(with registrar: FlutterPluginRegistrar) {
super.init() super.init()
self.registrar = registrar 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) platformUtil = PlatformUtil(plugin: self)
inAppBrowserManager = InAppBrowserManager(plugin: self) inAppBrowserManager = InAppBrowserManager(plugin: self)
headlessInAppWebViewManager = HeadlessInAppWebViewManager(plugin: self) headlessInAppWebViewManager = HeadlessInAppWebViewManager(plugin: self)
inAppWebViewStatic = InAppWebViewStatic(plugin: self) inAppWebViewManager = InAppWebViewManager(plugin: self)
credentialDatabase = CredentialDatabase(plugin: self) credentialDatabase = CredentialDatabase(plugin: self)
if #available(macOS 10.13, *) { if #available(macOS 10.13, *) {
myCookieManager = MyCookieManager(plugin: self) myCookieManager = MyCookieManager(plugin: self)
} }
myWebStorageManager = MyWebStorageManager(plugin: self) myWebStorageManager = MyWebStorageManager(plugin: self)
webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self) webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self)
// printJobManager = PrintJobManager() printJobManager = PrintJobManager(plugin: self)
} }
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
@ -68,8 +68,8 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin {
inAppBrowserManager = nil inAppBrowserManager = nil
headlessInAppWebViewManager?.dispose() headlessInAppWebViewManager?.dispose()
headlessInAppWebViewManager = nil headlessInAppWebViewManager = nil
inAppWebViewStatic?.dispose() inAppWebViewManager?.dispose()
inAppWebViewStatic = nil inAppWebViewManager = nil
credentialDatabase?.dispose() credentialDatabase?.dispose()
credentialDatabase = nil credentialDatabase = nil
if #available(macOS 10.13, *) { if #available(macOS 10.13, *) {
@ -80,7 +80,7 @@ public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin {
myWebStorageManager = nil myWebStorageManager = nil
webAuthenticationSessionManager?.dispose() webAuthenticationSessionManager?.dispose()
webAuthenticationSessionManager = nil webAuthenticationSessionManager = nil
// printJobManager?.dispose() printJobManager?.dispose()
// printJobManager = nil printJobManager = nil
} }
} }

View File

@ -18,7 +18,7 @@ public enum PrintJobState: Int {
public class PrintJobController : NSObject, Disposable { public class PrintJobController : NSObject, Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_" static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_"
var id: String var id: String
var registrar: FlutterPluginRegistrar? var plugin: InAppWebViewFlutterPlugin?
var job: NSPrintOperation? var job: NSPrintOperation?
var settings: PrintJobSettings? var settings: PrintJobSettings?
var channelDelegate: PrintJobChannelDelegate? var channelDelegate: PrintJobChannelDelegate?
@ -30,15 +30,17 @@ public class PrintJobController : NSObject, Disposable {
_ success: Bool, _ success: Bool,
_ contextInfo: UnsafeMutableRawPointer?) -> Void _ 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.id = id
self.registrar = registrar self.plugin = plugin
super.init() super.init()
self.job = job self.job = job
self.settings = settings self.settings = settings
let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id, if let registrar = plugin.registrar {
binaryMessenger: registrar.messenger) let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id,
self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel) binaryMessenger: registrar.messenger)
self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel)
}
} }
public func present(parentWindow: NSWindow? = nil, completionHandler: PrintJobController.CompletionHandler? = nil) { public func present(parentWindow: NSWindow? = nil, completionHandler: PrintJobController.CompletionHandler? = nil) {
@ -76,7 +78,8 @@ public class PrintJobController : NSObject, Disposable {
channelDelegate = nil channelDelegate = nil
completionHandler = nil completionHandler = nil
job = nil job = nil
PrintJobManager.jobs[id] = nil plugin?.printJobManager?.jobs[id] = nil
plugin = nil
} }
public func dispose() { public func dispose() {
@ -84,7 +87,7 @@ public class PrintJobController : NSObject, Disposable {
channelDelegate = nil channelDelegate = nil
completionHandler = nil completionHandler = nil
job = nil job = nil
PrintJobManager.jobs[id] = nil plugin?.printJobManager?.jobs[id] = nil
registrar = nil plugin = nil
} }
} }

View File

@ -8,18 +8,21 @@
import Foundation import Foundation
public class PrintJobManager: NSObject, Disposable { 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() super.init()
self.plugin = plugin
} }
public func dispose() { public func dispose() {
let jobs = PrintJobManager.jobs.values let jobValues = jobs.values
jobs.forEach { (job: PrintJobController?) in jobValues.forEach { (job: PrintJobController?) in
job?.dispose() job?.dispose()
} }
PrintJobManager.jobs.removeAll() jobs.removeAll()
plugin = nil
} }
deinit { deinit {

View File

@ -91,7 +91,7 @@ public class WebAuthenticationSession : NSObject, ASWebAuthenticationPresentatio
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil
session = nil session = nil
WebAuthenticationSessionManager.sessions[id] = nil plugin?.webAuthenticationSessionManager?.sessions[id] = nil
plugin = nil plugin = nil
} }

View File

@ -15,7 +15,7 @@ import SafariServices
public class WebAuthenticationSessionManager: ChannelDelegate { public class WebAuthenticationSessionManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webauthenticationsession" static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_webauthenticationsession"
var plugin: InAppWebViewFlutterPlugin? var plugin: InAppWebViewFlutterPlugin?
static var sessions: [String: WebAuthenticationSession?] = [:] var sessions: [String: WebAuthenticationSession?] = [:]
init(plugin: InAppWebViewFlutterPlugin) { init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: WebAuthenticationSessionManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger)) 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 _ = initialSettings.parse(settings: settings)
let session = WebAuthenticationSession(plugin: plugin, id: id, url: sessionUrl, callbackURLScheme: callbackURLScheme, settings: initialSettings) let session = WebAuthenticationSession(plugin: plugin, id: id, url: sessionUrl, callbackURLScheme: callbackURLScheme, settings: initialSettings)
session.prepare() session.prepare()
WebAuthenticationSessionManager.sessions[id] = session sessions[id] = session
result(true) result(true)
return return
} }
@ -63,12 +63,12 @@ public class WebAuthenticationSessionManager: ChannelDelegate {
public override func dispose() { public override func dispose() {
super.dispose() super.dispose()
let sessions = WebAuthenticationSessionManager.sessions.values let sessionValues = sessions.values
sessions.forEach { (session: WebAuthenticationSession?) in sessionValues.forEach { (session: WebAuthenticationSession?) in
session?.cancel() session?.cancel()
session?.dispose() session?.dispose()
} }
WebAuthenticationSessionManager.sessions.removeAll() sessions.removeAll()
plugin = nil plugin = nil
} }