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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
### Fixes

- Session Replay: Fix masking of non-styled `Text` Composables ([#4361](https://github.com/getsentry/sentry-java/pull/4361))
- Session Replay: Fix masking read-only `TextField` Composables ([#4362](https://github.com/getsentry/sentry-java/pull/4362))

## 8.10.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ internal object ComposeViewHierarchyNode {
return when {
isImage -> SentryReplayOptions.IMAGE_VIEW_CLASS_NAME
collapsedSemantics?.contains(SemanticsProperties.Text) == true ||
collapsedSemantics?.contains(SemanticsActions.SetText) == true -> SentryReplayOptions.TEXT_VIEW_CLASS_NAME
collapsedSemantics?.contains(SemanticsActions.SetText) == true ||
collapsedSemantics?.contains(SemanticsProperties.EditableText) == true -> SentryReplayOptions.TEXT_VIEW_CLASS_NAME
else -> "android.view.View"
}
}
Expand Down Expand Up @@ -87,7 +88,8 @@ internal object ComposeViewHierarchyNode {
val isVisible = !node.outerCoordinator.isTransparent() &&
(semantics == null || !semantics.contains(SemanticsProperties.InvisibleToUser)) &&
visibleRect.height() > 0 && visibleRect.width() > 0
val isEditable = semantics?.contains(SemanticsActions.SetText) == true
val isEditable = semantics?.contains(SemanticsActions.SetText) == true ||
semantics?.contains(SemanticsProperties.EditableText) == true
return when {
semantics?.contains(SemanticsProperties.Text) == true || isEditable -> {
val shouldMask = isVisible && node.shouldMask(isImage = false, options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ import androidx.compose.material3.TextField
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.editableText
import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -55,6 +58,7 @@ class ComposeMaskingOptionsTest {
System.setProperty("robolectric.areWindowsMarkedVisible", "true")
System.setProperty("robolectric.pixelCopyRenderMode", "hardware")
ComposeMaskingOptionsActivity.textModifierApplier = null
ComposeMaskingOptionsActivity.textFieldModifierApplier = null
ComposeMaskingOptionsActivity.containerModifierApplier = null
ComposeMaskingOptionsActivity.fontSizeApplier = null
}
Expand Down Expand Up @@ -90,6 +94,23 @@ class ComposeMaskingOptionsTest {
assertEquals("Random repo", (textNodes.first().layout as? ComposeTextLayout)?.layout?.layoutInput?.text?.text)
}

@Test
fun `when text input field is readOnly still masks it`() {
ComposeMaskingOptionsActivity.textFieldModifierApplier = {
// newer versions of compose basically do this when a TextField is readOnly
Modifier.clearAndSetSemantics { editableText = AnnotatedString("Placeholder") }
}
val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
shadowOf(Looper.getMainLooper()).idle()

val options = SentryOptions().apply {
sessionReplay.maskAllText = true
}

val textNodes = activity.get().collectNodesOfType<TextViewHierarchyNode>(options)
assertTrue(textNodes[1].shouldMask)
}

@Test
fun `when maskAllText is set to false all Text nodes are unmasked`() {
val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
Expand Down Expand Up @@ -231,6 +252,7 @@ private class ComposeMaskingOptionsActivity : ComponentActivity() {

companion object {
var textModifierApplier: (() -> Modifier)? = null
var textFieldModifierApplier: (() -> Modifier)? = null
var containerModifierApplier: (() -> Modifier)? = null
var fontSizeApplier: (() -> TextUnit)? = null
}
Expand All @@ -254,6 +276,7 @@ private class ComposeMaskingOptionsActivity : ComponentActivity() {
)
Text("Random repo", fontSize = fontSizeApplier?.invoke() ?: TextUnit.Unspecified)
TextField(
modifier = textFieldModifierApplier?.invoke() ?: Modifier,
value = TextFieldValue("Placeholder"),
onValueChange = { _ -> }
)
Expand Down
Loading