Files
custom_wda/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.m
2026-02-03 16:52:44 +08:00

166 lines
5.2 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "FBBaseActionsSynthesizer.h"
#import "FBErrorBuilder.h"
#import "FBLogger.h"
#import "FBMacros.h"
#import "FBMathUtils.h"
#import "FBXCElementSnapshotWrapper+Helpers.h"
#import "XCUIApplication+FBHelpers.h"
#import "XCUIElement.h"
#import "XCUIElement+FBIsVisible.h"
#import "XCUIElement+FBCaching.h"
#import "XCPointerEventPath.h"
#import "XCSynthesizedEventRecord.h"
#import "XCUIElement+FBUtilities.h"
#if !TARGET_OS_TV
@implementation FBBaseActionItem
+ (NSString *)actionName
{
@throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build];
return nil;
}
- (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
allItems:(NSArray *)allItems
currentItemIndex:(NSUInteger)currentItemIndex
error:(NSError **)error
{
@throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build];
return nil;
}
@end
@implementation FBBaseGestureItem
- (nullable XCUICoordinate *)hitpointWithElement:(nullable XCUIElement *)element
positionOffset:(nullable NSValue *)positionOffset
error:(NSError **)error
{
if (nil == element) {
CGVector offset = CGVectorMake(positionOffset.CGPointValue.x, positionOffset.CGPointValue.y);
// Only absolute offset is defined
return [[self.application coordinateWithNormalizedOffset:CGVectorMake(0, 0)] coordinateWithOffset:offset];
}
// The offset relative to the element is defined
if (nil == positionOffset) {
if (element.hittable) {
// short circuit element hitpoint
return element.hitPointCoordinate;
}
[FBLogger logFmt:@"Will use the frame of '%@' for hit point calculation instead", element.debugDescription];
}
if (CGRectIsEmpty(element.frame)) {
[FBLogger log:self.application.fb_descriptionRepresentation];
NSString *description = [NSString stringWithFormat:@"The element '%@' is not visible on the screen and thus is not interactable",
element.description];
if (error) {
*error = [[FBErrorBuilder.builder withDescription:description] build];
}
return nil;
}
if (nil == positionOffset) {
return [element coordinateWithNormalizedOffset:CGVectorMake(0.5, 0.5)];
}
CGVector offset = CGVectorMake(positionOffset.CGPointValue.x, positionOffset.CGPointValue.y);
// TODO: Shall we throw an exception if hitPoint is out of the element frame?
return [[element coordinateWithNormalizedOffset:CGVectorMake(0, 0)] coordinateWithOffset:offset];
}
@end
@implementation FBBaseActionItemsChain
- (instancetype)init
{
self = [super init];
if (self) {
_items = [NSMutableArray array];
_durationOffset = 0.0;
}
return self;
}
- (void)addItem:(FBBaseActionItem *)item __attribute__((noreturn))
{
@throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build];
}
- (nullable NSArray<XCPointerEventPath *> *)asEventPathsWithError:(NSError **)error
{
if (0 == self.items.count) {
if (error) {
*error = [[FBErrorBuilder.builder withDescription:@"Action items list cannot be empty"] build];
}
return nil;
}
NSMutableArray<XCPointerEventPath *> *result = [NSMutableArray array];
XCPointerEventPath *previousEventPath = nil;
XCPointerEventPath *currentEventPath = nil;
NSUInteger index = 0;
for (FBBaseActionItem *item in self.items.copy) {
NSArray<XCPointerEventPath *> *currentEventPaths = [item addToEventPath:currentEventPath
allItems:self.items.copy
currentItemIndex:index++
error:error];
if (currentEventPaths == nil) {
return nil;
}
currentEventPath = currentEventPaths.lastObject;
if (nil == currentEventPath) {
currentEventPath = previousEventPath;
} else if (currentEventPath != previousEventPath) {
[result addObjectsFromArray:currentEventPaths];
previousEventPath = currentEventPath;
}
}
return result.copy;
}
@end
@implementation FBBaseActionsSynthesizer
- (instancetype)initWithActions:(NSArray *)actions
forApplication:(XCUIApplication *)application
elementCache:(nullable FBElementCache *)elementCache
error:(NSError **)error
{
self = [super init];
if (self) {
if ((nil == actions || 0 == actions.count) && error) {
*error = [[FBErrorBuilder.builder withDescription:@"Actions list cannot be empty"] build];
return nil;
}
_actions = actions;
_application = application;
_elementCache = elementCache;
}
return self;
}
- (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error
{
@throw [[FBErrorBuilder.builder withDescription:@"Override synthesizeWithError method in subclasses"] build];
return nil;
}
@end
#endif