Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ open class NativeProxy {
return
}
pseudoSelectorManager.invalidate()
cssPlatformTransitionsManager.invalidate()
if (mHybridData.isValid) {
invalidateCpp()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,47 @@ internal class CSSPlatformTransitionReconciler(
private val repair: () -> Boolean,
) {
/** Keyed by window: getViewTreeObserver is per window, not per view. */
private val tracked = HashSet<ViewTreeObserver>()
private val tracked = HashMap<ViewTreeObserver, ViewTreeObserver.OnPreDrawListener>()

fun track(view: View) {
// A window torn down mid-animation never draws again, so its listener never retires.
tracked.removeAll { !it.isAlive }
tracked.keys.removeAll { !it.isAlive }

// A detached view hands out a temporary observer that is killed once the view attaches
// and its listeners are merged into the window's, leaving nothing to remove them from.
if (!view.isAttachedToWindow) return

val observer = view.viewTreeObserver
if (!observer.isAlive || !tracked.add(observer)) return
if (!observer.isAlive || tracked.containsKey(observer)) return
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

observer.addOnPreDrawListener(
val listener =
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
if (repair()) return true

// Retire here, not when the registry empties: a cancel fires its end
// callback mid-replacement, when the registry is briefly empty.
if (observer.isAlive) observer.removeOnPreDrawListener(this)
tracked.remove(observer)
retire(observer, this)
return true
}
},
)
}
tracked[observer] = listener
observer.addOnPreDrawListener(listener)
}

/** The listeners live on the window, which outlives the React context. */
fun invalidate() {
tracked.forEach { (observer, listener) ->
if (observer.isAlive) observer.removeOnPreDrawListener(listener)
}
tracked.clear()
}

private fun retire(
observer: ViewTreeObserver,
listener: ViewTreeObserver.OnPreDrawListener,
) {
if (observer.isAlive) observer.removeOnPreDrawListener(listener)
tracked.remove(observer)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ internal class CSSPlatformTransitionsManager(
private val reconciler = CSSPlatformTransitionReconciler(::repairClobberedValues)
private val startTokens = HashMap<Key, Long>()

@Volatile
private var invalidated = false

private var nextStartToken = 0L
private val interpolators = HashMap<InterpolatorKey, TimeInterpolator>()

Expand Down Expand Up @@ -75,12 +78,14 @@ internal class CSSPlatformTransitionsManager(
easingPointsY: FloatArray,
persistent: Boolean,
): Boolean {
if (invalidated) return false
val writer = cssPropertyWriterFor(propertyName) ?: return false
val context = reactContext.get() ?: return false
val scale = DurationScale.effectiveScale(context)
if (scale <= 0f) return false

UiThreadUtil.runOnUiThread {
if (invalidated) return@runOnUiThread
val key = Key(viewTag, propertyName)
// A fresh token invalidates any retry still queued for this key.
val token = ++nextStartToken
Expand All @@ -96,6 +101,21 @@ internal class CSSPlatformTransitionsManager(
return true
}

/** Animators keep ticking on their own, so a dead context has to stop them. */
fun invalidate() {
// Set before posting: a start already queued would otherwise register a new
// animator and listener behind the cleanup.
invalidated = true
UiThreadUtil.runOnUiThread {
startTokens.clear()
// Snapshot first: cancel() runs onAnimationEnd, which reads the map.
val running = animators.values.toList()
animators.clear()
running.forEach { it.animator.cancel() }
reconciler.invalidate()
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

fun removeTransition(
viewTag: Int,
propertyName: String,
Expand Down
Loading