blob: 10f1c17fc70fefbf77b025e38d0b76724d367573 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/css/parser/css_supports_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_impl.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_token_stream.h"
#include "third_party/blink/renderer/core/css/parser/css_selector_parser.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
namespace blink {
namespace {
// The result kUnknown must be converted to 'false' if passed to a context
// which requires a boolean value.
// TODO(crbug.com/1052274): This is supposed to happen at the top-level,
// but currently happens on ConsumeGeneralEnclosed's result.
CSSSupportsParser::Result EvalUnknown(CSSSupportsParser::Result result) {
return result == CSSSupportsParser::Result::kUnknown
? CSSSupportsParser::Result::kUnsupported
: result;
}
// https://drafts.csswg.org/css-syntax/#typedef-any-value
bool IsNextTokenAllowedForAnyValue(CSSParserTokenRange& range) {
switch (range.Peek().GetType()) {
case kBadStringToken:
case kEOFToken:
case kBadUrlToken:
return false;
case kRightParenthesisToken:
case kRightBracketToken:
case kRightBraceToken:
return range.Peek().GetBlockType() == CSSParserToken::kBlockEnd;
default:
return true;
}
}
// https://drafts.csswg.org/css-syntax/#typedef-any-value
bool ConsumeAnyValue(CSSParserTokenRange& range) {
DCHECK(!range.AtEnd());
while (IsNextTokenAllowedForAnyValue(range))
range.Consume();
return range.AtEnd();
}
} // namespace
CSSSupportsParser::Result CSSSupportsParser::ConsumeSupportsCondition(
CSSParserTokenStream& stream,
CSSParserImpl& parser) {
stream.ConsumeWhitespace();
CSSSupportsParser supports_parser(parser);
return supports_parser.ConsumeSupportsCondition(stream);
}
bool CSSSupportsParser::AtIdent(const CSSParserToken& token,
const char* ident) {
return token.GetType() == kIdentToken &&
EqualIgnoringASCIICase(token.Value(), ident);
}
bool CSSSupportsParser::ConsumeIfIdent(CSSParserTokenStream& stream,
const char* ident) {
if (!AtIdent(stream.Peek(), ident))
return false;
stream.ConsumeIncludingWhitespace();
return true;
}
// <supports-condition> = not <supports-in-parens>
// | <supports-in-parens> [ and <supports-in-parens> ]*
// | <supports-in-parens> [ or <supports-in-parens> ]*
CSSSupportsParser::Result CSSSupportsParser::ConsumeSupportsCondition(
CSSParserTokenStream& stream) {
// not <supports-in-parens>
stream.ConsumeWhitespace();
if (ConsumeIfIdent(stream, "not"))
return !ConsumeSupportsInParens(stream);
// <supports-in-parens> [ and <supports-in-parens> ]*
// | <supports-in-parens> [ or <supports-in-parens> ]*
Result result = ConsumeSupportsInParens(stream);
stream.ConsumeWhitespace();
if (AtIdent(stream.Peek(), "and")) {
stream.ConsumeWhitespace();
while (ConsumeIfIdent(stream, "and")) {
result = result & ConsumeSupportsInParens(stream);
stream.ConsumeWhitespace();
}
} else if (AtIdent(stream.Peek(), "or")) {
stream.ConsumeWhitespace();
while (ConsumeIfIdent(stream, "or")) {
result = result | ConsumeSupportsInParens(stream);
stream.ConsumeWhitespace();
}
}
return result;
}
bool CSSSupportsParser::IsSupportsInParens(const CSSParserToken& token) {
// All three productions for <supports-in-parens> must start with either a
// left parenthesis or a function.
return token.GetType() == kLeftParenthesisToken ||
token.GetType() == kFunctionToken;
}
bool CSSSupportsParser::IsEnclosedSupportsCondition(
const CSSParserToken& first_token,
const CSSParserToken& second_token) {
return (first_token.GetType() == kLeftParenthesisToken) &&
(AtIdent(second_token, "not") ||
second_token.GetType() == kLeftParenthesisToken ||
second_token.GetType() == kFunctionToken);
}
bool CSSSupportsParser::IsSupportsSelectorFn(
const CSSParserToken& first_token,
const CSSParserToken& second_token) {
return (first_token.GetType() == kFunctionToken &&
first_token.FunctionId() == CSSValueID::kSelector);
}
bool CSSSupportsParser::IsSupportsDecl(const CSSParserToken& first_token,
const CSSParserToken& second_token) {
return first_token.GetType() == kLeftParenthesisToken &&
second_token.GetType() == kIdentToken;
}
bool CSSSupportsParser::IsSupportsFeature(const CSSParserToken& first_token,
const CSSParserToken& second_token) {
return IsSupportsSelectorFn(first_token, second_token) ||
IsSupportsDecl(first_token, second_token);
}
bool CSSSupportsParser::IsGeneralEnclosed(const CSSParserToken& first_token) {
return first_token.GetType() == kLeftParenthesisToken ||
first_token.GetType() == kFunctionToken;
}
// <supports-in-parens> = ( <supports-condition> )
// | <supports-feature>
// | <general-enclosed>
CSSSupportsParser::Result CSSSupportsParser::ConsumeSupportsInParens(
CSSParserTokenStream& stream) {
CSSParserToken first_token = stream.Peek();
if (!IsSupportsInParens(first_token))
return Result::kParseFailure;
CSSParserTokenStream::BlockGuard guard(stream);
stream.ConsumeWhitespace();
// ( <supports-condition> )
if (IsEnclosedSupportsCondition(first_token, stream.Peek())) {
Result result = ConsumeSupportsCondition(stream);
return guard.AtEndOfBlock() ? result : Result::kParseFailure;
}
// <supports-feature>
if (IsSupportsFeature(first_token, stream.Peek())) {
Result result = ConsumeSupportsFeature(first_token, stream);
return guard.AtEndOfBlock() ? result : Result::kParseFailure;
}
// <general-enclosed>
//
// TODO(crbug.com/1052274): Support kUnknown beyond this point.
//
// The result kUnknown is supposed to be evaluated at the top level, but
// we have already shipped the behavior of evaluating it here, and Firefox
// does the same thing.
return EvalUnknown(ConsumeGeneralEnclosed(first_token, stream));
}
// <supports-feature> = <supports-selector-fn> | <supports-decl>
CSSSupportsParser::Result CSSSupportsParser::ConsumeSupportsFeature(
const CSSParserToken& first_token,
CSSParserTokenStream& stream) {
// <supports-selector-fn>
if (IsSupportsSelectorFn(first_token, stream.Peek()))
return ConsumeSupportsSelectorFn(first_token, stream);
// <supports-decl>
return ConsumeSupportsDecl(first_token, stream);
}
// <supports-selector-fn> = selector( <complex-selector> )
CSSSupportsParser::Result CSSSupportsParser::ConsumeSupportsSelectorFn(
const CSSParserToken& first_token,
CSSParserTokenStream& stream) {
DCHECK(IsSupportsSelectorFn(first_token, stream.Peek()));
auto block = stream.ConsumeUntilPeekedTypeIs<kRightParenthesisToken>();
if (CSSSelectorParser::SupportsComplexSelector(block, parser_.GetContext()))
return Result::kSupported;
return Result::kUnsupported;
}
// <supports-decl> = ( <declaration> )
CSSSupportsParser::Result CSSSupportsParser::ConsumeSupportsDecl(
const CSSParserToken& first_token,
CSSParserTokenStream& stream) {
if (!IsSupportsDecl(first_token, stream.Peek()))
return Result::kParseFailure;
if (parser_.ConsumeSupportsDeclaration(stream))
return Result::kSupported;
return Result::kUnsupported;
}
// <general-enclosed> = [ <function-token> <any-value> ) ]
// | ( <ident> <any-value> )
CSSSupportsParser::Result CSSSupportsParser::ConsumeGeneralEnclosed(
const CSSParserToken& first_token,
CSSParserTokenStream& stream) {
if (IsGeneralEnclosed(first_token)) {
auto block = stream.ConsumeUntilPeekedTypeIs<kRightParenthesisToken>();
// Note that <any-value> matches a sequence of one or more tokens, hence the
// block-range can't be empty.
// https://drafts.csswg.org/css-syntax-3/#typedef-any-value
if (block.AtEnd() || !ConsumeAnyValue(block))
return Result::kParseFailure;
stream.ConsumeWhitespace();
return Result::kUnknown;
}
return Result::kParseFailure;
}
} // namespace blink