2495 lines
109 KiB
Swift
Executable File
2495 lines
109 KiB
Swift
Executable File
//
|
|
// InAppWebView.swift
|
|
// flutter_inappwebview
|
|
//
|
|
// 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_inappwebview"
|
|
|
|
// https://github.com/taylorhakes/promise-polyfill/blob/master/src/index.js
|
|
let promisePolyfillJS = """
|
|
if (window.Promise == null) {
|
|
var setTimeoutFunc = setTimeout;
|
|
function isArray(x) {
|
|
return Boolean(x && typeof x.length !== "undefined");
|
|
};
|
|
function noop() {}
|
|
function bind(fn, thisArg) {
|
|
return function() {
|
|
fn.apply(thisArg, arguments);
|
|
};
|
|
};
|
|
function Promise(fn) {
|
|
if (!(this instanceof Promise))
|
|
throw new TypeError("Promises must be constructed via new");
|
|
if (typeof fn !== "function") throw new TypeError("not a function");
|
|
this._state = 0;
|
|
this._handled = false;
|
|
this._value = undefined;
|
|
this._deferreds = [];
|
|
doResolve(fn, this);
|
|
};
|
|
function handle(self, deferred) {
|
|
while (self._state === 3) {
|
|
self = self._value;
|
|
}
|
|
if (self._state === 0) {
|
|
self._deferreds.push(deferred);
|
|
return;
|
|
}
|
|
self._handled = true;
|
|
Promise._immediateFn(function() {
|
|
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
|
|
if (cb === null) {
|
|
(self._state === 1 ? resolve : reject)(deferred.promise, self._value);
|
|
return;
|
|
}
|
|
var ret;
|
|
try {
|
|
ret = cb(self._value);
|
|
} catch (e) {
|
|
reject(deferred.promise, e);
|
|
return;
|
|
}
|
|
resolve(deferred.promise, ret);
|
|
});
|
|
};
|
|
function resolve(self, newValue) {
|
|
try {
|
|
if (newValue === self)
|
|
throw new TypeError("A promise cannot be resolved with itself.");
|
|
if (
|
|
newValue &&
|
|
(typeof newValue === "object" || typeof newValue === "function")
|
|
) {
|
|
var then = newValue.then;
|
|
if (newValue instanceof Promise) {
|
|
self._state = 3;
|
|
self._value = newValue;
|
|
finale(self);
|
|
return;
|
|
} else if (typeof then === "function") {
|
|
doResolve(bind(then, newValue), self);
|
|
return;
|
|
}
|
|
}
|
|
self._state = 1;
|
|
self._value = newValue;
|
|
finale(self);
|
|
} catch (e) {
|
|
reject(self, e);
|
|
}
|
|
};
|
|
function reject(self, newValue) {
|
|
self._state = 2;
|
|
self._value = newValue;
|
|
finale(self);
|
|
};
|
|
function finale(self) {
|
|
if (self._state === 2 && self._deferreds.length === 0) {
|
|
Promise._immediateFn(function() {
|
|
if (!self._handled) {
|
|
Promise._unhandledRejectionFn(self._value);
|
|
}
|
|
});
|
|
}
|
|
for (var i = 0, len = self._deferreds.length; i < len; i++) {
|
|
handle(self, self._deferreds[i]);
|
|
}
|
|
self._deferreds = null;
|
|
};
|
|
function Handler(onFulfilled, onRejected, promise) {
|
|
this.onFulfilled = typeof onFulfilled === "function" ? onFulfilled : null;
|
|
this.onRejected = typeof onRejected === "function" ? onRejected : null;
|
|
this.promise = promise;
|
|
};
|
|
function doResolve(fn, self) {
|
|
var done = false;
|
|
try {
|
|
fn(
|
|
function(value) {
|
|
if (done) return;
|
|
done = true;
|
|
resolve(self, value);
|
|
},
|
|
function(reason) {
|
|
if (done) return;
|
|
done = true;
|
|
reject(self, reason);
|
|
}
|
|
);
|
|
} catch (ex) {
|
|
if (done) return;
|
|
done = true;
|
|
reject(self, ex);
|
|
}
|
|
};
|
|
Promise.prototype["catch"] = function(onRejected) {
|
|
return this.then(null, onRejected);
|
|
};
|
|
Promise.prototype.then = function(onFulfilled, onRejected) {
|
|
var prom = new this.constructor(noop);
|
|
handle(this, new Handler(onFulfilled, onRejected, prom));
|
|
return prom;
|
|
};
|
|
Promise.prototype["finally"] = function finallyConstructor(callback) {
|
|
var constructor = this.constructor;
|
|
return this.then(
|
|
function(value) {
|
|
return constructor.resolve(callback()).then(function() {
|
|
return value;
|
|
});
|
|
},
|
|
function(reason) {
|
|
return constructor.resolve(callback()).then(function() {
|
|
return constructor.reject(reason);
|
|
});
|
|
}
|
|
);
|
|
};
|
|
Promise.all = function(arr) {
|
|
return new Promise(function(resolve, reject) {
|
|
if (!isArray(arr)) {
|
|
return reject(new TypeError("Promise.all accepts an array"));
|
|
}
|
|
var args = Array.prototype.slice.call(arr);
|
|
if (args.length === 0) return resolve([]);
|
|
var remaining = args.length;
|
|
function res(i, val) {
|
|
try {
|
|
if (val && (typeof val === "object" || typeof val === "function")) {
|
|
var then = val.then;
|
|
if (typeof then === "function") {
|
|
then.call(
|
|
val,
|
|
function(val) {
|
|
res(i, val);
|
|
},
|
|
reject
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
args[i] = val;
|
|
if (--remaining === 0) {
|
|
resolve(args);
|
|
}
|
|
} catch (ex) {
|
|
reject(ex);
|
|
}
|
|
}
|
|
for (var i = 0; i < args.length; i++) {
|
|
res(i, args[i]);
|
|
}
|
|
});
|
|
};
|
|
Promise.resolve = function(value) {
|
|
if (value && typeof value === "object" && value.constructor === Promise) {
|
|
return value;
|
|
}
|
|
|
|
return new Promise(function(resolve) {
|
|
resolve(value);
|
|
});
|
|
};
|
|
Promise.reject = function(value) {
|
|
return new Promise(function(resolve, reject) {
|
|
reject(value);
|
|
});
|
|
};
|
|
Promise.race = function(arr) {
|
|
return new Promise(function(resolve, reject) {
|
|
if (!isArray(arr)) {
|
|
return reject(new TypeError("Promise.race accepts an array"));
|
|
}
|
|
for (var i = 0, len = arr.length; i < len; i++) {
|
|
Promise.resolve(arr[i]).then(resolve, reject);
|
|
}
|
|
});
|
|
};
|
|
Promise._immediateFn =
|
|
(typeof setImmediate === "function" &&
|
|
function(fn) {
|
|
setImmediate(fn);
|
|
}) ||
|
|
function(fn) {
|
|
setTimeoutFunc(fn, 0);
|
|
};
|
|
Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
|
|
if (typeof console !== "undefined" && console) {
|
|
console.warn("Possible Unhandled Promise Rejection:", err);
|
|
}
|
|
};
|
|
}
|
|
"""
|
|
|
|
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;
|
|
});
|
|
}
|
|
"""
|
|
|
|
// 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 printJS = """
|
|
window.print = function() {
|
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrint", window.location.href);
|
|
}
|
|
"""
|
|
|
|
let platformReadyJS = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));";
|
|
|
|
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_inappwebview_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_inappwebview_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_inappwebview_url = null;
|
|
ajax.prototype._flutter_inappwebview_method = null;
|
|
ajax.prototype._flutter_inappwebview_isAsync = null;
|
|
ajax.prototype._flutter_inappwebview_user = null;
|
|
ajax.prototype._flutter_inappwebview_password = null;
|
|
ajax.prototype._flutter_inappwebview_password = null;
|
|
ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false;
|
|
ajax.prototype._flutter_inappwebview_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_inappwebview_url = url;
|
|
this._flutter_inappwebview_method = method;
|
|
this._flutter_inappwebview_isAsync = isAsync;
|
|
this._flutter_inappwebview_user = user;
|
|
this._flutter_inappwebview_password = password;
|
|
this._flutter_inappwebview_request_headers = {};
|
|
open.call(this, method, url, isAsync, user, password);
|
|
};
|
|
ajax.prototype.setRequestHeader = function(header, value) {
|
|
this._flutter_inappwebview_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_inappwebview_method,
|
|
url: self._flutter_inappwebview_url,
|
|
isAsync: self._flutter_inappwebview_isAsync,
|
|
user: self._flutter_inappwebview_user,
|
|
password: self._flutter_inappwebview_password,
|
|
withCredentials: self.withCredentials,
|
|
headers: self._flutter_inappwebview_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_inappwebview_already_onreadystatechange_wrapped) {
|
|
this._flutter_inappwebview_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_inappwebview_method,
|
|
url: self._flutter_inappwebview_url,
|
|
isAsync: self._flutter_inappwebview_isAsync,
|
|
user: self._flutter_inappwebview_user,
|
|
password: self._flutter_inappwebview_password,
|
|
withCredentials: self.withCredentials,
|
|
headers: self._flutter_inappwebview_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_inappwebview_method,
|
|
url: this._flutter_inappwebview_url,
|
|
isAsync: this._flutter_inappwebview_isAsync,
|
|
user: this._flutter_inappwebview_user,
|
|
password: this._flutter_inappwebview_password,
|
|
withCredentials: this.withCredentials,
|
|
headers: this._flutter_inappwebview_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];
|
|
var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header];
|
|
if (flutter_inappwebview_value == null) {
|
|
self._flutter_inappwebview_request_headers[header] = value;
|
|
} else {
|
|
self._flutter_inappwebview_request_headers[header] += ', ' + value;
|
|
}
|
|
setRequestHeader.call(self, header, value);
|
|
};
|
|
if ((self._flutter_inappwebview_method != result.method && result.method != null) || (self._flutter_inappwebview_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_inappwebview_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;
|
|
}
|
|
resource = (result.url != null) ? result.url : resource;
|
|
if (init == null) {
|
|
init = {};
|
|
}
|
|
if (result.method != null && result.method.length > 0) {
|
|
init.method = result.method;
|
|
}
|
|
if (result.headers != null && Object.keys(result.headers).length > 0) {
|
|
init.headers = convertJsonToHeaders(result.headers);
|
|
}
|
|
if (result.body != null && result.body.length > 0) {
|
|
init.body = convertArrayIntBodyToUint8Array(result.body);
|
|
}
|
|
if (result.mode != null && result.mode.length > 0) {
|
|
init.mode = result.mode;
|
|
}
|
|
if (result.credentials != null) {
|
|
init.credentials = convertJsonToCredential(result.credentials);
|
|
}
|
|
if (result.cache != null && result.cache.length > 0) {
|
|
init.cache = result.cache;
|
|
}
|
|
if (result.redirect != null && result.redirect.length > 0) {
|
|
init.redirect = result.redirect;
|
|
}
|
|
if (result.referrer != null && result.referrer.length > 0) {
|
|
init.referrer = result.referrer;
|
|
}
|
|
if (result.referrerPolicy != null && result.referrerPolicy.length > 0) {
|
|
init.referrerPolicy = result.referrerPolicy;
|
|
}
|
|
if (result.integrity != null && result.integrity.length > 0) {
|
|
init.integrity = result.integrity;
|
|
}
|
|
if (result.keepalive != null) {
|
|
init.keepalive = result.keepalive;
|
|
}
|
|
return fetch(resource, init);
|
|
}
|
|
return fetch(resource, init);
|
|
});
|
|
});
|
|
} else {
|
|
return fetch(resource, init);
|
|
}
|
|
};
|
|
})(window.fetch);
|
|
"""
|
|
|
|
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
|
|
|
|
var IABController: InAppBrowserWebViewController?
|
|
var channel: FlutterMethodChannel?
|
|
var options: InAppWebViewOptions?
|
|
var currentURL: URL?
|
|
var startPageTime: Int64 = 0
|
|
static var credentialsProposed: [URLCredential] = []
|
|
var lastScrollX: CGFloat = 0
|
|
var lastScrollY: CGFloat = 0
|
|
var isPausedTimers = false
|
|
var isPausedTimersCompletionHandler: (() -> Void)?
|
|
// This flag is used to block the "shouldOverrideUrlLoading" event when the WKWebView is loading the first time,
|
|
// in order to have the same behavior as Android
|
|
var activateShouldOverrideUrlLoading = false
|
|
|
|
init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, channel: FlutterMethodChannel?) {
|
|
|
|
super.init(frame: frame, configuration: configuration)
|
|
self.channel = channel
|
|
self.IABController = IABController
|
|
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)
|
|
|
|
addObserver(self,
|
|
forKeyPath: #keyPath(WKWebView.url),
|
|
options: [.new, .old],
|
|
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 promisePolyfillJSScript = WKUserScript(source: promisePolyfillJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
|
configuration.userContentController.addUserScript(promisePolyfillJSScript)
|
|
|
|
let javaScriptBridgeJSScript = WKUserScript(source: javaScriptBridgeJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
|
configuration.userContentController.addUserScript(javaScriptBridgeJSScript)
|
|
configuration.userContentController.add(self, name: "callHandler")
|
|
|
|
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 printJSScript = WKUserScript(source: printJS, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
|
configuration.userContentController.addUserScript(printJSScript)
|
|
|
|
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")
|
|
|
|
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)!)!
|
|
}
|
|
scrollView.automaticallyAdjustsScrollIndicatorInsets = (options?.automaticallyAdjustsScrollIndicatorInsets)!
|
|
} else {
|
|
// Fallback on earlier versions
|
|
}
|
|
|
|
scrollView.showsVerticalScrollIndicator = (options?.verticalScrollBarEnabled)!
|
|
scrollView.showsHorizontalScrollIndicator = (options?.horizontalScrollBarEnabled)!
|
|
scrollView.showsVerticalScrollIndicator = !(options?.disableVerticalScroll)!
|
|
scrollView.showsHorizontalScrollIndicator = !(options?.disableHorizontalScroll)!
|
|
|
|
// 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()
|
|
|
|
configuration.processPool = WKProcessPoolManager.sharedProcessPool
|
|
|
|
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)
|
|
} else if keyPath == #keyPath(WKWebView.url) && change?[NSKeyValueChangeKey.newKey] is URL {
|
|
let newUrl = change?[NSKeyValueChangeKey.newKey] as? URL
|
|
onUpdateVisitedHistory(url: newUrl!.absoluteString)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
scrollView
|
|
|
|
if #available(iOS 13.0, *) {
|
|
if newOptionsMap["isFraudulentWebsiteWarningEnabled"] != nil && options?.isFraudulentWebsiteWarningEnabled != newOptions.isFraudulentWebsiteWarningEnabled {
|
|
configuration.preferences.isFraudulentWebsiteWarningEnabled = newOptions.isFraudulentWebsiteWarningEnabled
|
|
}
|
|
if newOptionsMap["preferredContentMode"] != nil && options?.preferredContentMode != newOptions.preferredContentMode {
|
|
configuration.defaultWebpagePreferences.preferredContentMode = WKWebpagePreferences.ContentMode(rawValue: newOptions.preferredContentMode)!
|
|
}
|
|
if newOptionsMap["automaticallyAdjustsScrollIndicatorInsets"] != nil && options?.automaticallyAdjustsScrollIndicatorInsets != newOptions.automaticallyAdjustsScrollIndicatorInsets {
|
|
scrollView.automaticallyAdjustsScrollIndicatorInsets = newOptions.automaticallyAdjustsScrollIndicatorInsets
|
|
}
|
|
} 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 newOptionsMap["disableVerticalScroll"] != nil && options?.disableVerticalScroll != newOptions.disableVerticalScroll {
|
|
scrollView.showsVerticalScrollIndicator = !newOptions.disableVerticalScroll
|
|
}
|
|
if newOptionsMap["disableHorizontalScroll"] != nil && options?.disableHorizontalScroll != newOptions.disableHorizontalScroll {
|
|
scrollView.showsHorizontalScrollIndicator = !newOptions.disableHorizontalScroll
|
|
}
|
|
|
|
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?) {
|
|
var jsToInject = source
|
|
if let wrapper = jsWrapper {
|
|
let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: [])
|
|
let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8)
|
|
let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.count ?? 0) - 2))
|
|
jsToInject = String(format: wrapper, sourceString!)
|
|
}
|
|
evaluateJavaScript(jsToInject, completionHandler: {(value, error) in
|
|
if result == nil {
|
|
return
|
|
}
|
|
|
|
if error != nil {
|
|
let userInfo = (error! as NSError).userInfo
|
|
self.onConsoleMessage(message: userInfo["WKJavaScriptExceptionMessage"] as? String ?? "", messageLevel: 3)
|
|
}
|
|
|
|
if value == nil {
|
|
result!("")
|
|
return
|
|
}
|
|
|
|
result!(value)
|
|
})
|
|
}
|
|
|
|
public func evaluateJavascript(source: String, result: FlutterResult?) {
|
|
injectDeferredObject(source: source, withWrapper: nil, 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) {
|
|
|
|
if let url = navigationAction.request.url {
|
|
|
|
if activateShouldOverrideUrlLoading && (options?.useShouldOverrideUrlLoading)! {
|
|
|
|
let isForMainFrame = navigationAction.targetFrame?.isMainFrame ?? false
|
|
|
|
shouldOverrideUrlLoading(url: url, method: navigationAction.request.httpMethod, headers: navigationAction.request.allHTTPHeaderFields, isForMainFrame: isForMainFrame, navigationType: navigationAction.navigationType, result: { (result) -> Void in
|
|
if result is FlutterError {
|
|
print((result as! FlutterError).message)
|
|
}
|
|
else if (result as? NSObject) == FlutterMethodNotImplemented {
|
|
self.updateUrlTextFieldForIABController(navigationAction: navigationAction)
|
|
decisionHandler(.allow)
|
|
}
|
|
else {
|
|
var response: [String: Any]
|
|
if let r = result {
|
|
response = r as! [String: Any]
|
|
var action = response["action"] as? Int
|
|
action = action != nil ? action : 0;
|
|
switch action {
|
|
case 1:
|
|
self.updateUrlTextFieldForIABController(navigationAction: navigationAction)
|
|
decisionHandler(.allow)
|
|
break
|
|
default:
|
|
decisionHandler(.cancel)
|
|
}
|
|
return;
|
|
}
|
|
self.updateUrlTextFieldForIABController(navigationAction: navigationAction)
|
|
decisionHandler(.allow)
|
|
}
|
|
})
|
|
return
|
|
|
|
}
|
|
|
|
updateUrlTextFieldForIABController(navigationAction: navigationAction)
|
|
}
|
|
|
|
if !activateShouldOverrideUrlLoading {
|
|
activateShouldOverrideUrlLoading = true
|
|
}
|
|
|
|
decisionHandler(.allow)
|
|
}
|
|
|
|
public func updateUrlTextFieldForIABController(navigationAction: WKNavigationAction) {
|
|
if navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .backForward {
|
|
currentURL = url
|
|
if IABController != nil {
|
|
IABController!.updateUrlTextField(url: (currentURL?.absoluteString)!)
|
|
}
|
|
}
|
|
}
|
|
|
|
public func webView(_ webView: WKWebView,
|
|
decidePolicyFor navigationResponse: WKNavigationResponse,
|
|
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
|
|
if navigationResponse.isForMainFrame, let response = navigationResponse.response as? HTTPURLResponse {
|
|
if response.statusCode >= 400 {
|
|
onLoadHttpError(url: response.url!.absoluteString, statusCode: response.statusCode, description: "")
|
|
}
|
|
}
|
|
|
|
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 = []
|
|
evaluateJavaScript(platformReadyJS, completionHandler: nil)
|
|
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 = []
|
|
// used .performDefaultHandling to mantain consistency with Android
|
|
// because .cancelAuthenticationChallenge will call webView(_:didFail:withError:)
|
|
completionHandler(.performDefaultHandling, nil)
|
|
//completionHandler(.cancelAuthenticationChallenge, nil)
|
|
break
|
|
case 1:
|
|
let username = response["username"] as! String
|
|
let password = response["password"] as! String
|
|
let permanentPersistence = response["permanentPersistence"] as? Bool ?? false
|
|
let persistence = (permanentPersistence) ? URLCredential.Persistence.permanent : URLCredential.Persistence.forSession
|
|
let credential = URLCredential(user: username, password: password, persistence: persistence)
|
|
completionHandler(.useCredential, credential)
|
|
break
|
|
case 2:
|
|
if InAppWebView.credentialsProposed.count == 0 {
|
|
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) {
|
|
|
|
if (isPausedTimers) {
|
|
isPausedTimersCompletionHandler = completionHandler
|
|
return
|
|
}
|
|
|
|
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) {
|
|
let disableVerticalScroll = options?.disableVerticalScroll ?? false
|
|
let disableHorizontalScroll = options?.disableHorizontalScroll ?? false
|
|
if disableVerticalScroll && disableHorizontalScroll {
|
|
scrollView.contentOffset = CGPoint(x: lastScrollX, y: lastScrollY);
|
|
}
|
|
else if disableVerticalScroll {
|
|
if (scrollView.contentOffset.y >= 0 || scrollView.contentOffset.y < 0) {
|
|
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: lastScrollY);
|
|
}
|
|
}
|
|
else if disableHorizontalScroll {
|
|
if (scrollView.contentOffset.x >= 0 || scrollView.contentOffset.x < 0) {
|
|
scrollView.contentOffset = CGPoint(x: lastScrollX, y: scrollView.contentOffset.y);
|
|
}
|
|
}
|
|
if navigationDelegate != nil && !(disableVerticalScroll && disableHorizontalScroll) {
|
|
let x = Int(scrollView.contentOffset.x / scrollView.contentScaleFactor)
|
|
let y = Int(scrollView.contentOffset.y / scrollView.contentScaleFactor)
|
|
onScrollChanged(x: x, y: y)
|
|
}
|
|
setNeedsLayout()
|
|
lastScrollX = scrollView.contentOffset.x
|
|
lastScrollY = scrollView.contentOffset.y
|
|
}
|
|
|
|
public func webView(_ webView: WKWebView,
|
|
createWebViewWith configuration: WKWebViewConfiguration,
|
|
for navigationAction: WKNavigationAction,
|
|
windowFeatures: WKWindowFeatures) -> WKWebView? {
|
|
onCreateWindow(url: navigationAction.request.url!, navigationType: navigationAction.navigationType)
|
|
return nil
|
|
}
|
|
|
|
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 onLoadHttpError(url: String, statusCode: Int, description: String) {
|
|
var arguments: [String: Any] = ["url": url, "statusCode": statusCode, "description": description]
|
|
if IABController != nil {
|
|
arguments["uuid"] = IABController!.uuid
|
|
}
|
|
if let channel = getChannel() {
|
|
channel.invokeMethod("onLoadHttpError", 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 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, method: String?, headers: [String: String]?, isForMainFrame: Bool, navigationType: WKNavigationType, result: FlutterResult?) {
|
|
var arguments: [String: Any?] = [
|
|
"url": url.absoluteString,
|
|
"method": method,
|
|
"headers": headers,
|
|
"isForMainFrame": isForMainFrame,
|
|
"androidHasGesture": nil,
|
|
"androidIsRedirect": nil,
|
|
"iosWKNavigationType": navigationType.rawValue
|
|
]
|
|
if IABController != nil {
|
|
arguments["uuid"] = IABController!.uuid
|
|
}
|
|
if let channel = getChannel() {
|
|
channel.invokeMethod("shouldOverrideUrlLoading", arguments: arguments, result: result)
|
|
}
|
|
}
|
|
|
|
public func onCreateWindow(url: URL, navigationType: WKNavigationType) {
|
|
var arguments: [String: Any?] = [
|
|
"url": url.absoluteString,
|
|
"androidIsDialog": nil,
|
|
"androidIsUserGesture": nil,
|
|
"iosWKNavigationType": navigationType.rawValue
|
|
]
|
|
if IABController != nil {
|
|
arguments["uuid"] = IABController!.uuid
|
|
}
|
|
if let channel = getChannel() {
|
|
channel.invokeMethod("onCreateWindow", 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(message: String, messageLevel: Int) {
|
|
var arguments: [String: Any] = ["message": message, "messageLevel": messageLevel]
|
|
if IABController != nil {
|
|
arguments["uuid"] = IABController!.uuid
|
|
}
|
|
if let channel = getChannel() {
|
|
channel.invokeMethod("onConsoleMessage", arguments: arguments)
|
|
}
|
|
}
|
|
|
|
public func onUpdateVisitedHistory(url: String) {
|
|
var arguments: [String: Any?] = [
|
|
"url": url,
|
|
"androidIsReload": nil
|
|
]
|
|
if IABController != nil {
|
|
arguments["uuid"] = IABController!.uuid
|
|
}
|
|
if let channel = getChannel() {
|
|
channel.invokeMethod("onUpdateVisitedHistory", 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("if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {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(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
|
|
if handlerName == "onPrint" {
|
|
printCurrentPage(printCompletionHandler: nil)
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func getChannel() -> FlutterMethodChannel? {
|
|
return (IABController != nil) ? SwiftFlutterPlugin.instance!.channel! : ((channel != nil) ? channel! : nil);
|
|
}
|
|
|
|
public func findAllAsync(find: String?, completionHandler: ((Any?, Error?) -> Void)?) {
|
|
let startSearch = "wkwebview_FindAllAsync('\(find ?? "")');"
|
|
evaluateJavaScript(startSearch, completionHandler: completionHandler)
|
|
}
|
|
|
|
public func findNext(forward: Bool, completionHandler: ((Any?, Error?) -> Void)?) {
|
|
evaluateJavaScript("wkwebview_FindNext(\(forward ? "true" : "false"));", completionHandler: completionHandler)
|
|
}
|
|
|
|
public func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) {
|
|
evaluateJavaScript("wkwebview_ClearMatches();", completionHandler: completionHandler)
|
|
}
|
|
|
|
public func scrollTo(x: Int, y: Int) {
|
|
scrollView.setContentOffset(CGPoint(x: x, y: y), animated: false)
|
|
}
|
|
|
|
public func scrollBy(x: Int, y: Int) {
|
|
let newX = CGFloat(x) + scrollView.contentOffset.x
|
|
let newY = CGFloat(y) + scrollView.contentOffset.y
|
|
scrollView.setContentOffset(CGPoint(x: newX, y: newY), animated: false)
|
|
}
|
|
|
|
|
|
public func pauseTimers() {
|
|
isPausedTimers = true
|
|
let script = "alert();";
|
|
self.evaluateJavaScript(script, completionHandler: nil)
|
|
}
|
|
|
|
public func resumeTimers() {
|
|
if let completionHandler = isPausedTimersCompletionHandler {
|
|
completionHandler()
|
|
isPausedTimersCompletionHandler = nil
|
|
}
|
|
isPausedTimers = false
|
|
}
|
|
|
|
public func printCurrentPage(printCompletionHandler: ((_ completed: Bool, _ error: Error?) -> Void)?) {
|
|
let printController = UIPrintInteractionController.shared
|
|
let printFormatter = self.viewPrintFormatter()
|
|
printController.printFormatter = printFormatter
|
|
|
|
let completionHandler: UIPrintInteractionController.CompletionHandler = { (printController, completed, error) in
|
|
if !completed {
|
|
if let e = error {
|
|
print("[PRINT] Failed: \(e.localizedDescription)")
|
|
} else {
|
|
print("[PRINT] Canceled")
|
|
}
|
|
}
|
|
if let callback = printCompletionHandler {
|
|
callback(completed, error)
|
|
}
|
|
}
|
|
|
|
printController.present(animated: true, completionHandler: completionHandler)
|
|
}
|
|
|
|
public func getContentHeight() -> Int64 {
|
|
return Int64(scrollView.contentSize.height)
|
|
}
|
|
|
|
public func zoomBy(zoomFactor: Float) {
|
|
let currentZoomScale = scrollView.zoomScale
|
|
scrollView.setZoomScale(currentZoomScale * CGFloat(zoomFactor), animated: false)
|
|
}
|
|
|
|
public func getScale() -> Float {
|
|
return Float(scrollView.zoomScale)
|
|
}
|
|
|
|
public func dispose() {
|
|
stopLoading()
|
|
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.removeAllUserScripts()
|
|
removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress))
|
|
removeObserver(self, forKeyPath: #keyPath(WKWebView.url))
|
|
if #available(iOS 11.0, *) {
|
|
configuration.userContentController.removeAllContentRuleLists()
|
|
}
|
|
uiDelegate = nil
|
|
navigationDelegate = nil
|
|
scrollView.delegate = nil
|
|
IABController?.webView = nil
|
|
isPausedTimersCompletionHandler = nil
|
|
super.removeFromSuperview()
|
|
}
|
|
|
|
deinit {
|
|
print("InAppWebView - dealloc")
|
|
}
|
|
}
|