Use Optional::valueOr() instead of Optional::value_or()
[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 "CSSTokenizer.h"
34 #include "MediaList.h"
35 #include "MediaQueryParserContext.h"
36 #include <wtf/Vector.h>
37
38 namespace WebCore {
39
40 RefPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(const String& queryString, MediaQueryParserContext context)
41 {
42     return parseMediaQuerySet(CSSTokenizer(queryString).tokenRange(), context);
43 }
44
45 RefPtr<MediaQuerySet> MediaQueryParser::parseMediaQuerySet(CSSParserTokenRange range, MediaQueryParserContext context)
46 {
47     return MediaQueryParser(MediaQuerySetParser, context).parseInternal(range);
48 }
49
50 RefPtr<MediaQuerySet> MediaQueryParser::parseMediaCondition(CSSParserTokenRange range, MediaQueryParserContext context)
51 {
52     return MediaQueryParser(MediaConditionParser, context).parseInternal(range);
53 }
54
55 const MediaQueryParser::State MediaQueryParser::ReadRestrictor = &MediaQueryParser::readRestrictor;
56 const MediaQueryParser::State MediaQueryParser::ReadMediaNot = &MediaQueryParser::readMediaNot;
57 const MediaQueryParser::State MediaQueryParser::ReadMediaType = &MediaQueryParser::readMediaType;
58 const MediaQueryParser::State MediaQueryParser::ReadAnd = &MediaQueryParser::readAnd;
59 const MediaQueryParser::State MediaQueryParser::ReadFeatureStart = &MediaQueryParser::readFeatureStart;
60 const MediaQueryParser::State MediaQueryParser::ReadFeature = &MediaQueryParser::readFeature;
61 const MediaQueryParser::State MediaQueryParser::ReadFeatureColon = &MediaQueryParser::readFeatureColon;
62 const MediaQueryParser::State MediaQueryParser::ReadFeatureValue = &MediaQueryParser::readFeatureValue;
63 const MediaQueryParser::State MediaQueryParser::ReadFeatureEnd = &MediaQueryParser::readFeatureEnd;
64 const MediaQueryParser::State MediaQueryParser::SkipUntilComma = &MediaQueryParser::skipUntilComma;
65 const MediaQueryParser::State MediaQueryParser::SkipUntilBlockEnd = &MediaQueryParser::skipUntilBlockEnd;
66 const MediaQueryParser::State MediaQueryParser::Done = &MediaQueryParser::done;
67
68 MediaQueryParser::MediaQueryParser(ParserType parserType, MediaQueryParserContext context)
69     : m_parserType(parserType)
70     , m_mediaQueryData(context)
71     , m_querySet(MediaQuerySet::create())
72     
73 {
74     if (parserType == MediaQuerySetParser)
75         m_state = &MediaQueryParser::readRestrictor;
76     else // MediaConditionParser
77         m_state = &MediaQueryParser::readMediaNot;
78 }
79
80 MediaQueryParser::~MediaQueryParser() = default;
81
82 void MediaQueryParser::setStateAndRestrict(State state, MediaQuery::Restrictor restrictor)
83 {
84     m_mediaQueryData.setRestrictor(restrictor);
85     m_state = state;
86 }
87
88 // State machine member functions start here
89 void MediaQueryParser::readRestrictor(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& range)
90 {
91     readMediaType(type, token, range);
92 }
93
94 void MediaQueryParser::readMediaNot(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& range)
95 {
96     if (type == IdentToken && equalIgnoringASCIICase(token.value(), "not"))
97         setStateAndRestrict(ReadFeatureStart, MediaQuery::Not);
98     else
99         readFeatureStart(type, token, range);
100 }
101
102 static bool isRestrictorOrLogicalOperator(const CSSParserToken& token)
103 {
104     // FIXME: it would be more efficient to use lower-case always for tokenValue.
105     return equalIgnoringASCIICase(token.value(), "not")
106         || equalIgnoringASCIICase(token.value(), "and")
107         || equalIgnoringASCIICase(token.value(), "or")
108         || equalIgnoringASCIICase(token.value(), "only");
109 }
110
111 void MediaQueryParser::readMediaType(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& range)
112 {
113     if (type == LeftParenthesisToken) {
114         if (m_mediaQueryData.restrictor() != MediaQuery::None)
115             m_state = SkipUntilComma;
116         else
117             m_state = ReadFeature;
118     } else if (type == IdentToken) {
119         if (m_state == ReadRestrictor && equalIgnoringASCIICase(token.value(), "not"))
120             setStateAndRestrict(ReadMediaType, MediaQuery::Not);
121         else if (m_state == ReadRestrictor && equalIgnoringASCIICase(token.value(), "only"))
122             setStateAndRestrict(ReadMediaType, MediaQuery::Only);
123         else if (m_mediaQueryData.restrictor() != MediaQuery::None
124             && isRestrictorOrLogicalOperator(token)) {
125             m_state = SkipUntilComma;
126         } else {
127             m_mediaQueryData.setMediaType(token.value().toString());
128             m_state = ReadAnd;
129         }
130     } else if (type == EOFToken && (!m_querySet->queryVector().size() || m_state != ReadRestrictor))
131         m_state = Done;
132     else {
133         m_state = SkipUntilComma;
134         if (type == CommaToken)
135             skipUntilComma(type, token, range);
136     }
137 }
138
139 void MediaQueryParser::commitMediaQuery()
140 {
141     // FIXME-NEWPARSER: Convoluted and awful, but we can't change the MediaQuerySet yet because of the
142     // old parser.
143     static const NeverDestroyed<String> defaultMediaType { "all"_s };
144     MediaQuery mediaQuery { m_mediaQueryData.restrictor(), m_mediaQueryData.mediaType().valueOr(defaultMediaType), WTFMove(m_mediaQueryData.expressions()) };
145     m_mediaQueryData.clear();
146     m_querySet->addMediaQuery(WTFMove(mediaQuery));
147 }
148
149 void MediaQueryParser::readAnd(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& /*range*/)
150 {
151     if (type == IdentToken && equalIgnoringASCIICase(token.value(), "and")) {
152         m_state = ReadFeatureStart;
153     } else if (type == CommaToken && m_parserType != MediaConditionParser) {
154         commitMediaQuery();
155         m_state = ReadRestrictor;
156     } else if (type == EOFToken)
157         m_state = Done;
158     else
159         m_state = SkipUntilComma;
160 }
161
162 void MediaQueryParser::readFeatureStart(CSSParserTokenType type, const CSSParserToken& /*token*/, CSSParserTokenRange& /*range*/)
163 {
164     if (type == LeftParenthesisToken)
165         m_state = ReadFeature;
166     else
167         m_state = SkipUntilComma;
168 }
169
170 void MediaQueryParser::readFeature(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& /*range*/)
171 {
172     if (type == IdentToken) {
173         m_mediaQueryData.setMediaFeature(token.value().toString());
174         m_state = ReadFeatureColon;
175     } else
176         m_state = SkipUntilComma;
177 }
178
179 void MediaQueryParser::readFeatureColon(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& range)
180 {
181     if (type == ColonToken) {
182         while (range.peek().type() == WhitespaceToken)
183             range.consume();
184         if (range.peek().type() == RightParenthesisToken || range.peek().type() == EOFToken)
185             m_state = SkipUntilBlockEnd;
186         else
187             m_state = ReadFeatureValue;
188     } else if (type == RightParenthesisToken || type == EOFToken) {
189         m_mediaQueryData.addExpression(range);
190         readFeatureEnd(type, token, range);
191     } else
192         m_state = SkipUntilBlockEnd;
193 }
194
195 void MediaQueryParser::readFeatureValue(CSSParserTokenType type, const CSSParserToken& token, CSSParserTokenRange& range)
196 {
197     if (type == DimensionToken && token.unitType() == CSSPrimitiveValue::UnitType::CSS_UNKNOWN) {
198         range.consume();
199         m_state = SkipUntilComma;
200     } else {
201         m_mediaQueryData.addExpression(range);
202         m_state = ReadFeatureEnd;
203     }
204 }
205
206 void MediaQueryParser::readFeatureEnd(CSSParserTokenType type, const CSSParserToken& /*token*/, CSSParserTokenRange& /*range*/)
207 {
208     if (type == RightParenthesisToken || type == EOFToken) {
209         if (type != EOFToken && m_mediaQueryData.lastExpressionValid())
210             m_state = ReadAnd;
211         else
212             m_state = SkipUntilComma;
213     } else {
214         m_mediaQueryData.removeLastExpression();
215         m_state = SkipUntilBlockEnd;
216     }
217 }
218
219 void MediaQueryParser::skipUntilComma(CSSParserTokenType type, const CSSParserToken& /*token*/, CSSParserTokenRange& /*range*/)
220 {
221     if ((type == CommaToken && !m_blockWatcher.blockLevel()) || type == EOFToken) {
222         m_state = ReadRestrictor;
223         m_mediaQueryData.clear();
224         MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
225         m_querySet->addMediaQuery(WTFMove(query));
226     }
227 }
228
229 void MediaQueryParser::skipUntilBlockEnd(CSSParserTokenType /*type */, const CSSParserToken& token, CSSParserTokenRange& /*range*/)
230 {
231     if (token.getBlockType() == CSSParserToken::BlockEnd && !m_blockWatcher.blockLevel())
232         m_state = SkipUntilComma;
233 }
234
235 void MediaQueryParser::done(CSSParserTokenType /*type*/, const CSSParserToken& /*token*/, CSSParserTokenRange& /*range*/) { }
236
237 void MediaQueryParser::handleBlocks(const CSSParserToken& token)
238 {
239     if (token.getBlockType() == CSSParserToken::BlockStart
240         && (token.type() != LeftParenthesisToken || m_blockWatcher.blockLevel()))
241             m_state = SkipUntilBlockEnd;
242 }
243
244 void MediaQueryParser::processToken(const CSSParserToken& token, CSSParserTokenRange& range)
245 {
246     CSSParserTokenType type = token.type();
247
248     if (m_state != ReadFeatureValue || type == WhitespaceToken) {
249         handleBlocks(token);
250         m_blockWatcher.handleToken(token);
251         range.consume();
252     }
253
254     // Call the function that handles current state
255     if (type != WhitespaceToken)
256         ((this)->*(m_state))(type, token, range);
257 }
258
259 // The state machine loop
260 RefPtr<MediaQuerySet> MediaQueryParser::parseInternal(CSSParserTokenRange range)
261 {
262     while (!range.atEnd())
263         processToken(range.peek(), range);
264
265     // FIXME: Can we get rid of this special case?
266     if (m_parserType == MediaQuerySetParser)
267         processToken(CSSParserToken(EOFToken), range);
268
269     if (m_state != ReadAnd && m_state != ReadRestrictor && m_state != Done && m_state != ReadMediaNot) {
270         MediaQuery query = MediaQuery(MediaQuery::Not, "all", Vector<MediaQueryExpression>());
271         m_querySet->addMediaQuery(WTFMove(query));
272     } else if (m_mediaQueryData.currentMediaQueryChanged())
273         commitMediaQuery();
274
275     m_querySet->shrinkToFit();
276
277     return m_querySet;
278 }
279
280 MediaQueryParser::MediaQueryData::MediaQueryData(MediaQueryParserContext context)
281     : m_context(context)
282 {
283 }
284
285 void MediaQueryParser::MediaQueryData::clear()
286 {
287     m_restrictor = MediaQuery::None;
288     m_mediaType = WTF::nullopt;
289     m_mediaFeature = String();
290     m_expressions.clear();
291 }
292
293 void MediaQueryParser::MediaQueryData::addExpression(CSSParserTokenRange& range)
294 {
295     m_expressions.append(MediaQueryExpression { m_mediaFeature, range, m_context });
296 }
297
298 bool MediaQueryParser::MediaQueryData::lastExpressionValid()
299 {
300     return m_expressions.last().isValid();
301 }
302
303 void MediaQueryParser::MediaQueryData::removeLastExpression()
304 {
305     m_expressions.removeLast();
306 }
307
308 } // namespace WebCore