Skip to content

Commit d7cfed4

Browse files
authored
Move the initializeAccessible call into AccessibleFocusHelper (JetBrains#2746)
1 parent 23657ab commit d7cfed4

2 files changed

Lines changed: 18 additions & 29 deletions

File tree

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/a11y/Accessibility.desktop.kt

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.jetbrains.skiko.MainUIDispatcher
1111
import org.jetbrains.skiko.OS
1212
import org.jetbrains.skiko.hostOs
1313
import org.jetbrains.skiko.initializeCAccessible
14+
import androidx.compose.ui.scene.skia.SkiaLayerComponent
1415

1516
/**
1617
* A helper class for requesting accessibility focus on a given accessible.
@@ -29,6 +30,8 @@ internal class AccessibleFocusHelper(
2930

3031
@OptIn(DelicateCoroutinesApi::class)
3132
fun requestFocusOnAccessible(accessible: Accessible?) {
33+
initializeAccessible(accessible)
34+
3235
focusedAccessible = accessible
3336

3437
when (hostOs) {
@@ -83,29 +86,22 @@ internal class AccessibleFocusHelper(
8386
}
8487

8588
/**
86-
* This method should be called on custom [Accessible] creation (or its context if context is
87-
* created lazily).
88-
*
89-
* JDK's accessibility support (at least for macOS) builds mapping AccessibleContext -> Accessible.
90-
* Some [Accessible]s are built only when focus is settled and
91-
* since we have a hack [AccessibleFocusHelper.requestFocusOnAccessible], wrong mapping
92-
* can be built (ComponentAccessibleContext -> SkiaLayer instead of
93-
* ComponentAccessibleContext -> ComponentAccessible).
89+
* [sun.lwawt.macosx.CAccessible.getCAccessible] builds a mapping of [AccessibleContext] to
90+
* [sun.lwawt.macosx.CAccessible] instances (which wrap the corresponding [Accessible]).
91+
* If it is called with the Skia layer content [Accessible] (=[SkiaLayerComponent.contentRoot])
92+
* while the [AccessibleFocusHelper] hack is active ([AccessibleFocusHelper.focusedAccessible] is
93+
* not `null`), it builds an incorrect mapping, associating the focused [AccessibleContext] with
94+
* [SkiaLayerComponent.contentRoot].
9495
*
95-
* This method forces JDK's accessibility support to cache mapping
96-
* ComponentAccessibleContext -> ComponentAccessible, if it is called on
97-
* ComponentAccessibleContext creation.
96+
* To work around this problem, [initializeAccessible] explicitly calls
97+
* [sun.lwawt.macosx.CAccessible.getCAccessible] on the focused [Accessible], forcing the correct
98+
* association to be made. Future calls then just retrieve the already stored value.
9899
*
99-
* Related to the [issue](https://youtrack.jetbrains.com/issue/COMPOSE-176).
100+
* See also [Error when following the instructions of
101+
* VoiceOver](https://youtrack.jetbrains.com/issue/CMP-176).
100102
*/
101-
internal fun initializeAccessible(accessible: Accessible) {
102-
when (hostOs) {
103-
OS.MacOS -> {
104-
initializeCAccessible(accessible)
105-
}
106-
107-
else -> {
108-
// TODO: do we need something for Windows?
109-
}
103+
private fun initializeAccessible(accessible: Accessible?) {
104+
if ((accessible != null) && (hostOs == OS.MacOS)) {
105+
initializeCAccessible(accessible)
110106
}
111107
}

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/a11y/ComposeAccessible.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ import javax.accessibility.AccessibleValue
6565
import javax.swing.text.AttributeSet
6666
import javax.swing.text.SimpleAttributeSet
6767
import kotlin.math.roundToInt
68-
import kotlinx.atomicfu.atomic
6968
import org.jetbrains.skia.BreakIterator
7069

7170
private typealias ActionKey = SemanticsPropertyKey<AccessibilityAction<() -> Boolean>>
@@ -108,8 +107,6 @@ internal class ComposeAccessible(
108107
}
109108
}
110109

111-
private val isNativelyInitialized = atomic(false)
112-
113110
val composeAccessibleContext: ComposeAccessibleComponent by lazy { ComposeAccessibleComponent() }
114111

115112
private var disposed = false
@@ -121,14 +118,10 @@ internal class ComposeAccessible(
121118
override fun getAccessibleContext(): AccessibleContext? {
122119
if (disposed) {
123120
// The accessibility system keeps calling functions on the context even after the node
124-
// has been removed. We return null so it doesn't do that.
121+
// has been removed. We return `null` so it doesn't do that.
125122
return null
126123
}
127124

128-
// see doc for [nativeInitializeAccessible] for details, why this initialization is needed
129-
if (isNativelyInitialized.compareAndSet(expect = false, update = true)) {
130-
initializeAccessible(this)
131-
}
132125
return composeAccessibleContext
133126
}
134127

0 commit comments

Comments
 (0)