From 8a2602fd69d4c81bdb71bb7b1a3b07a1daa1f4f8 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 18 Feb 2025 18:56:12 -0800 Subject: [PATCH] Reland 2: Wire up native box shadow parsing (#49503) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49503 Relanding this change, now checking for the existing of FeatureFlags native module before checking it, with the assumption when the native module is unavailable (legacy arch) we will never do native processing, or even use the SVC. This aligns with the approach taken in `setupTimers` for the event loop, but is probably not the right long term solution for flags API. This also inlines a couple fixes originally up the stack, of missing propagation of inline state, and missing the gating in ReactNativeStyleAttributes. Changelog: [Internal] Reviewed By: joevilches Differential Revision: D69804412 fbshipit-source-id: 1f285994132cff75b6845b64cf26fceae37d4c92 --- .../View/ReactNativeStyleAttributes.js | 10 +- .../NativeComponent/BaseViewConfig.android.js | 12 +- .../NativeComponent/BaseViewConfig.ios.js | 12 +- .../components/view/BaseViewProps.cpp | 1 + .../view/BoxShadowPropsConversions.h | 311 ++++++++++++++++++ .../renderer/components/view/conversions.h | 97 ------ 6 files changed, 339 insertions(+), 104 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/components/view/BoxShadowPropsConversions.h diff --git a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js index d684d878050..25878285ed6 100644 --- a/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js +++ b/packages/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js @@ -10,6 +10,8 @@ import type {AnyAttributeType} from '../../Renderer/shims/ReactNativeTypes'; +import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags'; +import NativeReactNativeFeatureFlags from '../../../src/private/featureflags/specs/NativeReactNativeFeatureFlags'; import processAspectRatio from '../../StyleSheet/processAspectRatio'; import processBackgroundImage from '../../StyleSheet/processBackgroundImage'; import processBoxShadow from '../../StyleSheet/processBoxShadow'; @@ -136,7 +138,13 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = { /* * BoxShadow */ - boxShadow: {process: processBoxShadow}, + boxShadow: + NativeReactNativeFeatureFlags != null && + ReactNativeFeatureFlags.enableNativeCSSParsing() + ? true + : { + process: processBoxShadow, + }, /** * Linear Gradient diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js index 407943c4c9d..6ac361875ca 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js @@ -10,6 +10,8 @@ import type {PartialViewConfigWithoutName} from './PlatformBaseViewConfig'; +import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags'; +import NativeReactNativeFeatureFlags from '../../src/private/featureflags/specs/NativeReactNativeFeatureFlags'; import ReactNativeStyleAttributes from '../Components/View/ReactNativeStyleAttributes'; import {DynamicallyInjectedByGestureHandler} from './ViewConfigIgnore'; @@ -169,9 +171,13 @@ const validAttributesForNonEventProps = { experimental_backgroundImage: { process: require('../StyleSheet/processBackgroundImage').default, }, - boxShadow: { - process: require('../StyleSheet/processBoxShadow').default, - }, + boxShadow: + NativeReactNativeFeatureFlags != null && + ReactNativeFeatureFlags.enableNativeCSSParsing() + ? true + : { + process: require('../StyleSheet/processBoxShadow').default, + }, filter: { process: require('../StyleSheet/processFilter').default, }, diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js index d3288e2a031..246b65bfdd6 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -10,6 +10,8 @@ import type {PartialViewConfigWithoutName} from './PlatformBaseViewConfig'; +import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags'; +import NativeReactNativeFeatureFlags from '../../src/private/featureflags/specs/NativeReactNativeFeatureFlags'; import ReactNativeStyleAttributes from '../Components/View/ReactNativeStyleAttributes'; import { ConditionallyIgnoredEventHandlers, @@ -228,9 +230,13 @@ const validAttributesForNonEventProps = { filter: { process: require('../StyleSheet/processFilter').default, }, - boxShadow: { - process: require('../StyleSheet/processBoxShadow').default, - }, + boxShadow: + NativeReactNativeFeatureFlags != null && + ReactNativeFeatureFlags.enableNativeCSSParsing() + ? true + : { + process: require('../StyleSheet/processBoxShadow').default, + }, mixBlendMode: true, isolation: true, diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index 28e880f0e37..b4218879c67 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BoxShadowPropsConversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/BoxShadowPropsConversions.h new file mode 100644 index 00000000000..23a0a730765 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BoxShadowPropsConversions.h @@ -0,0 +1,311 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +inline void parseProcessedBoxShadow( + const PropsParserContext& context, + const RawValue& value, + std::vector& result) { + react_native_expect(value.hasType>()); + if (!value.hasType>()) { + result = {}; + return; + } + + std::vector boxShadows{}; + auto rawBoxShadows = static_cast>(value); + for (const auto& rawBoxShadow : rawBoxShadows) { + bool isMap = + rawBoxShadow.hasType>(); + react_native_expect(isMap); + if (!isMap) { + // If any box shadow is malformed then we should not apply any of them + // which is the web behavior. + result = {}; + return; + } + + auto rawBoxShadowMap = + static_cast>(rawBoxShadow); + BoxShadow boxShadow{}; + auto offsetX = rawBoxShadowMap.find("offsetX"); + react_native_expect(offsetX != rawBoxShadowMap.end()); + if (offsetX == rawBoxShadowMap.end()) { + result = {}; + return; + } + react_native_expect(offsetX->second.hasType()); + if (!offsetX->second.hasType()) { + result = {}; + return; + } + boxShadow.offsetX = (Float)offsetX->second; + + auto offsetY = rawBoxShadowMap.find("offsetY"); + react_native_expect(offsetY != rawBoxShadowMap.end()); + if (offsetY == rawBoxShadowMap.end()) { + result = {}; + return; + } + react_native_expect(offsetY->second.hasType()); + if (!offsetY->second.hasType()) { + result = {}; + return; + } + boxShadow.offsetY = (Float)offsetY->second; + + auto blurRadius = rawBoxShadowMap.find("blurRadius"); + if (blurRadius != rawBoxShadowMap.end()) { + react_native_expect(blurRadius->second.hasType()); + if (!blurRadius->second.hasType()) { + result = {}; + return; + } + boxShadow.blurRadius = (Float)blurRadius->second; + } + + auto spreadDistance = rawBoxShadowMap.find("spreadDistance"); + if (spreadDistance != rawBoxShadowMap.end()) { + react_native_expect(spreadDistance->second.hasType()); + if (!spreadDistance->second.hasType()) { + result = {}; + return; + } + boxShadow.spreadDistance = (Float)spreadDistance->second; + } + + auto inset = rawBoxShadowMap.find("inset"); + if (inset != rawBoxShadowMap.end()) { + react_native_expect(inset->second.hasType()); + if (!inset->second.hasType()) { + result = {}; + return; + } + boxShadow.inset = (bool)inset->second; + } + + auto color = rawBoxShadowMap.find("color"); + if (color != rawBoxShadowMap.end()) { + fromRawValue( + context.contextContainer, + context.surfaceId, + color->second, + boxShadow.color); + } + + boxShadows.push_back(boxShadow); + } + + result = boxShadows; +} + +inline SharedColor fromCSSColor(const CSSColor& cssColor) { + return hostPlatformColorFromRGBA( + cssColor.r, cssColor.g, cssColor.b, cssColor.a); +} + +inline std::optional fromCSSShadow(const CSSShadow& cssShadow) { + // TODO: handle non-px values + if (cssShadow.offsetX.unit != CSSLengthUnit::Px || + cssShadow.offsetY.unit != CSSLengthUnit::Px || + cssShadow.blurRadius.unit != CSSLengthUnit::Px || + cssShadow.spreadDistance.unit != CSSLengthUnit::Px) { + return {}; + } + + return BoxShadow{ + .offsetX = cssShadow.offsetX.value, + .offsetY = cssShadow.offsetY.value, + .blurRadius = cssShadow.blurRadius.value, + .spreadDistance = cssShadow.spreadDistance.value, + .color = fromCSSColor(cssShadow.color), + .inset = cssShadow.inset, + }; +} + +inline void parseUnprocessedBoxShadowString( + std::string&& value, + std::vector& result) { + auto boxShadowList = parseCSSProperty((std::string)value); + if (!std::holds_alternative(boxShadowList)) { + result = {}; + return; + } + + for (const auto& cssShadow : std::get(boxShadowList)) { + if (auto boxShadow = fromCSSShadow(cssShadow)) { + result.push_back(*boxShadow); + } else { + result = {}; + return; + } + } +} + +inline std::optional coerceLength(const RawValue& value) { + if (value.hasType()) { + return (Float)value; + } + + if (value.hasType()) { + auto len = parseCSSProperty((std::string)value); + if (!std::holds_alternative(len)) { + return {}; + } + + auto cssLen = std::get(len); + if (cssLen.unit != CSSLengthUnit::Px) { + return {}; + } + + return cssLen.value; + } + return {}; +} + +inline std::optional parseBoxShadowRawValue( + const PropsParserContext& context, + const RawValue& value) { + if (!value.hasType>()) { + return {}; + } + + auto boxShadow = std::unordered_map(value); + auto rawOffsetX = boxShadow.find("offsetX"); + if (rawOffsetX == boxShadow.end()) { + return {}; + } + auto offsetX = coerceLength(rawOffsetX->second); + if (!offsetX.has_value()) { + return {}; + } + + auto rawOffsetY = boxShadow.find("offsetY"); + if (rawOffsetY == boxShadow.end()) { + return {}; + } + auto offsetY = coerceLength(rawOffsetY->second); + if (!offsetY.has_value()) { + return {}; + } + + Float blurRadius = 0; + auto rawBlurRadius = boxShadow.find("blurRadius"); + if (rawBlurRadius != boxShadow.end()) { + if (auto blurRadiusValue = coerceLength(rawBlurRadius->second)) { + blurRadius = *blurRadiusValue; + } else { + return {}; + } + } + + Float spreadDistance = 0; + auto rawSpreadDistance = boxShadow.find("spreadDistance"); + if (rawSpreadDistance != boxShadow.end()) { + if (auto spreadDistanceValue = coerceLength(rawSpreadDistance->second)) { + spreadDistance = *spreadDistanceValue; + } else { + return {}; + } + } + + bool inset = false; + auto rawInset = boxShadow.find("inset"); + if (rawInset != boxShadow.end()) { + if (rawInset->second.hasType()) { + inset = (bool)rawInset->second; + } else { + return {}; + } + } + + SharedColor color; + auto rawColor = boxShadow.find("color"); + if (rawColor != boxShadow.end()) { + const auto& rawColorValue = rawColor->second; + if (rawColorValue.hasType()) { + auto cssColor = parseCSSProperty((std::string)rawColorValue); + if (!std::holds_alternative(cssColor)) { + return {}; + } + color = fromCSSColor(std::get(cssColor)); + } else { + fromRawValue( + context.contextContainer, context.surfaceId, rawColor->second, color); + if (!color) { + return {}; + } + } + } + + return BoxShadow{ + .offsetX = *offsetX, + .offsetY = *offsetY, + .blurRadius = blurRadius, + .spreadDistance = spreadDistance, + .color = color, + .inset = inset}; +} + +inline void parseUnprocessedBoxShadowList( + const PropsParserContext& context, + std::vector&& value, + std::vector& result) { + for (const auto& rawValue : value) { + if (auto boxShadow = parseBoxShadowRawValue(context, rawValue)) { + result.push_back(*boxShadow); + } else { + result = {}; + return; + } + } +} + +inline void parseUnprocessedBoxShadow( + const PropsParserContext& context, + const RawValue& value, + std::vector& result) { + if (value.hasType()) { + parseUnprocessedBoxShadowString((std::string)value, result); + } else if (value.hasType>()) { + parseUnprocessedBoxShadowList( + context, (std::vector)value, result); + } else { + result = {}; + } +} + +inline void fromRawValue( + const PropsParserContext& context, + const RawValue& value, + std::vector& result) { + if (ReactNativeFeatureFlags::enableNativeCSSParsing()) { + parseUnprocessedBoxShadow(context, value, result); + } else { + parseProcessedBoxShadow(context, value, result); + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h index 31f3f587550..f2a03483d83 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -1053,102 +1052,6 @@ inline void fromRawValue( } } -inline void fromRawValue( - const PropsParserContext& context, - const RawValue& value, - std::vector& result) { - react_native_expect(value.hasType>()); - if (!value.hasType>()) { - result = {}; - return; - } - - std::vector boxShadows{}; - auto rawBoxShadows = static_cast>(value); - for (const auto& rawBoxShadow : rawBoxShadows) { - bool isMap = - rawBoxShadow.hasType>(); - react_native_expect(isMap); - if (!isMap) { - // If any box shadow is malformed then we should not apply any of them - // which is the web behavior. - result = {}; - return; - } - - auto rawBoxShadowMap = - static_cast>(rawBoxShadow); - BoxShadow boxShadow{}; - auto offsetX = rawBoxShadowMap.find("offsetX"); - react_native_expect(offsetX != rawBoxShadowMap.end()); - if (offsetX == rawBoxShadowMap.end()) { - result = {}; - return; - } - react_native_expect(offsetX->second.hasType()); - if (!offsetX->second.hasType()) { - result = {}; - return; - } - boxShadow.offsetX = (Float)offsetX->second; - - auto offsetY = rawBoxShadowMap.find("offsetY"); - react_native_expect(offsetY != rawBoxShadowMap.end()); - if (offsetY == rawBoxShadowMap.end()) { - result = {}; - return; - } - react_native_expect(offsetY->second.hasType()); - if (!offsetY->second.hasType()) { - result = {}; - return; - } - boxShadow.offsetY = (Float)offsetY->second; - - auto blurRadius = rawBoxShadowMap.find("blurRadius"); - if (blurRadius != rawBoxShadowMap.end()) { - react_native_expect(blurRadius->second.hasType()); - if (!blurRadius->second.hasType()) { - result = {}; - return; - } - boxShadow.blurRadius = (Float)blurRadius->second; - } - - auto spreadDistance = rawBoxShadowMap.find("spreadDistance"); - if (spreadDistance != rawBoxShadowMap.end()) { - react_native_expect(spreadDistance->second.hasType()); - if (!spreadDistance->second.hasType()) { - result = {}; - return; - } - boxShadow.spreadDistance = (Float)spreadDistance->second; - } - - auto inset = rawBoxShadowMap.find("inset"); - if (inset != rawBoxShadowMap.end()) { - react_native_expect(inset->second.hasType()); - if (!inset->second.hasType()) { - result = {}; - return; - } - boxShadow.inset = (bool)inset->second; - } - - auto color = rawBoxShadowMap.find("color"); - if (color != rawBoxShadowMap.end()) { - fromRawValue( - context.contextContainer, - context.surfaceId, - color->second, - boxShadow.color); - } - - boxShadows.push_back(boxShadow); - } - - result = boxShadows; -} inline void fromRawValue( const PropsParserContext& context, const RawValue& value,