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 @@ -4,6 +4,7 @@

### Fixes

- Session Replay: Fix `VerifyError` in Compose masking under DexGuard/R8 obfuscation ([#5507](https://github.com/getsentry/sentry-java/pull/5507))
- Session Replay: Fix Compose view masking not working on obfuscated/minified builds ([#5503](https://github.com/getsentry/sentry-java/pull/5503))

## 8.43.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

package io.sentry.android.replay.util

import android.graphics.Rect
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.findRootCoordinates
import androidx.compose.ui.node.LayoutNode
import androidx.compose.ui.text.TextLayoutResult
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.roundToInt

internal class ComposeTextLayout(internal val layout: TextLayoutResult) : TextLayout {
Expand Down Expand Up @@ -176,7 +178,7 @@ internal fun LayoutCoordinates.boundsInWindow(rootCoordinates: LayoutCoordinates
val boundsBottom = bounds.bottom.fastCoerceIn(0f, rootHeight)
Comment thread
sentry-warden[bot] marked this conversation as resolved.

if (boundsLeft == boundsRight || boundsTop == boundsBottom) {
return Rect()
return Rect(0.0f, 0.0f, 0.0f, 0.0f)
}

val topLeft = root.localToWindow(Offset(boundsLeft, boundsTop))
Expand All @@ -200,5 +202,18 @@ internal fun LayoutCoordinates.boundsInWindow(rootCoordinates: LayoutCoordinates
val top = fastMinOf(topLeftY, topRightY, bottomLeftY, bottomRightY)
Comment thread
sentry-warden[bot] marked this conversation as resolved.
val bottom = fastMaxOf(topLeftY, topRightY, bottomLeftY, bottomRightY)

return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
return Rect(left, top, right, bottom)
}

internal fun Rect.toRect(): android.graphics.Rect {
// Round outward (floor min edges, ceil max edges) so that a sub-pixel but non-empty Rect doesn't
// collapse to a zero-width/height android.graphics.Rect. Otherwise a node could be marked visible
// and maskable based on the float bounds, while the integer rect the MaskRenderer draws has zero
// area, leaving sensitive content unmasked. Rounding outward also biases toward over-masking.
return android.graphics.Rect(
floor(left).toInt(),
floor(top).toInt(),
ceil(right).toInt(),
ceil(bottom).toInt(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import io.sentry.android.replay.util.findPainter
import io.sentry.android.replay.util.findTextColor
import io.sentry.android.replay.util.isMaskable
import io.sentry.android.replay.util.toOpaque
import io.sentry.android.replay.util.toRect
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.GenericViewHierarchyNode
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.ImageViewHierarchyNode
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.TextViewHierarchyNode
Expand Down Expand Up @@ -157,8 +158,8 @@ internal object ComposeViewHierarchyNode {
// If we're unable to retrieve the semantics configuration
// we should play safe and mask the whole node.
return GenericViewHierarchyNode(
x = visibleRect.left.toFloat(),
y = visibleRect.top.toFloat(),
x = visibleRect.left,
y = visibleRect.top,
width = node.width,
height = node.height,
elevation = (parent?.elevation ?: 0f),
Expand All @@ -168,17 +169,17 @@ internal object ComposeViewHierarchyNode {
isImportantForContentCapture = false, // will be set by children
isVisible =
!SentryLayoutNodeHelper.isTransparent(node) &&
visibleRect.height() > 0 &&
visibleRect.width() > 0,
visibleRect = visibleRect,
visibleRect.height > 0 &&
visibleRect.width > 0,
visibleRect = visibleRect.toRect(),
)
}

val isVisible =
!SentryLayoutNodeHelper.isTransparent(node) &&
(semantics == null || !semantics.contains(SemanticsProperties.InvisibleToUser)) &&
visibleRect.height() > 0 &&
visibleRect.width() > 0
visibleRect.height > 0 &&
visibleRect.width > 0
Comment thread
cursor[bot] marked this conversation as resolved.
val isEditable =
semantics?.contains(SemanticsActions.SetText) == true ||
semantics?.contains(SemanticsProperties.EditableText) == true
Expand Down Expand Up @@ -213,8 +214,8 @@ internal object ComposeViewHierarchyNode {
null
},
dominantColor = textColor?.toArgb()?.toOpaque(),
x = visibleRect.left.toFloat(),
y = visibleRect.top.toFloat(),
x = visibleRect.left,
y = visibleRect.top,
width = node.width,
height = node.height,
elevation = (parent?.elevation ?: 0f),
Expand All @@ -223,7 +224,7 @@ internal object ComposeViewHierarchyNode {
shouldMask = shouldMask,
isImportantForContentCapture = true,
isVisible = isVisible,
visibleRect = visibleRect,
visibleRect = visibleRect.toRect(),
)
}
else -> {
Expand All @@ -233,8 +234,8 @@ internal object ComposeViewHierarchyNode {

parent?.setImportantForCaptureToAncestors(true)
ImageViewHierarchyNode(
x = visibleRect.left.toFloat(),
y = visibleRect.top.toFloat(),
x = visibleRect.left,
y = visibleRect.top,
width = node.width,
height = node.height,
elevation = (parent?.elevation ?: 0f),
Expand All @@ -243,7 +244,7 @@ internal object ComposeViewHierarchyNode {
isVisible = isVisible,
isImportantForContentCapture = true,
shouldMask = shouldMask && painter.isMaskable(),
visibleRect = visibleRect,
visibleRect = visibleRect.toRect(),
)
} else {
val shouldMask = isVisible && semantics.shouldMask(isImage = false, options)
Expand All @@ -252,8 +253,8 @@ internal object ComposeViewHierarchyNode {
// TODO: traverse the ViewHierarchyNode here again. For now we can recommend
// TODO: using custom modifiers to obscure the entire node if it's sensitive
GenericViewHierarchyNode(
x = visibleRect.left.toFloat(),
y = visibleRect.top.toFloat(),
x = visibleRect.left,
y = visibleRect.top,
width = node.width,
height = node.height,
elevation = (parent?.elevation ?: 0f),
Expand All @@ -262,7 +263,7 @@ internal object ComposeViewHierarchyNode {
shouldMask = shouldMask,
isImportantForContentCapture = false, // will be set by children
isVisible = isVisible,
visibleRect = visibleRect,
visibleRect = visibleRect.toRect(),
)
}
}
Expand Down
Loading