@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.
@@ -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.10.6"
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
- // Configure modal presentation for touch passthrough if custom dimensions are set
1574
- if (width != nil || height != nil) && !toBack {
1575
- self.navigationWebViewController?.modalPresentationStyle = .overFullScreen
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
- if navController.presentingViewController == nil {
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
- let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
1845
- self.presentNavigationControllerSafely(navigationController, from: presenter, animated: isAnimated) { presented in
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
- navigationController.dismiss(animated: isAnimated, completion: nil)
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 app.canOpenURL(url) {
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
- private func tryOpenUniversalLink(_ url: URL, completion: @escaping (Bool) -> Void) {
2860
- // Only for http(s):// and authorized hosts
2861
- UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { opened in
2862
- completion(opened) // true => app opened, false => no associated app
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 Universal Link hosts: prefer app via universalLinksOnly
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 Universal Link detected \(scheme + host), try to open URLs in external apps")
2970
- tryOpenUniversalLink(url) { opened in
2971
- print("[InAppBrowser] Handle as Universal Link: \(opened)")
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 the native app / Universal Link. If none handles it, keep browsing in-place.
3268
- tryOpenUniversalLink(url) { [weak webView] opened in
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
- if url.absoluteString.contains("apps.apple.com") {
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
- let targetFrame = CustomWebViewFrameSupport.resolvedFrame(
3648
- width: customWidth,
3649
- height: customHeight,
3650
- x: customX,
3651
- y: customY,
3652
- fallbackSize: UIScreen.main.bounds.size
3653
- ) else {
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
- guard let passthroughView else {
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.10.6",
3
+ "version": "8.11.0",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",