Skip to content

Commit d6e53c5

Browse files
authored
Remove NativeAccessibleFocusHelper (#1147)
1 parent 3799325 commit d6e53c5

4 files changed

Lines changed: 21 additions & 126 deletions

File tree

Lines changed: 11 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,17 @@
11
package org.jetbrains.skiko
22

3-
import kotlinx.coroutines.*
4-
import java.awt.Component
5-
import java.awt.KeyboardFocusManager
6-
import java.awt.event.FocusEvent
7-
import java.beans.PropertyChangeEvent
83
import javax.accessibility.Accessible
9-
import javax.accessibility.AccessibleContext
104

115
/**
12-
* See [nativeInitializeAccessible] doc for details
6+
* Calls `sun.lwawt.macosx.CAccessible.getCAccessible(Accessible)` on the given [accessible] object; does nothing
7+
* if running on a non-Mac platform or in a JVM where there is no `CAccessible` class.
8+
*
9+
* Ideally, this and nativeInitializeAccessible should be in Compose, not Skiko.
10+
* Unfortunately, Compose doesn't currently allow native code, and implementing it via reflection is not
11+
* possible due to java.desktop module access restrictions:
12+
* > class androidx.compose.ui.platform.a11y.AccessibilityKt cannot access class sun.lwawt.macosx.CAccessible
13+
* > (in module java.desktop) because module java.desktop does not export sun.lwawt.macosx to unnamed module
14+
*
15+
* As such, this function is not to be considered public Skiko API.
1316
*/
14-
internal external fun initializeCAccessible(accessible: Accessible)
15-
16-
/**
17-
* A helper class for implementing requesting accessibility focus on a given accessible.
18-
*/
19-
internal class NativeAccessibleFocusHelper(
20-
private val component: Component,
21-
private val externalAccessible: Accessible?,
22-
) {
23-
24-
private var focusedAccessible: Accessible? = null
25-
26-
val accessibleContext: AccessibleContext?
27-
get() = (focusedAccessible ?: externalAccessible)?.accessibleContext
28-
29-
private var resetFocusAccessibleJob: Job? = null
30-
31-
@OptIn(DelicateCoroutinesApi::class)
32-
fun requestNativeFocusOnAccessible(accessible: Accessible?) {
33-
focusedAccessible = accessible
34-
35-
when (hostOs) {
36-
OS.Windows -> requestAccessBridgeFocusOnAccessible()
37-
OS.MacOS -> requestMacOSFocusOnAccessible(accessible)
38-
else -> {
39-
focusedAccessible = null
40-
return
41-
}
42-
}
43-
44-
// Listener spawns asynchronous notification post procedure, reading current focus owner
45-
// and its accessibility context. This timeout is used to deal with concurrency
46-
// TODO Find more reliable procedure
47-
resetFocusAccessibleJob?.cancel()
48-
resetFocusAccessibleJob = GlobalScope.launch(MainUIDispatcher) {
49-
delay(100)
50-
focusedAccessible = null
51-
}
52-
}
53-
54-
private fun requestAccessBridgeFocusOnAccessible() {
55-
val focusEvent = FocusEvent(component, FocusEvent.FOCUS_GAINED)
56-
component.focusListeners.forEach { it.focusGained(focusEvent) }
57-
}
58-
59-
private fun requestMacOSFocusOnAccessible(accessible: Accessible?) {
60-
val focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager()
61-
val listeners = focusManager.getPropertyChangeListeners("focusOwner")
62-
val event = PropertyChangeEvent(focusManager, "focusOwner", null, accessible)
63-
listeners.forEach { it.propertyChange(event) }
64-
}
65-
66-
fun dispose() {
67-
resetFocusAccessibleJob?.cancel()
68-
}
69-
}
17+
external fun initializeCAccessible(accessible: Accessible)

skiko/src/awtMain/kotlin/org/jetbrains/skiko/HardwareLayer.kt

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import java.awt.Canvas
77
import java.awt.Component
88
import java.awt.Graphics
99
import java.awt.event.InputMethodEvent
10-
import javax.accessibility.Accessible
1110
import javax.accessibility.AccessibleContext
1211

1312
internal open class HardwareLayer(
14-
externalAccessibleFactory: ((Component) -> Accessible)? = null
13+
private val accessibleContextProvider: ((Component) -> AccessibleContext)? = null
1514
) : Canvas() {
1615
companion object {
1716
init {
@@ -26,7 +25,6 @@ internal open class HardwareLayer(
2625
}
2726

2827
open fun dispose() {
29-
nativeAccessibleFocusHelper.dispose()
3028
nativeDispose()
3129
}
3230

@@ -58,18 +56,8 @@ internal open class HardwareLayer(
5856
private external fun getWindowHandle(platformInfo: Long): Long
5957
private external fun getCurrentDPI(platformInfo: Long): Int
6058

61-
@Suppress("LeakingThis")
62-
private val nativeAccessibleFocusHelper = NativeAccessibleFocusHelper(
63-
component = this,
64-
externalAccessible = externalAccessibleFactory?.invoke(this)
65-
)
66-
6759
override fun getAccessibleContext(): AccessibleContext {
68-
return nativeAccessibleFocusHelper.accessibleContext ?: super.getAccessibleContext()
69-
}
70-
71-
fun requestNativeFocusOnAccessible(accessible: Accessible?) {
72-
nativeAccessibleFocusHelper.requestNativeFocusOnAccessible(accessible)
60+
return accessibleContextProvider?.invoke(this) ?: super.getAccessibleContext()
7361
}
7462
}
7563

@@ -106,29 +94,3 @@ internal fun layerFrameLimiter(
10694
}
10795
)
10896
}
109-
110-
/**
111-
* This method should be called on custom [Accessible] creation (or its context if context is created lazily).
112-
*
113-
* JDK's accessibility support (at least for MacOS) builds mapping AccessibleContext -> Accessible.
114-
* Some [Accessible] are built only when focus is settled and
115-
* since we have a hack [NativeAccessibleFocusHelper.requestNativeFocusOnAccessible], wrong mapping can be built
116-
* (ComponentAccessibleContext -> SkiaLayer instead of ComponentAccessibleContext -> ComponentAccessible).
117-
*
118-
* This method forces JDK's accessibility support to cache mapping ComponentAccessibleContext -> ComponentAccessible,
119-
* if it is called on ComponentAccessibleContext creation.
120-
*
121-
* Related to the [issue](https://youtrack.jetbrains.com/issue/COMPOSE-176).
122-
*/
123-
@Suppress("unused")
124-
fun nativeInitializeAccessible(accessible: Accessible) {
125-
when (hostOs) {
126-
OS.MacOS -> {
127-
initializeCAccessible(accessible)
128-
}
129-
130-
else -> {
131-
// TODO: do we need something for Windows?
132-
}
133-
}
134-
}

skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import javax.swing.event.AncestorListener
2727
import kotlin.math.floor
2828

2929
actual open class SkiaLayer internal constructor(
30-
externalAccessibleFactory: ((Component) -> Accessible)? = null,
30+
accessibleContextProvider: ((Component) -> AccessibleContext)? = null,
3131
val properties: SkiaLayerProperties,
3232
private val renderFactory: RenderFactory = RenderFactory.Default,
3333
private val analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
@@ -48,15 +48,15 @@ actual open class SkiaLayer internal constructor(
4848
internal val backedLayer: HardwareLayer
4949

5050
constructor(
51-
externalAccessibleFactory: ((Component) -> Accessible)? = null,
51+
accessibleContextProvider: ((Component) -> AccessibleContext)? = null,
5252
isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled,
5353
isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled,
5454
frameBuffering: FrameBuffering = SkikoProperties.frameBuffering,
5555
renderApi: GraphicsApi = SkikoProperties.renderApi,
5656
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
5757
pixelGeometry: PixelGeometry = PixelGeometry.UNKNOWN,
5858
) : this(
59-
externalAccessibleFactory,
59+
accessibleContextProvider,
6060
SkiaLayerProperties(
6161
isVsyncEnabled,
6262
isVsyncFramelimitFallbackEnabled,
@@ -69,12 +69,12 @@ actual open class SkiaLayer internal constructor(
6969
)
7070

7171
constructor(
72-
externalAccessibleFactory: ((Component) -> Accessible)? = null,
72+
accessibleContextProvider: ((Component) -> AccessibleContext)? = null,
7373
properties: SkiaLayerProperties,
7474
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
7575
pixelGeometry: PixelGeometry = PixelGeometry.UNKNOWN,
7676
) : this(
77-
externalAccessibleFactory,
77+
accessibleContextProvider,
7878
properties,
7979
RenderFactory.Default,
8080
analytics,
@@ -89,7 +89,7 @@ actual open class SkiaLayer internal constructor(
8989

9090
init {
9191
layout = null
92-
backedLayer = object : HardwareLayer(externalAccessibleFactory) {
92+
backedLayer = object : HardwareLayer(accessibleContextProvider) {
9393
override fun paint(g: Graphics) {
9494
Logger.debug { "Paint called on HardwareLayer $this" }
9595
checkContentScale()
@@ -683,10 +683,6 @@ actual open class SkiaLayer internal constructor(
683683
}
684684
}
685685

686-
fun requestNativeFocusOnAccessible(accessible: Accessible?) {
687-
backedLayer.requestNativeFocusOnAccessible(accessible)
688-
}
689-
690686
override fun getAccessibleContext(): AccessibleContext {
691687
if (accessibleContext == null) {
692688
accessibleContext = AccessibleSkiaLayer()

skiko/src/awtMain/kotlin/org/jetbrains/skiko/swing/SkiaSwingLayer.kt

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import java.awt.Component
77
import java.awt.Graphics
88
import java.awt.Graphics2D
99
import java.awt.GraphicsConfiguration
10-
import javax.accessibility.Accessible
1110
import javax.accessibility.AccessibleContext
1211
import javax.swing.JPanel
1312
import javax.swing.SwingUtilities.isEventDispatchThread
@@ -27,7 +26,7 @@ import javax.swing.SwingUtilities.isEventDispatchThread
2726
open class SkiaSwingLayer(
2827
renderDelegate: SkikoRenderDelegate,
2928
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
30-
externalAccessibleFactory: ((Component) -> Accessible)? = null,
29+
private val accessibleContextProvider: ((Component) -> AccessibleContext)? = null,
3130
private val properties: SkiaLayerProperties = SkiaLayerProperties()
3231
) : JPanel() {
3332
internal companion object {
@@ -126,17 +125,7 @@ open class SkiaSwingLayer(
126125
}
127126
}
128127

129-
@Suppress("LeakingThis")
130-
private val nativeAccessibleFocusHelper = NativeAccessibleFocusHelper(
131-
component = this,
132-
externalAccessible = externalAccessibleFactory?.invoke(this)
133-
)
134-
135128
override fun getAccessibleContext(): AccessibleContext? {
136-
return nativeAccessibleFocusHelper.accessibleContext ?: super.getAccessibleContext()
137-
}
138-
139-
fun requestNativeFocusOnAccessible(accessible: Accessible?) {
140-
nativeAccessibleFocusHelper.requestNativeFocusOnAccessible(accessible)
129+
return accessibleContextProvider?.invoke(this) ?: super.getAccessibleContext()
141130
}
142131
}

0 commit comments

Comments
 (0)