From 339663219d723dfea729573581a13ba6554d2c93 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 21 May 2026 04:31:20 -0700 Subject: [PATCH] Fix percentage-based border radius (#56915) Summary: Changelog: [IOS][FIXED] Fixed percentage-based border radius `isUniform` checks if all corners are equal, which is fine in most cases. When it comes to setting broderRadius, it also needs to be checked whether the corner is circular or not. Reviewed By: javache Differential Revision: D105926696 --- .../ComponentViews/View/RCTViewComponentView.mm | 10 +++++----- .../react/renderer/components/view/primitives.h | 5 +++++ scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api | 1 + .../cxx-api/api-snapshots/ReactAndroidNewarchCxx.api | 1 + .../cxx-api/api-snapshots/ReactAndroidReleaseCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api | 1 + .../cxx-api/api-snapshots/ReactCommonNewarchCxx.api | 1 + .../cxx-api/api-snapshots/ReactCommonReleaseCxx.api | 1 + 11 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index b033b7c71914..5571b5350bff 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -1044,7 +1044,7 @@ - (void)invalidateLayer const bool useCoreAnimationBorderRendering = borderMetrics.borderColors.isUniform() && borderMetrics.borderWidths.isUniform() && borderMetrics.borderStyles.isUniform() && borderMetrics.borderStyles.left == BorderStyle::Solid && - borderMetrics.borderRadii.isUniform() && + areBorderRadiiCircular(borderMetrics.borderRadii) && ( // iOS draws borders in front of the content whereas CSS draws them behind // the content. For this reason, only use iOS border drawing when clipping @@ -1126,7 +1126,7 @@ - (void)invalidateLayer _outlineLayer.frame = CGRectInset( layer.bounds, -_props->outlineOffset - _props->outlineWidth, -_props->outlineOffset - _props->outlineWidth); - if (borderMetrics.borderRadii.isUniform() && borderMetrics.borderRadii.topLeft.horizontal == 0) { + if (areBorderRadiiCircular(borderMetrics.borderRadii) && borderMetrics.borderRadii.topLeft.horizontal == 0) { UIColor *outlineColor = RCTUIColorFromSharedColor(_props->outlineColor); _outlineLayer.borderWidth = _props->outlineWidth; _outlineLayer.borderColor = outlineColor.CGColor; @@ -1302,7 +1302,7 @@ - (void)invalidateLayer if (self.currentContainerView.clipsToBounds) { BOOL clipToPaddingBox = ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox(); if (!clipToPaddingBox) { - if (borderMetrics.borderRadii.isUniform()) { + if (areBorderRadiiCircular(borderMetrics.borderRadii)) { self.currentContainerView.layer.cornerRadius = borderMetrics.borderRadii.topLeft.horizontal; } else { CALayer *maskLayer = @@ -1325,7 +1325,7 @@ - (void)invalidateLayer } } else if ( !borderMetrics.borderWidths.isUniform() || borderMetrics.borderWidths.left != 0 || - !borderMetrics.borderRadii.isUniform()) { + !areBorderRadiiCircular(borderMetrics.borderRadii)) { CALayer *maskLayer = [self createMaskLayer:RCTCGRectFromRect(_layoutMetrics.getPaddingFrame()) cornerInsets:RCTGetCornerInsets( RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), @@ -1344,7 +1344,7 @@ - (void)shapeLayerToMatchView:(CALayer *)layer borderMetrics:(BorderMetrics)bord // Bounds is needed here to account for scaling transforms properly and ensure // we do not scale twice layer.frame = CGRectMake(0, 0, self.layer.bounds.size.width, self.layer.bounds.size.height); - if (borderMetrics.borderRadii.isUniform()) { + if (areBorderRadiiCircular(borderMetrics.borderRadii)) { layer.mask = nil; layer.cornerRadius = borderMetrics.borderRadii.topLeft.horizontal; layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h index 0bcd3ca6f8bf..aa7e385229f6 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h @@ -248,4 +248,9 @@ struct BorderMetrics { bool operator==(const BorderMetrics &rhs) const = default; }; +inline bool areBorderRadiiCircular(const BorderRadii &borderRadii) +{ + return borderRadii.isUniform() && borderRadii.topLeft.horizontal == borderRadii.topLeft.vertical; +} + } // namespace facebook::react diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 9dc4bac34d54..c7a004842b88 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -811,6 +811,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 862584bb3717..f1870723b698 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -810,6 +810,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index a5b82ce3c25a..193741980345 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -811,6 +811,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index f6155dd49e03..a47fda0e93b8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4052,6 +4052,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index a0671bc25eff..2b7425f6cd14 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -3677,6 +3677,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index d0e6be7fa841..a8fad8bcd9c1 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4052,6 +4052,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 3914705ee83b..7165bafdb493 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -420,6 +420,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::isColorMeaningful(const facebook::react::SharedColor& color) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 5988479bbd9d..fba9adb0e5c7 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -419,6 +419,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::isColorMeaningful(const facebook::react::SharedColor& color) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index c294e79f1ee6..4c4d591a7334 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -420,6 +420,7 @@ bool facebook::react::areAttributedStringFragmentsEquivalentDisplayWise(const fa bool facebook::react::areAttributedStringFragmentsEquivalentLayoutWise(const facebook::react::AttributedString::Fragment& lhs, const facebook::react::AttributedString::Fragment& rhs); bool facebook::react::areAttributedStringsEquivalentDisplayWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); bool facebook::react::areAttributedStringsEquivalentLayoutWise(const facebook::react::AttributedString& lhs, const facebook::react::AttributedString& rhs); +bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::react::TextAttributes& lhs, const facebook::react::TextAttributes& rhs); bool facebook::react::hasAccurateCPUTimeNanosForBenchmarks(); bool facebook::react::isColorMeaningful(const facebook::react::SharedColor& color) noexcept;