@capgo/capacitor-inappbrowser 8.6.35 → 8.7.0-beta.pr598.346.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.
@@ -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.35"
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),
@@ -140,6 +144,14 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
140
144
  private var activeWebViewId: String?
141
145
  private var hiddenWebViewContainers: [ObjectIdentifier: UIView] = [:]
142
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
143
155
  private var closeModalDescription: String?
144
156
  private var closeModalOk: String?
145
157
  private var closeModalCancel: String?
@@ -186,6 +198,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
186
198
  }
187
199
 
188
200
  private func unregisterWebView(id: String) {
201
+ detachLayeredWebView(id: id)
189
202
  if let webView = webViewControllers[id]?.capableWebView {
190
203
  cleanupHiddenWebViewContainer(for: webView)
191
204
  }
@@ -346,6 +359,26 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
346
359
  return stores
347
360
  }
348
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
+
349
382
  private func parseProxyRules(_ rawRules: [Any]) throws -> [NativeProxyRule] {
350
383
  try rawRules.enumerated().map { index, item in
351
384
  guard let dictionary = item as? [String: Any] else {
@@ -359,19 +392,155 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
359
392
  }
360
393
  }
361
394
 
362
- func presentView(webViewId: String? = nil, isAnimated: Bool = true) {
395
+ @discardableResult
396
+ func presentView(webViewId: String? = nil, isAnimated: Bool = true) -> Bool {
363
397
  let resolvedId = webViewId ?? activeWebViewId
364
398
  let navigationController = resolvedId.flatMap { navigationControllers[$0] } ?? self.navigationWebViewController
365
399
  guard let navigationController else {
366
400
  self.currentPluginCall?.reject("Navigation controller is not initialized")
367
- return
401
+ return false
368
402
  }
369
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
412
+ }
413
+
414
+ if let resolvedId {
415
+ detachLayeredWebView(id: resolvedId)
416
+ }
370
417
  dismissActiveKeyboard()
371
418
  let presenter = self.bridge?.viewController?.presentedViewController ?? self.bridge?.viewController
372
419
  presenter?.present(navigationController, animated: isAnimated, completion: {
373
420
  self.currentPluginCall?.resolve()
374
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()
375
544
  }
376
545
 
377
546
  private func dismissActiveKeyboard() {
@@ -572,6 +741,24 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
572
741
  }
573
742
  }
574
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
+
575
762
  @objc func clearCookies(_ call: CAPPluginCall) {
576
763
  guard let url = call.getString("url"),
577
764
  let host = URL(string: url)?.host else {
@@ -771,12 +958,15 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
771
958
  let enabledSafeBottomMargin = call.getBool("enabledSafeBottomMargin", false)
772
959
  let enabledSafeTopMargin = call.getBool("enabledSafeTopMargin", true)
773
960
  let hidden = call.getBool("hidden", false)
961
+ let toBack = call.getBool("toBack", false)
962
+ let transparentBackground = call.getBool("transparentBackground", true)
774
963
  self.isHidden = hidden
775
964
  let hiddenPopupWindow = call.getBool("hiddenPopupWindow", false)
776
965
  let allowWebViewJsVisibilityControl = self.getConfig().getBoolean("allowWebViewJsVisibilityControl", false)
777
966
  let allowScreenshotsFromWebPage = call.getBool("allowScreenshotsFromWebPage", false)
778
967
  let captureConsoleLogs = call.getBool("captureConsoleLogs", false)
779
968
  let handleDownloads = call.getBool("handleDownloads", false)
969
+ let persistWebViewData = call.getBool("persistWebViewData", true)
780
970
  let invisibilityModeRaw = call.getString("invisibilityMode", "AWARE")
781
971
  self.invisibilityMode = InvisibilityMode(rawValue: invisibilityModeRaw.uppercased()) ?? .aware
782
972
 
@@ -918,38 +1108,36 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
918
1108
  self.proxyBridges[webViewId] = proxyBridge
919
1109
  }
920
1110
 
921
- self.webViewController = WKWebViewController.init(
922
- url: url,
923
- headers: headers,
924
- isInspectable: isInspectable,
925
- credentials: credentials,
926
- preventDeeplink: preventDeeplink,
927
- blankNavigationTab: toolbarType == "blank",
928
- enabledSafeBottomMargin: enabledSafeBottomMargin,
929
- enabledSafeTopMargin: enabledSafeTopMargin,
930
- 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(
931
1132
  authorizedAppLinks: authorizedAppLinks,
932
- allowWebViewJsVisibilityControl: allowWebViewJsVisibilityControl,
933
- allowScreenshotsFromWebPage: allowScreenshotsFromWebPage,
934
- captureConsoleLogs: captureConsoleLogs,
935
- proxyRequests: legacyProxyRequests.isEnabled,
936
- proxySchemeHandler: proxyHandler,
937
- proxyBridge: proxyBridge,
938
- proxyBridgeAccessToken: proxyBridgeAccessToken,
939
- legacyProxyRequestURLRegexPattern: legacyProxyRequests.urlRegex?.pattern,
940
- documentStartUserScripts: self.documentStartUserScripts(
941
- authorizedAppLinks: authorizedAppLinks,
942
- openBlankTargetInWebView: openBlankTargetInWebView
943
- ),
944
1133
  openBlankTargetInWebView: openBlankTargetInWebView
945
1134
  )
946
-
947
- guard let webViewController = self.webViewController else {
948
- call.reject("Failed to initialize WebViewController")
949
- return
950
- }
1135
+ webViewController.initWebview(isInspectable: isInspectable)
1136
+ self.webViewController = webViewController
951
1137
 
952
1138
  webViewController.instanceId = webViewId
1139
+ webViewController.isLayeredBehind = toBack
1140
+ webViewController.transparentHostBackground = transparentBackground
953
1141
 
954
1142
  // Set HTTP method and body if provided
955
1143
  if let method = httpMethod {
@@ -1182,7 +1370,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1182
1370
  }
1183
1371
 
1184
1372
  // Configure modal presentation for touch passthrough if custom dimensions are set
1185
- if width != nil || height != nil {
1373
+ if (width != nil || height != nil) && !toBack {
1186
1374
  self.navigationWebViewController?.modalPresentationStyle = .overFullScreen
1187
1375
 
1188
1376
  // Create a pass-through container
@@ -1272,7 +1460,9 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1272
1460
  return
1273
1461
  }
1274
1462
  } else if !self.isPresentAfterPageLoad {
1275
- self.presentView(webViewId: webViewId, isAnimated: isAnimated)
1463
+ guard self.presentView(webViewId: webViewId, isAnimated: isAnimated) else {
1464
+ return
1465
+ }
1276
1466
  }
1277
1467
  call.resolve(["id": webViewId])
1278
1468
  }
@@ -1338,6 +1528,9 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1338
1528
  self.isHidden = hidden
1339
1529
 
1340
1530
  if hidden {
1531
+ if let resolvedId {
1532
+ self.detachLayeredWebView(id: resolvedId)
1533
+ }
1341
1534
  if !self.attachWebViewToWindow(webView) {
1342
1535
  call?.reject("Failed to get active window for hidden webview")
1343
1536
  return
@@ -1348,6 +1541,18 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1348
1541
  self.attachWebViewToController(webViewController, webView: webView)
1349
1542
  }
1350
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
+
1351
1556
  if let navController = navigationController {
1352
1557
  if let resolvedId {
1353
1558
  self.setActiveWebView(id: resolvedId, webView: webViewController, navigationController: navController)
@@ -1386,6 +1591,198 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
1386
1591
  self.setHiddenState(false, targetId: call.getString("id"), call: call)
1387
1592
  }
1388
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
+
1389
1786
  @objc func executeScript(_ call: CAPPluginCall) {
1390
1787
  guard let script = call.getString("code") else {
1391
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