blob: c64ea658fd4d82a236026452490199adce84fe35 [file] [log] [blame]
// Copyright 2014 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/media_query_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/media_type_names.h"
namespace blink {
scoped_refptr<MediaQuerySet> MediaQueryParser::ParseMediaQuerySet(
const String& query_string,
const ExecutionContext* execution_context) {
return ParseMediaQuerySet(
CSSParserTokenRange(CSSTokenizer(query_string).TokenizeToEOF()),
execution_context);
}
scoped_refptr<MediaQuerySet> MediaQueryParser::ParseMediaQuerySet(
CSSParserTokenRange range,
const ExecutionContext* execution_context) {
return MediaQueryParser(kMediaQuerySetParser, kHTMLStandardMode,
execution_context)
.ParseImpl(range);
}
scoped_refptr<MediaQuerySet> MediaQueryParser::ParseMediaQuerySetInMode(
CSSParserTokenRange range,
CSSParserMode mode,
const ExecutionContext* execution_context) {
return MediaQueryParser(kMediaQuerySetParser, mode, execution_context)
.ParseImpl(range);
}
scoped_refptr<MediaQuerySet> MediaQueryParser::ParseMediaCondition(
CSSParserTokenRange range,
const ExecutionContext* execution_context) {
return MediaQueryParser(kMediaConditionParser, kHTMLStandardMode,
execution_context)
.ParseImpl(range);
}
const MediaQueryParser::State MediaQueryParser::kReadRestrictor =
&MediaQueryParser::ReadRestrictor;
const MediaQueryParser::State MediaQueryParser::kReadMediaNot =
&MediaQueryParser::ReadMediaNot;
const MediaQueryParser::State MediaQueryParser::kReadMediaType =
&MediaQueryParser::ReadMediaType;
const MediaQueryParser::State MediaQueryParser::kReadAnd =
&MediaQueryParser::ReadAnd;
const MediaQueryParser::State MediaQueryParser::kReadFeatureStart =
&MediaQueryParser::ReadFeatureStart;
const MediaQueryParser::State MediaQueryParser::kReadFeature =
&MediaQueryParser::ReadFeature;
const MediaQueryParser::State MediaQueryParser::kReadFeatureColon =
&MediaQueryParser::ReadFeatureColon;
const MediaQueryParser::State MediaQueryParser::kReadFeatureValue =
&MediaQueryParser::ReadFeatureValue;
const MediaQueryParser::State MediaQueryParser::kReadFeatureEnd =
&MediaQueryParser::ReadFeatureEnd;
const MediaQueryParser::State MediaQueryParser::kSkipUntilComma =
&MediaQueryParser::SkipUntilComma;
const MediaQueryParser::State MediaQueryParser::kSkipUntilBlockEnd =
&MediaQueryParser::SkipUntilBlockEnd;
const MediaQueryParser::State MediaQueryParser::kDone = &MediaQueryParser::Done;
MediaQueryParser::MediaQueryParser(ParserType parser_type,
CSSParserMode mode,
const ExecutionContext* execution_context)
: parser_type_(parser_type),
query_set_(MediaQuerySet::Create()),
mode_(mode),
execution_context_(execution_context) {
if (parser_type == kMediaQuerySetParser)
state_ = &MediaQueryParser::ReadRestrictor;
else // MediaConditionParser
state_ = &MediaQueryParser::ReadMediaNot;
}
MediaQueryParser::~MediaQueryParser() = default;
void MediaQueryParser::SetStateAndRestrict(
State state,
MediaQuery::RestrictorType restrictor) {
media_query_data_.SetRestrictor(restrictor);
state_ = state;
}
// State machine member functions start here
void MediaQueryParser::ReadRestrictor(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
ReadMediaType(type, token, range);
}
void MediaQueryParser::ReadMediaNot(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (type == kIdentToken && EqualIgnoringASCIICase(token.Value(), "not"))
SetStateAndRestrict(kReadFeatureStart, MediaQuery::kNot);
else
ReadFeatureStart(type, token, range);
}
static bool IsRestrictorOrLogicalOperator(const CSSParserToken& token) {
// FIXME: it would be more efficient to use lower-case always for tokenValue.
return EqualIgnoringASCIICase(token.Value(), "not") ||
EqualIgnoringASCIICase(token.Value(), "and") ||
EqualIgnoringASCIICase(token.Value(), "or") ||
EqualIgnoringASCIICase(token.Value(), "only");
}
void MediaQueryParser::ReadMediaType(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (type == kLeftParenthesisToken) {
if (media_query_data_.Restrictor() != MediaQuery::kNone)
state_ = kSkipUntilComma;
else
state_ = kReadFeature;
} else if (type == kIdentToken) {
if (state_ == kReadRestrictor &&
EqualIgnoringASCIICase(token.Value(), "not")) {
SetStateAndRestrict(kReadMediaType, MediaQuery::kNot);
} else if (state_ == kReadRestrictor &&
EqualIgnoringASCIICase(token.Value(), "only")) {
SetStateAndRestrict(kReadMediaType, MediaQuery::kOnly);
} else if (media_query_data_.Restrictor() != MediaQuery::kNone &&
IsRestrictorOrLogicalOperator(token)) {
state_ = kSkipUntilComma;
} else {
media_query_data_.SetMediaType(token.Value().ToString());
state_ = kReadAnd;
}
} else if (type == kEOFToken &&
(!query_set_->QueryVector().size() || state_ != kReadRestrictor)) {
state_ = kDone;
} else {
state_ = kSkipUntilComma;
if (type == kCommaToken)
SkipUntilComma(type, token, range);
}
}
void MediaQueryParser::ReadAnd(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (type == kIdentToken && EqualIgnoringASCIICase(token.Value(), "and")) {
state_ = kReadFeatureStart;
} else if (type == kCommaToken && parser_type_ != kMediaConditionParser) {
query_set_->AddMediaQuery(media_query_data_.TakeMediaQuery());
state_ = kReadRestrictor;
} else if (type == kEOFToken) {
state_ = kDone;
} else {
state_ = kSkipUntilComma;
}
}
void MediaQueryParser::ReadFeatureStart(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (type == kLeftParenthesisToken)
state_ = kReadFeature;
else
state_ = kSkipUntilComma;
}
void MediaQueryParser::ReadFeature(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (type == kIdentToken) {
String media_feature = token.Value().ToString();
if (IsMediaFeatureAllowedInMode(media_feature)) {
media_query_data_.SetMediaFeature(media_feature);
state_ = kReadFeatureColon;
} else {
state_ = kSkipUntilComma;
}
} else {
state_ = kSkipUntilComma;
}
}
void MediaQueryParser::ReadFeatureColon(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (type == kColonToken) {
while (range.Peek().GetType() == kWhitespaceToken)
range.Consume();
if (range.Peek().GetType() == kRightParenthesisToken || type == kEOFToken)
state_ = kSkipUntilBlockEnd;
else
state_ = kReadFeatureValue;
} else if (type == kRightParenthesisToken || type == kEOFToken) {
media_query_data_.AddExpression(range, execution_context_);
ReadFeatureEnd(type, token, range);
} else {
state_ = kSkipUntilBlockEnd;
}
}
void MediaQueryParser::ReadFeatureValue(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (type == kDimensionToken &&
token.GetUnitType() == CSSPrimitiveValue::UnitType::kUnknown) {
range.Consume();
state_ = kSkipUntilComma;
} else {
media_query_data_.AddExpression(range, execution_context_);
state_ = kReadFeatureEnd;
}
}
void MediaQueryParser::ReadFeatureEnd(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (type == kRightParenthesisToken || type == kEOFToken) {
if (media_query_data_.LastExpressionValid())
state_ = kReadAnd;
else
state_ = kSkipUntilComma;
} else {
media_query_data_.RemoveLastExpression();
state_ = kSkipUntilBlockEnd;
}
}
void MediaQueryParser::SkipUntilComma(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if ((type == kCommaToken && !block_watcher_.BlockLevel()) ||
type == kEOFToken) {
state_ = kReadRestrictor;
media_query_data_.Clear();
query_set_->AddMediaQuery(MediaQuery::CreateNotAll());
}
}
void MediaQueryParser::SkipUntilBlockEnd(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {
if (token.GetBlockType() == CSSParserToken::kBlockEnd &&
!block_watcher_.BlockLevel())
state_ = kSkipUntilComma;
}
void MediaQueryParser::Done(CSSParserTokenType type,
const CSSParserToken& token,
CSSParserTokenRange& range) {}
void MediaQueryParser::HandleBlocks(const CSSParserToken& token) {
if (token.GetBlockType() == CSSParserToken::kBlockStart &&
(token.GetType() != kLeftParenthesisToken || block_watcher_.BlockLevel()))
state_ = kSkipUntilBlockEnd;
}
void MediaQueryParser::ProcessToken(const CSSParserToken& token,
CSSParserTokenRange& range) {
CSSParserTokenType type = token.GetType();
if (state_ != kReadFeatureValue || type == kWhitespaceToken) {
HandleBlocks(token);
block_watcher_.HandleToken(token);
range.Consume();
}
// Call the function that handles current state
if (type != kWhitespaceToken)
((this)->*(state_))(type, token, range);
}
// The state machine loop
scoped_refptr<MediaQuerySet> MediaQueryParser::ParseImpl(
CSSParserTokenRange range) {
while (!range.AtEnd())
ProcessToken(range.Peek(), range);
// FIXME: Can we get rid of this special case?
if (parser_type_ == kMediaQuerySetParser)
ProcessToken(CSSParserToken(kEOFToken), range);
if (state_ != kReadAnd && state_ != kReadRestrictor && state_ != kDone &&
state_ != kReadMediaNot)
query_set_->AddMediaQuery(MediaQuery::CreateNotAll());
else if (media_query_data_.CurrentMediaQueryChanged())
query_set_->AddMediaQuery(media_query_data_.TakeMediaQuery());
return query_set_;
}
bool MediaQueryParser::IsMediaFeatureAllowedInMode(
const String& media_feature) const {
return mode_ == kUASheetMode ||
media_feature != media_feature_names::kImmersiveMediaFeature;
}
MediaQueryData::MediaQueryData()
: restrictor_(MediaQuery::kNone),
media_type_(media_type_names::kAll),
media_type_set_(false),
fake_context_(*MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode,
SecureContextMode::kInsecureContext)) {}
void MediaQueryData::Clear() {
restrictor_ = MediaQuery::kNone;
media_type_ = media_type_names::kAll;
media_type_set_ = false;
media_feature_ = String();
expressions_.clear();
}
std::unique_ptr<MediaQuery> MediaQueryData::TakeMediaQuery() {
std::unique_ptr<MediaQuery> media_query = std::make_unique<MediaQuery>(
restrictor_, std::move(media_type_), std::move(expressions_));
Clear();
return media_query;
}
void MediaQueryData::AddExpression(CSSParserTokenRange& range,
const ExecutionContext* execution_context) {
expressions_.push_back(MediaQueryExp::Create(
media_feature_, range, fake_context_, execution_context));
}
bool MediaQueryData::LastExpressionValid() {
return expressions_.back().IsValid();
}
void MediaQueryData::RemoveLastExpression() {
expressions_.pop_back();
}
void MediaQueryData::SetMediaType(const String& media_type) {
media_type_ = media_type;
media_type_set_ = true;
}
} // namespace blink