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