iosWebViewFix/ios/Classes/InAppWebView.swift

2103 lines
94 KiB
Swift
Executable File

//
// 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
}
func JSONStringify(value: Any, prettyPrinted: Bool = false) -> String {
let options: JSONSerialization.WritingOptions = prettyPrinted ? .prettyPrinted : .init(rawValue: 0)
if JSONSerialization.isValidJSONObject(value) {
let data = try? JSONSerialization.data(withJSONObject: value, options: options)
if data != nil {
if let string = String(data: data!, encoding: .utf8) {
return string
}
}
}
return ""
}
let JAVASCRIPT_BRIDGE_NAME = "flutter_inappbrowser"
// the message needs to be concatenated with '' in order to have the same behavior like on Android
let consoleLogJS = """
(function(console) {
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);
}
})(window.console);
"""
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 findTextHighlightJS = """
var wkwebview_SearchResultCount = 0;
var wkwebview_CurrentHighlight = 0;
var wkwebview_IsDoneCounting = false;
function wkwebview_FindAllAsyncForElement(element, keyword) {
if (element) {
if (element.nodeType == 3) {
// Text node
var elementTmp = element;
while (true) {
var value = elementTmp.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break;
var span = document.createElement("span");
var text = document.createTextNode(value.substr(idx, keyword.length));
span.appendChild(text);
span.setAttribute(
"id",
"WKWEBVIEW_SEARCH_WORD_" + wkwebview_SearchResultCount
);
span.setAttribute("class", "wkwebview_Highlight");
var backgroundColor = wkwebview_SearchResultCount == 0 ? "#FF9732" : "#FFFF00";
span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;");
text = document.createTextNode(value.substr(idx + keyword.length));
element.deleteData(idx, value.length - idx);
var next = element.nextSibling;
element.parentNode.insertBefore(span, next);
element.parentNode.insertBefore(text, next);
element = text;
wkwebview_SearchResultCount++;
elementTmp = document.createTextNode(
value.substr(idx + keyword.length)
);
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
JSON.stringify({
activeMatchOrdinal: wkwebview_CurrentHighlight,
numberOfMatches: wkwebview_SearchResultCount,
isDoneCounting: wkwebview_IsDoneCounting
})
);
}
} else if (element.nodeType == 1) {
// Element node
if (
element.style.display != "none" &&
element.nodeName.toLowerCase() != "select"
) {
for (var i = element.childNodes.length - 1; i >= 0; i--) {
wkwebview_FindAllAsyncForElement(
element.childNodes[element.childNodes.length - 1 - i],
keyword
);
}
}
}
}
}
// the main entry point to start the search
function wkwebview_FindAllAsync(keyword) {
wkwebview_ClearMatches();
wkwebview_FindAllAsyncForElement(document.body, keyword.toLowerCase());
wkwebview_IsDoneCounting = true;
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
JSON.stringify({
activeMatchOrdinal: wkwebview_CurrentHighlight,
numberOfMatches: wkwebview_SearchResultCount,
isDoneCounting: wkwebview_IsDoneCounting
})
);
}
// helper function, recursively removes the highlights in elements and their childs
function wkwebview_ClearMatchesForElement(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute("class") == "wkwebview_Highlight") {
var text = element.removeChild(element.firstChild);
element.parentNode.insertBefore(text, element);
element.parentNode.removeChild(element);
return true;
} else {
var normalize = false;
for (var i = element.childNodes.length - 1; i >= 0; i--) {
if (wkwebview_ClearMatchesForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
// the main entry point to remove the highlights
function wkwebview_ClearMatches() {
wkwebview_SearchResultCount = 0;
wkwebview_CurrentHighlight = 0;
wkwebview_ClearMatchesForElement(document.body);
}
function wkwebview_FindNext(forward) {
if (wkwebview_SearchResultCount <= 0) return;
var idx = wkwebview_CurrentHighlight + (forward ? +1 : -1);
idx =
idx < 0
? wkwebview_SearchResultCount - 1
: idx >= wkwebview_SearchResultCount
? 0
: idx;
wkwebview_CurrentHighlight = idx;
var scrollTo = document.getElementById("WKWEBVIEW_SEARCH_WORD_" + idx);
if (scrollTo) {
var highlights = document.getElementsByClassName("wkwebview_Highlight");
for (var i = 0; i < highlights.length; i++) {
var span = highlights[i];
span.style.backgroundColor = "#FFFF00";
}
scrollTo.style.backgroundColor = "#FF9732";
scrollTo.scrollIntoView({
behavior: "auto",
block: "center"
});
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
JSON.stringify({
activeMatchOrdinal: wkwebview_CurrentHighlight,
numberOfMatches: wkwebview_SearchResultCount,
isDoneCounting: wkwebview_IsDoneCounting
})
);
}
}
"""
let variableForOnLoadResourceJS = "window._flutter_inappbrowser_useOnLoadResource"
let enableVariableForOnLoadResourceJS = "\(variableForOnLoadResourceJS) = $PLACEHOLDER_VALUE;"
let resourceObserverJS = """
(function() {
var observer = new PerformanceObserver(function(list) {
list.getEntries().forEach(function(entry) {
if (window.\(variableForOnLoadResourceJS) == null || window.\(variableForOnLoadResourceJS) == true) {
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onLoadResource", entry);
}
});
});
observer.observe({entryTypes: ['resource']});
})();
"""
let variableForShouldInterceptAjaxRequestJS = "window._flutter_inappbrowser_useShouldInterceptAjaxRequest"
let enableVariableForShouldInterceptAjaxRequestJS = "\(variableForShouldInterceptAjaxRequestJS) = $PLACEHOLDER_VALUE;"
let interceptAjaxRequestsJS = """
(function(ajax) {
var send = ajax.prototype.send;
var open = ajax.prototype.open;
var setRequestHeader = ajax.prototype.setRequestHeader;
ajax.prototype._flutter_inappbrowser_url = null;
ajax.prototype._flutter_inappbrowser_method = null;
ajax.prototype._flutter_inappbrowser_isAsync = null;
ajax.prototype._flutter_inappbrowser_user = null;
ajax.prototype._flutter_inappbrowser_password = null;
ajax.prototype._flutter_inappbrowser_password = null;
ajax.prototype._flutter_inappbrowser_already_onreadystatechange_wrapped = false;
ajax.prototype._flutter_inappbrowser_request_headers = {};
function convertRequestResponse(request, callback) {
if (request.response != null && request.responseType != null) {
switch (request.responseType) {
case 'arraybuffer':
callback(new Uint8Array(request.response));
return;
case 'blob':
const reader = new FileReader();
reader.addEventListener('loadend', function() {
callback(new Uint8Array(reader.result));
});
reader.readAsArrayBuffer(blob);
return;
case 'document':
callback(request.response.documentElement.outerHTML);
return;
case 'json':
callback(request.response);
return;
};
}
callback(null);
};
ajax.prototype.open = function(method, url, isAsync, user, password) {
isAsync = (isAsync != null) ? isAsync : true;
this._flutter_inappbrowser_url = url;
this._flutter_inappbrowser_method = method;
this._flutter_inappbrowser_isAsync = isAsync;
this._flutter_inappbrowser_user = user;
this._flutter_inappbrowser_password = password;
open.call(this, method, url, isAsync, user, password);
};
ajax.prototype.setRequestHeader = function(header, value) {
this._flutter_inappbrowser_request_headers[header] = value;
setRequestHeader.call(this, header, value);
};
function handleEvent(e) {
var self = this;
if (window.\(variableForShouldInterceptAjaxRequestJS) == null || window.\(variableForShouldInterceptAjaxRequestJS) == true) {
var headers = this.getAllResponseHeaders();
var responseHeaders = {};
if (headers != null) {
var arr = headers.trim().split(/[\\r\\n]+/);
arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift();
var value = parts.join(': ');
responseHeaders[header] = value;
});
}
convertRequestResponse(this, function(response) {
var ajaxRequest = {
method: self._flutter_inappbrowser_method,
url: self._flutter_inappbrowser_url,
isAsync: self._flutter_inappbrowser_isAsync,
user: self._flutter_inappbrowser_user,
password: self._flutter_inappbrowser_password,
withCredentials: self.withCredentials,
headers: self._flutter_inappbrowser_request_headers,
readyState: self.readyState,
status: self.status,
responseURL: self.responseURL,
responseType: self.responseType,
response: response,
responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null,
responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null,
statusText: self.statusText,
responseHeaders, responseHeaders,
event: {
type: e.type,
loaded: e.loaded,
lengthComputable: e.lengthComputable,
total: e.total
}
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) {
if (result != null) {
switch (result) {
case 0:
self.abort();
return;
};
}
});
});
}
};
ajax.prototype.send = function(data) {
var self = this;
if (window.\(variableForShouldInterceptAjaxRequestJS) == null || window.\(variableForShouldInterceptAjaxRequestJS) == true) {
if (!this._flutter_inappbrowser_already_onreadystatechange_wrapped) {
this._flutter_inappbrowser_already_onreadystatechange_wrapped = true;
var onreadystatechange = this.onreadystatechange;
this.onreadystatechange = function() {
if (window.\(variableForShouldInterceptAjaxRequestJS) == null || window.\(variableForShouldInterceptAjaxRequestJS) == true) {
var headers = this.getAllResponseHeaders();
var responseHeaders = {};
if (headers != null) {
var arr = headers.trim().split(/[\\r\\n]+/);
arr.forEach(function (line) {
var parts = line.split(': ');
var header = parts.shift();
var value = parts.join(': ');
responseHeaders[header] = value;
});
}
convertRequestResponse(this, function(response) {
var ajaxRequest = {
method: self._flutter_inappbrowser_method,
url: self._flutter_inappbrowser_url,
isAsync: self._flutter_inappbrowser_isAsync,
user: self._flutter_inappbrowser_user,
password: self._flutter_inappbrowser_password,
withCredentials: self.withCredentials,
headers: self._flutter_inappbrowser_request_headers,
readyState: self.readyState,
status: self.status,
responseURL: self.responseURL,
responseType: self.responseType,
response: response,
responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null,
responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null,
statusText: self.statusText,
responseHeaders: responseHeaders
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {
if (result != null) {
switch (result) {
case 0:
self.abort();
return;
};
}
if (onreadystatechange != null) {
onreadystatechange();
}
});
});
} else if (onreadystatechange != null) {
onreadystatechange();
}
};
}
this.addEventListener('loadstart', handleEvent);
this.addEventListener('load', handleEvent);
this.addEventListener('loadend', handleEvent);
this.addEventListener('progress', handleEvent);
this.addEventListener('error', handleEvent);
this.addEventListener('abort', handleEvent);
this.addEventListener('timeout', handleEvent);
var ajaxRequest = {
data: data,
method: this._flutter_inappbrowser_method,
url: this._flutter_inappbrowser_url,
isAsync: this._flutter_inappbrowser_isAsync,
user: this._flutter_inappbrowser_user,
password: this._flutter_inappbrowser_password,
withCredentials: this.withCredentials,
headers: this._flutter_inappbrowser_request_headers,
responseType: this.responseType
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {
if (result != null) {
switch (result.action) {
case 0:
self.abort();
return;
};
data = result.data;
self.withCredentials = result.withCredentials;
if (result.responseType != null) {
self.responseType = result.responseType;
};
for (var header in result.headers) {
var value = result.headers[header];
self.setRequestHeader(header, value);
};
if ((self._flutter_inappbrowser_method != result.method && result.method != null) || (self._flutter_inappbrowser_url != result.url && result.url != null)) {
self.abort();
self.open(result.method, result.url, result.isAsync, result.user, result.password);
return;
}
}
send.call(self, data);
});
} else {
send.call(this, data);
}
};
})(window.XMLHttpRequest);
"""
let variableForShouldInterceptFetchRequestsJS = "window._flutter_inappbrowser_useShouldInterceptFetchRequest"
let enableVariableForShouldInterceptFetchRequestsJS = "\(variableForShouldInterceptFetchRequestsJS) = $PLACEHOLDER_VALUE;"
let interceptFetchRequestsJS = """
(function(fetch) {
if (fetch == null) {
return;
}
function convertHeadersToJson(headers) {
var headersObj = {};
for (var header of headers.keys()) {
var value = headers.get(header);
headersObj[header] = value;
}
return headersObj;
}
function convertJsonToHeaders(headersJson) {
return new Headers(headersJson);
}
function convertBodyToArray(body) {
return new Response(body).arrayBuffer().then(function(arrayBuffer) {
var arr = Array.from(new Uint8Array(arrayBuffer));
return arr;
})
}
function convertArrayIntBodyToUint8Array(arrayIntBody) {
return new Uint8Array(arrayIntBody);
}
function convertCredentialsToJson(credentials) {
var credentialsObj = {};
if (window.FederatedCredential != null && credentials instanceof FederatedCredential) {
credentialsObj.type = credentials.type;
credentialsObj.id = credentials.id;
credentialsObj.name = credentials.name;
credentialsObj.protocol = credentials.protocol;
credentialsObj.provider = credentials.provider;
credentialsObj.iconURL = credentials.iconURL;
} else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) {
credentialsObj.type = credentials.type;
credentialsObj.id = credentials.id;
credentialsObj.name = credentials.name;
credentialsObj.password = credentials.password;
credentialsObj.iconURL = credentials.iconURL;
} else {
credentialsObj.type = 'default';
credentialsObj.value = credentials;
}
}
function convertJsonToCredential(credentialsJson) {
var credentials;
if (window.FederatedCredential != null && credentialsJson.type === 'federated') {
credentials = new FederatedCredential({
id: credentialsJson.id,
name: credentialsJson.name,
protocol: credentialsJson.protocol,
provider: credentialsJson.provider,
iconURL: credentialsJson.iconURL
});
} else if (window.PasswordCredential != null && credentialsJson.type === 'password') {
credentials = new PasswordCredential({
id: credentialsJson.id,
name: credentialsJson.name,
password: credentialsJson.password,
iconURL: credentialsJson.iconURL
});
} else {
credentials = credentialsJson;
}
return credentials;
}
window.fetch = async function(resource, init) {
if (window.\(variableForShouldInterceptFetchRequestsJS) == null || window.\(variableForShouldInterceptFetchRequestsJS) == true) {
var fetchRequest = {
url: null,
method: null,
headers: null,
body: null,
mode: null,
credentials: null,
cache: null,
redirect: null,
referrer: null,
referrerPolicy: null,
integrity: null,
keepalive: null
};
if (resource instanceof Request) {
fetchRequest.url = resource.url;
fetchRequest.method = resource.method;
fetchRequest.headers = resource.headers;
fetchRequest.body = resource.body;
fetchRequest.mode = resource.mode;
fetchRequest.credentials = resource.credentials;
fetchRequest.cache = resource.cache;
fetchRequest.redirect = resource.redirect;
fetchRequest.referrer = resource.referrer;
fetchRequest.referrerPolicy = resource.referrerPolicy;
fetchRequest.integrity = resource.integrity;
fetchRequest.keepalive = resource.keepalive;
} else {
fetchRequest.url = resource;
if (init != null) {
fetchRequest.method = init.method;
fetchRequest.headers = init.headers;
fetchRequest.body = init.body;
fetchRequest.mode = init.mode;
fetchRequest.credentials = init.credentials;
fetchRequest.cache = init.cache;
fetchRequest.redirect = init.redirect;
fetchRequest.referrer = init.referrer;
fetchRequest.referrerPolicy = init.referrerPolicy;
fetchRequest.integrity = init.integrity;
fetchRequest.keepalive = init.keepalive;
}
}
if (fetchRequest.headers instanceof Headers) {
fetchRequest.headers = convertHeadersToJson(fetchRequest.headers);
}
fetchRequest.credentials = convertCredentialsToJson(fetchRequest.credentials);
return convertBodyToArray(fetchRequest.body).then(function(body) {
fetchRequest.body = body;
return window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {
if (result != null) {
switch (result.action) {
case 0:
var controller = new AbortController();
if (init != null) {
init.signal = controller.signal;
} else {
init = {
signal: controller.signal
};
}
controller.abort();
break;
}
var resultResource = (result.url != null) ? result.url : resource;
var resultInit = init;
if (result.init != null) {
resultInit.method = result.method;
resultInit.headers = convertJsonToHeaders(result.headers);
resultInit.body = convertArrayIntBodyToUint8Array(result.body);
resultInit.mode = result.mode;
resultInit.credentials = convertJsonToCredential(result.credentials);
resultInit.cache = result.cache;
resultInit.redirect = result.redirect;
resultInit.referrer = result.referrer;
resultInit.referrerPolicy = result.referrerPolicy;
resultInit.integrity = result.integrity;
resultInit.keepalive = result.keepalive;
}
return fetch(resultResource, resultInit);
}
return fetch(resource, init);
});
});
} else {
return fetch(resource, init);
}
};
})(window.fetch);
"""
let interceptNavigationStateChangeJS = """
(function(window, document, history) {
history.pushState = (function(f) {
return function pushState(){
var ret = f.apply(this, arguments);
window.dispatchEvent(new Event('pushstate'));
window.dispatchEvent(new Event('_flutter_inappbrowser_locationchange'));
return ret;
};
})(history.pushState);
history.replaceState = ( function(f) {
return function replaceState(){
var ret = f.apply(this, arguments);
window.dispatchEvent(new Event('replacestate'));
window.dispatchEvent(new Event('_flutter_inappbrowser_locationchange'));
return ret;
};
})(history.replaceState);
window.addEventListener('popstate',function() {
window.dispatchEvent(new Event('_flutter_inappbrowser_locationchange'));
});
window.addEventListener('_flutter_inappbrowser_locationchange', function() {
window.webkit.messageHandlers["onNavigationStateChange"].postMessage(JSON.stringify({
url: document.location.href
}));
});
})(window, window.document, window.history);
"""
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
var IABController: InAppBrowserWebViewController?
var IAWController: FlutterWebViewController?
var options: InAppWebViewOptions?
var currentURL: URL?
var startPageTime: Int64 = 0
static var credentialsProposed: [URLCredential] = []
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()
if (options?.transparentBackground)! {
isOpaque = false
backgroundColor = UIColor.clear
scrollView.backgroundColor = UIColor.clear
}
// 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")
if (options?.useOnLoadResource)! {
let resourceObserverJSScript = WKUserScript(source: resourceObserverJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(resourceObserverJSScript)
}
let findTextHighlightJSScript = WKUserScript(source: findTextHighlightJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(findTextHighlightJSScript)
configuration.userContentController.add(self, name: "onFindResultReceived")
let interceptNavigationStateChangeJSScript = WKUserScript(source: interceptNavigationStateChangeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(interceptNavigationStateChangeJSScript)
configuration.userContentController.add(self, name: "onNavigationStateChange")
if (options?.useShouldInterceptAjaxRequest)! {
let interceptAjaxRequestsJSScript = WKUserScript(source: interceptAjaxRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(interceptAjaxRequestsJSScript)
}
if (options?.useShouldInterceptFetchRequest)! {
let interceptFetchRequestsJSScript = WKUserScript(source: interceptFetchRequestsJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
configuration.userContentController.addUserScript(interceptFetchRequestsJSScript)
}
if #available(iOS 9.0, *) {
if ((options?.incognito)!) {
configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
} else if ((options?.cacheEnabled)!) {
configuration.websiteDataStore = WKWebsiteDataStore.default()
}
}
if #available(iOS 11.0, *) {
if((options?.sharedCookiesEnabled)!) {
// More info to sending cookies with WKWebView
// https://stackoverflow.com/questions/26573137/can-i-set-the-cookies-to-be-used-by-a-wkwebview/26577303#26577303
// Set Cookies in iOS 11 and above, initialize websiteDataStore before setting cookies
// See also https://forums.developer.apple.com/thread/97194
// check if websiteDataStore has not been initialized before
if(!(options?.incognito)! && !(options?.cacheEnabled)!) {
configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
}
for cookie in HTTPCookieStorage.shared.cookies ?? [] {
configuration.websiteDataStore.httpCookieStore.setCookie(cookie, completionHandler: nil)
}
}
}
configuration.suppressesIncrementalRendering = (options?.suppressesIncrementalRendering)!
allowsBackForwardNavigationGestures = (options?.allowsBackForwardNavigationGestures)!
if #available(iOS 9.0, *) {
allowsLinkPreview = (options?.allowsLinkPreview)!
configuration.allowsPictureInPictureMediaPlayback = (options?.allowsPictureInPictureMediaPlayback)!
if (options?.applicationNameForUserAgent != nil && (options?.applicationNameForUserAgent)! != "") {
configuration.applicationNameForUserAgent = (options?.applicationNameForUserAgent)!
}
if (options?.userAgent != nil && (options?.userAgent)! != "") {
customUserAgent = (options?.userAgent)!
}
}
configuration.preferences.javaScriptCanOpenWindowsAutomatically = (options?.javaScriptCanOpenWindowsAutomatically)!
configuration.preferences.javaScriptEnabled = (options?.javaScriptEnabled)!
configuration.preferences.minimumFontSize = CGFloat((options?.minimumFontSize)!)
configuration.selectionGranularity = WKSelectionGranularity.init(rawValue: (options?.selectionGranularity)!)!
if #available(iOS 10.0, *) {
configuration.ignoresViewportScaleLimits = (options?.ignoresViewportScaleLimits)!
var dataDetectorTypes = WKDataDetectorTypes.init(rawValue: 0)
for type in options?.dataDetectorTypes ?? [] {
let dataDetectorType = getDataDetectorType(type: type)
dataDetectorTypes = WKDataDetectorTypes(rawValue: dataDetectorTypes.rawValue | dataDetectorType.rawValue)
}
configuration.dataDetectorTypes = dataDetectorTypes
} else {
// Fallback on earlier versions
}
if #available(iOS 13.0, *) {
configuration.preferences.isFraudulentWebsiteWarningEnabled = (options?.isFraudulentWebsiteWarningEnabled)!
if options?.preferredContentMode != nil {
configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: (options?.preferredContentMode)!)!
}
} else {
// Fallback on earlier versions
}
scrollView.showsVerticalScrollIndicator = (options?.verticalScrollBarEnabled)!
scrollView.showsHorizontalScrollIndicator = (options?.horizontalScrollBarEnabled)!
// options.debuggingEnabled is always enabled for iOS.
if (options?.clearCache)! {
clearCache()
}
}
@available(iOS 10.0, *)
public func getDataDetectorType(type: String) -> WKDataDetectorTypes {
switch type {
case "NONE":
return WKDataDetectorTypes.init(rawValue: 0)
case "PHONE_NUMBER":
return .phoneNumber
case "LINK":
return .link
case "ADDRESS":
return .address
case "CALENDAR_EVENT":
return .calendarEvent
case "TRACKING_NUMBER":
return .trackingNumber
case "FLIGHT_NUMBER":
return .flightNumber
case "LOOKUP_SUGGESTION":
return .lookupSuggestion
case "SPOTLIGHT_SUGGESTION":
return .spotlightSuggestion
case "ALL":
return .all
default:
return WKDataDetectorTypes.init(rawValue: 0)
}
}
public static func preWKWebViewConfiguration(options: InAppWebViewOptions?) -> WKWebViewConfiguration {
let configuration = WKWebViewConfiguration()
if #available(iOS 10.0, *) {
configuration.mediaTypesRequiringUserActionForPlayback = ((options?.mediaPlaybackRequiresUserGesture)!) ? .all : []
} else {
// Fallback on earlier versions
configuration.mediaPlaybackRequiresUserAction = (options?.mediaPlaybackRequiresUserGesture)!
}
configuration.allowsInlineMediaPlayback = (options?.allowsInlineMediaPlayback)!
if #available(iOS 11.0, *) {
if let schemes = options?.resourceCustomSchemes {
for scheme in schemes {
configuration.setURLSchemeHandler(CustomeSchemeHandler(), forURLScheme: scheme)
}
}
} else {
// Fallback on earlier versions
}
return configuration
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(WKWebView.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 = screenshot.pngData()!
}
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.instance!.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["transparentBackground"] != nil && options?.transparentBackground != newOptions.transparentBackground {
if newOptions.transparentBackground {
isOpaque = false
backgroundColor = UIColor.clear
scrollView.backgroundColor = UIColor.clear
} else {
isOpaque = true
backgroundColor = nil
scrollView.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
}
}
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 #available(iOS 9.0, *) {
if (newOptionsMap["incognito"] != nil && options?.incognito != newOptions.incognito && newOptions.incognito) {
configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
} else if (newOptionsMap["cacheEnabled"] != nil && options?.cacheEnabled != newOptions.cacheEnabled && newOptions.cacheEnabled) {
configuration.websiteDataStore = WKWebsiteDataStore.default()
}
}
if #available(iOS 11.0, *) {
if (newOptionsMap["sharedCookiesEnabled"] != nil && options?.sharedCookiesEnabled != newOptions.sharedCookiesEnabled && newOptions.sharedCookiesEnabled) {
if(!newOptions.incognito && !newOptions.cacheEnabled) {
configuration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
}
for cookie in HTTPCookieStorage.shared.cookies ?? [] {
configuration.websiteDataStore.httpCookieStore.setCookie(cookie, completionHandler: nil)
}
}
}
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["useOnLoadResource"] != nil && options?.useOnLoadResource != newOptions.useOnLoadResource && newOptions.useOnLoadResource {
let placeholderValue = newOptions.useOnLoadResource ? "true" : "false"
evaluateJavaScript(enableVariableForOnLoadResourceJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue), completionHandler: nil)
}
if newOptionsMap["useShouldInterceptAjaxRequest"] != nil && options?.useShouldInterceptAjaxRequest != newOptions.useShouldInterceptAjaxRequest && newOptions.useShouldInterceptAjaxRequest {
let placeholderValue = newOptions.useShouldInterceptAjaxRequest ? "true" : "false"
evaluateJavaScript(enableVariableForShouldInterceptAjaxRequestJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue), completionHandler: nil)
}
if newOptionsMap["useShouldInterceptFetchRequest"] != nil && options?.useShouldInterceptFetchRequest != newOptions.useShouldInterceptFetchRequest && newOptions.useShouldInterceptFetchRequest {
let placeholderValue = newOptions.useShouldInterceptFetchRequest ? "true" : "false"
evaluateJavaScript(enableVariableForShouldInterceptFetchRequestsJS.replacingOccurrences(of: "$PLACEHOLDER_VALUE", with: placeholderValue), 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["suppressesIncrementalRendering"] != nil && options?.suppressesIncrementalRendering != newOptions.suppressesIncrementalRendering {
configuration.suppressesIncrementalRendering = newOptions.suppressesIncrementalRendering
}
if newOptionsMap["allowsBackForwardNavigationGestures"] != nil && options?.allowsBackForwardNavigationGestures != newOptions.allowsBackForwardNavigationGestures {
allowsBackForwardNavigationGestures = newOptions.allowsBackForwardNavigationGestures
}
if newOptionsMap["allowsInlineMediaPlayback"] != nil && options?.allowsInlineMediaPlayback != newOptions.allowsInlineMediaPlayback {
configuration.allowsInlineMediaPlayback = newOptions.allowsInlineMediaPlayback
}
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["minimumFontSize"] != nil && options?.minimumFontSize != newOptions.minimumFontSize {
configuration.preferences.minimumFontSize = CGFloat(newOptions.minimumFontSize)
}
if newOptionsMap["selectionGranularity"] != nil && options?.selectionGranularity != newOptions.selectionGranularity {
configuration.selectionGranularity = WKSelectionGranularity.init(rawValue: newOptions.selectionGranularity)!
}
if #available(iOS 10.0, *) {
if newOptionsMap["ignoresViewportScaleLimits"] != nil && options?.ignoresViewportScaleLimits != newOptions.ignoresViewportScaleLimits {
configuration.ignoresViewportScaleLimits = newOptions.ignoresViewportScaleLimits
}
if newOptionsMap["dataDetectorTypes"] != nil && options?.dataDetectorTypes != newOptions.dataDetectorTypes {
var dataDetectorTypes = WKDataDetectorTypes.init(rawValue: 0)
for type in newOptions.dataDetectorTypes {
let dataDetectorType = getDataDetectorType(type: type)
dataDetectorTypes = WKDataDetectorTypes(rawValue: dataDetectorTypes.rawValue | dataDetectorType.rawValue)
}
configuration.dataDetectorTypes = dataDetectorTypes
}
} else {
// Fallback on earlier versions
}
if #available(iOS 13.0, *) {
configuration.preferences.isFraudulentWebsiteWarningEnabled = (options?.isFraudulentWebsiteWarningEnabled)!
configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: (options?.preferredContentMode)!)!
} else {
// Fallback on earlier versions
}
if newOptionsMap["verticalScrollBarEnabled"] != nil && options?.verticalScrollBarEnabled != newOptions.verticalScrollBarEnabled {
scrollView.showsVerticalScrollIndicator = newOptions.verticalScrollBarEnabled
}
if newOptionsMap["horizontalScrollBarEnabled"] != nil && options?.horizontalScrollBarEnabled != newOptions.horizontalScrollBarEnabled {
scrollView.showsHorizontalScrollIndicator = newOptions.horizontalScrollBarEnabled
}
if #available(iOS 9.0, *) {
if newOptionsMap["allowsLinkPreview"] != nil && options?.allowsLinkPreview != newOptions.allowsLinkPreview {
allowsLinkPreview = newOptions.allowsLinkPreview
}
if newOptionsMap["allowsPictureInPictureMediaPlayback"] != nil && options?.allowsPictureInPictureMediaPlayback != newOptions.allowsPictureInPictureMediaPlayback {
configuration.allowsPictureInPictureMediaPlayback = newOptions.allowsPictureInPictureMediaPlayback
}
if newOptionsMap["applicationNameForUserAgent"] != nil && options?.applicationNameForUserAgent != newOptions.applicationNameForUserAgent && newOptions.applicationNameForUserAgent != "" {
configuration.applicationNameForUserAgent = newOptions.applicationNameForUserAgent
}
if newOptionsMap["userAgent"] != nil && options?.userAgent != newOptions.userAgent && newOptions.userAgent != "" {
customUserAgent = newOptions.userAgent
}
}
if newOptionsMap["clearCache"] != nil && newOptions.clearCache {
clearCache()
}
if #available(iOS 11.0, *), newOptionsMap["contentBlockers"] != nil {
configuration.userContentController.removeAllContentRuleLists()
let contentBlockers = newOptions.contentBlockers
if contentBlockers.count > 0 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: String.Encoding.utf8)
WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "ContentBlockingRules",
encodedContentRuleList: blockRules) { (contentRuleList, error) in
if let error = error {
print(error.localizedDescription)
return
}
self.configuration.userContentController.add(contentRuleList!)
}
} catch {
print(error.localizedDescription)
}
}
}
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: 3)
}
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 evaluateJavascript(source: String, result: FlutterResult?) {
let jsWrapper = "(function(){return JSON.stringify(eval(%@));})();"
injectDeferredObject(source: source, withWrapper: jsWrapper, result: result)
}
public func injectJavascriptFileFromUrl(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 injectCSSCode(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 injectCSSFileFromUrl(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) {
let app = UIApplication.shared
if let url = navigationAction.request.url {
// Handle target="_blank"
if navigationAction.targetFrame == nil && (options?.useOnTargetBlank)! {
onTargetBlank(url: url)
decisionHandler(.cancel)
return
}
if navigationAction.navigationType == .linkActivated && (options?.useShouldOverrideUrlLoading)! {
shouldOverrideUrlLoading(url: url)
decisionHandler(.cancel)
return
}
// Handle phone and email links
if url.scheme == "tel" || url.scheme == "mailto" {
if app.canOpenURL(url) {
if #available(iOS 10.0, *) {
app.open(url)
} else {
app.openURL(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?.useOnDownloadStart)! {
let mimeType = navigationResponse.response.mimeType
if let url = navigationResponse.response.url {
if mimeType != nil && !mimeType!.starts(with: "text/") {
onDownloadStart(url: url.absoluteString)
decisionHandler(.cancel)
return
}
}
}
decisionHandler(.allow)
}
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!) {
currentURL = url
InAppWebView.credentialsProposed = []
onLoadStop(url: (currentURL?.absoluteString)!)
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) {
InAppWebView.credentialsProposed = []
onLoadError(url: (currentURL?.absoluteString)!, error: error)
if IABController != nil {
IABController!.backButton.isEnabled = canGoBack
IABController!.forwardButton.isEnabled = canGoForward
IABController!.spinner.stopAnimating()
}
}
public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPDigest {
let host = challenge.protectionSpace.host
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)
}
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 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 {
for (protectionSpace, credentials) in CredentialDatabase.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 = []
completionHandler(.performDefaultHandling, nil)
}
return;
}
completionHandler(.performDefaultHandling, 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)
}
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;
}
completionHandler(.performDefaultHandling, nil)
}
})
}
else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
onReceivedClientCertRequest(challenge: challenge, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
}
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 key = SwiftFlutterPlugin.instance!.registrar!.lookupKey(forAsset: certificatePath)
let path = Bundle.main.path(forResource: key, ofType: nil)!
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)
}
break
case 2:
completionHandler(.cancelAuthenticationChallenge, nil)
break
default:
completionHandler(.performDefaultHandling, nil)
}
return;
}
completionHandler(.performDefaultHandling, nil)
}
})
}
else {
completionHandler(.performDefaultHandling, nil)
}
}
struct IdentityAndTrust {
var identityRef:SecIdentity
var trust:SecTrust
var certArray:AnyObject
}
func extractIdentity(PKCS12Data:NSData, password: String) -> IdentityAndTrust? {
var identityAndTrust:IdentityAndTrust?
var securityError:OSStatus = errSecSuccess
var importResult: CFArray? = nil
securityError = SecPKCS12Import(
PKCS12Data as NSData,
[kSecImportExportPassphrase as String: password] as NSDictionary,
&importResult
)
if securityError == errSecSuccess {
let certItems:CFArray = importResult! as CFArray;
let certItemsArray:Array = certItems as Array
let dict:AnyObject? = certItemsArray.first;
if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> {
// grab the identity
let identityPointer:AnyObject? = certEntry["identity"];
let secIdentityRef:SecIdentity = (identityPointer as! SecIdentity?)!;
// grab the trust
let trustPointer:AnyObject? = certEntry["trust"];
let trustRef:SecTrust = trustPointer as! SecTrust;
// grab the cert
let chainPointer:AnyObject? = certEntry["chain"];
identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: chainPointer!);
}
} else {
print("Security Error: " + securityError.description)
if #available(iOS 11.3, *) {
print(SecCopyErrorMessageString(securityError,nil))
}
}
return identityAndTrust;
}
func createAlertDialog(message: String?, responseMessage: String?, confirmButtonTitle: String?, completionHandler: @escaping () -> Void) {
let title = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
let alertController = UIAlertController(title: title, message: nil,
preferredStyle: UIAlertController.Style.alert);
alertController.addAction(UIAlertAction(title: okButton, style: UIAlertAction.Style.default) {
_ in completionHandler()}
);
let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
presentingViewController.present(alertController, animated: true, completion: {})
}
public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
onJsAlert(message: message, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
}
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;
}
}
self.createAlertDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, completionHandler: completionHandler)
}
})
}
func createConfirmDialog(message: String?, responseMessage: String?, confirmButtonTitle: String?, cancelButtonTitle: String?, completionHandler: @escaping (Bool) -> Void) {
let dialogMessage = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
let cancelButton = cancelButtonTitle != nil && !cancelButtonTitle!.isEmpty ? cancelButtonTitle : NSLocalizedString("Cancel", comment: "")
let alertController = UIAlertController(title: nil, message: dialogMessage, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: okButton, style: .default, handler: { (action) in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: cancelButton, style: .cancel, handler: { (action) in
completionHandler(false)
}))
let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
presentingViewController.present(alertController, animated: true, completion: nil)
}
public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (Bool) -> Void) {
onJsConfirm(message: message, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
}
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;
}
}
self.createConfirmDialog(message: message, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, completionHandler: completionHandler)
}
})
}
func createPromptDialog(message: String, defaultValue: String?, responseMessage: String?, confirmButtonTitle: String?, cancelButtonTitle: String?, value: String?, completionHandler: @escaping (String?) -> Void) {
let dialogMessage = responseMessage != nil && !responseMessage!.isEmpty ? responseMessage : message
let okButton = confirmButtonTitle != nil && !confirmButtonTitle!.isEmpty ? confirmButtonTitle : NSLocalizedString("Ok", comment: "")
let cancelButton = cancelButtonTitle != nil && !cancelButtonTitle!.isEmpty ? cancelButtonTitle : NSLocalizedString("Cancel", comment: "")
let alertController = UIAlertController(title: nil, message: dialogMessage, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.text = defaultValue
}
alertController.addAction(UIAlertAction(title: okButton, style: .default, handler: { (action) in
if let v = value {
completionHandler(v)
}
else if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler("")
}
}))
alertController.addAction(UIAlertAction(title: cancelButton, style: .cancel, handler: { (action) in
completionHandler(nil)
}))
let presentingViewController = ((self.IABController != nil) ? self.IABController! : self.window!.rootViewController!)
presentingViewController.present(alertController, animated: true, completion: nil)
}
public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultText defaultValue: String?, initiatedByFrame frame: WKFrameInfo,
completionHandler: @escaping (String?) -> Void) {
onJsPrompt(message: message, defaultValue: defaultValue, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
}
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;
}
}
self.createPromptDialog(message: message, defaultValue: defaultValue, responseMessage: responseMessage, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, value: value, completionHandler: completionHandler)
}
})
}
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 onFindResultReceived(activeMatchOrdinal: Int, numberOfMatches: Int, isDoneCounting: Bool) {
var arguments: [String : Any] = [
"activeMatchOrdinal": activeMatchOrdinal,
"numberOfMatches": numberOfMatches,
"isDoneCounting": isDoneCounting
]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onFindResultReceived", arguments: arguments)
}
}
public func onNavigationStateChange(url: String) {
var arguments: [String : Any] = [
"url": url
]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onNavigationStateChange", 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 onDownloadStart(url: String) {
var arguments: [String: Any] = ["url": url]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onDownloadStart", arguments: arguments)
}
}
public func onLoadResourceCustomScheme(scheme: String, url: String, result: FlutterResult?) {
var arguments: [String: Any] = ["scheme": scheme, "url": url]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onLoadResourceCustomScheme", arguments: arguments, result: result)
}
}
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 onTargetBlank(url: URL) {
var arguments: [String: Any] = ["url": url.absoluteString]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onTargetBlank", arguments: arguments)
}
}
public func onReceivedHttpAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
var arguments: [String: Any?] = [
"host": challenge.protectionSpace.host,
"protocol": challenge.protectionSpace.protocol,
"realm": challenge.protectionSpace.realm,
"port": challenge.protectionSpace.port,
"previousFailureCount": challenge.previousFailureCount
]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onReceivedHttpAuthRequest", arguments: arguments, result: result)
}
}
public func onReceivedServerTrustAuthRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
var serverCertificateData: NSData?
let serverTrust = challenge.protectionSpace.serverTrust!
if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
let serverCertificateCFData = SecCertificateCopyData(serverCertificate)
let data = CFDataGetBytePtr(serverCertificateCFData)
let size = CFDataGetLength(serverCertificateCFData)
serverCertificateData = NSData(bytes: data, length: size)
}
var arguments: [String: Any?] = [
"host": challenge.protectionSpace.host,
"protocol": challenge.protectionSpace.protocol,
"realm": challenge.protectionSpace.realm,
"port": challenge.protectionSpace.port,
"previousFailureCount": challenge.previousFailureCount,
"serverCertificate": serverCertificateData,
"error": -1,
"message": "",
]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onReceivedServerTrustAuthRequest", arguments: arguments, result: result)
}
}
public func onReceivedClientCertRequest(challenge: URLAuthenticationChallenge, result: FlutterResult?) {
var arguments: [String: Any?] = [
"host": challenge.protectionSpace.host,
"protocol": challenge.protectionSpace.protocol,
"realm": challenge.protectionSpace.realm,
"port": challenge.protectionSpace.port
]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onReceivedClientCertRequest", arguments: arguments, result: result)
}
}
public func onJsAlert(message: String, result: FlutterResult?) {
var arguments: [String: Any] = ["message": message]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onJsAlert", arguments: arguments, result: result)
}
}
public func onJsConfirm(message: String, result: FlutterResult?) {
var arguments: [String: Any] = ["message": message]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onJsConfirm", arguments: arguments, result: result)
}
}
public func onJsPrompt(message: String, defaultValue: String?, result: FlutterResult?) {
var arguments: [String: Any] = ["message": message, "defaultValue": defaultValue as Any]
if IABController != nil {
arguments["uuid"] = IABController!.uuid
}
if let channel = getChannel() {
channel.invokeMethod("onJsPrompt", arguments: arguments, result: result)
}
}
public func onConsoleMessage(sourceURL: String, lineNumber: Int, message: String, messageLevel: Int) {
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 = 1
switch (message.name) {
case "consoleLog":
messageLevel = 1
break;
case "consoleDebug":
// on Android, console.debug is TIP
messageLevel = 0
break;
case "consoleError":
messageLevel = 3
break;
case "consoleInfo":
// on Android, console.info is LOG
messageLevel = 1
break;
case "consoleWarn":
messageLevel = 2
break;
default:
messageLevel = 1
break;
}
onConsoleMessage(sourceURL: "", lineNumber: 1, message: message.body as! String, messageLevel: messageLevel)
}
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)
} else if message.name == "onFindResultReceived" {
if let resource = convertToDictionary(text: message.body as! String) {
let activeMatchOrdinal = resource["activeMatchOrdinal"] as! Int
let numberOfMatches = resource["numberOfMatches"] as! Int
let isDoneCounting = resource["isDoneCounting"] as! Bool
self.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting)
}
} else if message.name == "onNavigationStateChange" {
if let resource = convertToDictionary(text: message.body as! String) {
let url = resource["url"] as! String
self.onNavigationStateChange(url: url)
}
}
}
private func getChannel() -> FlutterMethodChannel? {
return (IABController != nil) ? SwiftFlutterPlugin.instance!.channel! : ((IAWController != nil) ? IAWController!.channel! : nil);
}
func findAllAsync(find: String?, completionHandler: ((Any?, Error?) -> Void)?) {
let startSearch = "wkwebview_FindAllAsync('\(find ?? "")');"
evaluateJavaScript(startSearch, completionHandler: completionHandler)
}
func findNext(forward: Bool, completionHandler: ((Any?, Error?) -> Void)?) {
evaluateJavaScript("wkwebview_FindNext(\(forward ? "true" : "false"));", completionHandler: completionHandler)
}
func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) {
evaluateJavaScript("wkwebview_ClearMatches();", completionHandler: completionHandler)
}
public override func removeFromSuperview() {
configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog")
configuration.userContentController.removeScriptMessageHandler(forName: "consoleDebug")
configuration.userContentController.removeScriptMessageHandler(forName: "consoleError")
configuration.userContentController.removeScriptMessageHandler(forName: "consoleInfo")
configuration.userContentController.removeScriptMessageHandler(forName: "consoleWarn")
configuration.userContentController.removeScriptMessageHandler(forName: "callHandler")
configuration.userContentController.removeScriptMessageHandler(forName: "onFindResultReceived")
configuration.userContentController.removeScriptMessageHandler(forName: "onNavigationStateChange")
configuration.userContentController.removeAllUserScripts()
removeObserver(self, forKeyPath: "estimatedProgress")
super.removeFromSuperview()
uiDelegate = nil
navigationDelegate = nil
scrollView.delegate = nil
IAWController?.channel?.setMethodCallHandler(nil)
IABController?.webView = nil
IAWController?.webView = nil
}
}