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
- Added MacOS support
@ -46,6 +50,12 @@
- Removed `URLProtectionSpace.iosIsProxy` property
- `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
- Fixed iOS `toolbarTopTintColor` InAppBrowser option

View File

@ -2,6 +2,8 @@ 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;
@ -24,15 +26,25 @@ public class FlutterWebViewFactory extends PlatformViewFactory {
@Override
public PlatformView create(Context context, int id, Object args) {
HashMap<String, Object> params = (HashMap<String, Object>) args;
PlatformWebView flutterWebView;
WebViewImplementation implementation = WebViewImplementation.fromValue((Integer) params.get("implementation"));
switch (implementation) {
case NATIVE:
default:
flutterWebView = new FlutterWebView(plugin, context, id, params);
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);
}
flutterWebView.makeInitialLoad(params);
return flutterWebView;
}

View File

@ -2,18 +2,25 @@ package com.pichillilorenzo.flutter_inappwebview;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.http.SslCertificate;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.pichillilorenzo.flutter_inappwebview.types.Size2D;
import com.pichillilorenzo.flutter_inappwebview.types.SyncBaseCallbackResultImpl;
import org.json.JSONArray;
@ -242,6 +249,31 @@ public class Util {
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) {
try {
Class.forName(className);

View File

@ -9,13 +9,12 @@ import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
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.Size2D;
import com.pichillilorenzo.flutter_inappwebview.webview.in_app_webview.FlutterWebView;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class HeadlessInAppWebView implements Disposable {
@ -54,15 +53,16 @@ public class HeadlessInAppWebView implements Disposable {
ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0);
if (mainView != null && flutterWebView != null) {
View view = flutterWebView.getView();
final Map<String, Object> initialSize = (Map<String, Object>) params.get("initialSize");
Size2D size = Size2D.fromMap(initialSize);
if (size != null) {
if (view != null) {
final Map<String, Object> initialSize = (Map<String, Object>) params.get("initialSize");
Size2D size = Size2D.fromMap(initialSize);
if (size == null) {
size = new Size2D(-1, -1);
}
setSize(size);
} else {
view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mainView.addView(view, 0);
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) {
if (flutterWebView != null && flutterWebView.webView != null) {
View view = flutterWebView.getView();
float scale = Util.getPixelDensity(view.getContext());
view.setLayoutParams(new FrameLayout.LayoutParams((int) (size.getWidth() * scale), (int) (size.getHeight() * scale)));
if (view != null) {
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() {
if (flutterWebView != null && flutterWebView.webView != null) {
View view = flutterWebView.getView();
float scale = Util.getPixelDensity(view.getContext());
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
return new Size2D(layoutParams.width / scale, layoutParams.height / scale);
if (view != null) {
float scale = Util.getPixelDensity(view.getContext());
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;
}
@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() {
if (channelDelegate != null) {
channelDelegate.dispose();
@ -100,7 +133,10 @@ public class HeadlessInAppWebView implements Disposable {
if (contentView != null) {
ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0);
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 'convert_to_inappwebview.dart';
import 'take_screenshot.dart';
import 'custom_size.dart';
import 'run_and_dispose.dart';
@ -11,5 +12,6 @@ void main() {
takeScreenshot();
customSize();
setGetSettings();
convertToInAppWebView();
});
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,12 +3,11 @@
export "FLUTTER_ROOT=/Users/lorenzopichilli/fvm/versions/2.10.4"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
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_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ=="
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
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: [
InAppWebView(
key: webViewKey,
headlessWebView: headlessWebView,
initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev')),
URLRequest(url: Uri.parse('https://google.com')),
// initialUrlRequest:
// URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')),
// initialFile: "assets/index.html",
@ -125,6 +126,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) async {
webViewController = controller;
print(await controller.getUrl());
},
onLoadStart: (controller, url) async {
setState(() {

View File

@ -15,6 +15,13 @@ import 'package:pointer_interceptor/pointer_interceptor.dart';
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 {
WidgetsFlutterBinding.ensureInitialized();
// await Permission.camera.request();
@ -29,6 +36,10 @@ Future main() async {
await localhostServer.start();
}
headlessWebView.run();
await Future.delayed(Duration(seconds: 1));
runApp(MyApp());
}

View File

@ -61,6 +61,19 @@ public class HeadlessInAppWebView : Disposable {
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() {
channelDelegate?.dispose()
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 {
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!,
withFrame: frame,
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 permissionRequest = PermissionRequest(origin: origin, resources: [type.rawValue], frame: frame)
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.PermissionRequestCallback()
callback.nonNullSuccess = { (response: PermissionResponse) in
if let action = response.action {
decisionHandlerCalled = true
switch action {
case 1:
decisionHandler(.grant)
@ -1588,7 +1590,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true
}
callback.defaultBehaviour = { (response: PermissionResponse?) in
decisionHandler(.deny)
if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.deny)
}
}
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
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 permissionRequest = PermissionRequest(origin: origin, resources: ["deviceOrientationAndMotion"], frame: frame)
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.PermissionRequestCallback()
callback.nonNullSuccess = { (response: PermissionResponse) in
if let action = response.action {
decisionHandlerCalled = true
switch action {
case 1:
decisionHandler(.grant)
@ -1628,7 +1635,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true
}
callback.defaultBehaviour = { (response: PermissionResponse?) in
decisionHandler(.deny)
if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.deny)
}
}
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? ""))
@ -1694,13 +1704,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return
}
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.ShouldOverrideUrlLoadingCallback()
callback.nonNullSuccess = { (response: WKNavigationActionPolicy) in
decisionHandlerCalled = true
decisionHandler(response)
return false
}
callback.defaultBehaviour = { (response: WKNavigationActionPolicy?) in
decisionHandler(.allow)
if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.allow)
}
}
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? ""))
@ -1726,13 +1741,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let useOnNavigationResponse = settings?.useOnNavigationResponse
if useOnNavigationResponse != nil, useOnNavigationResponse! {
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.NavigationResponseCallback()
callback.nonNullSuccess = { (response: WKNavigationResponsePolicy) in
decisionHandlerCalled = true
decisionHandler(response)
return false
}
callback.defaultBehaviour = { (response: WKNavigationResponsePolicy?) in
decisionHandler(.allow)
if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(.allow)
}
}
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? ""))
@ -1747,7 +1767,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
}
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)
return
} else {
@ -1856,6 +1876,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return
}
var completionHandlerCalled = false
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest ||
@ -1869,6 +1890,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedHttpAuthRequestCallback()
callback.nonNullSuccess = { (response: HttpAuthResponse) in
if let action = response.action {
completionHandlerCalled = true
switch action {
case 0:
InAppWebView.credentialsProposed = []
@ -1917,7 +1939,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true
}
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
print(code + ", " + (message ?? ""))
@ -1944,6 +1969,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedServerTrustAuthRequestCallback()
callback.nonNullSuccess = { (response: ServerTrustAuthResponse) in
if let action = response.action {
completionHandlerCalled = true
switch action {
case 0:
InAppWebView.credentialsProposed = []
@ -1964,7 +1990,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true
}
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
print(code + ", " + (message ?? ""))
@ -1981,6 +2010,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
let callback = WebViewChannelDelegate.ReceivedClientCertRequestCallback()
callback.nonNullSuccess = { (response: ClientCertResponse) in
if let action = response.action {
completionHandlerCalled = true
switch action {
case 0:
completionHandler(.cancelAuthenticationChallenge, nil)
@ -2017,7 +2047,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true
}
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
print(code + ", " + (message ?? ""))
@ -2102,9 +2135,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return
}
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsAlertCallback()
callback.nonNullSuccess = { (response: JsAlertResponse) in
if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1
switch action {
case 0:
@ -2118,14 +2154,20 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true
}
callback.defaultBehaviour = { (response: JsAlertResponse?) in
let responseMessage = response?.message
let confirmButtonTitle = response?.confirmButtonTitle
self.createAlertDialog(message: message, responseMessage: responseMessage,
confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler)
if !completionHandlerCalled {
completionHandlerCalled = true
let responseMessage = response?.message
let confirmButtonTitle = response?.confirmButtonTitle
self.createAlertDialog(message: message, responseMessage: responseMessage,
confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler)
}
}
callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? ""))
completionHandler()
if !completionHandlerCalled {
completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler()
}
}
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,
completionHandler: @escaping (Bool) -> Void) {
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsConfirmCallback()
callback.nonNullSuccess = { (response: JsConfirmResponse) in
if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1
switch action {
case 0:
@ -2178,14 +2223,20 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true
}
callback.defaultBehaviour = { (response: JsConfirmResponse?) in
let responseMessage = response?.message
let confirmButtonTitle = response?.confirmButtonTitle
let cancelButtonTitle = response?.cancelButtonTitle
self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler)
if !completionHandlerCalled {
completionHandlerCalled = true
let responseMessage = response?.message
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
print(code + ", " + (message ?? ""))
completionHandler(false)
if !completionHandlerCalled {
completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler(false)
}
}
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,
completionHandler: @escaping (String?) -> Void) {
var completionHandlerCalled = false
let callback = WebViewChannelDelegate.JsPromptCallback()
callback.nonNullSuccess = { (response: JsPromptResponse) in
if response.handledByClient {
completionHandlerCalled = true
let action = response.action ?? 1
switch action {
case 0:
@ -2249,16 +2304,22 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return true
}
callback.defaultBehaviour = { (response: JsPromptResponse?) in
let responseMessage = response?.message
let confirmButtonTitle = response?.confirmButtonTitle
let cancelButtonTitle = response?.cancelButtonTitle
let value = response?.value
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle,
cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler)
if !completionHandlerCalled {
completionHandlerCalled = true
let responseMessage = response?.message
let confirmButtonTitle = response?.confirmButtonTitle
let cancelButtonTitle = response?.cancelButtonTitle
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
print(code + ", " + (message ?? ""))
completionHandler(nil)
if !completionHandlerCalled {
completionHandlerCalled = true
print(code + ", " + (message ?? ""))
completionHandler(nil)
}
}
if let channelDelegate = channelDelegate {
@ -2375,13 +2436,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
return
}
var decisionHandlerCalled = false
let callback = WebViewChannelDelegate.ShouldAllowDeprecatedTLSCallback()
callback.nonNullSuccess = { (action: Bool) in
decisionHandlerCalled = true
decisionHandler(action)
return false
}
callback.defaultBehaviour = { (action: Bool?) in
decisionHandler(false)
if !decisionHandlerCalled {
decisionHandlerCalled = true
decisionHandler(false)
}
}
callback.error = { [weak callback] (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? ""))

View File

@ -740,6 +740,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return JsAlertResponse.fromMap(map: obj as? [String:Any?])
}
}
deinit {
self.defaultBehaviour(nil)
}
}
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?])
}
}
deinit {
self.defaultBehaviour(nil)
}
}
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?])
}
}
deinit {
self.defaultBehaviour(nil)
}
}
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?])
}
}
deinit {
self.defaultBehaviour(nil)
}
}
public func onPermissionRequest(request: PermissionRequest, callback: PermissionRequestCallback) {
@ -871,6 +887,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return WKNavigationActionPolicy.cancel
}
}
deinit {
self.defaultBehaviour(nil)
}
}
public func shouldOverrideUrlLoading(navigationAction: WKNavigationAction, callback: ShouldOverrideUrlLoadingCallback) {
@ -922,6 +942,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return HttpAuthResponse.fromMap(map: obj as? [String:Any?])
}
}
deinit {
self.defaultBehaviour(nil)
}
}
public func onReceivedHttpAuthRequest(challenge: HttpAuthenticationChallenge, callback: ReceivedHttpAuthRequestCallback) {
@ -939,6 +963,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return ServerTrustAuthResponse.fromMap(map: obj as? [String:Any?])
}
}
deinit {
self.defaultBehaviour(nil)
}
}
public func onReceivedServerTrustAuthRequest(challenge: ServerTrustChallenge, callback: ReceivedServerTrustAuthRequestCallback) {
@ -956,6 +984,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return ClientCertResponse.fromMap(map: obj as? [String:Any?])
}
}
deinit {
self.defaultBehaviour(nil)
}
}
public func onReceivedClientCertRequest(challenge: ClientCertChallenge, callback: ReceivedClientCertRequestCallback) {
@ -1030,6 +1062,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return WKNavigationResponsePolicy.cancel
}
}
deinit {
self.defaultBehaviour(nil)
}
}
public func onNavigationResponse(navigationResponse: WKNavigationResponse, callback: NavigationResponseCallback) {
@ -1050,6 +1086,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
return false
}
}
deinit {
self.defaultBehaviour(nil)
}
}
public func shouldAllowDeprecatedTLS(challenge: URLAuthenticationChallenge, callback: ShouldAllowDeprecatedTLSCallback) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -54,6 +54,20 @@ public class HeadlessInAppWebView : Disposable {
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() {
channelDelegate?.dispose()
channelDelegate = nil

View File

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

View File

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

View File

@ -1,6 +1,6 @@
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.
version: 6.0.0-beta.3
version: 6.0.0-beta.4
homepage: https://inappwebview.dev/
repository: https://github.com/pichillilorenzo/flutter_inappwebview
issue_tracker: https://github.com/pichillilorenzo/flutter_inappwebview/issues