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
13namespace blink {
14
15PassRefPtrWillBeRawPtr<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
24PassRefPtrWillBeRawPtr<MediaQuerySet> MediaQueryParser::parseMediaCondition(MediaQueryTokenIterator token, MediaQueryTokenIterator endToken)
25{
26    return MediaQueryParser(MediaConditionParser).parseImpl(token, endToken);
27}
28
29const MediaQueryParser::State MediaQueryParser::ReadRestrictor = &MediaQueryParser::readRestrictor;
30const MediaQueryParser::State MediaQueryParser::ReadMediaType = &MediaQueryParser::readMediaType;
31const MediaQueryParser::State MediaQueryParser::ReadAnd = &MediaQueryParser::readAnd;
32const MediaQueryParser::State MediaQueryParser::ReadFeatureStart = &MediaQueryParser::readFeatureStart;
33const MediaQueryParser::State MediaQueryParser::ReadFeature = &MediaQueryParser::readFeature;
34const MediaQueryParser::State MediaQueryParser::ReadFeatureColon = &MediaQueryParser::readFeatureColon;
35const MediaQueryParser::State MediaQueryParser::ReadFeatureValue = &MediaQueryParser::readFeatureValue;
36const MediaQueryParser::State MediaQueryParser::ReadFeatureEnd = &MediaQueryParser::readFeatureEnd;
37const MediaQueryParser::State MediaQueryParser::SkipUntilComma = &MediaQueryParser::skipUntilComma;
38const MediaQueryParser::State MediaQueryParser::SkipUntilBlockEnd = &MediaQueryParser::skipUntilBlockEnd;
39const MediaQueryParser::State MediaQueryParser::Done = &MediaQueryParser::done;
40
41MediaQueryParser::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
51MediaQueryParser::~MediaQueryParser() { };
52
53void 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
60void MediaQueryParser::readRestrictor(MediaQueryTokenType type, const MediaQueryToken& token)
61{
62    readMediaType(type, token);
63}
64
65void 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
87void 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
101void MediaQueryParser::readFeatureStart(MediaQueryTokenType type, const MediaQueryToken& token)
102{
103    if (type == LeftParenthesisToken)
104        m_state = ReadFeature;
105    else
106        m_state = SkipUntilComma;
107}
108
109void 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
119void 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
129void 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
139void 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
154void 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
163void MediaQueryParser::skipUntilBlockEnd(MediaQueryTokenType type, const MediaQueryToken& token)
164{
165    if (token.blockType() == MediaQueryToken::BlockEnd && !m_blockWatcher.blockLevel())
166        m_state = SkipUntilComma;
167}
168
169void MediaQueryParser::done(MediaQueryTokenType type, const MediaQueryToken& token) { }
170
171void 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
178void 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
191PassRefPtrWillBeRawPtr<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
204MediaQueryData::MediaQueryData()
205    : m_restrictor(MediaQuery::None)
206    , m_mediaType(MediaTypeNames::all)
207    , m_expressions(adoptPtrWillBeNoop(new ExpressionHeapVector))
208    , m_mediaTypeSet(false)
209{
210}
211
212void 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
222PassOwnPtrWillBeRawPtr<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
229bool 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
238void 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
260void MediaQueryData::setMediaType(const String& mediaType)
261{
262    m_mediaType = mediaType;
263    m_mediaTypeSet = true;
264}
265
266} // namespace blink
267