[CSS Parser] Eliminate in-place lowercasing in the parser.
[WebKit-https.git] / Source / WebCore / css / parser / MediaQueryParser.cpp
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Copyright (C) 2016 Apple Inc. All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //    * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //    * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //    * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "config.h"
31 #include "MediaQueryParser.h"
32
33 #include "CSSParserIdioms.h"
34 #include "CSSTokenizer.h"
35 #include <wtf/Vector.h>
36
37 namespace WebCore {
38
39 RefPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(const String& queryString)
40 {
41     return parseMediaQuerySet(CSSTokenizer::Scope(queryString).tokenRange());
42 }
43
44 RefPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(CSSParserTokenRange range)
45 {
46     return MediaQueryParser(MediaQuerySetParser).parseInternal(range);
47 }
48
49 RefPtr<MediaQuerySet> MediaQueryParser::parseMediaCondition(CSSParserTokenRange range)
50 {
51     return MediaQueryParser(MediaConditionParser).parseInternal(range);
52 }
53
54 const MediaQueryParser::State MediaQueryParser::ReadRestrictor = &MediaQueryParser::readRestrictor;
55 const MediaQueryParser::State MediaQueryParser::ReadMediaNot = &MediaQueryParser::readMediaNot;
56 const MediaQueryParser::State MediaQueryParser::ReadMediaType = &MediaQueryParser::readMediaType;
57 const MediaQueryParser::State MediaQueryParser::ReadAnd = &MediaQueryParser::readAnd;
58 const MediaQueryParser::State MediaQueryParser::ReadFeatureStart = &MediaQueryParser::readFeatureStart;
59 const MediaQueryParser::State MediaQueryParser::ReadFeature = &MediaQueryParser::readFeature;
60 const MediaQueryParser::State MediaQueryParser::ReadFeatureColon = &MediaQueryParser::readFeatureColon;
61 const MediaQueryParser::State MediaQueryParser::ReadFeatureValue = &MediaQueryParser::readFeatureValue;
62 const MediaQueryParser::State MediaQueryParser::ReadFeatureEnd = &MediaQueryParser::readFeatureEnd;
63 const MediaQueryParser::State MediaQueryParser::SkipUntilComma = &MediaQueryParser::skipUntilComma;
64 const MediaQueryParser::State MediaQueryParser::SkipUntilBlockEnd = &MediaQueryParser::skipUntilBlockEnd;
65 const MediaQueryParser::State MediaQueryParser::Done = &MediaQueryParser::done;
66
67 MediaQueryParser::MediaQueryParser(ParserType parserType)
68     : m_parserType(parserType)
69     , m_querySet(MediaQuerySet::create())
70 {
71     if (parserType == MediaQuerySetParser)
72         m_state = &MediaQueryParser::readRestrictor;
73     else // MediaConditionParser
74         m_state = &MediaQueryParser::readMediaNot;
75 }
76
77 MediaQueryParser::~MediaQueryParser() { }
78
79 void MediaQueryParser::setStateAndRestrict(State state, MediaQuery::Restrictor restrictor)
80 {
81     m_mediaQueryData.setRestrictor(restrictor);
82     m_state = state;
83 }
84
85 // State machine member functions start here
86 void MediaQueryParser::readRestrictor(CSSParserTokenType type, const CSSParserToken& token)
87 {
88     readMediaType(type, token);
89 }
90
91 void MediaQueryParser::readMediaNot(CSSParserTokenType type, const CSSParserToken& token)
92 {
93     if (type == IdentToken && equalIgnoringASCIICase(token.value(), "not"))
94         setStateAndRestrict(ReadFeatureStart, MediaQuery::Not);
95     else
96         readFeatureStart(type, token);
97 }
98
99 static bool isRestrictorOrLogicalOperator(const CSSParserToken& token)
100 {
101     // FIXME: it would be more efficient to use lower-case always for tokenValue.
102     return equalIgnoringASCIICase(token.value(), "not")
103         || equalIgnoringASCIICase(token.value(), "and")
104         || equalIgnoringASCIICase(token.value(), "or")
105         || equalIgnoringASCIICase(token.value(), "only");
106 }
107
108 void MediaQueryParser::readMediaType(CSSParserTokenType type, const CSSParserToken& token)
109 {
110     if (type == LeftParenthesisToken) {
111         if (m_mediaQueryData.restrictor() != MediaQuery::None)
112             m_state = SkipUntilComma;
113         else
114             m_state = ReadFeature;
115     } else if (type == IdentToken) {
116         if (m_state == ReadRestrictor && equalIgnoringASCIICase(token.value(), "not"))
117             setStateAndRestrict(ReadMediaType, MediaQuery::Not);
118         else if (m_state == ReadRestrictor && equalIgnoringASCIICase(token.value(), "only"))
119             setStateAndRestrict(ReadMediaType, MediaQuery::Only);
120         else if (m_mediaQueryData.restrictor() != MediaQuery::None
121             && isRestrictorOrLogicalOperator(token)) {
122             m_state = SkipUntilComma;
123         } else {
124             m_mediaQueryData.setMediaType(token.value().toString());
125             m_state = ReadAnd;
126         }
127     } else if (type == EOFToken && (!m_querySet->queryVector().size() || m_state != ReadRestrictor))
128         m_state = Done;
129     else {
130         m_state = SkipUntilComma;
131         if (type == CommaToken)
132             skipUntilComma(type, token);
133     }
134 }
135
136 void MediaQueryParser::commitMediaQuery()
137 {
138     // FIXME-NEWPARSER: Convoluted and awful, but we can't change the MediaQuerySet yet because of the
139     // old parser.
140     MediaQuery mediaQuery = MediaQuery(m_mediaQueryData.restrictor(), m_mediaQueryData.mediaType(), WTFMove(m_mediaQueryData.expressions()));
141     m_mediaQueryData.clear();
142     m_querySet->addMediaQuery(WTFMove(mediaQuery));
143 }
144
145 void MediaQueryParser::readAnd(CSSParserTokenType type, const CSSParserToken& token)
146 {
147     if (type == IdentToken && equalIgnoringASCIICase(token.value(), "and")) {
148         m_state = ReadFeatureStart;
149     } else if (type == CommaToken && m_parserType != MediaConditionParser) {
150         commitMediaQuery();
151         m_state = ReadRestrictor;
152     } else if (type == EOFToken)
153         m_state = Done;
154     else
155         m_state = SkipUntilComma;
156 }
157
158 void MediaQueryParser::readFeatureStart(CSSParserTokenType type, const CSSParserToken& /*token*/)
159 {
160     if (type == LeftParenthesisToken)
161         m_state = ReadFeature;
162     else
163         m_state = SkipUntilComma;
164 }
165
166 void MediaQueryParser::readFeature(CSSParserTokenType type, const CSSParserToken& token)
167 {
168     if (type == IdentToken) {
169         m_mediaQueryData.setMediaFeature(token.value().toString());
170         m_state = ReadFeatureColon;
171     } else
172         m_state = SkipUntilComma;
173 }
174
175 void MediaQueryParser::readFeatureColon(CSSParserTokenType type, const CSSParserToken& token)
176 {
177     if (type == ColonToken)
178         m_state = ReadFeatureValue;
179     else if (type == RightParenthesisToken || type == EOFToken)
180         readFeatureEnd(type, token);
181     else
182         m_state = SkipUntilBlockEnd;
183 }
184
185 void MediaQueryParser::readFeatureValue(CSSParserTokenType type, const CSSParserToken& token)
186 {
187     if (type == DimensionToken && token.unitType() == CSSPrimitiveValue::UnitTypes::CSS_UNKNOWN)
188         m_state = SkipUntilComma;
189     else {
190         if (m_mediaQueryData.tryAddParserToken(type, token))
191             m_state = ReadFeatureEnd;
192         else
193             m_state = SkipUntilBlockEnd;
194     }
195 }
196
197 void MediaQueryParser::readFeatureEnd(CSSParserTokenType type, const CSSParserToken& token)
198 {
199     if (type == RightParenthesisToken || type == EOFToken) {
200         if (type != EOFToken && m_mediaQueryData.addExpression())
201             m_state = ReadAnd;
202         else
203             m_state = SkipUntilComma;
204     } else if (type == DelimiterToken && token.delimiter() == '/') {
205         m_mediaQueryData.tryAddParserToken(type, token);
206         m_state = ReadFeatureValue;
207     } else
208         m_state = SkipUntilBlockEnd;
209 }
210
211 void MediaQueryParser::skipUntilComma(CSSParserTokenType type, const CSSParserToken& /*token*/)
212 {
213     if ((type == CommaToken && !m_blockWatcher.blockLevel()) || type == EOFToken) {
214         m_state = ReadRestrictor;
215         m_mediaQueryData.clear();
216         MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
217         m_querySet->addMediaQuery(WTFMove(query));
218     }
219 }
220
221 void MediaQueryParser::skipUntilBlockEnd(CSSParserTokenType /*type */, const CSSParserToken& token)
222 {
223     if (token.getBlockType() == CSSParserToken::BlockEnd && !m_blockWatcher.blockLevel())
224         m_state = SkipUntilComma;
225 }
226
227 void MediaQueryParser::done(CSSParserTokenType /*type*/, const CSSParserToken& /*token*/) { }
228
229 void MediaQueryParser::handleBlocks(const CSSParserToken& token)
230 {
231     if (token.getBlockType() == CSSParserToken::BlockStart
232         && (token.type() != LeftParenthesisToken || m_blockWatcher.blockLevel()))
233             m_state = SkipUntilBlockEnd;
234 }
235
236 void MediaQueryParser::processToken(const CSSParserToken& token)
237 {
238     CSSParserTokenType type = token.type();
239
240     handleBlocks(token);
241     m_blockWatcher.handleToken(token);
242
243     // Call the function that handles current state
244     if (type != WhitespaceToken)
245         ((this)->*(m_state))(type, token);
246 }
247
248 // The state machine loop
249 RefPtr<MediaQuerySet> MediaQueryParser::parseInternal(CSSParserTokenRange range)
250 {
251     while (!range.atEnd())
252         processToken(range.consume());
253
254     // FIXME: Can we get rid of this special case?
255     if (m_parserType == MediaQuerySetParser)
256         processToken(CSSParserToken(EOFToken));
257
258     if (m_state != ReadAnd && m_state != ReadRestrictor && m_state != Done && m_state != ReadMediaNot) {
259         MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
260         m_querySet->addMediaQuery(WTFMove(query));
261     } else if (m_mediaQueryData.currentMediaQueryChanged())
262         commitMediaQuery();
263
264     return m_querySet;
265 }
266
267 MediaQueryData::MediaQueryData()
268     : m_restrictor(MediaQuery::None)
269     , m_mediaType("all")
270     , m_mediaTypeSet(false)
271 {
272 }
273
274 void MediaQueryData::clear()
275 {
276     m_restrictor = MediaQuery::None;
277     m_mediaType = "all";
278     m_mediaTypeSet = false;
279     m_mediaFeature = String();
280     m_valueList.clear();
281     m_expressions.clear();
282 }
283
284 bool MediaQueryData::addExpression()
285 {
286     MediaQueryExpression expression = MediaQueryExpression(m_mediaFeature, m_valueList);
287     bool isValid = expression.isValid();
288     m_expressions.append(WTFMove(expression));
289     m_valueList.clear();
290     return isValid;
291 }
292
293 bool MediaQueryData::tryAddParserToken(CSSParserTokenType type, const CSSParserToken& token)
294 {
295     if (type == NumberToken || type == PercentageToken || type == DimensionToken
296         || type == DelimiterToken || type == IdentToken) {
297         m_valueList.append(token);
298         return true;
299     }
300
301     return false;
302 }
303
304 void MediaQueryData::setMediaType(const String& mediaType)
305 {
306     m_mediaType = mediaType;
307     m_mediaTypeSet = true;
308 }
309
310 } // namespace WebCsore