On Wed, 8 Jan 2025 01:29:29 GMT, Michael Strauß <mstra...@openjdk.org> wrote:

>> The CSS Selectors specification defines the `:root` pseudo-class that 
>> matches root nodes:
>> https://www.w3.org/TR/selectors-4/#the-root-pseudo
>> 
>> JavaFX uses the non-standard `.root` style class for the same purpose. We 
>> should also support the `:root` pseudo-class for increased compatibility of 
>> JavaFX CSS with the web specification.
>> 
>> Additionally, we should also support the following child-indexed 
>> pseudo-classes:
>> `:first-child`
>> `:last-child`
>> `:only-child`
>> `:nth-child()` with arguments `even` and `odd`
>> 
>> The `An+B [of S]?` microsyntax for `:nth-child()` is not supported, as this 
>> would require major changes in JavaFX CSS.
>
> Michael Strauß has updated the pull request with a new target base due to a 
> merge or a rebase. The incremental webrev excludes the unrelated changes 
> brought in by the merge/rebase. The pull request contains seven additional 
> commits since the last revision:
> 
>  - Merge branch 'master' into feature/root-pseudo-class
>  - whitespace error
>  - add child-indexed pseudo-classes
>  - add pseudo-class table to Parent
>  - update cssref
>  - Set :root pseudo-class on sub-scene root node
>  - Set :root pseudo-class on root node

modules/javafx.graphics/src/main/docs/javafx/scene/doc-files/cssref.html line 
1530:

> 1528:     <p>In the following example, all background color of all buttons 
> uses the
> 1529:       looked up color "abc".</p>
> 1530:     <p class="example">:root { abc: #f00 }<br>

I see many places where `.root` is removed in favor of `:root`, but both are 
supported still?  At least, the code in `Scene` leads me to believe that it 
will also apply the `.root` style class still.  Should this legacy style class 
be mentioned in the CSS documentation still?

modules/javafx.graphics/src/main/java/javafx/scene/Parent.java line 341:

> 339: 
> 340:         @Override
> 341:         protected void onChanged(Change<Node> c) {

I hate to bring this up, and it perhaps should be dealt with separately, but 
how safe is this code in `onProposedChange` / `onChanged` when it is re-entered 
due to the many potential listener callbacks?  For example, the removal of a 
child on `parent` can trigger list change listeners from the user that can 
trigger all kinds of other changes that eventually lead to more children 
changes on this node, triggering a nested `onChanged`; the new changes are 
adding more opportunities in the form of listeners on pseudo state changes.

For example, `onProposedChange` will blatantly reset `geomChanged` to `false` 
in all cases, even if it rejects the change later.  It doesn't check if an 
`onChange` is in progress higher up the call stack, effectively resetting a 
flag that is in active use...

I believe this may be quite a difficult problem to solve if nested calls remain 
allowed, and one of the easiest ways to fix it IMHO is to reject any changes 
(in `onProposedChange`) when a change is already in progress. For users this 
would effectively mean that listeners triggered during a child modification 
will not be allowed to make further modifications to the same children list 
until the first one completes.  This is not a common situation, but can happen 
with very dynamic UI's that construct new nodes in response to triggers; 
without such protection for nested changes, the errors that crop up are often 
of a form that seem to be multi-threading issues (where certain invariants seem 
to be broken), while in reality they're just re-entrant code calls that use 
instance fields in an unsafe manner.

modules/javafx.graphics/src/main/java/javafx/scene/Parent.java line 375:

> 373:                         }
> 374: 
> 375:                         toggleStructuralPseudoClasses(n, List.of());

This (and other `toggleStructuralPseudoClasses` and `pseudoClassStateChanged` 
calls later) introduces new opportunities for user listener call backs in the 
middle of a pretty critical code path.

`n.getParent().children.remove(n);` a few lines above is also such an 
opportunity.

modules/javafx.graphics/src/main/java/javafx/scene/Parent.java line 381:

> 379:                     // the index of the first change to separate 
> unchanged from changed elements.
> 380:                     if (firstDirtyChildIndex < 0) {
> 381:                         firstDirtyChildIndex = from;

Does this need some special casing in case the first element is modified 
indirectly?  Let's say I have two elements, I remove the last element.  
Indirectly, the first element now gets the "only" and "last" pseudo classes; 
this may need to be communicated as a modification to the peer for example?

modules/javafx.graphics/src/main/java/javafx/scene/Parent.java line 470:

> 468:             // Add the "last-child" pseudo-class to the last child.
> 469:             if (size() > 0) {
> 470:                 
> getLast().pseudoClassStateChanged(LAST_CHILD_PSEUDO_CLASS, true);

These code paths seem to do double work.  Would it not be better to treat each 
flag separately, instead of having some paths manipulate multiple flags, that 
then may get set/reset again individually?  The first `if/else if` in this 
block will reset all flags, only to have some of them be set/reset again later.

This may also lead to listener confusion; let's say I have 2 children.  I 
remove the last child.  The "only child" block (`size() == 1`) triggers; it 
removes the `odd` pseudo class; a listener may trigger on this; however, just a 
bit later, this `odd` tag is then re-added (at least, I hope it is as I'm not 
entirely sure the `firstDirtyChildIndex` will be set low enough...)

modules/javafx.graphics/src/main/java/javafx/scene/Parent.java line 479:

> 477:                     
> n.pseudoClassStateChanged(NTH_EVEN_CHILD_PSEUDO_CLASS, i % 2 != 0);
> 478:                     
> n.pseudoClassStateChanged(NTH_ODD_CHILD_PSEUDO_CLASS, i % 2 == 0);
> 479:                 }

With this new code, Nodes with many children being manipulated may incur some 
significant new costs now that these pseudo classes need to be changed for 
potentially many/all children.  Where before adding a new first child mostly 
had a cost in shifting the child array with a copy, now we also potentially 
have two listener call backs per child (and I'm unsure, but these may even 
trigger some CSS recalculations or CSS cache invalidations).  Nodes with many 
children may suffer from this, and mostly needlessly as these pseudo classes 
will see very limited use.

I think there's some potential for performance regression here that is perhaps 
sufficient to warrant the exclusion of this feature or making it optional.

modules/javafx.graphics/src/test/java/test/javafx/css/StylesheetTest.java line 
790:

> 788:             """.getBytes(StandardCharsets.UTF_8)));
> 789: 
> 790:         assertNotEquals(Background.fill(Color.RED), 
> root.getBackground());

This is just checking the default state of a stack pane.  Would it not be 
fairer to first set it up in a Scene, with a different stylesheet or no 
stylesheet, then applying one that uses `:root` and checking the before and 
after states?

modules/javafx.graphics/src/test/java/test/javafx/scene/Parent_pseudoClasses_Test.java
 line 60:

> 58:         assertNotPseudoClass(ONLY_CHILD, child1);
> 59:         assertPseudoClass(ONLY_CHILD, child2);
> 60:     }

How about a case where the last child is removed, and seeing if the first and 
now only child gets the correct states?  I think there currently is definitely 
a bug where the odd/even may not be reapplied in that case...

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

PR Review Comment: https://git.openjdk.org/jfx/pull/1652#discussion_r1907081969
PR Review Comment: https://git.openjdk.org/jfx/pull/1652#discussion_r1907034950
PR Review Comment: https://git.openjdk.org/jfx/pull/1652#discussion_r1907039143
PR Review Comment: https://git.openjdk.org/jfx/pull/1652#discussion_r1907077704
PR Review Comment: https://git.openjdk.org/jfx/pull/1652#discussion_r1907061458
PR Review Comment: https://git.openjdk.org/jfx/pull/1652#discussion_r1907071898
PR Review Comment: https://git.openjdk.org/jfx/pull/1652#discussion_r1907088577
PR Review Comment: https://git.openjdk.org/jfx/pull/1652#discussion_r1907090798

Reply via email to