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
- Added InAppWebView keep alive feature
- Added `hasJavaScriptHandler`, `hasUserScript`, `hasWebMessageListener` InAppWebViewController methods
- `HeadlessInAppWebView.webViewController` could be `null`
- Removed `throwIfAlreadyOpened`, `throwIfNotOpened` InAppBrowser methods
- Removed `throwIfAlreadyOpened`, `throwIfNotOpened` ChromeSafariBrowser methods
- Merged "fix #1389 #1315 contextMenu ios 13" [#1575](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1575) (thanks to [heralight](https://github.com/heralight))
- Merged "fix: remove ignored flutter_export_environment.sh" [#1593](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1593) (thanks to [Sunbreak](https://github.com/Sunbreak))
- Merged "Fix AndroidX migration URL in README.md" [#1529](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1529) (thanks to [cslee](https://github.com/cslee))

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@ public class ChromeSafariBrowserManager extends ChannelDelegateImpl {
public InAppWebViewFlutterPlugin plugin;
public String id;
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) {
super(new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -124,21 +124,6 @@ public class ProxyManager extends ChannelDelegateImpl {
@Override
public void dispose() {
super.dispose();
if (proxyController != null) {
// Clears the proxy settings
proxyController.clearProxyOverride(new Executor() {
@Override
public void execute(Runnable command) {
}
}, new Runnable() {
@Override
public void run() {
}
});
proxyController = null;
}
plugin = null;
}
}

View File

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

View File

@ -57,7 +57,6 @@ public class TracingControllerManager implements Disposable {
channelDelegate.dispose();
channelDelegate = null;
}
tracingController = 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.pm.PackageInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.ValueCallback;
import android.webkit.WebSettings;
import android.webkit.WebView;
@ -13,9 +16,12 @@ import androidx.annotation.Nullable;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebView;
import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl;
import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.FlutterWebView;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -25,14 +31,19 @@ import java.util.Set;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class InAppWebViewStatic extends ChannelDelegateImpl {
protected static final String LOG_TAG = "InAppWebViewStatic";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static";
public class InAppWebViewManager extends ChannelDelegateImpl {
protected static final String LOG_TAG = "InAppWebViewManager";
public static final String METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager";
@Nullable
public InAppWebViewFlutterPlugin plugin;
public InAppWebViewStatic(final InAppWebViewFlutterPlugin plugin) {
public final Map<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));
this.plugin = plugin;
}
@ -41,7 +52,11 @@ public class InAppWebViewStatic extends ChannelDelegateImpl {
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) {
case "getDefaultUserAgent":
result.success(WebSettings.getDefaultUserAgent(plugin.applicationContext));
if (plugin != null) {
result.success(WebSettings.getDefaultUserAgent(plugin.applicationContext));
} else {
result.success(null);
}
break;
case "clearClientCertPreferences":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@ -118,6 +133,13 @@ public class InAppWebViewStatic extends ChannelDelegateImpl {
result.success(false);
}
break;
case "disposeKeepAlive":
final String keepAliveId = (String) call.argument("keepAliveId");
if (keepAliveId != null) {
disposeKeepAlive(keepAliveId);
}
result.success(true);
break;
default:
result.notImplemented();
}
@ -133,9 +155,37 @@ public class InAppWebViewStatic extends ChannelDelegateImpl {
return webViewPackageInfoMap;
}
public void disposeKeepAlive(@NonNull String keepAliveId) {
FlutterWebView flutterWebView = keepAliveWebViews.get(keepAliveId);
if (flutterWebView != null) {
flutterWebView.keepAliveId = null;
// be sure to remove the view from the previous parent.
View view = flutterWebView.getView();
if (view != null) {
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
parent.removeView(view);
}
}
flutterWebView.dispose();
}
if (keepAliveWebViews.containsKey(keepAliveId)) {
keepAliveWebViews.put(keepAliveId, null);
}
}
@Override
public void dispose() {
super.dispose();
Collection<FlutterWebView> flutterWebViews = keepAliveWebViews.values();
for (FlutterWebView flutterWebView : flutterWebViews) {
String keepAliveId = flutterWebView.keepAliveId;
if (keepAliveId != null) {
disposeKeepAlive(flutterWebView.keepAliveId);
}
}
keepAliveWebViews.clear();
windowWebViewMessages.clear();
plugin = null;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
//
// InAppWebViewStatic.swift
// InAppWebViewManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/12/2019.
@ -8,14 +8,18 @@
import Foundation
import WebKit
public class InAppWebViewStatic: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static"
public class InAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"
var plugin: SwiftFlutterPlugin?
var webViewForUserAgent: WKWebView?
var defaultUserAgent: String?
var keepAliveWebViews: [String:FlutterWebViewController?] = [:]
var windowWebViews: [Int64:WebViewTransport] = [:]
var windowAutoincrementId: Int64 = 0
init(plugin: SwiftFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: InAppWebViewStatic.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger()))
super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger()))
self.plugin = plugin
}
@ -36,6 +40,11 @@ public class InAppWebViewStatic: ChannelDelegate {
result(false)
}
break
case "disposeKeepAlive":
let keepAliveId = arguments!["keepAliveId"] as! String
disposeKeepAlive(keepAliveId: keepAliveId)
result(true)
break
default:
result(FlutterMethodNotImplemented)
break
@ -67,11 +76,27 @@ public class InAppWebViewStatic: ChannelDelegate {
}
}
public func disposeKeepAlive(keepAliveId: String) {
if let flutterWebView = keepAliveWebViews[keepAliveId] as? FlutterWebViewController {
flutterWebView.keepAliveId = nil
flutterWebView.dispose()
keepAliveWebViews[keepAliveId] = nil
}
}
public override func dispose() {
super.dispose()
plugin = nil
let keepAliveWebViewValues = keepAliveWebViews.values
keepAliveWebViewValues.forEach {(keepAliveWebView: FlutterWebViewController?) in
if let keepAliveId = keepAliveWebView?.keepAliveId {
disposeKeepAlive(keepAliveId: keepAliveId)
}
}
keepAliveWebViews.removeAll()
windowWebViews.removeAll()
webViewForUserAgent = nil
defaultUserAgent = nil
plugin = nil
}
deinit {

View File

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

View File

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

View File

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

View File

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

View File

@ -8,18 +8,21 @@
import Foundation
public class PrintJobManager: NSObject, Disposable {
static var jobs: [String: PrintJobController?] = [:]
var plugin: SwiftFlutterPlugin?
var jobs: [String: PrintJobController?] = [:]
public override init() {
public init(plugin: SwiftFlutterPlugin?) {
super.init()
self.plugin = plugin
}
public func dispose() {
let jobs = PrintJobManager.jobs.values
jobs.forEach { (job: PrintJobController?) in
let jobValues = jobs.values
jobValues.forEach { (job: PrintJobController?) in
job?.dispose()
}
PrintJobManager.jobs.removeAll()
jobs.removeAll()
plugin = nil
}
deinit {

View File

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

View File

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

View File

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

View File

@ -105,9 +105,14 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
}
public func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] {
guard let plugin = plugin else {
return []
}
var uiActivities: [UIActivity] = []
menuItemList.forEach { (menuItem) in
let activity = CustomUIActivity(viewId: id, id: menuItem["id"] as! Int64, url: URL, title: title, label: menuItem["label"] as? String, type: nil, image: .fromMap(map: menuItem["image"] as? [String:Any?]))
let activity = CustomUIActivity(plugin: plugin, viewId: id, id: menuItem["id"] as! Int64, url: URL,
title: title, label: menuItem["label"] as? String, type: nil,
image: .fromMap(map: menuItem["image"] as? [String:Any?]))
uiActivities.append(activity)
}
return uiActivities
@ -125,7 +130,7 @@ public class SafariViewController: SFSafariViewController, SFSafariViewControlle
channelDelegate?.dispose()
channelDelegate = nil
delegate = nil
ChromeSafariBrowserManager.browsers[id] = nil
plugin?.chromeSafariBrowserManager?.browsers[id] = nil
plugin = nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,13 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import '../types/proxy_rule.dart';
import 'webview_feature.dart';
import '../in_app_webview/webview.dart';
import '../types/main.dart';
part 'proxy_controller.g.dart';
///Manages setting and clearing a process-specific override for the Android system-wide proxy settings that govern network requests made by [WebView].
///
///[WebView] may make network requests in order to fetch content that is not otherwise read from the file system or provided directly by application code.
@ -78,7 +82,8 @@ class ProxyController {
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - ProxyConfig](https://developer.android.com/reference/androidx/webkit/ProxyConfig))
class ProxySettings {
@ExchangeableObject(copyMethod: true)
class ProxySettings_ {
///List of bypass rules.
///
///A bypass rule describes URLs that should skip proxy override settings and make a direct connection instead. These can be URLs or IP addresses. Wildcards are accepted.
@ -99,7 +104,7 @@ class ProxySettings {
///Port number is optional and defaults to `80` for `HTTP`, `443` for `HTTPS` and `1080` for `SOCKS`.
///
///The correct syntax for hosts is defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3.2.2).
List<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"`.
///
@ -128,7 +133,7 @@ class ProxySettings {
///**NOTE**: available only if [WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS] feature is supported.
bool reverseBypassEnabled;
ProxySettings(
ProxySettings_(
{this.bypassRules = const [],
this.directs = const [],
this.proxyRules = const [],
@ -136,40 +141,40 @@ class ProxySettings {
this.removeImplicitRules,
this.reverseBypassEnabled = false});
Map<String, dynamic> toMap() {
return {
"bypassRules": bypassRules,
"directs": directs,
"proxyRules": proxyRules.map((e) => e.toMap()).toList(),
"bypassSimpleHostnames": bypassSimpleHostnames,
"removeImplicitRules": removeImplicitRules,
"reverseBypassEnabled": reverseBypassEnabled
};
}
static ProxySettings fromMap(Map<String, dynamic> map) {
var settings = ProxySettings();
settings.bypassRules = map["bypassRules"];
settings.directs = map["directs"];
settings.proxyRules = (map["proxyRules"].cast<Map<String, dynamic>>()
as List<Map<String, dynamic>>)
.map((e) => ProxyRule.fromMap(e)) as List<ProxyRule>;
settings.bypassSimpleHostnames = map["bypassSimpleHostnames"];
settings.removeImplicitRules = map["removeImplicitRules"];
settings.reverseBypassEnabled = map["reverseBypassEnabled"];
return settings;
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
ProxySettings copy() {
return ProxySettings.fromMap(this.toMap());
}
// Map<String, dynamic> toMap() {
// return {
// "bypassRules": bypassRules,
// "directs": directs,
// "proxyRules": proxyRules.map((e) => e.toMap()).toList(),
// "bypassSimpleHostnames": bypassSimpleHostnames,
// "removeImplicitRules": removeImplicitRules,
// "reverseBypassEnabled": reverseBypassEnabled
// };
// }
//
// static ProxySettings fromMap(Map<String, dynamic> map) {
// var settings = ProxySettings();
// settings.bypassRules = map["bypassRules"];
// settings.directs = map["directs"];
// settings.proxyRules = (map["proxyRules"].cast<Map<String, dynamic>>()
// as List<Map<String, dynamic>>)
// .map((e) => ProxyRule.fromMap(e)) as List<ProxyRule>;
// settings.bypassSimpleHostnames = map["bypassSimpleHostnames"];
// settings.removeImplicitRules = map["removeImplicitRules"];
// settings.reverseBypassEnabled = map["reverseBypassEnabled"];
// return settings;
// }
//
// Map<String, dynamic> toJson() {
// return this.toMap();
// }
//
// @override
// String toString() {
// return 'ProxySettings{bypassRules: $bypassRules, directs: $directs, proxyRules: $proxyRules, bypassSimpleHostnames: $bypassSimpleHostnames, removeImplicitRules: $removeImplicitRules, reverseBypassEnabled: $reverseBypassEnabled}';
// }
//
// ProxySettings copy() {
// return ProxySettings.fromMap(this.toMap());
// }
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,6 +27,7 @@ import 'in_app_webview.dart';
import 'in_app_webview_settings.dart';
import 'webview.dart';
import '_static_channel.dart';
import 'in_app_webview_keep_alive.dart';
import '../print_job/main.dart';
import '../find_interaction/main.dart';
@ -52,17 +53,23 @@ final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView<String>([
///callback. Instead, if you are using an [InAppBrowser] instance, you can get it through the [InAppBrowser.webViewController] attribute.
class InAppWebViewController {
WebView? _webview;
late MethodChannel _channel;
static MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL;
Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap =
MethodChannel? _channel;
static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL;
// properties to be saved and restored for keep alive feature
Map<String, JavaScriptHandlerCallback> _javaScriptHandlersMap =
HashMap<String, JavaScriptHandlerCallback>();
final Map<UserScriptInjectionTime, List<UserScript>> _userScripts = {
Map<UserScriptInjectionTime, List<UserScript>> _userScripts = {
UserScriptInjectionTime.AT_DOCUMENT_START: <UserScript>[],
UserScriptInjectionTime.AT_DOCUMENT_END: <UserScript>[]
};
Set<String> _webMessageListenerObjNames = Set();
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;
InAppBrowser? _inAppBrowser;
@ -82,7 +89,8 @@ class InAppWebViewController {
this._id = id;
this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id');
this._channel.setMethodCallHandler((call) async {
this._channel?.setMethodCallHandler((call) async {
if (_channel == null) return null;
try {
return await handleMethod(call);
} on Error catch (e) {
@ -136,12 +144,31 @@ class InAppWebViewController {
}
void _init() {
// ignore: deprecated_member_use_from_same_package
this.android = AndroidInAppWebViewController(channel: _channel);
// ignore: deprecated_member_use_from_same_package
this.ios = IOSInAppWebViewController(channel: _channel);
this.webStorage = WebStorage(
android = AndroidInAppWebViewController(channel: _channel!);
ios = IOSInAppWebViewController(channel: _channel!);
webStorage = WebStorage(
localStorage: LocalStorage(this), sessionStorage: SessionStorage(this));
if (_webview is InAppWebView) {
final keepAlive = (_webview as InAppWebView).keepAlive;
if (keepAlive != null) {
InAppWebViewControllerKeepAliveProps? props = _keepAliveMap[keepAlive];
if (props == null) {
// save controller properties to restore it later
_keepAliveMap[keepAlive] = InAppWebViewControllerKeepAliveProps(
injectedScriptsFromURL: _injectedScriptsFromURL,
javaScriptHandlersMap: _javaScriptHandlersMap,
userScripts: _userScripts,
webMessageListenerObjNames: _webMessageListenerObjNames);
} else {
// restore controller properties
_injectedScriptsFromURL = props.injectedScriptsFromURL;
_javaScriptHandlersMap = props.javaScriptHandlersMap;
_userScripts = props.userScripts;
_webMessageListenerObjNames = props.webMessageListenerObjNames;
}
}
}
}
_debugLog(String method, dynamic args) {
@ -1374,10 +1401,10 @@ class InAppWebViewController {
return null;
}
if (javaScriptHandlersMap.containsKey(handlerName)) {
if (_javaScriptHandlersMap.containsKey(handlerName)) {
// convert result to json
try {
return jsonEncode(await javaScriptHandlersMap[handlerName]!(args));
return jsonEncode(await _javaScriptHandlersMap[handlerName]!(args));
} catch (error, stacktrace) {
developer.log(error.toString() + '\n' + stacktrace.toString(),
name: 'JavaScript Handler "$handlerName"');
@ -1404,7 +1431,7 @@ class InAppWebViewController {
///- Web
Future<WebUri?> getUrl() async {
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;
}
@ -1419,7 +1446,7 @@ class InAppWebViewController {
///- Web
Future<String?> getTitle() async {
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.
@ -1430,7 +1457,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.estimatedProgress](https://developer.apple.com/documentation/webkit/wkwebview/1415007-estimatedprogress))
Future<int?> getProgress() async {
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.
@ -1693,7 +1720,7 @@ class InAppWebViewController {
() =>
allowingReadAccessTo?.toString() ??
iosAllowingReadAccessTo?.toString());
await _channel.invokeMethod('loadUrl', args);
await _channel?.invokeMethod('loadUrl', args);
}
///Loads the given [url] with [postData] (x-www-form-urlencoded) using `POST` method into this WebView.
@ -1717,7 +1744,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url.toString());
args.putIfAbsent('postData', () => postData);
await _channel.invokeMethod('postUrl', args);
await _channel?.invokeMethod('postUrl', args);
}
///Loads the given [data] into this WebView, using [baseUrl] as the base URL for the content.
@ -1771,7 +1798,7 @@ class InAppWebViewController {
() =>
allowingReadAccessTo?.toString() ??
iosAllowingReadAccessTo?.toString());
await _channel.invokeMethod('loadData', args);
await _channel?.invokeMethod('loadData', args);
}
///Loads the given [assetFilePath].
@ -1813,7 +1840,7 @@ class InAppWebViewController {
assert(assetFilePath.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('assetFilePath', () => assetFilePath);
await _channel.invokeMethod('loadFile', args);
await _channel?.invokeMethod('loadFile', args);
}
///Reloads the WebView.
@ -1827,7 +1854,7 @@ class InAppWebViewController {
///- Web ([Official API - Location.reload](https://developer.mozilla.org/en-US/docs/Web/API/Location/reload))
Future<void> reload() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('reload', args);
await _channel?.invokeMethod('reload', args);
}
///Goes back in the history of the WebView.
@ -1841,7 +1868,7 @@ class InAppWebViewController {
///- Web ([Official API - History.back](https://developer.mozilla.org/en-US/docs/Web/API/History/back))
Future<void> goBack() async {
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.
@ -1852,7 +1879,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.canGoBack](https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback))
Future<bool> canGoBack() async {
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.
@ -1866,7 +1893,7 @@ class InAppWebViewController {
///- Web ([Official API - History.forward](https://developer.mozilla.org/en-US/docs/Web/API/History/forward))
Future<void> goForward() async {
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.
@ -1877,7 +1904,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.canGoForward](https://developer.apple.com/documentation/webkit/wkwebview/1414962-cangoforward))
Future<bool> canGoForward() async {
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.
@ -1892,7 +1919,7 @@ class InAppWebViewController {
Future<void> goBackOrForward({required int steps}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('steps', () => steps);
await _channel.invokeMethod('goBackOrForward', args);
await _channel?.invokeMethod('goBackOrForward', args);
}
///Returns a boolean value indicating whether the WebView can go back or forward the given number of steps. Steps is negative if backward and positive if forward.
@ -1904,7 +1931,7 @@ class InAppWebViewController {
Future<bool> canGoBackOrForward({required int steps}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('steps', () => steps);
return await _channel.invokeMethod('canGoBackOrForward', args);
return await _channel?.invokeMethod('canGoBackOrForward', args);
}
///Navigates to a [WebHistoryItem] from the back-forward [WebHistory.list] and sets it as the current item.
@ -1932,7 +1959,7 @@ class InAppWebViewController {
///- Web
Future<bool> isLoading() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('isLoading', args);
return await _channel?.invokeMethod('isLoading', args);
}
///Stops the WebView from loading.
@ -1946,7 +1973,7 @@ class InAppWebViewController {
///- Web ([Official API - Window.stop](https://developer.mozilla.org/en-US/docs/Web/API/Window/stop))
Future<void> stopLoading() async {
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.
@ -1976,7 +2003,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source);
args.putIfAbsent('contentWorld', () => contentWorld?.toMap());
var data = await _channel.invokeMethod('evaluateJavascript', args);
var data = await _channel?.invokeMethod('evaluateJavascript', args);
if (data != null && (Util.isAndroid || Util.isWeb)) {
try {
// try to json decode the data coming from JavaScript
@ -2015,7 +2042,7 @@ class InAppWebViewController {
args.putIfAbsent('urlFile', () => urlFile.toString());
args.putIfAbsent(
'scriptHtmlTagAttributes', () => scriptHtmlTagAttributes?.toMap());
await _channel.invokeMethod('injectJavascriptFileFromUrl', args);
await _channel?.invokeMethod('injectJavascriptFileFromUrl', args);
}
///Evaluates the content of a JavaScript file into the WebView from the flutter assets directory.
@ -2055,7 +2082,7 @@ class InAppWebViewController {
Future<void> injectCSSCode({required String source}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('source', () => source);
await _channel.invokeMethod('injectCSSCode', args);
await _channel?.invokeMethod('injectCSSCode', args);
}
///Injects an external CSS file into the WebView from a defined url.
@ -2082,7 +2109,7 @@ class InAppWebViewController {
args.putIfAbsent('urlFile', () => urlFile.toString());
args.putIfAbsent(
'cssLinkHtmlTagAttributes', () => cssLinkHtmlTagAttributes?.toMap());
await _channel.invokeMethod('injectCSSFileFromUrl', args);
await _channel?.invokeMethod('injectCSSFileFromUrl', args);
}
///Injects a CSS file into the WebView from the flutter assets directory.
@ -2164,7 +2191,7 @@ class InAppWebViewController {
required JavaScriptHandlerCallback callback}) {
assert(!_JAVASCRIPT_HANDLER_FORBIDDEN_NAMES.contains(handlerName),
'"$handlerName" is a forbidden name!');
this.javaScriptHandlersMap[handlerName] = (callback);
this._javaScriptHandlersMap[handlerName] = (callback);
}
///Removes a JavaScript message handler previously added with the [addJavaScriptHandler] associated to [handlerName] key.
@ -2177,7 +2204,17 @@ class InAppWebViewController {
///- MacOS
JavaScriptHandlerCallback? removeJavaScriptHandler(
{required String handlerName}) {
return this.javaScriptHandlersMap.remove(handlerName);
return this._javaScriptHandlersMap.remove(handlerName);
}
///Returns `true` if a JavaScript handler with [handlerName] already exists, otherwise `false`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- MacOS
bool hasJavaScriptHandler({required String handlerName}) {
return this._javaScriptHandlersMap.containsKey(handlerName);
}
///Takes a screenshot of the WebView's visible viewport and returns a [Uint8List]. Returns `null` if it wasn't be able to take it.
@ -2197,7 +2234,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent(
'screenshotConfiguration', () => screenshotConfiguration?.toMap());
return await _channel.invokeMethod('takeScreenshot', args);
return await _channel?.invokeMethod('takeScreenshot', args);
}
///Use [setSettings] instead.
@ -2233,7 +2270,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('settings', () => settings.toMap());
await _channel.invokeMethod('setSettings', args);
await _channel?.invokeMethod('setSettings', args);
}
///Gets the current WebView settings. Returns `null` if it wasn't able to get them.
@ -2247,7 +2284,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? settings =
await _channel.invokeMethod('getSettings', args);
await _channel?.invokeMethod('getSettings', args);
if (settings != null) {
settings = settings.cast<String, dynamic>();
return InAppWebViewSettings.fromMap(settings as Map<String, dynamic>);
@ -2268,7 +2305,7 @@ class InAppWebViewController {
Future<WebHistory?> getCopyBackForwardList() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? result =
(await _channel.invokeMethod('getCopyBackForwardList', args))
(await _channel?.invokeMethod('getCopyBackForwardList', args))
?.cast<String, dynamic>();
return WebHistory.fromMap(result);
}
@ -2281,7 +2318,7 @@ class InAppWebViewController {
///- MacOS
Future<void> clearCache() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearCache', args);
await _channel?.invokeMethod('clearCache', args);
}
///Use [FindInteractionController.findAll] instead.
@ -2289,7 +2326,7 @@ class InAppWebViewController {
Future<void> findAllAsync({required String find}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('find', () => find);
await _channel.invokeMethod('findAll', args);
await _channel?.invokeMethod('findAll', args);
}
///Use [FindInteractionController.findNext] instead.
@ -2297,14 +2334,14 @@ class InAppWebViewController {
Future<void> findNext({required bool forward}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('forward', () => forward);
await _channel.invokeMethod('findNext', args);
await _channel?.invokeMethod('findNext', args);
}
///Use [FindInteractionController.clearMatches] instead.
@Deprecated("Use FindInteractionController.clearMatches instead")
Future<void> clearMatches() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearMatches', args);
await _channel?.invokeMethod('clearMatches', args);
}
///Use [tRexRunnerHtml] instead.
@ -2342,7 +2379,7 @@ class InAppWebViewController {
args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y);
args.putIfAbsent('animated', () => animated);
await _channel.invokeMethod('scrollTo', args);
await _channel?.invokeMethod('scrollTo', args);
}
///Moves the scrolled position of the WebView.
@ -2368,7 +2405,7 @@ class InAppWebViewController {
args.putIfAbsent('x', () => x);
args.putIfAbsent('y', () => y);
args.putIfAbsent('animated', () => animated);
await _channel.invokeMethod('scrollBy', args);
await _channel?.invokeMethod('scrollBy', args);
}
///On Android native WebView, it pauses all layout, parsing, and JavaScript timers for all WebViews.
@ -2384,7 +2421,7 @@ class InAppWebViewController {
///- MacOS
Future<void> pauseTimers() async {
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.
@ -2399,7 +2436,7 @@ class InAppWebViewController {
///- MacOS
Future<void> resumeTimers() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('resumeTimers', args);
await _channel?.invokeMethod('resumeTimers', args);
}
///Prints the current page.
@ -2422,7 +2459,7 @@ class InAppWebViewController {
{PrintJobSettings? settings}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("settings", () => settings?.toMap());
String? jobId = await _channel.invokeMethod('printCurrentPage', args);
String? jobId = await _channel?.invokeMethod('printCurrentPage', args);
if (jobId != null) {
return PrintJobController(id: jobId);
}
@ -2442,7 +2479,7 @@ class InAppWebViewController {
///- Web ([Official API - Document.documentElement.scrollHeight](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight))
Future<int?> getContentHeight() async {
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) {
// try to use javascript
var scrollHeight = await evaluateJavascript(
@ -2469,7 +2506,7 @@ class InAppWebViewController {
///- Web ([Official API - Document.documentElement.scrollWidth](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth))
Future<int?> getContentWidth() async {
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) {
// try to use javascript
var scrollHeight = await evaluateJavascript(
@ -2503,7 +2540,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('zoomFactor', () => zoomFactor);
args.putIfAbsent('animated', () => iosAnimated ?? animated);
return await _channel.invokeMethod('zoomBy', args);
return await _channel?.invokeMethod('zoomBy', args);
}
///Gets the URL that was originally requested for the current page.
@ -2519,7 +2556,7 @@ class InAppWebViewController {
///- Web
Future<WebUri?> getOriginalUrl() async {
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;
}
@ -2530,7 +2567,7 @@ class InAppWebViewController {
///- iOS ([Official API - UIScrollView.zoomScale](https://developer.apple.com/documentation/uikit/uiscrollview/1619419-zoomscale))
Future<double?> getZoomScale() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getZoomScale', args);
return await _channel?.invokeMethod('getZoomScale', args);
}
///Use [getZoomScale] instead.
@ -2554,7 +2591,7 @@ class InAppWebViewController {
///- Web
Future<String?> getSelectedText() async {
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.
@ -2567,7 +2604,7 @@ class InAppWebViewController {
Future<InAppWebViewHitTestResult?> getHitTestResult() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? hitTestResultMap =
await _channel.invokeMethod('getHitTestResult', args);
await _channel?.invokeMethod('getHitTestResult', args);
if (hitTestResultMap == null) {
return null;
@ -2589,7 +2626,7 @@ class InAppWebViewController {
///- iOS ([Official API - UIResponder.resignFirstResponder](https://developer.apple.com/documentation/uikit/uiresponder/1621097-resignfirstresponder))
Future<void> clearFocus() async {
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.
@ -2600,7 +2637,7 @@ class InAppWebViewController {
Future<void> setContextMenu(ContextMenu? contextMenu) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("contextMenu", () => contextMenu?.toMap());
await _channel.invokeMethod('setContextMenu', args);
await _channel?.invokeMethod('setContextMenu', args);
_inAppBrowser?.contextMenu = contextMenu;
}
@ -2614,7 +2651,7 @@ class InAppWebViewController {
Future<RequestFocusNodeHrefResult?> requestFocusNodeHref() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? result =
await _channel.invokeMethod('requestFocusNodeHref', args);
await _channel?.invokeMethod('requestFocusNodeHref', args);
return result != null
? RequestFocusNodeHrefResult(
url: result['url'] != null ? WebUri(result['url']) : null,
@ -2634,7 +2671,7 @@ class InAppWebViewController {
Future<RequestImageRefResult?> requestImageRef() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<dynamic, dynamic>? result =
await _channel.invokeMethod('requestImageRef', args);
await _channel?.invokeMethod('requestImageRef', args);
return result != null
? RequestImageRefResult(
url: result['url'] != null ? WebUri(result['url']) : null,
@ -2730,7 +2767,7 @@ class InAppWebViewController {
try {
Map<String, dynamic> args = <String, dynamic>{};
themeColor = UtilColor.fromStringRepresentation(
await _channel.invokeMethod('getMetaThemeColor', args));
await _channel?.invokeMethod('getMetaThemeColor', args));
return themeColor;
} catch (e) {
// not implemented
@ -2773,7 +2810,7 @@ class InAppWebViewController {
///- Web ([Official API - Window.scrollX](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX))
Future<int?> getScrollX() async {
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.
@ -2789,7 +2826,7 @@ class InAppWebViewController {
///- Web ([Official API - Window.scrollY](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY))
Future<int?> getScrollY() async {
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).
@ -2801,7 +2838,7 @@ class InAppWebViewController {
Future<SslCertificate?> getCertificate() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? sslCertificateMap =
(await _channel.invokeMethod('getCertificate', args))
(await _channel?.invokeMethod('getCertificate', args))
?.cast<String, dynamic>();
return SslCertificate.fromMap(sslCertificateMap);
}
@ -2824,7 +2861,7 @@ class InAppWebViewController {
if (!(_userScripts[userScript.injectionTime]?.contains(userScript) ??
false)) {
_userScripts[userScript.injectionTime]?.add(userScript);
await _channel.invokeMethod('addUserScript', args);
await _channel?.invokeMethod('addUserScript', args);
}
}
@ -2870,7 +2907,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('userScript', () => userScript.toMap());
args.putIfAbsent('index', () => index);
await _channel.invokeMethod('removeUserScript', args);
await _channel?.invokeMethod('removeUserScript', args);
return true;
}
@ -2907,7 +2944,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('groupName', () => groupName);
await _channel.invokeMethod('removeUserScriptsByGroupName', args);
await _channel?.invokeMethod('removeUserScriptsByGroupName', args);
}
///Removes the [userScripts] from the webpages content.
@ -2947,7 +2984,18 @@ class InAppWebViewController {
_userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear();
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.
@ -2991,7 +3039,7 @@ class InAppWebViewController {
args.putIfAbsent('functionBody', () => functionBody);
args.putIfAbsent('arguments', () => arguments);
args.putIfAbsent('contentWorld', () => contentWorld?.toMap());
var data = await _channel.invokeMethod('callAsyncJavaScript', args);
var data = await _channel?.invokeMethod('callAsyncJavaScript', args);
if (data == null) {
return null;
}
@ -3034,7 +3082,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("filePath", () => filePath);
args.putIfAbsent("autoname", () => autoname);
return await _channel.invokeMethod('saveWebArchive', args);
return await _channel?.invokeMethod('saveWebArchive', args);
}
///Indicates whether the webpage context is capable of using features that require [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
@ -3051,7 +3099,7 @@ class InAppWebViewController {
///- Web ([Official API - Window.isSecureContext](https://developer.mozilla.org/en-US/docs/Web/API/Window/isSecureContext))
Future<bool> isSecureContext() async {
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.
@ -3074,7 +3122,7 @@ class InAppWebViewController {
Future<WebMessageChannel?> createWebMessageChannel() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? result =
(await _channel.invokeMethod('createWebMessageChannel', args))
(await _channel?.invokeMethod('createWebMessageChannel', args))
?.cast<String, dynamic>();
return WebMessageChannel.fromMap(result);
}
@ -3102,7 +3150,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('message', () => message.toMap());
args.putIfAbsent('targetOrigin', () => targetOrigin.toString());
await _channel.invokeMethod('postWebMessage', args);
await _channel?.invokeMethod('postWebMessage', args);
}
///Adds a [WebMessageListener] to the WebView and injects a JavaScript object into each frame that the [WebMessageListener] will listen on.
@ -3278,7 +3326,18 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('webMessageListener', () => webMessageListener.toMap());
await _channel.invokeMethod('addWebMessageListener', args);
await _channel?.invokeMethod('addWebMessageListener', args);
}
///Returns `true` if the [webMessageListener] has been already added, otherwise `false`.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- MacOS
bool hasWebMessageListener(WebMessageListener webMessageListener) {
return _webMessageListenerObjNames
.contains(webMessageListener.jsObjectName);
}
///Returns `true` if the webpage can scroll vertically, otherwise `false`.
@ -3294,7 +3353,7 @@ class InAppWebViewController {
///- Web
Future<bool> canScrollVertically() async {
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`.
@ -3310,7 +3369,7 @@ class InAppWebViewController {
///- Web
Future<bool> canScrollHorizontally() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('canScrollHorizontally', args);
return await _channel?.invokeMethod('canScrollHorizontally', args);
}
///Starts Safe Browsing initialization.
@ -3327,7 +3386,7 @@ class InAppWebViewController {
///- Android native WebView ([Official API - WebView.startSafeBrowsing](https://developer.android.com/reference/android/webkit/WebView#startSafeBrowsing(android.content.Context,%20android.webkit.ValueCallback%3Cjava.lang.Boolean%3E)))
Future<bool> startSafeBrowsing() async {
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.
@ -3336,7 +3395,7 @@ class InAppWebViewController {
///- Android native WebView ([Official API - WebView.clearSslPreferences](https://developer.android.com/reference/android/webkit/WebView#clearSslPreferences()))
Future<void> clearSslPreferences() async {
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.
@ -3346,7 +3405,7 @@ class InAppWebViewController {
///- Android native WebView ([Official API - WebView.onPause](https://developer.android.com/reference/android/webkit/WebView#onPause()))
Future<void> pause() async {
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].
@ -3355,7 +3414,7 @@ class InAppWebViewController {
///- Android native WebView ([Official API - WebView.onResume](https://developer.android.com/reference/android/webkit/WebView#onResume()))
Future<void> resume() async {
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.
@ -3368,7 +3427,7 @@ class InAppWebViewController {
Future<bool> pageDown({required bool bottom}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("bottom", () => bottom);
return await _channel.invokeMethod('pageDown', args);
return await _channel?.invokeMethod('pageDown', args);
}
///Scrolls the contents of this WebView up by half the view size.
@ -3381,7 +3440,7 @@ class InAppWebViewController {
Future<bool> pageUp({required bool top}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("top", () => top);
return await _channel.invokeMethod('pageUp', args);
return await _channel?.invokeMethod('pageUp', args);
}
///Performs zoom in in this WebView.
@ -3391,7 +3450,7 @@ class InAppWebViewController {
///- Android native WebView ([Official API - WebView.zoomIn](https://developer.android.com/reference/android/webkit/WebView#zoomIn()))
Future<bool> zoomIn() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('zoomIn', args);
return await _channel?.invokeMethod('zoomIn', args);
}
///Performs zoom out in this WebView.
@ -3401,7 +3460,7 @@ class InAppWebViewController {
///- Android native WebView ([Official API - WebView.zoomOut](https://developer.android.com/reference/android/webkit/WebView#zoomOut()))
Future<bool> zoomOut() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('zoomOut', args);
return await _channel?.invokeMethod('zoomOut', args);
}
///Clears the internal back/forward list.
@ -3410,7 +3469,7 @@ class InAppWebViewController {
///- Android native WebView ([Official API - WebView.clearHistory](https://developer.android.com/reference/android/webkit/WebView#clearHistory()))
Future<void> clearHistory() async {
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.
@ -3420,7 +3479,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.reloadFromOrigin](https://developer.apple.com/documentation/webkit/wkwebview/1414956-reloadfromorigin))
Future<void> reloadFromOrigin() async {
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.
@ -3443,7 +3502,7 @@ class InAppWebViewController {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('pdfConfiguration',
() => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap());
return await _channel.invokeMethod('createPdf', args);
return await _channel?.invokeMethod('createPdf', args);
}
///Creates a web archive of the web views current contents asynchronously.
@ -3458,7 +3517,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.createWebArchiveData](https://developer.apple.com/documentation/webkit/wkwebview/3650491-createwebarchivedata))
Future<Uint8List?> createWebArchiveData() async {
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.
@ -3468,7 +3527,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.hasOnlySecureContent](https://developer.apple.com/documentation/webkit/wkwebview/1415002-hasonlysecurecontent))
Future<bool> hasOnlySecureContent() async {
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.
@ -3482,7 +3541,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.pauseAllMediaPlayback](https://developer.apple.com/documentation/webkit/wkwebview/3752240-pauseallmediaplayback)).
Future<void> pauseAllMediaPlayback() async {
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.
@ -3500,7 +3559,7 @@ class InAppWebViewController {
Future<void> setAllMediaPlaybackSuspended({required bool suspended}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("suspended", () => suspended);
return await _channel.invokeMethod('setAllMediaPlaybackSuspended', args);
return await _channel?.invokeMethod('setAllMediaPlaybackSuspended', args);
}
///Closes all media the web view is presenting, including picture-in-picture video and fullscreen video.
@ -3514,7 +3573,7 @@ class InAppWebViewController {
///- MacOS ([Official API - WKWebView.closeAllMediaPresentations](https://developer.apple.com/documentation/webkit/wkwebview/3752235-closeallmediapresentations)).
Future<void> closeAllMediaPresentations() async {
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.
@ -3531,7 +3590,7 @@ class InAppWebViewController {
Future<MediaPlaybackState?> requestMediaPlaybackState() async {
Map<String, dynamic> args = <String, dynamic>{};
return MediaPlaybackState.fromNativeValue(
await _channel.invokeMethod('requestMediaPlaybackState', args));
await _channel?.invokeMethod('requestMediaPlaybackState', args));
}
///Returns `true` if the [WebView] is in fullscreen mode, otherwise `false`.
@ -3542,7 +3601,7 @@ class InAppWebViewController {
///- MacOS
Future<bool> isInFullscreen() async {
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.
@ -3557,7 +3616,7 @@ class InAppWebViewController {
Future<MediaCaptureState?> getCameraCaptureState() async {
Map<String, dynamic> args = <String, dynamic>{};
return MediaCaptureState.fromNativeValue(
await _channel.invokeMethod('getCameraCaptureState', args));
await _channel?.invokeMethod('getCameraCaptureState', args));
}
///Changes whether the webpage is using the camera to capture images or video.
@ -3572,7 +3631,7 @@ class InAppWebViewController {
Future<void> setCameraCaptureState({required MediaCaptureState state}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('state', () => state.toNativeValue());
await _channel.invokeMethod('setCameraCaptureState', args);
await _channel?.invokeMethod('setCameraCaptureState', args);
}
///Returns a [MediaCaptureState] that indicates whether the webpage is using the microphone to capture audio.
@ -3587,7 +3646,7 @@ class InAppWebViewController {
Future<MediaCaptureState?> getMicrophoneCaptureState() async {
Map<String, dynamic> args = <String, dynamic>{};
return MediaCaptureState.fromNativeValue(
await _channel.invokeMethod('getMicrophoneCaptureState', args));
await _channel?.invokeMethod('getMicrophoneCaptureState', args));
}
///Changes whether the webpage is using the microphone to capture audio.
@ -3603,7 +3662,7 @@ class InAppWebViewController {
{required MediaCaptureState state}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('state', () => state.toNativeValue());
await _channel.invokeMethod('setMicrophoneCaptureState', args);
await _channel?.invokeMethod('setMicrophoneCaptureState', args);
}
///Loads the web content from the data you provide as if the data were the response to the request.
@ -3639,7 +3698,7 @@ class InAppWebViewController {
args.putIfAbsent('urlRequest', () => urlRequest.toMap());
args.putIfAbsent('data', () => data);
args.putIfAbsent('urlResponse', () => urlResponse?.toMap());
await _channel.invokeMethod('loadSimulatedRequest', args);
await _channel?.invokeMethod('loadSimulatedRequest', args);
}
///Returns the iframe `id` attribute used on the Web platform.
@ -3648,7 +3707,7 @@ class InAppWebViewController {
///- Web
Future<String?> getIFrameId() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getIFrameId', args);
return await _channel?.invokeMethod('getIFrameId', args);
}
///Gets the default user agent.
@ -3812,6 +3871,19 @@ class InAppWebViewController {
return await _staticChannel.invokeMethod('handlesURLScheme', args);
}
///Disposes the WebView that is using the [keepAlive] instance
///for the keep alive feature.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
static Future<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].
///
///**Supported Platforms/Implementations**:
@ -3831,7 +3903,7 @@ class InAppWebViewController {
'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css');
///Used internally.
MethodChannel getChannel() {
MethodChannel? getChannel() {
return _channel;
}
@ -3839,4 +3911,23 @@ class InAppWebViewController {
dynamic getViewId() {
return _id;
}
///Disposes the controller.
void dispose({bool isKeepAlive = false}) {
if (!isKeepAlive) {
_channel?.setMethodCallHandler(null);
}
android.dispose();
ios.dispose();
_channel = null;
_webview = null;
_inAppBrowser = null;
webStorage.dispose();
if (!isKeepAlive) {
_javaScriptHandlersMap.clear();
_userScripts.clear();
_webMessageListenerObjNames.clear();
_injectedScriptsFromURL.clear();
}
}
}

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 'apple/main.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}
final FindInteractionController? findInteractionController;
///{@template flutter_inappwebview.WebView.implementation}
///Represents the WebView native implementation to be used.
///The default value is [WebViewImplementation.NATIVE].
///{@endtemplate}
final WebViewImplementation implementation;
///{@macro flutter_inappwebview.WebView}
WebView(
{this.windowId,
@ -1314,6 +1308,5 @@ abstract class WebView {
this.contextMenu,
this.initialUserScripts,
this.pullToRefreshController,
this.findInteractionController,
this.implementation = WebViewImplementation.NATIVE});
this.findInteractionController});
}

View File

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

View File

@ -44,19 +44,6 @@ class PullToRefreshController {
this.settings = settings ?? PullToRefreshSettings();
}
void initMethodChannel(dynamic id) {
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id');
this._channel?.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
print(e);
print(e.stackTrace);
}
});
}
_debugLog(String method, dynamic args) {
debugLog(
className: this.runtimeType.toString(),
@ -228,4 +215,28 @@ class PullToRefreshController {
args.putIfAbsent('attributedTitle', () => attributedTitle.toMap());
await _channel?.invokeMethod('setStyledTitle', args);
}
///Disposes the controller.
void dispose({bool isKeepAlive = false}) {
if (!isKeepAlive) {
_channel?.setMethodCallHandler(null);
}
_channel = null;
}
}
extension InternalPullToRefreshController on PullToRefreshController {
void init(dynamic id) {
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id');
this._channel?.setMethodCallHandler((call) async {
if (_channel == null) return null;
try {
return await _handleMethod(call);
} on Error catch (e) {
print(e);
print(e.stackTrace);
}
});
}
}

View File

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

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.
WebAuthenticationSessionCompletionHandler onComplete;
late MethodChannel _channel;
MethodChannel? _channel;
static const MethodChannel _sharedChannel = const MethodChannel(
'com.pichillilorenzo/flutter_webauthenticationsession');
@ -104,7 +104,7 @@ class WebAuthenticationSession implements Disposable {
initialSettings ?? WebAuthenticationSessionSettings();
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_webauthenticationsession_$id');
this._channel.setMethodCallHandler((call) async {
this._channel?.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
@ -147,10 +147,10 @@ class WebAuthenticationSession implements Disposable {
///- iOS ([Official API - ASWebAuthenticationSession.canStart](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3516277-canstart))
Future<bool> canStart() async {
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.
///
@ -161,10 +161,10 @@ class WebAuthenticationSession implements Disposable {
///- iOS ([Official API - ASWebAuthenticationSession.start](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990953-start))
Future<bool> start() async {
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.
///Calling [cancel] on an already canceled session has no effect.
@ -173,17 +173,19 @@ class WebAuthenticationSession implements Disposable {
///- iOS ([Official API - ASWebAuthenticationSession.cancel](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990951-cancel))
Future<void> cancel() async {
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**:
///- iOS
@override
Future<void> dispose() async {
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)

View File

@ -18,13 +18,13 @@ class WebMessageChannel {
///The second [WebMessagePort] object of the channel.
final WebMessagePort port2;
late MethodChannel _channel;
MethodChannel? _channel;
WebMessageChannel(
{required this.id, required this.port1, required this.port2}) {
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_channel_$id');
this._channel.setMethodCallHandler((call) async {
this._channel?.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
@ -97,7 +97,7 @@ class WebMessagePort {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index);
await _webMessageChannel._channel
.invokeMethod('setWebMessageCallback', args);
?.invokeMethod('setWebMessageCallback', args);
this._onMessage = onMessage;
}
@ -106,14 +106,14 @@ class WebMessagePort {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index);
args.putIfAbsent('message', () => message.toMap());
await _webMessageChannel._channel.invokeMethod('postMessage', args);
await _webMessageChannel._channel?.invokeMethod('postMessage', args);
}
///Close the message port and free any resources associated with it.
Future<void> close() async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('index', () => this._index);
await _webMessageChannel._channel.invokeMethod('close', args);
await _webMessageChannel._channel?.invokeMethod('close', args);
}
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)
OnPostMessageCallback? onPostMessage;
late MethodChannel _channel;
MethodChannel? _channel;
WebMessageListener(
{required this.jsObjectName,
@ -47,7 +47,7 @@ class WebMessageListener {
"allowedOriginRules cannot contain empty strings");
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${id}_$jsObjectName');
this._channel.setMethodCallHandler((call) async {
this._channel?.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
@ -114,6 +114,6 @@ class JavaScriptReplyProxy {
Future<void> postMessage(String message) async {
Map<String, dynamic> args = <String, dynamic>{};
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;
WebStorage({required this.localStorage, required this.sessionStorage});
///Disposes the web storage.
void dispose() {
localStorage.dispose();
sessionStorage.dispose();
}
}
///Class that represents a single web storage item of the JavaScript `window.sessionStorage` and `window.localStorage` objects.
@ -51,7 +57,7 @@ class WebStorageItem {
///Class that provides methods to manage the JavaScript [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) object.
///It is used by [LocalStorage] and [SessionStorage].
class Storage {
late InAppWebViewController _controller;
InAppWebViewController? _controller;
///The web storage type: `window.sessionStorage` or `window.localStorage`.
WebStorageType webStorageType;
@ -69,7 +75,7 @@ class Storage {
///- iOS
///- Web
Future<int?> length() async {
var result = await _controller.evaluateJavascript(source: """
var result = await _controller?.evaluateJavascript(source: """
window.$webStorageType.length;
""");
return result != null ? int.parse(json.decode(result)) : null;
@ -85,7 +91,7 @@ class Storage {
///- Web
Future<void> setItem({required String key, required dynamic value}) async {
var encodedValue = json.encode(value);
await _controller.evaluateJavascript(source: """
await _controller?.evaluateJavascript(source: """
window.$webStorageType.setItem("$key", ${value is String ? encodedValue : "JSON.stringify($encodedValue)"});
""");
}
@ -99,7 +105,7 @@ class Storage {
///- iOS
///- Web
Future<dynamic> getItem({required String key}) async {
var itemValue = await _controller.evaluateJavascript(source: """
var itemValue = await _controller?.evaluateJavascript(source: """
window.$webStorageType.getItem("$key");
""");
@ -123,7 +129,7 @@ class Storage {
///- iOS
///- Web
Future<void> removeItem({required String key}) async {
await _controller.evaluateJavascript(source: """
await _controller?.evaluateJavascript(source: """
window.$webStorageType.removeItem("$key");
""");
}
@ -140,7 +146,7 @@ class Storage {
var webStorageItems = <WebStorageItem>[];
List<Map<dynamic, dynamic>>? items =
(await _controller.evaluateJavascript(source: """
(await _controller?.evaluateJavascript(source: """
(function() {
var webStorageItems = [];
for(var i = 0; i < window.$webStorageType.length; i++){
@ -177,7 +183,7 @@ class Storage {
///- iOS
///- Web
Future<void> clear() async {
await _controller.evaluateJavascript(source: """
await _controller?.evaluateJavascript(source: """
window.$webStorageType.clear();
""");
}
@ -192,11 +198,16 @@ class Storage {
///- iOS
///- Web
Future<String> key({required int index}) async {
var result = await _controller.evaluateJavascript(source: """
var result = await _controller?.evaluateJavascript(source: """
window.$webStorageType.key($index);
""");
return result != null ? json.decode(result) : null;
}
///Disposes the storage.
void dispose() {
_controller = null;
}
}
///Class that provides methods to manage the JavaScript `window.localStorage` object.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
//
// InAppWebViewStatic.swift
// InAppWebViewManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/12/2019.
@ -9,14 +9,18 @@ import Foundation
import WebKit
import FlutterMacOS
public class InAppWebViewStatic: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static"
public class InAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"
var plugin: InAppWebViewFlutterPlugin?
var webViewForUserAgent: WKWebView?
var defaultUserAgent: String?
var keepAliveWebViews: [String:FlutterWebViewController?] = [:]
var windowWebViews: [Int64:WebViewTransport] = [:]
var windowAutoincrementId: Int64 = 0
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: InAppWebViewStatic.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
@ -37,6 +41,11 @@ public class InAppWebViewStatic: ChannelDelegate {
result(false)
}
break
case "disposeKeepAlive":
let keepAliveId = arguments!["keepAliveId"] as! String
disposeKeepAlive(keepAliveId: keepAliveId)
result(true)
break
default:
result(FlutterMethodNotImplemented)
break
@ -68,11 +77,27 @@ public class InAppWebViewStatic: ChannelDelegate {
}
}
public func disposeKeepAlive(keepAliveId: String) {
if let flutterWebView = keepAliveWebViews[keepAliveId] as? FlutterWebViewController {
flutterWebView.keepAliveId = nil
flutterWebView.dispose()
keepAliveWebViews[keepAliveId] = nil
}
}
public override func dispose() {
super.dispose()
plugin = nil
let keepAliveWebViewValues = keepAliveWebViews.values
keepAliveWebViewValues.forEach {(keepAliveWebView: FlutterWebViewController?) in
if let keepAliveId = keepAliveWebView?.keepAliveId {
disposeKeepAlive(keepAliveId: keepAliveId)
}
}
keepAliveWebViews.removeAll()
windowWebViews.removeAll()
webViewForUserAgent = nil
defaultUserAgent = nil
plugin = nil
}
deinit {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,18 +8,21 @@
import Foundation
public class PrintJobManager: NSObject, Disposable {
static var jobs: [String: PrintJobController?] = [:]
var plugin: InAppWebViewFlutterPlugin?
var jobs: [String: PrintJobController?] = [:]
public override init() {
public init(plugin: InAppWebViewFlutterPlugin?) {
super.init()
self.plugin = plugin
}
public func dispose() {
let jobs = PrintJobManager.jobs.values
jobs.forEach { (job: PrintJobController?) in
let jobValues = jobs.values
jobValues.forEach { (job: PrintJobController?) in
job?.dispose()
}
PrintJobManager.jobs.removeAll()
jobs.removeAll()
plugin = nil
}
deinit {

View File

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

View File

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