From 0aa073b7ba2c8a31e38bf438a214c84241a6c6f9 Mon Sep 17 00:00:00 2001 From: Sebastiano Poggi Date: Mon, 3 Aug 2026 17:47:29 +0200 Subject: [PATCH] [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 --- .../MacPlatformServicesReflectionTest.kt | 71 +++++++++++++++++++ .../window/macos/MacPlatformServices.kt | 61 ++++++++++++++-- 2 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 platform/jewel/int-ui/int-ui-standalone-tests/src/test/kotlin/org/jetbrains/jewel/intui/standalone/window/macos/MacPlatformServicesReflectionTest.kt diff --git a/platform/jewel/int-ui/int-ui-standalone-tests/src/test/kotlin/org/jetbrains/jewel/intui/standalone/window/macos/MacPlatformServicesReflectionTest.kt b/platform/jewel/int-ui/int-ui-standalone-tests/src/test/kotlin/org/jetbrains/jewel/intui/standalone/window/macos/MacPlatformServicesReflectionTest.kt new file mode 100644 index 000000000000..0b8c904ea6b4 --- /dev/null +++ b/platform/jewel/int-ui/int-ui-standalone-tests/src/test/kotlin/org/jetbrains/jewel/intui/standalone/window/macos/MacPlatformServicesReflectionTest.kt @@ -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") + } +} diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/window/macos/MacPlatformServices.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/window/macos/MacPlatformServices.kt index 75e6abcb1a10..86fca4ad7b59 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/window/macos/MacPlatformServices.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/window/macos/MacPlatformServices.kt @@ -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)