fix(ios-qa): make SwiftUI device taps observable and reliable

This commit is contained in:
t
2026-07-14 16:35:30 -07:00
parent 8e25d1583d
commit 145fe2d4d1
14 changed files with 2278 additions and 251 deletions
+44 -2
View File
@@ -126,6 +126,36 @@ static BOOL DBT_LoadIOKit(void) {
return _IOKitLoaded;
}
// KIF enables accessibility automation before it asks UIKit/SwiftUI for the
// accessibility tree. Without this switch SwiftUI exposes only its hosting
// shell: /elements has no controls and a synthesized tap can return YES while
// Button.action never fires. Keep this DEBUG-only with the rest of this file.
typedef int (*DBTAXSAutomationEnabledFn)(void);
typedef void (*DBTAXSSetAutomationEnabledFn)(int);
static DBTAXSAutomationEnabledFn _DBTAXSAutomationEnabled;
static DBTAXSSetAutomationEnabledFn _DBTAXSSetAutomationEnabled;
static int _DBTInitialAutomationState = -1;
static void DBT_RestoreAccessibilityAutomation(void) {
if (_DBTAXSSetAutomationEnabled && _DBTInitialAutomationState >= 0) {
_DBTAXSSetAutomationEnabled(_DBTInitialAutomationState);
}
}
static void DBT_EnableAccessibilityAutomation(void) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void *handle = dlopen("/usr/lib/libAccessibility.dylib", RTLD_NOW | RTLD_LOCAL);
if (!handle) return;
_DBTAXSAutomationEnabled = (DBTAXSAutomationEnabledFn)dlsym(handle, "_AXSAutomationEnabled");
_DBTAXSSetAutomationEnabled = (DBTAXSSetAutomationEnabledFn)dlsym(handle, "_AXSSetAutomationEnabled");
if (!_DBTAXSAutomationEnabled || !_DBTAXSSetAutomationEnabled) return;
_DBTInitialAutomationState = _DBTAXSAutomationEnabled();
if (!_DBTInitialAutomationState) _DBTAXSSetAutomationEnabled(1);
atexit(DBT_RestoreAccessibilityAutomation);
});
}
static IOHIDEventRef DBT_IOHIDEventWithTouch(UITouch *touch) CF_RETURNS_RETAINED;
static IOHIDEventRef DBT_IOHIDEventWithTouch(UITouch *touch) {
if (!DBT_LoadIOKit()) return NULL;
@@ -172,6 +202,7 @@ static IOHIDEventRef DBT_IOHIDEventWithTouch(UITouch *touch) {
- (void)setGestureView:(UIView *)view;
- (void)_setLocationInWindow:(CGPoint)location resetPrevious:(BOOL)resetPrevious;
- (void)_setIsFirstTouchForView:(BOOL)firstTouchForView;
- (void)_setIsTapToClick:(BOOL)tapToClick;
- (void)_setHidEvent:(IOHIDEventRef)event;
@end
@@ -229,6 +260,10 @@ static id DBT_HitTestView(UIWindow *window, CGPoint point) {
@implementation DebugBridgeTouch
+ (void)prepareForAutomation {
DBT_EnableAccessibilityAutomation();
}
+ (BOOL)sendTapAtPoint:(CGPoint)point inWindow:(UIWindow *)window {
if (!window) return NO;
@@ -247,10 +282,17 @@ static id DBT_HitTestView(UIWindow *window, CGPoint point) {
[touch setPhase:UITouchPhaseBegan];
if ([touch respondsToSelector:@selector(_setIsFirstTouchForView:)]) {
[touch _setIsFirstTouchForView:YES];
} else if ([touch respondsToSelector:@selector(_setIsTapToClick:)]) {
[touch _setIsTapToClick:YES];
Ivar flagsIvar = class_getInstanceVariable([UITouch class], "_touchFlags");
if (flagsIvar) {
ptrdiff_t offset = ivar_getOffset(flagsIvar);
char *flags = (__bridge void *)touch + offset;
*flags |= (char)0x01;
}
}
[touch setTimestamp:[[NSProcessInfo processInfo] systemUptime]];
if ([touch respondsToSelector:@selector(setGestureView:)] &&
[hit isKindOfClass:[UIView class]]) {
if ([touch respondsToSelector:@selector(setGestureView:)]) {
[touch setGestureView:(UIView *)hit];
}