Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ NS_ASSUME_NONNULL_BEGIN

@interface WKWebViewConfiguration (Detox)

- (BOOL)shouldDisableWebKitSecurity;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻


@end

NS_ASSUME_NONNULL_END
116 changes: 94 additions & 22 deletions detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,61 +28,121 @@ @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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this soft-failing approach dangerous here?


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:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
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
11 changes: 9 additions & 2 deletions detox/test/ios/UI/OverlayMessageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
}
}
Expand Down
Loading