From 5ce7be0808c241f0d69a1a411d000695ec280d19 Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Mon, 25 Sep 2023 16:52:00 +0300 Subject: [PATCH] Fix CAccessibility not registering its listener(s) with ComposeAccessibleComponent, preventing us from reporting changes to it. --- .../compose/ui/platform/Accessibility.desktop.kt | 14 +++++++------- .../compose/ui/platform/ComposeAccessible.kt | 14 ++++++++++---- .../compose/ui/platform/ComposeSceneAccessible.kt | 12 +++++------- .../compose/ui/platform/AccessibilityTest.kt | 8 ++++---- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Accessibility.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Accessibility.desktop.kt index c4e2ef469c1e4..9a8158d17e07c 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Accessibility.desktop.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/Accessibility.desktop.kt @@ -70,21 +70,21 @@ 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 ) @@ -92,13 +92,13 @@ internal class AccessibilityControllerImpl( 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 ) @@ -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 ) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/ComposeAccessible.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/ComposeAccessible.kt index 6b0fa224df8b0..17ed5ba5c08aa 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/ComposeAccessible.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/ComposeAccessible.kt @@ -80,17 +80,23 @@ private typealias ActionKey = SemanticsPropertyKey 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 { @@ -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()) diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/ComposeSceneAccessible.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/ComposeSceneAccessible.kt index 154469e750278..348d757afa9d3 100644 --- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/ComposeSceneAccessible.kt +++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/ComposeSceneAccessible.kt @@ -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 @@ -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 diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/AccessibilityTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/AccessibilityTest.kt index aa7ee00621930..f387fd22c9dd4 100644 --- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/AccessibilityTest.kt +++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/AccessibilityTest.kt @@ -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)) @@ -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) } } \ No newline at end of file