// 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 "CSSParserIdioms.h" #include "CSSTokenizer.h" #include namespace WebCore { RefPtr MediaQueryParser::parseMediaQuerySet(const String& queryString) { return parseMediaQuerySet(CSSTokenizer(queryString).tokenRange()); } RefPtr MediaQueryParser::parseMediaQuerySet(CSSParserTokenRange range) { return MediaQueryParser(MediaQuerySetParser).parseInternal(range); } RefPtr MediaQueryParser::parseMediaCondition(CSSParserTokenRange range) { return MediaQueryParser(MediaConditionParser).parseInternal(range); } const MediaQueryParser::State MediaQueryParser::ReadRestrictor = &MediaQueryParser::readRestrictor; const MediaQueryParser::State MediaQueryParser::ReadMediaNot = &MediaQueryParser::readMediaNot; 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) : m_parserType(parserType) , m_querySet(MediaQuerySet::create()) { if (parserType == MediaQuerySetParser) m_state = &MediaQueryParser::readRestrictor; else // MediaConditionParser m_state = &MediaQueryParser::readMediaNot; } MediaQueryParser::~MediaQueryParser() { } 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) { readMediaType(type, token); } void MediaQueryParser::readMediaNot(CSSParserTokenType type, const CSSParserToken& token) { if (type == IdentToken && equalIgnoringASCIICase(token.value(), "not")) setStateAndRestrict(ReadFeatureStart, MediaQuery::Not); else readFeatureStart(type, token); } 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) { 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); } } void MediaQueryParser::commitMediaQuery() { // FIXME-NEWPARSER: Convoluted and awful, but we can't change the MediaQuerySet yet because of the // old parser. MediaQuery mediaQuery = MediaQuery(m_mediaQueryData.restrictor(), m_mediaQueryData.mediaType(), WTFMove(m_mediaQueryData.expressions())); m_mediaQueryData.clear(); m_querySet->addMediaQuery(WTFMove(mediaQuery)); } void MediaQueryParser::readAnd(CSSParserTokenType type, const CSSParserToken& token) { 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*/) { if (type == LeftParenthesisToken) m_state = ReadFeature; else m_state = SkipUntilComma; } void MediaQueryParser::readFeature(CSSParserTokenType type, const CSSParserToken& token) { if (type == IdentToken) { m_mediaQueryData.setMediaFeature(token.value().toString()); m_state = ReadFeatureColon; } else m_state = SkipUntilComma; } void MediaQueryParser::readFeatureColon(CSSParserTokenType type, const CSSParserToken& token) { if (type == ColonToken) m_state = ReadFeatureValue; else if (type == RightParenthesisToken || type == EOFToken) readFeatureEnd(type, token); else m_state = SkipUntilBlockEnd; } void MediaQueryParser::readFeatureValue(CSSParserTokenType type, const CSSParserToken& token) { if (type == DimensionToken && token.unitType() == CSSPrimitiveValue::UnitType::CSS_UNKNOWN) m_state = SkipUntilComma; else { if (m_mediaQueryData.tryAddParserToken(type, token)) m_state = ReadFeatureEnd; else m_state = SkipUntilBlockEnd; } } void MediaQueryParser::readFeatureEnd(CSSParserTokenType type, const CSSParserToken& token) { if (type == RightParenthesisToken || type == EOFToken) { if (type != EOFToken && m_mediaQueryData.addExpression()) m_state = ReadAnd; else m_state = SkipUntilComma; } else if (type == DelimiterToken && token.delimiter() == '/') { m_mediaQueryData.tryAddParserToken(type, token); m_state = ReadFeatureValue; } else m_state = SkipUntilBlockEnd; } void MediaQueryParser::skipUntilComma(CSSParserTokenType type, const CSSParserToken& /*token*/) { if ((type == CommaToken && !m_blockWatcher.blockLevel()) || type == EOFToken) { m_state = ReadRestrictor; m_mediaQueryData.clear(); MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector()); m_querySet->addMediaQuery(WTFMove(query)); } } void MediaQueryParser::skipUntilBlockEnd(CSSParserTokenType /*type */, const CSSParserToken& token) { if (token.getBlockType() == CSSParserToken::BlockEnd && !m_blockWatcher.blockLevel()) m_state = SkipUntilComma; } void MediaQueryParser::done(CSSParserTokenType /*type*/, const CSSParserToken& /*token*/) { } void MediaQueryParser::handleBlocks(const CSSParserToken& token) { if (token.getBlockType() == CSSParserToken::BlockStart && (token.type() != LeftParenthesisToken || m_blockWatcher.blockLevel())) m_state = SkipUntilBlockEnd; } void MediaQueryParser::processToken(const CSSParserToken& token) { CSSParserTokenType type = token.type(); handleBlocks(token); m_blockWatcher.handleToken(token); // Call the function that handles current state if (type != WhitespaceToken) ((this)->*(m_state))(type, token); } // The state machine loop RefPtr MediaQueryParser::parseInternal(CSSParserTokenRange range) { while (!range.atEnd()) processToken(range.consume()); // FIXME: Can we get rid of this special case? if (m_parserType == MediaQuerySetParser) processToken(CSSParserToken(EOFToken)); if (m_state != ReadAnd && m_state != ReadRestrictor && m_state != Done && m_state != ReadMediaNot) { MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector()); m_querySet->addMediaQuery(WTFMove(query)); } else if (m_mediaQueryData.currentMediaQueryChanged()) commitMediaQuery(); return m_querySet; } MediaQueryData::MediaQueryData() : m_restrictor(MediaQuery::None) , m_mediaType("all") , m_mediaTypeSet(false) { } void MediaQueryData::clear() { m_restrictor = MediaQuery::None; m_mediaType = "all"; m_mediaTypeSet = false; m_mediaFeature = String(); m_valueList.clear(); m_expressions.clear(); } bool MediaQueryData::addExpression() { MediaQueryExpression expression = MediaQueryExpression(m_mediaFeature, m_valueList); bool isValid = expression.isValid(); m_expressions.append(WTFMove(expression)); m_valueList.clear(); return isValid; } bool MediaQueryData::tryAddParserToken(CSSParserTokenType type, const CSSParserToken& token) { if (type == NumberToken || type == PercentageToken || type == DimensionToken || type == DelimiterToken || type == IdentToken) { m_valueList.append(token); return true; } return false; } void MediaQueryData::setMediaType(const String& mediaType) { m_mediaType = mediaType; m_mediaTypeSet = true; } } // namespace WebCsore