Isolate extension WebView profiles and follow host proxy settings

This commit is contained in:
Levi committed 2026-09-14 00:30:52 +08:00
1 parent cee5f3336e
commit a0e84a75b1
556 files changed
+71097 -31

No files matched your search

@@ -0,0 +1,172 @@
//
// CredentialDatabase.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 29/10/2019.
//
import Foundation
import FlutterMacOS
public class CredentialDatabase: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_credential_database"
var plugin: InAppWebViewFlutterPlugin?
static var credentialStore = URLCredentialStorage.shared
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getAllAuthCredentials":
var allCredentials: [[String: Any?]] = []
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
var crendentials: [[String: Any?]] = []
for c in credentials {
let credential: [String: Any?] = c.value.toMap()
crendentials.append(credential)
}
if crendentials.count > 0 {
let dict: [String : Any] = [
"protectionSpace": protectionSpace.toMap(),
"credentials": crendentials
]
allCredentials.append(dict)
}
}
result(allCredentials)
break
case "getHttpAuthCredentials":
var crendentials: [[String: Any?]] = []
let host = arguments!["host"] as! String
let urlProtocol = arguments!["protocol"] as? String
let urlPort = arguments!["port"] as? Int ?? 0
var realm = arguments!["realm"] as? String;
if let r = realm, r.isEmpty {
realm = nil
}
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
if protectionSpace.host == host && protectionSpace.realm == realm &&
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
for c in credentials {
crendentials.append(c.value.toMap())
}
break
}
}
result(crendentials)
break
case "setHttpAuthCredential":
let host = arguments!["host"] as! String
let urlProtocol = arguments!["protocol"] as? String
let urlPort = arguments!["port"] as? Int ?? 0
var realm = arguments!["realm"] as? String;
if let r = realm, r.isEmpty {
realm = nil
}
let username = arguments!["username"] as! String
let password = arguments!["password"] as! String
let credential = URLCredential(user: username, password: password, persistence: .permanent)
CredentialDatabase.credentialStore.set(credential,
for: URLProtectionSpace(host: host, port: urlPort, protocol: urlProtocol,
realm: realm, authenticationMethod: NSURLAuthenticationMethodHTTPBasic))
result(true)
break
case "removeHttpAuthCredential":
let host = arguments!["host"] as! String
let urlProtocol = arguments!["protocol"] as? String
let urlPort = arguments!["port"] as? Int ?? 0
var realm = arguments!["realm"] as? String;
if let r = realm, r.isEmpty {
realm = nil
}
let username = arguments!["username"] as! String
let password = arguments!["password"] as! String
var credential: URLCredential? = nil;
var protectionSpaceCredential: URLProtectionSpace? = nil
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
if protectionSpace.host == host && protectionSpace.realm == realm &&
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
for c in credentials {
if c.value.user == username, c.value.password == password {
credential = c.value
protectionSpaceCredential = protectionSpace
break
}
}
}
if credential != nil {
break
}
}
if let c = credential, let protectionSpace = protectionSpaceCredential {
CredentialDatabase.credentialStore.remove(c, for: protectionSpace)
}
result(true)
break
case "removeHttpAuthCredentials":
let host = arguments!["host"] as! String
let urlProtocol = arguments!["protocol"] as? String
let urlPort = arguments!["port"] as? Int ?? 0
var realm = arguments!["realm"] as? String;
if let r = realm, r.isEmpty {
realm = nil
}
var credentialsToRemove: [URLCredential] = [];
var protectionSpaceCredential: URLProtectionSpace? = nil
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
if protectionSpace.host == host && protectionSpace.realm == realm &&
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
protectionSpaceCredential = protectionSpace
for c in credentials {
if let _ = c.value.user, let _ = c.value.password {
credentialsToRemove.append(c.value)
}
}
break
}
}
if let protectionSpace = protectionSpaceCredential {
for credential in credentialsToRemove {
CredentialDatabase.credentialStore.remove(credential, for: protectionSpace)
}
}
result(true)
break
case "clearAllAuthCredentials":
for (protectionSpace, credentials) in CredentialDatabase.credentialStore.allCredentials {
for credential in credentials {
CredentialDatabase.credentialStore.remove(credential.value, for: protectionSpace)
}
}
result(true)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,112 @@
//
// FindInteractionChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
import FlutterMacOS
public class FindInteractionChannelDelegate: ChannelDelegate {
private weak var findInteractionController: FindInteractionController?
public init(findInteractionController: FindInteractionController, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.findInteractionController = findInteractionController
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "findAll":
if let findInteractionController = findInteractionController {
let find = arguments!["find"] as! String
findInteractionController.findAll(find: find, completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "findNext":
if let findInteractionController = findInteractionController {
let forward = arguments!["forward"] as! Bool
findInteractionController.findNext(forward: forward, completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "clearMatches":
if let findInteractionController = findInteractionController {
findInteractionController.clearMatches(completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "setSearchText":
if let findInteractionController = findInteractionController {
let searchText = arguments!["searchText"] as? String
findInteractionController.searchText = searchText
result(true)
} else {
result(false)
}
break
case "getSearchText":
result(findInteractionController?.searchText)
break
case "getActiveFindSession":
if let findInteractionController = findInteractionController {
result(findInteractionController.activeFindSession?.toMap())
} else {
result(nil)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onFindResultReceived(activeMatchOrdinal: Int, numberOfMatches: Int, isDoneCounting: Bool) {
if isDoneCounting, let findInteractionController = findInteractionController {
findInteractionController.activeFindSession = FindSession(resultCount: numberOfMatches,
highlightedResultIndex: activeMatchOrdinal,
searchResultDisplayStyle: 2) // matches UIFindSession.SearchResultDisplayStyle.none
}
let arguments: [String : Any?] = [
"activeMatchOrdinal": activeMatchOrdinal,
"numberOfMatches": numberOfMatches,
"isDoneCounting": isDoneCounting
]
channel?.invokeMethod("onFindResultReceived", arguments: arguments)
}
public override func dispose() {
super.dispose()
findInteractionController = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,101 @@
//
// FindInteractionController.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
import FlutterMacOS
public class FindInteractionController: NSObject, Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_";
var plugin: InAppWebViewFlutterPlugin?
var webView: InAppWebView?
var channelDelegate: FindInteractionChannelDelegate?
var settings: FindInteractionSettings?
var shouldCallOnRefresh = false
var searchText: String?
var activeFindSession: FindSession?
public init(plugin: InAppWebViewFlutterPlugin, id: Any, webView: InAppWebView, settings: FindInteractionSettings?) {
super.init()
self.plugin = plugin
self.webView = webView
self.settings = settings
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id),
binaryMessenger: registrar.messenger)
self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel)
}
}
public func prepare() {
// if let settings = settings {
//
// }
}
public func findAll(find: String?, completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
var find = find
if find == nil {
find = searchText
} else {
// updated searchText
searchText = find
}
guard let find else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
if find != "" {
let startSearch = "window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync('\(find)');"
webView.evaluateJavaScript(startSearch, completionHandler: completionHandler)
}
}
public func findNext(forward: Bool, completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler)
}
public func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();", completionHandler: completionHandler)
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
webView = nil
activeFindSession = nil
plugin = nil
}
deinit {
debugPrint("FindInteractionControl - dealloc")
dispose()
}
}
@@ -0,0 +1,25 @@
//
// FindInteractionSettings.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
public class FindInteractionSettings: ISettings<FindInteractionController> {
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> FindInteractionSettings {
let _ = super.parse(settings: settings)
return self
}
override func getRealSettings(obj: FindInteractionController?) -> [String: Any?] {
let realSettings: [String: Any?] = toMap()
return realSettings
}
}
@@ -0,0 +1,29 @@
// Internal Gopeed host bridge. No extension-facing API.
import Foundation
import WebKit
import Network
import FlutterMacOS
final class GopeedProfiles {
private static var stores: [String: WKWebsiteDataStore] = [:]
static func store(_ identifier: String) -> WKWebsiteDataStore? { stores[identifier] }
static func prepare(arguments: NSDictionary?, result: @escaping FlutterResult) {
guard #available(macOS 14.0, iOS 17.0, *),
let identifier = arguments?["gopeedProfileId"] as? String,
let uuid = UUID(uuidString: identifier),
let proxyURL = arguments?["proxyUrl"] as? String,
let url = URL(string: proxyURL), url.scheme == "socks5",
let host = url.host, host == "127.0.0.1",
let port = url.port, port > 0 && port <= 65535 else {
result(FlutterError(code: "UNAVAILABLE", message: "Isolated WebView profiles and proxy require macOS 14 / iOS 17 or newer and valid host configuration", details: nil))
return
}
let store = stores[identifier] ?? WKWebsiteDataStore(forIdentifier: uuid)
let endpoint = nw_endpoint_create_host(host, String(port))
let proxy = nw_proxy_config_create_socksv5(endpoint)
store.__proxyConfigurations = [proxy]
stores[identifier] = store
result(true)
}
}
@@ -0,0 +1,100 @@
//
// HeadlessInAppWebView.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 26/03/21.
//
import Foundation
import FlutterMacOS
public class HeadlessInAppWebView: Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_headless_inappwebview_"
var id: String
var channelDelegate: HeadlessWebViewChannelDelegate?
var flutterWebView: FlutterWebViewController?
var plugin: InAppWebViewFlutterPlugin?
public init(plugin: InAppWebViewFlutterPlugin, id: String, flutterWebView: FlutterWebViewController) {
self.id = id
self.flutterWebView = flutterWebView
self.plugin = plugin
let channel = FlutterMethodChannel(name: HeadlessInAppWebView.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: plugin.registrar!.messenger)
self.channelDelegate = HeadlessWebViewChannelDelegate(headlessWebView: self, channel: channel)
}
public func onWebViewCreated() {
channelDelegate?.onWebViewCreated();
}
public func prepare(params: NSDictionary) {
if let view = flutterWebView?.view() {
view.alphaValue = 0.01
let initialSize = params["initialSize"] as? [String: Any?]
if let size = Size2D.fromMap(map: initialSize) {
setSize(size: size)
} else {
view.frame = CGRect(x: 0.0, y: 0.0, width: NSApplication.shared.mainWindow?.contentView?.bounds.width ?? 0.0,
height: NSApplication.shared.mainWindow?.contentView?.bounds.height ?? 0.0)
}
/// Note: The WKWebView behaves very unreliable when rendering offscreen
/// on a device. This is especially true with JavaScript, which simply
/// won't be executed sometimes.
/// So, add the headless WKWebView to the view hierarchy.
/// This way is also possible to take screenshots.
let wrapperView = NSView() // wrapper view with frame zero
wrapperView.addSubview(view, positioned: .below, relativeTo: nil)
NSApplication.shared.mainWindow?.contentView?.addSubview(wrapperView, positioned: .below, relativeTo: nil)
}
}
public func setSize(size: Size2D) {
if let view = flutterWebView?.view() {
let width = size.width == -1.0 ? NSApplication.shared.mainWindow?.contentView?.bounds.width ?? 0.0 : CGFloat(size.width)
let height = size.height == -1.0 ? NSApplication.shared.mainWindow?.contentView?.bounds.height ?? 0.0 : CGFloat(size.height)
view.frame = CGRect(x: 0.0, y: 0.0, width: width, height: height)
}
}
public func getSize() -> Size2D? {
if let view = flutterWebView?.view() {
return Size2D(width: Double(view.frame.width), height: Double(view.frame.height))
}
return nil
}
public func disposeAndGetFlutterWebView(withFrame frame: CGRect) -> FlutterWebViewController? {
let newFlutterWebView = flutterWebView
if let view = flutterWebView?.view() {
// restore WebView frame and alpha
view.frame = frame
view.alphaValue = 1.0
// remove from parent
view.removeFromSuperview()
dispose(disposeWebView: false)
}
return newFlutterWebView
}
public func dispose(disposeWebView: Bool) {
channelDelegate?.dispose()
channelDelegate = nil
plugin?.headlessInAppWebViewManager?.webViews[id] = nil
if disposeWebView {
flutterWebView?.dispose(removeFromSuperview: true)
}
flutterWebView = nil
plugin = nil
}
public func dispose() {
dispose(disposeWebView: true)
}
deinit {
debugPrint("HeadlessInAppWebView - dealloc")
dispose()
}
}
@@ -0,0 +1,71 @@
//
// HeadlessInAppWebViewManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/2020.
//
import Foundation
import FlutterMacOS
import AppKit
import WebKit
import Foundation
import AVFoundation
public class HeadlessInAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"
var plugin: InAppWebViewFlutterPlugin?
var webViews: [String: HeadlessInAppWebView?] = [:]
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
let id: String = arguments!["id"] as! String
switch call.method {
case "run":
let params = arguments!["params"] as! [String: Any?]
run(id: id, params: params)
result(true)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func run(id: String, params: [String: Any?]) {
guard let plugin = plugin else {
return
}
let flutterWebView = FlutterWebViewController(plugin: plugin,
withFrame: CGRect.zero,
viewIdentifier: id,
params: params as NSDictionary)
let headlessInAppWebView = HeadlessInAppWebView(plugin: plugin, id: id, flutterWebView: flutterWebView)
webViews[id] = headlessInAppWebView
headlessInAppWebView.prepare(params: params as NSDictionary)
headlessInAppWebView.onWebViewCreated()
flutterWebView.makeInitialLoad(params: params as NSDictionary)
}
public override func dispose() {
super.dispose()
let headlessWebViews = webViews.values
headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in
headlessWebView?.dispose()
}
webViews.removeAll()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,64 @@
//
// HeadlessWebViewChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/05/22.
//
import Foundation
import FlutterMacOS
public class HeadlessWebViewChannelDelegate: ChannelDelegate {
private weak var headlessWebView: HeadlessInAppWebView?
public init(headlessWebView: HeadlessInAppWebView, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.headlessWebView = headlessWebView
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "dispose":
if let headlessWebView = headlessWebView {
headlessWebView.dispose()
result(true)
} else {
result(false)
}
break
case "setSize":
if let headlessWebView = headlessWebView {
let sizeMap = arguments!["size"] as? [String: Any?]
if let size = Size2D.fromMap(map: sizeMap) {
headlessWebView.setSize(size: size)
}
result(true)
} else {
result(false)
}
break
case "getSize":
result(headlessWebView?.getSize()?.toMap())
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onWebViewCreated() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onWebViewCreated", arguments: arguments)
}
public override func dispose() {
super.dispose()
headlessWebView = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,45 @@
//
// Options.swift
// flutter_inappwebview
//
// Created by Lorenzo on 26/09/18.
//
import Foundation
@objcMembers
public class ISettings<T>: NSObject {
override init(){
super.init()
}
func parse(settings: [String: Any?]) -> ISettings<T> {
for (key, value) in settings {
if !(value is NSNull), value != nil {
if self.responds(to: Selector(key)) {
self.setValue(value, forKey: key)
} else if self.responds(to: Selector("_" + key)) {
self.setValue(value, forKey: "_" + key)
}
}
}
return self
}
func toMap() -> [String: Any?] {
var settings: [String: Any?] = [:]
let mirrored_object = Mirror(reflecting: self)
for (_, attr) in mirrored_object.children.enumerated() {
if let property_name = attr.label as String? {
settings[property_name] = attr.value
}
}
return settings
}
func getRealSettings(obj: T?) -> [String: Any?] {
let realSettings: [String: Any?] = toMap()
return realSettings
}
}
@@ -0,0 +1,41 @@
//
// InAppBrowserChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/05/22.
//
import Foundation
import FlutterMacOS
public class InAppBrowserChannelDelegate: ChannelDelegate {
public override init(channel: FlutterMethodChannel) {
super.init(channel: channel)
}
public func onBrowserCreated() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onBrowserCreated", arguments: arguments)
}
public func onMenuItemClicked(menuItem: InAppBrowserMenuItem) {
let arguments: [String: Any?] = [
"id": menuItem.id
]
channel?.invokeMethod("onMenuItemClicked", arguments: arguments)
}
public func onMainWindowWillClose() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onMainWindowWillClose", arguments: arguments)
}
public func onExit() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onExit", arguments: arguments)
}
deinit {
dispose()
}
}
@@ -0,0 +1,17 @@
//
// InAppBrowserDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 14/02/21.
//
import Foundation
public protocol InAppBrowserDelegate {
func didChangeTitle(title: String?)
func didStartNavigation(url: URL?)
func didUpdateVisitedHistory(url: URL?)
func didFinishNavigation(url: URL?)
func didFailNavigation(url: URL?, error: Error)
func didChangeProgress(progress: Double)
}
@@ -0,0 +1,122 @@
//
// InAppBrowserManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 18/12/2019.
//
import FlutterMacOS
import AppKit
import WebKit
import Foundation
import AVFoundation
public class InAppBrowserManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappbrowser"
static let WEBVIEW_STORYBOARD = "WebView"
static let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController"
static let NAV_STORYBOARD_CONTROLLER_ID = "navController"
var plugin: InAppWebViewFlutterPlugin?
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "open":
open(arguments: arguments!)
result(true)
break
case "openWithSystemBrowser":
let url = arguments!["url"] as! String
openWithSystemBrowser(url: url, result: result)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func open(arguments: NSDictionary) {
let id = arguments["id"] as! String
let urlRequest = arguments["urlRequest"] as? [String:Any?]
let assetFilePath = arguments["assetFilePath"] as? String
let data = arguments["data"] as? String
let mimeType = arguments["mimeType"] as? String
let encoding = arguments["encoding"] as? String
let baseUrl = arguments["baseUrl"] as? String
let settings = arguments["settings"] as! [String: Any?]
let windowId = arguments["windowId"] as? Int64
let initialUserScripts = arguments["initialUserScripts"] as? [[String: Any]]
let menuItems = arguments["menuItems"] as! [[String: Any?]]
let browserSettings = InAppBrowserSettings()
let _ = browserSettings.parse(settings: settings)
let webViewSettings = InAppWebViewSettings()
let _ = webViewSettings.parse(settings: settings)
let webViewController = InAppBrowserWebViewController()
webViewController.plugin = plugin
webViewController.browserSettings = browserSettings
webViewController.webViewSettings = webViewSettings
webViewController.id = id
webViewController.initialUrlRequest = urlRequest != nil ? URLRequest.init(fromPluginMap: urlRequest!) : nil
webViewController.initialFile = assetFilePath
webViewController.initialData = data
webViewController.initialMimeType = mimeType
webViewController.initialEncoding = encoding
webViewController.initialBaseUrl = baseUrl
webViewController.windowId = windowId
webViewController.initialUserScripts = initialUserScripts ?? []
webViewController.isHidden = browserSettings.hidden
let window = InAppBrowserWindow(contentViewController: webViewController)
window.browserSettings = browserSettings
window.contentViewController = webViewController
for menuItem in menuItems {
window.menuItems.append(InAppBrowserMenuItem.fromMap(map: menuItem)!)
}
window.prepare()
if #available(macOS 10.12, *), browserSettings.windowType == .tabbed {
NSApplication.shared.mainWindow?.addTabbedWindow(window, ordered: .above)
} else if browserSettings.windowType == .child {
NSApplication.shared.mainWindow?.addChildWindow(window, ordered: .above)
} else {
window.windowController?.showWindow(self)
}
window.makeKeyAndOrderFront(self)
if browserSettings.hidden {
// https://github.com/pichillilorenzo/flutter_inappwebview/issues/1939
// without calling first window.makeKeyAndOrderFront(self)
// window.hide() would deallocate and dispose the InAppBrowserWindow
window.hide()
NSApplication.shared.mainWindow?.makeKeyAndOrderFront(self)
}
}
public func openWithSystemBrowser(url: String, result: @escaping FlutterResult) {
let absoluteUrl = URL(string: url)!.absoluteURL
if !NSWorkspace.shared.open(absoluteUrl) {
result(FlutterError(code: "InAppBrowserManager", message: url + " cannot be opened!", details: nil))
return
}
result(true)
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,91 @@
//
// InAppBrowserOptions.swift
// flutter_inappwebview
//
// Created by Lorenzo on 17/09/18.
//
import Foundation
@objcMembers
public class InAppBrowserSettings: ISettings<InAppBrowserWebViewController> {
var hidden = false
var hideToolbarTop = true
var toolbarTopBackgroundColor: String?
var hideUrlBar = false
var hideProgressBar = false
var toolbarTopFixedTitle: String?
var windowType = InAppBrowserWindowType.window
var windowAlphaValue = 1.0
var _windowStyleMask: NSNumber?
var windowStyleMask: NSWindow.StyleMask? {
get {
return _windowStyleMask != nil ?
NSWindow.StyleMask.init(rawValue: _windowStyleMask!.uintValue) :
nil
}
set {
if let newValue = newValue {
_windowStyleMask = NSNumber.init(value: newValue.rawValue)
} else {
_windowStyleMask = nil
}
}
}
var _windowTitlebarSeparatorStyle: NSNumber?
@available(macOS 11.0, *)
var windowTitlebarSeparatorStyle: NSTitlebarSeparatorStyle? {
get {
return _windowTitlebarSeparatorStyle != nil ?
NSTitlebarSeparatorStyle.init(rawValue: _windowTitlebarSeparatorStyle!.intValue) :
nil
}
set {
if let newValue = newValue {
_windowTitlebarSeparatorStyle = NSNumber.init(value: newValue.rawValue)
} else {
_windowTitlebarSeparatorStyle = nil
}
}
}
var windowFrame: NSRect?
var hideDefaultMenuItems = false
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> InAppBrowserSettings {
let _ = super.parse(settings: settings)
if let windowType = settings["windowType"] as? String {
self.windowType = InAppBrowserWindowType.init(rawValue: windowType) ?? InAppBrowserWindowType.child
}
if let windowFrame = settings["windowFrame"] as? [String:Double] {
self.windowFrame = NSRect(x: windowFrame["x"]!,
y: windowFrame["y"]!,
width: windowFrame["width"]!,
height: windowFrame["height"]!)
}
return self
}
override func getRealSettings(obj: InAppBrowserWebViewController?) -> [String: Any?] {
var realOptions: [String: Any?] = toMap()
realOptions["windowType"] = windowType.rawValue
if let inAppBrowserWebViewController = obj {
realOptions["hidden"] = inAppBrowserWebViewController.isHidden
realOptions["hideUrlBar"] = inAppBrowserWebViewController.window?.searchBar?.isHidden
realOptions["hideProgressBar"] = inAppBrowserWebViewController.progressBar.isHidden
realOptions["hideToolbarTop"] = !(inAppBrowserWebViewController.window?.toolbar?.isVisible ?? true)
realOptions["toolbarTopBackgroundColor"] = inAppBrowserWebViewController.window?.backgroundColor.hexString
realOptions["windowAlphaValue"] = inAppBrowserWebViewController.window?.alphaValue
realOptions["windowStyleMask"] = inAppBrowserWebViewController.window?.styleMask.rawValue
if #available(macOS 11.0, *) {
realOptions["windowTitlebarSeparatorStyle"] = inAppBrowserWebViewController.window?.titlebarSeparatorStyle.rawValue
}
realOptions["windowFrame"] = inAppBrowserWebViewController.window?.frame.toMap()
}
return realOptions
}
}
@@ -0,0 +1,339 @@
//
// InAppBrowserWebViewController.swift
// flutter_inappwebview
//
// Created by Lorenzo on 17/09/18.
//
import FlutterMacOS
import AppKit
import WebKit
import Foundation
public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelegate, Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_";
var progressBar: NSProgressIndicator!
var window: InAppBrowserWindow?
var id: String = ""
var plugin: InAppWebViewFlutterPlugin?
var windowId: Int64?
var webView: InAppWebView?
var channelDelegate: InAppBrowserChannelDelegate?
var initialUrlRequest: URLRequest?
var initialFile: String?
var browserSettings: InAppBrowserSettings?
var webViewSettings: InAppWebViewSettings?
var initialData: String?
var initialMimeType: String?
var initialEncoding: String?
var initialBaseUrl: String?
var initialUserScripts: [[String: Any]] = []
var isHidden = false
public override func loadView() {
guard let plugin = plugin, let registrar = plugin.registrar else {
return
}
let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: registrar.messenger)
channelDelegate = InAppBrowserChannelDelegate(channel: channel)
var userScripts: [UserScript] = []
for initialUserScript in initialUserScripts {
userScripts.append(UserScript.fromMap(map: initialUserScript, windowId: windowId)!)
}
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings)
if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] {
webView = webViewTransport.webView
webView!.initialUserScripts = userScripts
} else {
webView = InAppWebView(id: nil,
plugin: nil,
frame: .zero,
configuration: preWebviewConfiguration,
userScripts: userScripts)
}
guard let webView = webView else {
return
}
webView.inAppBrowserDelegate = self
webView.id = id
webView.plugin = plugin
webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel)
let findInteractionController = FindInteractionController(
plugin: plugin,
id: id, webView: webView, settings: nil)
webView.findInteractionController = findInteractionController
findInteractionController.prepare()
prepareWebView()
webView.windowCreated = true
progressBar = NSProgressIndicator()
progressBar.style = .bar
progressBar.isIndeterminate = false
progressBar.startAnimation(self)
view = NSView(frame: NSApplication.shared.mainWindow?.frame ?? .zero)
view.addSubview(webView)
view.addSubview(progressBar, positioned: .above, relativeTo: webView)
}
public override func viewDidLoad() {
super.viewDidLoad()
webView?.translatesAutoresizingMaskIntoConstraints = false
progressBar.translatesAutoresizingMaskIntoConstraints = false
webView?.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true
webView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true
webView?.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
webView?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
progressBar.topAnchor.constraint(equalTo: self.view.topAnchor, constant: -6.0).isActive = true
progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
progressBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
if let wId = windowId {
channelDelegate?.onBrowserCreated()
webView?.runWindowBeforeCreatedCallbacks()
} else {
if #available(macOS 10.13, *) {
if let contentBlockers = webView?.settings?.contentBlockers, contentBlockers.count > 0 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: .utf8)
WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "ContentBlockingRules",
encodedContentRuleList: blockRules) { (contentRuleList, error) in
if let error = error {
print(error.localizedDescription)
return
}
let configuration = self.webView!.configuration
configuration.userContentController.add(contentRuleList!)
self.initLoad()
}
return
} catch {
print(error.localizedDescription)
}
}
}
initLoad()
}
}
public override func viewDidAppear() {
super.viewDidAppear()
window = view.window as? InAppBrowserWindow
}
public func initLoad() {
if let initialFile = initialFile {
do {
try webView?.loadFile(assetFilePath: initialFile)
}
catch let error as NSError {
dump(error)
}
}
else if let initialData = initialData {
let baseUrl = URL(string: initialBaseUrl ?? "about:blank")!
var allowingReadAccessToURL: URL? = nil
if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, baseUrl.scheme == "file" {
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
if allowingReadAccessToURL?.scheme != "file" {
allowingReadAccessToURL = nil
}
}
webView?.loadData(data: initialData, mimeType: initialMimeType!, encoding: initialEncoding!, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL)
}
else if let initialUrlRequest = initialUrlRequest {
var allowingReadAccessToURL: URL? = nil
if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, let url = initialUrlRequest.url, url.scheme == "file" {
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
if allowingReadAccessToURL?.scheme != "file" {
allowingReadAccessToURL = nil
}
}
webView?.loadUrl(urlRequest: initialUrlRequest, allowingReadAccessTo: allowingReadAccessToURL)
}
channelDelegate?.onBrowserCreated()
}
public func prepareWebView() {
webView?.settings = webViewSettings
webView?.prepare()
if let browserSettings = browserSettings {
if browserSettings.hideProgressBar {
progressBar.isHidden = true
}
}
}
public func didChangeTitle(title: String?) {
guard let title = title else {
return
}
if let browserSettings = browserSettings,
let toolbarTopFixedTitle = browserSettings.toolbarTopFixedTitle {
window?.title = toolbarTopFixedTitle
} else {
window?.title = title
}
window?.update()
}
public func didStartNavigation(url: URL?) {
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
window?.backButton?.isEnabled = webView?.canGoBack ?? false
progressBar.doubleValue = 0.0
progressBar.isHidden = false
guard let url = url else {
return
}
window?.searchBar?.stringValue = url.absoluteString
}
public func didUpdateVisitedHistory(url: URL?) {
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
window?.backButton?.isEnabled = webView?.canGoBack ?? false
guard let url = url else {
return
}
window?.searchBar?.stringValue = url.absoluteString
}
public func didFinishNavigation(url: URL?) {
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
window?.backButton?.isEnabled = webView?.canGoBack ?? false
progressBar.doubleValue = 0.0
progressBar.isHidden = true
guard let url = url else {
return
}
window?.searchBar?.stringValue = url.absoluteString
}
public func didFailNavigation(url: URL?, error: Error) {
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
window?.backButton?.isEnabled = webView?.canGoBack ?? false
progressBar.doubleValue = 0.0
progressBar.isHidden = true
}
public func didChangeProgress(progress: Double) {
progressBar.isHidden = false
progressBar.doubleValue = progress * 100
if progress == 100.0 {
progressBar.isHidden = true
}
}
@objc public func reload() {
webView?.reload()
didUpdateVisitedHistory(url: webView?.url)
}
@objc public func goBack() {
if let webView = webView, webView.canGoBack {
webView.goBack()
}
}
@objc public func goForward() {
if let webView = webView, webView.canGoForward {
webView.goForward()
}
}
@objc public func goBackOrForward(steps: Int) {
webView?.goBackOrForward(steps: steps)
}
@objc public func onMenuItemClicked(sender: Any?) {
var identifier: String?
if let sender = sender as? NSMenuItem {
identifier = sender.identifier?.rawValue
}
if identifier == nil, let sender = sender as? NSButton {
identifier = sender.identifier?.rawValue
}
if let identifier = identifier, let window = window {
let menuItem = window.menuItems.first { item in
return item.id == Int64(identifier)
}
if let menuItem = menuItem {
channelDelegate?.onMenuItemClicked(menuItem: menuItem)
}
}
}
public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) {
window?.setSettings(newSettings: newSettings, newSettingsMap: newSettingsMap)
let newInAppWebViewSettings = InAppWebViewSettings()
let _ = newInAppWebViewSettings.parse(settings: newSettingsMap)
webView?.setSettings(newSettings: newInAppWebViewSettings, newSettingsMap: newSettingsMap)
if newSettingsMap["hideProgressBar"] != nil, browserSettings?.hideProgressBar != newSettings.hideProgressBar {
progressBar.isHidden = newSettings.hideProgressBar
}
browserSettings = newSettings
webViewSettings = newInAppWebViewSettings
}
public func getSettings() -> [String: Any?]? {
let webViewSettingsMap = webView?.getSettings()
if (self.browserSettings == nil || webViewSettingsMap == nil) {
return nil
}
var settingsMap = self.browserSettings!.getRealSettings(obj: self)
settingsMap.merge(webViewSettingsMap!, uniquingKeysWith: { (current, _) in current })
return settingsMap
}
public func hide() {
isHidden = true
window?.hide()
}
public func show() {
isHidden = false
window?.show()
}
public func close() {
window?.close()
}
public func dispose() {
channelDelegate?.onExit()
channelDelegate?.dispose()
channelDelegate = nil
webView?.dispose()
webView?.removeFromSuperview()
webView = nil
window = nil
plugin = nil
}
deinit {
debugPrint("InAppBrowserWebViewController - dealloc")
dispose()
}
}
@@ -0,0 +1,387 @@
//
// InAppBrowserNavigationController.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 14/02/21.
//
import Foundation
struct ToolbarIdentifiers {
static let searchBar = NSToolbarItem.Identifier(rawValue: "SearchBar")
static let backButton = NSToolbarItem.Identifier(rawValue: "BackButton")
static let forwardButton = NSToolbarItem.Identifier(rawValue: "ForwardButton")
static let reloadButton = NSToolbarItem.Identifier(rawValue: "ReloadButton")
static let menuButton = NSToolbarItem.Identifier(rawValue: "MenuButton")
}
public class InAppBrowserWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate, NSSearchFieldDelegate {
var searchItem: NSToolbarItem?
var backItem: NSToolbarItem?
var forwardItem: NSToolbarItem?
var reloadItem: NSToolbarItem?
var menuItem: NSToolbarItem?
var actionItems: [NSToolbarItem] = []
var reloadButton: NSButton? {
get {
return reloadItem?.view as? NSButton
}
}
var backButton: NSButton? {
get {
return backItem?.view as? NSButton
}
}
var forwardButton: NSButton? {
get {
return forwardItem?.view as? NSButton
}
}
var searchBar: NSSearchField? {
get {
if #available(macOS 11.0, *), let searchItem = searchItem as? NSSearchToolbarItem {
return searchItem.searchField
} else {
return searchItem?.view as? NSSearchField
}
}
}
var menuButton: NSPopUpButton? {
get {
return menuItem?.view as? NSPopUpButton
}
}
var browserSettings: InAppBrowserSettings?
var menuItems: [InAppBrowserMenuItem] = []
public func prepare() {
title = ""
collectionBehavior = .fullScreenPrimary
delegate = self
NotificationCenter.default.addObserver(self,
selector: #selector(onMainWindowWillClose(_:)),
name: NSWindow.willCloseNotification,
object: NSApplication.shared.mainWindow)
if #available(macOS 10.13, *) {
let windowToolbar = NSToolbar()
windowToolbar.delegate = self
if #available(macOS 11.0, *) {
searchItem = NSSearchToolbarItem(itemIdentifier: ToolbarIdentifiers.searchBar)
(searchItem as! NSSearchToolbarItem).searchField.delegate = self
toolbarStyle = .expanded
} else {
searchItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.searchBar)
let textField = NSSearchField()
textField.usesSingleLineMode = true
textField.delegate = self
searchItem?.view = textField
}
searchItem?.label = ""
windowToolbar.displayMode = .default
backItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.backButton)
backItem?.label = ""
if let webViewController = contentViewController as? InAppBrowserWebViewController {
if #available(macOS 11.0, *) {
backItem?.view = NSButton(image: NSImage(systemSymbolName: "chevron.left",
accessibilityDescription: "Go Back")!,
target: webViewController,
action: #selector(InAppBrowserWebViewController.goBack))
} else {
backItem?.view = NSButton(title: "\u{2039}",
target: webViewController,
action: #selector(InAppBrowserWebViewController.goBack))
}
}
forwardItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.forwardButton)
forwardItem?.label = ""
if let webViewController = contentViewController as? InAppBrowserWebViewController {
if #available(macOS 11.0, *) {
forwardItem?.view = NSButton(image: NSImage(systemSymbolName: "chevron.right",
accessibilityDescription: "Go Forward")!,
target: webViewController,
action: #selector(InAppBrowserWebViewController.goForward))
} else {
forwardItem?.view = NSButton(title: "\u{203A}",
target: webViewController,
action: #selector(InAppBrowserWebViewController.goForward))
}
}
reloadItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.reloadButton)
reloadItem?.label = ""
if let webViewController = contentViewController as? InAppBrowserWebViewController {
if #available(macOS 11.0, *) {
reloadItem?.view = NSButton(image: NSImage(systemSymbolName: "arrow.counterclockwise",
accessibilityDescription: "Reload")!,
target: webViewController,
action: #selector(InAppBrowserWebViewController.reload))
} else {
reloadItem?.view = NSButton(title: "Reload",
target: webViewController,
action: #selector(InAppBrowserWebViewController.reload))
}
}
if #available(macOS 10.15, *), !menuItems.isEmpty {
menuItem = NSMenuToolbarItem(itemIdentifier: ToolbarIdentifiers.menuButton)
if let menuItem = menuItem as? NSMenuToolbarItem {
menuItem.label = ""
if #available(macOS 11.0, *) {
menuItem.image = NSImage(systemSymbolName: "ellipsis.circle",
accessibilityDescription: "Options")
menuItem.showsIndicator = true
menuItem.isBordered = true
} else {
menuItem.title = "Options"
}
let menu = NSMenu()
menuItems = menuItems.sorted(by: {$0.order ?? 0 < $1.order ?? 0})
for item in menuItems {
if !item.showAsAction {
let nsItem = NSMenuItem(title: item.title, action: #selector(InAppBrowserWebViewController.onMenuItemClicked), keyEquivalent: "")
nsItem.identifier = NSUserInterfaceItemIdentifier.init(String(item.id))
nsItem.image = item.icon
menu.addItem(nsItem)
} else {
let actionItem = NSMenuToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: String(item.id)))
actionItem.label = ""
if let webViewController = contentViewController as? InAppBrowserWebViewController {
let actionButton = NSButton(title: item.title,
target: webViewController,
action: #selector(InAppBrowserWebViewController.onMenuItemClicked))
actionButton.identifier = NSUserInterfaceItemIdentifier.init(String(item.id))
actionButton.image = item.icon
actionItem.view = actionButton
}
actionItems.append(actionItem)
}
}
menuItem.menu = menu
}
}
if #available(macOS 10.14, *) {
windowToolbar.centeredItemIdentifier = ToolbarIdentifiers.searchBar
}
toolbar = windowToolbar
}
forwardButton?.isEnabled = false
backButton?.isEnabled = false
if let browserSettings = browserSettings {
if let toolbarTopFixedTitle = browserSettings.toolbarTopFixedTitle {
title = toolbarTopFixedTitle
}
if !browserSettings.hideToolbarTop {
toolbar?.isVisible = true
if browserSettings.hideUrlBar {
if #available(macOS 11.0, *) {
(searchItem as! NSSearchToolbarItem).searchField.isHidden = true
} else {
searchItem?.view?.isHidden = true
}
}
if let bgColor = browserSettings.toolbarTopBackgroundColor, !bgColor.isEmpty {
backgroundColor = NSColor(hexString: bgColor)
}
}
else {
toolbar?.isVisible = false
}
if #available(macOS 11.0, *), let windowTitlebarSeparatorStyle = browserSettings.windowTitlebarSeparatorStyle {
titlebarSeparatorStyle = windowTitlebarSeparatorStyle
}
alphaValue = browserSettings.windowAlphaValue
if let windowStyleMask = browserSettings.windowStyleMask {
styleMask = windowStyleMask
}
if let windowFrame = browserSettings.windowFrame {
setFrame(windowFrame, display: true)
}
reloadItem?.view?.isHidden = browserSettings.hideDefaultMenuItems
backItem?.view?.isHidden = browserSettings.hideDefaultMenuItems
forwardItem?.view?.isHidden = browserSettings.hideDefaultMenuItems
}
}
public func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [[ ToolbarIdentifiers.menuButton,
ToolbarIdentifiers.searchBar,
ToolbarIdentifiers.backButton,
ToolbarIdentifiers.forwardButton,
ToolbarIdentifiers.reloadButton,
.flexibleSpace ], actionItems.compactMap({ item in
return item.itemIdentifier
})].flatMap { $0 }
}
public func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [[.flexibleSpace,
ToolbarIdentifiers.searchBar,
.flexibleSpace,
ToolbarIdentifiers.reloadButton,
ToolbarIdentifiers.backButton,
ToolbarIdentifiers.forwardButton],
actionItems.compactMap({ item in
return item.itemIdentifier
}),
[ToolbarIdentifiers.menuButton]].flatMap { $0 }
}
public func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
switch(itemIdentifier) {
case ToolbarIdentifiers.searchBar:
return searchItem
case ToolbarIdentifiers.backButton:
return backItem
case ToolbarIdentifiers.forwardButton:
return forwardItem
case ToolbarIdentifiers.reloadButton:
return reloadItem
case ToolbarIdentifiers.menuButton:
return menuItem
default:
let actionItem = actionItems.first { item in
return item.itemIdentifier == itemIdentifier
}
return actionItem
}
}
public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
// ENTER key
var searchField: NSSearchField? = nil
if #available(macOS 11.0, *), let searchBar = searchItem as? NSSearchToolbarItem {
searchField = searchBar.searchField
} else if let searchBar = searchItem {
searchField = searchBar.view as? NSSearchField
}
guard let searchField,
let urlEncoded = searchField.stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: urlEncoded) else {
return false
}
let request = URLRequest(url: url)
(contentViewController as? InAppBrowserWebViewController)?.webView?.load(request)
return true
}
return false
}
public func hide() {
orderOut(self)
}
public func show() {
let mainWindow = parent ?? NSApplication.shared.mainWindow
if #available(macOS 10.12, *),
!(mainWindow?.tabbedWindows?.contains(self) ?? false),
browserSettings?.windowType == .tabbed {
mainWindow?.addTabbedWindow(self, ordered: .above)
} else if !(mainWindow?.childWindows?.contains(self) ?? false) {
mainWindow?.addChildWindow(self, ordered: .above)
}
makeKeyAndOrderFront(self)
NSApplication.shared.activate(ignoringOtherApps: true)
}
public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) {
if newSettingsMap["hidden"] != nil, browserSettings?.hidden != newSettings.hidden {
if newSettings.hidden {
hide()
}
else {
show()
}
}
if newSettingsMap["hideUrlBar"] != nil, browserSettings?.hideUrlBar != newSettings.hideUrlBar {
searchBar?.isHidden = newSettings.hideUrlBar
}
if newSettingsMap["hideToolbarTop"] != nil, browserSettings?.hideToolbarTop != newSettings.hideToolbarTop {
toolbar?.isVisible = !newSettings.hideToolbarTop
}
if newSettingsMap["toolbarTopBackgroundColor"] != nil, browserSettings?.toolbarTopBackgroundColor != newSettings.toolbarTopBackgroundColor {
if let bgColor = newSettings.toolbarTopBackgroundColor, !bgColor.isEmpty {
backgroundColor = NSColor(hexString: bgColor)
} else {
backgroundColor = nil
}
}
if #available(macOS 11.0, *), newSettingsMap["windowTitlebarSeparatorStyle"] != nil,
browserSettings?.windowTitlebarSeparatorStyle != newSettings.windowTitlebarSeparatorStyle {
titlebarSeparatorStyle = newSettings.windowTitlebarSeparatorStyle!
}
if newSettingsMap["windowAlphaValue"] != nil, browserSettings?.windowAlphaValue != newSettings.windowAlphaValue {
alphaValue = newSettings.windowAlphaValue
}
if newSettingsMap["windowStyleMask"] != nil, browserSettings?.windowStyleMask != newSettings.windowStyleMask {
styleMask = newSettings.windowStyleMask!
}
if newSettingsMap["windowFrame"] != nil, browserSettings?.windowFrame != newSettings.windowFrame {
setFrame(newSettings.windowFrame!, display: true)
}
if newSettingsMap["hideDefaultMenuItems"] != nil, browserSettings?.hideDefaultMenuItems != newSettings.hideDefaultMenuItems {
reloadItem?.view?.isHidden = newSettings.hideDefaultMenuItems
backItem?.view?.isHidden = newSettings.hideDefaultMenuItems
forwardItem?.view?.isHidden = newSettings.hideDefaultMenuItems
}
browserSettings = newSettings
}
public func windowWillClose(_ notification: Notification) {
dispose()
}
@objc func onMainWindowWillClose(_ notification: Notification) {
if let webViewController = contentViewController as? InAppBrowserWebViewController {
webViewController.channelDelegate?.onMainWindowWillClose()
}
}
public func dispose() {
delegate = nil
NotificationCenter.default.removeObserver(self,
name: NSWindow.willCloseNotification,
object: NSApplication.shared.mainWindow)
if let webViewController = contentViewController as? InAppBrowserWebViewController {
webViewController.dispose()
}
if #available(macOS 11.0, *) {
(searchItem as? NSSearchToolbarItem)?.searchField.delegate = nil
} else {
(searchItem?.view as? NSTextField)?.delegate = nil
searchItem?.view = nil
}
searchItem = nil
(backItem?.view as? NSButton)?.target = nil
backItem?.view = nil
backItem = nil
(forwardItem?.view as? NSButton)?.target = nil
forwardItem?.view = nil
forwardItem = nil
(reloadItem?.view as? NSButton)?.target = nil
reloadItem?.view = nil
reloadItem = nil
}
deinit {
debugPrint("InAppBrowserWindow - dealloc")
dispose()
}
}
@@ -0,0 +1,17 @@
//
// ContextMenuOptions.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 30/05/2020.
//
import Foundation
public class ContextMenuSettings: ISettings<NSObject> {
var hideDefaultSystemContextMenuItems = false;
override init(){
super.init()
}
}
@@ -0,0 +1,45 @@
//
// CustomSchemeHandler.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 25/10/2019.
//
import FlutterMacOS
import Foundation
import WebKit
@available(macOS 10.13, *)
public class CustomSchemeHandler: NSObject, WKURLSchemeHandler {
var schemeHandlers: [Int: WKURLSchemeTask] = [:]
public func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
schemeHandlers[urlSchemeTask.hash] = urlSchemeTask
let inAppWebView = webView as! InAppWebView
let request = WebResourceRequest.init(fromURLRequest: urlSchemeTask.request)
let callback = WebViewChannelDelegate.LoadResourceWithCustomSchemeCallback()
callback.nonNullSuccess = { (response: CustomSchemeResponse) in
if (self.schemeHandlers[urlSchemeTask.hash] != nil) {
let urlResponse = URLResponse(url: request.url, mimeType: response.contentType, expectedContentLength: -1, textEncodingName: response.contentEncoding)
urlSchemeTask.didReceive(urlResponse)
urlSchemeTask.didReceive(response.data)
urlSchemeTask.didFinish()
self.schemeHandlers.removeValue(forKey: urlSchemeTask.hash)
}
return false
}
callback.error = { (code: String, message: String?, details: Any?) in
print(code + ", " + (message ?? ""))
}
if let channelDelegate = inAppWebView.channelDelegate {
channelDelegate.onLoadResourceWithCustomScheme(request: request, callback: callback)
} else {
callback.defaultBehaviour(nil)
}
}
public func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
schemeHandlers.removeValue(forKey: urlSchemeTask.hash)
}
}
@@ -0,0 +1,202 @@
//
// FlutterWebViewController.swift
// flutter_inappwebview
//
// Created by Lorenzo on 13/11/18.
//
import Foundation
import WebKit
import FlutterMacOS
public class FlutterWebViewController: NSView, Disposable {
var keepAliveId: String?
init(plugin: InAppWebViewFlutterPlugin, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) {
super.init(frame: frame)
keepAliveId = params["keepAliveId"] as? String
let initialSettings = params["initialSettings"] as! [String: Any?]
let windowId = params["windowId"] as? Int64
let initialUserScripts = params["initialUserScripts"] as? [[String: Any]]
var userScripts: [UserScript] = []
if let initialUserScripts = initialUserScripts {
for initialUserScript in initialUserScripts {
userScripts.append(UserScript.fromMap(map: initialUserScript, windowId: windowId)!)
}
}
let settings = InAppWebViewSettings()
let _ = settings.parse(settings: initialSettings)
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: settings)
var webView: InAppWebView?
if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] {
webView = webViewTransport.webView
webView!.id = viewId
webView!.plugin = plugin
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId),
binaryMessenger: registrar.messenger)
webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel)
}
webView!.frame = self.bounds
webView!.initialUserScripts = userScripts
} else {
webView = InAppWebView(id: viewId,
plugin: plugin,
frame: self.bounds,
configuration: preWebviewConfiguration,
userScripts: userScripts)
}
let findInteractionController = FindInteractionController(
plugin: plugin,
id: viewId, webView: webView!, settings: nil)
webView!.findInteractionController = findInteractionController
findInteractionController.prepare()
webView!.autoresizingMask = [.width, .height]
self.autoresizesSubviews = true
self.autoresizingMask = [.width, .height]
self.addSubview(webView!)
webView!.settings = settings
webView!.prepare()
webView!.windowCreated = true
}
required init?(coder nsCoder: NSCoder) {
super.init(coder: nsCoder)
}
public func webView() -> InAppWebView? {
for subview in self.subviews
{
if let item = subview as? InAppWebView
{
return item
}
}
return nil
}
public func view() -> NSView {
return self
}
public func makeInitialLoad(params: NSDictionary) {
guard let webView = webView() else {
return
}
let windowId = params["windowId"] as? Int64
let initialUrlRequest = params["initialUrlRequest"] as? [String: Any?]
let initialFile = params["initialFile"] as? String
let initialData = params["initialData"] as? [String: String?]
if windowId == nil {
if #available(macOS 10.13, *) {
webView.configuration.userContentController.removeAllContentRuleLists()
if let contentBlockers = webView.settings?.contentBlockers, contentBlockers.count > 0 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: .utf8)
WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "ContentBlockingRules",
encodedContentRuleList: blockRules) { (contentRuleList, error) in
if let error = error {
print(error.localizedDescription)
return
}
let configuration = webView.configuration
configuration.userContentController.add(contentRuleList!)
self.load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData)
}
return
} catch {
print(error.localizedDescription)
}
}
}
load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData)
}
else if let wId = windowId {
webView.runWindowBeforeCreatedCallbacks()
}
}
func load(initialUrlRequest: [String:Any?]?, initialFile: String?, initialData: [String: String?]?) {
guard let webView = webView() else {
return
}
if let initialFile = initialFile {
do {
try webView.loadFile(assetFilePath: initialFile)
}
catch let error as NSError {
dump(error)
}
}
else if let initialData = initialData, let data = initialData["data"]!,
let mimeType = initialData["mimeType"]!, let encoding = initialData["encoding"]!,
let baseUrl = URL(string: initialData["baseUrl"]! ?? "about:blank") {
var allowingReadAccessToURL: URL? = nil
if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, baseUrl.scheme == "file" {
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
if allowingReadAccessToURL?.scheme != "file" {
allowingReadAccessToURL = nil
}
}
webView.loadData(data: data,
mimeType: mimeType,
encoding: encoding,
baseUrl: baseUrl,
allowingReadAccessTo: allowingReadAccessToURL)
}
else if let initialUrlRequest = initialUrlRequest {
let urlRequest = URLRequest.init(fromPluginMap: initialUrlRequest)
var allowingReadAccessToURL: URL? = nil
if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, let url = urlRequest.url, url.scheme == "file" {
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
if allowingReadAccessToURL?.scheme != "file" {
allowingReadAccessToURL = nil
}
}
webView.loadUrl(urlRequest: urlRequest, allowingReadAccessTo: allowingReadAccessToURL)
}
}
// method added to fix:
// https://github.com/pichillilorenzo/flutter_inappwebview/issues/1837
public func dispose(removeFromSuperview: Bool) {
if keepAliveId == nil {
if let webView = webView() {
webView.dispose()
if removeFromSuperview {
webView.removeFromSuperview()
}
}
if removeFromSuperview {
self.removeFromSuperview()
}
}
}
public func dispose() {
dispose(removeFromSuperview: false)
}
deinit {
debugPrint("FlutterWebViewController - dealloc")
dispose()
}
}
@@ -0,0 +1,72 @@
//
// FlutterWebViewFactory.swift
// flutter_inappwebview
//
// Created by Lorenzo on 13/11/18.
//
import SwiftUI
import Cocoa
import FlutterMacOS
import Foundation
public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
static let VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview"
private var plugin: InAppWebViewFlutterPlugin
init(plugin: InAppWebViewFlutterPlugin) {
self.plugin = plugin
super.init()
}
public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {
return FlutterStandardMessageCodec.sharedInstance()
}
public func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView {
let arguments = args as? NSDictionary
var flutterWebView: FlutterWebViewController?
var id: Any = viewId
let keepAliveId = arguments?["keepAliveId"] as? String
let headlessWebViewId = arguments?["headlessWebViewId"] as? String
if let headlessWebViewId = headlessWebViewId,
let headlessWebView = plugin.headlessInAppWebViewManager?.webViews[headlessWebViewId],
let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: .zero) {
flutterWebView = platformView
flutterWebView?.keepAliveId = keepAliveId
}
if let keepAliveId = keepAliveId,
flutterWebView == nil,
let keepAliveWebView = plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] {
flutterWebView = keepAliveWebView
if let view = flutterWebView?.view() {
// remove from parent
view.removeFromSuperview()
}
}
let shouldMakeInitialLoad = flutterWebView == nil
if flutterWebView == nil {
if let keepAliveId = keepAliveId {
id = keepAliveId
}
flutterWebView = FlutterWebViewController(plugin: plugin,
withFrame: .zero,
viewIdentifier: id,
params: arguments!)
}
if let keepAliveId = keepAliveId {
plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] = flutterWebView!
}
if shouldMakeInitialLoad {
flutterWebView?.makeInitialLoad(params: arguments!)
}
return flutterWebView!
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,124 @@
//
// InAppWebViewManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/12/2019.
//
import Foundation
import WebKit
import FlutterMacOS
public class InAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"
var plugin: InAppWebViewFlutterPlugin?
var webViewForUserAgent: WKWebView?
var defaultUserAgent: String?
var keepAliveWebViews: [String:FlutterWebViewController?] = [:]
var windowWebViews: [Int64:WebViewTransport] = [:]
var windowAutoincrementId: Int64 = 0
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: InAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getDefaultUserAgent":
getDefaultUserAgent(completionHandler: { (value) in
result(value)
})
break
case "handlesURLScheme":
let urlScheme = arguments!["urlScheme"] as! String
if #available(macOS 10.13, *) {
result(WKWebView.handlesURLScheme(urlScheme))
} else {
result(false)
}
break
case "disposeKeepAlive":
let keepAliveId = arguments!["keepAliveId"] as! String
disposeKeepAlive(keepAliveId: keepAliveId)
result(true)
break
case "clearAllCache":
let includeDiskFiles = arguments!["includeDiskFiles"] as! Bool
clearAllCache(includeDiskFiles: includeDiskFiles, completionHandler: {
result(true)
})
default:
result(FlutterMethodNotImplemented)
break
}
}
public func getDefaultUserAgent(completionHandler: @escaping (_ value: String?) -> Void) {
if defaultUserAgent == nil {
webViewForUserAgent = WKWebView()
webViewForUserAgent?.evaluateJavaScript("navigator.userAgent") { (value, error) in
if error != nil {
print("Error occurred to get userAgent")
self.webViewForUserAgent = nil
completionHandler(nil)
return
}
if let unwrappedUserAgent = value as? String {
self.defaultUserAgent = unwrappedUserAgent
completionHandler(self.defaultUserAgent)
} else {
print("Failed to get userAgent")
}
self.webViewForUserAgent = nil
}
} else {
completionHandler(defaultUserAgent)
}
}
public func disposeKeepAlive(keepAliveId: String) {
if let flutterWebView = keepAliveWebViews[keepAliveId] as? FlutterWebViewController {
flutterWebView.keepAliveId = nil
flutterWebView.dispose(removeFromSuperview: true)
keepAliveWebViews[keepAliveId] = nil
}
}
public func clearAllCache(includeDiskFiles: Bool, completionHandler: @escaping () -> Void) {
var websiteDataTypes = Set([WKWebsiteDataTypeMemoryCache])
if includeDiskFiles {
websiteDataTypes.insert(WKWebsiteDataTypeDiskCache)
if #available(macOS 10.13.4, *) {
websiteDataTypes.insert(WKWebsiteDataTypeFetchCache)
}
websiteDataTypes.insert(WKWebsiteDataTypeOfflineWebApplicationCache)
}
let date = NSDate(timeIntervalSince1970: 0)
WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: date as Date, completionHandler: completionHandler)
}
public override func dispose() {
super.dispose()
let keepAliveWebViewValues = keepAliveWebViews.values
keepAliveWebViewValues.forEach {(keepAliveWebView: FlutterWebViewController?) in
if let keepAliveId = keepAliveWebView?.keepAliveId {
disposeKeepAlive(keepAliveId: keepAliveId)
}
}
keepAliveWebViews.removeAll()
windowWebViews.removeAll()
webViewForUserAgent = nil
defaultUserAgent = nil
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,118 @@
//
// InAppWebViewSettings.swift
// flutter_inappwebview
//
// Created by Lorenzo on 21/10/18.
//
import Foundation
import WebKit
@objcMembers
public class InAppWebViewSettings: ISettings<InAppWebView> {
var gopeedProfileId = ""
var useShouldOverrideUrlLoading = false
var useOnLoadResource = false
var useOnDownloadStart = false
@available(*, deprecated, message: "Use InAppWebViewManager.clearAllCache instead.")
var clearCache = false
var userAgent = ""
var applicationNameForUserAgent = ""
var javaScriptEnabled = true
var javaScriptCanOpenWindowsAutomatically = false
var mediaPlaybackRequiresUserGesture = true
var resourceCustomSchemes: [String] = []
var contentBlockers: [[String: [String : Any]]] = []
var minimumFontSize = 0
var useShouldInterceptAjaxRequest = false
var interceptOnlyAsyncAjaxRequests = true
var useShouldInterceptFetchRequest = false
var incognito = false
var cacheEnabled = true
var transparentBackground = false
var supportZoom = true
var allowUniversalAccessFromFileURLs = false
var allowFileAccessFromFileURLs = false
var enableViewportScale = false
var suppressesIncrementalRendering = false
var allowsAirPlayForMediaPlayback = true
var allowsBackForwardNavigationGestures = true
var allowsLinkPreview = true
var isFraudulentWebsiteWarningEnabled = true
var preferredContentMode = 0
var sharedCookiesEnabled = false
var mediaType: String? = nil
var pageZoom = 1.0
var limitsNavigationsToAppBoundDomains = false
var useOnNavigationResponse = false
var applePayAPIEnabled = false
var allowingReadAccessTo: String? = nil
var underPageBackgroundColor: String?
var isTextInteractionEnabled = true
var isSiteSpecificQuirksModeEnabled = true
var upgradeKnownHostsToHTTPS = true
var isElementFullscreenEnabled = true
var isInspectable = false
var shouldPrintBackgrounds = false
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> InAppWebViewSettings {
let _ = super.parse(settings: settings)
if #available(macOS 10.15, *) {} else {
applePayAPIEnabled = false
}
return self
}
override func getRealSettings(obj: InAppWebView?) -> [String: Any?] {
var realSettings: [String: Any?] = toMap()
if let webView = obj {
let configuration = webView.configuration
realSettings["userAgent"] = webView.customUserAgent
realSettings["applicationNameForUserAgent"] = configuration.applicationNameForUserAgent
realSettings["allowsAirPlayForMediaPlayback"] = configuration.allowsAirPlayForMediaPlayback
realSettings["allowsLinkPreview"] = webView.allowsLinkPreview
realSettings["javaScriptCanOpenWindowsAutomatically"] = configuration.preferences.javaScriptCanOpenWindowsAutomatically
if #available(macOS 10.12, *) {
realSettings["mediaPlaybackRequiresUserGesture"] = configuration.mediaTypesRequiringUserActionForPlayback == .all
}
realSettings["minimumFontSize"] = Int(configuration.preferences.minimumFontSize)
realSettings["suppressesIncrementalRendering"] = configuration.suppressesIncrementalRendering
realSettings["allowsBackForwardNavigationGestures"] = webView.allowsBackForwardNavigationGestures
if #available(macOS 10.15, *) {
realSettings["isFraudulentWebsiteWarningEnabled"] = configuration.preferences.isFraudulentWebsiteWarningEnabled
realSettings["preferredContentMode"] = configuration.defaultWebpagePreferences.preferredContentMode.rawValue
}
realSettings["allowUniversalAccessFromFileURLs"] = configuration.value(forKey: "allowUniversalAccessFromFileURLs") as? Bool
realSettings["allowFileAccessFromFileURLs"] = configuration.preferences.value(forKey: "allowFileAccessFromFileURLs") as? Bool
realSettings["javaScriptEnabled"] = configuration.preferences.javaScriptEnabled
if #available(macOS 11.0, *) {
realSettings["mediaType"] = webView.mediaType
realSettings["pageZoom"] = Float(webView.pageZoom)
realSettings["limitsNavigationsToAppBoundDomains"] = configuration.limitsNavigationsToAppBoundDomains
realSettings["javaScriptEnabled"] = configuration.defaultWebpagePreferences.allowsContentJavaScript
}
if #available(macOS 11.3, *) {
realSettings["isTextInteractionEnabled"] = configuration.preferences.isTextInteractionEnabled
realSettings["upgradeKnownHostsToHTTPS"] = configuration.upgradeKnownHostsToHTTPS
}
if #available(macOS 12.0, *) {
realSettings["underPageBackgroundColor"] = webView.underPageBackgroundColor.hexString
}
if #available(macOS 12.3, *) {
realSettings["isSiteSpecificQuirksModeEnabled"] = configuration.preferences.isSiteSpecificQuirksModeEnabled
realSettings["isElementFullscreenEnabled"] = configuration.preferences.isElementFullscreenEnabled
}
if #available(macOS 13.3, *) {
realSettings["isInspectable"] = webView.isInspectable
realSettings["shouldPrintBackgrounds"] = configuration.preferences.shouldPrintBackgrounds
}
}
return realSettings
}
}
@@ -0,0 +1,80 @@
//
// WebMessageChannel.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
import FlutterMacOS
public class WebMessageChannel: FlutterMethodCallDelegate {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_"
var id: String
var plugin: InAppWebViewFlutterPlugin?
var channelDelegate: WebMessageChannelChannelDelegate?
weak var webView: InAppWebView?
var ports: [WebMessagePort] = []
public init(plugin: InAppWebViewFlutterPlugin, id: String) {
self.id = id
self.plugin = plugin
super.init()
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: registrar.messenger)
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel)
}
self.ports = [
WebMessagePort(name: "port1", index: 0, webMessageChannelId: self.id, webMessageChannel: self),
WebMessagePort(name: "port2", index: 1, webMessageChannelId: self.id, webMessageChannel: self)
]
}
public func initJsInstance(webView: InAppWebView, completionHandler: ((WebMessageChannel?) -> Void)? = nil) {
self.webView = webView
if let webView = self.webView {
webView.evaluateJavascript(source: """
(function() {
\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel();
})();
""") { (_) in
completionHandler?(self)
}
} else {
completionHandler?(nil)
}
}
public func toMap() -> [String:Any?] {
return [
"id": id
]
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
for port in ports {
port.dispose()
}
ports.removeAll()
webView?.evaluateJavascript(source: """
(function() {
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
if (webMessageChannel != null) {
webMessageChannel.port1.close();
webMessageChannel.port2.close();
delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
}
})();
""")
webView = nil
plugin = nil
}
deinit {
debugPrint("WebMessageChannel - dealloc")
dispose()
}
}
@@ -0,0 +1,103 @@
//
// WebMessageChannelChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
import FlutterMacOS
public class WebMessageChannelChannelDelegate: ChannelDelegate {
private weak var webMessageChannel: WebMessageChannel?
public init(webMessageChannel: WebMessageChannel, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.webMessageChannel = webMessageChannel
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "setWebMessageCallback":
if let _ = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
do {
try port.setWebMessageCallback { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
case "postMessage":
if let webView = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
var message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
var ports: [WebMessagePort] = []
if let notConnectedPorts = message.ports {
for notConnectedPort in notConnectedPorts {
if let webMessageChannel = webView.webMessageChannels[notConnectedPort.webMessageChannelId] {
ports.append(webMessageChannel.ports[Int(notConnectedPort.index)])
}
}
}
message.ports = ports
do {
try port.postMessage(message: message) { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
case "close":
if let _ = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
let index = arguments!["index"] as! Int
let port = ports[index]
do {
try port.close { (_) in
result(true)
}
} catch let error as NSError {
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
}
} else {
result(true)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onMessage(index: Int64, message: WebMessage?) {
let arguments: [String:Any?] = [
"index": index,
"message": message?.toMap()
]
channel?.invokeMethod("onMessage", arguments: arguments)
}
public override func dispose() {
super.dispose()
webMessageChannel = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,193 @@
//
// WebMessageListener.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
import WebKit
import FlutterMacOS
public class WebMessageListener: FlutterMethodCallDelegate {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_listener_"
var id: String
var jsObjectName: String
var allowedOriginRules: Set<String>
var channelDelegate: WebMessageListenerChannelDelegate?
weak var webView: InAppWebView?
var plugin: InAppWebViewFlutterPlugin?
public init(plugin: InAppWebViewFlutterPlugin, id: String, jsObjectName: String, allowedOriginRules: Set<String>) {
self.id = id
self.plugin = plugin
self.jsObjectName = jsObjectName
self.allowedOriginRules = allowedOriginRules
super.init()
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.id + "_" + self.jsObjectName,
binaryMessenger: registrar.messenger)
self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel)
}
}
public func assertOriginRulesValid() throws {
for (index, originRule) in allowedOriginRules.enumerated() {
if originRule.isEmpty {
throw NSError(domain: "allowedOriginRules[\(index)] is empty", code: 0)
}
if originRule == "*" {
continue
}
if let url = URL(string: originRule) {
guard let scheme = url.scheme else {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if scheme == "http" || scheme == "https", url.host == nil || url.host!.isEmpty {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if scheme != "http", scheme != "https", url.host != nil || url.port != nil {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if url.host == nil || url.host!.isEmpty, url.port != nil {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if !url.path.isEmpty {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
if let hostname = url.host {
if let firstIndex = hostname.firstIndex(of: "*") {
let distance = hostname.distance(from: hostname.startIndex, to: firstIndex)
if distance != 0 || (distance == 0 && hostname.prefix(2) != "*.") {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
if hostname.hasPrefix("[") {
if !hostname.hasSuffix("]") {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
let ipv6 = String(hostname[indexRange])
if !Util.isIPv6(address: ipv6) {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
}
} else {
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
}
}
}
public func initJsInstance(webView: InAppWebView) {
self.webView = webView
if let webView = self.webView {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let allowedOriginRulesString = allowedOriginRules.map { (allowedOriginRule) -> String in
if allowedOriginRule == "*" {
return "'*'"
}
let rule = URL(string: allowedOriginRule)!
let host = rule.host != nil ? "'" + rule.host!.replacingOccurrences(of: "\'", with: "\\'") + "'" : "null"
return """
{scheme: '\(rule.scheme!)', host: \(host), port: \(rule.port != nil ? String(rule.port!) : "null")}
"""
}.joined(separator: ", ")
let source = """
(function() {
var allowedOriginRules = [\(allowedOriginRulesString)];
var isPageBlank = window.location.href === "about:blank";
var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null;
var host = !isPageBlank ? window.location.hostname : null;
var port = !isPageBlank ? window.location.port : null;
if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) {
window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)');
}
})();
"""
webView.configuration.userContentController.addPluginScript(PluginScript(
groupName: "WebMessageListener-" + id + "-" + jsObjectName,
source: source,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: false,
messageHandlerNames: ["onWebMessageListenerPostMessageReceived"]
))
webView.configuration.userContentController.sync(scriptMessageHandler: webView)
}
}
public static func fromMap(plugin: InAppWebViewFlutterPlugin, map: [String:Any?]?) -> WebMessageListener? {
guard let map = map else {
return nil
}
return WebMessageListener(
plugin: plugin,
id: map["id"] as! String,
jsObjectName: map["jsObjectName"] as! String,
allowedOriginRules: Set(map["allowedOriginRules"] as! [String])
)
}
public func isOriginAllowed(scheme: String?, host: String?, port: Int?) -> Bool {
for allowedOriginRule in allowedOriginRules {
if allowedOriginRule == "*" {
return true
}
if scheme == nil || scheme!.isEmpty {
continue
}
if scheme == nil || scheme!.isEmpty, host == nil || host!.isEmpty, port == nil || port == 0 {
continue
}
if let rule = URL(string: allowedOriginRule) {
let rulePort = rule.port == nil || rule.port == 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port!
let currentPort = port == nil || port == 0 ? (scheme == "https" ? 443 : 80) : port!
var IPv6: String? = nil
if let hostname = rule.host, hostname.hasPrefix("[") {
let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
do {
IPv6 = try Util.normalizeIPv6(address: String(hostname[indexRange]))
} catch {}
}
var hostIPv6: String? = nil
if let host = host, Util.isIPv6(address: host) {
do {
hostIPv6 = try Util.normalizeIPv6(address: host)
} catch {}
}
let schemeAllowed = scheme != nil && !scheme!.isEmpty && scheme == rule.scheme
let hostAllowed = rule.host == nil ||
rule.host!.isEmpty ||
host == rule.host ||
(rule.host!.hasPrefix("*") && host != nil && host!.hasSuffix(rule.host!.split(separator: "*", omittingEmptySubsequences: false)[1])) ||
(hostIPv6 != nil && IPv6 != nil && hostIPv6 == IPv6)
let portAllowed = rulePort == currentPort
if schemeAllowed, hostAllowed, portAllowed {
return true
}
}
}
return false
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
webView = nil
plugin = nil
}
deinit {
debugPrint("WebMessageListener - dealloc")
dispose()
}
}
@@ -0,0 +1,73 @@
//
// WebMessageListenerChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
import FlutterMacOS
public class WebMessageListenerChannelDelegate: ChannelDelegate {
private weak var webMessageListener: WebMessageListener?
public init(webMessageListener: WebMessageListener, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.webMessageListener = webMessageListener
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "postMessage":
if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let message = WebMessage.fromMap(map: arguments!["message"] as! [String: Any?])
let source = """
(function() {
var webMessageListener = window['\(jsObjectNameEscaped)'];
if (webMessageListener != null) {
var event = {data: \(message.jsData)};
if (webMessageListener.onmessage != null) {
webMessageListener.onmessage(event);
}
for (var listener of webMessageListener.listeners) {
listener(event);
}
}
})();
"""
webView.evaluateJavascript(source: source) { (_) in
result(true)
}
} else {
result(true)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onPostMessage(message: WebMessage?, sourceOrigin: URL?, isMainFrame: Bool) {
let arguments: [String:Any?] = [
"message": message?.toMap(),
"sourceOrigin": sourceOrigin?.absoluteString,
"isMainFrame": isMainFrame
]
channel?.invokeMethod("onPostMessage", arguments: arguments)
}
public override func dispose() {
super.dispose()
webMessageListener = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,87 @@
//
// WebViewChannelDelegateMethods.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/10/22.
//
import Foundation
public enum WebViewChannelDelegateMethods: String {
case getUrl = "getUrl"
case getTitle = "getTitle"
case getProgress = "getProgress"
case loadUrl = "loadUrl"
case postUrl = "postUrl"
case loadData = "loadData"
case loadFile = "loadFile"
case evaluateJavascript = "evaluateJavascript"
case injectJavascriptFileFromUrl = "injectJavascriptFileFromUrl"
case injectCSSCode = "injectCSSCode"
case injectCSSFileFromUrl = "injectCSSFileFromUrl"
case reload = "reload"
case goBack = "goBack"
case canGoBack = "canGoBack"
case goForward = "goForward"
case canGoForward = "canGoForward"
case goBackOrForward = "goBackOrForward"
case canGoBackOrForward = "canGoBackOrForward"
case stopLoading = "stopLoading"
case isLoading = "isLoading"
case takeScreenshot = "takeScreenshot"
case setSettings = "setSettings"
case getSettings = "getSettings"
case close = "close"
case show = "show"
case hide = "hide"
case isHidden = "isHidden"
case getCopyBackForwardList = "getCopyBackForwardList"
@available(*, deprecated, message: "Use FindInteractionController.findAll instead.")
case findAll = "findAll"
@available(*, deprecated, message: "Use FindInteractionController.findNext instead.")
case findNext = "findNext"
@available(*, deprecated, message: "Use FindInteractionController.clearMatches instead.")
case clearMatches = "clearMatches"
@available(*, deprecated, message: "Use InAppWebViewManager.clearAllCache instead.")
case clearCache = "clearCache"
case scrollTo = "scrollTo"
case scrollBy = "scrollBy"
case pauseTimers = "pauseTimers"
case resumeTimers = "resumeTimers"
case printCurrentPage = "printCurrentPage"
case getContentHeight = "getContentHeight"
case getContentWidth = "getContentWidth"
case reloadFromOrigin = "reloadFromOrigin"
case getOriginalUrl = "getOriginalUrl"
case getZoomScale = "getZoomScale"
case hasOnlySecureContent = "hasOnlySecureContent"
case getSelectedText = "getSelectedText"
case getScrollX = "getScrollX"
case getScrollY = "getScrollY"
case getCertificate = "getCertificate"
case addUserScript = "addUserScript"
case removeUserScript = "removeUserScript"
case removeUserScriptsByGroupName = "removeUserScriptsByGroupName"
case removeAllUserScripts = "removeAllUserScripts"
case callAsyncJavaScript = "callAsyncJavaScript"
case createPdf = "createPdf"
case createWebArchiveData = "createWebArchiveData"
case saveWebArchive = "saveWebArchive"
case isSecureContext = "isSecureContext"
case createWebMessageChannel = "createWebMessageChannel"
case postWebMessage = "postWebMessage"
case addWebMessageListener = "addWebMessageListener"
case canScrollVertically = "canScrollVertically"
case canScrollHorizontally = "canScrollHorizontally"
case pauseAllMediaPlayback = "pauseAllMediaPlayback"
case setAllMediaPlaybackSuspended = "setAllMediaPlaybackSuspended"
case closeAllMediaPresentations = "closeAllMediaPresentations"
case requestMediaPlaybackState = "requestMediaPlaybackState"
case getMetaThemeColor = "getMetaThemeColor"
case isInFullscreen = "isInFullscreen"
case getCameraCaptureState = "getCameraCaptureState"
case setCameraCaptureState = "setCameraCaptureState"
case getMicrophoneCaptureState = "getMicrophoneCaptureState"
case setMicrophoneCaptureState = "setMicrophoneCaptureState"
case loadSimulatedRequest = "loadSimulatedRequest"
}
@@ -0,0 +1,86 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
import Cocoa
import FlutterMacOS
import AppKit
import WebKit
import Foundation
import AVFoundation
import SafariServices
public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin {
var registrar: FlutterPluginRegistrar?
var platformUtil: PlatformUtil?
var inAppWebViewManager: InAppWebViewManager?
var myCookieManager: Any?
var myWebStorageManager: MyWebStorageManager?
var credentialDatabase: CredentialDatabase?
var inAppBrowserManager: InAppBrowserManager?
var headlessInAppWebViewManager: HeadlessInAppWebViewManager?
var webAuthenticationSessionManager: WebAuthenticationSessionManager?
var printJobManager: PrintJobManager?
var webViewControllers: [String: InAppBrowserWebViewController?] = [:]
var safariViewControllers: [String: Any?] = [:]
public init(with registrar: FlutterPluginRegistrar) {
super.init()
self.registrar = registrar
registrar.register(FlutterWebViewFactory(plugin: self) as FlutterPlatformViewFactory, withId: FlutterWebViewFactory.VIEW_TYPE_ID)
platformUtil = PlatformUtil(plugin: self)
inAppBrowserManager = InAppBrowserManager(plugin: self)
headlessInAppWebViewManager = HeadlessInAppWebViewManager(plugin: self)
inAppWebViewManager = InAppWebViewManager(plugin: self)
credentialDatabase = CredentialDatabase(plugin: self)
if #available(macOS 10.13, *) {
myCookieManager = MyCookieManager(plugin: self)
}
myWebStorageManager = MyWebStorageManager(plugin: self)
webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self)
printJobManager = PrintJobManager(plugin: self)
}
public static func register(with registrar: FlutterPluginRegistrar) {
let _ = InAppWebViewFlutterPlugin(with: registrar)
}
public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
platformUtil?.dispose()
platformUtil = nil
inAppBrowserManager?.dispose()
inAppBrowserManager = nil
headlessInAppWebViewManager?.dispose()
headlessInAppWebViewManager = nil
inAppWebViewManager?.dispose()
inAppWebViewManager = nil
credentialDatabase?.dispose()
credentialDatabase = nil
if #available(macOS 10.13, *) {
(myCookieManager as! MyCookieManager?)?.dispose()
myCookieManager = nil
}
myWebStorageManager?.dispose()
myWebStorageManager = nil
webAuthenticationSessionManager?.dispose()
webAuthenticationSessionManager = nil
printJobManager?.dispose()
printJobManager = nil
}
}
@@ -0,0 +1,26 @@
//
// LeakAvoider.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/12/2019.
//
import Foundation
import FlutterMacOS
public class LeakAvoider: NSObject {
weak var delegate : FlutterMethodCallDelegate?
init(delegate: FlutterMethodCallDelegate) {
super.init()
self.delegate = delegate
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
self.delegate?.handle(call, result: result)
}
deinit {
debugPrint("LeakAvoider - dealloc")
}
}
@@ -0,0 +1,305 @@
//
// MyCookieManager.swift
// flutter_inappwebview
//
// Created by Lorenzo on 26/10/18.
//
import Foundation
import WebKit
import FlutterMacOS
@available(macOS 10.13, *)
public class MyCookieManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_cookiemanager"
var plugin: InAppWebViewFlutterPlugin?
static var httpCookieStore = WKWebsiteDataStore.default().httpCookieStore
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
if call.method == "gopeed.prepareProfile" {
GopeedProfiles.prepare(arguments: arguments, result: result)
return
}
let profileID = arguments?["gopeedProfileId"] as? String ?? ""
let selectedStore = GopeedProfiles.store(profileID)
if !profileID.isEmpty && selectedStore == nil {
result(FlutterError(code: "UNAVAILABLE", message: "WebView profile is not initialized", details: nil))
return
}
let cookieStore = (selectedStore ?? WKWebsiteDataStore.default()).httpCookieStore
switch call.method {
case "setCookie":
let url = arguments!["url"] as! String
let name = arguments!["name"] as! String
let value = arguments!["value"] as! String
let path = arguments!["path"] as! String
var expiresDate: Int64?
if let expiresDateString = arguments!["expiresDate"] as? String {
expiresDate = Int64(expiresDateString)
}
let maxAge = arguments!["maxAge"] as? Int64
let isSecure = arguments!["isSecure"] as? Bool
let isHttpOnly = arguments!["isHttpOnly"] as? Bool
let sameSite = arguments!["sameSite"] as? String
let domain = arguments!["domain"] as? String
MyCookieManager.setCookie(url: url,
name: name,
value: value,
path: path,
domain: domain,
expiresDate: expiresDate,
maxAge: maxAge,
isSecure: isSecure,
isHttpOnly: isHttpOnly,
sameSite: sameSite,
result: result, cookieStore: cookieStore)
break
case "getCookies":
let url = arguments!["url"] as! String
MyCookieManager.getCookies(url: url, result: result, cookieStore: cookieStore)
break
case "getAllCookies":
MyCookieManager.getAllCookies(result: result, cookieStore: cookieStore)
break
case "deleteCookie":
let url = arguments!["url"] as! String
let name = arguments!["name"] as! String
let path = arguments!["path"] as! String
let domain = arguments!["domain"] as? String
MyCookieManager.deleteCookie(url: url, name: name, path: path, domain: domain, result: result, cookieStore: cookieStore)
break;
case "deleteCookies":
let url = arguments!["url"] as! String
let path = arguments!["path"] as! String
let domain = arguments!["domain"] as? String
MyCookieManager.deleteCookies(url: url, path: path, domain: domain, result: result, cookieStore: cookieStore)
break;
case "deleteAllCookies":
MyCookieManager.deleteAllCookies(result: result, cookieStore: cookieStore)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public static func setCookie(url: String,
name: String,
value: String,
path: String,
domain: String?,
expiresDate: Int64?,
maxAge: Int64?,
isSecure: Bool?,
isHttpOnly: Bool?,
sameSite: String?,
result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var properties: [HTTPCookiePropertyKey: Any] = [:]
properties[.originURL] = url
properties[.name] = name
properties[.value] = value
properties[.path] = path
if domain != nil {
properties[.domain] = domain
}
if expiresDate != nil {
// convert from milliseconds
properties[.expires] = Date(timeIntervalSince1970: TimeInterval(Double(expiresDate!)/1000))
}
if maxAge != nil {
properties[.maximumAge] = String(maxAge!)
}
if isSecure != nil && isSecure! {
properties[.secure] = "TRUE"
}
if isHttpOnly != nil && isHttpOnly! {
properties[.init("HttpOnly")] = "YES"
}
if sameSite != nil {
if #available(macOS 10.15, *) {
var sameSiteValue = HTTPCookieStringPolicy(rawValue: "None")
switch sameSite {
case "Lax":
sameSiteValue = HTTPCookieStringPolicy.sameSiteLax
case "Strict":
sameSiteValue = HTTPCookieStringPolicy.sameSiteStrict
default:
break
}
properties[.sameSitePolicy] = sameSiteValue
} else {
properties[.init("SameSite")] = sameSite
}
}
if let cookie = HTTPCookie(properties: properties) {
cookieStore.setCookie(cookie, completionHandler: {() in
result(true)
})
} else {
result(false)
}
}
public static func getCookies(url: String, result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var cookieList: [[String: Any?]] = []
if let urlHost = URL(string: url)?.host {
cookieStore.getAllCookies { (cookies) in
for cookie in cookies {
if urlHost.hasSuffix(cookie.domain) || ".\(urlHost)".hasSuffix(cookie.domain) {
var sameSite: String? = nil
if #available(macOS 10.15, *) {
if let sameSiteValue = cookie.sameSitePolicy?.rawValue {
sameSite = sameSiteValue.prefix(1).capitalized + sameSiteValue.dropFirst()
}
}
var expiresDateTimestamp: Int64 = -1
if let expiresDate = cookie.expiresDate?.timeIntervalSince1970 {
// convert to milliseconds
expiresDateTimestamp = Int64(expiresDate * 1000)
}
cookieList.append([
"name": cookie.name,
"value": cookie.value,
"expiresDate": expiresDateTimestamp != -1 ? expiresDateTimestamp : nil,
"isSessionOnly": cookie.isSessionOnly,
"domain": cookie.domain,
"sameSite": sameSite,
"isSecure": cookie.isSecure,
"isHttpOnly": cookie.isHTTPOnly,
"path": cookie.path,
])
}
}
result(cookieList)
}
return
} else {
print("Cannot get WebView cookies. No HOST found for URL: \(url)")
}
result(cookieList)
}
public static func getAllCookies(result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var cookieList: [[String: Any?]] = []
cookieStore.getAllCookies { (cookies) in
for cookie in cookies {
var sameSite: String? = nil
if #available(macOS 10.15, *) {
if let sameSiteValue = cookie.sameSitePolicy?.rawValue {
sameSite = sameSiteValue.prefix(1).capitalized + sameSiteValue.dropFirst()
}
}
var expiresDateTimestamp: Int64 = -1
if let expiresDate = cookie.expiresDate?.timeIntervalSince1970 {
// convert to milliseconds
expiresDateTimestamp = Int64(expiresDate * 1000)
}
cookieList.append([
"name": cookie.name,
"value": cookie.value,
"expiresDate": expiresDateTimestamp != -1 ? expiresDateTimestamp : nil,
"isSessionOnly": cookie.isSessionOnly,
"domain": cookie.domain,
"sameSite": sameSite,
"isSecure": cookie.isSecure,
"isHttpOnly": cookie.isHTTPOnly,
"path": cookie.path,
])
}
result(cookieList)
}
}
public static func deleteCookie(url: String, name: String, path: String, domain: String?, result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var domain = domain
cookieStore.getAllCookies { (cookies) in
for cookie in cookies {
var originURL = url
if cookie.properties![.originURL] is String {
originURL = cookie.properties![.originURL] as! String
}
else if cookie.properties![.originURL] is URL {
originURL = (cookie.properties![.originURL] as! URL).absoluteString
}
if domain == nil, let domainUrl = URL(string: originURL) {
domain = domainUrl.host
}
if let domain = domain, cookie.domain == domain, cookie.name == name, cookie.path == path {
cookieStore.delete(cookie, completionHandler: {
result(true)
})
return
}
}
result(false)
}
}
public static func deleteCookies(url: String, path: String, domain: String?, result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
var domain = domain
let dispatchGroup = DispatchGroup()
cookieStore.getAllCookies { (cookies) in
for cookie in cookies {
var originURL = url
if cookie.properties![.originURL] is String {
originURL = cookie.properties![.originURL] as! String
}
else if cookie.properties![.originURL] is URL {
originURL = (cookie.properties![.originURL] as! URL).absoluteString
}
if domain == nil, let domainUrl = URL(string: originURL) {
domain = domainUrl.host
}
if let domain = domain, cookie.domain == domain, cookie.path == path {
dispatchGroup.enter()
cookieStore.delete(cookie) {
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
result(true)
}
}
}
public static func deleteAllCookies(result: @escaping FlutterResult, cookieStore: WKHTTPCookieStore = WKWebsiteDataStore.default().httpCookieStore) {
cookieStore.getAllCookies { cookies in
let group = DispatchGroup()
for cookie in cookies {
group.enter()
cookieStore.delete(cookie) { group.leave() }
}
group.notify(queue: .main) { result(true) }
}
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,95 @@
//
// MyWebStorageManager.swift
// connectivity
//
// Created by Lorenzo Pichilli on 16/12/2019.
//
import Foundation
import WebKit
import FlutterMacOS
public class MyWebStorageManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webstoragemanager"
var plugin: InAppWebViewFlutterPlugin?
static var websiteDataStore = WKWebsiteDataStore.default()
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "fetchDataRecords":
let dataTypes = Set(arguments!["dataTypes"] as! [String])
MyWebStorageManager.fetchDataRecords(dataTypes: dataTypes, result: result)
break
case "removeDataFor":
let dataTypes = Set(arguments!["dataTypes"] as! [String])
let recordList = arguments!["recordList"] as! [[String: Any?]]
MyWebStorageManager.removeDataFor(dataTypes: dataTypes, recordList: recordList, result: result)
break
case "removeDataModifiedSince":
let dataTypes = Set(arguments!["dataTypes"] as! [String])
let timestamp = arguments!["timestamp"] as! Int64
MyWebStorageManager.removeDataModifiedSince(dataTypes: dataTypes, timestamp: timestamp, result: result)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public static func fetchDataRecords(dataTypes: Set<String>, result: @escaping FlutterResult) {
var recordList: [[String: Any?]] = []
MyWebStorageManager.websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in
for record in data {
recordList.append([
"displayName": record.displayName,
"dataTypes": record.dataTypes.map({ (dataType) -> String in
return dataType
})
])
}
result(recordList)
}
}
public static func removeDataFor(dataTypes: Set<String>, recordList: [[String: Any?]], result: @escaping FlutterResult) {
var records: [WKWebsiteDataRecord] = []
MyWebStorageManager.websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in
for record in data {
for r in recordList {
let displayName = r["displayName"] as! String
if (record.displayName == displayName) {
records.append(record)
break
}
}
}
websiteDataStore.removeData(ofTypes: dataTypes, for: records) {
result(true)
}
}
}
public static func removeDataModifiedSince(dataTypes: Set<String>, timestamp: Int64, result: @escaping FlutterResult) {
let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp))
MyWebStorageManager.websiteDataStore.removeData(ofTypes: dataTypes, modifiedSince: date as Date) {
result(true)
}
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,67 @@
//
// PlatformUtil.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 01/03/21.
//
import Foundation
import FlutterMacOS
public class PlatformUtil: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_platformutil"
var plugin: InAppWebViewFlutterPlugin?
init(plugin: InAppWebViewFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: plugin.registrar!.messenger))
self.plugin = plugin
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getSystemVersion":
let version = ProcessInfo.processInfo.operatingSystemVersion
result("\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)")
break
case "formatDate":
let date = arguments!["date"] as! Int64
let format = arguments!["format"] as! String
let locale = PlatformUtil.getLocaleFromString(locale: arguments!["locale"] as? String)
let timezone = TimeZone.init(abbreviation: arguments!["timezone"] as? String ?? "UTC")!
result(PlatformUtil.formatDate(date: date, format: format, locale: locale, timezone: timezone))
break
default:
result(FlutterMethodNotImplemented)
break
}
}
static public func getLocaleFromString(locale: String?) -> Locale {
guard let locale = locale else {
return Locale.init(identifier: "en_US")
}
return Locale.init(identifier: locale)
}
static public func getDateFromMilliseconds(date: Int64) -> Date {
return Date(timeIntervalSince1970: TimeInterval(Double(date)/1000))
}
static public func formatDate(date: Int64, format: String, locale: Locale, timezone: TimeZone) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
formatter.timeZone = timezone
return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date))
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,21 @@
//
// CallAsyncJavaScriptBelowIOS14WrapperJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS = """
(function(obj) {
(async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) {
\(PluginScriptsUtil.VAR_FUNCTION_BODY)
})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) {
window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': value, 'error': null, 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'});
}).catch(function(error) {
window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': null, 'error': error + '', 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'});
});
return null;
})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ));
"""
@@ -0,0 +1,52 @@
//
// ConsoleLogJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT"
let CONSOLE_LOG_JS_PLUGIN_SCRIPT = PluginScript(
groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: CONSOLE_LOG_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"])
// the message needs to be concatenated with '' in order to have the same behavior like on Android
let CONSOLE_LOG_JS_SOURCE = """
(function(console) {
function _callHandler(oldLog, args) {
var message = '';
for (var i in args) {
try {
message += message === '' ? args[i] : ' ' + args[i];
} catch(ignored) {}
}
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers[oldLog].postMessage({'message': message, '_windowId': _windowId});
}
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() {
oldLogs[oldLog].apply(null, arguments);
_callHandler(oldLog, arguments);
}
})(k);
}
})(window.console);
"""
@@ -0,0 +1,36 @@
//
// EnableViewportScaleJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT"
let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ENABLE_VIEWPORT_SCALE_JS_SOURCE,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """
(function() {
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width');
document.getElementsByTagName('head')[0].appendChild(meta);
})()
"""
let NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE = """
(function() {
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent);
document.getElementsByTagName('head')[0].appendChild(meta);
})()
"""
@@ -0,0 +1,73 @@
//
// FindElementsAtPointJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT"
let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: FIND_ELEMENTS_AT_POINT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
/**
https://developer.android.com/reference/android/webkit/WebView.HitTestResult
*/
let FIND_ELEMENTS_AT_POINT_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) {
var hitTestResultType = {
UNKNOWN_TYPE: 0,
PHONE_TYPE: 2,
GEO_TYPE: 3,
EMAIL_TYPE: 4,
IMAGE_TYPE: 5,
SRC_ANCHOR_TYPE: 7,
SRC_IMAGE_ANCHOR_TYPE: 8,
EDIT_TEXT_TYPE: 9
};
var element = document.elementFromPoint(x, y);
var data = {
type: 0,
extra: null
};
while (element) {
if (element.tagName === 'IMG' && element.src) {
if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) {
data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE;
} else {
data.type = hitTestResultType.IMAGE_TYPE;
}
data.extra = element.src;
break;
} else if (element.tagName === 'A' && element.href) {
if (element.href.indexOf('mailto:') === 0) {
data.type = hitTestResultType.EMAIL_TYPE;
data.extra = element.href.replace('mailto:', '');
} else if (element.href.indexOf('tel:') === 0) {
data.type = hitTestResultType.PHONE_TYPE;
data.extra = element.href.replace('tel:', '');
} else if (element.href.indexOf('geo:') === 0) {
data.type = hitTestResultType.GEO_TYPE;
data.extra = element.href.replace('geo:', '');
} else {
data.type = hitTestResultType.SRC_ANCHOR_TYPE;
data.extra = element.href;
}
break;
} else if (
(element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) ||
element.tagName === 'TEXTAREA') {
data.type = hitTestResultType.EDIT_TEXT_TYPE
}
element = element.parentNode;
}
return data;
}
"""
@@ -0,0 +1,187 @@
//
// FindTextHighlightJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT"
let FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._searchResultCount"
let FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._currentHighlight"
let FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._isDoneCounting"
let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: FIND_TEXT_HIGHLIGHT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: ["onFindResultReceived"])
let FIND_TEXT_HIGHLIGHT_JS_SOURCE = """
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0;
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0;
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false;
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement = function(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",
"\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)
);
span.setAttribute("class", "\(JAVASCRIPT_BRIDGE_NAME)_Highlight");
var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) == 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;
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)++;
elementTmp = document.createTextNode(
value.substr(idx + keyword.length)
);
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
{
'findResult': {
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
},
'_windowId': _windowId
}
);
}
} 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--) {
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(
element.childNodes[element.childNodes.length - 1 - i],
keyword
);
}
}
}
}
}
// the main entry point to start the search
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync = function(keyword) {
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(document.body, keyword.toLowerCase());
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = true;
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
{
'findResult': {
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
},
'_windowId': _windowId
}
);
}
// helper function, recursively removes the highlights in elements and their children
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement = function(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute("class") == "\(JAVASCRIPT_BRIDGE_NAME)_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 (window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
// the main entry point to remove the highlights
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches = function() {
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0;
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0;
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false;
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(document.body);
}
window.\(JAVASCRIPT_BRIDGE_NAME)._findNext = function(forward) {
if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) <= 0) return;
var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) + (forward ? +1 : -1);
idx =
idx < 0
? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - 1
: idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)
? 0
: idx;
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = idx;
var scrollTo = document.getElementById("\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + idx);
if (scrollTo) {
var highlights = document.getElementsByClassName("\(JAVASCRIPT_BRIDGE_NAME)_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"
});
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
{
'findResult': {
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
},
'_windowId': _windowId
}
);
}
}
"""
@@ -0,0 +1,268 @@
//
// InterceptAjaxRequestsJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT"
let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptAjaxRequest"
let FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._interceptOnlyAsyncAjaxRequests";
let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = PluginScript(
groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: INTERCEPT_AJAX_REQUEST_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: [])
func createInterceptOnlyAsyncAjaxRequestsPluginScript(onlyAsync: Bool) -> PluginScript {
return PluginScript(groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: "\(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) = \(onlyAsync);",
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: []
);
}
let INTERCEPT_AJAX_REQUEST_JS_SOURCE = """
\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) = true;
(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 (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == 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;
var canBeIntercepted = self._flutter_inappwebview_isAsync || \(FLAG_VARIABLE_FOR_INTERCEPT_ONLY_ASYNC_AJAX_REQUESTS_JS_SOURCE) === false;
if (canBeIntercepted && (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true)) {
if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) {
this._flutter_inappwebview_already_onreadystatechange_wrapped = true;
var onreadystatechange = this.onreadystatechange;
this.onreadystatechange = function() {
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == 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);
\(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(data).then(function(data) {
var ajaxRequest = {
data: data,
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,
responseType: self.responseType
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {
if (result != null) {
switch (result) {
case 0:
self.abort();
return;
};
if (result.data != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) && result.data.length > 0) {
var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.data);
if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) {
var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString);
if (result.headers != null) {
result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];
} else {
result.headers = { 'Content-Type': formDataContentType };
}
}
}
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) || result.data == null) {
data = result.data;
} else if (result.data.length > 0) {
data = new Uint8Array(result.data);
}
self.withCredentials = result.withCredentials;
if (result.responseType != null && self._flutter_inappwebview_isAsync) {
self.responseType = result.responseType;
};
if (result.headers != null) {
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._flutter_inappwebview_isAsync != result.isAsync && result.isAsync != null) ||
(self._flutter_inappwebview_user != result.user && result.user != null) ||
(self._flutter_inappwebview_password != result.password && result.password != null)) {
self.abort();
self.open(result.method, result.url, result.isAsync, result.user, result.password);
}
}
send.call(self, data);
});
});
} else {
send.call(this, data);
}
};
})(window.XMLHttpRequest);
"""
@@ -0,0 +1,153 @@
//
// InterceptFetchRequestsJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT"
let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptFetchRequest"
let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = PluginScript(
groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: INTERCEPT_FETCH_REQUEST_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: [])
let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """
\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) = true;
(function(fetch) {
if (fetch == null) {
return;
}
window.fetch = async function(resource, init) {
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == 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 != null ? resource.toString() : null;
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 = \(JAVASCRIPT_UTIL_VAR_NAME).convertHeadersToJson(fetchRequest.headers);
}
fetchRequest.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertCredentialsToJson(fetchRequest.credentials);
return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(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;
}
if (result.body != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) && result.body.length > 0) {
var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.body);
if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) {
var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString);
if (result.headers != null) {
result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];
} else {
result.headers = { 'Content-Type': formDataContentType };
}
}
}
resource = result.url;
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 = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToHeaders(result.headers);
}
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) || result.body == null) {
init.body = result.body;
} else if (result.body.length > 0) {
init.body = new Uint8Array(result.body);
}
if (result.mode != null && result.mode.length > 0) {
init.mode = result.mode;
}
if (result.credentials != null) {
init.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).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);
"""
@@ -0,0 +1,243 @@
//
// javaScriptBridgeJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"
let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"
let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript(
groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: JAVASCRIPT_BRIDGE_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: ["callHandler"])
let JAVASCRIPT_BRIDGE_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME) = {};
\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
var _callHandlerID = setTimeout(function(){});
window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1)), '_windowId': _windowId} );
return new Promise(function(resolve, reject) {
window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = {resolve: resolve, reject: reject};
});
};
\(WEB_MESSAGE_LISTENER_JS_SOURCE)
\(UTIL_JS_SOURCE)
"""
let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));";
let JAVASCRIPT_UTIL_VAR_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._Util"
/*
https://github.com/github/fetch/blob/master/fetch.js
*/
let UTIL_JS_SOURCE = """
\(JAVASCRIPT_UTIL_VAR_NAME) = {
support: {
searchParams: 'URLSearchParams' in window,
iterable: 'Symbol' in window && 'iterator' in Symbol,
blob:
'FileReader' in window &&
'Blob' in window &&
(function() {
try {
new Blob();
return true;
} catch (e) {
return false;
}
})(),
formData: 'FormData' in window,
arrayBuffer: 'ArrayBuffer' in window
},
isDataView: function(obj) {
return obj && DataView.prototype.isPrototypeOf(obj);
},
fileReaderReady: function(reader) {
return new Promise(function(resolve, reject) {
reader.onload = function() {
resolve(reader.result);
};
reader.onerror = function() {
reject(reader.error);
};
});
},
readBlobAsArrayBuffer: function(blob) {
var reader = new FileReader();
var promise = \(JAVASCRIPT_UTIL_VAR_NAME).fileReaderReady(reader);
reader.readAsArrayBuffer(blob);
return promise;
},
convertBodyToArrayBuffer: function(body) {
var viewClasses = [
'[object Int8Array]',
'[object Uint8Array]',
'[object Uint8ClampedArray]',
'[object Int16Array]',
'[object Uint16Array]',
'[object Int32Array]',
'[object Uint32Array]',
'[object Float32Array]',
'[object Float64Array]'
];
var isArrayBufferView = null;
if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer) {
isArrayBufferView =
ArrayBuffer.isView ||
function(obj) {
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
};
}
var bodyUsed = false;
this._bodyInit = body;
if (!body) {
this._bodyText = '';
} else if (typeof body === 'string') {
this._bodyText = body;
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.blob && Blob.prototype.isPrototypeOf(body)) {
this._bodyBlob = body;
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.formData && FormData.prototype.isPrototypeOf(body)) {
this._bodyFormData = body;
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this._bodyText = body.toString();
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME).isDataView(body)) {
this._bodyArrayBuffer = bufferClone(body.buffer);
this._bodyInit = new Blob([this._bodyArrayBuffer]);
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
this._bodyArrayBuffer = bufferClone(body);
} else {
this._bodyText = body = Object.prototype.toString.call(body);
}
this.blob = function () {
if (bodyUsed) {
return Promise.reject(new TypeError('Already read'));
}
bodyUsed = true;
if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob);
} else if (this._bodyArrayBuffer) {
return Promise.resolve(new Blob([this._bodyArrayBuffer]));
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as blob');
} else {
return Promise.resolve(new Blob([this._bodyText]));
}
};
if (this._bodyArrayBuffer) {
if (bodyUsed) {
return Promise.reject(new TypeError('Already read'));
}
bodyUsed = true;
if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
return Promise.resolve(
this._bodyArrayBuffer.buffer.slice(
this._bodyArrayBuffer.byteOffset,
this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
)
);
} else {
return Promise.resolve(this._bodyArrayBuffer);
}
}
return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME).readBlobAsArrayBuffer);
},
isString: function(variable) {
return typeof variable === 'string' || variable instanceof String;
},
convertBodyRequest: function(body) {
if (body == null) {
return new Promise((resolve, reject) => resolve(null));
}
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && body instanceof URLSearchParams)) {
return new Promise((resolve, reject) => resolve(body.toString()));
}
if (window.Response != null) {
return new Response(body).arrayBuffer().then(function(arrayBuffer) {
return Array.from(new Uint8Array(arrayBuffer));
});
}
return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyToArrayBuffer(body).then(function(arrayBuffer) {
return Array.from(new Uint8Array(arrayBuffer));
});
},
arrayBufferToString: function(arrayBuffer) {
var uint8Array = new Uint8Array(arrayBuffer);
return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, '');
},
isBodyFormData: function(bodyString) {
return bodyString.indexOf('------WebKitFormBoundary') >= 0;
},
getFormDataContentType: function(bodyString) {
var boundary = bodyString.substr(2, 40);
return 'multipart/form-data; boundary=' + boundary;
},
convertHeadersToJson: function(headers) {
var headersObj = {};
for (var header of headers.keys()) {
var value = headers.get(header);
headersObj[header] = value;
}
return headersObj;
},
convertJsonToHeaders: function(headersJson) {
return new Headers(headersJson);
},
convertCredentialsToJson: function(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;
}
return credentialsObj;
},
convertJsonToCredential: function(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.value == null ? undefined : credentialsJson.value;
}
return credentials;
}
};
"""
@@ -0,0 +1,39 @@
//
// resourceObserverJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT"
let FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useOnLoadResource"
let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ON_LOAD_RESOURCE_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ON_LOAD_RESOURCE_JS_SOURCE = """
\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) = true;
(function() {
var observer = new PerformanceObserver(function(list) {
list.getEntries().forEach(function(entry) {
if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == true) {
var resource = {
"url": entry.name,
"initiatorType": entry.initiatorType,
"startTime": entry.startTime,
"duration": entry.duration
};
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onLoadResource", resource);
}
});
});
observer.observe({entryTypes: ['resource']});
})();
"""
@@ -0,0 +1,33 @@
//
// OnScrollEvent.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/10/22.
//
import Foundation
let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT"
let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ON_SCROLL_CHANGED_EVENT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: ["onScrollChanged"])
let ON_SCROLL_CHANGED_EVENT_JS_SOURCE = """
(function(){
document.addEventListener('scroll', function(e) {
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
window.webkit.messageHandlers["onScrollChanged"].postMessage(
{
x: window.scrollX,
y: window.scrollY,
_windowId: _windowId
}
);
});
})();
"""
@@ -0,0 +1,26 @@
//
// OnWindowBlurEventJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT"
let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ON_WINDOW_BLUR_EVENT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ON_WINDOW_BLUR_EVENT_JS_SOURCE = """
(function(){
window.addEventListener('blur', function(e) {
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowBlur');
});
})();
"""
@@ -0,0 +1,26 @@
//
// OnWindowFocusEventJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT"
let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ON_WINDOW_FOCUS_EVENT_JS_SOURCE,
source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ON_WINDOW_FOCUS_EVENT_JS_SOURCE = """
(function(){
window.addEventListener('focus', function(e) {
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowFocus');
});
})();
"""
@@ -0,0 +1,31 @@
//
// OriginalViewPortMetaTagContentJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT"
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = "";
(function() {
var metaTagNodes = document.head.getElementsByTagName('meta');
for (var i = 0; i < metaTagNodes.length; i++) {
var metaTagNode = metaTagNodes[i];
if (metaTagNode.name === "viewport") {
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content;
}
}
})();
"""
@@ -0,0 +1,31 @@
//
// PluginScripts.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
public class PluginScriptsUtil {
public static let VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE"
public static let VAR_FUNCTION_ARGUMENT_NAMES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_NAMES"
public static let VAR_FUNCTION_ARGUMENT_VALUES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_VALUES"
public static let VAR_FUNCTION_ARGUMENTS_OBJ = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENTS_OBJ"
public static let VAR_FUNCTION_BODY = "$IN_APP_WEBVIEW_FUNCTION_BODY"
public static let VAR_RESULT_UUID = "$IN_APP_WEBVIEW_RESULT_UUID"
public static let GET_SELECTED_TEXT_JS_SOURCE = """
(function(){
var txt;
if (window.getSelection) {
txt = window.getSelection().toString();
} else if (window.document.getSelection) {
txt = window.document.getSelection().toString();
} else if (window.document.selection) {
txt = window.document.selection.createRange().text;
}
return txt;
})();
"""
}
@@ -0,0 +1,24 @@
//
// PrintJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT"
let PRINT_JS_PLUGIN_SCRIPT = PluginScript(
groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: PRINT_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: [])
let PRINT_JS_SOURCE = """
window.print = function() {
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrintRequest", window.location.href);
}
"""
@@ -0,0 +1,27 @@
//
// PromisePolyfillJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
import WebKit
let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PROMISE_POLYFILL_JS_PLUGIN_SCRIPT"
let PROMISE_POLYFILL_JS_PLUGIN_SCRIPT = PluginScript(
groupName: PROMISE_POLYFILL_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: PROMISE_POLYFILL_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
requiredInAllContentWorlds: true,
messageHandlerNames: [])
// https://github.com/tildeio/rsvp.js
let PROMISE_POLYFILL_JS_SOURCE = """
if (window.Promise == null) {
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.RSVP={})}(this,function(t){"use strict";function e(t){var e=t._promiseCallbacks;return e||(e=t._promiseCallbacks={}),e}var r={mixin:function(t){return t.on=this.on,t.off=this.off,t.trigger=this.trigger,t._promiseCallbacks=void 0,t},on:function(t,r){if("function"!=typeof r)throw new TypeError("Callback must be a function");var n=e(this),o=n[t];o||(o=n[t]=[]),-1===o.indexOf(r)&&o.push(r)},off:function(t,r){var n=e(this);if(r){var o=n[t],i=o.indexOf(r);-1!==i&&o.splice(i,1)}else n[t]=[]},trigger:function(t,r,n){var o=e(this)[t];if(o)for(var i=0;i<o.length;i++)(0,o[i])(r,n)}},n={instrument:!1};function o(t,e){if(2!==arguments.length)return n[t];n[t]=e}r.mixin(n);var i=[];function s(t,e,r){1===i.push({name:t,payload:{key:e._guidKey,id:e._id,eventName:t,detail:e._result,childId:r&&r._id,label:e._label,timeStamp:Date.now(),error:n["instrument-with-stack"]?new Error(e._label):null}})&&setTimeout(function(){for(var t=0;t<i.length;t++){var e=i[t],r=e.payload;r.guid=r.key+r.id,r.childGuid=r.key+r.childId,r.error&&(r.stack=r.error.stack),n.trigger(e.name,e.payload)}i.length=0},50)}function u(t,e){if(t&&"object"==typeof t&&t.constructor===this)return t;var r=new this(c,e);return m(r,t),r}function c(){}var a=void 0,f=1,l=2,h={error:null};function p(t){try{return t.then}catch(t){return h.error=t,h}}var y=void 0;function _(){try{var t=y;return y=null,t.apply(this,arguments)}catch(t){return h.error=t,h}}function v(t){return y=t,_}function d(t,e,r){if(e.constructor===t.constructor&&r===A&&t.constructor.resolve===u)!function(t,e){e._state===f?b(t,e._result):e._state===l?(e._onError=null,g(t,e._result)):j(e,void 0,function(r){e===r?b(t,r):m(t,r)},function(e){return g(t,e)})}(t,e);else if(r===h){var o=h.error;h.error=null,g(t,o)}else"function"==typeof r?function(t,e,r){n.async(function(t){var n=!1,o=v(r).call(e,function(r){n||(n=!0,e===r?b(t,r):m(t,r))},function(e){n||(n=!0,g(t,e))},"Settle: "+(t._label||" unknown promise"));if(!n&&o===h){n=!0;var i=h.error;h.error=null,g(t,i)}},t)}(t,e,r):b(t,e)}function m(t,e){var r,n;t===e?b(t,e):(n=typeof(r=e),null===r||"object"!==n&&"function"!==n?b(t,e):d(t,e,p(e)))}function w(t){t._onError&&t._onError(t._result),O(t)}function b(t,e){t._state===a&&(t._result=e,t._state=f,0===t._subscribers.length?n.instrument&&s("fulfilled",t):n.async(O,t))}function g(t,e){t._state===a&&(t._state=l,t._result=e,n.async(w,t))}function j(t,e,r,o){var i=t._subscribers,s=i.length;t._onError=null,i[s]=e,i[s+f]=r,i[s+l]=o,0===s&&t._state&&n.async(O,t)}function O(t){var e=t._subscribers,r=t._state;if(n.instrument&&s(r===f?"fulfilled":"rejected",t),0!==e.length){for(var o=void 0,i=void 0,u=t._result,c=0;c<e.length;c+=3)o=e[c],i=e[c+r],o?E(r,o,i,u):i(u);t._subscribers.length=0}}function E(t,e,r,n){var o="function"==typeof r,i=void 0;if(i=o?v(r)(n):n,e._state!==a);else if(i===e)g(e,new TypeError("A promises callback cannot return that same promise."));else if(i===h){var s=h.error;h.error=null,g(e,s)}else o?m(e,i):t===f?b(e,i):t===l&&g(e,i)}function A(t,e,r){var o=this._state;if(o===f&&!t||o===l&&!e)return n.instrument&&s("chained",this,this),this;this._onError=null;var i=new this.constructor(c,r),u=this._result;if(n.instrument&&s("chained",this,i),o===a)j(this,i,t,e);else{var h=o===f?t:e;n.async(function(){return E(o,i,h,u)})}return i}var T=function(){function t(t,e,r,n){this._instanceConstructor=t,this.promise=new t(c,n),this._abortOnReject=r,this._isUsingOwnPromise=t===k,this._isUsingOwnResolve=t.resolve===u,this._init.apply(this,arguments)}return t.prototype._init=function(t,e){var r=e.length||0;this.length=r,this._remaining=r,this._result=new Array(r),this._enumerate(e)},t.prototype._enumerate=function(t){for(var e=this.length,r=this.promise,n=0;r._state===a&&n<e;n++)this._eachEntry(t[n],n,!0);this._checkFullfillment()},t.prototype._checkFullfillment=function(){if(0===this._remaining){var t=this._result;b(this.promise,t),this._result=null}},t.prototype._settleMaybeThenable=function(t,e,r){var n=this._instanceConstructor;if(this._isUsingOwnResolve){var o=p(t);if(o===A&&t._state!==a)t._onError=null,this._settledAt(t._state,e,t._result,r);else if("function"!=typeof o)this._settledAt(f,e,t,r);else if(this._isUsingOwnPromise){var i=new n(c);d(i,t,o),this._willSettleAt(i,e,r)}else this._willSettleAt(new n(function(e){return e(t)}),e,r)}else this._willSettleAt(n.resolve(t),e,r)},t.prototype._eachEntry=function(t,e,r){null!==t&&"object"==typeof t?this._settleMaybeThenable(t,e,r):this._setResultAt(f,e,t,r)},t.prototype._settledAt=function(t,e,r,n){var o=this.promise;o._state===a&&(this._abortOnReject&&t===l?g(o,r):(this._setResultAt(t,e,r,n),this._checkFullfillment()))},t.prototype._setResultAt=function(t,e,r,n){this._remaining--,this._result[e]=r},t.prototype._willSettleAt=function(t,e,r){var n=this;j(t,void 0,function(t){return n._settledAt(f,e,t,r)},function(t){return n._sLine truncated
window.Promise = RSVP.Promise;
}
"""
@@ -0,0 +1,37 @@
//
// SupportZoomJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT"
let NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT = PluginScript(
groupName: NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: NOT_SUPPORT_ZOOM_JS_SOURCE,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let NOT_SUPPORT_ZOOM_JS_SOURCE = """
(function() {
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');
document.getElementsByTagName('head')[0].appendChild(meta);
})()
"""
let SUPPORT_ZOOM_JS_SOURCE = """
(function() {
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent);
document.getElementsByTagName('head')[0].appendChild(meta);
})()
"""
@@ -0,0 +1,10 @@
//
// WebMessageChannelJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels"
@@ -0,0 +1,116 @@
//
// WebMessageListenerJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
let WEB_MESSAGE_LISTENER_JS_SOURCE = """
function FlutterInAppWebViewWebMessageListener(jsObjectName) {
this.jsObjectName = jsObjectName;
this.listeners = [];
this.onmessage = null;
}
FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(data) {
var message = {
"data": window.ArrayBuffer != null && data instanceof ArrayBuffer ? Array.from(new Uint8Array(data)) : (data != null ? data.toString() : null),
"type": window.ArrayBuffer != null && data instanceof ArrayBuffer ? 1 : 0
};
window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message});
};
FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) {
if (listener == null) {
return;
}
this.listeners.push(listener);
};
FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) {
if (listener == null) {
return;
}
var index = this.listeners.indexOf(listener);
if (index >= 0) {
this.listeners.splice(index, 1);
}
};
window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) {
// replace ipv4 address if any
var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/);
if (ipv4) {
ip_string = ipv4[1];
ipv4 = ipv4[2].match(/[0-9]+/g);
for (var i = 0;i < 4;i ++) {
var byte = parseInt(ipv4[i],10);
ipv4[i] = ("0" + byte.toString(16)).substr(-2);
}
ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3];
}
// take care of leading and trailing ::
ip_string = ip_string.replace(/^:|:$/g, '');
var ipv6 = ip_string.split(':');
for (var i = 0; i < ipv6.length; i ++) {
var hex = ipv6[i];
if (hex != "") {
// normalize leading zeros
ipv6[i] = ("0000" + hex).substr(-4);
}
else {
// normalize grouped zeros ::
hex = [];
for (var j = ipv6.length; j <= 8; j ++) {
hex.push('0000');
}
ipv6[i] = hex.join(':');
}
}
return ipv6.join(':');
}
window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) {
for (var rule of allowedOriginRules) {
if (rule === "*") {
return true;
}
if (scheme == null || scheme === "") {
continue;
}
if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) {
continue;
}
var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port;
var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port;
var IPv6 = null;
if (rule.host != null && rule.host[0] === "[") {
try {
IPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(rule.host.substring(1, rule.host.length - 1));
} catch {}
}
var hostIPv6 = null;
try {
hostIPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(host);
} catch {}
var schemeAllowed = scheme == rule.scheme;
var hostAllowed = rule.host == null ||
rule.host === "" ||
host === rule.host ||
(rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) ||
(hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6);
var portAllowed = rulePort === currentPort;
if (schemeAllowed && hostAllowed && portAllowed) {
return true;
}
}
return false;
}
"""
@@ -0,0 +1,19 @@
//
// WindowIdJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT"
let WINDOW_ID_VARIABLE_JS_SOURCE = "window._\(JAVASCRIPT_BRIDGE_NAME)_windowId"
let WINDOW_ID_INITIALIZE_JS_SOURCE = """
(function() {
\(WINDOW_ID_VARIABLE_JS_SOURCE) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE);
return \(WINDOW_ID_VARIABLE_JS_SOURCE);
})()
"""
@@ -0,0 +1,34 @@
//
// CustomUIPrintPageRenderer.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
//
//public class CustomUIPrintPageRenderer: UIPrintPageRenderer {
// private var _numberOfPages: Int?
// private var forceRenderingQuality: Int?
//
// public init(numberOfPage: Int? = nil, forceRenderingQuality: Int? = nil) {
// super.init()
// self._numberOfPages = numberOfPage
// self.forceRenderingQuality = forceRenderingQuality
// }
//
// open override var numberOfPages: Int {
// get {
// return _numberOfPages ?? super.numberOfPages
// }
// }
//
// @available(iOS 14.5, *)
// open override func currentRenderingQuality(forRequested requestedRenderingQuality: UIPrintRenderingQuality) -> UIPrintRenderingQuality {
// if let forceRenderingQuality = forceRenderingQuality,
// let quality = UIPrintRenderingQuality.init(rawValue: forceRenderingQuality) {
// return quality
// }
// return super.currentRenderingQuality(forRequested: requestedRenderingQuality)
// }
//}
@@ -0,0 +1,98 @@
//
// PrintAttributes.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
public class PrintAttributes: NSObject {
var orientation: NSPrintInfo.PaperOrientation?
var margins: NSEdgeInsets?
var paperRect: CGRect?
var colorMode: String?
var duplex: Int?
var paperName: String?
var localizedPaperName: String?
var horizontalPagination: UInt?
var verticalPagination: UInt?
var jobDisposition: String?
var printableRect: NSRect?
var isHorizontallyCentered: Bool?
var isVerticallyCentered: Bool?
var isSelectionOnly: Bool?
var scalingFactor: CGFloat?
var jobSavingURL: String?
var detailedErrorReporting: Bool?
var faxNumber: String?
var headerAndFooter: Bool?
var mustCollate: Bool?
var pagesAcross: Int?
var pagesDown: Int?
var time: Int?
public init(fromPrintJobController: PrintJobController) {
super.init()
if let job = fromPrintJobController.job {
let printInfo = job.printInfo
let printInfoDictionary = printInfo.dictionary()
orientation = printInfo.orientation
margins = NSEdgeInsets(top: printInfo.topMargin,
left: printInfo.leftMargin,
bottom: printInfo.bottomMargin,
right: printInfo.rightMargin)
paperRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: printInfo.paperSize)
colorMode = printInfo.printSettings["ColorModel"] as? String
duplex = printInfo.printSettings["com_apple_print_PrintSettings_PMDuplexing"] as? Int
paperName = printInfo.paperName?.rawValue
localizedPaperName = printInfo.localizedPaperName
horizontalPagination = printInfo.horizontalPagination.rawValue
verticalPagination = printInfo.verticalPagination.rawValue
jobDisposition = printInfo.jobDisposition.rawValue
printableRect = printInfo.imageablePageBounds
isHorizontallyCentered = printInfo.isHorizontallyCentered
isVerticallyCentered = printInfo.isVerticallyCentered
isSelectionOnly = printInfo.isSelectionOnly
scalingFactor = printInfo.scalingFactor
jobSavingURL = (printInfoDictionary[NSPrintInfo.AttributeKey.jobSavingURL] as? URL)?.absoluteString
detailedErrorReporting = printInfoDictionary[NSPrintInfo.AttributeKey.detailedErrorReporting] as? Bool
faxNumber = printInfoDictionary[NSPrintInfo.AttributeKey.faxNumber] as? String
headerAndFooter = printInfoDictionary[NSPrintInfo.AttributeKey.headerAndFooter] as? Bool
mustCollate = printInfoDictionary[NSPrintInfo.AttributeKey.mustCollate] as? Bool
pagesAcross = printInfoDictionary[NSPrintInfo.AttributeKey.pagesAcross] as? Int
pagesDown = printInfoDictionary[NSPrintInfo.AttributeKey.pagesDown] as? Int
if let timestamp = (printInfoDictionary[NSPrintInfo.AttributeKey.time] as? Date)?.timeIntervalSince1970 {
time = Int(timestamp)
}
}
}
public func toMap () -> [String:Any?] {
return [
"paperRect": paperRect?.toMap(),
"margins": margins?.toMap(),
"orientation": orientation?.rawValue,
"colorMode": colorMode,
"duplex": duplex,
"paperName": paperName,
"localizedPaperName": localizedPaperName,
"horizontalPagination": horizontalPagination,
"verticalPagination": verticalPagination,
"jobDisposition": jobDisposition,
"printableRect": printableRect?.toMap(),
"isHorizontallyCentered": isHorizontallyCentered,
"isVerticallyCentered": isVerticallyCentered,
"isSelectionOnly": isSelectionOnly,
"scalingFactor": scalingFactor,
"jobSavingURL": jobSavingURL,
"detailedErrorReporting": detailedErrorReporting,
"faxNumber": faxNumber,
"headerAndFooter": headerAndFooter,
"mustCollate": mustCollate,
"pagesAcross": pagesAcross,
"pagesDown": pagesDown,
"time": time
]
}
}
@@ -0,0 +1,60 @@
//
// PrintJobChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
import FlutterMacOS
public class PrintJobChannelDelegate: ChannelDelegate {
private weak var printJobController: PrintJobController?
public init(printJobController: PrintJobController, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.printJobController = printJobController
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getInfo":
if let printJobController = printJobController {
result(printJobController.getInfo()?.toMap())
} else {
result(false)
}
break
case "dispose":
if let printJobController = printJobController {
printJobController.dispose()
result(true)
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onComplete(completed: Bool, error: Error?) {
let arguments: [String: Any?] = [
"completed": completed,
"error": error?.localizedDescription
]
channel?.invokeMethod("onComplete", arguments: arguments)
}
public override func dispose() {
super.dispose()
printJobController = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,93 @@
//
// PrintJob.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
import FlutterMacOS
public enum PrintJobState: Int {
case created = 1
case started = 3
case completed = 5
case canceled = 7
}
public class PrintJobController: NSObject, Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_"
var id: String
var plugin: InAppWebViewFlutterPlugin?
var job: NSPrintOperation?
var settings: PrintJobSettings?
var channelDelegate: PrintJobChannelDelegate?
var state = PrintJobState.created
var creationTime = Int64(Date().timeIntervalSince1970 * 1000)
private var completionHandler: PrintJobController.CompletionHandler?
public typealias CompletionHandler = (_ printOperation: NSPrintOperation,
_ success: Bool,
_ contextInfo: UnsafeMutableRawPointer?) -> Void
public init(plugin: InAppWebViewFlutterPlugin, id: String, job: NSPrintOperation? = nil, settings: PrintJobSettings? = nil) {
self.id = id
self.plugin = plugin
super.init()
self.job = job
self.settings = settings
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: registrar.messenger)
self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel)
}
}
public func present(parentWindow: NSWindow? = nil, completionHandler: PrintJobController.CompletionHandler? = nil) {
guard let job = job else {
return
}
state = .started
self.completionHandler = completionHandler
if let mainWindow = parentWindow ?? NSApplication.shared.mainWindow {
job.runModal(for: mainWindow, delegate: self, didRun: #selector(printOperationDidRun), contextInfo: nil)
}
}
@objc func printOperationDidRun(printOperation: NSPrintOperation,
success: Bool,
contextInfo: UnsafeMutableRawPointer?) {
state = success ? .completed : .canceled
channelDelegate?.onComplete(completed: success, error: nil)
if let completionHandler = completionHandler {
completionHandler(printOperation, success, contextInfo)
self.completionHandler = nil
}
}
public func getInfo() -> PrintJobInfo? {
guard let _ = job else {
return nil
}
return PrintJobInfo.init(fromPrintJobController: self)
}
public func disposeNoDismiss() {
channelDelegate?.dispose()
channelDelegate = nil
completionHandler = nil
job = nil
plugin?.printJobManager?.jobs[id] = nil
plugin = nil
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
completionHandler = nil
job = nil
plugin?.printJobManager?.jobs[id] = nil
plugin = nil
}
}
@@ -0,0 +1,80 @@
//
// PrintJobInfo.swift
// flutter_downloader
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
public class PrintJobInfo: NSObject {
var state: PrintJobState
var attributes: PrintAttributes
var creationTime: Int64
var numberOfPages: Int?
var copies: Int?
var label: String?
var printer: NSPrinter?
var pageOrder: Int?
var preferredRenderingQuality: Int?
var showsProgressPanel: Bool?
var showsPrintPanel: Bool?
var canSpawnSeparateThread: Bool?
var isCopyingOperation: Bool?
var currentPage: Int?
var firstPage: Int?
var lastPage: Int?
public init(fromPrintJobController: PrintJobController) {
state = fromPrintJobController.state
creationTime = fromPrintJobController.creationTime
attributes = PrintAttributes.init(fromPrintJobController: fromPrintJobController)
super.init()
if let job = fromPrintJobController.job {
let printInfo = job.printInfo
let printInfoDictionary = printInfo.dictionary()
printer = printInfo.printer
copies = printInfo.printSettings["com_apple_print_PrintSettings_PMCopies"] as? Int
label = job.jobTitle
firstPage = printInfoDictionary[NSPrintInfo.AttributeKey.firstPage] as? Int
lastPage = printInfoDictionary[NSPrintInfo.AttributeKey.lastPage] as? Int
if let firstPage = firstPage, let lastPage = lastPage {
numberOfPages = lastPage - firstPage + 1
}
if numberOfPages == nil {
numberOfPages = printInfo.printSettings["com_apple_print_PrintSettings_PMLastPage"] as? Int
if numberOfPages == nil || numberOfPages! > job.pageRange.length {
numberOfPages = job.pageRange.length
}
}
pageOrder = job.pageOrder.rawValue
preferredRenderingQuality = job.preferredRenderingQuality.rawValue
showsProgressPanel = job.showsProgressPanel
showsPrintPanel = job.showsPrintPanel
canSpawnSeparateThread = job.canSpawnSeparateThread
isCopyingOperation = job.isCopyingOperation
currentPage = job.currentPage
}
}
public func toMap () -> [String:Any?] {
return [
"state": state.rawValue,
"attributes": attributes.toMap(),
"numberOfPages": numberOfPages,
"copies": copies,
"creationTime": creationTime,
"label": label,
"printer": printer?.toMap(),
"pageOrder": pageOrder,
"preferredRenderingQuality": preferredRenderingQuality,
"showsProgressPanel": showsProgressPanel,
"showsPrintPanel": showsPrintPanel,
"canSpawnSeparateThread": canSpawnSeparateThread,
"isCopyingOperation": isCopyingOperation,
"currentPage": currentPage,
"firstPage": firstPage,
"lastPage": lastPage
]
}
}
@@ -0,0 +1,31 @@
//
// PrintJobManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
public class PrintJobManager: NSObject, Disposable {
var plugin: InAppWebViewFlutterPlugin?
var jobs: [String: PrintJobController?] = [:]
public init(plugin: InAppWebViewFlutterPlugin?) {
super.init()
self.plugin = plugin
}
public func dispose() {
let jobValues = jobs.values
jobValues.forEach { (job: PrintJobController?) in
job?.dispose()
}
jobs.removeAll()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,210 @@
//
// PrintJobSettings.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
@objcMembers
public class PrintJobSettings: ISettings<PrintJobController> {
public var handledByClient = false
public var jobName: String?
public var _orientation: NSNumber?
public var orientation: Int? {
get {
return _orientation?.intValue
}
set {
if let newValue = newValue {
_orientation = NSNumber.init(value: newValue)
} else {
_orientation = nil
}
}
}
public var _numberOfPages: NSNumber?
public var numberOfPages: Int64? {
get {
return _numberOfPages?.int64Value
}
set {
if let newValue = newValue {
_numberOfPages = NSNumber.init(value: newValue)
} else {
_numberOfPages = nil
}
}
}
public var margins: NSEdgeInsets?
public var colorMode: String?
public var showsNumberOfCopies = true
public var showsPaperOrientation = true
public var showsPaperSize = true
public var showsScaling = true
public var showsPageRange = true
public var showsPageSetupAccessory = true
public var showsPreview = true
public var showsPrintSelection = true
public var showsPrintPanel = true
public var showsProgressPanel = true
public var _scalingFactor: NSNumber?
public var scalingFactor: Double? {
get {
return _scalingFactor?.doubleValue
}
set {
if let newValue = newValue {
_scalingFactor = NSNumber.init(value: newValue)
} else {
_scalingFactor = nil
}
}
}
public var jobDisposition: String?
public var jobSavingURL: String?
public var paperName: String?
public var _horizontalPagination: NSNumber?
public var horizontalPagination: UInt? {
get {
return _horizontalPagination?.uintValue
}
set {
if let newValue = newValue {
_horizontalPagination = NSNumber.init(value: newValue)
} else {
_horizontalPagination = nil
}
}
}
public var _verticalPagination: NSNumber?
public var verticalPagination: UInt? {
get {
return _verticalPagination?.uintValue
}
set {
if let newValue = newValue {
_verticalPagination = NSNumber.init(value: newValue)
} else {
_verticalPagination = nil
}
}
}
public var isHorizontallyCentered = true
public var isVerticallyCentered = true
public var _pageOrder: NSNumber?
public var pageOrder: Int? {
get {
return _pageOrder?.intValue
}
set {
if let newValue = newValue {
_pageOrder = NSNumber.init(value: newValue)
} else {
_pageOrder = nil
}
}
}
public var canSpawnSeparateThread = true
public var copies = 1
public var _firstPage: NSNumber?
public var firstPage: Int64? {
get {
return _firstPage?.int64Value
}
set {
if let newValue = newValue {
_firstPage = NSNumber.init(value: newValue)
} else {
_firstPage = nil
}
}
}
public var _lastPage: NSNumber?
public var lastPage: Int64? {
get {
return _lastPage?.int64Value
}
set {
if let newValue = newValue {
_lastPage = NSNumber.init(value: newValue)
} else {
_lastPage = nil
}
}
}
public var detailedErrorReporting = false
public var faxNumber: String?
public var headerAndFooter = true
public var _mustCollate: NSNumber?
public var mustCollate: Bool? {
get {
return _mustCollate?.boolValue
}
set {
if let newValue = newValue {
_mustCollate = NSNumber.init(value: newValue)
} else {
_mustCollate = nil
}
}
}
public var _pagesAcross: NSNumber?
public var pagesAcross: Int64? {
get {
return _pagesAcross?.int64Value
}
set {
if let newValue = newValue {
_pagesAcross = NSNumber.init(value: newValue)
} else {
_pagesAcross = nil
}
}
}
public var _pagesDown: NSNumber?
public var pagesDown: Int64? {
get {
return _pagesDown?.int64Value
}
set {
if let newValue = newValue {
_pagesDown = NSNumber.init(value: newValue)
} else {
_pagesDown = nil
}
}
}
public var _time: NSNumber?
public var time: Int64? {
get {
return _time?.int64Value
}
set {
if let newValue = newValue {
_time = NSNumber.init(value: newValue)
} else {
_time = nil
}
}
}
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> PrintJobSettings {
let _ = super.parse(settings: settings)
if let marginsMap = settings["margins"] as? [String : Double] {
margins = NSEdgeInsets.fromMap(map: marginsMap)
}
return self
}
override func getRealSettings(obj: PrintJobController?) -> [String: Any?] {
var realOptions: [String: Any?] = toMap()
return realOptions
}
}
@@ -0,0 +1,32 @@
//
// BaseCallbackResult.swift
// shared-apple
//
// Created by Lorenzo Pichilli on 17/10/22.
//
import Foundation
public class BaseCallbackResult<T>: CallbackResult<T> {
override init() {
super.init()
self.success = { [weak self] (obj: Any?) in
let result: T? = self?.decodeResult(obj)
var shouldRunDefaultBehaviour = false
if let result = result {
shouldRunDefaultBehaviour = self?.nonNullSuccess(result) ?? shouldRunDefaultBehaviour
} else {
shouldRunDefaultBehaviour = self?.nullSuccess() ?? shouldRunDefaultBehaviour
}
if shouldRunDefaultBehaviour {
self?.defaultBehaviour(result)
}
}
self.notImplemented = { [weak self] in
self?.defaultBehaviour(nil)
}
}
}
@@ -0,0 +1,23 @@
//
// CGRect.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
extension CGRect {
public static func fromMap(map: [String: Double]) -> CGRect {
return CGRect(x: map["x"]!, y: map["y"]!, width: map["width"]!, height: map["height"]!)
}
public func toMap () -> [String:Any?] {
return [
"x": minX,
"y": minY,
"width": width,
"height": height
]
}
}
@@ -0,0 +1,18 @@
//
// CallbackResult.swift
// shared-apple
//
// Created by Lorenzo Pichilli on 17/10/22.
//
import Foundation
public class CallbackResult<T>: MethodChannelResult {
public var notImplemented: () -> Void = {}
public var success: (Any?) -> Void = {_ in }
public var error: (String, String?, Any?) -> Void = {_,_,_ in }
public var nonNullSuccess: (T) -> Bool = {_ in true}
public var nullSuccess: () -> Bool = {true}
public var defaultBehaviour: (T?) -> Void = {_ in }
public var decodeResult: (Any?) -> T? = {_ in nil}
}
@@ -0,0 +1,28 @@
//
// ChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 04/05/22.
//
import Foundation
import FlutterMacOS
public class ChannelDelegate: FlutterMethodCallDelegate, Disposable {
var channel: FlutterMethodChannel?
public init(channel: FlutterMethodChannel) {
super.init()
self.channel = channel
self.channel?.setMethodCallHandler(handle)
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
}
public func dispose() {
channel?.setMethodCallHandler(nil)
channel = nil
}
}
@@ -0,0 +1,22 @@
//
// ClientCertChallenge.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/02/21.
//
import Foundation
public class ClientCertChallenge: NSObject {
var protectionSpace: URLProtectionSpace!
public init(fromChallenge: URLAuthenticationChallenge) {
protectionSpace = fromChallenge.protectionSpace
}
public func toMap () -> [String:Any?] {
return [
"protectionSpace": protectionSpace.toMap(),
]
}
}
@@ -0,0 +1,33 @@
//
// ClientCertResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class ClientCertResponse: NSObject {
var certificatePath: String
var certificatePassword: String?
var keyStoreType: String?
var action: Int?
public init(certificatePath: String, certificatePassword: String? = nil, keyStoreType: String? = nil, action: Int? = nil) {
self.certificatePath = certificatePath
self.certificatePassword = certificatePassword
self.keyStoreType = keyStoreType
self.action = action
}
public static func fromMap(map: [String:Any?]?) -> ClientCertResponse? {
guard let map = map else {
return nil
}
let certificatePath = map["certificatePath"] as! String
let certificatePassword = map["certificatePassword"] as? String
let keyStoreType = map["keyStoreType"] as? String
let action = map["action"] as? Int
return ClientCertResponse(certificatePath: certificatePath, certificatePassword: certificatePassword, keyStoreType: keyStoreType, action: action)
}
}
@@ -0,0 +1,31 @@
//
// CreateWindowAction.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
import WebKit
public class CreateWindowAction: NSObject {
var navigationAction: WKNavigationAction
var windowId: Int64
var windowFeatures: WKWindowFeatures
var isDialog: Bool?
public init(navigationAction: WKNavigationAction, windowId: Int64, windowFeatures: WKWindowFeatures, isDialog: Bool? = nil) {
self.navigationAction = navigationAction
self.windowId = windowId
self.windowFeatures = windowFeatures
self.isDialog = isDialog
}
public func toMap () -> [String:Any?] {
var map = navigationAction.toMap()
map["windowId"] = windowId
map["windowFeatures"] = windowFeatures.toMap()
map["isDialog"] = isDialog
return map
}
}
@@ -0,0 +1,31 @@
//
// CustomSchemeResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
import FlutterMacOS
public class CustomSchemeResponse: NSObject {
var data: Data
var contentType: String
var contentEncoding: String
public init(data: Data, contentType: String, contentEncoding: String) {
self.data = data
self.contentType = contentType
self.contentEncoding = contentEncoding
}
public static func fromMap(map: [String:Any?]?) -> CustomSchemeResponse? {
guard let map = map else {
return nil
}
let data = map["data"] as! FlutterStandardTypedData
let contentType = map["contentType"] as! String
let contentEncoding = map["contentEncoding"] as! String
return CustomSchemeResponse(data: data.data, contentType: contentType, contentEncoding: contentEncoding)
}
}
@@ -0,0 +1,12 @@
//
// Disposable.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 04/05/22.
//
import Foundation
public protocol Disposable {
func dispose() -> Void
}
@@ -0,0 +1,42 @@
//
// DownloadStartRequest.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 17/04/22.
//
import Foundation
public class DownloadStartRequest: NSObject {
var url: String
var userAgent: String?
var contentDisposition: String?
var mimeType: String?
var contentLength: Int64
var suggestedFilename: String?
var textEncodingName: String?
public init(url: String, userAgent: String?, contentDisposition: String?,
mimeType: String?, contentLength: Int64,
suggestedFilename: String?, textEncodingName: String?) {
self.url = url
self.userAgent = userAgent
self.contentDisposition = contentDisposition
self.mimeType = mimeType
self.contentLength = contentLength
self.suggestedFilename = suggestedFilename
self.textEncodingName = textEncodingName
}
public func toMap () -> [String:Any?] {
return [
"url": url,
"userAgent": userAgent,
"contentDisposition": contentDisposition,
"mimeType": mimeType,
"contentLength": contentLength,
"suggestedFilename": suggestedFilename,
"textEncodingName": textEncodingName
]
}
}
@@ -0,0 +1,28 @@
//
// UIFindSession.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
public class FindSession: NSObject {
var resultCount: Int
var highlightedResultIndex: Int
var searchResultDisplayStyle: Int
public init(resultCount: Int, highlightedResultIndex: Int, searchResultDisplayStyle: Int) {
self.resultCount = resultCount
self.highlightedResultIndex = highlightedResultIndex
self.searchResultDisplayStyle = searchResultDisplayStyle
}
public func toMap () -> [String:Any?] {
return [
"resultCount": resultCount,
"highlightedResultIndex": highlightedResultIndex,
"searchResultDisplayStyle": searchResultDisplayStyle
]
}
}
@@ -0,0 +1,19 @@
//
// FlutterMethodCallDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/12/2019.
//
import Foundation
import FlutterMacOS
public class FlutterMethodCallDelegate: NSObject {
public override init() {
super.init()
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
}
}
@@ -0,0 +1,26 @@
//
// FlutterMethodChannel.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 06/05/22.
//
import Foundation
import FlutterMacOS
extension FlutterMethodChannel {
public func invokeMethod(_ method: String, arguments: Any, callback: MethodChannelResult) {
invokeMethod(method, arguments: arguments) {(result) -> Void in
if result is FlutterError {
let error = result as! FlutterError
callback.error(error.code, error.message, error.details)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
callback.notImplemented()
}
else {
callback.success(result)
}
}
}
}
@@ -0,0 +1,44 @@
//
// HitTestResult.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
public enum HitTestResultType: Int {
case unknownType = 0
case phoneType = 2
case geoType = 3
case emailType = 4
case imageType = 5
case srcAnchorType = 7
case srcImageAnchorType = 8
case editTextType = 9
}
public class HitTestResult: NSObject {
var type: HitTestResultType
var extra: String?
public init(type: HitTestResultType, extra: String?) {
self.type = type
self.extra = extra
}
public static func fromMap(map: [String:Any?]?) -> HitTestResult? {
guard let map = map else {
return nil
}
let type = HitTestResultType.init(rawValue: map["type"] as? Int ?? HitTestResultType.unknownType.rawValue) ?? HitTestResultType.unknownType
return HitTestResult(type: type, extra: map["extra"] as? String)
}
public func toMap () -> [String:Any?] {
return [
"type": type.rawValue,
"extra": extra,
]
}
}
@@ -0,0 +1,33 @@
//
// HttpAuthResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class HttpAuthResponse: NSObject {
var username: String
var password: String
var permanentPersistence: Bool
var action: Int?
public init(username: String, password: String, permanentPersistence: Bool, action: Int? = nil) {
self.username = username
self.password = password
self.permanentPersistence = permanentPersistence
self.action = action
}
public static func fromMap(map: [String:Any?]?) -> HttpAuthResponse? {
guard let map = map else {
return nil
}
let username = map["username"] as! String
let password = map["password"] as! String
let permanentPersistence = map["permanentPersistence"] as! Bool
let action = map["action"] as? Int
return HttpAuthResponse(username: username, password: password, permanentPersistence: permanentPersistence, action: action)
}
}
@@ -0,0 +1,34 @@
//
// HttpAuthenticationChallenge.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/02/21.
//
import Foundation
public class HttpAuthenticationChallenge: NSObject {
var protectionSpace: URLProtectionSpace!
var previousFailureCount: Int = 0
var failureResponse: URLResponse?
var error: Error?
var proposedCredential: URLCredential?
public init(fromChallenge: URLAuthenticationChallenge) {
protectionSpace = fromChallenge.protectionSpace
previousFailureCount = fromChallenge.previousFailureCount
failureResponse = fromChallenge.failureResponse
error = fromChallenge.error
proposedCredential = fromChallenge.proposedCredential
}
public func toMap () -> [String:Any?] {
return [
"protectionSpace": protectionSpace.toMap(),
"previousFailureCount": previousFailureCount,
"failureResponse": failureResponse?.toMap(),
"error": error?.localizedDescription,
"proposedCredential": proposedCredential?.toMap()
]
}
}
@@ -0,0 +1,49 @@
//
// InAppBrowserMenuItem.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 23/05/23.
//
import Foundation
import FlutterMacOS
public class InAppBrowserMenuItem: NSObject {
var id: Int64
var title: String
var order: Int64?
var icon: NSImage?
var iconColor: NSColor?
var showAsAction = false
public init(id: Int64, title: String, order: Int64?, icon: NSImage?, iconColor: NSColor?, showAsAction: Bool) {
self.id = id
self.title = title
self.order = order
self.icon = icon
self.iconColor = iconColor
self.showAsAction = showAsAction
if let icon = icon, let iconColor = iconColor {
self.icon = icon.tint(color: iconColor)
}
}
public static func fromMap(map: [String:Any?]?) -> InAppBrowserMenuItem? {
guard let map = map else {
return nil
}
let id = map["id"] as! Int64
let title = map["title"] as! String
let order = map["order"] as? Int64
var icon = NSImage.fromMap(map: map["icon"] as? [String : Any?])
if let data = map["icon"] as? FlutterStandardTypedData {
icon = NSImage(data: data.data)
}
var iconColor: NSColor? = nil
if let hexString = map["iconColor"] as? String {
iconColor = NSColor(hexString: hexString)
}
let showAsAction = map["showAsAction"] as! Bool
return InAppBrowserMenuItem(id: id, title: title, order: order, icon: icon, iconColor: iconColor, showAsAction: showAsAction)
}
}
@@ -0,0 +1,14 @@
//
// InAppBrowserWindowType.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 19/10/22.
//
import Foundation
public enum InAppBrowserWindowType: String {
case window = "WINDOW"
case child = "CHILD"
case tabbed = "TABBED"
}
@@ -0,0 +1,33 @@
//
// JsAlertResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 06/05/22.
//
import Foundation
public class JsAlertResponse: NSObject {
var message: String
var confirmButtonTitle: String
var handledByClient: Bool
var action: Int?
public init(message: String, confirmButtonTitle: String, handledByClient: Bool, action: Int? = nil) {
self.message = message
self.confirmButtonTitle = confirmButtonTitle
self.handledByClient = handledByClient
self.action = action
}
public static func fromMap(map: [String:Any?]?) -> JsAlertResponse? {
guard let map = map else {
return nil
}
let message = map["message"] as! String
let confirmButtonTitle = map["confirmButtonTitle"] as! String
let handledByClient = map["handledByClient"] as! Bool
let action = map["action"] as? Int
return JsAlertResponse(message: message, confirmButtonTitle: confirmButtonTitle, handledByClient: handledByClient, action: action)
}
}
@@ -0,0 +1,36 @@
//
// JsConfirmResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class JsConfirmResponse: NSObject {
var message: String
var confirmButtonTitle: String
var cancelButtonTitle: String
var handledByClient: Bool
var action: Int?
public init(message: String, confirmButtonTitle: String, cancelButtonTitle: String, handledByClient: Bool, action: Int? = nil) {
self.message = message
self.confirmButtonTitle = confirmButtonTitle
self.cancelButtonTitle = cancelButtonTitle
self.handledByClient = handledByClient
self.action = action
}
public static func fromMap(map: [String:Any?]?) -> JsConfirmResponse? {
guard let map = map else {
return nil
}
let message = map["message"] as! String
let confirmButtonTitle = map["confirmButtonTitle"] as! String
let cancelButtonTitle = map["cancelButtonTitle"] as! String
let handledByClient = map["handledByClient"] as! Bool
let action = map["action"] as? Int
return JsConfirmResponse(message: message, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle, handledByClient: handledByClient, action: action)
}
}
@@ -0,0 +1,43 @@
//
// JsPromptResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class JsPromptResponse: NSObject {
var message: String
var defaultValue: String
var confirmButtonTitle: String
var cancelButtonTitle: String
var handledByClient: Bool
var value: String?
var action: Int?
public init(message: String, defaultValue: String, confirmButtonTitle: String, cancelButtonTitle: String, handledByClient: Bool, value: String? = nil, action: Int? = nil) {
self.message = message
self.defaultValue = defaultValue
self.confirmButtonTitle = confirmButtonTitle
self.cancelButtonTitle = cancelButtonTitle
self.handledByClient = handledByClient
self.value = value
self.action = action
}
public static func fromMap(map: [String:Any?]?) -> JsPromptResponse? {
guard let map = map else {
return nil
}
let message = map["message"] as! String
let defaultValue = map["defaultValue"] as! String
let confirmButtonTitle = map["confirmButtonTitle"] as! String
let cancelButtonTitle = map["cancelButtonTitle"] as! String
let handledByClient = map["handledByClient"] as! Bool
let value = map["value"] as? String
let action = map["action"] as? Int
return JsPromptResponse(message: message, defaultValue: defaultValue, confirmButtonTitle: confirmButtonTitle, cancelButtonTitle: cancelButtonTitle,
handledByClient: handledByClient, value: value, action: action)
}
}
@@ -0,0 +1,14 @@
//
// MethodChannelResult.swift
// shared-apple
//
// Created by Lorenzo Pichilli on 17/10/22.
//
import Foundation
public protocol MethodChannelResult {
var success: (_ obj: Any?) -> Void { get set }
var error: (_ code: String, _ message: String?, _ details: Any?) -> Void { get set }
var notImplemented: () -> Void { get set }
}
@@ -0,0 +1,63 @@
//
// NSAttributedString.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/03/21.
//
import Foundation
extension NSAttributedString {
public static func fromMap(map: [String:Any?]?) -> NSAttributedString? {
guard let map = map, let string = map["string"] as? String else {
return nil
}
var attributes: [NSAttributedString.Key : Any] = [:]
if let backgroundColor = map["backgroundColor"] as? String {
attributes[.backgroundColor] = NSColor(hexString: backgroundColor)
}
if let baselineOffset = map["baselineOffset"] as? Double {
attributes[.baselineOffset] = baselineOffset
}
if let expansion = map["expansion"] as? Double {
attributes[.expansion] = expansion
}
if let foregroundColor = map["foregroundColor"] as? String {
attributes[.foregroundColor] = NSColor(hexString: foregroundColor)
}
if let kern = map["kern"] as? Double {
attributes[.kern] = kern
}
if let ligature = map["ligature"] as? Int64 {
attributes[.ligature] = ligature
}
if let obliqueness = map["obliqueness"] as? Double {
attributes[.obliqueness] = obliqueness
}
if let strikethroughColor = map["strikethroughColor"] as? String {
attributes[.strikethroughColor] = NSColor(hexString: strikethroughColor)
}
if let strikethroughStyle = map["strikethroughStyle"] as? Int64 {
attributes[.strikethroughStyle] = strikethroughStyle
}
if let strokeColor = map["strokeColor"] as? String {
attributes[.strokeColor] = NSColor(hexString: strokeColor)
}
if let strokeWidth = map["strokeWidth"] as? Double {
attributes[.strokeWidth] = strokeWidth
}
if let textEffect = map["textEffect"] as? String {
attributes[.textEffect] = textEffect
}
if let underlineColor = map["underlineColor"] as? String {
attributes[.underlineColor] = NSColor(hexString: underlineColor)
}
if let underlineStyle = map["underlineStyle"] as? Int64 {
attributes[.underlineStyle] = underlineStyle
}
return NSAttributedString(string: string, attributes: attributes)
}
}
@@ -0,0 +1,56 @@
//
// NSColor.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 19/02/21.
//
import Foundation
extension NSColor {
convenience init(hexString: String) {
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt64()
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
}
var hexString: String? {
guard let rgbColor = usingColorSpace(.sRGB) else {
return "#FFFFFF"
}
let red: CGFloat = rgbColor.redComponent
let green: CGFloat = rgbColor.greenComponent
let blue: CGFloat = rgbColor.blueComponent
let alpha: CGFloat = rgbColor.alphaComponent
if alpha == 1.0 {
return String(
format: "#%02lX%02lX%02lX",
Int(red * 0xFF),
Int(green * 0xFF),
Int(blue * 0xFF)
)
}
else {
return String(
format: "#%02lX%02lX%02lX%02lX",
Int(red * 0xFF),
Int(green * 0xFF),
Int(blue * 0xFF),
Int(alpha * 0xFF)
)
}
}
}
@@ -0,0 +1,23 @@
//
// UIEdgeInsets.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 11/05/22.
//
import Foundation
extension NSEdgeInsets {
public static func fromMap(map: [String: Double]) -> NSEdgeInsets {
return NSEdgeInsets.init(top: map["top"]!, left: map["left"]!, bottom: map["bottom"]!, right: map["right"]!)
}
public func toMap () -> [String:Any?] {
return [
"top": top,
"right": self.right,
"bottom": bottom,
"left": self.left
]
}
}
@@ -0,0 +1,36 @@
//
// NSImage.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 23/05/23.
//
import Foundation
import FlutterMacOS
extension NSImage {
public static func fromMap(map: [String:Any?]?) -> NSImage? {
guard let map = map else {
return nil
}
if let name = map["name"] as? String {
return NSImage(named: name)
}
if #available(macOS 11.0, *), let systemName = map["systemName"] as? String {
return NSImage(systemSymbolName: systemName, accessibilityDescription: nil)
}
if let data = map["data"] as? FlutterStandardTypedData {
return NSImage(data: data.data)
}
return nil
}
func tint(color: NSColor) -> NSImage {
return NSImage(size: size, flipped: false) { (rect) -> Bool in
color.set()
rect.fill()
self.draw(in: rect, from: NSRect(origin: .zero, size: self.size), operation: .destinationIn, fraction: 1.0)
return true
}
}
}
@@ -0,0 +1,18 @@
//
// NSPrinter.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 17/10/22.
//
import Foundation
extension NSPrinter {
public func toMap () -> [String:Any?] {
return [
"type": type.rawValue,
"languageLevel": languageLevel,
"name": name
]
}
}
@@ -0,0 +1,29 @@
//
// PermissionRequest.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 21/04/22.
//
import Foundation
import WebKit
public class PermissionRequest: NSObject {
var origin: String
var resources: [StringOrInt]
var frame: WKFrameInfo
public init(origin: String, resources: [StringOrInt], frame: WKFrameInfo) {
self.origin = origin
self.resources = resources
self.frame = frame
}
public func toMap () -> [String:Any?] {
return [
"origin": origin,
"resources": resources,
"frame": frame.toMap()
]
}
}
@@ -0,0 +1,27 @@
//
// PermissionResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class PermissionResponse: NSObject {
var resources: [Any]
var action: Int?
public init(resources: [Any], action: Int? = nil) {
self.resources = resources
self.action = action
}
public static func fromMap(map: [String:Any?]?) -> PermissionResponse? {
guard let map = map else {
return nil
}
let resources = map["resources"] as! [Any]
let action = map["action"] as? Int
return PermissionResponse(resources: resources, action: action)
}
}
@@ -0,0 +1,109 @@
//
// PluginScript.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 17/02/21.
//
import Foundation
import WebKit
public class PluginScript: UserScript {
var requiredInAllContentWorlds = false
var messageHandlerNames: [String] = []
public override init(source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool) {
super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly)
}
public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) {
super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly)
self.requiredInAllContentWorlds = requiredInAllContentWorlds
self.messageHandlerNames = messageHandlerNames
}
@available(macOS 11.0, *)
public override init(source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld) {
super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld)
self.contentWorld = contentWorld
}
@available(macOS 11.0, *)
public init(source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) {
super.init(source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld)
self.requiredInAllContentWorlds = requiredInAllContentWorlds
self.messageHandlerNames = messageHandlerNames
}
@available(macOS 11.0, *)
public init(groupName: String, source: String, injectionTime: WKUserScriptInjectionTime, forMainFrameOnly: Bool, in contentWorld: WKContentWorld, requiredInAllContentWorlds: Bool = false, messageHandlerNames: [String] = []) {
super.init(groupName: groupName, source: source, injectionTime: injectionTime, forMainFrameOnly: forMainFrameOnly, in: contentWorld)
self.requiredInAllContentWorlds = requiredInAllContentWorlds
self.messageHandlerNames = messageHandlerNames
}
public func copyAndSet(groupName: String? = nil,
source: String? = nil,
injectionTime: WKUserScriptInjectionTime? = nil,
forMainFrameOnly: Bool? = nil,
requiredInAllContentWorlds: Bool? = nil,
messageHandlerNames: [String]? = nil) -> PluginScript {
if #available(macOS 11.0, *) {
return PluginScript(
groupName: groupName ?? self.groupName!,
source: source ?? self.source,
injectionTime: injectionTime ?? self.injectionTime,
forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly,
in: self.contentWorld,
requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds,
messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames
)
}
return PluginScript(
groupName: groupName ?? self.groupName!,
source: source ?? self.source,
injectionTime: injectionTime ?? self.injectionTime,
forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly,
requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds,
messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames
)
}
@available(macOS 11.0, *)
public func copyAndSet(groupName: String? = nil,
source: String? = nil,
injectionTime: WKUserScriptInjectionTime? = nil,
forMainFrameOnly: Bool? = nil,
contentWorld: WKContentWorld? = nil,
requiredInAllContentWorlds: Bool? = nil,
messageHandlerNames: [String]? = nil) -> PluginScript {
return PluginScript(
groupName: groupName ?? self.groupName!,
source: source ?? self.source,
injectionTime: injectionTime ?? self.injectionTime,
forMainFrameOnly: forMainFrameOnly ?? self.isForMainFrameOnly,
in: contentWorld ?? self.contentWorld,
requiredInAllContentWorlds: requiredInAllContentWorlds ?? self.requiredInAllContentWorlds,
messageHandlerNames: messageHandlerNames ?? self.messageHandlerNames
)
}
static func == (lhs: PluginScript, rhs: PluginScript) -> Bool {
if #available(macOS 11.0, *) {
return lhs.groupName == rhs.groupName &&
lhs.source == rhs.source &&
lhs.injectionTime == rhs.injectionTime &&
lhs.isForMainFrameOnly == rhs.isForMainFrameOnly &&
lhs.contentWorld == rhs.contentWorld &&
lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds &&
lhs.messageHandlerNames == rhs.messageHandlerNames
} else {
return lhs.groupName == rhs.groupName &&
lhs.source == rhs.source &&
lhs.injectionTime == rhs.injectionTime &&
lhs.isForMainFrameOnly == rhs.isForMainFrameOnly &&
lhs.requiredInAllContentWorlds == rhs.requiredInAllContentWorlds &&
lhs.messageHandlerNames == rhs.messageHandlerNames
}
}
}
@@ -0,0 +1,15 @@
//
// SecCertificate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 19/02/21.
//
import Foundation
extension SecCertificate {
var data: Data {
let serverCertificateCFData = SecCertificateCopyData(self)
return serverCertificateCFData as Data
}
}
@@ -0,0 +1,24 @@
//
// ServerTrustAuthResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class ServerTrustAuthResponse: NSObject {
var action: Int?
public init(action: Int? = nil) {
self.action = action
}
public static func fromMap(map: [String:Any?]?) -> ServerTrustAuthResponse? {
guard let map = map else {
return nil
}
let action = map["action"] as? Int
return ServerTrustAuthResponse(action: action)
}
}
@@ -0,0 +1,22 @@
//
// ServerTrustChallenge.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/02/21.
//
import Foundation
public class ServerTrustChallenge: NSObject {
var protectionSpace: URLProtectionSpace!
public init(fromChallenge: URLAuthenticationChallenge) {
protectionSpace = fromChallenge.protectionSpace
}
public func toMap () -> [String:Any?] {
return [
"protectionSpace": protectionSpace.toMap()
]
}
}
@@ -0,0 +1,35 @@
//
// Size.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 26/03/21.
//
import Foundation
public class Size2D: NSObject {
var width: Double
var height: Double
public init(width: Double, height: Double) {
self.width = width
self.height = height
}
public static func fromMap(map: [String:Any?]?) -> Size2D? {
guard let map = map else {
return nil
}
return Size2D(
width: map["width"] as? Double ?? -1.0,
height: map["height"] as? Double ?? -1.0
)
}
public func toMap() -> [String:Any?] {
return [
"width": width,
"height": height
]
}
}
@@ -0,0 +1,30 @@
//
// SslCertificate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/02/21.
//
import Foundation
public class SslCertificate: NSObject {
var x509Certificate: Data
var issuedBy: Any?
var issuedTo: Any?
var validNotAfterDate: Any?
var validNotBeforeDate: Any?
public init(x509Certificate: Data) {
self.x509Certificate = x509Certificate
}
public func toMap () -> [String:Any?] {
return [
"x509Certificate": x509Certificate,
"issuedBy": issuedBy,
"issuedTo": issuedTo,
"validNotAfterDate": validNotAfterDate,
"validNotBeforeDate": validNotBeforeDate
]
}
}
@@ -0,0 +1,50 @@
//
// SslError.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/02/21.
//
import Foundation
public class SslError: NSObject {
var errorType: SecTrustResultType?
var message: String?
public init(errorType: SecTrustResultType?) {
self.errorType = errorType
var sslErrorMessage: String? = nil
switch errorType {
case .deny:
sslErrorMessage = "Indicates a user-configured deny; do not proceed."
break
case .fatalTrustFailure:
sslErrorMessage = "Indicates a trust failure which cannot be overridden by the user."
break
case .invalid:
sslErrorMessage = "Indicates an invalid setting or result."
break
case .otherError:
sslErrorMessage = "Indicates a failure other than that of trust evaluation."
break
case .recoverableTrustFailure:
sslErrorMessage = "Indicates a trust policy failure which can be overridden by the user."
break
case .unspecified:
sslErrorMessage = "Indicates the evaluation succeeded and the certificate is implicitly trusted, but user intent was not explicitly specified."
break
default:
sslErrorMessage = nil
}
self.message = sslErrorMessage
}
public func toMap () -> [String:Any?] {
return [
"code": errorType?.rawValue,
"message": message
]
}
}
@@ -0,0 +1,13 @@
//
// StringOrInt.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 21/04/22.
//
import Foundation
public protocol StringOrInt { }
extension Int: StringOrInt { }
extension String: StringOrInt { }
@@ -0,0 +1,20 @@
//
// URLAuthenticationChallenge.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 19/02/21.
//
import Foundation
extension URLAuthenticationChallenge {
public func toMap () -> [String:Any?] {
return [
"protectionSpace": protectionSpace.toMap(),
"previousFailureCount": previousFailureCount,
"failureResponse": failureResponse?.toMap(),
"error": error?.localizedDescription,
"proposedCredential": proposedCredential?.toMap(),
]
}
}
@@ -0,0 +1,26 @@
//
// URLCredential.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 19/02/21.
//
import Foundation
extension URLCredential {
public func toMap () -> [String:Any?] {
var x509Certificates: [Data] = []
// certificates could be nil!!!
if certificates != nil {
for certificate in certificates {
x509Certificates.append((certificate as! SecCertificate).data)
}
}
return [
"password": password,
"username": user,
"certificates": x509Certificates,
"persistence": persistence.rawValue
]
}
}
@@ -0,0 +1,63 @@
//
// URLProtectionSpace.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 19/02/21.
//
import Foundation
extension URLProtectionSpace {
var x509Certificate: Data? {
guard let serverTrust = serverTrust else {
return nil
}
var secResult = SecTrustResultType.invalid
let secTrustEvaluateStatus = SecTrustEvaluate(serverTrust, &secResult);
if secTrustEvaluateStatus == errSecSuccess, let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
return serverCertificate.data
}
return nil
}
var sslCertificate: SslCertificate? {
var sslCertificate: SslCertificate? = nil
if let x509Certificate = x509Certificate {
sslCertificate = SslCertificate(x509Certificate: x509Certificate)
}
return sslCertificate
}
var sslError: SslError? {
guard let serverTrust = serverTrust else {
return nil
}
var secResult = SecTrustResultType.invalid
SecTrustEvaluate(serverTrust, &secResult);
guard let sslErrorType = secResult != SecTrustResultType.proceed ? secResult : nil else {
return nil
}
return SslError(errorType: sslErrorType)
}
public func toMap () -> [String:Any?] {
return [
"host": host,
"protocol": self.protocol,
"realm": realm,
"port": port,
"sslCertificate": sslCertificate?.toMap(),
"sslError": sslError?.toMap(),
"authenticationMethod": authenticationMethod,
"distinguishedNames": distinguishedNames,
"receivesCredentialSecurely": receivesCredentialSecurely,
"proxyType": proxyType
]
}
}
Loaded 100 of 556 files, more files were not shown because too many files have changed in this diff. Show more