Skip to content

Conversation

hjohn
Copy link
Collaborator

@hjohn hjohn commented Aug 23, 2025

Ensures proper propagation of layout flags when using forceParentLayout = true.

This was the root cause of issue #1874

Note

Apparently it is still quite easy to mess up the layout flags. Basically, the layout flag tracked by Parent should always either be CLEAN for any scene graph branch, or !CLEAN + a layout pulse is scheduled on the corresponding Scene.

However, with careful use of the public API requestLayout one can get these flags in a bad state still:

Let's say I have a branch A (root node under Scene) -> B -> C, and a layout is in progress, and we're currently in the layoutChildren method of C. The flag performingLayout will be true for all nodes in the branch A -> B -> C. The layout method will set the layout flag to CLEAN as its first action, so when we're at C::layoutChildren, all flags have been reset to CLEAN already. See the Parent::layout method for how all this works.

Now, to mess up the flags, all you need to do is call requestLayout on B or C from the layoutChildren of C (or indirectly by changing something and something is listening to this and schedules a layout on something somewhere in this branch); note that requestLayout is not documented to be illegal to call during layout, and some classes in FX will do so (ScrollPaneSkin, NumberAxis, etc..) risking the flags getting in a bad state... -- usually you get away with this, as there are many ways that layout is triggered, and eventually the flags will get overwritten and reset to a consistent state.

The bad state occurs because this code path is followed (all code from Parent):

public void requestLayout() {
    clearSizeCache();
    markDirtyLayout(false, forceParentLayout);
}

Calls to markDirtyLayout(false, false):

private void markDirtyLayout(boolean local, boolean forceParentLayout) {
    setLayoutFlag(LayoutFlags.NEEDS_LAYOUT);
    if (local || layoutRoot) {
        if (sceneRoot) {
            Toolkit.getToolkit().requestNextPulse();
            if (getSubScene() != null) {
                getSubScene().setDirtyLayout(this);
            }
        } else {
            markDirtyLayoutBranch();
        }
    } else {
        requestParentLayout(forceParentLayout);
    }
}

Before going into the else (as none of the nodes is a layout root, and local was set to false) it will do setLayoutFlag(LayoutFlags.NEEDS_LAYOUT) -- this will set a flag on some node; to eventually end up in a consistent state, it must mark all ancestors as well and schedule a pulse (with Toolkit.getToolkit().requestNextPulse())... but:

void requestParentLayout(boolean forceParentLayout) {
    if (!layoutRoot) {
        final Parent p = getParent();
        if (p != null && (!p.performingLayout || forceParentLayout)) {

            /*
             * The forceParentLayout flag must be propagated to mark all ancestors
             * as needing layout. Failure to do so while performingLayout is true 
             * would stop the propagation mid-tree. This leaves some nodes as needing 
             * layout, while its ancestors are clean, which is an inconsistent state.
             */

            p.requestLayout(forceParentLayout);
        }
    }
}

Here there is a guard !p.isPerformingLayout, blocking propagation up the tree. As said, this flag is true for all nodes during a layout of the same branch. The end result thus is that some nodes have their layout flag changed to NEEDS_LAYOUT, but it was not propagated, nor was a pulse scheduled...


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8360940: Layout stops updating when using Parent#setNeedsLayout(true) due to incorrect state management (Bug - P4)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1879/head:pull/1879
$ git checkout pull/1879

Update a local copy of the PR:
$ git checkout pull/1879
$ git pull https://git.openjdk.org/jfx.git pull/1879/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1879

View PR using the GUI difftool:
$ git pr show -t 1879

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1879.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Aug 23, 2025

👋 Welcome back jhendrikx! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Aug 23, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot changed the title JDK-8360940 Layout stops updating when using (public api) Parent#setNeedsLayout(true) due to incorrect state management 8360940: Layout stops updating when using (public api) Parent#setNeedsLayout(true) due to incorrect state management Aug 23, 2025
@hjohn hjohn marked this pull request as ready for review August 23, 2025 17:59
@openjdk openjdk bot added the rfr Ready for review label Aug 23, 2025
@mlbridge
Copy link

mlbridge bot commented Aug 23, 2025

Webrevs

@andy-goryachev-oracle
Copy link
Contributor

/reviewers 2

@openjdk
Copy link

openjdk bot commented Aug 25, 2025

@andy-goryachev-oracle
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@kevinrushforth kevinrushforth self-requested a review September 5, 2025 14:21
@kevinrushforth
Copy link
Member

The fix looks correct. Can you provide an automated test?

@kevinrushforth
Copy link
Member

I recommend removing (public api) from the title of the JBS bug and this PR -- we don't typically do that and it seems superfluous here.

@hjohn hjohn changed the title 8360940: Layout stops updating when using (public api) Parent#setNeedsLayout(true) due to incorrect state management 8360940: Layout stops updating when using Parent#setNeedsLayout(true) due to incorrect state management Sep 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfr Ready for review
Development

Successfully merging this pull request may close these issues.

3 participants