diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserActivity.java index 8e3401b2..f5e7c5ee 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_browser/InAppBrowserActivity.java @@ -104,6 +104,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow pullToRefreshLayout.prepare(); webView = findViewById(R.id.webView); + webView.id = id; webView.windowId = windowId; webView.inAppBrowserDelegate = this; webView.plugin = manager.plugin; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/WebViewChannelDelegate.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/WebViewChannelDelegate.java index aea0b5f5..3a3f4a79 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/WebViewChannelDelegate.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/webview/WebViewChannelDelegate.java @@ -1112,7 +1112,10 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl { public void onLoadResourceCustomScheme(String url, @NonNull LoadResourceCustomSchemeCallback callback) { MethodChannel channel = getChannel(); - if (channel == null) return; + if (channel == null) { + callback.defaultBehaviour(null); + return; + } Map obj = new HashMap<>(); obj.put("url", url); channel.invokeMethod("onLoadResourceCustomScheme", obj, callback); @@ -1146,7 +1149,10 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl { public void shouldInterceptRequest(WebResourceRequestExt request, @NonNull ShouldInterceptRequestCallback callback) { MethodChannel channel = getChannel(); - if (channel == null) return; + if (channel == null) { + callback.defaultBehaviour(null); + return; + } channel.invokeMethod("shouldInterceptRequest", request.toMap(), callback); } diff --git a/ios/Classes/CredentialDatabase.swift b/ios/Classes/CredentialDatabase.swift index 42c88425..3fe15a91 100755 --- a/ios/Classes/CredentialDatabase.swift +++ b/ios/Classes/CredentialDatabase.swift @@ -7,7 +7,7 @@ import Foundation -class CredentialDatabase: ChannelDelegate { +public class CredentialDatabase: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_credential_database" static var registrar: FlutterPluginRegistrar? static var credentialStore: URLCredentialStorage? @@ -193,4 +193,8 @@ class CredentialDatabase: ChannelDelegate { CredentialDatabase.registrar = nil CredentialDatabase.credentialStore = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index e884ecb2..d9afab44 100644 --- a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift +++ b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -61,4 +61,8 @@ public class HeadlessInAppWebViewManager: ChannelDelegate { } HeadlessInAppWebViewManager.webViews.removeAll() } + + deinit { + dispose() + } } diff --git a/ios/Classes/HeadlessInAppWebView/HeadlessWebViewChannelDelegate.swift b/ios/Classes/HeadlessInAppWebView/HeadlessWebViewChannelDelegate.swift index 9619b719..0b686da8 100644 --- a/ios/Classes/HeadlessInAppWebView/HeadlessWebViewChannelDelegate.swift +++ b/ios/Classes/HeadlessInAppWebView/HeadlessWebViewChannelDelegate.swift @@ -8,7 +8,7 @@ import Foundation public class HeadlessWebViewChannelDelegate : ChannelDelegate { - private var headlessWebView: HeadlessInAppWebView? + private weak var headlessWebView: HeadlessInAppWebView? public init(headlessWebView: HeadlessInAppWebView, channel: FlutterMethodChannel) { super.init(channel: channel) @@ -56,4 +56,8 @@ public class HeadlessWebViewChannelDelegate : ChannelDelegate { super.dispose() headlessWebView = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/InAppBrowser/InAppBrowserChannelDelegate.swift b/ios/Classes/InAppBrowser/InAppBrowserChannelDelegate.swift index 4053b731..41593618 100644 --- a/ios/Classes/InAppBrowser/InAppBrowserChannelDelegate.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserChannelDelegate.swift @@ -21,4 +21,8 @@ public class InAppBrowserChannelDelegate : ChannelDelegate { let arguments: [String: Any?] = [:] channel?.invokeMethod("onExit", arguments: arguments) } + + deinit { + dispose() + } } diff --git a/ios/Classes/InAppBrowser/InAppBrowserManager.swift b/ios/Classes/InAppBrowser/InAppBrowserManager.swift index 8dd465eb..8d77a76a 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserManager.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserManager.swift @@ -138,4 +138,8 @@ public class InAppBrowserManager: ChannelDelegate { super.dispose() InAppBrowserManager.registrar = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift index 09878c1b..07733470 100755 --- a/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowser/InAppBrowserWebViewController.swift @@ -24,7 +24,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega var tmpWindow: UIWindow? var id: String = "" var windowId: Int64? - var webView: InAppWebView! + var webView: InAppWebView? var channelDelegate: InAppBrowserChannelDelegate? var initialUrlRequest: URLRequest? var initialFile: String? @@ -38,7 +38,6 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega var previousStatusBarStyle = -1 var initialUserScripts: [[String: Any]] = [] var pullToRefreshInitialSettings: [String: Any?] = [:] - var methodCallDelegate: InAppWebViewMethodHandler? public override func loadView() { let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) @@ -52,20 +51,24 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings) if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { webView = webViewTransport.webView - webView.contextMenu = contextMenu - webView.channel = channel - webView.initialUserScripts = userScripts + webView!.contextMenu = contextMenu + webView!.initialUserScripts = userScripts } else { - webView = InAppWebView(frame: .zero, - configuration: preWebviewConfiguration, - contextMenu: contextMenu, - channel: channel, - userScripts: userScripts) + webView = InAppWebView(id: nil, + registrar: nil, + frame: .zero, + configuration: preWebviewConfiguration, + contextMenu: contextMenu, + userScripts: userScripts) } - webView.inAppBrowserDelegate = self - methodCallDelegate = InAppWebViewMethodHandler(webView: webView!) - channel.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle) + guard let webView = webView else { + return + } + + webView.inAppBrowserDelegate = self + webView.id = id + webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel) let pullToRefreshSettings = PullToRefreshSettings() let _ = pullToRefreshSettings.parse(settings: pullToRefreshInitialSettings) @@ -87,38 +90,41 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega public override func viewDidLoad() { super.viewDidLoad() - webView.translatesAutoresizingMaskIntoConstraints = false + webView?.translatesAutoresizingMaskIntoConstraints = false progressBar.translatesAutoresizingMaskIntoConstraints = false if #available(iOS 9.0, *) { - webView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true - webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true - webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true - webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true + webView?.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true + webView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true + webView?.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true + webView?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true progressBar.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true progressBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true } else { - view.addConstraints([ - NSLayoutConstraint(item: webView!, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0), - NSLayoutConstraint(item: webView!, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0), - NSLayoutConstraint(item: webView!, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0), - NSLayoutConstraint(item: webView!, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0) - ]) - - view.addConstraints([ - NSLayoutConstraint(item: progressBar!, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0), - NSLayoutConstraint(item: progressBar!, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0), - NSLayoutConstraint(item: progressBar!, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0) - ]) + if let webView = webView { + view.addConstraints([ + NSLayoutConstraint(item: webView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0), + NSLayoutConstraint(item: webView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0), + NSLayoutConstraint(item: webView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0), + NSLayoutConstraint(item: webView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0) + ]) + } + if let progressBar = progressBar { + view.addConstraints([ + NSLayoutConstraint(item: progressBar, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0), + NSLayoutConstraint(item: progressBar, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0), + NSLayoutConstraint(item: progressBar, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0) + ]) + } } if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { - webView.load(webViewTransport.request) + webView?.load(webViewTransport.request) } else { if #available(iOS 11.0, *) { - if let contentBlockers = webView.settings?.contentBlockers, contentBlockers.count > 0 { + if let contentBlockers = webView?.settings?.contentBlockers, contentBlockers.count > 0 { do { let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) @@ -165,27 +171,22 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega allowingReadAccessToURL = nil } } - webView.loadData(data: initialData, mimeType: initialMimeType!, encoding: initialEncoding!, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL) + webView?.loadData(data: initialData, mimeType: initialMimeType!, encoding: initialEncoding!, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL) } else if let initialUrlRequest = initialUrlRequest { var allowingReadAccessToURL: URL? = nil - if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, let url = initialUrlRequest.url, url.scheme == "file" { + if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, let url = initialUrlRequest.url, url.scheme == "file" { allowingReadAccessToURL = URL(string: allowingReadAccessTo) if allowingReadAccessToURL?.scheme != "file" { allowingReadAccessToURL = nil } } - webView.loadUrl(urlRequest: initialUrlRequest, allowingReadAccessTo: allowingReadAccessToURL) + webView?.loadUrl(urlRequest: initialUrlRequest, allowingReadAccessTo: allowingReadAccessToURL) } channelDelegate?.onBrowserCreated() } - deinit { - debugPrint("InAppBrowserWebViewController - dealloc") - dispose() - } - public override func viewDidDisappear(_ animated: Bool) { dispose() super.viewDidDisappear(animated) @@ -203,8 +204,8 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func prepareWebView() { - webView.settings = webViewSettings - webView.prepare() + webView?.settings = webViewSettings + webView?.prepare() searchBar = UISearchBar() searchBar.keyboardType = .URL @@ -305,8 +306,8 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func didStartNavigation(url: URL?) { - forwardButton.isEnabled = webView.canGoForward - backButton.isEnabled = webView.canGoBack + forwardButton.isEnabled = webView?.canGoForward ?? false + backButton.isEnabled = webView?.canGoBack ?? false progressBar.setProgress(0.0, animated: false) guard let url = url else { return @@ -315,8 +316,8 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func didUpdateVisitedHistory(url: URL?) { - forwardButton.isEnabled = webView.canGoForward - backButton.isEnabled = webView.canGoBack + forwardButton.isEnabled = webView?.canGoForward ?? false + backButton.isEnabled = webView?.canGoBack ?? false guard let url = url else { return } @@ -324,8 +325,8 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func didFinishNavigation(url: URL?) { - forwardButton.isEnabled = webView.canGoForward - backButton.isEnabled = webView.canGoBack + forwardButton.isEnabled = webView?.canGoForward ?? false + backButton.isEnabled = webView?.canGoBack ?? false progressBar.setProgress(0.0, animated: false) guard let url = url else { return @@ -334,8 +335,8 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } public func didFailNavigation(url: URL?, error: Error) { - forwardButton.isEnabled = webView.canGoForward - backButton.isEnabled = webView.canGoBack + forwardButton.isEnabled = webView?.canGoForward ?? false + backButton.isEnabled = webView?.canGoBack ?? false progressBar.setProgress(0.0, animated: false) } @@ -350,7 +351,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega return } let request = URLRequest(url: url) - webView.load(request) + webView?.load(request) } public func show(completion: (() -> Void)? = nil) { @@ -381,12 +382,12 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } @objc public func reload() { - webView.reload() - didUpdateVisitedHistory(url: webView.url) + webView?.reload() + didUpdateVisitedHistory(url: webView?.url) } @objc public func share() { - let vc = UIActivityViewController(activityItems: [webView.url?.absoluteString ?? ""], applicationActivities: []) + let vc = UIActivityViewController(activityItems: [webView?.url?.absoluteString ?? ""], applicationActivities: []) present(vc, animated: true, completion: nil) } @@ -413,26 +414,25 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega } @objc public func goBack() { - if webView.canGoBack { + if let webView = webView, webView.canGoBack { webView.goBack() } } @objc public func goForward() { - if webView.canGoForward { + if let webView = webView, webView.canGoForward { webView.goForward() } } @objc public func goBackOrForward(steps: Int) { - webView.goBackOrForward(steps: steps) + webView?.goBackOrForward(steps: steps) } public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) { - - let newInAppWebViewOptions = InAppWebViewSettings() - let _ = newInAppWebViewOptions.parse(settings: newSettingsMap) - self.webView.setSettings(newSettings: newInAppWebViewOptions, newSettingsMap: newSettingsMap) + let newInAppWebViewSettings = InAppWebViewSettings() + let _ = newInAppWebViewSettings.parse(settings: newSettingsMap) + webView?.setSettings(newSettings: newInAppWebViewSettings, newSettingsMap: newSettingsMap) if newSettingsMap["hidden"] != nil, browserSettings?.hidden != newSettings.hidden { if newSettings.hidden { @@ -537,12 +537,12 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega progressBar.isHidden = newSettings.hideProgressBar } - self.browserSettings = newSettings - self.webViewSettings = newInAppWebViewOptions + browserSettings = newSettings + webViewSettings = newInAppWebViewSettings } public func getSettings() -> [String: Any?]? { - let webViewSettingsMap = self.webView.getSettings() + let webViewSettingsMap = webView?.getSettings() if (self.browserSettings == nil || webViewSettingsMap == nil) { return nil } @@ -568,7 +568,10 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega backButton.target = nil reloadButton.target = nil shareButton.target = nil - methodCallDelegate?.webView = nil - methodCallDelegate = nil + } + + deinit { + debugPrint("InAppBrowserWebViewController - dealloc") + dispose() } } diff --git a/ios/Classes/InAppWebView/ContextMenuSettings.swift b/ios/Classes/InAppWebView/ContextMenuSettings.swift index e0b5594d..a1df58d9 100644 --- a/ios/Classes/InAppWebView/ContextMenuSettings.swift +++ b/ios/Classes/InAppWebView/ContextMenuSettings.swift @@ -7,7 +7,7 @@ import Foundation -class ContextMenuSettings: ISettings { +public class ContextMenuSettings: ISettings { var hideDefaultSystemContextMenuItems = false; diff --git a/ios/Classes/InAppWebView/CustomSchemeHandler.swift b/ios/Classes/InAppWebView/CustomSchemeHandler.swift index 072779af..2c1546b1 100755 --- a/ios/Classes/InAppWebView/CustomSchemeHandler.swift +++ b/ios/Classes/InAppWebView/CustomSchemeHandler.swift @@ -10,37 +10,37 @@ import Foundation import WebKit @available(iOS 11.0, *) -class CustomSchemeHandler : NSObject, WKURLSchemeHandler { +public class CustomSchemeHandler : NSObject, WKURLSchemeHandler { var schemeHandlers: [Int:WKURLSchemeTask] = [:] - func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { + public func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { schemeHandlers[urlSchemeTask.hash] = urlSchemeTask let inAppWebView = webView as! InAppWebView if let url = urlSchemeTask.request.url { - inAppWebView.onLoadResourceCustomScheme(url: url.absoluteString, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") + let callback = WebViewChannelDelegate.LoadResourceCustomSchemeCallback() + callback.nonNullSuccess = { (response: CustomSchemeResponse) in + if (self.schemeHandlers[urlSchemeTask.hash] != nil) { + let urlResponse = URLResponse(url: url, mimeType: response.contentType, expectedContentLength: -1, textEncodingName: response.contentEncoding) + urlSchemeTask.didReceive(urlResponse) + urlSchemeTask.didReceive(response.data) + urlSchemeTask.didFinish() + self.schemeHandlers.removeValue(forKey: urlSchemeTask.hash) } - else if (result as? NSObject) == FlutterMethodNotImplemented {} - else { - let json: [String: Any] - if let r = result { - json = r as! [String: Any] - let urlResponse = URLResponse(url: url, mimeType: (json["contentType"] as! String), expectedContentLength: -1, textEncodingName: (json["contentEncoding"] as! String)) - let data = json["data"] as! FlutterStandardTypedData - if (self.schemeHandlers[urlSchemeTask.hash] != nil) { - urlSchemeTask.didReceive(urlResponse) - urlSchemeTask.didReceive(data.data) - urlSchemeTask.didFinish() - self.schemeHandlers.removeValue(forKey: urlSchemeTask.hash) - } - } - } - }) + return false + } + callback.error = { (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + } + + if let channelDelegate = inAppWebView.channelDelegate { + channelDelegate.onLoadResourceCustomScheme(url: url, callback: callback) + } else { + callback.defaultBehaviour(nil) + } } } - func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { + public func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { schemeHandlers.removeValue(forKey: urlSchemeTask.hash) } } diff --git a/ios/Classes/InAppWebView/FlutterWebViewController.swift b/ios/Classes/InAppWebView/FlutterWebViewController.swift index 8827eee2..bdabaa1e 100755 --- a/ios/Classes/InAppWebView/FlutterWebViewController.swift +++ b/ios/Classes/InAppWebView/FlutterWebViewController.swift @@ -8,24 +8,13 @@ import Foundation import WebKit -public class FlutterWebViewController: NSObject, FlutterPlatformView { +public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable { - private weak var registrar: FlutterPluginRegistrar? - var webView: InAppWebView? - var viewId: Any = 0 - var channel: FlutterMethodChannel? var myView: UIView? - var methodCallDelegate: InAppWebViewMethodHandler? init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) { super.init() - self.registrar = registrar - self.viewId = viewId - - channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_" + String(describing: viewId), - binaryMessenger: registrar.messenger()) - myView = UIView(frame: frame) myView!.clipsToBounds = true @@ -46,23 +35,26 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { let _ = settings.parse(settings: initialSettings) let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: settings) + var webView: InAppWebView? + if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { webView = webViewTransport.webView + webView!.id = viewId + let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId), + binaryMessenger: registrar.messenger()) + webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel) webView!.frame = myView!.bounds webView!.contextMenu = contextMenu - webView!.channel = channel! webView!.initialUserScripts = userScripts } else { - webView = InAppWebView(frame: myView!.bounds, + webView = InAppWebView(id: viewId, + registrar: registrar, + frame: myView!.bounds, configuration: preWebviewConfiguration, contextMenu: contextMenu, - channel: channel!, userScripts: userScripts) } - methodCallDelegate = InAppWebViewMethodHandler(webView: webView!) - channel!.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle) - let pullToRefreshSettings = PullToRefreshSettings() let _ = pullToRefreshSettings.parse(settings: pullToRefreshInitialSettings) let pullToRefreshControl = PullToRefreshControl(registrar: registrar, id: viewId, settings: pullToRefreshSettings) @@ -80,11 +72,26 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { webView!.windowCreated = true } + public func webView() -> InAppWebView? { + for subview in myView?.subviews ?? [] + { + if let item = subview as? InAppWebView + { + return item + } + } + return nil + } + public func view() -> UIView { return myView! } public func makeInitialLoad(params: NSDictionary) { + guard let webView = webView() else { + return + } + let windowId = params["windowId"] as? Int64 let initialUrlRequest = params["initialUrlRequest"] as? [String: Any?] let initialFile = params["initialFile"] as? String @@ -92,8 +99,8 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { if windowId == nil { if #available(iOS 11.0, *) { - self.webView!.configuration.userContentController.removeAllContentRuleLists() - if let contentBlockers = webView!.settings?.contentBlockers, contentBlockers.count > 0 { + webView.configuration.userContentController.removeAllContentRuleLists() + if let contentBlockers = webView.settings?.contentBlockers, contentBlockers.count > 0 { do { let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: []) let blockRules = String(data: jsonData, encoding: String.Encoding.utf8) @@ -106,7 +113,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { return } - let configuration = self.webView!.configuration + let configuration = webView.configuration configuration.userContentController.add(contentRuleList!) self.load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData) @@ -120,14 +127,18 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData) } else if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { - webView!.load(webViewTransport.request) + webView.load(webViewTransport.request) } } func load(initialUrlRequest: [String:Any?]?, initialFile: String?, initialData: [String: String]?) { + guard let webView = webView() else { + return + } + if let initialFile = initialFile { do { - try webView?.loadFile(assetFilePath: initialFile) + try webView.loadFile(assetFilePath: initialFile) } catch let error as NSError { dump(error) @@ -139,34 +150,31 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { let encoding = initialData["encoding"]! let baseUrl = URL(string: initialData["baseUrl"]!)! var allowingReadAccessToURL: URL? = nil - if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, baseUrl.scheme == "file" { + if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, baseUrl.scheme == "file" { allowingReadAccessToURL = URL(string: allowingReadAccessTo) if allowingReadAccessToURL?.scheme != "file" { allowingReadAccessToURL = nil } } - webView?.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL) + webView.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL) } else if let initialUrlRequest = initialUrlRequest { let urlRequest = URLRequest.init(fromPluginMap: initialUrlRequest) var allowingReadAccessToURL: URL? = nil - if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, let url = urlRequest.url, url.scheme == "file" { + if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, let url = urlRequest.url, url.scheme == "file" { allowingReadAccessToURL = URL(string: allowingReadAccessTo) if allowingReadAccessToURL?.scheme != "file" { allowingReadAccessToURL = nil } } - webView?.loadUrl(urlRequest: urlRequest, allowingReadAccessTo: allowingReadAccessToURL) + webView.loadUrl(urlRequest: urlRequest, allowingReadAccessTo: allowingReadAccessToURL) } } - func dispose() { - channel?.setMethodCallHandler(nil) - channel = nil - methodCallDelegate?.dispose() - methodCallDelegate = nil - webView?.dispose() - webView = nil + public func dispose() { + if let webView = webView() { + webView.dispose() + } myView = nil } diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index c030af3c..b2e89aeb 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -12,12 +12,14 @@ import WebKit public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate, WKDownloadDelegate, - PullToRefreshDelegate { + PullToRefreshDelegate, Disposable { + static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_" + var id: Any? // viewId var windowId: Int64? var windowCreated = false var inAppBrowserDelegate: InAppBrowserDelegate? - var channel: FlutterMethodChannel? + var channelDelegate: WebViewChannelDelegate? var settings: InAppWebViewSettings? var pullToRefreshControl: PullToRefreshControl? var webMessageChannels: [String:WebMessageChannel] = [:] @@ -63,9 +65,15 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, var oldZoomScale = Float(1.0) - init(frame: CGRect, configuration: WKWebViewConfiguration, contextMenu: [String: Any]?, channel: FlutterMethodChannel?, userScripts: [UserScript] = []) { + init(id: Any?, registrar: FlutterPluginRegistrar?, frame: CGRect, configuration: WKWebViewConfiguration, + contextMenu: [String: Any]?, userScripts: [UserScript] = []) { super.init(frame: frame, configuration: configuration) - self.channel = channel + self.id = id + if let id = id, let registrar = registrar { + let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: id), + binaryMessenger: registrar.messenger()) + self.channelDelegate = WebViewChannelDelegate(webView: self, channel: channel) + } self.contextMenu = contextMenu self.initialUserScripts = userScripts uiDelegate = self @@ -189,10 +197,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, self.nativeLoupeGesture?.isEnabled = false self.nativeLoupeGesture?.isEnabled = true } else { - self.onLongPressHitTestResult(hitTestResult: hitTestResult) + self.channelDelegate?.onLongPressHitTestResult(hitTestResult: hitTestResult) } } else if sender == self.longPressRecognizer { - self.onLongPressHitTestResult(hitTestResult: HitTestResult(type: .unknownType, extra: nil)) + self.channelDelegate?.onLongPressHitTestResult(hitTestResult: HitTestResult(type: .unknownType, extra: nil)) } }) } @@ -213,13 +221,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, (id is Int64 ? String(id as! Int64) : id as! String) if !self.responds(to: Selector(targetMethodName)) { let customAction: () -> Void = { - let arguments: [String: Any?] = [ - "id": id, - "iosId": id is Int64 ? String(id as! Int64) : id as! String, - "androidId": nil, - "title": title - ] - self.channel?.invokeMethod("onContextMenuActionItemClicked", arguments: arguments) + self.channelDelegate?.onContextMenuActionItemClicked(id: id, title: title) } let castedCustomAction: AnyObject = unsafeBitCast(customAction as @convention(block) () -> Void, to: AnyObject.self) let swizzledImplementation = imp_implementationWithBlock(castedCustomAction) @@ -261,13 +263,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if contextMenuIsShowing, !action.description.starts(with: "onContextMenuActionItemClicked-") { let id = action.description.compactMap({ $0.asciiValue?.description }).joined() - let arguments: [String: Any?] = [ - "id": id, - "iosId": id, - "androidId": nil, - "title": action.description - ] - self.channel?.invokeMethod("onContextMenuActionItemClicked", arguments: arguments) + self.channelDelegate?.onContextMenuActionItemClicked(id: id, title: action.description) } } @@ -602,23 +598,24 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contextMenuIsShowing = true + let hitTestResult = HitTestResult(type: .unknownType, extra: nil) + if let lastLongPressTouhLocation = lastLongPressTouchPoint { if configuration.preferences.javaScriptEnabled { self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(lastLongPressTouhLocation.x),\(lastLongPressTouhLocation.y))", completionHandler: {(value, error) in if error != nil { print("Long press gesture recognizer error: \(error?.localizedDescription ?? "")") - } else if var value = value as? [String: Any?] { - value["type"] = value["type"] as? Int - self.channel?.invokeMethod("onCreateContextMenu", arguments: value) + } else if let value = value as? [String: Any?] { + self.channelDelegate?.onCreateContextMenu(hitTestResult: HitTestResult.fromMap(map: value) ?? hitTestResult) } else { - self.channel?.invokeMethod("onCreateContextMenu", arguments: [:]) + self.channelDelegate?.onCreateContextMenu(hitTestResult: hitTestResult) } }) } else { - channel?.invokeMethod("onCreateContextMenu", arguments: [:]) + channelDelegate?.onCreateContextMenu(hitTestResult: hitTestResult) } } else { - channel?.invokeMethod("onCreateContextMenu", arguments: [:]) + channelDelegate?.onCreateContextMenu(hitTestResult: hitTestResult) } } @@ -626,11 +623,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if contextMenuIsShowing == false { return } - contextMenuIsShowing = false - - let arguments: [String: Any] = [:] - channel?.invokeMethod("onHideContextMenu", arguments: arguments) + channelDelegate?.onHideContextMenu() } override public func observeValue(forKeyPath keyPath: String?, of object: Any?, @@ -638,16 +632,16 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if keyPath == #keyPath(WKWebView.estimatedProgress) { initializeWindowIdJS() let progress = Int(estimatedProgress * 100) - onProgressChanged(progress: progress) + channelDelegate?.onProgressChanged(progress: progress) inAppBrowserDelegate?.didChangeProgress(progress: estimatedProgress) } else if keyPath == #keyPath(WKWebView.url) && change?[.newKey] is URL { initializeWindowIdJS() let newUrl = change?[NSKeyValueChangeKey.newKey] as? URL - onUpdateVisitedHistory(url: newUrl?.absoluteString) + channelDelegate?.onUpdateVisitedHistory(url: newUrl?.absoluteString, isReload: nil) inAppBrowserDelegate?.didUpdateVisitedHistory(url: newUrl) } else if keyPath == #keyPath(WKWebView.title) && change?[.newKey] is String { let newTitle = change?[.newKey] as? String - onTitleChanged(title: newTitle) + channelDelegate?.onTitleChanged(title: newTitle) inAppBrowserDelegate?.didChangeTitle(title: newTitle) } else if keyPath == #keyPath(UIScrollView.contentOffset) { let newContentOffset = change?[.newKey] as? CGPoint @@ -671,17 +665,17 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } if oldState != newState { if keyPath == #keyPath(WKWebView.cameraCaptureState) { - onCameraCaptureStateChanged(oldState: oldState, newState: newState) + channelDelegate?.onCameraCaptureStateChanged(oldState: oldState, newState: newState) } else { - onMicrophoneCaptureStateChanged(oldState: oldState, newState: newState) + channelDelegate?.onMicrophoneCaptureStateChanged(oldState: oldState, newState: newState) } } } // else if keyPath == #keyPath(WKWebView.fullscreenState) { // if fullscreenState == .enteringFullscreen { -// onEnterFullscreen() +// channelDelegate?.onEnterFullscreen() // } else if fullscreenState == .exitingFullscreen { -// onExitFullscreen() +// channelDelegate?.onExitFullscreen() // } // } } @@ -1270,7 +1264,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let errorMessage = userInfo["WKJavaScriptExceptionMessage"] ?? userInfo["NSLocalizedDescription"] as? String ?? error.localizedDescription - self.onConsoleMessage(message: String(describing: errorMessage), messageLevel: 3) + self.channelDelegate?.onConsoleMessage(message: String(describing: errorMessage), messageLevel: 3) } if value == nil { @@ -1308,7 +1302,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let errorMessage = userInfo["WKJavaScriptExceptionMessage"] ?? userInfo["NSLocalizedDescription"] as? String ?? error.localizedDescription - self.onConsoleMessage(message: String(describing: errorMessage), messageLevel: 3) + self.channelDelegate?.onConsoleMessage(message: String(describing: errorMessage), messageLevel: 3) break } @@ -1374,7 +1368,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, body["error"] = userInfo["WKJavaScriptExceptionMessage"] ?? userInfo["NSLocalizedDescription"] as? String ?? error.localizedDescription - self.onConsoleMessage(message: String(describing: body["error"]), messageLevel: 3) + self.channelDelegate?.onConsoleMessage(message: String(describing: body["error"]), messageLevel: 3) break } @@ -1419,7 +1413,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let errorMessage = userInfo["WKJavaScriptExceptionMessage"] ?? userInfo["NSLocalizedDescription"] as? String ?? error.localizedDescription - self.onConsoleMessage(message: String(describing: errorMessage), messageLevel: 3) + self.channelDelegate?.onConsoleMessage(message: String(describing: errorMessage), messageLevel: 3) completionHandler?(nil) self.callAsyncJavaScriptBelowIOS14Results.removeValue(forKey: resultUuid) } @@ -1550,36 +1544,37 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, decisionHandler: @escaping (WKPermissionDecision) -> Void) { let origin = "\(origin.protocol)://\(origin.host)\(origin.port != 0 ? ":" + String(origin.port) : "")" let permissionRequest = PermissionRequest(origin: origin, resources: [type.rawValue], frame: frame) - - onPermissionRequest(request: permissionRequest, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - decisionHandler(.prompt) - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - decisionHandler(.prompt) - } - else { - var response: [String: Any] - if let r = result { - response = r as! [String: Any] - var action = response["action"] as? Int - action = action != nil ? action : 0; - switch action { - case 1: - decisionHandler(.grant) - break - case 2: - decisionHandler(.prompt) - break - default: - decisionHandler(.deny) - } - return; + + let callback = WebViewChannelDelegate.PermissionRequestCallback() + callback.nonNullSuccess = { (response: PermissionResponse) in + if let action = response.action { + switch action { + case 1: + decisionHandler(.grant) + break + case 2: + decisionHandler(.prompt) + break + default: + decisionHandler(.deny) } - decisionHandler(.prompt) + return false } - }) + return true + } + callback.defaultBehaviour = { (response: PermissionResponse?) in + decisionHandler(.deny) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onPermissionRequest(request: permissionRequest, callback: callback) + } else { + callback.defaultBehaviour(nil) + } } @available(iOS 15.0, *) @@ -1592,35 +1587,36 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let origin = "\(origin.protocol)://\(origin.host)\(origin.port != 0 ? ":" + String(origin.port) : "")" let permissionRequest = PermissionRequest(origin: origin, resources: ["deviceOrientationAndMotion"], frame: frame) - onPermissionRequest(request: permissionRequest, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - decisionHandler(.prompt) - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - decisionHandler(.prompt) - } - else { - var response: [String: Any] - if let r = result { - response = r as! [String: Any] - var action = response["action"] as? Int - action = action != nil ? action : 0; - switch action { - case 1: - decisionHandler(.grant) - break - case 2: - decisionHandler(.prompt) - break - default: - decisionHandler(.deny) - } - return; + let callback = WebViewChannelDelegate.PermissionRequestCallback() + callback.nonNullSuccess = { (response: PermissionResponse) in + if let action = response.action { + switch action { + case 1: + decisionHandler(.grant) + break + case 2: + decisionHandler(.prompt) + break + default: + decisionHandler(.deny) } - decisionHandler(.prompt) + return false } - }) + return true + } + callback.defaultBehaviour = { (response: PermissionResponse?) in + decisionHandler(.deny) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onPermissionRequest(request: permissionRequest, callback: callback) + } else { + callback.defaultBehaviour(nil) + } } @available(iOS 13.0, *) @@ -1643,7 +1639,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: suggestedFilename, textEncodingName: response.textEncodingName) - onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) } download.delegate = nil // cancel the download @@ -1661,7 +1657,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: response.expectedContentLength, suggestedFilename: response.suggestedFilename, textEncodingName: response.textEncodingName) - onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) } download.delegate = nil } @@ -1675,39 +1671,24 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, return } - if navigationAction.request.url != nil { - - if let useShouldOverrideUrlLoading = settings?.useShouldOverrideUrlLoading, useShouldOverrideUrlLoading { - shouldOverrideUrlLoading(navigationAction: navigationAction, result: { (result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - decisionHandler(.allow) - return - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - decisionHandler(.allow) - return - } - else { - var response: [String: Any] - if let r = result { - response = r as! [String: Any] - let action = response["action"] as? Int - let navigationActionPolicy = WKNavigationActionPolicy - .init(rawValue: action ?? WKNavigationActionPolicy.cancel.rawValue) ?? - WKNavigationActionPolicy.cancel - decisionHandler(navigationActionPolicy) - return; - } - decisionHandler(.allow) - } - }) - return - - } + let callback = WebViewChannelDelegate.ShouldOverrideUrlLoadingCallback() + callback.nonNullSuccess = { (response: WKNavigationActionPolicy) in + decisionHandler(response) + return false + } + callback.defaultBehaviour = { (response: WKNavigationActionPolicy?) in + decisionHandler(.allow) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let useShouldOverrideUrlLoading = settings?.useShouldOverrideUrlLoading, useShouldOverrideUrlLoading, let channelDelegate = channelDelegate { + channelDelegate.shouldOverrideUrlLoading(navigationAction: navigationAction, callback: callback) + } else { + callback.defaultBehaviour(nil) } - - decisionHandler(.allow) } public func webView(_ webView: WKWebView, @@ -1723,36 +1704,30 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, headers: response.allHeaderFields, statusCode: response.statusCode, reasonPhrase: nil) - onReceivedHttpError(request: request, errorResponse: errorResponse) + channelDelegate?.onReceivedHttpError(request: request, errorResponse: errorResponse) } let useOnNavigationResponse = settings?.useOnNavigationResponse if useOnNavigationResponse != nil, useOnNavigationResponse! { - onNavigationResponse(navigationResponse: navigationResponse, result: { (result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - decisionHandler(.allow) - return - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - decisionHandler(.allow) - return - } - else { - var response: [String: Any] - if let r = result { - response = r as! [String: Any] - let action = response["action"] as? Int - let navigationActionPolicy = WKNavigationResponsePolicy - .init(rawValue: action ?? WKNavigationResponsePolicy.cancel.rawValue) ?? - WKNavigationResponsePolicy.cancel - decisionHandler(navigationActionPolicy) - return; - } - decisionHandler(.allow) - } - }) + let callback = WebViewChannelDelegate.NavigationResponseCallback() + callback.nonNullSuccess = { (response: WKNavigationResponsePolicy) in + decisionHandler(response) + return false + } + callback.defaultBehaviour = { (response: WKNavigationResponsePolicy?) in + decisionHandler(.allow) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onNavigationResponse(navigationResponse: navigationResponse, callback: callback) + } else { + callback.defaultBehaviour(nil) + } } if let useOnDownloadStart = settings?.useOnDownloadStart, useOnDownloadStart { @@ -1770,7 +1745,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, contentLength: navigationResponse.response.expectedContentLength, suggestedFilename: navigationResponse.response.suggestedFilename, textEncodingName: navigationResponse.response.textEncodingName) - onDownloadStartRequest(request: downloadStartRequest) + channelDelegate?.onDownloadStartRequest(request: downloadStartRequest) if useOnNavigationResponse == nil || !useOnNavigationResponse! { decisionHandler(.cancel) } @@ -1796,7 +1771,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, configuration.userContentController.resetContentWorlds(windowId: windowId) } - onLoadStart(url: url?.absoluteString) + channelDelegate?.onLoadStart(url: url?.absoluteString) inAppBrowserDelegate?.didStartNavigation(url: url) } @@ -1815,7 +1790,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, setNeedsLayout() } - onLoadStop(url: url?.absoluteString) + channelDelegate?.onLoadStop(url: url?.absoluteString) inAppBrowserDelegate?.didFinishNavigation(url: url) } @@ -1853,7 +1828,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let webResourceRequest = WebResourceRequest(url: urlError, headers: nil) let webResourceError = WebResourceError(errorCode: errorCode, errorDescription: errorDescription) - onReceivedError(request: webResourceRequest, error: webResourceError) + channelDelegate?.onReceivedError(request: webResourceRequest, error: webResourceError) inAppBrowserDelegate?.didFailNavigation(url: url, error: error) } @@ -1874,164 +1849,172 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let prot = challenge.protectionSpace.protocol let realm = challenge.protectionSpace.realm let port = challenge.protectionSpace.port - onReceivedHttpAuthRequest(challenge: challenge, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - completionHandler(.performDefaultHandling, nil) - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - completionHandler(.performDefaultHandling, nil) - } - else { - var response: [String: Any] - if let r = result { - response = r as! [String: Any] - var action = response["action"] as? Int - action = action != nil ? action : 0; - switch action { - case 0: - InAppWebView.credentialsProposed = [] - // used .performDefaultHandling to mantain consistency with Android - // because .cancelAuthenticationChallenge will call webView(_:didFail:withError:) - completionHandler(.performDefaultHandling, nil) - //completionHandler(.cancelAuthenticationChallenge, nil) - break - case 1: - let username = response["username"] as! String - let password = response["password"] as! String - let permanentPersistence = response["permanentPersistence"] as? Bool ?? false - let persistence = (permanentPersistence) ? URLCredential.Persistence.permanent : URLCredential.Persistence.forSession - let credential = URLCredential(user: username, password: password, persistence: persistence) - completionHandler(.useCredential, credential) - break - case 2: - 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 { - InAppWebView.credentialsProposed.append(credential.value) - } - break + + let callback = WebViewChannelDelegate.ReceivedHttpAuthRequestCallback() + callback.nonNullSuccess = { (response: HttpAuthResponse) in + if let action = response.action { + switch action { + case 0: + InAppWebView.credentialsProposed = [] + // used .performDefaultHandling to mantain consistency with Android + // because .cancelAuthenticationChallenge will call webView(_:didFail:withError:) + completionHandler(.performDefaultHandling, nil) + //completionHandler(.cancelAuthenticationChallenge, nil) + break + case 1: + let username = response.username + let password = response.password + let permanentPersistence = response.permanentPersistence + let persistence = (permanentPersistence) ? URLCredential.Persistence.permanent : URLCredential.Persistence.forSession + let credential = URLCredential(user: username, password: password, persistence: persistence) + completionHandler(.useCredential, credential) + break + case 2: + 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 { + InAppWebView.credentialsProposed.append(credential.value) } + break } } - if InAppWebView.credentialsProposed.count == 0, let credential = challenge.proposedCredential { - InAppWebView.credentialsProposed.append(credential) - } - - if let credential = InAppWebView.credentialsProposed.popLast() { - completionHandler(.useCredential, credential) - } - else { - completionHandler(.performDefaultHandling, nil) - } - break - default: - InAppWebView.credentialsProposed = [] + } + if InAppWebView.credentialsProposed.count == 0, let credential = challenge.proposedCredential { + InAppWebView.credentialsProposed.append(credential) + } + + if let credential = InAppWebView.credentialsProposed.popLast() { + completionHandler(.useCredential, credential) + } + else { completionHandler(.performDefaultHandling, nil) - } - return; + } + break + default: + InAppWebView.credentialsProposed = [] + completionHandler(.performDefaultHandling, nil) } - completionHandler(.performDefaultHandling, nil) + return false } - }) + return true + } + callback.defaultBehaviour = { (response: HttpAuthResponse?) in + completionHandler(.performDefaultHandling, nil) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onReceivedHttpAuthRequest(challenge: HttpAuthenticationChallenge(fromChallenge: challenge), callback: callback) + } else { + callback.defaultBehaviour(nil) + } } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { - guard let serverTrust = challenge.protectionSpace.serverTrust else { completionHandler(.performDefaultHandling, nil) return } - - onReceivedServerTrustAuthRequest(challenge: challenge, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - completionHandler(.performDefaultHandling, nil) - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - completionHandler(.performDefaultHandling, nil) - } - else { - var response: [String: Any] - if let r = result { - response = r as! [String: Any] - var action = response["action"] as? Int - action = action != nil ? action : 0; - switch action { - case 0: - InAppWebView.credentialsProposed = [] - completionHandler(.cancelAuthenticationChallenge, nil) - break - case 1: - let exceptions = SecTrustCopyExceptions(serverTrust) - SecTrustSetExceptions(serverTrust, exceptions) - let credential = URLCredential(trust: serverTrust) - completionHandler(.useCredential, credential) - break - default: - InAppWebView.credentialsProposed = [] - completionHandler(.performDefaultHandling, nil) - } - return; + + if let scheme = challenge.protectionSpace.protocol, scheme == "https", + let sslCertificate = challenge.protectionSpace.sslCertificate { + InAppWebView.sslCertificatesMap[challenge.protectionSpace.host] = sslCertificate + } + + let callback = WebViewChannelDelegate.ReceivedServerTrustAuthRequestCallback() + callback.nonNullSuccess = { (response: ServerTrustAuthResponse) in + if let action = response.action { + switch action { + case 0: + InAppWebView.credentialsProposed = [] + completionHandler(.cancelAuthenticationChallenge, nil) + break + case 1: + let exceptions = SecTrustCopyExceptions(serverTrust) + SecTrustSetExceptions(serverTrust, exceptions) + let credential = URLCredential(trust: serverTrust) + completionHandler(.useCredential, credential) + break + default: + InAppWebView.credentialsProposed = [] + completionHandler(.performDefaultHandling, nil) } - completionHandler(.performDefaultHandling, nil) + return false } - }) + return true + } + callback.defaultBehaviour = { (response: ServerTrustAuthResponse?) in + completionHandler(.performDefaultHandling, nil) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onReceivedServerTrustAuthRequest(challenge: ServerTrustChallenge(fromChallenge: challenge), callback: callback) + } else { + callback.defaultBehaviour(nil) + } } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate { - onReceivedClientCertRequest(challenge: challenge, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - completionHandler(.performDefaultHandling, nil) - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - completionHandler(.performDefaultHandling, nil) - } - else { - var response: [String: Any] - if let r = result { - response = r as! [String: Any] - var action = response["action"] as? Int - action = action != nil ? action : 0; - switch action { - case 0: - completionHandler(.cancelAuthenticationChallenge, nil) - break - case 1: - let certificatePath = response["certificatePath"] as! String; - let certificatePassword = response["certificatePassword"] as? String ?? ""; + let callback = WebViewChannelDelegate.ReceivedClientCertRequestCallback() + callback.nonNullSuccess = { (response: ClientCertResponse) in + if let action = response.action { + switch action { + case 0: + completionHandler(.cancelAuthenticationChallenge, nil) + break + case 1: + let certificatePath = response.certificatePath + let certificatePassword = response.certificatePassword ?? ""; + + do { + let path = try Util.getAbsPathAsset(assetFilePath: certificatePath) + let PKCS12Data = NSData(contentsOfFile: path)! - do { - let path = try Util.getAbsPathAsset(assetFilePath: certificatePath) - let PKCS12Data = NSData(contentsOfFile: path)! - - if let identityAndTrust: IdentityAndTrust = self.extractIdentity(PKCS12Data: PKCS12Data, password: certificatePassword) { - let urlCredential: URLCredential = URLCredential( - identity: identityAndTrust.identityRef, - certificates: identityAndTrust.certArray as? [AnyObject], - persistence: URLCredential.Persistence.forSession); - completionHandler(.useCredential, urlCredential) - } else { - completionHandler(.performDefaultHandling, nil) - } - } catch { - print(error.localizedDescription) + if let identityAndTrust: IdentityAndTrust = self.extractIdentity(PKCS12Data: PKCS12Data, password: certificatePassword) { + let urlCredential: URLCredential = URLCredential( + identity: identityAndTrust.identityRef, + certificates: identityAndTrust.certArray as? [AnyObject], + persistence: URLCredential.Persistence.forSession); + completionHandler(.useCredential, urlCredential) + } else { completionHandler(.performDefaultHandling, nil) } - - break - case 2: - completionHandler(.cancelAuthenticationChallenge, nil) - break - default: + } catch { + print(error.localizedDescription) completionHandler(.performDefaultHandling, nil) - } - return; + } + + break + case 2: + completionHandler(.cancelAuthenticationChallenge, nil) + break + default: + completionHandler(.performDefaultHandling, nil) } - completionHandler(.performDefaultHandling, nil) + return false } - }) + return true + } + callback.defaultBehaviour = { (response: ClientCertResponse?) in + completionHandler(.performDefaultHandling, nil) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onReceivedClientCertRequest(challenge: ClientCertChallenge(fromChallenge: challenge), callback: callback) + } else { + callback.defaultBehaviour(nil) + } } else { completionHandler(.performDefaultHandling, nil) @@ -2105,41 +2088,37 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, return } - onJsAlert(frame: frame, message: message, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - completionHandler() - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - self.createAlertDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, completionHandler: completionHandler) - } - else { - let response: [String: Any] - var responseMessage: String?; - var confirmButtonTitle: String?; - - if let r = result { - response = r as! [String: Any] - responseMessage = response["message"] as? String - confirmButtonTitle = response["confirmButtonTitle"] as? String - let handledByClient = response["handledByClient"] as? Bool - if handledByClient != nil, handledByClient! { - var action = response["action"] as? Int - action = action != nil ? action : 1; - switch action { - case 0: - completionHandler() - break - default: - completionHandler() - } - return; - } + let callback = WebViewChannelDelegate.JsAlertCallback() + callback.nonNullSuccess = { (response: JsAlertResponse) in + if response.handledByClient { + let action = response.action ?? 1 + switch action { + case 0: + completionHandler() + break + default: + completionHandler() } - - self.createAlertDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler) + return false } - }) + return true + } + callback.defaultBehaviour = { (response: JsAlertResponse?) in + let responseMessage = response?.message + let confirmButtonTitle = response?.confirmButtonTitle + self.createAlertDialog(message: message, responseMessage: responseMessage, + confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler) + } + callback.error = { (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + completionHandler() + } + + if let channelDelegate = channelDelegate { + channelDelegate.onJsAlert(url: frame.request.url, message: message, isMainFrame: frame.isMainFrame, callback: callback) + } else { + callback.defaultBehaviour(nil) + } } func createConfirmDialog(message: String?, responseMessage: String?, confirmButtonTitle: String?, cancelButtonTitle: String?, completionHandler: @escaping (Bool) -> Void) { @@ -2166,46 +2145,40 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { - - onJsConfirm(frame: frame, message: message, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - completionHandler(false) - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - self.createConfirmDialog(message: message, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, completionHandler: completionHandler) - } - else { - let response: [String: Any] - var responseMessage: String?; - var confirmButtonTitle: String?; - var cancelButtonTitle: String?; - - if let r = result { - response = r as! [String: Any] - responseMessage = response["message"] as? String - confirmButtonTitle = response["confirmButtonTitle"] as? String - cancelButtonTitle = response["cancelButtonTitle"] as? String - let handledByClient = response["handledByClient"] as? Bool - if handledByClient != nil, handledByClient! { - var action = response["action"] as? Int - action = action != nil ? action : 1; - switch action { - case 0: - completionHandler(true) - break - case 1: - completionHandler(false) - break - default: - completionHandler(false) - } - return; - } + let callback = WebViewChannelDelegate.JsConfirmCallback() + callback.nonNullSuccess = { (response: JsConfirmResponse) in + if response.handledByClient { + let action = response.action ?? 1 + switch action { + case 0: + completionHandler(true) + break + case 1: + completionHandler(false) + break + default: + completionHandler(false) } - self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler) + return false } - }) + return true + } + callback.defaultBehaviour = { (response: JsConfirmResponse?) in + let responseMessage = response?.message + let confirmButtonTitle = response?.confirmButtonTitle + let cancelButtonTitle = response?.cancelButtonTitle + self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler) + } + callback.error = { (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + completionHandler(false) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onJsConfirm(url: frame.request.url, message: message, isMainFrame: frame.isMainFrame, callback: callback) + } else { + callback.defaultBehaviour(nil) + } } func createPromptDialog(message: String, defaultValue: String?, responseMessage: String?, confirmButtonTitle: String?, cancelButtonTitle: String?, value: String?, completionHandler: @escaping (String?) -> Void) { @@ -2243,48 +2216,42 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { - onJsPrompt(frame: frame, message: message, defaultValue: defaultValue, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - completionHandler(nil) - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: nil, confirmButtonTitle: nil, cancelButtonTitle: nil, value: nil, completionHandler: completionHandler) - } - else { - let response: [String: Any] - var responseMessage: String?; - var confirmButtonTitle: String?; - var cancelButtonTitle: String?; - var value: String?; - - if let r = result { - response = r as! [String: Any] - responseMessage = response["message"] as? String - confirmButtonTitle = response["confirmButtonTitle"] as? String - cancelButtonTitle = response["cancelButtonTitle"] as? String - let handledByClient = response["handledByClient"] as? Bool - value = response["value"] as? String; - if handledByClient != nil, handledByClient! { - var action = response["action"] as? Int - action = action != nil ? action : 1; - switch action { - case 0: - completionHandler(value) - break - case 1: - completionHandler(nil) - break - default: - completionHandler(nil) - } - return; - } + let callback = WebViewChannelDelegate.JsPromptCallback() + callback.nonNullSuccess = { (response: JsPromptResponse) in + if response.handledByClient { + let action = response.action ?? 1 + switch action { + case 0: + completionHandler(response.value) + break + case 1: + completionHandler(nil) + break + default: + completionHandler(nil) } - - self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler) + return false } - }) + return true + } + callback.defaultBehaviour = { (response: JsPromptResponse?) in + let responseMessage = response?.message + let confirmButtonTitle = response?.confirmButtonTitle + let cancelButtonTitle = response?.cancelButtonTitle + let value = response?.value + self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, + cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler) + } + callback.error = { (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + completionHandler(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onJsPrompt(url: frame.request.url, message: message, defaultValue: defaultValue, isMainFrame: frame.isMainFrame, callback: callback) + } else { + callback.defaultBehaviour(nil) + } } /// UIScrollViewDelegate is somehow bugged: @@ -2318,7 +2285,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, (disableHorizontalScroll && scrollView.contentOffset.y != oldContentOffset?.y) { let x = Int(scrollView.contentOffset.x / scrollView.contentScaleFactor) let y = Int(scrollView.contentOffset.y / scrollView.contentScaleFactor) - onScrollChanged(x: x, y: y) + channelDelegate?.onScrollChanged(x: x, y: y) } lastScrollX = scrollView.contentOffset.x lastScrollY = scrollView.contentOffset.y @@ -2328,7 +2295,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, if overScrolledHorizontally || overScrolledVertically { let x = Int(lastScrollX / scrollView.contentScaleFactor) let y = Int(lastScrollY / scrollView.contentScaleFactor) - self.onOverScrolled(x: x, y: y, + channelDelegate?.onOverScrolled(x: x, y: y, clampedX: overScrolledHorizontally, clampedY: overScrolledVertically) } @@ -2337,7 +2304,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, public func scrollViewDidZoom(_ scrollView: UIScrollView) { let newScale = Float(scrollView.zoomScale) if newScale != oldZoomScale { - self.onZoomScaleChanged(newScale: newScale, oldScale: oldZoomScale) + channelDelegate?.onZoomScaleChanged(newScale: newScale, oldScale: oldZoomScale) oldZoomScale = newScale } } @@ -2349,7 +2316,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, InAppWebView.windowAutoincrementId += 1 let windowId = InAppWebView.windowAutoincrementId - let windowWebView = InAppWebView(frame: CGRect.zero, configuration: configuration, contextMenu: nil, channel: nil) + let windowWebView = InAppWebView(id: nil, registrar: nil, frame: CGRect.zero, configuration: configuration, contextMenu: nil) windowWebView.windowId = windowId let webViewTransport = WebViewTransport( @@ -2360,36 +2327,28 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, InAppWebView.windowWebViews[windowId] = webViewTransport windowWebView.stopLoading() - var arguments: [String: Any?] = navigationAction.toMap() - arguments["windowId"] = windowId - arguments["isDialog"] = nil - arguments["windowFeatures"] = windowFeatures.toMap() - - channel?.invokeMethod("onCreateWindow", arguments: arguments, result: { (result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - if InAppWebView.windowWebViews[windowId] != nil { - InAppWebView.windowWebViews.removeValue(forKey: windowId) - } - return + let createWindowAction = CreateWindowAction(navigationAction: navigationAction, windowId: windowId, windowFeatures: windowFeatures, isDialog: nil) + + let callback = WebViewChannelDelegate.CreateWindowCallback() + callback.nonNullSuccess = { (handledByClient: Bool) in + return !handledByClient + } + callback.defaultBehaviour = { (handledByClient: Bool?) in + if InAppWebView.windowWebViews[windowId] != nil { + InAppWebView.windowWebViews.removeValue(forKey: windowId) } - else if (result as? NSObject) == FlutterMethodNotImplemented { - if InAppWebView.windowWebViews[windowId] != nil { - InAppWebView.windowWebViews.removeValue(forKey: windowId) - } - return - } - else { - var handledByClient = false - if result != nil, result is Bool { - handledByClient = result as! Bool - } - if !handledByClient, InAppWebView.windowWebViews[windowId] != nil { - InAppWebView.windowWebViews.removeValue(forKey: windowId) - self.loadUrl(urlRequest: navigationAction.request, allowingReadAccessTo: nil) - } - } - }) + self.loadUrl(urlRequest: navigationAction.request, allowingReadAccessTo: nil) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.onCreateWindow(createWindowAction: createWindowAction, callback: callback) + } else { + callback.defaultBehaviour(nil) + } return windowWebView } @@ -2402,54 +2361,42 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, return } - shouldAllowDeprecatedTLS(challenge: challenge, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - decisionHandler(false) - } - else if (result as? NSObject) == FlutterMethodNotImplemented { - decisionHandler(false) - } - else { - var response: [String: Any] - if let r = result { - response = r as! [String: Any] - var action = response["action"] as? Int - action = action != nil ? action : 0; - switch action { - case 0: - decisionHandler(false) - break - case 1: - decisionHandler(true) - break - default: - decisionHandler(false) - } - return; - } - decisionHandler(false) - } - }) + let callback = WebViewChannelDelegate.ShouldAllowDeprecatedTLSCallback() + callback.nonNullSuccess = { (action: Bool) in + decisionHandler(action) + return false + } + callback.defaultBehaviour = { (action: Bool?) in + decisionHandler(false) + } + callback.error = { [weak callback] (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + callback?.defaultBehaviour(nil) + } + + if let channelDelegate = channelDelegate { + channelDelegate.shouldAllowDeprecatedTLS(challenge: challenge, callback: callback) + } else { + callback.defaultBehaviour(nil) + } } public func webViewDidClose(_ webView: WKWebView) { - let arguments: [String: Any?] = [:] - channel?.invokeMethod("onCloseWindow", arguments: arguments) + channelDelegate?.onCloseWindow() } public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { - onWebContentProcessDidTerminate() + channelDelegate?.onWebContentProcessDidTerminate() } public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - onPageCommitVisible(url: url?.absoluteString) + channelDelegate?.onPageCommitVisible(url: url?.absoluteString) } public func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) { - onDidReceiveServerRedirectForProvisionalNavigation() + channelDelegate?.onDidReceiveServerRedirectForProvisionalNavigation() } // @available(iOS 13.0, *) @@ -2554,196 +2501,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, // //onContextMenuWillPresentForElement(linkURL: elementInfo.linkURL?.absoluteString) // } - public func onLoadStart(url: String?) { - let arguments: [String: Any?] = ["url": url] - channel?.invokeMethod("onLoadStart", arguments: arguments) - } - - public func onLoadStop(url: String?) { - let arguments: [String: Any?] = ["url": url] - channel?.invokeMethod("onLoadStop", arguments: arguments) - } - - public func onReceivedError(request: WebResourceRequest, error: WebResourceError) { - let arguments: [String: Any?] = [ - "request": request.toMap(), - "error": error.toMap() - ] - channel?.invokeMethod("onReceivedError", arguments: arguments) - } - - public func onReceivedHttpError(request: WebResourceRequest, errorResponse: WebResourceResponse) { - let arguments: [String: Any?] = [ - "request": request.toMap(), - "errorResponse": errorResponse.toMap() - ] - channel?.invokeMethod("onReceivedHttpError", arguments: arguments) - } - - public func onProgressChanged(progress: Int) { - let arguments: [String: Any] = ["progress": progress] - channel?.invokeMethod("onProgressChanged", arguments: arguments) - } - - public func onFindResultReceived(activeMatchOrdinal: Int, numberOfMatches: Int, isDoneCounting: Bool) { - let arguments: [String : Any] = [ - "activeMatchOrdinal": activeMatchOrdinal, - "numberOfMatches": numberOfMatches, - "isDoneCounting": isDoneCounting - ] - channel?.invokeMethod("onFindResultReceived", arguments: arguments) - } - - public func onScrollChanged(x: Int, y: Int) { - let arguments: [String: Any] = ["x": x, "y": y] - channel?.invokeMethod("onScrollChanged", arguments: arguments) - } - - public func onZoomScaleChanged(newScale: Float, oldScale: Float) { - let arguments: [String: Any] = ["newScale": newScale, "oldScale": oldScale] - channel?.invokeMethod("onZoomScaleChanged", arguments: arguments) - } - - public func onOverScrolled(x: Int, y: Int, clampedX: Bool, clampedY: Bool) { - let arguments: [String: Any] = ["x": x, "y": y, "clampedX": clampedX, "clampedY": clampedY] - channel?.invokeMethod("onOverScrolled", arguments: arguments) - } - - public func onDownloadStartRequest(request: DownloadStartRequest) { - channel?.invokeMethod("onDownloadStartRequest", arguments: request.toMap()) - } - - public func onLoadResourceCustomScheme(url: String, result: FlutterResult?) { - let arguments: [String: Any] = ["url": url] - channel?.invokeMethod("onLoadResourceCustomScheme", arguments: arguments, result: result) - } - - public func shouldOverrideUrlLoading(navigationAction: WKNavigationAction, result: FlutterResult?) { - channel?.invokeMethod("shouldOverrideUrlLoading", arguments: navigationAction.toMap(), result: result) - } - - public func onNavigationResponse(navigationResponse: WKNavigationResponse, result: FlutterResult?) { - channel?.invokeMethod("onNavigationResponse", arguments: navigationResponse.toMap(), result: result) - } - - public func onPermissionRequest(request: PermissionRequest, result: FlutterResult?) { - channel?.invokeMethod("onPermissionRequest", arguments: request.toMap(), result: result) - } - - public func onReceivedHttpAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) { - channel?.invokeMethod("onReceivedHttpAuthRequest", - arguments: HttpAuthenticationChallenge(fromChallenge: challenge).toMap(), result: result) - } - - public func onReceivedServerTrustAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) { - if let scheme = challenge.protectionSpace.protocol, scheme == "https", - let sslCertificate = challenge.protectionSpace.sslCertificate { - InAppWebView.sslCertificatesMap[challenge.protectionSpace.host] = sslCertificate - } - channel?.invokeMethod("onReceivedServerTrustAuthRequest", - arguments: ServerTrustChallenge(fromChallenge: challenge).toMap(), result: result) - } - - public func onReceivedClientCertRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) { - channel?.invokeMethod("onReceivedClientCertRequest", - arguments: ClientCertChallenge(fromChallenge: challenge).toMap(), result: result) - } - - public func shouldAllowDeprecatedTLS(challenge: URLAuthenticationChallenge, result: FlutterResult?) { - channel?.invokeMethod("shouldAllowDeprecatedTLS", arguments: challenge.toMap(), result: result) - } - - public func onJsAlert(frame: WKFrameInfo, message: String, result: FlutterResult?) { - let arguments: [String: Any?] = [ - "url": frame.request.url?.absoluteString, - "message": message, - "isMainFrame": frame.isMainFrame - ] - channel?.invokeMethod("onJsAlert", arguments: arguments, result: result) - } - - public func onJsConfirm(frame: WKFrameInfo, message: String, result: FlutterResult?) { - let arguments: [String: Any?] = [ - "url": frame.request.url?.absoluteString, - "message": message, - "isMainFrame": frame.isMainFrame - ] - channel?.invokeMethod("onJsConfirm", arguments: arguments, result: result) - } - - public func onJsPrompt(frame: WKFrameInfo, message: String, defaultValue: String?, result: FlutterResult?) { - let arguments: [String: Any?] = [ - "url": frame.request.url?.absoluteString, - "message": message, - "defaultValue": defaultValue as Any, - "isMainFrame": frame.isMainFrame - ] - channel?.invokeMethod("onJsPrompt", arguments: arguments, result: result) - } - - public func onConsoleMessage(message: String, messageLevel: Int) { - let arguments: [String: Any] = ["message": message, "messageLevel": messageLevel] - channel?.invokeMethod("onConsoleMessage", arguments: arguments) - } - - public func onUpdateVisitedHistory(url: String?) { - let arguments: [String: Any?] = [ - "url": url, - "androidIsReload": nil - ] - channel?.invokeMethod("onUpdateVisitedHistory", arguments: arguments) - } - - public func onTitleChanged(title: String?) { - let arguments: [String: Any?] = [ - "title": title - ] - channel?.invokeMethod("onTitleChanged", arguments: arguments) - } - - public func onLongPressHitTestResult(hitTestResult: HitTestResult) { - channel?.invokeMethod("onLongPressHitTestResult", arguments: hitTestResult.toMap()) - } - - public func onCallJsHandler(handlerName: String, _callHandlerID: Int64, args: String) { - let arguments: [String: Any] = ["handlerName": handlerName, "args": args] - - // invoke flutter javascript handler and send back flutter data as a JSON Object to javascript - channel?.invokeMethod("onCallJsHandler", arguments: arguments, result: {(result) -> Void in - if result is FlutterError { - print((result as! FlutterError).message ?? "") - } - else if (result as? NSObject) == FlutterMethodNotImplemented {} - else { - var json = "null" - if let r = result { - json = r as! String - } - - self.evaluateJavaScript(""" -if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { - window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)](\(json)); - delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; -} -""", completionHandler: nil) - } - }) - } - - public func onWebContentProcessDidTerminate() { - channel?.invokeMethod("onWebContentProcessDidTerminate", arguments: []) - } - - public func onPageCommitVisible(url: String?) { - let arguments: [String: Any?] = [ - "url": url - ] - channel?.invokeMethod("onPageCommitVisible", arguments: arguments) - } - - public func onDidReceiveServerRedirectForProvisionalNavigation() { - channel?.invokeMethod("onDidReceiveServerRedirectForProvisionalNavigation", arguments: []) - } // https://stackoverflow.com/a/42840541/4637638 public func isVideoPlayerWindow(_ notificationObject: AnyObject?) -> Bool { @@ -2765,38 +2522,18 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { @objc func onEnterFullscreen(_ notification: Notification) { if (isVideoPlayerWindow(notification.object as AnyObject?)) { - onEnterFullscreen() + channelDelegate?.onEnterFullscreen() inFullscreen = true } } - public func onEnterFullscreen() { - channel?.invokeMethod("onEnterFullscreen", arguments: []) - } - @objc func onExitFullscreen(_ notification: Notification) { if (isVideoPlayerWindow(notification.object as AnyObject?)) { - onExitFullscreen() + channelDelegate?.onExitFullscreen() inFullscreen = false } } - public func onExitFullscreen() { - channel?.invokeMethod("onExitFullscreen", arguments: []) - } - - @available(iOS 15.0, *) - public func onCameraCaptureStateChanged(oldState: WKMediaCaptureState?, newState: WKMediaCaptureState?) { - let arguments = ["oldState": oldState?.rawValue, "newState": newState?.rawValue] - channel?.invokeMethod("onCameraCaptureStateChanged", arguments: arguments) - } - - @available(iOS 15.0, *) - public func onMicrophoneCaptureStateChanged(oldState: WKMediaCaptureState?, newState: WKMediaCaptureState?) { - let arguments = ["oldState": oldState?.rawValue, "newState": newState?.rawValue] - channel?.invokeMethod("onMicrophoneCaptureStateChanged", arguments: arguments) - } - // public func onContextMenuConfigurationForElement(linkURL: String?, result: FlutterResult?) { // let arguments: [String: Any?] = ["linkURL": linkURL] // channel?.invokeMethod("onContextMenuConfigurationForElement", arguments: arguments, result: result) @@ -2850,7 +2587,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { webView = webViewTransport.webView } - webView.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) + webView.channelDelegate?.onConsoleMessage(message: consoleMessage, messageLevel: messageLevel) } else if message.name == "callHandler" { let body = message.body as! [String: Any?] let handlerName = body["handlerName"] as! String @@ -2865,7 +2602,28 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { webView = webViewTransport.webView } - webView.onCallJsHandler(handlerName: handlerName, _callHandlerID: _callHandlerID, args: args) + + let callback = WebViewChannelDelegate.CallJsHandlerCallback() + callback.defaultBehaviour = { (response: Any?) in + var json = "null" + if let r = response as? String { + json = r + } + + self.evaluateJavaScript(""" +if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { + window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)](\(json)); + delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)]; +} +""", completionHandler: nil) + } + callback.error = { (code: String, message: String?, details: Any?) in + print(code + ", " + (message ?? "")) + } + + if let channelDelegate = webView.channelDelegate { + channelDelegate.onCallJsHandler(handlerName: handlerName, args: args, callback: callback) + } } else if message.name == "onFindResultReceived" { let body = message.body as! [String: Any?] let findResult = body["findResult"] as! [String: Any] @@ -2878,7 +2636,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { webView = webViewTransport.webView } - webView.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) + webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting) } else if message.name == "onCallAsyncJavaScriptResultBelowIOS14Received" { let body = message.body as! [String: Any?] let resultUuid = body["resultUuid"] as! String @@ -2895,7 +2653,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { let index = body["index"] as! Int64 let webMessage = body["message"] as? String if let webMessageChannel = webMessageChannels[webMessageChannelId] { - webMessageChannel.onMessage(index: index, message: webMessage) + webMessageChannel.channelDelegate?.onMessage(index: index, message: webMessage) } } else if message.name == "onWebMessageListenerPostMessageReceived" { let body = message.body as! [String: Any?] @@ -2926,7 +2684,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { if let scheme = scheme, !scheme.isEmpty, let host = host, !host.isEmpty { sourceOrigin = URL(string: "\(scheme)://\(host)\(port != nil && port != 0 ? ":" + String(port!) : "")") } - webMessageListener.onPostMessage(message: messageData, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) + webMessageListener.channelDelegate?.onPostMessage(message: messageData, sourceOrigin: sourceOrigin, isMainFrame: isMainFrame) } } } @@ -3166,8 +2924,14 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { webMessageChannels.removeAll() } + // https://stackoverflow.com/a/58001395/4637638 + public override var inputAccessoryView: UIView? { + return settings?.disableInputAccessoryView ?? false ? nil : super.inputAccessoryView + } + public func dispose() { - channel = nil + channelDelegate?.dispose() + channelDelegate = nil removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) removeObserver(self, forKeyPath: #keyPath(WKWebView.url)) removeObserver(self, forKeyPath: #keyPath(WKWebView.title)) @@ -3226,9 +2990,4 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { deinit { debugPrint("InAppWebView - dealloc") } - - // https://stackoverflow.com/a/58001395/4637638 - public override var inputAccessoryView: UIView? { - return settings?.disableInputAccessoryView ?? false ? nil : super.inputAccessoryView - } } diff --git a/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift b/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift new file mode 100644 index 00000000..b249382e --- /dev/null +++ b/ios/Classes/InAppWebView/WebMessage/WebMessageChannel.swift @@ -0,0 +1,74 @@ +// +// WebMessageChannel.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 10/03/21. +// + +import Foundation + +public class WebMessageChannel : FlutterMethodCallDelegate { + static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" + var id: String + var channelDelegate: WebMessageChannelChannelDelegate? + weak var webView: InAppWebView? + var ports: [WebMessagePort] = [] + + public init(id: String) { + self.id = id + super.init() + let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id, + binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) + self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel) + self.ports = [ + WebMessagePort(name: "port1", webMessageChannel: self), + WebMessagePort(name: "port2", webMessageChannel: self) + ] + } + + public func initJsInstance(webView: InAppWebView, completionHandler: ((WebMessageChannel) -> Void)? = nil) { + self.webView = webView + if let webView = self.webView { + webView.evaluateJavascript(source: """ + (function() { + \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel(); + })(); + """) { (_) in + completionHandler?(self) + } + } else { + completionHandler?(self) + } + } + + public func toMap() -> [String:Any?] { + return [ + "id": id + ] + } + + public func dispose() { + channelDelegate?.dispose() + channelDelegate = nil + for port in ports { + port.dispose() + } + ports.removeAll() + webView?.evaluateJavascript(source: """ + (function() { + var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + if (webMessageChannel != null) { + webMessageChannel.port1.close(); + webMessageChannel.port2.close(); + delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; + } + })(); + """) + webView = nil + } + + deinit { + debugPrint("WebMessageChannel - dealloc") + dispose() + } +} diff --git a/ios/Classes/Types/WebMessageChannel.swift b/ios/Classes/InAppWebView/WebMessage/WebMessageChannelChannelDelegate.swift similarity index 57% rename from ios/Classes/Types/WebMessageChannel.swift rename to ios/Classes/InAppWebView/WebMessage/WebMessageChannelChannelDelegate.swift index e34ef2fb..aeefe888 100644 --- a/ios/Classes/Types/WebMessageChannel.swift +++ b/ios/Classes/InAppWebView/WebMessage/WebMessageChannelChannelDelegate.swift @@ -1,43 +1,18 @@ // -// WebMessageChannel.swift +// WebMessageChannelChannelDelegate.swift // flutter_inappwebview // -// Created by Lorenzo Pichilli on 10/03/21. +// Created by Lorenzo Pichilli on 07/05/22. // import Foundation -public class WebMessageChannel : FlutterMethodCallDelegate { - var id: String - var channel: FlutterMethodChannel? - var webView: InAppWebView? - var ports: [WebMessagePort] = [] +public class WebMessageChannelChannelDelegate : ChannelDelegate { + private weak var webMessageChannel: WebMessageChannel? - public init(id: String) { - self.id = id - super.init() - self.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_web_message_channel_" + id, - binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) - self.channel?.setMethodCallHandler(self.handle) - self.ports = [ - WebMessagePort(name: "port1", webMessageChannel: self), - WebMessagePort(name: "port2", webMessageChannel: self) - ] - } - - public func initJsInstance(webView: InAppWebView, completionHandler: ((WebMessageChannel) -> Void)? = nil) { - self.webView = webView - if let webView = self.webView { - webView.evaluateJavascript(source: """ - (function() { - \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel(); - })(); - """) { (_) in - completionHandler?(self) - } - } else { - completionHandler?(self) - } + public init(webMessageChannel: WebMessageChannel, channel: FlutterMethodChannel) { + super.init(channel: channel) + self.webMessageChannel = webMessageChannel } public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -45,7 +20,7 @@ public class WebMessageChannel : FlutterMethodCallDelegate { switch call.method { case "setWebMessageCallback": - if let _ = webView, ports.count > 0 { + if let _ = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 { let index = arguments!["index"] as! Int let port = ports[index] do { @@ -57,11 +32,11 @@ public class WebMessageChannel : FlutterMethodCallDelegate { } } else { - result(true) + result(true) } break case "postMessage": - if let webView = webView, ports.count > 0 { + if let webView = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 { let index = arguments!["index"] as! Int let port = ports[index] let message = arguments!["message"] as! [String: Any?] @@ -90,7 +65,7 @@ public class WebMessageChannel : FlutterMethodCallDelegate { } break case "close": - if let _ = webView, ports.count > 0 { + if let _ = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 { let index = arguments!["index"] as! Int let port = ports[index] do { @@ -118,34 +93,12 @@ public class WebMessageChannel : FlutterMethodCallDelegate { channel?.invokeMethod("onMessage", arguments: arguments) } - public func toMap () -> [String:Any?] { - return [ - "id": id - ] - } - - public func dispose() { - channel?.setMethodCallHandler(nil) - channel = nil - for port in ports { - port.dispose() - } - ports.removeAll() - webView?.evaluateJavascript(source: """ - (function() { - var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; - if (webMessageChannel != null) { - webMessageChannel.port1.close(); - webMessageChannel.port2.close(); - delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"]; - } - })(); - """) - webView = nil + public override func dispose() { + super.dispose() + webMessageChannel = nil } deinit { - debugPrint("WebMessageChannel - dealloc") dispose() } } diff --git a/ios/Classes/Types/WebMessageListener.swift b/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift similarity index 79% rename from ios/Classes/Types/WebMessageListener.swift rename to ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift index 8c0d32f5..0250013d 100644 --- a/ios/Classes/Types/WebMessageListener.swift +++ b/ios/Classes/InAppWebView/WebMessage/WebMessageListener.swift @@ -9,19 +9,19 @@ import Foundation import WebKit public class WebMessageListener : FlutterMethodCallDelegate { - + static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_listener_" var jsObjectName: String var allowedOriginRules: Set - var channel: FlutterMethodChannel? - var webView: InAppWebView? + var channelDelegate: WebMessageListenerChannelDelegate? + weak var webView: InAppWebView? public init(jsObjectName: String, allowedOriginRules: Set) { self.jsObjectName = jsObjectName self.allowedOriginRules = allowedOriginRules super.init() - self.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_web_message_listener_" + self.jsObjectName, + let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.jsObjectName, binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) - self.channel?.setMethodCallHandler(self.handle) + self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel) } public func assertOriginRulesValid() throws { @@ -122,41 +122,6 @@ public class WebMessageListener : FlutterMethodCallDelegate { ) } - public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - let arguments = call.arguments as? NSDictionary - - switch call.method { - case "postMessage": - if let webView = webView { - let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'") - let messageEscaped = (arguments!["message"] as! String).replacingOccurrences(of: "\'", with: "\\'") - let source = """ - (function() { - var webMessageListener = window['\(jsObjectNameEscaped)']; - if (webMessageListener != null) { - var event = {data: '\(messageEscaped)'}; - if (webMessageListener.onmessage != null) { - webMessageListener.onmessage(event); - } - for (var listener of webMessageListener.listeners) { - listener(event); - } - } - })(); - """ - webView.evaluateJavascript(source: source) { (_) in - result(true) - } - } else { - result(true) - } - break - default: - result(FlutterMethodNotImplemented) - break - } - } - public func isOriginAllowed(scheme: String?, host: String?, port: Int?) -> Bool { for allowedOriginRule in allowedOriginRules { if allowedOriginRule == "*" { @@ -204,19 +169,10 @@ public class WebMessageListener : FlutterMethodCallDelegate { } return false } - - public func onPostMessage(message: String?, sourceOrigin: URL?, isMainFrame: Bool) { - let arguments: [String:Any?] = [ - "message": message, - "sourceOrigin": sourceOrigin?.absoluteString, - "isMainFrame": isMainFrame - ] - channel?.invokeMethod("onPostMessage", arguments: arguments) - } public func dispose() { - channel?.setMethodCallHandler(nil) - channel = nil + channelDelegate?.dispose() + channelDelegate = nil webView = nil } diff --git a/ios/Classes/InAppWebView/WebMessage/WebMessageListenerChannelDelegate.swift b/ios/Classes/InAppWebView/WebMessage/WebMessageListenerChannelDelegate.swift new file mode 100644 index 00000000..7872acb7 --- /dev/null +++ b/ios/Classes/InAppWebView/WebMessage/WebMessageListenerChannelDelegate.swift @@ -0,0 +1,71 @@ +// +// WebMessageListenerChannelDelegate.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation + +public class WebMessageListenerChannelDelegate : ChannelDelegate { + private weak var webMessageListener: WebMessageListener? + + public init(webMessageListener: WebMessageListener, channel: FlutterMethodChannel) { + super.init(channel: channel) + self.webMessageListener = webMessageListener + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? NSDictionary + + switch call.method { + case "postMessage": + if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName { + let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'") + let messageEscaped = (arguments!["message"] as! String).replacingOccurrences(of: "\'", with: "\\'") + let source = """ + (function() { + var webMessageListener = window['\(jsObjectNameEscaped)']; + if (webMessageListener != null) { + var event = {data: '\(messageEscaped)'}; + if (webMessageListener.onmessage != null) { + webMessageListener.onmessage(event); + } + for (var listener of webMessageListener.listeners) { + listener(event); + } + } + })(); + """ + webView.evaluateJavascript(source: source) { (_) in + result(true) + } + } else { + result(true) + } + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public func onPostMessage(message: String?, sourceOrigin: URL?, isMainFrame: Bool) { + let arguments: [String:Any?] = [ + "message": message, + "sourceOrigin": sourceOrigin?.absoluteString, + "isMainFrame": isMainFrame + ] + channel?.invokeMethod("onPostMessage", arguments: arguments) + } + + public override func dispose() { + super.dispose() + webMessageListener = nil + } + + deinit { + dispose() + } +} + diff --git a/ios/Classes/InAppWebView/WebViewChannelDelegate.swift b/ios/Classes/InAppWebView/WebViewChannelDelegate.swift new file mode 100644 index 00000000..e2975986 --- /dev/null +++ b/ios/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -0,0 +1,1066 @@ +// +// WebViewChannelDelegate.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 06/05/22. +// + +import Foundation +import WebKit + +public class WebViewChannelDelegate : ChannelDelegate { + private weak var webView: InAppWebView? + + public init(webView: InAppWebView, channel: FlutterMethodChannel) { + super.init(channel: channel) + self.webView = webView + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? NSDictionary + + switch call.method { + case "getUrl": + result(webView?.url?.absoluteString) + break + case "getTitle": + result(webView?.title) + break + case "getProgress": + result( (webView != nil) ? Int(webView!.estimatedProgress * 100) : nil ) + break + case "loadUrl": + let urlRequest = arguments!["urlRequest"] as! [String:Any?] + let allowingReadAccessTo = arguments!["allowingReadAccessTo"] as? String + var allowingReadAccessToURL: URL? = nil + if let allowingReadAccessTo = allowingReadAccessTo { + allowingReadAccessToURL = URL(string: allowingReadAccessTo) + } + webView?.loadUrl(urlRequest: URLRequest.init(fromPluginMap: urlRequest), allowingReadAccessTo: allowingReadAccessToURL) + result(true) + break + case "postUrl": + if let webView = webView { + let url = arguments!["url"] as! String + let postData = arguments!["postData"] as! FlutterStandardTypedData + webView.postUrl(url: URL(string: url)!, postData: postData.data) + } + result(true) + break + case "loadData": + let data = arguments!["data"] as! String + let mimeType = arguments!["mimeType"] as! String + let encoding = arguments!["encoding"] as! String + let baseUrl = URL(string: arguments!["baseUrl"] as! String)! + let allowingReadAccessTo = arguments!["allowingReadAccessTo"] as? String + var allowingReadAccessToURL: URL? = nil + if let allowingReadAccessTo = allowingReadAccessTo { + allowingReadAccessToURL = URL(string: allowingReadAccessTo) + } + webView?.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL) + result(true) + break + case "loadFile": + let assetFilePath = arguments!["assetFilePath"] as! String + + do { + try webView?.loadFile(assetFilePath: assetFilePath) + } + catch let error as NSError { + result(FlutterError(code: "WebViewChannelDelegate", message: error.domain, details: nil)) + return + } + result(true) + break + case "evaluateJavascript": + if let webView = webView { + let source = arguments!["source"] as! String + let contentWorldMap = arguments!["contentWorld"] as? [String:Any?] + if #available(iOS 14.0, *), let contentWorldMap = contentWorldMap { + let contentWorld = WKContentWorld.fromMap(map: contentWorldMap, windowId: webView.windowId)! + webView.evaluateJavascript(source: source, contentWorld: contentWorld) { (value) in + result(value) + } + } else { + webView.evaluateJavascript(source: source) { (value) in + result(value) + } + } + } + else { + result(nil) + } + break + case "injectJavascriptFileFromUrl": + let urlFile = arguments!["urlFile"] as! String + let scriptHtmlTagAttributes = arguments!["scriptHtmlTagAttributes"] as? [String:Any?] + webView?.injectJavascriptFileFromUrl(urlFile: urlFile, scriptHtmlTagAttributes: scriptHtmlTagAttributes) + result(true) + break + case "injectCSSCode": + let source = arguments!["source"] as! String + webView?.injectCSSCode(source: source) + result(true) + break + case "injectCSSFileFromUrl": + let urlFile = arguments!["urlFile"] as! String + let cssLinkHtmlTagAttributes = arguments!["cssLinkHtmlTagAttributes"] as? [String:Any?] + webView?.injectCSSFileFromUrl(urlFile: urlFile, cssLinkHtmlTagAttributes: cssLinkHtmlTagAttributes) + result(true) + break + case "reload": + webView?.reload() + result(true) + break + case "goBack": + webView?.goBack() + result(true) + break + case "canGoBack": + result(webView?.canGoBack ?? false) + break + case "goForward": + webView?.goForward() + result(true) + break + case "canGoForward": + result(webView?.canGoForward ?? false) + break + case "goBackOrForward": + let steps = arguments!["steps"] as! Int + webView?.goBackOrForward(steps: steps) + result(true) + break + case "canGoBackOrForward": + let steps = arguments!["steps"] as! Int + result(webView?.canGoBackOrForward(steps: steps) ?? false) + break + case "stopLoading": + webView?.stopLoading() + result(true) + break + case "isLoading": + result(webView?.isLoading ?? false) + break + case "takeScreenshot": + if let webView = webView, #available(iOS 11.0, *) { + let screenshotConfiguration = arguments!["screenshotConfiguration"] as? [String: Any?] + webView.takeScreenshot(with: screenshotConfiguration, completionHandler: { (screenshot) -> Void in + result(screenshot) + }) + } + else { + result(nil) + } + break + case "setSettings": + if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { + let inAppBrowserSettings = InAppBrowserSettings() + let inAppBrowserSettingsMap = arguments!["settings"] as! [String: Any] + let _ = inAppBrowserSettings.parse(settings: inAppBrowserSettingsMap) + iabController.setSettings(newSettings: inAppBrowserSettings, newSettingsMap: inAppBrowserSettingsMap) + } else { + let inAppWebViewSettings = InAppWebViewSettings() + let inAppWebViewSettingsMap = arguments!["settings"] as! [String: Any] + let _ = inAppWebViewSettings.parse(settings: inAppWebViewSettingsMap) + webView?.setSettings(newSettings: inAppWebViewSettings, newSettingsMap: inAppWebViewSettingsMap) + } + result(true) + break + case "getSettings": + if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { + result(iabController.getSettings()) + } else { + result(webView?.getSettings()) + } + break + case "close": + if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { + iabController.close { + result(true) + } + } else { + result(FlutterMethodNotImplemented) + } + break + case "show": + if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { + iabController.show { + result(true) + } + } else { + result(FlutterMethodNotImplemented) + } + break + case "hide": + if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { + iabController.hide { + result(true) + } + } else { + result(FlutterMethodNotImplemented) + } + break + case "getCopyBackForwardList": + result(webView?.getCopyBackForwardList()) + break + case "findAllAsync": + if let webView = webView { + let find = arguments!["find"] as! String + webView.findAllAsync(find: find, completionHandler: {(value, error) in + if error != nil { + result(FlutterError(code: "WebViewChannelDelegate", message: error?.localizedDescription, details: nil)) + return + } + result(true) + }) + } else { + result(false) + } + break + case "findNext": + if let webView = webView { + let forward = arguments!["forward"] as! Bool + webView.findNext(forward: forward, completionHandler: {(value, error) in + if error != nil { + result(FlutterError(code: "WebViewChannelDelegate", message: error?.localizedDescription, details: nil)) + return + } + result(true) + }) + } else { + result(false) + } + break + case "clearMatches": + if let webView = webView { + webView.clearMatches(completionHandler: {(value, error) in + if error != nil { + result(FlutterError(code: "WebViewChannelDelegate", message: error?.localizedDescription, details: nil)) + return + } + result(true) + }) + } else { + result(false) + } + break + case "clearCache": + webView?.clearCache() + result(true) + break + case "scrollTo": + let x = arguments!["x"] as! Int + let y = arguments!["y"] as! Int + let animated = arguments!["animated"] as! Bool + webView?.scrollTo(x: x, y: y, animated: animated) + result(true) + break + case "scrollBy": + let x = arguments!["x"] as! Int + let y = arguments!["y"] as! Int + let animated = arguments!["animated"] as! Bool + webView?.scrollBy(x: x, y: y, animated: animated) + result(true) + break + case "pauseTimers": + webView?.pauseTimers() + result(true) + break + case "resumeTimers": + webView?.resumeTimers() + result(true) + break + case "printCurrentPage": + if let webView = webView { + webView.printCurrentPage(printCompletionHandler: {(completed, error) in + if !completed, let err = error { + print(err.localizedDescription) + result(false) + return + } + result(true) + }) + } else { + result(false) + } + break + case "getContentHeight": + result(webView?.getContentHeight()) + break + case "zoomBy": + let zoomFactor = (arguments!["zoomFactor"] as! NSNumber).floatValue + let animated = arguments!["animated"] as! Bool + webView?.zoomBy(zoomFactor: zoomFactor, animated: animated) + result(true) + break + case "reloadFromOrigin": + webView?.reloadFromOrigin() + result(true) + break + case "getOriginalUrl": + result(webView?.getOriginalUrl()?.absoluteString) + break + case "getZoomScale": + result(webView?.getZoomScale()) + break + case "hasOnlySecureContent": + result(webView?.hasOnlySecureContent ?? false) + break + case "getSelectedText": + if let webView = webView { + webView.getSelectedText { (value, error) in + if let err = error { + print(err.localizedDescription) + result("") + return + } + result(value) + } + } + else { + result(nil) + } + break + case "getHitTestResult": + if let webView = webView { + webView.getHitTestResult { (hitTestResult) in + result(hitTestResult.toMap()) + } + } + else { + result(nil) + } + break + case "clearFocus": + webView?.clearFocus() + result(true) + break + case "setContextMenu": + if let webView = webView { + let contextMenu = arguments!["contextMenu"] as? [String: Any] + webView.contextMenu = contextMenu + result(true) + } else { + result(false) + } + break + case "requestFocusNodeHref": + if let webView = webView { + webView.requestFocusNodeHref { (value, error) in + if let err = error { + print(err.localizedDescription) + result(nil) + return + } + result(value) + } + } else { + result(nil) + } + break + case "requestImageRef": + if let webView = webView { + webView.requestImageRef { (value, error) in + if let err = error { + print(err.localizedDescription) + result(nil) + return + } + result(value) + } + } else { + result(nil) + } + break + case "getScrollX": + if let webView = webView { + result(Int(webView.scrollView.contentOffset.x)) + } else { + result(nil) + } + break + case "getScrollY": + if let webView = webView { + result(Int(webView.scrollView.contentOffset.y)) + } else { + result(nil) + } + break + case "getCertificate": + result(webView?.getCertificate()?.toMap()) + break + case "addUserScript": + if let webView = webView { + let userScriptMap = arguments!["userScript"] as! [String: Any?] + let userScript = UserScript.fromMap(map: userScriptMap, windowId: webView.windowId)! + webView.configuration.userContentController.addUserOnlyScript(userScript) + webView.configuration.userContentController.sync(scriptMessageHandler: webView) + } + result(true) + break + case "removeUserScript": + let index = arguments!["index"] as! Int + let userScriptMap = arguments!["userScript"] as! [String: Any?] + let userScript = UserScript.fromMap(map: userScriptMap, windowId: webView?.windowId)! + webView?.configuration.userContentController.removeUserOnlyScript(at: index, injectionTime: userScript.injectionTime) + result(true) + break + case "removeUserScriptsByGroupName": + let groupName = arguments!["groupName"] as! String + webView?.configuration.userContentController.removeUserOnlyScripts(with: groupName) + result(true) + break + case "removeAllUserScripts": + webView?.configuration.userContentController.removeAllUserOnlyScripts() + result(true) + break + case "callAsyncJavaScript": + if let webView = webView, #available(iOS 10.3, *) { + if #available(iOS 14.0, *) { + let functionBody = arguments!["functionBody"] as! String + let functionArguments = arguments!["arguments"] as! [String:Any] + var contentWorld = WKContentWorld.page + if let contentWorldMap = arguments!["contentWorld"] as? [String:Any?] { + contentWorld = WKContentWorld.fromMap(map: contentWorldMap, windowId: webView.windowId)! + } + webView.callAsyncJavaScript(functionBody: functionBody, arguments: functionArguments, contentWorld: contentWorld) { (value) in + result(value) + } + } else { + let functionBody = arguments!["functionBody"] as! String + let functionArguments = arguments!["arguments"] as! [String:Any] + webView.callAsyncJavaScript(functionBody: functionBody, arguments: functionArguments) { (value) in + result(value) + } + } + } + else { + result(nil) + } + break + case "createPdf": + if let webView = webView, #available(iOS 14.0, *) { + let configuration = arguments!["pdfConfiguration"] as? [String: Any?] + webView.createPdf(configuration: configuration, completionHandler: { (pdf) -> Void in + result(pdf) + }) + } + else { + result(nil) + } + break + case "createWebArchiveData": + if let webView = webView, #available(iOS 14.0, *) { + webView.createWebArchiveData(dataCompletionHandler: { (webArchiveData) -> Void in + result(webArchiveData) + }) + } + else { + result(nil) + } + break + case "saveWebArchive": + if let webView = webView, #available(iOS 14.0, *) { + let filePath = arguments!["filePath"] as! String + let autoname = arguments!["autoname"] as! Bool + webView.saveWebArchive(filePath: filePath, autoname: autoname, completionHandler: { (path) -> Void in + result(path) + }) + } + else { + result(nil) + } + break + case "isSecureContext": + if let webView = webView { + webView.isSecureContext(completionHandler: { (isSecureContext) in + result(isSecureContext) + }) + } + else { + result(false) + } + break + case "createWebMessageChannel": + if let webView = webView { + let _ = webView.createWebMessageChannel { (webMessageChannel) in + result(webMessageChannel.toMap()) + } + } else { + result(nil) + } + break + case "postWebMessage": + if let webView = webView { + let message = arguments!["message"] as! [String: Any?] + let targetOrigin = arguments!["targetOrigin"] as! String + + var ports: [WebMessagePort] = [] + let portsMap = message["ports"] as? [[String: Any?]] + if let portsMap = portsMap { + for portMap in portsMap { + let webMessageChannelId = portMap["webMessageChannelId"] as! String + let index = portMap["index"] as! Int + if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] { + ports.append(webMessageChannel.ports[index]) + } + } + } + let webMessage = WebMessage(data: message["data"] as? String, ports: ports) + do { + try webView.postWebMessage(message: webMessage, targetOrigin: targetOrigin) { (_) in + result(true) + } + } catch let error as NSError { + result(FlutterError(code: "WebViewChannelDelegate", message: error.domain, details: nil)) + } + } else { + result(false) + } + break + case "addWebMessageListener": + if let webView = webView { + let webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?] + let webMessageListener = WebMessageListener.fromMap(map: webMessageListenerMap)! + do { + try webView.addWebMessageListener(webMessageListener: webMessageListener) + result(false) + } catch let error as NSError { + result(FlutterError(code: "WebViewChannelDelegate", message: error.domain, details: nil)) + } + } else { + result(false) + } + break + case "canScrollVertically": + if let webView = webView { + result(webView.canScrollVertically()) + } else { + result(false) + } + break + case "canScrollHorizontally": + if let webView = webView { + result(webView.canScrollHorizontally()) + } else { + result(false) + } + break + case "pauseAllMediaPlayback": + if let webView = webView, #available(iOS 15.0, *) { + webView.pauseAllMediaPlayback(completionHandler: { () -> Void in + result(true) + }) + } else { + result(false) + } + break + case "setAllMediaPlaybackSuspended": + if let webView = webView, #available(iOS 15.0, *) { + let suspended = arguments!["suspended"] as! Bool + webView.setAllMediaPlaybackSuspended(suspended, completionHandler: { () -> Void in + result(true) + }) + } else { + result(false) + } + break + case "closeAllMediaPresentations": + if let webView = self.webView, #available(iOS 14.5, *) { + // closeAllMediaPresentations with completionHandler v15.0 makes the app crash + // with error EXC_BAD_ACCESS, so use closeAllMediaPresentations v14.5 + webView.closeAllMediaPresentations() + result(true) + } else { + result(false) + } + break + case "requestMediaPlaybackState": + if let webView = webView, #available(iOS 15.0, *) { + webView.requestMediaPlaybackState(completionHandler: { (state) -> Void in + result(state.rawValue) + }) + } else { + result(nil) + } + break + case "getMetaThemeColor": + if let webView = webView, #available(iOS 15.0, *) { + result(webView.themeColor?.hexString) + } else { + result(nil) + } + break + case "isInFullscreen": + // if let webView = webView, #available(iOS 15.0, *) { + // result(webView.fullscreenState == .inFullscreen) + // } + if let webView = webView { + result(webView.inFullscreen) + } + else { + result(false) + } + break + case "getCameraCaptureState": + if let webView = webView, #available(iOS 15.0, *) { + result(webView.cameraCaptureState.rawValue) + } else { + result(nil) + } + break + case "setCameraCaptureState": + if let webView = webView, #available(iOS 15.0, *) { + let state = WKMediaCaptureState.init(rawValue: arguments!["state"] as! Int) ?? WKMediaCaptureState.none + webView.setCameraCaptureState(state) { + result(true) + } + } else { + result(false) + } + break + case "getMicrophoneCaptureState": + if let webView = webView, #available(iOS 15.0, *) { + result(webView.microphoneCaptureState.rawValue) + } else { + result(nil) + } + break + case "setMicrophoneCaptureState": + if let webView = webView, #available(iOS 15.0, *) { + let state = WKMediaCaptureState.init(rawValue: arguments!["state"] as! Int) ?? WKMediaCaptureState.none + webView.setMicrophoneCaptureState(state) { + result(true) + } + } else { + result(false) + } + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public func onFindResultReceived(activeMatchOrdinal: Int, numberOfMatches: Int, isDoneCounting: Bool) { + let arguments: [String : Any?] = [ + "activeMatchOrdinal": activeMatchOrdinal, + "numberOfMatches": numberOfMatches, + "isDoneCounting": isDoneCounting + ] + channel?.invokeMethod("onFindResultReceived", arguments: arguments) + } + + public func onLongPressHitTestResult(hitTestResult: HitTestResult) { + channel?.invokeMethod("onLongPressHitTestResult", arguments: hitTestResult.toMap()) + } + + public func onScrollChanged(x: Int, y: Int) { + let arguments: [String: Any?] = ["x": x, "y": y] + channel?.invokeMethod("onScrollChanged", arguments: arguments) + } + + public func onDownloadStartRequest(request: DownloadStartRequest) { + channel?.invokeMethod("onDownloadStartRequest", arguments: request.toMap()) + } + + public func onCreateContextMenu(hitTestResult: HitTestResult) { + channel?.invokeMethod("onCreateContextMenu", arguments: hitTestResult.toMap()) + } + + public func onOverScrolled(x: Int, y: Int, clampedX: Bool, clampedY: Bool) { + let arguments: [String: Any?] = ["x": x, "y": y, "clampedX": clampedX, "clampedY": clampedY] + channel?.invokeMethod("onOverScrolled", arguments: arguments) + } + + public func onContextMenuActionItemClicked(id: Any, title: String) { + let arguments: [String: Any?] = [ + "id": id, + "iosId": id is Int64 ? String(id as! Int64) : id as! String, + "androidId": nil, + "title": title + ] + channel?.invokeMethod("onContextMenuActionItemClicked", arguments: arguments) + } + + public func onHideContextMenu() { + let arguments: [String: Any?] = [:] + channel?.invokeMethod("onHideContextMenu", arguments: arguments) + } + + public func onEnterFullscreen() { + let arguments: [String: Any?] = [:] + channel?.invokeMethod("onEnterFullscreen", arguments: arguments) + } + + public func onExitFullscreen() { + let arguments: [String: Any?] = [:] + channel?.invokeMethod("onExitFullscreen", arguments: arguments) + } + + public class JsAlertCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return JsAlertResponse.fromMap(map: obj as? [String:Any?]) + } + } + } + + public func onJsAlert(url: URL?, message: String, isMainFrame: Bool, callback: JsAlertCallback) { + if channel == nil { + callback.defaultBehaviour(nil) + return + } + let arguments: [String: Any?] = [ + "url": url?.absoluteString, + "message": message, + "isMainFrame": isMainFrame + ] + channel?.invokeMethod("onJsAlert", arguments: arguments, callback: callback) + } + + public class JsConfirmCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return JsConfirmResponse.fromMap(map: obj as? [String:Any?]) + } + } + } + + public func onJsConfirm(url: URL?, message: String, isMainFrame: Bool, callback: JsConfirmCallback) { + if channel == nil { + callback.defaultBehaviour(nil) + return + } + let arguments: [String: Any?] = [ + "url": url?.absoluteString, + "message": message, + "isMainFrame": isMainFrame + ] + channel?.invokeMethod("onJsConfirm", arguments: arguments, callback: callback) + } + + public class JsPromptCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return JsPromptResponse.fromMap(map: obj as? [String:Any?]) + } + } + } + + public func onJsPrompt(url: URL?, message: String, defaultValue: String?, isMainFrame: Bool, callback: JsPromptCallback) { + if channel == nil { + callback.defaultBehaviour(nil) + return + } + let arguments: [String: Any?] = [ + "url": url?.absoluteString, + "message": message, + "defaultValue": defaultValue, + "isMainFrame": isMainFrame + ] + channel?.invokeMethod("onJsConfirm", arguments: arguments, callback: callback) + } + + public class CreateWindowCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return obj is Bool && obj as! Bool + } + } + } + + public func onCreateWindow(createWindowAction: CreateWindowAction, callback: CreateWindowCallback) { + if channel == nil { + callback.defaultBehaviour(nil) + return + } + channel?.invokeMethod("onCreateWindow", arguments: createWindowAction.toMap(), callback: callback) + } + + public func onCloseWindow() { + let arguments: [String: Any?] = [:] + channel?.invokeMethod("onCloseWindow", arguments: arguments) + } + + public func onConsoleMessage(message: String, messageLevel: Int) { + let arguments: [String: Any?] = [ + "message": message, + "messageLevel": messageLevel + ] + channel?.invokeMethod("onConsoleMessage", arguments: arguments) + } + + public func onProgressChanged(progress: Int) { + let arguments: [String: Any?] = [ + "progress": progress + ] + channel?.invokeMethod("onProgressChanged", arguments: arguments) + } + + public func onTitleChanged(title: String?) { + let arguments: [String: Any?] = [ + "title": title + ] + channel?.invokeMethod("onTitleChanged", arguments: arguments) + } + + public class PermissionRequestCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return PermissionResponse.fromMap(map: obj as? [String:Any?]) + } + } + } + + public func onPermissionRequest(request: PermissionRequest, callback: PermissionRequestCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + channel.invokeMethod("onPermissionRequest", arguments: request.toMap(), callback: callback); + } + + public class ShouldOverrideUrlLoadingCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + if let obj = obj as? [String: Any?], let action = obj["action"] as? Int { + return WKNavigationActionPolicy.init(rawValue: action) ?? WKNavigationActionPolicy.cancel + } + return WKNavigationActionPolicy.cancel + } + } + } + + public func shouldOverrideUrlLoading(navigationAction: WKNavigationAction, callback: ShouldOverrideUrlLoadingCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + channel.invokeMethod("shouldOverrideUrlLoading", arguments: navigationAction.toMap(), callback: callback); + } + + public func onLoadStart(url: String?) { + let arguments: [String: Any?] = ["url": url] + channel?.invokeMethod("onLoadStart", arguments: arguments) + } + + public func onLoadStop(url: String?) { + let arguments: [String: Any?] = ["url": url] + channel?.invokeMethod("onLoadStop", arguments: arguments) + } + + public func onUpdateVisitedHistory(url: String?, isReload: Bool?) { + let arguments: [String: Any?] = [ + "url": url, + "isReload": nil + ] + channel?.invokeMethod("onUpdateVisitedHistory", arguments: arguments) + } + + public func onReceivedError(request: WebResourceRequest, error: WebResourceError) { + let arguments: [String: Any?] = [ + "request": request.toMap(), + "error": error.toMap() + ] + channel?.invokeMethod("onReceivedError", arguments: arguments) + } + + public func onReceivedHttpError(request: WebResourceRequest, errorResponse: WebResourceResponse) { + let arguments: [String: Any?] = [ + "request": request.toMap(), + "errorResponse": errorResponse.toMap() + ] + channel?.invokeMethod("onReceivedHttpError", arguments: arguments) + } + + public class ReceivedHttpAuthRequestCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return HttpAuthResponse.fromMap(map: obj as? [String:Any?]) + } + } + } + + public func onReceivedHttpAuthRequest(challenge: HttpAuthenticationChallenge, callback: ReceivedHttpAuthRequestCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + channel.invokeMethod("onReceivedHttpAuthRequest", arguments: challenge.toMap(), callback: callback) + } + + public class ReceivedServerTrustAuthRequestCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return ServerTrustAuthResponse.fromMap(map: obj as? [String:Any?]) + } + } + } + + public func onReceivedServerTrustAuthRequest(challenge: ServerTrustChallenge, callback: ReceivedServerTrustAuthRequestCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + channel.invokeMethod("onReceivedServerTrustAuthRequest", arguments: challenge.toMap(), callback: callback); + } + + public class ReceivedClientCertRequestCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return ClientCertResponse.fromMap(map: obj as? [String:Any?]) + } + } + } + + public func onReceivedClientCertRequest(challenge: ClientCertChallenge, callback: ReceivedClientCertRequestCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + channel.invokeMethod("onReceivedClientCertRequest", arguments: challenge.toMap(), callback: callback); + } + + public func onZoomScaleChanged(newScale: Float, oldScale: Float) { + let arguments: [String: Any?] = [ + "newScale": newScale, + "oldScale": oldScale + ] + channel?.invokeMethod("onZoomScaleChanged", arguments: arguments) + } + + public func onPageCommitVisible(url: String?) { + let arguments: [String: Any?] = [ + "url": url + ] + channel?.invokeMethod("onPageCommitVisible", arguments: arguments) + } + + public class LoadResourceCustomSchemeCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return CustomSchemeResponse.fromMap(map: obj as? [String:Any?]) + } + } + } + + public func onLoadResourceCustomScheme(url: URL, callback: LoadResourceCustomSchemeCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + let arguments: [String: Any?] = ["url": url.absoluteString] + channel.invokeMethod("onLoadResourceCustomScheme", arguments: arguments, callback: callback) + } + + public class CallJsHandlerCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + return obj + } + } + } + + public func onCallJsHandler(handlerName: String, args: String, callback: CallJsHandlerCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + let arguments: [String: Any?] = [ + "handlerName": handlerName, + "args": args + ] + channel.invokeMethod("onCallJsHandler", arguments: arguments, callback: callback); + } + + public class NavigationResponseCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + if let obj = obj as? [String: Any?], let action = obj["action"] as? Int { + return WKNavigationResponsePolicy.init(rawValue: action) ?? WKNavigationResponsePolicy.cancel + } + return WKNavigationResponsePolicy.cancel + } + } + } + + public func onNavigationResponse(navigationResponse: WKNavigationResponse, callback: NavigationResponseCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + channel.invokeMethod("onNavigationResponse", arguments: navigationResponse.toMap(), callback: callback); + } + + public class ShouldAllowDeprecatedTLSCallback : BaseCallbackResult { + override init() { + super.init() + self.decodeResult = { (obj: Any?) in + if let obj = obj as? [String: Any?], let action = obj["action"] as? Int { + return action == 1 + } + return false + } + } + } + + public func shouldAllowDeprecatedTLS(challenge: URLAuthenticationChallenge, callback: ShouldAllowDeprecatedTLSCallback) { + guard let channel = channel else { + callback.defaultBehaviour(nil) + return + } + channel.invokeMethod("shouldAllowDeprecatedTLS", arguments: challenge.toMap(), callback: callback) + } + + public func onWebContentProcessDidTerminate() { + let arguments: [String: Any?] = [:] + channel?.invokeMethod("onWebContentProcessDidTerminate", arguments: arguments) + } + + public func onDidReceiveServerRedirectForProvisionalNavigation() { + let arguments: [String: Any?] = [:] + channel?.invokeMethod("onDidReceiveServerRedirectForProvisionalNavigation", arguments: arguments) + } + + @available(iOS 15.0, *) + public func onCameraCaptureStateChanged(oldState: WKMediaCaptureState?, newState: WKMediaCaptureState?) { + let arguments = [ + "oldState": oldState?.rawValue, + "newState": newState?.rawValue + ] + channel?.invokeMethod("onCameraCaptureStateChanged", arguments: arguments) + } + + @available(iOS 15.0, *) + public func onMicrophoneCaptureStateChanged(oldState: WKMediaCaptureState?, newState: WKMediaCaptureState?) { + let arguments = [ + "oldState": oldState?.rawValue, + "newState": newState?.rawValue + ] + channel?.invokeMethod("onMicrophoneCaptureStateChanged", arguments: arguments) + } + + public override func dispose() { + super.dispose() + webView = nil + } + + deinit { + debugPrint("WebViewChannelDelegate - dealloc") + dispose() + } +} diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift deleted file mode 100644 index c267e81d..00000000 --- a/ios/Classes/InAppWebViewMethodHandler.swift +++ /dev/null @@ -1,654 +0,0 @@ -// -// WebViewMethodHandler.swift -// flutter_inappwebview -// -// Created by Lorenzo Pichilli on 01/02/21. -// - -import Foundation -import WebKit - -public class InAppWebViewMethodHandler: FlutterMethodCallDelegate { - var webView: InAppWebView? - - init(webView: InAppWebView) { - super.init() - self.webView = webView - } - - public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - let arguments = call.arguments as? NSDictionary - - switch call.method { - case "getUrl": - result(webView?.url?.absoluteString) - break - case "getTitle": - result(webView?.title) - break - case "getProgress": - result( (webView != nil) ? Int(webView!.estimatedProgress * 100) : nil ) - break - case "loadUrl": - let urlRequest = arguments!["urlRequest"] as! [String:Any?] - let allowingReadAccessTo = arguments!["allowingReadAccessTo"] as? String - var allowingReadAccessToURL: URL? = nil - if let allowingReadAccessTo = allowingReadAccessTo { - allowingReadAccessToURL = URL(string: allowingReadAccessTo) - } - webView?.loadUrl(urlRequest: URLRequest.init(fromPluginMap: urlRequest), allowingReadAccessTo: allowingReadAccessToURL) - result(true) - break - case "postUrl": - if let webView = webView { - let url = arguments!["url"] as! String - let postData = arguments!["postData"] as! FlutterStandardTypedData - webView.postUrl(url: URL(string: url)!, postData: postData.data) - } - result(true) - break - case "loadData": - let data = arguments!["data"] as! String - let mimeType = arguments!["mimeType"] as! String - let encoding = arguments!["encoding"] as! String - let baseUrl = URL(string: arguments!["baseUrl"] as! String)! - let allowingReadAccessTo = arguments!["allowingReadAccessTo"] as? String - var allowingReadAccessToURL: URL? = nil - if let allowingReadAccessTo = allowingReadAccessTo { - allowingReadAccessToURL = URL(string: allowingReadAccessTo) - } - webView?.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL) - result(true) - break - case "loadFile": - let assetFilePath = arguments!["assetFilePath"] as! String - - do { - try webView?.loadFile(assetFilePath: assetFilePath) - } - catch let error as NSError { - result(FlutterError(code: "InAppWebViewMethodHandler", message: error.domain, details: nil)) - return - } - result(true) - break - case "evaluateJavascript": - if let webView = webView { - let source = arguments!["source"] as! String - let contentWorldMap = arguments!["contentWorld"] as? [String:Any?] - if #available(iOS 14.0, *), let contentWorldMap = contentWorldMap { - let contentWorld = WKContentWorld.fromMap(map: contentWorldMap, windowId: webView.windowId)! - webView.evaluateJavascript(source: source, contentWorld: contentWorld) { (value) in - result(value) - } - } else { - webView.evaluateJavascript(source: source) { (value) in - result(value) - } - } - } - else { - result(nil) - } - break - case "injectJavascriptFileFromUrl": - let urlFile = arguments!["urlFile"] as! String - let scriptHtmlTagAttributes = arguments!["scriptHtmlTagAttributes"] as? [String:Any?] - webView?.injectJavascriptFileFromUrl(urlFile: urlFile, scriptHtmlTagAttributes: scriptHtmlTagAttributes) - result(true) - break - case "injectCSSCode": - let source = arguments!["source"] as! String - webView?.injectCSSCode(source: source) - result(true) - break - case "injectCSSFileFromUrl": - let urlFile = arguments!["urlFile"] as! String - let cssLinkHtmlTagAttributes = arguments!["cssLinkHtmlTagAttributes"] as? [String:Any?] - webView?.injectCSSFileFromUrl(urlFile: urlFile, cssLinkHtmlTagAttributes: cssLinkHtmlTagAttributes) - result(true) - break - case "reload": - webView?.reload() - result(true) - break - case "goBack": - webView?.goBack() - result(true) - break - case "canGoBack": - result(webView?.canGoBack ?? false) - break - case "goForward": - webView?.goForward() - result(true) - break - case "canGoForward": - result(webView?.canGoForward ?? false) - break - case "goBackOrForward": - let steps = arguments!["steps"] as! Int - webView?.goBackOrForward(steps: steps) - result(true) - break - case "canGoBackOrForward": - let steps = arguments!["steps"] as! Int - result(webView?.canGoBackOrForward(steps: steps) ?? false) - break - case "stopLoading": - webView?.stopLoading() - result(true) - break - case "isLoading": - result(webView?.isLoading ?? false) - break - case "takeScreenshot": - if let webView = webView, #available(iOS 11.0, *) { - let screenshotConfiguration = arguments!["screenshotConfiguration"] as? [String: Any?] - webView.takeScreenshot(with: screenshotConfiguration, completionHandler: { (screenshot) -> Void in - result(screenshot) - }) - } - else { - result(nil) - } - break - case "setSettings": - if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { - let inAppBrowserSettings = InAppBrowserSettings() - let inAppBrowserSettingsMap = arguments!["settings"] as! [String: Any] - let _ = inAppBrowserSettings.parse(settings: inAppBrowserSettingsMap) - iabController.setSettings(newSettings: inAppBrowserSettings, newSettingsMap: inAppBrowserSettingsMap) - } else { - let inAppWebViewSettings = InAppWebViewSettings() - let inAppWebViewSettingsMap = arguments!["settings"] as! [String: Any] - let _ = inAppWebViewSettings.parse(settings: inAppWebViewSettingsMap) - webView?.setSettings(newSettings: inAppWebViewSettings, newSettingsMap: inAppWebViewSettingsMap) - } - result(true) - break - case "getSettings": - if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { - result(iabController.getSettings()) - } else { - result(webView?.getSettings()) - } - break - case "close": - if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { - iabController.close { - result(true) - } - } else { - result(FlutterMethodNotImplemented) - } - break - case "show": - if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { - iabController.show { - result(true) - } - } else { - result(FlutterMethodNotImplemented) - } - break - case "hide": - if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController { - iabController.hide { - result(true) - } - } else { - result(FlutterMethodNotImplemented) - } - break - case "getCopyBackForwardList": - result(webView?.getCopyBackForwardList()) - break - case "findAllAsync": - if let webView = webView { - let find = arguments!["find"] as! String - webView.findAllAsync(find: find, completionHandler: {(value, error) in - if error != nil { - result(FlutterError(code: "InAppWebViewMethodHandler", message: error?.localizedDescription, details: nil)) - return - } - result(true) - }) - } else { - result(false) - } - break - case "findNext": - if let webView = webView { - let forward = arguments!["forward"] as! Bool - webView.findNext(forward: forward, completionHandler: {(value, error) in - if error != nil { - result(FlutterError(code: "InAppWebViewMethodHandler", message: error?.localizedDescription, details: nil)) - return - } - result(true) - }) - } else { - result(false) - } - break - case "clearMatches": - if let webView = webView { - webView.clearMatches(completionHandler: {(value, error) in - if error != nil { - result(FlutterError(code: "InAppWebViewMethodHandler", message: error?.localizedDescription, details: nil)) - return - } - result(true) - }) - } else { - result(false) - } - break - case "clearCache": - webView?.clearCache() - result(true) - break - case "scrollTo": - let x = arguments!["x"] as! Int - let y = arguments!["y"] as! Int - let animated = arguments!["animated"] as! Bool - webView?.scrollTo(x: x, y: y, animated: animated) - result(true) - break - case "scrollBy": - let x = arguments!["x"] as! Int - let y = arguments!["y"] as! Int - let animated = arguments!["animated"] as! Bool - webView?.scrollBy(x: x, y: y, animated: animated) - result(true) - break - case "pauseTimers": - webView?.pauseTimers() - result(true) - break - case "resumeTimers": - webView?.resumeTimers() - result(true) - break - case "printCurrentPage": - if let webView = webView { - webView.printCurrentPage(printCompletionHandler: {(completed, error) in - if !completed, let err = error { - print(err.localizedDescription) - result(false) - return - } - result(true) - }) - } else { - result(false) - } - break - case "getContentHeight": - result(webView?.getContentHeight()) - break - case "zoomBy": - let zoomFactor = (arguments!["zoomFactor"] as! NSNumber).floatValue - let animated = arguments!["animated"] as! Bool - webView?.zoomBy(zoomFactor: zoomFactor, animated: animated) - result(true) - break - case "reloadFromOrigin": - webView?.reloadFromOrigin() - result(true) - break - case "getOriginalUrl": - result(webView?.getOriginalUrl()?.absoluteString) - break - case "getZoomScale": - result(webView?.getZoomScale()) - break - case "hasOnlySecureContent": - result(webView?.hasOnlySecureContent ?? false) - break - case "getSelectedText": - if let webView = webView { - webView.getSelectedText { (value, error) in - if let err = error { - print(err.localizedDescription) - result("") - return - } - result(value) - } - } - else { - result(nil) - } - break - case "getHitTestResult": - if let webView = webView { - webView.getHitTestResult { (hitTestResult) in - result(hitTestResult.toMap()) - } - } - else { - result(nil) - } - break - case "clearFocus": - webView?.clearFocus() - result(true) - break - case "setContextMenu": - if let webView = webView { - let contextMenu = arguments!["contextMenu"] as? [String: Any] - webView.contextMenu = contextMenu - result(true) - } else { - result(false) - } - break - case "requestFocusNodeHref": - if let webView = webView { - webView.requestFocusNodeHref { (value, error) in - if let err = error { - print(err.localizedDescription) - result(nil) - return - } - result(value) - } - } else { - result(nil) - } - break - case "requestImageRef": - if let webView = webView { - webView.requestImageRef { (value, error) in - if let err = error { - print(err.localizedDescription) - result(nil) - return - } - result(value) - } - } else { - result(nil) - } - break - case "getScrollX": - if let webView = webView { - result(Int(webView.scrollView.contentOffset.x)) - } else { - result(nil) - } - break - case "getScrollY": - if let webView = webView { - result(Int(webView.scrollView.contentOffset.y)) - } else { - result(nil) - } - break - case "getCertificate": - result(webView?.getCertificate()?.toMap()) - break - case "addUserScript": - if let webView = webView { - let userScriptMap = arguments!["userScript"] as! [String: Any?] - let userScript = UserScript.fromMap(map: userScriptMap, windowId: webView.windowId)! - webView.configuration.userContentController.addUserOnlyScript(userScript) - webView.configuration.userContentController.sync(scriptMessageHandler: webView) - } - result(true) - break - case "removeUserScript": - let index = arguments!["index"] as! Int - let userScriptMap = arguments!["userScript"] as! [String: Any?] - let userScript = UserScript.fromMap(map: userScriptMap, windowId: webView?.windowId)! - webView?.configuration.userContentController.removeUserOnlyScript(at: index, injectionTime: userScript.injectionTime) - result(true) - break - case "removeUserScriptsByGroupName": - let groupName = arguments!["groupName"] as! String - webView?.configuration.userContentController.removeUserOnlyScripts(with: groupName) - result(true) - break - case "removeAllUserScripts": - webView?.configuration.userContentController.removeAllUserOnlyScripts() - result(true) - break - case "callAsyncJavaScript": - if let webView = webView, #available(iOS 10.3, *) { - if #available(iOS 14.0, *) { - let functionBody = arguments!["functionBody"] as! String - let functionArguments = arguments!["arguments"] as! [String:Any] - var contentWorld = WKContentWorld.page - if let contentWorldMap = arguments!["contentWorld"] as? [String:Any?] { - contentWorld = WKContentWorld.fromMap(map: contentWorldMap, windowId: webView.windowId)! - } - webView.callAsyncJavaScript(functionBody: functionBody, arguments: functionArguments, contentWorld: contentWorld) { (value) in - result(value) - } - } else { - let functionBody = arguments!["functionBody"] as! String - let functionArguments = arguments!["arguments"] as! [String:Any] - webView.callAsyncJavaScript(functionBody: functionBody, arguments: functionArguments) { (value) in - result(value) - } - } - } - else { - result(nil) - } - break - case "createPdf": - if let webView = webView, #available(iOS 14.0, *) { - let configuration = arguments!["pdfConfiguration"] as? [String: Any?] - webView.createPdf(configuration: configuration, completionHandler: { (pdf) -> Void in - result(pdf) - }) - } - else { - result(nil) - } - break - case "createWebArchiveData": - if let webView = webView, #available(iOS 14.0, *) { - webView.createWebArchiveData(dataCompletionHandler: { (webArchiveData) -> Void in - result(webArchiveData) - }) - } - else { - result(nil) - } - break - case "saveWebArchive": - if let webView = webView, #available(iOS 14.0, *) { - let filePath = arguments!["filePath"] as! String - let autoname = arguments!["autoname"] as! Bool - webView.saveWebArchive(filePath: filePath, autoname: autoname, completionHandler: { (path) -> Void in - result(path) - }) - } - else { - result(nil) - } - break - case "isSecureContext": - if let webView = webView { - webView.isSecureContext(completionHandler: { (isSecureContext) in - result(isSecureContext) - }) - } - else { - result(false) - } - break - case "createWebMessageChannel": - if let webView = webView { - let _ = webView.createWebMessageChannel { (webMessageChannel) in - result(webMessageChannel.toMap()) - } - } else { - result(nil) - } - break - case "postWebMessage": - if let webView = webView { - let message = arguments!["message"] as! [String: Any?] - let targetOrigin = arguments!["targetOrigin"] as! String - - var ports: [WebMessagePort] = [] - let portsMap = message["ports"] as? [[String: Any?]] - if let portsMap = portsMap { - for portMap in portsMap { - let webMessageChannelId = portMap["webMessageChannelId"] as! String - let index = portMap["index"] as! Int - if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] { - ports.append(webMessageChannel.ports[index]) - } - } - } - let webMessage = WebMessage(data: message["data"] as? String, ports: ports) - do { - try webView.postWebMessage(message: webMessage, targetOrigin: targetOrigin) { (_) in - result(true) - } - } catch let error as NSError { - result(FlutterError(code: "InAppWebViewMethodHandler", message: error.domain, details: nil)) - } - } else { - result(false) - } - break - case "addWebMessageListener": - if let webView = webView { - let webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?] - let webMessageListener = WebMessageListener.fromMap(map: webMessageListenerMap)! - do { - try webView.addWebMessageListener(webMessageListener: webMessageListener) - result(false) - } catch let error as NSError { - result(FlutterError(code: "InAppWebViewMethodHandler", message: error.domain, details: nil)) - } - } else { - result(false) - } - break - case "canScrollVertically": - if let webView = webView { - result(webView.canScrollVertically()) - } else { - result(false) - } - break - case "canScrollHorizontally": - if let webView = webView { - result(webView.canScrollHorizontally()) - } else { - result(false) - } - break - case "pauseAllMediaPlayback": - if let webView = webView, #available(iOS 15.0, *) { - webView.pauseAllMediaPlayback(completionHandler: { () -> Void in - result(true) - }) - } else { - result(false) - } - break - case "setAllMediaPlaybackSuspended": - if let webView = webView, #available(iOS 15.0, *) { - let suspended = arguments!["suspended"] as! Bool - webView.setAllMediaPlaybackSuspended(suspended, completionHandler: { () -> Void in - result(true) - }) - } else { - result(false) - } - break - case "closeAllMediaPresentations": - if let webView = self.webView, #available(iOS 14.5, *) { - // closeAllMediaPresentations with completionHandler v15.0 makes the app crash - // with error EXC_BAD_ACCESS, so use closeAllMediaPresentations v14.5 - webView.closeAllMediaPresentations() - result(true) - } else { - result(false) - } - break - case "requestMediaPlaybackState": - if let webView = webView, #available(iOS 15.0, *) { - webView.requestMediaPlaybackState(completionHandler: { (state) -> Void in - result(state.rawValue) - }) - } else { - result(nil) - } - break - case "getMetaThemeColor": - if let webView = webView, #available(iOS 15.0, *) { - result(webView.themeColor?.hexString) - } else { - result(nil) - } - break - case "isInFullscreen": -// if let webView = webView, #available(iOS 15.0, *) { -// result(webView.fullscreenState == .inFullscreen) -// } - if let webView = webView { - result(webView.inFullscreen) - } - else { - result(false) - } - break - case "getCameraCaptureState": - if let webView = webView, #available(iOS 15.0, *) { - result(webView.cameraCaptureState.rawValue) - } else { - result(nil) - } - break - case "setCameraCaptureState": - if let webView = webView, #available(iOS 15.0, *) { - let state = WKMediaCaptureState.init(rawValue: arguments!["state"] as! Int) ?? WKMediaCaptureState.none - webView.setCameraCaptureState(state) { - result(true) - } - } else { - result(false) - } - break - case "getMicrophoneCaptureState": - if let webView = webView, #available(iOS 15.0, *) { - result(webView.microphoneCaptureState.rawValue) - } else { - result(nil) - } - break - case "setMicrophoneCaptureState": - if let webView = webView, #available(iOS 15.0, *) { - let state = WKMediaCaptureState.init(rawValue: arguments!["state"] as! Int) ?? WKMediaCaptureState.none - webView.setMicrophoneCaptureState(state) { - result(true) - } - } else { - result(false) - } - break - default: - result(FlutterMethodNotImplemented) - break - } - } - - public func dispose() { - webView = nil - } - - deinit { - debugPrint("InAppWebViewMethodHandler - dealloc") - dispose() - } -} diff --git a/ios/Classes/InAppWebViewStatic.swift b/ios/Classes/InAppWebViewStatic.swift index 3f0dfbc5..1982cc66 100755 --- a/ios/Classes/InAppWebViewStatic.swift +++ b/ios/Classes/InAppWebViewStatic.swift @@ -8,7 +8,7 @@ import Foundation import WebKit -class InAppWebViewStatic: ChannelDelegate { +public class InAppWebViewStatic: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static" static var registrar: FlutterPluginRegistrar? static var webViewForUserAgent: WKWebView? @@ -73,4 +73,8 @@ class InAppWebViewStatic: ChannelDelegate { InAppWebViewStatic.webViewForUserAgent = nil InAppWebViewStatic.defaultUserAgent = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/MyCookieManager.swift b/ios/Classes/MyCookieManager.swift index 04cf7626..cc94ee6f 100755 --- a/ios/Classes/MyCookieManager.swift +++ b/ios/Classes/MyCookieManager.swift @@ -9,7 +9,7 @@ import Foundation import WebKit @available(iOS 11.0, *) -class MyCookieManager: ChannelDelegate { +public class MyCookieManager: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_cookiemanager" static var registrar: FlutterPluginRegistrar? static var httpCookieStore: WKHTTPCookieStore? @@ -295,4 +295,8 @@ class MyCookieManager: ChannelDelegate { MyCookieManager.registrar = nil MyCookieManager.httpCookieStore = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/MyWebStorageManager.swift b/ios/Classes/MyWebStorageManager.swift index 0b5eca8f..dd010724 100755 --- a/ios/Classes/MyWebStorageManager.swift +++ b/ios/Classes/MyWebStorageManager.swift @@ -9,7 +9,7 @@ import Foundation import WebKit @available(iOS 9.0, *) -class MyWebStorageManager: ChannelDelegate { +public class MyWebStorageManager: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webstoragemanager" static var registrar: FlutterPluginRegistrar? static var websiteDataStore: WKWebsiteDataStore? @@ -105,4 +105,8 @@ class MyWebStorageManager: ChannelDelegate { MyWebStorageManager.registrar = nil MyWebStorageManager.websiteDataStore = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/PlatformUtil.swift b/ios/Classes/PlatformUtil.swift index 0f3afbec..9aa88e29 100644 --- a/ios/Classes/PlatformUtil.swift +++ b/ios/Classes/PlatformUtil.swift @@ -7,7 +7,7 @@ import Foundation -class PlatformUtil: ChannelDelegate { +public class PlatformUtil: ChannelDelegate { static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_platformutil" static var registrar: FlutterPluginRegistrar? @@ -59,4 +59,8 @@ class PlatformUtil: ChannelDelegate { super.dispose() PlatformUtil.registrar = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/PullToRefresh/PullToRefreshChannelDelegate.swift b/ios/Classes/PullToRefresh/PullToRefreshChannelDelegate.swift index df33c16a..af70b451 100644 --- a/ios/Classes/PullToRefresh/PullToRefreshChannelDelegate.swift +++ b/ios/Classes/PullToRefresh/PullToRefreshChannelDelegate.swift @@ -8,9 +8,9 @@ import Foundation public class PullToRefreshChannelDelegate : ChannelDelegate { - private var pullToRefreshControl: PullToRefreshControl? + private weak var pullToRefreshControl: PullToRefreshControl? - public init(pullToRefreshControl: PullToRefreshControl , channel: FlutterMethodChannel) { + public init(pullToRefreshControl: PullToRefreshControl, channel: FlutterMethodChannel) { super.init(channel: channel) self.pullToRefreshControl = pullToRefreshControl } @@ -87,4 +87,8 @@ public class PullToRefreshChannelDelegate : ChannelDelegate { super.dispose() pullToRefreshControl = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift index 50eacb4b..d4f726aa 100755 --- a/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift +++ b/ios/Classes/SafariViewController/ChromeSafariBrowserManager.swift @@ -94,4 +94,8 @@ public class ChromeSafariBrowserManager: ChannelDelegate { } ChromeSafariBrowserManager.browsers.removeAll() } + + deinit { + dispose() + } } diff --git a/ios/Classes/SafariViewController/SafariViewControllerChannelDelegate.swift b/ios/Classes/SafariViewController/SafariViewControllerChannelDelegate.swift index 423efd8f..2ace93cb 100644 --- a/ios/Classes/SafariViewController/SafariViewControllerChannelDelegate.swift +++ b/ios/Classes/SafariViewController/SafariViewControllerChannelDelegate.swift @@ -8,8 +8,7 @@ import Foundation public class SafariViewControllerChannelDelegate : ChannelDelegate { - - private var safariViewController: SafariViewController? + private weak var safariViewController: SafariViewController? public init(safariViewController: SafariViewController, channel: FlutterMethodChannel) { super.init(channel: channel) @@ -60,4 +59,8 @@ public class SafariViewControllerChannelDelegate : ChannelDelegate { super.dispose() safariViewController = nil } + + deinit { + dispose() + } } diff --git a/ios/Classes/Types/BaseCallbackResult.swift b/ios/Classes/Types/BaseCallbackResult.swift new file mode 100644 index 00000000..f7edb4a5 --- /dev/null +++ b/ios/Classes/Types/BaseCallbackResult.swift @@ -0,0 +1,32 @@ +// +// BaseCallbackResult.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 06/05/22. +// + +import Foundation + +public class BaseCallbackResult : CallbackResult { + + override init() { + super.init() + + self.success = { [weak self] (obj: Any?) in + let result: T? = self?.decodeResult(obj) + var shouldRunDefaultBehaviour = false + if let result = result { + shouldRunDefaultBehaviour = self?.nonNullSuccess(result) ?? shouldRunDefaultBehaviour + } else { + shouldRunDefaultBehaviour = self?.nullSuccess() ?? shouldRunDefaultBehaviour + } + if shouldRunDefaultBehaviour { + self?.defaultBehaviour(result) + } + } + + self.notImplemented = { [weak self] in + self?.defaultBehaviour(nil) + } + } +} diff --git a/ios/Classes/Types/CallbackResult.swift b/ios/Classes/Types/CallbackResult.swift new file mode 100644 index 00000000..5b0ba2b5 --- /dev/null +++ b/ios/Classes/Types/CallbackResult.swift @@ -0,0 +1,18 @@ +// +// CallbackResult.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 06/05/22. +// + +import Foundation + +public class CallbackResult : MethodChannelResult { + public var notImplemented: () -> Void = {} + public var success: (Any?) -> Void = {_ in } + public var error: (String, String?, Any?) -> Void = {_,_,_ in } + public var nonNullSuccess: (T) -> Bool = {_ in true} + public var nullSuccess: () -> Bool = {true} + public var defaultBehaviour: (T?) -> Void = {_ in } + public var decodeResult: (Any?) -> T? = {_ in nil} +} diff --git a/ios/Classes/Types/ClientCertChallenge.swift b/ios/Classes/Types/ClientCertChallenge.swift index f725e646..f2381ee4 100644 --- a/ios/Classes/Types/ClientCertChallenge.swift +++ b/ios/Classes/Types/ClientCertChallenge.swift @@ -7,7 +7,7 @@ import Foundation -class ClientCertChallenge: NSObject { +public class ClientCertChallenge: NSObject { var protectionSpace: URLProtectionSpace! public init(fromChallenge: URLAuthenticationChallenge) { diff --git a/ios/Classes/Types/ClientCertResponse.swift b/ios/Classes/Types/ClientCertResponse.swift new file mode 100644 index 00000000..75ae6818 --- /dev/null +++ b/ios/Classes/Types/ClientCertResponse.swift @@ -0,0 +1,33 @@ +// +// ClientCertResponse.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation + +public class ClientCertResponse : NSObject { + var certificatePath: String + var certificatePassword: String? + var keyStoreType: String? + var action: Int? + + public init(certificatePath: String, certificatePassword: String? = nil, keyStoreType: String? = nil, action: Int? = nil) { + self.certificatePath = certificatePath + self.certificatePassword = certificatePassword + self.keyStoreType = keyStoreType + self.action = action + } + + public static func fromMap(map: [String:Any?]?) -> ClientCertResponse? { + guard let map = map else { + return nil + } + let certificatePath = map["certificatePath"] as! String + let certificatePassword = map["certificatePassword"] as? String + let keyStoreType = map["keyStoreType"] as? String + let action = map["action"] as? Int + return ClientCertResponse(certificatePath: certificatePath, certificatePassword: certificatePassword, keyStoreType: keyStoreType, action: action) + } +} diff --git a/ios/Classes/Types/CreateWindowAction.swift b/ios/Classes/Types/CreateWindowAction.swift new file mode 100644 index 00000000..db7f7658 --- /dev/null +++ b/ios/Classes/Types/CreateWindowAction.swift @@ -0,0 +1,31 @@ +// +// CreateWindowAction.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation +import WebKit + +public class CreateWindowAction : NSObject { + var navigationAction: WKNavigationAction + var windowId: Int64 + var windowFeatures: WKWindowFeatures + var isDialog: Bool? + + public init(navigationAction: WKNavigationAction, windowId: Int64, windowFeatures: WKWindowFeatures, isDialog: Bool? = nil) { + self.navigationAction = navigationAction + self.windowId = windowId + self.windowFeatures = windowFeatures + self.isDialog = isDialog + } + + public func toMap () -> [String:Any?] { + var map = navigationAction.toMap() + map["windowId"] = windowId + map["windowFeatures"] = windowFeatures.toMap() + map["isDialog"] = isDialog + return map + } +} diff --git a/ios/Classes/Types/CustomSchemeResponse.swift b/ios/Classes/Types/CustomSchemeResponse.swift new file mode 100644 index 00000000..eaf3380b --- /dev/null +++ b/ios/Classes/Types/CustomSchemeResponse.swift @@ -0,0 +1,30 @@ +// +// CustomSchemeResponse.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation + +public class CustomSchemeResponse : NSObject { + var data: Data + var contentType: String + var contentEncoding: String + + public init(data: Data, contentType: String, contentEncoding: String) { + self.data = data + self.contentType = contentType + self.contentEncoding = contentEncoding + } + + public static func fromMap(map: [String:Any?]?) -> CustomSchemeResponse? { + guard let map = map else { + return nil + } + let data = map["data"] as! FlutterStandardTypedData + let contentType = map["contentType"] as! String + let contentEncoding = map["contentEncoding"] as! String + return CustomSchemeResponse(data: data.data, contentType: contentType, contentEncoding: contentEncoding) + } +} diff --git a/ios/Classes/Types/FlutterMethodChannel.swift b/ios/Classes/Types/FlutterMethodChannel.swift new file mode 100644 index 00000000..a7409d58 --- /dev/null +++ b/ios/Classes/Types/FlutterMethodChannel.swift @@ -0,0 +1,26 @@ +// +// FlutterMethodChannel.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 06/05/22. +// + +import Foundation +import Flutter + +extension FlutterMethodChannel { + public func invokeMethod(_ method: String, arguments: Any, callback: MethodChannelResult) { + invokeMethod(method, arguments: arguments) {(result) -> Void in + if result is FlutterError { + let error = result as! FlutterError + callback.error(error.code, error.message, error.details) + } + else if (result as? NSObject) == FlutterMethodNotImplemented { + callback.notImplemented() + } + else { + callback.success(result) + } + } + } +} diff --git a/ios/Classes/Types/HttpAuthResponse.swift b/ios/Classes/Types/HttpAuthResponse.swift new file mode 100644 index 00000000..f2f85d64 --- /dev/null +++ b/ios/Classes/Types/HttpAuthResponse.swift @@ -0,0 +1,33 @@ +// +// HttpAuthResponse.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation + +public class HttpAuthResponse : NSObject { + var username: String + var password: String + var permanentPersistence: Bool + var action: Int? + + public init(username: String, password: String, permanentPersistence: Bool, action: Int? = nil) { + self.username = username + self.password = password + self.permanentPersistence = permanentPersistence + self.action = action + } + + public static func fromMap(map: [String:Any?]?) -> HttpAuthResponse? { + guard let map = map else { + return nil + } + let username = map["username"] as! String + let password = map["password"] as! String + let permanentPersistence = map["permanentPersistence"] as! Bool + let action = map["action"] as? Int + return HttpAuthResponse(username: username, password: password, permanentPersistence: permanentPersistence, action: action) + } +} diff --git a/ios/Classes/Types/HttpAuthenticationChallenge.swift b/ios/Classes/Types/HttpAuthenticationChallenge.swift index 8926a8ae..b22a0092 100644 --- a/ios/Classes/Types/HttpAuthenticationChallenge.swift +++ b/ios/Classes/Types/HttpAuthenticationChallenge.swift @@ -7,7 +7,7 @@ import Foundation -class HttpAuthenticationChallenge: NSObject { +public class HttpAuthenticationChallenge: NSObject { var protectionSpace: URLProtectionSpace! var previousFailureCount: Int = 0 var failureResponse: URLResponse? diff --git a/ios/Classes/Types/JsAlertResponse.swift b/ios/Classes/Types/JsAlertResponse.swift new file mode 100644 index 00000000..3e6b3e78 --- /dev/null +++ b/ios/Classes/Types/JsAlertResponse.swift @@ -0,0 +1,33 @@ +// +// JsAlertResponse.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 06/05/22. +// + +import Foundation + +public class JsAlertResponse: NSObject { + var message: String + var confirmButtonTitle: String + var handledByClient: Bool + var action: Int? + + public init(message: String, confirmButtonTitle: String, handledByClient: Bool, action: Int? = nil) { + self.message = message + self.confirmButtonTitle = confirmButtonTitle + self.handledByClient = handledByClient + self.action = action + } + + public static func fromMap(map: [String:Any?]?) -> JsAlertResponse? { + guard let map = map else { + return nil + } + let message = map["message"] as! String + let confirmButtonTitle = map["confirmButtonTitle"] as! String + let handledByClient = map["handledByClient"] as! Bool + let action = map["action"] as? Int + return JsAlertResponse(message: message, confirmButtonTitle: confirmButtonTitle, handledByClient: handledByClient, action: action) + } +} diff --git a/ios/Classes/Types/JsConfirmResponse.swift b/ios/Classes/Types/JsConfirmResponse.swift new file mode 100644 index 00000000..76bfbe20 --- /dev/null +++ b/ios/Classes/Types/JsConfirmResponse.swift @@ -0,0 +1,36 @@ +// +// JsConfirmResponse.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation + +public class JsConfirmResponse: NSObject { + var message: String + var confirmButtonTitle: String + var cancelButtonTitle: String + var handledByClient: Bool + var action: Int? + + public init(message: String, confirmButtonTitle: String, cancelButtonTitle: String, handledByClient: Bool, action: Int? = nil) { + self.message = message + self.confirmButtonTitle = confirmButtonTitle + self.cancelButtonTitle = cancelButtonTitle + self.handledByClient = handledByClient + self.action = action + } + + public static func fromMap(map: [String:Any?]?) -> JsConfirmResponse? { + guard let map = map else { + return nil + } + let message = map["message"] as! String + let confirmButtonTitle = map["confirmButtonTitle"] as! String + let cancelButtonTitle = map["cancelButtonTitle"] as! String + let handledByClient = map["handledByClient"] as! Bool + let action = map["action"] as? Int + return JsConfirmResponse(message: message, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, handledByClient: handledByClient, action: action) + } +} diff --git a/ios/Classes/Types/JsPromptResponse.swift b/ios/Classes/Types/JsPromptResponse.swift new file mode 100644 index 00000000..5c40b492 --- /dev/null +++ b/ios/Classes/Types/JsPromptResponse.swift @@ -0,0 +1,43 @@ +// +// JsPromptResponse.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation + +public class JsPromptResponse: NSObject { + var message: String + var defaultValue: String + var confirmButtonTitle: String + var cancelButtonTitle: String + var handledByClient: Bool + var value: String? + var action: Int? + + public init(message: String, defaultValue: String, confirmButtonTitle: String, cancelButtonTitle: String, handledByClient: Bool, value: String? = nil, action: Int? = nil) { + self.message = message + self.defaultValue = defaultValue + self.confirmButtonTitle = confirmButtonTitle + self.cancelButtonTitle = cancelButtonTitle + self.handledByClient = handledByClient + self.value = value + self.action = action + } + + public static func fromMap(map: [String:Any?]?) -> JsPromptResponse? { + guard let map = map else { + return nil + } + let message = map["message"] as! String + let defaultValue = map["defaultValue"] as! String + let confirmButtonTitle = map["confirmButtonTitle"] as! String + let cancelButtonTitle = map["cancelButtonTitle"] as! String + let handledByClient = map["handledByClient"] as! Bool + let value = map["value"] as? String + let action = map["action"] as? Int + return JsPromptResponse(message: message, defaultValue: defaultValue, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, + handledByClient: handledByClient, value: value, action: action) + } +} diff --git a/ios/Classes/Types/MethodChannelResult.swift b/ios/Classes/Types/MethodChannelResult.swift new file mode 100644 index 00000000..d1555e39 --- /dev/null +++ b/ios/Classes/Types/MethodChannelResult.swift @@ -0,0 +1,14 @@ +// +// MethodChannelResult.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 06/05/22. +// + +import Foundation + +public protocol MethodChannelResult { + var success: (_ obj: Any?) -> Void { get set } + var error: (_ code: String, _ message: String?, _ details: Any?) -> Void { get set } + var notImplemented: () -> Void { get set } +} diff --git a/ios/Classes/Types/PermissionResponse.swift b/ios/Classes/Types/PermissionResponse.swift new file mode 100644 index 00000000..0032aed6 --- /dev/null +++ b/ios/Classes/Types/PermissionResponse.swift @@ -0,0 +1,27 @@ +// +// PermissionResponse.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation + +public class PermissionResponse : NSObject { + var resources: [String] + var action: Int? + + public init(resources: [String], action: Int? = nil) { + self.resources = resources + self.action = action + } + + public static func fromMap(map: [String:Any?]?) -> PermissionResponse? { + guard let map = map else { + return nil + } + let resources = map["resources"] as! [String] + let action = map["action"] as? Int + return PermissionResponse(resources: resources, action: action) + } +} diff --git a/ios/Classes/Types/ServerTrustAuthResponse.swift b/ios/Classes/Types/ServerTrustAuthResponse.swift new file mode 100644 index 00000000..d78fb07c --- /dev/null +++ b/ios/Classes/Types/ServerTrustAuthResponse.swift @@ -0,0 +1,24 @@ +// +// ServerTrustAuthResponse.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 07/05/22. +// + +import Foundation + +public class ServerTrustAuthResponse : NSObject { + var action: Int? + + public init(action: Int? = nil) { + self.action = action + } + + public static func fromMap(map: [String:Any?]?) -> ServerTrustAuthResponse? { + guard let map = map else { + return nil + } + let action = map["action"] as? Int + return ServerTrustAuthResponse(action: action) + } +} diff --git a/ios/Classes/Types/ServerTrustChallenge.swift b/ios/Classes/Types/ServerTrustChallenge.swift index a23adb6b..190c831b 100644 --- a/ios/Classes/Types/ServerTrustChallenge.swift +++ b/ios/Classes/Types/ServerTrustChallenge.swift @@ -7,7 +7,7 @@ import Foundation -class ServerTrustChallenge: NSObject { +public class ServerTrustChallenge: NSObject { var protectionSpace: URLProtectionSpace! public init(fromChallenge: URLAuthenticationChallenge) { diff --git a/ios/Classes/Util.swift b/ios/Classes/Util.swift index 0bf74e84..419d25ce 100644 --- a/ios/Classes/Util.swift +++ b/ios/Classes/Util.swift @@ -213,7 +213,7 @@ public class Util { } else { // normalize grouped zeros :: var zeros: [String] = [] - for j in ipv6.count...8 { + for _ in ipv6.count...8 { zeros.append("0000") } fullIPv6[i] = zeros.joined(separator: ":") diff --git a/ios/Classes/WKProcessPoolManager.swift b/ios/Classes/WKProcessPoolManager.swift index 84dc8485..038fb4d7 100755 --- a/ios/Classes/WKProcessPoolManager.swift +++ b/ios/Classes/WKProcessPoolManager.swift @@ -8,6 +8,6 @@ import Foundation import WebKit -class WKProcessPoolManager { +public class WKProcessPoolManager { static let sharedProcessPool = WKProcessPool() }