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), 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		/// <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			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				// Only peek at the token as the underlying media feature parser needs to parse the leading ident.
216				{
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			// Styles like (1em < width < 1em), (1em <= width <= 1em) or (1/1 <= aspect-ratio <= 2/1)
235			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			// https://drafts.csswg.org/mediaqueries/#media-descriptor-table
249
250			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			// https://searchfox.org/wubkat/source/Source/WebCore/css/query/MediaQueryFeatures.cpp#192
289			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			// https://searchfox.org/mozilla-central/source/servo/components/style/gecko/media_features.rs#744
297			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			// https://github.com/search?q=%2F%5C(-ms-%5B%5E)%3A%5D%2B%5B)%3A%5D%2F%20language%3ACSS&type=code
305			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			// https://github.com/search?q=%2F%5C(-o-%5B%5E)%3A%5D%2B%5B)%3A%5D%2F%20language%3ACSS&type=code
312			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, MediaQuery, "not ((width: 2px) or (width: 3px))");
418		// assert_parse!(CssAtomSet::ATOMS, MediaQuery, "not ((hover) or (pointer))");
419		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media print{}");
420		// assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media print,(prefers-reduced-motion: reduce){}");
421		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 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) {}");
434		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(grid){a{padding:4px}}");
435		assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media(min-width:0){background:white}");
436		// assert_parse!(
437		// 	MediaRule,
438		// 	"@media(grid){a{color-scheme:light}}",
439		// 	"@media (grid: 0) {\n\ta {\n\t\tcolor-scheme: light;\n\t}\n}"
440		// );
441
442		// IE media hack
443		// assert_parse!(CssAtomSet::ATOMS, MediaRule, "@media (min-width: 0\\0) {\n\n}");
444	}
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]
452	// fn test_errors() {
453	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "(hover) and or (pointer)");
454	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "(pointer) or and (pointer)");
455	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "(pointer) not and (pointer)");
456	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "only and (pointer)");
457	// 	assert_parse_error!(CssAtomSet::ATOMS, MediaQuery, "not and (pointer)");
458	// }
459
460	#[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}