blob: 41131d5356518da9874c2f9a3111fe1897993689 [file] [log] [blame]
/*
* 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;
}
}