CSS Lookups and their origins (possible regression)

John Hendrikx john.hendrikx at gmail.com
Tue Jul 9 00:07:20 UTC 2024


Hi List,

TLDR; should a CSS reference like -fx-base convert all styles that use 
this value (or derive from it) become AUTHOR level styles (higher 
priority than setters) ?

Long version:

In JavaFX 21, I did a fix (see #1072) to solve a problem where a CSS 
value could be reset on an unrelated control.

This happened when the CSS engine encountered a stylable that is 
overridden by the user (with a setter), and decided NOT to proceed with 
the full CSS value calculation (as it could not override the user 
setting if that CSS value had lower priority).  However, not proceeding 
with the calculation meant that a "SKIP" was stored in a shared cache 
which was incorrect.  This is because when this "SKIP" is later 
encountered for an unrelated control (the cache entries are shared for 
controls with the same styles at the same level), they could get their 
values reset because they were assumed to be unstyled.

However, this fix has exposed what seems to be a deeper bug or perhaps 
an unfortunate default:

JavaFX has a special feature where you can refer to certain other styles 
by using a reference (which is resolved, recursively, to a final 
value).  This does not seem to be a CSS standard, but is a feature only 
FX has.

It works by saying something like:

     -fx-base: RED;

And then using it like this:

     -fx-text-fill: -fx-base;

This feature works accross stylesheets of different origins, so an 
AUTHOR stylesheet can specify -fx-base, and when a USER_AGENT refers to 
-fx-base, the value comes from the AUTHOR stylesheet.

JavaFX then changes the origin of the style to the highest priority 
encountered while resolving the reference.  This means that Modena can 
specify "-fx-text-fill: -fx-base", and when "-fx-base" is then part of 
the AUTHOR style sheet, that ALL Modena styles that use -fx-base will be 
considered AUTHOR level styles, as per this comment:

// The origin of this parsed value is the greatest of

// any of the resolved reference. If a resolved reference

// comes from an inline style, for example, then the value

// calculated from the resolved lookup should have inline

// as its origin. Otherwise, an inline style could be

// stored in shared cache.

I feel that this is a really unfortunate choice.  The style after all 
was specified by Modena, only its value came from another (higher 
priority) style sheet.  I think a more logical choice would have been to 
not change the priority at all, unless a "-fx-text-fill" is explicitly 
made part of the AUTHOR stylesheet.

A consequence of this (and which is much more visible after the fix) is 
that creating a Label with a setTextFill(Color.YELLOW) in its 
constructor will only result in a yellow text fill if the AUTHOR 
stylesheet did not override any of the Modena colors involved in 
calculating the Modena -fx-text-fill default. Overriding -fx-base in any 
way will result in the text fill of the label to be overridden (as the 
reference came from an AUTHOR stylesheet, which trumps a setter which is 
of a lower style origin).

The comment also alludes to a potential problem.  If an inline style 
would specify "-fx-base", but would be treated as if it came from Modena 
(USER_AGENT level), then this value could get stored in the cache as 
everything except INLINE styles can be cached. However, I feel that the 
changing of style origin level was then probably done to solve a CACHING 
problem, instead of what made logical sense for users.  If we agree that 
a resolved reference should not change the style origin level, then this 
would need to be addressed, by perhaps marking such a calculated value 
as uncacheable, instead of overloading the meaning of style origin.

I'd like to hear your thoughts, and also how to proceed.  JavaFX 
versions before 21 seemingly allowed overriding reference without much 
consequence because if the user overrode the value manually, the cache 
entry would be set to "SKIP".  Now that this is no longer the case, 
JavaFX more aggressively overrides user set values if they happen to use 
a referenced value.  See code below.

--John

.root {

-fx-base: #ff0000;

}

packageapp;

importjavafx.application.Application;

importjavafx.scene.Scene;

importjavafx.scene.control.Label;

importjavafx.scene.paint.Color;

importjavafx.stage.Stage;

publicclassTestApp extendsApplication {

publicstaticvoidmain(String[] args) {

launch(args);

}

@Override

publicvoidstart(Stage primaryStage) {

Scene scene = newScene(newMyLabel());

// See the difference with/without -fx-base in the stylesheet

scene.getStylesheets().add(TestApp.class.getResource("/style.css").toExternalForm());

primaryStage.setScene(scene);

primaryStage.show();

}

}

classMyLabel extendsLabel {

publicMyLabel() {

setTextFill(Color.YELLOW);

setText("Hello world");

}

}

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20240709/bc40400f/attachment.htm>


More information about the openjfx-dev mailing list