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,90 @@
## 1.1.2
- Updated flutter_inappwebview_platform_interface version to ^1.3.0
## 1.1.1
- Updated flutter_inappwebview_platform_interface version to ^1.2.0
## 1.1.0+3
- Updated flutter_inappwebview_platform_interface version
## ## 1.1.0+2
- Updated pubspec.yaml
## 1.1.0+1
- Fixed "v6.1.0 fails to compile on Xcode 15" [#2288](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2288)
## 1.1.0
- Fixed XCode 16 build
- Updates minimum supported SDK version to Flutter 3.24/Dart 3.5.
- Merged "Add privacy manifest for iOS" [#2029](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2029) (thanks to [ueman](https://github.com/ueman))
## 1.0.13
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.10`
## 1.0.12
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.9`
- Fix typos and other code improvements (thanks to [michalsrutek](https://github.com/michalsrutek))
- Fixed "runtime issue of SecTrustCopyExceptions 'This method should not be called on the main thread as it may lead to UI unresponsiveness.' when using onReceivedServerTrustAuthRequest" [#1924](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1924)
- Merged "💥 Fix iPad crash due to missing sourceView" [#1933](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1933) (thanks to [michalsrutek](https://github.com/michalsrutek))
- Merged "💥 Fix crash - remove force unwrapping from dispose method" [#1932](https://github.com/pichillilorenzo/flutter_inappwebview/pull/1932) (thanks to [michalsrutek](https://github.com/michalsrutek))
## 1.0.11
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.8`
## 1.0.10
- Updated `flutter_inappwebview_platform_interface` version dependency to `^1.0.7`
## 1.0.9
- Implemented `InAppWebViewSettings.interceptOnlyAsyncAjaxRequests`
- Updated `useShouldInterceptAjaxRequest` automatic infer logic
- Updated `CookieManager` methods return value
- Fixed "iOS crash at public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)" [#1912](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1912)
## 1.0.8
- Fixed error in InterceptAjaxRequestJS 'Failed to set responseType property'
- Fixed shouldInterceptAjaxRequest javascript code when overriding XMLHttpRequest.open method parameters
## 1.0.7
- Fixed "getFavicons: _TypeError: type '_Map<String, dynamic>' is not a subtype of type 'Iterable<dynamic>'" [#1897](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1897)
## 1.0.6
- Possible fix for "iOS Fatal Crash" [#1894](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1894)
## 1.0.5
- Call `super.dispose();` on `InAppBrowser` and `ChromeSafari` implementations
## 1.0.4
- Fixed "Cloudflare Turnstile failure" [#1738](https://github.com/pichillilorenzo/flutter_inappwebview/issues/1738)
## 1.0.3
- Fixed `InAppBrowserMenuItem.iconColor` not working
## 1.0.2
- Added `PlatformPrintJobController.onComplete` setter
- Updated `flutter_inappwebview_platform_interface` version dependency to `1.0.2`
## 1.0.1
- Updated README
## 1.0.0
Initial release.
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2023 Lorenzo Pichilli
Licensed 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.
@@ -0,0 +1,13 @@
# flutter\_inappwebview\_ios
The Apple iOS WKWebView implementation of [`flutter_inappwebview`](https://pub.dev/packages/flutter_inappwebview).
## Usage
This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin),
which means you can simply use `flutter_inappwebview`
normally. This package will be automatically included in your app when you do,
so you do not need to add it to your `pubspec.yaml`.
However, if you `import` this package to use any of its APIs directly, you
should add it to your `pubspec.yaml` as usual.
@@ -0,0 +1,171 @@
//
// CredentialDatabase.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 29/10/2019.
//
import Foundation
public class CredentialDatabase: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_credential_database"
static let credentialStore = URLCredentialStorage.shared
private var plugin: SwiftFlutterPlugin?
init(plugin: SwiftFlutterPlugin) {
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,158 @@
//
// FindInteractionChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
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 "isFindNavigatorVisible":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
result(interaction.isFindNavigatorVisible)
} else {
result(false)
}
} else {
result(false)
}
break
case "updateResultCount":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
interaction.updateResultCount()
result(true)
} else {
result(false)
}
} else {
result(false)
}
break
case "presentFindNavigator":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
interaction.presentFindNavigator(showingReplace: false)
result(true)
} else {
result(false)
}
} else {
result(false)
}
break
case "dismissFindNavigator":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
interaction.dismissFindNavigator()
result(true)
} else {
result(false)
}
} else {
result(false)
}
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,162 @@
//
// FindInteractionController.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
import Flutter
public class FindInteractionController: NSObject, Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_"
var webView: InAppWebView?
var channelDelegate: FindInteractionChannelDelegate?
private var plugin: SwiftFlutterPlugin?
private var settings: FindInteractionSettings?
private var _searchText: String? = nil
var searchText: String? {
get {
if #available(iOS 16.0, *), let interaction = webView?.findInteraction {
return interaction.searchText
}
return _searchText
}
set {
if #available(iOS 16.0, *), let interaction = webView?.findInteraction {
interaction.searchText = newValue
}
self._searchText = newValue
}
}
private var _activeFindSession: FindSession? = nil
var activeFindSession: FindSession? {
get {
if #available(iOS 16.0, *), let interaction = webView?.findInteraction {
if let activeFindSession = interaction.activeFindSession {
return FindSession.fromUIFindSession(uiFindSession: activeFindSession)
}
return nil
}
return _activeFindSession
}
set {
self._activeFindSession = newValue
}
}
public init(plugin: SwiftFlutterPlugin, 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 #available(iOS 16.0, *), webView.isFindInteractionEnabled {
if let interaction = webView.findInteraction {
interaction.searchText = find
interaction.presentFindNavigator(showingReplace: false)
}
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
} else 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
}
if #available(iOS 16.0, *), webView.isFindInteractionEnabled {
if let interaction = webView.findInteraction {
if forward {
interaction.findNext()
} else {
interaction.findPrevious()
}
}
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
} else {
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
}
if #available(iOS 16.0, *), webView.isFindInteractionEnabled {
if let interaction = webView.findInteraction {
interaction.searchText = nil
interaction.dismissFindNavigator()
}
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
} else {
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 Flutter
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,99 @@
//
// HeadlessInAppWebView.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 26/03/21.
//
import Foundation
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: SwiftFlutterPlugin?
public init(plugin: SwiftFlutterPlugin, 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.alpha = 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: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
if let keyWindow = UIApplication.shared.keyWindow {
/// 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.
keyWindow.insertSubview(view, at: 0)
keyWindow.sendSubviewToBack(view)
}
}
}
public func setSize(size: Size2D) {
if let view = flutterWebView?.view() {
let width = size.width == -1.0 ? UIScreen.main.bounds.width : CGFloat(size.width)
let height = size.height == -1.0 ? UIScreen.main.bounds.height : 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.alpha = 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 Flutter
import UIKit
import WebKit
import Foundation
import AVFoundation
public class HeadlessInAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"
var plugin: SwiftFlutterPlugin?
var webViews: [String: HeadlessInAppWebView?] = [:]
init(plugin: SwiftFlutterPlugin) {
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,63 @@
//
// HeadlessWebViewChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/05/22.
//
import Foundation
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,50 @@
//
// 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?] = [:]
var counts = UInt32()
let properties = class_copyPropertyList(object_getClass(self), &counts)
for i in 0..<counts {
if let property = properties?.advanced(by: Int(i)).pointee {
let cName = property_getName(property)
let name = String(cString: cName)
let key = !name.hasPrefix("_") ? name : String(name.suffix(from: name.index(name.startIndex, offsetBy: 1)))
settings[key] = self.value(forKey: key)
}
}
free(properties)
return settings
}
func getRealSettings(obj: T?) -> [String: Any?] {
let realSettings: [String: Any?] = toMap()
return realSettings
}
}
@@ -0,0 +1,35 @@
//
// InAppBrowserChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/05/22.
//
import Foundation
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 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,153 @@
//
// InAppBrowserManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 18/12/2019.
//
import Flutter
import UIKit
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: SwiftFlutterPlugin?
private var previousStatusBarStyle = -1
init(plugin: SwiftFlutterPlugin) {
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 prepareInAppBrowserWebViewController(settings: [String: Any?]) -> InAppBrowserWebViewController {
if previousStatusBarStyle == -1 {
previousStatusBarStyle = UIApplication.shared.statusBarStyle.rawValue
}
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.isHidden = browserSettings.hidden
webViewController.webViewSettings = webViewSettings
webViewController.previousStatusBarStyle = previousStatusBarStyle
return webViewController
}
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 contextMenu = arguments["contextMenu"] as! [String: Any]
let windowId = arguments["windowId"] as? Int64
let initialUserScripts = arguments["initialUserScripts"] as? [[String: Any]]
let pullToRefreshInitialSettings = arguments["pullToRefreshSettings"] as! [String: Any?]
let menuItems = arguments["menuItems"] as! [[String: Any?]]
let webViewController = prepareInAppBrowserWebViewController(settings: settings)
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.contextMenu = contextMenu
webViewController.windowId = windowId
webViewController.initialUserScripts = initialUserScripts ?? []
webViewController.pullToRefreshInitialSettings = pullToRefreshInitialSettings
for menuItem in menuItems {
webViewController.menuItems.append(InAppBrowserMenuItem.fromMap(map: menuItem)!)
}
presentViewController(webViewController: webViewController)
}
public func presentViewController(webViewController: InAppBrowserWebViewController) {
let storyboard = UIStoryboard(name: InAppBrowserManager.WEBVIEW_STORYBOARD, bundle: Bundle(for: InAppWebViewFlutterPlugin.self))
let navController = storyboard.instantiateViewController(withIdentifier: InAppBrowserManager.NAV_STORYBOARD_CONTROLLER_ID) as! InAppBrowserNavigationController
webViewController.edgesForExtendedLayout = []
navController.pushViewController(webViewController, animated: false)
webViewController.prepareNavigationControllerBeforeViewWillAppear()
var animated = true
if let browserSettings = webViewController.browserSettings, browserSettings.hidden {
animated = false
}
guard let visibleViewController = UIApplication.shared.visibleViewController else {
assertionFailure("Failure init the visibleViewController!")
return
}
if let popover = navController.popoverPresentationController {
let sourceView = visibleViewController.view ?? UIView()
popover.sourceRect = CGRect(x: sourceView.bounds.midX, y: sourceView.bounds.midY, width: 0, height: 0)
popover.permittedArrowDirections = []
popover.sourceView = sourceView
}
visibleViewController.present(navController, animated: animated)
}
public func openWithSystemBrowser(url: String, result: @escaping FlutterResult) {
let absoluteUrl = URL(string: url)!.absoluteURL
if !UIApplication.shared.canOpenURL(absoluteUrl) {
result(FlutterError(code: "InAppBrowserManager", message: url + " cannot be opened!", details: nil))
return
}
else {
if #available(iOS 10.0, *) {
UIApplication.shared.open(absoluteUrl)
} else {
UIApplication.shared.openURL(absoluteUrl)
}
}
result(true)
}
public override func dispose() {
super.dispose()
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,19 @@
//
// InAppBrowserNavigationController.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 14/02/21.
//
import Foundation
public class InAppBrowserNavigationController: UINavigationController {
var tmpWindow: UIWindow?
deinit {
debugPrint("InAppBrowserNavigationController - dealloc")
tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0)
tmpWindow = nil
UIApplication.shared.delegate?.window??.makeKeyAndVisible()
}
}
@@ -0,0 +1,62 @@
//
// 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 toolbarTopTranslucent = true
var toolbarTopBarTintColor: String?
var toolbarTopTintColor: String?
var hideToolbarBottom = true
var toolbarBottomBackgroundColor: String?
var toolbarBottomTintColor: String?
var toolbarBottomTranslucent = true
var closeButtonCaption: String?
var closeButtonColor: String?
var presentationStyle = 0 //fullscreen
var transitionStyle = 0 //crossDissolve
var hideCloseButton = false
var menuButtonColor: String?
override init(){
super.init()
}
override func getRealSettings(obj: InAppBrowserWebViewController?) -> [String: Any?] {
var realOptions: [String: Any?] = toMap()
if let inAppBrowserWebViewController = obj {
realOptions["hidden"] = inAppBrowserWebViewController.isHidden
realOptions["hideUrlBar"] = inAppBrowserWebViewController.searchBar.isHidden
realOptions["hideProgressBar"] = inAppBrowserWebViewController.progressBar.isHidden
realOptions["closeButtonCaption"] = inAppBrowserWebViewController.closeButton.title
realOptions["closeButtonColor"] = inAppBrowserWebViewController.closeButton.tintColor?.hexString
realOptions["menuButtonColor"] = inAppBrowserWebViewController.menuButton?.tintColor?.hexString
if let navController = inAppBrowserWebViewController.navigationController {
realOptions["hideToolbarTop"] = navController.navigationBar.isHidden
realOptions["toolbarTopBackgroundColor"] = navController.navigationBar.backgroundColor?.hexString
realOptions["toolbarTopTranslucent"] = navController.navigationBar.isTranslucent
realOptions["toolbarTopBarTintColor"] = navController.navigationBar.barTintColor?.hexString
realOptions["toolbarTopTintColor"] = navController.navigationBar.tintColor?.hexString
realOptions["hideToolbarBottom"] = navController.toolbar.isHidden
realOptions["toolbarBottomBackgroundColor"] = navController.toolbar.barTintColor?.hexString
realOptions["toolbarBottomTranslucent"] = navController.toolbar.isTranslucent
realOptions["toolbarBottomTintColor"] = navController.toolbar.tintColor?.hexString
realOptions["presentationStyle"] = navController.modalPresentationStyle.rawValue
realOptions["transitionStyle"] = navController.modalTransitionStyle.rawValue
}
}
return realOptions
}
}
@@ -0,0 +1,654 @@
//
// InAppBrowserWebViewController.swift
// flutter_inappwebview
//
// Created by Lorenzo on 17/09/18.
//
import Flutter
import UIKit
import WebKit
import Foundation
public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelegate, UIScrollViewDelegate, UISearchBarDelegate, Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_"
var closeButton: UIBarButtonItem!
var reloadButton: UIBarButtonItem!
var backButton: UIBarButtonItem!
var forwardButton: UIBarButtonItem!
var shareButton: UIBarButtonItem!
var searchBar: UISearchBar!
var progressBar: UIProgressView!
var menuButton: UIBarButtonItem?
private var _menu: Any?
@available(iOS 13.0, *)
var menu: UIMenu? {
set {
_menu = newValue
}
get {
return _menu as? UIMenu
}
}
var tmpWindow: UIWindow?
var id: String = ""
var plugin: SwiftFlutterPlugin?
var windowId: Int64?
var webView: InAppWebView?
var channelDelegate: InAppBrowserChannelDelegate?
var initialUrlRequest: URLRequest?
var initialFile: String?
var contextMenu: [String: Any]?
var browserSettings: InAppBrowserSettings?
var webViewSettings: InAppWebViewSettings?
var initialData: String?
var initialMimeType: String?
var initialEncoding: String?
var initialBaseUrl: String?
var previousStatusBarStyle = -1
var initialUserScripts: [[String: Any]] = []
var pullToRefreshInitialSettings: [String: Any?] = [:]
var isHidden = false
var menuItems: [InAppBrowserMenuItem] = []
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!.contextMenu = contextMenu
webView!.initialUserScripts = userScripts
} else {
webView = InAppWebView(id: nil,
plugin: nil,
frame: .zero,
configuration: preWebviewConfiguration,
contextMenu: contextMenu,
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 pullToRefreshSettings = PullToRefreshSettings()
let _ = pullToRefreshSettings.parse(settings: pullToRefreshInitialSettings)
let pullToRefreshControl = PullToRefreshControl(plugin: plugin, id: id, settings: pullToRefreshSettings)
webView.pullToRefreshControl = pullToRefreshControl
pullToRefreshControl.delegate = webView
pullToRefreshControl.prepare()
let findInteractionController = FindInteractionController(
plugin: plugin,
id: id, webView: webView, settings: nil)
webView.findInteractionController = findInteractionController
findInteractionController.prepare()
prepareWebView()
webView.windowCreated = true
progressBar = UIProgressView(progressViewStyle: .bar)
view = UIView()
view.addSubview(webView)
view.insertSubview(progressBar, aboveSubview: webView)
}
public override func viewDidLoad() {
super.viewDidLoad()
webView?.translatesAutoresizingMaskIntoConstraints = false
progressBar.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 9.0, *) {
webView?.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true
webView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true
webView?.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
webView?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
progressBar.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true
progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
progressBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
} else {
if let webView = webView {
view.addConstraints([
NSLayoutConstraint(item: webView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: webView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: webView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: webView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0)
])
}
if let progressBar = progressBar {
view.addConstraints([
NSLayoutConstraint(item: progressBar, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0),
NSLayoutConstraint(item: progressBar, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: progressBar, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: 0)
])
}
}
if windowId != nil {
channelDelegate?.onBrowserCreated()
webView?.runWindowBeforeCreatedCallbacks()
} else {
if #available(iOS 11.0, *) {
if let contentBlockers = webView?.settings?.contentBlockers, contentBlockers.count > 0 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: .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 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 override func viewDidDisappear(_ animated: Bool) {
dispose()
super.viewDidDisappear(animated)
}
public override func viewWillDisappear (_ animated: Bool) {
super.viewWillDisappear(animated)
}
public func prepareNavigationControllerBeforeViewWillAppear() {
if let browserOptions = browserSettings {
navigationController?.modalPresentationStyle = UIModalPresentationStyle(rawValue: browserOptions.presentationStyle)!
navigationController?.modalTransitionStyle = UIModalTransitionStyle(rawValue: browserOptions.transitionStyle)!
}
}
public func prepareWebView() {
webView?.settings = webViewSettings
webView?.prepare()
searchBar = UISearchBar()
searchBar.keyboardType = .URL
searchBar.sizeToFit()
searchBar.delegate = self
navigationItem.titleView = searchBar
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
reloadButton = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(reload))
shareButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(share))
forwardButton = UIBarButtonItem(title: "\u{203A}", style: .plain, target: self, action: #selector(goForward))
forwardButton.isEnabled = false
backButton = UIBarButtonItem(title: "\u{2039}", style: .plain, target: self, action: #selector(goBack))
backButton.isEnabled = false
toolbarItems = [backButton, spacer, forwardButton, spacer, shareButton, spacer, reloadButton]
for state: UIControl.State in [.normal, .disabled, .highlighted, .selected] {
forwardButton.setTitleTextAttributes([
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 50.0),
NSAttributedString.Key.baselineOffset: 2.5
], for: state)
backButton.setTitleTextAttributes([
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 50.0),
NSAttributedString.Key.baselineOffset: 2.5
], for: state)
}
if let browserSettings = browserSettings {
if !browserSettings.hideToolbarTop {
navigationController?.navigationBar.isHidden = false
if browserSettings.hideUrlBar {
searchBar.isHidden = true
}
if let bgColor = browserSettings.toolbarTopBackgroundColor, !bgColor.isEmpty {
navigationController?.navigationBar.backgroundColor = UIColor(hexString: bgColor)
}
if let barTintColor = browserSettings.toolbarTopBarTintColor, !barTintColor.isEmpty {
navigationController?.navigationBar.barTintColor = UIColor(hexString: barTintColor)
}
if let tintColor = browserSettings.toolbarTopTintColor, !tintColor.isEmpty {
navigationController?.navigationBar.tintColor = UIColor(hexString: tintColor)
}
navigationController?.navigationBar.isTranslucent = browserSettings.toolbarTopTranslucent
}
else {
navigationController?.navigationBar.isHidden = true
}
if !browserSettings.hideToolbarBottom {
navigationController?.isToolbarHidden = false
if let bgColor = browserSettings.toolbarBottomBackgroundColor, !bgColor.isEmpty {
navigationController?.toolbar.barTintColor = UIColor(hexString: bgColor)
}
if let tintColor = browserSettings.toolbarBottomTintColor, !tintColor.isEmpty {
navigationController?.toolbar.tintColor = UIColor(hexString: tintColor)
}
navigationController?.toolbar.isTranslucent = false
}
else {
navigationController?.isToolbarHidden = true
}
if let closeButtonCaption = browserSettings.closeButtonCaption, !closeButtonCaption.isEmpty {
closeButton = UIBarButtonItem(title: closeButtonCaption, style: .plain, target: self, action: #selector(close))
} else {
setDefaultCloseButton()
}
if let closeButtonColor = browserSettings.closeButtonColor, !closeButtonColor.isEmpty {
closeButton.tintColor = UIColor(hexString: closeButtonColor)
}
if browserSettings.hideProgressBar {
progressBar.isHidden = true
}
navigationItem.rightBarButtonItems = []
if !browserSettings.hideCloseButton {
navigationItem.rightBarButtonItems = [closeButton]
}
if #available(iOS 14.0, *), !menuItems.isEmpty {
var uiActions: [UIAction] = []
menuItems = menuItems.sorted(by: {$0.order ?? 0 < $1.order ?? 0})
for menuItem in menuItems {
let uiAction = UIAction(title: menuItem.title, image: menuItem.icon, handler: {_ in
self.channelDelegate?.onMenuItemClicked(menuItem: menuItem)
})
if !menuItem.showAsAction {
uiActions.append(uiAction)
} else {
let buttonItem = UIBarButtonItem(primaryAction: uiAction)
buttonItem.tintColor = menuItem.iconColor
navigationItem.rightBarButtonItems?.append(buttonItem)
}
}
if !uiActions.isEmpty {
menu = UIMenu(title: "", options: .displayInline, children: uiActions)
menuButton = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu)
if let menuButtonColor = browserSettings.menuButtonColor, !menuButtonColor.isEmpty {
menuButton?.tintColor = UIColor(hexString: menuButtonColor)
}
let index = browserSettings.hideCloseButton ? 0 : 1
navigationItem.rightBarButtonItems?.insert(menuButton!, at: index)
}
}
}
}
func setDefaultCloseButton() {
if closeButton != nil {
closeButton.target = nil
closeButton.action = nil
}
var barButtonSystemItem = UIBarButtonItem.SystemItem.cancel
if #available(iOS 13.0, *) {
barButtonSystemItem = UIBarButtonItem.SystemItem.close
}
closeButton = UIBarButtonItem(barButtonSystemItem: barButtonSystemItem, target: self, action: #selector(close))
}
public func didChangeTitle(title: String?) {
guard let _ = title else {
return
}
}
public func didStartNavigation(url: URL?) {
forwardButton.isEnabled = webView?.canGoForward ?? false
backButton.isEnabled = webView?.canGoBack ?? false
progressBar.setProgress(0.0, animated: false)
guard let url = url else {
return
}
searchBar.text = url.absoluteString
}
public func didUpdateVisitedHistory(url: URL?) {
forwardButton.isEnabled = webView?.canGoForward ?? false
backButton.isEnabled = webView?.canGoBack ?? false
guard let url = url else {
return
}
searchBar.text = url.absoluteString
}
public func didFinishNavigation(url: URL?) {
forwardButton.isEnabled = webView?.canGoForward ?? false
backButton.isEnabled = webView?.canGoBack ?? false
progressBar.setProgress(0.0, animated: false)
guard let url = url else {
return
}
searchBar.text = url.absoluteString
}
public func didFailNavigation(url: URL?, error: Error) {
forwardButton.isEnabled = webView?.canGoForward ?? false
backButton.isEnabled = webView?.canGoBack ?? false
progressBar.setProgress(0.0, animated: false)
}
public func didChangeProgress(progress: Double) {
progressBar.setProgress(Float(progress), animated: true)
}
public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text,
let urlEncoded = text.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: urlEncoded) else {
return
}
let request = URLRequest(url: url)
webView?.load(request)
}
public func show(completion: (() -> Void)? = nil) {
if let navController = navigationController as? InAppBrowserNavigationController, let window = navController.tmpWindow {
isHidden = false
window.alpha = 0.0
window.isHidden = false
window.makeKeyAndVisible()
UIView.animate(withDuration: 0.2) {
window.alpha = 1.0
completion?()
}
}
}
public func hide(completion: (() -> Void)? = nil) {
if let navController = navigationController as? InAppBrowserNavigationController, let window = navController.tmpWindow {
isHidden = true
window.alpha = 1.0
UIView.animate(withDuration: 0.2) {
window.alpha = 0.0
} completion: { (finished) in
if finished {
window.isHidden = true
UIApplication.shared.delegate?.window??.makeKeyAndVisible()
completion?()
}
}
}
}
@objc public func reload() {
webView?.reload()
didUpdateVisitedHistory(url: webView?.url)
}
@objc public func share() {
let vc = UIActivityViewController(activityItems: [webView?.url?.absoluteString ?? ""], applicationActivities: [])
present(vc, animated: true, completion: nil)
}
public func close(completion: (() -> Void)? = nil) {
if (navigationController?.responds(to: #selector(getter: navigationController?.presentingViewController)))! {
navigationController?.presentingViewController?.dismiss(animated: true, completion: {() -> Void in
completion?()
})
}
else {
navigationController?.parent?.dismiss(animated: true, completion: {() -> Void in
completion?()
})
}
}
@objc public func close() {
if (navigationController?.responds(to: #selector(getter: navigationController?.presentingViewController)))! {
navigationController?.presentingViewController?.dismiss(animated: true, completion: nil)
}
else {
navigationController?.parent?.dismiss(animated: true, completion: nil)
}
}
@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)
}
public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) {
let newInAppWebViewSettings = InAppWebViewSettings()
let _ = newInAppWebViewSettings.parse(settings: newSettingsMap)
webView?.setSettings(newSettings: newInAppWebViewSettings, newSettingsMap: newSettingsMap)
if newSettingsMap["hidden"] != nil, browserSettings?.hidden != newSettings.hidden {
if newSettings.hidden {
hide()
}
else {
show()
}
}
if newSettingsMap["hideUrlBar"] != nil, browserSettings?.hideUrlBar != newSettings.hideUrlBar {
searchBar.isHidden = newSettings.hideUrlBar
}
if newSettingsMap["hideToolbarTop"] != nil, browserSettings?.hideToolbarTop != newSettings.hideToolbarTop {
navigationController?.navigationBar.isHidden = newSettings.hideToolbarTop
}
if newSettingsMap["toolbarTopBackgroundColor"] != nil, browserSettings?.toolbarTopBackgroundColor != newSettings.toolbarTopBackgroundColor {
if let bgColor = newSettings.toolbarTopBackgroundColor, !bgColor.isEmpty {
navigationController?.navigationBar.backgroundColor = UIColor(hexString: bgColor)
} else {
navigationController?.navigationBar.backgroundColor = nil
}
}
if newSettingsMap["toolbarTopBarTintColor"] != nil, browserSettings?.toolbarTopBarTintColor != newSettings.toolbarTopBarTintColor {
if let barTintColor = newSettings.toolbarTopBarTintColor, !barTintColor.isEmpty {
navigationController?.navigationBar.barTintColor = UIColor(hexString: barTintColor)
} else {
navigationController?.navigationBar.barTintColor = nil
}
}
if newSettingsMap["toolbarTopTintColor"] != nil, browserSettings?.toolbarTopTintColor != newSettings.toolbarTopTintColor {
if let tintColor = newSettings.toolbarTopTintColor, !tintColor.isEmpty {
navigationController?.navigationBar.tintColor = UIColor(hexString: tintColor)
} else {
navigationController?.navigationBar.tintColor = nil
}
}
if newSettingsMap["hideToolbarBottom"] != nil, browserSettings?.hideToolbarBottom != newSettings.hideToolbarBottom {
navigationController?.isToolbarHidden = !newSettings.hideToolbarBottom
}
if newSettingsMap["toolbarBottomBackgroundColor"] != nil, browserSettings?.toolbarBottomBackgroundColor != newSettings.toolbarBottomBackgroundColor {
if let bgColor = newSettings.toolbarBottomBackgroundColor, !bgColor.isEmpty {
navigationController?.toolbar.barTintColor = UIColor(hexString: bgColor)
} else {
navigationController?.toolbar.barTintColor = nil
}
}
if newSettingsMap["toolbarBottomTintColor"] != nil, browserSettings?.toolbarBottomTintColor != newSettings.toolbarBottomTintColor {
if let tintColor = newSettings.toolbarBottomTintColor, !tintColor.isEmpty {
navigationController?.toolbar.tintColor = UIColor(hexString: tintColor)
} else {
navigationController?.toolbar.tintColor = nil
}
}
if newSettingsMap["toolbarTopTranslucent"] != nil, browserSettings?.toolbarTopTranslucent != newSettings.toolbarTopTranslucent {
navigationController?.navigationBar.isTranslucent = newSettings.toolbarTopTranslucent
}
if newSettingsMap["toolbarBottomTranslucent"] != nil, browserSettings?.toolbarBottomTranslucent != newSettings.toolbarBottomTranslucent {
navigationController?.toolbar.isTranslucent = newSettings.toolbarBottomTranslucent
}
if newSettingsMap["closeButtonCaption"] != nil, browserSettings?.closeButtonCaption != newSettings.closeButtonCaption {
if let closeButtonCaption = newSettings.closeButtonCaption, !closeButtonCaption.isEmpty {
if let oldTitle = closeButton.title, !oldTitle.isEmpty {
closeButton.title = closeButtonCaption
} else {
closeButton.target = nil
closeButton.action = nil
closeButton = UIBarButtonItem(title: closeButtonCaption, style: .plain, target: self, action: #selector(close))
}
} else {
setDefaultCloseButton()
}
}
if newSettingsMap["closeButtonColor"] != nil, browserSettings?.closeButtonColor != newSettings.closeButtonColor {
if let tintColor = newSettings.closeButtonColor, !tintColor.isEmpty {
closeButton.tintColor = UIColor(hexString: tintColor)
} else {
closeButton.tintColor = nil
}
}
if newSettingsMap["hideCloseButton"] != nil, browserSettings?.hideCloseButton != newSettings.hideCloseButton {
if !newSettings.hideCloseButton {
navigationItem.rightBarButtonItems = [closeButton]
} else {
navigationItem.rightBarButtonItems = []
}
}
if newSettingsMap["presentationStyle"] != nil, browserSettings?.presentationStyle != newSettings.presentationStyle {
navigationController?.modalPresentationStyle = UIModalPresentationStyle(rawValue: newSettings.presentationStyle)!
}
if newSettingsMap["transitionStyle"] != nil, browserSettings?.transitionStyle != newSettings.transitionStyle {
navigationController?.modalTransitionStyle = UIModalTransitionStyle(rawValue: newSettings.transitionStyle)!
}
if newSettingsMap["hideProgressBar"] != nil, browserSettings?.hideProgressBar != newSettings.hideProgressBar {
progressBar.isHidden = newSettings.hideProgressBar
}
if newSettingsMap["menuButtonColor"] != nil, browserSettings?.menuButtonColor != newSettings.menuButtonColor {
if let tintColor = newSettings.menuButtonColor, !tintColor.isEmpty {
menuButton?.tintColor = UIColor(hexString: tintColor)
} else {
menuButton?.tintColor = nil
}
}
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 dispose() {
channelDelegate?.onExit()
channelDelegate?.dispose()
channelDelegate = nil
webView?.dispose()
webView?.removeFromSuperview()
webView = nil
view = nil
if previousStatusBarStyle != -1, let statusBarStyle = UIStatusBarStyle(rawValue: previousStatusBarStyle) {
UIApplication.shared.statusBarStyle = statusBarStyle
}
transitioningDelegate = nil
searchBar?.delegate = nil
closeButton?.target = nil
forwardButton?.target = nil
backButton?.target = nil
reloadButton?.target = nil
shareButton?.target = nil
menuButton?.target = nil
plugin = nil
}
deinit {
debugPrint("InAppBrowserWebViewController - 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 Flutter
import Foundation
import WebKit
@available(iOS 11.0, *)
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,213 @@
//
// FlutterWebViewController.swift
// flutter_inappwebview
//
// Created by Lorenzo on 13/11/18.
//
import Foundation
import WebKit
public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable {
var myView: UIView?
var keepAliveId: String?
init(plugin: SwiftFlutterPlugin, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) {
super.init()
myView = UIView(frame: frame)
myView!.clipsToBounds = true
keepAliveId = params["keepAliveId"] as? String
let initialSettings = params["initialSettings"] as! [String: Any?]
let contextMenu = params["contextMenu"] as? [String: Any]
let windowId = params["windowId"] as? Int64
let initialUserScripts = params["initialUserScripts"] as? [[String: Any]]
let pullToRefreshInitialSettings = params["pullToRefreshSettings"] 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 = myView!.bounds
webView!.contextMenu = contextMenu
webView!.initialUserScripts = userScripts
} else {
webView = InAppWebView(id: viewId,
plugin: plugin,
frame: myView!.bounds,
configuration: preWebviewConfiguration,
contextMenu: contextMenu,
userScripts: userScripts)
}
let pullToRefreshSettings = PullToRefreshSettings()
let _ = pullToRefreshSettings.parse(settings: pullToRefreshInitialSettings)
let pullToRefreshControl = PullToRefreshControl(plugin: plugin, id: viewId, settings: pullToRefreshSettings)
webView!.pullToRefreshControl = pullToRefreshControl
pullToRefreshControl.delegate = webView!
pullToRefreshControl.prepare()
let findInteractionController = FindInteractionController(
plugin: plugin,
id: viewId, webView: webView!, settings: nil)
webView!.findInteractionController = findInteractionController
findInteractionController.prepare()
webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
myView!.autoresizesSubviews = true
myView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
myView!.addSubview(webView!)
webView!.settings = settings
webView!.prepare()
webView!.windowCreated = true
}
public func webView() -> InAppWebView? {
for subview in myView?.subviews ?? []
{
if let item = subview as? InAppWebView
{
return item
}
}
return nil
}
public func view() -> UIView {
return myView!
}
public func makeInitialLoad(params: NSDictionary) {
guard let webView = webView() else {
return
}
let windowId = params["windowId"] as? Int64
let initialUrlRequest = params["initialUrlRequest"] as? [String: Any?]
let initialFile = params["initialFile"] as? String
let initialData = params["initialData"] as? [String: String?]
if windowId == nil {
if #available(iOS 11.0, *) {
webView.configuration.userContentController.removeAllContentRuleLists()
if let contentBlockers = webView.settings?.contentBlockers, contentBlockers.count > 0 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: .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 windowId != nil {
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 {
myView?.removeFromSuperview()
}
myView = nil
}
}
public func dispose() {
dispose(removeFromSuperview: false)
}
deinit {
debugPrint("FlutterWebViewController - dealloc")
dispose()
}
}
@@ -0,0 +1,74 @@
//
// FlutterWebViewFactory.swift
// flutter_inappwebview
//
// Created by Lorenzo on 13/11/18.
//
import Flutter
import Foundation
public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
static let VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview"
private var plugin: SwiftFlutterPlugin
init(plugin: SwiftFlutterPlugin) {
self.plugin = plugin
super.init()
}
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
let arguments = args as? NSDictionary
var flutterWebView: FlutterWebViewController?
var id: Any = viewId
let keepAliveId = arguments?["keepAliveId"] as? String
let headlessWebViewId = arguments?["headlessWebViewId"] as? String
let preventGestureDelay = arguments?["preventGestureDelay"] as? Bool ?? false
if let headlessWebViewId = headlessWebViewId,
let headlessWebView = plugin.headlessInAppWebViewManager?.webViews[headlessWebViewId],
let platformView = headlessWebView?.disposeAndGetFlutterWebView(withFrame: frame) {
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: frame,
viewIdentifier: id,
params: arguments!)
}
if let keepAliveId = keepAliveId {
plugin.inAppWebViewManager?.keepAliveWebViews[keepAliveId] = flutterWebView!
}
flutterWebView?.webView()?.preventGestureDelay = preventGestureDelay
if shouldMakeInitialLoad {
flutterWebView?.makeInitialLoad(params: arguments!)
}
return flutterWebView!
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,128 @@
//
// InAppWebViewManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/12/2019.
//
import Foundation
import WebKit
public class InAppWebViewManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_manager"
var plugin: SwiftFlutterPlugin?
var webViewForUserAgent: WKWebView?
var defaultUserAgent: String?
var keepAliveWebViews: [String:FlutterWebViewController?] = [:]
var windowWebViews: [Int64:WebViewTransport] = [:]
var windowAutoincrementId: Int64 = 0
init(plugin: SwiftFlutterPlugin) {
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(iOS 11.0, *) {
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) {
if #available(iOS 9.0, *) {
var websiteDataTypes = Set([WKWebsiteDataTypeMemoryCache])
if includeDiskFiles {
websiteDataTypes.insert(WKWebsiteDataTypeDiskCache)
if #available(iOS 11.3, *) {
websiteDataTypes.insert(WKWebsiteDataTypeFetchCache)
}
websiteDataTypes.insert(WKWebsiteDataTypeOfflineWebApplicationCache)
}
let date = NSDate(timeIntervalSince1970: 0)
WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: date as Date, completionHandler: completionHandler)
} else {
URLCache.shared.removeAllCachedResponses()
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,181 @@
//
// 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 verticalScrollBarEnabled = true
var horizontalScrollBarEnabled = 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 disableVerticalScroll = false
var disableHorizontalScroll = false
var disableContextMenu = false
var supportZoom = true
var allowUniversalAccessFromFileURLs = false
var allowFileAccessFromFileURLs = false
var disallowOverScroll = false
var enableViewportScale = false
var suppressesIncrementalRendering = false
var allowsAirPlayForMediaPlayback = true
var allowsBackForwardNavigationGestures = true
var allowsLinkPreview = true
var ignoresViewportScaleLimits = false
var allowsInlineMediaPlayback = false
var allowsPictureInPictureMediaPlayback = true
var isFraudulentWebsiteWarningEnabled = true
var selectionGranularity = 0
var dataDetectorTypes: [String] = ["NONE"] // WKDataDetectorTypeNone
var preferredContentMode = 0
var sharedCookiesEnabled = false
var automaticallyAdjustsScrollIndicatorInsets = false
var accessibilityIgnoresInvertColors = false
var decelerationRate = "NORMAL" // UIScrollView.DecelerationRate.normal
var alwaysBounceVertical = false
var alwaysBounceHorizontal = false
var scrollsToTop = true
var isPagingEnabled = false
var maximumZoomScale = 1.0
var minimumZoomScale = 1.0
var contentInsetAdjustmentBehavior = 2 // UIScrollView.ContentInsetAdjustmentBehavior.never
var isDirectionalLockEnabled = false
var mediaType: String? = nil
var pageZoom = 1.0
var limitsNavigationsToAppBoundDomains = false
var useOnNavigationResponse = false
var applePayAPIEnabled = false
var allowingReadAccessTo: String? = nil
var disableLongPressContextMenuOnLinks = false
var disableInputAccessoryView = false
var underPageBackgroundColor: String?
var isTextInteractionEnabled = true
var isSiteSpecificQuirksModeEnabled = true
var upgradeKnownHostsToHTTPS = true
var isElementFullscreenEnabled = true
var isFindInteractionEnabled = false
var minimumViewportInset: UIEdgeInsets? = nil
var maximumViewportInset: UIEdgeInsets? = nil
var isInspectable = false
var shouldPrintBackgrounds = false
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> InAppWebViewSettings {
var settings = settings // re-assing to be able to use removeValue
if let minimumViewportInsetMap = settings["minimumViewportInset"] as? [String : Double] {
minimumViewportInset = UIEdgeInsets.fromMap(map: minimumViewportInsetMap)
settings.removeValue(forKey: "minimumViewportInset")
}
if let maximumViewportInsetMap = settings["maximumViewportInset"] as? [String : Double] {
maximumViewportInset = UIEdgeInsets.fromMap(map: maximumViewportInsetMap)
settings.removeValue(forKey: "maximumViewportInset")
}
let _ = super.parse(settings: settings)
if #available(iOS 13.0, *) {} 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
if #available(iOS 9.0, *) {
realSettings["userAgent"] = webView.customUserAgent
realSettings["applicationNameForUserAgent"] = configuration.applicationNameForUserAgent
realSettings["allowsAirPlayForMediaPlayback"] = configuration.allowsAirPlayForMediaPlayback
realSettings["allowsLinkPreview"] = webView.allowsLinkPreview
realSettings["allowsPictureInPictureMediaPlayback"] = configuration.allowsPictureInPictureMediaPlayback
}
realSettings["javaScriptCanOpenWindowsAutomatically"] = configuration.preferences.javaScriptCanOpenWindowsAutomatically
if #available(iOS 10.0, *) {
realSettings["mediaPlaybackRequiresUserGesture"] = configuration.mediaTypesRequiringUserActionForPlayback == .all
realSettings["ignoresViewportScaleLimits"] = configuration.ignoresViewportScaleLimits
realSettings["dataDetectorTypes"] = Util.getDataDetectorTypeString(type: configuration.dataDetectorTypes)
} else {
realSettings["mediaPlaybackRequiresUserGesture"] = configuration.mediaPlaybackRequiresUserAction
}
realSettings["minimumFontSize"] = Int(configuration.preferences.minimumFontSize)
realSettings["suppressesIncrementalRendering"] = configuration.suppressesIncrementalRendering
realSettings["allowsBackForwardNavigationGestures"] = webView.allowsBackForwardNavigationGestures
realSettings["allowsInlineMediaPlayback"] = configuration.allowsInlineMediaPlayback
if #available(iOS 13.0, *) {
realSettings["isFraudulentWebsiteWarningEnabled"] = configuration.preferences.isFraudulentWebsiteWarningEnabled
realSettings["preferredContentMode"] = configuration.defaultWebpagePreferences.preferredContentMode.rawValue
realSettings["automaticallyAdjustsScrollIndicatorInsets"] = webView.scrollView.automaticallyAdjustsScrollIndicatorInsets
}
realSettings["selectionGranularity"] = configuration.selectionGranularity.rawValue
if #available(iOS 11.0, *) {
realSettings["accessibilityIgnoresInvertColors"] = webView.accessibilityIgnoresInvertColors
realSettings["contentInsetAdjustmentBehavior"] = webView.scrollView.contentInsetAdjustmentBehavior.rawValue
}
realSettings["decelerationRate"] = Util.getDecelerationRateString(type: webView.scrollView.decelerationRate)
realSettings["alwaysBounceVertical"] = webView.scrollView.alwaysBounceVertical
realSettings["alwaysBounceHorizontal"] = webView.scrollView.alwaysBounceHorizontal
realSettings["scrollsToTop"] = webView.scrollView.scrollsToTop
realSettings["isPagingEnabled"] = webView.scrollView.isPagingEnabled
realSettings["maximumZoomScale"] = webView.scrollView.maximumZoomScale
realSettings["minimumZoomScale"] = webView.scrollView.minimumZoomScale
realSettings["allowUniversalAccessFromFileURLs"] = configuration.value(forKey: "allowUniversalAccessFromFileURLs")
realSettings["allowFileAccessFromFileURLs"] = configuration.preferences.value(forKey: "allowFileAccessFromFileURLs")
realSettings["isDirectionalLockEnabled"] = webView.scrollView.isDirectionalLockEnabled
realSettings["javaScriptEnabled"] = configuration.preferences.javaScriptEnabled
if #available(iOS 14.0, *) {
realSettings["mediaType"] = webView.mediaType
realSettings["pageZoom"] = Float(webView.pageZoom)
realSettings["limitsNavigationsToAppBoundDomains"] = configuration.limitsNavigationsToAppBoundDomains
realSettings["javaScriptEnabled"] = configuration.defaultWebpagePreferences.allowsContentJavaScript
}
if #available(iOS 15.0, *) {
realSettings["isTextInteractionEnabled"] = configuration.preferences.isTextInteractionEnabled
realSettings["upgradeKnownHostsToHTTPS"] = configuration.upgradeKnownHostsToHTTPS
realSettings["underPageBackgroundColor"] = webView.underPageBackgroundColor.hexString
}
if #available(iOS 15.4, *) {
realSettings["isSiteSpecificQuirksModeEnabled"] = configuration.preferences.isSiteSpecificQuirksModeEnabled
realSettings["isElementFullscreenEnabled"] = configuration.preferences.isElementFullscreenEnabled
}
if #available(iOS 15.5, *) {
realSettings["minimumViewportInset"] = webView.minimumViewportInset.toMap()
realSettings["maximumViewportInset"] = webView.maximumViewportInset.toMap()
}
if #available(iOS 16.0, *) {
realSettings["isFindInteractionEnabled"] = webView.isFindInteractionEnabled
}
if #available(iOS 16.4, *) {
realSettings["isInspectable"] = webView.isInspectable
realSettings["shouldPrintBackgrounds"] = configuration.preferences.shouldPrintBackgrounds
}
}
return realSettings
}
}
@@ -0,0 +1,79 @@
//
// WebMessageChannel.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
public class WebMessageChannel: FlutterMethodCallDelegate {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_"
var id: String
var plugin: SwiftFlutterPlugin?
var channelDelegate: WebMessageChannelChannelDelegate?
weak var webView: InAppWebView?
var ports: [WebMessagePort] = []
public init(plugin: SwiftFlutterPlugin, 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,102 @@
//
// WebMessageChannelChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
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]
let 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,192 @@
//
// WebMessageListener.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/03/21.
//
import Foundation
import WebKit
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: SwiftFlutterPlugin?
public init(plugin: SwiftFlutterPlugin, 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: SwiftFlutterPlugin, 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,72 @@
//
// WebMessageListenerChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class WebMessageListenerChannelDelegate: ChannelDelegate {
private weak var webMessageListener: WebMessageListener?
public init(webMessageListener: WebMessageListener, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.webMessageListener = webMessageListener
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "postMessage":
if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName {
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
let 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,93 @@
//
// 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 zoomBy = "zoomBy"
case reloadFromOrigin = "reloadFromOrigin"
case getOriginalUrl = "getOriginalUrl"
case getZoomScale = "getZoomScale"
case hasOnlySecureContent = "hasOnlySecureContent"
case getSelectedText = "getSelectedText"
case getHitTestResult = "getHitTestResult"
case clearFocus = "clearFocus"
case setContextMenu = "setContextMenu"
case requestFocusNodeHref = "requestFocusNodeHref"
case requestImageRef = "requestImageRef"
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,23 @@
/*
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 <Flutter/Flutter.h>
@interface InAppWebViewFlutterPlugin : NSObject<FlutterPlugin>
+ (void)registerWithRegistrar:(nonnull NSObject<FlutterPluginRegistrar> *)registrar;
@end
@@ -0,0 +1,32 @@
/*
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 "InAppWebViewFlutterPlugin.h"
#if __has_include(<flutter_inappwebview_ios/flutter_inappwebview_ios-Swift.h>)
#import <flutter_inappwebview_ios/flutter_inappwebview_ios-Swift.h>
#else
// Support project import fallback if the generated compatibility header
// is not copied when this plugin is created as a library.
// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
#import "flutter_inappwebview_ios-Swift.h"
#endif
@implementation InAppWebViewFlutterPlugin : NSObject
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftFlutterPlugin registerWithRegistrar:registrar];
}
@end
@@ -0,0 +1,25 @@
//
// LeakAvoider.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/12/2019.
//
import Foundation
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,314 @@
//
// MyCookieManager.swift
// flutter_inappwebview
//
// Created by Lorenzo on 26/10/18.
//
import Foundation
import WebKit
@available(iOS 11.0, *)
public class MyCookieManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_cookiemanager"
static let httpCookieStore = WKWebsiteDataStore.default().httpCookieStore
private var plugin: SwiftFlutterPlugin?
init(plugin: SwiftFlutterPlugin) {
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(iOS 13.0, *) {
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(iOS 13.0, *) {
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(iOS 13.0, *) {
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) {
if #available(iOS 16.0, *) {
domain = domainUrl.host()
} else {
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) {
if #available(iOS 16.0, *) {
domain = domainUrl.host()
} else {
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,96 @@
//
// MyWebStorageManager.swift
// connectivity
//
// Created by Lorenzo Pichilli on 16/12/2019.
//
import Foundation
import WebKit
@available(iOS 9.0, *)
public class MyWebStorageManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webstoragemanager"
static let websiteDataStore = WKWebsiteDataStore.default()
private var plugin: SwiftFlutterPlugin?
init(plugin: SwiftFlutterPlugin) {
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
}
}
}
MyWebStorageManager.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,66 @@
//
// PlatformUtil.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 01/03/21.
//
import Foundation
public class PlatformUtil: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_platformutil"
var plugin: SwiftFlutterPlugin?
init(plugin: SwiftFlutterPlugin) {
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 device = UIDevice.current
result(device.systemVersion)
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,267 @@
//
// 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,62 @@
//
// LastTouchedAnchorOrImageJS.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 16/02/21.
//
import Foundation
let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT"
let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT = PluginScript(
groupName: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME,
source: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE,
injectionTime: .atDocumentStart,
forMainFrameOnly: true,
requiredInAllContentWorlds: false,
messageHandlerNames: [])
let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE = """
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = null;
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null;
(function() {
document.addEventListener('touchstart', function(event) {
var target = event.target;
while (target) {
if (target.tagName === 'IMG') {
var img = target;
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = {
url: img.src
};
var parent = img.parentNode;
while (parent) {
if (parent.tagName === 'A') {
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = {
title: parent.textContent,
url: parent.href,
src: img.src
};
break;
}
parent = parent.parentNode;
}
return;
} else if (target.tagName === 'A') {
var link = target;
var images = link.getElementsByTagName('img');
var img = (images.length > 0) ? images[0] : null;
var imgSrc = (img != null) ? img.src : null;
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = (img != null) ? {url: imgSrc} : window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched;
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = {
title: link.textContent,
url: link.href,
src: imgSrc
};
return;
}
target = target.parentNode;
}
});
})();
"""
@@ -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,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,56 @@
//
// PrintAttributes.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 10/05/22.
//
import Foundation
public class PrintAttributes: NSObject {
var orientation: UIPrintInfo.Orientation?
var duplex: UIPrintInfo.Duplex?
var outputType: UIPrintInfo.OutputType?
var margins: UIEdgeInsets?
var footerHeight: Double?
var headerHeight: Double?
var paperRect: CGRect?
var printableRect: CGRect?
var maximumContentHeight: Double?
var maximumContentWidth: Double?
public init(fromPrintJobController: PrintJobController) {
super.init()
if let printPageRenderer = fromPrintJobController.printPageRenderer {
footerHeight = printPageRenderer.footerHeight
headerHeight = printPageRenderer.headerHeight
paperRect = printPageRenderer.paperRect
printableRect = printPageRenderer.printableRect
}
if let printFormatter = fromPrintJobController.printFormatter {
maximumContentHeight = printFormatter.maximumContentHeight
maximumContentWidth = printFormatter.maximumContentWidth
margins = printFormatter.perPageContentInsets
}
if let job = fromPrintJobController.job, let printInfo = job.printInfo {
orientation = printInfo.orientation
duplex = printInfo.duplex
outputType = printInfo.outputType
}
}
public func toMap () -> [String:Any?] {
return [
"footerHeight": footerHeight,
"headerHeight": headerHeight,
"paperRect": paperRect?.toMap(),
"printableRect": printableRect?.toMap(),
"margins": margins?.toMap(),
"maximumContentHeight": maximumContentHeight,
"maximumContentWidth": maximumContentWidth,
"orientation": orientation?.rawValue,
"duplex": duplex?.rawValue,
"outputType": outputType?.rawValue
]
}
}
@@ -0,0 +1,68 @@
//
// PrintJobChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
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 "dismiss":
if let job = printJobController?.job {
let animated = arguments!["animated"] as! Bool
job.dismiss(animated: animated)
result(true)
} else {
result(false)
}
break
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,102 @@
//
// PrintJob.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 09/05/22.
//
import Foundation
public enum PrintJobState: Int {
case created = 1
case started = 3
case completed = 5
case failed = 6
case canceled = 7
}
public class PrintJobController: NSObject, Disposable, UIPrintInteractionControllerDelegate {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_"
var id: String
var plugin: SwiftFlutterPlugin?
var job: UIPrintInteractionController?
var settings: PrintJobSettings?
var printFormatter: UIPrintFormatter?
var printPageRenderer: UIPrintPageRenderer?
var channelDelegate: PrintJobChannelDelegate?
var state = PrintJobState.created
var creationTime = Int64(Date().timeIntervalSince1970 * 1000)
public init(plugin: SwiftFlutterPlugin, id: String, job: UIPrintInteractionController? = nil, settings: PrintJobSettings? = nil) {
self.id = id
self.plugin = plugin
super.init()
self.job = job
self.settings = settings
self.printFormatter = job?.printFormatter
self.printPageRenderer = job?.printPageRenderer
self.job?.delegate = self
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 printInteractionControllerWillStartJob(_ printInteractionController: UIPrintInteractionController) {
state = .started
}
public func present(animated: Bool, completionHandler: UIPrintInteractionController.CompletionHandler? = nil) {
guard let job = job else {
return
}
job.present(animated: animated, completionHandler: { [weak self] (printController, completed, error) in
if !completed {
if let _ = error {
self?.state = .failed
} else {
self?.state = .canceled
}
} else {
self?.state = .completed
}
self?.channelDelegate?.onComplete(completed: completed, error: error)
if let completionHandler = completionHandler {
completionHandler(printController, completed, error)
}
})
}
public func getInfo() -> PrintJobInfo? {
guard let _ = job else {
return nil
}
return PrintJobInfo.init(fromPrintJobController: self)
}
public func disposeNoDismiss() {
channelDelegate?.dispose()
channelDelegate = nil
printFormatter = nil
printPageRenderer = nil
job?.delegate = nil
job = nil
plugin?.printJobManager?.jobs[id] = nil
plugin = nil
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
printFormatter = nil
printPageRenderer = nil
job?.delegate = nil
job?.dismiss(animated: false)
job = nil
plugin?.printJobManager?.jobs[id] = nil
plugin = nil
}
}
@@ -0,0 +1,45 @@
//
// 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 label: String?
var printerId: String?
public init(fromPrintJobController: PrintJobController) {
state = fromPrintJobController.state
creationTime = fromPrintJobController.creationTime
attributes = PrintAttributes.init(fromPrintJobController: fromPrintJobController)
super.init()
if let printPageRenderer = fromPrintJobController.printPageRenderer {
numberOfPages = printPageRenderer.numberOfPages
}
if let job = fromPrintJobController.job, let printInfo = job.printInfo {
label = printInfo.jobName
printerId = printInfo.printerID
}
}
public func toMap () -> [String:Any?] {
return [
"state": state.rawValue,
"attributes": attributes.toMap(),
"copies": nil,
"numberOfPages": numberOfPages,
"creationTime": creationTime,
"label": label,
"printer": [
"id": printerId
]
]
}
}
@@ -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: SwiftFlutterPlugin?
var jobs: [String: PrintJobController?] = [:]
public init(plugin: SwiftFlutterPlugin?) {
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,154 @@
//
// 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 animated = true
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: Int? {
get {
return _numberOfPages?.intValue
}
set {
if let newValue = newValue {
_numberOfPages = NSNumber.init(value: newValue)
} else {
_numberOfPages = nil
}
}
}
public var _forceRenderingQuality: NSNumber?
public var forceRenderingQuality: Int? {
get {
return _forceRenderingQuality?.intValue
}
set {
if let newValue = newValue {
_forceRenderingQuality = NSNumber.init(value: newValue)
} else {
_forceRenderingQuality = nil
}
}
}
public var margins: UIEdgeInsets?
public var _duplexMode: NSNumber?
public var duplexMode: Int? {
get {
return _duplexMode?.intValue
}
set {
if let newValue = newValue {
_duplexMode = NSNumber.init(value: newValue)
} else {
_duplexMode = nil
}
}
}
public var _outputType: NSNumber?
public var outputType: Int? {
get {
return _outputType?.intValue
}
set {
if let newValue = newValue {
_outputType = NSNumber.init(value: newValue)
} else {
_outputType = nil
}
}
}
public var showsNumberOfCopies = true
public var showsPaperSelectionForLoadedPapers = false
public var showsPaperOrientation = true
public var _maximumContentHeight: NSNumber?
public var maximumContentHeight: Double? {
get {
return _maximumContentHeight?.doubleValue
}
set {
if let newValue = newValue {
_maximumContentHeight = NSNumber.init(value: newValue)
} else {
_maximumContentHeight = nil
}
}
}
public var _maximumContentWidth: NSNumber?
public var maximumContentWidth: Double? {
get {
return _maximumContentWidth?.doubleValue
}
set {
if let newValue = newValue {
_maximumContentWidth = NSNumber.init(value: newValue)
} else {
_maximumContentWidth = nil
}
}
}
public var _footerHeight: NSNumber?
public var footerHeight: Double? {
get {
return _footerHeight?.doubleValue
}
set {
if let newValue = newValue {
_footerHeight = NSNumber.init(value: newValue)
} else {
_footerHeight = nil
}
}
}
public var _headerHeight: NSNumber?
public var headerHeight: Double? {
get {
return _headerHeight?.doubleValue
}
set {
if let newValue = newValue {
_headerHeight = NSNumber.init(value: newValue)
} else {
_headerHeight = 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 = UIEdgeInsets.fromMap(map: marginsMap)
}
return self
}
override func getRealSettings(obj: PrintJobController?) -> [String: Any?] {
let realOptions: [String: Any?] = toMap()
return realOptions
}
}
@@ -0,0 +1,108 @@
//
// PullToRefreshChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/05/22.
//
import Foundation
public class PullToRefreshChannelDelegate: ChannelDelegate {
private weak var pullToRefreshControl: PullToRefreshControl?
public init(pullToRefreshControl: PullToRefreshControl, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.pullToRefreshControl = pullToRefreshControl
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "setEnabled":
if let pullToRefreshView = pullToRefreshControl {
let enabled = arguments!["enabled"] as! Bool
if enabled {
pullToRefreshView.delegate?.enablePullToRefresh()
} else {
pullToRefreshView.delegate?.disablePullToRefresh()
}
result(true)
} else {
result(false)
}
break
case "isEnabled":
if let pullToRefreshView = pullToRefreshControl {
result(pullToRefreshView.delegate?.isPullToRefreshEnabled())
} else {
result(false)
}
break
case "setRefreshing":
if let pullToRefreshView = pullToRefreshControl {
let refreshing = arguments!["refreshing"] as! Bool
if refreshing {
pullToRefreshView.beginRefreshing()
} else {
pullToRefreshView.endRefreshing()
}
result(true)
} else {
result(false)
}
break
case "isRefreshing":
if let pullToRefreshView = pullToRefreshControl {
result(pullToRefreshView.isRefreshing)
} else {
result(false)
}
break
case "setColor":
if let pullToRefreshView = pullToRefreshControl {
let color = arguments!["color"] as! String
pullToRefreshView.tintColor = UIColor(hexString: color)
result(true)
} else {
result(false)
}
break
case "setBackgroundColor":
if let pullToRefreshView = pullToRefreshControl {
let color = arguments!["color"] as! String
pullToRefreshView.backgroundColor = UIColor(hexString: color)
result(true)
} else {
result(false)
}
break
case "setStyledTitle":
if let pullToRefreshView = pullToRefreshControl {
let attributedTitleMap = arguments!["attributedTitle"] as! [String: Any?]
pullToRefreshView.attributedTitle = NSAttributedString.fromMap(map: attributedTitleMap)
result(true)
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onRefresh() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onRefresh", arguments: arguments)
}
public override func dispose() {
super.dispose()
pullToRefreshControl = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,74 @@
//
// File.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 03/03/21.
//
import Foundation
import Flutter
public class PullToRefreshControl: UIRefreshControl, Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_"
var plugin: SwiftFlutterPlugin?
var channelDelegate: PullToRefreshChannelDelegate?
var settings: PullToRefreshSettings?
var shouldCallOnRefresh = false
var delegate: PullToRefreshDelegate?
public init(plugin: SwiftFlutterPlugin, id: Any, settings: PullToRefreshSettings?) {
super.init()
self.plugin = plugin
self.settings = settings
if let registrar = plugin.registrar {
let channel = FlutterMethodChannel(name: PullToRefreshControl.METHOD_CHANNEL_NAME_PREFIX + String(describing: id),
binaryMessenger: registrar.messenger())
self.channelDelegate = PullToRefreshChannelDelegate(pullToRefreshControl: self, channel: channel)
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
public func prepare() {
if let settings = settings {
if settings.enabled {
delegate?.enablePullToRefresh()
}
if let color = settings.color, !color.isEmpty {
tintColor = UIColor(hexString: color)
}
if let backgroundTintColor = settings.backgroundColor, !backgroundTintColor.isEmpty {
backgroundColor = UIColor(hexString: backgroundTintColor)
}
if let attributedTitleMap = settings.attributedTitle {
attributedTitle = NSAttributedString.fromMap(map: attributedTitleMap)
}
}
addTarget(self, action: #selector(updateShouldCallOnRefresh), for: .valueChanged)
}
public func onRefresh() {
shouldCallOnRefresh = false
channelDelegate?.onRefresh()
}
@objc public func updateShouldCallOnRefresh() {
shouldCallOnRefresh = true
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
removeTarget(self, action: #selector(updateShouldCallOnRefresh), for: .valueChanged)
delegate = nil
plugin = nil
}
deinit {
debugPrint("PullToRefreshControl - dealloc")
dispose()
}
}
@@ -0,0 +1,14 @@
//
// PullToRefreshDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 04/03/21.
//
import Foundation
public protocol PullToRefreshDelegate {
func enablePullToRefresh()
func disablePullToRefresh()
func isPullToRefreshEnabled() -> Bool
}
@@ -0,0 +1,33 @@
//
// pullToRefreshSettings.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 03/03/21.
//
import Foundation
public class PullToRefreshSettings: ISettings<PullToRefreshControl> {
var enabled = true
var color: String?
var backgroundColor: String?
var attributedTitle: [String: Any?]?
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> PullToRefreshSettings {
let _ = super.parse(settings: settings)
if let attributedTitle = settings["attributedTitle"] as? [String: Any?] {
self.attributedTitle = attributedTitle
}
return self
}
override func getRealSettings(obj: PullToRefreshControl?) -> [String: Any?] {
let realSettings: [String: Any?] = toMap()
return realSettings
}
}
@@ -0,0 +1,148 @@
//
// ChromeSafariBrowserManager.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 18/12/2019.
//
import Flutter
import UIKit
import WebKit
import Foundation
import AVFoundation
import SafariServices
public class ChromeSafariBrowserManager: ChannelDelegate {
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_chromesafaribrowser"
var plugin: SwiftFlutterPlugin?
var browsers: [String: SafariViewController?] = [:]
var prewarmingTokens: [String: Any?] = [:]
init(plugin: SwiftFlutterPlugin) {
super.init(channel: FlutterMethodChannel(name: ChromeSafariBrowserManager.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":
let id = arguments!["id"] as! String
let url = arguments!["url"] as! String
let settings = arguments!["settings"] as! [String: Any?]
let menuItemList = arguments!["menuItemList"] as! [[String: Any]]
open(id: id, url: url, settings: settings, menuItemList: menuItemList, result: result)
break
case "isAvailable":
if #available(iOS 9.0, *) {
result(true)
} else {
result(false)
}
break
case "clearWebsiteData":
if #available(iOS 16.0, *) {
SFSafariViewController.DataStore.default.clearWebsiteData {
result(true)
}
} else {
result(false)
}
case "prewarmConnections":
if #available(iOS 15.0, *) {
let stringURLs = arguments!["URLs"] as! [String]
var URLs: [URL] = []
for stringURL in stringURLs {
if let url = URL(string: stringURL) {
URLs.append(url)
}
}
let prewarmingToken = SFSafariViewController.prewarmConnections(to: URLs)
let prewarmingTokenId = NSUUID().uuidString
prewarmingTokens[prewarmingTokenId] = prewarmingToken
result([
"id": prewarmingTokenId
])
} else {
result(nil)
}
case "invalidatePrewarmingToken":
if #available(iOS 15.0, *) {
let prewarmingToken = arguments!["prewarmingToken"] as! [String:Any?]
if let prewarmingTokenId = prewarmingToken["id"] as? String,
let prewarmingToken = prewarmingTokens[prewarmingTokenId] as? SFSafariViewController.PrewarmingToken? {
prewarmingToken?.invalidate()
prewarmingTokens[prewarmingTokenId] = nil
}
result(true)
} else {
result(false)
}
default:
result(FlutterMethodNotImplemented)
break
}
}
public func open(id: String, url: String, settings: [String: Any?], menuItemList: [[String: Any]], result: @escaping FlutterResult) {
let absoluteUrl = URL(string: url)!.absoluteURL
if #available(iOS 9.0, *), let plugin = plugin {
if let flutterViewController = UIApplication.shared.visibleViewController {
// flutterViewController could be casted to FlutterViewController if needed
let safariSettings = SafariBrowserSettings()
let _ = safariSettings.parse(settings: settings)
let safari: SafariViewController
if #available(iOS 11.0, *) {
let config = SFSafariViewController.Configuration()
safari = SafariViewController(plugin: plugin, id: id, url: absoluteUrl, configuration: config,
menuItemList: menuItemList, safariSettings: safariSettings)
} else {
// Fallback on earlier versions
safari = SafariViewController(plugin: plugin, id: id, url: absoluteUrl, entersReaderIfAvailable: safariSettings.entersReaderIfAvailable,
menuItemList: menuItemList, safariSettings: safariSettings)
}
safari.prepareSafariBrowser()
flutterViewController.present(safari, animated: true) {
result(true)
}
browsers[id] = safari
}
return
}
result(FlutterError.init(code: "ChromeSafariBrowserManager", message: "SafariViewController is not available!", details: nil))
}
public override func dispose() {
super.dispose()
let browserValues = browsers.values
browserValues.forEach { (browser: SafariViewController?) in
browser?.close(result: nil)
browser?.dispose()
}
browsers.removeAll()
if #available(iOS 15.0, *) {
let prewarmingTokensValues = prewarmingTokens.values
prewarmingTokensValues.forEach { (prewarmingToken: Any?) in
if let prewarmingToken = prewarmingToken as? SFSafariViewController.PrewarmingToken? {
prewarmingToken?.invalidate()
}
}
prewarmingTokens.removeAll()
}
plugin = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,55 @@
//
// CustomUIActivity.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/05/22.
//
import Foundation
class CustomUIActivity: UIActivity {
var plugin: SwiftFlutterPlugin
var viewId: String
var id: Int64
var url: URL
var title: String?
var type: UIActivity.ActivityType?
var label: String?
var image: UIImage?
init(plugin: SwiftFlutterPlugin, viewId: String, id: Int64, url: URL, title: String?, label: String?, type: UIActivity.ActivityType?, image: UIImage?) {
self.plugin = plugin
self.viewId = viewId
self.id = id
self.url = url
self.title = title
self.label = label
self.type = type
self.image = image
}
override class var activityCategory: UIActivity.Category {
return .action
}
override var activityType: UIActivity.ActivityType? {
return type
}
override var activityTitle: String? {
return label
}
override var activityImage: UIImage? {
return image
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
return true
}
override func perform() {
let browser = plugin.chromeSafariBrowserManager?.browsers[viewId]
browser??.channelDelegate?.onItemActionPerform(id: id, url: url, title: title)
}
}
@@ -0,0 +1,52 @@
//
// SafariBrowserOptions.swift
// flutter_inappwebview
//
// Created by Lorenzo on 26/09/18.
//
import Foundation
@available(iOS 9.0, *)
@objcMembers
public class SafariBrowserSettings: ISettings<SafariViewController> {
var entersReaderIfAvailable = false
var barCollapsingEnabled = false
var dismissButtonStyle = 0 //done
var preferredBarTintColor: String?
var preferredControlTintColor: String?
var presentationStyle = 0 //fullscreen
var transitionStyle = 0 //coverVertical
var activityButton: [String:Any?]?
var eventAttribution: [String:Any?]?
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> SafariBrowserSettings {
let _ = super.parse(settings: settings)
activityButton = settings["activityButton"] as? [String : Any?]
eventAttribution = settings["eventAttribution"] as? [String : Any?]
return self
}
override func getRealSettings(obj: SafariViewController?) -> [String: Any?] {
var realOptions: [String: Any?] = toMap()
if let safariViewController = obj {
if #available(iOS 11.0, *) {
realOptions["entersReaderIfAvailable"] = safariViewController.configuration.entersReaderIfAvailable
realOptions["barCollapsingEnabled"] = safariViewController.configuration.barCollapsingEnabled
realOptions["dismissButtonStyle"] = safariViewController.dismissButtonStyle.rawValue
}
if #available(iOS 10.0, *) {
realOptions["preferredBarTintColor"] = safariViewController.preferredBarTintColor?.hexString
realOptions["preferredControlTintColor"] = safariViewController.preferredControlTintColor?.hexString
}
realOptions["presentationStyle"] = safariViewController.modalPresentationStyle.rawValue
realOptions["transitionStyle"] = safariViewController.modalTransitionStyle.rawValue
}
return realOptions
}
}
@@ -0,0 +1,141 @@
//
// SafariViewController.swift
// flutter_inappwebview
//
// Created by Lorenzo on 25/09/18.
//
import Foundation
import SafariServices
@available(iOS 9.0, *)
public class SafariViewController: SFSafariViewController, SFSafariViewControllerDelegate, Disposable {
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_chromesafaribrowser_"
var channelDelegate: SafariViewControllerChannelDelegate?
var safariSettings: SafariBrowserSettings
var id: String
var plugin: SwiftFlutterPlugin?
var menuItemList: [[String: Any]] = []
@available(iOS 11.0, *)
public init(plugin: SwiftFlutterPlugin, id: String, url: URL, configuration: SFSafariViewController.Configuration, menuItemList: [[String: Any]] = [], safariSettings: SafariBrowserSettings) {
self.id = id
self.plugin = plugin
self.menuItemList = menuItemList
self.safariSettings = safariSettings
SafariViewController.prepareConfig(configuration: configuration, safariSettings: safariSettings)
super.init(url: url, configuration: configuration)
let channel = FlutterMethodChannel(name: SafariViewController.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: plugin.registrar!.messenger())
self.channelDelegate = SafariViewControllerChannelDelegate(safariViewController: self, channel: channel)
self.delegate = self
}
public init(plugin: SwiftFlutterPlugin, id: String, url: URL, entersReaderIfAvailable: Bool, menuItemList: [[String: Any]] = [], safariSettings: SafariBrowserSettings) {
self.id = id
self.plugin = plugin
self.menuItemList = menuItemList
self.safariSettings = safariSettings
super.init(url: url, entersReaderIfAvailable: entersReaderIfAvailable)
let channel = FlutterMethodChannel(name: SafariViewController.METHOD_CHANNEL_NAME_PREFIX + id,
binaryMessenger: plugin.registrar!.messenger())
self.channelDelegate = SafariViewControllerChannelDelegate(safariViewController: self, channel: channel)
self.delegate = self
}
public static func prepareConfig(configuration: SFSafariViewController.Configuration, safariSettings: SafariBrowserSettings) {
configuration.entersReaderIfAvailable = safariSettings.entersReaderIfAvailable
configuration.barCollapsingEnabled = safariSettings.barCollapsingEnabled
if #available(iOS 15.0, *), let activityButtonMap = safariSettings.activityButton {
configuration.activityButton = .fromMap(map: activityButtonMap)
}
if #available(iOS 15.2, *), let eventAttributionMap = safariSettings.eventAttribution {
configuration.eventAttribution = .fromMap(map: eventAttributionMap)
}
}
func prepareSafariBrowser() {
if #available(iOS 11.0, *) {
self.dismissButtonStyle = SFSafariViewController.DismissButtonStyle(rawValue: safariSettings.dismissButtonStyle)!
}
if #available(iOS 10.0, *) {
if let preferredBarTintColor = safariSettings.preferredBarTintColor, !preferredBarTintColor.isEmpty {
self.preferredBarTintColor = UIColor(hexString: preferredBarTintColor)
}
if let preferredControlTintColor = safariSettings.preferredControlTintColor, !preferredControlTintColor.isEmpty {
self.preferredControlTintColor = UIColor(hexString: preferredControlTintColor)
}
}
self.modalPresentationStyle = UIModalPresentationStyle(rawValue: safariSettings.presentationStyle)!
self.modalTransitionStyle = UIModalTransitionStyle(rawValue: safariSettings.transitionStyle)!
}
public override func viewWillAppear(_ animated: Bool) {
// prepareSafariBrowser()
super.viewWillAppear(animated)
channelDelegate?.onOpened()
}
public override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
channelDelegate?.onClosed()
self.dispose()
}
func close(result: FlutterResult?) {
dismiss(animated: true)
// wait for the animation
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400), execute: {() -> Void in
if let result = result {
result(true)
}
})
}
public func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
close(result: nil)
}
public func safariViewController(_ controller: SFSafariViewController,
didCompleteInitialLoad didLoadSuccessfully: Bool) {
channelDelegate?.onCompletedInitialLoad(didLoadSuccessfully: didLoadSuccessfully)
}
public func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] {
guard let plugin = plugin else {
return []
}
var uiActivities: [UIActivity] = []
menuItemList.forEach { (menuItem) in
let activity = CustomUIActivity(plugin: plugin, viewId: id, id: menuItem["id"] as! Int64, url: URL,
title: title, label: menuItem["label"] as? String, type: nil,
image: .fromMap(map: menuItem["image"] as? [String:Any?]))
uiActivities.append(activity)
}
return uiActivities
}
public func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo url: URL) {
channelDelegate?.onInitialLoadDidRedirect(url: url)
}
public func safariViewControllerWillOpenInBrowser(_ controller: SFSafariViewController) {
channelDelegate?.onWillOpenInBrowser()
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
delegate = nil
plugin?.chromeSafariBrowserManager?.browsers[id] = nil
plugin = nil
}
deinit {
debugPrint("SafariViewController - dealloc")
dispose()
}
}
@@ -0,0 +1,80 @@
//
// SafariViewControllerChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/05/22.
//
import Foundation
public class SafariViewControllerChannelDelegate: ChannelDelegate {
private weak var safariViewController: SafariViewController?
public init(safariViewController: SafariViewController, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.safariViewController = safariViewController
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
// let arguments = call.arguments as? NSDictionary
switch call.method {
case "close":
if let safariViewController = safariViewController {
safariViewController.close(result: result)
} else {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onOpened() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onOpened", arguments: arguments)
}
public func onCompletedInitialLoad(didLoadSuccessfully: Bool) {
let arguments: [String: Any?] = [
"didLoadSuccessfully": didLoadSuccessfully
]
channel?.invokeMethod("onCompletedInitialLoad", arguments: arguments)
}
public func onInitialLoadDidRedirect(url: URL) {
let arguments: [String: Any?] = [
"url": url.absoluteString
]
channel?.invokeMethod("onInitialLoadDidRedirect", arguments: arguments)
}
public func onWillOpenInBrowser() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onWillOpenInBrowser", arguments: arguments)
}
public func onClosed() {
let arguments: [String: Any?] = [:]
channel?.invokeMethod("onClosed", arguments: arguments)
}
public func onItemActionPerform(id: Int64, url: URL, title: String?) {
let arguments: [String: Any?] = [
"id": id,
"url": url.absoluteString,
"title": title,
]
channel?.invokeMethod("onItemActionPerform", arguments: arguments)
}
public override func dispose() {
super.dispose()
safariViewController = nil
}
deinit {
dispose()
}
}
@@ -0,0 +1,94 @@
/*
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 Flutter
import UIKit
import WebKit
import Foundation
import AVFoundation
import SafariServices
public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
var registrar: FlutterPluginRegistrar?
var platformUtil: PlatformUtil?
var inAppWebViewManager: InAppWebViewManager?
var myCookieManager: Any?
var myWebStorageManager: Any?
var credentialDatabase: CredentialDatabase?
var inAppBrowserManager: InAppBrowserManager?
var headlessInAppWebViewManager: HeadlessInAppWebViewManager?
var chromeSafariBrowserManager: ChromeSafariBrowserManager?
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)
chromeSafariBrowserManager = ChromeSafariBrowserManager(plugin: self)
inAppWebViewManager = InAppWebViewManager(plugin: self)
credentialDatabase = CredentialDatabase(plugin: self)
if #available(iOS 11.0, *) {
myCookieManager = MyCookieManager(plugin: self)
}
if #available(iOS 9.0, *) {
myWebStorageManager = MyWebStorageManager(plugin: self)
}
webAuthenticationSessionManager = WebAuthenticationSessionManager(plugin: self)
printJobManager = PrintJobManager(plugin: self)
}
public static func register(with registrar: FlutterPluginRegistrar) {
let _ = SwiftFlutterPlugin(with: registrar)
}
public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
platformUtil?.dispose()
platformUtil = nil
inAppBrowserManager?.dispose()
inAppBrowserManager = nil
headlessInAppWebViewManager?.dispose()
headlessInAppWebViewManager = nil
chromeSafariBrowserManager?.dispose()
chromeSafariBrowserManager = nil
inAppWebViewManager?.dispose()
inAppWebViewManager = nil
credentialDatabase?.dispose()
credentialDatabase = nil
if #available(iOS 11.0, *) {
(myCookieManager as! MyCookieManager?)?.dispose()
myCookieManager = nil
}
if #available(iOS 9.0, *) {
(myWebStorageManager as! MyWebStorageManager?)?.dispose()
myWebStorageManager = nil
}
webAuthenticationSessionManager?.dispose()
webAuthenticationSessionManager = nil
printJobManager?.dispose()
printJobManager = nil
}
}
@@ -0,0 +1,24 @@
//
// ActivityButton.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 26/10/22.
//
import Foundation
import SafariServices
@available(iOS 15.0, *)
extension SFSafariViewController.ActivityButton {
public static func fromMap(map: [String:Any?]?) -> SFSafariViewController.ActivityButton? {
guard let map = map else {
return nil
}
if let templateImageMap = map["templateImage"] as? [String:Any?],
let templateImage = UIImage.fromMap(map: templateImageMap),
let extensionIdentifier = map["extensionIdentifier"] as? String {
return .init(templateImage: templateImage, extensionIdentifier: extensionIdentifier)
}
return nil
}
}
@@ -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,21 @@
//
// CGSize.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 28/10/22.
//
import Foundation
extension CGSize {
public static func fromMap(map: [String: Double]) -> CGSize {
return CGSize(width: map["width"]!, height: map["height"]!)
}
public func toMap () -> [String:Any?] {
return [
"width": width,
"height": height
]
}
}
@@ -0,0 +1,18 @@
//
// CallbackResult.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 06/05/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,27 @@
//
// ChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 04/05/22.
//
import Foundation
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,30 @@
//
// CustomSchemeResponse.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/05/22.
//
import Foundation
public class CustomSchemeResponse: NSObject {
var data: Data
var contentType: String
var contentEncoding: String
public init(data: Data, contentType: String, contentEncoding: String) {
self.data = data
self.contentType = contentType
self.contentEncoding = contentEncoding
}
public static func fromMap(map: [String:Any?]?) -> CustomSchemeResponse? {
guard let map = map else {
return nil
}
let data = map["data"] as! FlutterStandardTypedData
let contentType = map["contentType"] as! String
let contentEncoding = map["contentEncoding"] as! String
return CustomSchemeResponse(data: data.data, contentType: contentType, contentEncoding: contentEncoding)
}
}
@@ -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,18 @@
//
// FlutterMethodCallDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 15/12/2019.
//
import Foundation
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 Flutter
extension FlutterMethodChannel {
public func invokeMethod(_ method: String, arguments: Any, callback: MethodChannelResult) {
invokeMethod(method, arguments: arguments) {(result) -> Void in
if result is FlutterError {
let error = result as! FlutterError
callback.error(error.code, error.message, error.details)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {
callback.notImplemented()
}
else {
callback.success(result)
}
}
}
}
@@ -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,48 @@
//
// InAppBrowserMenuItem.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 21/05/23.
//
import Foundation
public class InAppBrowserMenuItem: NSObject {
var id: Int64
var title: String
var order: Int64?
var icon: UIImage?
var iconColor: UIColor?
var showAsAction = false
public init(id: Int64, title: String, order: Int64?, icon: UIImage?, iconColor: UIColor?, showAsAction: Bool) {
self.id = id
self.title = title
self.order = order
self.icon = icon
self.iconColor = iconColor
self.showAsAction = showAsAction
if #available(iOS 13.0, *), let icon = icon, let iconColor = iconColor {
icon.withTintColor(iconColor, renderingMode: .alwaysOriginal)
}
}
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 = UIImage.fromMap(map: map["icon"] as? [String : Any?])
if let data = map["icon"] as? FlutterStandardTypedData {
icon = UIImage(data: data.data)
}
var iconColor: UIColor? = nil
if let hexString = map["iconColor"] as? String {
iconColor = UIColor(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,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
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 06/05/22.
//
import Foundation
public protocol MethodChannelResult {
var success: (_ obj: Any?) -> Void { get set }
var error: (_ code: String, _ message: String?, _ details: Any?) -> Void { get set }
var notImplemented: () -> Void { get set }
}
@@ -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] = UIColor(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] = UIColor(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] = UIColor(hexString: strikethroughColor)
}
if let strikethroughStyle = map["strikethroughStyle"] as? Int64 {
attributes[.strikethroughStyle] = strikethroughStyle
}
if let strokeColor = map["strokeColor"] as? String {
attributes[.strokeColor] = UIColor(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] = UIColor(hexString: underlineColor)
}
if let underlineStyle = map["underlineStyle"] as? Int64 {
attributes[.underlineStyle] = underlineStyle
}
return NSAttributedString(string: string, attributes: attributes)
}
}
@@ -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(iOS 14.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(iOS 14.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(iOS 14.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(iOS 14.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(iOS 14.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(iOS 14.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
}
}
}
Loaded 100 of 556 files, more files were not shown because too many files have changed in this diff. Show more