RFR: 8345348: CSS media feature queries [v13]

John Hendrikx jhendrikx at openjdk.org
Mon Apr 14 13:10:52 UTC 2025


On Sun, 13 Apr 2025 17:34:15 GMT, Michael Strauß <mstrauss at openjdk.org> wrote:

>> Implementation of [CSS media queries](https://gist.github.com/mstr2/cbb93bff03e073ec0c32aac317b22de7).
>
> Michael Strauß has updated the pull request incrementally with one additional commit since the last revision:
> 
>   improved implementation of NullCoalescingPropertyBase

A partial review, I didn't look at the tests.

I noticed some non-intuitive equals/hashCode overrides, but found no evidence they're needed to make the non-test code work; if they're only there to make testing more convenient, I really think the overrides should be removed.  In general, IMHO, any equals/hashCode override that doesn't fully test equality (apart from `transient` or derived fields) should be avoided as it will be surprising if such classes are ever used with `equals` or any hash based collection classes.

If there's need for partial equality, then externalize this.

modules/javafx.graphics/src/main/java/com/sun/javafx/application/preferences/PreferenceProperties.java line 246:

> 244:         }
> 245: 
> 246:         public synchronized void fireValueChangedIfNecessary() {

I noticed that you made many methods `synchronized` in this class, but I have trouble seeing why this is.  A short explanation may be in order.

So, if I had to guess: it's possible to change the value override and/or the updating of all properties from a different thread, which is not the FX thread.  However, what guarantees are now given to listeners (if any)?  The value changed events can now also be triggered from any thread, unless wrapped in a `Platform.runLater`.

What I mean here is, let's say I listen on `accentColorProperty` of `Platform.Preferences`, on what thread can the change event happen?  The `Platform.Preferences` class does not mention anything special, so I think it is fair to assume it should be on the FX thread, allowing me to make modifications to an active scene graph in my change handler; however that will fail if the change notification was not triggered from the FX thread.

modules/javafx.graphics/src/main/java/com/sun/javafx/css/media/MediaRule.java line 129:

> 127:     public int hashCode() {
> 128:         return hash;
> 129:     }

You're basically saying here that a media rule with or without parent but the same queries is the same.  However, parent is taken into account while writing/reading and evaluating.  I think this deserves a justification.

modules/javafx.graphics/src/main/java/com/sun/javafx/css/media/expression/FunctionExpression.java line 72:

> 70:     public String toString() {
> 71:         return "(" + (featureValue != null ? featureName + ": " + featureValue : featureName) + ")";
> 72:     }

It's really unusual to override these methods in a record.  What reason do you have for excluding the function from equals/hashCode ?  What does it mean when all fields match but a different function is used?  Where is equality used?

modules/javafx.graphics/src/main/java/com/sun/javafx/css/parser/TokenStream.java line 31:

> 29: import java.util.function.Predicate;
> 30: 
> 31: public final class TokenStream {

I think the naming of the methods in this class leaves something to be desired.

- `Token consume()` **OK**
- `Token consume(Predicate)` -> `consumeIf` or `consumeIfMatches`
    Makes it clearer, as `consume` seems to imply something is always consumed (ie. it still skips one token if the predicate didn't match returning `null`).
- `Token peek()` **OK**
- `reconsume` -> `unconsume`, `undoConsume`, `undo`, `previous`, `resetToPrevious`, `decrementIndex`
    Nothing is consumed which reconsume seems to imply, instead it moves the index back one place so the next call to `consume` may indeed reconsume a token; reconsume as-is would do nothing (and if it returned anything it would be same the as `current`.
- `boolean peekMany(Predicate...)` -> `matches`
    It doesn't work like `peek`.  `peekMany` would imply it returns a `List<Token>`; it also doesn't convey that it returns a `boolean`.
- `reset(int)` -> `setIndex`
    This seems to be similar to what say `InputStream` provides, but `InputStream` hides the `index` parameter so you can't set it to some arbitrary value (like skipping ahead).  If you want to mimic this pattern, I'd suggest removing the parameter and providing a `mark` method (or make it non-public).

modules/javafx.graphics/src/main/java/javafx/css/Rule.java line 64:

> 62:     private final MediaRule mediaRule;
> 63: 
> 64:     private List<Selector> selectors = null;

I dislike the accessor pattern somewhat; I wonder, would it be possible to:

- Make `Rule` sealed: `public abstract sealed class Rule permits InternalRule` (*) similar to how this was done for `Selector`: `public abstract sealed class Selector permits SimpleSelector, CompoundSelector`
- `InternalRule` would not be public API, and can therefore introduce a public method to get `MediaRule`
- `Rule` does not have a public constructor, so IMHO there's no risk in there ever being an instance of `Rule` directly (similar reasoning was used to when the `SimpleSelector` / `CompountSelector` refactoring was done).

This way getting access to the `getMediaRule` method is just a simple `instanceof` check away:

     MediaRule rule = rule instanceof InternalRule ir ? ir.getMediaRule() : null;

(*) `InternalRule` is just a place holder name

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

PR Review: https://git.openjdk.org/jfx/pull/1655#pullrequestreview-2763805568
PR Review Comment: https://git.openjdk.org/jfx/pull/1655#discussion_r2041819506
PR Review Comment: https://git.openjdk.org/jfx/pull/1655#discussion_r2041903462
PR Review Comment: https://git.openjdk.org/jfx/pull/1655#discussion_r2041884462
PR Review Comment: https://git.openjdk.org/jfx/pull/1655#discussion_r2041936819
PR Review Comment: https://git.openjdk.org/jfx/pull/1655#discussion_r2041960550


More information about the openjfx-dev mailing list