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::position::Ratio;
14 use crate::values::computed::CSSPixelLength;
15 use crate::values::computed::Resolution;
16 use crate::Atom;
17 use app_units::Au;
18 use euclid::default::Size2D;
19 
viewport_size(device: &Device) -> Size2D<Au>20 fn viewport_size(device: &Device) -> Size2D<Au> {
21     if let Some(pc) = device.pres_context() {
22         if pc.mIsRootPaginatedDocument() != 0 {
23             // We want the page size, including unprintable areas and margins.
24             // FIXME(emilio, bug 1414600): Not quite!
25             let area = &pc.mPageSize;
26             return Size2D::new(Au(area.width), Au(area.height));
27         }
28     }
29     device.au_viewport_size()
30 }
31 
device_size(device: &Device) -> Size2D<Au>32 fn device_size(device: &Device) -> Size2D<Au> {
33     let mut width = 0;
34     let mut height = 0;
35     unsafe {
36         bindings::Gecko_MediaFeatures_GetDeviceSize(device.document(), &mut width, &mut height);
37     }
38     Size2D::new(Au(width), Au(height))
39 }
40 
41 /// https://drafts.csswg.org/mediaqueries-4/#width
eval_width( device: &Device, value: Option<CSSPixelLength>, range_or_operator: Option<RangeOrOperator>, ) -> bool42 fn eval_width(
43     device: &Device,
44     value: Option<CSSPixelLength>,
45     range_or_operator: Option<RangeOrOperator>,
46 ) -> bool {
47     RangeOrOperator::evaluate(
48         range_or_operator,
49         value.map(Au::from),
50         viewport_size(device).width,
51     )
52 }
53 
54 /// https://drafts.csswg.org/mediaqueries-4/#device-width
eval_device_width( device: &Device, value: Option<CSSPixelLength>, range_or_operator: Option<RangeOrOperator>, ) -> bool55 fn eval_device_width(
56     device: &Device,
57     value: Option<CSSPixelLength>,
58     range_or_operator: Option<RangeOrOperator>,
59 ) -> bool {
60     RangeOrOperator::evaluate(
61         range_or_operator,
62         value.map(Au::from),
63         device_size(device).width,
64     )
65 }
66 
67 /// https://drafts.csswg.org/mediaqueries-4/#height
eval_height( device: &Device, value: Option<CSSPixelLength>, range_or_operator: Option<RangeOrOperator>, ) -> bool68 fn eval_height(
69     device: &Device,
70     value: Option<CSSPixelLength>,
71     range_or_operator: Option<RangeOrOperator>,
72 ) -> bool {
73     RangeOrOperator::evaluate(
74         range_or_operator,
75         value.map(Au::from),
76         viewport_size(device).height,
77     )
78 }
79 
80 /// https://drafts.csswg.org/mediaqueries-4/#device-height
eval_device_height( device: &Device, value: Option<CSSPixelLength>, range_or_operator: Option<RangeOrOperator>, ) -> bool81 fn eval_device_height(
82     device: &Device,
83     value: Option<CSSPixelLength>,
84     range_or_operator: Option<RangeOrOperator>,
85 ) -> bool {
86     RangeOrOperator::evaluate(
87         range_or_operator,
88         value.map(Au::from),
89         device_size(device).height,
90     )
91 }
92 
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>,93 fn eval_aspect_ratio_for<F>(
94     device: &Device,
95     query_value: Option<Ratio>,
96     range_or_operator: Option<RangeOrOperator>,
97     get_size: F,
98 ) -> bool
99 where
100     F: FnOnce(&Device) -> Size2D<Au>,
101 {
102     let query_value = match query_value {
103         Some(v) => v,
104         None => return true,
105     };
106 
107     let size = get_size(device);
108     let value = Ratio::new(size.width.0 as f32, size.height.0 as f32);
109     RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value)
110 }
111 
112 /// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
eval_aspect_ratio( device: &Device, query_value: Option<Ratio>, range_or_operator: Option<RangeOrOperator>, ) -> bool113 fn eval_aspect_ratio(
114     device: &Device,
115     query_value: Option<Ratio>,
116     range_or_operator: Option<RangeOrOperator>,
117 ) -> bool {
118     eval_aspect_ratio_for(device, query_value, range_or_operator, viewport_size)
119 }
120 
121 /// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio
eval_device_aspect_ratio( device: &Device, query_value: Option<Ratio>, range_or_operator: Option<RangeOrOperator>, ) -> bool122 fn eval_device_aspect_ratio(
123     device: &Device,
124     query_value: Option<Ratio>,
125     range_or_operator: Option<RangeOrOperator>,
126 ) -> bool {
127     eval_aspect_ratio_for(device, query_value, range_or_operator, device_size)
128 }
129 
130 /// 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>, ) -> bool131 fn eval_device_pixel_ratio(
132     device: &Device,
133     query_value: Option<f32>,
134     range_or_operator: Option<RangeOrOperator>,
135 ) -> bool {
136     eval_resolution(
137         device,
138         query_value.map(Resolution::from_dppx),
139         range_or_operator,
140     )
141 }
142 
143 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
144 #[repr(u8)]
145 enum Orientation {
146     Landscape,
147     Portrait,
148 }
149 
eval_orientation_for<F>(device: &Device, value: Option<Orientation>, get_size: F) -> bool where F: FnOnce(&Device) -> Size2D<Au>,150 fn eval_orientation_for<F>(device: &Device, value: Option<Orientation>, get_size: F) -> bool
151 where
152     F: FnOnce(&Device) -> Size2D<Au>,
153 {
154     let query_orientation = match value {
155         Some(v) => v,
156         None => return true,
157     };
158 
159     let size = get_size(device);
160 
161     // Per spec, square viewports should be 'portrait'
162     let is_landscape = size.width > size.height;
163     match query_orientation {
164         Orientation::Landscape => is_landscape,
165         Orientation::Portrait => !is_landscape,
166     }
167 }
168 
169 /// https://drafts.csswg.org/mediaqueries-4/#orientation
eval_orientation(device: &Device, value: Option<Orientation>) -> bool170 fn eval_orientation(device: &Device, value: Option<Orientation>) -> bool {
171     eval_orientation_for(device, value, viewport_size)
172 }
173 
174 /// FIXME: There's no spec for `-moz-device-orientation`.
eval_device_orientation(device: &Device, value: Option<Orientation>) -> bool175 fn eval_device_orientation(device: &Device, value: Option<Orientation>) -> bool {
176     eval_orientation_for(device, value, device_size)
177 }
178 
179 /// Values for the display-mode media feature.
180 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
181 #[repr(u8)]
182 #[allow(missing_docs)]
183 pub enum DisplayMode {
184     Browser = 0,
185     MinimalUi,
186     Standalone,
187     Fullscreen,
188 }
189 
190 /// https://w3c.github.io/manifest/#the-display-mode-media-feature
eval_display_mode(device: &Device, query_value: Option<DisplayMode>) -> bool191 fn eval_display_mode(device: &Device, query_value: Option<DisplayMode>) -> bool {
192     match query_value {
193         Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) },
194         None => true,
195     }
196 }
197 
198 /// https://drafts.csswg.org/mediaqueries-4/#grid
eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool199 fn eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
200     // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature
201     // is always 0.
202     let supports_grid = false;
203     query_value.map_or(supports_grid, |v| v == supports_grid)
204 }
205 
206 /// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d
eval_transform_3d(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool207 fn eval_transform_3d(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
208     let supports_transforms = true;
209     query_value.map_or(supports_transforms, |v| v == supports_transforms)
210 }
211 
212 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
213 #[repr(u8)]
214 enum Scan {
215     Progressive,
216     Interlace,
217 }
218 
219 /// https://drafts.csswg.org/mediaqueries-4/#scan
eval_scan(_: &Device, _: Option<Scan>) -> bool220 fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
221     // Since Gecko doesn't support the 'tv' media type, the 'scan' feature never
222     // matches.
223     false
224 }
225 
226 /// https://drafts.csswg.org/mediaqueries-4/#color
eval_color( device: &Device, query_value: Option<u32>, range_or_operator: Option<RangeOrOperator>, ) -> bool227 fn eval_color(
228     device: &Device,
229     query_value: Option<u32>,
230     range_or_operator: Option<RangeOrOperator>,
231 ) -> bool {
232     let color_bits_per_channel =
233         unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) };
234     RangeOrOperator::evaluate(range_or_operator, query_value, color_bits_per_channel)
235 }
236 
237 /// https://drafts.csswg.org/mediaqueries-4/#color-index
eval_color_index( _: &Device, query_value: Option<u32>, range_or_operator: Option<RangeOrOperator>, ) -> bool238 fn eval_color_index(
239     _: &Device,
240     query_value: Option<u32>,
241     range_or_operator: Option<RangeOrOperator>,
242 ) -> bool {
243     // We should return zero if the device does not use a color lookup table.
244     let index = 0;
245     RangeOrOperator::evaluate(range_or_operator, query_value, index)
246 }
247 
248 /// https://drafts.csswg.org/mediaqueries-4/#monochrome
eval_monochrome( _: &Device, query_value: Option<u32>, range_or_operator: Option<RangeOrOperator>, ) -> bool249 fn eval_monochrome(
250     _: &Device,
251     query_value: Option<u32>,
252     range_or_operator: Option<RangeOrOperator>,
253 ) -> bool {
254     // For color devices we should return 0.
255     // FIXME: On a monochrome device, return the actual color depth, not 0!
256     let depth = 0;
257     RangeOrOperator::evaluate(range_or_operator, query_value, depth)
258 }
259 
260 /// https://drafts.csswg.org/mediaqueries-4/#resolution
eval_resolution( device: &Device, query_value: Option<Resolution>, range_or_operator: Option<RangeOrOperator>, ) -> bool261 fn eval_resolution(
262     device: &Device,
263     query_value: Option<Resolution>,
264     range_or_operator: Option<RangeOrOperator>,
265 ) -> bool {
266     let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) };
267     RangeOrOperator::evaluate(
268         range_or_operator,
269         query_value.map(|r| r.dppx()),
270         resolution_dppx,
271     )
272 }
273 
274 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
275 #[repr(u8)]
276 enum PrefersReducedMotion {
277     NoPreference,
278     Reduce,
279 }
280 
281 /// Values for the prefers-color-scheme media feature.
282 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
283 #[repr(u8)]
284 #[allow(missing_docs)]
285 pub enum PrefersColorScheme {
286     Light,
287     Dark,
288     NoPreference,
289 }
290 
291 /// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool292 fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool {
293     let prefers_reduced =
294         unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) };
295     let query_value = match query_value {
296         Some(v) => v,
297         None => return prefers_reduced,
298     };
299 
300     match query_value {
301         PrefersReducedMotion::NoPreference => !prefers_reduced,
302         PrefersReducedMotion::Reduce => prefers_reduced,
303     }
304 }
305 
306 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
307 #[repr(u8)]
308 enum OverflowBlock {
309     None,
310     Scroll,
311     OptionalPaged,
312     Paged,
313 }
314 
315 /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block
eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> bool316 fn eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> bool {
317     // For the time being, assume that printing (including previews)
318     // is the only time when we paginate, and we are otherwise always
319     // scrolling. This is true at the moment in Firefox, but may need
320     // updating in the future (e.g., ebook readers built with Stylo, a
321     // billboard mode that doesn't support overflow at all).
322     //
323     // If this ever changes, don't forget to change eval_overflow_inline too.
324     let scrolling = device.media_type() != MediaType::print();
325     let query_value = match query_value {
326         Some(v) => v,
327         None => return true,
328     };
329 
330     match query_value {
331         OverflowBlock::None | OverflowBlock::OptionalPaged => false,
332         OverflowBlock::Scroll => scrolling,
333         OverflowBlock::Paged => !scrolling,
334     }
335 }
336 
337 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
338 #[repr(u8)]
339 enum OverflowInline {
340     None,
341     Scroll,
342 }
343 
344 /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline
eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) -> bool345 fn eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) -> bool {
346     // See the note in eval_overflow_block.
347     let scrolling = device.media_type() != MediaType::print();
348     let query_value = match query_value {
349         Some(v) => v,
350         None => return scrolling,
351     };
352 
353     match query_value {
354         OverflowInline::None => !scrolling,
355         OverflowInline::Scroll => scrolling,
356     }
357 }
358 
359 /// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool360 fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool {
361     let prefers_color_scheme =
362         unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document()) };
363     match query_value {
364         Some(v) => prefers_color_scheme == v,
365         None => prefers_color_scheme != PrefersColorScheme::NoPreference,
366     }
367 }
368 
369 bitflags! {
370     /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
371     struct PointerCapabilities: u8 {
372         const COARSE = structs::PointerCapabilities_Coarse;
373         const FINE = structs::PointerCapabilities_Fine;
374         const HOVER = structs::PointerCapabilities_Hover;
375     }
376 }
377 
primary_pointer_capabilities(device: &Device) -> PointerCapabilities378 fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities {
379     PointerCapabilities::from_bits_truncate(unsafe {
380         bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document())
381     })
382 }
383 
all_pointer_capabilities(device: &Device) -> PointerCapabilities384 fn all_pointer_capabilities(device: &Device) -> PointerCapabilities {
385     PointerCapabilities::from_bits_truncate(unsafe {
386         bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document())
387     })
388 }
389 
390 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
391 #[repr(u8)]
392 enum Pointer {
393     None,
394     Coarse,
395     Fine,
396 }
397 
eval_pointer_capabilities( query_value: Option<Pointer>, pointer_capabilities: PointerCapabilities, ) -> bool398 fn eval_pointer_capabilities(
399     query_value: Option<Pointer>,
400     pointer_capabilities: PointerCapabilities,
401 ) -> bool {
402     let query_value = match query_value {
403         Some(v) => v,
404         None => return !pointer_capabilities.is_empty(),
405     };
406 
407     match query_value {
408         Pointer::None => pointer_capabilities.is_empty(),
409         Pointer::Coarse => pointer_capabilities.intersects(PointerCapabilities::COARSE),
410         Pointer::Fine => pointer_capabilities.intersects(PointerCapabilities::FINE),
411     }
412 }
413 
414 /// https://drafts.csswg.org/mediaqueries-4/#pointer
eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool415 fn eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
416     eval_pointer_capabilities(query_value, primary_pointer_capabilities(device))
417 }
418 
419 /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer
eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool420 fn eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
421     eval_pointer_capabilities(query_value, all_pointer_capabilities(device))
422 }
423 
424 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
425 #[repr(u8)]
426 enum Hover {
427     None,
428     Hover,
429 }
430 
eval_hover_capabilities( query_value: Option<Hover>, pointer_capabilities: PointerCapabilities, ) -> bool431 fn eval_hover_capabilities(
432     query_value: Option<Hover>,
433     pointer_capabilities: PointerCapabilities,
434 ) -> bool {
435     let can_hover = pointer_capabilities.intersects(PointerCapabilities::HOVER);
436     let query_value = match query_value {
437         Some(v) => v,
438         None => return can_hover,
439     };
440 
441     match query_value {
442         Hover::None => !can_hover,
443         Hover::Hover => can_hover,
444     }
445 }
446 
447 /// https://drafts.csswg.org/mediaqueries-4/#hover
eval_hover(device: &Device, query_value: Option<Hover>) -> bool448 fn eval_hover(device: &Device, query_value: Option<Hover>) -> bool {
449     eval_hover_capabilities(query_value, primary_pointer_capabilities(device))
450 }
451 
452 /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover
eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool453 fn eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool {
454     eval_hover_capabilities(query_value, all_pointer_capabilities(device))
455 }
456 
eval_moz_is_glyph( device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>, ) -> bool457 fn eval_moz_is_glyph(
458     device: &Device,
459     query_value: Option<bool>,
460     _: Option<RangeOrOperator>,
461 ) -> bool {
462     let is_glyph = device.document().mIsSVGGlyphsDocument();
463     query_value.map_or(is_glyph, |v| v == is_glyph)
464 }
465 
eval_moz_is_resource_document( device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>, ) -> bool466 fn eval_moz_is_resource_document(
467     device: &Device,
468     query_value: Option<bool>,
469     _: Option<RangeOrOperator>,
470 ) -> bool {
471     let is_resource_doc =
472         unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) };
473     query_value.map_or(is_resource_doc, |v| v == is_resource_doc)
474 }
475 
eval_system_metric( device: &Device, query_value: Option<bool>, metric: Atom, accessible_from_content: bool, ) -> bool476 fn eval_system_metric(
477     device: &Device,
478     query_value: Option<bool>,
479     metric: Atom,
480     accessible_from_content: bool,
481 ) -> bool {
482     let supports_metric = unsafe {
483         bindings::Gecko_MediaFeatures_HasSystemMetric(
484             device.document(),
485             metric.as_ptr(),
486             accessible_from_content,
487         )
488     };
489     query_value.map_or(supports_metric, |v| v == supports_metric)
490 }
491 
eval_moz_touch_enabled( device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>, ) -> bool492 fn eval_moz_touch_enabled(
493     device: &Device,
494     query_value: Option<bool>,
495     _: Option<RangeOrOperator>,
496 ) -> bool {
497     eval_system_metric(
498         device,
499         query_value,
500         atom!("-moz-touch-enabled"),
501         /* accessible_from_content = */ true,
502     )
503 }
504 
eval_moz_os_version( device: &Device, query_value: Option<Atom>, _: Option<RangeOrOperator>, ) -> bool505 fn eval_moz_os_version(
506     device: &Device,
507     query_value: Option<Atom>,
508     _: Option<RangeOrOperator>,
509 ) -> bool {
510     let query_value = match query_value {
511         Some(v) => v,
512         None => return false,
513     };
514 
515     let os_version =
516         unsafe { bindings::Gecko_MediaFeatures_GetOperatingSystemVersion(device.document()) };
517 
518     query_value.as_ptr() == os_version
519 }
520 
521 macro_rules! system_metric_feature {
522     ($feature_name:expr) => {{
523         fn __eval(device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
524             eval_system_metric(
525                 device,
526                 query_value,
527                 $feature_name,
528                 /* accessible_from_content = */ false,
529             )
530         }
531 
532         feature!(
533             $feature_name,
534             AllowsRanges::No,
535             Evaluator::BoolInteger(__eval),
536             ParsingRequirements::CHROME_AND_UA_ONLY,
537         )
538     }};
539 }
540 
541 lazy_static! {
542     /// Adding new media features requires (1) adding the new feature to this
543     /// array, with appropriate entries (and potentially any new code needed
544     /// to support new types in these entries and (2) ensuring that either
545     /// nsPresContext::MediaFeatureValuesChanged is called when the value that
546     /// would be returned by the evaluator function could change.
547     pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 53] = [
548         feature!(
549             atom!("width"),
550             AllowsRanges::Yes,
551             Evaluator::Length(eval_width),
552             ParsingRequirements::empty(),
553         ),
554         feature!(
555             atom!("height"),
556             AllowsRanges::Yes,
557             Evaluator::Length(eval_height),
558             ParsingRequirements::empty(),
559         ),
560         feature!(
561             atom!("aspect-ratio"),
562             AllowsRanges::Yes,
563             Evaluator::NumberRatio(eval_aspect_ratio),
564             ParsingRequirements::empty(),
565         ),
566         feature!(
567             atom!("orientation"),
568             AllowsRanges::No,
569             keyword_evaluator!(eval_orientation, Orientation),
570             ParsingRequirements::empty(),
571         ),
572         feature!(
573             atom!("device-width"),
574             AllowsRanges::Yes,
575             Evaluator::Length(eval_device_width),
576             ParsingRequirements::empty(),
577         ),
578         feature!(
579             atom!("device-height"),
580             AllowsRanges::Yes,
581             Evaluator::Length(eval_device_height),
582             ParsingRequirements::empty(),
583         ),
584         feature!(
585             atom!("device-aspect-ratio"),
586             AllowsRanges::Yes,
587             Evaluator::NumberRatio(eval_device_aspect_ratio),
588             ParsingRequirements::empty(),
589         ),
590         feature!(
591             atom!("-moz-device-orientation"),
592             AllowsRanges::No,
593             keyword_evaluator!(eval_device_orientation, Orientation),
594             ParsingRequirements::empty(),
595         ),
596         // Webkit extensions that we support for de-facto web compatibility.
597         // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
598         feature!(
599             atom!("device-pixel-ratio"),
600             AllowsRanges::Yes,
601             Evaluator::Float(eval_device_pixel_ratio),
602             ParsingRequirements::WEBKIT_PREFIX,
603         ),
604         // -webkit-transform-3d.
605         feature!(
606             atom!("transform-3d"),
607             AllowsRanges::No,
608             Evaluator::BoolInteger(eval_transform_3d),
609             ParsingRequirements::WEBKIT_PREFIX,
610         ),
611         feature!(
612             atom!("-moz-device-pixel-ratio"),
613             AllowsRanges::Yes,
614             Evaluator::Float(eval_device_pixel_ratio),
615             ParsingRequirements::empty(),
616         ),
617         feature!(
618             atom!("resolution"),
619             AllowsRanges::Yes,
620             Evaluator::Resolution(eval_resolution),
621             ParsingRequirements::empty(),
622         ),
623         feature!(
624             atom!("display-mode"),
625             AllowsRanges::No,
626             keyword_evaluator!(eval_display_mode, DisplayMode),
627             ParsingRequirements::empty(),
628         ),
629         feature!(
630             atom!("grid"),
631             AllowsRanges::No,
632             Evaluator::BoolInteger(eval_grid),
633             ParsingRequirements::empty(),
634         ),
635         feature!(
636             atom!("scan"),
637             AllowsRanges::No,
638             keyword_evaluator!(eval_scan, Scan),
639             ParsingRequirements::empty(),
640         ),
641         feature!(
642             atom!("color"),
643             AllowsRanges::Yes,
644             Evaluator::Integer(eval_color),
645             ParsingRequirements::empty(),
646         ),
647         feature!(
648             atom!("color-index"),
649             AllowsRanges::Yes,
650             Evaluator::Integer(eval_color_index),
651             ParsingRequirements::empty(),
652         ),
653         feature!(
654             atom!("monochrome"),
655             AllowsRanges::Yes,
656             Evaluator::Integer(eval_monochrome),
657             ParsingRequirements::empty(),
658         ),
659         feature!(
660             atom!("prefers-reduced-motion"),
661             AllowsRanges::No,
662             keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
663             ParsingRequirements::empty(),
664         ),
665         feature!(
666             atom!("overflow-block"),
667             AllowsRanges::No,
668             keyword_evaluator!(eval_overflow_block, OverflowBlock),
669             ParsingRequirements::empty(),
670         ),
671         feature!(
672             atom!("overflow-inline"),
673             AllowsRanges::No,
674             keyword_evaluator!(eval_overflow_inline, OverflowInline),
675             ParsingRequirements::empty(),
676         ),
677         feature!(
678             atom!("prefers-color-scheme"),
679             AllowsRanges::No,
680             keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
681             ParsingRequirements::empty(),
682         ),
683         feature!(
684             atom!("pointer"),
685             AllowsRanges::No,
686             keyword_evaluator!(eval_pointer, Pointer),
687             ParsingRequirements::empty(),
688         ),
689         feature!(
690             atom!("any-pointer"),
691             AllowsRanges::No,
692             keyword_evaluator!(eval_any_pointer, Pointer),
693             ParsingRequirements::empty(),
694         ),
695         feature!(
696             atom!("hover"),
697             AllowsRanges::No,
698             keyword_evaluator!(eval_hover, Hover),
699             ParsingRequirements::empty(),
700         ),
701         feature!(
702             atom!("any-hover"),
703             AllowsRanges::No,
704             keyword_evaluator!(eval_any_hover, Hover),
705             ParsingRequirements::empty(),
706         ),
707 
708         // Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
709         // Internal because it is really only useful in the user agent anyway
710         // and therefore not worth standardizing.
711         feature!(
712             atom!("-moz-is-glyph"),
713             AllowsRanges::No,
714             Evaluator::BoolInteger(eval_moz_is_glyph),
715             ParsingRequirements::CHROME_AND_UA_ONLY,
716         ),
717         feature!(
718             atom!("-moz-is-resource-document"),
719             AllowsRanges::No,
720             Evaluator::BoolInteger(eval_moz_is_resource_document),
721             ParsingRequirements::CHROME_AND_UA_ONLY,
722         ),
723         feature!(
724             atom!("-moz-os-version"),
725             AllowsRanges::No,
726             Evaluator::Ident(eval_moz_os_version),
727             ParsingRequirements::CHROME_AND_UA_ONLY,
728         ),
729         system_metric_feature!(atom!("-moz-scrollbar-start-backward")),
730         system_metric_feature!(atom!("-moz-scrollbar-start-forward")),
731         system_metric_feature!(atom!("-moz-scrollbar-end-backward")),
732         system_metric_feature!(atom!("-moz-scrollbar-end-forward")),
733         system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional")),
734         system_metric_feature!(atom!("-moz-overlay-scrollbars")),
735         system_metric_feature!(atom!("-moz-windows-default-theme")),
736         system_metric_feature!(atom!("-moz-mac-graphite-theme")),
737         system_metric_feature!(atom!("-moz-mac-yosemite-theme")),
738         system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar")),
739         system_metric_feature!(atom!("-moz-windows-compositor")),
740         system_metric_feature!(atom!("-moz-windows-classic")),
741         system_metric_feature!(atom!("-moz-windows-glass")),
742         system_metric_feature!(atom!("-moz-menubar-drag")),
743         system_metric_feature!(atom!("-moz-swipe-animation-enabled")),
744         system_metric_feature!(atom!("-moz-gtk-csd-available")),
745         system_metric_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default")),
746         system_metric_feature!(atom!("-moz-gtk-csd-transparent-background")),
747         system_metric_feature!(atom!("-moz-gtk-csd-minimize-button")),
748         system_metric_feature!(atom!("-moz-gtk-csd-maximize-button")),
749         system_metric_feature!(atom!("-moz-gtk-csd-close-button")),
750         system_metric_feature!(atom!("-moz-gtk-csd-reversed-placement")),
751         system_metric_feature!(atom!("-moz-system-dark-theme")),
752         // This is the only system-metric media feature that's accessible to
753         // content as of today.
754         // FIXME(emilio): Restrict (or remove?) when bug 1035774 lands.
755         feature!(
756             atom!("-moz-touch-enabled"),
757             AllowsRanges::No,
758             Evaluator::BoolInteger(eval_moz_touch_enabled),
759             ParsingRequirements::empty(),
760         ),
761     ];
762 }
763