From a623ee718dc4a7806d449d73329fb59c27f94fc7 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 11 Nov 2023 13:18:02 +0100 Subject: [PATCH] Throw an error if any controller is used after being disposed, created internal ChannelController class --- CHANGELOG.md | 3 +- lib/src/android/proxy_controller.dart | 1 - .../chrome_safari_action_button.dart | 1 + .../chrome_safari_browser.dart | 54 ++-- .../find_interaction_controller.dart | 44 +-- lib/src/in_app_browser/in_app_browser.dart | 44 ++- .../headless_in_app_webview.dart | 26 +- .../in_app_webview_controller.dart | 274 ++++++++++-------- .../in_app_webview_keep_alive.dart | 8 +- lib/src/in_app_webview/main.dart | 2 +- lib/src/print_job/print_job_controller.dart | 31 +- .../pull_to_refresh_controller.dart | 48 ++- lib/src/util.dart | 59 ++++ .../headless_in_app_web_view_web_element.dart | 24 +- lib/src/web/platform_util.dart | 23 +- .../web_authenticate_session.dart | 30 +- lib/src/web_message/web_message_channel.dart | 25 +- lib/src/web_message/web_message_listener.dart | 23 +- lib/src/web_message/web_message_port.dart | 6 +- lib/src/web_storage/web_storage.dart | 7 +- 20 files changed, 368 insertions(+), 365 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b0bbac..33dd0171 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 6.0.0-beta.26 -- Updated return value for `CookieManager.setCookie` method to be `Future`. The return value indicates whether the cookie was set successfully. +- Throw an error if any controller is used after being disposed +- Updated return value for `CookieManager.setCookie` method to be `Future`. The return value indicates whether the cookie was set successfully - Merged "feat(ios): optional tradeoff to fix ios input delay" [#1665](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1665) (thanks to [andreasgangso](https://github.com/andreasgangso)) - Merged "Fix ios multiple flutter presenting error" [#1736](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1736) (thanks to [AlexT84](https://github.com/AlexT84)) - Merged "fix cert parsing for ios 12" [#1822](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1822) (thanks to [darkang3lz92](https://github.com/darkang3lz92)) diff --git a/lib/src/android/proxy_controller.dart b/lib/src/android/proxy_controller.dart index 988db110..1bea3928 100644 --- a/lib/src/android/proxy_controller.dart +++ b/lib/src/android/proxy_controller.dart @@ -4,7 +4,6 @@ import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_i import '../types/proxy_rule.dart'; import 'webview_feature.dart'; import '../in_app_webview/webview.dart'; -import '../types/main.dart'; part 'proxy_controller.g.dart'; diff --git a/lib/src/chrome_safari_browser/chrome_safari_action_button.dart b/lib/src/chrome_safari_browser/chrome_safari_action_button.dart index 89cc03cd..7f2a56bf 100644 --- a/lib/src/chrome_safari_browser/chrome_safari_action_button.dart +++ b/lib/src/chrome_safari_browser/chrome_safari_action_button.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart'; import 'chrome_safari_browser.dart'; +import 'chrome_safari_browser_menu_item.dart'; import '../web_uri.dart'; part 'chrome_safari_action_button.g.dart'; diff --git a/lib/src/chrome_safari_browser/chrome_safari_browser.dart b/lib/src/chrome_safari_browser/chrome_safari_browser.dart index cb55e224..e4f636ba 100755 --- a/lib/src/chrome_safari_browser/chrome_safari_browser.dart +++ b/lib/src/chrome_safari_browser/chrome_safari_browser.dart @@ -26,7 +26,7 @@ import 'chrome_safari_browser_secondary_toolbar.dart'; ///**Supported Platforms/Implementations**: ///- Android ///- iOS -class ChromeSafariBrowser { +class ChromeSafariBrowser extends ChannelController { ///Debug settings. static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings(); @@ -37,36 +37,23 @@ class ChromeSafariBrowser { Map _menuItems = new HashMap(); ChromeSafariBrowserSecondaryToolbar? _secondaryToolbar; bool _isOpened = false; - MethodChannel? _channel; static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser'); ChromeSafariBrowser() { id = IdGenerator.generate(); - this._channel = + channel = MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id'); - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); _isOpened = false; } _init() { - this._channel = + channel = MethodChannel('com.pichillilorenzo/flutter_chromesafaribrowser_$id'); - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } _debugLog(String method, dynamic args) { @@ -116,7 +103,7 @@ class ChromeSafariBrowser { break; case "onClosed": _isOpened = false; - _dispose(); + dispose(); onClosed(); break; case "onItemActionPerform": @@ -196,8 +183,8 @@ class ChromeSafariBrowser { List? otherLikelyURLs, WebUri? referrer, @Deprecated('Use settings instead') - // ignore: deprecated_member_use_from_same_package - ChromeSafariBrowserClassOptions? options, + // ignore: deprecated_member_use_from_same_package + ChromeSafariBrowserClassOptions? options, ChromeSafariBrowserSettings? settings}) async { assert(!_isOpened, 'The browser is already opened.'); _isOpened = true; @@ -262,7 +249,7 @@ class ChromeSafariBrowser { args.putIfAbsent('otherLikelyURLs', () => otherLikelyURLs?.map((e) => e.toString()).toList()); args.putIfAbsent('referrer', () => referrer?.toString()); - await _channel?.invokeMethod("launchUrl", args); + await channel?.invokeMethod("launchUrl", args); } ///Tells the browser of a likely future navigation to a URL. @@ -283,7 +270,7 @@ class ChromeSafariBrowser { args.putIfAbsent('url', () => url?.toString()); args.putIfAbsent('otherLikelyURLs', () => otherLikelyURLs?.map((e) => e.toString()).toList()); - return await _channel?.invokeMethod("mayLaunchUrl", args); + return await channel?.invokeMethod("mayLaunchUrl", args) ?? false; } ///Requests to validate a relationship between the application and an origin. @@ -308,7 +295,7 @@ class ChromeSafariBrowser { Map args = {}; args.putIfAbsent('relation', () => relation.toNativeValue()); args.putIfAbsent('origin', () => origin.toString()); - return await _channel?.invokeMethod("validateRelationship", args); + return await channel?.invokeMethod("validateRelationship", args) ?? false; } ///Closes the [ChromeSafariBrowser] instance. @@ -318,7 +305,7 @@ class ChromeSafariBrowser { ///- iOS Future close() async { Map args = {}; - await _channel?.invokeMethod("close", args); + await channel?.invokeMethod("close", args); } ///Set a custom action button. @@ -342,7 +329,7 @@ class ChromeSafariBrowser { Map args = {}; args.putIfAbsent('icon', () => icon); args.putIfAbsent('description', () => description); - await _channel?.invokeMethod("updateActionButton", args); + await channel?.invokeMethod("updateActionButton", args); _actionButton?.icon = icon; _actionButton?.description = description; } @@ -368,7 +355,7 @@ class ChromeSafariBrowser { ChromeSafariBrowserSecondaryToolbar secondaryToolbar) async { Map args = {}; args.putIfAbsent('secondaryToolbar', () => secondaryToolbar.toMap()); - await _channel?.invokeMethod("updateSecondaryToolbar", args); + await channel?.invokeMethod("updateSecondaryToolbar", args); this._secondaryToolbar = secondaryToolbar; } @@ -414,7 +401,7 @@ class ChromeSafariBrowser { ///- Android static Future getMaxToolbarItems() async { Map args = {}; - return await _sharedChannel.invokeMethod("getMaxToolbarItems", args); + return await _sharedChannel.invokeMethod("getMaxToolbarItems", args) ?? 0; } ///Clear associated website data accrued from browsing activity within your app. @@ -543,8 +530,9 @@ class ChromeSafariBrowser { } ///Disposes the channel. - void _dispose() { - _channel?.setMethodCallHandler(null); - _channel = null; + @override + @mustCallSuper + void dispose() { + disposeChannel(); } } diff --git a/lib/src/find_interaction/find_interaction_controller.dart b/lib/src/find_interaction/find_interaction_controller.dart index d67e59b1..03d7b8a7 100644 --- a/lib/src/find_interaction/find_interaction_controller.dart +++ b/lib/src/find_interaction/find_interaction_controller.dart @@ -8,9 +8,7 @@ import '../util.dart'; ///- Android native WebView ///- iOS ///- MacOS -class FindInteractionController { - MethodChannel? _channel; - +class FindInteractionController extends ChannelController { ///Debug settings. static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings(); @@ -81,7 +79,7 @@ class FindInteractionController { Future findAll({String? find}) async { Map args = {}; args.putIfAbsent('find', () => find); - await _channel?.invokeMethod('findAll', args); + await channel?.invokeMethod('findAll', args); } ///Highlights and scrolls to the next match found by [findAll]. Notifies [FindInteractionController.onFindResultReceived] listener. @@ -99,7 +97,7 @@ class FindInteractionController { Future findNext({bool forward = true}) async { Map args = {}; args.putIfAbsent('forward', () => forward); - await _channel?.invokeMethod('findNext', args); + await channel?.invokeMethod('findNext', args); } ///Clears the highlighting surrounding text matches created by [findAll]. @@ -114,7 +112,7 @@ class FindInteractionController { ///- MacOS Future clearMatches() async { Map args = {}; - await _channel?.invokeMethod('clearMatches', args); + await channel?.invokeMethod('clearMatches', args); } ///Pre-populate the search text to be used. @@ -129,7 +127,7 @@ class FindInteractionController { Future setSearchText(String? searchText) async { Map args = {}; args.putIfAbsent('searchText', () => searchText); - await _channel?.invokeMethod('setSearchText', args); + await channel?.invokeMethod('setSearchText', args); } ///Get the search text used. @@ -143,7 +141,7 @@ class FindInteractionController { ///- MacOS Future getSearchText() async { Map args = {}; - return await _channel?.invokeMethod('getSearchText', args); + return await channel?.invokeMethod('getSearchText', args); } ///A Boolean value that indicates when the find panel displays onscreen. @@ -154,7 +152,7 @@ class FindInteractionController { ///- iOS ([Official API - UIFindInteraction.isFindNavigatorVisible](https://developer.apple.com/documentation/uikit/uifindinteraction/3975828-isfindnavigatorvisible?changes=_2)) Future isFindNavigatorVisible() async { Map args = {}; - return await _channel?.invokeMethod('isFindNavigatorVisible', args); + return await channel?.invokeMethod('isFindNavigatorVisible', args); } ///Updates the results the interface displays for the active search. @@ -165,7 +163,7 @@ class FindInteractionController { ///- iOS ([Official API - UIFindInteraction.updateResultCount](https://developer.apple.com/documentation/uikit/uifindinteraction/3975835-updateresultcount?changes=_2)) Future updateResultCount() async { Map args = {}; - await _channel?.invokeMethod('updateResultCount', args); + await channel?.invokeMethod('updateResultCount', args); } ///Begins a search, displaying the find panel. @@ -176,7 +174,7 @@ class FindInteractionController { ///- iOS ([Official API - UIFindInteraction.presentFindNavigator](https://developer.apple.com/documentation/uikit/uifindinteraction/3975832-presentfindnavigator?changes=_2)) Future presentFindNavigator() async { Map args = {}; - await _channel?.invokeMethod('presentFindNavigator', args); + await channel?.invokeMethod('presentFindNavigator', args); } ///Dismisses the find panel, if present. @@ -187,7 +185,7 @@ class FindInteractionController { ///- iOS ([Official API - UIFindInteraction.dismissFindNavigator](https://developer.apple.com/documentation/uikit/uifindinteraction/3975827-dismissfindnavigator?changes=_2)) Future dismissFindNavigator() async { Map args = {}; - await _channel?.invokeMethod('dismissFindNavigator', args); + await channel?.invokeMethod('dismissFindNavigator', args); } ///If there's a currently active find session, returns the active find session. @@ -199,33 +197,23 @@ class FindInteractionController { Future getActiveFindSession() async { Map args = {}; Map? result = - (await _channel?.invokeMethod('getActiveFindSession', args)) + (await channel?.invokeMethod('getActiveFindSession', args)) ?.cast(); return FindSession.fromMap(result); } ///Disposes the controller. + @override void dispose({bool isKeepAlive = false}) { - if (!isKeepAlive) { - _channel?.setMethodCallHandler(null); - } - _channel = null; + disposeChannel(removeMethodCallHandler: !isKeepAlive); } } extension InternalFindInteractionController on FindInteractionController { void init(dynamic id) { - this._channel = MethodChannel( + channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_find_interaction_$id'); - - this._channel?.setMethodCallHandler((call) async { - if (_channel == null) return null; - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } } diff --git a/lib/src/in_app_browser/in_app_browser.dart b/lib/src/in_app_browser/in_app_browser.dart index 66653eb1..f54ebc78 100755 --- a/lib/src/in_app_browser/in_app_browser.dart +++ b/lib/src/in_app_browser/in_app_browser.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'dart:typed_data'; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../context_menu/context_menu.dart'; @@ -28,7 +29,7 @@ import '../pull_to_refresh/pull_to_refresh_controller.dart'; ///- Android native WebView ///- iOS ///- MacOS -class InAppBrowser { +class InAppBrowser extends ChannelController { ///Debug settings. static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings(); @@ -48,7 +49,6 @@ class InAppBrowser { final UnmodifiableListView? initialUserScripts; bool _isOpened = false; - MethodChannel? _channel; static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser'); @@ -70,18 +70,13 @@ class InAppBrowser { } _init() { - this._channel = + channel = MethodChannel('com.pichillilorenzo/flutter_inappbrowser_$id'); - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); + _webViewController = new InAppWebViewController.fromInAppBrowser( - this._channel!, this, this.initialUserScripts); + channel!, this, this.initialUserScripts); pullToRefreshController?.init(id); findInteractionController?.init(id); } @@ -113,7 +108,7 @@ class InAppBrowser { case "onExit": _debugLog(call.method, call.arguments); _isOpened = false; - _dispose(); + dispose(); onExit(); break; default: @@ -366,7 +361,7 @@ class InAppBrowser { assert(_isOpened, 'The browser is not opened.'); Map args = {}; - await _channel?.invokeMethod('show', args); + await channel?.invokeMethod('show', args); } ///Hides the [InAppBrowser] window. Calling this has no effect if the [InAppBrowser] was already hidden. @@ -379,7 +374,7 @@ class InAppBrowser { assert(_isOpened, 'The browser is not opened.'); Map args = {}; - await _channel?.invokeMethod('hide', args); + await channel?.invokeMethod('hide', args); } ///Closes the [InAppBrowser] window. @@ -392,7 +387,7 @@ class InAppBrowser { assert(_isOpened, 'The browser is not opened.'); Map args = {}; - await _channel?.invokeMethod('close', args); + await channel?.invokeMethod('close', args); } ///Check if the Web View of the [InAppBrowser] instance is hidden. @@ -405,7 +400,7 @@ class InAppBrowser { assert(_isOpened, 'The browser is not opened.'); Map args = {}; - return await _channel?.invokeMethod('isHidden', args); + return await channel?.invokeMethod('isHidden', args) ?? false; } ///Use [setSettings] instead. @@ -415,7 +410,7 @@ class InAppBrowser { Map args = {}; args.putIfAbsent('settings', () => options.toMap()); - await _channel?.invokeMethod('setSettings', args); + await channel?.invokeMethod('setSettings', args); } ///Use [getSettings] instead. @@ -425,7 +420,7 @@ class InAppBrowser { Map args = {}; Map? options = - await _channel?.invokeMethod('getSettings', args); + await channel?.invokeMethod('getSettings', args); if (options != null) { options = options.cast(); return InAppBrowserClassOptions.fromMap(options as Map); @@ -446,7 +441,7 @@ class InAppBrowser { Map args = {}; args.putIfAbsent('settings', () => settings.toMap()); - await _channel?.invokeMethod('setSettings', args); + await channel?.invokeMethod('setSettings', args); } ///Gets the current [InAppBrowser] settings. Returns `null` if it wasn't able to get them. @@ -461,7 +456,7 @@ class InAppBrowser { Map args = {}; Map? settings = - await _channel?.invokeMethod('getSettings', args); + await channel?.invokeMethod('getSettings', args); if (settings != null) { settings = settings.cast(); return InAppBrowserClassSettings.fromMap( @@ -1361,9 +1356,10 @@ class InAppBrowser { void onContentSizeChanged(Size oldContentSize, Size newContentSize) {} ///Disposes the channel and controllers. - void _dispose() { - _channel?.setMethodCallHandler(null); - _channel = null; + @override + @mustCallSuper + void dispose() { + disposeChannel(); _webViewController?.dispose(); _webViewController = null; pullToRefreshController?.dispose(); diff --git a/lib/src/in_app_webview/headless_in_app_webview.dart b/lib/src/in_app_webview/headless_in_app_webview.dart index 37eee388..310a83dc 100644 --- a/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/lib/src/in_app_webview/headless_in_app_webview.dart @@ -29,7 +29,7 @@ import '../types/disposable.dart'; ///- Web ///- MacOS ///{@endtemplate} -class HeadlessInAppWebView implements WebView, Disposable { +class HeadlessInAppWebView extends ChannelController implements WebView, Disposable { ///View ID. late final String id; @@ -38,7 +38,6 @@ class HeadlessInAppWebView implements WebView, Disposable { static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview'); - MethodChannel? _channel; InAppWebViewController? _webViewController; @@ -187,19 +186,13 @@ class HeadlessInAppWebView implements WebView, Disposable { _webViewController = InAppWebViewController(id, this); pullToRefreshController?.init(id); findInteractionController?.init(id); - this._channel = + channel = MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview_$id'); - this._channel?.setMethodCallHandler((call) async { - try { - return await handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } - Future handleMethod(MethodCall call) async { + Future _handleMethod(MethodCall call) async { switch (call.method) { case "onWebViewCreated": if (onWebViewCreated != null && _webViewController != null) { @@ -310,9 +303,8 @@ class HeadlessInAppWebView implements WebView, Disposable { return; } Map args = {}; - await _channel?.invokeMethod('dispose', args); - _channel?.setMethodCallHandler(null); - _channel = null; + await channel?.invokeMethod('dispose', args); + disposeChannel(); _started = false; _running = false; _webViewController?.dispose(); @@ -353,7 +345,7 @@ class HeadlessInAppWebView implements WebView, Disposable { Map args = {}; args.putIfAbsent('size', () => size.toMap()); - await _channel?.invokeMethod('setSize', args); + await channel?.invokeMethod('setSize', args); } ///Gets the current size in pixels of the WebView. @@ -372,7 +364,7 @@ class HeadlessInAppWebView implements WebView, Disposable { Map args = {}; Map sizeMap = - (await _channel?.invokeMethod('getSize', args)) + (await channel?.invokeMethod('getSize', args)) ?.cast(); return MapSize.fromMap(sizeMap); } diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index ce252146..9b9c56fa 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -50,12 +50,11 @@ final _JAVASCRIPT_HANDLER_FORBIDDEN_NAMES = UnmodifiableListView([ /// ///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 [InAppBrowser] instance, you can get it through the [InAppBrowser.webViewController] attribute. -class InAppWebViewController { +class InAppWebViewController extends ChannelController { WebView? _webview; - MethodChannel? _channel; static final MethodChannel _staticChannel = IN_APP_WEBVIEW_STATIC_CHANNEL; - // properties to be saved and restored for keep alive feature + // List of properties to be saved and restored for keep alive feature Map _javaScriptHandlersMap = HashMap(); Map> _userScripts = { @@ -64,6 +63,8 @@ class InAppWebViewController { }; Set _webMessageListenerObjNames = Set(); Map _injectedScriptsFromURL = {}; + Set _webMessageChannels = Set(); + Set _webMessageListeners = Set(); // static map that contains the properties to be saved and restored for keep alive feature static final Map @@ -86,17 +87,10 @@ class InAppWebViewController { InAppWebViewController(dynamic id, WebView webview) { this._id = id; - this._channel = - MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); - this._channel?.setMethodCallHandler((call) async { - if (_channel == null) return null; - try { - return await handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + channel = MethodChannel('com.pichillilorenzo/flutter_inappwebview_$id'); + handler = handleMethod; + initMethodCallHandler(); + this._webview = webview; final initialUserScripts = webview.initialUserScripts; @@ -122,7 +116,7 @@ class InAppWebViewController { MethodChannel channel, InAppBrowser inAppBrowser, UnmodifiableListView? initialUserScripts) { - this._channel = channel; + this.channel = channel; this._inAppBrowser = inAppBrowser; if (initialUserScripts != null) { @@ -143,8 +137,8 @@ class InAppWebViewController { } void _init() { - android = AndroidInAppWebViewController(channel: _channel!); - ios = IOSInAppWebViewController(channel: _channel!); + android = AndroidInAppWebViewController(channel: channel!); + ios = IOSInAppWebViewController(channel: channel!); webStorage = WebStorage( localStorage: LocalStorage(this), sessionStorage: SessionStorage(this)); @@ -158,13 +152,17 @@ class InAppWebViewController { injectedScriptsFromURL: _injectedScriptsFromURL, javaScriptHandlersMap: _javaScriptHandlersMap, userScripts: _userScripts, - webMessageListenerObjNames: _webMessageListenerObjNames); + 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; + _webMessageListeners = props.webMessageListeners; } } } @@ -180,7 +178,7 @@ class InAppWebViewController { args: args); } - Future handleMethod(MethodCall call) async { + Future _handleMethod(MethodCall call) async { if (WebView.debugLoggingSettings.enabled && call.method != "onCallJsHandler") { _debugLog(call.method, call.arguments); @@ -1430,7 +1428,7 @@ class InAppWebViewController { ///- Web Future getUrl() async { Map args = {}; - String? url = await _channel?.invokeMethod('getUrl', args); + String? url = await channel?.invokeMethod('getUrl', args); return url != null ? WebUri(url) : null; } @@ -1445,7 +1443,7 @@ class InAppWebViewController { ///- Web Future getTitle() async { Map args = {}; - return await _channel?.invokeMethod('getTitle', args); + return await channel?.invokeMethod('getTitle', args); } ///Gets the progress for the current page. The progress value is between 0 and 100. @@ -1456,7 +1454,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.estimatedProgress](https://developer.apple.com/documentation/webkit/wkwebview/1415007-estimatedprogress)) Future getProgress() async { Map args = {}; - return await _channel?.invokeMethod('getProgress', args); + return await channel?.invokeMethod('getProgress', args); } ///Gets the content html of the page. It first tries to get the content through javascript. @@ -1704,7 +1702,7 @@ class InAppWebViewController { Future loadUrl( {required URLRequest urlRequest, @Deprecated('Use allowingReadAccessTo instead') - Uri? iosAllowingReadAccessTo, + Uri? iosAllowingReadAccessTo, WebUri? allowingReadAccessTo}) async { assert(urlRequest.url != null && urlRequest.url.toString().isNotEmpty); assert( @@ -1719,7 +1717,7 @@ class InAppWebViewController { () => allowingReadAccessTo?.toString() ?? iosAllowingReadAccessTo?.toString()); - await _channel?.invokeMethod('loadUrl', args); + await channel?.invokeMethod('loadUrl', args); } ///Loads the given [url] with [postData] (x-www-form-urlencoded) using `POST` method into this WebView. @@ -1743,7 +1741,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('url', () => url.toString()); args.putIfAbsent('postData', () => postData); - await _channel?.invokeMethod('postUrl', args); + await channel?.invokeMethod('postUrl', args); } ///Loads the given [data] into this WebView, using [baseUrl] as the base URL for the content. @@ -1770,11 +1768,10 @@ class InAppWebViewController { String mimeType = "text/html", String encoding = "utf8", WebUri? baseUrl, - @Deprecated('Use historyUrl instead') - Uri? androidHistoryUrl, + @Deprecated('Use historyUrl instead') Uri? androidHistoryUrl, WebUri? historyUrl, @Deprecated('Use allowingReadAccessTo instead') - Uri? iosAllowingReadAccessTo, + Uri? iosAllowingReadAccessTo, WebUri? allowingReadAccessTo}) async { assert( allowingReadAccessTo == null || allowingReadAccessTo.isScheme("file")); @@ -1797,7 +1794,7 @@ class InAppWebViewController { () => allowingReadAccessTo?.toString() ?? iosAllowingReadAccessTo?.toString()); - await _channel?.invokeMethod('loadData', args); + await channel?.invokeMethod('loadData', args); } ///Loads the given [assetFilePath]. @@ -1839,7 +1836,7 @@ class InAppWebViewController { assert(assetFilePath.isNotEmpty); Map args = {}; args.putIfAbsent('assetFilePath', () => assetFilePath); - await _channel?.invokeMethod('loadFile', args); + await channel?.invokeMethod('loadFile', args); } ///Reloads the WebView. @@ -1853,7 +1850,7 @@ class InAppWebViewController { ///- Web ([Official API - Location.reload](https://developer.mozilla.org/en-US/docs/Web/API/Location/reload)) Future reload() async { Map args = {}; - await _channel?.invokeMethod('reload', args); + await channel?.invokeMethod('reload', args); } ///Goes back in the history of the WebView. @@ -1867,7 +1864,7 @@ class InAppWebViewController { ///- Web ([Official API - History.back](https://developer.mozilla.org/en-US/docs/Web/API/History/back)) Future goBack() async { Map args = {}; - await _channel?.invokeMethod('goBack', args); + await channel?.invokeMethod('goBack', args); } ///Returns a boolean value indicating whether the WebView can move backward. @@ -1878,7 +1875,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.canGoBack](https://developer.apple.com/documentation/webkit/wkwebview/1414966-cangoback)) Future canGoBack() async { Map args = {}; - return await _channel?.invokeMethod('canGoBack', args); + return await channel?.invokeMethod('canGoBack', args) ?? false; } ///Goes forward in the history of the WebView. @@ -1892,7 +1889,7 @@ class InAppWebViewController { ///- Web ([Official API - History.forward](https://developer.mozilla.org/en-US/docs/Web/API/History/forward)) Future goForward() async { Map args = {}; - await _channel?.invokeMethod('goForward', args); + await channel?.invokeMethod('goForward', args); } ///Returns a boolean value indicating whether the WebView can move forward. @@ -1903,7 +1900,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.canGoForward](https://developer.apple.com/documentation/webkit/wkwebview/1414962-cangoforward)) Future canGoForward() async { Map args = {}; - return await _channel?.invokeMethod('canGoForward', args); + return await channel?.invokeMethod('canGoForward', args) ?? false; } ///Goes to the history item that is the number of steps away from the current item. Steps is negative if backward and positive if forward. @@ -1918,7 +1915,7 @@ class InAppWebViewController { Future goBackOrForward({required int steps}) async { Map args = {}; args.putIfAbsent('steps', () => steps); - await _channel?.invokeMethod('goBackOrForward', args); + await channel?.invokeMethod('goBackOrForward', args); } ///Returns a boolean value indicating whether the WebView can go back or forward the given number of steps. Steps is negative if backward and positive if forward. @@ -1930,7 +1927,8 @@ class InAppWebViewController { Future canGoBackOrForward({required int steps}) async { Map args = {}; args.putIfAbsent('steps', () => steps); - return await _channel?.invokeMethod('canGoBackOrForward', args); + return await channel?.invokeMethod('canGoBackOrForward', args) ?? + false; } ///Navigates to a [WebHistoryItem] from the back-forward [WebHistory.list] and sets it as the current item. @@ -1958,7 +1956,7 @@ class InAppWebViewController { ///- Web Future isLoading() async { Map args = {}; - return await _channel?.invokeMethod('isLoading', args); + return await channel?.invokeMethod('isLoading', args) ?? false; } ///Stops the WebView from loading. @@ -1972,7 +1970,7 @@ class InAppWebViewController { ///- Web ([Official API - Window.stop](https://developer.mozilla.org/en-US/docs/Web/API/Window/stop)) Future stopLoading() async { Map args = {}; - await _channel?.invokeMethod('stopLoading', args); + await channel?.invokeMethod('stopLoading', args); } ///Evaluates JavaScript [source] code into the WebView and returns the result of the evaluation. @@ -2002,7 +2000,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('source', () => source); args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); - var data = await _channel?.invokeMethod('evaluateJavascript', args); + var data = await channel?.invokeMethod('evaluateJavascript', args); if (data != null && (Util.isAndroid || Util.isWeb)) { try { // try to json decode the data coming from JavaScript @@ -2041,7 +2039,7 @@ class InAppWebViewController { args.putIfAbsent('urlFile', () => urlFile.toString()); args.putIfAbsent( 'scriptHtmlTagAttributes', () => scriptHtmlTagAttributes?.toMap()); - await _channel?.invokeMethod('injectJavascriptFileFromUrl', args); + await channel?.invokeMethod('injectJavascriptFileFromUrl', args); } ///Evaluates the content of a JavaScript file into the WebView from the flutter assets directory. @@ -2081,7 +2079,7 @@ class InAppWebViewController { Future injectCSSCode({required String source}) async { Map args = {}; args.putIfAbsent('source', () => source); - await _channel?.invokeMethod('injectCSSCode', args); + await channel?.invokeMethod('injectCSSCode', args); } ///Injects an external CSS file into the WebView from a defined url. @@ -2108,7 +2106,7 @@ class InAppWebViewController { args.putIfAbsent('urlFile', () => urlFile.toString()); args.putIfAbsent( 'cssLinkHtmlTagAttributes', () => cssLinkHtmlTagAttributes?.toMap()); - await _channel?.invokeMethod('injectCSSFileFromUrl', args); + await channel?.invokeMethod('injectCSSFileFromUrl', args); } ///Injects a CSS file into the WebView from the flutter assets directory. @@ -2233,7 +2231,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent( 'screenshotConfiguration', () => screenshotConfiguration?.toMap()); - return await _channel?.invokeMethod('takeScreenshot', args); + return await channel?.invokeMethod('takeScreenshot', args); } ///Use [setSettings] instead. @@ -2269,7 +2267,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('settings', () => settings.toMap()); - await _channel?.invokeMethod('setSettings', args); + await channel?.invokeMethod('setSettings', args); } ///Gets the current WebView settings. Returns `null` if it wasn't able to get them. @@ -2283,7 +2281,7 @@ class InAppWebViewController { Map args = {}; Map? settings = - await _channel?.invokeMethod('getSettings', args); + await channel?.invokeMethod('getSettings', args); if (settings != null) { settings = settings.cast(); return InAppWebViewSettings.fromMap(settings as Map); @@ -2304,7 +2302,7 @@ class InAppWebViewController { Future getCopyBackForwardList() async { Map args = {}; Map? result = - (await _channel?.invokeMethod('getCopyBackForwardList', args)) + (await channel?.invokeMethod('getCopyBackForwardList', args)) ?.cast(); return WebHistory.fromMap(result); } @@ -2317,7 +2315,7 @@ class InAppWebViewController { ///- MacOS Future clearCache() async { Map args = {}; - await _channel?.invokeMethod('clearCache', args); + await channel?.invokeMethod('clearCache', args); } ///Use [FindInteractionController.findAll] instead. @@ -2325,7 +2323,7 @@ class InAppWebViewController { Future findAllAsync({required String find}) async { Map args = {}; args.putIfAbsent('find', () => find); - await _channel?.invokeMethod('findAll', args); + await channel?.invokeMethod('findAll', args); } ///Use [FindInteractionController.findNext] instead. @@ -2333,14 +2331,14 @@ class InAppWebViewController { Future findNext({required bool forward}) async { Map args = {}; args.putIfAbsent('forward', () => forward); - await _channel?.invokeMethod('findNext', args); + await channel?.invokeMethod('findNext', args); } ///Use [FindInteractionController.clearMatches] instead. @Deprecated("Use FindInteractionController.clearMatches instead") Future clearMatches() async { Map args = {}; - await _channel?.invokeMethod('clearMatches', args); + await channel?.invokeMethod('clearMatches', args); } ///Use [tRexRunnerHtml] instead. @@ -2378,7 +2376,7 @@ class InAppWebViewController { args.putIfAbsent('x', () => x); args.putIfAbsent('y', () => y); args.putIfAbsent('animated', () => animated); - await _channel?.invokeMethod('scrollTo', args); + await channel?.invokeMethod('scrollTo', args); } ///Moves the scrolled position of the WebView. @@ -2404,7 +2402,7 @@ class InAppWebViewController { args.putIfAbsent('x', () => x); args.putIfAbsent('y', () => y); args.putIfAbsent('animated', () => animated); - await _channel?.invokeMethod('scrollBy', args); + await channel?.invokeMethod('scrollBy', args); } ///On Android native WebView, it pauses all layout, parsing, and JavaScript timers for all WebViews. @@ -2420,7 +2418,7 @@ class InAppWebViewController { ///- MacOS Future pauseTimers() async { Map args = {}; - await _channel?.invokeMethod('pauseTimers', args); + await channel?.invokeMethod('pauseTimers', args); } ///On Android, it resumes all layout, parsing, and JavaScript timers for all WebViews. This will resume dispatching all timers. @@ -2435,7 +2433,7 @@ class InAppWebViewController { ///- MacOS Future resumeTimers() async { Map args = {}; - await _channel?.invokeMethod('resumeTimers', args); + await channel?.invokeMethod('resumeTimers', args); } ///Prints the current page. @@ -2458,7 +2456,8 @@ class InAppWebViewController { {PrintJobSettings? settings}) async { Map args = {}; args.putIfAbsent("settings", () => settings?.toMap()); - String? jobId = await _channel?.invokeMethod('printCurrentPage', args); + String? jobId = + await channel?.invokeMethod('printCurrentPage', args); if (jobId != null) { return PrintJobController(id: jobId); } @@ -2478,7 +2477,7 @@ class InAppWebViewController { ///- Web ([Official API - Document.documentElement.scrollHeight](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight)) Future getContentHeight() async { Map args = {}; - var height = await _channel?.invokeMethod('getContentHeight', args); + var height = await channel?.invokeMethod('getContentHeight', args); if (height == null || height == 0) { // try to use javascript var scrollHeight = await evaluateJavascript( @@ -2505,7 +2504,7 @@ class InAppWebViewController { ///- Web ([Official API - Document.documentElement.scrollWidth](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth)) Future getContentWidth() async { Map args = {}; - var height = await _channel?.invokeMethod('getContentWidth', args); + var height = await channel?.invokeMethod('getContentWidth', args); if (height == null || height == 0) { // try to use javascript var scrollHeight = await evaluateJavascript( @@ -2539,7 +2538,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('zoomFactor', () => zoomFactor); args.putIfAbsent('animated', () => iosAnimated ?? animated); - return await _channel?.invokeMethod('zoomBy', args); + return await channel?.invokeMethod('zoomBy', args); } ///Gets the URL that was originally requested for the current page. @@ -2555,7 +2554,7 @@ class InAppWebViewController { ///- Web Future getOriginalUrl() async { Map args = {}; - String? url = await _channel?.invokeMethod('getOriginalUrl', args); + String? url = await channel?.invokeMethod('getOriginalUrl', args); return url != null ? WebUri(url) : null; } @@ -2566,7 +2565,7 @@ class InAppWebViewController { ///- iOS ([Official API - UIScrollView.zoomScale](https://developer.apple.com/documentation/uikit/uiscrollview/1619419-zoomscale)) Future getZoomScale() async { Map args = {}; - return await _channel?.invokeMethod('getZoomScale', args); + return await channel?.invokeMethod('getZoomScale', args); } ///Use [getZoomScale] instead. @@ -2590,7 +2589,7 @@ class InAppWebViewController { ///- Web Future getSelectedText() async { Map args = {}; - return await _channel?.invokeMethod('getSelectedText', args); + return await channel?.invokeMethod('getSelectedText', args); } ///Gets the hit result for hitting an HTML elements. @@ -2603,7 +2602,7 @@ class InAppWebViewController { Future getHitTestResult() async { Map args = {}; Map? hitTestResultMap = - await _channel?.invokeMethod('getHitTestResult', args); + await channel?.invokeMethod('getHitTestResult', args); if (hitTestResultMap == null) { return null; @@ -2625,7 +2624,7 @@ class InAppWebViewController { ///- iOS ([Official API - UIResponder.resignFirstResponder](https://developer.apple.com/documentation/uikit/uiresponder/1621097-resignfirstresponder)) Future clearFocus() async { Map args = {}; - return await _channel?.invokeMethod('clearFocus', args); + return await channel?.invokeMethod('clearFocus', args); } ///Sets or updates the WebView context menu to be used next time it will appear. @@ -2636,7 +2635,7 @@ class InAppWebViewController { Future setContextMenu(ContextMenu? contextMenu) async { Map args = {}; args.putIfAbsent("contextMenu", () => contextMenu?.toMap()); - await _channel?.invokeMethod('setContextMenu', args); + await channel?.invokeMethod('setContextMenu', args); _inAppBrowser?.contextMenu = contextMenu; } @@ -2650,7 +2649,7 @@ class InAppWebViewController { Future requestFocusNodeHref() async { Map args = {}; Map? result = - await _channel?.invokeMethod('requestFocusNodeHref', args); + await channel?.invokeMethod('requestFocusNodeHref', args); return result != null ? RequestFocusNodeHrefResult( url: result['url'] != null ? WebUri(result['url']) : null, @@ -2670,7 +2669,7 @@ class InAppWebViewController { Future requestImageRef() async { Map args = {}; Map? result = - await _channel?.invokeMethod('requestImageRef', args); + await channel?.invokeMethod('requestImageRef', args); return result != null ? RequestImageRefResult( url: result['url'] != null ? WebUri(result['url']) : null, @@ -2766,7 +2765,7 @@ class InAppWebViewController { try { Map args = {}; themeColor = UtilColor.fromStringRepresentation( - await _channel?.invokeMethod('getMetaThemeColor', args)); + await channel?.invokeMethod('getMetaThemeColor', args)); return themeColor; } catch (e) { // not implemented @@ -2809,7 +2808,7 @@ class InAppWebViewController { ///- Web ([Official API - Window.scrollX](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX)) Future getScrollX() async { Map args = {}; - return await _channel?.invokeMethod('getScrollX', args); + return await channel?.invokeMethod('getScrollX', args); } ///Returns the scrolled top position of the current WebView. @@ -2825,7 +2824,7 @@ class InAppWebViewController { ///- Web ([Official API - Window.scrollY](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY)) Future getScrollY() async { Map args = {}; - return await _channel?.invokeMethod('getScrollY', args); + return await channel?.invokeMethod('getScrollY', args); } ///Gets the SSL certificate for the main top-level page or null if there is no certificate (the site is not secure). @@ -2837,7 +2836,7 @@ class InAppWebViewController { Future getCertificate() async { Map args = {}; Map? sslCertificateMap = - (await _channel?.invokeMethod('getCertificate', args)) + (await channel?.invokeMethod('getCertificate', args)) ?.cast(); return SslCertificate.fromMap(sslCertificateMap); } @@ -2860,7 +2859,7 @@ class InAppWebViewController { if (!(_userScripts[userScript.injectionTime]?.contains(userScript) ?? false)) { _userScripts[userScript.injectionTime]?.add(userScript); - await _channel?.invokeMethod('addUserScript', args); + await channel?.invokeMethod('addUserScript', args); } } @@ -2906,7 +2905,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('userScript', () => userScript.toMap()); args.putIfAbsent('index', () => index); - await _channel?.invokeMethod('removeUserScript', args); + await channel?.invokeMethod('removeUserScript', args); return true; } @@ -2943,7 +2942,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('groupName', () => groupName); - await _channel?.invokeMethod('removeUserScriptsByGroupName', args); + await channel?.invokeMethod('removeUserScriptsByGroupName', args); } ///Removes the [userScripts] from the webpage’s content. @@ -2983,7 +2982,7 @@ class InAppWebViewController { _userScripts[UserScriptInjectionTime.AT_DOCUMENT_END]?.clear(); Map args = {}; - await _channel?.invokeMethod('removeAllUserScripts', args); + await channel?.invokeMethod('removeAllUserScripts', args); } ///Returns `true` if the [userScript] has been already added, otherwise `false`. @@ -3038,7 +3037,7 @@ class InAppWebViewController { args.putIfAbsent('functionBody', () => functionBody); args.putIfAbsent('arguments', () => arguments); args.putIfAbsent('contentWorld', () => contentWorld?.toMap()); - var data = await _channel?.invokeMethod('callAsyncJavaScript', args); + var data = await channel?.invokeMethod('callAsyncJavaScript', args); if (data == null) { return null; } @@ -3081,7 +3080,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent("filePath", () => filePath); args.putIfAbsent("autoname", () => autoname); - return await _channel?.invokeMethod('saveWebArchive', args); + return await channel?.invokeMethod('saveWebArchive', args); } ///Indicates whether the webpage context is capable of using features that require [secure contexts](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). @@ -3098,7 +3097,7 @@ class InAppWebViewController { ///- Web ([Official API - Window.isSecureContext](https://developer.mozilla.org/en-US/docs/Web/API/Window/isSecureContext)) Future isSecureContext() async { Map args = {}; - return await _channel?.invokeMethod('isSecureContext', args); + return await channel?.invokeMethod('isSecureContext', args) ?? false; } ///Creates a message channel to communicate with JavaScript and returns the message channel with ports that represent the endpoints of this message channel. @@ -3121,9 +3120,13 @@ class InAppWebViewController { Future createWebMessageChannel() async { Map args = {}; Map? result = - (await _channel?.invokeMethod('createWebMessageChannel', args)) + (await channel?.invokeMethod('createWebMessageChannel', args)) ?.cast(); - return WebMessageChannel.fromMap(result); + final webMessageChannel = WebMessageChannel.fromMap(result); + if (webMessageChannel != null) { + _webMessageChannels.add(webMessageChannel); + } + return webMessageChannel; } ///Post a message to main frame. The embedded application can restrict the messages to a certain target origin. @@ -3149,7 +3152,7 @@ class InAppWebViewController { Map args = {}; args.putIfAbsent('message', () => message.toMap()); args.putIfAbsent('targetOrigin', () => targetOrigin.toString()); - await _channel?.invokeMethod('postWebMessage', args); + await channel?.invokeMethod('postWebMessage', args); } ///Adds a [WebMessageListener] to the WebView and injects a JavaScript object into each frame that the [WebMessageListener] will listen on. @@ -3318,14 +3321,17 @@ class InAppWebViewController { ///- MacOS Future addWebMessageListener( WebMessageListener webMessageListener) async { + assert(!_webMessageListeners.contains(webMessageListener), + "${webMessageListener} was already added."); assert( !_webMessageListenerObjNames.contains(webMessageListener.jsObjectName), "jsObjectName ${webMessageListener.jsObjectName} was already added."); + _webMessageListeners.add(webMessageListener); _webMessageListenerObjNames.add(webMessageListener.jsObjectName); Map args = {}; args.putIfAbsent('webMessageListener', () => webMessageListener.toMap()); - await _channel?.invokeMethod('addWebMessageListener', args); + await channel?.invokeMethod('addWebMessageListener', args); } ///Returns `true` if the [webMessageListener] has been already added, otherwise `false`. @@ -3335,8 +3341,8 @@ class InAppWebViewController { ///- iOS ///- MacOS bool hasWebMessageListener(WebMessageListener webMessageListener) { - return _webMessageListenerObjNames - .contains(webMessageListener.jsObjectName); + return _webMessageListeners.contains(webMessageListener) || + _webMessageListenerObjNames.contains(webMessageListener.jsObjectName); } ///Returns `true` if the webpage can scroll vertically, otherwise `false`. @@ -3352,7 +3358,8 @@ class InAppWebViewController { ///- Web Future canScrollVertically() async { Map args = {}; - return await _channel?.invokeMethod('canScrollVertically', args); + return await channel?.invokeMethod('canScrollVertically', args) ?? + false; } ///Returns `true` if the webpage can scroll horizontally, otherwise `false`. @@ -3368,7 +3375,8 @@ class InAppWebViewController { ///- Web Future canScrollHorizontally() async { Map args = {}; - return await _channel?.invokeMethod('canScrollHorizontally', args); + return await channel?.invokeMethod('canScrollHorizontally', args) ?? + false; } ///Starts Safe Browsing initialization. @@ -3385,7 +3393,8 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.startSafeBrowsing](https://developer.android.com/reference/android/webkit/WebView#startSafeBrowsing(android.content.Context,%20android.webkit.ValueCallback%3Cjava.lang.Boolean%3E))) Future startSafeBrowsing() async { Map args = {}; - return await _channel?.invokeMethod('startSafeBrowsing', args); + return await channel?.invokeMethod('startSafeBrowsing', args) ?? + false; } ///Clears the SSL preferences table stored in response to proceeding with SSL certificate errors. @@ -3394,7 +3403,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.clearSslPreferences](https://developer.android.com/reference/android/webkit/WebView#clearSslPreferences())) Future clearSslPreferences() async { Map args = {}; - await _channel?.invokeMethod('clearSslPreferences', args); + await channel?.invokeMethod('clearSslPreferences', args); } ///Does a best-effort attempt to pause any processing that can be paused safely, such as animations and geolocation. Note that this call does not pause JavaScript. @@ -3404,7 +3413,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.onPause](https://developer.android.com/reference/android/webkit/WebView#onPause())) Future pause() async { Map args = {}; - await _channel?.invokeMethod('pause', args); + await channel?.invokeMethod('pause', args); } ///Resumes a WebView after a previous call to [pause]. @@ -3413,7 +3422,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.onResume](https://developer.android.com/reference/android/webkit/WebView#onResume())) Future resume() async { Map args = {}; - await _channel?.invokeMethod('resume', args); + await channel?.invokeMethod('resume', args); } ///Scrolls the contents of this WebView down by half the page size. @@ -3426,7 +3435,7 @@ class InAppWebViewController { Future pageDown({required bool bottom}) async { Map args = {}; args.putIfAbsent("bottom", () => bottom); - return await _channel?.invokeMethod('pageDown', args); + return await channel?.invokeMethod('pageDown', args) ?? false; } ///Scrolls the contents of this WebView up by half the view size. @@ -3439,7 +3448,7 @@ class InAppWebViewController { Future pageUp({required bool top}) async { Map args = {}; args.putIfAbsent("top", () => top); - return await _channel?.invokeMethod('pageUp', args); + return await channel?.invokeMethod('pageUp', args) ?? false; } ///Performs zoom in in this WebView. @@ -3449,7 +3458,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.zoomIn](https://developer.android.com/reference/android/webkit/WebView#zoomIn())) Future zoomIn() async { Map args = {}; - return await _channel?.invokeMethod('zoomIn', args); + return await channel?.invokeMethod('zoomIn', args) ?? false; } ///Performs zoom out in this WebView. @@ -3459,7 +3468,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.zoomOut](https://developer.android.com/reference/android/webkit/WebView#zoomOut())) Future zoomOut() async { Map args = {}; - return await _channel?.invokeMethod('zoomOut', args); + return await channel?.invokeMethod('zoomOut', args) ?? false; } ///Clears the internal back/forward list. @@ -3468,7 +3477,7 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebView.clearHistory](https://developer.android.com/reference/android/webkit/WebView#clearHistory())) Future clearHistory() async { Map args = {}; - return await _channel?.invokeMethod('clearHistory', args); + return await channel?.invokeMethod('clearHistory', args); } ///Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible. @@ -3478,7 +3487,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.reloadFromOrigin](https://developer.apple.com/documentation/webkit/wkwebview/1414956-reloadfromorigin)) Future reloadFromOrigin() async { Map args = {}; - await _channel?.invokeMethod('reloadFromOrigin', args); + await channel?.invokeMethod('reloadFromOrigin', args); } ///Generates PDF data from the web view’s contents asynchronously. @@ -3495,13 +3504,13 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.createPdf](https://developer.apple.com/documentation/webkit/wkwebview/3650490-createpdf)) Future createPdf( {@Deprecated("Use pdfConfiguration instead") - // ignore: deprecated_member_use_from_same_package - IOSWKPDFConfiguration? iosWKPdfConfiguration, + // ignore: deprecated_member_use_from_same_package + IOSWKPDFConfiguration? iosWKPdfConfiguration, PDFConfiguration? pdfConfiguration}) async { Map args = {}; args.putIfAbsent('pdfConfiguration', () => pdfConfiguration?.toMap() ?? iosWKPdfConfiguration?.toMap()); - return await _channel?.invokeMethod('createPdf', args); + return await channel?.invokeMethod('createPdf', args); } ///Creates a web archive of the web view’s current contents asynchronously. @@ -3516,7 +3525,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.createWebArchiveData](https://developer.apple.com/documentation/webkit/wkwebview/3650491-createwebarchivedata)) Future createWebArchiveData() async { Map args = {}; - return await _channel?.invokeMethod('createWebArchiveData', args); + return await channel?.invokeMethod('createWebArchiveData', args); } ///A Boolean value indicating whether all resources on the page have been loaded over securely encrypted connections. @@ -3526,7 +3535,8 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.hasOnlySecureContent](https://developer.apple.com/documentation/webkit/wkwebview/1415002-hasonlysecurecontent)) Future hasOnlySecureContent() async { Map args = {}; - return await _channel?.invokeMethod('hasOnlySecureContent', args); + return await channel?.invokeMethod('hasOnlySecureContent', args) ?? + false; } ///Pauses playback of all media in the web view. @@ -3540,7 +3550,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.pauseAllMediaPlayback](https://developer.apple.com/documentation/webkit/wkwebview/3752240-pauseallmediaplayback)). Future pauseAllMediaPlayback() async { Map args = {}; - return await _channel?.invokeMethod('pauseAllMediaPlayback', args); + return await channel?.invokeMethod('pauseAllMediaPlayback', args); } ///Changes whether the webpage is suspending playback of all media in the page. @@ -3558,7 +3568,7 @@ class InAppWebViewController { Future setAllMediaPlaybackSuspended({required bool suspended}) async { Map args = {}; args.putIfAbsent("suspended", () => suspended); - return await _channel?.invokeMethod('setAllMediaPlaybackSuspended', args); + return await channel?.invokeMethod('setAllMediaPlaybackSuspended', args); } ///Closes all media the web view is presenting, including picture-in-picture video and fullscreen video. @@ -3572,7 +3582,7 @@ class InAppWebViewController { ///- MacOS ([Official API - WKWebView.closeAllMediaPresentations](https://developer.apple.com/documentation/webkit/wkwebview/3752235-closeallmediapresentations)). Future closeAllMediaPresentations() async { Map args = {}; - return await _channel?.invokeMethod('closeAllMediaPresentations', args); + return await channel?.invokeMethod('closeAllMediaPresentations', args); } ///Requests the playback status of media in the web view. @@ -3589,7 +3599,7 @@ class InAppWebViewController { Future requestMediaPlaybackState() async { Map args = {}; return MediaPlaybackState.fromNativeValue( - await _channel?.invokeMethod('requestMediaPlaybackState', args)); + await channel?.invokeMethod('requestMediaPlaybackState', args)); } ///Returns `true` if the [WebView] is in fullscreen mode, otherwise `false`. @@ -3600,7 +3610,7 @@ class InAppWebViewController { ///- MacOS Future isInFullscreen() async { Map args = {}; - return await _channel?.invokeMethod('isInFullscreen', args); + return await channel?.invokeMethod('isInFullscreen', args) ?? false; } ///Returns a [MediaCaptureState] that indicates whether the webpage is using the camera to capture images or video. @@ -3615,7 +3625,7 @@ class InAppWebViewController { Future getCameraCaptureState() async { Map args = {}; return MediaCaptureState.fromNativeValue( - await _channel?.invokeMethod('getCameraCaptureState', args)); + await channel?.invokeMethod('getCameraCaptureState', args)); } ///Changes whether the webpage is using the camera to capture images or video. @@ -3630,7 +3640,7 @@ class InAppWebViewController { Future setCameraCaptureState({required MediaCaptureState state}) async { Map args = {}; args.putIfAbsent('state', () => state.toNativeValue()); - await _channel?.invokeMethod('setCameraCaptureState', args); + await channel?.invokeMethod('setCameraCaptureState', args); } ///Returns a [MediaCaptureState] that indicates whether the webpage is using the microphone to capture audio. @@ -3645,7 +3655,7 @@ class InAppWebViewController { Future getMicrophoneCaptureState() async { Map args = {}; return MediaCaptureState.fromNativeValue( - await _channel?.invokeMethod('getMicrophoneCaptureState', args)); + await channel?.invokeMethod('getMicrophoneCaptureState', args)); } ///Changes whether the webpage is using the microphone to capture audio. @@ -3661,7 +3671,7 @@ class InAppWebViewController { {required MediaCaptureState state}) async { Map args = {}; args.putIfAbsent('state', () => state.toNativeValue()); - await _channel?.invokeMethod('setMicrophoneCaptureState', args); + await channel?.invokeMethod('setMicrophoneCaptureState', args); } ///Loads the web content from the data you provide as if the data were the response to the request. @@ -3697,7 +3707,7 @@ class InAppWebViewController { args.putIfAbsent('urlRequest', () => urlRequest.toMap()); args.putIfAbsent('data', () => data); args.putIfAbsent('urlResponse', () => urlResponse?.toMap()); - await _channel?.invokeMethod('loadSimulatedRequest', args); + await channel?.invokeMethod('loadSimulatedRequest', args); } ///Returns the iframe `id` attribute used on the Web platform. @@ -3706,7 +3716,7 @@ class InAppWebViewController { ///- Web Future getIFrameId() async { Map args = {}; - return await _channel?.invokeMethod('getIFrameId', args); + return await channel?.invokeMethod('getIFrameId', args); } ///Gets the default user agent. @@ -3717,7 +3727,9 @@ class InAppWebViewController { ///- MacOS static Future getDefaultUserAgent() async { Map args = {}; - return await _staticChannel.invokeMethod('getDefaultUserAgent', args); + return await _staticChannel.invokeMethod( + 'getDefaultUserAgent', args) ?? + ''; } ///Clears the client certificate preferences stored in response to proceeding/cancelling client cert requests. @@ -3777,7 +3789,9 @@ class InAppWebViewController { {required List hosts}) async { Map args = {}; args.putIfAbsent('hosts', () => hosts); - return await _staticChannel.invokeMethod('setSafeBrowsingAllowlist', args); + return await _staticChannel.invokeMethod( + 'setSafeBrowsingAllowlist', args) ?? + false; } ///If WebView has already been loaded into the current process this method will return the package that was used to load it. @@ -3833,7 +3847,8 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebViewCompat.getVariationsHeader](https://developer.android.com/reference/androidx/webkit/WebViewCompat#getVariationsHeader())) static Future getVariationsHeader() async { Map args = {}; - return await _staticChannel.invokeMethod('getVariationsHeader', args); + return await _staticChannel.invokeMethod( + 'getVariationsHeader', args); } ///Returns `true` if WebView is running in multi process mode. @@ -3850,7 +3865,9 @@ class InAppWebViewController { ///- Android native WebView ([Official API - WebViewCompat.isMultiProcessEnabled](https://developer.android.com/reference/androidx/webkit/WebViewCompat#isMultiProcessEnabled())) static Future isMultiProcessEnabled() async { Map args = {}; - return await _staticChannel.invokeMethod('isMultiProcessEnabled', args); + return await _staticChannel.invokeMethod( + 'isMultiProcessEnabled', args) ?? + false; } ///Returns a Boolean value that indicates whether WebKit natively supports resources with the specified URL scheme. @@ -3901,24 +3918,17 @@ class InAppWebViewController { static Future get tRexRunnerCss async => await rootBundle.loadString( 'packages/flutter_inappwebview/assets/t_rex_runner/t-rex.css'); - ///Used internally. - MethodChannel? getChannel() { - return _channel; - } - - ///Used internally. + ///View ID used internally. dynamic getViewId() { return _id; } ///Disposes the controller. + @override void dispose({bool isKeepAlive = false}) { - if (!isKeepAlive) { - _channel?.setMethodCallHandler(null); - } + disposeChannel(removeMethodCallHandler: !isKeepAlive); android.dispose(); ios.dispose(); - _channel = null; _webview = null; _inAppBrowser = null; webStorage.dispose(); @@ -3927,6 +3937,18 @@ class InAppWebViewController { _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 InAppWebViewController { + get handleMethod => _handleMethod; +} diff --git a/lib/src/in_app_webview/in_app_webview_keep_alive.dart b/lib/src/in_app_webview/in_app_webview_keep_alive.dart index 1e30ba7c..ccd752b1 100644 --- a/lib/src/in_app_webview/in_app_webview_keep_alive.dart +++ b/lib/src/in_app_webview/in_app_webview_keep_alive.dart @@ -1,5 +1,7 @@ import '../types/main.dart'; import '../util.dart'; +import '../web_message/web_message_channel.dart'; +import '../web_message/web_message_listener.dart'; import 'in_app_webview.dart'; import 'in_app_webview_controller.dart'; @@ -24,10 +26,14 @@ class InAppWebViewControllerKeepAliveProps { Map> userScripts; Set webMessageListenerObjNames; Map injectedScriptsFromURL; + Set webMessageChannels = Set(); + Set webMessageListeners = Set(); InAppWebViewControllerKeepAliveProps( {required this.javaScriptHandlersMap, required this.userScripts, required this.webMessageListenerObjNames, - required this.injectedScriptsFromURL}); + required this.injectedScriptsFromURL, + required this.webMessageChannels, + required this.webMessageListeners}); } diff --git a/lib/src/in_app_webview/main.dart b/lib/src/in_app_webview/main.dart index f202e1f3..04a2f898 100644 --- a/lib/src/in_app_webview/main.dart +++ b/lib/src/in_app_webview/main.dart @@ -1,6 +1,6 @@ export 'webview.dart'; export 'in_app_webview.dart'; -export 'in_app_webview_controller.dart'; +export 'in_app_webview_controller.dart' hide InternalInAppWebViewController; export 'in_app_webview_settings.dart' show InAppWebViewSettings, diff --git a/lib/src/print_job/print_job_controller.dart b/lib/src/print_job/print_job_controller.dart index 4f8c7669..8c8feb5f 100644 --- a/lib/src/print_job/print_job_controller.dart +++ b/lib/src/print_job/print_job_controller.dart @@ -1,7 +1,7 @@ import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview/src/util.dart'; import '../types/print_job_info.dart'; import '../in_app_webview/in_app_webview_controller.dart'; -import '../types/disposable.dart'; ///A completion handler for the [PrintJobController]. typedef PrintJobCompletionHandler = Future Function( @@ -13,12 +13,10 @@ typedef PrintJobCompletionHandler = Future Function( ///- Android native WebView ///- iOS ///- MacOS -class PrintJobController implements Disposable { +class PrintJobController extends ChannelController { ///Print job ID. final String id; - MethodChannel? _channel; - ///A completion handler used to handle the conclusion of the print job (for instance, to reset state) and to handle any errors encountered in printing. /// ///**Supported Platforms/Implementations**: @@ -27,16 +25,10 @@ class PrintJobController implements Disposable { PrintJobCompletionHandler onComplete; PrintJobController({required this.id}) { - this._channel = MethodChannel( + channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_printjobcontroller_$id'); - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } Future _handleMethod(MethodCall call) async { @@ -60,7 +52,7 @@ class PrintJobController implements Disposable { ///- Android native WebView ([Official API - PrintJob.cancel](https://developer.android.com/reference/android/print/PrintJob#cancel())) Future cancel() async { Map args = {}; - await _channel?.invokeMethod('cancel', args); + await channel?.invokeMethod('cancel', args); } ///Restarts this print job. @@ -70,7 +62,7 @@ class PrintJobController implements Disposable { ///- Android native WebView ([Official API - PrintJob.restart](https://developer.android.com/reference/android/print/PrintJob#restart())) Future restart() async { Map args = {}; - await _channel?.invokeMethod('restart', args); + await channel?.invokeMethod('restart', args); } ///Dismisses the printing-options sheet or popover. @@ -85,7 +77,7 @@ class PrintJobController implements Disposable { Future dismiss({bool animated: true}) async { Map args = {}; args.putIfAbsent("animated", () => animated); - await _channel?.invokeMethod('dismiss', args); + await channel?.invokeMethod('dismiss', args); } ///Gets the [PrintJobInfo] that describes this job. @@ -101,7 +93,7 @@ class PrintJobController implements Disposable { Future getInfo() async { Map args = {}; Map? infoMap = - (await _channel?.invokeMethod('getInfo', args)) + (await channel?.invokeMethod('getInfo', args)) ?.cast(); return PrintJobInfo.fromMap(infoMap); } @@ -115,8 +107,7 @@ class PrintJobController implements Disposable { @override Future dispose() async { Map args = {}; - await _channel?.invokeMethod('dispose', args); - _channel?.setMethodCallHandler(null); - _channel = null; + await channel?.invokeMethod('dispose', args); + disposeChannel(); } } diff --git a/lib/src/pull_to_refresh/pull_to_refresh_controller.dart b/lib/src/pull_to_refresh/pull_to_refresh_controller.dart index f15d2ee1..1d08c061 100644 --- a/lib/src/pull_to_refresh/pull_to_refresh_controller.dart +++ b/lib/src/pull_to_refresh/pull_to_refresh_controller.dart @@ -20,12 +20,11 @@ import '../debug_logging_settings.dart'; ///**Supported Platforms/Implementations**: ///- Android native WebView ///- iOS -class PullToRefreshController { +class PullToRefreshController extends ChannelController { @Deprecated("Use settings instead") // ignore: deprecated_member_use_from_same_package late PullToRefreshOptions options; late PullToRefreshSettings settings; - MethodChannel? _channel; ///Debug settings. static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings(); @@ -73,7 +72,7 @@ class PullToRefreshController { Future setEnabled(bool enabled) async { Map args = {}; args.putIfAbsent('enabled', () => enabled); - await _channel?.invokeMethod('setEnabled', args); + await channel?.invokeMethod('setEnabled', args); } ///Returns `true` is pull-to-refresh feature is enabled, otherwise `false`. @@ -83,13 +82,13 @@ class PullToRefreshController { ///- iOS ([Official API - UIScrollView.refreshControl](https://developer.apple.com/documentation/uikit/uiscrollview/2127691-refreshcontrol)) Future isEnabled() async { Map args = {}; - return await _channel?.invokeMethod('isEnabled', args); + return await channel?.invokeMethod('isEnabled', args) ?? false; } Future _setRefreshing(bool refreshing) async { Map args = {}; args.putIfAbsent('refreshing', () => refreshing); - await _channel?.invokeMethod('setRefreshing', args); + await channel?.invokeMethod('setRefreshing', args); } ///Tells the controller that a refresh operation was started programmatically. @@ -126,7 +125,7 @@ class PullToRefreshController { ///- iOS ([Official API - UIRefreshControl.isRefreshing](https://developer.apple.com/documentation/uikit/uirefreshcontrol/1624844-isrefreshing)) Future isRefreshing() async { Map args = {}; - return await _channel?.invokeMethod('isRefreshing', args); + return await channel?.invokeMethod('isRefreshing', args) ?? false; } ///Sets the color of the refresh control. @@ -137,7 +136,7 @@ class PullToRefreshController { Future setColor(Color color) async { Map args = {}; args.putIfAbsent('color', () => color.toHex()); - await _channel?.invokeMethod('setColor', args); + await channel?.invokeMethod('setColor', args); } ///Sets the background color of the refresh control. @@ -148,7 +147,7 @@ class PullToRefreshController { Future setBackgroundColor(Color color) async { Map args = {}; args.putIfAbsent('color', () => color.toHex()); - await _channel?.invokeMethod('setBackgroundColor', args); + await channel?.invokeMethod('setBackgroundColor', args); } ///Set the distance to trigger a sync in dips. @@ -158,7 +157,7 @@ class PullToRefreshController { Future setDistanceToTriggerSync(int distanceToTriggerSync) async { Map args = {}; args.putIfAbsent('distanceToTriggerSync', () => distanceToTriggerSync); - await _channel?.invokeMethod('setDistanceToTriggerSync', args); + await channel?.invokeMethod('setDistanceToTriggerSync', args); } ///Sets the distance that the refresh indicator can be pulled beyond its resting position during a swipe gesture. @@ -168,7 +167,7 @@ class PullToRefreshController { Future setSlingshotDistance(int slingshotDistance) async { Map args = {}; args.putIfAbsent('slingshotDistance', () => slingshotDistance); - await _channel?.invokeMethod('setSlingshotDistance', args); + await channel?.invokeMethod('setSlingshotDistance', args); } ///Gets the default distance that the refresh indicator can be pulled beyond its resting position during a swipe gesture. @@ -177,7 +176,7 @@ class PullToRefreshController { ///- Android native WebView ([Official API - SwipeRefreshLayout.DEFAULT_SLINGSHOT_DISTANCE](https://developer.android.com/reference/androidx/swiperefreshlayout/widget/SwipeRefreshLayout#DEFAULT_SLINGSHOT_DISTANCE())) Future getDefaultSlingshotDistance() async { Map args = {}; - return await _channel?.invokeMethod('getDefaultSlingshotDistance', args); + return await channel?.invokeMethod('getDefaultSlingshotDistance', args) ?? 0; } ///Use [setIndicatorSize] instead. @@ -185,7 +184,7 @@ class PullToRefreshController { Future setSize(AndroidPullToRefreshSize size) async { Map args = {}; args.putIfAbsent('size', () => size.toNativeValue()); - await _channel?.invokeMethod('setSize', args); + await channel?.invokeMethod('setSize', args); } ///Sets the size of the refresh indicator. One of [PullToRefreshSize.DEFAULT], or [PullToRefreshSize.LARGE]. @@ -195,7 +194,7 @@ class PullToRefreshController { Future setIndicatorSize(PullToRefreshSize size) async { Map args = {}; args.putIfAbsent('size', () => size.toNativeValue()); - await _channel?.invokeMethod('setSize', args); + await channel?.invokeMethod('setSize', args); } ///Use [setStyledTitle] instead. @@ -203,7 +202,7 @@ class PullToRefreshController { Future setAttributedTitle(IOSNSAttributedString attributedTitle) async { Map args = {}; args.putIfAbsent('attributedTitle', () => attributedTitle.toMap()); - await _channel?.invokeMethod('setStyledTitle', args); + await channel?.invokeMethod('setStyledTitle', args); } ///Sets the styled title text to display in the refresh control. @@ -213,30 +212,21 @@ class PullToRefreshController { Future setStyledTitle(AttributedString attributedTitle) async { Map args = {}; args.putIfAbsent('attributedTitle', () => attributedTitle.toMap()); - await _channel?.invokeMethod('setStyledTitle', args); + await channel?.invokeMethod('setStyledTitle', args); } ///Disposes the controller. + @override void dispose({bool isKeepAlive = false}) { - if (!isKeepAlive) { - _channel?.setMethodCallHandler(null); - } - _channel = null; + disposeChannel(removeMethodCallHandler: !isKeepAlive); } } extension InternalPullToRefreshController on PullToRefreshController { void init(dynamic id) { - this._channel = MethodChannel( + channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id'); - this._channel?.setMethodCallHandler((call) async { - if (_channel == null) return null; - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } } diff --git a/lib/src/util.dart b/lib/src/util.dart index 689aeee2..2daf0148 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -4,8 +4,10 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'debug_logging_settings.dart'; +import 'types/disposable.dart'; class Util { static bool get isWeb => kIsWeb; @@ -582,3 +584,60 @@ void debugLog( class Color_ extends Color { Color_(int value) : super(value); } + +abstract class ChannelController implements Disposable { + MethodChannel? _channel; + Future Function(MethodCall call)? _handler; + + static bool debugAssertNotDisposed(ChannelController controller) { + assert(() { + if (controller.disposed) { + throw FlutterError( + 'A ${controller.runtimeType} was used after being disposed.\n' + 'Once the ${controller.runtimeType} has been disposed, it ' + 'can no longer be used.', + ); + } + return true; + }()); + return true; + } +} + +extension InternalChannelController on ChannelController { + set channel (MethodChannel? channel) => _channel = channel; + + MethodChannel? get channel { + assert(ChannelController.debugAssertNotDisposed(this)); + return this._channel; + } + + set handler (Future Function(MethodCall call)? handler) => _handler = handler; + + Future Function(MethodCall call)? get handler => _handler; + + bool get disposed => _channel == null; + + initMethodCallHandler() { + assert(channel != null, 'Method Channel for ${runtimeType} not initialized!'); + assert(handler != null, 'Method Call Handler for ${runtimeType} not initialized!'); + + channel?.setMethodCallHandler((call) async { + if (disposed) return null; + try { + return await handler!(call); + } on Error catch (e) { + print(e); + print(e.stackTrace); + } + }); + } + + disposeChannel({bool removeMethodCallHandler = true}) { + if (removeMethodCallHandler) { + channel?.setMethodCallHandler(null); + } + channel = null; + handler = null; + } +} \ No newline at end of file diff --git a/lib/src/web/headless_in_app_web_view_web_element.dart b/lib/src/web/headless_in_app_web_view_web_element.dart index baa4474e..5b6ae0bc 100644 --- a/lib/src/web/headless_in_app_web_view_web_element.dart +++ b/lib/src/web/headless_in_app_web_view_web_element.dart @@ -5,13 +5,11 @@ import 'package:flutter/services.dart'; import 'headless_inappwebview_manager.dart'; import 'in_app_web_view_web_element.dart'; import '../util.dart'; -import '../types/disposable.dart'; -class HeadlessInAppWebViewWebElement implements Disposable { +class HeadlessInAppWebViewWebElement extends ChannelController { String id; late BinaryMessenger _messenger; InAppWebViewWebElement? webView; - late MethodChannel? _channel; HeadlessInAppWebViewWebElement( {required this.id, @@ -19,23 +17,16 @@ class HeadlessInAppWebViewWebElement implements Disposable { required this.webView}) { this._messenger = messenger; - _channel = MethodChannel( + channel = MethodChannel( 'com.pichillilorenzo/flutter_headless_inappwebview_${this.id}', const StandardMethodCodec(), _messenger, ); - - this._channel?.setMethodCallHandler((call) async { - try { - return await handleMethodCall(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } - Future handleMethodCall(MethodCall call) async { + Future _handleMethod(MethodCall call) async { switch (call.method) { case "dispose": dispose(); @@ -57,7 +48,7 @@ class HeadlessInAppWebViewWebElement implements Disposable { } void onWebViewCreated() async { - await _channel?.invokeMethod("onWebViewCreated"); + await channel?.invokeMethod("onWebViewCreated"); } void setSize(Size size) { @@ -73,8 +64,7 @@ class HeadlessInAppWebViewWebElement implements Disposable { @override void dispose() { - _channel?.setMethodCallHandler(null); - _channel = null; + disposeChannel(); HeadlessInAppWebViewManager.webViews.putIfAbsent(id, () => null); webView?.dispose(); webView = null; diff --git a/lib/src/web/platform_util.dart b/lib/src/web/platform_util.dart index 96e64eb1..7242ff31 100644 --- a/lib/src/web/platform_util.dart +++ b/lib/src/web/platform_util.dart @@ -1,35 +1,27 @@ import 'dart:async'; import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview/src/util.dart'; import 'dart:js' as js; import 'web_platform_manager.dart'; -import '../types/disposable.dart'; -class PlatformUtil implements Disposable { +class PlatformUtil extends ChannelController { late BinaryMessenger _messenger; - late MethodChannel? _channel; PlatformUtil({required BinaryMessenger messenger}) { this._messenger = messenger; - _channel = MethodChannel( + channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_platformutil', const StandardMethodCodec(), _messenger, ); - - this._channel?.setMethodCallHandler((call) async { - try { - return await handleMethodCall(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } - Future handleMethodCall(MethodCall call) async { + Future _handleMethod(MethodCall call) async { switch (call.method) { case "getWebCookieExpirationDate": int timestamp = call.arguments['date']; @@ -51,7 +43,6 @@ class PlatformUtil implements Disposable { @override void dispose() { - _channel?.setMethodCallHandler(null); - _channel = null; + disposeChannel(); } } diff --git a/lib/src/web_authentication_session/web_authenticate_session.dart b/lib/src/web_authentication_session/web_authenticate_session.dart index 6f377fb7..c535d4f8 100755 --- a/lib/src/web_authentication_session/web_authenticate_session.dart +++ b/lib/src/web_authentication_session/web_authenticate_session.dart @@ -1,11 +1,9 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../util.dart'; import '../debug_logging_settings.dart'; import '../types/main.dart'; -import '../types/disposable.dart'; import '../web_uri.dart'; import 'web_authenticate_session_settings.dart'; @@ -37,7 +35,7 @@ typedef WebAuthenticationSessionCompletionHandler = Future Function( ///**Supported Platforms/Implementations**: ///- iOS ///- MacOS -class WebAuthenticationSession implements Disposable { +class WebAuthenticationSession extends ChannelController { ///Debug settings. static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings(); @@ -56,7 +54,6 @@ class WebAuthenticationSession implements Disposable { ///A completion handler the session calls when it completes successfully, or when the user cancels the session. WebAuthenticationSessionCompletionHandler onComplete; - MethodChannel? _channel; static const MethodChannel _sharedChannel = const MethodChannel( 'com.pichillilorenzo/flutter_webauthenticationsession'); @@ -102,16 +99,10 @@ class WebAuthenticationSession implements Disposable { id = IdGenerator.generate(); this.initialSettings = initialSettings ?? WebAuthenticationSessionSettings(); - this._channel = MethodChannel( + channel = MethodChannel( 'com.pichillilorenzo/flutter_webauthenticationsession_$id'); - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } _debugLog(String method, dynamic args) { @@ -147,7 +138,7 @@ class WebAuthenticationSession implements Disposable { ///- iOS ([Official API - ASWebAuthenticationSession.canStart](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/3516277-canstart)) Future canStart() async { Map args = {}; - return await _channel?.invokeMethod('canStart', args); + return await channel?.invokeMethod('canStart', args) ?? false; } ///Starts the web authentication session. @@ -161,7 +152,7 @@ class WebAuthenticationSession implements Disposable { ///- iOS ([Official API - ASWebAuthenticationSession.start](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990953-start)) Future start() async { Map args = {}; - return await _channel?.invokeMethod('start', args); + return await channel?.invokeMethod('start', args) ?? false; } ///Cancels the web authentication session. @@ -173,7 +164,7 @@ class WebAuthenticationSession implements Disposable { ///- iOS ([Official API - ASWebAuthenticationSession.cancel](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession/2990951-cancel)) Future cancel() async { Map args = {}; - await _channel?.invokeMethod("cancel", args); + await channel?.invokeMethod("cancel", args); } ///Disposes the web authentication session. @@ -183,9 +174,8 @@ class WebAuthenticationSession implements Disposable { @override Future dispose() async { Map args = {}; - await _channel?.invokeMethod("dispose", args); - _channel?.setMethodCallHandler(null); - _channel = null; + await channel?.invokeMethod("dispose", args); + disposeChannel(); } ///Returns `true` if [ASWebAuthenticationSession](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) @@ -196,6 +186,6 @@ class WebAuthenticationSession implements Disposable { ///- iOS static Future isAvailable() async { Map args = {}; - return await _sharedChannel.invokeMethod("isAvailable", args); + return await _sharedChannel.invokeMethod("isAvailable", args) ?? false; } } diff --git a/lib/src/web_message/web_message_channel.dart b/lib/src/web_message/web_message_channel.dart index 0035b6c6..0d5fffbf 100644 --- a/lib/src/web_message/web_message_channel.dart +++ b/lib/src/web_message/web_message_channel.dart @@ -1,4 +1,5 @@ import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview/src/util.dart'; import 'web_message_port.dart'; ///The representation of the [HTML5 message channels](https://html.spec.whatwg.org/multipage/web-messaging.html#message-channels). @@ -7,7 +8,7 @@ import 'web_message_port.dart'; ///- Android native WebView ///- iOS ///- MacOS -class WebMessageChannel { +class WebMessageChannel extends ChannelController { ///Message Channel ID used internally. final String id; @@ -17,20 +18,12 @@ class WebMessageChannel { ///The second [WebMessagePort] object of the channel. final WebMessagePort port2; - MethodChannel? _channel; - WebMessageChannel( {required this.id, required this.port1, required this.port2}) { - this._channel = MethodChannel( + channel = MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_web_message_channel_$id'); - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } static WebMessageChannel? fromMap(Map? map) { @@ -62,6 +55,12 @@ class WebMessageChannel { return null; } + ///Disposes the web message channel. + @override + void dispose() { + disposeChannel(); + } + @override String toString() { return 'WebMessageChannel{id: $id, port1: $port1, port2: $port2}'; @@ -69,5 +68,5 @@ class WebMessageChannel { } extension InternalWebMessageChannel on WebMessageChannel { - MethodChannel? get channel => _channel; + MethodChannel? get internalChannel => channel; } diff --git a/lib/src/web_message/web_message_listener.dart b/lib/src/web_message/web_message_listener.dart index d2f80909..f3136c50 100644 --- a/lib/src/web_message/web_message_listener.dart +++ b/lib/src/web_message/web_message_listener.dart @@ -10,7 +10,7 @@ import '../web_uri.dart'; ///- Android native WebView ///- iOS ///- MacOS -class WebMessageListener { +class WebMessageListener extends ChannelController { ///Message Listener ID used internally. late final String id; @@ -34,8 +34,6 @@ class WebMessageListener { ///**Official Android API**: https://developer.android.com/reference/androidx/webkit/WebViewCompat.WebMessageListener#onPostMessage(android.webkit.WebView,%20androidx.webkit.WebMessageCompat,%20android.net.Uri,%20boolean,%20androidx.webkit.JavaScriptReplyProxy) OnPostMessageCallback? onPostMessage; - MethodChannel? _channel; - WebMessageListener( {required this.jsObjectName, Set? allowedOriginRules, @@ -45,16 +43,10 @@ class WebMessageListener { allowedOriginRules != null ? allowedOriginRules : Set.from(["*"]); assert(!this.allowedOriginRules.contains(""), "allowedOriginRules cannot contain empty strings"); - this._channel = MethodChannel( + channel= MethodChannel( 'com.pichillilorenzo/flutter_inappwebview_web_message_listener_${id}_$jsObjectName'); - this._channel?.setMethodCallHandler((call) async { - try { - return await _handleMethod(call); - } on Error catch (e) { - print(e); - print(e.stackTrace); - } - }); + handler = _handleMethod; + initMethodCallHandler(); } Future _handleMethod(MethodCall call) async { @@ -78,6 +70,11 @@ class WebMessageListener { return null; } + @override + void dispose() { + disposeChannel(); + } + Map toMap() { return { "id": id, @@ -114,7 +111,7 @@ class JavaScriptReplyProxy { Future postMessage(String message) async { Map args = {}; args.putIfAbsent('message', () => message); - await _webMessageListener._channel?.invokeMethod('postMessage', args); + await _webMessageListener.channel?.invokeMethod('postMessage', args); } @override diff --git a/lib/src/web_message/web_message_port.dart b/lib/src/web_message/web_message_port.dart index 57c42643..7df227c8 100644 --- a/lib/src/web_message/web_message_port.dart +++ b/lib/src/web_message/web_message_port.dart @@ -37,7 +37,7 @@ class WebMessagePort { Future setWebMessageCallback(WebMessageCallback? onMessage) async { Map args = {}; args.putIfAbsent('index', () => this._index); - await _webMessageChannel.channel + await _webMessageChannel.internalChannel ?.invokeMethod('setWebMessageCallback', args); this._onMessage = onMessage; } @@ -47,14 +47,14 @@ class WebMessagePort { Map args = {}; args.putIfAbsent('index', () => this._index); args.putIfAbsent('message', () => message.toMap()); - await _webMessageChannel.channel?.invokeMethod('postMessage', args); + await _webMessageChannel.internalChannel?.invokeMethod('postMessage', args); } ///Close the message port and free any resources associated with it. Future close() async { Map args = {}; args.putIfAbsent('index', () => this._index); - await _webMessageChannel.channel?.invokeMethod('close', args); + await _webMessageChannel.internalChannel?.invokeMethod('close', args); } @ExchangeableObjectMethod(toMapMergeWith: true) diff --git a/lib/src/web_storage/web_storage.dart b/lib/src/web_storage/web_storage.dart index 28d43a86..e6e78dc3 100644 --- a/lib/src/web_storage/web_storage.dart +++ b/lib/src/web_storage/web_storage.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import '../in_app_webview/in_app_webview_controller.dart'; +import '../types/disposable.dart'; import '../types/main.dart'; import 'web_storage_item.dart'; @@ -12,7 +13,7 @@ import 'web_storage_item.dart'; ///- iOS ///- MacOS ///- Web -class WebStorage { +class WebStorage implements Disposable { ///Represents `window.localStorage`. LocalStorage localStorage; @@ -22,6 +23,7 @@ class WebStorage { WebStorage({required this.localStorage, required this.sessionStorage}); ///Disposes the web storage. + @override void dispose() { localStorage.dispose(); sessionStorage.dispose(); @@ -30,7 +32,7 @@ class WebStorage { ///Class that provides methods to manage the JavaScript [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) object. ///It is used by [LocalStorage] and [SessionStorage]. -class Storage { +class Storage implements Disposable { InAppWebViewController? _controller; ///The web storage type: `window.sessionStorage` or `window.localStorage`. @@ -179,6 +181,7 @@ class Storage { } ///Disposes the storage. + @override void dispose() { _controller = null; }