初始化提交
This commit is contained in:
153
WebDriverAgentLib/Routing/FBElementUtils.m
Normal file
153
WebDriverAgentLib/Routing/FBElementUtils.m
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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 <objc/runtime.h>
|
||||
|
||||
#import "FBXCAccessibilityElement.h"
|
||||
#import "FBElementUtils.h"
|
||||
#import "FBElementTypeTransformer.h"
|
||||
|
||||
NSString *const FBUnknownAttributeException = @"FBUnknownAttributeException";
|
||||
static NSString *const WD_PREFIX = @"wd";
|
||||
static NSString *const OBJC_PROP_GETTER_PREFIX = @"G";
|
||||
static NSString *const OBJC_PROP_ATTRIBS_SEPARATOR = @",";
|
||||
|
||||
@implementation FBElementUtils
|
||||
|
||||
+ (NSSet<NSString *> *)selectorNamesWithProtocol:(Protocol *)protocol
|
||||
{
|
||||
unsigned int count;
|
||||
struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, YES, YES, &count);
|
||||
NSMutableSet<NSString *> *result = [NSMutableSet set];
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
SEL sel = methods[i].name;
|
||||
if (nil != sel) {
|
||||
[result addObject:NSStringFromSelector(sel)];
|
||||
}
|
||||
}
|
||||
free(methods);
|
||||
return result.copy;
|
||||
}
|
||||
|
||||
+ (NSString *)wdAttributeNameForAttributeName:(NSString *)name
|
||||
{
|
||||
NSAssert(name.length > 0, @"Attribute name cannot be empty", nil);
|
||||
NSDictionary *attributeNamesMapping = [self.class wdAttributeNamesMapping];
|
||||
NSString *result = attributeNamesMapping[name];
|
||||
if (nil == result) {
|
||||
NSString *description = [NSString stringWithFormat:@"The attribute '%@' is unknown. Valid attribute names are: %@", name, [attributeNamesMapping.allKeys sortedArrayUsingSelector:@selector(compare:)]];
|
||||
@throw [NSException exceptionWithName:FBUnknownAttributeException reason:description userInfo:@{}];
|
||||
return nil;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSSet<NSNumber *> *)uniqueElementTypesWithElements:(NSArray<id<FBElement>> *)elements
|
||||
{
|
||||
NSMutableSet *matchingTypes = [NSMutableSet set];
|
||||
[elements enumerateObjectsUsingBlock:^(id<FBElement> element, NSUInteger elementIdx, BOOL *stopElementsEnum) {
|
||||
[matchingTypes addObject: @([FBElementTypeTransformer elementTypeWithTypeName:element.wdType])];
|
||||
}];
|
||||
return matchingTypes.copy;
|
||||
}
|
||||
|
||||
+ (NSDictionary<NSString *, NSString *> *)wdAttributeNamesMapping
|
||||
{
|
||||
static NSDictionary *attributeNamesMapping;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSMutableDictionary *wdPropertyGettersMapping = [NSMutableDictionary new];
|
||||
unsigned int propsCount = 0;
|
||||
Protocol * aProtocol = objc_getProtocol(protocol_getName(@protocol(FBElement)));
|
||||
objc_property_t *properties = protocol_copyPropertyList(aProtocol, &propsCount);
|
||||
for (unsigned int i = 0; i < propsCount; ++i) {
|
||||
objc_property_t property = properties[i];
|
||||
const char *name = property_getName(property);
|
||||
NSString *nsName = [NSString stringWithUTF8String:name];
|
||||
if (nil == nsName || ![nsName hasPrefix:WD_PREFIX]) {
|
||||
continue;
|
||||
}
|
||||
[wdPropertyGettersMapping setObject:[NSNull null] forKey:nsName];
|
||||
const char *c_attributes = property_getAttributes(property);
|
||||
NSString *attributes = [NSString stringWithUTF8String:c_attributes];
|
||||
if (nil == attributes) {
|
||||
continue;
|
||||
}
|
||||
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
|
||||
NSArray *splitAttrs = [attributes componentsSeparatedByString:OBJC_PROP_ATTRIBS_SEPARATOR];
|
||||
for (NSString *part in splitAttrs) {
|
||||
if ([part hasPrefix:OBJC_PROP_GETTER_PREFIX]) {
|
||||
[wdPropertyGettersMapping setObject:[part substringFromIndex:1] forKey:nsName];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(properties);
|
||||
|
||||
NSMutableDictionary *resultCache = [NSMutableDictionary new];
|
||||
for (NSString *propName in wdPropertyGettersMapping) {
|
||||
if ([[wdPropertyGettersMapping valueForKey:propName] isKindOfClass:NSNull.class]) {
|
||||
// no getter
|
||||
[resultCache setValue:propName forKey:propName];
|
||||
} else {
|
||||
// has getter method
|
||||
[resultCache setValue:[wdPropertyGettersMapping objectForKey:propName] forKey:propName];
|
||||
}
|
||||
NSString *aliasName;
|
||||
if (propName.length <= WD_PREFIX.length + 1) {
|
||||
aliasName = [NSString stringWithFormat:@"%@",
|
||||
[propName substringWithRange:NSMakeRange(WD_PREFIX.length, 1)].lowercaseString];
|
||||
} else {
|
||||
NSString *propNameWithoutPrefix = [propName substringFromIndex:WD_PREFIX.length];
|
||||
NSString *firstPropNameCharacter = [propNameWithoutPrefix substringWithRange:NSMakeRange(0, 1)];
|
||||
if (![propNameWithoutPrefix isEqualToString:[propNameWithoutPrefix uppercaseString]]) {
|
||||
// Lowercase the first character for the alias if the property name is not an uppercase abbreviation
|
||||
firstPropNameCharacter = firstPropNameCharacter.lowercaseString;
|
||||
}
|
||||
aliasName = [NSString stringWithFormat:@"%@%@", firstPropNameCharacter, [propNameWithoutPrefix substringFromIndex:1]];
|
||||
}
|
||||
if ([[wdPropertyGettersMapping valueForKey:propName] isKindOfClass:NSNull.class]) {
|
||||
// no getter
|
||||
[resultCache setValue:propName forKey:aliasName];
|
||||
} else {
|
||||
// has getter method
|
||||
[resultCache setValue:[wdPropertyGettersMapping objectForKey:propName] forKey:aliasName];
|
||||
}
|
||||
}
|
||||
attributeNamesMapping = resultCache.copy;
|
||||
});
|
||||
return attributeNamesMapping;
|
||||
}
|
||||
|
||||
+ (NSString *)uidWithAccessibilityElement:(id<FBXCAccessibilityElement>)element
|
||||
{
|
||||
unsigned long long elementId = [self.class idWithAccessibilityElement:element];
|
||||
int processId = element.processIdentifier;
|
||||
if (elementId < 1 || processId < 1) {
|
||||
return nil;
|
||||
}
|
||||
uint8_t b[16] = {0};
|
||||
memcpy(b, &elementId, sizeof(long long));
|
||||
memcpy(b + sizeof(long long), &processId, sizeof(int));
|
||||
NSUUID *uuidValue = [[NSUUID alloc] initWithUUIDBytes:b];
|
||||
return uuidValue.UUIDString;
|
||||
}
|
||||
|
||||
static BOOL FBShouldUsePayloadForUIDExtraction = YES;
|
||||
static dispatch_once_t oncePayloadToken;
|
||||
+ (unsigned long long)idWithAccessibilityElement:(id<FBXCAccessibilityElement>)element
|
||||
{
|
||||
dispatch_once(&oncePayloadToken, ^{
|
||||
FBShouldUsePayloadForUIDExtraction = [(NSObject *)element respondsToSelector:@selector(payload)];
|
||||
});
|
||||
return FBShouldUsePayloadForUIDExtraction
|
||||
? [[element.payload objectForKey:@"uid.elementID"] longLongValue]
|
||||
: [[(NSObject *)element valueForKey:@"_elementID"] longLongValue];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user