/* * Copyright (c) 2004-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the LICENSE * file in the root directory of this source tree. * */ #if FB_SONARKIT_ENABLED #import "CKComponent+Sonar.h" #import #import #import #import #import #import #import /** This protocol isn't actually adopted anywhere, it just lets us use the SEL below */ @protocol SonarKitLayoutComponentKitOverrideInformalProtocol - (NSString *)sonar_componentNameOverride; - (NSString *)sonar_componentDecorationOverride; - (NSArray *> *> *)sonar_additionalDataOverride; @end static BOOL AccessibilityContextIsDefault(CKComponentAccessibilityContext accessibilityContext) { return accessibilityContext == CKComponentAccessibilityContext(); } static NSDictionary *AccessibilityContextDict(CKComponentAccessibilityContext accessibilityContext) { NSMutableDictionary *accessibilityDict = [NSMutableDictionary new]; if (accessibilityContext.isAccessibilityElement != nil) { accessibilityDict[@"isAccessibilityElement"] = SKObject(@([accessibilityContext.isAccessibilityElement boolValue])); } if (accessibilityContext.accessibilityLabel.hasText()) { accessibilityDict[@"accessibilityLabel"] = SKObject(accessibilityContext.accessibilityLabel.value()); } if (accessibilityContext.accessibilityHint.hasText()) { accessibilityDict[@"accessibilityHint"] = SKObject(accessibilityContext.accessibilityHint.value()); } if (accessibilityContext.accessibilityValue.hasText()) { accessibilityDict[@"accessibilityValue"] = SKObject(accessibilityContext.accessibilityValue.value()); } if (accessibilityContext.accessibilityTraits != nil) { accessibilityDict[@"accessibilityTraits"] = SKObject(@([accessibilityContext.accessibilityTraits integerValue])); } if (accessibilityContext.accessibilityComponentAction) { accessibilityDict[@"accessibilityComponentAction.identifier"] = SKObject(@(accessibilityContext.accessibilityComponentAction.identifier().c_str())); } return accessibilityDict; } FB_LINKABLE(CKComponent_Sonar) @implementation CKComponent (Sonar) - (NSString *)sonar_getName { if ([self respondsToSelector:@selector(sonar_componentNameOverride)]) { return [(id)self sonar_componentNameOverride]; } return NSStringFromClass([self class]); } - (NSString *)sonar_getDecoration { if ([self respondsToSelector:@selector(sonar_componentDecorationOverride)]) { return [(id)self sonar_componentDecorationOverride]; } return @"componentkit"; } - (NSArray *> *> *)sonar_getData { static NSDictionary *UIControlEventsEnumMap = @{ @(UIControlEventTouchDown): @"UIControlEventTouchDown", @(UIControlEventTouchDownRepeat): @"UIControlEventTouchDownRepeat", @(UIControlEventTouchDragInside): @"UIControlEventTouchDragInside", @(UIControlEventTouchDragOutside): @"UIControlEventTouchDragOutside", @(UIControlEventTouchDragEnter): @"UIControlEventTouchDragEnter", @(UIControlEventTouchDragExit): @"UIControlEventTouchDragExit", @(UIControlEventTouchUpInside): @"UIControlEventTouchUpInside", @(UIControlEventTouchUpOutside): @"UIControlEventTouchUpOutside", @(UIControlEventTouchCancel): @"UIControlEventTouchTouchCancel", @(UIControlEventValueChanged): @"UIControlEventValueChanged", @(UIControlEventPrimaryActionTriggered): @"UIControlEventPrimaryActionTriggered", @(UIControlEventEditingDidBegin): @"UIControlEventEditingDidBegin", @(UIControlEventEditingChanged): @"UIControlEventEditingChanged", @(UIControlEventEditingDidEnd): @"UIControlEventEditingDidEnd", @(UIControlEventEditingDidEndOnExit): @"UIControlEventEditingDidEndOnExit", }; NSMutableArray *> *> *data = [NSMutableArray new]; [data addObject: [SKNamed newWithName: @"CKComponent" withValue: @{ @"frame": SKObject(self.viewContext.frame), @"controller": SKObject(NSStringFromClass([self.controller class])), }]]; if (self.viewContext.view) { auto _actions = _CKComponentDebugControlActionsForComponent(self); if (_actions.size() > 0) { NSMutableDictionary *actions = [NSMutableDictionary new]; for (NSNumber *controlEvent : [UIControlEventsEnumMap allKeys]) { NSMutableArray *> *responders = [NSMutableArray new]; for (const auto action : _actions) { if ((action.first & [controlEvent integerValue]) == 0) { continue; } for (auto responder : action.second) { id initialTarget = _CKTypedComponentDebugInitialTarget(responder).get(self); const CKActionInfo actionInfo = CKActionFind(responder.selector(), initialTarget); [responders addObject: @{ @"initialTarget": SKObject(NSStringFromClass([initialTarget class])), @"identifier": SKObject(@(responder.identifier().c_str())), @"handler": SKObject(NSStringFromClass([actionInfo.responder class])), @"selector": SKObject(NSStringFromSelector(responder.selector())), }]; } } if (responders.count > 0) { actions[UIControlEventsEnumMap[controlEvent]] = responders; } } [data addObject: [SKNamed newWithName: @"Actions" withValue: actions]]; } } // Only add accessibility panel if accessibilityContext is not default CKComponentAccessibilityContext accessibilityContext = [self viewConfiguration].accessibilityContext(); if (!AccessibilityContextIsDefault(accessibilityContext)) { [data addObject: [SKNamed newWithName: @"Accessibility" withValue: @{ @"accessibilityContext": AccessibilityContextDict(accessibilityContext), @"accessibilityEnabled": SKMutableObject(@(CK::Component::Accessibility::IsAccessibilityEnabled())), }]]; } if ([self respondsToSelector:@selector(sonar_additionalDataOverride)]) { [data addObjectsFromArray:[(id)self sonar_additionalDataOverride]]; } return data; } - (NSDictionary *)sonar_getDataMutations { return @{ @"CKComponentAccessibility.accessibilityEnabled": ^(NSNumber *value) { CK::Component::Accessibility::SetForceAccessibilityEnabled([value boolValue]); } }; } @end #endif