/* * 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 #import "CKStatelessComponent+Sonar.h" /** 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]; } auto const canBeReusedCounter = self.flipper_canBeReusedCounter; if (canBeReusedCounter > 0) { return [NSString stringWithFormat:@"%@ (Can be reused x%lu)", NSStringFromClass([self class]), (unsigned long)canBeReusedCounter]; } 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])), }]]; auto const canBeReusedCounter = self.flipper_canBeReusedCounter; if (canBeReusedCounter > 0) { [data addObject: [SKNamed newWithName: @"Convert to CKRenderComponent" withValue: @{ @"This component can be reused" : SKObject([NSString stringWithFormat:@"%lu times", (unsigned long)canBeReusedCounter]) }]]; } 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) { auto debugTarget = _CKTypedComponentDebugInitialTarget(responder); if (debugTarget.isBlockBaseAction()) { [responders addObject: @{ @"identifier": SKObject(@(responder.identifier().c_str())), @"selector": SKObject(NSStringFromSelector(responder.selector())), }]; } else { id initialTarget = debugTarget.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]); } }; } static char const kCanBeReusedKey = ' '; - (void)setFlipper_canBeReusedCounter:(NSUInteger)canBeReusedCounter { objc_setAssociatedObject(self, &kCanBeReusedKey, @(canBeReusedCounter), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSUInteger)flipper_canBeReusedCounter { return [objc_getAssociatedObject(self, &kCanBeReusedKey) unsignedIntegerValue]; } @end #endif