Skip to main content

css_ast/rules/media/
mod.rs

1use super::prelude::*;
2use css_parse::Box;
3
4mod features;
5pub use features::*;
6
7/// <https://drafts.csswg.org/mediaqueries-4/>
8#[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))]
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		/// <https://drafts.csswg.org/mediaqueries-5/#media-descriptor-table>
185		#[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			Hack(HackMediaFeature),
194		}
195	}
196}
197
198apply_medias!(media_feature);
199
200impl<'a> Peek<'a> for MediaFeature<'a> {
201	const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::LeftParen]);
202}
203
204impl<'a> Parse<'a> for MediaFeature<'a> {
205	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
206	where
207		I: Iterator<Item = Cursor> + Clone,
208	{
209		let checkpoint = p.checkpoint();
210		let mut c = p.peek_n(2);
211		macro_rules! match_media {
212			( $($name: ident($typ: ty): $pat: pat,)+) => {
213				// Only peek at the token as the underlying media feature parser needs to parse the leading ident.
214				{
215					match p.to_atom::<CssAtomSet>(c) {
216						$($pat => <$typ>::parse(p).map(Self::$name),)+
217						_ => Err(Diagnostic::new(c, Diagnostic::expected_ident))?
218					}
219				}
220			}
221		}
222		if c == Kind::Ident {
223			let value = apply_medias!(match_media).or_else(|err| {
224				p.rewind(checkpoint);
225				if let Ok(hack) = p.parse::<HackMediaFeature>() { Ok(Self::Hack(hack)) } else { Err(err) }
226			})?;
227			Ok(value)
228		} else {
229			// Styles like (1em < width < 1em), (1em <= width <= 1em) or (1/1 <= aspect-ratio <= 2/1)
230			let mut n = 3;
231			while c != Kind::Ident && c != Kind::RightParen && n <= 7 {
232				c = p.peek_n(n);
233				n += 1;
234			}
235			if c != Kind::Ident {
236				c = p.next();
237				Err(Diagnostic::new(c, Diagnostic::unexpected))?
238			}
239			apply_medias!(match_media)
240		}
241	}
242}
243
244macro_rules! apply_medias {
245	($macro: ident) => {
246		$macro! {
247			// https://drafts.csswg.org/mediaqueries/#media-descriptor-table
248
249			AnyHover(AnyHoverMediaFeature): CssAtomSet::AnyHover,
250			AnyPointer(AnyPointerMediaFeature): CssAtomSet::AnyPointer,
251			AspectRatio(Box<'a, AspectRatioMediaFeature<'a>>): CssAtomSet::AspectRatio | CssAtomSet::MinAspectRatio | CssAtomSet::MaxAspectRatio,
252			Color(ColorMediaFeature): CssAtomSet::Color | CssAtomSet::MaxColor | CssAtomSet::MinColor,
253			ColorGamut(ColorGamutMediaFeature): CssAtomSet::ColorGamut,
254			ColorIndex(ColorIndexMediaFeature): CssAtomSet::ColorIndex | CssAtomSet::MaxColorIndex | CssAtomSet::MinColorIndex,
255			DeviceAspectRatio(Box<'a, DeviceAspectRatioMediaFeature<'a>>): CssAtomSet::DeviceAspectRatio | CssAtomSet::MaxDeviceAspectRatio | CssAtomSet::MinDeviceAspectRatio,
256			DeviceHeight(DeviceHeightMediaFeature): CssAtomSet::DeviceHeight | CssAtomSet::MaxDeviceHeight | CssAtomSet::MinDeviceHeight,
257			DeviceWidth(DeviceWidthMediaFeature): CssAtomSet::DeviceWidth | CssAtomSet::MaxDeviceWidth | CssAtomSet::MinDeviceWidth,
258			DisplayMode(DisplayModeMediaFeature): CssAtomSet::DisplayMode,
259			DynamicRange(DynamicRangeMediaFeature): CssAtomSet::DynamicRange,
260			EnvironmentBlending(EnvironmentBlendingMediaFeature): CssAtomSet::EnvironmentBlending,
261			ForcedColors(ForcedColorsMediaFeature): CssAtomSet::ForcedColors,
262			Grid(GridMediaFeature): CssAtomSet::Grid,
263			Height(HeightMediaFeature): CssAtomSet::Height | CssAtomSet::MaxHeight | CssAtomSet::MinHeight,
264			HorizontalViewportSegments(HorizontalViewportSegmentsMediaFeature): CssAtomSet::HorizontalViewportSegments | CssAtomSet::MaxHorizontalViewportSegments | CssAtomSet::MinHorizontalViewportSegments,
265			Hover(HoverMediaFeature): CssAtomSet::Hover,
266			InvertedColors(InvertedColorsMediaFeature): CssAtomSet::InvertedColors,
267			Monochrome(MonochromeMediaFeature): CssAtomSet::Monochrome | CssAtomSet::MaxMonochrome | CssAtomSet::MinMonochrome,
268			NavControls(NavControlsMediaFeature): CssAtomSet::NavControls,
269			Orientation(OrientationMediaFeature): CssAtomSet::Orientation,
270			OverflowBlock(OverflowBlockMediaFeature): CssAtomSet::OverflowBlock,
271			OverflowInline(OverflowInlineMediaFeature): CssAtomSet::OverflowInline,
272			Pointer(PointerMediaFeature): CssAtomSet::Pointer,
273			PrefersColorScheme(PrefersColorSchemeMediaFeature): CssAtomSet::PrefersColorScheme,
274			PrefersContrast(PrefersContrastMediaFeature): CssAtomSet::PrefersContrast,
275			PrefersReducedData(PrefersReducedDataMediaFeature): CssAtomSet::PrefersReducedData,
276			PrefersReducedMotion(PrefersReducedMotionMediaFeature): CssAtomSet::PrefersReducedMotion,
277			PrefersReducedTransparency(PrefersReducedTransparencyMediaFeature): CssAtomSet::PrefersReducedTransparency,
278			Resolution(ResolutionMediaFeature): CssAtomSet::Resolution | CssAtomSet::MaxResolution | CssAtomSet::MinResolution,
279			Scan(ScanMediaFeature): CssAtomSet::Scan,
280			Scripting(ScriptingMediaFeature): CssAtomSet::Scripting,
281			Update(UpdateMediaFeature): CssAtomSet::Update,
282			VerticalViewportSegments(VerticalViewportSegmentsMediaFeature): CssAtomSet::VerticalViewportSegments | CssAtomSet::MaxVerticalViewportSegments | CssAtomSet::MinVerticalViewportSegments,
283			VideoColorGamut(VideoColorGamutMediaFeature): CssAtomSet::VideoColorGamut,
284			VideoDynamicRange(VideoDynamicRangeMediaFeature): CssAtomSet::VideoDynamicRange,
285			Width(WidthMediaFeature): CssAtomSet::Width | CssAtomSet::MaxWidth | CssAtomSet::MinWidth,
286
287			// https://searchfox.org/wubkat/source/Source/WebCore/css/query/MediaQueryFeatures.cpp#192
288			WebkitAnimationMediaFeature(WebkitAnimationMediaFeature): CssAtomSet::_WebkitAnimation,
289			WebkitDevicePixelRatioMediaFeature(WebkitDevicePixelRatioMediaFeature): CssAtomSet::_WebkitDevicePixelRatio,
290			WebkitTransform2dMediaFeature(WebkitTransform2dMediaFeature): CssAtomSet::_WebkitTransform2d,
291			WebkitTransform3dMediaFeature(WebkitTransform3dMediaFeature): CssAtomSet::_WebkitTransform3d,
292			WebkitTransitionMediaFeature(WebkitTransitionMediaFeature): CssAtomSet::_WebkitTransition,
293			WebkitVideoPlayableInlineMediaFeature(WebkitVideoPlayableInlineMediaFeature): CssAtomSet::_WebkitVideoPlayableInline,
294
295			// https://searchfox.org/mozilla-central/source/servo/components/style/gecko/media_features.rs#744
296			MozDeviceOrientationMediaFeature(MozDeviceOrientationMediaFeature): CssAtomSet::_MozDeviceOrientation,
297			MozDevicePixelRatioMediaFeature(MozDevicePixelRatioMediaFeature): CssAtomSet::_MozDevicePixelRatio | CssAtomSet::_MozMaxDevicePixelRatio | CssAtomSet::_MozMinDevicePixelRatio,
298			MozMacGraphiteThemeMediaFeature(MozMacGraphiteThemeMediaFeature): CssAtomSet::_MozMacGraphiteTheme,
299			MozMaemoClassicMediaFeature(MozMaemoClassicMediaFeature): CssAtomSet::_MozMaemoClassicTheme,
300			MozImagesInMenusMediaFeature(MozImagesInMenusMediaFeature): CssAtomSet::_MozImagesInMenus,
301			MozOsVersionMenusMediaFeature(MozOsVersionMediaFeature): CssAtomSet::_MozOsVersion,
302
303			// https://github.com/search?q=%2F%5C(-ms-%5B%5E)%3A%5D%2B%5B)%3A%5D%2F%20language%3ACSS&type=code
304			MsHighContrastMediaFeature(MsHighContrastMediaFeature): CssAtomSet::_MsHighContrast,
305			MsViewStateMediaFeature(MsViewStateMediaFeature): CssAtomSet::_MsViewState,
306			MsImeAlignMediaFeature(MsImeAlignMediaFeature): CssAtomSet::_MsImeAlign,
307			MsDevicePixelRatioMediaFeature(MsDevicePixelRatioMediaFeature): CssAtomSet::_MsDevicePixelRatio,
308			MsColumnCountMediaFeature(MsColumnCountMediaFeature): CssAtomSet::_MsColumnCount,
309
310			// https://github.com/search?q=%2F%5C(-o-%5B%5E)%3A%5D%2B%5B)%3A%5D%2F%20language%3ACSS&type=code
311			ODevicePixelRatioMediaFeature(ODevicePixelRatioMediaFeature): CssAtomSet::_ODevicePixelRatio,
312		}
313	};
314}
315use apply_medias;
316
317#[cfg(test)]
318mod tests {
319	use super::*;
320	use crate::CssAtomSet;
321	use css_parse::assert_parse;
322
323	#[test]
324	fn test_writes() {
325		assert_parse!(
326			CssAtomSet::ATOMS,
327			MediaQuery,
328			"print",
329			MediaQuery { precondition: None, media_type: Some(MediaType::Print(_)), and: None, condition: None }
330		);
331		assert_parse!(CssAtomSet::ATOMS, MediaQueryList, "print, tv");
332		assert_parse!(
333			CssAtomSet::ATOMS,
334			MediaQuery,
335			"not embossed",
336			MediaQuery {
337				precondition: Some(MediaPreCondition::Not(_)),
338				media_type: Some(MediaType::Custom(_)),
339				and: None,
340				condition: None
341			}
342		);
343		assert_parse!(
344			CssAtomSet::ATOMS,
345			MediaQuery,
346			"only screen",
347			MediaQuery {
348				precondition: Some(MediaPreCondition::Only(_)),
349				media_type: Some(MediaType::Screen(_)),
350				and: None,
351				condition: None
352			}
353		);
354		assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(grid)", MediaFeature::Grid(_));
355		assert_parse!(
356			CssAtomSet::ATOMS,
357			MediaQuery,
358			"screen and (grid)",
359			MediaQuery {
360				precondition: None,
361				media_type: Some(MediaType::Screen(_)),
362				and: Some(_),
363				condition: Some(MediaCondition::Is(MediaFeature::Grid(_))),
364			}
365		);
366		assert_parse!(
367			CssAtomSet::ATOMS,
368			MediaQuery,
369			"screen and (hover)and (pointer)",
370			MediaQuery {
371				precondition: None,
372				media_type: Some(MediaType::Screen(_)),
373				and: Some(_),
374				condition: Some(MediaCondition::And(_))
375			}
376		);
377		assert_parse!(
378			CssAtomSet::ATOMS,
379			MediaQuery,
380			"screen and (orientation:landscape)",
381			MediaQuery {
382				precondition: None,
383				media_type: Some(MediaType::Screen(_)),
384				and: Some(_),
385				condition: Some(MediaCondition::Is(MediaFeature::Orientation(_))),
386			}
387		);
388		assert_parse!(CssAtomSet::ATOMS, MediaQuery, "(hover)and (pointer)");
389		assert_parse!(CssAtomSet::ATOMS, MediaQuery, "(hover)or (pointer)");
390		assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(aspect-ratio:16/9)", MediaFeature::AspectRatio(_));
391		assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(1/1<aspect-ratio<16/9)", MediaFeature::AspectRatio(_));
392		assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(1/1<=aspect-ratio<=16/9)", MediaFeature::AspectRatio(_));
393		assert_parse!(
394			CssAtomSet::ATOMS,
395			MediaFeature,
396			"(min-device-aspect-ratio:16/9)",
397			MediaFeature::DeviceAspectRatio(_)
398		);
399		assert_parse!(
400			CssAtomSet::ATOMS,
401			MediaFeature,
402			"(min-resolution:2dppx)",
403			MediaFeature::Resolution(ResolutionMediaFeature::Min(..))
404		);
405		assert_parse!(
406			CssAtomSet::ATOMS,
407			MediaFeature,
408			"(2dppx<resolution)",
409			MediaFeature::Resolution(ResolutionMediaFeature::Right(..))
410		);
411		assert_parse!(CssAtomSet::ATOMS, MediaFeature, "(update:fast)", MediaFeature::Update(_));
412		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(aspect-ratio:16/9){a{color:red}}");
413		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(update:none)and (resolution>=2x){}");
414		// assert_parse!(CssAtomSet::ATOMS, MediaQuery, "not ((width: 2px) or (width: 3px))");
415		// assert_parse!(CssAtomSet::ATOMS, MediaQuery, "not ((hover) or (pointer))");
416		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media print{}");
417		// assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media print,(prefers-reduced-motion: reduce){}");
418		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:1200px){}");
419		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:1200px){body{color:red;}}");
420		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:1200px){@page{}}");
421		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media screen{body{color:black}}");
422		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(max-width:575.98px)and (prefers-reduced-motion:reduce){}");
423		// assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media only screen and(max-device-width:800px),only screen and (device-width:1024px) and (device-height: 600px),only screen and (width:1280px) and (orientation:landscape), only screen and (device-width:800px), only screen and (max-width:767px) {}");
424		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(grid){a{padding:4px}}");
425		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:0){background:white}");
426		// assert_parse!(
427		// 	MediaRule,
428		// 	"@media(grid){a{color-scheme:light}}",
429		// 	"@media (grid: 0) {\n\ta {\n\t\tcolor-scheme: light;\n\t}\n}"
430		// );
431
432		// IE media hack
433		// assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media (min-width: 0\\0) {\n\n}");
434	}
435
436	// #[test]
437	// fn test_errors() {
438	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "(hover) and or (pointer)");
439	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "(pointer) or and (pointer)");
440	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "(pointer) not and (pointer)");
441	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "only and (pointer)");
442	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "not and (pointer)");
443	// }
444
445	#[test]
446	#[cfg(feature = "visitable")]
447	fn test_media_rule_visits() {
448		use crate::assert_visits;
449		assert_visits!(
450			"@media (min-width: 768px) { body { color: red; } }",
451			MediaRule,
452			WidthMediaFeature,
453			Length,
454			StyleRule,
455			SelectorList,
456			CompoundSelector,
457			Tag,
458			HtmlTag,
459			StyleValue,
460			ColorStyleValue,
461			Color
462		);
463		assert_visits!(
464			"@media screen and (min-width: 768px) { body { color: red; } }",
465			MediaRule,
466			MediaType,
467			WidthMediaFeature,
468			Length,
469			StyleRule,
470			SelectorList,
471			CompoundSelector,
472			Tag,
473			HtmlTag,
474			StyleValue,
475			ColorStyleValue,
476			Color
477		);
478		assert_visits!(
479			"@media only screen { body { color: red; } }",
480			MediaRule,
481			MediaPreCondition,
482			MediaType,
483			StyleRule,
484			SelectorList,
485			CompoundSelector,
486			Tag,
487			HtmlTag,
488			StyleValue,
489			ColorStyleValue,
490			Color
491		);
492	}
493}