Skip to content

Commit 5546e1e

Browse files
committed
fix(replay): Use global visible rect when text layout is not laid out in Compose (#4361)
* WIP * fix(replay): Use global visible rect when text layout is not laid out in Compose * Revert * Changelog
1 parent 5b51c8f commit 5546e1e

File tree

4 files changed

+43
-9
lines changed

4 files changed

+43
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Session Replay: Fix inconsistent `segment_id` ([#4471](https://github.com/getsentry/sentry-java/pull/4471))
1414
- Session Replay: Do not capture current replay for cached events from the past ([#4474](https://github.com/getsentry/sentry-java/pull/4474))
1515
- Session Replay: Fix crash on devices with the Unisoc/Spreadtrum T606 chipset ([#4477](https://github.com/getsentry/sentry-java/pull/4477))
16+
- Session Replay: Fix masking of non-styled `Text` Composables ([#4361](https://github.com/getsentry/sentry-java/pull/4361))
1617

1718
## 7.22.5
1819

sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import androidx.compose.ui.semantics.SemanticsActions
1414
import androidx.compose.ui.semantics.SemanticsProperties
1515
import androidx.compose.ui.semantics.getOrNull
1616
import androidx.compose.ui.text.TextLayoutResult
17+
import androidx.compose.ui.unit.TextUnit
1718
import io.sentry.SentryLevel
1819
import io.sentry.SentryOptions
1920
import io.sentry.SentryReplayOptions
@@ -100,14 +101,19 @@ internal object ComposeViewHierarchyNode {
100101
?.invoke(textLayoutResults)
101102

102103
val (color, hasFillModifier) = node.findTextAttributes()
103-
var textColor = textLayoutResults.firstOrNull()?.layoutInput?.style?.color
104+
val textLayoutResult = textLayoutResults.firstOrNull()
105+
var textColor = textLayoutResult?.layoutInput?.style?.color
104106
if (textColor?.isUnspecified == true) {
105107
textColor = color
106108
}
107-
// TODO: support multiple text layouts
109+
val isLaidOut = textLayoutResult?.layoutInput?.style?.fontSize != TextUnit.Unspecified
108110
// TODO: support editable text (currently there's a way to get @Composable's padding only via reflection, and we can't reliably mask input fields based on TextLayout, so we mask the whole view instead)
109111
TextViewHierarchyNode(
110-
layout = if (textLayoutResults.isNotEmpty() && !isEditable) ComposeTextLayout(textLayoutResults.first(), hasFillModifier) else null,
112+
layout = if (textLayoutResult != null && !isEditable && isLaidOut) {
113+
ComposeTextLayout(textLayoutResult, hasFillModifier)
114+
} else {
115+
null
116+
},
111117
dominantColor = textColor?.toArgb()?.toOpaque(),
112118
x = visibleRect.left.toFloat(),
113119
y = visibleRect.top.toFloat(),

sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/ComposeMaskingOptionsTest.kt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import androidx.compose.ui.platform.testTag
1818
import androidx.compose.ui.semantics.invisibleToUser
1919
import androidx.compose.ui.semantics.semantics
2020
import androidx.compose.ui.text.input.TextFieldValue
21+
import androidx.compose.ui.unit.TextUnit
2122
import androidx.compose.ui.unit.dp
23+
import androidx.compose.ui.unit.sp
2224
import androidx.test.ext.junit.runners.AndroidJUnit4
2325
import coil.compose.AsyncImage
2426
import io.sentry.SentryOptions
@@ -39,6 +41,7 @@ import java.io.File
3941
import kotlin.test.Test
4042
import kotlin.test.assertEquals
4143
import kotlin.test.assertFalse
44+
import kotlin.test.assertNull
4245
import kotlin.test.assertTrue
4346

4447
@RunWith(AndroidJUnit4::class)
@@ -50,6 +53,7 @@ class ComposeMaskingOptionsTest {
5053
System.setProperty("robolectric.areWindowsMarkedVisible", "true")
5154
ComposeMaskingOptionsActivity.textModifierApplier = null
5255
ComposeMaskingOptionsActivity.containerModifierApplier = null
56+
ComposeMaskingOptionsActivity.fontSizeApplier = null
5357
}
5458

5559
@Test
@@ -63,8 +67,23 @@ class ComposeMaskingOptionsTest {
6367
val textNodes = activity.get().collectNodesOfType<TextViewHierarchyNode>(options)
6468
assertEquals(4, textNodes.size) // [TextField, Text, Button, Activity Title]
6569
assertTrue(textNodes.all { it.shouldMask })
66-
// just a sanity check for parsing the tree
67-
assertEquals("Random repo", (textNodes[1].layout as ComposeTextLayout).layout.layoutInput.text.text)
70+
// no fontSize specified - we don't use the text layout
71+
assertNull(textNodes.first().layout)
72+
}
73+
74+
@Test
75+
fun `when text is laid out nodes use it`() {
76+
ComposeMaskingOptionsActivity.fontSizeApplier = { 20.sp }
77+
val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
78+
shadowOf(Looper.getMainLooper()).idle()
79+
80+
val options = SentryOptions().apply {
81+
sessionReplay.maskAllText = true
82+
}
83+
84+
val textNodes = activity.get().collectNodesOfType<TextViewHierarchyNode>(options)
85+
// the text should be laid out when fontSize is specified
86+
assertEquals("Random repo", (textNodes.first().layout as? ComposeTextLayout)?.layout?.layoutInput?.text?.text)
6887
}
6988

7089
@Test
@@ -202,6 +221,7 @@ private class ComposeMaskingOptionsActivity : ComponentActivity() {
202221
companion object {
203222
var textModifierApplier: (() -> Modifier)? = null
204223
var containerModifierApplier: (() -> Modifier)? = null
224+
var fontSizeApplier: (() -> TextUnit)? = null
205225
}
206226

207227
override fun onCreate(savedInstanceState: Bundle?) {
@@ -221,11 +241,11 @@ private class ComposeMaskingOptionsActivity : ComponentActivity() {
221241
contentDescription = null,
222242
modifier = Modifier.padding(vertical = 16.dp)
223243
)
244+
Text("Random repo", fontSize = fontSizeApplier?.invoke() ?: TextUnit.Unspecified)
224245
TextField(
225246
value = TextFieldValue("Placeholder"),
226247
onValueChange = { _ -> }
227248
)
228-
Text("Random repo")
229249
Button(
230250
onClick = {},
231251
modifier = Modifier

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ fun Github(
106106
val scope = rememberCoroutineScope()
107107

108108
LaunchedEffect(perPage) {
109-
result = GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
109+
result = try {
110+
GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
111+
} catch (e: Throwable) {
112+
"error"
113+
}
110114
}
111115

112116
SentryTraced("github-$user") {
@@ -133,12 +137,15 @@ fun Github(
133137
user = newText
134138
}
135139
)
136-
Text("Random repo $result")
140+
Text("Random repo: $result")
137141
Button(
138142
onClick = {
139143
scope.launch {
140-
result =
144+
result = try {
141145
GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
146+
} catch (e: Throwable) {
147+
"error"
148+
}
142149
}
143150
},
144151
modifier = Modifier

0 commit comments

Comments
 (0)