@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.1"
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
- let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
318
- presenter?.present(navigationController, animated: true, completion: nil)
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
- presenter?.present(navigationController, animated: isAnimated, completion: {
425
- self.currentPluginCall?.resolve()
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
- // Apply background color to whole view to ensure no gaps
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.navigationWebViewController?.view.backgroundColor = color
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
- // Set appropriate background color
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
- presenter?.present(navController, animated: true, completion: {
1614
- call?.resolve()
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
- presenter?.present(navigationController, animated: isAnimated, completion: {
1689
- call.resolve()
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 headers = call.getObject("headers", [:]).mapValues { String(describing: $0 as Any) }
2031
- let credentials = self.readCredentials(call)
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
- guard let webSource = self.webSource(for: urlString) else {
2035
- call.reject("Invalid URL format")
2207
+ if self.safariViewController != nil {
2208
+ call.reject("Browser is already open")
2036
2209
  return
2037
2210
  }
2038
2211
 
2039
- let initialUrl = webSource.remoteURL ?? URL(string: "about:blank")!
2212
+ let safariVC = SFSafariViewController(url: url)
2213
+ safariVC.delegate = self
2214
+ safariVC.modalPresentationStyle = .fullScreen
2040
2215
 
2041
- self.webViewController = WKWebViewController.init(url: initialUrl, headers: headers, isInspectable: isInspectable, credentials: credentials, preventDeeplink: preventDeeplink, blankNavigationTab: true, enabledSafeBottomMargin: false, enabledSafeTopMargin: true)
2216
+ self.safariViewController = safariVC
2217
+ self.safariOpenedUrl = url.absoluteString
2042
2218
 
2043
- guard let webViewController = self.webViewController else {
2044
- call.reject("Failed to initialize WebViewController")
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
- if self.bridge?.statusBarVisible == true {
2049
- let subviews = self.bridge?.webView?.superview?.subviews
2050
- if let emptyStatusBarIndex = subviews?.firstIndex(where: { $0.subviews.isEmpty }) {
2051
- if let emptyStatusBar = subviews?[emptyStatusBarIndex] {
2052
- webViewController.capacitorStatusBar = emptyStatusBar
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
- webViewController.source = webSource
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
- private var statusBarBackgroundView: UIView?
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 = UIView()
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
- NSLayoutConstraint.activate([
1166
- statusBarBackgroundView.topAnchor.constraint(equalTo: navView.topAnchor),
1167
- statusBarBackgroundView.leadingAnchor.constraint(equalTo: navView.leadingAnchor),
1168
- statusBarBackgroundView.trailingAnchor.constraint(equalTo: navView.trailingAnchor),
1169
- statusBarBackgroundView.bottomAnchor.constraint(equalTo: navController.navigationBar.bottomAnchor, constant: 1)
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
- applyNavigationBarBackground(color: color)
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
- userContentController.removeScriptMessageHandler(forName: "messageHandler")
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
- self.activeNativeNavigationForWebview = parent.activeNativeNavigationForWebview
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.removeScriptMessageHandler(forName: "messageHandler")
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.10.1",
3
+ "version": "8.10.3-beta.pr621.476.1",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",