Skip to content

Commit

Permalink
Fix CAccessibility not registering its listener(s) with ComposeAccess…
Browse files Browse the repository at this point in the history
…ibleComponent, preventing us from reporting changes to it.
  • Loading branch information
m-sasha committed Oct 7, 2023
1 parent 5b48791 commit 5ce7be0
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,35 +70,35 @@ internal class AccessibilityControllerImpl(
if (entry.value != prev) {
when (entry.key) {
SemanticsProperties.Text -> {
component.accessibleContext.firePropertyChange(
component.composeAccessibleContext.firePropertyChange(
ACCESSIBLE_TEXT_PROPERTY,
prev, entry.value
)
}

SemanticsProperties.EditableText -> {
component.accessibleContext.firePropertyChange(
component.composeAccessibleContext.firePropertyChange(
ACCESSIBLE_TEXT_PROPERTY,
prev, entry.value
)
}

SemanticsProperties.TextSelectionRange -> {
component.accessibleContext.firePropertyChange(
component.composeAccessibleContext.firePropertyChange(
ACCESSIBLE_CARET_PROPERTY,
prev, (entry.value as TextRange).start
)
}

SemanticsProperties.Focused ->
if (entry.value as Boolean) {
component.accessibleContext.firePropertyChange(
component.composeAccessibleContext.firePropertyChange(
ACCESSIBLE_STATE_PROPERTY,
null, AccessibleState.FOCUSED
)
onFocusReceived(component)
} else {
component.accessibleContext.firePropertyChange(
component.composeAccessibleContext.firePropertyChange(
ACCESSIBLE_STATE_PROPERTY,
AccessibleState.FOCUSED, null
)
Expand All @@ -107,13 +107,13 @@ internal class AccessibilityControllerImpl(
SemanticsProperties.ToggleableState -> {
when (entry.value as ToggleableState) {
ToggleableState.On ->
component.accessibleContext.firePropertyChange(
component.composeAccessibleContext.firePropertyChange(
ACCESSIBLE_STATE_PROPERTY,
null, AccessibleState.CHECKED
)

ToggleableState.Off, ToggleableState.Indeterminate ->
component.accessibleContext.firePropertyChange(
component.composeAccessibleContext.firePropertyChange(
ACCESSIBLE_STATE_PROPERTY,
AccessibleState.CHECKED, null
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,23 @@ private typealias ActionKey = SemanticsPropertyKey<AccessibilityAction<() -> Boo
internal class ComposeAccessible(
var semanticsNode: SemanticsNode,
val controller: AccessibilityControllerImpl? = null
) : Accessible {
) : Accessible,
// Must be a subclass of java.awt.Component because CAccessible only registers property
// listeners with the accessible context if the Accessible is an instance of java.awt.Component
// (see constructor of sun.lwawt.macosx.CAccessible), even though there's no reason for it.
// The property change listener is what allows us to update CAccessible when some value changes.
java.awt.Component()
{
private val isNativelyInitialized = AtomicBoolean(false)

val accessibleContext: ComposeAccessibleComponent by lazy { ComposeAccessibleComponent() }
val composeAccessibleContext: ComposeAccessibleComponent by lazy { ComposeAccessibleComponent() }

override fun getAccessibleContext(): AccessibleContext {
// see doc for [nativeInitializeAccessible] for details, why this initialization is needed
if (isNativelyInitialized.compareAndSet(false, true)) {
nativeInitializeAccessible(this)
}
return accessibleContext
return composeAccessibleContext
}

open inner class ComposeAccessibleComponent : AccessibleContext(), AccessibleComponent, AccessibleAction {
Expand Down Expand Up @@ -441,7 +447,7 @@ internal class ComposeAccessible(
}
}

open inner class ComposeAccessibleText() : AccessibleText,
open inner class ComposeAccessibleText : AccessibleText,
AccessibleExtendedText {
override fun getIndexAtPoint(p: Point): Int {
return textLayoutResult!!.getOffsetForPosition(p.toComposeOffset())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@ internal class ComposeSceneAccessible(
val controller = owner.accessibilityController as? AccessibilityControllerImpl
?: continue
val rootAccessible = controller.rootAccessible
val context =
rootAccessible.getAccessibleContext() as? AccessibleComponent
?: continue
val context = rootAccessible.composeAccessibleContext
val accessibleOnPoint = context.getAccessibleAt(p) ?: continue
if (accessibleOnPoint != rootAccessible) {
// TODO: ^ this check produce weird behavior
Expand Down Expand Up @@ -117,19 +115,19 @@ internal class ComposeSceneAccessible(
}

override fun getSize(): Dimension? {
return getMainOwnerAccessibleRoot()?.accessibleContext?.size
return getMainOwnerAccessibleRoot()?.composeAccessibleContext?.size
}

override fun getLocationOnScreen(): Point? {
return getMainOwnerAccessibleRoot()?.accessibleContext?.locationOnScreen
return getMainOwnerAccessibleRoot()?.composeAccessibleContext?.locationOnScreen
}

override fun getLocation(): Point? {
return getMainOwnerAccessibleRoot()?.accessibleContext?.location
return getMainOwnerAccessibleRoot()?.composeAccessibleContext?.location
}

override fun getBounds(): Rectangle? {
return getMainOwnerAccessibleRoot()?.accessibleContext?.bounds
return getMainOwnerAccessibleRoot()?.composeAccessibleContext?.bounds
}

override fun isShowing(): Boolean = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class AccessibilityTest {
}

val node = rule.onNodeWithTag("text").fetchSemanticsNode()
val accessibleNode = ComposeAccessible(node)
val accessibleText = accessibleNode.accessibleContext.accessibleText!!
val accessibleContext = ComposeAccessible(node).accessibleContext
val accessibleText = accessibleContext.accessibleText!!
assertEquals(22, accessibleText.charCount)

assertEquals("H", accessibleText.getAtIndex(AccessibleText.CHARACTER, 0))
Expand Down Expand Up @@ -128,8 +128,8 @@ class AccessibilityTest {
}

private fun SemanticsNodeInteraction.assertHasAccessibleRole(role: AccessibleRole) {
val accessible = ComposeAccessible(fetchSemanticsNode())
assertThat(accessible.accessibleContext.accessibleRole).isEqualTo(role)
val accessibleContext = ComposeAccessible(fetchSemanticsNode()).accessibleContext
assertThat(accessibleContext.accessibleRole).isEqualTo(role)
}

}

0 comments on commit 5ce7be0

Please sign in to comment.