d4a7224ee3f216728059a36848bc94fd4596f388
[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         // FIXME-NEWPARSER: Find a way to avoid this.
170         const_cast<CSSParserToken&>(token).convertToASCIILowercaseInPlace();
171         m_mediaQueryData.setMediaFeature(token.value().toString());
172         m_state = ReadFeatureColon;
173     } else
174         m_state = SkipUntilComma;
175 }
176
177 void MediaQueryParser::readFeatureColon(CSSParserTokenType type, const CSSParserToken& token)
178 {
179     if (type == ColonToken)
180         m_state = ReadFeatureValue;
181     else if (type == RightParenthesisToken || type == EOFToken)
182         readFeatureEnd(type, token);
183     else
184         m_state = SkipUntilBlockEnd;
185 }
186
187 void MediaQueryParser::readFeatureValue(CSSParserTokenType type, const CSSParserToken& token)
188 {
189     if (type == DimensionToken && token.unitType() == CSSPrimitiveValue::UnitTypes::CSS_UNKNOWN)
190         m_state = SkipUntilComma;
191     else {
192         if (m_mediaQueryData.tryAddParserToken(type, token))
193             m_state = ReadFeatureEnd;
194         else
195             m_state = SkipUntilBlockEnd;
196     }
197 }
198
199 void MediaQueryParser::readFeatureEnd(CSSParserTokenType type, const CSSParserToken& token)
200 {
201     if (type == RightParenthesisToken || type == EOFToken) {
202         if (type != EOFToken && m_mediaQueryData.addExpression())
203             m_state = ReadAnd;
204         else
205             m_state = SkipUntilComma;
206     } else if (type == DelimiterToken && token.delimiter() == '/') {
207         m_mediaQueryData.tryAddParserToken(type, token);
208         m_state = ReadFeatureValue;
209     } else
210         m_state = SkipUntilBlockEnd;
211 }
212
213 void MediaQueryParser::skipUntilComma(CSSParserTokenType type, const CSSParserToken& /*token*/)
214 {
215     if ((type == CommaToken && !m_blockWatcher.blockLevel()) || type == EOFToken) {
216         m_state = ReadRestrictor;
217         m_mediaQueryData.clear();
218         MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
219         m_querySet->addMediaQuery(WTFMove(query));
220     }
221 }
222
223 void MediaQueryParser::skipUntilBlockEnd(CSSParserTokenType /*type */, const CSSParserToken& token)
224 {
225     if (token.getBlockType() == CSSParserToken::BlockEnd && !m_blockWatcher.blockLevel())
226         m_state = SkipUntilComma;
227 }
228
229 void MediaQueryParser::done(CSSParserTokenType /*type*/, const CSSParserToken& /*token*/) { }
230
231 void MediaQueryParser::handleBlocks(const CSSParserToken& token)
232 {
233     if (token.getBlockType() == CSSParserToken::BlockStart
234         && (token.type() != LeftParenthesisToken || m_blockWatcher.blockLevel()))
235             m_state = SkipUntilBlockEnd;
236 }
237
238 void MediaQueryParser::processToken(const CSSParserToken& token)
239 {
240     CSSParserTokenType type = token.type();
241
242     handleBlocks(token);
243     m_blockWatcher.handleToken(token);
244
245     // Call the function that handles current state
246     if (type != WhitespaceToken)
247         ((this)->*(m_state))(type, token);
248 }
249
250 // The state machine loop
251 RefPtr<MediaQuerySet> MediaQueryParser::parseInternal(CSSParserTokenRange range)
252 {
253     while (!range.atEnd())
254         processToken(range.consume());
255
256     // FIXME: Can we get rid of this special case?
257     if (m_parserType == MediaQuerySetParser)
258         processToken(CSSParserToken(EOFToken));
259
260     if (m_state != ReadAnd && m_state != ReadRestrictor && m_state != Done && m_state != ReadMediaNot) {
261         MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
262         m_querySet->addMediaQuery(WTFMove(query));
263     } else if (m_mediaQueryData.currentMediaQueryChanged())
264         commitMediaQuery();
265
266     return m_querySet;
267 }
268
269 MediaQueryData::MediaQueryData()
270     : m_restrictor(MediaQuery::None)
271     , m_mediaType("all")
272     , m_mediaTypeSet(false)
273 {
274 }
275
276 void MediaQueryData::clear()
277 {
278     m_restrictor = MediaQuery::None;
279     m_mediaType = "all";
280     m_mediaTypeSet = false;
281     m_mediaFeature = String();
282     m_valueList.clear();
283     m_expressions.clear();
284 }
285
286 bool MediaQueryData::addExpression()
287 {
288     MediaQueryExpression expression = MediaQueryExpression(m_mediaFeature, m_valueList);
289     bool isValid = expression.isValid();
290     m_expressions.append(WTFMove(expression));
291     m_valueList.clear();
292     return isValid;
293 }
294
295 bool MediaQueryData::tryAddParserToken(CSSParserTokenType type, const CSSParserToken& token)
296 {
297     if (type == NumberToken || type == PercentageToken || type == DimensionToken
298         || type == DelimiterToken || type == IdentToken) {
299         m_valueList.append(token);
300         return true;
301     }
302
303     return false;
304 }
305
306 void MediaQueryData::setMediaType(const String& mediaType)
307 {
308     m_mediaType = mediaType;
309     m_mediaTypeSet = true;
310 }
311
312 } // namespace WebCsore