-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/master' into backdoor
- Loading branch information
Showing
80 changed files
with
3,258 additions
and
459 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
detox/ios/Detox/Invocation/WKWebView+evaluateJSAfterLoading.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// WKWebView+evaluateJSAfterLoading.swift (Detox) | ||
// Created by Asaf Korem (Wix.com) on 2024. | ||
// | ||
|
||
import WebKit | ||
|
||
fileprivate let log = DetoxLog(category: "WebView") | ||
|
||
/// Extends WKWebView with the ability to evaluate JavaScript after the web view has | ||
/// finished loading. | ||
extension WKWebView { | ||
func evaluateJSAfterLoading( | ||
_ javaScriptString: String, | ||
completionHandler: ((Any?, Error?) -> Void)? = nil | ||
) { | ||
let cleanJavaScriptString = replaceConsecutiveSpacesAndTabs(in: javaScriptString) | ||
log.debug("Evaluating JavaScript after loading: `\(cleanJavaScriptString)`") | ||
|
||
var observation: NSKeyValueObservation? | ||
observation = self.observe( | ||
\.isLoading, options: [.new, .old, .initial] | ||
) { (webView, change) in | ||
guard change.newValue == false else { return } | ||
|
||
observation?.invalidate() | ||
|
||
log.debug("Evaluating JavaScript on web-view: `\(cleanJavaScriptString)`") | ||
webView.evaluateJavaScript(cleanJavaScriptString, completionHandler: completionHandler) | ||
} | ||
} | ||
|
||
private func replaceConsecutiveSpacesAndTabs(in input: String) -> String { | ||
let pattern = "[ \\t\\r\\n]+" | ||
let regex = try! NSRegularExpression(pattern: pattern, options: []) | ||
let range = NSRange(location: 0, length: input.utf16.count) | ||
let modifiedString = regex.stringByReplacingMatches(in: input, options: [], range: range, withTemplate: " ") | ||
return modifiedString | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// | ||
// WKWebView+findView.swift (Detox) | ||
// Created by Asaf Korem (Wix.com) on 2024. | ||
// | ||
|
||
import WebKit | ||
|
||
/// Extends WKWebView with the ability to find a web view element. | ||
extension WKWebView { | ||
/// Finds a web view element by the given `predicate` at the given `index`. | ||
class func findView( | ||
by predicate: Predicate?, | ||
atIndex index: Int? | ||
) throws -> WKWebView { | ||
let webView: WKWebView? | ||
|
||
if let predicate = predicate { | ||
guard let ancestor = Element(predicate: predicate, index: index).view as? UIView else { | ||
throw dtx_errorForFatalError( | ||
"Failed to find web view with predicate: \(predicate.description)") | ||
} | ||
|
||
webView = try findWebViewDescendant(in: ancestor) | ||
} else { | ||
webView = try findWebViewDescendant() | ||
} | ||
|
||
guard let webView = webView else { | ||
throw dtx_errorForFatalError( | ||
"Failed to find web view with predicate: `\(predicate?.description ?? "")` " + | ||
"at index: `\(index ?? 0)`") | ||
} | ||
|
||
return webView | ||
} | ||
|
||
fileprivate class func findWebViewDescendant( | ||
in ancestor: UIView? = nil | ||
) throws -> WKWebView? { | ||
let predicate = NSPredicate.init { (view, _) -> Bool in | ||
return view is WKWebView | ||
} | ||
|
||
var webViews: [WKWebView] | ||
if let ancestor = ancestor { | ||
webViews = UIView.dtx_findViews(inHierarchy: ancestor, passing: predicate).compactMap { | ||
$0 as? WKWebView | ||
} | ||
} else { | ||
webViews = UIView.dtx_findViewsInAllWindows(passing: predicate).compactMap { | ||
$0 as? WKWebView | ||
} | ||
} | ||
|
||
if webViews.count == 0 { | ||
return nil | ||
} else if webViews.count > 1 { | ||
throw dtx_errorForFatalError( | ||
"Found more than one matching web view in the hierarchy. " + | ||
"Please specify a predicate to find the correct web view.") | ||
} else { | ||
return webViews.first | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// | ||
// WKWebViewConfiguration+Detox.h (Detox) | ||
// Created by Asaf Korem (Wix.com) on 2024. | ||
// | ||
|
||
@import WebKit; | ||
|
||
NS_ASSUME_NONNULL_BEGIN | ||
|
||
@interface WKWebViewConfiguration (Detox) | ||
|
||
@end | ||
|
||
NS_ASSUME_NONNULL_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// | ||
// WKWebViewConfiguration+Detox.m (Detox) | ||
// Created by Asaf Korem (Wix.com) on 2024. | ||
// | ||
|
||
#import "WKWebViewConfiguration+Detox.h" | ||
|
||
@import ObjectiveC; | ||
|
||
void WKPreferencesSetWebSecurityEnabled(id, bool); | ||
|
||
@interface DTXFakeWKPreferencesRef: NSObject | ||
@property (nonatomic) void* _apiObject; | ||
@end | ||
|
||
@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. | ||
/// 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); | ||
} | ||
|
||
@implementation WKWebViewConfiguration (Detox) | ||
|
||
+ (void)load { | ||
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)); | ||
|
||
if (didAddMethod) { | ||
class_replaceMethod(class, | ||
swizzledSelector, | ||
method_getImplementation(originalMethod), | ||
method_getTypeEncoding(originalMethod)); | ||
} else { | ||
method_exchangeImplementations(originalMethod, swizzledMethod); | ||
} | ||
}); | ||
} | ||
|
||
- (void)dtx_setPreferences:(WKPreferences *)preferences { | ||
DTXPreferencesSetWebSecurityEnabled(preferences, NO); | ||
|
||
[self dtx_setPreferences:preferences]; | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// | ||
// WebAction.swift (Detox) | ||
// Created by Asaf Korem (Wix.com) on 2024. | ||
// | ||
|
||
import WebKit | ||
|
||
/// Represents a web action to be performed on a web view. | ||
class WebAction: WebInteraction { | ||
var webAction: WebActionType | ||
var params: [Any]? | ||
|
||
override init(json: [String: Any]) throws { | ||
self.webAction = WebActionType(rawValue: json["webAction"] as! String)! | ||
self.params = json["params"] as? [Any] | ||
try super.init(json: json) | ||
} | ||
|
||
override var description: String { | ||
return "WebAction: \(webAction.rawValue)" | ||
} | ||
|
||
func perform(completionHandler: @escaping ([String: Any]?, Error?) -> Void) { | ||
var jsString: String | ||
var webView: WKWebView | ||
|
||
do { | ||
jsString = try WebCodeBuilder() | ||
.with(predicate: webPredicate, atIndex: webAtIndex) | ||
.with(action: webAction, params: params) | ||
.build() | ||
|
||
webView = try WKWebView.findView(by: predicate, atIndex: atIndex) | ||
} catch { | ||
completionHandler(nil, error) | ||
return | ||
} | ||
|
||
webView.evaluateJSAfterLoading(jsString) { (result, error) in | ||
if let error = error { | ||
completionHandler( | ||
["result": false, "error": error.localizedDescription], | ||
dtx_errorForFatalError( | ||
"Failed to evaluate JavaScript on web view: \(webView.debugDescription). " + | ||
"Error: \(error.localizedDescription)") | ||
) | ||
} else if let jsError = (result as? [String: Any])?["error"] as? String { | ||
completionHandler( | ||
["result": false, "error": jsError], | ||
dtx_errorForFatalError( | ||
"Failed to evaluate JavaScript on web view: \(webView.debugDescription). " + | ||
"JS exception: \(jsError)") | ||
) | ||
} else if let result = (result as? [String: Any])?["result"] as? String { | ||
completionHandler(["result": result], nil) | ||
} else { | ||
completionHandler(nil, nil) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.