diff --git a/detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.h b/detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.h index 8eb108951b..3372039de8 100644 --- a/detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.h +++ b/detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.h @@ -9,6 +9,8 @@ NS_ASSUME_NONNULL_BEGIN @interface WKWebViewConfiguration (Detox) +- (BOOL)shouldDisableWebKitSecurity; + @end NS_ASSUME_NONNULL_END diff --git a/detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.m b/detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.m index f1bc7f39a3..dabfbe259a 100644 --- a/detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.m +++ b/detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.m @@ -7,7 +7,19 @@ @import ObjectiveC; -void WKPreferencesSetWebSecurityEnabled(id, bool); + +//void WKPreferencesSetWebSecurityEnabled(id, bool); + + +// Private WebKit API declarations +typedef struct OpaqueWKPreferences* WKPreferencesRef; +extern void WKPreferencesSetWebSecurityEnabled(WKPreferencesRef, bool); + + +@interface WKWebView (DetoxSecurity) +- (instancetype)dtx_initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration; +@end + @interface DTXFakeWKPreferencesRef: NSObject @property (nonatomic) void* _apiObject; @@ -16,29 +28,45 @@ @interface DTXFakeWKPreferencesRef: NSObject @implementation DTXFakeWKPreferencesRef @end + /// Set web-security policy for WebKit (e.g. CORS restriction). /// /// @note Since we can't access the `WKPreferencesSetWebSecurityEnabled` directly with -/// a `WKPreferences*`, we wrap it in a `WKPreferencesRef`, which can be passed to this function. +/// a `WKPreferences*`, we wrap it in a `WKPreferencesRef` in the getWKPrefsRef function, which can be passed to this function. /// This private API is not officially supported on iOS, and generally used for debugging / testing /// purposes on MacOS only. So there's no guarantee that it will work in the future. void DTXPreferencesSetWebSecurityEnabled(WKPreferences* prefs, bool enabled) { - DTXFakeWKPreferencesRef* fakeRef = [DTXFakeWKPreferencesRef new]; - - Ivar ivar = class_getInstanceVariable([WKPreferences class], "_preferences"); - void* realPreferences = (void*)(((uintptr_t)prefs) + ivar_getOffset(ivar)); - fakeRef._apiObject = realPreferences; - - WKPreferencesSetWebSecurityEnabled(fakeRef, enabled); + if (!prefs) return; + + if (@available(iOS 18.0, *)) { + void *prefsPtr = (__bridge void *)(prefs); + WKPreferencesSetWebSecurityEnabled((WKPreferencesRef)prefsPtr, enabled); + + } else { + DTXFakeWKPreferencesRef* fakeRef = [DTXFakeWKPreferencesRef new]; + + Ivar ivar = class_getInstanceVariable([WKPreferences class], "_preferences"); + void* realPreferences = (void*)(((uintptr_t)prefs) + ivar_getOffset(ivar)); + fakeRef._apiObject = realPreferences; + WKPreferencesSetWebSecurityEnabled((__bridge WKPreferencesRef)fakeRef, enabled); + } } @implementation WKWebViewConfiguration (Detox) + (void)load { + [self swizzleWKWebViewConfigurationSetPreferences]; + + if (@available(iOS 18.0, *)) { + [self swizzleWKWebViewInitWithFrameConfiguration]; + } +} + ++ (void)swizzleWKWebViewConfigurationSetPreferences { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; - + SEL originalSelector = @selector(setPreferences:); SEL swizzledSelector = @selector(dtx_setPreferences:); @@ -46,31 +74,75 @@ + (void)load { Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, - originalSelector, - method_getImplementation(swizzledMethod), - method_getTypeEncoding(swizzledMethod)); + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, - swizzledSelector, - method_getImplementation(originalMethod), - method_getTypeEncoding(originalMethod)); + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } -- (void)dtx_setPreferences:(WKPreferences *)preferences { - if ([self shouldDisableWebKitSecurity]) { - DTXPreferencesSetWebSecurityEnabled(preferences, NO); - } ++ (void)swizzleWKWebViewInitWithFrameConfiguration { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class wkWebViewClass = [WKWebView class]; + + SEL originalSelector = @selector(initWithFrame:configuration:); + SEL swizzledSelector = @selector(dtx_initWithFrame:configuration:); + + Method originalMethod = class_getInstanceMethod(wkWebViewClass, originalSelector); + Method swizzledMethod = class_getInstanceMethod(wkWebViewClass, swizzledSelector); - [self dtx_setPreferences:preferences]; + BOOL didAddMethod = class_addMethod(wkWebViewClass, + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + + if (didAddMethod) { + class_replaceMethod(wkWebViewClass, + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } + }); +} + +- (void)dtx_setPreferences:(WKPreferences *)preferences { + if ([self shouldDisableWebKitSecurity]) { + DTXPreferencesSetWebSecurityEnabled(preferences, NO); + } + [self dtx_setPreferences:preferences]; } - (BOOL)shouldDisableWebKitSecurity { - return [NSUserDefaults.standardUserDefaults boolForKey:@"detoxDisableWebKitSecurity"]; + return [NSUserDefaults.standardUserDefaults boolForKey:@"detoxDisableWebKitSecurity"]; +} + +@end + + +@implementation WKWebView (DetoxSecurity) + +- (instancetype)dtx_initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration { + BOOL shouldDisable = [configuration shouldDisableWebKitSecurity]; + if (shouldDisable) { + if (!configuration.preferences) { + configuration.preferences = [[WKPreferences alloc] init]; + } + + DTXPreferencesSetWebSecurityEnabled(configuration.preferences, !shouldDisable); + } + + return [self dtx_initWithFrame:frame configuration:configuration]; } @end diff --git a/detox/test/ios/UI/OverlayMessageView.swift b/detox/test/ios/UI/OverlayMessageView.swift index 18171beac5..0b06d07b6d 100644 --- a/detox/test/ios/UI/OverlayMessageView.swift +++ b/detox/test/ios/UI/OverlayMessageView.swift @@ -8,7 +8,14 @@ import UIKit @objc class OverlayMessageView: UIView { private var timer: Timer? private(set) var message: String - private let displayDuration: TimeInterval = 2.0 + + private func getDisplayDuration() -> TimeInterval { + if #available(iOS 18, *) { + return 3.0 + } else { + return 2.0 + } + } private let messageLabel: UILabel = { let label = UILabel() @@ -72,7 +79,7 @@ import UIKit } private func startTimer() { - timer = Timer.scheduledTimer(withTimeInterval: displayDuration, repeats: false) { [weak self] _ in + timer = Timer.scheduledTimer(withTimeInterval: getDisplayDuration(), repeats: false) { [weak self] _ in self?.removeFromSuperview() } }