mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[JEWEL-1387] Fix native window lookup on JBR 25
On JBR 25 the AWT window peer is sun.lwawt.macosx.LWCWindowPeer, a subclass of sun.lwawt.LWWindowPeer that inherits getPlatformWindow() without declaring it. The getDeclaredMethod lookup on the runtime class threw NoSuchMethodException, so getWindowFromJavaWindow returned ID.NIL and native window chrome updates (updateColors, updateFullScreenButtons) silently became no-ops on macOS. Search the whole class hierarchy for both the getPlatformWindow() method and the ptr field (the latter had the same latent hardcoded-depth assumption), and add regression tests for the hierarchy-walking lookups. closes https://github.com/JetBrains/intellij-community/pull/3598 (cherry picked from commit 60e96be3384981411db33eccf5bdb5150283656a) (cherry picked from commit bdc19ad03ffc1fd053354bdc5f80e52b462545c2) IJ-MR-220560 GitOrigin-RevId: b2fb4e8b2776d561a526ebbcabde3a061c6cd54c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
82443b5357
commit
0aa073b7ba
+71
@@ -0,0 +1,71 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jewel.intui.standalone.window.macos
|
||||
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* Regression test for JEWEL-1387: the reflective lookups used to resolve the native window must search the whole class
|
||||
* hierarchy, not just the runtime class.
|
||||
*
|
||||
* On JBR 25 the AWT window peer is `sun.lwawt.macosx.LWCWindowPeer`, a subclass of `sun.lwawt.LWWindowPeer` that
|
||||
* inherits (but does not declare) `getPlatformWindow()`. A `getDeclaredMethod` call on the runtime class alone throws
|
||||
* [NoSuchMethodException] and silently breaks native window chrome updates on macOS.
|
||||
*/
|
||||
internal class MacPlatformServicesReflectionTest {
|
||||
@Suppress("UnusedPrivateMember", "FunctionOnlyReturningConstant")
|
||||
private open class BasePeer {
|
||||
@JvmField internal val ptr: Long = 42L
|
||||
|
||||
fun getPlatformWindow(): Any = this
|
||||
}
|
||||
|
||||
// Mimics JBR 25's LWCWindowPeer: inherits getPlatformWindow() and ptr without declaring them
|
||||
private class SubclassPeer : BasePeer()
|
||||
|
||||
@Test
|
||||
fun `findMethodInHierarchy finds method declared on the class itself`() {
|
||||
val method = MacPlatformServicesDefaultImpl.findMethodInHierarchy(BasePeer::class.java, "getPlatformWindow")
|
||||
|
||||
assertNotNull(method, "Should find a method declared directly on the class")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findMethodInHierarchy finds method inherited from a superclass`() {
|
||||
val method = MacPlatformServicesDefaultImpl.findMethodInHierarchy(SubclassPeer::class.java, "getPlatformWindow")
|
||||
|
||||
assertNotNull(method, "Should find a method declared on a superclass (JBR 25 LWCWindowPeer scenario)")
|
||||
assertEquals(BasePeer::class.java, method.declaringClass, "Method should be resolved from the superclass")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findMethodInHierarchy returns null for a missing method`() {
|
||||
val method = MacPlatformServicesDefaultImpl.findMethodInHierarchy(SubclassPeer::class.java, "doesNotExist")
|
||||
|
||||
assertNull(method, "Should return null instead of throwing for missing methods")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findFieldInHierarchy finds field declared on the class itself`() {
|
||||
val field = MacPlatformServicesDefaultImpl.findFieldInHierarchy(BasePeer::class.java, "ptr")
|
||||
|
||||
assertNotNull(field, "Should find a field declared directly on the class")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findFieldInHierarchy finds field inherited from a superclass`() {
|
||||
val field = MacPlatformServicesDefaultImpl.findFieldInHierarchy(SubclassPeer::class.java, "ptr")
|
||||
|
||||
assertNotNull(field, "Should find a field declared on a superclass")
|
||||
assertEquals(BasePeer::class.java, field.declaringClass, "Field should be resolved from the superclass")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findFieldInHierarchy returns null for a missing field`() {
|
||||
val field = MacPlatformServicesDefaultImpl.findFieldInHierarchy(SubclassPeer::class.java, "doesNotExist")
|
||||
|
||||
assertNull(field, "Should return null instead of throwing for missing fields")
|
||||
}
|
||||
}
|
||||
+54
-7
@@ -7,9 +7,12 @@ import com.sun.jna.Callback
|
||||
import com.sun.jna.Pointer
|
||||
import java.awt.Component
|
||||
import java.awt.Window
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Method
|
||||
import javax.swing.SwingUtilities
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import org.jetbrains.jewel.foundation.InternalJewelApi
|
||||
import org.jetbrains.jewel.foundation.util.JewelLogger
|
||||
import org.jetbrains.jewel.intui.standalone.styling.default
|
||||
@@ -60,14 +63,16 @@ public object MacPlatformServicesDefaultImpl : MacPlatformServices {
|
||||
try {
|
||||
val cPlatformWindow = getPlatformWindow(w)
|
||||
if (cPlatformWindow != null) {
|
||||
val ptr = cPlatformWindow.javaClass.superclass.getDeclaredField("ptr")
|
||||
val ptr = findFieldInHierarchy(cPlatformWindow.javaClass, "ptr")
|
||||
if (ptr == null) {
|
||||
logger.warn("Fail to get cPlatformWindow from awt window: no 'ptr' field found.")
|
||||
return ID.NIL
|
||||
}
|
||||
ptr.setAccessible(true)
|
||||
return ID(ptr.getLong(cPlatformWindow))
|
||||
}
|
||||
} catch (e: IllegalAccessException) {
|
||||
logger.warn("Fail to get cPlatformWindow from awt window.", e)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
logger.warn("Fail to get cPlatformWindow from awt window.", e)
|
||||
}
|
||||
return ID.NIL
|
||||
}
|
||||
@@ -79,15 +84,19 @@ public object MacPlatformServicesDefaultImpl : MacPlatformServices {
|
||||
val getPeer = componentAccessor.javaClass.getMethod("getPeer", Component::class.java).accessible()
|
||||
val peer = getPeer.invoke(componentAccessor, w)
|
||||
if (peer != null) {
|
||||
val cWindowPeerClass: Class<*> = peer.javaClass
|
||||
val getPlatformWindowMethod = cWindowPeerClass.getDeclaredMethod("getPlatformWindow")
|
||||
// The method may be declared on a superclass of the runtime peer class (e.g., on JBR 25 the peer
|
||||
// is sun.lwawt.macosx.LWCWindowPeer, which inherits getPlatformWindow() from sun.lwawt.LWWindowPeer),
|
||||
// so we must search the whole class hierarchy rather than just the runtime class.
|
||||
val getPlatformWindowMethod = findMethodInHierarchy(peer.javaClass, "getPlatformWindow")
|
||||
if (getPlatformWindowMethod == null) {
|
||||
logger.warn("Fail to get cPlatformWindow from awt window: no getPlatformWindow() method found.")
|
||||
return null
|
||||
}
|
||||
val cPlatformWindow = getPlatformWindowMethod.invoke(peer)
|
||||
if (cPlatformWindow != null) {
|
||||
return cPlatformWindow
|
||||
}
|
||||
}
|
||||
} catch (e: NoSuchMethodException) {
|
||||
logger.warn("Fail to get cPlatformWindow from awt window.", e)
|
||||
} catch (e: IllegalAccessException) {
|
||||
logger.warn("Fail to get cPlatformWindow from awt window.", e)
|
||||
} catch (e: InvocationTargetException) {
|
||||
@@ -98,6 +107,44 @@ public object MacPlatformServicesDefaultImpl : MacPlatformServices {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a method with the given [name] and no parameters declared on [clazz] or any of its superclasses, or `null`
|
||||
* if no such method exists.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@ApiStatus.Internal
|
||||
@InternalJewelApi
|
||||
public fun findMethodInHierarchy(clazz: Class<*>, name: String): Method? {
|
||||
var current: Class<*>? = clazz
|
||||
while (current != null) {
|
||||
try {
|
||||
return current.getDeclaredMethod(name)
|
||||
} catch (_: NoSuchMethodException) {
|
||||
current = current.superclass
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a field with the given [name] declared on [clazz] or any of its superclasses, or `null` if no such field
|
||||
* exists.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@ApiStatus.Internal
|
||||
@InternalJewelApi
|
||||
public fun findFieldInHierarchy(clazz: Class<*>, name: String): Field? {
|
||||
var current: Class<*>? = clazz
|
||||
while (current != null) {
|
||||
try {
|
||||
return current.getDeclaredField(name)
|
||||
} catch (_: NoSuchFieldException) {
|
||||
current = current.superclass
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public override fun updateColors(w: Window) {
|
||||
SwingUtilities.invokeLater {
|
||||
val window = getWindowFromJavaWindow(w)
|
||||
|
||||
Reference in New Issue
Block a user