Skip to content

Commit

Permalink
Remove ReactViewGroup Legacy Background Path (#46159)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #46159

## This Diff

This removes the legacy path from ReactViewGroup and its view manager.

## This Stack

This removes the non-Style-applicator background management paths of the different native components. There have been multiple conflicting changes, and bugs added bc harder to reason about, which motivates making this change as soon as possible. This also lets us formalize guarantees that BaseViewManager may safely manipulate background styling of all built in native components.

There is one still known issue, where BackgroundStyleApplicator does not propagate I18nManager derived layout direction to borders (compared to Android derived root direction). This is mostly an issue for apps that with LTR and RTL context, or force a layout direction, which I would guess is relatively rare, so my plan is to forward fix this later this by enabling set_android_layout_direction which will solve that problem mopre generically.

Changelog: [Internal]

Reviewed By: tdn120

Differential Revision: D61657251

fbshipit-source-id: 6d00a1cac79450d306cf28446e6397d31ceffb19
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Sep 17, 2024
1 parent 890970a commit 6291ff0
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 266 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -327,13 +327,6 @@ public float getInnerBorderRadius(float computedRadius, float borderWidth) {
return Math.max(computedRadius - borderWidth, 0);
}

// TODO: This API is unsafe and should be removed when
// BackgroundStyleApplicator is rolled out
@Deprecated(forRemoval = true, since = "0.76.0")
public ComputedBorderRadius getComputedBorderRadius() {
return mComputedBorderRadius;
}

public void setColor(int color) {
mColor = color;
invalidateSelf();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.view.MotionEvent;
import android.view.View;
Expand All @@ -31,20 +29,16 @@
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.R;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactNoCrashSoftException;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.touch.OnInterceptTouchEventListener;
import com.facebook.react.touch.ReactHitSlopView;
import com.facebook.react.touch.ReactInterceptingViewGroup;
import com.facebook.react.uimanager.BackgroundStyleApplicator;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.LengthPercentage;
import com.facebook.react.uimanager.LengthPercentageType;
import com.facebook.react.uimanager.MeasureSpecAssertions;
Expand All @@ -56,20 +50,14 @@
import com.facebook.react.uimanager.ReactOverflowViewWithInset;
import com.facebook.react.uimanager.ReactPointerEventsView;
import com.facebook.react.uimanager.ReactZIndexedViewGroup;
import com.facebook.react.uimanager.RootView;
import com.facebook.react.uimanager.RootViewUtil;
import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable;
import com.facebook.react.uimanager.style.BackgroundImageLayer;
import com.facebook.react.uimanager.style.BorderRadiusProp;
import com.facebook.react.uimanager.style.BorderStyle;
import com.facebook.react.uimanager.style.ComputedBorderRadius;
import com.facebook.react.uimanager.style.CornerRadii;
import com.facebook.react.uimanager.style.LogicalEdge;
import com.facebook.react.uimanager.style.Overflow;
import java.util.List;

/**
* Backing for a React View. Has support for borders, but since borders aren't common, lazy
Expand Down Expand Up @@ -238,44 +226,12 @@ public void dispatchProvideStructure(ViewStructure structure) {

@Override
public void setBackgroundColor(int color) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setBackgroundColor(this, color);
} else {
if (color == Color.TRANSPARENT && mCSSBackgroundDrawable == null) {
// don't do anything, no need to allocate ReactBackgroundDrawable for transparent background
} else {
getOrCreateReactViewBackground().setColor(color);
}
}
}

@UnstableReactNativeAPI
/*package*/ void setBackgroundImage(@Nullable List<BackgroundImageLayer> backgroundImageLayers) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setBackgroundImage(this, backgroundImageLayers);
} else {
getOrCreateReactViewBackground().setBackgroundImage(backgroundImageLayers);
}
BackgroundStyleApplicator.setBackgroundColor(this, color);
}

@Deprecated(since = "0.76.0", forRemoval = true)
public void setTranslucentBackgroundDrawable(@Nullable Drawable background) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setFeedbackUnderlay(this, background);
} else {
// it's required to call setBackground to null, as in some of the cases we may set new
// background to be a layer drawable that contains a drawable that has been setup
// as a background previously. This will not work correctly as the drawable callback logic is
// messed up in AOSP
updateBackgroundDrawable(null);
if (mCSSBackgroundDrawable != null && background != null) {
LayerDrawable layerDrawable =
new LayerDrawable(new Drawable[] {mCSSBackgroundDrawable, background});
updateBackgroundDrawable(layerDrawable);
} else if (background != null) {
updateBackgroundDrawable(background);
}
}
BackgroundStyleApplicator.setFeedbackUnderlay(this, background);
}

@Override
Expand Down Expand Up @@ -344,20 +300,12 @@ public void setNeedsOffscreenAlphaCompositing(boolean needsOffscreenAlphaComposi
}

public void setBorderWidth(int position, float width) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setBorderWidth(
this, LogicalEdge.values()[position], PixelUtil.toDIPFromPixel(width));
} else {
getOrCreateReactViewBackground().setBorderWidth(position, width);
}
BackgroundStyleApplicator.setBorderWidth(
this, LogicalEdge.values()[position], PixelUtil.toDIPFromPixel(width));
}

public void setBorderColor(int position, @Nullable Integer color) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setBorderColor(this, LogicalEdge.values()[position], color);
} else {
getOrCreateReactViewBackground().setBorderColor(position, color);
}
BackgroundStyleApplicator.setBorderColor(this, LogicalEdge.values()[position], color);
}

/**
Expand All @@ -373,34 +321,21 @@ public void setBorderRadius(float borderRadius) {
*/
@Deprecated(since = "0.75.0", forRemoval = true)
public void setBorderRadius(float borderRadius, int position) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
@Nullable
LengthPercentage radius =
Float.isNaN(borderRadius)
? null
: new LengthPercentage(borderRadius, LengthPercentageType.POINT);
BackgroundStyleApplicator.setBorderRadius(this, BorderRadiusProp.values()[position], radius);
} else {
getOrCreateReactViewBackground().setRadius(borderRadius, position);
}
@Nullable
LengthPercentage radius =
Float.isNaN(borderRadius)
? null
: new LengthPercentage(borderRadius, LengthPercentageType.POINT);
BackgroundStyleApplicator.setBorderRadius(this, BorderRadiusProp.values()[position], radius);
}

public void setBorderRadius(BorderRadiusProp property, @Nullable LengthPercentage borderRadius) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setBorderRadius(this, property, borderRadius);
} else {
CSSBackgroundDrawable backgroundDrawable = getOrCreateReactViewBackground();
backgroundDrawable.setBorderRadius(property, borderRadius);
}
BackgroundStyleApplicator.setBorderRadius(this, property, borderRadius);
}

public void setBorderStyle(@Nullable String style) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
BackgroundStyleApplicator.setBorderStyle(
this, style == null ? null : BorderStyle.fromString(style));
} else {
getOrCreateReactViewBackground().setBorderStyle(style);
}
BackgroundStyleApplicator.setBorderStyle(
this, style == null ? null : BorderStyle.fromString(style));
}

@Override
Expand Down Expand Up @@ -861,39 +796,8 @@ private boolean needsIsolatedLayer() {

@VisibleForTesting
public int getBackgroundColor() {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
@Nullable Integer color = BackgroundStyleApplicator.getBackgroundColor(this);
return color == null ? DEFAULT_BACKGROUND_COLOR : color;
} else {
if (getBackground() != null) {
return ((CSSBackgroundDrawable) getBackground()).getColor();
}
return DEFAULT_BACKGROUND_COLOR;
}
}

/* package */ CSSBackgroundDrawable getOrCreateReactViewBackground() {
if (mCSSBackgroundDrawable == null) {
mCSSBackgroundDrawable = new CSSBackgroundDrawable(getContext());
Drawable backgroundDrawable = getBackground();
updateBackgroundDrawable(
null); // required so that drawable callback is cleared before we add the
// drawable back as a part of LayerDrawable
if (backgroundDrawable == null) {
updateBackgroundDrawable(mCSSBackgroundDrawable);
} else {
LayerDrawable layerDrawable =
new LayerDrawable(new Drawable[] {mCSSBackgroundDrawable, backgroundDrawable});
updateBackgroundDrawable(layerDrawable);
}
if (!ReactNativeFeatureFlags.setAndroidLayoutDirection()) {
mCSSBackgroundDrawable.setLayoutDirectionOverride(
I18nUtil.getInstance().isRTL(getContext())
? LAYOUT_DIRECTION_RTL
: LAYOUT_DIRECTION_LTR);
}
}
return mCSSBackgroundDrawable;
@Nullable Integer color = BackgroundStyleApplicator.getBackgroundColor(this);
return color == null ? DEFAULT_BACKGROUND_COLOR : color;
}

@Override
Expand Down Expand Up @@ -983,33 +887,10 @@ && needsIsolatedLayer()) {

@Override
protected void dispatchDraw(Canvas canvas) {
if (ReactNativeFeatureFlags.enableBackgroundStyleApplicator()) {
if (mOverflow != Overflow.VISIBLE || getTag(R.id.filter) != null) {
BackgroundStyleApplicator.clipToPaddingBox(this, canvas);
}
super.dispatchDraw(canvas);
return;
}

try {
dispatchOverflowDraw(canvas);
super.dispatchDraw(canvas);
} catch (NullPointerException | StackOverflowError e) {
// Adding special exception management for StackOverflowError for logging purposes.
// This will be removed in the future.
RootView rootView = RootViewUtil.getRootView(ReactViewGroup.this);
if (rootView != null) {
rootView.handleException(e);
} else {
if (getContext() instanceof ReactContext) {
ReactContext reactContext = (ReactContext) getContext();
reactContext.handleException(
new IllegalViewOperationException("StackOverflowException", this, e));
} else {
throw e;
}
}
if (mOverflow != Overflow.VISIBLE || getTag(R.id.filter) != null) {
BackgroundStyleApplicator.clipToPaddingBox(this, canvas);
}
super.dispatchDraw(canvas);
}

@Override
Expand Down Expand Up @@ -1048,83 +929,6 @@ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return result;
}

private void dispatchOverflowDraw(Canvas canvas) {
Overflow tempOverflow = mOverflow;

// If the view contains a filter, we clip to the padding box.
if (getTag(R.id.filter) != null) {
tempOverflow = Overflow.HIDDEN;
}

switch (tempOverflow) {
case VISIBLE:
if (mPath != null) {
mPath.rewind();
}
break;
case HIDDEN:
case SCROLL:
float left = 0f;
float top = 0f;
float right = getWidth();
float bottom = getHeight();

boolean hasClipPath = false;

if (mCSSBackgroundDrawable != null) {
final RectF borderWidth = mCSSBackgroundDrawable.getDirectionAwareBorderInsets();

if (borderWidth.top > 0
|| borderWidth.left > 0
|| borderWidth.bottom > 0
|| borderWidth.right > 0) {
left += borderWidth.left;
top += borderWidth.top;
right -= borderWidth.right;
bottom -= borderWidth.bottom;
}

final ComputedBorderRadius borderRadius =
mCSSBackgroundDrawable.getComputedBorderRadius();

if (borderRadius.hasRoundedBorders()) {
if (mPath == null) {
mPath = new Path();
}

CornerRadii topLeftRadius = borderRadius.getTopLeft().toPixelFromDIP();
CornerRadii topRightRadius = borderRadius.getTopRight().toPixelFromDIP();
CornerRadii bottomLeftRadius = borderRadius.getBottomLeft().toPixelFromDIP();
CornerRadii bottomRightRadius = borderRadius.getBottomRight().toPixelFromDIP();

mPath.rewind();
mPath.addRoundRect(
new RectF(left, top, right, bottom),
new float[] {
Math.max(topLeftRadius.getHorizontal() - borderWidth.left, 0),
Math.max(topLeftRadius.getVertical() - borderWidth.top, 0),
Math.max(topRightRadius.getHorizontal() - borderWidth.right, 0),
Math.max(topRightRadius.getVertical() - borderWidth.top, 0),
Math.max(bottomRightRadius.getHorizontal() - borderWidth.right, 0),
Math.max(bottomRightRadius.getVertical() - borderWidth.bottom, 0),
Math.max(bottomLeftRadius.getHorizontal() - borderWidth.left, 0),
Math.max(bottomLeftRadius.getVertical() - borderWidth.bottom, 0),
},
Path.Direction.CW);
canvas.clipPath(mPath);
hasClipPath = true;
}
}

if (!hasClipPath) {
canvas.clipRect(new RectF(left, top, right, bottom));
}
break;
default:
break;
}
}

public void setOpacityIfPossible(float opacity) {
mBackfaceOpacity = opacity;
setBackfaceVisibilityDependantOpacity();
Expand Down
Loading

0 comments on commit 6291ff0

Please sign in to comment.