Skip to content

Commit a964eca

Browse files
adityasharatfacebook-github-bot
authored andcommitted
Explicitly unsets all view properties on the host view
Summary: Explicitly unsets all view properties on the host view Reviewed By: zielinskimz Differential Revision: D61968547 fbshipit-source-id: bfc31f9eed94ff632a75d49dbdbe3f8d5eb15e71
1 parent 4f87436 commit a964eca

File tree

4 files changed

+157
-25
lines changed

4 files changed

+157
-25
lines changed

litho-core/src/main/java/com/facebook/litho/LithoNodeUtils.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.facebook.litho.MountSpecLithoRenderUnit.UpdateState
2727
import com.facebook.litho.annotations.ImportantForAccessibility
2828
import com.facebook.litho.config.LithoDebugConfigurations
2929
import com.facebook.litho.drawable.BorderColorDrawable
30+
import com.facebook.litho.host.HostViewAttributesCleanupBinder
3031
import com.facebook.rendercore.MountState
3132
import com.facebook.rendercore.RenderUnit
3233
import com.facebook.rendercore.primitives.Primitive
@@ -386,18 +387,27 @@ object LithoNodeUtils {
386387
}
387388
}
388389

390+
val config = node.headComponentContext.lithoConfiguration.componentsConfig
391+
389392
if (viewAttributes != null) {
390393
renderUnit.addOptionalMountBinder(
391394
ViewAttributesViewBinder.create(
392395
ViewAttributesViewBinder.Model(
393396
renderUnit = renderUnit,
394397
viewAttributes = viewAttributes,
395-
cloneStateListAnimators =
396-
node.headComponentContext.lithoConfiguration.componentsConfig
397-
.cloneStateListAnimators,
398+
cloneStateListAnimators = config.cloneStateListAnimators,
398399
isRootHost = id == MountState.ROOT_HOST_ID)))
399400
}
400401

402+
if (config.isHostViewAttributesCleanUpEnabled && component is HostComponent) {
403+
renderUnit.addOptionalMountBinder(
404+
RenderUnit.DelegateBinder.createDelegateBinder(
405+
model = null,
406+
binder = HostViewAttributesCleanupBinder(),
407+
) as RenderUnit.DelegateBinder<Any?, Any, Any>,
408+
)
409+
}
410+
401411
return renderUnit
402412
}
403413

litho-core/src/main/java/com/facebook/litho/ViewAttributes.kt

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ class ViewAttributes {
578578
view.setTag(ComponentHost.COMPONENT_NODE_INFO_ID, nodeInfo)
579579
}
580580

581-
private fun unsetAccessibilityDelegate(view: View) {
581+
fun unsetAccessibilityDelegate(view: View) {
582582
if (view !is ComponentHost && view.getTag(ComponentHost.COMPONENT_NODE_INFO_ID) == null) {
583583
return
584584
}
@@ -600,7 +600,7 @@ class ViewAttributes {
600600
view.isClickable = true
601601
}
602602

603-
private fun unsetClickHandler(view: View) {
603+
fun unsetClickHandler(view: View) {
604604
view.setOnClickListener(null)
605605
view.isClickable = false
606606
}
@@ -621,7 +621,7 @@ class ViewAttributes {
621621
}
622622
}
623623

624-
private fun unsetLongClickHandler(view: View) {
624+
fun unsetLongClickHandler(view: View) {
625625
val listener = getComponentLongClickListener(view)
626626
if (listener != null) {
627627
listener.eventHandler = null
@@ -665,7 +665,7 @@ class ViewAttributes {
665665
listener.eventHandler = focusChangeHandler
666666
}
667667

668-
private fun unsetFocusChangeHandler(view: View) {
668+
fun unsetFocusChangeHandler(view: View) {
669669
val listener = getComponentFocusChangeListener(view)
670670
if (listener != null) {
671671
listener.eventHandler = null
@@ -705,7 +705,7 @@ class ViewAttributes {
705705
}
706706
}
707707

708-
private fun unsetTouchHandler(view: View) {
708+
fun unsetTouchHandler(view: View) {
709709
val listener = getComponentTouchListener(view)
710710
if (listener != null) {
711711
listener.eventHandler = null
@@ -725,7 +725,7 @@ class ViewAttributes {
725725
}
726726
}
727727

728-
private fun unsetInterceptTouchEventHandler(view: View) {
728+
fun unsetInterceptTouchEventHandler(view: View) {
729729
if (view is ComponentHost) {
730730
view.setInterceptTouchEventHandler(null)
731731
}
@@ -755,7 +755,7 @@ class ViewAttributes {
755755
}
756756
}
757757

758-
private fun unsetViewId(view: View) {
758+
fun unsetViewId(view: View) {
759759
view.id = View.NO_ID
760760
}
761761

@@ -776,11 +776,11 @@ class ViewAttributes {
776776
}
777777
}
778778

779-
private fun unsetViewTag(view: View) {
779+
fun unsetViewTag(view: View) {
780780
view.tag = null
781781
}
782782

783-
private fun unsetViewTags(view: View, viewTags: SparseArray<Any>?) {
783+
fun unsetViewTags(view: View, viewTags: SparseArray<Any>?) {
784784
if (view is ComponentHost) {
785785
view.setViewTags(null)
786786
} else {
@@ -816,15 +816,15 @@ class ViewAttributes {
816816
}
817817
}
818818

819-
private fun unsetAmbientShadowColor(view: View, @ColorInt ambientShadowColor: Int) {
819+
fun unsetAmbientShadowColor(view: View, @ColorInt ambientShadowColor: Int) {
820820
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && ambientShadowColor != Color.BLACK) {
821821
// Android documentation says black is the default:
822822
// https://developer.android.com/reference/android/view/View#getOutlineAmbientShadowColor()
823823
view.outlineAmbientShadowColor = Color.BLACK
824824
}
825825
}
826826

827-
private fun unsetSpotShadowColor(view: View, @ColorInt spotShadowColor: Int) {
827+
fun unsetSpotShadowColor(view: View, @ColorInt spotShadowColor: Int) {
828828
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && spotShadowColor != Color.BLACK) {
829829
// Android documentation says black is the default:
830830
// https://developer.android.com/reference/android/view/View#getOutlineSpotShadowColor()
@@ -878,7 +878,7 @@ class ViewAttributes {
878878
view.contentDescription = contentDescription
879879
}
880880

881-
private fun unsetContentDescription(view: View) {
881+
fun unsetContentDescription(view: View) {
882882
view.contentDescription = null
883883
}
884884

@@ -899,7 +899,7 @@ class ViewAttributes {
899899
}
900900
}
901901

902-
private fun unsetFocusable(view: View, flags: Int) {
902+
fun unsetFocusable(view: View, flags: Int) {
903903
view.isFocusable = LithoMountData.isViewFocusable(flags)
904904
}
905905

@@ -950,7 +950,7 @@ class ViewAttributes {
950950
ViewCompat.setTooltipText(view, tooltipText)
951951
}
952952

953-
private fun unsetTooltipText(view: View) {
953+
fun unsetTooltipText(view: View) {
954954
ViewCompat.setTooltipText(view, null)
955955
}
956956

@@ -1036,7 +1036,7 @@ class ViewAttributes {
10361036
}
10371037

10381038
@Suppress("deprecation")
1039-
private fun setBackgroundCompat(view: View, drawable: Drawable?) {
1039+
fun setBackgroundCompat(view: View, drawable: Drawable?) {
10401040
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
10411041
view.setBackgroundDrawable(drawable)
10421042
} else {
@@ -1047,11 +1047,15 @@ class ViewAttributes {
10471047
private fun unsetViewForeground(view: View, attributes: ViewAttributes) {
10481048
val foreground = attributes.foreground
10491049
if (foreground != null) {
1050-
check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1051-
("MountState has a ViewAttributes with foreground however the current Android version doesn't support foreground on Views")
1052-
}
1053-
view.foreground = null
1050+
unsetForeground(view)
1051+
}
1052+
}
1053+
1054+
fun unsetForeground(view: View) {
1055+
check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1056+
("MountState has a ViewAttributes with foreground however the current Android version doesn't support foreground on Views")
10541057
}
1058+
view.foreground = null
10551059
}
10561060

10571061
private fun setViewLayoutDirection(view: View, attributes: ViewAttributes) {
@@ -1061,7 +1065,7 @@ class ViewAttributes {
10611065
view.layoutDirection = attributes.layoutDirection.getLayoutDirectionForView()
10621066
}
10631067

1064-
private fun unsetViewLayoutDirection(view: View) {
1068+
fun unsetViewLayoutDirection(view: View) {
10651069
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
10661070
return
10671071
}
@@ -1101,11 +1105,17 @@ class ViewAttributes {
11011105
if (attributes.stateListAnimator == null && attributes.stateListAnimatorRes == 0) {
11021106
return
11031107
}
1108+
unsetViewStateListAnimator(view)
1109+
}
1110+
1111+
fun unsetViewStateListAnimator(view: View) {
11041112
check(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
11051113
("MountState has a ViewAttributes with stateListAnimator, however the current Android version doesn't support stateListAnimator on Views")
11061114
}
1107-
view.stateListAnimator.jumpToCurrentState()
1108-
view.stateListAnimator = null
1115+
if (view.stateListAnimator != null) {
1116+
view.stateListAnimator.jumpToCurrentState()
1117+
view.stateListAnimator = null
1118+
}
11091119
}
11101120

11111121
private fun setViewLayerType(view: View, attributes: ViewAttributes) {

litho-core/src/main/java/com/facebook/litho/config/ComponentsConfiguration.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ internal constructor(
163163
* ultimate source of truth.
164164
*/
165165
@JvmField val useComponentTreePropContainerAsSourceOfTruth: Boolean = false,
166+
167+
/**
168+
* When enabled the framework will add an additional binder to Host RenderUnits to clean up view
169+
* attributes that may have been added by non-litho code.
170+
*/
171+
@JvmField val isHostViewAttributesCleanUpEnabled: Boolean = false,
166172
) {
167173

168174
val shouldAddRootHostViewOrDisableBgFgOutputs: Boolean =
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
2+
3+
package com.facebook.litho.host
4+
5+
import android.content.Context
6+
import android.os.Build
7+
import android.view.View
8+
import android.view.ViewOutlineProvider
9+
import androidx.core.view.ViewCompat
10+
import com.facebook.litho.ComponentHost
11+
import com.facebook.litho.ViewAttributes.Companion.setBackgroundCompat
12+
import com.facebook.litho.ViewAttributes.Companion.unsetAccessibilityDelegate
13+
import com.facebook.litho.ViewAttributes.Companion.unsetAmbientShadowColor
14+
import com.facebook.litho.ViewAttributes.Companion.unsetClickHandler
15+
import com.facebook.litho.ViewAttributes.Companion.unsetContentDescription
16+
import com.facebook.litho.ViewAttributes.Companion.unsetFocusChangeHandler
17+
import com.facebook.litho.ViewAttributes.Companion.unsetForeground
18+
import com.facebook.litho.ViewAttributes.Companion.unsetInterceptTouchEventHandler
19+
import com.facebook.litho.ViewAttributes.Companion.unsetLongClickHandler
20+
import com.facebook.litho.ViewAttributes.Companion.unsetSpotShadowColor
21+
import com.facebook.litho.ViewAttributes.Companion.unsetTooltipText
22+
import com.facebook.litho.ViewAttributes.Companion.unsetTouchHandler
23+
import com.facebook.litho.ViewAttributes.Companion.unsetViewId
24+
import com.facebook.litho.ViewAttributes.Companion.unsetViewLayoutDirection
25+
import com.facebook.litho.ViewAttributes.Companion.unsetViewStateListAnimator
26+
import com.facebook.litho.ViewAttributes.Companion.unsetViewTag
27+
import com.facebook.litho.ViewAttributes.Companion.unsetViewTags
28+
import com.facebook.rendercore.Host
29+
import com.facebook.rendercore.RenderUnit
30+
31+
class HostViewAttributesCleanupBinder : RenderUnit.Binder<Any?, Host, Any> {
32+
33+
override fun shouldUpdate(
34+
currentModel: Any?,
35+
newModel: Any?,
36+
currentLayoutData: Any?,
37+
nextLayoutData: Any?
38+
): Boolean = false
39+
40+
override fun bind(context: Context, content: Host, model: Any?, layoutData: Any?): Any? {
41+
return null
42+
}
43+
44+
override fun unbind(
45+
context: Context,
46+
content: Host,
47+
model: Any?,
48+
layoutData: Any?,
49+
bindData: Any?
50+
) {
51+
unsetAllViewAttributes(content)
52+
}
53+
}
54+
55+
private fun unsetAllViewAttributes(content: Host) {
56+
57+
if (content is ComponentHost) {
58+
content.setSafeViewModificationsEnabled(true)
59+
}
60+
61+
content.visibility = View.VISIBLE
62+
unsetClickHandler(content)
63+
unsetLongClickHandler(content)
64+
unsetFocusChangeHandler(content)
65+
unsetTouchHandler(content)
66+
unsetInterceptTouchEventHandler(content)
67+
unsetViewId(content)
68+
unsetViewTag(content)
69+
unsetViewTags(content, null)
70+
unsetViewStateListAnimator(content)
71+
ViewCompat.setElevation(content, 0f)
72+
unsetAmbientShadowColor(content, -1)
73+
unsetSpotShadowColor(content, -1)
74+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
75+
content.outlineProvider = ViewOutlineProvider.BACKGROUND
76+
}
77+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
78+
content.clipToOutline = false
79+
}
80+
content.clipChildren = true
81+
unsetContentDescription(content)
82+
unsetTooltipText(content)
83+
content.scaleX = 1f
84+
content.scaleY = 1f
85+
content.alpha = 1f
86+
content.rotation = 0f
87+
content.rotationX = 0f
88+
content.rotationY = 0f
89+
content.isClickable = true
90+
content.isLongClickable = true
91+
content.isFocusable = false
92+
content.isEnabled = true
93+
content.isSelected = false
94+
ViewCompat.setKeyboardNavigationCluster(content, false)
95+
ViewCompat.setImportantForAccessibility(content, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
96+
unsetAccessibilityDelegate(content)
97+
setBackgroundCompat(content, null)
98+
unsetForeground(content)
99+
unsetViewLayoutDirection(content)
100+
content.setLayerType(View.LAYER_TYPE_NONE, null)
101+
ViewCompat.setSystemGestureExclusionRects(content, emptyList())
102+
103+
if (content is ComponentHost) {
104+
content.setSafeViewModificationsEnabled(false)
105+
}
106+
}

0 commit comments

Comments
 (0)