diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index b55e5ae3ae0a..aed6ca782ce4 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -288,13 +288,21 @@ let reactJsErrorHandler = RNTarget( dependencies: [.reactNativeDependencies, .jsi, .reactFeatureFlags, .reactDebug, .reactTurboModuleBridging] ) +/// React-renderercss.podspec +let reactRendererCss = RNTarget( + name: .reactRendererCss, + path: "ReactCommon/react/renderer/css", + excludedPaths: ["tests"], + dependencies: [.reactNativeDependencies, .reactDebug, .reactUtils] +) + /// React-graphicsApple /// This represents the React-graphicsApple BUCK module let reactGraphicsApple = RNTarget( name: .reactGraphicsApple, path: "ReactCommon/react/renderer/graphics/platform/ios", linkedFrameworks: ["UIKit", "CoreGraphics"], - dependencies: [.reactDebug, .jsi, .reactUtils, .reactNativeDependencies] + dependencies: [.reactDebug, .jsi, .reactUtils, .reactNativeDependencies, .reactRendererCss] ) /// React-graphics.podspec @@ -302,7 +310,7 @@ let reactGraphics = RNTarget( name: .reactGraphics, path: "ReactCommon/react/renderer/graphics", excludedPaths: ["platform", "tests"], - dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactRendererDebug, .reactUtils, .reactGraphicsApple] + dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactRendererDebug, .reactUtils, .reactGraphicsApple, .reactRendererCss] ) /// ReactCommon.podspec @@ -492,7 +500,7 @@ let reactFabric = RNTarget( "observers/resize/tests", "scheduler/tests", ], - dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga, .reactJsInspectorTracing], + dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .reactRendererCss, .yoga, .reactJsInspectorTracing], sources: ["animated", "animationbackend", "animations", "attributedstring", "core", "componentregistry", "componentregistry/native", "components/root", "components/view", "components/view/platform/cxx", "components/scrollview", "components/scrollview/platform/cxx", "components/scrollview/platform/ios", "components/legacyviewmanagerinterop", "components/legacyviewmanagerinterop/platform/ios", "dom", "scheduler", "mounting", "observers/events", "observers/intersection", "observers/mutation", "observers/resize", "telemetry", "consistency", "leakchecker", "uimanager", "uimanager/consistency", "viewtransition"] ) @@ -714,6 +722,7 @@ let targets = [ reactPerformanceTimeline, reactRuntimeScheduler, rctTypesafety, + reactRendererCss, reactGraphics, reactGraphicsApple, reactImageManager, diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index 7a13eb84ef36..80da0e0ba82b 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -77,6 +77,7 @@ Pod::Spec.new do |s| end s.subspec "attributedstring" do |ss| + ss.dependency "React-renderercss" ss.source_files = podspec_sources("react/renderer/attributedstring/**/*.{m,mm,cpp,h}", "react/renderer/attributedstring/**/*.{h}") ss.exclude_files = "react/renderer/attributedstring/tests" ss.header_dir = "react/renderer/attributedstring" @@ -217,6 +218,7 @@ Pod::Spec.new do |s| end ss.subspec "intersection" do |sss| + sss.dependency "React-renderercss" sss.source_files = podspec_sources("react/renderer/observers/intersection/**/*.{m,mm,cpp,h}", "react/renderer/observers/intersection/**/*.h") sss.exclude_files = "react/renderer/observers/intersection/tests" sss.header_dir = "react/renderer/observers/intersection" diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt index a337c84bf388..1b6e6bbfae9b 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt @@ -21,6 +21,7 @@ target_link_libraries(react_renderer_attributedstring react_debug rrc_view react_renderer_core + react_renderer_css react_renderer_debug react_renderer_graphics react_renderer_mapbuffer diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/graphics/CMakeLists.txt index b0b2bfe607dc..b501873adb85 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/graphics/CMakeLists.txt @@ -32,6 +32,7 @@ target_link_libraries(react_renderer_graphics folly_runtime react_cxxstableapi react_debug + react_renderer_css react_renderer_debug react_utils ) diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec b/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec index 65e1527bbcac..21beee0c7fba 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec +++ b/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec @@ -57,6 +57,7 @@ Pod::Spec.new do |s| s.dependency "React-utils" s.dependency "React-rendererdebug" s.dependency "React-cxxstableapi" + s.dependency "React-renderercss" depend_on_js_engine(s) add_rn_third_party_dependencies(s) diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.cpp new file mode 100644 index 000000000000..a4ea14b927cd --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.cpp @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include + +namespace facebook::react { + +void fromRawValueShared( + const ContextContainer& contextContainer, + int32_t surfaceId, + const RawValue& value, + SharedColor& result, + parsePlatformColorFn parsePlatformColor) { + ColorComponents colorComponents = { + .red = 0, .green = 0, .blue = 0, .alpha = 0}; + + if (ReactNativeFeatureFlags::enableNativeCSSParsing() && + value.hasType()) { + auto cssColor = parseCSSProperty((std::string)value); + if (std::holds_alternative(cssColor)) { + auto c = std::get(cssColor); + result = hostPlatformColorFromRGBA(c.r, c.g, c.b, c.a); + return; + } + // Unparseable string - fall through to parsePlatformColor + result = parsePlatformColor(contextContainer, surfaceId, value); + } else if (value.hasType()) { + auto argb = (int64_t)value; + auto ratio = 255.f; + colorComponents.alpha = ((argb >> 24) & 0xFF) / ratio; + colorComponents.red = ((argb >> 16) & 0xFF) / ratio; + colorComponents.green = ((argb >> 8) & 0xFF) / ratio; + colorComponents.blue = (argb & 0xFF) / ratio; + + result = colorFromComponents(colorComponents); + } else if (value.hasType>()) { + auto items = (std::vector)value; + auto length = items.size(); + react_native_expect(length == 3 || length == 4); + colorComponents.red = items.at(0); + colorComponents.green = items.at(1); + colorComponents.blue = items.at(2); + colorComponents.alpha = length == 4 ? items.at(3) : 1.0f; + + result = colorFromComponents(colorComponents); + } else { + if (value.hasType>()) { + const auto& items = (std::unordered_map)value; + if (items.find("space") != items.end()) { + colorComponents.red = (float)items.at("r"); + colorComponents.green = (float)items.at("g"); + colorComponents.blue = (float)items.at("b"); + colorComponents.alpha = (float)items.at("a"); + colorComponents.colorSpace = getDefaultColorSpace(); + std::string space = (std::string)items.at("space"); + if (space == "display-p3") { + colorComponents.colorSpace = ColorSpace::DisplayP3; + } else if (space == "srgb") { + colorComponents.colorSpace = ColorSpace::sRGB; + } + result = colorFromComponents(colorComponents); + return; + } + } + result = parsePlatformColor(contextContainer, surfaceId, value); + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h b/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h index 91d73c080751..71cff8430265 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/fromRawValueShared.h @@ -8,74 +8,19 @@ #pragma once #include -#include -#include #include -#include -#include #include #include namespace facebook::react { + using parsePlatformColorFn = SharedColor (*)(const ContextContainer &, int32_t, const RawValue &); -inline void fromRawValueShared( +void fromRawValueShared( const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value, SharedColor &result, - parsePlatformColorFn parsePlatformColor) -{ - ColorComponents colorComponents = {0, 0, 0, 0}; - - if (ReactNativeFeatureFlags::enableNativeCSSParsing() && value.hasType()) { - auto cssColor = parseCSSProperty((std::string)value); - if (std::holds_alternative(cssColor)) { - auto c = std::get(cssColor); - result = hostPlatformColorFromRGBA(c.r, c.g, c.b, c.a); - return; - } - // Unparseable string - fall through to parsePlatformColor - result = parsePlatformColor(contextContainer, surfaceId, value); - } else if (value.hasType()) { - auto argb = (int64_t)value; - auto ratio = 255.f; - colorComponents.alpha = ((argb >> 24) & 0xFF) / ratio; - colorComponents.red = ((argb >> 16) & 0xFF) / ratio; - colorComponents.green = ((argb >> 8) & 0xFF) / ratio; - colorComponents.blue = (argb & 0xFF) / ratio; - - result = colorFromComponents(colorComponents); - } else if (value.hasType>()) { - auto items = (std::vector)value; - auto length = items.size(); - react_native_expect(length == 3 || length == 4); - colorComponents.red = items.at(0); - colorComponents.green = items.at(1); - colorComponents.blue = items.at(2); - colorComponents.alpha = length == 4 ? items.at(3) : 1.0f; + parsePlatformColorFn parsePlatformColor); - result = colorFromComponents(colorComponents); - } else { - if (value.hasType>()) { - const auto &items = (std::unordered_map)value; - if (items.find("space") != items.end()) { - colorComponents.red = (float)items.at("r"); - colorComponents.green = (float)items.at("g"); - colorComponents.blue = (float)items.at("b"); - colorComponents.alpha = (float)items.at("a"); - colorComponents.colorSpace = getDefaultColorSpace(); - std::string space = (std::string)items.at("space"); - if (space == "display-p3") { - colorComponents.colorSpace = ColorSpace::DisplayP3; - } else if (space == "srgb") { - colorComponents.colorSpace = ColorSpace::sRGB; - } - result = colorFromComponents(colorComponents); - return; - } - } - result = parsePlatformColor(contextContainer, surfaceId, value); - } -} } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.cpp new file mode 100644 index 000000000000..321b82c5d135 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.cpp @@ -0,0 +1,125 @@ +/* + * 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. + */ + +#include + +#include "configurePlatformColorCacheInvalidationHook.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +size_t hashGetColourArguments( + int32_t surfaceId, + const std::vector& resourcePaths) { + size_t seed = std::hash{}(surfaceId); + for (const auto& path : resourcePaths) { + seed ^= + std::hash{}(path) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; +} + +SharedColor parsePlatformColor( + const ContextContainer& contextContainer, + int32_t surfaceId, + const RawValue& value) { + Color color{}; + if (value.hasType>()) { + // Mixed array + string values, so read as a map of RawValue (a map of + // vector would assert on the fallback string). + auto map = (std::unordered_map)value; + + std::vector resourcePaths; + auto resourcePathsIt = map.find("resource_paths"); + if (resourcePathsIt != map.end() && + resourcePathsIt->second.hasType>()) { + resourcePaths = (std::vector)resourcePathsIt->second; + } + + bool resolved = false; + if (!resourcePaths.empty()) { + // Cache the (costly) JNI results. A cached nullopt is an explicit miss, + // distinct from a path that resolves to transparent (ARGB 0). + static std::mutex getColorCacheMutex; + static folly::EvictingCacheMap> + getColorCache(64); + + // Listen for appearance changes, which should invalidate the cache + static std::once_flag setupCacheInvalidation; + std::call_once( + setupCacheInvalidation, + configurePlatformColorCacheInvalidationHook, + [&] { + std::scoped_lock lock(getColorCacheMutex); + getColorCache.clear(); + }); + + auto hash = hashGetColourArguments(surfaceId, resourcePaths); + std::optional resolvedColor; + { + std::scoped_lock lock(getColorCacheMutex); + auto iterator = getColorCache.find(hash); + if (iterator != getColorCache.end()) { + resolvedColor = iterator->second; + } else { + const auto& fabricUIManager = + contextContainer.at>("FabricUIManager"); + // Boxed Integer: null is an explicit miss; a non-null value may be 0 + // (transparent black). + static auto getColorFromJava = + fabricUIManager->getClass() + ->getMethod)>("getColor"); + auto javaResourcePaths = + jni::JArrayClass::newArray(resourcePaths.size()); + + for (int i = 0; i < resourcePaths.size(); i++) { + javaResourcePaths->setElement( + i, *jni::make_jstring(resourcePaths[i])); + } + auto boxedColor = + getColorFromJava(fabricUIManager, surfaceId, *javaResourcePaths); + if (boxedColor) { + resolvedColor = static_cast(boxedColor->value()); + } + getColorCache.set(hash, resolvedColor); + } + } + if (resolvedColor.has_value()) { + color = *resolvedColor; + resolved = true; + } + } + + // No path resolved: parse the raw fallback with the shared CSS parser (the + // same parser iOS Fabric uses). + if (!resolved) { + auto fallbackIt = map.find("fallback"); + if (fallbackIt != map.end() && + fallbackIt->second.hasType()) { + auto cssColor = + parseCSSProperty((std::string)fallbackIt->second); + if (std::holds_alternative(cssColor)) { + const auto& c = std::get(cssColor); + color = hostPlatformColorFromRGBA(c.r, c.g, c.b, c.a); + } + } + } + } + + return color; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.h index 4ff0d7577c09..2f8457ac164a 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/PlatformColorParser.h @@ -8,112 +8,19 @@ #pragma once #include - -#include "configurePlatformColorCacheInvalidationHook.h" - -#include -#include #include -#include -#include #include #include #include -#include -#include -#include +#include #include -#include #include namespace facebook::react { -inline size_t hashGetColourArguments(int32_t surfaceId, const std::vector &resourcePaths) -{ - size_t seed = std::hash{}(surfaceId); - for (const auto &path : resourcePaths) { - seed ^= std::hash{}(path) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - } - return seed; -} - -inline SharedColor -parsePlatformColor(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value) -{ - Color color{}; - if (value.hasType>()) { - // Mixed array + string values, so read as a map of RawValue (a map of - // vector would assert on the fallback string). - auto map = (std::unordered_map)value; - - std::vector resourcePaths; - auto resourcePathsIt = map.find("resource_paths"); - if (resourcePathsIt != map.end() && resourcePathsIt->second.hasType>()) { - resourcePaths = (std::vector)resourcePathsIt->second; - } +size_t hashGetColourArguments(int32_t surfaceId, const std::vector &resourcePaths); - bool resolved = false; - if (!resourcePaths.empty()) { - // Cache the (costly) JNI results. A cached nullopt is an explicit miss, - // distinct from a path that resolves to transparent (ARGB 0). - static std::mutex getColorCacheMutex; - static folly::EvictingCacheMap> getColorCache(64); - - // Listen for appearance changes, which should invalidate the cache - static std::once_flag setupCacheInvalidation; - std::call_once(setupCacheInvalidation, configurePlatformColorCacheInvalidationHook, [&] { - std::scoped_lock lock(getColorCacheMutex); - getColorCache.clear(); - }); - - auto hash = hashGetColourArguments(surfaceId, resourcePaths); - std::optional resolvedColor; - { - std::scoped_lock lock(getColorCacheMutex); - auto iterator = getColorCache.find(hash); - if (iterator != getColorCache.end()) { - resolvedColor = iterator->second; - } else { - const auto &fabricUIManager = contextContainer.at>("FabricUIManager"); - // Boxed Integer: null is an explicit miss; a non-null value may be 0 - // (transparent black). - static auto getColorFromJava = - fabricUIManager->getClass()->getMethod)>( - "getColor"); - auto javaResourcePaths = jni::JArrayClass::newArray(resourcePaths.size()); - - for (int i = 0; i < resourcePaths.size(); i++) { - javaResourcePaths->setElement(i, *jni::make_jstring(resourcePaths[i])); - } - auto boxedColor = getColorFromJava(fabricUIManager, surfaceId, *javaResourcePaths); - if (boxedColor) { - resolvedColor = static_cast(boxedColor->value()); - } - getColorCache.set(hash, resolvedColor); - } - } - if (resolvedColor.has_value()) { - color = *resolvedColor; - resolved = true; - } - } - - // No path resolved: parse the raw fallback with the shared CSS parser (the - // same parser iOS Fabric uses). - if (!resolved) { - auto fallbackIt = map.find("fallback"); - if (fallbackIt != map.end() && fallbackIt->second.hasType()) { - auto cssColor = parseCSSProperty((std::string)fallbackIt->second); - if (std::holds_alternative(cssColor)) { - const auto &c = std::get(cssColor); - color = hostPlatformColorFromRGBA(c.r, c.g, c.b, c.a); - } - } - } - } - - return color; -} +SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value); inline void fromRawValue(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value, SharedColor &result) diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/PlatformColorParser.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/PlatformColorParser.cpp new file mode 100644 index 000000000000..9a2cc748ffe9 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/PlatformColorParser.cpp @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#include + +namespace facebook::react { + +SharedColor parsePlatformColor( + const ContextContainer& /*contextContainer*/, + int32_t /*surfaceId*/, + const RawValue& /*value*/) { + float alpha = 0; + float red = 0; + float green = 0; + float blue = 0; + + return {colorFromComponents( + {.red = red, .green = green, .blue = blue, .alpha = alpha})}; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/PlatformColorParser.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/PlatformColorParser.h index 84b81fec280a..0a9f0e110cff 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/PlatformColorParser.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/cxx/react/renderer/graphics/PlatformColorParser.h @@ -8,8 +8,6 @@ #pragma once #include - -#include #include #include #include @@ -17,16 +15,7 @@ namespace facebook::react { -inline SharedColor -parsePlatformColor(const ContextContainer & /*contextContainer*/, int32_t /*surfaceId*/, const RawValue & /*value*/) -{ - float alpha = 0; - float red = 0; - float green = 0; - float blue = 0; - - return {colorFromComponents({red, green, blue, alpha})}; -} +SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value); inline void fromRawValue(const ContextContainer &contextContainer, int32_t surfaceId, const RawValue &value, SharedColor &result) diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.h b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.h index 28afc62d43f3..0a9f0e110cff 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/platform/ios/react/renderer/graphics/PlatformColorParser.h @@ -8,8 +8,6 @@ #pragma once #include - -#include #include #include #include diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/observers/intersection/CMakeLists.txt index 8892e3bc70a1..701e87132f09 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/CMakeLists.txt @@ -19,6 +19,7 @@ target_link_libraries(react_renderer_observers_intersection react_cxxstableapi react_debug react_renderer_core + react_renderer_css react_renderer_graphics react_renderer_mounting react_renderer_runtimescheduler