import 'dart:collection';
import 'dart:convert';
import 'dart:core';
import 'dart:developer' as developer;
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart';

import '../in_app_browser/in_app_browser.dart';
import '../print_job/main.dart';
import '../web_message/main.dart';
import '../web_storage/web_storage.dart';

import '_static_channel.dart';
import 'headless_in_app_webview.dart';

/// Object specifying creation parameters for creating a [IOSInAppWebViewController].
///
/// When adding additional fields make sure they can be null or have a default
/// value to avoid breaking changes. See [PlatformInAppWebViewControllerCreationParams] for
/// more information.
@immutable
class IOSInAppWebViewControllerCreationParams
    extends PlatformInAppWebViewControllerCreationParams {
  /// Creates a new [IOSInAppWebViewControllerCreationParams] instance.
  const IOSInAppWebViewControllerCreationParams({
    required super.id,
    super.webviewParams,
  });

  /// Creates a [IOSInAppWebViewControllerCreationParams] instance based on [PlatformInAppWebViewControllerCreationParams].
  factory IOSInAppWebViewControllerCreationParams.fromPlatformInAppWebViewControllerCreationParams(
    // Recommended placeholder to prevent being broken by platform interface.
    // ignore: avoid_unused_constructor_parameters
    PlatformInAppWebViewControllerCreationParams params,
  ) {
    return IOSInAppWebViewControllerCreationParams(
      id: params.id,
      webviewParams: params.webviewParams,
    );
  }
}

///Controls a WebView, such as an [InAppWebView] widget instance, a [IOSHeadlessInAppWebView] instance or [IOSInAppBrowser] WebView instance.
///
///If you are using the [InAppWebView] widget, an [InAppWebViewController] instance can be obtained by setting the [InAppWebView.onWebViewCreated]
///callback. Instead, if you are using an [IOSInAppBrowser] instance, you can get it through the [IOSInAppBrowser.webViewController] attribute.
class IOSInAppWebViewController extends PlatformInAppWebViewController
    with ChannelController {
  static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL;

  // List of properties to be saved and restored for keep alive feature
  Map<String, Function> _javaScriptHandlersMap = HashMap<String, Function>();
  Map<UserScriptInjectionTime, List<UserScript>> _userScripts = {
    UserScriptInjectionTime.AT_DOCUMENT_START: <UserScript>[],
    UserScriptInjectionTime.AT_DOCUMENT_END: <UserScript>[],
  };
  Set<String> _webMessageListenerObjNames = Set();
  Map<String, ScriptHtmlTagAttributes> _injectedScriptsFromURL = {};
  Set<IOSWebMessageChannel> _webMessageChannels = Set();
  Set<IOSWebMessageListener> _webMessageListeners = Set();

  // static map that contains the properties to be saved and restored for keep alive feature
  static final Map<InAppWebViewKeepAlive, InAppWebViewControllerKeepAliveProps?>
  _keepAliveMap = {};

  IOSInAppBrowser? _inAppBrowser;

  PlatformInAppBrowserEvents? get _inAppBrowserEventHandler =>
      _inAppBrowser?.eventHandler;

  dynamic _controllerFromPlatform;

  @override
  late IOSWebStorage webStorage;

  IOSInAppWebViewController(PlatformInAppWebViewControllerCreationParams params)
    : super.implementation(
        params is IOSInAppWebViewControllerCreationParams
            ? params
            : IOSInAppWebViewControllerCreationParams.fromPlatformInAppWebViewControllerCreationParams(
                params,
              ),
      ) {
    channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id');
    handler = handleMethod;
    initMethodCallHandler();

    final initialUserScripts = webviewParams?.initialUserScripts;
    if (initialUserScripts != null) {
      for (final userScript in initialUserScripts) {
        if (userScript.injectionTime ==
            UserScriptInjectionTime.AT_DOCUMENT_START) {
          this._userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.add(
            userScript,
          );
        } else {
          this._userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.add(
            userScript,
          );
        }
      }
    }

    this._init(params);
  }

  static final IOSInAppWebViewController _staticValue =
      IOSInAppWebViewController(
        IOSInAppWebViewControllerCreationParams(id: null),
      );

  factory IOSInAppWebViewController.static() {
    return _staticValue;
  }

  IOSInAppWebViewController.fromInAppBrowser(
    PlatformInAppWebViewControllerCreationParams params,
    MethodChannel channel,
    IOSInAppBrowser inAppBrowser,
    UnmodifiableListView<UserScript>? initialUserScripts,
  ) : super.implementation(
        params is IOSInAppWebViewControllerCreationParams
            ? params
            : IOSInAppWebViewControllerCreationParams.fromPlatformInAppWebViewControllerCreationParams(
                params,
              ),
      ) {
    this.channel = channel;
    this._inAppBrowser = inAppBrowser;

    if (initialUserScripts != null) {
      for (final userScript in initialUserScripts) {
        if (userScript.injectionTime ==
            UserScriptInjectionTime.AT_DOCUMENT_START) {
          this._userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.add(
            userScript,
          );
        } else {
          this._userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.add(
            userScript,
          );
        }
      }
    }
    this._init(params);
  }

  void _init(PlatformInAppWebViewControllerCreationParams params) {
    _controllerFromPlatform =
        params.webviewParams?.controllerFromPlatform?.call(this) ?? this;

    webStorage = IOSWebStorage(
      IOSWebStorageCreationParams(
        localStorage: IOSLocalStorage.defaultStorage(controller: this),
        sessionStorage: IOSSessionStorage.defaultStorage(controller: this),
      ),
    );

    if (params.webviewParams is PlatformInAppWebViewWidgetCreationParams) {
      final keepAlive =
          (params.webviewParams as PlatformInAppWebViewWidgetCreationParams)
              .keepAlive;
      if (keepAlive != null) {
        InAppWebViewControllerKeepAliveProps? props = _keepAliveMap[keepAlive];
        if (props == null) {
          // save controller properties to restore it later
          _keepAliveMap[keepAlive] = InAppWebViewControllerKeepAliveProps(
            injectedScriptsFromURL: _injectedScriptsFromURL,
            javaScriptHandlersMap: _javaScriptHandlersMap,
            userScripts: _userScripts,
            webMessageListenerObjNames: _webMessageListenerObjNames,
            webMessageChannels: _webMessageChannels,
            webMessageListeners: _webMessageListeners,
          );
        } else {
          // restore controller properties
          _injectedScriptsFromURL = props.injectedScriptsFromURL;
          _javaScriptHandlersMap = props.javaScriptHandlersMap;
          _userScripts = props.userScripts;
          _webMessageListenerObjNames = props.webMessageListenerObjNames;
          _webMessageChannels =
              props.webMessageChannels as Set<IOSWebMessageChannel>;
          _webMessageListeners =
              props.webMessageListeners as Set<IOSWebMessageListener>;
        }
      }
    }
  }

  _debugLog(String method, dynamic args) {
    debugLog(
      className: this.runtimeType.toString(),
      name: _inAppBrowser == null
          ? "WebView"
          : _inAppBrowser.runtimeType.toString(),
      id: (getViewId() ?? _inAppBrowser?.id).toString(),
      debugLoggingSettings: PlatformInAppWebViewController.debugLoggingSettings,
      method: method,
      args: args,
    );
  }

  Future<dynamic> _handleMethod(MethodCall call) async {
    if (PlatformInAppWebViewController.debugLoggingSettings.enabled &&
        call.method != "onCallJsHandler") {
      _debugLog(call.method, call.arguments);
    }

    switch (call.method) {
      case "onLoadStart":
        _injectedScriptsFromURL.clear();
        if ((webviewParams != null && webviewParams!.onLoadStart != null) ||
            _inAppBrowserEventHandler != null) {
          String? url = call.arguments["url"];
          WebUri? uri = url != null ? WebUri(url) : null;
          if (webviewParams != null && webviewParams!.onLoadStart != null)
            webviewParams!.onLoadStart!(_controllerFromPlatform, uri);
          else
            _inAppBrowserEventHandler!.onLoadStart(uri);
        }
        break;
      case "onLoadStop":
        if ((webviewParams != null && webviewParams!.onLoadStop != null) ||
            _inAppBrowserEventHandler != null) {
          String? url = call.arguments["url"];
          WebUri? uri = url != null ? WebUri(url) : null;
          if (webviewParams != null && webviewParams!.onLoadStop != null)
            webviewParams!.onLoadStop!(_controllerFromPlatform, uri);
          else
            _inAppBrowserEventHandler!.onLoadStop(uri);
        }
        break;
      case "onReceivedError":
        if ((webviewParams != null &&
                (webviewParams!.onReceivedError != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.onLoadError != null)) ||
            _inAppBrowserEventHandler != null) {
          WebResourceRequest request = WebResourceRequest.fromMap(
            call.arguments["request"].cast<String, dynamic>(),
          )!;
          WebResourceError error = WebResourceError.fromMap(
            call.arguments["error"].cast<String, dynamic>(),
          )!;
          var isForMainFrame = request.isForMainFrame ?? false;

          if (webviewParams != null) {
            if (webviewParams!.onReceivedError != null)
              webviewParams!.onReceivedError!(
                _controllerFromPlatform,
                request,
                error,
              );
            else if (isForMainFrame) {
              // ignore: deprecated_member_use_from_same_package
              webviewParams!.onLoadError!(
                _controllerFromPlatform,
                request.url,
                error.type.toNativeValue() ?? -1,
                error.description,
              );
            }
          } else {
            if (isForMainFrame) {
              _inAppBrowserEventHandler!.onLoadError(
                request.url,
                error.type.toNativeValue() ?? -1,
                error.description,
              );
            }
            _inAppBrowserEventHandler!.onReceivedError(request, error);
          }
        }
        break;
      case "onReceivedHttpError":
        if ((webviewParams != null &&
                (webviewParams!.onReceivedHttpError != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.onLoadHttpError != null)) ||
            _inAppBrowserEventHandler != null) {
          WebResourceRequest request = WebResourceRequest.fromMap(
            call.arguments["request"].cast<String, dynamic>(),
          )!;
          WebResourceResponse errorResponse = WebResourceResponse.fromMap(
            call.arguments["errorResponse"].cast<String, dynamic>(),
          )!;
          var isForMainFrame = request.isForMainFrame ?? false;

          if (webviewParams != null) {
            if (webviewParams!.onReceivedHttpError != null)
              webviewParams!.onReceivedHttpError!(
                _controllerFromPlatform,
                request,
                errorResponse,
              );
            else if (isForMainFrame) {
              // ignore: deprecated_member_use_from_same_package
              webviewParams!.onLoadHttpError!(
                _controllerFromPlatform,
                request.url,
                errorResponse.statusCode ?? -1,
                errorResponse.reasonPhrase ?? '',
              );
            }
          } else {
            if (isForMainFrame) {
              _inAppBrowserEventHandler!.onLoadHttpError(
                request.url,
                errorResponse.statusCode ?? -1,
                errorResponse.reasonPhrase ?? '',
              );
            }
            _inAppBrowserEventHandler!.onReceivedHttpError(
              request,
              errorResponse,
            );
          }
        }
        break;
      case "onProgressChanged":
        if ((webviewParams != null &&
                webviewParams!.onProgressChanged != null) ||
            _inAppBrowserEventHandler != null) {
          int progress = call.arguments["progress"];
          if (webviewParams != null && webviewParams!.onProgressChanged != null)
            webviewParams!.onProgressChanged!(
              _controllerFromPlatform,
              progress,
            );
          else
            _inAppBrowserEventHandler!.onProgressChanged(progress);
        }
        break;
      case "shouldOverrideUrlLoading":
        if ((webviewParams != null &&
                webviewParams!.shouldOverrideUrlLoading != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          NavigationAction navigationAction = NavigationAction.fromMap(
            arguments,
          )!;

          if (webviewParams != null &&
              webviewParams!.shouldOverrideUrlLoading != null)
            return (await webviewParams!.shouldOverrideUrlLoading!(
              _controllerFromPlatform,
              navigationAction,
            ))?.toNativeValue();
          return (await _inAppBrowserEventHandler!.shouldOverrideUrlLoading(
            navigationAction,
          ))?.toNativeValue();
        }
        break;
      case "onConsoleMessage":
        if ((webviewParams != null &&
                webviewParams!.onConsoleMessage != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          ConsoleMessage consoleMessage = ConsoleMessage.fromMap(arguments)!;
          if (webviewParams != null && webviewParams!.onConsoleMessage != null)
            webviewParams!.onConsoleMessage!(
              _controllerFromPlatform,
              consoleMessage,
            );
          else
            _inAppBrowserEventHandler!.onConsoleMessage(consoleMessage);
        }
        break;
      case "onScrollChanged":
        if ((webviewParams != null && webviewParams!.onScrollChanged != null) ||
            _inAppBrowserEventHandler != null) {
          int x = call.arguments["x"];
          int y = call.arguments["y"];
          if (webviewParams != null && webviewParams!.onScrollChanged != null)
            webviewParams!.onScrollChanged!(_controllerFromPlatform, x, y);
          else
            _inAppBrowserEventHandler!.onScrollChanged(x, y);
        }
        break;
      case "onDownloadStarting":
        if ((webviewParams != null &&
                (webviewParams!.onDownloadStart != null ||
                    webviewParams!.onDownloadStartRequest != null ||
                    webviewParams!.onDownloadStarting != null)) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          DownloadStartRequest downloadStartRequest =
              DownloadStartRequest.fromMap(arguments)!;

          if (webviewParams != null) {
            if (webviewParams!.onDownloadStarting != null)
              return (await webviewParams!.onDownloadStarting!(
                _controllerFromPlatform,
                downloadStartRequest,
              ))?.toMap();
            else if (webviewParams!.onDownloadStartRequest != null)
              webviewParams!.onDownloadStartRequest!(
                _controllerFromPlatform,
                downloadStartRequest,
              );
            else {
              webviewParams!.onDownloadStart!(
                _controllerFromPlatform,
                downloadStartRequest.url,
              );
            }
          } else {
            _inAppBrowserEventHandler!.onDownloadStart(
              downloadStartRequest.url,
            );
            _inAppBrowserEventHandler!.onDownloadStartRequest(
              downloadStartRequest,
            );
            return (await _inAppBrowserEventHandler!.onDownloadStarting(
              downloadStartRequest,
            ))?.toMap();
          }
        }
        break;
      case "onLoadResourceWithCustomScheme":
        if ((webviewParams != null &&
                (webviewParams!.onLoadResourceWithCustomScheme != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.onLoadResourceCustomScheme != null)) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> requestMap = call.arguments["request"]
              .cast<String, dynamic>();
          WebResourceRequest request = WebResourceRequest.fromMap(requestMap)!;

          if (webviewParams != null) {
            if (webviewParams!.onLoadResourceWithCustomScheme != null)
              return (await webviewParams!.onLoadResourceWithCustomScheme!(
                _controllerFromPlatform,
                request,
              ))?.toMap();
            else {
              return (await params
                      .webviewParams!
                      // ignore: deprecated_member_use_from_same_package
                      .onLoadResourceCustomScheme!(
                    _controllerFromPlatform,
                    request.url,
                  ))
                  ?.toMap();
            }
          } else {
            return ((await _inAppBrowserEventHandler!
                        .onLoadResourceWithCustomScheme(request)) ??
                    (await _inAppBrowserEventHandler!
                        .onLoadResourceCustomScheme(request.url)))
                ?.toMap();
          }
        }
        break;
      case "onCreateWindow":
        if ((webviewParams != null && webviewParams!.onCreateWindow != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          CreateWindowAction createWindowAction = CreateWindowAction.fromMap(
            arguments,
          )!;

          if (webviewParams != null && webviewParams!.onCreateWindow != null)
            return await webviewParams!.onCreateWindow!(
              _controllerFromPlatform,
              createWindowAction,
            );
          else
            return await _inAppBrowserEventHandler!.onCreateWindow(
              createWindowAction,
            );
        }
        break;
      case "onCloseWindow":
        if (webviewParams != null && webviewParams!.onCloseWindow != null)
          webviewParams!.onCloseWindow!(_controllerFromPlatform);
        else if (_inAppBrowserEventHandler != null)
          _inAppBrowserEventHandler!.onCloseWindow();
        break;
      case "onTitleChanged":
        if ((webviewParams != null && webviewParams!.onTitleChanged != null) ||
            _inAppBrowserEventHandler != null) {
          String? title = call.arguments["title"];
          if (webviewParams != null && webviewParams!.onTitleChanged != null)
            webviewParams!.onTitleChanged!(_controllerFromPlatform, title);
          else
            _inAppBrowserEventHandler!.onTitleChanged(title);
        }
        break;
      case "onGeolocationPermissionsShowPrompt":
        if ((webviewParams != null &&
                (webviewParams!.onGeolocationPermissionsShowPrompt != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnGeolocationPermissionsShowPrompt !=
                        null)) ||
            _inAppBrowserEventHandler != null) {
          String origin = call.arguments["origin"];

          if (webviewParams != null) {
            if (webviewParams!.onGeolocationPermissionsShowPrompt != null)
              return (await webviewParams!.onGeolocationPermissionsShowPrompt!(
                _controllerFromPlatform,
                origin,
              ))?.toMap();
            else {
              return (await params
                      .webviewParams!
                      // ignore: deprecated_member_use_from_same_package
                      .androidOnGeolocationPermissionsShowPrompt!(
                    _controllerFromPlatform,
                    origin,
                  ))
                  ?.toMap();
            }
          } else {
            return ((await _inAppBrowserEventHandler!
                        .onGeolocationPermissionsShowPrompt(origin)) ??
                    (await _inAppBrowserEventHandler!
                        .androidOnGeolocationPermissionsShowPrompt(origin)))
                ?.toMap();
          }
        }
        break;
      case "onGeolocationPermissionsHidePrompt":
        if (webviewParams != null &&
            (webviewParams!.onGeolocationPermissionsHidePrompt != null ||
                // ignore: deprecated_member_use_from_same_package
                webviewParams!.androidOnGeolocationPermissionsHidePrompt !=
                    null)) {
          if (webviewParams!.onGeolocationPermissionsHidePrompt != null)
            webviewParams!.onGeolocationPermissionsHidePrompt!(
              _controllerFromPlatform,
            );
          else {
            // ignore: deprecated_member_use_from_same_package
            webviewParams!.androidOnGeolocationPermissionsHidePrompt!(
              _controllerFromPlatform,
            );
          }
        } else if (_inAppBrowserEventHandler != null) {
          _inAppBrowserEventHandler!.onGeolocationPermissionsHidePrompt();
          // ignore: deprecated_member_use_from_same_package
          _inAppBrowserEventHandler!
              .androidOnGeolocationPermissionsHidePrompt();
        }
        break;
      case "shouldInterceptRequest":
        if ((webviewParams != null &&
                (webviewParams!.shouldInterceptRequest != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidShouldInterceptRequest != null)) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          WebResourceRequest request = WebResourceRequest.fromMap(arguments)!;

          if (webviewParams != null) {
            if (webviewParams!.shouldInterceptRequest != null)
              return (await webviewParams!.shouldInterceptRequest!(
                _controllerFromPlatform,
                request,
              ))?.toMap();
            else {
              // ignore: deprecated_member_use_from_same_package
              return (await webviewParams!.androidShouldInterceptRequest!(
                _controllerFromPlatform,
                request,
              ))?.toMap();
            }
          } else {
            return ((await _inAppBrowserEventHandler!.shouldInterceptRequest(
                      request,
                    )) ??
                    (await _inAppBrowserEventHandler!
                        .androidShouldInterceptRequest(request)))
                ?.toMap();
          }
        }
        break;
      case "onRenderProcessUnresponsive":
        if ((webviewParams != null &&
                (webviewParams!.onRenderProcessUnresponsive != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnRenderProcessUnresponsive !=
                        null)) ||
            _inAppBrowserEventHandler != null) {
          String? url = call.arguments["url"];
          WebUri? uri = url != null ? WebUri(url) : null;

          if (webviewParams != null) {
            if (webviewParams!.onRenderProcessUnresponsive != null)
              return (await webviewParams!.onRenderProcessUnresponsive!(
                _controllerFromPlatform,
                uri,
              ))?.toNativeValue();
            else {
              // ignore: deprecated_member_use_from_same_package
              return (await webviewParams!.androidOnRenderProcessUnresponsive!(
                _controllerFromPlatform,
                uri,
              ))?.toNativeValue();
            }
          } else {
            return ((await _inAppBrowserEventHandler!
                        .onRenderProcessUnresponsive(uri)) ??
                    (await _inAppBrowserEventHandler!
                        .androidOnRenderProcessUnresponsive(uri)))
                ?.toNativeValue();
          }
        }
        break;
      case "onRenderProcessResponsive":
        if ((webviewParams != null &&
                (webviewParams!.onRenderProcessResponsive != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnRenderProcessResponsive != null)) ||
            _inAppBrowserEventHandler != null) {
          String? url = call.arguments["url"];
          WebUri? uri = url != null ? WebUri(url) : null;

          if (webviewParams != null) {
            if (webviewParams!.onRenderProcessResponsive != null)
              return (await webviewParams!.onRenderProcessResponsive!(
                _controllerFromPlatform,
                uri,
              ))?.toNativeValue();
            else {
              // ignore: deprecated_member_use_from_same_package
              return (await webviewParams!.androidOnRenderProcessResponsive!(
                _controllerFromPlatform,
                uri,
              ))?.toNativeValue();
            }
          } else {
            return ((await _inAppBrowserEventHandler!.onRenderProcessResponsive(
                      uri,
                    )) ??
                    (await _inAppBrowserEventHandler!
                        .androidOnRenderProcessResponsive(uri)))
                ?.toNativeValue();
          }
        }
        break;
      case "onRenderProcessGone":
        if ((webviewParams != null &&
                (webviewParams!.onRenderProcessGone != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnRenderProcessGone != null)) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          RenderProcessGoneDetail detail = RenderProcessGoneDetail.fromMap(
            arguments,
          )!;

          if (webviewParams != null) {
            if (webviewParams!.onRenderProcessGone != null)
              webviewParams!.onRenderProcessGone!(
                _controllerFromPlatform,
                detail,
              );
            else {
              // ignore: deprecated_member_use_from_same_package
              webviewParams!.androidOnRenderProcessGone!(
                _controllerFromPlatform,
                detail,
              );
            }
          } else if (_inAppBrowserEventHandler != null) {
            _inAppBrowserEventHandler!.onRenderProcessGone(detail);
            // ignore: deprecated_member_use_from_same_package
            _inAppBrowserEventHandler!.androidOnRenderProcessGone(detail);
          }
        }
        break;
      case "onFormResubmission":
        if ((webviewParams != null &&
                (webviewParams!.onFormResubmission != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnFormResubmission != null)) ||
            _inAppBrowserEventHandler != null) {
          String? url = call.arguments["url"];
          WebUri? uri = url != null ? WebUri(url) : null;

          if (webviewParams != null) {
            if (webviewParams!.onFormResubmission != null)
              return (await webviewParams!.onFormResubmission!(
                _controllerFromPlatform,
                uri,
              ))?.toNativeValue();
            else {
              // ignore: deprecated_member_use_from_same_package
              return (await webviewParams!.androidOnFormResubmission!(
                _controllerFromPlatform,
                uri,
              ))?.toNativeValue();
            }
          } else {
            return ((await _inAppBrowserEventHandler!.onFormResubmission(
                      uri,
                    )) ??
                    // ignore: deprecated_member_use_from_same_package
                    (await _inAppBrowserEventHandler!.androidOnFormResubmission(
                      uri,
                    )))
                ?.toNativeValue();
          }
        }
        break;
      case "onZoomScaleChanged":
        if ((webviewParams != null &&
                // ignore: deprecated_member_use_from_same_package
                (webviewParams!.androidOnScaleChanged != null ||
                    webviewParams!.onZoomScaleChanged != null)) ||
            _inAppBrowserEventHandler != null) {
          double oldScale = call.arguments["oldScale"];
          double newScale = call.arguments["newScale"];

          if (webviewParams != null) {
            if (webviewParams!.onZoomScaleChanged != null)
              webviewParams!.onZoomScaleChanged!(
                _controllerFromPlatform,
                oldScale,
                newScale,
              );
            else {
              // ignore: deprecated_member_use_from_same_package
              webviewParams!.androidOnScaleChanged!(
                _controllerFromPlatform,
                oldScale,
                newScale,
              );
            }
          } else {
            _inAppBrowserEventHandler!.onZoomScaleChanged(oldScale, newScale);
            // ignore: deprecated_member_use_from_same_package
            _inAppBrowserEventHandler!.androidOnScaleChanged(
              oldScale,
              newScale,
            );
          }
        }
        break;
      case "onReceivedIcon":
        if ((webviewParams != null &&
                (webviewParams!.onReceivedIcon != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnReceivedIcon != null)) ||
            _inAppBrowserEventHandler != null) {
          Uint8List icon = Uint8List.fromList(
            call.arguments["icon"].cast<int>(),
          );

          if (webviewParams != null) {
            if (webviewParams!.onReceivedIcon != null)
              webviewParams!.onReceivedIcon!(_controllerFromPlatform, icon);
            else {
              // ignore: deprecated_member_use_from_same_package
              webviewParams!.androidOnReceivedIcon!(
                _controllerFromPlatform,
                icon,
              );
            }
          } else {
            _inAppBrowserEventHandler!.onReceivedIcon(icon);
            // ignore: deprecated_member_use_from_same_package
            _inAppBrowserEventHandler!.androidOnReceivedIcon(icon);
          }
        }
        break;
      case "onReceivedTouchIconUrl":
        if ((webviewParams != null &&
                (webviewParams!.onReceivedTouchIconUrl != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnReceivedTouchIconUrl != null)) ||
            _inAppBrowserEventHandler != null) {
          String url = call.arguments["url"];
          bool precomposed = call.arguments["precomposed"];
          WebUri uri = WebUri(url);

          if (webviewParams != null) {
            if (webviewParams!.onReceivedTouchIconUrl != null)
              webviewParams!.onReceivedTouchIconUrl!(
                _controllerFromPlatform,
                uri,
                precomposed,
              );
            else {
              // ignore: deprecated_member_use_from_same_package
              webviewParams!.androidOnReceivedTouchIconUrl!(
                _controllerFromPlatform,
                uri,
                precomposed,
              );
            }
          } else {
            _inAppBrowserEventHandler!.onReceivedTouchIconUrl(uri, precomposed);
            // ignore: deprecated_member_use_from_same_package
            _inAppBrowserEventHandler!.androidOnReceivedTouchIconUrl(
              uri,
              precomposed,
            );
          }
        }
        break;
      case "onJsAlert":
        if ((webviewParams != null && webviewParams!.onJsAlert != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          JsAlertRequest jsAlertRequest = JsAlertRequest.fromMap(arguments)!;

          if (webviewParams != null && webviewParams!.onJsAlert != null)
            return (await webviewParams!.onJsAlert!(
              _controllerFromPlatform,
              jsAlertRequest,
            ))?.toMap();
          else
            return (await _inAppBrowserEventHandler!.onJsAlert(
              jsAlertRequest,
            ))?.toMap();
        }
        break;
      case "onJsConfirm":
        if ((webviewParams != null && webviewParams!.onJsConfirm != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          JsConfirmRequest jsConfirmRequest = JsConfirmRequest.fromMap(
            arguments,
          )!;

          if (webviewParams != null && webviewParams!.onJsConfirm != null)
            return (await webviewParams!.onJsConfirm!(
              _controllerFromPlatform,
              jsConfirmRequest,
            ))?.toMap();
          else
            return (await _inAppBrowserEventHandler!.onJsConfirm(
              jsConfirmRequest,
            ))?.toMap();
        }
        break;
      case "onJsPrompt":
        if ((webviewParams != null && webviewParams!.onJsPrompt != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          JsPromptRequest jsPromptRequest = JsPromptRequest.fromMap(arguments)!;

          if (webviewParams != null && webviewParams!.onJsPrompt != null)
            return (await webviewParams!.onJsPrompt!(
              _controllerFromPlatform,
              jsPromptRequest,
            ))?.toMap();
          else
            return (await _inAppBrowserEventHandler!.onJsPrompt(
              jsPromptRequest,
            ))?.toMap();
        }
        break;
      case "onJsBeforeUnload":
        if ((webviewParams != null &&
                (webviewParams!.onJsBeforeUnload != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnJsBeforeUnload != null)) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          JsBeforeUnloadRequest jsBeforeUnloadRequest =
              JsBeforeUnloadRequest.fromMap(arguments)!;

          if (webviewParams != null) {
            if (webviewParams!.onJsBeforeUnload != null)
              return (await webviewParams!.onJsBeforeUnload!(
                _controllerFromPlatform,
                jsBeforeUnloadRequest,
              ))?.toMap();
            else {
              // ignore: deprecated_member_use_from_same_package
              return (await webviewParams!.androidOnJsBeforeUnload!(
                _controllerFromPlatform,
                jsBeforeUnloadRequest,
              ))?.toMap();
            }
          } else {
            return ((await _inAppBrowserEventHandler!.onJsBeforeUnload(
                      jsBeforeUnloadRequest,
                    )) ??
                    (await _inAppBrowserEventHandler!.androidOnJsBeforeUnload(
                      jsBeforeUnloadRequest,
                    )))
                ?.toMap();
          }
        }
        break;
      case "onSafeBrowsingHit":
        if ((webviewParams != null &&
                (webviewParams!.onSafeBrowsingHit != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnSafeBrowsingHit != null)) ||
            _inAppBrowserEventHandler != null) {
          String url = call.arguments["url"];
          SafeBrowsingThreat? threatType = SafeBrowsingThreat.fromNativeValue(
            call.arguments["threatType"],
          );
          WebUri uri = WebUri(url);

          if (webviewParams != null) {
            if (webviewParams!.onSafeBrowsingHit != null)
              return (await webviewParams!.onSafeBrowsingHit!(
                _controllerFromPlatform,
                uri,
                threatType,
              ))?.toMap();
            else {
              // ignore: deprecated_member_use_from_same_package
              return (await webviewParams!.androidOnSafeBrowsingHit!(
                _controllerFromPlatform,
                uri,
                threatType,
              ))?.toMap();
            }
          } else {
            return ((await _inAppBrowserEventHandler!.onSafeBrowsingHit(
                      uri,
                      threatType,
                    )) ??
                    (await _inAppBrowserEventHandler!.androidOnSafeBrowsingHit(
                      uri,
                      threatType,
                    )))
                ?.toMap();
          }
        }
        break;
      case "onReceivedLoginRequest":
        if ((webviewParams != null &&
                (webviewParams!.onReceivedLoginRequest != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnReceivedLoginRequest != null)) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          LoginRequest loginRequest = LoginRequest.fromMap(arguments)!;

          if (webviewParams != null) {
            if (webviewParams!.onReceivedLoginRequest != null)
              webviewParams!.onReceivedLoginRequest!(
                _controllerFromPlatform,
                loginRequest,
              );
            else {
              // ignore: deprecated_member_use_from_same_package
              webviewParams!.androidOnReceivedLoginRequest!(
                _controllerFromPlatform,
                loginRequest,
              );
            }
          } else {
            _inAppBrowserEventHandler!.onReceivedLoginRequest(loginRequest);
            // ignore: deprecated_member_use_from_same_package
            _inAppBrowserEventHandler!.androidOnReceivedLoginRequest(
              loginRequest,
            );
          }
        }
        break;
      case "onPermissionRequestCanceled":
        if ((webviewParams != null &&
                webviewParams!.onPermissionRequestCanceled != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          PermissionRequest permissionRequest = PermissionRequest.fromMap(
            arguments,
          )!;

          if (webviewParams != null &&
              webviewParams!.onPermissionRequestCanceled != null)
            webviewParams!.onPermissionRequestCanceled!(
              _controllerFromPlatform,
              permissionRequest,
            );
          else
            _inAppBrowserEventHandler!.onPermissionRequestCanceled(
              permissionRequest,
            );
        }
        break;
      case "onRequestFocus":
        if ((webviewParams != null && webviewParams!.onRequestFocus != null) ||
            _inAppBrowserEventHandler != null) {
          if (webviewParams != null && webviewParams!.onRequestFocus != null)
            webviewParams!.onRequestFocus!(_controllerFromPlatform);
          else
            _inAppBrowserEventHandler!.onRequestFocus();
        }
        break;
      case "onReceivedHttpAuthRequest":
        if ((webviewParams != null &&
                webviewParams!.onReceivedHttpAuthRequest != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          HttpAuthenticationChallenge challenge =
              HttpAuthenticationChallenge.fromMap(arguments)!;

          if (webviewParams != null &&
              webviewParams!.onReceivedHttpAuthRequest != null)
            return (await webviewParams!.onReceivedHttpAuthRequest!(
              _controllerFromPlatform,
              challenge,
            ))?.toMap();
          else
            return (await _inAppBrowserEventHandler!.onReceivedHttpAuthRequest(
              challenge,
            ))?.toMap();
        }
        break;
      case "onReceivedServerTrustAuthRequest":
        if ((webviewParams != null &&
                webviewParams!.onReceivedServerTrustAuthRequest != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          ServerTrustChallenge challenge = ServerTrustChallenge.fromMap(
            arguments,
          )!;

          if (webviewParams != null &&
              webviewParams!.onReceivedServerTrustAuthRequest != null)
            return (await webviewParams!.onReceivedServerTrustAuthRequest!(
              _controllerFromPlatform,
              challenge,
            ))?.toMap();
          else
            return (await _inAppBrowserEventHandler!
                    .onReceivedServerTrustAuthRequest(challenge))
                ?.toMap();
        }
        break;
      case "onReceivedClientCertRequest":
        if ((webviewParams != null &&
                webviewParams!.onReceivedClientCertRequest != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          ClientCertChallenge challenge = ClientCertChallenge.fromMap(
            arguments,
          )!;

          if (webviewParams != null &&
              webviewParams!.onReceivedClientCertRequest != null)
            return (await webviewParams!.onReceivedClientCertRequest!(
              _controllerFromPlatform,
              challenge,
            ))?.toMap();
          else
            return (await _inAppBrowserEventHandler!
                    .onReceivedClientCertRequest(challenge))
                ?.toMap();
        }
        break;
      case "onFindResultReceived":
        if ((webviewParams != null &&
                (webviewParams!.onFindResultReceived != null ||
                    (webviewParams!.findInteractionController != null &&
                        webviewParams!
                                .findInteractionController!
                                .params
                                .onFindResultReceived !=
                            null))) ||
            _inAppBrowserEventHandler != null) {
          int activeMatchOrdinal = call.arguments["activeMatchOrdinal"];
          int numberOfMatches = call.arguments["numberOfMatches"];
          bool isDoneCounting = call.arguments["isDoneCounting"];
          if (webviewParams != null) {
            if (webviewParams!.findInteractionController != null &&
                webviewParams!
                        .findInteractionController!
                        .params
                        .onFindResultReceived !=
                    null)
              webviewParams!
                  .findInteractionController!
                  .params
                  .onFindResultReceived!(
                webviewParams!.findInteractionController!,
                activeMatchOrdinal,
                numberOfMatches,
                isDoneCounting,
              );
            else
              webviewParams!.onFindResultReceived!(
                _controllerFromPlatform,
                activeMatchOrdinal,
                numberOfMatches,
                isDoneCounting,
              );
          } else {
            if (_inAppBrowser!.findInteractionController != null &&
                _inAppBrowser!
                        .findInteractionController!
                        .onFindResultReceived !=
                    null)
              _inAppBrowser!.findInteractionController!.onFindResultReceived!(
                webviewParams!.findInteractionController!,
                activeMatchOrdinal,
                numberOfMatches,
                isDoneCounting,
              );
            else
              _inAppBrowserEventHandler!.onFindResultReceived(
                activeMatchOrdinal,
                numberOfMatches,
                isDoneCounting,
              );
          }
        }
        break;
      case "onPermissionRequest":
        if ((webviewParams != null &&
                (webviewParams!.onPermissionRequest != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.androidOnPermissionRequest != null)) ||
            _inAppBrowserEventHandler != null) {
          String origin = call.arguments["origin"];
          List<String> resources = call.arguments["resources"].cast<String>();

          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          PermissionRequest permissionRequest = PermissionRequest.fromMap(
            arguments,
          )!;

          if (webviewParams != null) {
            if (webviewParams!.onPermissionRequest != null)
              return (await webviewParams!.onPermissionRequest!(
                _controllerFromPlatform,
                permissionRequest,
              ))?.toMap();
            else {
              return (await webviewParams!.androidOnPermissionRequest!(
                _controllerFromPlatform,
                origin,
                resources,
              ))?.toMap();
            }
          } else {
            return (await _inAppBrowserEventHandler!.onPermissionRequest(
                  permissionRequest,
                ))?.toMap() ??
                (await _inAppBrowserEventHandler!.androidOnPermissionRequest(
                  origin,
                  resources,
                ))?.toMap();
          }
        }
        break;
      case "onUpdateVisitedHistory":
        if ((webviewParams != null &&
                webviewParams!.onUpdateVisitedHistory != null) ||
            _inAppBrowserEventHandler != null) {
          String? url = call.arguments["url"];
          bool? isReload = call.arguments["isReload"];
          WebUri? uri = url != null ? WebUri(url) : null;
          if (webviewParams != null &&
              webviewParams!.onUpdateVisitedHistory != null)
            webviewParams!.onUpdateVisitedHistory!(
              _controllerFromPlatform,
              uri,
              isReload,
            );
          else
            _inAppBrowserEventHandler!.onUpdateVisitedHistory(uri, isReload);
        }
        break;
      case "onWebContentProcessDidTerminate":
        if (webviewParams != null &&
            (webviewParams!.onWebContentProcessDidTerminate != null ||
                // ignore: deprecated_member_use_from_same_package
                webviewParams!.iosOnWebContentProcessDidTerminate != null)) {
          if (webviewParams!.onWebContentProcessDidTerminate != null)
            webviewParams!.onWebContentProcessDidTerminate!(
              _controllerFromPlatform,
            );
          else {
            // ignore: deprecated_member_use_from_same_package
            webviewParams!.iosOnWebContentProcessDidTerminate!(
              _controllerFromPlatform,
            );
          }
        } else if (_inAppBrowserEventHandler != null) {
          _inAppBrowserEventHandler!.onWebContentProcessDidTerminate();
          // ignore: deprecated_member_use_from_same_package
          _inAppBrowserEventHandler!.iosOnWebContentProcessDidTerminate();
        }
        break;
      case "onPageCommitVisible":
        if ((webviewParams != null &&
                webviewParams!.onPageCommitVisible != null) ||
            _inAppBrowserEventHandler != null) {
          String? url = call.arguments["url"];
          WebUri? uri = url != null ? WebUri(url) : null;
          if (webviewParams != null &&
              webviewParams!.onPageCommitVisible != null)
            webviewParams!.onPageCommitVisible!(_controllerFromPlatform, uri);
          else
            _inAppBrowserEventHandler!.onPageCommitVisible(uri);
        }
        break;
      case "onDidReceiveServerRedirectForProvisionalNavigation":
        if (webviewParams != null &&
            (webviewParams!
                        .onDidReceiveServerRedirectForProvisionalNavigation !=
                    null ||
                params
                        .webviewParams!
                        // ignore: deprecated_member_use_from_same_package
                        .iosOnDidReceiveServerRedirectForProvisionalNavigation !=
                    null)) {
          if (webviewParams!
                  .onDidReceiveServerRedirectForProvisionalNavigation !=
              null)
            webviewParams!.onDidReceiveServerRedirectForProvisionalNavigation!(
              _controllerFromPlatform,
            );
          else {
            params
                .webviewParams!
                // ignore: deprecated_member_use_from_same_package
                .iosOnDidReceiveServerRedirectForProvisionalNavigation!(
              _controllerFromPlatform,
            );
          }
        } else if (_inAppBrowserEventHandler != null) {
          _inAppBrowserEventHandler!
              .onDidReceiveServerRedirectForProvisionalNavigation();
          _inAppBrowserEventHandler!
              .iosOnDidReceiveServerRedirectForProvisionalNavigation();
        }
        break;
      case "onNavigationResponse":
        if ((webviewParams != null &&
                (webviewParams!.onNavigationResponse != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.iosOnNavigationResponse != null)) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          // ignore: deprecated_member_use_from_same_package
          IOSWKNavigationResponse iosOnNavigationResponse =
              // ignore: deprecated_member_use_from_same_package
              IOSWKNavigationResponse.fromMap(arguments)!;

          NavigationResponse navigationResponse = NavigationResponse.fromMap(
            arguments,
          )!;

          if (webviewParams != null) {
            if (webviewParams!.onNavigationResponse != null)
              return (await webviewParams!.onNavigationResponse!(
                _controllerFromPlatform,
                navigationResponse,
              ))?.toNativeValue();
            else {
              // ignore: deprecated_member_use_from_same_package
              return (await webviewParams!.iosOnNavigationResponse!(
                _controllerFromPlatform,
                iosOnNavigationResponse,
              ))?.toNativeValue();
            }
          } else {
            return (await _inAppBrowserEventHandler!.onNavigationResponse(
                  navigationResponse,
                ))?.toNativeValue() ??
                (await _inAppBrowserEventHandler!.iosOnNavigationResponse(
                  iosOnNavigationResponse,
                ))?.toNativeValue();
          }
        }
        break;
      case "shouldAllowDeprecatedTLS":
        if ((webviewParams != null &&
                (webviewParams!.shouldAllowDeprecatedTLS != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.iosShouldAllowDeprecatedTLS != null)) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          URLAuthenticationChallenge challenge =
              URLAuthenticationChallenge.fromMap(arguments)!;

          if (webviewParams != null) {
            if (webviewParams!.shouldAllowDeprecatedTLS != null)
              return (await webviewParams!.shouldAllowDeprecatedTLS!(
                _controllerFromPlatform,
                challenge,
              ))?.toNativeValue();
            else {
              // ignore: deprecated_member_use_from_same_package
              return (await webviewParams!.iosShouldAllowDeprecatedTLS!(
                _controllerFromPlatform,
                challenge,
              ))?.toNativeValue();
            }
          } else {
            return (await _inAppBrowserEventHandler!.shouldAllowDeprecatedTLS(
                  challenge,
                ))?.toNativeValue() ??
                // ignore: deprecated_member_use_from_same_package
                (await _inAppBrowserEventHandler!.iosShouldAllowDeprecatedTLS(
                  challenge,
                ))?.toNativeValue();
          }
        }
        break;
      case "onLongPressHitTestResult":
        if ((webviewParams != null &&
                webviewParams!.onLongPressHitTestResult != null) ||
            _inAppBrowserEventHandler != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          InAppWebViewHitTestResult hitTestResult =
              InAppWebViewHitTestResult.fromMap(arguments)!;

          if (webviewParams != null &&
              webviewParams!.onLongPressHitTestResult != null)
            webviewParams!.onLongPressHitTestResult!(
              _controllerFromPlatform,
              hitTestResult,
            );
          else
            _inAppBrowserEventHandler!.onLongPressHitTestResult(hitTestResult);
        }
        break;
      case "onCreateContextMenu":
        ContextMenu? contextMenu;
        if (webviewParams != null && webviewParams!.contextMenu != null) {
          contextMenu = webviewParams!.contextMenu;
        } else if (_inAppBrowserEventHandler != null &&
            _inAppBrowser!.contextMenu != null) {
          contextMenu = _inAppBrowser!.contextMenu;
        }

        if (contextMenu != null && contextMenu.onCreateContextMenu != null) {
          Map<String, dynamic> arguments = call.arguments
              .cast<String, dynamic>();
          InAppWebViewHitTestResult hitTestResult =
              InAppWebViewHitTestResult.fromMap(arguments)!;

          contextMenu.onCreateContextMenu!(hitTestResult);
        }
        break;
      case "onHideContextMenu":
        ContextMenu? contextMenu;
        if (webviewParams != null && webviewParams!.contextMenu != null) {
          contextMenu = webviewParams!.contextMenu;
        } else if (_inAppBrowserEventHandler != null &&
            _inAppBrowser!.contextMenu != null) {
          contextMenu = _inAppBrowser!.contextMenu;
        }

        if (contextMenu != null && contextMenu.onHideContextMenu != null) {
          contextMenu.onHideContextMenu!();
        }
        break;
      case "onContextMenuActionItemClicked":
        ContextMenu? contextMenu;
        if (webviewParams != null && webviewParams!.contextMenu != null) {
          contextMenu = webviewParams!.contextMenu;
        } else if (_inAppBrowserEventHandler != null &&
            _inAppBrowser!.contextMenu != null) {
          contextMenu = _inAppBrowser!.contextMenu;
        }

        if (contextMenu != null) {
          int? androidId = call.arguments["androidId"];
          String? iosId = call.arguments["iosId"];
          dynamic id = call.arguments["id"];
          String title = call.arguments["title"];

          ContextMenuItem menuItemClicked = ContextMenuItem(
            id: id,
            // ignore: deprecated_member_use_from_same_package
            androidId: androidId,
            // ignore: deprecated_member_use_from_same_package
            iosId: iosId,
            title: title,
            action: null,
          );

          for (var menuItem in contextMenu.menuItems) {
            if (menuItem.id == id) {
              menuItemClicked = menuItem;
              if (menuItem.action != null) {
                menuItem.action!();
              }
              break;
            }
          }

          if (contextMenu.onContextMenuActionItemClicked != null) {
            contextMenu.onContextMenuActionItemClicked!(menuItemClicked);
          }
        }
        break;
      case "onEnterFullscreen":
        if (webviewParams != null && webviewParams!.onEnterFullscreen != null)
          webviewParams!.onEnterFullscreen!(_controllerFromPlatform);
        else if (_inAppBrowserEventHandler != null)
          _inAppBrowserEventHandler!.onEnterFullscreen();
        break;
      case "onExitFullscreen":
        if (webviewParams != null && webviewParams!.onExitFullscreen != null)
          webviewParams!.onExitFullscreen!(_controllerFromPlatform);
        else if (_inAppBrowserEventHandler != null)
          _inAppBrowserEventHandler!.onExitFullscreen();
        break;
      case "onOverScrolled":
        if ((webviewParams != null && webviewParams!.onOverScrolled != null) ||
            _inAppBrowserEventHandler != null) {
          int x = call.arguments["x"];
          int y = call.arguments["y"];
          bool clampedX = call.arguments["clampedX"];
          bool clampedY = call.arguments["clampedY"];

          if (webviewParams != null && webviewParams!.onOverScrolled != null)
            webviewParams!.onOverScrolled!(
              _controllerFromPlatform,
              x,
              y,
              clampedX,
              clampedY,
            );
          else
            _inAppBrowserEventHandler!.onOverScrolled(x, y, clampedX, clampedY);
        }
        break;
      case "onWindowFocus":
        if (webviewParams != null && webviewParams!.onWindowFocus != null)
          webviewParams!.onWindowFocus!(_controllerFromPlatform);
        else if (_inAppBrowserEventHandler != null)
          _inAppBrowserEventHandler!.onWindowFocus();
        break;
      case "onWindowBlur":
        if (webviewParams != null && webviewParams!.onWindowBlur != null)
          webviewParams!.onWindowBlur!(_controllerFromPlatform);
        else if (_inAppBrowserEventHandler != null)
          _inAppBrowserEventHandler!.onWindowBlur();
        break;
      case "onPrintRequest":
        if ((webviewParams != null &&
                (webviewParams!.onPrintRequest != null ||
                    // ignore: deprecated_member_use_from_same_package
                    webviewParams!.onPrint != null)) ||
            _inAppBrowserEventHandler != null) {
          String? url = call.arguments["url"];
          String? printJobId = call.arguments["printJobId"];
          WebUri? uri = url != null ? WebUri(url) : null;
          IOSPrintJobController? printJob = printJobId != null
              ? IOSPrintJobController(
                  IOSPrintJobControllerCreationParams(id: printJobId),
                )
              : null;

          if (webviewParams != null) {
            if (webviewParams!.onPrintRequest != null)
              return await webviewParams!.onPrintRequest!(
                _controllerFromPlatform,
                uri,
                printJob,
              );
            else {
              // ignore: deprecated_member_use_from_same_package
              webviewParams!.onPrint!(_controllerFromPlatform, uri);
              return false;
            }
          } else {
            // ignore: deprecated_member_use_from_same_package
            _inAppBrowserEventHandler!.onPrint(uri);
            return await _inAppBrowserEventHandler!.onPrintRequest(
              uri,
              printJob,
            );
          }
        }
        break;
      case "onInjectedScriptLoaded":
        String id = call.arguments[0];
        var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad;
        if ((webviewParams != null || _inAppBrowserEventHandler != null) &&
            onLoadCallback != null) {
          onLoadCallback();
        }
        break;
      case "onInjectedScriptError":
        String id = call.arguments[0];
        var onErrorCallback = _injectedScriptsFromURL[id]?.onError;
        if ((webviewParams != null || _inAppBrowserEventHandler != null) &&
            onErrorCallback != null) {
          onErrorCallback();
        }
        break;
      case "onCameraCaptureStateChanged":
        if ((webviewParams != null &&
                webviewParams!.onCameraCaptureStateChanged != null) ||
            _inAppBrowserEventHandler != null) {
          var oldState = MediaCaptureState.fromNativeValue(
            call.arguments["oldState"],
          );
          var newState = MediaCaptureState.fromNativeValue(
            call.arguments["newState"],
          );

          if (webviewParams != null &&
              webviewParams!.onCameraCaptureStateChanged != null)
            webviewParams!.onCameraCaptureStateChanged!(
              _controllerFromPlatform,
              oldState,
              newState,
            );
          else
            _inAppBrowserEventHandler!.onCameraCaptureStateChanged(
              oldState,
              newState,
            );
        }
        break;
      case "onMicrophoneCaptureStateChanged":
        if ((webviewParams != null &&
                webviewParams!.onMicrophoneCaptureStateChanged != null) ||
            _inAppBrowserEventHandler != null) {
          var oldState = MediaCaptureState.fromNativeValue(
            call.arguments["oldState"],
          );
          var newState = MediaCaptureState.fromNativeValue(
            call.arguments["newState"],
          );

          if (webviewParams != null &&
              webviewParams!.onMicrophoneCaptureStateChanged != null)
            webviewParams!.onMicrophoneCaptureStateChanged!(
              _controllerFromPlatform,
              oldState,
              newState,
            );
          else
            _inAppBrowserEventHandler!.onMicrophoneCaptureStateChanged(
              oldState,
              newState,
            );
        }
        break;
      case "onContentSizeChanged":
        if ((webviewParams != null &&
                webviewParams!.onContentSizeChanged != null) ||
            _inAppBrowserEventHandler != null) {
          var oldContentSize = MapSize.fromMap(
            call.arguments["oldContentSize"]?.cast<String, dynamic>(),
          )!;
          var newContentSize = MapSize.fromMap(
            call.arguments["newContentSize"]?.cast<String, dynamic>(),
          )!;

          if (webviewParams != null &&
              webviewParams!.onContentSizeChanged != null)
            webviewParams!.onContentSizeChanged!(
              _controllerFromPlatform,
              oldContentSize,
              newContentSize,
            );
          else
            _inAppBrowserEventHandler!.onContentSizeChanged(
              oldContentSize,
              newContentSize,
            );
        }
        break;
      case "onCallJsHandler":
        String handlerName = call.arguments["handlerName"];
        Map<String, dynamic> handlerDataMap = call.arguments["data"]
            .cast<String, dynamic>();
        // decode args to json
        handlerDataMap["args"] = jsonDecode(handlerDataMap["args"]);
        final handlerData = JavaScriptHandlerFunctionData.fromMap(
          handlerDataMap,
        )!;

        _debugLog(handlerName, handlerData);

        switch (handlerName) {
          case "onLoadResource":
            if ((webviewParams != null &&
                    webviewParams!.onLoadResource != null) ||
                _inAppBrowserEventHandler != null) {
              Map<String, dynamic> arguments = handlerData.args[0]
                  .cast<String, dynamic>();
              arguments["startTime"] = arguments["startTime"] is int
                  ? arguments["startTime"].toDouble()
                  : arguments["startTime"];
              arguments["duration"] = arguments["duration"] is int
                  ? arguments["duration"].toDouble()
                  : arguments["duration"];

              var response = LoadedResource.fromMap(arguments)!;

              if (webviewParams != null &&
                  webviewParams!.onLoadResource != null)
                webviewParams!.onLoadResource!(
                  _controllerFromPlatform,
                  response,
                );
              else
                _inAppBrowserEventHandler!.onLoadResource(response);
            }
            return null;
          case "shouldInterceptAjaxRequest":
            if ((webviewParams != null &&
                    webviewParams!.shouldInterceptAjaxRequest != null) ||
                _inAppBrowserEventHandler != null) {
              Map<String, dynamic> arguments = handlerData.args[0]
                  .cast<String, dynamic>();
              AjaxRequest request = AjaxRequest.fromMap(arguments)!;

              if (webviewParams != null &&
                  webviewParams!.shouldInterceptAjaxRequest != null)
                return jsonEncode(
                  await params.webviewParams!.shouldInterceptAjaxRequest!(
                    _controllerFromPlatform,
                    request,
                  ),
                );
              else
                return jsonEncode(
                  await _inAppBrowserEventHandler!.shouldInterceptAjaxRequest(
                    request,
                  ),
                );
            }
            return null;
          case "onAjaxReadyStateChange":
            if ((webviewParams != null &&
                    webviewParams!.onAjaxReadyStateChange != null) ||
                _inAppBrowserEventHandler != null) {
              Map<String, dynamic> arguments = handlerData.args[0]
                  .cast<String, dynamic>();
              AjaxRequest request = AjaxRequest.fromMap(arguments)!;

              if (webviewParams != null &&
                  webviewParams!.onAjaxReadyStateChange != null)
                return jsonEncode(
                  (await webviewParams!.onAjaxReadyStateChange!(
                    _controllerFromPlatform,
                    request,
                  ))?.toNativeValue(),
                );
              else
                return jsonEncode(
                  (await _inAppBrowserEventHandler!.onAjaxReadyStateChange(
                    request,
                  ))?.toNativeValue(),
                );
            }
            return null;
          case "onAjaxProgress":
            if ((webviewParams != null &&
                    webviewParams!.onAjaxProgress != null) ||
                _inAppBrowserEventHandler != null) {
              Map<String, dynamic> arguments = handlerData.args[0]
                  .cast<String, dynamic>();
              AjaxRequest request = AjaxRequest.fromMap(arguments)!;

              if (webviewParams != null &&
                  webviewParams!.onAjaxProgress != null)
                return jsonEncode(
                  (await webviewParams!.onAjaxProgress!(
                    _controllerFromPlatform,
                    request,
                  ))?.toNativeValue(),
                );
              else
                return jsonEncode(
                  (await _inAppBrowserEventHandler!.onAjaxProgress(
                    request,
                  ))?.toNativeValue(),
                );
            }
            return null;
          case "shouldInterceptFetchRequest":
            if ((webviewParams != null &&
                    webviewParams!.shouldInterceptFetchRequest != null) ||
                _inAppBrowserEventHandler != null) {
              Map<String, dynamic> arguments = handlerData.args[0]
                  .cast<String, dynamic>();
              FetchRequest request = FetchRequest.fromMap(arguments)!;

              if (webviewParams != null &&
                  webviewParams!.shouldInterceptFetchRequest != null)
                return jsonEncode(
                  await webviewParams!.shouldInterceptFetchRequest!(
                    _controllerFromPlatform,
                    request,
                  ),
                );
              else
                return jsonEncode(
                  await _inAppBrowserEventHandler!.shouldInterceptFetchRequest(
                    request,
                  ),
                );
            }
            return null;
          case "onWindowFocus":
            if (webviewParams != null && webviewParams!.onWindowFocus != null)
              webviewParams!.onWindowFocus!(_controllerFromPlatform);
            else if (_inAppBrowserEventHandler != null)
              _inAppBrowserEventHandler!.onWindowFocus();
            return null;
          case "onWindowBlur":
            if (webviewParams != null && webviewParams!.onWindowBlur != null)
              webviewParams!.onWindowBlur!(_controllerFromPlatform);
            else if (_inAppBrowserEventHandler != null)
              _inAppBrowserEventHandler!.onWindowBlur();
            return null;
          case "onInjectedScriptLoaded":
            String id = handlerData.args[0];
            var onLoadCallback = _injectedScriptsFromURL[id]?.onLoad;
            if ((webviewParams != null || _inAppBrowserEventHandler != null) &&
                onLoadCallback != null) {
              onLoadCallback();
            }
            return null;
          case "onInjectedScriptError":
            String id = handlerData.args[0];
            var onErrorCallback = _injectedScriptsFromURL[id]?.onError;
            if ((webviewParams != null || _inAppBrowserEventHandler != null) &&
                onErrorCallback != null) {
              onErrorCallback();
            }
            return null;
        }

        if (_javaScriptHandlersMap.containsKey(handlerName)) {
          // convert result to json
          try {
            var jsHandlerResult = null;
            if (_javaScriptHandlersMap[handlerName]
                is JavaScriptHandlerCallback) {
              jsHandlerResult =
                  await (_javaScriptHandlersMap[handlerName]
                      as JavaScriptHandlerCallback)(handlerData.args);
            } else if (_javaScriptHandlersMap[handlerName]
                is JavaScriptHandlerFunction) {
              jsHandlerResult =
                  await (_javaScriptHandlersMap[handlerName]
                      as JavaScriptHandlerFunction)(handlerData);
            } else {
              jsHandlerResult = await _javaScriptHandlersMap[handlerName]!();
            }
            return jsonEncode(jsHandlerResult);
          } catch (error, stacktrace) {
            developer.log(
              error.toString() + '\n' + stacktrace.toString(),
              name: 'JavaScript Handler "$handlerName"',
            );
            throw Exception(error.toString().replaceFirst('Exception: ', ''));
          }
        }
        break;
      default:
        throw UnimplementedError("Unimplemented ${call.method} method");
    }
    return null;
  }

  @override
  Future<WebUri?> getUrl() async {
    Map<String, dynamic> args = <String, dynamic>{};
    String? url = await channel?.invokeMethod<String?>('getUrl', args);
    return url != null ? WebUri(url) : null;
  }

  @override
  Future<String?> getTitle() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<String?>('getTitle', args);
  }

  @override
  Future<int?> getProgress() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<int?>('getProgress', args);
  }

  @override
  Future<String?> getHtml() async {
    String? html;

    InAppWebViewSettings? settings = await getSettings();
    if (settings != null && settings.javaScriptEnabled == true) {
      html = await evaluateJavascript(
        source: "window.document.getElementsByTagName('html')[0].outerHTML;",
      );
      if (html != null && html.isNotEmpty) return html;
    }

    var webviewUrl = await getUrl();
    if (webviewUrl == null) {
      return html;
    }

    if (webviewUrl.isScheme("file")) {
      var assetPathSplit = webviewUrl.toString().split("/flutter_assets/");
      var assetPath = assetPathSplit[assetPathSplit.length - 1];
      try {
        var bytes = await rootBundle.load(assetPath);
        html = utf8.decode(bytes.buffer.asUint8List());
      } catch (e) {}
    } else {
      try {
        HttpClient client = HttpClient();
        var htmlRequest = await client.getUrl(webviewUrl);
        html = await (await htmlRequest.close())
            .transform(Utf8Decoder())
            .join();
      } catch (e) {
        developer.log(e.toString(), name: this.runtimeType.toString());
      }
    }

    return html;
  }

  @override
  Future<List<Favicon>> getFavicons() async {
    List<Favicon> favicons = [];

    var webviewUrl = await getUrl();

    if (webviewUrl == null) {
      return favicons;
    }

    String? manifestUrl;

    var html = await getHtml();
    if (html == null || html.isEmpty) {
      return favicons;
    }
    var assetPathBase;

    if (webviewUrl.isScheme("file")) {
      var assetPathSplit = webviewUrl.toString().split("/flutter_assets/");
      assetPathBase = assetPathSplit[0] + "/flutter_assets/";
    }

    InAppWebViewSettings? settings = await getSettings();
    if (settings != null && settings.javaScriptEnabled == true) {
      List<Map<dynamic, dynamic>> links =
          (await evaluateJavascript(
            source: """
(function() {
  var linkNodes = document.head.getElementsByTagName("link");
  var links = [];
  for (var i = 0; i < linkNodes.length; i++) {
    var linkNode = linkNodes[i];
    if (linkNode.rel === 'manifest') {
      links.push(
        {
          rel: linkNode.rel,
          href: linkNode.href,
          sizes: null
        }
      );
    } else if (linkNode.rel != null && linkNode.rel.indexOf('icon') >= 0) {
      links.push(
        {
          rel: linkNode.rel,
          href: linkNode.href,
          sizes: linkNode.sizes != null && linkNode.sizes.value != "" ? linkNode.sizes.value : null
        }
      );
    }
  }
  return links;
})();
""",
          ))?.cast<Map<dynamic, dynamic>>() ??
          [];
      for (var link in links) {
        if (link["rel"] == "manifest") {
          manifestUrl = link["href"];
          if (!_isUrlAbsolute(manifestUrl!)) {
            if (manifestUrl.startsWith("/")) {
              manifestUrl = manifestUrl.substring(1);
            }
            manifestUrl =
                ((assetPathBase == null)
                    ? webviewUrl.scheme + "://" + webviewUrl.host + "/"
                    : assetPathBase) +
                manifestUrl;
          }
          continue;
        }
        favicons.addAll(
          _createFavicons(
            webviewUrl,
            assetPathBase,
            link["href"],
            link["rel"],
            link["sizes"],
            false,
          ),
        );
      }
    }

    // try to get /favicon.ico
    try {
      HttpClient client = HttpClient();
      var faviconUrl =
          webviewUrl.scheme + "://" + webviewUrl.host + "/favicon.ico";
      var faviconUri = WebUri(faviconUrl);
      var headRequest = await client.headUrl(faviconUri);
      var headResponse = await headRequest.close();
      if (headResponse.statusCode == 200) {
        favicons.add(Favicon(url: faviconUri, rel: "shortcut icon"));
      }
    } catch (e) {
      developer.log(
        "/favicon.ico file not found: " + e.toString(),
        name: runtimeType.toString(),
      );
    }

    // try to get the manifest file
    HttpClientRequest? manifestRequest;
    HttpClientResponse? manifestResponse;
    bool manifestFound = false;
    if (manifestUrl == null) {
      manifestUrl =
          webviewUrl.scheme + "://" + webviewUrl.host + "/manifest.json";
    }
    try {
      HttpClient client = HttpClient();
      manifestRequest = await client.getUrl(Uri.parse(manifestUrl));
      manifestResponse = await manifestRequest.close();
      manifestFound =
          manifestResponse.statusCode == 200 &&
          manifestResponse.headers.contentType?.mimeType == "application/json";
    } catch (e) {
      developer.log(
        "Manifest file not found: " + e.toString(),
        name: this.runtimeType.toString(),
      );
    }

    if (manifestFound) {
      try {
        Map<String, dynamic> manifest = json.decode(
          await manifestResponse!.transform(Utf8Decoder()).join(),
        );
        if (manifest.containsKey("icons")) {
          for (Map<String, dynamic> icon in manifest["icons"]) {
            favicons.addAll(
              _createFavicons(
                webviewUrl,
                assetPathBase,
                icon["src"],
                icon["rel"],
                icon["sizes"],
                true,
              ),
            );
          }
        }
      } catch (e) {
        developer.log(
          "Cannot get favicons from Manifest file. It might not have a valid format: " +
              e.toString(),
          error: e,
          name: runtimeType.toString(),
        );
      }
    }

    return favicons;
  }

  bool _isUrlAbsolute(String url) {
    return url.startsWith("http://") || url.startsWith("https://");
  }

  List<Favicon> _createFavicons(
    WebUri url,
    String? assetPathBase,
    String urlIcon,
    String? rel,
    String? sizes,
    bool isManifest,
  ) {
    List<Favicon> favicons = [];

    List<String> urlSplit = urlIcon.split("/");
    if (!_isUrlAbsolute(urlIcon)) {
      if (urlIcon.startsWith("/")) {
        urlIcon = urlIcon.substring(1);
      }
      urlIcon =
          ((assetPathBase == null)
              ? url.scheme + "://" + url.host + "/"
              : assetPathBase) +
          urlIcon;
    }
    if (isManifest) {
      rel = (sizes != null)
          ? urlSplit[urlSplit.length - 1]
                .replaceFirst("-" + sizes, "")
                .split(" ")[0]
                .split(".")[0]
          : null;
    }
    if (sizes != null && sizes.isNotEmpty && sizes != "any") {
      List<String> sizesSplit = sizes.split(" ");
      for (String size in sizesSplit) {
        int width = int.parse(size.split("x")[0]);
        int height = int.parse(size.split("x")[1]);
        favicons.add(
          Favicon(url: WebUri(urlIcon), rel: rel, width: width, height: height),
        );
      }
    } else {
      favicons.add(
        Favicon(url: WebUri(urlIcon), rel: rel, width: null, height: null),
      );
    }

    return favicons;
  }

  @override
  Future<void> loadUrl({
    required URLRequest urlRequest,
    @Deprecated('Use allowingReadAccessTo instead')
    Uri? iosAllowingReadAccessTo,
    WebUri? allowingReadAccessTo,
  }) async {
    assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty);
    assert(
      allowingReadAccessTo == null || allowingReadAccessTo.isScheme("file"),
    );
    assert(
      iosAllowingReadAccessTo == null ||
          iosAllowingReadAccessTo.isScheme("file"),
    );

    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('urlRequest', () => urlRequest.toMap());
    args.putIfAbsent(
      'allowingReadAccessTo',
      () =>
          allowingReadAccessTo?.toString() ??
          iosAllowingReadAccessTo?.toString(),
    );
    await channel?.invokeMethod('loadUrl', args);
  }

  @override
  Future<void> postUrl({
    required WebUri url,
    required Uint8List postData,
  }) async {
    assert(url.toString().isNotEmpty);
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('url', () => url.toString());
    args.putIfAbsent('postData', () => postData);
    await channel?.invokeMethod('postUrl', args);
  }

  @override
  Future<void> loadData({
    required String data,
    String mimeType = "text/html",
    String encoding = "utf8",
    WebUri? baseUrl,
    @Deprecated('Use historyUrl instead') Uri? androidHistoryUrl,
    WebUri? historyUrl,
    @Deprecated('Use allowingReadAccessTo instead')
    Uri? iosAllowingReadAccessTo,
    WebUri? allowingReadAccessTo,
  }) async {
    assert(
      allowingReadAccessTo == null || allowingReadAccessTo.isScheme("file"),
    );
    assert(
      iosAllowingReadAccessTo == null ||
          iosAllowingReadAccessTo.isScheme("file"),
    );

    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('data', () => data);
    args.putIfAbsent('mimeType', () => mimeType);
    args.putIfAbsent('encoding', () => encoding);
    args.putIfAbsent('baseUrl', () => baseUrl?.toString() ?? "about:blank");
    args.putIfAbsent(
      'historyUrl',
      () =>
          historyUrl?.toString() ??
          androidHistoryUrl?.toString() ??
          "about:blank",
    );
    args.putIfAbsent(
      'allowingReadAccessTo',
      () =>
          allowingReadAccessTo?.toString() ??
          iosAllowingReadAccessTo?.toString(),
    );
    await channel?.invokeMethod('loadData', args);
  }

  @override
  Future<void> loadFile({required String assetFilePath}) async {
    assert(assetFilePath.isNotEmpty);
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('assetFilePath', () => assetFilePath);
    await channel?.invokeMethod('loadFile', args);
  }

  @override
  Future<void> reload() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('reload', args);
  }

  @override
  Future<void> goBack() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('goBack', args);
  }

  @override
  Future<bool> canGoBack() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<bool>('canGoBack', args) ?? false;
  }

  @override
  Future<void> goForward() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('goForward', args);
  }

  @override
  Future<bool> canGoForward() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<bool>('canGoForward', args) ?? false;
  }

  @override
  Future<void> goBackOrForward({required int steps}) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('steps', () => steps);
    await channel?.invokeMethod('goBackOrForward', args);
  }

  @override
  Future<bool> canGoBackOrForward({required int steps}) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('steps', () => steps);
    return await channel?.invokeMethod<bool>('canGoBackOrForward', args) ??
        false;
  }

  @override
  Future<void> goTo({required WebHistoryItem historyItem}) async {
    var steps = historyItem.offset;
    if (steps != null) {
      await goBackOrForward(steps: steps);
    }
  }

  @override
  Future<bool> isLoading() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<bool>('isLoading', args) ?? false;
  }

  @override
  Future<void> stopLoading() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('stopLoading', args);
  }

  @override
  Future<dynamic> evaluateJavascript({
    required String source,
    ContentWorld? contentWorld,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('source', () => source);
    args.putIfAbsent('contentWorld', () => contentWorld?.toMap());
    var data = await channel?.invokeMethod('evaluateJavascript', args);
    return data;
  }

  @override
  Future<void> injectJavascriptFileFromUrl({
    required WebUri urlFile,
    ScriptHtmlTagAttributes? scriptHtmlTagAttributes,
  }) async {
    assert(urlFile.toString().isNotEmpty);
    var id = scriptHtmlTagAttributes?.id;
    if (scriptHtmlTagAttributes != null && id != null) {
      _injectedScriptsFromURL[id] = scriptHtmlTagAttributes;
    }
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('urlFile', () => urlFile.toString());
    args.putIfAbsent(
      'scriptHtmlTagAttributes',
      () => scriptHtmlTagAttributes?.toMap(),
    );
    await channel?.invokeMethod('injectJavascriptFileFromUrl', args);
  }

  @override
  Future<dynamic> injectJavascriptFileFromAsset({
    required String assetFilePath,
  }) async {
    String source = await rootBundle.loadString(assetFilePath);
    return await evaluateJavascript(source: source);
  }

  @override
  Future<void> injectCSSCode({required String source}) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('source', () => source);
    await channel?.invokeMethod('injectCSSCode', args);
  }

  @override
  Future<void> injectCSSFileFromUrl({
    required WebUri urlFile,
    CSSLinkHtmlTagAttributes? cssLinkHtmlTagAttributes,
  }) async {
    assert(urlFile.toString().isNotEmpty);
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('urlFile', () => urlFile.toString());
    args.putIfAbsent(
      'cssLinkHtmlTagAttributes',
      () => cssLinkHtmlTagAttributes?.toMap(),
    );
    await channel?.invokeMethod('injectCSSFileFromUrl', args);
  }

  @override
  Future<void> injectCSSFileFromAsset({required String assetFilePath}) async {
    String source = await rootBundle.loadString(assetFilePath);
    await injectCSSCode(source: source);
  }

  @override
  void addJavaScriptHandler({
    required String handlerName,
    required Function callback,
  }) {
    assert(
      !kJavaScriptHandlerForbiddenNames.contains(handlerName),
      '"$handlerName" is a forbidden name!',
    );
    this._javaScriptHandlersMap[handlerName] = (callback);
  }

  @override
  Function? removeJavaScriptHandler({required String handlerName}) {
    return this._javaScriptHandlersMap.remove(handlerName);
  }

  @override
  bool hasJavaScriptHandler({required String handlerName}) {
    return this._javaScriptHandlersMap.containsKey(handlerName);
  }

  @override
  Future<Uint8List?> takeScreenshot({
    ScreenshotConfiguration? screenshotConfiguration,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent(
      'screenshotConfiguration',
      () => screenshotConfiguration?.toMap(),
    );
    return await channel?.invokeMethod<Uint8List?>('takeScreenshot', args);
  }

  @override
  @Deprecated('Use setSettings instead')
  Future<void> setOptions({required InAppWebViewGroupOptions options}) async {
    InAppWebViewSettings settings =
        InAppWebViewSettings.fromMap(options.toMap()) ?? InAppWebViewSettings();
    await setSettings(settings: settings);
  }

  @override
  @Deprecated('Use getSettings instead')
  Future<InAppWebViewGroupOptions?> getOptions() async {
    InAppWebViewSettings? settings = await getSettings();

    Map<dynamic, dynamic>? options = settings?.toMap();
    if (options != null) {
      options = options.cast<String, dynamic>();
      return InAppWebViewGroupOptions.fromMap(options as Map<String, dynamic>);
    }

    return null;
  }

  @override
  Future<void> setSettings({required InAppWebViewSettings settings}) async {
    Map<String, dynamic> args = <String, dynamic>{};

    args.putIfAbsent('settings', () => settings.toMap());
    await channel?.invokeMethod('setSettings', args);
  }

  @override
  Future<InAppWebViewSettings?> getSettings() async {
    Map<String, dynamic> args = <String, dynamic>{};

    Map<dynamic, dynamic>? settings = await channel?.invokeMethod(
      'getSettings',
      args,
    );
    if (settings != null) {
      settings = settings.cast<String, dynamic>();
      return InAppWebViewSettings.fromMap(settings as Map<String, dynamic>);
    }

    return null;
  }

  @override
  Future<WebHistory?> getCopyBackForwardList() async {
    Map<String, dynamic> args = <String, dynamic>{};
    Map<String, dynamic>? result = (await channel?.invokeMethod(
      'getCopyBackForwardList',
      args,
    ))?.cast<String, dynamic>();
    return WebHistory.fromMap(result);
  }

  @override
  @Deprecated("Use InAppWebViewController.clearAllCache instead")
  Future<void> clearCache() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('clearCache', args);
  }

  @override
  @Deprecated("Use FindInteractionController.findAll instead")
  Future<void> findAllAsync({required String find}) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('find', () => find);
    await channel?.invokeMethod('findAll', args);
  }

  @override
  @Deprecated("Use FindInteractionController.findNext instead")
  Future<void> findNext({required bool forward}) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('forward', () => forward);
    await channel?.invokeMethod('findNext', args);
  }

  @override
  @Deprecated("Use FindInteractionController.clearMatches instead")
  Future<void> clearMatches() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('clearMatches', args);
  }

  @override
  @Deprecated("Use tRexRunnerHtml instead")
  Future<String> getTRexRunnerHtml() async {
    return await tRexRunnerHtml;
  }

  @override
  @Deprecated("Use tRexRunnerCss instead")
  Future<String> getTRexRunnerCss() async {
    return await tRexRunnerCss;
  }

  @override
  Future<void> scrollTo({
    required int x,
    required int y,
    bool animated = false,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('x', () => x);
    args.putIfAbsent('y', () => y);
    args.putIfAbsent('animated', () => animated);
    await channel?.invokeMethod('scrollTo', args);
  }

  @override
  Future<void> scrollBy({
    required int x,
    required int y,
    bool animated = false,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('x', () => x);
    args.putIfAbsent('y', () => y);
    args.putIfAbsent('animated', () => animated);
    await channel?.invokeMethod('scrollBy', args);
  }

  @override
  Future<void> pauseTimers() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('pauseTimers', args);
  }

  @override
  Future<void> resumeTimers() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('resumeTimers', args);
  }

  @override
  Future<IOSPrintJobController?> printCurrentPage({
    PrintJobSettings? settings,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent("settings", () => settings?.toMap());
    String? jobId = await channel?.invokeMethod<String?>(
      'printCurrentPage',
      args,
    );
    if (jobId != null) {
      return IOSPrintJobController(
        PlatformPrintJobControllerCreationParams(id: jobId),
      );
    }
    return null;
  }

  @override
  Future<int?> getContentHeight() async {
    Map<String, dynamic> args = <String, dynamic>{};
    var height = await channel?.invokeMethod('getContentHeight', args);
    if (height == null || height == 0) {
      // try to use javascript
      var scrollHeight = await evaluateJavascript(
        source: "document.documentElement.scrollHeight;",
      );
      if (scrollHeight != null && scrollHeight is num) {
        height = scrollHeight.toInt();
      }
    }
    return height;
  }

  @override
  Future<int?> getContentWidth() async {
    Map<String, dynamic> args = <String, dynamic>{};
    var height = await channel?.invokeMethod('getContentWidth', args);
    if (height == null || height == 0) {
      // try to use javascript
      var scrollHeight = await evaluateJavascript(
        source: "document.documentElement.scrollWidth;",
      );
      if (scrollHeight != null && scrollHeight is num) {
        height = scrollHeight.toInt();
      }
    }
    return height;
  }

  @override
  Future<void> zoomBy({
    required double zoomFactor,
    @Deprecated('Use animated instead') bool? iosAnimated,
    bool animated = false,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('zoomFactor', () => zoomFactor);
    args.putIfAbsent('animated', () => iosAnimated ?? animated);
    return await channel?.invokeMethod('zoomBy', args);
  }

  @override
  Future<WebUri?> getOriginalUrl() async {
    Map<String, dynamic> args = <String, dynamic>{};
    String? url = await channel?.invokeMethod<String?>('getOriginalUrl', args);
    return url != null ? WebUri(url) : null;
  }

  @override
  Future<double?> getZoomScale() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<double?>('getZoomScale', args);
  }

  @override
  @Deprecated('Use getZoomScale instead')
  Future<double?> getScale() async {
    return await getZoomScale();
  }

  @override
  Future<String?> getSelectedText() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<String?>('getSelectedText', args);
  }

  @override
  Future<InAppWebViewHitTestResult?> getHitTestResult() async {
    Map<String, dynamic> args = <String, dynamic>{};
    Map<dynamic, dynamic>? hitTestResultMap = await channel?.invokeMethod(
      'getHitTestResult',
      args,
    );

    if (hitTestResultMap == null) {
      return null;
    }

    hitTestResultMap = hitTestResultMap.cast<String, dynamic>();

    InAppWebViewHitTestResultType? type =
        InAppWebViewHitTestResultType.fromNativeValue(
          hitTestResultMap["type"]?.toInt(),
        );
    String? extra = hitTestResultMap["extra"];
    return InAppWebViewHitTestResult(type: type, extra: extra);
  }

  @override
  Future<void> clearFocus() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod('clearFocus', args);
  }

  @override
  Future<void> setInputMethodEnabled(bool enabled) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent("enabled", () => enabled);
    return await channel?.invokeMethod('setInputMethodEnabled', args);
  }

  @override
  Future<void> hideInputMethod() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod('hideInputMethod', args);
  }

  @override
  Future<bool?> requestFocus({
    FocusDirection? direction,
    InAppWebViewRect? previouslyFocusedRect,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent("direction", () => direction?.toNativeValue());
    args.putIfAbsent(
      "previouslyFocusedRect",
      () => previouslyFocusedRect?.toMap(),
    );
    return await channel?.invokeMethod<bool>('requestFocus', args);
  }

  @override
  Future<void> setContextMenu(ContextMenu? contextMenu) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent("contextMenu", () => contextMenu?.toMap());
    await channel?.invokeMethod('setContextMenu', args);
    _inAppBrowser?.setContextMenu(contextMenu);
  }

  @override
  Future<RequestFocusNodeHrefResult?> requestFocusNodeHref() async {
    Map<String, dynamic> args = <String, dynamic>{};
    Map<dynamic, dynamic>? result = await channel?.invokeMethod(
      'requestFocusNodeHref',
      args,
    );
    return result != null
        ? RequestFocusNodeHrefResult(
            url: result['url'] != null ? WebUri(result['url']) : null,
            title: result['title'],
            src: result['src'],
          )
        : null;
  }

  @override
  Future<RequestImageRefResult?> requestImageRef() async {
    Map<String, dynamic> args = <String, dynamic>{};
    Map<dynamic, dynamic>? result = await channel?.invokeMethod(
      'requestImageRef',
      args,
    );
    return result != null
        ? RequestImageRefResult(
            url: result['url'] != null ? WebUri(result['url']) : null,
          )
        : null;
  }

  @override
  Future<List<MetaTag>> getMetaTags() async {
    List<MetaTag> metaTags = [];

    List<Map<dynamic, dynamic>>? metaTagList = (await evaluateJavascript(
      source: """
(function() {
  var metaTags = [];
  var metaTagNodes = document.head.getElementsByTagName('meta');
  for (var i = 0; i < metaTagNodes.length; i++) {
    var metaTagNode = metaTagNodes[i];
    
    var otherAttributes = metaTagNode.getAttributeNames();
    var nameIndex = otherAttributes.indexOf("name");
    if (nameIndex !== -1) otherAttributes.splice(nameIndex, 1);
    var contentIndex = otherAttributes.indexOf("content");
    if (contentIndex !== -1) otherAttributes.splice(contentIndex, 1);
    
    var attrs = [];
    for (var j = 0; j < otherAttributes.length; j++) {
      var otherAttribute = otherAttributes[j];
      attrs.push(
        {
          name: otherAttribute,
          value: metaTagNode.getAttribute(otherAttribute)
        }
      );
    }

    metaTags.push(
      {
        name: metaTagNode.name,
        content: metaTagNode.content,
        attrs: attrs
      }
    );
  }
  return metaTags;
})();
    """,
    ))?.cast<Map<dynamic, dynamic>>();

    if (metaTagList == null) {
      return metaTags;
    }

    for (var metaTag in metaTagList) {
      var attrs = <MetaTagAttribute>[];

      for (var metaTagAttr in metaTag["attrs"]) {
        attrs.add(
          MetaTagAttribute(
            name: metaTagAttr["name"],
            value: metaTagAttr["value"],
          ),
        );
      }

      metaTags.add(
        MetaTag(
          name: metaTag["name"],
          content: metaTag["content"],
          attrs: attrs,
        ),
      );
    }

    return metaTags;
  }

  @override
  Future<Color?> getMetaThemeColor() async {
    Color? themeColor;

    try {
      Map<String, dynamic> args = <String, dynamic>{};
      themeColor = UtilColor.fromStringRepresentation(
        await channel?.invokeMethod('getMetaThemeColor', args),
      );
      return themeColor;
    } catch (e) {
      // not implemented
    }

    // try using javascript
    var metaTags = await getMetaTags();
    MetaTag? metaTagThemeColor;

    for (var metaTag in metaTags) {
      if (metaTag.name == "theme-color") {
        metaTagThemeColor = metaTag;
        break;
      }
    }

    if (metaTagThemeColor == null) {
      return null;
    }

    var colorValue = metaTagThemeColor.content;

    themeColor = colorValue != null
        ? UtilColor.fromStringRepresentation(colorValue)
        : null;

    return themeColor;
  }

  @override
  Future<int?> getScrollX() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<int?>('getScrollX', args);
  }

  @override
  Future<int?> getScrollY() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<int?>('getScrollY', args);
  }

  @override
  Future<SslCertificate?> getCertificate() async {
    Map<String, dynamic> args = <String, dynamic>{};
    Map<String, dynamic>? sslCertificateMap = (await channel?.invokeMethod(
      'getCertificate',
      args,
    ))?.cast<String, dynamic>();
    return SslCertificate.fromMap(sslCertificateMap);
  }

  @override
  Future<void> addUserScript({required UserScript userScript}) async {
    assert(webviewParams?.windowId == null);

    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('userScript', () => userScript.toMap());
    if (!(_userScripts[userScript.injectionTime]?.contains(userScript) ??
        false)) {
      _userScripts[userScript.injectionTime]?.add(userScript);
      await channel?.invokeMethod('addUserScript', args);
    }
  }

  @override
  Future<void> addUserScripts({required List<UserScript> userScripts}) async {
    assert(webviewParams?.windowId == null);

    for (var i = 0; i < userScripts.length; i++) {
      await addUserScript(userScript: userScripts[i]);
    }
  }

  @override
  Future<bool> removeUserScript({required UserScript userScript}) async {
    assert(webviewParams?.windowId == null);

    var index = _userScripts[userScript.injectionTime]?.indexOf(userScript);
    if (index == null || index == -1) {
      return false;
    }

    _userScripts[userScript.injectionTime]?.remove(userScript);
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('userScript', () => userScript.toMap());
    args.putIfAbsent('index', () => index);
    await channel?.invokeMethod('removeUserScript', args);

    return true;
  }

  @override
  Future<void> removeUserScriptsByGroupName({required String groupName}) async {
    assert(webviewParams?.windowId == null);

    final List<UserScript> userScriptsAtDocumentStart = List.from(
      _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START] ?? [],
    );
    for (final userScript in userScriptsAtDocumentStart) {
      if (userScript.groupName == groupName) {
        _userScripts[userScript.injectionTime]?.remove(userScript);
      }
    }

    final List<UserScript> userScriptsAtDocumentEnd = List.from(
      _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END] ?? [],
    );
    for (final userScript in userScriptsAtDocumentEnd) {
      if (userScript.groupName == groupName) {
        _userScripts[userScript.injectionTime]?.remove(userScript);
      }
    }

    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('groupName', () => groupName);
    await channel?.invokeMethod('removeUserScriptsByGroupName', args);
  }

  @override
  Future<void> removeUserScripts({
    required List<UserScript> userScripts,
  }) async {
    assert(webviewParams?.windowId == null);

    for (final userScript in userScripts) {
      await removeUserScript(userScript: userScript);
    }
  }

  @override
  Future<void> removeAllUserScripts() async {
    assert(webviewParams?.windowId == null);

    _userScripts[UserScriptInjectionTime.AT_DOCUMENT_START]?.clear();
    _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear();

    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('removeAllUserScripts', args);
  }

  @override
  bool hasUserScript({required UserScript userScript}) {
    return _userScripts[userScript.injectionTime]?.contains(userScript) ??
        false;
  }

  @override
  Future<CallAsyncJavaScriptResult?> callAsyncJavaScript({
    required String functionBody,
    Map<String, dynamic> arguments = const <String, dynamic>{},
    ContentWorld? contentWorld,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('functionBody', () => functionBody);
    args.putIfAbsent('arguments', () => arguments);
    args.putIfAbsent('contentWorld', () => contentWorld?.toMap());
    var data = await channel?.invokeMethod('callAsyncJavaScript', args);
    if (data == null) {
      return null;
    }
    return CallAsyncJavaScriptResult(
      value: data["value"],
      error: data["error"],
    );
  }

  @override
  Future<String?> saveWebArchive({
    required String filePath,
    bool autoname = false,
  }) async {
    if (!autoname) {
      assert(
        WebArchiveFormat.WEBARCHIVE.isSupported() &&
            filePath.endsWith(
              "." + WebArchiveFormat.WEBARCHIVE.toNativeValue()!,
            ),
      );
    }

    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent("filePath", () => filePath);
    args.putIfAbsent("autoname", () => autoname);
    return await channel?.invokeMethod<String?>('saveWebArchive', args);
  }

  @override
  Future<bool> isSecureContext() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<bool>('isSecureContext', args) ?? false;
  }

  @override
  Future<IOSWebMessageChannel?> createWebMessageChannel() async {
    Map<String, dynamic> args = <String, dynamic>{};
    Map<String, dynamic>? result = (await channel?.invokeMethod(
      'createWebMessageChannel',
      args,
    ))?.cast<String, dynamic>();
    final webMessageChannel = IOSWebMessageChannel.static().fromMap(result);
    if (webMessageChannel != null) {
      _webMessageChannels.add(webMessageChannel);
    }
    return webMessageChannel;
  }

  @override
  Future<void> postWebMessage({
    required WebMessage message,
    WebUri? targetOrigin,
  }) async {
    if (targetOrigin == null) {
      targetOrigin = WebUri('');
    }
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('message', () => message.toMap());
    args.putIfAbsent('targetOrigin', () => targetOrigin.toString());
    await channel?.invokeMethod('postWebMessage', args);
  }

  @override
  Future<void> addWebMessageListener(
    PlatformWebMessageListener webMessageListener,
  ) async {
    assert(
      !_webMessageListeners.contains(webMessageListener),
      "${webMessageListener} was already added.",
    );
    assert(
      !_webMessageListenerObjNames.contains(
        webMessageListener.params.jsObjectName,
      ),
      "jsObjectName ${webMessageListener.params.jsObjectName} was already added.",
    );
    _webMessageListeners.add(webMessageListener as IOSWebMessageListener);
    _webMessageListenerObjNames.add(webMessageListener.params.jsObjectName);

    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('webMessageListener', () => webMessageListener.toMap());
    await channel?.invokeMethod('addWebMessageListener', args);
  }

  @override
  bool hasWebMessageListener(PlatformWebMessageListener webMessageListener) {
    return _webMessageListeners.contains(webMessageListener) ||
        _webMessageListenerObjNames.contains(
          webMessageListener.params.jsObjectName,
        );
  }

  @override
  Future<bool> canScrollVertically() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<bool>('canScrollVertically', args) ??
        false;
  }

  @override
  Future<bool> canScrollHorizontally() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<bool>('canScrollHorizontally', args) ??
        false;
  }

  @override
  Future<void> reloadFromOrigin() async {
    Map<String, dynamic> args = <String, dynamic>{};
    await channel?.invokeMethod('reloadFromOrigin', args);
  }

  @override
  Future<Uint8List?> createPdf({
    @Deprecated("Use pdfConfiguration instead")
    // ignore: deprecated_member_use_from_same_package
    IOSWKPDFConfiguration? iosWKPdfConfiguration,
    PDFConfiguration? pdfConfiguration,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent(
      'pdfConfiguration',
      () => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap(),
    );
    return await channel?.invokeMethod<Uint8List?>('createPdf', args);
  }

  @override
  Future<Uint8List?> createWebArchiveData() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod('createWebArchiveData', args);
  }

  @override
  Future<bool> hasOnlySecureContent() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<bool>('hasOnlySecureContent', args) ??
        false;
  }

  @override
  Future<void> pauseAllMediaPlayback() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod('pauseAllMediaPlayback', args);
  }

  @override
  Future<void> setAllMediaPlaybackSuspended({required bool suspended}) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent("suspended", () => suspended);
    return await channel?.invokeMethod('setAllMediaPlaybackSuspended', args);
  }

  @override
  Future<void> closeAllMediaPresentations() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod('closeAllMediaPresentations', args);
  }

  @override
  Future<MediaPlaybackState?> requestMediaPlaybackState() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return MediaPlaybackState.fromNativeValue(
      await channel?.invokeMethod('requestMediaPlaybackState', args),
    );
  }

  @override
  Future<bool> isInFullscreen() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<bool>('isInFullscreen', args) ?? false;
  }

  @override
  Future<MediaCaptureState?> getCameraCaptureState() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return MediaCaptureState.fromNativeValue(
      await channel?.invokeMethod('getCameraCaptureState', args),
    );
  }

  @override
  Future<void> setCameraCaptureState({required MediaCaptureState state}) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('state', () => state.toNativeValue());
    await channel?.invokeMethod('setCameraCaptureState', args);
  }

  @override
  Future<MediaCaptureState?> getMicrophoneCaptureState() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return MediaCaptureState.fromNativeValue(
      await channel?.invokeMethod('getMicrophoneCaptureState', args),
    );
  }

  @override
  Future<void> setMicrophoneCaptureState({
    required MediaCaptureState state,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('state', () => state.toNativeValue());
    await channel?.invokeMethod('setMicrophoneCaptureState', args);
  }

  @override
  Future<void> loadSimulatedRequest({
    required URLRequest urlRequest,
    required Uint8List data,
    URLResponse? urlResponse,
  }) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('urlRequest', () => urlRequest.toMap());
    args.putIfAbsent('data', () => data);
    args.putIfAbsent('urlResponse', () => urlResponse?.toMap());
    await channel?.invokeMethod('loadSimulatedRequest', args);
  }

  @override
  Future<Uint8List?> saveState() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await channel?.invokeMethod<Uint8List?>('saveState', args);
  }

  @override
  Future<bool> restoreState(Uint8List? state) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('state', () => state);
    return await channel?.invokeMethod<bool>('restoreState', args) ?? false;
  }

  @override
  Future<String> getDefaultUserAgent() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await _staticChannel.invokeMethod<String>(
          'getDefaultUserAgent',
          args,
        ) ??
        '';
  }

  @override
  Future<bool> handlesURLScheme(String urlScheme) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('urlScheme', () => urlScheme);
    return await _staticChannel.invokeMethod('handlesURLScheme', args);
  }

  @override
  Future<void> disposeKeepAlive(InAppWebViewKeepAlive keepAlive) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('keepAliveId', () => keepAlive.id);
    await _staticChannel.invokeMethod('disposeKeepAlive', args);
    _keepAliveMap[keepAlive] = null;
  }

  @override
  Future<void> clearAllCache({bool includeDiskFiles = true}) async {
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('includeDiskFiles', () => includeDiskFiles);
    await _staticChannel.invokeMethod('clearAllCache', args);
  }

  @override
  Future<void> setJavaScriptBridgeName(String bridgeName) async {
    assert(
      RegExp(r'^[a-zA-Z_]\w*$').hasMatch(bridgeName),
      'bridgeName must be a non-empty string with only alphanumeric and underscore characters. It can\'t start with a number.',
    );
    Map<String, dynamic> args = <String, dynamic>{};
    args.putIfAbsent('bridgeName', () => bridgeName);
    await _staticChannel.invokeMethod('setJavaScriptBridgeName', args);
  }

  @override
  Future<String> getJavaScriptBridgeName() async {
    Map<String, dynamic> args = <String, dynamic>{};
    return await _staticChannel.invokeMethod<String>(
          'getJavaScriptBridgeName',
          args,
        ) ??
        '';
  }

  @override
  Future<String> get tRexRunnerHtml async => await rootBundle.loadString(
    'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.html',
  );

  @override
  Future<String> get tRexRunnerCss async => await rootBundle.loadString(
    'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css',
  );

  @override
  dynamic getViewId() {
    return id;
  }

  @override
  void dispose({bool isKeepAlive = false}) {
    disposeChannel(removeMethodCallHandler: !isKeepAlive);
    _inAppBrowser = null;
    webStorage.dispose();
    if (!isKeepAlive) {
      _controllerFromPlatform = null;
      _javaScriptHandlersMap.clear();
      _userScripts.clear();
      _webMessageListenerObjNames.clear();
      _injectedScriptsFromURL.clear();
      for (final webMessageChannel in _webMessageChannels) {
        webMessageChannel.dispose();
      }
      _webMessageChannels.clear();
      for (final webMessageListener in _webMessageListeners) {
        webMessageListener.dispose();
      }
      _webMessageListeners.clear();
    }
  }
}

extension InternalInAppWebViewController on IOSInAppWebViewController {
  get handleMethod => _handleMethod;
}
