style/servo/
media_queries.rs

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//! Servo's media-query device and expression representation.
6
7use crate::color::AbsoluteColor;
8use crate::context::QuirksMode;
9use crate::custom_properties::CssEnvironment;
10use crate::derives::*;
11use crate::font_metrics::FontMetrics;
12use crate::logical_geometry::WritingMode;
13use crate::media_queries::MediaType;
14use crate::properties::style_structs::Font;
15use crate::properties::ComputedValues;
16use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
17use crate::queries::values::PrefersColorScheme;
18use crate::values::computed::font::GenericFontFamily;
19use crate::values::computed::{
20    CSSPixelLength, Context, Length, LineHeight, NonNegativeLength, Resolution,
21};
22use crate::values::specified::color::{ColorSchemeFlags, ForcedColors};
23use crate::values::specified::font::{
24    QueryFontMetricsFlags, FONT_MEDIUM_CAP_PX, FONT_MEDIUM_CH_PX, FONT_MEDIUM_EX_PX,
25    FONT_MEDIUM_IC_PX, FONT_MEDIUM_LINE_HEIGHT_PX, FONT_MEDIUM_PX,
26};
27use crate::values::specified::ViewportVariant;
28use crate::values::KeyframesName;
29use app_units::{Au, AU_PER_PX};
30use euclid::default::Size2D as UntypedSize2D;
31use euclid::{Scale, SideOffsets2D, Size2D};
32use mime::Mime;
33use parking_lot::RwLock;
34use servo_arc::Arc;
35use std::fmt::Debug;
36use std::mem;
37use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
38use style_traits::{CSSPixel, DevicePixel};
39
40/// A trait used to query font metrics in clients of Stylo. This is used by Device to
41/// query font metrics in a way that is specific to the client using Stylo.
42pub trait FontMetricsProvider: Debug + Sync {
43    /// Query the font metrics for the given font and the given base font size.
44    fn query_font_metrics(
45        &self,
46        vertical: bool,
47        font: &Font,
48        base_size: CSSPixelLength,
49        flags: QueryFontMetricsFlags,
50    ) -> FontMetrics;
51    /// Gets the base size given a generic font family.
52    fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length;
53}
54
55/// A device is a structure that represents the current media a given document
56/// is displayed in.
57///
58/// This is the struct against which media queries are evaluated.
59/// and contains all the viewport rule state.
60///
61/// This structure also contains atomics used for computing root font-relative
62/// units. These atomics use relaxed ordering, since when computing the style
63/// of the root element, there can't be any other style being computed at the
64/// same time (given we need the style of the parent to compute everything else).
65#[derive(Debug, MallocSizeOf)]
66pub struct Device {
67    /// The current media type used by de device.
68    media_type: MediaType,
69    /// The current viewport size, in CSS pixels.
70    viewport_size: Size2D<f32, CSSPixel>,
71    /// The current device pixel ratio, from CSS pixels to device pixels.
72    device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
73    /// The current quirks mode.
74    #[ignore_malloc_size_of = "Pure stack type"]
75    quirks_mode: QuirksMode,
76
77    /// Current computed style of the root element, used for calculations of
78    /// root font-relative units.
79    #[ignore_malloc_size_of = "Arc"]
80    root_style: RwLock<Arc<ComputedValues>>,
81    /// Font size of the root element, used for rem units in other elements.
82    #[ignore_malloc_size_of = "Pure stack type"]
83    root_font_size: AtomicU32,
84    /// Line height of the root element, used for rlh units in other elements.
85    #[ignore_malloc_size_of = "Pure stack type"]
86    root_line_height: AtomicU32,
87    /// X-height of the root element, used for rex units in other elements.
88    #[ignore_malloc_size_of = "Pure stack type"]
89    root_font_metrics_ex: AtomicU32,
90    /// Cap-height of the root element, used for rcap units in other elements.
91    #[ignore_malloc_size_of = "Pure stack type"]
92    root_font_metrics_cap: AtomicU32,
93    /// Advance measure (ch) of the root element, used for rch units in other elements.
94    #[ignore_malloc_size_of = "Pure stack type"]
95    root_font_metrics_ch: AtomicU32,
96    /// Ideographic advance measure of the root element, used for ric units in other elements.
97    #[ignore_malloc_size_of = "Pure stack type"]
98    root_font_metrics_ic: AtomicU32,
99    /// Whether any styles computed in the document relied on the root font-size
100    /// by using rem units.
101    #[ignore_malloc_size_of = "Pure stack type"]
102    used_root_font_size: AtomicBool,
103    /// Whether any styles computed in the document relied on the root line-height
104    /// by using rlh units.
105    #[ignore_malloc_size_of = "Pure stack type"]
106    used_root_line_height: AtomicBool,
107    /// Whether any styles computed in the document relied on the root font metrics
108    /// by using rcap, rch, rex, or ric units. This is a lock instead of an atomic
109    /// in order to prevent concurrent writes to the root font metric values.
110    #[ignore_malloc_size_of = "Pure stack type"]
111    used_root_font_metrics: RwLock<bool>,
112    /// Whether any styles computed in the document relied on font metrics.
113    used_font_metrics: AtomicBool,
114    /// Whether any styles computed in the document relied on the viewport size.
115    #[ignore_malloc_size_of = "Pure stack type"]
116    used_viewport_units: AtomicBool,
117    /// Whether the user prefers light mode or dark mode
118    #[ignore_malloc_size_of = "Pure stack type"]
119    prefers_color_scheme: PrefersColorScheme,
120    /// The CssEnvironment object responsible of getting CSS environment
121    /// variables.
122    environment: CssEnvironment,
123    /// An implementation of a trait which implements support for querying font metrics.
124    #[ignore_malloc_size_of = "Owned by embedder"]
125    font_metrics_provider: Box<dyn FontMetricsProvider>,
126    /// The default computed values for this Device.
127    #[ignore_malloc_size_of = "Arc is shared"]
128    default_computed_values: Arc<ComputedValues>,
129}
130
131impl Device {
132    /// Trivially construct a new `Device`.
133    pub fn new(
134        media_type: MediaType,
135        quirks_mode: QuirksMode,
136        viewport_size: Size2D<f32, CSSPixel>,
137        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
138        font_metrics_provider: Box<dyn FontMetricsProvider>,
139        default_computed_values: Arc<ComputedValues>,
140        prefers_color_scheme: PrefersColorScheme,
141    ) -> Device {
142        let default_values =
143            ComputedValues::initial_values_with_font_override(Font::initial_values());
144        let root_style = RwLock::new(Arc::clone(&default_values));
145        Device {
146            media_type,
147            viewport_size,
148            device_pixel_ratio,
149            quirks_mode,
150            root_style,
151            root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
152            root_line_height: AtomicU32::new(FONT_MEDIUM_LINE_HEIGHT_PX.to_bits()),
153            root_font_metrics_ex: AtomicU32::new(FONT_MEDIUM_EX_PX.to_bits()),
154            root_font_metrics_cap: AtomicU32::new(FONT_MEDIUM_CAP_PX.to_bits()),
155            root_font_metrics_ch: AtomicU32::new(FONT_MEDIUM_CH_PX.to_bits()),
156            root_font_metrics_ic: AtomicU32::new(FONT_MEDIUM_IC_PX.to_bits()),
157            used_root_font_size: AtomicBool::new(false),
158            used_root_line_height: AtomicBool::new(false),
159            used_root_font_metrics: RwLock::new(false),
160            used_font_metrics: AtomicBool::new(false),
161            used_viewport_units: AtomicBool::new(false),
162            prefers_color_scheme,
163            environment: CssEnvironment,
164            font_metrics_provider,
165            default_computed_values,
166        }
167    }
168
169    /// Get the relevant environment to resolve `env()` functions.
170    #[inline]
171    pub fn environment(&self) -> &CssEnvironment {
172        &self.environment
173    }
174
175    /// Return the default computed values for this device.
176    pub fn default_computed_values(&self) -> &ComputedValues {
177        &self.default_computed_values
178    }
179
180    /// Store a pointer to the root element's computed style, for use in
181    /// calculation of root font-relative metrics.
182    pub fn set_root_style(&self, style: &Arc<ComputedValues>) {
183        *self.root_style.write() = style.clone();
184    }
185
186    /// Get the font size of the root element (for rem)
187    pub fn root_font_size(&self) -> CSSPixelLength {
188        self.used_root_font_size.store(true, Ordering::Relaxed);
189        CSSPixelLength::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed)))
190    }
191
192    /// Set the font size of the root element (for rem), in zoom-independent CSS pixels.
193    pub fn set_root_font_size(&self, size: f32) {
194        self.root_font_size.store(size.to_bits(), Ordering::Relaxed)
195    }
196
197    /// Get the line height of the root element (for rlh)
198    pub fn root_line_height(&self) -> CSSPixelLength {
199        self.used_root_line_height.store(true, Ordering::Relaxed);
200        CSSPixelLength::new(f32::from_bits(
201            self.root_line_height.load(Ordering::Relaxed),
202        ))
203    }
204
205    /// Set the line height of the root element (for rlh), in zoom-independent CSS pixels.
206    pub fn set_root_line_height(&self, size: f32) {
207        self.root_line_height
208            .store(size.to_bits(), Ordering::Relaxed);
209    }
210
211    /// Get the x-height of the root element (for rex)
212    pub fn root_font_metrics_ex(&self) -> Length {
213        self.ensure_root_font_metrics_updated();
214        Length::new(f32::from_bits(
215            self.root_font_metrics_ex.load(Ordering::Relaxed),
216        ))
217    }
218
219    /// Set the x-height of the root element (for rex), in zoom-independent CSS pixels.
220    pub fn set_root_font_metrics_ex(&self, size: f32) -> bool {
221        let size = size.to_bits();
222        let previous = self.root_font_metrics_ex.swap(size, Ordering::Relaxed);
223        previous != size
224    }
225
226    /// Get the cap-height of the root element (for rcap)
227    pub fn root_font_metrics_cap(&self) -> Length {
228        self.ensure_root_font_metrics_updated();
229        Length::new(f32::from_bits(
230            self.root_font_metrics_cap.load(Ordering::Relaxed),
231        ))
232    }
233
234    /// Set the cap-height of the root element (for rcap), in zoom-independent CSS pixels.
235    pub fn set_root_font_metrics_cap(&self, size: f32) -> bool {
236        let size = size.to_bits();
237        let previous = self.root_font_metrics_cap.swap(size, Ordering::Relaxed);
238        previous != size
239    }
240
241    /// Get the advance measure of the root element (for rch)
242    pub fn root_font_metrics_ch(&self) -> Length {
243        self.ensure_root_font_metrics_updated();
244        Length::new(f32::from_bits(
245            self.root_font_metrics_ch.load(Ordering::Relaxed),
246        ))
247    }
248
249    /// Set the advance measure of the root element (for rch), in zoom-independent CSS pixels.
250    pub fn set_root_font_metrics_ch(&self, size: f32) -> bool {
251        let size = size.to_bits();
252        let previous = self.root_font_metrics_ch.swap(size, Ordering::Relaxed);
253        previous != size
254    }
255
256    /// Get the ideographic advance measure of the root element (for ric)
257    pub fn root_font_metrics_ic(&self) -> Length {
258        self.ensure_root_font_metrics_updated();
259        Length::new(f32::from_bits(
260            self.root_font_metrics_ic.load(Ordering::Relaxed),
261        ))
262    }
263
264    /// Set the ideographic advance measure of the root element (for ric), in zoom-independent CSS pixels.
265    pub fn set_root_font_metrics_ic(&self, size: f32) -> bool {
266        let size = size.to_bits();
267        let previous = self.root_font_metrics_ic.swap(size, Ordering::Relaxed);
268        previous != size
269    }
270
271    /// Returns the computed line-height for the font in a given computed values instance.
272    ///
273    /// If you pass down an element, then the used line-height is returned.
274    pub fn calc_line_height(
275        &self,
276        font: &crate::properties::style_structs::Font,
277        _writing_mode: WritingMode,
278        _element: Option<()>,
279    ) -> NonNegativeLength {
280        (match font.line_height {
281            // TODO: compute `normal` from the font metrics
282            LineHeight::Normal => CSSPixelLength::new(0.),
283            LineHeight::Number(number) => font.font_size.computed_size() * number.0,
284            LineHeight::Length(length) => length.0,
285        })
286        .into()
287    }
288
289    /// Get the quirks mode of the current device.
290    pub fn quirks_mode(&self) -> QuirksMode {
291        self.quirks_mode
292    }
293
294    /// Sets the body text color for the "inherit color from body" quirk.
295    ///
296    /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
297    pub fn set_body_text_color(&self, _color: AbsoluteColor) {
298        // Servo doesn't implement this quirk (yet)
299    }
300
301    /// Gets the base size given a generic font family.
302    pub fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length {
303        self.font_metrics_provider.base_size_for_generic(generic)
304    }
305
306    /// Whether a given animation name may be referenced from style.
307    pub fn animation_name_may_be_referenced(&self, _: &KeyframesName) -> bool {
308        // Assume it is, since we don't have any good way to prove it's not.
309        true
310    }
311
312    /// Returns whether we ever looked up the root font size of the Device.
313    pub fn used_root_font_size(&self) -> bool {
314        self.used_root_font_size.load(Ordering::Relaxed)
315    }
316
317    /// Returns whether we ever looked up the root line-height of the device.
318    pub fn used_root_line_height(&self) -> bool {
319        self.used_root_line_height.load(Ordering::Relaxed)
320    }
321
322    /// Returns whether we ever looked up the root font metrics of the device.
323    pub fn used_root_font_metrics(&self) -> bool {
324        *self.used_root_font_metrics.read()
325    }
326
327    /// Returns whether font metrics have been queried.
328    pub fn used_font_metrics(&self) -> bool {
329        self.used_font_metrics.load(Ordering::Relaxed)
330    }
331
332    /// Get the viewport size on this [`Device`].
333    pub fn viewport_size(&self) -> Size2D<f32, CSSPixel> {
334        self.viewport_size
335    }
336
337    /// Set the viewport size on this [`Device`].
338    ///
339    /// Note that this does not update any associated `Stylist`. For this you must call
340    /// `Stylist::media_features_change_changed_style` and
341    /// `Stylist::force_stylesheet_origins_dirty`.
342    pub fn set_viewport_size(&mut self, viewport_size: Size2D<f32, CSSPixel>) {
343        self.viewport_size = viewport_size;
344    }
345
346    /// Returns the viewport size of the current device in app units, needed,
347    /// among other things, to resolve viewport units.
348    #[inline]
349    pub fn au_viewport_size(&self) -> UntypedSize2D<Au> {
350        Size2D::new(
351            Au::from_f32_px(self.viewport_size.width),
352            Au::from_f32_px(self.viewport_size.height),
353        )
354    }
355
356    /// Like the above, but records that we've used viewport units.
357    pub fn au_viewport_size_for_viewport_unit_resolution(
358        &self,
359        _: ViewportVariant,
360    ) -> UntypedSize2D<Au> {
361        self.used_viewport_units.store(true, Ordering::Relaxed);
362        // Servo doesn't have dynamic UA interfaces that affect the viewport,
363        // so we can just ignore the ViewportVariant.
364        self.au_viewport_size()
365    }
366
367    /// Whether viewport units were used since the last device change.
368    pub fn used_viewport_units(&self) -> bool {
369        self.used_viewport_units.load(Ordering::Relaxed)
370    }
371
372    /// Returns the number of app units per device pixel we're using currently.
373    pub fn app_units_per_device_pixel(&self) -> i32 {
374        (AU_PER_PX as f32 / self.device_pixel_ratio.0) as i32
375    }
376
377    /// Returns the device pixel ratio, ignoring the full zoom factor.
378    pub fn device_pixel_ratio_ignoring_full_zoom(&self) -> Scale<f32, CSSPixel, DevicePixel> {
379        self.device_pixel_ratio
380    }
381
382    /// Returns the device pixel ratio.
383    pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
384        self.device_pixel_ratio
385    }
386
387    /// Set a new device pixel ratio on this [`Device`].
388    ///
389    /// Note that this does not update any associated `Stylist`. For this you must call
390    /// `Stylist::media_features_change_changed_style` and
391    /// `Stylist::force_stylesheet_origins_dirty`.
392    pub fn set_device_pixel_ratio(
393        &mut self,
394        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
395    ) {
396        self.device_pixel_ratio = device_pixel_ratio;
397    }
398
399    /// Gets the size of the scrollbar in CSS pixels.
400    pub fn scrollbar_inline_size(&self) -> CSSPixelLength {
401        // TODO: implement this.
402        CSSPixelLength::new(0.0)
403    }
404
405    /// Queries font metrics using the [`FontMetricsProvider`] interface.
406    pub fn query_font_metrics(
407        &self,
408        vertical: bool,
409        font: &Font,
410        base_size: CSSPixelLength,
411        flags: QueryFontMetricsFlags,
412        track_usage: bool,
413    ) -> FontMetrics {
414        if track_usage {
415            self.used_font_metrics.store(true, Ordering::Relaxed);
416        }
417        self.font_metrics_provider
418            .query_font_metrics(vertical, font, base_size, flags)
419    }
420
421    fn ensure_root_font_metrics_updated(&self) {
422        let mut guard = self.used_root_font_metrics.write();
423        let previously_computed = mem::replace(&mut *guard, true);
424        if !previously_computed {
425            self.update_root_font_metrics();
426        }
427    }
428
429    /// Compute the root element's font metrics, and returns a bool indicating whether
430    /// the font metrics have changed since the previous restyle.
431    pub fn update_root_font_metrics(&self) -> bool {
432        let root_style = self.root_style.read();
433        let root_effective_zoom = (*root_style).effective_zoom;
434        let root_font_size = (*root_style).get_font().clone_font_size().computed_size();
435
436        let root_font_metrics = self.query_font_metrics(
437            (*root_style).writing_mode.is_upright(),
438            &(*root_style).get_font(),
439            root_font_size,
440            QueryFontMetricsFlags::USE_USER_FONT_SET
441                | QueryFontMetricsFlags::NEEDS_CH
442                | QueryFontMetricsFlags::NEEDS_IC,
443            /* track_usage = */ false,
444        );
445
446        let mut root_font_metrics_changed = false;
447        root_font_metrics_changed |= self.set_root_font_metrics_ex(
448            root_effective_zoom.unzoom(root_font_metrics.x_height_or_default(root_font_size).px()),
449        );
450        root_font_metrics_changed |= self.set_root_font_metrics_ch(
451            root_effective_zoom.unzoom(
452                root_font_metrics
453                    .zero_advance_measure_or_default(
454                        root_font_size,
455                        (*root_style).writing_mode.is_upright(),
456                    )
457                    .px(),
458            ),
459        );
460        root_font_metrics_changed |= self.set_root_font_metrics_cap(
461            root_effective_zoom.unzoom(root_font_metrics.cap_height_or_default().px()),
462        );
463        root_font_metrics_changed |= self.set_root_font_metrics_ic(
464            root_effective_zoom.unzoom(root_font_metrics.ic_width_or_default(root_font_size).px()),
465        );
466
467        root_font_metrics_changed
468    }
469
470    /// Return the media type of the current device.
471    pub fn media_type(&self) -> MediaType {
472        self.media_type.clone()
473    }
474
475    /// Returns whether document colors are enabled.
476    pub fn forced_colors(&self) -> ForcedColors {
477        ForcedColors::None
478    }
479
480    /// Returns the default background color.
481    pub fn default_background_color(&self) -> AbsoluteColor {
482        AbsoluteColor::WHITE
483    }
484
485    /// Returns the default foreground color.
486    pub fn default_color(&self) -> AbsoluteColor {
487        AbsoluteColor::BLACK
488    }
489
490    /// Set the [`PrefersColorScheme`] value on this [`Device`].
491    ///
492    /// Note that this does not update any associated `Stylist`. For this you must call
493    /// `Stylist::media_features_change_changed_style` and
494    /// `Stylist::force_stylesheet_origins_dirty`.
495    pub fn set_color_scheme(&mut self, new_color_scheme: PrefersColorScheme) {
496        self.prefers_color_scheme = new_color_scheme;
497    }
498
499    /// Returns the color scheme of this [`Device`].
500    pub fn color_scheme(&self) -> PrefersColorScheme {
501        self.prefers_color_scheme
502    }
503
504    pub(crate) fn is_dark_color_scheme(&self, _: ColorSchemeFlags) -> bool {
505        false
506    }
507
508    /// Returns safe area insets
509    pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> {
510        SideOffsets2D::zero()
511    }
512
513    /// Returns true if the given MIME type is supported
514    pub fn is_supported_mime_type(&self, mime_type: &str) -> bool {
515        match mime_type.parse::<Mime>() {
516            Ok(m) => {
517                // Keep this in sync with 'image_classifer' from
518                // components/net/mime_classifier.rs
519                m == mime::IMAGE_BMP
520                    || m == mime::IMAGE_GIF
521                    || m == mime::IMAGE_PNG
522                    || m == mime::IMAGE_JPEG
523                    || m == "image/x-icon"
524                    || m == "image/webp"
525            },
526            _ => false,
527        }
528    }
529
530    /// Return whether the document is a chrome document.
531    #[inline]
532    pub fn chrome_rules_enabled_for_document(&self) -> bool {
533        false
534    }
535}
536
537/// https://drafts.csswg.org/mediaqueries-4/#width
538fn eval_width(context: &Context) -> CSSPixelLength {
539    CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
540}
541
542#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
543#[repr(u8)]
544enum Scan {
545    Progressive,
546    Interlace,
547}
548
549/// https://drafts.csswg.org/mediaqueries-4/#scan
550fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
551    // Since we doesn't support the 'tv' media type, the 'scan' feature never
552    // matches.
553    false
554}
555
556/// https://drafts.csswg.org/mediaqueries-4/#resolution
557fn eval_resolution(context: &Context) -> Resolution {
558    Resolution::from_dppx(context.device().device_pixel_ratio.0)
559}
560
561/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
562fn eval_device_pixel_ratio(context: &Context) -> f32 {
563    eval_resolution(context).dppx()
564}
565
566fn eval_prefers_color_scheme(context: &Context, query_value: Option<PrefersColorScheme>) -> bool {
567    match query_value {
568        Some(v) => context.device().prefers_color_scheme == v,
569        None => true,
570    }
571}
572
573/// A list with all the media features that Servo supports.
574pub static MEDIA_FEATURES: [QueryFeatureDescription; 6] = [
575    feature!(
576        atom!("width"),
577        AllowsRanges::Yes,
578        Evaluator::Length(eval_width),
579        FeatureFlags::empty(),
580    ),
581    feature!(
582        atom!("scan"),
583        AllowsRanges::No,
584        keyword_evaluator!(eval_scan, Scan),
585        FeatureFlags::empty(),
586    ),
587    feature!(
588        atom!("resolution"),
589        AllowsRanges::Yes,
590        Evaluator::Resolution(eval_resolution),
591        FeatureFlags::empty(),
592    ),
593    feature!(
594        atom!("device-pixel-ratio"),
595        AllowsRanges::Yes,
596        Evaluator::Float(eval_device_pixel_ratio),
597        FeatureFlags::WEBKIT_PREFIX,
598    ),
599    feature!(
600        atom!("-moz-device-pixel-ratio"),
601        AllowsRanges::Yes,
602        Evaluator::Float(eval_device_pixel_ratio),
603        FeatureFlags::empty(),
604    ),
605    feature!(
606        atom!("prefers-color-scheme"),
607        AllowsRanges::No,
608        keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
609        FeatureFlags::empty(),
610    ),
611];