@capgo/capacitor-inappbrowser 8.10.1 → 8.10.3-beta.pr621.476.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2649,6 +2649,33 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
2649
2649
|
WebView.HitTestResult result = view.getHitTestResult();
|
|
2650
2650
|
String data = result != null ? result.getExtra() : null;
|
|
2651
2651
|
|
|
2652
|
+
// Authorized App Links with target=_blank should open the native app,
|
|
2653
|
+
// not spawn a managed popup (which previously left the UI in a broken state).
|
|
2654
|
+
if (
|
|
2655
|
+
data != null &&
|
|
2656
|
+
!_options.getPreventDeeplink() &&
|
|
2657
|
+
isHttpOrHttpsUrl(data) &&
|
|
2658
|
+
isAuthorizedAppLink(data, _options.getAuthorizedAppLinks())
|
|
2659
|
+
) {
|
|
2660
|
+
try {
|
|
2661
|
+
Log.d("InAppBrowser", "Opening authorized blank-target link externally: " + data);
|
|
2662
|
+
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(data));
|
|
2663
|
+
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
2664
|
+
view.getContext().startActivity(intent);
|
|
2665
|
+
return false;
|
|
2666
|
+
} catch (ActivityNotFoundException | SecurityException e) {
|
|
2667
|
+
Log.w("InAppBrowser", "No app for authorized blank-target link, loading in current WebView", e);
|
|
2668
|
+
final String fallbackUrl = data;
|
|
2669
|
+
view.post(() -> {
|
|
2670
|
+
if (isDismissing || _webView == null || _webView != view) {
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
_webView.loadUrl(fallbackUrl);
|
|
2674
|
+
});
|
|
2675
|
+
return false;
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2652
2679
|
if (shouldLoadBlankTargetInCurrentWebView(data)) {
|
|
2653
2680
|
Log.d("InAppBrowser", "Loading target=_blank link in current WebView: " + data);
|
|
2654
2681
|
view.post(() -> _webView.loadUrl(data));
|
|
@@ -2,6 +2,7 @@ import Foundation
|
|
|
2
2
|
import Capacitor
|
|
3
3
|
import WebKit
|
|
4
4
|
import AuthenticationServices
|
|
5
|
+
import SafariServices
|
|
5
6
|
|
|
6
7
|
enum ActiveWebViewSupport {
|
|
7
8
|
static func shouldActivateNewWebView(isHidden: Bool, hasActiveWebView: Bool) -> Bool {
|
|
@@ -13,6 +14,70 @@ enum ActiveWebViewSupport {
|
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
enum BlankTargetNavigationSupport {
|
|
18
|
+
enum Action: Equatable {
|
|
19
|
+
case openExternalApp
|
|
20
|
+
case loadInCurrentWebView
|
|
21
|
+
case createPopup
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Decides how a `target=_blank` / new-window request should be handled.
|
|
25
|
+
static func resolve(
|
|
26
|
+
urlIsHttpOrHttps: Bool,
|
|
27
|
+
openBlankTargetInWebView: Bool,
|
|
28
|
+
preventDeeplink: Bool,
|
|
29
|
+
isAuthorizedAppLink: Bool
|
|
30
|
+
) -> Action {
|
|
31
|
+
guard urlIsHttpOrHttps else {
|
|
32
|
+
return .createPopup
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if isAuthorizedAppLink && !preventDeeplink {
|
|
36
|
+
return .openExternalApp
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if openBlankTargetInWebView || preventDeeplink {
|
|
40
|
+
return .loadInCurrentWebView
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return .createPopup
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Prefer the parent content VC so we never present from a UINavigationController
|
|
47
|
+
/// whose root view was replaced (custom width/height PassThroughView).
|
|
48
|
+
static func popupPresenter(
|
|
49
|
+
parentController: UIViewController,
|
|
50
|
+
bridgeViewController: UIViewController?
|
|
51
|
+
) -> UIViewController {
|
|
52
|
+
if parentController.view.window != nil {
|
|
53
|
+
return parentController
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if let navigationController = parentController.navigationController,
|
|
57
|
+
navigationController.view.window != nil {
|
|
58
|
+
if let visible = navigationController.visibleViewController,
|
|
59
|
+
visible.view.window != nil {
|
|
60
|
+
return visible
|
|
61
|
+
}
|
|
62
|
+
return navigationController
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var top = bridgeViewController
|
|
66
|
+
while let presented = top?.presentedViewController {
|
|
67
|
+
top = presented
|
|
68
|
+
}
|
|
69
|
+
return top ?? parentController
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static func topPresenter(from root: UIViewController) -> UIViewController {
|
|
73
|
+
var top = root
|
|
74
|
+
while let presented = top.presentedViewController {
|
|
75
|
+
top = presented
|
|
76
|
+
}
|
|
77
|
+
return top
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
16
81
|
protocol ProxyRequestLocating {
|
|
17
82
|
func hasPendingProxyRequest(_ requestId: String) -> Bool
|
|
18
83
|
}
|
|
@@ -51,6 +116,42 @@ enum ProxyResponseRoutingSupport {
|
|
|
51
116
|
}
|
|
52
117
|
}
|
|
53
118
|
|
|
119
|
+
|
|
120
|
+
enum StatusBarBackgroundLayoutSupport {
|
|
121
|
+
enum Placement: Equatable {
|
|
122
|
+
case skip
|
|
123
|
+
case host(pinToNavigationBar: Bool)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Decide where/how to pin the status-bar chrome without painting a PassThroughView gap.
|
|
127
|
+
static func placement(
|
|
128
|
+
isPassThroughOverlay: Bool,
|
|
129
|
+
blankNavigationTab: Bool,
|
|
130
|
+
navigationBarInHostHierarchy: Bool
|
|
131
|
+
) -> Placement {
|
|
132
|
+
if isPassThroughOverlay && blankNavigationTab {
|
|
133
|
+
// Custom-frame blank overlays must stay transparent in the y-offset gap.
|
|
134
|
+
return .skip
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if blankNavigationTab || !navigationBarInHostHierarchy {
|
|
138
|
+
return .host(pinToNavigationBar: false)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return .host(pinToNavigationBar: true)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static func hostView(
|
|
145
|
+
navigationControllerView: UIView,
|
|
146
|
+
framedContentView: UIView?
|
|
147
|
+
) -> UIView? {
|
|
148
|
+
if let passThrough = navigationControllerView as? PassThroughView {
|
|
149
|
+
return framedContentView ?? passThrough.framedContentView
|
|
150
|
+
}
|
|
151
|
+
return navigationControllerView
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
54
155
|
enum CustomWebViewFrameSupport {
|
|
55
156
|
static func resolvedFrame(
|
|
56
157
|
width: CGFloat?,
|
|
@@ -98,7 +199,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
98
199
|
case aware = "AWARE"
|
|
99
200
|
case fakeVisible = "FAKE_VISIBLE"
|
|
100
201
|
}
|
|
101
|
-
private let pluginVersion: String = "8.10.
|
|
202
|
+
private let pluginVersion: String = "8.10.3"
|
|
102
203
|
public let identifier = "CapgoInAppBrowserPlugin"
|
|
103
204
|
public let jsName = "CapgoInAppBrowser"
|
|
104
205
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -155,6 +256,8 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
155
256
|
private var closeModalDescription: String?
|
|
156
257
|
private var closeModalOk: String?
|
|
157
258
|
private var closeModalCancel: String?
|
|
259
|
+
private var safariViewController: SFSafariViewController?
|
|
260
|
+
private var safariOpenedUrl: String?
|
|
158
261
|
private var openSecureWindowCall: CAPPluginCall?
|
|
159
262
|
|
|
160
263
|
private func setup() {
|
|
@@ -314,8 +417,25 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
314
417
|
return nil
|
|
315
418
|
}
|
|
316
419
|
} else {
|
|
317
|
-
|
|
318
|
-
|
|
420
|
+
navigationController.modalPresentationStyle = .overFullScreen
|
|
421
|
+
navigationController.modalTransitionStyle = .crossDissolve
|
|
422
|
+
|
|
423
|
+
let presenter = BlankTargetNavigationSupport.popupPresenter(
|
|
424
|
+
parentController: parentController,
|
|
425
|
+
bridgeViewController: self.bridge?.viewController
|
|
426
|
+
)
|
|
427
|
+
presentNavigationControllerSafely(navigationController, from: presenter, animated: true) { [weak self] presented in
|
|
428
|
+
if !presented {
|
|
429
|
+
self?.unregisterWebView(id: popupId)
|
|
430
|
+
}
|
|
431
|
+
self?.notifyPopupWindowOpened(
|
|
432
|
+
id: popupId,
|
|
433
|
+
parentId: parentController.instanceId,
|
|
434
|
+
url: navigationAction.request.url?.absoluteString,
|
|
435
|
+
visible: presented
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
return popupWebView
|
|
319
439
|
}
|
|
320
440
|
notifyPopupWindowOpened(
|
|
321
441
|
id: popupId,
|
|
@@ -326,6 +446,43 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
326
446
|
return popupWebView
|
|
327
447
|
}
|
|
328
448
|
|
|
449
|
+
private func presentNavigationControllerSafely(
|
|
450
|
+
_ navigationController: UINavigationController,
|
|
451
|
+
from presenter: UIViewController?,
|
|
452
|
+
animated: Bool,
|
|
453
|
+
completion: ((Bool) -> Void)? = nil
|
|
454
|
+
) {
|
|
455
|
+
guard let presenter else {
|
|
456
|
+
completion?(false)
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
let presentBlock = {
|
|
461
|
+
if navigationController.presentingViewController != nil {
|
|
462
|
+
completion?(true)
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
let top = BlankTargetNavigationSupport.topPresenter(from: presenter)
|
|
467
|
+
guard top.presentedViewController == nil else {
|
|
468
|
+
print("[InAppBrowser] Skipping present; presenter already has a presented VC")
|
|
469
|
+
completion?(false)
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
top.present(navigationController, animated: animated) {
|
|
473
|
+
completion?(true)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if Thread.isMainThread,
|
|
478
|
+
!presenter.isBeingPresented,
|
|
479
|
+
!presenter.isBeingDismissed {
|
|
480
|
+
presentBlock()
|
|
481
|
+
} else {
|
|
482
|
+
DispatchQueue.main.async(execute: presentBlock)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
329
486
|
private func resolveWebViewController(for id: String?) -> WKWebViewController? {
|
|
330
487
|
if let id {
|
|
331
488
|
return webViewControllers[id]
|
|
@@ -421,9 +578,13 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
421
578
|
}
|
|
422
579
|
dismissActiveKeyboard()
|
|
423
580
|
let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
581
|
+
presentNavigationControllerSafely(navigationController, from: presenter, animated: isAnimated) { presented in
|
|
582
|
+
if presented {
|
|
583
|
+
self.currentPluginCall?.resolve()
|
|
584
|
+
} else {
|
|
585
|
+
self.currentPluginCall?.reject("Failed to present webview")
|
|
586
|
+
}
|
|
587
|
+
}
|
|
427
588
|
return true
|
|
428
589
|
}
|
|
429
590
|
|
|
@@ -1461,14 +1622,10 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1461
1622
|
// Apply status bar background color via the special view
|
|
1462
1623
|
webViewController.setupStatusBarBackground(color: color)
|
|
1463
1624
|
|
|
1464
|
-
//
|
|
1625
|
+
// Color the webview controller only — never paint PassThroughView or the
|
|
1626
|
+
// y-offset gap would hide the host Capacitor app.
|
|
1465
1627
|
webViewController.view.backgroundColor = color
|
|
1466
|
-
self.
|
|
1467
|
-
|
|
1468
|
-
// Apply status bar background color
|
|
1469
|
-
if let navController = self.navigationWebViewController {
|
|
1470
|
-
navController.view.backgroundColor = color
|
|
1471
|
-
}
|
|
1628
|
+
self.applyBlankToolbarBackground(color, to: self.navigationWebViewController)
|
|
1472
1629
|
} else {
|
|
1473
1630
|
// Follow system appearance if no specific color
|
|
1474
1631
|
let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
|
|
@@ -1479,10 +1636,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1479
1636
|
// Apply status bar background color via the special view
|
|
1480
1637
|
webViewController.setupStatusBarBackground(color: backgroundColor)
|
|
1481
1638
|
|
|
1482
|
-
|
|
1483
|
-
if let navController = self.navigationWebViewController {
|
|
1484
|
-
navController.view.backgroundColor = backgroundColor
|
|
1485
|
-
}
|
|
1639
|
+
self.applyBlankToolbarBackground(backgroundColor, to: self.navigationWebViewController)
|
|
1486
1640
|
}
|
|
1487
1641
|
|
|
1488
1642
|
}
|
|
@@ -1610,9 +1764,13 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1610
1764
|
|
|
1611
1765
|
if navController.presentingViewController == nil {
|
|
1612
1766
|
let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1767
|
+
self.presentNavigationControllerSafely(navController, from: presenter, animated: true) { presented in
|
|
1768
|
+
if presented {
|
|
1769
|
+
call?.resolve()
|
|
1770
|
+
} else {
|
|
1771
|
+
call?.reject("Failed to present webview")
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1616
1774
|
return
|
|
1617
1775
|
}
|
|
1618
1776
|
}
|
|
@@ -1664,7 +1822,6 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1664
1822
|
}
|
|
1665
1823
|
}
|
|
1666
1824
|
}
|
|
1667
|
-
|
|
1668
1825
|
@objc func bringToFront(_ call: CAPPluginCall) {
|
|
1669
1826
|
DispatchQueue.main.async {
|
|
1670
1827
|
let isAnimated = call.getBool("isAnimated", true)
|
|
@@ -1685,9 +1842,13 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1685
1842
|
self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navigationController)
|
|
1686
1843
|
if navigationController.presentingViewController == nil {
|
|
1687
1844
|
let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1845
|
+
self.presentNavigationControllerSafely(navigationController, from: presenter, animated: isAnimated) { presented in
|
|
1846
|
+
if presented {
|
|
1847
|
+
call.resolve()
|
|
1848
|
+
} else {
|
|
1849
|
+
call.reject("Failed to present webview")
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1691
1852
|
return
|
|
1692
1853
|
}
|
|
1693
1854
|
call.resolve()
|
|
@@ -1923,6 +2084,20 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1923
2084
|
}
|
|
1924
2085
|
}
|
|
1925
2086
|
|
|
2087
|
+
|
|
2088
|
+
/// Keep PassThroughView clear so custom y-offsets show the host app behind the gap.
|
|
2089
|
+
private func applyBlankToolbarBackground(_ color: UIColor, to navigationController: UINavigationController?) {
|
|
2090
|
+
guard let navigationController else { return }
|
|
2091
|
+
|
|
2092
|
+
if let passThrough = navigationController.view as? PassThroughView {
|
|
2093
|
+
passThrough.backgroundColor = .clear
|
|
2094
|
+
passThrough.framedContentView?.backgroundColor = color
|
|
2095
|
+
return
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
navigationController.view.backgroundColor = color
|
|
2099
|
+
}
|
|
2100
|
+
|
|
1926
2101
|
private func normalizedAuthorizedHosts(from links: [String]) -> [String] {
|
|
1927
2102
|
links.compactMap { link in
|
|
1928
2103
|
guard let host = URLComponents(string: link)?.host?.lowercased() else {
|
|
@@ -2011,12 +2186,6 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2011
2186
|
self.setup()
|
|
2012
2187
|
}
|
|
2013
2188
|
|
|
2014
|
-
let isInspectable = call.getBool("isInspectable", false)
|
|
2015
|
-
let preventDeeplink = call.getBool("preventDeeplink", false)
|
|
2016
|
-
self.isPresentAfterPageLoad = call.getBool("isPresentAfterPageLoad", false)
|
|
2017
|
-
|
|
2018
|
-
self.currentPluginCall = call
|
|
2019
|
-
|
|
2020
2189
|
guard let urlString = call.getString("url") else {
|
|
2021
2190
|
call.reject("Must provide a URL to open")
|
|
2022
2191
|
return
|
|
@@ -2027,70 +2196,42 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2027
2196
|
return
|
|
2028
2197
|
}
|
|
2029
2198
|
|
|
2030
|
-
let
|
|
2031
|
-
|
|
2199
|
+
guard let url = URL(string: urlString),
|
|
2200
|
+
let scheme = url.scheme?.lowercased(),
|
|
2201
|
+
["http", "https"].contains(scheme) else {
|
|
2202
|
+
call.reject("Must provide a valid http(s) URL to open")
|
|
2203
|
+
return
|
|
2204
|
+
}
|
|
2032
2205
|
|
|
2033
2206
|
DispatchQueue.main.async {
|
|
2034
|
-
|
|
2035
|
-
call.reject("
|
|
2207
|
+
if self.safariViewController != nil {
|
|
2208
|
+
call.reject("Browser is already open")
|
|
2036
2209
|
return
|
|
2037
2210
|
}
|
|
2038
2211
|
|
|
2039
|
-
let
|
|
2212
|
+
let safariVC = SFSafariViewController(url: url)
|
|
2213
|
+
safariVC.delegate = self
|
|
2214
|
+
safariVC.modalPresentationStyle = .fullScreen
|
|
2040
2215
|
|
|
2041
|
-
self.
|
|
2216
|
+
self.safariViewController = safariVC
|
|
2217
|
+
self.safariOpenedUrl = url.absoluteString
|
|
2042
2218
|
|
|
2043
|
-
guard let
|
|
2044
|
-
|
|
2219
|
+
guard let rootPresenter = self.bridge?.viewController else {
|
|
2220
|
+
self.safariViewController = nil
|
|
2221
|
+
self.safariOpenedUrl = nil
|
|
2222
|
+
call.reject("Unable to present Safari View Controller")
|
|
2045
2223
|
return
|
|
2046
2224
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
emptyStatusBar.removeFromSuperview()
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2225
|
+
let top = BlankTargetNavigationSupport.topPresenter(from: rootPresenter)
|
|
2226
|
+
guard top.presentedViewController == nil else {
|
|
2227
|
+
self.safariViewController = nil
|
|
2228
|
+
self.safariOpenedUrl = nil
|
|
2229
|
+
call.reject("Unable to present Safari View Controller: another view is already presented")
|
|
2230
|
+
return
|
|
2056
2231
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
webViewController.leftNavigationBarItemTypes = [.back, .forward, .reload]
|
|
2060
|
-
webViewController.capBrowserPlugin = self
|
|
2061
|
-
webViewController.hasDynamicTitle = true
|
|
2062
|
-
|
|
2063
|
-
self.navigationWebViewController = UINavigationController.init(rootViewController: webViewController)
|
|
2064
|
-
self.navigationWebViewController?.navigationBar.isTranslucent = false
|
|
2065
|
-
|
|
2066
|
-
// Ensure no lines or borders appear by default
|
|
2067
|
-
self.navigationWebViewController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
|
2068
|
-
self.navigationWebViewController?.navigationBar.shadowImage = UIImage()
|
|
2069
|
-
self.navigationWebViewController?.navigationBar.setValue(true, forKey: "hidesShadow")
|
|
2070
|
-
|
|
2071
|
-
// Use system appearance
|
|
2072
|
-
let isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark
|
|
2073
|
-
let backgroundColor = isDarkMode ? UIColor.black : UIColor.white
|
|
2074
|
-
let textColor = isDarkMode ? UIColor.white : UIColor.black
|
|
2075
|
-
|
|
2076
|
-
// Apply colors
|
|
2077
|
-
webViewController.setupStatusBarBackground(color: backgroundColor)
|
|
2078
|
-
webViewController.tintColor = textColor
|
|
2079
|
-
self.navigationWebViewController?.navigationBar.tintColor = textColor
|
|
2080
|
-
self.navigationWebViewController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]
|
|
2081
|
-
webViewController.statusBarStyle = isDarkMode ? .lightContent : .darkContent
|
|
2082
|
-
webViewController.updateStatusBarStyle()
|
|
2083
|
-
|
|
2084
|
-
// Always hide toolbar to ensure no bottom bar
|
|
2085
|
-
self.navigationWebViewController?.setToolbarHidden(true, animated: false)
|
|
2086
|
-
|
|
2087
|
-
self.navigationWebViewController?.modalPresentationStyle = .overCurrentContext
|
|
2088
|
-
self.navigationWebViewController?.modalTransitionStyle = .crossDissolve
|
|
2089
|
-
|
|
2090
|
-
if !self.isPresentAfterPageLoad {
|
|
2091
|
-
self.presentView()
|
|
2232
|
+
top.present(safariVC, animated: true) {
|
|
2233
|
+
call.resolve()
|
|
2092
2234
|
}
|
|
2093
|
-
call.resolve()
|
|
2094
2235
|
}
|
|
2095
2236
|
}
|
|
2096
2237
|
|
|
@@ -2187,6 +2328,17 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2187
2328
|
let isAnimated = call.getBool("isAnimated", true)
|
|
2188
2329
|
|
|
2189
2330
|
DispatchQueue.main.async {
|
|
2331
|
+
if call.getString("id") == nil, let safariViewController = self.safariViewController {
|
|
2332
|
+
let url = self.safariOpenedUrl ?? ""
|
|
2333
|
+
self.safariViewController = nil
|
|
2334
|
+
self.safariOpenedUrl = nil
|
|
2335
|
+
safariViewController.dismiss(animated: isAnimated) {
|
|
2336
|
+
self.notifyListeners("closeEvent", data: ["url": url])
|
|
2337
|
+
call.resolve()
|
|
2338
|
+
}
|
|
2339
|
+
return
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2190
2342
|
let targetId = call.getString("id") ?? self.activeWebViewId
|
|
2191
2343
|
if let targetId,
|
|
2192
2344
|
let webViewController = self.webViewControllers[targetId],
|
|
@@ -2230,6 +2382,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2230
2382
|
}
|
|
2231
2383
|
}
|
|
2232
2384
|
|
|
2385
|
+
|
|
2233
2386
|
private func showPrivacyScreen() {
|
|
2234
2387
|
if privacyScreen == nil {
|
|
2235
2388
|
let newPrivacyScreen = UIImageView()
|
|
@@ -2411,3 +2564,23 @@ extension CapgoInAppBrowserPlugin: ASWebAuthenticationPresentationContextProvidi
|
|
|
2411
2564
|
return self.bridge?.viewController?.view.window ?? ASPresentationAnchor()
|
|
2412
2565
|
}
|
|
2413
2566
|
}
|
|
2567
|
+
|
|
2568
|
+
extension CapgoInAppBrowserPlugin: SFSafariViewControllerDelegate {
|
|
2569
|
+
public func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
|
2570
|
+
guard safariViewController != nil else {
|
|
2571
|
+
return
|
|
2572
|
+
}
|
|
2573
|
+
let url = safariOpenedUrl ?? ""
|
|
2574
|
+
safariViewController = nil
|
|
2575
|
+
safariOpenedUrl = nil
|
|
2576
|
+
notifyListeners("closeEvent", data: ["url": url])
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
public func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
|
|
2580
|
+
if didLoadSuccessfully {
|
|
2581
|
+
notifyListeners("browserPageLoaded", data: [:])
|
|
2582
|
+
} else {
|
|
2583
|
+
notifyListeners("pageLoadError", data: [:])
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
@@ -47,6 +47,35 @@ private enum BlobDownloadSupport {
|
|
|
47
47
|
static let chunkBytes = 64 * 1024
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/// Script message handlers registered on every in-app browser WKWebView.
|
|
51
|
+
/// Popup configs from `createWebViewWith` copy parent handlers, so we must
|
|
52
|
+
/// remove these names before re-adding or WKWebView raises NSInvalidArgumentException.
|
|
53
|
+
enum ScriptMessageHandlerSupport {
|
|
54
|
+
static let allNames = [
|
|
55
|
+
"messageHandler",
|
|
56
|
+
"preShowScriptError",
|
|
57
|
+
"preShowScriptSuccess",
|
|
58
|
+
"close",
|
|
59
|
+
"hide",
|
|
60
|
+
"show",
|
|
61
|
+
"blobDownload",
|
|
62
|
+
"blobDownloadStart",
|
|
63
|
+
"blobDownloadChunk",
|
|
64
|
+
"blobDownloadFinish",
|
|
65
|
+
"blobDownloadAbort",
|
|
66
|
+
"takeScreenshot",
|
|
67
|
+
"consoleMessageHandler",
|
|
68
|
+
"magicPrint",
|
|
69
|
+
"capgoProxyBridge"
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
static func removeAll(from userContentController: WKUserContentController) {
|
|
73
|
+
for name in allNames {
|
|
74
|
+
userContentController.removeScriptMessageHandler(forName: name)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
50
79
|
private struct BlobDownloadSession {
|
|
51
80
|
let destinationURL: URL
|
|
52
81
|
let sourceURL: String?
|
|
@@ -1101,7 +1130,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
1101
1130
|
open var statusBarStyle: UIStatusBarStyle = .default
|
|
1102
1131
|
|
|
1103
1132
|
// Status bar background view
|
|
1104
|
-
|
|
1133
|
+
var statusBarBackgroundView: UIView?
|
|
1105
1134
|
|
|
1106
1135
|
// Status bar height
|
|
1107
1136
|
private var statusBarHeight: CGFloat {
|
|
@@ -1151,25 +1180,60 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
1151
1180
|
// Make status bar background with colored view underneath
|
|
1152
1181
|
open func setupStatusBarBackground(color: UIColor) {
|
|
1153
1182
|
statusBarBackgroundView?.removeFromSuperview()
|
|
1154
|
-
statusBarBackgroundView =
|
|
1183
|
+
statusBarBackgroundView = nil
|
|
1155
1184
|
|
|
1156
|
-
guard let navController = navigationController
|
|
1157
|
-
let navView = navController.view,
|
|
1158
|
-
let statusBarBackgroundView else {
|
|
1185
|
+
guard let navController = navigationController else {
|
|
1159
1186
|
return
|
|
1160
1187
|
}
|
|
1161
|
-
navView.insertSubview(statusBarBackgroundView, at: 0)
|
|
1162
|
-
statusBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
1163
|
-
statusBarBackgroundView.backgroundColor = color
|
|
1164
1188
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1189
|
+
if let passThrough = navController.view as? PassThroughView {
|
|
1190
|
+
// Never paint the full-screen overlay — that fills the custom y-offset gap.
|
|
1191
|
+
passThrough.backgroundColor = .clear
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
let navigationBar = navController.navigationBar
|
|
1195
|
+
let hostCandidate = StatusBarBackgroundLayoutSupport.hostView(
|
|
1196
|
+
navigationControllerView: navController.view,
|
|
1197
|
+
framedContentView: (navController.view as? PassThroughView)?.framedContentView
|
|
1198
|
+
)
|
|
1199
|
+
let navigationBarInHost = hostCandidate.map { navigationBar.isDescendant(of: $0) } ?? false
|
|
1200
|
+
let placement = StatusBarBackgroundLayoutSupport.placement(
|
|
1201
|
+
isPassThroughOverlay: navController.view is PassThroughView,
|
|
1202
|
+
blankNavigationTab: blankNavigationTab,
|
|
1203
|
+
navigationBarInHostHierarchy: navigationBarInHost
|
|
1204
|
+
)
|
|
1205
|
+
|
|
1206
|
+
guard case .host(let pinToNavigationBar) = placement,
|
|
1207
|
+
let hostView = hostCandidate else {
|
|
1208
|
+
return
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
let backgroundView = UIView()
|
|
1212
|
+
statusBarBackgroundView = backgroundView
|
|
1213
|
+
hostView.insertSubview(backgroundView, at: 0)
|
|
1214
|
+
backgroundView.translatesAutoresizingMaskIntoConstraints = false
|
|
1215
|
+
backgroundView.backgroundColor = color
|
|
1216
|
+
|
|
1217
|
+
var constraints = [
|
|
1218
|
+
backgroundView.topAnchor.constraint(equalTo: hostView.topAnchor),
|
|
1219
|
+
backgroundView.leadingAnchor.constraint(equalTo: hostView.leadingAnchor),
|
|
1220
|
+
backgroundView.trailingAnchor.constraint(equalTo: hostView.trailingAnchor)
|
|
1221
|
+
]
|
|
1222
|
+
|
|
1223
|
+
if pinToNavigationBar {
|
|
1224
|
+
constraints.append(
|
|
1225
|
+
backgroundView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 1)
|
|
1226
|
+
)
|
|
1227
|
+
applyNavigationBarBackground(color: color)
|
|
1228
|
+
} else {
|
|
1229
|
+
// Blank toolbar / detached nav bar: cover status-bar height only.
|
|
1230
|
+
// Avoid pinning to a hidden UINavigationBar (removed from hierarchy → crash).
|
|
1231
|
+
constraints.append(
|
|
1232
|
+
backgroundView.heightAnchor.constraint(equalToConstant: max(statusBarHeight, 20))
|
|
1233
|
+
)
|
|
1234
|
+
}
|
|
1171
1235
|
|
|
1172
|
-
|
|
1236
|
+
NSLayoutConstraint.activate(constraints)
|
|
1173
1237
|
}
|
|
1174
1238
|
|
|
1175
1239
|
// Override to use our custom status bar style
|
|
@@ -1973,16 +2037,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
1973
2037
|
}
|
|
1974
2038
|
let userContentController = webConfiguration.userContentController
|
|
1975
2039
|
userContentController.removeAllUserScripts()
|
|
1976
|
-
|
|
1977
|
-
userContentController.removeScriptMessageHandler(forName: "preShowScriptError")
|
|
1978
|
-
userContentController.removeScriptMessageHandler(forName: "preShowScriptSuccess")
|
|
1979
|
-
userContentController.removeScriptMessageHandler(forName: "close")
|
|
1980
|
-
userContentController.removeScriptMessageHandler(forName: "hide")
|
|
1981
|
-
userContentController.removeScriptMessageHandler(forName: "show")
|
|
1982
|
-
userContentController.removeScriptMessageHandler(forName: "takeScreenshot")
|
|
1983
|
-
userContentController.removeScriptMessageHandler(forName: "consoleMessageHandler")
|
|
1984
|
-
userContentController.removeScriptMessageHandler(forName: "magicPrint")
|
|
1985
|
-
userContentController.removeScriptMessageHandler(forName: "capgoProxyBridge")
|
|
2040
|
+
ScriptMessageHandlerSupport.removeAll(from: userContentController)
|
|
1986
2041
|
|
|
1987
2042
|
if proxyRequests || proxySchemeHandler != nil, let handler = proxySchemeHandler {
|
|
1988
2043
|
WKWebView.enableCustomSchemeHandling(for: ["https", "http"])
|
|
@@ -2229,7 +2284,9 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
2229
2284
|
self.enabledSafeTopMargin = parent.enabledSafeTopMargin
|
|
2230
2285
|
self.blockedHosts = parent.blockedHosts
|
|
2231
2286
|
self.authorizedAppLinks = parent.authorizedAppLinks
|
|
2232
|
-
|
|
2287
|
+
// createWebViewWith configs copy parent script handlers; start clean so
|
|
2288
|
+
// initWebview can register blobDownload/etc without NSInvalidArgumentException.
|
|
2289
|
+
configuration.userContentController = WKUserContentController()
|
|
2233
2290
|
self.initialWebConfiguration = configuration
|
|
2234
2291
|
self.persistWebViewData = parent.persistWebViewData
|
|
2235
2292
|
self.disableOverscroll = parent.disableOverscroll
|
|
@@ -2513,20 +2570,7 @@ public extension WKWebViewController {
|
|
|
2513
2570
|
webView.loadHTMLString("", baseURL: nil)
|
|
2514
2571
|
|
|
2515
2572
|
webView.configuration.userContentController.removeAllUserScripts()
|
|
2516
|
-
webView.configuration.userContentController
|
|
2517
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "close")
|
|
2518
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "hide")
|
|
2519
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "show")
|
|
2520
|
-
if allowScreenshotsFromWebPage {
|
|
2521
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "takeScreenshot")
|
|
2522
|
-
}
|
|
2523
|
-
if captureConsoleLogs {
|
|
2524
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "consoleMessageHandler")
|
|
2525
|
-
}
|
|
2526
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "preShowScriptSuccess")
|
|
2527
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "preShowScriptError")
|
|
2528
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "magicPrint")
|
|
2529
|
-
webView.configuration.userContentController.removeScriptMessageHandler(forName: "capgoProxyBridge")
|
|
2573
|
+
ScriptMessageHandlerSupport.removeAll(from: webView.configuration.userContentController)
|
|
2530
2574
|
|
|
2531
2575
|
webView.removeFromSuperview()
|
|
2532
2576
|
// Also clean progress bar view if present
|
|
@@ -3211,6 +3255,31 @@ extension WKWebViewController: WKUIDelegate {
|
|
|
3211
3255
|
}
|
|
3212
3256
|
|
|
3213
3257
|
print("[InAppBrowser] Handling popup/new window request for URL: \(url.absoluteString)")
|
|
3258
|
+
|
|
3259
|
+
let isAuthorized = isUrlAuthorized(url, authorizedLinks: authorizedAppLinks)
|
|
3260
|
+
switch BlankTargetNavigationSupport.resolve(
|
|
3261
|
+
urlIsHttpOrHttps: isHttpOrHttps(url),
|
|
3262
|
+
openBlankTargetInWebView: openBlankTargetInWebView,
|
|
3263
|
+
preventDeeplink: preventDeeplink,
|
|
3264
|
+
isAuthorizedAppLink: isAuthorized
|
|
3265
|
+
) {
|
|
3266
|
+
case .openExternalApp:
|
|
3267
|
+
// Prefer the native app / Universal Link. If none handles it, keep browsing in-place.
|
|
3268
|
+
tryOpenUniversalLink(url) { [weak webView] opened in
|
|
3269
|
+
if !opened {
|
|
3270
|
+
DispatchQueue.main.async {
|
|
3271
|
+
webView?.load(navigationAction.request)
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
return nil
|
|
3276
|
+
case .loadInCurrentWebView:
|
|
3277
|
+
webView.load(navigationAction.request)
|
|
3278
|
+
return nil
|
|
3279
|
+
case .createPopup:
|
|
3280
|
+
break
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3214
3283
|
return capBrowserPlugin?.createManagedPopupWebView(
|
|
3215
3284
|
from: self,
|
|
3216
3285
|
configuration: configuration,
|