Unverified Commit 485fde3e authored by Nikolai Rykunov's avatar Nikolai Rykunov Committed by GitHub

Accessibility focus on wrong component fix (#793)

* Introduce a way to initialize CAccessible for Accessible to build proper context -> accessible mapping

* Add test for getCAccessible call
parent bba4bb33
package org.jetbrains.skiko
import javax.accessibility.Accessible
/**
* See [nativeInitializeAccessible] doc for details
*/
internal external fun initializeCAccessible(accessible: Accessible)
\ No newline at end of file
...@@ -2,7 +2,10 @@ package org.jetbrains.skiko ...@@ -2,7 +2,10 @@ package org.jetbrains.skiko
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import java.awt.* import java.awt.Canvas
import java.awt.Component
import java.awt.Graphics
import java.awt.KeyboardFocusManager
import java.awt.event.FocusEvent import java.awt.event.FocusEvent
import java.awt.event.InputMethodEvent import java.awt.event.InputMethodEvent
import java.beans.PropertyChangeEvent import java.beans.PropertyChangeEvent
...@@ -134,3 +137,29 @@ internal fun layerFrameLimiter( ...@@ -134,3 +137,29 @@ internal fun layerFrameLimiter(
} }
) )
} }
/**
* This method should be called on custom [Accessible] creation (or its context if context is created lazily).
*
* JDK's accessibility support (at least for MacOS) builds mapping AccessibleContext -> Accessible.
* Some [Accessible] are built only when focus is settled and
* since we have a hack [requestNativeFocusOnAccessible], wrong mapping can be built
* (ComponentAccessibleContext -> SkiaLayer instead of ComponentAccessibleContext -> ComponentAccessible).
*
* This method forces JDK's accessibility support to cache mapping ComponentAccessibleContext -> ComponentAccessible,
* if it is called on ComponentAccessibleContext creation.
*
* Related to the [issue](https://youtrack.jetbrains.com/issue/COMPOSE-176).
*/
@Suppress("unused")
fun nativeInitializeAccessible(accessible: Accessible) {
when (hostOs) {
OS.MacOS -> {
initializeCAccessible(accessible)
}
else -> {
// TODO: do we need something for Windows?
}
}
}
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import javax.accessibility.Accessible
/** /**
* Provides [MetalAdapter] that holds pointer to native [MTLDevice](https://developer.apple.com/documentation/metal/mtldevice) * Provides [MetalAdapter] that holds pointer to native [MTLDevice](https://developer.apple.com/documentation/metal/mtldevice)
* chosen using [adapterPriority] * chosen using [adapterPriority]
......
#import <jawt.h>
#import <jawt_md.h>
extern "C" {
JNIEXPORT void JNICALL Java_org_jetbrains_skiko_AccessibilityKt_initializeCAccessible(
JNIEnv *env, jobject obj, jobject accessible)
{
@autoreleasepool {
jclass accClass = env->FindClass("sun/lwawt/macosx/CAccessible");
if (accClass == NULL) {
return;
}
jmethodID getCAccessibleMethod = env->GetStaticMethodID(
accClass, "getCAccessible",
"(Ljavax/accessibility/Accessible;)Lsun/lwawt/macosx/CAccessible;"
);
if (getCAccessibleMethod == NULL) {
return;
}
jobject result = env->CallStaticObjectMethod(accClass, getCAccessibleMethod, accessible);
if (result == NULL) {
return;
}
}
}
} // extern "C"
\ No newline at end of file
package org.jetbrains.skiko
import org.junit.Assume.assumeTrue
import java.util.*
import javax.accessibility.Accessible
import javax.accessibility.AccessibleContext
import javax.accessibility.AccessibleRole
import javax.accessibility.AccessibleStateSet
import kotlin.test.Test
class AccessibilityKtTest {
private companion object {
init {
Library.load()
}
}
@Test
fun `getCAccessible is available`() {
assumeTrue(hostOs == OS.MacOS)
val context = object : AccessibleContext() {
override fun getAccessibleRole(): AccessibleRole {
return AccessibleRole.PANEL
}
override fun getAccessibleStateSet(): AccessibleStateSet {
return AccessibleStateSet()
}
override fun getAccessibleIndexInParent(): Int {
return 0
}
override fun getAccessibleChildrenCount(): Int {
return 0
}
override fun getAccessibleChild(i: Int): Accessible? {
return null
}
override fun getLocale(): Locale {
return Locale.getDefault()
}
}
val accessible = Accessible {
context
}
initializeCAccessible(accessible)
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment