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