Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@
.left40 {
margin-left: 40px;
}
.nowrap {
white-space: nowrap;
}
</style>
</head>
<body>
Expand Down Expand Up @@ -864,7 +867,7 @@ <h4>@media</h4>
<span class="grammar">&lt;media-feature&gt;:</span>
</figcaption>
</figure>
<h5>Evaluating Media Features in a Boolean Context</h5>
<h5 id="mf-boolean-context">Evaluating Media Features in a Boolean Context</h5>
<p>If the colon and value is omitted, the media feature is evaluated in a boolean context.
This is a convenient shorthand for features that have a reasonable default value. A media feature that is
evaluated in a boolean context evaluates to <code>true</code> if it would be <code>true</code> for any value
Expand Down Expand Up @@ -942,6 +945,12 @@ <h5>Combining Media Features</h5>
</ul>
<table class="csspropertytable" id="mediafeatures">
<caption>Available media features</caption>
<colgroup>
<col style="min-width: 230px">
<col style="width: 30%">
<col style="min-width: 80px">
<col style="width: 70%">
</colgroup>
<tbody>
<tr>
<th class="propertyname subheader" scope="col">Viewport Characteristics</th>
Expand All @@ -962,7 +971,7 @@ <h5>Combining Media Features</h5>
<td>corresponds to <code>Scene.height</code></td>
</tr>
<tr>
<td class="value">aspect-ratio</td>
<td class="value nowrap">aspect-ratio</td>
<td class="value"><a href="#typenumber" class="typeref">&lt;number&gt;</a></td>
<td>range</td>
<td>aspect ratio = <code>width</code> / <code>height</code></td>
Expand All @@ -974,7 +983,7 @@ <h5>Combining Media Features</h5>
<td><code>portrait</code> if <code>height</code> &gt;= <code>width</code>, <code>landscape</code> otherwise</td>
</tr>
<tr>
<td class="value">display-mode</td>
<td class="value nowrap">display-mode</td>
<td class="value">fullscreen | standalone</td>
<td>discrete</td>
<td><code>fullscreen</code> if <code>Stage.isFullScreen()</code>, <code>standalone</code> otherwise</td>
Expand All @@ -986,35 +995,58 @@ <h5>Combining Media Features</h5>
<th class="subheader" scope="col">Comments</th>
</tr>
<tr>
<td class="value">prefers-color-scheme</td>
<td class="value nowrap">prefers-color-scheme</td>
<td class="value">light | dark</td>
<td>discrete</td>
<td></td>
</tr>
<tr>
<td class="value">prefers-reduced-data</td>
<td class="value nowrap">prefers-reduced-data</td>
<td class="value">no-preference | reduce</td>
<td>discrete</td>
<td><code>no-preference</code> evaluates as <code>false</code></td>
</tr>
<tr>
<td class="value">prefers-reduced-motion</td>
<td class="value nowrap">prefers-reduced-motion</td>
<td class="value">no-preference | reduce</td>
<td>discrete</td>
<td><code>no-preference</code> evaluates as <code>false</code></td>
</tr>
<tr>
<td class="value">prefers-reduced-transparency</td>
<td class="value nowrap">prefers-reduced-transparency</td>
<td class="value">no-preference | reduce</td>
<td>discrete</td>
<td><code>no-preference</code> evaluates as <code>false</code></td>
</tr>
<tr>
<td class="value">-fx-prefers-persistent-scrollbars</td>
<td class="value nowrap">-fx-prefers-persistent-scrollbars</td>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just noticed this: why the -fx prefix is here but not in all the others?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In CSS, this is called a vendor prefix. It is used for all symbols that have not yet achieved what amounts to universal concensus. JavaFX uses vendor prefixes quite extensively; in fact, almost all styleable properties are vendor-prefixed (though not all: visibility and transition are standard-compliant and don't use a vendor prefix).

Most of the media features we support are standard-compliant, and you'll find that they also work exactly the same in other CSS applications like web browsers. However, -fx-prefers-persistent-scrollbars is not a standard feature; neither is -fx-supports-conditional-feature.

<td class="value">no-preference | persistent</td>
<td>discrete</td>
<td><code>no-preference</code> evaluates as <code>false</code></td>
</tr>
<tr>
<th class="propertyname subheader" scope="col">Platform Features</th>
<th class="subheader" scope="col">Value</th>
<th class="subheader" scope="col">Type</th>
<th class="subheader" scope="col">Comments</th>
</tr>
<tr>
<td class="value nowrap">-fx-supports-conditional-feature</td>
<td class="value">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this cell looks way too busy. do you think it might be worth either extracting it into a dedicated table where each feature can be explained/described?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried arranging the constants in several different ways, including a table-like vertical listing. However, that introduces huge amounts of whitespace that doesn't help readability either. I'm not too excited about adding descriptions to all of the constants; that information is only a click away now that we're linking to the Platform.isSupported(ConditionalFeature) method. I'd like to avoid duplicating normative specification (the CSS reference is a normative specification, not merely an informational document).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, especially since you've added the link.

graphics | controls | media | web | swt | swing |
fxml | scene3d | effect | <span class="nowrap">shape-clip</span> |
<span class="nowrap">input-method</span> | <span class="nowrap">transparent-window</span> |
<span class="nowrap">unified-window</span> | <span class="nowrap">extended-window</span> |
<span class="nowrap">two-level-focus</span> | <span class="nowrap">virtual-keyboard</span> |
<span class="nowrap">input-touch</span> | <span class="nowrap">input-multitouch</span> |
<span class="nowrap">input-pointer</span></td>
<td>discrete</td>
<td>Evaluates to <code>true</code> if
<code><a href="../../application/Platform.html#isSupported(javafx.application.ConditionalFeature)">
Platform.isSupported(ConditionalFeature)</a></code> returns <code>true</code> for the specified
conditional feature.<br>
This media feature cannot be evaluated in a <a href="#mf-boolean-context">boolean context</a>.</td>
</tr>
</tbody>
</table>
<h3><a id="introexamples">Examples</a></h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@

package com.sun.javafx.css.media;

import com.sun.javafx.application.PlatformImpl;
import com.sun.javafx.css.media.expression.ConjunctionExpression;
import com.sun.javafx.css.media.expression.FunctionExpression;
import com.sun.javafx.css.media.expression.RangeExpression;
import com.sun.javafx.css.parser.Token;
import javafx.application.ColorScheme;
import javafx.application.ConditionalFeature;
import java.util.Locale;
import java.util.function.BiFunction;
import java.util.function.Function;
Expand Down Expand Up @@ -138,6 +140,17 @@ lowerCaseFeatureName, lowerCaseTextValue(featureValue),
case "-fx-prefers-persistent-scrollbars" -> booleanPreferenceExpression(
lowerCaseFeatureName, featureValue, "persistent", MediaQueryContext::isPersistentScrollBars);

case "-fx-supports-conditional-feature" -> {
String lowerCaseFeatureValue = checkNotNullValue(lowerCaseFeatureName, lowerCaseTextValue(featureValue));
var feature = enumValue(ConditionalFeature::valueOf, lowerCaseFeatureName, lowerCaseFeatureValue);
boolean supported = PlatformImpl.isSupported(feature);

yield FunctionExpression.of(
lowerCaseFeatureName, lowerCaseFeatureValue,
() -> supported ? TriState.TRUE : TriState.FALSE,
_ -> supported, true);
}

default -> DEFAULT.apply(featureName, featureValue);
};
}
Expand Down Expand Up @@ -217,7 +230,7 @@ private static <T> T checkNotNullValue(String featureName, T featureValue) {

private static <T extends Enum<T>> T enumValue(Function<String, T> func, String featureName, String featureValue) {
try {
return func.apply(featureValue.toUpperCase(Locale.ROOT));
return func.apply(featureValue.toUpperCase(Locale.ROOT).replace('-', '_'));
} catch (IllegalArgumentException e) {
throw unknownValue(featureName, featureValue);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
package test.com.sun.javafx.css.media;

import com.sun.javafx.css.media.MediaQuerySerializer;
import com.sun.javafx.css.media.MediaFeatures;
import com.sun.javafx.css.media.SizeQueryType;
import com.sun.javafx.css.media.TriState;
import com.sun.javafx.css.media.expression.ConjunctionExpression;
import com.sun.javafx.css.media.expression.ConstantExpression;
import com.sun.javafx.css.media.expression.EqualExpression;
Expand All @@ -38,6 +40,8 @@
import com.sun.javafx.css.media.expression.NegationExpression;
import com.sun.javafx.css.media.expression.DisjunctionExpression;
import com.sun.javafx.css.media.MediaQuery;
import com.sun.javafx.css.parser.CssLexer;
import com.sun.javafx.css.parser.Token;
import javafx.css.Size;
import javafx.css.SizeUnits;
import javafx.css.StyleConverter;
Expand Down Expand Up @@ -177,6 +181,25 @@ void serializeComplexExpression() throws IOException {
assertEquals(expected, actual);
}

@Test
void serializeConditionalFeatureExpression() throws IOException {
var supported = MediaFeatures.discreteQueryExpression(
"-fx-supports-conditional-feature", new Token(CssLexer.IDENT, "scene3d"));

var expected = FunctionExpression.of("-fx-supports-conditional-feature", "scene3d", _ -> null, true);
var actual = deserialize(serialize(supported));
assertEquals(expected, actual);
assertEquals(TriState.TRUE, actual.evaluate());

var unsupported = MediaFeatures.discreteQueryExpression(
"-fx-supports-conditional-feature", new Token(CssLexer.IDENT, "media"));

expected = FunctionExpression.of("-fx-supports-conditional-feature", "media", _ -> null, true);
actual = deserialize(serialize(unsupported));
assertEquals(expected, actual);
assertEquals(TriState.FALSE, actual.evaluate());
}

private byte[] serialize(MediaQuery mediaQuery) throws IOException {
var output = new ByteArrayOutputStream();
MediaQuerySerializer.writeBinary(mediaQuery, new DataOutputStream(output), stringStore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
package test.javafx.css;

import com.sun.javafx.css.RuleHelper;
import com.sun.javafx.css.media.MediaFeatures;
import com.sun.javafx.css.media.SizeQueryType;
import com.sun.javafx.css.media.TriState;
import com.sun.javafx.css.media.expression.ConjunctionExpression;
import com.sun.javafx.css.media.expression.ConstantExpression;
import com.sun.javafx.css.media.expression.DisjunctionExpression;
Expand All @@ -37,12 +39,15 @@
import com.sun.javafx.css.media.expression.LessExpression;
import com.sun.javafx.css.media.expression.LessOrEqualExpression;
import com.sun.javafx.css.media.expression.NegationExpression;
import com.sun.javafx.scene.SceneContext;
import javafx.application.ColorScheme;
import javafx.css.CssParser;
import javafx.css.CssParserShim;
import javafx.css.Size;
import javafx.css.SizeUnits;
import javafx.css.Stylesheet;
import javafx.scene.Group;
import javafx.scene.Scene;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -336,6 +341,78 @@ void parsePrefersPersistentScrollBars_booleanContext() {
mediaRule.getQueries().getFirst());
}

@Test
void parseSupportsConditionalFeature() {
Stylesheet stylesheet = new CssParser().parse("""
@media (-fx-supports-conditional-feature: ScEnE3D),
(-fx-supports-conditional-feature: transparent-window),
(-fx-supports-conditional-feature: media) {
.foo { bar: baz; }
}
""");

var context = new SceneContext(new Scene(new Group()));
var mediaRule = RuleHelper.getMediaRule(stylesheet.getRules().getFirst());
assertEquals(3, mediaRule.getQueries().size());

var expected = FunctionExpression.of("-fx-supports-conditional-feature", "scene3d", _ -> null, true);
var actual = mediaRule.getQueries().get(0);
assertEquals(expected, actual);
assertEquals(TriState.TRUE, actual.evaluate());
assertTrue(expected.evaluate(context));

expected = FunctionExpression.of("-fx-supports-conditional-feature", "transparent-window", _ -> null, true);
actual = mediaRule.getQueries().get(1);
assertEquals(expected, actual);
assertEquals(TriState.TRUE, actual.evaluate());
assertTrue(expected.evaluate(context));

expected = FunctionExpression.of("-fx-supports-conditional-feature", "media", _ -> null, true);
actual = mediaRule.getQueries().get(2);
assertEquals(expected, actual);
assertEquals(TriState.FALSE, actual.evaluate());
assertFalse(expected.evaluate(context));
}

@Test
void invalidConditionalFeatureValueEvaluatesToFalse() {
String stylesheetText = """
@media (-fx-supports-conditional-feature: invalid-feature) {
.foo { bar: baz; }
}
""";

Stylesheet stylesheet = new CssParserShim().parseUnmerged(stylesheetText, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the case of invalid conditional feature like

@media (-fx-supports-conditional-feature: xxx)
{
    .button { -fx-background-color: red; }
}

the CssParser writes to stderr - is that expected?
if so, should it be tested?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I think that CssParser is one of the... let's call it least well-developed parts of JavaFX. Errors from MediaQueryParser are fed back into CssParser, where they are treated like all other errors. So that's not "new" code at work here.

Should the way CssParser reports errors be tested? Maybe, but then again, that time is better spent just discarding it entirely and replacing it with an implementation that's not as bad. In any case, it's out of scope for this PR because the changes here don't touch error reporting at all.


var mediaRule = RuleHelper.getMediaRule(stylesheet.getRules().getFirst());
assertEquals(1, mediaRule.getQueries().size());
assertEquals(ConstantExpression.of(false), mediaRule.getQueries().getFirst());

stylesheet = new CssParser().parse(stylesheetText);
assertEquals(0, stylesheet.getRules().size());
}

@Test
void supportsConditionalFeatureCannotBeUsedInBooleanContext() {
assertThrows(IllegalArgumentException.class,
() -> MediaFeatures.discreteQueryExpression("-fx-supports-conditional-feature", null));

String stylesheetText = """
@media (-fx-supports-conditional-feature) {
.foo { bar: baz; }
}
""";

Stylesheet stylesheet = new CssParserShim().parseUnmerged(stylesheetText, true);

var mediaRule = RuleHelper.getMediaRule(stylesheet.getRules().getFirst());
assertEquals(1, mediaRule.getQueries().size());
assertEquals(ConstantExpression.of(false), mediaRule.getQueries().getFirst());

stylesheet = new CssParser().parse(stylesheetText);
assertEquals(0, stylesheet.getRules().size());
}

@Test
void emptyMediaQuery() {
Stylesheet stylesheet = new CssParser().parse("""
Expand Down