1use super::prelude::*;
2use css_parse::Box;
3
4mod features;
5pub use features::*;
6
7#[node]
9#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
11#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.media"))]
12#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
13#[derive(csskit_derives::NodeWithMetadata)]
14#[metadata(node_kinds = AtRule, used_at_rules = Media)]
15pub struct MediaRule<'a> {
16 #[cfg_attr(feature = "visitable", visit(skip))]
17 #[atom(CssAtomSet::Media)]
18 pub name: T![AtKeyword],
19 pub prelude: MediaQueryList<'a>,
20 #[metadata(block)]
21 pub block: MediaRuleBlock<'a>,
22}
23
24#[node]
25#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
27#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
28#[derive(csskit_derives::NodeWithMetadata)]
29pub struct MediaRuleBlock<'a>(pub Block<'a, StyleValue<'a>, Rule<'a>, CssMetadata>);
30#[node]
31#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
33#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
34#[derive(csskit_derives::NodeWithMetadata)]
35pub struct MediaQueryList<'a>(pub CommaSeparated<'a, MediaQuery<'a>, 1>);
36
37#[node]
38#[derive(
39 Parse, Peek, ToCursors, IntoCursor, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
40)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
42#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
43#[derive(csskit_derives::NodeWithMetadata)]
44pub enum MediaType {
45 #[atom(CssAtomSet::All)]
46 #[cfg_attr(feature = "visitable", visit(skip))]
47 All(T![Ident]),
48 #[atom(CssAtomSet::Print)]
49 #[cfg_attr(feature = "visitable", visit(skip))]
50 Print(T![Ident]),
51 #[atom(CssAtomSet::Screen)]
52 #[cfg_attr(feature = "visitable", visit(skip))]
53 Screen(T![Ident]),
54 #[cfg_attr(feature = "visitable", visit(skip))]
55 Custom(T![Ident]),
56}
57
58#[node]
59#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
61#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
62#[derive(csskit_derives::NodeWithMetadata)]
63pub enum MediaPreCondition {
64 #[atom(CssAtomSet::Not)]
65 #[cfg_attr(feature = "visitable", visit(skip))]
66 Not(T![Ident]),
67 #[atom(CssAtomSet::Only)]
68 #[cfg_attr(feature = "visitable", visit(skip))]
69 Only(T![Ident]),
70}
71
72#[node]
73#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
75#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
76#[derive(csskit_derives::NodeWithMetadata)]
77pub struct MediaQuery<'a> {
78 precondition: Option<MediaPreCondition>,
79 media_type: Option<MediaType>,
80 #[cfg_attr(feature = "visitable", visit(skip))]
81 and: Option<T![Ident]>,
82 condition: Option<MediaCondition<'a>>,
83}
84
85impl<'a> Peek<'a> for MediaQuery<'a> {
86 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Ident, Kind::LeftParen]);
87}
88
89impl<'a> Parse<'a> for MediaQuery<'a> {
90 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
91 where
92 I: Iterator<Item = Cursor> + Clone,
93 {
94 let mut precondition = None;
95 let mut media_type = None;
96 let mut and = None;
97 let mut condition = None;
98 if p.peek::<T!['(']>() {
99 condition = Some(p.parse::<MediaCondition<'a>>()?);
100 return Ok(Self { precondition, media_type, and, condition });
101 }
102 let c = p.peek_n(1);
103 if MediaPreCondition::peek(p, c) {
104 precondition = Some(p.parse::<MediaPreCondition>()?);
105 } else if MediaType::peek(p, c) {
106 media_type = Some(p.parse::<MediaType>()?);
107 } else {
108 Err(Diagnostic::new(c, Diagnostic::expected_ident))?
109 }
110 if p.peek::<T![Ident]>() && precondition.is_some() {
111 let c: Cursor = p.peek_n(1);
112 if MediaType::peek(p, c) {
113 media_type = Some(p.parse::<MediaType>()?);
114 } else {
115 Err(Diagnostic::new(c, Diagnostic::expected_ident))?
116 }
117 }
118 let c = p.peek_n(1);
119 if c == Kind::Ident && p.equals_atom(c, &CssAtomSet::And) {
120 and = Some(p.parse::<T![Ident]>()?);
121 condition = Some(p.parse::<MediaCondition>()?);
122 }
123 Ok(Self { precondition, media_type, and, condition })
124 }
125}
126
127#[node]
128#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
129#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
130#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
131#[derive(csskit_derives::NodeWithMetadata)]
132pub enum MediaCondition<'a> {
133 Is(MediaFeature<'a>),
134 Not(T![Ident], MediaFeature<'a>),
135 And(Vec<'a, (MediaFeature<'a>, Option<T![Ident]>)>),
136 Or(Vec<'a, (MediaFeature<'a>, Option<T![Ident]>)>),
137}
138
139impl<'a> FeatureConditionList<'a> for MediaCondition<'a> {
140 type FeatureCondition = MediaFeature<'a>;
141 fn keyword_is_not<I>(p: &Parser<'a, I>, c: Cursor) -> bool
142 where
143 I: Iterator<Item = Cursor> + Clone,
144 {
145 p.equals_atom(c, &CssAtomSet::Not)
146 }
147 fn keyword_is_and<I>(p: &Parser<'a, I>, c: Cursor) -> bool
148 where
149 I: Iterator<Item = Cursor> + Clone,
150 {
151 p.equals_atom(c, &CssAtomSet::And)
152 }
153 fn keyword_is_or<I>(p: &Parser<'a, I>, c: Cursor) -> bool
154 where
155 I: Iterator<Item = Cursor> + Clone,
156 {
157 p.equals_atom(c, &CssAtomSet::Or)
158 }
159 fn build_is(feature: MediaFeature<'a>) -> Self {
160 Self::Is(feature)
161 }
162 fn build_not(keyword: T![Ident], feature: MediaFeature<'a>) -> Self {
163 Self::Not(keyword, feature)
164 }
165 fn build_and(feature: Vec<'a, (MediaFeature<'a>, Option<T![Ident]>)>) -> Self {
166 Self::And(feature)
167 }
168 fn build_or(feature: Vec<'a, (MediaFeature<'a>, Option<T![Ident]>)>) -> Self {
169 Self::Or(feature)
170 }
171}
172
173impl<'a> Parse<'a> for MediaCondition<'a> {
174 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
175 where
176 I: Iterator<Item = Cursor> + Clone,
177 {
178 Self::parse_condition(p)
179 }
180}
181
182macro_rules! media_feature {
183 ( $($name: ident($typ: ty): $pat: pat,)+) => {
184 #[node]
186 #[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
187 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
188 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
189 #[derive(csskit_derives::NodeWithMetadata)]
190 pub enum MediaFeature<'a> {
191 $($name($typ),)+
192 #[cfg_attr(feature = "visitable", visit(skip))]
193 Custom(CustomMediaFeature),
194 #[cfg_attr(feature = "visitable", visit(skip))]
195 Hack(HackMediaFeature),
196 }
197 }
198}
199
200apply_medias!(media_feature);
201
202impl<'a> Peek<'a> for MediaFeature<'a> {
203 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::LeftParen]);
204}
205
206impl<'a> Parse<'a> for MediaFeature<'a> {
207 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
208 where
209 I: Iterator<Item = Cursor> + Clone,
210 {
211 let checkpoint = p.checkpoint();
212 let mut c = p.peek_n(2);
213 macro_rules! match_media {
214 ( $($name: ident($typ: ty): $pat: pat,)+) => {
215 {
217 match p.to_atom::<CssAtomSet>(c) {
218 $($pat => <$typ>::parse(p).map(Self::$name),)+
219 _ => Err(Diagnostic::new(c, Diagnostic::expected_ident))?
220 }
221 }
222 }
223 }
224 if c == Kind::Ident && c.token().is_dashed_ident() {
225 return p.parse::<CustomMediaFeature>().map(Self::Custom);
226 }
227 if c == Kind::Ident {
228 let value = apply_medias!(match_media).or_else(|err| {
229 p.rewind(checkpoint);
230 if let Ok(hack) = p.parse::<HackMediaFeature>() { Ok(Self::Hack(hack)) } else { Err(err) }
231 })?;
232 Ok(value)
233 } else {
234 c = css_parse::peek_ranged_feature_name(p);
236 if c != Kind::Ident {
237 c = p.next();
238 Err(Diagnostic::new(c, Diagnostic::unexpected))?
239 }
240 apply_medias!(match_media)
241 }
242 }
243}
244
245macro_rules! apply_medias {
246 ($macro: ident) => {
247 $macro! {
248 AnyHover(AnyHoverMediaFeature): CssAtomSet::AnyHover,
251 AnyPointer(AnyPointerMediaFeature): CssAtomSet::AnyPointer,
252 AspectRatio(Box<'a, AspectRatioMediaFeature<'a>>): CssAtomSet::AspectRatio | CssAtomSet::MinAspectRatio | CssAtomSet::MaxAspectRatio,
253 Color(ColorMediaFeature<'a>): CssAtomSet::Color | CssAtomSet::MaxColor | CssAtomSet::MinColor,
254 ColorGamut(ColorGamutMediaFeature): CssAtomSet::ColorGamut,
255 ColorIndex(ColorIndexMediaFeature<'a>): CssAtomSet::ColorIndex | CssAtomSet::MaxColorIndex | CssAtomSet::MinColorIndex,
256 DeviceAspectRatio(Box<'a, DeviceAspectRatioMediaFeature<'a>>): CssAtomSet::DeviceAspectRatio | CssAtomSet::MaxDeviceAspectRatio | CssAtomSet::MinDeviceAspectRatio,
257 DeviceHeight(DeviceHeightMediaFeature<'a>): CssAtomSet::DeviceHeight | CssAtomSet::MaxDeviceHeight | CssAtomSet::MinDeviceHeight,
258 DeviceWidth(DeviceWidthMediaFeature<'a>): CssAtomSet::DeviceWidth | CssAtomSet::MaxDeviceWidth | CssAtomSet::MinDeviceWidth,
259 DisplayMode(DisplayModeMediaFeature): CssAtomSet::DisplayMode,
260 DynamicRange(DynamicRangeMediaFeature): CssAtomSet::DynamicRange,
261 EnvironmentBlending(EnvironmentBlendingMediaFeature): CssAtomSet::EnvironmentBlending,
262 ForcedColors(ForcedColorsMediaFeature): CssAtomSet::ForcedColors,
263 Grid(GridMediaFeature): CssAtomSet::Grid,
264 Height(HeightMediaFeature<'a>): CssAtomSet::Height | CssAtomSet::MaxHeight | CssAtomSet::MinHeight,
265 HorizontalViewportSegments(HorizontalViewportSegmentsMediaFeature<'a>): CssAtomSet::HorizontalViewportSegments | CssAtomSet::MaxHorizontalViewportSegments | CssAtomSet::MinHorizontalViewportSegments,
266 Hover(HoverMediaFeature): CssAtomSet::Hover,
267 InvertedColors(InvertedColorsMediaFeature): CssAtomSet::InvertedColors,
268 Monochrome(MonochromeMediaFeature<'a>): CssAtomSet::Monochrome | CssAtomSet::MaxMonochrome | CssAtomSet::MinMonochrome,
269 NavControls(NavControlsMediaFeature): CssAtomSet::NavControls,
270 Orientation(OrientationMediaFeature): CssAtomSet::Orientation,
271 OverflowBlock(OverflowBlockMediaFeature): CssAtomSet::OverflowBlock,
272 OverflowInline(OverflowInlineMediaFeature): CssAtomSet::OverflowInline,
273 Pointer(PointerMediaFeature): CssAtomSet::Pointer,
274 PrefersColorScheme(PrefersColorSchemeMediaFeature): CssAtomSet::PrefersColorScheme,
275 PrefersContrast(PrefersContrastMediaFeature): CssAtomSet::PrefersContrast,
276 PrefersReducedData(PrefersReducedDataMediaFeature): CssAtomSet::PrefersReducedData,
277 PrefersReducedMotion(PrefersReducedMotionMediaFeature): CssAtomSet::PrefersReducedMotion,
278 PrefersReducedTransparency(PrefersReducedTransparencyMediaFeature): CssAtomSet::PrefersReducedTransparency,
279 Resolution(ResolutionMediaFeature<'a>): CssAtomSet::Resolution | CssAtomSet::MaxResolution | CssAtomSet::MinResolution,
280 Scan(ScanMediaFeature): CssAtomSet::Scan,
281 Scripting(ScriptingMediaFeature): CssAtomSet::Scripting,
282 Update(UpdateMediaFeature): CssAtomSet::Update,
283 VerticalViewportSegments(VerticalViewportSegmentsMediaFeature<'a>): CssAtomSet::VerticalViewportSegments | CssAtomSet::MaxVerticalViewportSegments | CssAtomSet::MinVerticalViewportSegments,
284 VideoColorGamut(VideoColorGamutMediaFeature): CssAtomSet::VideoColorGamut,
285 VideoDynamicRange(VideoDynamicRangeMediaFeature): CssAtomSet::VideoDynamicRange,
286 Width(WidthMediaFeature<'a>): CssAtomSet::Width | CssAtomSet::MaxWidth | CssAtomSet::MinWidth,
287
288 WebkitAnimationMediaFeature(WebkitAnimationMediaFeature): CssAtomSet::_WebkitAnimation,
290 WebkitDevicePixelRatioMediaFeature(WebkitDevicePixelRatioMediaFeature<'a>): CssAtomSet::_WebkitDevicePixelRatio,
291 WebkitTransform2dMediaFeature(WebkitTransform2dMediaFeature): CssAtomSet::_WebkitTransform2d,
292 WebkitTransform3dMediaFeature(WebkitTransform3dMediaFeature): CssAtomSet::_WebkitTransform3d,
293 WebkitTransitionMediaFeature(WebkitTransitionMediaFeature): CssAtomSet::_WebkitTransition,
294 WebkitVideoPlayableInlineMediaFeature(WebkitVideoPlayableInlineMediaFeature): CssAtomSet::_WebkitVideoPlayableInline,
295
296 MozDeviceOrientationMediaFeature(MozDeviceOrientationMediaFeature): CssAtomSet::_MozDeviceOrientation,
298 MozDevicePixelRatioMediaFeature(MozDevicePixelRatioMediaFeature<'a>): CssAtomSet::_MozDevicePixelRatio | CssAtomSet::_MozMaxDevicePixelRatio | CssAtomSet::_MozMinDevicePixelRatio,
299 MozMacGraphiteThemeMediaFeature(MozMacGraphiteThemeMediaFeature): CssAtomSet::_MozMacGraphiteTheme,
300 MozMaemoClassicMediaFeature(MozMaemoClassicMediaFeature): CssAtomSet::_MozMaemoClassicTheme,
301 MozImagesInMenusMediaFeature(MozImagesInMenusMediaFeature): CssAtomSet::_MozImagesInMenus,
302 MozOsVersionMenusMediaFeature(MozOsVersionMediaFeature): CssAtomSet::_MozOsVersion,
303
304 MsHighContrastMediaFeature(MsHighContrastMediaFeature): CssAtomSet::_MsHighContrast,
306 MsViewStateMediaFeature(MsViewStateMediaFeature): CssAtomSet::_MsViewState,
307 MsImeAlignMediaFeature(MsImeAlignMediaFeature): CssAtomSet::_MsImeAlign,
308 MsDevicePixelRatioMediaFeature(MsDevicePixelRatioMediaFeature<'a>): CssAtomSet::_MsDevicePixelRatio,
309 MsColumnCountMediaFeature(MsColumnCountMediaFeature<'a>): CssAtomSet::_MsColumnCount,
310
311 ODevicePixelRatioMediaFeature(ODevicePixelRatioMediaFeature<'a>): CssAtomSet::_ODevicePixelRatio,
313 }
314 };
315}
316use apply_medias;
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::CssAtomSet;
322 use css_parse::{assert_parse, assert_parse_error};
323
324 #[test]
325 fn test_writes() {
326 assert_parse!(
327 CssAtomSet::ATOMS,
328 MediaQuery,
329 "print",
330 MediaQuery { precondition: None, media_type: Some(MediaType::Print(_)), and: None, condition: None }
331 );
332 assert_parse!(CssAtomSet::ATOMS, MediaQueryList, "print, tv");
333 assert_parse!(CssAtomSet::ATOMS, MediaQueryList, "(--modern)");
334 assert_parse!(CssAtomSet::ATOMS, MediaQueryList, "screen and (--modern)");
335 assert_parse!(
336 CssAtomSet::ATOMS,
337 MediaQuery,
338 "not embossed",
339 MediaQuery {
340 precondition: Some(MediaPreCondition::Not(_)),
341 media_type: Some(MediaType::Custom(_)),
342 and: None,
343 condition: None
344 }
345 );
346 assert_parse!(
347 CssAtomSet::ATOMS,
348 MediaQuery,
349 "only screen",
350 MediaQuery {
351 precondition: Some(MediaPreCondition::Only(_)),
352 media_type: Some(MediaType::Screen(_)),
353 and: None,
354 condition: None
355 }
356 );
357 assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(grid)", MediaFeature::Grid(_));
358 assert_parse!(
359 CssAtomSet::ATOMS,
360 MediaQuery,
361 "screen and (grid)",
362 MediaQuery {
363 precondition: None,
364 media_type: Some(MediaType::Screen(_)),
365 and: Some(_),
366 condition: Some(MediaCondition::Is(MediaFeature::Grid(_))),
367 }
368 );
369 assert_parse!(
370 CssAtomSet::ATOMS,
371 MediaQuery,
372 "screen and (hover)and (pointer)",
373 MediaQuery {
374 precondition: None,
375 media_type: Some(MediaType::Screen(_)),
376 and: Some(_),
377 condition: Some(MediaCondition::And(_))
378 }
379 );
380 assert_parse!(
381 CssAtomSet::ATOMS,
382 MediaQuery,
383 "screen and (orientation:landscape)",
384 MediaQuery {
385 precondition: None,
386 media_type: Some(MediaType::Screen(_)),
387 and: Some(_),
388 condition: Some(MediaCondition::Is(MediaFeature::Orientation(_))),
389 }
390 );
391 assert_parse!(CssAtomSet::ATOMS, MediaQuery, "(hover)and (pointer)");
392 assert_parse!(CssAtomSet::ATOMS, MediaQuery, "(hover)or (pointer)");
393 assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(aspect-ratio:16/9)", MediaFeature::AspectRatio(_));
394 assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(1/1<aspect-ratio<16/9)", MediaFeature::AspectRatio(_));
395 assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(1/1<=aspect-ratio<=16/9)", MediaFeature::AspectRatio(_));
396 assert_parse!(
397 CssAtomSet::ATOMS,
398 MediaFeature,
399 "(min-device-aspect-ratio:16/9)",
400 MediaFeature::DeviceAspectRatio(_)
401 );
402 assert_parse!(
403 CssAtomSet::ATOMS,
404 MediaFeature,
405 "(min-resolution:2dppx)",
406 MediaFeature::Resolution(ResolutionMediaFeature::Min(..))
407 );
408 assert_parse!(
409 CssAtomSet::ATOMS,
410 MediaFeature,
411 "(2dppx<resolution)",
412 MediaFeature::Resolution(ResolutionMediaFeature::Right(..))
413 );
414 assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(update:fast)", MediaFeature::Update(_));
415 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(aspect-ratio:16/9){a{color:red}}");
416 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(update:none)and (resolution>=2x){}");
417 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media print{}");
420 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:1200px){}");
422 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(max-width:calc(48rem - 0.02px)){}");
423 assert_parse!(
424 CssAtomSet::ATOMS,
425 MediaFeature,
426 "(calc(1px + 2px)<width<=max(40em,50vw))",
427 MediaFeature::Width(_)
428 );
429 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:1200px){body{color:red;}}");
430 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:1200px){@page{}}");
431 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media screen{body{color:black}}");
432 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(max-width:575.98px)and (prefers-reduced-motion:reduce){}");
433 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(grid){a{padding:4px}}");
435 assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:0){background:white}");
436 }
445
446 #[test]
447 fn fuzz_regression_ranged_feature_lookahead_overflow() {
448 assert_parse_error!(CssAtomSet::ATOMS, MediaFeature, &format!("({}", "a(".repeat(300)));
449 }
450
451 #[test]
461 #[cfg(feature = "visitable")]
462 fn test_media_rule_visits() {
463 use crate::assert_visits;
464 assert_visits!(
465 "@media (min-width: 768px) { body { color: red; } }",
466 MediaRule,
467 MediaCondition,
468 WidthMediaFeature,
469 Length,
470 StyleRule,
471 SelectorList,
472 CompoundSelector,
473 Tag,
474 HtmlTag,
475 StyleValue,
476 ColorStyleValue,
477 Color
478 );
479 assert_visits!(
480 "@media screen and (min-width: 768px) { body { color: red; } }",
481 MediaRule,
482 MediaType,
483 MediaCondition,
484 WidthMediaFeature,
485 Length,
486 StyleRule,
487 SelectorList,
488 CompoundSelector,
489 Tag,
490 HtmlTag,
491 StyleValue,
492 ColorStyleValue,
493 Color
494 );
495 assert_visits!(
496 "@media only screen { body { color: red; } }",
497 MediaRule,
498 MediaPreCondition,
499 MediaType,
500 StyleRule,
501 SelectorList,
502 CompoundSelector,
503 Tag,
504 HtmlTag,
505 StyleValue,
506 ColorStyleValue,
507 Color
508 );
509 }
510}