From cbaff1c7aa0e3239b0a7b3ec61cffe7b35ae030d Mon Sep 17 00:00:00 2001 From: zhongwuzw Date: Tue, 28 Jan 2025 11:20:18 -0800 Subject: [PATCH] Fabric: Fixes crash of dynamic color when light/dark mode changed (#48496) Summary: The reason is when light/dark mode changed, the `hash` value also changed because we used `color.getColor()`. leads to size balanced break. ``` Assertion failed: (index_.size() == lru_.size()), function size, file EvictingCacheMap.h, line 439. (lldb) bt * thread https://github.com/facebook/react-native/issues/1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT frame #0: 0x00000001089a9108 libsystem_kernel.dylib`__pthread_kill + 8 frame https://github.com/facebook/react-native/issues/1: 0x0000000105de3408 libsystem_pthread.dylib`pthread_kill + 256 frame https://github.com/facebook/react-native/issues/2: 0x000000018016c4ec libsystem_c.dylib`abort + 104 frame https://github.com/facebook/react-native/issues/3: 0x000000018016b934 libsystem_c.dylib`__assert_rtn + 268 * frame https://github.com/facebook/react-native/issues/4: 0x00000001073e386c React_FabricComponents`folly::EvictingCacheMap, folly::HeterogeneousAccessHash, folly::HeterogeneousAccessEqualTo>::size(this=0x0000600003900348) const at EvictingCacheMap.h:439:5 frame https://github.com/facebook/react-native/issues/5: 0x00000001073e34f4 React_FabricComponents`void folly::EvictingCacheMap, folly::HeterogeneousAccessHash, folly::HeterogeneousAccessEqualTo>::setImpl(this=0x0000600003900348, key=0x000000016b9f20a8, value=nullptr, promote=true, pruneHook=folly::EvictingCacheMap, folly::HeterogeneousAccessHash, folly::HeterogeneousAccessEqualTo >::PruneHookCall @ 0x000000016b9f1cc8) at EvictingCacheMap.h:674:27 frame https://github.com/facebook/react-native/issues/6: 0x00000001073deb88 React_FabricComponents`folly::EvictingCacheMap, folly::HeterogeneousAccessHash, folly::HeterogeneousAccessEqualTo>::set(this=0x0000600003900348, key=0x000000016b9f20a8, value=ptr = 0x60000024ae20 strong=2 weak=1, promote=true, pruneHook=folly::EvictingCacheMap, folly::HeterogeneousAccessHash, folly::HeterogeneousAccessEqualTo >::PruneHookCall @ 0x000000016b9f1d98) at EvictingCacheMap.h:346:5 frame https://github.com/facebook/react-native/issues/7: 0x00000001073d91dc React_FabricComponents`facebook::react::SimpleThreadSafeCache, 256>::get(this=0x0000600003900348, key=0x000000016b9f20a8, generator= Lambda in File RCTTextLayoutManager.mm at Line 337) const at SimpleThreadSafeCache.h:40:12 frame https://github.com/facebook/react-native/issues/8: 0x00000001073d9058 React_FabricComponents`-[RCTTextLayoutManager _nsAttributedStringFromAttributedString:](self=0x0000600003900340, _cmd="_nsAttributedStringFromAttributedString:", attributedString=AttributedString @ 0x000000016b9f20a8) at RCTTextLayoutManager.mm:337:42 frame https://github.com/facebook/react-native/issues/9: 0x00000001073d6378 React_FabricComponents`-[RCTTextLayoutManager drawAttributedString:paragraphAttributes:frame:drawHighlightPath:](self=0x0000600003900340, _cmd="drawAttributedString:paragraphAttributes:frame:drawHighlightPath:", attributedString=AttributedString @ 0x000000016b9f23a8, paragraphAttributes=ParagraphAttributes @ 0x000000016b9f2378, frame=(origin = (x = 0, y = 0), size = (width = 92, height = 21.666748046875)), block=0x00000001061602d0) at RCTTextLayoutManager.mm:73:56 frame https://github.com/facebook/react-native/issues/10: 0x000000010616020c RCTFabric`-[RCTParagraphTextView drawRect:](self=0x000000012beb9dc0, _cmd="drawRect:", rect=(origin = (x = 0, y = 0.000081380208335701809), size = (width = 92, height = 21.666666666666664))) at RCTParagraphComponentView.mm:346:3 frame https://github.com/facebook/react-native/issues/11: 0x0000000186043e60 UIKitCore`-[UIView(CALayerDelegate) drawLayer:inContext:] + 584 frame https://github.com/facebook/react-native/issues/12: 0x000000018af40080 QuartzCore`CABackingStoreUpdate_ + 244 frame https://github.com/facebook/react-native/issues/13: 0x000000018b0bec88 QuartzCore`invocation function for block in CA::Layer::display_() + 108 frame https://github.com/facebook/react-native/issues/14: 0x000000018b0b5524 QuartzCore`-[CALayer _display] + 1596 frame https://github.com/facebook/react-native/issues/15: 0x000000018b0c7e74 QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 392 frame https://github.com/facebook/react-native/issues/16: 0x000000018affca50 QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double, double*) + 464 frame https://github.com/facebook/react-native/issues/17: 0x000000018b02b260 QuartzCore`CA::Transaction::commit() + 652 frame https://github.com/facebook/react-native/issues/18: 0x000000018b02c7b4 QuartzCore`CA::Transaction::flush_as_runloop_observer(bool) + 68 frame https://github.com/facebook/react-native/issues/19: 0x0000000185ad6c1c UIKitCore`_UIApplicationFlushCATransaction + 48 frame https://github.com/facebook/react-native/issues/20: 0x0000000185a07ccc UIKitCore`__setupUpdateSequence_block_invoke_2 + 352 frame https://github.com/facebook/react-native/issues/21: 0x000000018505d28c UIKitCore`_UIUpdateSequenceRun + 76 frame https://github.com/facebook/react-native/issues/22: 0x0000000185a07670 UIKitCore`schedulerStepScheduledMainSection + 168 frame https://github.com/facebook/react-native/issues/23: 0x0000000185a06aa8 UIKitCore`runloopSourceCallback + 80 frame https://github.com/facebook/react-native/issues/24: 0x000000018041b7c4 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24 frame https://github.com/facebook/react-native/issues/25: 0x000000018041b70c CoreFoundation`__CFRunLoopDoSource0 + 172 frame https://github.com/facebook/react-native/issues/26: 0x000000018041ae70 CoreFoundation`__CFRunLoopDoSources0 + 232 frame https://github.com/facebook/react-native/issues/27: 0x00000001804153b4 CoreFoundation`__CFRunLoopRun + 788 frame https://github.com/facebook/react-native/issues/28: 0x0000000180414c24 CoreFoundation`CFRunLoopRunSpecific + 552 frame https://github.com/facebook/react-native/issues/29: 0x000000019020ab10 GraphicsServices`GSEventRunModal + 160 frame https://github.com/facebook/react-native/issues/30: 0x0000000185ad82fc UIKitCore`-[UIApplication _run] + 796 frame https://github.com/facebook/react-native/issues/31: 0x0000000185adc4f4 UIKitCore`UIApplicationMain + 124 frame https://github.com/facebook/react-native/issues/32: 0x0000000104521f68 RNTester.debug.dylib`main(argc=1, argv=0x000000016b9f5af8) at main.m:15:12 frame https://github.com/facebook/react-native/issues/33: 0x00000001045b9410 dyld_sim`start_sim + 20 frame https://github.com/facebook/react-native/issues/34: 0x0000000104796274 dyld`start + 2840 ``` ## Changelog: [IOS] [FIXED] - Fabric: Fixes crash of dynamic color when light/dark mode changed Pull Request resolved: https://github.com/facebook/react-native/pull/48496 Test Plan: RNTester -> PlatformColor example -> changed the dark/light mode in the system settings -> go back to App and pop and push the PlatformColor example, it would crash: ![Simulator Screen Recording - iPhone 16 - 2025-01-05 at 15 46 08](https://github.com/user-attachments/assets/f6faaf80-ad03-49c6-9a56-b1117bdc2659) Reviewed By: sammy-SC Differential Revision: D68157559 Pulled By: cipolleschi fbshipit-source-id: 01959845b742ce748186d3877b2792f0f9132ff5 --- .../renderer/graphics/HostPlatformColor.h | 13 +- .../renderer/graphics/HostPlatformColor.mm | 122 ++++++++++++++++-- .../renderer/graphics/PlatformColorParser.mm | 2 +- .../renderer/graphics/RCTPlatformColorUtils.h | 8 +- .../graphics/RCTPlatformColorUtils.mm | 1 + .../renderer/graphics/UIColor+Graphics.h | 18 +++ .../renderer/graphics/UIColor+Graphics.mm | 23 ++++ 7 files changed, 167 insertions(+), 20 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.h create mode 100644 packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.mm diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h index 377e9aac6dbbe6..7562b4a396e7ae 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.h @@ -24,8 +24,12 @@ struct Color { Color(int32_t color); Color(const DynamicColor& dynamicColor); Color(const ColorComponents& components); - Color(std::shared_ptr uiColor); + Color() : uiColor_(nullptr){}; int32_t getColor() const; + int32_t getUIColorHash() const; + + static Color createSemanticColor(std::vector& semanticItems); + std::shared_ptr getUIColor() const { return uiColor_; } @@ -48,6 +52,7 @@ struct Color { } private: + Color(std::shared_ptr uiColor); std::shared_ptr uiColor_; }; @@ -59,7 +64,7 @@ namespace HostPlatformColor { #define NO_DESTROY #endif -NO_DESTROY static const facebook::react::Color UndefinedColor = Color(nullptr); +NO_DESTROY static const facebook::react::Color UndefinedColor = Color(); } // namespace HostPlatformColor inline Color @@ -103,8 +108,6 @@ inline float blueFromHostPlatformColor(Color color) { template <> struct std::hash { size_t operator()(const facebook::react::Color& color) const { - auto seed = size_t{0}; - facebook::react::hash_combine(seed, color.getColor()); - return seed; + return color.getUIColorHash(); } }; diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm index 2a80746fa0b5a6..fbfeba1b1b00ba 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/HostPlatformColor.mm @@ -6,9 +6,11 @@ */ #import "HostPlatformColor.h" +#import "UIColor+Graphics.h" #import #import +#import #import #import @@ -19,13 +21,31 @@ namespace facebook::react { namespace { + +bool UIColorIsP3ColorSpace(const std::shared_ptr &uiColor) +{ + UIColor *color = unwrapManagedObject(uiColor); + CGColorSpaceRef colorSpace = CGColorGetColorSpace(color.CGColor); + + if (CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelRGB) { + CFStringRef name = CGColorSpaceGetName(colorSpace); + if (name != NULL && CFEqual(name, kCGColorSpaceDisplayP3)) { + return true; + } + } + return false; +} + UIColor *_Nullable UIColorFromInt32(int32_t intColor) { CGFloat a = CGFloat((intColor >> 24) & 0xFF) / 255.0; CGFloat r = CGFloat((intColor >> 16) & 0xFF) / 255.0; CGFloat g = CGFloat((intColor >> 8) & 0xFF) / 255.0; CGFloat b = CGFloat(intColor & 0xFF) / 255.0; - return [UIColor colorWithRed:r green:g blue:b alpha:a]; + + UIColor *color = [UIColor colorWithRed:r green:g blue:b alpha:a]; + color.reactHash = facebook::react::hash_combine(intColor, 0); + return color; } UIColor *_Nullable UIColorFromDynamicColor(const facebook::react::DynamicColor &dynamicColor) @@ -56,6 +76,7 @@ } } }]; + color.reactHash = facebook::react::hash_combine(dark, light, highContrastDark, highContrastLight, 0); return color; } else { return nil; @@ -64,37 +85,95 @@ return nil; } -int32_t ColorFromUIColor(UIColor *color) +int32_t ColorFromColorComponents(const facebook::react::ColorComponents &components) { float ratio = 255; + auto color = ((int32_t)round((float)components.alpha * ratio) & 0xff) << 24 | + ((int)round((float)components.red * ratio) & 0xff) << 16 | + ((int)round((float)components.green * ratio) & 0xff) << 8 | ((int)round((float)components.blue * ratio) & 0xff); + return color; +} + +int32_t ColorFromUIColor(UIColor *color) +{ CGFloat rgba[4]; [color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]]; - return ((int32_t)round((float)rgba[3] * ratio) & 0xff) << 24 | ((int)round((float)rgba[0] * ratio) & 0xff) << 16 | - ((int)round((float)rgba[1] * ratio) & 0xff) << 8 | ((int)round((float)rgba[2] * ratio) & 0xff); + return ColorFromColorComponents({(float)rgba[0], (float)rgba[1], (float)rgba[2], (float)rgba[3]}); } -int32_t ColorFromUIColor(const std::shared_ptr &uiColor) +int32_t ColorFromUIColorForSpecificTraitCollection( + const std::shared_ptr &uiColor, + UITraitCollection *traitCollection) { UIColor *color = (UIColor *)unwrapManagedObject(uiColor); if (color) { - UITraitCollection *currentTraitCollection = [UITraitCollection currentTraitCollection]; - color = [color resolvedColorWithTraitCollection:currentTraitCollection]; + color = [color resolvedColorWithTraitCollection:traitCollection]; return ColorFromUIColor(color); } return 0; } +int32_t ColorFromUIColor(const std::shared_ptr &uiColor) +{ + return ColorFromUIColorForSpecificTraitCollection(uiColor, [UITraitCollection currentTraitCollection]); +} + UIColor *_Nullable UIColorFromComponentsColor(const facebook::react::ColorComponents &components) { + UIColor *uiColor = nil; if (components.colorSpace == ColorSpace::DisplayP3) { - return [UIColor colorWithDisplayP3Red:components.red - green:components.green - blue:components.blue - alpha:components.alpha]; + uiColor = [UIColor colorWithDisplayP3Red:components.red + green:components.green + blue:components.blue + alpha:components.alpha]; + } else { + uiColor = [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; + } + + auto color = ColorFromColorComponents(components); + uiColor.reactHash = facebook::react::hash_combine(color, components.colorSpace == ColorSpace::DisplayP3); + + return uiColor; +} + +int32_t hashFromUIColor(const std::shared_ptr &uiColor) +{ + if (uiColor == nullptr) { + return 0; } - return [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha]; + + static UITraitCollection *darkModeTraitCollection = + [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark]; + auto darkColor = ColorFromUIColorForSpecificTraitCollection(uiColor, darkModeTraitCollection); + + static UITraitCollection *lightModeTraitCollection = + [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight]; + auto lightColor = ColorFromUIColorForSpecificTraitCollection(uiColor, lightModeTraitCollection); + + static UITraitCollection *darkModeAccessibilityContrastTraitCollection = + [UITraitCollection traitCollectionWithTraitsFromCollections:@[ + darkModeTraitCollection, + [UITraitCollection traitCollectionWithAccessibilityContrast:UIAccessibilityContrastHigh] + ]]; + auto darkAccessibilityContrastColor = + ColorFromUIColorForSpecificTraitCollection(uiColor, darkModeAccessibilityContrastTraitCollection); + + static UITraitCollection *lightModeAccessibilityContrastTraitCollection = + [UITraitCollection traitCollectionWithTraitsFromCollections:@[ + lightModeTraitCollection, + [UITraitCollection traitCollectionWithAccessibilityContrast:UIAccessibilityContrastHigh] + ]]; + auto lightAccessibilityContrastColor = + ColorFromUIColorForSpecificTraitCollection(uiColor, lightModeAccessibilityContrastTraitCollection); + return facebook::react::hash_combine( + darkColor, + lightColor, + darkAccessibilityContrastColor, + lightAccessibilityContrastColor, + UIColorIsP3ColorSpace(uiColor)); } + } // anonymous namespace Color::Color(int32_t color) @@ -114,6 +193,11 @@ int32_t ColorFromUIColor(const std::shared_ptr &uiColor) Color::Color(std::shared_ptr uiColor) { + UIColor *color = ((UIColor *)unwrapManagedObject(uiColor)); + if (color && color.reactHash == 0) { + auto colorHash = hashFromUIColor(uiColor); + color.reactHash = colorHash; + } uiColor_ = std::move(uiColor); } @@ -121,7 +205,8 @@ int32_t ColorFromUIColor(const std::shared_ptr &uiColor) { return (!uiColor_ && !other.uiColor_) || (uiColor_ && other.uiColor_ && - [unwrapManagedObject(getUIColor()) isEqual:unwrapManagedObject(other.getUIColor())]); + ((UIColor *)unwrapManagedObject(getUIColor())).reactHash == + ((UIColor *)unwrapManagedObject(other.getUIColor())).reactHash); } bool Color::operator!=(const Color &other) const @@ -142,6 +227,17 @@ int32_t ColorFromUIColor(const std::shared_ptr &uiColor) return static_cast(rgba[channelId]); } +int32_t Color::getUIColorHash() const +{ + return [(UIColor *)unwrapManagedObject(uiColor_) reactHash]; +} + +Color Color::createSemanticColor(std::vector &semanticItems) +{ + auto semanticColor = RCTPlatformColorFromSemanticItems(semanticItems); + return Color(wrapManagedObject(semanticColor)); +} + } // namespace facebook::react NS_ASSUME_NONNULL_END diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm index 7b32e6ee915b95..7cb04798066724 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.mm @@ -56,7 +56,7 @@ SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t auto items = (std::unordered_map)value; if (items.find("semantic") != items.end() && items.at("semantic").hasType>()) { auto semanticItems = (std::vector)items.at("semantic"); - return {wrapManagedObject(RCTPlatformColorFromSemanticItems(semanticItems))}; + return SharedColor(Color::createSemanticColor(semanticItems)); } else if ( items.find("dynamic") != items.end() && items.at("dynamic").hasType>()) { diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h index 6d2689e8918c76..a45743a234907c 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.h @@ -8,9 +8,15 @@ #pragma once #import -#import #import +namespace facebook { +namespace react { +struct ColorComponents; +struct Color; +} // namespace react +} // namespace facebook + facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems( std::vector& semanticItems); UIColor* RCTPlatformColorFromSemanticItems( diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm index 7f12a3ab00c9b4..3e037710add58a 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/RCTPlatformColorUtils.mm @@ -9,6 +9,7 @@ #import #import +#import #import #include diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.h new file mode 100644 index 00000000000000..da2038a1f6906a --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIColor (Graphics) +@property (nonatomic, assign) int32_t reactHash; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.mm b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.mm new file mode 100644 index 00000000000000..4a60e985943aa0 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/UIColor+Graphics.mm @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import "UIColor+Graphics.h" + +@implementation UIColor (Graphics) + +- (int32_t)reactHash +{ + return [objc_getAssociatedObject(self, _cmd) intValue]; +} + +- (void)setReactHash:(int32_t)reactHash +{ + objc_setAssociatedObject(self, @selector(reactHash), @(reactHash), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +@end