RFR: 8324941: POC for Headless platform for JavaFX [v4]

Andy Goryachev angorya at openjdk.org
Fri Jun 27 20:41:47 UTC 2025


On Mon, 23 Jun 2025 08:26:30 GMT, Johan Vos <jvos at openjdk.org> wrote:

>> After spending a year in the sandbox repository, the Headless Platform is now ready to be reviewed in the main repository.
>> 
>> ### the Headless Platform
>> The Headless Platform is a top-level com.sun.glass.ui platform that replaces the second-level Monocle-Headless subplatform, that is part of the top-level Monocle platform. 
>> The platform can be used like any other platform, especially for running headless JavaFX applications, or for running tests (e.g. on CI systems)
>> 
>> ### changes
>> The code for the Headless Platform is in a new package com.sun.glass.ui.headless in the javafx.graphics module, and it does not require a code change in other packages.
>> This PR adds a simple change in the `build.gradle` file, to make the Headless Platform the standard when running headless tests (instead of using Monocle/Headless)
>> 
>> ### enable the Headless Platform
>> Setting the system property `glass.platform` to `Headless` will select the Headless Platform instead of the default one (either gtk, mac or win).
>> 
>> ### testing
>> `gradlew --info -PHEADLESS_TEST=true -PFULL_TEST=true :systemTests:cleanTest :systemTests:test`
>> runs all the system tests, apart from the robot tests. There are 2 failing tests, but there are valid reasons for those to fail.
>> 
>> ### robot tests
>> Most of the robot tests are working on headless as well. add `-PUSE_ROBOT` to test those.
>
> Johan Vos has updated the pull request incrementally with one additional commit since the last revision:
> 
>   Process reviewer comments

Added a little code to the monkey tester to take a snapshot of the scene.
Here is the result launching it on the headless platform on macOS 15.5:

![1751056379225](https://github.com/user-attachments/assets/967de7d4-a5b9-416a-a711-2599b67a6c8c)

Good job!

Question: While the screen size is limited in this version to 1000x1000, the size of the snapshot exceeds that.  Is that expected?

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessApplication.java line 22:

> 20:     private final NestedRunnableProcessor processor = new NestedRunnableProcessor();
> 21:     private final HeadlessWindowManager windowManager = new HeadlessWindowManager();
> 22:     private Screen[] screens = null;

minor: no need to assign null, also L24

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessApplication.java line 41:

> 39:     @Override
> 40:     protected void _invokeAndWait(Runnable runnable) {
> 41:         throw new UnsupportedOperationException("Not supported yet.");

is there a follow-up bug for this, or the list of things to do?

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessApplication.java line 92:

> 90:     @Override
> 91:     protected void staticCursor_setVisible(boolean visible) {
> 92:         throw new UnsupportedOperationException("Not supported yet.");

should it be a no-op instead?  will cursor be supported at all?

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessRobot.java line 109:

> 107:         }
> 108:         int modifiers = 0;
> 109:         view.notifyMouse(mouseEvent, buttonEvent, (int)mouseX-wx, (int)mouseY-wy, (int)mouseX, (int)mouseY, modifiers, false, false);

should the coordinates be rounded instead of floor'ed (cast to int)?
(also in other places below)

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessRobot.java line 161:

> 159:         int repeat = Math.abs(wheelAmt);
> 160:         for (int i = 0; i < repeat; i++) {
> 161:             view.notifyScroll((int) mouseX, (int) mouseY, wx, wy, 0, dff, 0, 0, 0, 0, 0, multiplierX, multiplierY);

are all parameters correct?  `modifiers` and `lines`, specifically?

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessRobot.java line 179:

> 177:     public void getScreenCapture(int x, int y, int width, int height, int[] data, boolean scaleToFit) {
> 178:         checkWindowFocused();
> 179:         ((HeadlessWindow)activeWindow).getScreenCapture(x, y, width, height, data, scaleToFit);

maybe the `activeWindow` can be declared as `HeadlessWindow` to avoid casting

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessRobot.java line 231:

> 229:         if (windows.isEmpty()) return null;
> 230:         if (windows.size() == 1) return (HeadlessWindow)windows.get(0);
> 231:         return (HeadlessWindow)windows.get(windows.size() -1);

call me old fashioned, but would a straightforward reverse `for` loop be better in this case?  no unnecessary memory allocations required.

here and in two methods below?

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessWindow.java line 19:

> 17: 
> 18:     private static final AtomicInteger ptrCount = new AtomicInteger(0);
> 19:     private long ptr;

since `ptr` is long, should `ptrCount` be `AtomicLong`?

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessWindow.java line 316:

> 314:         int b = rgba & 0xFF;
> 315: 
> 316:         Color color = Color.color(

could use `Color::rgb(int,int,int,double)`;

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessWindow.java line 329:

> 327:             for (int j = 0; j < width; j++) {
> 328:                 int idx = i * width + j;
> 329:                 int fidx = (y + i) * 1000 + x + j;

where does 1000 come from?

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessWindow.java line 341:

> 339:         int offsetX = this.getX();
> 340:         int offsetY = this.getY();
> 341:         int stride = 1000;

same magic 1000 here

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/HeadlessWindow.java line 356:

> 354: 
> 355:     void clearRect(int x0, int w0, int y0, int h0) {
> 356:         int stride = 1000;

and here...

modules/javafx.graphics/src/main/java/com/sun/glass/ui/headless/NestedRunnableProcessor.java line 40:

> 38:             latch.await();
> 39:         } catch (InterruptedException e) {
> 40:             e.printStackTrace();

should we print here or send to `Application.reportException(e)` ?

-------------

PR Comment: https://git.openjdk.org/jfx/pull/1836#issuecomment-3014300491
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172457055
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172458562
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172463000
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172698720
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172703243
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172705046
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172729945
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172790233
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172819470
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172820759
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172821109
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172821466
PR Review Comment: https://git.openjdk.org/jfx/pull/1836#discussion_r2172827253


More information about the openjfx-dev mailing list