Skip to content

Commit

Permalink
Fabric: Fixes crash of dynamic color when light/dark mode changed (#4…
Browse files Browse the repository at this point in the history
…8496)

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 #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00000001089a9108 libsystem_kernel.dylib`__pthread_kill + 8
    frame #1: 0x0000000105de3408 libsystem_pthread.dylib`pthread_kill + 256
    frame #2: 0x000000018016c4ec libsystem_c.dylib`abort + 104
    frame #3: 0x000000018016b934 libsystem_c.dylib`__assert_rtn + 268
  * frame #4: 0x00000001073e386c React_FabricComponents`folly::EvictingCacheMap<facebook::react::AttributedString, std::__1::shared_ptr<void>, folly::HeterogeneousAccessHash<facebook::react::AttributedString, void>, folly::HeterogeneousAccessEqualTo<facebook::react::AttributedString, void>>::size(this=0x0000600003900348) const at EvictingCacheMap.h:439:5
    frame #5: 0x00000001073e34f4 React_FabricComponents`void folly::EvictingCacheMap<facebook::react::AttributedString, std::__1::shared_ptr<void>, folly::HeterogeneousAccessHash<facebook::react::AttributedString, void>, folly::HeterogeneousAccessEqualTo<facebook::react::AttributedString, void>>::setImpl<facebook::react::AttributedString>(this=0x0000600003900348, key=0x000000016b9f20a8, value=nullptr, promote=true, pruneHook=folly::EvictingCacheMap<facebook::react::AttributedString, std::__1::shared_ptr<void>, folly::HeterogeneousAccessHash<facebook::react::AttributedString, void>, folly::HeterogeneousAccessEqualTo<facebook::react::AttributedString, void> >::PruneHookCall @ 0x000000016b9f1cc8) at EvictingCacheMap.h:674:27
    frame #6: 0x00000001073deb88 React_FabricComponents`folly::EvictingCacheMap<facebook::react::AttributedString, std::__1::shared_ptr<void>, folly::HeterogeneousAccessHash<facebook::react::AttributedString, void>, folly::HeterogeneousAccessEqualTo<facebook::react::AttributedString, void>>::set(this=0x0000600003900348, key=0x000000016b9f20a8, value=ptr = 0x60000024ae20 strong=2 weak=1, promote=true, pruneHook=folly::EvictingCacheMap<facebook::react::AttributedString, std::__1::shared_ptr<void>, folly::HeterogeneousAccessHash<facebook::react::AttributedString, void>, folly::HeterogeneousAccessEqualTo<facebook::react::AttributedString, void> >::PruneHookCall @ 0x000000016b9f1d98) at EvictingCacheMap.h:346:5
    frame #7: 0x00000001073d91dc React_FabricComponents`facebook::react::SimpleThreadSafeCache<facebook::react::AttributedString, std::__1::shared_ptr<void>, 256>::get(this=0x0000600003900348, key=0x000000016b9f20a8, generator= Lambda in File RCTTextLayoutManager.mm at Line 337) const at SimpleThreadSafeCache.h:40:12
    frame #8: 0x00000001073d9058 React_FabricComponents`-[RCTTextLayoutManager _nsAttributedStringFromAttributedString:](self=0x0000600003900340, _cmd="_nsAttributedStringFromAttributedString:", attributedString=AttributedString @ 0x000000016b9f20a8) at RCTTextLayoutManager.mm:337:42
    frame #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 #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 #11: 0x0000000186043e60 UIKitCore`-[UIView(CALayerDelegate) drawLayer:inContext:] + 584
    frame #12: 0x000000018af40080 QuartzCore`CABackingStoreUpdate_ + 244
    frame #13: 0x000000018b0bec88 QuartzCore`invocation function for block in CA::Layer::display_() + 108
    frame #14: 0x000000018b0b5524 QuartzCore`-[CALayer _display] + 1596
    frame #15: 0x000000018b0c7e74 QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 392
    frame #16: 0x000000018affca50 QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double, double*) + 464
    frame #17: 0x000000018b02b260 QuartzCore`CA::Transaction::commit() + 652
    frame #18: 0x000000018b02c7b4 QuartzCore`CA::Transaction::flush_as_runloop_observer(bool) + 68
    frame #19: 0x0000000185ad6c1c UIKitCore`_UIApplicationFlushCATransaction + 48
    frame #20: 0x0000000185a07ccc UIKitCore`__setupUpdateSequence_block_invoke_2 + 352
    frame #21: 0x000000018505d28c UIKitCore`_UIUpdateSequenceRun + 76
    frame #22: 0x0000000185a07670 UIKitCore`schedulerStepScheduledMainSection + 168
    frame #23: 0x0000000185a06aa8 UIKitCore`runloopSourceCallback + 80
    frame #24: 0x000000018041b7c4 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
    frame #25: 0x000000018041b70c CoreFoundation`__CFRunLoopDoSource0 + 172
    frame #26: 0x000000018041ae70 CoreFoundation`__CFRunLoopDoSources0 + 232
    frame #27: 0x00000001804153b4 CoreFoundation`__CFRunLoopRun + 788
    frame #28: 0x0000000180414c24 CoreFoundation`CFRunLoopRunSpecific + 552
    frame #29: 0x000000019020ab10 GraphicsServices`GSEventRunModal + 160
    frame #30: 0x0000000185ad82fc UIKitCore`-[UIApplication _run] + 796
    frame #31: 0x0000000185adc4f4 UIKitCore`UIApplicationMain + 124
    frame #32: 0x0000000104521f68 RNTester.debug.dylib`main(argc=1, argv=0x000000016b9f5af8) at main.m:15:12
    frame #33: 0x00000001045b9410 dyld_sim`start_sim + 20
    frame #34: 0x0000000104796274 dyld`start + 2840
```

## Changelog:

[IOS] [FIXED] - Fabric: Fixes crash of dynamic color when light/dark mode changed

Pull Request resolved: #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
  • Loading branch information
zhongwuzw authored and facebook-github-bot committed Jan 28, 2025
1 parent ff2e403 commit cbaff1c
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ struct Color {
Color(int32_t color);
Color(const DynamicColor& dynamicColor);
Color(const ColorComponents& components);
Color(std::shared_ptr<void> uiColor);
Color() : uiColor_(nullptr){};
int32_t getColor() const;
int32_t getUIColorHash() const;

static Color createSemanticColor(std::vector<std::string>& semanticItems);

std::shared_ptr<void> getUIColor() const {
return uiColor_;
}
Expand All @@ -48,6 +52,7 @@ struct Color {
}

private:
Color(std::shared_ptr<void> uiColor);
std::shared_ptr<void> uiColor_;
};

Expand All @@ -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
Expand Down Expand Up @@ -103,8 +108,6 @@ inline float blueFromHostPlatformColor(Color color) {
template <>
struct std::hash<facebook::react::Color> {
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();
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
*/

#import "HostPlatformColor.h"
#import "UIColor+Graphics.h"

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <react/renderer/graphics/RCTPlatformColorUtils.h>
#import <react/utils/ManagedObjectWrapper.h>
#import <string>

Expand All @@ -19,13 +21,31 @@
namespace facebook::react {

namespace {

bool UIColorIsP3ColorSpace(const std::shared_ptr<void> &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)
Expand Down Expand Up @@ -56,6 +76,7 @@
}
}
}];
color.reactHash = facebook::react::hash_combine(dark, light, highContrastDark, highContrastLight, 0);
return color;
} else {
return nil;
Expand All @@ -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<void> &uiColor)
int32_t ColorFromUIColorForSpecificTraitCollection(
const std::shared_ptr<void> &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<void> &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<void> &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)
Expand All @@ -114,14 +193,20 @@ int32_t ColorFromUIColor(const std::shared_ptr<void> &uiColor)

Color::Color(std::shared_ptr<void> uiColor)
{
UIColor *color = ((UIColor *)unwrapManagedObject(uiColor));
if (color && color.reactHash == 0) {
auto colorHash = hashFromUIColor(uiColor);
color.reactHash = colorHash;
}
uiColor_ = std::move(uiColor);
}

bool Color::operator==(const Color &other) const
{
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
Expand All @@ -142,6 +227,17 @@ int32_t ColorFromUIColor(const std::shared_ptr<void> &uiColor)
return static_cast<float>(rgba[channelId]);
}

int32_t Color::getUIColorHash() const
{
return [(UIColor *)unwrapManagedObject(uiColor_) reactHash];
}

Color Color::createSemanticColor(std::vector<std::string> &semanticItems)
{
auto semanticColor = RCTPlatformColorFromSemanticItems(semanticItems);
return Color(wrapManagedObject(semanticColor));
}

} // namespace facebook::react

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t
auto items = (std::unordered_map<std::string, RawValue>)value;
if (items.find("semantic") != items.end() && items.at("semantic").hasType<std::vector<std::string>>()) {
auto semanticItems = (std::vector<std::string>)items.at("semantic");
return {wrapManagedObject(RCTPlatformColorFromSemanticItems(semanticItems))};
return SharedColor(Color::createSemanticColor(semanticItems));
} else if (
items.find("dynamic") != items.end() &&
items.at("dynamic").hasType<std::unordered_map<std::string, RawValue>>()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
#pragma once

#import <UIKit/UIKit.h>
#import <react/renderer/graphics/HostPlatformColor.h>
#import <vector>

namespace facebook {
namespace react {
struct ColorComponents;
struct Color;
} // namespace react
} // namespace facebook

facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(
std::vector<std::string>& semanticItems);
UIColor* RCTPlatformColorFromSemanticItems(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <react/renderer/graphics/HostPlatformColor.h>
#import <react/utils/ManagedObjectWrapper.h>

#include <string>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIColor (Graphics)
@property (nonatomic, assign) int32_t reactHash;
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -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 <objc/runtime.h>
#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

0 comments on commit cbaff1c

Please sign in to comment.