1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 //! Gecko's media feature list and evaluator.
6 
7 use crate::gecko_bindings::bindings;
8 use crate::gecko_bindings::structs;
9 use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
10 use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
11 use crate::media_queries::media_feature_expression::RangeOrOperator;
12 use crate::media_queries::{Device, MediaType};
13 use crate::values::computed::CSSPixelLength;
14 use crate::values::computed::Ratio;
15 use crate::values::computed::Resolution;
16 use crate::Atom;
17 use app_units::Au;
18 use euclid::default::Size2D;
19 
device_size(device: &Device) -> Size2D<Au>20 fn device_size(device: &Device) -> Size2D<Au> {
21     let mut width = 0;
22     let mut height = 0;
23     unsafe {
24         bindings::Gecko_MediaFeatures_GetDeviceSize(device.document(), &mut width, &mut height);
25     }
26     Size2D::new(Au(width), Au(height))
27 }
28 
29 /// https://drafts.csswg.org/mediaqueries-4/#width
eval_width( device: &Device, value: Option<CSSPixelLength>, range_or_operator: Option<RangeOrOperator>, ) -> bool30 fn eval_width(
31     device: &Device,
32     value: Option<CSSPixelLength>,
33     range_or_operator: Option<RangeOrOperator>,
34 ) -> bool {
35     RangeOrOperator::evaluate(
36         range_or_operator,
37         value.map(Au::from),
38         device.au_viewport_size().width,
39     )
40 }
41 
42 /// https://drafts.csswg.org/mediaqueries-4/#device-width
eval_device_width( device: &Device, value: Option<CSSPixelLength>, range_or_operator: Option<RangeOrOperator>, ) -> bool43 fn eval_device_width(
44     device: &Device,
45     value: Option<CSSPixelLength>,
46     range_or_operator: Option<RangeOrOperator>,
47 ) -> bool {
48     RangeOrOperator::evaluate(
49         range_or_operator,
50         value.map(Au::from),
51         device_size(device).width,
52     )
53 }
54 
55 /// https://drafts.csswg.org/mediaqueries-4/#height
eval_height( device: &Device, value: Option<CSSPixelLength>, range_or_operator: Option<RangeOrOperator>, ) -> bool56 fn eval_height(
57     device: &Device,
58     value: Option<CSSPixelLength>,
59     range_or_operator: Option<RangeOrOperator>,
60 ) -> bool {
61     RangeOrOperator::evaluate(
62         range_or_operator,
63         value.map(Au::from),
64         device.au_viewport_size().height,
65     )
66 }
67 
68 /// https://drafts.csswg.org/mediaqueries-4/#device-height
eval_device_height( device: &Device, value: Option<CSSPixelLength>, range_or_operator: Option<RangeOrOperator>, ) -> bool69 fn eval_device_height(
70     device: &Device,
71     value: Option<CSSPixelLength>,
72     range_or_operator: Option<RangeOrOperator>,
73 ) -> bool {
74     RangeOrOperator::evaluate(
75         range_or_operator,
76         value.map(Au::from),
77         device_size(device).height,
78     )
79 }
80 
eval_aspect_ratio_for<F>( device: &Device, query_value: Option<Ratio>, range_or_operator: Option<RangeOrOperator>, get_size: F, ) -> bool where F: FnOnce(&Device) -> Size2D<Au>,81 fn eval_aspect_ratio_for<F>(
82     device: &Device,
83     query_value: Option<Ratio>,
84     range_or_operator: Option<RangeOrOperator>,
85     get_size: F,
86 ) -> bool
87 where
88     F: FnOnce(&Device) -> Size2D<Au>,
89 {
90     // A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
91     // to convert it if necessary.
92     // FIXME: we may need to update here once
93     // https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
94     let query_value = match query_value {
95         Some(v) => v.used_value(),
96         None => return true,
97     };
98 
99     let size = get_size(device);
100     let value = Ratio::new(size.width.0 as f32, size.height.0 as f32);
101     RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value)
102 }
103 
104 /// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
eval_aspect_ratio( device: &Device, query_value: Option<Ratio>, range_or_operator: Option<RangeOrOperator>, ) -> bool105 fn eval_aspect_ratio(
106     device: &Device,
107     query_value: Option<Ratio>,
108     range_or_operator: Option<RangeOrOperator>,
109 ) -> bool {
110     eval_aspect_ratio_for(
111         device,
112         query_value,
113         range_or_operator,
114         Device::au_viewport_size,
115     )
116 }
117 
118 /// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio
eval_device_aspect_ratio( device: &Device, query_value: Option<Ratio>, range_or_operator: Option<RangeOrOperator>, ) -> bool119 fn eval_device_aspect_ratio(
120     device: &Device,
121     query_value: Option<Ratio>,
122     range_or_operator: Option<RangeOrOperator>,
123 ) -> bool {
124     eval_aspect_ratio_for(device, query_value, range_or_operator, device_size)
125 }
126 
127 /// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
eval_device_pixel_ratio( device: &Device, query_value: Option<f32>, range_or_operator: Option<RangeOrOperator>, ) -> bool128 fn eval_device_pixel_ratio(
129     device: &Device,
130     query_value: Option<f32>,
131     range_or_operator: Option<RangeOrOperator>,
132 ) -> bool {
133     eval_resolution(
134         device,
135         query_value.map(Resolution::from_dppx),
136         range_or_operator,
137     )
138 }
139 
140 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
141 #[repr(u8)]
142 enum Orientation {
143     Landscape,
144     Portrait,
145 }
146 
eval_orientation_for<F>(device: &Device, value: Option<Orientation>, get_size: F) -> bool where F: FnOnce(&Device) -> Size2D<Au>,147 fn eval_orientation_for<F>(device: &Device, value: Option<Orientation>, get_size: F) -> bool
148 where
149     F: FnOnce(&Device) -> Size2D<Au>,
150 {
151     let query_orientation = match value {
152         Some(v) => v,
153         None => return true,
154     };
155 
156     let size = get_size(device);
157 
158     // Per spec, square viewports should be 'portrait'
159     let is_landscape = size.width > size.height;
160     match query_orientation {
161         Orientation::Landscape => is_landscape,
162         Orientation::Portrait => !is_landscape,
163     }
164 }
165 
166 /// https://drafts.csswg.org/mediaqueries-4/#orientation
eval_orientation(device: &Device, value: Option<Orientation>) -> bool167 fn eval_orientation(device: &Device, value: Option<Orientation>) -> bool {
168     eval_orientation_for(device, value, Device::au_viewport_size)
169 }
170 
171 /// FIXME: There's no spec for `-moz-device-orientation`.
eval_device_orientation(device: &Device, value: Option<Orientation>) -> bool172 fn eval_device_orientation(device: &Device, value: Option<Orientation>) -> bool {
173     eval_orientation_for(device, value, device_size)
174 }
175 
176 /// Values for the display-mode media feature.
177 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
178 #[repr(u8)]
179 #[allow(missing_docs)]
180 pub enum DisplayMode {
181     Browser = 0,
182     MinimalUi,
183     Standalone,
184     Fullscreen,
185 }
186 
187 /// https://w3c.github.io/manifest/#the-display-mode-media-feature
eval_display_mode(device: &Device, query_value: Option<DisplayMode>) -> bool188 fn eval_display_mode(device: &Device, query_value: Option<DisplayMode>) -> bool {
189     match query_value {
190         Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) },
191         None => true,
192     }
193 }
194 
195 /// https://drafts.csswg.org/mediaqueries-4/#grid
eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool196 fn eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
197     // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature
198     // is always 0.
199     let supports_grid = false;
200     query_value.map_or(supports_grid, |v| v == supports_grid)
201 }
202 
203 /// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d
eval_transform_3d(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool204 fn eval_transform_3d(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
205     let supports_transforms = true;
206     query_value.map_or(supports_transforms, |v| v == supports_transforms)
207 }
208 
209 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
210 #[repr(u8)]
211 enum Scan {
212     Progressive,
213     Interlace,
214 }
215 
216 /// https://drafts.csswg.org/mediaqueries-4/#scan
eval_scan(_: &Device, _: Option<Scan>) -> bool217 fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
218     // Since Gecko doesn't support the 'tv' media type, the 'scan' feature never
219     // matches.
220     false
221 }
222 
223 /// https://drafts.csswg.org/mediaqueries-4/#color
eval_color( device: &Device, query_value: Option<u32>, range_or_operator: Option<RangeOrOperator>, ) -> bool224 fn eval_color(
225     device: &Device,
226     query_value: Option<u32>,
227     range_or_operator: Option<RangeOrOperator>,
228 ) -> bool {
229     let color_bits_per_channel =
230         unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) };
231     RangeOrOperator::evaluate(range_or_operator, query_value, color_bits_per_channel)
232 }
233 
234 /// https://drafts.csswg.org/mediaqueries-4/#color-index
eval_color_index( _: &Device, query_value: Option<u32>, range_or_operator: Option<RangeOrOperator>, ) -> bool235 fn eval_color_index(
236     _: &Device,
237     query_value: Option<u32>,
238     range_or_operator: Option<RangeOrOperator>,
239 ) -> bool {
240     // We should return zero if the device does not use a color lookup table.
241     let index = 0;
242     RangeOrOperator::evaluate(range_or_operator, query_value, index)
243 }
244 
245 /// https://drafts.csswg.org/mediaqueries-4/#monochrome
eval_monochrome( device: &Device, query_value: Option<u32>, range_or_operator: Option<RangeOrOperator>, ) -> bool246 fn eval_monochrome(
247     device: &Device,
248     query_value: Option<u32>,
249     range_or_operator: Option<RangeOrOperator>,
250 ) -> bool {
251     // For color devices we should return 0.
252     let depth =
253         unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(device.document()) };
254     RangeOrOperator::evaluate(range_or_operator, query_value, depth)
255 }
256 
257 /// https://drafts.csswg.org/mediaqueries-4/#resolution
eval_resolution( device: &Device, query_value: Option<Resolution>, range_or_operator: Option<RangeOrOperator>, ) -> bool258 fn eval_resolution(
259     device: &Device,
260     query_value: Option<Resolution>,
261     range_or_operator: Option<RangeOrOperator>,
262 ) -> bool {
263     let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) };
264     RangeOrOperator::evaluate(
265         range_or_operator,
266         query_value.map(|r| r.dppx()),
267         resolution_dppx,
268     )
269 }
270 
271 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
272 #[repr(u8)]
273 enum PrefersReducedMotion {
274     NoPreference,
275     Reduce,
276 }
277 
278 /// Values for the prefers-color-scheme media feature.
279 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
280 #[repr(u8)]
281 #[allow(missing_docs)]
282 pub enum PrefersColorScheme {
283     Light,
284     Dark,
285 }
286 
287 /// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool288 fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool {
289     let prefers_reduced =
290         unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) };
291     let query_value = match query_value {
292         Some(v) => v,
293         None => return prefers_reduced,
294     };
295 
296     match query_value {
297         PrefersReducedMotion::NoPreference => !prefers_reduced,
298         PrefersReducedMotion::Reduce => prefers_reduced,
299     }
300 }
301 
302 /// Possible values for prefers-contrast media query.
303 /// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
304 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
305 #[repr(u8)]
306 pub enum PrefersContrast {
307     /// More contrast is preferred. Corresponds to an accessibility theme
308     /// being enabled or Firefox forcing high contrast colors.
309     More,
310     /// Low contrast is preferred.
311     Less,
312     /// The default value if neither high or low contrast is enabled.
313     NoPreference,
314 }
315 
316 /// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
eval_prefers_contrast(device: &Device, query_value: Option<PrefersContrast>) -> bool317 fn eval_prefers_contrast(device: &Device, query_value: Option<PrefersContrast>) -> bool {
318     let prefers_contrast =
319         unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document()) };
320     match query_value {
321         Some(v) => v == prefers_contrast,
322         None => prefers_contrast != PrefersContrast::NoPreference,
323     }
324 }
325 
326 /// Possible values for the forced-colors media query.
327 /// https://drafts.csswg.org/mediaqueries-5/#forced-colors
328 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
329 #[repr(u8)]
330 pub enum ForcedColors {
331     /// Page colors are not being forced.
332     None,
333     /// Page colors are being forced.
334     Active,
335 }
336 
337 /// https://drafts.csswg.org/mediaqueries-5/#forced-colors
eval_forced_colors(device: &Device, query_value: Option<ForcedColors>) -> bool338 fn eval_forced_colors(device: &Device, query_value: Option<ForcedColors>) -> bool {
339     let forced = !device.use_document_colors();
340     match query_value {
341         Some(query_value) => forced == (query_value == ForcedColors::Active),
342         None => forced,
343     }
344 }
345 
346 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
347 #[repr(u8)]
348 enum OverflowBlock {
349     None,
350     Scroll,
351     OptionalPaged,
352     Paged,
353 }
354 
355 /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block
eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> bool356 fn eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> bool {
357     // For the time being, assume that printing (including previews)
358     // is the only time when we paginate, and we are otherwise always
359     // scrolling. This is true at the moment in Firefox, but may need
360     // updating in the future (e.g., ebook readers built with Stylo, a
361     // billboard mode that doesn't support overflow at all).
362     //
363     // If this ever changes, don't forget to change eval_overflow_inline too.
364     let scrolling = device.media_type() != MediaType::print();
365     let query_value = match query_value {
366         Some(v) => v,
367         None => return true,
368     };
369 
370     match query_value {
371         OverflowBlock::None | OverflowBlock::OptionalPaged => false,
372         OverflowBlock::Scroll => scrolling,
373         OverflowBlock::Paged => !scrolling,
374     }
375 }
376 
377 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
378 #[repr(u8)]
379 enum OverflowInline {
380     None,
381     Scroll,
382 }
383 
384 /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline
eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) -> bool385 fn eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) -> bool {
386     // See the note in eval_overflow_block.
387     let scrolling = device.media_type() != MediaType::print();
388     let query_value = match query_value {
389         Some(v) => v,
390         None => return scrolling,
391     };
392 
393     match query_value {
394         OverflowInline::None => !scrolling,
395         OverflowInline::Scroll => scrolling,
396     }
397 }
398 
399 /// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool400 fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool {
401     let prefers_color_scheme =
402         unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document()) };
403     match query_value {
404         Some(v) => prefers_color_scheme == v,
405         None => true,
406     }
407 }
408 
409 /// Values for the -moz-toolbar-prefers-color-scheme media feature.
410 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
411 #[repr(u8)]
412 enum ToolbarPrefersColorScheme {
413     Dark,
414     Light,
415     System,
416 }
417 
418 /// The color-scheme of the toolbar in the current Firefox theme. This is based
419 /// on a pref managed by the front-end.
eval_toolbar_prefers_color_scheme(d: &Device, query_value: Option<ToolbarPrefersColorScheme>) -> bool420 fn eval_toolbar_prefers_color_scheme(d: &Device, query_value: Option<ToolbarPrefersColorScheme>) -> bool {
421     let toolbar_value = match static_prefs::pref!("browser.theme.toolbar-theme") {
422         0 => ToolbarPrefersColorScheme::Dark,
423         1 => ToolbarPrefersColorScheme::Light,
424         _ => ToolbarPrefersColorScheme::System,
425     };
426 
427     let query_value = match query_value {
428         Some(v) => v,
429         None => return true,
430     };
431 
432     if query_value == toolbar_value {
433         return true;
434     }
435 
436     if toolbar_value != ToolbarPrefersColorScheme::System {
437         return false;
438     }
439 
440     // System might match light and dark as well.
441     match query_value {
442         ToolbarPrefersColorScheme::Dark => eval_prefers_color_scheme(d, Some(PrefersColorScheme::Dark)),
443         ToolbarPrefersColorScheme::Light => eval_prefers_color_scheme(d, Some(PrefersColorScheme::Light)),
444         ToolbarPrefersColorScheme::System => true,
445     }
446 }
447 
448 bitflags! {
449     /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
450     struct PointerCapabilities: u8 {
451         const COARSE = structs::PointerCapabilities_Coarse;
452         const FINE = structs::PointerCapabilities_Fine;
453         const HOVER = structs::PointerCapabilities_Hover;
454     }
455 }
456 
primary_pointer_capabilities(device: &Device) -> PointerCapabilities457 fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities {
458     PointerCapabilities::from_bits_truncate(unsafe {
459         bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document())
460     })
461 }
462 
all_pointer_capabilities(device: &Device) -> PointerCapabilities463 fn all_pointer_capabilities(device: &Device) -> PointerCapabilities {
464     PointerCapabilities::from_bits_truncate(unsafe {
465         bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document())
466     })
467 }
468 
469 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
470 #[repr(u8)]
471 enum Pointer {
472     None,
473     Coarse,
474     Fine,
475 }
476 
eval_pointer_capabilities( query_value: Option<Pointer>, pointer_capabilities: PointerCapabilities, ) -> bool477 fn eval_pointer_capabilities(
478     query_value: Option<Pointer>,
479     pointer_capabilities: PointerCapabilities,
480 ) -> bool {
481     let query_value = match query_value {
482         Some(v) => v,
483         None => return !pointer_capabilities.is_empty(),
484     };
485 
486     match query_value {
487         Pointer::None => pointer_capabilities.is_empty(),
488         Pointer::Coarse => pointer_capabilities.intersects(PointerCapabilities::COARSE),
489         Pointer::Fine => pointer_capabilities.intersects(PointerCapabilities::FINE),
490     }
491 }
492 
493 /// https://drafts.csswg.org/mediaqueries-4/#pointer
eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool494 fn eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
495     eval_pointer_capabilities(query_value, primary_pointer_capabilities(device))
496 }
497 
498 /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer
eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool499 fn eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
500     eval_pointer_capabilities(query_value, all_pointer_capabilities(device))
501 }
502 
503 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
504 #[repr(u8)]
505 enum Hover {
506     None,
507     Hover,
508 }
509 
eval_hover_capabilities( query_value: Option<Hover>, pointer_capabilities: PointerCapabilities, ) -> bool510 fn eval_hover_capabilities(
511     query_value: Option<Hover>,
512     pointer_capabilities: PointerCapabilities,
513 ) -> bool {
514     let can_hover = pointer_capabilities.intersects(PointerCapabilities::HOVER);
515     let query_value = match query_value {
516         Some(v) => v,
517         None => return can_hover,
518     };
519 
520     match query_value {
521         Hover::None => !can_hover,
522         Hover::Hover => can_hover,
523     }
524 }
525 
526 /// https://drafts.csswg.org/mediaqueries-4/#hover
eval_hover(device: &Device, query_value: Option<Hover>) -> bool527 fn eval_hover(device: &Device, query_value: Option<Hover>) -> bool {
528     eval_hover_capabilities(query_value, primary_pointer_capabilities(device))
529 }
530 
531 /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover
eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool532 fn eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool {
533     eval_hover_capabilities(query_value, all_pointer_capabilities(device))
534 }
535 
eval_moz_is_glyph( device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>, ) -> bool536 fn eval_moz_is_glyph(
537     device: &Device,
538     query_value: Option<bool>,
539     _: Option<RangeOrOperator>,
540 ) -> bool {
541     let is_glyph = device.document().mIsSVGGlyphsDocument();
542     query_value.map_or(is_glyph, |v| v == is_glyph)
543 }
544 
eval_moz_print_preview( device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>, ) -> bool545 fn eval_moz_print_preview(
546     device: &Device,
547     query_value: Option<bool>,
548     _: Option<RangeOrOperator>,
549 ) -> bool {
550     let is_print_preview = device.is_print_preview();
551     if is_print_preview {
552         debug_assert_eq!(device.media_type(), MediaType::print());
553     }
554     query_value.map_or(is_print_preview, |v| v == is_print_preview)
555 }
556 
eval_moz_non_native_content_theme( device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>, ) -> bool557 fn eval_moz_non_native_content_theme(
558     device: &Device,
559     query_value: Option<bool>,
560     _: Option<RangeOrOperator>,
561 ) -> bool {
562     let non_native_theme =
563         unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(device.document()) };
564     query_value.map_or(non_native_theme, |v| v == non_native_theme)
565 }
566 
eval_moz_is_resource_document( device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>, ) -> bool567 fn eval_moz_is_resource_document(
568     device: &Device,
569     query_value: Option<bool>,
570     _: Option<RangeOrOperator>,
571 ) -> bool {
572     let is_resource_doc =
573         unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) };
574     query_value.map_or(is_resource_doc, |v| v == is_resource_doc)
575 }
576 
eval_moz_os_version( device: &Device, query_value: Option<Atom>, _: Option<RangeOrOperator>, ) -> bool577 fn eval_moz_os_version(
578     device: &Device,
579     query_value: Option<Atom>,
580     _: Option<RangeOrOperator>,
581 ) -> bool {
582     let query_value = match query_value {
583         Some(v) => v,
584         None => return false,
585     };
586 
587     let os_version =
588         unsafe { bindings::Gecko_MediaFeatures_GetOperatingSystemVersion(device.document()) };
589 
590     query_value.as_ptr() == os_version
591 }
592 
eval_moz_windows_non_native_menus( device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>, ) -> bool593 fn eval_moz_windows_non_native_menus(
594     device: &Device,
595     query_value: Option<bool>,
596     _: Option<RangeOrOperator>,
597 ) -> bool {
598     let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") {
599         0 => false,
600         1 => true,
601         _ => {
602             eval_moz_os_version(device, Some(atom!("windows-win10")), None) &&
603                 get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32)
604         },
605     };
606 
607     query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus)
608 }
609 
get_lnf_int(int_id: i32) -> i32610 fn get_lnf_int(int_id: i32) -> i32 {
611     unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) }
612 }
613 
get_lnf_int_as_bool(int_id: i32) -> bool614 fn get_lnf_int_as_bool(int_id: i32) -> bool {
615     get_lnf_int(int_id) != 0
616 }
617 
get_scrollbar_start_backward(int_id: i32) -> bool618 fn get_scrollbar_start_backward(int_id: i32) -> bool {
619     (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartBackward as i32) != 0
620 }
621 
get_scrollbar_start_forward(int_id: i32) -> bool622 fn get_scrollbar_start_forward(int_id: i32) -> bool {
623     (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartForward as i32) != 0
624 }
625 
get_scrollbar_end_backward(int_id: i32) -> bool626 fn get_scrollbar_end_backward(int_id: i32) -> bool {
627     (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndBackward as i32) != 0
628 }
629 
get_scrollbar_end_forward(int_id: i32) -> bool630 fn get_scrollbar_end_forward(int_id: i32) -> bool {
631     (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndForward as i32) != 0
632 }
633 
634 macro_rules! lnf_int_feature {
635     ($feature_name:expr, $int_id:ident, $get_value:ident) => {{
636         fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
637             let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32);
638             query_value.map_or(value, |v| v == value)
639         }
640 
641         feature!(
642             $feature_name,
643             AllowsRanges::No,
644             Evaluator::BoolInteger(__eval),
645             ParsingRequirements::CHROME_AND_UA_ONLY,
646         )
647     }};
648     ($feature_name:expr, $int_id:ident) => {{
649         lnf_int_feature!($feature_name, $int_id, get_lnf_int_as_bool)
650     }};
651 }
652 
653 /// bool pref-based features are an slightly less convenient to start using
654 /// version of @supports -moz-bool-pref, but with some benefits, mainly that
655 /// they can support dynamic changes, and don't require a pref lookup every time
656 /// they're used.
657 ///
658 /// In order to use them you need to make sure that the pref defined as a static
659 /// pref, with `rust: true`. The feature name needs to be defined in
660 /// `StaticAtoms.py` just like the others. In order to support dynamic changes,
661 /// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp
662 macro_rules! bool_pref_feature {
663     ($feature_name:expr, $pref:tt) => {{
664         fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
665             let value = static_prefs::pref!($pref);
666             query_value.map_or(value, |v| v == value)
667         }
668 
669         feature!(
670             $feature_name,
671             AllowsRanges::No,
672             Evaluator::BoolInteger(__eval),
673             ParsingRequirements::CHROME_AND_UA_ONLY,
674         )
675     }};
676 }
677 
678 /// Adding new media features requires (1) adding the new feature to this
679 /// array, with appropriate entries (and potentially any new code needed
680 /// to support new types in these entries and (2) ensuring that either
681 /// nsPresContext::MediaFeatureValuesChanged is called when the value that
682 /// would be returned by the evaluator function could change.
683 pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [
684     feature!(
685         atom!("width"),
686         AllowsRanges::Yes,
687         Evaluator::Length(eval_width),
688         ParsingRequirements::empty(),
689     ),
690     feature!(
691         atom!("height"),
692         AllowsRanges::Yes,
693         Evaluator::Length(eval_height),
694         ParsingRequirements::empty(),
695     ),
696     feature!(
697         atom!("aspect-ratio"),
698         AllowsRanges::Yes,
699         Evaluator::NumberRatio(eval_aspect_ratio),
700         ParsingRequirements::empty(),
701     ),
702     feature!(
703         atom!("orientation"),
704         AllowsRanges::No,
705         keyword_evaluator!(eval_orientation, Orientation),
706         ParsingRequirements::empty(),
707     ),
708     feature!(
709         atom!("device-width"),
710         AllowsRanges::Yes,
711         Evaluator::Length(eval_device_width),
712         ParsingRequirements::empty(),
713     ),
714     feature!(
715         atom!("device-height"),
716         AllowsRanges::Yes,
717         Evaluator::Length(eval_device_height),
718         ParsingRequirements::empty(),
719     ),
720     feature!(
721         atom!("device-aspect-ratio"),
722         AllowsRanges::Yes,
723         Evaluator::NumberRatio(eval_device_aspect_ratio),
724         ParsingRequirements::empty(),
725     ),
726     feature!(
727         atom!("-moz-device-orientation"),
728         AllowsRanges::No,
729         keyword_evaluator!(eval_device_orientation, Orientation),
730         ParsingRequirements::empty(),
731     ),
732     // Webkit extensions that we support for de-facto web compatibility.
733     // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
734     feature!(
735         atom!("device-pixel-ratio"),
736         AllowsRanges::Yes,
737         Evaluator::Float(eval_device_pixel_ratio),
738         ParsingRequirements::WEBKIT_PREFIX,
739     ),
740     // -webkit-transform-3d.
741     feature!(
742         atom!("transform-3d"),
743         AllowsRanges::No,
744         Evaluator::BoolInteger(eval_transform_3d),
745         ParsingRequirements::WEBKIT_PREFIX,
746     ),
747     feature!(
748         atom!("-moz-device-pixel-ratio"),
749         AllowsRanges::Yes,
750         Evaluator::Float(eval_device_pixel_ratio),
751         ParsingRequirements::empty(),
752     ),
753     feature!(
754         atom!("resolution"),
755         AllowsRanges::Yes,
756         Evaluator::Resolution(eval_resolution),
757         ParsingRequirements::empty(),
758     ),
759     feature!(
760         atom!("display-mode"),
761         AllowsRanges::No,
762         keyword_evaluator!(eval_display_mode, DisplayMode),
763         ParsingRequirements::empty(),
764     ),
765     feature!(
766         atom!("grid"),
767         AllowsRanges::No,
768         Evaluator::BoolInteger(eval_grid),
769         ParsingRequirements::empty(),
770     ),
771     feature!(
772         atom!("scan"),
773         AllowsRanges::No,
774         keyword_evaluator!(eval_scan, Scan),
775         ParsingRequirements::empty(),
776     ),
777     feature!(
778         atom!("color"),
779         AllowsRanges::Yes,
780         Evaluator::Integer(eval_color),
781         ParsingRequirements::empty(),
782     ),
783     feature!(
784         atom!("color-index"),
785         AllowsRanges::Yes,
786         Evaluator::Integer(eval_color_index),
787         ParsingRequirements::empty(),
788     ),
789     feature!(
790         atom!("monochrome"),
791         AllowsRanges::Yes,
792         Evaluator::Integer(eval_monochrome),
793         ParsingRequirements::empty(),
794     ),
795     feature!(
796         atom!("prefers-reduced-motion"),
797         AllowsRanges::No,
798         keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
799         ParsingRequirements::empty(),
800     ),
801     feature!(
802         atom!("prefers-contrast"),
803         AllowsRanges::No,
804         keyword_evaluator!(eval_prefers_contrast, PrefersContrast),
805         // Note: by default this is only enabled in browser chrome and
806         // ua. It can be enabled on the web via the
807         // layout.css.prefers-contrast.enabled preference. See
808         // disabed_by_pref in media_feature_expression.rs for how that
809         // is done.
810         ParsingRequirements::empty(),
811     ),
812     feature!(
813         atom!("forced-colors"),
814         AllowsRanges::No,
815         keyword_evaluator!(eval_forced_colors, ForcedColors),
816         ParsingRequirements::empty(),
817     ),
818     feature!(
819         atom!("overflow-block"),
820         AllowsRanges::No,
821         keyword_evaluator!(eval_overflow_block, OverflowBlock),
822         ParsingRequirements::empty(),
823     ),
824     feature!(
825         atom!("overflow-inline"),
826         AllowsRanges::No,
827         keyword_evaluator!(eval_overflow_inline, OverflowInline),
828         ParsingRequirements::empty(),
829     ),
830     feature!(
831         atom!("prefers-color-scheme"),
832         AllowsRanges::No,
833         keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
834         ParsingRequirements::empty(),
835     ),
836     feature!(
837         atom!("pointer"),
838         AllowsRanges::No,
839         keyword_evaluator!(eval_pointer, Pointer),
840         ParsingRequirements::empty(),
841     ),
842     feature!(
843         atom!("any-pointer"),
844         AllowsRanges::No,
845         keyword_evaluator!(eval_any_pointer, Pointer),
846         ParsingRequirements::empty(),
847     ),
848     feature!(
849         atom!("hover"),
850         AllowsRanges::No,
851         keyword_evaluator!(eval_hover, Hover),
852         ParsingRequirements::empty(),
853     ),
854     feature!(
855         atom!("any-hover"),
856         AllowsRanges::No,
857         keyword_evaluator!(eval_any_hover, Hover),
858         ParsingRequirements::empty(),
859     ),
860     // Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
861     // Internal because it is really only useful in the user agent anyway
862     // and therefore not worth standardizing.
863     feature!(
864         atom!("-moz-is-glyph"),
865         AllowsRanges::No,
866         Evaluator::BoolInteger(eval_moz_is_glyph),
867         ParsingRequirements::CHROME_AND_UA_ONLY,
868     ),
869     feature!(
870         atom!("-moz-is-resource-document"),
871         AllowsRanges::No,
872         Evaluator::BoolInteger(eval_moz_is_resource_document),
873         ParsingRequirements::CHROME_AND_UA_ONLY,
874     ),
875     feature!(
876         atom!("-moz-os-version"),
877         AllowsRanges::No,
878         Evaluator::Ident(eval_moz_os_version),
879         ParsingRequirements::CHROME_AND_UA_ONLY,
880     ),
881     feature!(
882         atom!("-moz-print-preview"),
883         AllowsRanges::No,
884         Evaluator::BoolInteger(eval_moz_print_preview),
885         ParsingRequirements::CHROME_AND_UA_ONLY,
886     ),
887     feature!(
888         atom!("-moz-non-native-content-theme"),
889         AllowsRanges::No,
890         Evaluator::BoolInteger(eval_moz_non_native_content_theme),
891         ParsingRequirements::CHROME_AND_UA_ONLY,
892     ),
893     feature!(
894         atom!("-moz-toolbar-prefers-color-scheme"),
895         AllowsRanges::No,
896         keyword_evaluator!(eval_toolbar_prefers_color_scheme, ToolbarPrefersColorScheme),
897         ParsingRequirements::CHROME_AND_UA_ONLY,
898     ),
899     feature!(
900         atom!("-moz-windows-non-native-menus"),
901         AllowsRanges::No,
902         Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
903         ParsingRequirements::CHROME_AND_UA_ONLY,
904     ),
905 
906     lnf_int_feature!(atom!("-moz-scrollbar-start-backward"), ScrollArrowStyle, get_scrollbar_start_backward),
907     lnf_int_feature!(atom!("-moz-scrollbar-start-forward"), ScrollArrowStyle, get_scrollbar_start_forward),
908     lnf_int_feature!(atom!("-moz-scrollbar-end-backward"), ScrollArrowStyle, get_scrollbar_end_backward),
909     lnf_int_feature!(atom!("-moz-scrollbar-end-forward"), ScrollArrowStyle, get_scrollbar_end_forward),
910     lnf_int_feature!(atom!("-moz-scrollbar-thumb-proportional"), ScrollSliderStyle),
911     lnf_int_feature!(atom!("-moz-overlay-scrollbars"), UseOverlayScrollbars),
912     lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag),
913     lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
914     lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme),
915     lnf_int_feature!(atom!("-moz-mac-big-sur-theme"), MacBigSurTheme),
916     lnf_int_feature!(atom!("-moz-windows-accent-color-in-titlebar"), WindowsAccentColorInTitlebar),
917     lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor),
918     lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic),
919     lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass),
920     lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled),
921     lnf_int_feature!(atom!("-moz-gtk-csd-available"), GTKCSDAvailable),
922     lnf_int_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default"), GTKCSDHideTitlebarByDefault),
923     lnf_int_feature!(atom!("-moz-gtk-csd-transparent-background"), GTKCSDTransparentBackground),
924     lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton),
925     lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton),
926     lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton),
927     lnf_int_feature!(atom!("-moz-gtk-csd-reversed-placement"), GTKCSDReversedPlacement),
928     lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme),
929     bool_pref_feature!(atom!("-moz-proton"), "browser.proton.enabled"),
930     bool_pref_feature!(atom!("-moz-proton-places-tooltip"), "browser.proton.places-tooltip.enabled"),
931 ];
932