1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "config.h" 6 #include "core/css/parser/MediaQueryParser.h" 7 8 #include "core/MediaTypeNames.h" 9 #include "core/css/parser/CSSPropertyParser.h" 10 #include "core/css/parser/MediaQueryTokenizer.h" 11 #include "wtf/Vector.h" 12 13 namespace blink { 14 15 PassRefPtrWillBeRawPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(const String& queryString) 16 { 17 // FIXME: Replace the MediaQueryTokenizer with a generic CSSTokenizer, once there is one, 18 // or better yet, replace the MediaQueryParser with a generic thread-safe CSS parser. 19 Vector<MediaQueryToken> tokens; 20 MediaQueryTokenizer::tokenize(queryString, tokens); 21 return MediaQueryParser(MediaQuerySetParser).parseImpl(tokens.begin(), tokens.end()); 22 } 23 24 PassRefPtrWillBeRawPtr<MediaQuerySet> MediaQueryParser::parseMediaCondition(MediaQueryTokenIterator token, MediaQueryTokenIterator endToken) 25 { 26 return MediaQueryParser(MediaConditionParser).parseImpl(token, endToken); 27 } 28 29 const MediaQueryParser::State MediaQueryParser::ReadRestrictor = &MediaQueryParser::readRestrictor; 30 const MediaQueryParser::State MediaQueryParser::ReadMediaType = &MediaQueryParser::readMediaType; 31 const MediaQueryParser::State MediaQueryParser::ReadAnd = &MediaQueryParser::readAnd; 32 const MediaQueryParser::State MediaQueryParser::ReadFeatureStart = &MediaQueryParser::readFeatureStart; 33 const MediaQueryParser::State MediaQueryParser::ReadFeature = &MediaQueryParser::readFeature; 34 const MediaQueryParser::State MediaQueryParser::ReadFeatureColon = &MediaQueryParser::readFeatureColon; 35 const MediaQueryParser::State MediaQueryParser::ReadFeatureValue = &MediaQueryParser::readFeatureValue; 36 const MediaQueryParser::State MediaQueryParser::ReadFeatureEnd = &MediaQueryParser::readFeatureEnd; 37 const MediaQueryParser::State MediaQueryParser::SkipUntilComma = &MediaQueryParser::skipUntilComma; 38 const MediaQueryParser::State MediaQueryParser::SkipUntilBlockEnd = &MediaQueryParser::skipUntilBlockEnd; 39 const MediaQueryParser::State MediaQueryParser::Done = &MediaQueryParser::done; 40 41 MediaQueryParser::MediaQueryParser(ParserType parserType) 42 : m_parserType(parserType) 43 , m_querySet(MediaQuerySet::create()) 44 { 45 if (parserType == MediaQuerySetParser) 46 m_state = &MediaQueryParser::readRestrictor; 47 else // MediaConditionParser 48 m_state = &MediaQueryParser::readFeatureStart; 49 } 50 51 MediaQueryParser::~MediaQueryParser() { }; 52 53 void MediaQueryParser::setStateAndRestrict(State state, MediaQuery::Restrictor restrictor) 54 { 55 m_mediaQueryData.setRestrictor(restrictor); 56 m_state = state; 57 } 58 59 // State machine member functions start here 60 void MediaQueryParser::readRestrictor(MediaQueryTokenType type, const MediaQueryToken& token) 61 { 62 readMediaType(type, token); 63 } 64 65 void MediaQueryParser::readMediaType(MediaQueryTokenType type, const MediaQueryToken& token) 66 { 67 if (type == LeftParenthesisToken) { 68 m_state = ReadFeature; 69 } else if (type == IdentToken) { 70 if (m_state == ReadRestrictor && equalIgnoringCase(token.value(), "not")) { 71 setStateAndRestrict(ReadMediaType, MediaQuery::Not); 72 } else if (m_state == ReadRestrictor && equalIgnoringCase(token.value(), "only")) { 73 setStateAndRestrict(ReadMediaType, MediaQuery::Only); 74 } else { 75 m_mediaQueryData.setMediaType(token.value()); 76 m_state = ReadAnd; 77 } 78 } else if (type == EOFToken && (!m_querySet->queryVector().size() || m_state != ReadRestrictor)) { 79 m_state = Done; 80 } else { 81 m_state = SkipUntilComma; 82 if (type == CommaToken) 83 skipUntilComma(type, token); 84 } 85 } 86 87 void MediaQueryParser::readAnd(MediaQueryTokenType type, const MediaQueryToken& token) 88 { 89 if (type == IdentToken && equalIgnoringCase(token.value(), "and")) { 90 m_state = ReadFeatureStart; 91 } else if (type == CommaToken && m_parserType != MediaConditionParser) { 92 m_querySet->addMediaQuery(m_mediaQueryData.takeMediaQuery()); 93 m_state = ReadRestrictor; 94 } else if (type == EOFToken) { 95 m_state = Done; 96 } else { 97 m_state = SkipUntilComma; 98 } 99 } 100 101 void MediaQueryParser::readFeatureStart(MediaQueryTokenType type, const MediaQueryToken& token) 102 { 103 if (type == LeftParenthesisToken) 104 m_state = ReadFeature; 105 else 106 m_state = SkipUntilComma; 107 } 108 109 void MediaQueryParser::readFeature(MediaQueryTokenType type, const MediaQueryToken& token) 110 { 111 if (type == IdentToken) { 112 m_mediaQueryData.setMediaFeature(token.value()); 113 m_state = ReadFeatureColon; 114 } else { 115 m_state = SkipUntilComma; 116 } 117 } 118 119 void MediaQueryParser::readFeatureColon(MediaQueryTokenType type, const MediaQueryToken& token) 120 { 121 if (type == ColonToken) 122 m_state = ReadFeatureValue; 123 else if (type == RightParenthesisToken || type == EOFToken) 124 readFeatureEnd(type, token); 125 else 126 m_state = SkipUntilBlockEnd; 127 } 128 129 void MediaQueryParser::readFeatureValue(MediaQueryTokenType type, const MediaQueryToken& token) 130 { 131 if (type == DimensionToken && token.unitType() == CSSPrimitiveValue::CSS_UNKNOWN) { 132 m_state = SkipUntilComma; 133 } else { 134 m_mediaQueryData.addParserValue(type, token); 135 m_state = ReadFeatureEnd; 136 } 137 } 138 139 void MediaQueryParser::readFeatureEnd(MediaQueryTokenType type, const MediaQueryToken& token) 140 { 141 if (type == RightParenthesisToken || type == EOFToken) { 142 if (m_mediaQueryData.addExpression()) 143 m_state = ReadAnd; 144 else 145 m_state = SkipUntilComma; 146 } else if (type == DelimiterToken && token.delimiter() == '/') { 147 m_mediaQueryData.addParserValue(type, token); 148 m_state = ReadFeatureValue; 149 } else { 150 m_state = SkipUntilBlockEnd; 151 } 152 } 153 154 void MediaQueryParser::skipUntilComma(MediaQueryTokenType type, const MediaQueryToken& token) 155 { 156 if ((type == CommaToken && !m_blockWatcher.blockLevel()) || type == EOFToken) { 157 m_state = ReadRestrictor; 158 m_mediaQueryData.clear(); 159 m_querySet->addMediaQuery(MediaQuery::createNotAll()); 160 } 161 } 162 163 void MediaQueryParser::skipUntilBlockEnd(MediaQueryTokenType type, const MediaQueryToken& token) 164 { 165 if (token.blockType() == MediaQueryToken::BlockEnd && !m_blockWatcher.blockLevel()) 166 m_state = SkipUntilComma; 167 } 168 169 void MediaQueryParser::done(MediaQueryTokenType type, const MediaQueryToken& token) { } 170 171 void MediaQueryParser::handleBlocks(const MediaQueryToken& token) 172 { 173 if (token.blockType() == MediaQueryToken::BlockStart 174 && (token.type() != LeftParenthesisToken || m_blockWatcher.blockLevel())) 175 m_state = SkipUntilBlockEnd; 176 } 177 178 void MediaQueryParser::processToken(const MediaQueryToken& token) 179 { 180 MediaQueryTokenType type = token.type(); 181 182 handleBlocks(token); 183 m_blockWatcher.handleToken(token); 184 185 // Call the function that handles current state 186 if (type != WhitespaceToken && type != CommentToken) 187 ((this)->*(m_state))(type, token); 188 } 189 190 // The state machine loop 191 PassRefPtrWillBeRawPtr<MediaQuerySet> MediaQueryParser::parseImpl(MediaQueryTokenIterator token, MediaQueryTokenIterator endToken) 192 { 193 for (; token != endToken; ++token) 194 processToken(*token); 195 196 if (m_state != ReadAnd && m_state != ReadRestrictor && m_state != Done && (m_parserType != MediaConditionParser || m_state != ReadFeatureStart)) 197 m_querySet->addMediaQuery(MediaQuery::createNotAll()); 198 else if (m_mediaQueryData.currentMediaQueryChanged()) 199 m_querySet->addMediaQuery(m_mediaQueryData.takeMediaQuery()); 200 201 return m_querySet; 202 } 203 204 MediaQueryData::MediaQueryData() 205 : m_restrictor(MediaQuery::None) 206 , m_mediaType(MediaTypeNames::all) 207 , m_expressions(adoptPtrWillBeNoop(new ExpressionHeapVector)) 208 , m_mediaTypeSet(false) 209 { 210 } 211 212 void MediaQueryData::clear() 213 { 214 m_restrictor = MediaQuery::None; 215 m_mediaType = MediaTypeNames::all; 216 m_mediaTypeSet = false; 217 m_mediaFeature = String(); 218 m_valueList.destroyAndClear(); 219 m_expressions = adoptPtrWillBeNoop(new ExpressionHeapVector); 220 } 221 222 PassOwnPtrWillBeRawPtr<MediaQuery> MediaQueryData::takeMediaQuery() 223 { 224 OwnPtrWillBeRawPtr<MediaQuery> mediaQuery = adoptPtrWillBeNoop(new MediaQuery(m_restrictor, m_mediaType, m_expressions.release())); 225 clear(); 226 return mediaQuery.release(); 227 } 228 229 bool MediaQueryData::addExpression() 230 { 231 OwnPtrWillBeRawPtr<MediaQueryExp> expression = MediaQueryExp::createIfValid(m_mediaFeature, &m_valueList); 232 bool isValid = !!expression; 233 m_expressions->append(expression.release()); 234 m_valueList.destroyAndClear(); 235 return isValid; 236 } 237 238 void MediaQueryData::addParserValue(MediaQueryTokenType type, const MediaQueryToken& token) 239 { 240 CSSParserValue value; 241 if (type == NumberToken || type == PercentageToken || type == DimensionToken) { 242 value.setFromNumber(token.numericValue(), token.unitType()); 243 value.isInt = (token.numericValueType() == IntegerValueType); 244 } else if (type == DelimiterToken) { 245 value.unit = CSSParserValue::Operator; 246 value.iValue = token.delimiter(); 247 value.id = CSSValueInvalid; 248 value.isInt = false; 249 } else { 250 CSSParserFunction* function = new CSSParserFunction; 251 function->name.init(token.value()); 252 value.setFromFunction(function); 253 CSSParserString tokenValue; 254 tokenValue.init(token.value()); 255 value.id = cssValueKeywordID(tokenValue); 256 } 257 m_valueList.addValue(value); 258 } 259 260 void MediaQueryData::setMediaType(const String& mediaType) 261 { 262 m_mediaType = mediaType; 263 m_mediaTypeSet = true; 264 } 265 266 } // namespace blink 267