From 2d31a2f58b980fa2c840d360c91f637a71113d92 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 23 Apr 2022 04:02:37 +0200 Subject: [PATCH] =?UTF-8?q?Managed=20iOS=20native=20detachFromEngine=20flu?= =?UTF-8?q?tter=20plugin=20event=20and=20updated=20dispose=C2=A0methods,?= =?UTF-8?q?=20Updated=20Android=20native=20HeadlessInAppWebViewManager.dis?= =?UTF-8?q?pose=20and=20HeadlessInAppWebView.dispose=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 ++ .../HeadlessInAppWebView.java | 14 +++-- .../HeadlessInAppWebViewManager.java | 6 ++ ios/Classes/CredentialDatabase.swift | 62 +++++++++++++++---- .../HeadlessInAppWebViewManager.swift | 11 ++++ .../InAppBrowser/InAppBrowserManager.swift | 6 ++ .../InAppBrowserWebViewController.swift | 13 ++-- .../FlutterWebViewController.swift | 3 +- ios/Classes/InAppWebView/InAppWebView.swift | 16 ++--- ios/Classes/InAppWebViewMethodHandler.swift | 8 ++- ios/Classes/InAppWebViewStatic.swift | 8 +++ ios/Classes/MyCookieManager.swift | 46 +++++++++++--- ios/Classes/MyWebStorageManager.swift | 32 ++++++++-- ios/Classes/PlatformUtil.swift | 6 ++ .../PullToRefresh/PullToRefreshControl.swift | 1 + .../ChromeSafariBrowserManager.swift | 6 ++ .../SafariViewController.swift | 17 +++-- ios/Classes/SwiftFlutterPlugin.swift | 25 +++++++- .../Types/WKUserContentController.swift | 6 +- ios/Classes/Types/WebMessage.swift | 1 + ios/Classes/Types/WebMessageChannel.swift | 3 +- ios/Classes/Types/WebMessageListener.swift | 1 + ios/Classes/Types/WebMessagePort.swift | 1 + ios/Classes/Util.swift | 4 +- pubspec.yaml | 2 +- 25 files changed, 245 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a8cd27..3f8d43c1 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.4.1 + +- Managed iOS native `detachFromEngine` flutter plugin event and updated `dispose` methods +- Updated Android native `HeadlessInAppWebViewManager.dispose` and `HeadlessInAppWebView.dispose` methods + ## 5.4.0+3 - Fixed Android error in some cases when calling `setServiceWorkerClient` java method on `ServiceWorkerManager` initialization diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java index b389ef96..bcf68b5e 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java @@ -110,12 +110,16 @@ public class HeadlessInAppWebView implements MethodChannel.MethodCallHandler { public void dispose() { channel.setMethodCallHandler(null); HeadlessInAppWebViewManager.webViews.remove(id); - ViewGroup contentView = (ViewGroup) plugin.activity.findViewById(android.R.id.content); - ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); - if (mainView != null) { - mainView.removeView(flutterWebView.getView()); + if (plugin != null) { + ViewGroup contentView = (ViewGroup) plugin.activity.findViewById(android.R.id.content); + ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); + if (mainView != null && flutterWebView != null) { + mainView.removeView(flutterWebView.getView()); + } + } + if (flutterWebView != null) { + flutterWebView.dispose(); } - flutterWebView.dispose(); flutterWebView = null; plugin = null; } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java index 6539601c..e15b0095 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java @@ -26,7 +26,9 @@ import androidx.annotation.Nullable; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview.in_app_webview.FlutterWebView; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import io.flutter.plugin.common.MethodCall; @@ -77,6 +79,10 @@ public class HeadlessInAppWebViewManager implements MethodChannel.MethodCallHand public void dispose() { channel.setMethodCallHandler(null); + Collection headlessInAppWebViews = webViews.values(); + for (HeadlessInAppWebView headlessInAppWebView : headlessInAppWebViews) { + headlessInAppWebView.dispose(); + } webViews.clear(); } } diff --git a/ios/Classes/CredentialDatabase.swift b/ios/Classes/CredentialDatabase.swift index d4c4f64c..5bfb1e64 100755 --- a/ios/Classes/CredentialDatabase.swift +++ b/ios/Classes/CredentialDatabase.swift @@ -31,7 +31,11 @@ class CredentialDatabase: NSObject, FlutterPlugin { switch call.method { case "getAllAuthCredentials": var allCredentials: [[String: Any?]] = [] - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + guard let credentialStore = CredentialDatabase.credentialStore else { + result(allCredentials) + return + } + for (protectionSpace, credentials) in credentialStore.allCredentials { var crendentials: [[String: Any?]] = [] for c in credentials { let credential: [String: Any?] = c.value.toMap() @@ -43,10 +47,17 @@ class CredentialDatabase: NSObject, FlutterPlugin { "credentials": crendentials ] allCredentials.append(dict) - } } + } + } result(allCredentials) break case "getHttpAuthCredentials": + var crendentials: [[String: Any?]] = [] + guard let credentialStore = CredentialDatabase.credentialStore else { + result(crendentials) + return + } + let host = arguments!["host"] as! String let urlProtocol = arguments!["protocol"] as? String let urlPort = arguments!["port"] as? Int ?? 0 @@ -54,9 +65,8 @@ class CredentialDatabase: NSObject, FlutterPlugin { if let r = realm, r.isEmpty { realm = nil } - var crendentials: [[String: Any?]] = [] - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + for (protectionSpace, credentials) in credentialStore.allCredentials { if protectionSpace.host == host && protectionSpace.realm == realm && protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort { for c in credentials { @@ -68,6 +78,11 @@ class CredentialDatabase: NSObject, FlutterPlugin { result(crendentials) break case "setHttpAuthCredential": + guard let credentialStore = CredentialDatabase.credentialStore else { + result(false) + return + } + let host = arguments!["host"] as! String let urlProtocol = arguments!["protocol"] as? String let urlPort = arguments!["port"] as? Int ?? 0 @@ -78,11 +93,17 @@ class CredentialDatabase: NSObject, FlutterPlugin { let username = arguments!["username"] as! String let password = arguments!["password"] as! String let credential = URLCredential(user: username, password: password, persistence: .permanent) - CredentialDatabase.credentialStore!.set(credential, - for: URLProtectionSpace(host: host, port: urlPort, protocol: urlProtocol, realm: realm, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)) + credentialStore.set(credential, + for: URLProtectionSpace(host: host, port: urlPort, protocol: urlProtocol, + realm: realm, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)) result(true) break case "removeHttpAuthCredential": + guard let credentialStore = CredentialDatabase.credentialStore else { + result(false) + return + } + let host = arguments!["host"] as! String let urlProtocol = arguments!["protocol"] as? String let urlPort = arguments!["port"] as? Int ?? 0 @@ -96,7 +117,7 @@ class CredentialDatabase: NSObject, FlutterPlugin { var credential: URLCredential? = nil; var protectionSpaceCredential: URLProtectionSpace? = nil - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + for (protectionSpace, credentials) in credentialStore.allCredentials { if protectionSpace.host == host && protectionSpace.realm == realm && protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort { for c in credentials { @@ -113,12 +134,17 @@ class CredentialDatabase: NSObject, FlutterPlugin { } if let c = credential, let protectionSpace = protectionSpaceCredential { - CredentialDatabase.credentialStore!.remove(c, for: protectionSpace) + credentialStore.remove(c, for: protectionSpace) } result(true) break case "removeHttpAuthCredentials": + guard let credentialStore = CredentialDatabase.credentialStore else { + result(false) + return + } + let host = arguments!["host"] as! String let urlProtocol = arguments!["protocol"] as? String let urlPort = arguments!["port"] as? Int ?? 0 @@ -130,7 +156,7 @@ class CredentialDatabase: NSObject, FlutterPlugin { var credentialsToRemove: [URLCredential] = []; var protectionSpaceCredential: URLProtectionSpace? = nil - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + for (protectionSpace, credentials) in credentialStore.allCredentials { if protectionSpace.host == host && protectionSpace.realm == realm && protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort { protectionSpaceCredential = protectionSpace @@ -145,16 +171,21 @@ class CredentialDatabase: NSObject, FlutterPlugin { if let protectionSpace = protectionSpaceCredential { for credential in credentialsToRemove { - CredentialDatabase.credentialStore!.remove(credential, for: protectionSpace) + credentialStore.remove(credential, for: protectionSpace) } } result(true) break case "clearAllAuthCredentials": - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + guard let credentialStore = CredentialDatabase.credentialStore else { + result(false) + return + } + + for (protectionSpace, credentials) in credentialStore.allCredentials { for credential in credentials { - CredentialDatabase.credentialStore!.remove(credential.value, for: protectionSpace) + credentialStore.remove(credential.value, for: protectionSpace) } } result(true) @@ -164,4 +195,11 @@ class CredentialDatabase: NSObject, FlutterPlugin { break } } + + public func dispose() { + CredentialDatabase.channel?.setMethodCallHandler(nil) + CredentialDatabase.channel = nil + CredentialDatabase.registrar = nil + CredentialDatabase.credentialStore = nil + } } diff --git a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index 0bcc777c..9a75e97e 100644 --- a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -57,4 +57,15 @@ public class HeadlessInAppWebViewManager: NSObject, FlutterPlugin { headlessInAppWebView.onWebViewCreated() flutterWebView.makeInitialLoad(params: params as NSDictionary) } + + public func dispose() { + HeadlessInAppWebViewManager.channel?.setMethodCallHandler(nil) + HeadlessInAppWebViewManager.channel = nil + HeadlessInAppWebViewManager.registrar = nil + let headlessWebViews = HeadlessInAppWebViewManager.webViews.values + headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView) in + headlessWebView.dispose() + } + HeadlessInAppWebViewManager.webViews.removeAll() + } } diff --git a/ios/Classes/InAppBrowser/InAppBrowserManager.swift b/ios/Classes/InAppBrowser/InAppBrowserManager.swift index defda931..1bf90000 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserManager.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserManager.swift @@ -140,4 +140,10 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { } result(true) } + + public func dispose() { + InAppBrowserManager.channel?.setMethodCallHandler(nil) + InAppBrowserManager.channel = nil + InAppBrowserManager.registrar = nil + } } diff --git a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift index f806105d..ffcc6f7f 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -182,6 +182,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega deinit { print("InAppBrowserWebViewController - dealloc") + dispose() } public override func viewDidDisappear(_ animated: Bool) { @@ -550,7 +551,10 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func dispose() { - webView.dispose() + onExit() + channel?.setMethodCallHandler(nil) + channel = nil + webView?.dispose() webView = nil view = nil if previousStatusBarStyle != -1 { @@ -563,18 +567,15 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega backButton.target = nil reloadButton.target = nil shareButton.target = nil - onExit() - channel?.setMethodCallHandler(nil) - channel = nil methodCallDelegate?.webView = nil methodCallDelegate = nil } public func onBrowserCreated() { - channel!.invokeMethod("onBrowserCreated", arguments: []) + channel?.invokeMethod("onBrowserCreated", arguments: []) } public func onExit() { - channel!.invokeMethod("onExit", arguments: []) + channel?.invokeMethod("onExit", arguments: []) } } diff --git a/ios/Classes/InAppWebView/FlutterWebViewController.swift b/ios/Classes/InAppWebView/FlutterWebViewController.swift index 8f883d2b..aa8f51a3 100755 --- a/ios/Classes/InAppWebView/FlutterWebViewController.swift +++ b/ios/Classes/InAppWebView/FlutterWebViewController.swift @@ -164,7 +164,8 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { func dispose() { channel?.setMethodCallHandler(nil) - methodCallDelegate?.webView = nil + channel = nil + methodCallDelegate?.dispose() methodCallDelegate = nil webView?.dispose() webView = nil diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index 225c3973..4e79f56b 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -1673,8 +1673,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi completionHandler(.useCredential, credential) break case 2: - if InAppWebView.credentialsProposed.count == 0 { - for (protectionSpace, credentials) in CredentialDatabase.credentialStore!.allCredentials { + if InAppWebView.credentialsProposed.count == 0, let credentialStore = CredentialDatabase.credentialStore { + for (protectionSpace, credentials) in credentialStore.allCredentials { if protectionSpace.host == host && protectionSpace.realm == realm && protectionSpace.protocol == prot && protectionSpace.port == port { for credential in credentials { @@ -2900,6 +2900,12 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } public func dispose() { + channel = nil + removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) + removeObserver(self, forKeyPath: #keyPath(WKWebView.url)) + removeObserver(self, forKeyPath: #keyPath(WKWebView.title)) + scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset)) + scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale)) resumeTimers() stopLoading() disposeWebMessageChannels() @@ -2920,15 +2926,10 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { InAppWebView.windowWebViews.removeValue(forKey: wId) } configuration.userContentController.dispose(windowId: windowId) - removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) - removeObserver(self, forKeyPath: #keyPath(WKWebView.url)) - removeObserver(self, forKeyPath: #keyPath(WKWebView.title)) NotificationCenter.default.removeObserver(self) for imp in customIMPs { imp_removeBlock(imp) } - scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset)) - scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale)) longPressRecognizer.removeTarget(self, action: #selector(longPressGestureDetected)) longPressRecognizer.delegate = nil scrollView.removeGestureRecognizer(longPressRecognizer) @@ -2945,7 +2946,6 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { navigationDelegate = nil scrollView.delegate = nil isPausedTimersCompletionHandler = nil - channel = nil SharedLastTouchPointTimestamp.removeValue(forKey: self) callAsyncJavaScriptBelowIOS14Results.removeAll() super.removeFromSuperview() diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift index ca13aa8c..fe45528d 100644 --- a/ios/Classes/InAppWebViewMethodHandler.swift +++ b/ios/Classes/InAppWebViewMethodHandler.swift @@ -553,8 +553,12 @@ public class InAppWebViewMethodHandler: FlutterMethodCallDelegate { } } - deinit { - print("InAppWebViewMethodHandler - dealloc") + public func dispose() { webView = nil } + + deinit { + print("InAppWebViewMethodHandler - dealloc") + dispose() + } } diff --git a/ios/Classes/InAppWebViewStatic.swift b/ios/Classes/InAppWebViewStatic.swift index aa36a906..58e5f874 100755 --- a/ios/Classes/InAppWebViewStatic.swift +++ b/ios/Classes/InAppWebViewStatic.swift @@ -72,4 +72,12 @@ class InAppWebViewStatic: NSObject, FlutterPlugin { completionHandler(defaultUserAgent) } } + + public func dispose() { + InAppWebViewStatic.channel?.setMethodCallHandler(nil) + InAppWebViewStatic.channel = nil + InAppWebViewStatic.registrar = nil + InAppWebViewStatic.webViewForUserAgent = nil + InAppWebViewStatic.defaultUserAgent = nil + } } diff --git a/ios/Classes/MyCookieManager.swift b/ios/Classes/MyCookieManager.swift index 80fb2c9a..1f892d87 100755 --- a/ios/Classes/MyCookieManager.swift +++ b/ios/Classes/MyCookieManager.swift @@ -100,6 +100,11 @@ class MyCookieManager: NSObject, FlutterPlugin { isHttpOnly: Bool?, sameSite: String?, result: @escaping FlutterResult) { + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(false) + return + } + var properties: [HTTPCookiePropertyKey: Any] = [:] properties[.originURL] = url properties[.name] = name @@ -138,7 +143,7 @@ class MyCookieManager: NSObject, FlutterPlugin { let cookie = HTTPCookie(properties: properties)! - MyCookieManager.httpCookieStore!.setCookie(cookie, completionHandler: {() in + httpCookieStore.setCookie(cookie, completionHandler: {() in result(true) }) } @@ -146,8 +151,13 @@ class MyCookieManager: NSObject, FlutterPlugin { public static func getCookies(url: String, result: @escaping FlutterResult) { var cookieList: [[String: Any?]] = [] + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(cookieList) + return + } + if let urlHost = URL(string: url)?.host { - MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in + httpCookieStore.getAllCookies { (cookies) in for cookie in cookies { if urlHost.hasSuffix(cookie.domain) || ".\(urlHost)".hasSuffix(cookie.domain) { var sameSite: String? = nil @@ -189,7 +199,12 @@ class MyCookieManager: NSObject, FlutterPlugin { public static func getAllCookies(result: @escaping FlutterResult) { var cookieList: [[String: Any?]] = [] - MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(cookieList) + return + } + + httpCookieStore.getAllCookies { (cookies) in for cookie in cookies { var sameSite: String? = nil if #available(iOS 13.0, *) { @@ -221,7 +236,12 @@ class MyCookieManager: NSObject, FlutterPlugin { } public static func deleteCookie(url: String, name: String, domain: String, path: String, result: @escaping FlutterResult) { - MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(false) + return + } + + httpCookieStore.getAllCookies { (cookies) in for cookie in cookies { var originURL = "" if cookie.properties![.originURL] is String { @@ -234,7 +254,7 @@ class MyCookieManager: NSObject, FlutterPlugin { continue } if (cookie.domain == domain || cookie.domain == ".\(domain)" || ".\(cookie.domain)" == domain) && cookie.name == name && cookie.path == path { - MyCookieManager.httpCookieStore!.delete(cookie, completionHandler: { + httpCookieStore.delete(cookie, completionHandler: { result(true) }) return @@ -245,7 +265,12 @@ class MyCookieManager: NSObject, FlutterPlugin { } public static func deleteCookies(url: String, domain: String, path: String, result: @escaping FlutterResult) { - MyCookieManager.httpCookieStore!.getAllCookies { (cookies) in + guard let httpCookieStore = MyCookieManager.httpCookieStore else { + result(false) + return + } + + httpCookieStore.getAllCookies { (cookies) in for cookie in cookies { var originURL = "" if cookie.properties![.originURL] is String { @@ -258,7 +283,7 @@ class MyCookieManager: NSObject, FlutterPlugin { continue } if (cookie.domain == domain || cookie.domain == ".\(domain)" || ".\(cookie.domain)" == domain) && cookie.path == path { - MyCookieManager.httpCookieStore!.delete(cookie, completionHandler: nil) + httpCookieStore.delete(cookie, completionHandler: nil) } } result(true) @@ -272,4 +297,11 @@ class MyCookieManager: NSObject, FlutterPlugin { result(true) }) } + + public func dispose() { + MyCookieManager.channel?.setMethodCallHandler(nil) + MyCookieManager.channel = nil + MyCookieManager.registrar = nil + MyCookieManager.httpCookieStore = nil + } } diff --git a/ios/Classes/MyWebStorageManager.swift b/ios/Classes/MyWebStorageManager.swift index 8daaeb23..426f30e5 100755 --- a/ios/Classes/MyWebStorageManager.swift +++ b/ios/Classes/MyWebStorageManager.swift @@ -53,7 +53,13 @@ class MyWebStorageManager: NSObject, FlutterPlugin { public static func fetchDataRecords(dataTypes: Set, result: @escaping FlutterResult) { var recordList: [[String: Any?]] = [] - MyWebStorageManager.websiteDataStore!.fetchDataRecords(ofTypes: dataTypes) { (data) in + + guard let websiteDataStore = MyWebStorageManager.websiteDataStore else { + result(recordList) + return + } + + websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in for record in data { recordList.append([ "displayName": record.displayName, @@ -68,7 +74,13 @@ class MyWebStorageManager: NSObject, FlutterPlugin { public static func removeDataFor(dataTypes: Set, recordList: [[String: Any?]], result: @escaping FlutterResult) { var records: [WKWebsiteDataRecord] = [] - MyWebStorageManager.websiteDataStore!.fetchDataRecords(ofTypes: dataTypes) { (data) in + + guard let websiteDataStore = MyWebStorageManager.websiteDataStore else { + result(false) + return + } + + websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in for record in data { for r in recordList { let displayName = r["displayName"] as! String @@ -78,16 +90,28 @@ class MyWebStorageManager: NSObject, FlutterPlugin { } } } - MyWebStorageManager.websiteDataStore!.removeData(ofTypes: dataTypes, for: records) { + websiteDataStore.removeData(ofTypes: dataTypes, for: records) { result(true) } } } public static func removeDataModifiedSince(dataTypes: Set, timestamp: Int64, result: @escaping FlutterResult) { + guard let websiteDataStore = MyWebStorageManager.websiteDataStore else { + result(false) + return + } + let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp)) - MyWebStorageManager.websiteDataStore!.removeData(ofTypes: dataTypes, modifiedSince: date as Date) { + websiteDataStore.removeData(ofTypes: dataTypes, modifiedSince: date as Date) { result(true) } } + + public func dispose() { + MyWebStorageManager.channel?.setMethodCallHandler(nil) + MyWebStorageManager.channel = nil + MyWebStorageManager.registrar = nil + MyWebStorageManager.websiteDataStore = nil + } } diff --git a/ios/Classes/PlatformUtil.swift b/ios/Classes/PlatformUtil.swift index e84c96ce..0bf29b40 100644 --- a/ios/Classes/PlatformUtil.swift +++ b/ios/Classes/PlatformUtil.swift @@ -60,4 +60,10 @@ class PlatformUtil: NSObject, FlutterPlugin { formatter.timeZone = timezone return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date)) } + + public func dispose() { + PlatformUtil.channel?.setMethodCallHandler(nil) + PlatformUtil.channel = nil + PlatformUtil.registrar = nil + } } diff --git a/ios/Classes/PullToRefresh/PullToRefreshControl.swift b/ios/Classes/PullToRefresh/PullToRefreshControl.swift index 8753ce62..481bf8d9 100644 --- a/ios/Classes/PullToRefresh/PullToRefreshControl.swift +++ b/ios/Classes/PullToRefresh/PullToRefreshControl.swift @@ -109,5 +109,6 @@ public class PullToRefreshControl : UIRefreshControl, FlutterPlugin { deinit { print("PullToRefreshControl - dealloc") + dispose() } } diff --git a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift index 137d0105..e351427e 100755 --- a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift +++ b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift @@ -91,4 +91,10 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { result(FlutterError.init(code: "ChromeSafariBrowserManager", message: "SafariViewController is not available!", details: nil)) } + + public func dispose() { + ChromeSafariBrowserManager.channel?.setMethodCallHandler(nil) + ChromeSafariBrowserManager.channel = nil + ChromeSafariBrowserManager.registrar = nil + } } diff --git a/ios/Classes/SafariViewController/SafariViewController.swift b/ios/Classes/SafariViewController/SafariViewController.swift index 0dbea527..83786fe5 100755 --- a/ios/Classes/SafariViewController/SafariViewController.swift +++ b/ios/Classes/SafariViewController/SafariViewController.swift @@ -22,6 +22,7 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa deinit { print("SafariViewController - dealloc") + dispose() } public func prepareMethodChannel() { @@ -123,20 +124,21 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa // } public func onChromeSafariBrowserOpened() { - channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: []) + channel?.invokeMethod("onChromeSafariBrowserOpened", arguments: []) } public func onChromeSafariBrowserCompletedInitialLoad() { - channel!.invokeMethod("onChromeSafariBrowserCompletedInitialLoad", arguments: []) + channel?.invokeMethod("onChromeSafariBrowserCompletedInitialLoad", arguments: []) } public func onChromeSafariBrowserClosed() { - channel!.invokeMethod("onChromeSafariBrowserClosed", arguments: []) + channel?.invokeMethod("onChromeSafariBrowserClosed", arguments: []) } public func dispose() { + channel?.setMethodCallHandler(nil) + channel = nil delegate = nil - channel!.setMethodCallHandler(nil) } } @@ -180,7 +182,12 @@ class CustomUIActivity : UIActivity { } override func perform() { - let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_chromesafaribrowser_" + viewId, binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) + guard let registrar = SwiftFlutterPlugin.instance?.registrar else { + return + } + + let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_chromesafaribrowser_" + viewId, + binaryMessenger: registrar.messenger()) let arguments: [String: Any?] = [ "url": url.absoluteString, diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift index 98977ee1..75631fac 100755 --- a/ios/Classes/SwiftFlutterPlugin.swift +++ b/ios/Classes/SwiftFlutterPlugin.swift @@ -49,16 +49,39 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { headlessInAppWebViewManager = HeadlessInAppWebViewManager(registrar: registrar) chromeSafariBrowserManager = ChromeSafariBrowserManager(registrar: registrar) inAppWebViewStatic = InAppWebViewStatic(registrar: registrar) + credentialDatabase = CredentialDatabase(registrar: registrar) if #available(iOS 11.0, *) { myCookieManager = MyCookieManager(registrar: registrar) } if #available(iOS 9.0, *) { myWebStorageManager = MyWebStorageManager(registrar: registrar) } - credentialDatabase = CredentialDatabase(registrar: registrar) } public static func register(with registrar: FlutterPluginRegistrar) { SwiftFlutterPlugin.instance = SwiftFlutterPlugin(with: registrar) } + + public func detachFromEngine(for registrar: FlutterPluginRegistrar) { + platformUtil?.dispose() + platformUtil = nil + inAppBrowserManager?.dispose() + inAppBrowserManager = nil + headlessInAppWebViewManager?.dispose() + headlessInAppWebViewManager = nil + chromeSafariBrowserManager?.dispose() + chromeSafariBrowserManager = nil + inAppWebViewStatic?.dispose() + inAppWebViewStatic = nil + credentialDatabase?.dispose() + credentialDatabase = nil + if #available(iOS 11.0, *) { + (myCookieManager as! MyCookieManager?)?.dispose() + myCookieManager = nil + } + if #available(iOS 9.0, *) { + (myWebStorageManager as! MyWebStorageManager?)?.dispose() + myWebStorageManager = nil + } + } } diff --git a/ios/Classes/Types/WKUserContentController.swift b/ios/Classes/Types/WKUserContentController.swift index 10115da4..53c901a7 100644 --- a/ios/Classes/Types/WKUserContentController.swift +++ b/ios/Classes/Types/WKUserContentController.swift @@ -21,7 +21,7 @@ extension WKUserContentController { var contentWorlds: Set { get { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) - return WKUserContentController._contentWorlds[tmpAddress]! + return WKUserContentController._contentWorlds[tmpAddress] ?? [] } set(newValue) { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) @@ -33,7 +33,7 @@ extension WKUserContentController { var userOnlyScripts: [WKUserScriptInjectionTime:OrderedSet] { get { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) - return WKUserContentController._userOnlyScripts[tmpAddress]! + return WKUserContentController._userOnlyScripts[tmpAddress] ?? [:] } set(newValue) { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) @@ -45,7 +45,7 @@ extension WKUserContentController { var pluginScripts: [WKUserScriptInjectionTime:OrderedSet] { get { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) - return WKUserContentController._pluginScripts[tmpAddress]! + return WKUserContentController._pluginScripts[tmpAddress] ?? [:] } set(newValue) { let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) diff --git a/ios/Classes/Types/WebMessage.swift b/ios/Classes/Types/WebMessage.swift index 169926b2..106232c3 100644 --- a/ios/Classes/Types/WebMessage.swift +++ b/ios/Classes/Types/WebMessage.swift @@ -23,5 +23,6 @@ public class WebMessage : NSObject { deinit { print("WebMessage - dealloc") + dispose() } } diff --git a/ios/Classes/Types/WebMessageChannel.swift b/ios/Classes/Types/WebMessageChannel.swift index de9e75aa..2a06b28e 100644 --- a/ios/Classes/Types/WebMessageChannel.swift +++ b/ios/Classes/Types/WebMessageChannel.swift @@ -126,6 +126,7 @@ public class WebMessageChannel : FlutterMethodCallDelegate { public func dispose() { channel?.setMethodCallHandler(nil) + channel = nil for port in ports { port.dispose() } @@ -140,11 +141,11 @@ public class WebMessageChannel : FlutterMethodCallDelegate { } })(); """) - channel = nil webView = nil } deinit { print("WebMessageChannel - dealloc") + dispose() } } diff --git a/ios/Classes/Types/WebMessageListener.swift b/ios/Classes/Types/WebMessageListener.swift index 0d85c331..21150c84 100644 --- a/ios/Classes/Types/WebMessageListener.swift +++ b/ios/Classes/Types/WebMessageListener.swift @@ -222,5 +222,6 @@ public class WebMessageListener : FlutterMethodCallDelegate { deinit { print("WebMessageListener - dealloc") + dispose() } } diff --git a/ios/Classes/Types/WebMessagePort.swift b/ios/Classes/Types/WebMessagePort.swift index dff16845..801ab68f 100644 --- a/ios/Classes/Types/WebMessagePort.swift +++ b/ios/Classes/Types/WebMessagePort.swift @@ -118,5 +118,6 @@ public class WebMessagePort : NSObject { deinit { print("WebMessagePort - dealloc") + dispose() } } diff --git a/ios/Classes/Util.swift b/ios/Classes/Util.swift index 121930ab..0bf74e84 100644 --- a/ios/Classes/Util.swift +++ b/ios/Classes/Util.swift @@ -12,7 +12,7 @@ var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:] public class Util { public static func getUrlAsset(assetFilePath: String) throws -> URL { - let key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: assetFilePath) + let key = SwiftFlutterPlugin.instance?.registrar?.lookupKey(forAsset: assetFilePath) guard let assetURL = Bundle.main.url(forResource: key, withExtension: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } @@ -20,7 +20,7 @@ public class Util { } public static func getAbsPathAsset(assetFilePath: String) throws -> String { - let key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: assetFilePath) + let key = SwiftFlutterPlugin.instance?.registrar?.lookupKey(forAsset: assetFilePath) guard let assetAbsPath = Bundle.main.path(forResource: key, ofType: nil) else { throw NSError(domain: assetFilePath + " asset file cannot be found!", code: 0) } diff --git a/pubspec.yaml b/pubspec.yaml index 359bb63c..51954c14 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -version: 5.4.0+3 +version: 5.4.1 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: