Diff
Modified: trunk/LayoutTests/imported/w3c/ChangeLog (290036 => 290037)
--- trunk/LayoutTests/imported/w3c/ChangeLog 2022-02-17 19:05:45 UTC (rev 290036)
+++ trunk/LayoutTests/imported/w3c/ChangeLog 2022-02-17 19:38:06 UTC (rev 290037)
@@ -1,3 +1,12 @@
+2022-02-17 Antti Koivisto <an...@apple.com>
+
+ [CSS Container Queries] Support full range notation in size queries
+ https://bugs.webkit.org/show_bug.cgi?id=236771
+
+ Reviewed by Dean Jackson.
+
+ * web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt:
+
2022-02-17 Matt Woodrow <mattwood...@apple.com>
Fix abs-pos breadth issue when using 'auto'
Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt (290036 => 290037)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt 2022-02-17 19:05:45 UTC (rev 290036)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/at-container-parsing-expected.txt 2022-02-17 19:38:06 UTC (rev 290037)
@@ -21,16 +21,16 @@
PASS size((width: 100px) and (height: 100px))
PASS size((width: 50px) or (height: 100px))
PASS size(width < 100px)
-FAIL size(100px < width) assert_equals: expected 2 but got 0
-FAIL size(100px < width < 200px) assert_equals: expected 2 but got 0
+PASS size(100px < width)
+PASS size(100px < width < 200px)
PASS foo(width)
PASS size(asdf)
-FAIL size(resolution > 100dpi) assert_equals: expected 2 but got 0
-FAIL size(resolution: 150dpi) assert_equals: expected 2 but got 0
+PASS size(resolution > 100dpi)
+PASS size(resolution: 150dpi)
PASS size(color)
-FAIL size(min-color: 1) assert_equals: expected 2 but got 0
-FAIL size(color-index >= 1) assert_equals: expected 2 but got 0
-FAIL (color-index >= 1) assert_equals: expected 2 but got 0
+PASS size(min-color: 1)
+PASS size(color-index >= 1)
+PASS (color-index >= 1)
PASS size(grid)
PASS (grid)
PASS screen
Modified: trunk/Source/WebCore/ChangeLog (290036 => 290037)
--- trunk/Source/WebCore/ChangeLog 2022-02-17 19:05:45 UTC (rev 290036)
+++ trunk/Source/WebCore/ChangeLog 2022-02-17 19:38:06 UTC (rev 290037)
@@ -1,3 +1,38 @@
+2022-02-17 Antti Koivisto <an...@apple.com>
+
+ [CSS Container Queries] Support full range notation in size queries
+ https://bugs.webkit.org/show_bug.cgi?id=236771
+
+ Reviewed by Dean Jackson.
+
+ Parse and evaluate notations like (100px < width) and (100px < width < 200px).
+
+ * css/ContainerQuery.h:
+
+ There are now optional left and right comparisons.
+
+ * css/ContainerQueryParser.cpp:
+ (WebCore::ContainerQueryParser::consumeSizeQuery):
+ (WebCore::ContainerQueryParser::consumeSizeFeature):
+ (WebCore::ContainerQueryParser::consumePlainSizeFeature):
+
+ Evaluate the plain (foo: value) notation.
+
+ (WebCore::ContainerQueryParser::consumeRangeSizeFeature):
+
+ Evaluate range notation.
+
+ (WebCore::ContainerQueryParser::consumeValue):
+ * css/ContainerQueryParser.h:
+ * style/ContainerQueryEvaluator.cpp:
+ (WebCore::Style::ContainerQueryEvaluator::evaluateSizeFeature const):
+
+ Evaluate both comparisons if they exist.
+
+ * style/ContainerQueryEvaluator.h:
+ (WebCore::Style::toEvaluationResult):
+ (WebCore::Style::operator|):
+
2022-02-17 Matt Woodrow <mattwood...@apple.com>
Fix abs-pos breadth issue when using 'auto'
Modified: trunk/Source/WebCore/css/ContainerQuery.h (290036 => 290037)
--- trunk/Source/WebCore/css/ContainerQuery.h 2022-02-17 19:05:45 UTC (rev 290036)
+++ trunk/Source/WebCore/css/ContainerQuery.h 2022-02-17 19:38:06 UTC (rev 290037)
@@ -42,7 +42,7 @@
using ContainerQuery = std::variant<ContainerCondition, SizeQuery, UnknownQuery>;
enum class LogicalOperator : uint8_t { And, Or, Not };
-enum class ComparisonOperator : uint8_t { LessThan, LessThanOrEqual, Equal, GreaterThan, GreaterThanOrEqual, True };
+enum class ComparisonOperator : uint8_t { LessThan, LessThanOrEqual, Equal, GreaterThan, GreaterThanOrEqual };
struct ContainerCondition {
LogicalOperator logicalOperator { LogicalOperator::And };
@@ -54,10 +54,15 @@
Vector<SizeQuery> queries;
};
+struct Comparison {
+ ComparisonOperator op { ComparisonOperator::Equal };
+ RefPtr<CSSValue> value;
+};
+
struct SizeFeature {
- ComparisonOperator comparisonOperator { ComparisonOperator::Equal };
AtomString name;
- RefPtr<CSSValue> value;
+ std::optional<Comparison> leftComparison;
+ std::optional<Comparison> rightComparison;
};
namespace FeatureNames {
Modified: trunk/Source/WebCore/css/ContainerQueryParser.cpp (290036 => 290037)
--- trunk/Source/WebCore/css/ContainerQueryParser.cpp 2022-02-17 19:05:45 UTC (rev 290036)
+++ trunk/Source/WebCore/css/ContainerQueryParser.cpp 2022-02-17 19:38:06 UTC (rev 290037)
@@ -136,7 +136,7 @@
std::optional<CQ::SizeQuery> ContainerQueryParser::consumeSizeQuery(CSSParserTokenRange& range)
{
- if (range.peek().type() != IdentToken || range.peek().id() == CSSValueNot) {
+ if (range.peek().type() == LeftParenthesisToken || (range.peek().type() == IdentToken && range.peek().id() == CSSValueNot)) {
auto sizeCondition = consumeCondition<CQ::SizeCondition>(range);
if (!sizeCondition)
return { };
@@ -147,82 +147,142 @@
if (!sizeFeature)
return { };
- range.consumeWhitespace();
- if (!range.atEnd())
- return { };
-
return { *sizeFeature };
}
std::optional<CQ::SizeFeature> ContainerQueryParser::consumeSizeFeature(CSSParserTokenRange& range)
{
- // FIXME: Support value-first (100px < width) and full range (100px < width < 200px) notations.
+ auto rangeCopy = range;
+ if (auto sizeFeature = consumePlainSizeFeature(range))
+ return sizeFeature;
- auto nameToken = range.consumeIncludingWhitespace();
- ASSERT(nameToken.type() == IdentToken);
+ range = rangeCopy;
+ return consumeRangeSizeFeature(range);
+}
- auto name = nameToken.value();
+std::optional<CQ::SizeFeature> ContainerQueryParser::consumePlainSizeFeature(CSSParserTokenRange& range)
+{
+ auto consumePlainFeatureName = [&]() -> std::pair<AtomString, CQ::ComparisonOperator> {
+ if (range.peek().type() != IdentToken)
+ return { };
+ auto token = range.consumeIncludingWhitespace();
+ auto name = token.value();
+ if (name.startsWith("min-"))
+ return { name.substring(4).toAtomString(), CQ::ComparisonOperator::GreaterThanOrEqual };
+ if (name.startsWith("max-"))
+ return { name.substring(4).toAtomString(), CQ::ComparisonOperator::LessThanOrEqual };
+ return { name.toAtomString(), CQ::ComparisonOperator::Equal };
+ };
+
+ auto [featureName, op] = consumePlainFeatureName();
+ if (featureName.isEmpty())
+ return { };
+
+ range.consumeWhitespace();
+
+ if (range.atEnd()) {
+ if (op != CQ::ComparisonOperator::Equal)
+ return { };
+ return CQ::SizeFeature { featureName, { }, { } };
+ }
+
+ if (range.peek().type() != ColonToken)
+ return { };
+
+ range.consumeIncludingWhitespace();
if (range.atEnd())
- return CQ::SizeFeature { CQ::ComparisonOperator::True, name.toAtomString(), { } };
+ return { };
- auto consumeOperator = [&]() -> std::optional<CQ::ComparisonOperator> {
- auto opToken = range.consume();
+ auto value = consumeValue(range);
+
+ return CQ::SizeFeature { featureName, { }, CQ::Comparison { op, WTFMove(value) } };
+}
+
+std::optional<CQ::SizeFeature> ContainerQueryParser::consumeRangeSizeFeature(CSSParserTokenRange& range)
+{
+ auto consumeFeatureName = [&]() -> AtomString {
+ if (range.peek().type() != IdentToken)
+ return { };
+ return range.consumeIncludingWhitespace().value().toAtomString();
+ };
+
+ auto consumeRangeOperator = [&]() -> std::optional<CQ::ComparisonOperator> {
if (range.atEnd())
return { };
- if (opToken.type() == ColonToken) {
- if (name.startsWith("min-")) {
- name = name.substring(4);
- return CQ::ComparisonOperator::GreaterThanOrEqual;
- }
- if (name.startsWith("max-")) {
- name = name.substring(4);
+ auto opToken = range.consume();
+ if (range.atEnd() || opToken.type() != DelimiterToken)
+ return { };
+
+ switch (opToken.delimiter()) {
+ case '=':
+ range.consumeWhitespace();
+ return CQ::ComparisonOperator::Equal;
+ case '<':
+ if (range.peek().type() == DelimiterToken && range.peek().delimiter() == '=') {
+ range.consumeIncludingWhitespace();
return CQ::ComparisonOperator::LessThanOrEqual;
}
- return CQ::ComparisonOperator::Equal;
- }
- if (opToken.type() == DelimiterToken) {
- switch (opToken.delimiter()) {
- case '=':
- return CQ::ComparisonOperator::Equal;
- case '<':
- if (range.peek().type() == DelimiterToken && range.peek().delimiter() == '=') {
- range.consume();
- return CQ::ComparisonOperator::LessThanOrEqual;
- }
- return CQ::ComparisonOperator::LessThan;
- case '>':
- if (range.peek().type() == DelimiterToken && range.peek().delimiter() == '=') {
- range.consume();
- return CQ::ComparisonOperator::GreaterThanOrEqual;
- }
- return CQ::ComparisonOperator::GreaterThan;
- default:
- return { };
+ range.consumeWhitespace();
+ return CQ::ComparisonOperator::LessThan;
+ case '>':
+ if (range.peek().type() == DelimiterToken && range.peek().delimiter() == '=') {
+ range.consumeIncludingWhitespace();
+ return CQ::ComparisonOperator::GreaterThanOrEqual;
}
+ range.consumeWhitespace();
+ return CQ::ComparisonOperator::GreaterThan;
+ default:
+ return { };
}
- return { };
};
- auto op = consumeOperator();
- if (!op)
- return { };
+ auto consumeLeftComparison = [&]() -> std::optional<CQ::Comparison> {
+ if (range.peek().type() == IdentToken)
+ return { };
+ auto value = consumeValue(range);
+ auto op = consumeRangeOperator();
+ if (!op)
+ return { };
- range.consumeWhitespace();
+ return CQ::Comparison { *op, WTFMove(value) };
+ };
- auto featureName = name.toAtomString();
+ auto consumeRightComparison = [&]() -> std::optional<CQ::Comparison> {
+ auto op = consumeRangeOperator();
+ if (!op)
+ return { };
+ auto value = consumeValue(range);
- auto consumeValue = [&]() -> RefPtr<CSSValue> {
- if (featureName == CQ::FeatureNames::orientation())
- return CSSPropertyParserHelpers::consumeIdent(range);
- if (featureName == CQ::FeatureNames::aspectRatio())
- return CSSPropertyParserHelpers::consumeAspectRatioValue(range);
- return CSSPropertyParserHelpers::consumeLength(range, m_context.mode, ValueRange::All);
+ return CQ::Comparison { *op, WTFMove(value) };
};
- auto value = consumeValue();
+ auto leftComparison = consumeLeftComparison();
- return CQ::SizeFeature { *op, WTFMove(featureName), WTFMove(value) };
+ auto featureName = consumeFeatureName();
+ if (featureName.isEmpty())
+ return { };
+
+ auto rightComparison = consumeRightComparison();
+
+ if (!leftComparison && !rightComparison)
+ return { };
+
+ return CQ::SizeFeature { WTFMove(featureName), WTFMove(leftComparison), WTFMove(rightComparison) };
}
+RefPtr<CSSValue> ContainerQueryParser::consumeValue(CSSParserTokenRange& range)
+{
+ if (range.atEnd())
+ return nullptr;
+ if (auto value = CSSPropertyParserHelpers::consumeIdent(range))
+ return value;
+ if (auto value = CSSPropertyParserHelpers::consumeLength(range, m_context.mode, ValueRange::All))
+ return value;
+ if (auto value = CSSPropertyParserHelpers::consumeAspectRatioValue(range))
+ return value;
+ range.consumeIncludingWhitespace();
+ return nullptr;
}
+
+}
Modified: trunk/Source/WebCore/css/ContainerQueryParser.h (290036 => 290037)
--- trunk/Source/WebCore/css/ContainerQueryParser.h 2022-02-17 19:05:45 UTC (rev 290036)
+++ trunk/Source/WebCore/css/ContainerQueryParser.h 2022-02-17 19:38:06 UTC (rev 290037)
@@ -41,6 +41,9 @@
std::optional<CQ::SizeQuery> consumeSizeQuery(CSSParserTokenRange&);
template<typename ConditionType> std::optional<ConditionType> consumeCondition(CSSParserTokenRange&);
std::optional<CQ::SizeFeature> consumeSizeFeature(CSSParserTokenRange&);
+ std::optional<CQ::SizeFeature> consumePlainSizeFeature(CSSParserTokenRange&);
+ std::optional<CQ::SizeFeature> consumeRangeSizeFeature(CSSParserTokenRange&);
+ RefPtr<CSSValue> consumeValue(CSSParserTokenRange&);
ContainerQueryParser(const CSSParserContext& context)
: m_context(context) { }
Modified: trunk/Source/WebCore/style/ContainerQueryEvaluator.cpp (290036 => 290037)
--- trunk/Source/WebCore/style/ContainerQueryEvaluator.cpp 2022-02-17 19:05:45 UTC (rev 290036)
+++ trunk/Source/WebCore/style/ContainerQueryEvaluator.cpp 2022-02-17 19:38:06 UTC (rev 290037)
@@ -76,7 +76,7 @@
auto container = resolveContainer();
if (!container)
- return { };
+ return false;
return evaluateQuery(filteredContainerQuery.query, *container) == EvaluationResult::True;
}
@@ -108,16 +108,8 @@
return EvaluationResult::Unknown;
switch (condition.logicalOperator) {
- case CQ::LogicalOperator::Not: {
- switch (evaluateQuery(condition.queries.first(), container)) {
- case EvaluationResult::True:
- return EvaluationResult::False;
- case EvaluationResult::False:
- return EvaluationResult::True;
- case EvaluationResult::Unknown:
- return EvaluationResult::Unknown;
- }
- }
+ case CQ::LogicalOperator::Not:
+ return !evaluateQuery(condition.queries.first(), container);
case CQ::LogicalOperator::And: {
auto result = EvaluationResult::True;
for (auto query : condition.queries) {
@@ -162,12 +154,8 @@
auto ContainerQueryEvaluator::evaluateSizeFeature(const CQ::SizeFeature& sizeFeature, const ResolvedContainer& container) const -> EvaluationResult
{
- auto toEvaluationResult = [](bool boolean) {
- return boolean ? EvaluationResult::True : EvaluationResult::False;
- };
-
- auto compare = [&](auto left, auto right) {
- switch (sizeFeature.comparisonOperator) {
+ auto compare = [](CQ::ComparisonOperator op, auto left, auto right) {
+ switch (op) {
case CQ::ComparisonOperator::LessThan:
return left < right;
case CQ::ComparisonOperator::GreaterThan:
@@ -178,21 +166,55 @@
return left >= right;
case CQ::ComparisonOperator::Equal:
return left == right;
- case CQ::ComparisonOperator::True:
- ASSERT_NOT_REACHED();
- return false;
}
};
+ enum class Side : uint8_t { Left, Right };
+ auto evaluateSizeComparison = [&](LayoutUnit size, const std::optional<CQ::Comparison>& comparison, Side side) {
+ if (!comparison)
+ return EvaluationResult::True;
+ auto expressionSize = computeSize(comparison->value.get(), container.conversionData);
+ if (!expressionSize)
+ return EvaluationResult::Unknown;
+ auto left = side == Side::Left ? *expressionSize : size;
+ auto right = side == Side::Left ? size : *expressionSize;
+
+ return toEvaluationResult(compare(comparison->op, left, right));
+ };
+
auto evaluateSize = [&](LayoutUnit size) {
- if (sizeFeature.comparisonOperator == CQ::ComparisonOperator::True)
+ if (!sizeFeature.leftComparison && !sizeFeature.rightComparison)
return toEvaluationResult(!!size);
- auto expressionSize = computeSize(sizeFeature.value.get(), container.conversionData);
- if (!expressionSize)
+ auto leftResult = evaluateSizeComparison(size, sizeFeature.leftComparison, Side::Left);
+ auto rightResult = evaluateSizeComparison(size, sizeFeature.rightComparison, Side::Right);
+
+ return leftResult & rightResult;
+ };
+
+ auto evaluateAspectRatioComparison = [&](double aspectRatio, const std::optional<CQ::Comparison>& comparison, Side side) {
+ if (!comparison)
+ return EvaluationResult::True;
+
+ if (!is<CSSValueList>(comparison->value))
return EvaluationResult::Unknown;
- return toEvaluationResult(compare(size, *expressionSize));
+ auto& ratioList = downcast<CSSValueList>(*comparison->value);
+ if (ratioList.length() != 2)
+ return EvaluationResult::Unknown;
+
+ auto first = dynamicDowncast<CSSPrimitiveValue>(ratioList.item(0));
+ auto second = dynamicDowncast<CSSPrimitiveValue>(ratioList.item(1));
+
+ if (!first || !second || !first->isNumberOrInteger() || !second->isNumberOrInteger())
+ return EvaluationResult::Unknown;
+
+ auto expressionRatio = first->doubleValue() / second->doubleValue();
+
+ auto left = side == Side::Left ? expressionRatio : aspectRatio;
+ auto right = side == Side::Left ? aspectRatio : expressionRatio;
+
+ return toEvaluationResult(compare(comparison->op, left, right));
};
enum class Axis : uint8_t { Both, Block, Inline, Width, Height };
@@ -245,25 +267,14 @@
return EvaluationResult::Unknown;
auto boxRatio = container.renderer.contentWidth().toDouble() / container.renderer.contentHeight().toDouble();
- if (sizeFeature.comparisonOperator == CQ::ComparisonOperator::True)
+
+ if (!sizeFeature.leftComparison && !sizeFeature.rightComparison)
return toEvaluationResult(!!boxRatio);
- if (!is<CSSValueList>(sizeFeature.value))
- return EvaluationResult::Unknown;
+ auto leftResult = evaluateAspectRatioComparison(boxRatio, sizeFeature.leftComparison, Side::Left);
+ auto rightResult = evaluateAspectRatioComparison(boxRatio, sizeFeature.rightComparison, Side::Right);
- auto& ratioList = downcast<CSSValueList>(*sizeFeature.value);
- if (ratioList.length() != 2)
- return EvaluationResult::Unknown;
-
- auto first = dynamicDowncast<CSSPrimitiveValue>(ratioList.item(0));
- auto second = dynamicDowncast<CSSPrimitiveValue>(ratioList.item(1));
-
- if (!first || !second || !first->isNumberOrInteger() || !second->isNumberOrInteger())
- return EvaluationResult::Unknown;
-
- auto expressionRatio = first->doubleValue() / second->doubleValue();
-
- return toEvaluationResult(compare(boxRatio, expressionRatio));
+ return leftResult & rightResult;
}
if (sizeFeature.name == CQ::FeatureNames::orientation()) {
@@ -270,11 +281,16 @@
if (!containerSupportsRequiredAxis(Axis::Both))
return EvaluationResult::Unknown;
- if (!is<CSSPrimitiveValue>(sizeFeature.value) || sizeFeature.comparisonOperator != CQ::ComparisonOperator::Equal)
+ if (!sizeFeature.rightComparison)
return EvaluationResult::Unknown;
- auto& value = downcast<CSSPrimitiveValue>(*sizeFeature.value);
+ auto& comparison = *sizeFeature.rightComparison;
+ if (!is<CSSPrimitiveValue>(comparison.value) || comparison.op != CQ::ComparisonOperator::Equal)
+ return EvaluationResult::Unknown;
+
+ auto& value = downcast<CSSPrimitiveValue>(*sizeFeature.rightComparison->value);
+
bool isPortrait = container.renderer.contentHeight() >= container.renderer.contentWidth();
if (value.valueID() == CSSValuePortrait)
return toEvaluationResult(isPortrait);
Modified: trunk/Source/WebCore/style/ContainerQueryEvaluator.h (290036 => 290037)
--- trunk/Source/WebCore/style/ContainerQueryEvaluator.h 2022-02-17 19:05:45 UTC (rev 290036)
+++ trunk/Source/WebCore/style/ContainerQueryEvaluator.h 2022-02-17 19:38:06 UTC (rev 290037)
@@ -33,6 +33,8 @@
namespace Style {
+enum class EvaluationResult : uint8_t { False, True, Unknown };
+
class ContainerQueryEvaluator {
public:
ContainerQueryEvaluator(const Vector<Ref<const Element>>& containers);
@@ -41,7 +43,6 @@
private:
struct ResolvedContainer;
- enum class EvaluationResult : uint8_t { False, True, Unknown };
EvaluationResult evaluateQuery(const CQ::ContainerQuery&, const ResolvedContainer&) const;
EvaluationResult evaluateQuery(const CQ::SizeQuery&, const ResolvedContainer&) const;
@@ -51,5 +52,31 @@
const Vector<Ref<const Element>>& m_containers;
};
+inline EvaluationResult toEvaluationResult(bool boolean)
+{
+ return boolean ? EvaluationResult::True : EvaluationResult::False;
+};
+
+inline EvaluationResult operator&(EvaluationResult left, EvaluationResult right)
+{
+ if (left == EvaluationResult::Unknown || right == EvaluationResult::Unknown)
+ return EvaluationResult::Unknown;
+ if (left == EvaluationResult::True && right == EvaluationResult::True)
+ return EvaluationResult::True;
+ return EvaluationResult::False;
}
+
+inline EvaluationResult operator!(EvaluationResult result)
+{
+ switch (result) {
+ case EvaluationResult::True:
+ return EvaluationResult::False;
+ case EvaluationResult::False:
+ return EvaluationResult::True;
+ case EvaluationResult::Unknown:
+ return EvaluationResult::Unknown;
+ }
}
+
+}
+}