iosWebViewFix/ios/Classes/InAppWebView.swift
Matthew Lloyd 0dfbfe9a35 iOS: Call setNeedsLayout() in scrollViewDidScroll()
Otherwise, the web view scrolls in blank white space which
doesn't get painted until the scroll gesture is finished,
because the WKWebView hasn't rendered that part of the
viewport to its offscreen buffer.
2019-04-26 18:02:30 -04:00

820 lines
35 KiB
Swift

//
// InAppWebView.swift
// flutter_inappbrowser
//
// Created by Lorenzo on 21/10/18.
//
import Flutter
import Foundation
import WebKit
func currentTimeInMilliSeconds() -> Int64 {
let currentDate = Date()
let since1970 = currentDate.timeIntervalSince1970
return Int64(since1970 * 1000)
}
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
// the message needs to be concatenated with '' in order to have the same behavior like on Android
let consoleLogJS = """
(function() {
var oldLogs = {
'consoleLog': console.log,
'consoleDebug': console.debug,
'consoleError': console.error,
'consoleInfo': console.info,
'consoleWarn': console.warn
};
for (var k in oldLogs) {
(function(oldLog) {
console[oldLog.replace('console', '').toLowerCase()] = function() {
var message = '';
for (var i in arguments) {
if (message == '') {
message += arguments[i];
}
else {
message += ' ' + arguments[i];
}
}
window.webkit.messageHandlers[oldLog].postMessage(message);
}
})(k);
}
})();
"""
let resourceObserverJS = """
(function() {
var observer = new PerformanceObserver(function(list) {
list.getEntries().forEach(function(entry) {
window.webkit.messageHandlers['resourceLoaded'].postMessage(JSON.stringify(entry));
});
});
observer.observe({entryTypes: ['resource', 'mark', 'measure']});
})();
"""
let JAVASCRIPT_BRIDGE_NAME = "flutter_inappbrowser"
let javaScriptBridgeJS = """
window.\(JAVASCRIPT_BRIDGE_NAME) = {};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
var _callHandlerID = setTimeout(function(){});
window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1))} );
return new Promise(function(resolve, reject) {
window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = resolve;
});
}
"""
let platformReadyJS = "window.dispatchEvent(new Event('flutterInAppBrowserPlatformReady'));";
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
var IABController: InAppBrowserWebViewController?
var IAWController: FlutterWebViewController?
var options: InAppWebViewOptions?
var currentURL: URL?
var WKNavigationMap: [String: [String: Any]] = [:]
var startPageTime: Int64 = 0
init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, IAWController: FlutterWebViewController?) {
super.init(frame: frame, configuration: configuration)
self.IABController = IABController
self.IAWController = IAWController
uiDelegate = self
navigationDelegate = self
scrollView.delegate = self
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
public func prepare() {
addObserver(self,
forKeyPath: #keyPath(WKWebView.estimatedProgress),
options: .new,
context: nil)
configuration.userContentController = WKUserContentController()
configuration.preferences = WKPreferences()
// prevent webView from bouncing
if (options?.disallowOverScroll)! {
if responds(to: #selector(getter: scrollView)) {
scrollView.bounces = false
}
else {
for subview: UIView in subviews {
if subview is UIScrollView {
(subview as! UIScrollView).bounces = false
}
}
}
}
if (options?.enableViewportScale)! {
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
let userScript = WKUserScript(source: jscript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(userScript)
}
// Prevents long press on links that cause WKWebView exit
let jscriptWebkitTouchCallout = WKUserScript(source: "document.body.style.webkitTouchCallout='none';", injectionTime: .atDocumentEnd, forMainFrameOnly: true)
configuration.userContentController.addUserScript(jscriptWebkitTouchCallout)
let consoleLogJSScript = WKUserScript(source: consoleLogJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(consoleLogJSScript)
configuration.userContentController.add(self, name: "consoleLog")
configuration.userContentController.add(self, name: "consoleDebug")
configuration.userContentController.add(self, name: "consoleError")
configuration.userContentController.add(self, name: "consoleInfo")
configuration.userContentController.add(self, name: "consoleWarn")
let javaScriptBridgeJSScript = WKUserScript(source: javaScriptBridgeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(javaScriptBridgeJSScript)
configuration.userContentController.add(self, name: "callHandler")
let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(resourceObserverJSScript)
configuration.userContentController.add(self, name: "resourceLoaded")
if #available(iOS 10.0, *) {
configuration.mediaTypesRequiringUserActionForPlayback = ((options?.mediaPlaybackRequiresUserGesture)!) ? .all : []
} else {
// Fallback on earlier versions
configuration.mediaPlaybackRequiresUserAction = (options?.mediaPlaybackRequiresUserGesture)!
}
configuration.allowsInlineMediaPlayback = (options?.allowsInlineMediaPlayback)!
//keyboardDisplayRequiresUserAction = browserOptions?.keyboardDisplayRequiresUserAction
configuration.suppressesIncrementalRendering = (options?.suppressesIncrementalRendering)!
allowsBackForwardNavigationGestures = (options?.allowsBackForwardNavigationGestures)!
if #available(iOS 9.0, *) {
allowsLinkPreview = (options?.allowsLinkPreview)!
}
if #available(iOS 10.0, *) {
configuration.ignoresViewportScaleLimits = (options?.ignoresViewportScaleLimits)!
}
configuration.allowsInlineMediaPlayback = (options?.allowsInlineMediaPlayback)!
if #available(iOS 9.0, *) {
configuration.allowsPictureInPictureMediaPlayback = (options?.allowsPictureInPictureMediaPlayback)!
}
configuration.preferences.javaScriptCanOpenWindowsAutomatically = (options?.javaScriptCanOpenWindowsAutomatically)!
configuration.preferences.javaScriptEnabled = (options?.javaScriptEnabled)!
if ((options?.userAgent)! != "") {
if #available(iOS 9.0, *) {
customUserAgent = (options?.userAgent)!
}
}
if (options?.clearCache)! {
clearCache()
}
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" {
let progress = Int(estimatedProgress * 100)
onProgressChanged(progress: progress)
}
}
public func goBackOrForward(steps: Int) {
if canGoBackOrForward(steps: steps) {
if (steps > 0) {
let index = steps - 1
go(to: self.backForwardList.forwardList[index])
}
else if (steps < 0){
let backListLength = self.backForwardList.backList.count
let index = backListLength + steps
go(to: self.backForwardList.backList[index])
}
}
}
public func canGoBackOrForward(steps: Int) -> Bool {
let currentIndex = self.backForwardList.backList.count
return (steps >= 0)
? steps <= self.backForwardList.forwardList.count
: currentIndex + steps >= 0
}
public func takeScreenshot (completionHandler: @escaping (_ screenshot: Data?) -> Void) {
if #available(iOS 11.0, *) {
takeSnapshot(with: nil, completionHandler: {(image, error) -> Void in
var imageData: Data? = nil
if let screenshot = image {
imageData = UIImagePNGRepresentation(screenshot)!
}
completionHandler(imageData)
})
} else {
completionHandler(nil)
}
}
public func loadUrl(url: URL, headers: [String: String]?) {
var request = URLRequest(url: url)
currentURL = url
if headers != nil {
if let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest {
for (key, value) in headers! {
mutableRequest.setValue(value, forHTTPHeaderField: key)
}
request = mutableRequest as URLRequest
}
}
load(request)
}
public func postUrl(url: URL, postData: Data, completionHandler: @escaping () -> Void) {
var request = URLRequest(url: url)
currentURL = url
request.httpMethod = "POST"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
var returnString = ""
if data != nil {
returnString = String(data: data!, encoding: .utf8) ?? ""
}
DispatchQueue.main.async(execute: {() -> Void in
self.loadHTMLString(returnString, baseURL: url)
completionHandler()
})
}
task.resume()
}
public func loadData(data: String, mimeType: String, encoding: String, baseUrl: String) {
let url = URL(string: baseUrl)!
currentURL = url
if #available(iOS 9.0, *) {
load(data.data(using: .utf8)!, mimeType: mimeType, characterEncodingName: encoding, baseURL: url)
} else {
loadHTMLString(data, baseURL: url)
}
}
public func loadFile(url: String, headers: [String: String]?) throws {
let key = SwiftFlutterPlugin.registrar!.lookupKey(forAsset: url)
let assetURL = Bundle.main.url(forResource: key, withExtension: nil)
if assetURL == nil {
throw NSError(domain: url + " asset file cannot be found!", code: 0)
}
loadUrl(url: assetURL!, headers: headers)
}
func setOptions(newOptions: InAppWebViewOptions, newOptionsMap: [String: Any]) {
if newOptionsMap["disallowOverScroll"] != nil && options?.disallowOverScroll != newOptions.disallowOverScroll {
if responds(to: #selector(getter: scrollView)) {
scrollView.bounces = !newOptions.disallowOverScroll
}
else {
for subview: UIView in subviews {
if subview is UIScrollView {
(subview as! UIScrollView).bounces = !newOptions.disallowOverScroll
}
}
}
}
if newOptionsMap["enableViewportScale"] != nil && options?.enableViewportScale != newOptions.enableViewportScale && newOptions.enableViewportScale {
let jscript = "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"
evaluateJavaScript(jscript, completionHandler: nil)
}
if newOptionsMap["mediaPlaybackRequiresUserGesture"] != nil && options?.mediaPlaybackRequiresUserGesture != newOptions.mediaPlaybackRequiresUserGesture {
if #available(iOS 10.0, *) {
configuration.mediaTypesRequiringUserActionForPlayback = (newOptions.mediaPlaybackRequiresUserGesture) ? .all : []
} else {
// Fallback on earlier versions
configuration.mediaPlaybackRequiresUserAction = newOptions.mediaPlaybackRequiresUserGesture
}
}
if newOptionsMap["allowsInlineMediaPlayback"] != nil && options?.allowsInlineMediaPlayback != newOptions.allowsInlineMediaPlayback {
configuration.allowsInlineMediaPlayback = newOptions.allowsInlineMediaPlayback
}
// if newOptionsMap["keyboardDisplayRequiresUserAction"] != nil && browserOptions?.keyboardDisplayRequiresUserAction != newOptions.keyboardDisplayRequiresUserAction {
// self.webView.keyboardDisplayRequiresUserAction = newOptions.keyboardDisplayRequiresUserAction
// }
if newOptionsMap["suppressesIncrementalRendering"] != nil && options?.suppressesIncrementalRendering != newOptions.suppressesIncrementalRendering {
configuration.suppressesIncrementalRendering = newOptions.suppressesIncrementalRendering
}
if newOptionsMap["allowsBackForwardNavigationGestures"] != nil && options?.allowsBackForwardNavigationGestures != newOptions.allowsBackForwardNavigationGestures {
allowsBackForwardNavigationGestures = newOptions.allowsBackForwardNavigationGestures
}
if newOptionsMap["allowsLinkPreview"] != nil && options?.allowsLinkPreview != newOptions.allowsLinkPreview {
if #available(iOS 9.0, *) {
allowsLinkPreview = newOptions.allowsLinkPreview
}
}
if newOptionsMap["ignoresViewportScaleLimits"] != nil && options?.ignoresViewportScaleLimits != newOptions.ignoresViewportScaleLimits {
if #available(iOS 10.0, *) {
configuration.ignoresViewportScaleLimits = newOptions.ignoresViewportScaleLimits
}
}
if newOptionsMap["allowsInlineMediaPlayback"] != nil && options?.allowsInlineMediaPlayback != newOptions.allowsInlineMediaPlayback {
configuration.allowsInlineMediaPlayback = newOptions.allowsInlineMediaPlayback
}
if newOptionsMap["allowsPictureInPictureMediaPlayback"] != nil && options?.allowsPictureInPictureMediaPlayback != newOptions.allowsPictureInPictureMediaPlayback {
if #available(iOS 9.0, *) {
configuration.allowsPictureInPictureMediaPlayback = newOptions.allowsPictureInPictureMediaPlayback
}
}
if newOptionsMap["javaScriptCanOpenWindowsAutomatically"] != nil && options?.javaScriptCanOpenWindowsAutomatically != newOptions.javaScriptCanOpenWindowsAutomatically {
configuration.preferences.javaScriptCanOpenWindowsAutomatically = newOptions.javaScriptCanOpenWindowsAutomatically
}
if newOptionsMap["javaScriptEnabled"] != nil && options?.javaScriptEnabled != newOptions.javaScriptEnabled {
configuration.preferences.javaScriptEnabled = newOptions.javaScriptEnabled
}
if newOptionsMap["userAgent"] != nil && options?.userAgent != newOptions.userAgent && (newOptions.userAgent != "") {
if #available(iOS 9.0, *) {
customUserAgent = newOptions.userAgent
}
}
if newOptionsMap["clearCache"] != nil && newOptions.clearCache {
clearCache()
}
self.options = newOptions
}
func getOptions() -> [String: Any]? {
if (self.options == nil) {
return nil
}
return self.options!.getHashMap()
}
public func clearCache() {
if #available(iOS 9.0, *) {
//let websiteDataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache])
let date = NSDate(timeIntervalSince1970: 0)
WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: date as Date, completionHandler:{ })
} else {
var libraryPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, false).first!
libraryPath += "/Cookies"
do {
try FileManager.default.removeItem(atPath: libraryPath)
} catch {
print("can't clear cache")
}
URLCache.shared.removeAllCachedResponses()
}
}
public func injectDeferredObject(source: String, withWrapper jsWrapper: String, result: FlutterResult?) {
let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: [])
let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8)
if sourceArrayString != nil {
let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.count ?? 0) - 2))
let jsToInject = String(format: jsWrapper, sourceString!)
evaluateJavaScript(jsToInject, completionHandler: {(value, error) in
if result == nil {
return
}
if error != nil {
let userInfo = (error! as NSError).userInfo
self.onConsoleMessage(sourceURL: (userInfo["WKJavaScriptExceptionSourceURL"] as? URL)?.absoluteString ?? "", lineNumber: userInfo["WKJavaScriptExceptionLineNumber"] as! Int, message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: "ERROR")
}
if value == nil {
result!("")
return
}
do {
let data: Data = ("[" + String(describing: value!) + "]").data(using: String.Encoding.utf8, allowLossyConversion: false)!
let json: Array<Any> = try JSONSerialization.jsonObject(with: data, options: []) as! Array<Any>
if json[0] is String {
result!(json[0])
}
else {
result!(value)
}
} catch let error as NSError {
result!(FlutterError(code: "InAppBrowserFlutterPlugin", message: "Failed to load: \(error.localizedDescription)", details: error))
}
})
}
}
public func injectScriptCode(source: String, result: FlutterResult?) {
let jsWrapper = "(function(){return JSON.stringify(eval(%@));})();"
injectDeferredObject(source: source, withWrapper: jsWrapper, result: result)
}
public func injectScriptFile(urlFile: String) {
let jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document);"
injectDeferredObject(source: urlFile, withWrapper: jsWrapper, result: nil)
}
public func injectStyleCode(source: String) {
let jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document);"
injectDeferredObject(source: source, withWrapper: jsWrapper, result: nil)
}
public func injectStyleFile(urlFile: String) {
let jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document);"
injectDeferredObject(source: urlFile, withWrapper: jsWrapper, result: nil)
}
public func getCopyBackForwardList() -> [String: Any] {
let currentList = backForwardList
let currentIndex = currentList.backList.count
var completeList = currentList.backList
if currentList.currentItem != nil {
completeList.append(currentList.currentItem!)
}
completeList.append(contentsOf: currentList.forwardList)
var history: [[String: String]] = []
for historyItem in completeList {
var historyItemMap: [String: String] = [:]
historyItemMap["originalUrl"] = historyItem.initialURL.absoluteString
historyItemMap["title"] = historyItem.title
historyItemMap["url"] = historyItem.url.absoluteString
history.append(historyItemMap)
}
var result: [String: Any] = [:]
result["history"] = history
result["currentIndex"] = currentIndex
return result;
}
public func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
if url.absoluteString != url.absoluteString && (options?.useOnLoadResource)! {
WKNavigationMap[url.absoluteString] = [
"startTime": currentTimeInMilliSeconds(),
"request": navigationAction.request
]
}
if navigationAction.navigationType == .linkActivated && (options?.useShouldOverrideUrlLoading)! {
shouldOverrideUrlLoading(url: url)
decisionHandler(.cancel)
return
}
if navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .backForward {
currentURL = url
if IABController != nil {
IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!)
}
}
}
decisionHandler(.allow)
}
public func webView(_ webView: WKWebView,
decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if (options?.useOnLoadResource)! {
if let url = navigationResponse.response.url {
if WKNavigationMap[url.absoluteString] != nil {
let startResourceTime: Int64 = (WKNavigationMap[url.absoluteString]!["startTime"] as! Int64)
let startTime: Int64 = startResourceTime - startPageTime;
let duration: Int64 = currentTimeInMilliSeconds() - startResourceTime;
onLoadResource(response: navigationResponse.response, fromRequest: WKNavigationMap[url.absoluteString]!["request"] as? URLRequest, withData: Data(), startTime: startTime, duration: duration)
}
}
}
decisionHandler(.allow)
}
// func webView(_ webView: WKWebView,
// decidePolicyFor navigationResponse: WKNavigationResponse,
// decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
// let mimeType = navigationResponse.response.mimeType
// if mimeType != nil && !mimeType!.starts(with: "text/") {
// download(url: webView.url)
// decisionHandler(.cancel)
// return
// }
// decisionHandler(.allow)
// }
//
// func download (url: URL?) {
// let filename = url?.lastPathComponent
//
// let destination: DownloadRequest.DownloadFileDestination = { _, _ in
// let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
// let fileURL = documentsURL.appendingPathComponent(filename!)
//
// return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
// }
//
// Alamofire.download((url?.absoluteString)!, to: destination).downloadProgress { progress in
// print("Download Progress: \(progress.fractionCompleted)")
// }.response { response in
// if response.error == nil, let path = response.destinationURL?.path {
// UIAlertView(title: nil, message: "File saved to " + path, delegate: nil, cancelButtonTitle: nil).show()
// }
// else {
// UIAlertView(title: nil, message: "Cannot save " + filename!, delegate: nil, cancelButtonTitle: nil).show()
// }
// }
// }
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.startPageTime = currentTimeInMilliSeconds()
onLoadStart(url: (currentURL?.absoluteString)!)
if IABController != nil {
// loading url, start spinner, update back/forward
IABController!.backButton.isEnabled = canGoBack
IABController!.forwardButton.isEnabled = canGoForward
if (IABController!.browserOptions?.spinner)! {
IABController!.spinner.startAnimating()
}
}
}
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.WKNavigationMap = [:]
currentURL = url
onLoadStop(url: (currentURL?.absoluteString)!)
evaluateJavaScript(platformReadyJS, completionHandler: nil)
if IABController != nil {
IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!)
IABController!.backButton.isEnabled = canGoBack
IABController!.forwardButton.isEnabled = canGoForward
IABController!.spinner.stopAnimating()
}
}
public func webView(_ view: WKWebView,
didFailProvisionalNavigation navigation: WKNavigation!,
withError error: Error) {
webView(view, didFail: navigation, withError: error)
}
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
onLoadError(url: (currentURL?.absoluteString)!, error: error)
if IABController != nil {
IABController!.backButton.isEnabled = canGoBack
IABController!.forwardButton.isEnabled = canGoForward
IABController!.spinner.stopAnimating()
}
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if navigationDelegate != nil {
let x = Int(scrollView.contentOffset.x / scrollView.contentScaleFactor)
let y = Int(scrollView.contentOffset.y / scrollView.contentScaleFactor)
onScrollChanged(x: x, y: y)
}
setNeedsLayout()
}
public func onLoadStart(url: String) {
var arguments: [String: Any] = ["url": url]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onLoadStart", arguments: arguments)
}
}
public func onLoadStop(url: String) {
var arguments: [String: Any] = ["url": url]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onLoadStop", arguments: arguments)
}
}
public func onLoadError(url: String, error: Error) {
var arguments: [String: Any] = ["url": url, "code": error._code, "message": error.localizedDescription]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onLoadError", arguments: arguments)
}
}
public func onProgressChanged(progress: Int) {
var arguments: [String: Any] = ["progress": progress]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onProgressChanged", arguments: arguments)
}
}
public func onLoadResource(response: URLResponse, fromRequest request: URLRequest?, withData data: Data, startTime: Int64, duration: Int64) {
var headersResponse = (response as! HTTPURLResponse).allHeaderFields as! [String: String]
headersResponse.lowercaseKeys()
var headersRequest = request!.allHTTPHeaderFields! as [String: String]
headersRequest.lowercaseKeys()
var arguments: [String : Any] = [
"response": [
"url": response.url!.absoluteString,
"statusCode": (response as! HTTPURLResponse).statusCode,
"headers": headersResponse,
"startTime": startTime,
"duration": duration,
"data": data
],
"request": [
"url": request!.url!.absoluteString,
"headers": headersRequest,
"method": request!.httpMethod!
]
]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onLoadResource", arguments: arguments)
}
}
public func onScrollChanged(x: Int, y: Int) {
var arguments: [String: Any] = ["x": x, "y": y]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onScrollChanged", arguments: arguments)
}
}
public func shouldOverrideUrlLoading(url: URL) {
var arguments: [String: Any] = ["url": url.absoluteString]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("shouldOverrideUrlLoading", arguments: arguments)
}
}
public func onConsoleMessage(sourceURL: String, lineNumber: Int, message: String, messageLevel: String) {
var arguments: [String: Any] = ["sourceURL": sourceURL, "lineNumber": lineNumber, "message": message, "messageLevel": messageLevel]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onConsoleMessage", arguments: arguments)
}
}
public func onCallJsHandler(handlerName: String, _callHandlerID: Int64, args: String) {
var arguments: [String: Any] = ["handlerName": handlerName, "args": args]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
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("window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)](\(json)); delete window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)];", completionHandler: nil)
}
})
}
}
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name.starts(with: "console") {
var messageLevel = "LOG"
switch (message.name) {
case "consoleLog":
messageLevel = "LOG"
break;
case "consoleDebug":
// on Android, console.debug is TIP
messageLevel = "TIP"
break;
case "consoleError":
messageLevel = "ERROR"
break;
case "consoleInfo":
// on Android, console.info is LOG
messageLevel = "LOG"
break;
case "consoleWarn":
messageLevel = "WARNING"
break;
default:
messageLevel = "LOG"
break;
}
onConsoleMessage(sourceURL: "", lineNumber: 1, message: message.body as! String, messageLevel: messageLevel)
}
else if message.name == "resourceLoaded" && (options?.useOnLoadResource)! {
if let resource = convertToDictionary(text: message.body as! String) {
// escape special chars
let resourceName = (resource["name"] as! String).addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
let url = URL(string: resourceName!)!
if !UIApplication.shared.canOpenURL(url) {
return
}
let startTime: Int64 = Int64(resource["startTime"] as! Double)
let duration: Int64 = Int64(resource["duration"] as! Double)
var urlRequest = URLRequest(url: url)
urlRequest.allHTTPHeaderFields = [:]
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error != nil {
print(error)
return
}
var withData = data
if withData == nil {
withData = Data()
}
self.onLoadResource(response: response!, fromRequest: urlRequest, withData: withData!, startTime: startTime, duration: duration)
}
task.resume()
}
}
else if message.name == "callHandler" {
let body = message.body as! [String: Any]
let handlerName = body["handlerName"] as! String
let _callHandlerID = body["_callHandlerID"] as! Int64
let args = body["args"] as! String
onCallJsHandler(handlerName: handlerName, _callHandlerID: _callHandlerID, args: args)
}
}
private func getChannel() -> FlutterMethodChannel? {
return (IABController != nil) ? SwiftFlutterPlugin.channel! : ((IAWController != nil) ? IAWController!.channel! : nil);
}
}