blob: cb5a08aa3eb7d6b8c8257dc848dcec113419c9cd [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Copyright (C) 2016 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:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "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 THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR 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 "MediaQueryParser.h"
#include "CSSTokenizer.h"
#include "MediaList.h"
#include "MediaQueryParserContext.h"
#include <wtf/Vector.h>
namespace WebCore {
RefPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(const String& queryString, MediaQueryParserContext context)
{
auto tokenizer = CSSTokenizer::tryCreate(queryString);
if (UNLIKELY(!tokenizer))
return nullptr;
return parseMediaQuerySet(tokenizer->tokenRange(), context);
}
RefPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(CSSParserTokenRange range, MediaQueryParserContext context)
{
return MediaQueryParser(MediaQuerySetParser, context).parseInternal(range);
}
RefPtr<MediaQuerySet> MediaQueryParser::parseMediaCondition(CSSParserTokenRange range, MediaQueryParserContext context)
{
return MediaQueryParser(MediaConditionParser, context).parseInternal(range);
}
RefPtr<MediaQuerySet> MediaQueryParser::parseContainerQuery(CSSParserTokenRange range, MediaQueryParserContext context)
{
if (range.atEnd())
return nullptr;
if (range.peek().type() != LeftParenthesisToken && range.peek().type() != FunctionToken)
return nullptr;
return MediaQueryParser(ContainerQueryParser, context).parseInternal(range);
}
const MediaQueryParser::State MediaQueryParser::ReadRestrictor = &MediaQueryParser::readRestrictor;
const MediaQueryParser::State MediaQueryParser::ReadMediaNot = &MediaQueryParser::readMediaNot;
const MediaQueryParser::State MediaQueryParser::ReadContainerQuery = &MediaQueryParser::readContainerQuery;
const MediaQueryParser::State MediaQueryParser::ReadMediaType = &MediaQueryParser::readMediaType;
const MediaQueryParser::State MediaQueryParser::ReadAnd = &MediaQueryParser::readAnd;
const MediaQueryParser::State MediaQueryParser::ReadFeatureStart = &MediaQueryParser::readFeatureStart;
const MediaQueryParser::State MediaQueryParser::ReadFeature = &MediaQueryParser::readFeature;
const MediaQueryParser::State MediaQueryParser::ReadFeatureColon = &MediaQueryParser::readFeatureColon;
const MediaQueryParser::State MediaQueryParser::ReadFeatureValue = &MediaQueryParser::readFeatureValue;
const MediaQueryParser::State MediaQueryParser::ReadFeatureEnd = &MediaQueryParser::readFeatureEnd;
const MediaQueryParser::State MediaQueryParser::SkipUntilComma = &MediaQueryParser::skipUntilComma;
const MediaQueryParser::State MediaQueryParser::SkipUntilBlockEnd = &MediaQueryParser::skipUntilBlockEnd;
const MediaQueryParser::State MediaQueryParser::Done = &MediaQueryParser::done;
MediaQueryParser::MediaQueryParser(ParserType parserType, MediaQueryParserContext context)
: m_parserType(parserType)
, m_mediaQueryData(context)
, m_querySet(MediaQuerySet::create())
{
switch (m_parserType) {
case MediaQuerySetParser:
m_state = &MediaQueryParser::readRestrictor;
break;
case MediaConditionParser:
m_state = &MediaQueryParser::readMediaNot;
break;
case ContainerQueryParser:
m_state = &MediaQueryParser::readContainerQuery;
break;
}
}
MediaQueryParser::~MediaQueryParser() = default;
void MediaQueryParser::setStateAndRestrict(State state, MediaQuery::Restrictor restrictor)
{
m_mediaQueryData.setRestrictor(restrictor);
m_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 == IdentToken && equalIgnoringASCIICase(token.value(), "not"))
setStateAndRestrict(ReadFeatureStart, MediaQuery::Not);
else
readFeatureStart(type, token, range);
}
void MediaQueryParser::readContainerQuery(CSSParserTokenType type, const CSSParserToken&, CSSParserTokenRange&)
{
if (type == FunctionToken || type == LeftParenthesisToken)
m_state = ReadFeature;
}
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 == LeftParenthesisToken) {
if (m_mediaQueryData.restrictor() != MediaQuery::None)
m_state = SkipUntilComma;
else
m_state = ReadFeature;
} else if (type == IdentToken) {
if (m_state == ReadRestrictor && equalIgnoringASCIICase(token.value(), "not"))
setStateAndRestrict(ReadMediaType, MediaQuery::Not);
else if (m_state == ReadRestrictor && equalIgnoringASCIICase(token.value(), "only"))
setStateAndRestrict(ReadMediaType, MediaQuery::Only);
else if (m_mediaQueryData.restrictor() != MediaQuery::None
&& isRestrictorOrLogicalOperator(token)) {
m_state = SkipUntilComma;
} else {
m_mediaQueryData.setMediaType(token.value().toString());
m_state = ReadAnd;
}
} else if (type == EOFToken && (!m_querySet->queryVector().size() || m_state != ReadRestrictor))
m_state = Done;
else {
m_state = SkipUntilComma;
if (type == CommaToken)
skipUntilComma(type, token, range);
}
}
void MediaQueryParser::commitMediaQuery()
{
// FIXME-NEWPARSER: Convoluted and awful, but we can't change the MediaQuerySet yet because of the
// old parser.
static const NeverDestroyed<String> defaultMediaType { "all"_s };
MediaQuery mediaQuery { m_mediaQueryData.restrictor(), m_mediaQueryData.mediaType().value_or(defaultMediaType), WTFMove(m_mediaQueryData.expressions()) };
m_mediaQueryData.clear();
m_querySet->addMediaQuery(WTFMove(mediaQuery));
}
void MediaQueryParser::readAnd(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& /*range*/)
{
if (type == IdentToken && equalIgnoringASCIICase(token.value(), "and")) {
m_state = ReadFeatureStart;
} else if (type == CommaToken && m_parserType != MediaConditionParser) {
commitMediaQuery();
m_state = ReadRestrictor;
} else if (type == EOFToken)
m_state = Done;
else
m_state = SkipUntilComma;
}
void MediaQueryParser::readFeatureStart(CSSParserTokenType type, const CSSParserToken& /*token*/, CSSParserTokenRange& /*range*/)
{
if (type == LeftParenthesisToken)
m_state = ReadFeature;
else
m_state = SkipUntilComma;
}
void MediaQueryParser::readFeature(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& /*range*/)
{
if (type == IdentToken) {
m_mediaQueryData.setMediaFeature(token.value().toString());
m_state = ReadFeatureColon;
} else
m_state = SkipUntilComma;
}
void MediaQueryParser::readFeatureColon(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& range)
{
if (type == ColonToken) {
while (range.peek().type() == WhitespaceToken)
range.consume();
if (range.peek().type() == RightParenthesisToken || range.peek().type() == EOFToken)
m_state = SkipUntilBlockEnd;
else
m_state = ReadFeatureValue;
} else if (type == RightParenthesisToken || type == EOFToken) {
m_mediaQueryData.addExpression(range);
readFeatureEnd(type, token, range);
} else
m_state = SkipUntilBlockEnd;
}
void MediaQueryParser::readFeatureValue(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& range)
{
if (type == DimensionToken && token.unitType() == CSSUnitType::CSS_UNKNOWN) {
range.consume();
m_state = SkipUntilComma;
} else {
m_mediaQueryData.addExpression(range);
m_state = ReadFeatureEnd;
}
}
void MediaQueryParser::readFeatureEnd(CSSParserTokenType type, const CSSParserToken& /*token*/, CSSParserTokenRange& /*range*/)
{
if (type == RightParenthesisToken || type == EOFToken) {
if (type != EOFToken && m_mediaQueryData.lastExpressionValid())
m_state = ReadAnd;
else
m_state = SkipUntilComma;
} else {
m_mediaQueryData.removeLastExpression();
m_state = SkipUntilBlockEnd;
}
}
void MediaQueryParser::skipUntilComma(CSSParserTokenType type, const CSSParserToken& /*token*/, CSSParserTokenRange& /*range*/)
{
if ((type == CommaToken && !m_blockWatcher.blockLevel()) || type == EOFToken) {
m_state = ReadRestrictor;
m_mediaQueryData.clear();
MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
m_querySet->addMediaQuery(WTFMove(query));
}
}
void MediaQueryParser::skipUntilBlockEnd(CSSParserTokenType /*type */, const CSSParserToken& token, CSSParserTokenRange& /*range*/)
{
if (token.getBlockType() == CSSParserToken::BlockEnd && !m_blockWatcher.blockLevel())
m_state = SkipUntilComma;
}
void MediaQueryParser::done(CSSParserTokenType /*type*/, const CSSParserToken& /*token*/, CSSParserTokenRange& /*range*/) { }
void MediaQueryParser::handleBlocks(const CSSParserToken& token)
{
if (token.getBlockType() != CSSParserToken::BlockStart)
return;
auto shouldSkipBlock = [&] {
// FIXME: Nested blocks should be supported.
if (m_blockWatcher.blockLevel())
return true;
if (token.type() == LeftParenthesisToken)
return false;
if (m_parserType == ContainerQueryParser && token.type() == FunctionToken)
return !equalLettersIgnoringASCIICase(token.value(), "size");
return true;
}();
if (shouldSkipBlock)
m_state = SkipUntilBlockEnd;
}
void MediaQueryParser::processToken(const CSSParserToken& token, CSSParserTokenRange& range)
{
CSSParserTokenType type = token.type();
if (type == WhitespaceToken) {
range.consume();
return;
}
if (m_state != ReadFeatureValue) {
handleBlocks(token);
m_blockWatcher.handleToken(token);
range.consume();
}
// Call the function that handles current state
((this)->*(m_state))(type, token, range);
}
// The state machine loop
RefPtr<MediaQuerySet> MediaQueryParser::parseInternal(CSSParserTokenRange& range)
{
while (!range.atEnd())
processToken(range.peek(), range);
// FIXME: Can we get rid of this special case?
if (m_parserType == MediaQuerySetParser)
processToken(CSSParserToken(EOFToken), range);
if (m_state != ReadAnd && m_state != ReadRestrictor && m_state != Done && m_state != ReadMediaNot) {
MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
m_querySet->addMediaQuery(WTFMove(query));
} else if (m_mediaQueryData.currentMediaQueryChanged())
commitMediaQuery();
m_querySet->shrinkToFit();
return m_querySet;
}
MediaQueryParser::MediaQueryData::MediaQueryData(MediaQueryParserContext context)
: m_context(context)
{
}
void MediaQueryParser::MediaQueryData::clear()
{
m_restrictor = MediaQuery::None;
m_mediaType = std::nullopt;
m_mediaFeature = String();
m_expressions.clear();
}
void MediaQueryParser::MediaQueryData::addExpression(CSSParserTokenRange& range)
{
m_expressions.append(MediaQueryExpression { m_mediaFeature, range, m_context });
}
bool MediaQueryParser::MediaQueryData::lastExpressionValid()
{
return m_expressions.last().isValid();
}
void MediaQueryParser::MediaQueryData::removeLastExpression()
{
m_expressions.removeLast();
}
} // namespace WebCore