Added InAppWebView.headlessWebView property to convert an HeadlessWebView to InAppWebView widget

This commit is contained in:
Lorenzo Pichilli 2022-10-20 16:34:37 +02:00
parent 840aeabdb6
commit 92eba92a6c
39 changed files with 676 additions and 137 deletions

View File

@ -1,3 +1,7 @@
## 6.0.0-beta.4
- Added `InAppWebView.headlessWebView` property to convert an `HeadlessWebView` to `InAppWebView` widget
## 6.0.0-beta.3 ## 6.0.0-beta.3
- Added MacOS support - Added MacOS support
@ -46,6 +50,12 @@
- Removed `URLProtectionSpace.iosIsProxy` property - Removed `URLProtectionSpace.iosIsProxy` property
- `historyUrl` and `baseUrl` of `InAppWebViewInitialData` can be `null` - `historyUrl` and `baseUrl` of `InAppWebViewInitialData` can be `null`
## 5.5.0+4
- Fixed "Many crashes on iOS: Completion handler was not called" [#1221](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1221)
- Fixed "webView:didReceiveAuthenticationChallenge:completionHandler" [#1128](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1128)
- Merged "Fix missing import for Flutter 2.8.1" [#1381](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1381) (thanks to [chandrabezzo](https://github.com/chandrabezzo))
## 5.5.0+3 ## 5.5.0+3
- Fixed iOS `toolbarTopTintColor` InAppBrowser option - Fixed iOS `toolbarTopTintColor` InAppBrowser option

View File

@ -2,6 +2,8 @@ package com.pichillilorenzo.flutter_inappwebview;
import android.content.Context; 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.in_app_webview.FlutterWebView;
import com.pichillilorenzo.flutter_inappwebview.webview.PlatformWebView; import com.pichillilorenzo.flutter_inappwebview.webview.PlatformWebView;
import com.pichillilorenzo.flutter_inappwebview.types.WebViewImplementation; import com.pichillilorenzo.flutter_inappwebview.types.WebViewImplementation;
@ -24,15 +26,25 @@ public class FlutterWebViewFactory extends PlatformViewFactory {
@Override @Override
public PlatformView create(Context context, int id, Object args) { public PlatformView create(Context context, int id, Object args) {
HashMap<String, Object> params = (HashMap<String, Object>) args; HashMap<String, Object> params = (HashMap<String, Object>) args;
PlatformWebView flutterWebView = null;
PlatformWebView flutterWebView; String headlessWebViewId = (String) params.get("headlessWebViewId");
WebViewImplementation implementation = WebViewImplementation.fromValue((Integer) params.get("implementation")); if (headlessWebViewId != null) {
switch (implementation) { HeadlessInAppWebView headlessInAppWebView = HeadlessInAppWebViewManager.webViews.get(headlessWebViewId);
case NATIVE: if (headlessInAppWebView != null) {
default: flutterWebView = headlessInAppWebView.disposeAndGetFlutterWebView();
flutterWebView = new FlutterWebView(plugin, context, id, params); }
}
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);
} }
flutterWebView.makeInitialLoad(params);
return flutterWebView; return flutterWebView;
} }

View File

@ -2,18 +2,25 @@ package com.pichillilorenzo.flutter_inappwebview;
import android.content.Context; import android.content.Context;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.http.SslCertificate; import android.net.http.SslCertificate;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview.types.Size2D;
import com.pichillilorenzo.flutter_inappwebview.types.SyncBaseCallbackResultImpl; import com.pichillilorenzo.flutter_inappwebview.types.SyncBaseCallbackResultImpl;
import org.json.JSONArray; import org.json.JSONArray;
@ -242,6 +249,31 @@ public class Util {
return context.getResources().getDisplayMetrics().density; return context.getResources().getDisplayMetrics().density;
} }
public static Size2D getFullscreenSize(Context context) {
Size2D fullscreenSize = new Size2D(-1, -1);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (wm != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
final WindowMetrics metrics = wm.getCurrentWindowMetrics();
// Gets all excluding insets
final WindowInsets windowInsets = metrics.getWindowInsets();
Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
| WindowInsets.Type.displayCutout());
int insetsWidth = insets.right + insets.left;
int insetsHeight = insets.top + insets.bottom;
final Rect bounds = metrics.getBounds();
fullscreenSize.setWidth(bounds.width() - insetsWidth);
fullscreenSize.setHeight(bounds.height() - insetsHeight);
} else {
DisplayMetrics displayMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(displayMetrics);
fullscreenSize.setWidth(displayMetrics.widthPixels);
fullscreenSize.setHeight(displayMetrics.heightPixels);
}
}
return fullscreenSize;
}
public static boolean isClass(String className) { public static boolean isClass(String className) {
try { try {
Class.forName(className); Class.forName(className);

View File

@ -9,13 +9,12 @@ import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.Util; import com.pichillilorenzo.flutter_inappwebview.Util;
import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.FlutterWebView;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable; import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview.types.Size2D; import com.pichillilorenzo.flutter_inappwebview.types.Size2D;
import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.FlutterWebView;
import java.util.Map; import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
public class HeadlessInAppWebView implements Disposable { public class HeadlessInAppWebView implements Disposable {
@ -54,15 +53,16 @@ public class HeadlessInAppWebView implements Disposable {
ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0);
if (mainView != null && flutterWebView != null) { if (mainView != null && flutterWebView != null) {
View view = flutterWebView.getView(); View view = flutterWebView.getView();
final Map<String, Object> initialSize = (Map<String, Object>) params.get("initialSize"); if (view != null) {
Size2D size = Size2D.fromMap(initialSize); final Map<String, Object> initialSize = (Map<String, Object>) params.get("initialSize");
if (size != null) { Size2D size = Size2D.fromMap(initialSize);
if (size == null) {
size = new Size2D(-1, -1);
}
setSize(size); setSize(size);
} else { mainView.addView(view, 0);
view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); view.setVisibility(View.INVISIBLE);
} }
mainView.addView(view, 0);
view.setVisibility(View.INVISIBLE);
} }
} }
} }
@ -71,8 +71,13 @@ public class HeadlessInAppWebView implements Disposable {
public void setSize(@NonNull Size2D size) { public void setSize(@NonNull Size2D size) {
if (flutterWebView != null && flutterWebView.webView != null) { if (flutterWebView != null && flutterWebView.webView != null) {
View view = flutterWebView.getView(); View view = flutterWebView.getView();
float scale = Util.getPixelDensity(view.getContext()); if (view != null) {
view.setLayoutParams(new FrameLayout.LayoutParams((int) (size.getWidth() * scale), (int) (size.getHeight() * scale))); float scale = Util.getPixelDensity(view.getContext());
Size2D fullscreenSize = Util.getFullscreenSize(view.getContext());
int width = (int) (size.getWidth() == -1 ? fullscreenSize.getWidth() : (size.getWidth() * scale));
int height = (int) (size.getWidth() == -1 ? fullscreenSize.getHeight() : (size.getHeight() * scale));
view.setLayoutParams(new FrameLayout.LayoutParams(width, height));
}
} }
} }
@ -80,13 +85,41 @@ public class HeadlessInAppWebView implements Disposable {
public Size2D getSize() { public Size2D getSize() {
if (flutterWebView != null && flutterWebView.webView != null) { if (flutterWebView != null && flutterWebView.webView != null) {
View view = flutterWebView.getView(); View view = flutterWebView.getView();
float scale = Util.getPixelDensity(view.getContext()); if (view != null) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); float scale = Util.getPixelDensity(view.getContext());
return new Size2D(layoutParams.width / scale, layoutParams.height / scale); Size2D fullscreenSize = Util.getFullscreenSize(view.getContext());
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
return new Size2D(
fullscreenSize.getWidth() == layoutParams.width ? layoutParams.width : (layoutParams.width / scale),
fullscreenSize.getHeight() == layoutParams.height ? layoutParams.height : (layoutParams.height / scale)
);
}
} }
return null; return null;
} }
@Nullable
public FlutterWebView disposeAndGetFlutterWebView() {
FlutterWebView newFlutterWebView = flutterWebView;
if (flutterWebView != null) {
View view = flutterWebView.getView();
if (view != null) {
// restore WebView layout params and visibility
view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
view.setVisibility(View.VISIBLE);
// remove from parent
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
parent.removeView(view);
}
}
// set to null to avoid to be disposed before calling "dispose()"
flutterWebView = null;
dispose();
}
return newFlutterWebView;
}
public void dispose() { public void dispose() {
if (channelDelegate != null) { if (channelDelegate != null) {
channelDelegate.dispose(); channelDelegate.dispose();
@ -100,7 +133,10 @@ public class HeadlessInAppWebView implements Disposable {
if (contentView != null) { if (contentView != null) {
ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0);
if (mainView != null && flutterWebView != null) { if (mainView != null && flutterWebView != null) {
mainView.removeView(flutterWebView.getView()); View view = flutterWebView.getView();
if (view != null) {
mainView.removeView(flutterWebView.getView());
}
} }
} }
} }

View File

@ -0,0 +1,74 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_test/flutter_test.dart';
import '../constants.dart';
void convertToInAppWebView() {
final shouldSkip = kIsWeb
? false
: ![
TargetPlatform.android,
TargetPlatform.iOS,
].contains(defaultTargetPlatform);
testWidgets('convert to InAppWebView', (WidgetTester tester) async {
final Completer<InAppWebViewController> controllerCompleter =
Completer<InAppWebViewController>();
final Completer<void> pageLoaded = Completer<void>();
var headlessWebView = new HeadlessInAppWebView(
initialUrlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1),
onWebViewCreated: (controller) {
controllerCompleter.complete(controller);
},
);
headlessWebView.onLoadStop = (controller, url) async {
pageLoaded.complete();
};
await headlessWebView.run();
expect(headlessWebView.isRunning(), true);
final InAppWebViewController controller = await controllerCompleter.future;
await pageLoaded.future;
final String? url = (await controller.getUrl())?.toString();
expect(url, TEST_CROSS_PLATFORM_URL_1.toString());
final Completer<InAppWebViewController> widgetControllerCompleter =
Completer<InAppWebViewController>();
final Completer<String> loadedUrl = Completer<String>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
headlessWebView: headlessWebView,
onWebViewCreated: (controller) {
widgetControllerCompleter.complete(controller);
},
onLoadStop: (controller, url) {
if (url.toString() == TEST_CROSS_PLATFORM_URL_2.toString() &&
!loadedUrl.isCompleted) {
loadedUrl.complete(url.toString());
}
},
),
),
);
final InAppWebViewController widgetController = await widgetControllerCompleter.future;
expect(headlessWebView.isRunning(), false);
expect((await widgetController.getUrl())?.toString(), TEST_CROSS_PLATFORM_URL_1.toString());
await widgetController.loadUrl(
urlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_2));
expect(await loadedUrl.future, TEST_CROSS_PLATFORM_URL_2.toString());
}, skip: shouldSkip);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'convert_to_inappwebview.dart';
import 'take_screenshot.dart'; import 'take_screenshot.dart';
import 'custom_size.dart'; import 'custom_size.dart';
import 'run_and_dispose.dart'; import 'run_and_dispose.dart';
@ -11,5 +12,6 @@ void main() {
takeScreenshot(); takeScreenshot();
customSize(); customSize();
setGetSettings(); setGetSettings();
convertToInAppWebView();
}); });
} }

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -26,7 +27,9 @@ void takeScreenshot() {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
onLoadStop: (controller, url) async { onLoadStop: (controller, url) async {
pageLoaded.complete(); if (!pageLoaded.isCompleted) {
pageLoaded.complete();
}
}); });
await headlessWebView.run(); await headlessWebView.run();

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@ -3,12 +3,11 @@
export "FLUTTER_ROOT=/Users/lorenzopichilli/fvm/versions/2.10.4" export "FLUTTER_ROOT=/Users/lorenzopichilli/fvm/versions/2.10.4"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example" export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/lib/main.dart" export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_BUILD_DIR=build" export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1" export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ=="
export "DART_OBFUSCATION=false" export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true" export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false" export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/.dart_tool/package_config.json" export "PACKAGE_CONFIG=.dart_tool/package_config.json"

View File

@ -0,0 +1,19 @@
//
// Generated file. Do not edit.
//
// ignore_for_file: directives_ordering
// ignore_for_file: lines_longer_than_80_chars
// ignore_for_file: depend_on_referenced_packages
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:url_launcher_web/url_launcher_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs
void registerPlugins(Registrar registrar) {
FlutterInAppWebViewWebPlatform.registerWith(registrar);
UrlLauncherPlugin.registerWith(registrar);
registrar.registerMessageHandler();
}

View File

@ -114,8 +114,9 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
children: [ children: [
InAppWebView( InAppWebView(
key: webViewKey, key: webViewKey,
headlessWebView: headlessWebView,
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev')), URLRequest(url: Uri.parse('https://google.com')),
// initialUrlRequest: // initialUrlRequest:
// URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')), // URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')),
// initialFile: "assets/index.html", // initialFile: "assets/index.html",
@ -125,6 +126,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
pullToRefreshController: pullToRefreshController, pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) async { onWebViewCreated: (controller) async {
webViewController = controller; webViewController = controller;
print(await controller.getUrl());
}, },
onLoadStart: (controller, url) async { onLoadStart: (controller, url) async {
setState(() { setState(() {

View File

@ -15,6 +15,13 @@ import 'package:pointer_interceptor/pointer_interceptor.dart';
InAppLocalhostServer localhostServer = new InAppLocalhostServer(documentRoot: 'assets'); InAppLocalhostServer localhostServer = new InAppLocalhostServer(documentRoot: 'assets');
var headlessWebView = new HeadlessInAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse('https://flutter.dev')),
shouldOverrideUrlLoading: (controller, navigationAction) async {
return NavigationActionPolicy.ALLOW;
},
);
Future main() async { Future main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// await Permission.camera.request(); // await Permission.camera.request();
@ -29,6 +36,10 @@ Future main() async {
await localhostServer.start(); await localhostServer.start();
} }
headlessWebView.run();
await Future.delayed(Duration(seconds: 1));
runApp(MyApp()); runApp(MyApp());
} }

View File

@ -61,6 +61,19 @@ public class HeadlessInAppWebView : Disposable {
return nil return nil
} }
public func disposeAndGetFlutterWebView(withFrame frame: CGRect) -> FlutterWebViewController? {
let newFlutterWebView = flutterWebView
if let view = flutterWebView?.view() {
// restore WebView frame and alpha
view.frame = frame
view.alpha = 1.0
// remove from parent
view.removeFromSuperview()
dispose()
}
return newFlutterWebView
}
public func dispose() { public func dispose() {
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil

View File

@ -23,6 +23,13 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
let arguments = args as? NSDictionary let arguments = args as? NSDictionary
if let headlessWebViewId = arguments?["headlessWebViewId"] as? String,
let headlessWebView = HeadlessInAppWebViewManager.webViews[headlessWebViewId],
let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: frame) {
return platformView
}
let webviewController = FlutterWebViewController(registrar: registrar!, let webviewController = FlutterWebViewController(registrar: registrar!,
withFrame: frame, withFrame: frame,
viewIdentifier: viewId, viewIdentifier: viewId,

View File

@ -1570,9 +1570,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let origin = "\(origin.protocol)://\(origin.host)\(origin.port != 0 ? ":" + String(origin.port) : "")" let origin = "\(origin.protocol)://\(origin.host)\(origin.port != 0 ? ":" + String(origin.port) : "")"
let permissionRequest = PermissionRequest(origin: origin, resources: [type.rawValue], frame: frame) let permissionRequest = PermissionRequest(origin: origin, resources: [type.rawValue], frame: frame)
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.PermissionRequestCallback() let callback = WebViewChannelDelegate.PermissionRequestCallback()
callback.nonNullSuccess = { (response: PermissionResponse) in callback.nonNullSuccess = { (response: PermissionResponse) in
if let action = response.action { if let action = response.action {
decisionHandlerCalled = true
switch action { switch action {
case 1: case 1:
decisionHandler(.grant) decisionHandler(.grant)
@ -1588,7 +1590,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: PermissionResponse?) in callback.defaultBehaviour = { (response: PermissionResponse?) in
decisionHandler(.deny) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.deny)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1610,9 +1615,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let origin = "\(origin.protocol)://\(origin.host)\(origin.port != 0 ? ":" + String(origin.port) : "")" let origin = "\(origin.protocol)://\(origin.host)\(origin.port != 0 ? ":" + String(origin.port) : "")"
let permissionRequest = PermissionRequest(origin: origin, resources: ["deviceOrientationAndMotion"], frame: frame) let permissionRequest = PermissionRequest(origin: origin, resources: ["deviceOrientationAndMotion"], frame: frame)
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.PermissionRequestCallback() let callback = WebViewChannelDelegate.PermissionRequestCallback()
callback.nonNullSuccess = { (response: PermissionResponse) in callback.nonNullSuccess = { (response: PermissionResponse) in
if let action = response.action { if let action = response.action {
decisionHandlerCalled = true
switch action { switch action {
case 1: case 1:
decisionHandler(.grant) decisionHandler(.grant)
@ -1628,7 +1635,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: PermissionResponse?) in callback.defaultBehaviour = { (response: PermissionResponse?) in
decisionHandler(.deny) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.deny)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1694,13 +1704,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return return
} }
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.ShouldOverrideUrlLoadingCallback() let callback = WebViewChannelDelegate.ShouldOverrideUrlLoadingCallback()
callback.nonNullSuccess = { (response: WKNavigationActionPolicy) in callback.nonNullSuccess = { (response: WKNavigationActionPolicy) in
decisionHandlerCalled = true
decisionHandler(response) decisionHandler(response)
return false return false
} }
callback.defaultBehaviour = { (response: WKNavigationActionPolicy?) in callback.defaultBehaviour = { (response: WKNavigationActionPolicy?) in
decisionHandler(.allow) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.allow)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1726,13 +1741,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let useOnNavigationResponse = settings?.useOnNavigationResponse let useOnNavigationResponse = settings?.useOnNavigationResponse
if useOnNavigationResponse != nil, useOnNavigationResponse! { if useOnNavigationResponse != nil, useOnNavigationResponse! {
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.NavigationResponseCallback() let callback = WebViewChannelDelegate.NavigationResponseCallback()
callback.nonNullSuccess = { (response: WKNavigationResponsePolicy) in callback.nonNullSuccess = { (response: WKNavigationResponsePolicy) in
decisionHandlerCalled = true
decisionHandler(response) decisionHandler(response)
return false return false
} }
callback.defaultBehaviour = { (response: WKNavigationResponsePolicy?) in callback.defaultBehaviour = { (response: WKNavigationResponsePolicy?) in
decisionHandler(.allow) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.allow)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1747,7 +1767,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
} }
if let useOnDownloadStart = settings?.useOnDownloadStart, useOnDownloadStart { if let useOnDownloadStart = settings?.useOnDownloadStart, useOnDownloadStart {
if #available(iOS 14.5, *), !navigationResponse.canShowMIMEType { if #available(iOS 14.5, *), !navigationResponse.canShowMIMEType, useOnNavigationResponse == nil || !useOnNavigationResponse! {
decisionHandler(.download) decisionHandler(.download)
return return
} else { } else {
@ -1856,6 +1876,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return return
} }
var completionHandlerCalled = false
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic || if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault || challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest || challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest ||
@ -1869,6 +1890,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedHttpAuthRequestCallback() let callback = WebViewChannelDelegate.ReceivedHttpAuthRequestCallback()
callback.nonNullSuccess = { (response: HttpAuthResponse) in callback.nonNullSuccess = { (response: HttpAuthResponse) in
if let action = response.action { if let action = response.action {
completionHandlerCalled = true
switch action { switch action {
case 0: case 0:
InAppWebView.credentialsProposed = [] InAppWebView.credentialsProposed = []
@ -1917,7 +1939,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: HttpAuthResponse?) in callback.defaultBehaviour = { (response: HttpAuthResponse?) in
completionHandler(.performDefaultHandling, nil) if !completionHandlerCalled {
completionHandlerCalled = true
completionHandler(.performDefaultHandling, nil)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1944,6 +1969,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedServerTrustAuthRequestCallback() let callback = WebViewChannelDelegate.ReceivedServerTrustAuthRequestCallback()
callback.nonNullSuccess = { (response: ServerTrustAuthResponse) in callback.nonNullSuccess = { (response: ServerTrustAuthResponse) in
if let action = response.action { if let action = response.action {
completionHandlerCalled = true
switch action { switch action {
case 0: case 0:
InAppWebView.credentialsProposed = [] InAppWebView.credentialsProposed = []
@ -1964,7 +1990,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: ServerTrustAuthResponse?) in callback.defaultBehaviour = { (response: ServerTrustAuthResponse?) in
completionHandler(.performDefaultHandling, nil) if !completionHandlerCalled {
completionHandlerCalled = true
completionHandler(.performDefaultHandling, nil)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1981,6 +2010,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedClientCertRequestCallback() let callback = WebViewChannelDelegate.ReceivedClientCertRequestCallback()
callback.nonNullSuccess = { (response: ClientCertResponse) in callback.nonNullSuccess = { (response: ClientCertResponse) in
if let action = response.action { if let action = response.action {
completionHandlerCalled = true
switch action { switch action {
case 0: case 0:
completionHandler(.cancelAuthenticationChallenge, nil) completionHandler(.cancelAuthenticationChallenge, nil)
@ -2017,7 +2047,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: ClientCertResponse?) in callback.defaultBehaviour = { (response: ClientCertResponse?) in
completionHandler(.performDefaultHandling, nil) if !completionHandlerCalled {
completionHandlerCalled = true
completionHandler(.performDefaultHandling, nil)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -2102,9 +2135,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return return
} }
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsAlertCallback() let callback = WebViewChannelDelegate.JsAlertCallback()
callback.nonNullSuccess = { (response: JsAlertResponse) in callback.nonNullSuccess = { (response: JsAlertResponse) in
if response.handledByClient { if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1 let action = response.action ?? 1
switch action { switch action {
case 0: case 0:
@ -2118,14 +2154,20 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: JsAlertResponse?) in callback.defaultBehaviour = { (response: JsAlertResponse?) in
let responseMessage = response?.message if !completionHandlerCalled {
let confirmButtonTitle = response?.confirmButtonTitle completionHandlerCalled = true
self.createAlertDialog(message: message, responseMessage: responseMessage, let responseMessage = response?.message
confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler) let confirmButtonTitle = response?.confirmButtonTitle
self.createAlertDialog(message: message, responseMessage: responseMessage,
confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler)
}
} }
callback.error = { (code: String, message: String?, details: Any?) in callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) if !completionHandlerCalled {
completionHandler() completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler()
}
} }
if let channelDelegate = channelDelegate { if let channelDelegate = channelDelegate {
@ -2159,9 +2201,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (Bool) -> Void) { completionHandler: @escaping (Bool) -> Void) {
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsConfirmCallback() let callback = WebViewChannelDelegate.JsConfirmCallback()
callback.nonNullSuccess = { (response: JsConfirmResponse) in callback.nonNullSuccess = { (response: JsConfirmResponse) in
if response.handledByClient { if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1 let action = response.action ?? 1
switch action { switch action {
case 0: case 0:
@ -2178,14 +2223,20 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: JsConfirmResponse?) in callback.defaultBehaviour = { (response: JsConfirmResponse?) in
let responseMessage = response?.message if !completionHandlerCalled {
let confirmButtonTitle = response?.confirmButtonTitle completionHandlerCalled = true
let cancelButtonTitle = response?.cancelButtonTitle let responseMessage = response?.message
self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler) let confirmButtonTitle = response?.confirmButtonTitle
let cancelButtonTitle = response?.cancelButtonTitle
self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler)
}
} }
callback.error = { (code: String, message: String?, details: Any?) in callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) if !completionHandlerCalled {
completionHandler(false) completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler(false)
}
} }
if let channelDelegate = channelDelegate { if let channelDelegate = channelDelegate {
@ -2230,9 +2281,13 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo, public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (String?) -> Void) { completionHandler: @escaping (String?) -> Void) {
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsPromptCallback() let callback = WebViewChannelDelegate.JsPromptCallback()
callback.nonNullSuccess = { (response: JsPromptResponse) in callback.nonNullSuccess = { (response: JsPromptResponse) in
if response.handledByClient { if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1 let action = response.action ?? 1
switch action { switch action {
case 0: case 0:
@ -2249,16 +2304,22 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: JsPromptResponse?) in callback.defaultBehaviour = { (response: JsPromptResponse?) in
let responseMessage = response?.message if !completionHandlerCalled {
let confirmButtonTitle = response?.confirmButtonTitle completionHandlerCalled = true
let cancelButtonTitle = response?.cancelButtonTitle let responseMessage = response?.message
let value = response?.value let confirmButtonTitle = response?.confirmButtonTitle
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, let cancelButtonTitle = response?.cancelButtonTitle
cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler) let value = response?.value
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle,
cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler)
}
} }
callback.error = { (code: String, message: String?, details: Any?) in callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) if !completionHandlerCalled {
completionHandler(nil) completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler(nil)
}
} }
if let channelDelegate = channelDelegate { if let channelDelegate = channelDelegate {
@ -2375,13 +2436,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return return
} }
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.ShouldAllowDeprecatedTLSCallback() let callback = WebViewChannelDelegate.ShouldAllowDeprecatedTLSCallback()
callback.nonNullSuccess = { (action: Bool) in callback.nonNullSuccess = { (action: Bool) in
decisionHandlerCalled = true
decisionHandler(action) decisionHandler(action)
return false return false
} }
callback.defaultBehaviour = { (action: Bool?) in callback.defaultBehaviour = { (action: Bool?) in
decisionHandler(false) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(false)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))

View File

@ -740,6 +740,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return JsAlertResponse.fromMap(map: obj as? [String:Any?]) return JsAlertResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onJsAlert(url: URL?, message: String, isMainFrame: Bool, callback: JsAlertCallback) { public func onJsAlert(url: URL?, message: String, isMainFrame: Bool, callback: JsAlertCallback) {
@ -762,6 +766,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return JsConfirmResponse.fromMap(map: obj as? [String:Any?]) return JsConfirmResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onJsConfirm(url: URL?, message: String, isMainFrame: Bool, callback: JsConfirmCallback) { public func onJsConfirm(url: URL?, message: String, isMainFrame: Bool, callback: JsConfirmCallback) {
@ -784,6 +792,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return JsPromptResponse.fromMap(map: obj as? [String:Any?]) return JsPromptResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onJsPrompt(url: URL?, message: String, defaultValue: String?, isMainFrame: Bool, callback: JsPromptCallback) { public func onJsPrompt(url: URL?, message: String, defaultValue: String?, isMainFrame: Bool, callback: JsPromptCallback) {
@ -851,6 +863,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return PermissionResponse.fromMap(map: obj as? [String:Any?]) return PermissionResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onPermissionRequest(request: PermissionRequest, callback: PermissionRequestCallback) { public func onPermissionRequest(request: PermissionRequest, callback: PermissionRequestCallback) {
@ -871,6 +887,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return WKNavigationActionPolicy.cancel return WKNavigationActionPolicy.cancel
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func shouldOverrideUrlLoading(navigationAction: WKNavigationAction, callback: ShouldOverrideUrlLoadingCallback) { public func shouldOverrideUrlLoading(navigationAction: WKNavigationAction, callback: ShouldOverrideUrlLoadingCallback) {
@ -922,6 +942,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return HttpAuthResponse.fromMap(map: obj as? [String:Any?]) return HttpAuthResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onReceivedHttpAuthRequest(challenge: HttpAuthenticationChallenge, callback: ReceivedHttpAuthRequestCallback) { public func onReceivedHttpAuthRequest(challenge: HttpAuthenticationChallenge, callback: ReceivedHttpAuthRequestCallback) {
@ -939,6 +963,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return ServerTrustAuthResponse.fromMap(map: obj as? [String:Any?]) return ServerTrustAuthResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onReceivedServerTrustAuthRequest(challenge: ServerTrustChallenge, callback: ReceivedServerTrustAuthRequestCallback) { public func onReceivedServerTrustAuthRequest(challenge: ServerTrustChallenge, callback: ReceivedServerTrustAuthRequestCallback) {
@ -956,6 +984,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return ClientCertResponse.fromMap(map: obj as? [String:Any?]) return ClientCertResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onReceivedClientCertRequest(challenge: ClientCertChallenge, callback: ReceivedClientCertRequestCallback) { public func onReceivedClientCertRequest(challenge: ClientCertChallenge, callback: ReceivedClientCertRequestCallback) {
@ -1030,6 +1062,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return WKNavigationResponsePolicy.cancel return WKNavigationResponsePolicy.cancel
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onNavigationResponse(navigationResponse: WKNavigationResponse, callback: NavigationResponseCallback) { public func onNavigationResponse(navigationResponse: WKNavigationResponse, callback: NavigationResponseCallback) {
@ -1050,6 +1086,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return false return false
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func shouldAllowDeprecatedTLS(challenge: URLAuthenticationChallenge, callback: ShouldAllowDeprecatedTLSCallback) { public func shouldAllowDeprecatedTLS(challenge: URLAuthenticationChallenge, callback: ShouldAllowDeprecatedTLSCallback) {

View File

@ -5,6 +5,7 @@ window.flutter_inappwebview = {
viewId: viewId, viewId: viewId,
iframeId: iframeId, iframeId: iframeId,
iframe: null, iframe: null,
iframeContainer: null,
windowAutoincrementId: 0, windowAutoincrementId: 0,
windows: {}, windows: {},
isFullscreen: false, isFullscreen: false,
@ -19,6 +20,7 @@ window.flutter_inappwebview = {
prepare: function(settings) { prepare: function(settings) {
webView.settings = settings; webView.settings = settings;
var iframe = document.getElementById(iframeId); var iframe = document.getElementById(iframeId);
var iframeContainer = document.getElementById(iframeId + '-container');
document.addEventListener('fullscreenchange', function(event) { document.addEventListener('fullscreenchange', function(event) {
// document.fullscreenElement will point to the element that // document.fullscreenElement will point to the element that
@ -37,6 +39,7 @@ window.flutter_inappwebview = {
if (iframe != null) { if (iframe != null) {
webView.iframe = iframe; webView.iframe = iframe;
webView.iframeContainer = iframeContainer;
iframe.addEventListener('load', function (event) { iframe.addEventListener('load', function (event) {
webView.windowAutoincrementId = 0; webView.windowAutoincrementId = 0;
webView.windows = {}; webView.windows = {};
@ -543,20 +546,20 @@ window.flutter_inappwebview = {
return false; return false;
}, },
getSize: function() { getSize: function() {
var iframe = webView.iframe; var iframeContainer = webView.iframeContainer;
var width = 0.0; var width = 0.0;
var height = 0.0; var height = 0.0;
if (iframe.style.width != null && iframe.style.width != '' && iframe.style.width.indexOf('px') > 0) { if (iframeContainer.style.width != null && iframeContainer.style.width != '' && iframeContainer.style.width.indexOf('px') > 0) {
width = parseFloat(iframe.style.width); width = parseFloat(iframeContainer.style.width);
} }
if (width == null || width == 0.0) { if (width == null || width == 0.0) {
width = iframe.getBoundingClientRect().width; width = iframeContainer.getBoundingClientRect().width;
} }
if (iframe.style.height != null && iframe.style.height != '' && iframe.style.height.indexOf('px') > 0) { if (iframeContainer.style.height != null && iframeContainer.style.height != '' && iframeContainer.style.height.indexOf('px') > 0) {
height = parseFloat(iframe.style.height); height = parseFloat(iframeContainer.style.height);
} }
if (height == null || height == 0.0) { if (height == null || height == 0.0) {
height = iframe.getBoundingClientRect().height; height = iframeContainer.getBoundingClientRect().height;
} }
return { return {

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@ -1,4 +1,5 @@
import 'dart:core'; import 'dart:core';
import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@ -1,4 +1,6 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/src/util.dart'; import 'package:flutter_inappwebview/src/util.dart';
@ -714,3 +716,10 @@ class HeadlessInAppWebView implements WebView, Disposable {
MediaCaptureState? newState, MediaCaptureState? newState,
)? onMicrophoneCaptureStateChanged; )? onMicrophoneCaptureStateChanged;
} }
extension InternalHeadlessInAppWebView on HeadlessInAppWebView {
Future<void> internalDispose() async {
_started = false;
_running = false;
}
}

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -7,6 +8,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter_inappwebview/src/in_app_webview/headless_in_app_webview.dart';
import 'package:flutter_inappwebview/src/util.dart';
import '../find_interaction/find_interaction_controller.dart'; import '../find_interaction/find_interaction_controller.dart';
import '../web/web_platform_manager.dart'; import '../web/web_platform_manager.dart';
@ -37,9 +40,21 @@ class InAppWebView extends StatefulWidget implements WebView {
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers; final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
///The window id of a [CreateWindowAction.windowId]. ///The window id of a [CreateWindowAction.windowId].
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
@override @override
final int? windowId; final int? windowId;
///The [HeadlessInAppWebView] to use to initialize this widget
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
///- Web
final HeadlessInAppWebView? headlessWebView;
const InAppWebView({ const InAppWebView({
Key? key, Key? key,
this.windowId, this.windowId,
@ -148,6 +163,7 @@ class InAppWebView extends StatefulWidget implements WebView {
this.onCameraCaptureStateChanged, this.onCameraCaptureStateChanged,
this.onMicrophoneCaptureStateChanged, this.onMicrophoneCaptureStateChanged,
this.gestureRecognizers, this.gestureRecognizers,
this.headlessWebView,
}) : super(key: key); }) : super(key: key);
@override @override
@ -609,8 +625,11 @@ class _InAppWebViewState extends State<InAppWebView> {
webViewHtmlElement.initialUrlRequest = widget.initialUrlRequest; webViewHtmlElement.initialUrlRequest = widget.initialUrlRequest;
webViewHtmlElement.initialFile = widget.initialFile; webViewHtmlElement.initialFile = widget.initialFile;
webViewHtmlElement.initialData = widget.initialData; webViewHtmlElement.initialData = widget.initialData;
webViewHtmlElement.headlessWebViewId = widget.headlessWebView?.isRunning() ?? false ? widget.headlessWebView?.id : null;
webViewHtmlElement.prepare(); webViewHtmlElement.prepare();
webViewHtmlElement.makeInitialLoad(); if (webViewHtmlElement.headlessWebViewId == null) {
webViewHtmlElement.makeInitialLoad();
}
_onPlatformViewCreated(viewId); _onPlatformViewCreated(viewId);
}, },
); );
@ -653,6 +672,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialSettings': initialSettings, 'initialSettings': initialSettings,
'contextMenu': widget.contextMenu?.toMap() ?? {}, 'contextMenu': widget.contextMenu?.toMap() ?? {},
'windowId': widget.windowId, 'windowId': widget.windowId,
'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false ? widget.headlessWebView?.id : null,
'implementation': widget.implementation.toNativeValue(), 'implementation': widget.implementation.toNativeValue(),
'initialUserScripts': 'initialUserScripts':
widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? widget.initialUserScripts?.map((e) => e.toMap()).toList() ??
@ -680,6 +700,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialSettings': initialSettings, 'initialSettings': initialSettings,
'contextMenu': widget.contextMenu?.toMap() ?? {}, 'contextMenu': widget.contextMenu?.toMap() ?? {},
'windowId': widget.windowId, 'windowId': widget.windowId,
'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false ? widget.headlessWebView?.id : null,
'implementation': widget.implementation.toNativeValue(), 'implementation': widget.implementation.toNativeValue(),
'initialUserScripts': 'initialUserScripts':
widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [],
@ -702,6 +723,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialSettings': initialSettings, 'initialSettings': initialSettings,
'contextMenu': widget.contextMenu?.toMap() ?? {}, 'contextMenu': widget.contextMenu?.toMap() ?? {},
'windowId': widget.windowId, 'windowId': widget.windowId,
'headlessWebViewId': widget.headlessWebView?.isRunning() ?? false ? widget.headlessWebView?.id : null,
'implementation': widget.implementation.toNativeValue(), 'implementation': widget.implementation.toNativeValue(),
'initialUserScripts': 'initialUserScripts':
widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [],
@ -732,10 +754,19 @@ class _InAppWebViewState extends State<InAppWebView> {
} }
void _onPlatformViewCreated(int id) { void _onPlatformViewCreated(int id) {
_controller = InAppWebViewController(id, widget); final viewId = (!kIsWeb && (widget.headlessWebView?.isRunning() ?? false)) ? widget.headlessWebView?.id : id;
widget.pullToRefreshController?.initMethodChannel(id); widget.headlessWebView?.internalDispose();
widget.findInteractionController?.initMethodChannel(id); _controller = InAppWebViewController(viewId, widget);
widget.pullToRefreshController?.initMethodChannel(viewId);
widget.findInteractionController?.initMethodChannel(viewId);
if (widget.onWebViewCreated != null) { if (widget.onWebViewCreated != null) {
debugLog(
className: "InAppWebView",
name: "WebView",
id: viewId?.toString(),
debugLoggingSettings: WebView.debugLoggingSettings,
method: "onWebViewCreated",
args: []);
widget.onWebViewCreated!(_controller!); widget.onWebViewCreated!(_controller!);
} }
} }

View File

@ -3,6 +3,8 @@ import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@ -7,7 +7,7 @@ export 'in_app_webview_settings.dart'
InAppWebViewGroupOptions, InAppWebViewGroupOptions,
WebViewOptions, WebViewOptions,
InAppWebViewOptions; InAppWebViewOptions;
export 'headless_in_app_webview.dart'; export 'headless_in_app_webview.dart' hide InternalHeadlessInAppWebView;
export 'android/main.dart'; export 'android/main.dart';
export 'apple/main.dart'; export 'apple/main.dart';
export '../find_interaction/find_interaction_controller.dart'; export '../find_interaction/find_interaction_controller.dart';

View File

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../in_app_webview/webview.dart'; import '../in_app_webview/webview.dart';
import '../in_app_browser/in_app_browser.dart'; import '../in_app_browser/in_app_browser.dart';

View File

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -1,6 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'headless_inappwebview_manager.dart';
import 'in_app_web_view_web_element.dart'; import 'in_app_web_view_web_element.dart';
import '../util.dart'; import '../util.dart';
import '../types/disposable.dart'; import '../types/disposable.dart';
@ -59,14 +61,21 @@ class HeadlessInAppWebViewWebElement implements Disposable {
} }
void setSize(Size size) { void setSize(Size size) {
webView?.iframe.style.width = size.width.toString() + "px"; webView?.iframeContainer.style.width = size.width.toString() + "px";
webView?.iframe.style.height = size.height.toString() + "px"; webView?.iframeContainer.style.height = size.height.toString() + "px";
}
InAppWebViewWebElement? disposeAndGetFlutterWebView() {
InAppWebViewWebElement? newFlutterWebView = webView;
dispose();
return newFlutterWebView;
} }
@override @override
void dispose() { void dispose() {
_channel?.setMethodCallHandler(null); _channel?.setMethodCallHandler(null);
_channel = null; _channel = null;
HeadlessInAppWebViewManager.webViews.putIfAbsent(id, () => null);
webView?.dispose(); webView?.dispose();
webView = null; webView = null;
} }

View File

@ -9,6 +9,8 @@ import 'headless_in_app_web_view_web_element.dart';
import '../types/main.dart'; import '../types/main.dart';
class HeadlessInAppWebViewManager { class HeadlessInAppWebViewManager {
static final Map<String, HeadlessInAppWebViewWebElement?> webViews = {};
static late MethodChannel _sharedChannel; static late MethodChannel _sharedChannel;
late BinaryMessenger _messenger; late BinaryMessenger _messenger;
@ -50,17 +52,18 @@ class HeadlessInAppWebViewManager {
var headlessWebView = HeadlessInAppWebViewWebElement( var headlessWebView = HeadlessInAppWebViewWebElement(
id: id, messenger: _messenger, webView: webView); id: id, messenger: _messenger, webView: webView);
WebPlatformManager.webViews.putIfAbsent(id, () => webView); WebPlatformManager.webViews.putIfAbsent(id, () => webView);
HeadlessInAppWebViewManager.webViews.putIfAbsent(id, () => headlessWebView);
prepare(webView, params); prepare(webView, params);
headlessWebView.onWebViewCreated(); headlessWebView.onWebViewCreated();
webView.makeInitialLoad(); webView.makeInitialLoad();
} }
void prepare(InAppWebViewWebElement webView, Map<String, dynamic> params) { void prepare(InAppWebViewWebElement webView, Map<String, dynamic> params) {
webView.iframe.style.display = 'none'; webView.iframeContainer.style.display = 'none';
Map<String, num>? initialSize = params["initialSize"]?.cast<String, num>(); Map<String, num>? initialSize = params["initialSize"]?.cast<String, num>();
if (initialSize != null) { if (initialSize != null) {
webView.iframe.style.width = initialSize["width"].toString() + 'px'; webView.iframeContainer.style.width = initialSize["width"].toString() + 'px';
webView.iframe.style.height = initialSize["height"].toString() + 'px'; webView.iframeContainer.style.height = initialSize["height"].toString() + 'px';
} }
Map<String, dynamic> initialSettings = Map<String, dynamic> initialSettings =
params["initialSettings"].cast<String, dynamic>(); params["initialSettings"].cast<String, dynamic>();
@ -74,7 +77,7 @@ class HeadlessInAppWebViewManager {
webView.initialFile = params["initialFile"]; webView.initialFile = params["initialFile"];
webView.initialData = InAppWebViewInitialData.fromMap( webView.initialData = InAppWebViewInitialData.fromMap(
params["initialData"]?.cast<String, dynamic>()); params["initialData"]?.cast<String, dynamic>());
document.body?.append(webView.iframe); document.body?.append(webView.iframeContainer);
webView.prepare(); webView.prepare();
} }
} }

View File

@ -1,8 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'dart:html'; import 'dart:html';
import 'dart:js' as js; import 'dart:js' as js;
import 'headless_inappwebview_manager.dart';
import 'web_platform_manager.dart'; import 'web_platform_manager.dart';
import '../in_app_webview/in_app_webview_settings.dart'; import '../in_app_webview/in_app_webview_settings.dart';
import '../types/main.dart'; import '../types/main.dart';
@ -11,14 +14,16 @@ import '../types/disposable.dart';
class InAppWebViewWebElement implements Disposable { class InAppWebViewWebElement implements Disposable {
late dynamic _viewId; late dynamic _viewId;
late BinaryMessenger _messenger; late BinaryMessenger _messenger;
late DivElement iframeContainer;
late IFrameElement iframe; late IFrameElement iframe;
late MethodChannel? _channel; late MethodChannel? _channel;
InAppWebViewSettings? initialSettings; InAppWebViewSettings? initialSettings;
URLRequest? initialUrlRequest; URLRequest? initialUrlRequest;
InAppWebViewInitialData? initialData; InAppWebViewInitialData? initialData;
String? initialFile; String? initialFile;
String? headlessWebViewId;
late InAppWebViewSettings settings; InAppWebViewSettings? settings;
late js.JsObject bridgeJsObject; late js.JsObject bridgeJsObject;
bool isLoading = false; bool isLoading = false;
@ -26,11 +31,17 @@ class InAppWebViewWebElement implements Disposable {
{required dynamic viewId, required BinaryMessenger messenger}) { {required dynamic viewId, required BinaryMessenger messenger}) {
this._viewId = viewId; this._viewId = viewId;
this._messenger = messenger; this._messenger = messenger;
iframeContainer = DivElement()
..id = 'flutter_inappwebview-$_viewId-container'
..style.height = '100%'
..style.width = '100%'
..style.border = 'none';
iframe = IFrameElement() iframe = IFrameElement()
..id = 'flutter_inappwebview-$_viewId' ..id = 'flutter_inappwebview-$_viewId'
..style.height = '100%' ..style.height = '100%'
..style.width = '100%' ..style.width = '100%'
..style.border = 'none'; ..style.border = 'none';
iframeContainer.append(iframe);
_channel = MethodChannel( _channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_$_viewId', 'com.pichillilorenzo/flutter_inappwebview_$_viewId',
@ -173,35 +184,59 @@ class InAppWebViewWebElement implements Disposable {
} }
void prepare() { void prepare() {
settings = initialSettings ?? InAppWebViewSettings(); if (headlessWebViewId != null) {
final headlessWebView = HeadlessInAppWebViewManager.webViews[headlessWebViewId!];
if (headlessWebView != null && headlessWebView.webView != null) {
final webView = headlessWebView.disposeAndGetFlutterWebView();
if (webView != null) {
webView.iframe.id = iframe.id;
iframe.remove();
iframeContainer.append(webView.iframe);
iframe = webView.iframe;
Set<Sandbox> sandbox = Set.from(Sandbox.values); initialSettings = webView.initialSettings;
settings = webView.settings;
initialUrlRequest = webView.initialUrlRequest;
initialData = webView.initialData;
initialFile = webView.initialFile;
if (settings.javaScriptEnabled != null && !settings.javaScriptEnabled!) { bridgeJsObject['webViews'][_viewId] = bridgeJsObject
sandbox.remove(Sandbox.ALLOW_SCRIPTS); .callMethod("createFlutterInAppWebView", [_viewId, iframe.id]);
}
}
} }
iframe.allow = settings.iframeAllow ?? iframe.allow; if (headlessWebViewId == null && settings == null) {
iframe.allowFullscreen = settings = initialSettings ?? InAppWebViewSettings();
settings.iframeAllowFullscreen ?? iframe.allowFullscreen;
iframe.referrerPolicy =
settings.iframeReferrerPolicy?.toNativeValue() ?? iframe.referrerPolicy;
iframe.name = settings.iframeName ?? iframe.name;
iframe.csp = settings.iframeCsp ?? iframe.csp;
if (settings.iframeSandbox != null && Set<Sandbox> sandbox = Set.from(Sandbox.values);
settings.iframeSandbox != Sandbox.ALLOW_ALL) {
iframe.setAttribute("sandbox", if (settings!.javaScriptEnabled != null && !settings!.javaScriptEnabled!) {
settings.iframeSandbox!.map((e) => e.toNativeValue()).join(" ")); sandbox.remove(Sandbox.ALLOW_SCRIPTS);
} else if (settings.iframeSandbox == Sandbox.ALLOW_ALL) { }
iframe.removeAttribute("sandbox");
} else if (sandbox != Sandbox.values) { iframe.allow = settings!.iframeAllow ?? iframe.allow;
iframe.setAttribute( iframe.allowFullscreen =
"sandbox", sandbox.map((e) => e.toNativeValue()).join(" ")); settings!.iframeAllowFullscreen ?? iframe.allowFullscreen;
settings.iframeSandbox = sandbox; iframe.referrerPolicy =
settings!.iframeReferrerPolicy?.toNativeValue() ?? iframe.referrerPolicy;
iframe.name = settings!.iframeName ?? iframe.name;
iframe.csp = settings!.iframeCsp ?? iframe.csp;
if (settings!.iframeSandbox != null &&
settings!.iframeSandbox != Sandbox.ALLOW_ALL) {
iframe.setAttribute("sandbox",
settings!.iframeSandbox!.map((e) => e.toNativeValue()).join(" "));
} else if (settings!.iframeSandbox == Sandbox.ALLOW_ALL) {
iframe.removeAttribute("sandbox");
} else if (sandbox != Sandbox.values) {
iframe.setAttribute(
"sandbox", sandbox.map((e) => e.toNativeValue()).join(" "));
settings!.iframeSandbox = sandbox;
}
} }
_callMethod("prepare", [js.JsObject.jsify(settings.toMap())]); _callMethod("prepare", [js.JsObject.jsify(settings!.toMap())]);
} }
dynamic _callMethod(Object method, [List? args]) { dynamic _callMethod(Object method, [List? args]) {
@ -405,7 +440,7 @@ class InAppWebViewWebElement implements Disposable {
Set<Sandbox> sandbox = getSandbox(); Set<Sandbox> sandbox = getSandbox();
if (newSettings.javaScriptEnabled != null && if (newSettings.javaScriptEnabled != null &&
settings.javaScriptEnabled != newSettings.javaScriptEnabled) { settings!.javaScriptEnabled != newSettings.javaScriptEnabled) {
if (!newSettings.javaScriptEnabled!) { if (!newSettings.javaScriptEnabled!) {
sandbox.remove(Sandbox.ALLOW_SCRIPTS); sandbox.remove(Sandbox.ALLOW_SCRIPTS);
} else { } else {
@ -413,23 +448,23 @@ class InAppWebViewWebElement implements Disposable {
} }
} }
if (settings.iframeAllow != newSettings.iframeAllow) { if (settings!.iframeAllow != newSettings.iframeAllow) {
iframe.allow = newSettings.iframeAllow; iframe.allow = newSettings.iframeAllow;
} }
if (settings.iframeAllowFullscreen != newSettings.iframeAllowFullscreen) { if (settings!.iframeAllowFullscreen != newSettings.iframeAllowFullscreen) {
iframe.allowFullscreen = newSettings.iframeAllowFullscreen; iframe.allowFullscreen = newSettings.iframeAllowFullscreen;
} }
if (settings.iframeReferrerPolicy != newSettings.iframeReferrerPolicy) { if (settings!.iframeReferrerPolicy != newSettings.iframeReferrerPolicy) {
iframe.referrerPolicy = newSettings.iframeReferrerPolicy?.toNativeValue(); iframe.referrerPolicy = newSettings.iframeReferrerPolicy?.toNativeValue();
} }
if (settings.iframeName != newSettings.iframeName) { if (settings!.iframeName != newSettings.iframeName) {
iframe.name = newSettings.iframeName; iframe.name = newSettings.iframeName;
} }
if (settings.iframeCsp != newSettings.iframeCsp) { if (settings!.iframeCsp != newSettings.iframeCsp) {
iframe.csp = newSettings.iframeCsp; iframe.csp = newSettings.iframeCsp;
} }
if (settings.iframeSandbox != newSettings.iframeSandbox) { if (settings!.iframeSandbox != newSettings.iframeSandbox) {
var sandbox = newSettings.iframeSandbox; var sandbox = newSettings.iframeSandbox;
if (sandbox != null && sandbox != Sandbox.ALLOW_ALL) { if (sandbox != null && sandbox != Sandbox.ALLOW_ALL) {
iframe.setAttribute( iframe.setAttribute(
@ -449,7 +484,7 @@ class InAppWebViewWebElement implements Disposable {
} }
Future<Map<String, dynamic>> getSettings() async { Future<Map<String, dynamic>> getSettings() async {
return settings.toMap(); return settings!.toMap();
} }
void onLoadStart(String url) async { void onLoadStart(String url) async {
@ -569,7 +604,7 @@ class InAppWebViewWebElement implements Disposable {
void dispose() { void dispose() {
_channel?.setMethodCallHandler(null); _channel?.setMethodCallHandler(null);
_channel = null; _channel = null;
iframe.remove(); iframeContainer.remove();
if (WebPlatformManager.webViews.containsKey(_viewId)) { if (WebPlatformManager.webViews.containsKey(_viewId)) {
WebPlatformManager.webViews.remove(_viewId); WebPlatformManager.webViews.remove(_viewId);
} }

View File

@ -19,7 +19,7 @@ class FlutterInAppWebViewWebPlatform {
var webView = var webView =
InAppWebViewWebElement(viewId: viewId, messenger: registrar); InAppWebViewWebElement(viewId: viewId, messenger: registrar);
WebPlatformManager.webViews.putIfAbsent(viewId, () => webView); WebPlatformManager.webViews.putIfAbsent(viewId, () => webView);
return webView.iframe; return webView.iframeContainer;
}); });
} }

View File

@ -54,6 +54,20 @@ public class HeadlessInAppWebView : Disposable {
return nil return nil
} }
public func disposeAndGetFlutterWebView(withFrame frame: CGRect) -> FlutterWebViewController? {
let newFlutterWebView = flutterWebView
if let view = flutterWebView?.view() {
// restore WebView frame and alpha
view.frame = frame
view.alphaValue = 1.0
// remove from parent
view.removeFromSuperview()
dispose()
}
return newFlutterWebView
}
public func dispose() { public func dispose() {
channelDelegate?.dispose() channelDelegate?.dispose()
channelDelegate = nil channelDelegate = nil

View File

@ -25,6 +25,13 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
public func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView { public func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView {
let arguments = args as? NSDictionary let arguments = args as? NSDictionary
if let headlessWebViewId = arguments?["headlessWebViewId"] as? String,
let headlessWebView = HeadlessInAppWebViewManager.webViews[headlessWebViewId],
let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: .zero) {
return platformView.view()
}
let webviewController = FlutterWebViewController(registrar: registrar!, let webviewController = FlutterWebViewController(registrar: registrar!,
withFrame: .zero, withFrame: .zero,
viewIdentifier: viewId, viewIdentifier: viewId,

View File

@ -1073,9 +1073,11 @@ public class InAppWebView: WKWebView, WKUIDelegate,
let origin = "\(origin.protocol)://\(origin.host)\(origin.port != 0 ? ":" + String(origin.port) : "")" let origin = "\(origin.protocol)://\(origin.host)\(origin.port != 0 ? ":" + String(origin.port) : "")"
let permissionRequest = PermissionRequest(origin: origin, resources: [type.rawValue], frame: frame) let permissionRequest = PermissionRequest(origin: origin, resources: [type.rawValue], frame: frame)
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.PermissionRequestCallback() let callback = WebViewChannelDelegate.PermissionRequestCallback()
callback.nonNullSuccess = { (response: PermissionResponse) in callback.nonNullSuccess = { (response: PermissionResponse) in
if let action = response.action { if let action = response.action {
decisionHandlerCalled = true
switch action { switch action {
case 1: case 1:
decisionHandler(.grant) decisionHandler(.grant)
@ -1091,7 +1093,10 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: PermissionResponse?) in callback.defaultBehaviour = { (response: PermissionResponse?) in
decisionHandler(.deny) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.deny)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1157,13 +1162,18 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return return
} }
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.ShouldOverrideUrlLoadingCallback() let callback = WebViewChannelDelegate.ShouldOverrideUrlLoadingCallback()
callback.nonNullSuccess = { (response: WKNavigationActionPolicy) in callback.nonNullSuccess = { (response: WKNavigationActionPolicy) in
decisionHandlerCalled = true
decisionHandler(response) decisionHandler(response)
return false return false
} }
callback.defaultBehaviour = { (response: WKNavigationActionPolicy?) in callback.defaultBehaviour = { (response: WKNavigationActionPolicy?) in
decisionHandler(.allow) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.allow)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1189,13 +1199,18 @@ public class InAppWebView: WKWebView, WKUIDelegate,
let useOnNavigationResponse = settings?.useOnNavigationResponse let useOnNavigationResponse = settings?.useOnNavigationResponse
if useOnNavigationResponse != nil, useOnNavigationResponse! { if useOnNavigationResponse != nil, useOnNavigationResponse! {
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.NavigationResponseCallback() let callback = WebViewChannelDelegate.NavigationResponseCallback()
callback.nonNullSuccess = { (response: WKNavigationResponsePolicy) in callback.nonNullSuccess = { (response: WKNavigationResponsePolicy) in
decisionHandlerCalled = true
decisionHandler(response) decisionHandler(response)
return false return false
} }
callback.defaultBehaviour = { (response: WKNavigationResponsePolicy?) in callback.defaultBehaviour = { (response: WKNavigationResponsePolicy?) in
decisionHandler(.allow) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.allow)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1210,7 +1225,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
} }
if let useOnDownloadStart = settings?.useOnDownloadStart, useOnDownloadStart { if let useOnDownloadStart = settings?.useOnDownloadStart, useOnDownloadStart {
if #available(macOS 11.3, *), !navigationResponse.canShowMIMEType { if #available(macOS 11.3, *), !navigationResponse.canShowMIMEType, useOnNavigationResponse == nil || !useOnNavigationResponse! {
decisionHandler(.download) decisionHandler(.download)
return return
} else { } else {
@ -1310,6 +1325,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return return
} }
var completionHandlerCalled = false
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic || if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault || challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest || challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest ||
@ -1323,6 +1339,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedHttpAuthRequestCallback() let callback = WebViewChannelDelegate.ReceivedHttpAuthRequestCallback()
callback.nonNullSuccess = { (response: HttpAuthResponse) in callback.nonNullSuccess = { (response: HttpAuthResponse) in
if let action = response.action { if let action = response.action {
completionHandlerCalled = true
switch action { switch action {
case 0: case 0:
InAppWebView.credentialsProposed = [] InAppWebView.credentialsProposed = []
@ -1371,7 +1388,10 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: HttpAuthResponse?) in callback.defaultBehaviour = { (response: HttpAuthResponse?) in
completionHandler(.performDefaultHandling, nil) if !completionHandlerCalled {
completionHandlerCalled = true
completionHandler(.performDefaultHandling, nil)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1398,6 +1418,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedServerTrustAuthRequestCallback() let callback = WebViewChannelDelegate.ReceivedServerTrustAuthRequestCallback()
callback.nonNullSuccess = { (response: ServerTrustAuthResponse) in callback.nonNullSuccess = { (response: ServerTrustAuthResponse) in
if let action = response.action { if let action = response.action {
completionHandlerCalled = true
switch action { switch action {
case 0: case 0:
InAppWebView.credentialsProposed = [] InAppWebView.credentialsProposed = []
@ -1418,7 +1439,10 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: ServerTrustAuthResponse?) in callback.defaultBehaviour = { (response: ServerTrustAuthResponse?) in
completionHandler(.performDefaultHandling, nil) if !completionHandlerCalled {
completionHandlerCalled = true
completionHandler(.performDefaultHandling, nil)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1435,6 +1459,7 @@ public class InAppWebView: WKWebView, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedClientCertRequestCallback() let callback = WebViewChannelDelegate.ReceivedClientCertRequestCallback()
callback.nonNullSuccess = { (response: ClientCertResponse) in callback.nonNullSuccess = { (response: ClientCertResponse) in
if let action = response.action { if let action = response.action {
completionHandlerCalled = true
switch action { switch action {
case 0: case 0:
completionHandler(.cancelAuthenticationChallenge, nil) completionHandler(.cancelAuthenticationChallenge, nil)
@ -1471,7 +1496,10 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: ClientCertResponse?) in callback.defaultBehaviour = { (response: ClientCertResponse?) in
completionHandler(.performDefaultHandling, nil) if !completionHandlerCalled {
completionHandlerCalled = true
completionHandler(.performDefaultHandling, nil)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))
@ -1573,9 +1601,12 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return return
} }
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsAlertCallback() let callback = WebViewChannelDelegate.JsAlertCallback()
callback.nonNullSuccess = { (response: JsAlertResponse) in callback.nonNullSuccess = { (response: JsAlertResponse) in
if response.handledByClient { if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1 let action = response.action ?? 1
switch action { switch action {
case 0: case 0:
@ -1589,14 +1620,20 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: JsAlertResponse?) in callback.defaultBehaviour = { (response: JsAlertResponse?) in
let responseMessage = response?.message if !completionHandlerCalled {
let confirmButtonTitle = response?.confirmButtonTitle completionHandlerCalled = true
self.createAlertDialog(message: message, responseMessage: responseMessage, let responseMessage = response?.message
confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler) let confirmButtonTitle = response?.confirmButtonTitle
self.createAlertDialog(message: message, responseMessage: responseMessage,
confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler)
}
} }
callback.error = { (code: String, message: String?, details: Any?) in callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) if !completionHandlerCalled {
completionHandler() completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler()
}
} }
if let channelDelegate = channelDelegate { if let channelDelegate = channelDelegate {
@ -1622,9 +1659,12 @@ public class InAppWebView: WKWebView, WKUIDelegate,
public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (Bool) -> Void) { completionHandler: @escaping (Bool) -> Void) {
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsConfirmCallback() let callback = WebViewChannelDelegate.JsConfirmCallback()
callback.nonNullSuccess = { (response: JsConfirmResponse) in callback.nonNullSuccess = { (response: JsConfirmResponse) in
if response.handledByClient { if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1 let action = response.action ?? 1
switch action { switch action {
case 0: case 0:
@ -1641,14 +1681,20 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: JsConfirmResponse?) in callback.defaultBehaviour = { (response: JsConfirmResponse?) in
let responseMessage = response?.message if !completionHandlerCalled {
let confirmButtonTitle = response?.confirmButtonTitle completionHandlerCalled = true
let cancelButtonTitle = response?.cancelButtonTitle let responseMessage = response?.message
self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler) let confirmButtonTitle = response?.confirmButtonTitle
let cancelButtonTitle = response?.cancelButtonTitle
self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler)
}
} }
callback.error = { (code: String, message: String?, details: Any?) in callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) if !completionHandlerCalled {
completionHandler(false) completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler(false)
}
} }
if let channelDelegate = channelDelegate { if let channelDelegate = channelDelegate {
@ -1678,9 +1724,13 @@ public class InAppWebView: WKWebView, WKUIDelegate,
public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo, public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (String?) -> Void) { completionHandler: @escaping (String?) -> Void) {
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsPromptCallback() let callback = WebViewChannelDelegate.JsPromptCallback()
callback.nonNullSuccess = { (response: JsPromptResponse) in callback.nonNullSuccess = { (response: JsPromptResponse) in
if response.handledByClient { if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1 let action = response.action ?? 1
switch action { switch action {
case 0: case 0:
@ -1697,16 +1747,22 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return true return true
} }
callback.defaultBehaviour = { (response: JsPromptResponse?) in callback.defaultBehaviour = { (response: JsPromptResponse?) in
let responseMessage = response?.message if !completionHandlerCalled {
let confirmButtonTitle = response?.confirmButtonTitle completionHandlerCalled = true
let cancelButtonTitle = response?.cancelButtonTitle let responseMessage = response?.message
let value = response?.value let confirmButtonTitle = response?.confirmButtonTitle
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, let cancelButtonTitle = response?.cancelButtonTitle
cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler) let value = response?.value
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle,
cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler)
}
} }
callback.error = { (code: String, message: String?, details: Any?) in callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) if !completionHandlerCalled {
completionHandler(nil) completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler(nil)
}
} }
if let channelDelegate = channelDelegate { if let channelDelegate = channelDelegate {
@ -1768,13 +1824,18 @@ public class InAppWebView: WKWebView, WKUIDelegate,
return return
} }
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.ShouldAllowDeprecatedTLSCallback() let callback = WebViewChannelDelegate.ShouldAllowDeprecatedTLSCallback()
callback.nonNullSuccess = { (action: Bool) in callback.nonNullSuccess = { (action: Bool) in
decisionHandlerCalled = true
decisionHandler(action) decisionHandler(action)
return false return false
} }
callback.defaultBehaviour = { (action: Bool?) in callback.defaultBehaviour = { (action: Bool?) in
decisionHandler(false) if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(false)
}
} }
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? "")) print(code + ", " + (message ?? ""))

View File

@ -834,6 +834,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return PermissionResponse.fromMap(map: obj as? [String:Any?]) return PermissionResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onPermissionRequest(request: PermissionRequest, callback: PermissionRequestCallback) { public func onPermissionRequest(request: PermissionRequest, callback: PermissionRequestCallback) {
@ -854,6 +858,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return WKNavigationActionPolicy.cancel return WKNavigationActionPolicy.cancel
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func shouldOverrideUrlLoading(navigationAction: WKNavigationAction, callback: ShouldOverrideUrlLoadingCallback) { public func shouldOverrideUrlLoading(navigationAction: WKNavigationAction, callback: ShouldOverrideUrlLoadingCallback) {
@ -905,6 +913,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return HttpAuthResponse.fromMap(map: obj as? [String:Any?]) return HttpAuthResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onReceivedHttpAuthRequest(challenge: HttpAuthenticationChallenge, callback: ReceivedHttpAuthRequestCallback) { public func onReceivedHttpAuthRequest(challenge: HttpAuthenticationChallenge, callback: ReceivedHttpAuthRequestCallback) {
@ -922,6 +934,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return ServerTrustAuthResponse.fromMap(map: obj as? [String:Any?]) return ServerTrustAuthResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onReceivedServerTrustAuthRequest(challenge: ServerTrustChallenge, callback: ReceivedServerTrustAuthRequestCallback) { public func onReceivedServerTrustAuthRequest(challenge: ServerTrustChallenge, callback: ReceivedServerTrustAuthRequestCallback) {
@ -939,6 +955,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return ClientCertResponse.fromMap(map: obj as? [String:Any?]) return ClientCertResponse.fromMap(map: obj as? [String:Any?])
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onReceivedClientCertRequest(challenge: ClientCertChallenge, callback: ReceivedClientCertRequestCallback) { public func onReceivedClientCertRequest(challenge: ClientCertChallenge, callback: ReceivedClientCertRequestCallback) {
@ -1013,6 +1033,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return WKNavigationResponsePolicy.cancel return WKNavigationResponsePolicy.cancel
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func onNavigationResponse(navigationResponse: WKNavigationResponse, callback: NavigationResponseCallback) { public func onNavigationResponse(navigationResponse: WKNavigationResponse, callback: NavigationResponseCallback) {
@ -1033,6 +1057,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return false return false
} }
} }
deinit {
self.defaultBehaviour(nil)
}
} }
public func shouldAllowDeprecatedTLS(challenge: URLAuthenticationChallenge, callback: ShouldAllowDeprecatedTLSCallback) { public func shouldAllowDeprecatedTLS(challenge: URLAuthenticationChallenge, callback: ShouldAllowDeprecatedTLSCallback) {

View File

@ -1,6 +1,6 @@
name: flutter_inappwebview name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
version: 6.0.0-beta.3 version: 6.0.0-beta.4
homepage: https://inappwebview.dev/ homepage: https://inappwebview.dev/
repository: https://github.com/pichillilorenzo/flutter_inappwebview repository: https://github.com/pichillilorenzo/flutter_inappwebview
issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues