diff --git a/CHANGELOG.md b/CHANGELOG.md index a1bb8a6656f..b3a7262f49e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Fixes - Allow multiple UncaughtExceptionHandlerIntegrations to be active at the same time ([#4462](https://github.com/getsentry/sentry-java/pull/4462)) +- Prevent repeated scroll target determination during a single scroll gesture ([#4557](https://github.com/getsentry/sentry-java/pull/4557)) + - This should reduce the number of ANRs seen in `SentryGestureListener` ## 8.17.0 diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java index f0183b21b56..7ffc5d2412f 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java @@ -139,6 +139,7 @@ public boolean onScroll( options .getLogger() .log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured."); + scrollState.type = GestureType.Scroll; return false; } else { options diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt index fcb07735760..3dd1f726d7b 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt @@ -11,11 +11,13 @@ import android.widget.AbsListView import android.widget.ListAdapter import androidx.core.view.ScrollingView import io.sentry.Breadcrumb +import io.sentry.ILogger import io.sentry.IScope import io.sentry.IScopes import io.sentry.PropagationContext import io.sentry.Scope import io.sentry.ScopeCallback +import io.sentry.SentryLevel import io.sentry.SentryLevel.INFO import io.sentry.android.core.SentryAndroidOptions import kotlin.test.Test @@ -28,6 +30,7 @@ import org.mockito.kotlin.doAnswer import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock import org.mockito.kotlin.never +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever @@ -56,7 +59,7 @@ class SentryGestureListenerScrollTest { val directions = setOf("up", "down", "left", "right") internal inline fun getSut( - resourceName: String = "test_scroll_view", + resourceName: String? = "test_scroll_view", touchWithinBounds: Boolean = true, direction: String = "", ): SentryGestureListener { @@ -229,6 +232,22 @@ class SentryGestureListenerScrollTest { verify(fixture.scope).propagationContext = any() } + @Test + fun `logs error message only once per gesture when no scroll target is found`() { + val logger = mock() + fixture.options.setLogger(logger) + fixture.options.isDebug = true + val sut = fixture.getSut(resourceName = null) + + sut.onDown(fixture.firstEvent) + fixture.eventsInBetween.forEach { sut.onScroll(fixture.firstEvent, it, 10.0f, 0f) } + sut.onUp(fixture.endEvent) + + // Verify that the error message is logged only once during the entire gesture + verify(logger, times(1)) + .log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured.") + } + internal class ScrollableView : View(mock()), ScrollingView { override fun computeVerticalScrollOffset(): Int = 0 diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/ViewHelpers.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/ViewHelpers.kt index 339357bea39..86123d0a3a2 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/ViewHelpers.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/ViewHelpers.kt @@ -67,6 +67,11 @@ internal inline fun mockView( return mockView } -internal fun Resources.mockForTarget(target: View, expectedResourceName: String) { - whenever(getResourceEntryName(target.id)).thenReturn(expectedResourceName) +internal fun Resources.mockForTarget(target: View, expectedResourceName: String?) { + if (expectedResourceName == null) { + whenever(getResourceEntryName(target.id)) + .thenThrow(Resources.NotFoundException("res not found")) + } else { + whenever(getResourceEntryName(target.id)).thenReturn(expectedResourceName) + } }