@capgo/capacitor-inappbrowser 8.6.34 → 8.7.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.
@@ -98,7 +98,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
98
98
  case aware = "AWARE"
99
99
  case fakeVisible = "FAKE_VISIBLE"
100
100
  }
101
- private let pluginVersion: String = "8.6.34"
101
+ private let pluginVersion: String = "8.7.0"
102
102
  public let identifier = "CapgoInAppBrowserPlugin"
103
103
  public let jsName = "CapgoInAppBrowser"
104
104
  public let pluginMethods: [CAPPluginMethod] = [
@@ -109,10 +109,14 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
109
109
  CAPPluginMethod(name: "getCookies", returnType: CAPPluginReturnPromise),
110
110
  CAPPluginMethod(name: "clearAllCookies", returnType: CAPPluginReturnPromise),
111
111
  CAPPluginMethod(name: "clearCache", returnType: CAPPluginReturnPromise),
112
+ CAPPluginMethod(name: "clearAllBrowsingData", returnType: CAPPluginReturnPromise),
112
113
  CAPPluginMethod(name: "reload", returnType: CAPPluginReturnPromise),
113
114
  CAPPluginMethod(name: "setUrl", returnType: CAPPluginReturnPromise),
114
115
  CAPPluginMethod(name: "show", returnType: CAPPluginReturnPromise),
115
116
  CAPPluginMethod(name: "hide", returnType: CAPPluginReturnPromise),
117
+ CAPPluginMethod(name: "sendToBack", returnType: CAPPluginReturnPromise),
118
+ CAPPluginMethod(name: "bringToFront", returnType: CAPPluginReturnPromise),
119
+ CAPPluginMethod(name: "dispatchInputEvent", returnType: CAPPluginReturnPromise),
116
120
  CAPPluginMethod(name: "close", returnType: CAPPluginReturnPromise),
117
121
  CAPPluginMethod(name: "executeScript", returnType: CAPPluginReturnPromise),
118
122
  CAPPluginMethod(name: "postMessage", returnType: CAPPluginReturnPromise),
@@ -138,11 +142,16 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
138
142
  private var proxyBridges: [String: ProxyBridge] = [:]
139
143
  private var webViewStack: [String] = []
140
144
  private var activeWebViewId: String?
141
- private weak var presentationContainerView: UIView?
142
- private var presentationContainerWasInteractive = true
143
- private var presentationContainerPreviousAlpha: CGFloat = 1
144
145
  private var hiddenWebViewContainers: [ObjectIdentifier: UIView] = [:]
145
146
  private var closeModalTitle: String?
147
+ private var layeredWebViewIds = Set<String>()
148
+ private var transparentHostWebViewIds = Set<String>()
149
+ private var hasStoredHostWebViewAppearance = false
150
+ private var originalHostWebViewBackgroundColor: UIColor?
151
+ private var originalHostWebViewIsOpaque = true
152
+ private var originalHostSubviewBackgrounds: [(UIView, UIColor?, Bool)] = []
153
+ private var originalHostSuperviewBackgroundColor: UIColor?
154
+ private var originalHostSuperviewIsOpaque = true
146
155
  private var closeModalDescription: String?
147
156
  private var closeModalOk: String?
148
157
  private var closeModalCancel: String?
@@ -181,7 +190,15 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
181
190
  }
182
191
  }
183
192
 
193
+ private func dismissNavigationControllerIfPresented(_ navigationController: UINavigationController?) {
194
+ guard let navigationController, navigationController.presentingViewController != nil else {
195
+ return
196
+ }
197
+ navigationController.dismiss(animated: false)
198
+ }
199
+
184
200
  private func unregisterWebView(id: String) {
201
+ detachLayeredWebView(id: id)
185
202
  if let webView = webViewControllers[id]?.capableWebView {
186
203
  cleanupHiddenWebViewContainer(for: webView)
187
204
  }
@@ -342,6 +359,26 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
342
359
  return stores
343
360
  }
344
361
 
362
+ private func allBrowsingDataStores() -> [WKWebsiteDataStore] {
363
+ var seen = Set<ObjectIdentifier>()
364
+ var stores: [WKWebsiteDataStore] = []
365
+
366
+ func append(_ store: WKWebsiteDataStore) {
367
+ let identifier = ObjectIdentifier(store)
368
+ if seen.insert(identifier).inserted {
369
+ stores.append(store)
370
+ }
371
+ }
372
+
373
+ append(WKWebsiteDataStore.default())
374
+ for controller in webViewControllers.values {
375
+ if let store = controller.websiteDataStore() {
376
+ append(store)
377
+ }
378
+ }
379
+ return stores
380
+ }
381
+
345
382
  private func parseProxyRules(_ rawRules: [Any]) throws -> [NativeProxyRule] {
346
383
  try rawRules.enumerated().map { index, item in
347
384
  guard let dictionary = item as? [String: Any] else {
@@ -355,19 +392,155 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
355
392
  }
356
393
  }
357
394
 
358
- func presentView(webViewId: String? = nil, isAnimated: Bool = true) {
395
+ @discardableResult
396
+ func presentView(webViewId: String? = nil, isAnimated: Bool = true) -> Bool {
359
397
  let resolvedId = webViewId ?? activeWebViewId
360
398
  let navigationController = resolvedId.flatMap { navigationControllers[$0] } ?? self.navigationWebViewController
361
399
  guard let navigationController else {
362
400
  self.currentPluginCall?.reject("Navigation controller is not initialized")
363
- return
401
+ return false
402
+ }
403
+
404
+ if let resolvedId,
405
+ let webViewController = webViewControllers[resolvedId],
406
+ webViewController.isLayeredBehind {
407
+ if !sendNavigationControllerToBack(id: resolvedId, transparentBackground: webViewController.transparentHostBackground) {
408
+ self.currentPluginCall?.reject("Failed to send webview to back")
409
+ return false
410
+ }
411
+ return true
364
412
  }
365
413
 
414
+ if let resolvedId {
415
+ detachLayeredWebView(id: resolvedId)
416
+ }
366
417
  dismissActiveKeyboard()
367
418
  let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
368
419
  presenter?.present(navigationController, animated: isAnimated, completion: {
369
420
  self.currentPluginCall?.resolve()
370
421
  })
422
+ return true
423
+ }
424
+
425
+ private func targetFrame(for webViewController: WKWebViewController, in hostWebView: UIView) -> CGRect {
426
+ return CustomWebViewFrameSupport.resolvedFrame(
427
+ width: webViewController.customWidth,
428
+ height: webViewController.customHeight,
429
+ x: webViewController.customX,
430
+ y: webViewController.customY,
431
+ fallbackSize: hostWebView.bounds.size
432
+ ) ?? hostWebView.bounds
433
+ }
434
+
435
+ @discardableResult
436
+ private func sendNavigationControllerToBack(id: String, transparentBackground: Bool) -> Bool {
437
+ guard let navigationController = navigationControllers[id],
438
+ let webViewController = webViewControllers[id],
439
+ let hostWebView = self.bridge?.webView else {
440
+ return false
441
+ }
442
+
443
+ if let webView = webViewController.capableWebView, webView.superview !== webViewController.view {
444
+ cleanupHiddenWebViewContainer(for: webView)
445
+ attachWebViewToController(webViewController, webView: webView)
446
+ }
447
+
448
+ dismissActiveKeyboard()
449
+ dismissNavigationControllerIfPresented(navigationController)
450
+ navigationController.view.removeFromSuperview()
451
+ if transparentBackground {
452
+ makeHostWebViewTransparent(for: id)
453
+ } else {
454
+ restoreHostWebViewBackgroundIfNeeded(for: id)
455
+ }
456
+ navigationController.view.frame = targetFrame(for: webViewController, in: hostWebView)
457
+ navigationController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
458
+ navigationController.view.isUserInteractionEnabled = false
459
+ hostWebView.addSubview(navigationController.view)
460
+ hostWebView.sendSubviewToBack(navigationController.view)
461
+ navigationController.view.setNeedsLayout()
462
+ navigationController.view.layoutIfNeeded()
463
+
464
+ layeredWebViewIds.insert(id)
465
+ webViewController.isLayeredBehind = true
466
+ webViewController.transparentHostBackground = transparentBackground
467
+ return true
468
+ }
469
+
470
+ private func detachLayeredWebView(id: String) {
471
+ if let navigationController = navigationControllers[id] {
472
+ if navigationController.view.superview === self.bridge?.webView {
473
+ navigationController.view.removeFromSuperview()
474
+ }
475
+ navigationController.view.isUserInteractionEnabled = true
476
+ }
477
+ layeredWebViewIds.remove(id)
478
+ restoreHostWebViewBackgroundIfNeeded(for: id)
479
+ }
480
+
481
+ private func storeSubviewBackgrounds(from view: UIView) {
482
+ for subview in view.subviews {
483
+ originalHostSubviewBackgrounds.append((subview, subview.backgroundColor, subview.isOpaque))
484
+ storeSubviewBackgrounds(from: subview)
485
+ }
486
+ }
487
+
488
+ private func clearBackgrounds(in view: UIView) {
489
+ view.isOpaque = false
490
+ view.backgroundColor = .clear
491
+ view.subviews.forEach { clearBackgrounds(in: $0) }
492
+ }
493
+
494
+ private func makeHostWebViewTransparent(for id: String) {
495
+ guard let hostWebView = self.bridge?.webView else {
496
+ return
497
+ }
498
+
499
+ transparentHostWebViewIds.insert(id)
500
+ if !hasStoredHostWebViewAppearance {
501
+ hasStoredHostWebViewAppearance = true
502
+ originalHostWebViewBackgroundColor = hostWebView.backgroundColor
503
+ originalHostWebViewIsOpaque = hostWebView.isOpaque
504
+ originalHostSuperviewBackgroundColor = hostWebView.superview?.backgroundColor
505
+ originalHostSuperviewIsOpaque = hostWebView.superview?.isOpaque ?? true
506
+ originalHostSubviewBackgrounds.removeAll()
507
+ storeSubviewBackgrounds(from: hostWebView)
508
+ }
509
+
510
+ clearBackgrounds(in: hostWebView)
511
+ hostWebView.superview?.isOpaque = false
512
+ hostWebView.superview?.backgroundColor = .clear
513
+ hostWebView.setNeedsLayout()
514
+ hostWebView.layoutIfNeeded()
515
+ }
516
+
517
+ private func restoreHostWebViewBackgroundIfNeeded(for id: String? = nil) {
518
+ if let id {
519
+ transparentHostWebViewIds.remove(id)
520
+ } else {
521
+ transparentHostWebViewIds.removeAll()
522
+ }
523
+ guard transparentHostWebViewIds.isEmpty, hasStoredHostWebViewAppearance else {
524
+ return
525
+ }
526
+
527
+ if let hostWebView = self.bridge?.webView {
528
+ hostWebView.backgroundColor = originalHostWebViewBackgroundColor
529
+ hostWebView.isOpaque = originalHostWebViewIsOpaque
530
+ hostWebView.superview?.backgroundColor = originalHostSuperviewBackgroundColor
531
+ hostWebView.superview?.isOpaque = originalHostSuperviewIsOpaque
532
+ for (view, backgroundColor, isOpaque) in originalHostSubviewBackgrounds {
533
+ view.backgroundColor = backgroundColor
534
+ view.isOpaque = isOpaque
535
+ }
536
+ hostWebView.setNeedsLayout()
537
+ hostWebView.layoutIfNeeded()
538
+ }
539
+
540
+ hasStoredHostWebViewAppearance = false
541
+ originalHostWebViewBackgroundColor = nil
542
+ originalHostSuperviewBackgroundColor = nil
543
+ originalHostSubviewBackgrounds.removeAll()
371
544
  }
372
545
 
373
546
  private func dismissActiveKeyboard() {
@@ -568,6 +741,24 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
568
741
  }
569
742
  }
570
743
 
744
+ @objc func clearAllBrowsingData(_ call: CAPPluginCall) {
745
+ DispatchQueue.main.async {
746
+ let dataStores = self.allBrowsingDataStores()
747
+ let dataTypes = WKWebsiteDataStore.allWebsiteDataTypes()
748
+ let group = DispatchGroup()
749
+ for dataStore in dataStores {
750
+ group.enter()
751
+ dataStore.removeData(ofTypes: dataTypes,
752
+ modifiedSince: Date(timeIntervalSince1970: 0)) {
753
+ group.leave()
754
+ }
755
+ }
756
+ group.notify(queue: .main) {
757
+ call.resolve()
758
+ }
759
+ }
760
+ }
761
+
571
762
  @objc func clearCookies(_ call: CAPPluginCall) {
572
763
  guard let url = call.getString("url"),
573
764
  let host = URL(string: url)?.host else {
@@ -767,12 +958,15 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
767
958
  let enabledSafeBottomMargin = call.getBool("enabledSafeBottomMargin", false)
768
959
  let enabledSafeTopMargin = call.getBool("enabledSafeTopMargin", true)
769
960
  let hidden = call.getBool("hidden", false)
961
+ let toBack = call.getBool("toBack", false)
962
+ let transparentBackground = call.getBool("transparentBackground", true)
770
963
  self.isHidden = hidden
771
964
  let hiddenPopupWindow = call.getBool("hiddenPopupWindow", false)
772
965
  let allowWebViewJsVisibilityControl = self.getConfig().getBoolean("allowWebViewJsVisibilityControl", false)
773
966
  let allowScreenshotsFromWebPage = call.getBool("allowScreenshotsFromWebPage", false)
774
967
  let captureConsoleLogs = call.getBool("captureConsoleLogs", false)
775
968
  let handleDownloads = call.getBool("handleDownloads", false)
969
+ let persistWebViewData = call.getBool("persistWebViewData", true)
776
970
  let invisibilityModeRaw = call.getString("invisibilityMode", "AWARE")
777
971
  self.invisibilityMode = InvisibilityMode(rawValue: invisibilityModeRaw.uppercased()) ?? .aware
778
972
 
@@ -914,38 +1108,36 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
914
1108
  self.proxyBridges[webViewId] = proxyBridge
915
1109
  }
916
1110
 
917
- self.webViewController = WKWebViewController.init(
918
- url: url,
919
- headers: headers,
920
- isInspectable: isInspectable,
921
- credentials: credentials,
922
- preventDeeplink: preventDeeplink,
923
- blankNavigationTab: toolbarType == "blank",
924
- enabledSafeBottomMargin: enabledSafeBottomMargin,
925
- enabledSafeTopMargin: enabledSafeTopMargin,
926
- blockedHosts: blockedHosts,
1111
+ let webViewController = WKWebViewController()
1112
+ webViewController.blankNavigationTab = toolbarType == "blank"
1113
+ webViewController.enabledSafeBottomMargin = enabledSafeBottomMargin
1114
+ webViewController.enabledSafeTopMargin = enabledSafeTopMargin
1115
+ webViewController.source = .remote(url)
1116
+ webViewController.setCredentials(credentials: credentials)
1117
+ webViewController.allowWebViewJsVisibilityControl = allowWebViewJsVisibilityControl
1118
+ webViewController.allowScreenshotsFromWebPage = allowScreenshotsFromWebPage
1119
+ webViewController.captureConsoleLogs = captureConsoleLogs
1120
+ webViewController.proxyRequests = legacyProxyRequests.isEnabled
1121
+ webViewController.proxySchemeHandler = proxyHandler
1122
+ webViewController.proxyBridge = proxyBridge
1123
+ webViewController.proxyBridgeAccessToken = proxyBridgeAccessToken
1124
+ webViewController.legacyProxyRequestURLRegexPattern = legacyProxyRequests.urlRegex?.pattern
1125
+ webViewController.openBlankTargetInWebView = openBlankTargetInWebView
1126
+ webViewController.persistWebViewData = persistWebViewData
1127
+ webViewController.setHeaders(headers: headers)
1128
+ webViewController.setPreventDeeplink(preventDeeplink: preventDeeplink)
1129
+ webViewController.setBlockedHosts(blockedHosts: blockedHosts)
1130
+ webViewController.setAuthorizedAppLinks(authorizedAppLinks: authorizedAppLinks)
1131
+ webViewController.documentStartUserScripts = self.documentStartUserScripts(
927
1132
  authorizedAppLinks: authorizedAppLinks,
928
- allowWebViewJsVisibilityControl: allowWebViewJsVisibilityControl,
929
- allowScreenshotsFromWebPage: allowScreenshotsFromWebPage,
930
- captureConsoleLogs: captureConsoleLogs,
931
- proxyRequests: legacyProxyRequests.isEnabled,
932
- proxySchemeHandler: proxyHandler,
933
- proxyBridge: proxyBridge,
934
- proxyBridgeAccessToken: proxyBridgeAccessToken,
935
- legacyProxyRequestURLRegexPattern: legacyProxyRequests.urlRegex?.pattern,
936
- documentStartUserScripts: self.documentStartUserScripts(
937
- authorizedAppLinks: authorizedAppLinks,
938
- openBlankTargetInWebView: openBlankTargetInWebView
939
- ),
940
1133
  openBlankTargetInWebView: openBlankTargetInWebView
941
1134
  )
942
-
943
- guard let webViewController = self.webViewController else {
944
- call.reject("Failed to initialize WebViewController")
945
- return
946
- }
1135
+ webViewController.initWebview(isInspectable: isInspectable)
1136
+ self.webViewController = webViewController
947
1137
 
948
1138
  webViewController.instanceId = webViewId
1139
+ webViewController.isLayeredBehind = toBack
1140
+ webViewController.transparentHostBackground = transparentBackground
949
1141
 
950
1142
  // Set HTTP method and body if provided
951
1143
  if let method = httpMethod {
@@ -1178,7 +1370,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1178
1370
  }
1179
1371
 
1180
1372
  // Configure modal presentation for touch passthrough if custom dimensions are set
1181
- if width != nil || height != nil {
1373
+ if (width != nil || height != nil) && !toBack {
1182
1374
  self.navigationWebViewController?.modalPresentationStyle = .overFullScreen
1183
1375
 
1184
1376
  // Create a pass-through container
@@ -1268,7 +1460,9 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1268
1460
  return
1269
1461
  }
1270
1462
  } else if !self.isPresentAfterPageLoad {
1271
- self.presentView(webViewId: webViewId, isAnimated: isAnimated)
1463
+ guard self.presentView(webViewId: webViewId, isAnimated: isAnimated) else {
1464
+ return
1465
+ }
1272
1466
  }
1273
1467
  call.resolve(["id": webViewId])
1274
1468
  }
@@ -1334,42 +1528,35 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1334
1528
  self.isHidden = hidden
1335
1529
 
1336
1530
  if hidden {
1337
- if let navController = navigationController, navController.presentingViewController != nil {
1338
- navController.view.isHidden = true
1339
- navController.view.isUserInteractionEnabled = false
1340
- if let containerView = navController.view.superview {
1341
- if self.presentationContainerView == nil || self.presentationContainerView !== containerView {
1342
- self.presentationContainerView = containerView
1343
- self.presentationContainerWasInteractive = containerView.isUserInteractionEnabled
1344
- self.presentationContainerPreviousAlpha = containerView.alpha
1345
- }
1346
- containerView.isUserInteractionEnabled = false
1347
- containerView.alpha = 0
1348
- }
1531
+ if let resolvedId {
1532
+ self.detachLayeredWebView(id: resolvedId)
1349
1533
  }
1350
-
1351
1534
  if !self.attachWebViewToWindow(webView) {
1352
1535
  call?.reject("Failed to get active window for hidden webview")
1353
1536
  return
1354
1537
  }
1538
+ self.dismissNavigationControllerIfPresented(navigationController)
1355
1539
  } else {
1356
1540
  if webView.superview !== webViewController.view {
1357
1541
  self.attachWebViewToController(webViewController, webView: webView)
1358
1542
  }
1359
1543
 
1544
+ if webViewController.isLayeredBehind, let resolvedId {
1545
+ if self.sendNavigationControllerToBack(id: resolvedId, transparentBackground: webViewController.transparentHostBackground) {
1546
+ if let navController = navigationController {
1547
+ self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navController)
1548
+ }
1549
+ call?.resolve()
1550
+ } else {
1551
+ call?.reject("Failed to send webview to back")
1552
+ }
1553
+ return
1554
+ }
1555
+
1360
1556
  if let navController = navigationController {
1361
1557
  if let resolvedId {
1362
1558
  self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navController)
1363
1559
  }
1364
- navController.view.isHidden = false
1365
- navController.view.isUserInteractionEnabled = true
1366
- if let containerView = self.presentationContainerView {
1367
- containerView.isUserInteractionEnabled = self.presentationContainerWasInteractive
1368
- containerView.alpha = self.presentationContainerPreviousAlpha
1369
- self.presentationContainerView = nil
1370
- self.presentationContainerWasInteractive = true
1371
- self.presentationContainerPreviousAlpha = 1
1372
- }
1373
1560
 
1374
1561
  if navController.presentingViewController == nil {
1375
1562
  let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
@@ -1404,6 +1591,198 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1404
1591
  self.setHiddenState(false, targetId: call.getString("id"), call: call)
1405
1592
  }
1406
1593
 
1594
+ @objc func sendToBack(_ call: CAPPluginCall) {
1595
+ DispatchQueue.main.async {
1596
+ let targetId = call.getString("id") ?? self.activeWebViewId
1597
+ guard let resolvedId = targetId,
1598
+ let webViewController = self.resolveWebViewController(for: resolvedId),
1599
+ let navigationController = self.resolveNavigationController(for: resolvedId) else {
1600
+ call.reject("WebView is not initialized")
1601
+ return
1602
+ }
1603
+
1604
+ let transparentBackground = call.getBool("transparentBackground", true)
1605
+ webViewController.isLayeredBehind = true
1606
+ webViewController.transparentHostBackground = transparentBackground
1607
+ guard self.sendNavigationControllerToBack(id: resolvedId, transparentBackground: transparentBackground) else {
1608
+ call.reject("Failed to send webview to back")
1609
+ return
1610
+ }
1611
+ self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navigationController)
1612
+ call.resolve()
1613
+ }
1614
+ }
1615
+
1616
+ @objc func bringToFront(_ call: CAPPluginCall) {
1617
+ DispatchQueue.main.async {
1618
+ let targetId = call.getString("id") ?? self.activeWebViewId
1619
+ guard let resolvedId = targetId,
1620
+ let webViewController = self.resolveWebViewController(for: resolvedId),
1621
+ let navigationController = self.resolveNavigationController(for: resolvedId) else {
1622
+ call.reject("WebView is not initialized")
1623
+ return
1624
+ }
1625
+
1626
+ webViewController.isLayeredBehind = false
1627
+ webViewController.transparentHostBackground = true
1628
+ self.detachLayeredWebView(id: resolvedId)
1629
+ if let webView = webViewController.capableWebView, webView.superview !== webViewController.view {
1630
+ self.attachWebViewToController(webViewController, webView: webView)
1631
+ }
1632
+ self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navigationController)
1633
+ if navigationController.presentingViewController == nil {
1634
+ let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
1635
+ presenter?.present(navigationController, animated: true, completion: {
1636
+ call.resolve()
1637
+ })
1638
+ return
1639
+ }
1640
+ call.resolve()
1641
+ }
1642
+ }
1643
+
1644
+ private func jsNumber(_ value: Float?, fallback: String = "0") -> String {
1645
+ guard let value, value.isFinite else {
1646
+ return fallback
1647
+ }
1648
+ return String(Double(value))
1649
+ }
1650
+
1651
+ private func dispatchInputScript(type: String, x: Float?, y: Float?, deltaX: Float?, deltaY: Float?) -> String? {
1652
+ if type == "scroll" {
1653
+ guard let x, let y, let deltaX, let deltaY,
1654
+ x.isFinite, y.isFinite, deltaX.isFinite, deltaY.isFinite else {
1655
+ return nil
1656
+ }
1657
+ let xLiteral = jsNumber(x)
1658
+ let yLiteral = jsNumber(y)
1659
+ let deltaXLiteral = jsNumber(deltaX)
1660
+ let deltaYLiteral = jsNumber(deltaY)
1661
+ return """
1662
+ (function() {
1663
+ const x = \(xLiteral);
1664
+ const y = \(yLiteral);
1665
+ const dx = \(deltaXLiteral);
1666
+ const dy = \(deltaYLiteral);
1667
+ let target = Number.isFinite(x) && Number.isFinite(y) ? document.elementFromPoint(x, y) : null;
1668
+ while (target && target !== document.body && target !== document.documentElement) {
1669
+ const style = window.getComputedStyle(target);
1670
+ const canScroll = /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY) &&
1671
+ (target.scrollHeight > target.clientHeight || target.scrollWidth > target.clientWidth);
1672
+ if (canScroll && typeof target.scrollBy === 'function') {
1673
+ target.scrollBy(dx, dy);
1674
+ return true;
1675
+ }
1676
+ target = target.parentElement;
1677
+ }
1678
+ window.scrollBy(dx, dy);
1679
+ return true;
1680
+ })();
1681
+ """
1682
+ }
1683
+
1684
+ let pointerEvents: [String]
1685
+ let mouseEvents: [String]
1686
+ let touchEvents: [String]
1687
+ switch type {
1688
+ case "click":
1689
+ pointerEvents = ["pointerdown", "pointerup"]
1690
+ mouseEvents = ["mousedown", "mouseup", "click"]
1691
+ touchEvents = ["touchstart", "touchend"]
1692
+ case "touchstart":
1693
+ pointerEvents = ["pointerdown"]
1694
+ mouseEvents = ["mousedown"]
1695
+ touchEvents = ["touchstart"]
1696
+ case "touchmove":
1697
+ pointerEvents = ["pointermove"]
1698
+ mouseEvents = ["mousemove"]
1699
+ touchEvents = ["touchmove"]
1700
+ case "touchend":
1701
+ pointerEvents = ["pointerup"]
1702
+ mouseEvents = ["mouseup"]
1703
+ touchEvents = ["touchend"]
1704
+ case "touchcancel":
1705
+ pointerEvents = ["pointercancel"]
1706
+ mouseEvents = []
1707
+ touchEvents = ["touchcancel"]
1708
+ default:
1709
+ return nil
1710
+ }
1711
+
1712
+ guard let x, let y, x.isFinite, y.isFinite else {
1713
+ return nil
1714
+ }
1715
+
1716
+ let pointerEventsData = try? JSONSerialization.data(withJSONObject: pointerEvents)
1717
+ let mouseEventsData = try? JSONSerialization.data(withJSONObject: mouseEvents)
1718
+ let touchEventsData = try? JSONSerialization.data(withJSONObject: touchEvents)
1719
+ let pointerEventsJSON = pointerEventsData.flatMap { String(data: $0, encoding: .utf8) } ?? "[]"
1720
+ let mouseEventsJSON = mouseEventsData.flatMap { String(data: $0, encoding: .utf8) } ?? "[]"
1721
+ let touchEventsJSON = touchEventsData.flatMap { String(data: $0, encoding: .utf8) } ?? "[]"
1722
+
1723
+ return """
1724
+ (function() {
1725
+ const x = \(jsNumber(x));
1726
+ const y = \(jsNumber(y));
1727
+ const target = document.elementFromPoint(x, y);
1728
+ if (!target) return false;
1729
+ const base = { bubbles: true, cancelable: true, composed: true, clientX: x, clientY: y, screenX: x, screenY: y };
1730
+ for (const name of \(pointerEventsJSON)) {
1731
+ if (typeof PointerEvent === 'function') {
1732
+ target.dispatchEvent(new PointerEvent(name, Object.assign({}, base, { pointerId: 1, pointerType: 'touch', isPrimary: true })));
1733
+ }
1734
+ }
1735
+ for (const name of \(mouseEventsJSON)) {
1736
+ target.dispatchEvent(new MouseEvent(name, base));
1737
+ }
1738
+ if (typeof TouchEvent === 'function' && typeof Touch === 'function') {
1739
+ for (const name of \(touchEventsJSON)) {
1740
+ try {
1741
+ const touch = new Touch({ identifier: 1, target: target, clientX: x, clientY: y, screenX: x, screenY: y });
1742
+ const activeTouches = name === 'touchend' || name === 'touchcancel' ? [] : [touch];
1743
+ target.dispatchEvent(new TouchEvent(name, {
1744
+ bubbles: true,
1745
+ cancelable: true,
1746
+ composed: true,
1747
+ touches: activeTouches,
1748
+ targetTouches: activeTouches,
1749
+ changedTouches: [touch]
1750
+ }));
1751
+ } catch (_) {}
1752
+ }
1753
+ }
1754
+ return true;
1755
+ })();
1756
+ """
1757
+ }
1758
+
1759
+ @objc func dispatchInputEvent(_ call: CAPPluginCall) {
1760
+ guard let type = call.getString("type") else {
1761
+ call.reject("Input event type is required")
1762
+ return
1763
+ }
1764
+ guard let script = dispatchInputScript(
1765
+ type: type,
1766
+ x: call.getFloat("x"),
1767
+ y: call.getFloat("y"),
1768
+ deltaX: call.getFloat("deltaX"),
1769
+ deltaY: call.getFloat("deltaY")
1770
+ ) else {
1771
+ call.reject("Unsupported input event type or invalid input payload: \(type)")
1772
+ return
1773
+ }
1774
+
1775
+ DispatchQueue.main.async {
1776
+ let targetId = call.getString("id") ?? self.activeWebViewId
1777
+ guard let webViewController = self.resolveWebViewController(for: targetId) else {
1778
+ call.reject("WebView is not initialized")
1779
+ return
1780
+ }
1781
+ webViewController.executeScript(script: script)
1782
+ call.resolve()
1783
+ }
1784
+ }
1785
+
1407
1786
  @objc func executeScript(_ call: CAPPluginCall) {
1408
1787
  guard let script = call.getString("code") else {
1409
1788
  call.reject("Cannot get script to execute")
@@ -1169,7 +1169,6 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
1169
1169
  }
1170
1170
  }
1171
1171
 
1172
-
1173
1172
  private func makeInitialRequestContext(from urlSchemeTask: WKURLSchemeTask, requestURL: URL) -> NativeRequestContext? {
1174
1173
  let requestURLString = requestURL.absoluteString
1175
1174