Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Building accessibility elements tree from unmerged root node #1750

Open
wants to merge 4 commits into
base: jb-main
Choose a base branch
from
Open
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 @@ -90,9 +90,15 @@ class ComponentsAccessibilitySemanticTest {

assertAccessibilityTree {
node {
isAccessibilityElement = true
label = "Content"
traits(UIAccessibilityTraitButton)
node {
isAccessibilityElement = true
label = "Content"
traits(UIAccessibilityTraitButton)
}
node {
isAccessibilityElement = false
label = "Content"
}
}
node {
isAccessibilityElement = true
Expand Down Expand Up @@ -682,4 +688,37 @@ class ComponentsAccessibilitySemanticTest {
}
}
}

@Test
fun testChildrenOfCollapsedNode() = runUIKitInstrumentedTest {
setContentWithAccessibilityEnabled {
Column {
Row(modifier = Modifier.testTag("row").clickable {}) {
Text("Foo", modifier = Modifier.testTag("row_title"))
Text("Bar", modifier = Modifier.testTag("row_subtitle"))
}
}
}

assertAccessibilityTree {
node {
label = "Foo\nBar"
identifier = "row"
isAccessibilityElement = true
traits(UIAccessibilityTraitButton)
}
node {
label = "Foo"
identifier = "row_title"
isAccessibilityElement = false
traits(UIAccessibilityTraitStaticText)
}
node {
label = "Bar"
identifier = "row_subtitle"
isAccessibilityElement = false
traits(UIAccessibilityTraitStaticText)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ class LayersAccessibilityTest {
label = "Root"
}
node {
label = "Popup 1"
}
node {
label = "Popup 2"
node {
label = "Popup 1"
}
node {
label = "Popup 2"
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,9 @@ data class AccessibilityTestNode(
* Removes all element that are not accessibility elements or does not work as elements containers.
*/
internal fun AccessibilityTestNode.normalized(): AccessibilityTestNode? {
val normalizedChildren = children?.flatMap {
it.normalized()?.let {
if (it.hasAccessibilityComponents) {
val normalizedChildren = children?.flatMap { child ->
child.normalized()?.let {
if (it.hasAccessibilityComponents || (it.children?.count() ?: 0) > 1) {
listOf(it)
} else {
it.children
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,10 @@ private sealed interface AccessibilityNode {
*/
class Semantics(
private val semanticsNode: SemanticsNode,
private val mediator: AccessibilityMediator
private val mediator: AccessibilityMediator,
private val ignoreSemanticChildren: Boolean = false
) : AccessibilityNode {
private val cachedConfig = semanticsNode.config
private val cachedConfig = semanticsNode.copyWithMergingEnabled().config

override val key: AccessibilityElementKey
get() = semanticsNode.semanticsKey
Expand All @@ -158,7 +159,11 @@ private sealed interface AccessibilityNode {
get() = mediator.convertToAppWindowCGRect(semanticsNode.boundsInWindow)

override val accessibilityInteropView: InteropWrappingView?
get() = cachedConfig.getOrNull(NativeAccessibilityViewSemanticsKey)
get() = if (ignoreSemanticChildren) {
null
} else {
cachedConfig.getOrNull(NativeAccessibilityViewSemanticsKey)
}

override val accessibilityLabel: String?
get() = cachedConfig.accessibilityLabel()
Expand Down Expand Up @@ -509,6 +514,7 @@ private class AccessibilityElement(
val indent = " ".repeat(depth * 2)
logger.apply {
log("${indent}${key}")
log("$indent isAccessibilityElement: ${isAccessibilityElement()}")
log("$indent containmentChain: ${debugContainmentChain()}")
log("$indent accessibilityLabel: ${accessibilityLabel()}")
log("$indent accessibilityValue: ${accessibilityValue()}")
Expand Down Expand Up @@ -822,16 +828,20 @@ internal class AccessibilityMediator(
// To align behavior, flatting the node with all its children and arranging them
// inside the synthetic container node.
val containerElement = createOrUpdateAccessibilityElement(
node = AccessibilityNode.Semantics(node, mediator = this),
node = AccessibilityNode.Semantics(
semanticsNode = node,
mediator = this,
ignoreSemanticChildren = true
),
children = emptyList()
)
createOrUpdateAccessibilityElement(
node = AccessibilityNode.Container(node, mediator = this),
node = AccessibilityNode.Container(containerNode = node, mediator = this),
children = listOf(containerElement) + children
)
} else {
createOrUpdateAccessibilityElement(
node = AccessibilityNode.Semantics(node, mediator = this),
node = AccessibilityNode.Semantics(semanticsNode = node, mediator = this),
children = children
)
}
Expand Down Expand Up @@ -869,7 +879,7 @@ internal class AccessibilityMediator(
* Performs a complete sync of the accessibility tree with the current semantics tree.
*/
private fun completeSync(): NodesSyncResult {
val rootSemanticsNode = owner.rootSemanticsNode
val rootSemanticsNode = owner.unmergedRootSemanticsNode

check(!view.isAccessibilityElement) {
"Root view must not be an accessibility element"
Expand Down