@capgo/capacitor-inappbrowser 8.10.6 → 8.11.0
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.
- package/README.md +111 -68
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/CapgoInAppBrowserPlugin.java +10 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCallbacks.java +4 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +7 -0
- package/dist/docs.json +59 -3
- package/dist/esm/definitions.d.ts +31 -6
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +221 -102
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +51 -26
- package/package.json +1 -1
|
@@ -78,6 +78,38 @@ enum BlankTargetNavigationSupport {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
enum AuthorizedAppLinkOpenSupport {
|
|
82
|
+
enum Outcome: Equatable {
|
|
83
|
+
case openedExternally
|
|
84
|
+
case loadInWebView
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Universal Link first; if that fails, system open (App Store / Safari / etc.).
|
|
88
|
+
/// Only stay in-webview when both external attempts fail.
|
|
89
|
+
static func resolve(universalLinkOpened: Bool, systemOpenSucceeded: Bool) -> Outcome {
|
|
90
|
+
if universalLinkOpened || systemOpenSucceeded {
|
|
91
|
+
return .openedExternally
|
|
92
|
+
}
|
|
93
|
+
return .loadInWebView
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
enum CustomSchemeOpenSupport {
|
|
98
|
+
static let alwaysAttemptOpenSchemes = ["tel", "mailto", "sms"]
|
|
99
|
+
|
|
100
|
+
/// `canOpenURL` needs LSApplicationQueriesSchemes; `open` does not.
|
|
101
|
+
/// Always attempt open for Mail/Phone/Messages even when the query fails.
|
|
102
|
+
static func shouldAttemptOpen(scheme: String?, canOpenURL: Bool) -> Bool {
|
|
103
|
+
if canOpenURL {
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
guard let scheme = scheme?.lowercased(), !scheme.isEmpty else {
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
return alwaysAttemptOpenSchemes.contains(scheme)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
81
113
|
protocol ProxyRequestLocating {
|
|
82
114
|
func hasPendingProxyRequest(_ requestId: String) -> Bool
|
|
83
115
|
}
|
|
@@ -116,7 +148,6 @@ enum ProxyResponseRoutingSupport {
|
|
|
116
148
|
}
|
|
117
149
|
}
|
|
118
150
|
|
|
119
|
-
|
|
120
151
|
enum StatusBarBackgroundLayoutSupport {
|
|
121
152
|
enum Placement: Equatable {
|
|
122
153
|
case skip
|
|
@@ -199,7 +230,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
199
230
|
case aware = "AWARE"
|
|
200
231
|
case fakeVisible = "FAKE_VISIBLE"
|
|
201
232
|
}
|
|
202
|
-
private let pluginVersion: String = "8.
|
|
233
|
+
private let pluginVersion: String = "8.11.0"
|
|
203
234
|
public let identifier = "CapgoInAppBrowserPlugin"
|
|
204
235
|
public let jsName = "CapgoInAppBrowser"
|
|
205
236
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -246,6 +277,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
246
277
|
private var hiddenWebViewContainers: [ObjectIdentifier: UIView] = [:]
|
|
247
278
|
private var closeModalTitle: String?
|
|
248
279
|
private var layeredWebViewIds = Set<String>()
|
|
280
|
+
private var framedOverlayWebViewIds = Set<String>()
|
|
249
281
|
private var transparentHostWebViewIds = Set<String>()
|
|
250
282
|
private var hasStoredHostWebViewAppearance = false
|
|
251
283
|
private var originalHostWebViewBackgroundColor: UIColor?
|
|
@@ -306,6 +338,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
306
338
|
}
|
|
307
339
|
|
|
308
340
|
private func unregisterWebView(id: String) {
|
|
341
|
+
detachFramedOverlayWebView(id: id)
|
|
309
342
|
detachLayeredWebView(id: id)
|
|
310
343
|
if let webView = webViewControllers[id]?.capableWebView {
|
|
311
344
|
cleanupHiddenWebViewContainer(for: webView)
|
|
@@ -573,7 +606,20 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
573
606
|
return true
|
|
574
607
|
}
|
|
575
608
|
|
|
609
|
+
if let resolvedId,
|
|
610
|
+
let webViewController = webViewControllers[resolvedId],
|
|
611
|
+
webViewController.shouldPresentAsFramedOverlay {
|
|
612
|
+
dismissActiveKeyboard()
|
|
613
|
+
if presentNavigationControllerAsFramedOverlay(id: resolvedId) {
|
|
614
|
+
self.currentPluginCall?.resolve()
|
|
615
|
+
return true
|
|
616
|
+
}
|
|
617
|
+
self.currentPluginCall?.reject("Failed to present webview")
|
|
618
|
+
return false
|
|
619
|
+
}
|
|
620
|
+
|
|
576
621
|
if let resolvedId {
|
|
622
|
+
detachFramedOverlayWebView(id: resolvedId)
|
|
577
623
|
detachLayeredWebView(id: resolvedId)
|
|
578
624
|
}
|
|
579
625
|
dismissActiveKeyboard()
|
|
@@ -588,61 +634,6 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
588
634
|
return true
|
|
589
635
|
}
|
|
590
636
|
|
|
591
|
-
private func targetFrame(for webViewController: WKWebViewController, in hostWebView: UIView) -> CGRect {
|
|
592
|
-
return CustomWebViewFrameSupport.resolvedFrame(
|
|
593
|
-
width: webViewController.customWidth,
|
|
594
|
-
height: webViewController.customHeight,
|
|
595
|
-
x: webViewController.customX,
|
|
596
|
-
y: webViewController.customY,
|
|
597
|
-
fallbackSize: hostWebView.bounds.size
|
|
598
|
-
) ?? hostWebView.bounds
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
@discardableResult
|
|
602
|
-
private func sendNavigationControllerToBack(id: String, transparentBackground: Bool) -> Bool {
|
|
603
|
-
guard let navigationController = navigationControllers[id],
|
|
604
|
-
let webViewController = webViewControllers[id],
|
|
605
|
-
let hostWebView = self.bridge?.webView else {
|
|
606
|
-
return false
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
if let webView = webViewController.capableWebView, webView.superview !== webViewController.view {
|
|
610
|
-
cleanupHiddenWebViewContainer(for: webView)
|
|
611
|
-
attachWebViewToController(webViewController, webView: webView)
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
dismissActiveKeyboard()
|
|
615
|
-
navigationController.view.removeFromSuperview()
|
|
616
|
-
if transparentBackground {
|
|
617
|
-
makeHostWebViewTransparent(for: id)
|
|
618
|
-
} else {
|
|
619
|
-
restoreHostWebViewBackgroundIfNeeded(for: id)
|
|
620
|
-
}
|
|
621
|
-
navigationController.view.frame = targetFrame(for: webViewController, in: hostWebView)
|
|
622
|
-
navigationController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
623
|
-
navigationController.view.isUserInteractionEnabled = false
|
|
624
|
-
hostWebView.addSubview(navigationController.view)
|
|
625
|
-
hostWebView.sendSubviewToBack(navigationController.view)
|
|
626
|
-
navigationController.view.setNeedsLayout()
|
|
627
|
-
navigationController.view.layoutIfNeeded()
|
|
628
|
-
|
|
629
|
-
layeredWebViewIds.insert(id)
|
|
630
|
-
webViewController.isLayeredBehind = true
|
|
631
|
-
webViewController.transparentHostBackground = transparentBackground
|
|
632
|
-
return true
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
private func detachLayeredWebView(id: String) {
|
|
636
|
-
if let navigationController = navigationControllers[id] {
|
|
637
|
-
if navigationController.view.superview === self.bridge?.webView {
|
|
638
|
-
navigationController.view.removeFromSuperview()
|
|
639
|
-
}
|
|
640
|
-
navigationController.view.isUserInteractionEnabled = true
|
|
641
|
-
}
|
|
642
|
-
layeredWebViewIds.remove(id)
|
|
643
|
-
restoreHostWebViewBackgroundIfNeeded(for: id)
|
|
644
|
-
}
|
|
645
|
-
|
|
646
637
|
private func storeSubviewBackgrounds(from view: UIView) {
|
|
647
638
|
for subview in view.subviews {
|
|
648
639
|
originalHostSubviewBackgrounds.append((subview, subview.backgroundColor, subview.isOpaque))
|
|
@@ -1570,42 +1561,9 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1570
1561
|
}
|
|
1571
1562
|
webViewController.updateTitleAppearance()
|
|
1572
1563
|
|
|
1573
|
-
//
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
// Create a pass-through container
|
|
1578
|
-
let containerView = PassThroughView()
|
|
1579
|
-
containerView.backgroundColor = .clear
|
|
1580
|
-
containerView.frame = UIScreen.main.bounds
|
|
1581
|
-
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
1582
|
-
containerView.passthroughView = self.bridge?.webView
|
|
1583
|
-
|
|
1584
|
-
let targetFrame = CustomWebViewFrameSupport.resolvedFrame(
|
|
1585
|
-
width: width.map { CGFloat($0) },
|
|
1586
|
-
height: height.map { CGFloat($0) },
|
|
1587
|
-
x: xPos.map { CGFloat($0) },
|
|
1588
|
-
y: yPos.map { CGFloat($0) },
|
|
1589
|
-
fallbackSize: UIScreen.main.bounds.size
|
|
1590
|
-
)
|
|
1591
|
-
containerView.targetFrame = targetFrame
|
|
1592
|
-
|
|
1593
|
-
// Replace the navigation controller's view with our pass-through container
|
|
1594
|
-
if let navController = self.navigationWebViewController,
|
|
1595
|
-
let originalView = navController.view {
|
|
1596
|
-
navController.view = containerView
|
|
1597
|
-
containerView.addSubview(originalView)
|
|
1598
|
-
containerView.framedContentView = originalView
|
|
1599
|
-
originalView.translatesAutoresizingMaskIntoConstraints = true
|
|
1600
|
-
originalView.autoresizingMask = []
|
|
1601
|
-
if let targetFrame {
|
|
1602
|
-
originalView.frame = targetFrame
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
} else {
|
|
1606
|
-
self.navigationWebViewController?.modalPresentationStyle = .overCurrentContext
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1564
|
+
// Custom width/height front browsers are attached as framed child overlays in
|
|
1565
|
+
// presentView (matching Android window sizing) so host taps outside the frame work.
|
|
1566
|
+
self.navigationWebViewController?.modalPresentationStyle = .overCurrentContext
|
|
1609
1567
|
self.navigationWebViewController?.modalTransitionStyle = .crossDissolve
|
|
1610
1568
|
if toolbarType == "blank" {
|
|
1611
1569
|
self.navigationWebViewController?.navigationBar.isHidden = true
|
|
@@ -1732,6 +1690,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1732
1690
|
|
|
1733
1691
|
if hidden {
|
|
1734
1692
|
if let resolvedId {
|
|
1693
|
+
self.detachFramedOverlayWebView(id: resolvedId)
|
|
1735
1694
|
self.detachLayeredWebView(id: resolvedId)
|
|
1736
1695
|
}
|
|
1737
1696
|
if !self.attachWebViewToWindow(webView) {
|
|
@@ -1760,9 +1719,22 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1760
1719
|
if let navController = navigationController {
|
|
1761
1720
|
if let resolvedId {
|
|
1762
1721
|
self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navController)
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1722
|
+
if navController.presentingViewController == nil && !self.framedOverlayWebViewIds.contains(resolvedId) {
|
|
1723
|
+
self.revealNavigationController(
|
|
1724
|
+
id: resolvedId,
|
|
1725
|
+
navigationController: navController,
|
|
1726
|
+
webViewController: webViewController,
|
|
1727
|
+
animated: true
|
|
1728
|
+
) { presented in
|
|
1729
|
+
if presented {
|
|
1730
|
+
call?.resolve()
|
|
1731
|
+
} else {
|
|
1732
|
+
call?.reject("Failed to present webview")
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return
|
|
1736
|
+
}
|
|
1737
|
+
} else if navController.presentingViewController == nil {
|
|
1766
1738
|
let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
|
|
1767
1739
|
self.presentNavigationControllerSafely(navController, from: presenter, animated: true) { presented in
|
|
1768
1740
|
if presented {
|
|
@@ -1840,9 +1812,13 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1840
1812
|
self.attachWebViewToController(webViewController, webView: webView)
|
|
1841
1813
|
}
|
|
1842
1814
|
self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navigationController)
|
|
1843
|
-
if navigationController.presentingViewController == nil {
|
|
1844
|
-
|
|
1845
|
-
|
|
1815
|
+
if navigationController.presentingViewController == nil && !self.framedOverlayWebViewIds.contains(resolvedId) {
|
|
1816
|
+
self.revealNavigationController(
|
|
1817
|
+
id: resolvedId,
|
|
1818
|
+
navigationController: navigationController,
|
|
1819
|
+
webViewController: webViewController,
|
|
1820
|
+
animated: isAnimated
|
|
1821
|
+
) { presented in
|
|
1846
1822
|
if presented {
|
|
1847
1823
|
call.resolve()
|
|
1848
1824
|
} else {
|
|
@@ -1851,6 +1827,12 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1851
1827
|
}
|
|
1852
1828
|
return
|
|
1853
1829
|
}
|
|
1830
|
+
if webViewController.shouldPresentAsFramedOverlay {
|
|
1831
|
+
guard self.presentNavigationControllerAsFramedOverlay(id: resolvedId) else {
|
|
1832
|
+
call.reject("Failed to present webview")
|
|
1833
|
+
return
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1854
1836
|
call.resolve()
|
|
1855
1837
|
}
|
|
1856
1838
|
}
|
|
@@ -2084,7 +2066,6 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2084
2066
|
}
|
|
2085
2067
|
}
|
|
2086
2068
|
|
|
2087
|
-
|
|
2088
2069
|
/// Keep PassThroughView clear so custom y-offsets show the host app behind the gap.
|
|
2089
2070
|
private func applyBlankToolbarBackground(_ color: UIColor, to navigationController: UINavigationController?) {
|
|
2090
2071
|
guard let navigationController else { return }
|
|
@@ -2344,9 +2325,12 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2344
2325
|
let webViewController = self.webViewControllers[targetId],
|
|
2345
2326
|
let navigationController = self.navigationControllers[targetId] {
|
|
2346
2327
|
let currentUrl = webViewController.url?.absoluteString ?? ""
|
|
2328
|
+
let wasPresented = navigationController.presentingViewController != nil
|
|
2347
2329
|
webViewController.cleanupWebView()
|
|
2348
2330
|
self.handleWebViewDidClose(id: targetId, url: currentUrl)
|
|
2349
|
-
|
|
2331
|
+
if wasPresented {
|
|
2332
|
+
navigationController.dismiss(animated: isAnimated, completion: nil)
|
|
2333
|
+
}
|
|
2350
2334
|
call.resolve()
|
|
2351
2335
|
return
|
|
2352
2336
|
}
|
|
@@ -2382,7 +2366,6 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2382
2366
|
}
|
|
2383
2367
|
}
|
|
2384
2368
|
|
|
2385
|
-
|
|
2386
2369
|
private func showPrivacyScreen() {
|
|
2387
2370
|
if privacyScreen == nil {
|
|
2388
2371
|
let newPrivacyScreen = UIImageView()
|
|
@@ -2584,3 +2567,139 @@ extension CapgoInAppBrowserPlugin: SFSafariViewControllerDelegate {
|
|
|
2584
2567
|
}
|
|
2585
2568
|
}
|
|
2586
2569
|
}
|
|
2570
|
+
|
|
2571
|
+
|
|
2572
|
+
extension CapgoInAppBrowserPlugin {
|
|
2573
|
+
private func targetFrame(for webViewController: WKWebViewController, in hostWebView: UIView) -> CGRect {
|
|
2574
|
+
return CustomWebViewFrameSupport.resolvedFrame(
|
|
2575
|
+
width: webViewController.customWidth,
|
|
2576
|
+
height: webViewController.customHeight,
|
|
2577
|
+
x: webViewController.customX,
|
|
2578
|
+
y: webViewController.customY,
|
|
2579
|
+
fallbackSize: hostWebView.bounds.size
|
|
2580
|
+
) ?? hostWebView.bounds
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
@discardableResult
|
|
2584
|
+
private func sendNavigationControllerToBack(id: String, transparentBackground: Bool) -> Bool {
|
|
2585
|
+
guard let navigationController = navigationControllers[id],
|
|
2586
|
+
let webViewController = webViewControllers[id],
|
|
2587
|
+
let hostWebView = self.bridge?.webView else {
|
|
2588
|
+
return false
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
if let webView = webViewController.capableWebView, webView.superview !== webViewController.view {
|
|
2592
|
+
cleanupHiddenWebViewContainer(for: webView)
|
|
2593
|
+
attachWebViewToController(webViewController, webView: webView)
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
dismissActiveKeyboard()
|
|
2597
|
+
detachFramedOverlayWebView(id: id)
|
|
2598
|
+
navigationController.view.removeFromSuperview()
|
|
2599
|
+
if transparentBackground {
|
|
2600
|
+
makeHostWebViewTransparent(for: id)
|
|
2601
|
+
} else {
|
|
2602
|
+
restoreHostWebViewBackgroundIfNeeded(for: id)
|
|
2603
|
+
}
|
|
2604
|
+
navigationController.view.frame = targetFrame(for: webViewController, in: hostWebView)
|
|
2605
|
+
navigationController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
2606
|
+
navigationController.view.isUserInteractionEnabled = false
|
|
2607
|
+
hostWebView.addSubview(navigationController.view)
|
|
2608
|
+
hostWebView.sendSubviewToBack(navigationController.view)
|
|
2609
|
+
navigationController.view.setNeedsLayout()
|
|
2610
|
+
navigationController.view.layoutIfNeeded()
|
|
2611
|
+
|
|
2612
|
+
layeredWebViewIds.insert(id)
|
|
2613
|
+
webViewController.isLayeredBehind = true
|
|
2614
|
+
webViewController.transparentHostBackground = transparentBackground
|
|
2615
|
+
return true
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
private func detachLayeredWebView(id: String) {
|
|
2619
|
+
if let navigationController = navigationControllers[id] {
|
|
2620
|
+
if navigationController.view.superview === self.bridge?.webView {
|
|
2621
|
+
navigationController.view.removeFromSuperview()
|
|
2622
|
+
}
|
|
2623
|
+
navigationController.view.isUserInteractionEnabled = true
|
|
2624
|
+
}
|
|
2625
|
+
layeredWebViewIds.remove(id)
|
|
2626
|
+
restoreHostWebViewBackgroundIfNeeded(for: id)
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
/// Front custom-dimension browsers are child VCs with an exact frame (Android-style).
|
|
2630
|
+
/// Full-screen modal + PassThroughView still leaves UITransitionView eating host taps.
|
|
2631
|
+
@discardableResult
|
|
2632
|
+
private func presentNavigationControllerAsFramedOverlay(id: String) -> Bool {
|
|
2633
|
+
guard let navigationController = navigationControllers[id],
|
|
2634
|
+
let webViewController = webViewControllers[id],
|
|
2635
|
+
let parent = self.bridge?.viewController else {
|
|
2636
|
+
return false
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
dismissNavigationControllerIfPresented(navigationController)
|
|
2640
|
+
detachLayeredWebView(id: id)
|
|
2641
|
+
|
|
2642
|
+
let frame = targetFrame(for: webViewController, in: parent.view)
|
|
2643
|
+
|
|
2644
|
+
if navigationController.parent !== parent {
|
|
2645
|
+
if navigationController.parent != nil {
|
|
2646
|
+
navigationController.willMove(toParent: nil)
|
|
2647
|
+
navigationController.view.removeFromSuperview()
|
|
2648
|
+
navigationController.removeFromParent()
|
|
2649
|
+
}
|
|
2650
|
+
parent.addChild(navigationController)
|
|
2651
|
+
parent.view.addSubview(navigationController.view)
|
|
2652
|
+
navigationController.didMove(toParent: parent)
|
|
2653
|
+
} else if navigationController.view.superview !== parent.view {
|
|
2654
|
+
parent.view.addSubview(navigationController.view)
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
navigationController.view.frame = frame
|
|
2658
|
+
navigationController.view.autoresizingMask = []
|
|
2659
|
+
navigationController.view.isUserInteractionEnabled = true
|
|
2660
|
+
parent.view.bringSubviewToFront(navigationController.view)
|
|
2661
|
+
navigationController.view.setNeedsLayout()
|
|
2662
|
+
navigationController.view.layoutIfNeeded()
|
|
2663
|
+
webViewController.applyCustomDimensions()
|
|
2664
|
+
|
|
2665
|
+
framedOverlayWebViewIds.insert(id)
|
|
2666
|
+
webViewController.isLayeredBehind = false
|
|
2667
|
+
return true
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
private func detachFramedOverlayWebView(id: String) {
|
|
2671
|
+
guard let navigationController = navigationControllers[id] else {
|
|
2672
|
+
framedOverlayWebViewIds.remove(id)
|
|
2673
|
+
return
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
if navigationController.parent != nil {
|
|
2677
|
+
navigationController.willMove(toParent: nil)
|
|
2678
|
+
navigationController.view.removeFromSuperview()
|
|
2679
|
+
navigationController.removeFromParent()
|
|
2680
|
+
} else if framedOverlayWebViewIds.contains(id) {
|
|
2681
|
+
navigationController.view.removeFromSuperview()
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
framedOverlayWebViewIds.remove(id)
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
private func revealNavigationController(
|
|
2688
|
+
id: String,
|
|
2689
|
+
navigationController: UINavigationController,
|
|
2690
|
+
webViewController: WKWebViewController,
|
|
2691
|
+
animated: Bool,
|
|
2692
|
+
completion: ((Bool) -> Void)? = nil
|
|
2693
|
+
) {
|
|
2694
|
+
if webViewController.shouldPresentAsFramedOverlay {
|
|
2695
|
+
completion?(presentNavigationControllerAsFramedOverlay(id: id))
|
|
2696
|
+
return
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
detachFramedOverlayWebView(id: id)
|
|
2700
|
+
let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
|
|
2701
|
+
presentNavigationControllerSafely(navigationController, from: presenter, animated: animated, completion: completion)
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
}
|
|
2705
|
+
|
|
@@ -490,6 +490,11 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
490
490
|
var customX: CGFloat?
|
|
491
491
|
var customY: CGFloat?
|
|
492
492
|
|
|
493
|
+
/// Front (non-toBack) browsers with a custom height use a framed child overlay.
|
|
494
|
+
var shouldPresentAsFramedOverlay: Bool {
|
|
495
|
+
!isLayeredBehind && customHeight != nil
|
|
496
|
+
}
|
|
497
|
+
|
|
493
498
|
internal var preShowSemaphore: DispatchSemaphore?
|
|
494
499
|
internal var preShowError: String?
|
|
495
500
|
private var isWebViewInitialized = false
|
|
@@ -2431,6 +2436,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
2431
2436
|
}
|
|
2432
2437
|
self.progressView?.alpha = 1
|
|
2433
2438
|
self.progressView?.setProgress(Float(estimatedProgress), animated: true)
|
|
2439
|
+
self.emit("browserPageLoadProgress", data: ["progress": estimatedProgress])
|
|
2434
2440
|
|
|
2435
2441
|
if estimatedProgress >= 1.0 {
|
|
2436
2442
|
UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseOut, animations: {
|
|
@@ -2839,8 +2845,9 @@ fileprivate extension WKWebViewController {
|
|
|
2839
2845
|
private func tryOpenCustomScheme(_ url: URL) -> Bool {
|
|
2840
2846
|
let app = UIApplication.shared
|
|
2841
2847
|
let shouldEmitCustomSchemeEvent = CustomSchemeInterceptSupport.shouldEmitInterceptEvent(for: url)
|
|
2848
|
+
let canOpen = app.canOpenURL(url)
|
|
2842
2849
|
|
|
2843
|
-
if
|
|
2850
|
+
if CustomSchemeOpenSupport.shouldAttemptOpen(scheme: url.scheme, canOpenURL: canOpen) {
|
|
2844
2851
|
app.open(url, options: [:], completionHandler: nil)
|
|
2845
2852
|
if shouldEmitCustomSchemeEvent {
|
|
2846
2853
|
emit("customSchemeIntercepted", data: ["url": url.absoluteString, "opened": true])
|
|
@@ -2856,10 +2863,22 @@ fileprivate extension WKWebViewController {
|
|
|
2856
2863
|
return true
|
|
2857
2864
|
}
|
|
2858
2865
|
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2866
|
+
/// Prefer Universal Links into the native app; if that fails, hand off to the system
|
|
2867
|
+
/// (App Store, Safari, etc.) before falling back to in-webview loading.
|
|
2868
|
+
private func openAuthorizedAppLink(_ url: URL, completion: @escaping (Bool) -> Void) {
|
|
2869
|
+
UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { universalLinkOpened in
|
|
2870
|
+
if universalLinkOpened {
|
|
2871
|
+
completion(true)
|
|
2872
|
+
return
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
UIApplication.shared.open(url, options: [:]) { systemOpenSucceeded in
|
|
2876
|
+
let outcome = AuthorizedAppLinkOpenSupport.resolve(
|
|
2877
|
+
universalLinkOpened: false,
|
|
2878
|
+
systemOpenSucceeded: systemOpenSucceeded
|
|
2879
|
+
)
|
|
2880
|
+
completion(outcome == .openedExternally)
|
|
2881
|
+
}
|
|
2863
2882
|
}
|
|
2864
2883
|
}
|
|
2865
2884
|
|
|
@@ -2963,12 +2982,12 @@ fileprivate extension WKWebViewController {
|
|
|
2963
2982
|
return
|
|
2964
2983
|
}
|
|
2965
2984
|
|
|
2966
|
-
// Authorized
|
|
2985
|
+
// Authorized App Link hosts: Universal Link first, then system open.
|
|
2967
2986
|
print("[InAppBrowser] Authorized App Links: \(self.authorizedAppLinks)")
|
|
2968
2987
|
if isUrlAuthorized(url, authorizedLinks: self.authorizedAppLinks) {
|
|
2969
|
-
print("[InAppBrowser] Authorized
|
|
2970
|
-
|
|
2971
|
-
print("[InAppBrowser]
|
|
2988
|
+
print("[InAppBrowser] Authorized App Link detected \(scheme + host), try to open URLs in external apps")
|
|
2989
|
+
openAuthorizedAppLink(url) { opened in
|
|
2990
|
+
print("[InAppBrowser] Authorized App Link opened externally: \(opened)")
|
|
2972
2991
|
completion(opened) // opened => cancel navigation; not opened => allow WebView
|
|
2973
2992
|
}
|
|
2974
2993
|
return
|
|
@@ -3264,8 +3283,8 @@ extension WKWebViewController: WKUIDelegate {
|
|
|
3264
3283
|
isAuthorizedAppLink: isAuthorized
|
|
3265
3284
|
) {
|
|
3266
3285
|
case .openExternalApp:
|
|
3267
|
-
// Prefer
|
|
3268
|
-
|
|
3286
|
+
// Prefer native app / Universal Link, then system open (App Store / Safari).
|
|
3287
|
+
openAuthorizedAppLink(url) { [weak webView] opened in
|
|
3269
3288
|
if !opened {
|
|
3270
3289
|
DispatchQueue.main.async {
|
|
3271
3290
|
webView?.load(navigationAction.request)
|
|
@@ -3438,6 +3457,7 @@ extension WKWebViewController: WKNavigationDelegate {
|
|
|
3438
3457
|
self.url = urlValue
|
|
3439
3458
|
delegate?.webViewController?(self, didStart: urlValue)
|
|
3440
3459
|
}
|
|
3460
|
+
emit("browserPageLoadStart")
|
|
3441
3461
|
}
|
|
3442
3462
|
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
3443
3463
|
if !didpageInit && self.capBrowserPlugin?.isPresentAfterPageLoad == true {
|
|
@@ -3561,7 +3581,10 @@ extension WKWebViewController: WKNavigationDelegate {
|
|
|
3561
3581
|
return
|
|
3562
3582
|
}
|
|
3563
3583
|
|
|
3564
|
-
|
|
3584
|
+
let appStoreHosts = ["apps.apple.com", "itunes.apple.com"]
|
|
3585
|
+
if !self.preventDeeplink,
|
|
3586
|
+
let appStoreHost = self.normalizeHost(url.host),
|
|
3587
|
+
appStoreHosts.contains(appStoreHost) {
|
|
3565
3588
|
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
|
3566
3589
|
decisionHandler(.cancel)
|
|
3567
3590
|
return
|
|
@@ -3643,14 +3666,18 @@ extension WKWebViewController: WKNavigationDelegate {
|
|
|
3643
3666
|
|
|
3644
3667
|
/// Apply custom dimensions to the view if specified
|
|
3645
3668
|
func applyCustomDimensions() {
|
|
3646
|
-
guard let navigationController = navigationController
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3669
|
+
guard let navigationController = navigationController else {
|
|
3670
|
+
return
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
let fallbackSize = navigationController.view.superview?.bounds.size ?? UIScreen.main.bounds.size
|
|
3674
|
+
guard let targetFrame = CustomWebViewFrameSupport.resolvedFrame(
|
|
3675
|
+
width: customWidth,
|
|
3676
|
+
height: customHeight,
|
|
3677
|
+
x: customX,
|
|
3678
|
+
y: customY,
|
|
3679
|
+
fallbackSize: fallbackSize
|
|
3680
|
+
) else {
|
|
3654
3681
|
return
|
|
3655
3682
|
}
|
|
3656
3683
|
|
|
@@ -3811,7 +3838,6 @@ class PassThroughView: UIView {
|
|
|
3811
3838
|
}
|
|
3812
3839
|
}
|
|
3813
3840
|
weak var framedContentView: UIView?
|
|
3814
|
-
weak var passthroughView: UIView?
|
|
3815
3841
|
|
|
3816
3842
|
override func layoutSubviews() {
|
|
3817
3843
|
super.layoutSubviews()
|
|
@@ -3824,12 +3850,11 @@ class PassThroughView: UIView {
|
|
|
3824
3850
|
}
|
|
3825
3851
|
|
|
3826
3852
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
3853
|
+
// Outside the browser frame: decline the hit. Callers that still use a full-screen
|
|
3854
|
+
// PassThroughView depend on the parent hierarchy continuing hit-testing; framed
|
|
3855
|
+
// front overlays avoid modal UITransitionView entirely instead.
|
|
3827
3856
|
if let frame = targetFrame, !frame.contains(point) {
|
|
3828
|
-
|
|
3829
|
-
return nil
|
|
3830
|
-
}
|
|
3831
|
-
let convertedPoint = convert(point, to: passthroughView)
|
|
3832
|
-
return passthroughView.hitTest(convertedPoint, with: event)
|
|
3857
|
+
return nil
|
|
3833
3858
|
}
|
|
3834
3859
|
|
|
3835
3860
|
return super.hitTest(point, with: event)
|