| /* |
| * Copyright (C) 2022 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "ContainerQueryParser.h" |
| |
| #include "CSSPrimitiveValue.h" |
| #include "CSSPropertyParserHelpers.h" |
| |
| namespace WebCore { |
| |
| std::optional<FilteredContainerQuery> ContainerQueryParser::consumeFilteredContainerQuery(CSSParserTokenRange& range, const CSSParserContext& context) |
| { |
| ContainerQueryParser parser(context); |
| return parser.consumeFilteredContainerQuery(range); |
| } |
| |
| std::optional<FilteredContainerQuery> ContainerQueryParser::consumeFilteredContainerQuery(CSSParserTokenRange& range) |
| { |
| auto consumeName = [&] { |
| if (range.peek().type() == LeftParenthesisToken || range.peek().type() == FunctionToken) |
| return nullAtom(); |
| auto nameValue = CSSPropertyParserHelpers::consumeSingleContainerName(range); |
| if (!nameValue) |
| return nullAtom(); |
| return AtomString { nameValue->stringValue() }; |
| }; |
| |
| auto name = consumeName(); |
| |
| m_requiredAxes = { }; |
| |
| auto query = consumeContainerQuery(range); |
| if (!query) |
| return { }; |
| |
| return FilteredContainerQuery { name, m_requiredAxes, *query }; |
| } |
| |
| std::optional<CQ::ContainerQuery> ContainerQueryParser::consumeContainerQuery(CSSParserTokenRange& range) |
| { |
| if (range.peek().type() == FunctionToken) { |
| auto name = range.peek().value(); |
| auto functionRange = range.consumeBlock(); |
| // This is where we would support style() queries. |
| return CQ::UnknownQuery { name.toString(), functionRange.serialize() }; |
| } |
| |
| if (range.peek().type() == LeftParenthesisToken) { |
| auto blockRange = range.consumeBlock(); |
| range.consumeWhitespace(); |
| |
| blockRange.consumeWhitespace(); |
| |
| // Try to parse as a condition first. |
| auto conditionRange = blockRange; |
| if (auto condition = consumeCondition<CQ::ContainerCondition>(conditionRange)) |
| return { condition }; |
| |
| if (auto sizeFeature = consumeSizeFeature(blockRange)) |
| return { *sizeFeature }; |
| |
| return CQ::UnknownQuery { { }, blockRange.serialize() }; |
| } |
| |
| return { }; |
| } |
| |
| template<typename ConditionType> |
| std::optional<ConditionType> ContainerQueryParser::consumeCondition(CSSParserTokenRange& range) |
| { |
| auto consumeQuery = [&](CSSParserTokenRange& range) { |
| if constexpr (std::is_same_v<CQ::ContainerCondition, ConditionType>) |
| return consumeContainerQuery(range); |
| // Style query support would be here. |
| }; |
| |
| if (range.peek().type() == IdentToken) { |
| if (range.peek().id() == CSSValueNot) { |
| range.consumeIncludingWhitespace(); |
| if (auto query = consumeQuery(range)) |
| return ConditionType { CQ::LogicalOperator::Not, { *query } }; |
| return { }; |
| } |
| } |
| |
| ConditionType condition; |
| |
| auto query = consumeQuery(range); |
| if (!query) |
| return { }; |
| |
| condition.queries.append(*query); |
| range.consumeWhitespace(); |
| |
| auto consumeOperator = [&]() -> std::optional<CQ::LogicalOperator> { |
| auto operatorToken = range.consumeIncludingWhitespace(); |
| if (operatorToken.type() != IdentToken) |
| return { }; |
| if (operatorToken.id() == CSSValueAnd) |
| return CQ::LogicalOperator::And; |
| if (operatorToken.id() == CSSValueOr) |
| return CQ::LogicalOperator::Or; |
| return { }; |
| }; |
| |
| while (!range.atEnd()) { |
| auto op = consumeOperator(); |
| if (!op) |
| return { }; |
| if (condition.queries.size() > 1 && condition.logicalOperator != *op) |
| return { }; |
| |
| condition.logicalOperator = *op; |
| |
| auto query = consumeQuery(range); |
| if (!query) |
| return { }; |
| |
| condition.queries.append(*query); |
| range.consumeWhitespace(); |
| } |
| |
| return condition; |
| } |
| |
| std::optional<CQ::SizeFeature> ContainerQueryParser::consumeSizeFeature(CSSParserTokenRange& range) |
| { |
| auto consume = [&] { |
| auto rangeCopy = range; |
| if (auto sizeFeature = consumePlainSizeFeature(range)) |
| return sizeFeature; |
| |
| range = rangeCopy; |
| return consumeRangeSizeFeature(range); |
| }; |
| |
| auto sizeFeature = consume(); |
| |
| if (!range.atEnd()) |
| return { }; |
| |
| if (sizeFeature) |
| m_requiredAxes.add(CQ::requiredAxesForFeature(sizeFeature->name)); |
| |
| return sizeFeature; |
| } |
| |
| static AtomString consumeFeatureName(CSSParserTokenRange& range) |
| { |
| if (range.peek().type() != IdentToken) |
| return nullAtom(); |
| return range.consumeIncludingWhitespace().value().convertToASCIILowercaseAtom(); |
| } |
| |
| std::optional<CQ::SizeFeature> ContainerQueryParser::consumePlainSizeFeature(CSSParserTokenRange& range) |
| { |
| auto consumePlainFeatureName = [&]() -> std::pair<AtomString, CQ::ComparisonOperator> { |
| auto name = consumeFeatureName(range); |
| if (name.isEmpty()) |
| return { }; |
| if (name.startsWith("min-"_s)) |
| return { StringView(name).substring(4).toAtomString(), CQ::ComparisonOperator::GreaterThanOrEqual }; |
| if (name.startsWith("max-"_s)) |
| return { StringView(name).substring(4).toAtomString(), CQ::ComparisonOperator::LessThanOrEqual }; |
| |
| return { name, 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, CQ::Syntax::Boolean, { }, { } }; |
| } |
| |
| if (range.peek().type() != ColonToken) |
| return { }; |
| |
| range.consumeIncludingWhitespace(); |
| if (range.atEnd()) |
| return { }; |
| |
| auto value = consumeValue(range); |
| if (!value) |
| return { }; |
| |
| return CQ::SizeFeature { featureName, CQ::Syntax::Colon, { }, CQ::Comparison { op, WTFMove(value) } }; |
| } |
| |
| std::optional<CQ::SizeFeature> ContainerQueryParser::consumeRangeSizeFeature(CSSParserTokenRange& range) |
| { |
| auto consumeRangeOperator = [&]() -> std::optional<CQ::ComparisonOperator> { |
| if (range.atEnd()) |
| return { }; |
| 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; |
| } |
| 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 { }; |
| } |
| }; |
| |
| bool didFailParsing = false; |
| |
| auto consumeLeftComparison = [&]() -> std::optional<CQ::Comparison> { |
| if (range.peek().type() == IdentToken) |
| return { }; |
| auto value = consumeValue(range); |
| if (!value) |
| return { }; |
| auto op = consumeRangeOperator(); |
| if (!op) { |
| didFailParsing = true; |
| return { }; |
| } |
| |
| return CQ::Comparison { *op, WTFMove(value) }; |
| }; |
| |
| auto consumeRightComparison = [&]() -> std::optional<CQ::Comparison> { |
| auto op = consumeRangeOperator(); |
| if (!op) |
| return { }; |
| auto value = consumeValue(range); |
| if (!value) { |
| didFailParsing = true; |
| return { }; |
| } |
| |
| return CQ::Comparison { *op, WTFMove(value) }; |
| }; |
| |
| auto leftComparison = consumeLeftComparison(); |
| |
| auto featureName = consumeFeatureName(range); |
| if (featureName.isEmpty()) |
| return { }; |
| |
| auto rightComparison = consumeRightComparison(); |
| |
| auto validateComparisons = [&] { |
| if (didFailParsing) |
| return false; |
| if (!leftComparison && !rightComparison) |
| return false; |
| if (!leftComparison || !rightComparison) |
| return true; |
| // Disallow comparisons like (a=b=c), (a=b<c). |
| if (leftComparison->op == CQ::ComparisonOperator::Equal || rightComparison->op == CQ::ComparisonOperator::Equal) |
| return false; |
| // Disallow comparisons like (a<b>c). |
| bool leftIsLess = leftComparison->op == CQ::ComparisonOperator::LessThan || leftComparison->op == CQ::ComparisonOperator::LessThanOrEqual; |
| bool rightIsLess = rightComparison->op == CQ::ComparisonOperator::LessThan || rightComparison->op == CQ::ComparisonOperator::LessThanOrEqual; |
| return leftIsLess == rightIsLess; |
| }; |
| |
| if (!validateComparisons()) |
| return { }; |
| |
| return CQ::SizeFeature { WTFMove(featureName), CQ::Syntax::Range, 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; |
| return nullptr; |
| } |
| |
| } |