@@ -3,51 +3,37 @@ package com.wix.detox.inquiry
33import android.util.Log
44import android.view.View
55import com.facebook.react.bridge.ReadableMap
6- import com.facebook.react.fabric.mounting.SurfaceMountingManager
6+ import com.facebook.react.fabric.FabricUIManager
77import com.wix.detox.inquiry.ViewLifecycleRegistry.markAnimated
88import com.wix.detox.inquiry.ViewLifecycleRegistry.markMounted
99import com.wix.detox.inquiry.ViewLifecycleRegistry.markUpdated
10- import com.wix.detox.inquiry.ViewLifecycleRegistry.markCustomEvent
1110
1211/* *
13- * Hook into React Native's Fabric new architecture to track view lifecycle events.
14- * This hooks into the exact points where views are mounted, updated, and animated.
12+ * Hook into React Native's Fabric new architecture to track animated views.
13+ * This provides precise tracking by intercepting the exact points where animated
14+ * properties are applied to views in Fabric.
1515 */
1616object DetoxFabricAnimationHook {
1717 private const val LOG_TAG = " DetoxFabricHook"
1818
1919 /* *
20- * Hook into IntBufferBatchMountItem.execute() to track animated view updates.
21- * This is called when animated props are applied to views in Fabric .
20+ * Hook into FabricUIManager.synchronouslyUpdateViewOnUIThread to track animated updates.
21+ * This marks views as animated whenever there's any animation activity, giving lots of false positives .
2222 */
23- fun hookIntBufferBatchMountItem (
24- viewTag : Int ,
23+ fun hookSynchronouslyUpdateViewOnUIThread (
24+ reactTag : Int ,
2525 props : ReadableMap ? ,
26- surfaceMountingManager : SurfaceMountingManager
26+ fabricUIManager : FabricUIManager
2727 ) {
2828 try {
2929 // Get the actual Android View
30- val androidView = getViewByTag(surfaceMountingManager, viewTag )
30+ val androidView = fabricUIManager.resolveView(reactTag )
3131 if (androidView == null ) {
32- Log .d(LOG_TAG , " View not found for tag: $viewTag " )
32+ Log .d(LOG_TAG , " View not found for tag: $reactTag " )
3333 return
3434 }
3535
36- // Check if this is an animated update
37- if (isAnimatedPropsUpdate(props)) {
38- Log .d(LOG_TAG , " Animated props update for view tag: $viewTag " )
39- markAnimated(androidView)
40-
41- // Log problematic animations
42- if (isProblematicAnimation(props)) {
43- Log .w(LOG_TAG , " Problematic animation detected for view tag: $viewTag " )
44- markCustomEvent(androidView, " problematic_animation" )
45- }
46- } else {
47- // Regular props update
48- markUpdated(androidView)
49- }
50-
36+ markAnimated(androidView)
5137 } catch (e: Exception ) {
5238 Log .w(LOG_TAG , " Failed to hook animated view update" , e)
5339 }
@@ -57,100 +43,20 @@ object DetoxFabricAnimationHook {
5743 * Hook into view mount operations to track when views are created.
5844 */
5945 fun hookViewMount (
60- viewTag : Int ,
61- surfaceMountingManager : SurfaceMountingManager
46+ reactTag : Int ,
47+ fabricUIManager : FabricUIManager
6248 ) {
6349 try {
64- val androidView = getViewByTag(surfaceMountingManager, viewTag )
50+ val androidView = fabricUIManager.resolveView(reactTag )
6551 if (androidView != null ) {
66- Log .d(LOG_TAG , " View mounted with tag: $viewTag " )
52+ Log .d(LOG_TAG , " View mounted with tag: $reactTag " )
6753 markMounted(androidView)
6854 }
6955 } catch (e: Exception ) {
7056 Log .w(LOG_TAG , " Failed to hook view mount" , e)
7157 }
7258 }
7359
74- /* *
75- * Get Android View by React Native view tag using reflection.
76- * This works around the fact that SurfaceMountingManager doesn't expose a direct getView method.
77- */
78- private fun getViewByTag (
79- surfaceMountingManager : SurfaceMountingManager ,
80- viewTag : Int
81- ): View ? {
82- return try {
83- // Use reflection to access the internal view registry
84- val viewRegistryField = surfaceMountingManager.javaClass.getDeclaredField(" mViewRegistry" )
85- viewRegistryField.isAccessible = true
86- val viewRegistry = viewRegistryField.get(surfaceMountingManager)
87-
88- // Get the view from the registry
89- val getViewMethod = viewRegistry.javaClass.getMethod(" getView" , Int ::class .java)
90- getViewMethod.invoke(viewRegistry, viewTag) as ? View
91- } catch (e: Exception ) {
92- Log .w(LOG_TAG , " Failed to get view by tag: $viewTag " , e)
93- null
94- }
95- }
96-
97- /* *
98- * Check if this is an animated props update by looking for animated properties.
99- */
100- private fun isAnimatedPropsUpdate (props : ReadableMap ? ): Boolean {
101- if (props == null ) return false
102-
103- val animatedKeys = setOf (
104- " transform" , " opacity" , " scaleX" , " scaleY" , " scale" ,
105- " translateX" , " translateY" , " rotateX" , " rotateY" , " rotateZ" ,
106- " backgroundColor" , " borderRadius" , " borderWidth"
107- )
108-
109- val iterator = props.keySetIterator()
110- while (iterator.hasNextKey()) {
111- val key = iterator.nextKey()
112- if (animatedKeys.any { key.contains(it, ignoreCase = true ) }) {
113- return true
114- }
115- }
116-
117- return false
118- }
119-
120- /* *
121- * Check if this animation might be problematic (infinite loops, conflicting animations, etc.).
122- */
123- private fun isProblematicAnimation (props : ReadableMap ? ): Boolean {
124- if (props == null ) return false
125-
126- // Check for potential infinite loop patterns
127- val transformKeys = mutableSetOf<String >()
128- val iterator = props.keySetIterator()
129-
130- while (iterator.hasNextKey()) {
131- val key = iterator.nextKey()
132- if (key.contains(" transform" , ignoreCase = true )) {
133- transformKeys.add(key)
134- }
135- }
136-
137- // Multiple transform properties might indicate conflicting animations
138- if (transformKeys.size > 3 ) {
139- Log .w(LOG_TAG , " Multiple transform properties detected: $transformKeys " )
140- return true
141- }
142-
143- // Check for opacity animations that might cause issues
144- if (props.hasKey(" opacity" )) {
145- val opacity = props.getDouble(" opacity" )
146- if (opacity < 0.0 || opacity > 1.0 ) {
147- Log .w(LOG_TAG , " Invalid opacity value: $opacity " )
148- return true
149- }
150- }
151-
152- return false
153- }
15460
15561 /* *
15662 * Get view coordinates for highlighting
@@ -167,21 +73,6 @@ object DetoxFabricAnimationHook {
16773 return coords
16874 }
16975
170- /* *
171- * Get view coordinates relative to root view
172- */
173- fun getViewCoordinatesRelativeToRoot (view : View , rootView : View ): IntArray {
174- val viewCoords = getViewCoordinates(view)
175- val rootCoords = getViewCoordinates(rootView)
176-
177- return intArrayOf(
178- viewCoords[0 ] - rootCoords[0 ], // Relative X
179- viewCoords[1 ] - rootCoords[1 ], // Relative Y
180- viewCoords[2 ], // Width
181- viewCoords[3 ] // Height
182- )
183- }
184-
18576 /* *
18677 * Log current registry statistics
18778 */
0 commit comments