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::font_metrics::FontMetrics;
11use crate::logical_geometry::WritingMode;
12use crate::media_queries::MediaType;
13use crate::properties::style_structs::Font;
14use crate::properties::ComputedValues;
15use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
16use crate::queries::values::PrefersColorScheme;
17use crate::values::computed::font::GenericFontFamily;
18use crate::values::computed::{
19    CSSPixelLength, Context, Length, LineHeight, NonNegativeLength, Resolution,
20};
21use crate::values::specified::color::{ColorSchemeFlags, ForcedColors};
22use crate::values::specified::font::{
23    QueryFontMetricsFlags, FONT_MEDIUM_LINE_HEIGHT_PX, FONT_MEDIUM_PX,
24};
25use crate::values::specified::ViewportVariant;
26use crate::values::KeyframesName;
27use app_units::{Au, AU_PER_PX};
28use euclid::default::Size2D as UntypedSize2D;
29use euclid::{Scale, SideOffsets2D, Size2D};
30use mime::Mime;
31use servo_arc::Arc;
32use std::fmt::Debug;
33use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
34use style_traits::{CSSPixel, DevicePixel};
35
36/// A trait used to query font metrics in clients of Stylo. This is used by Device to
37/// query font metrics in a way that is specific to the client using Stylo.
38pub trait FontMetricsProvider: Debug + Sync {
39    /// Query the font metrics for the given font and the given base font size.
40    fn query_font_metrics(
41        &self,
42        vertical: bool,
43        font: &Font,
44        base_size: CSSPixelLength,
45        flags: QueryFontMetricsFlags,
46    ) -> FontMetrics;
47    /// Gets the base size given a generic font family.
48    fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length;
49}
50
51/// A device is a structure that represents the current media a given document
52/// is displayed in.
53///
54/// This is the struct against which media queries are evaluated.
55#[derive(Debug, MallocSizeOf)]
56pub struct Device {
57    /// The current media type used by de device.
58    media_type: MediaType,
59    /// The current viewport size, in CSS pixels.
60    viewport_size: Size2D<f32, CSSPixel>,
61    /// The current device pixel ratio, from CSS pixels to device pixels.
62    device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
63    /// The current quirks mode.
64    #[ignore_malloc_size_of = "Pure stack type"]
65    quirks_mode: QuirksMode,
66
67    /// The font size of the root element
68    /// This is set when computing the style of the root
69    /// element, and used for rem units in other elements
70    ///
71    /// When computing the style of the root element, there can't be any
72    /// other style being computed at the same time, given we need the style of
73    /// the parent to compute everything else. So it is correct to just use
74    /// a relaxed atomic here.
75    #[ignore_malloc_size_of = "Pure stack type"]
76    root_font_size: AtomicU32,
77    /// Line height of the root element, used for rlh units in other elements.
78    #[ignore_malloc_size_of = "Pure stack type"]
79    root_line_height: AtomicU32,
80    /// Whether any styles computed in the document relied on the root font-size
81    /// by using rem units.
82    #[ignore_malloc_size_of = "Pure stack type"]
83    used_root_font_size: AtomicBool,
84    /// Whether any styles computed in the document relied on the root line-height
85    /// by using rlh units.
86    #[ignore_malloc_size_of = "Pure stack type"]
87    used_root_line_height: AtomicBool,
88    /// Whether any styles computed in the document relied on font metrics.
89    used_font_metrics: AtomicBool,
90    /// Whether any styles computed in the document relied on the viewport size.
91    #[ignore_malloc_size_of = "Pure stack type"]
92    used_viewport_units: AtomicBool,
93    /// Whether the user prefers light mode or dark mode
94    #[ignore_malloc_size_of = "Pure stack type"]
95    prefers_color_scheme: PrefersColorScheme,
96    /// The CssEnvironment object responsible of getting CSS environment
97    /// variables.
98    environment: CssEnvironment,
99    /// An implementation of a trait which implements support for querying font metrics.
100    #[ignore_malloc_size_of = "Owned by embedder"]
101    font_metrics_provider: Box<dyn FontMetricsProvider>,
102    /// The default computed values for this Device.
103    #[ignore_malloc_size_of = "Arc is shared"]
104    default_computed_values: Arc<ComputedValues>,
105}
106
107impl Device {
108    /// Trivially construct a new `Device`.
109    pub fn new(
110        media_type: MediaType,
111        quirks_mode: QuirksMode,
112        viewport_size: Size2D<f32, CSSPixel>,
113        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
114        font_metrics_provider: Box<dyn FontMetricsProvider>,
115        default_computed_values: Arc<ComputedValues>,
116        prefers_color_scheme: PrefersColorScheme,
117    ) -> Device {
118        Device {
119            media_type,
120            viewport_size,
121            device_pixel_ratio,
122            quirks_mode,
123            root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
124            root_line_height: AtomicU32::new(FONT_MEDIUM_LINE_HEIGHT_PX.to_bits()),
125            used_root_font_size: AtomicBool::new(false),
126            used_root_line_height: AtomicBool::new(false),
127            used_font_metrics: AtomicBool::new(false),
128            used_viewport_units: AtomicBool::new(false),
129            prefers_color_scheme,
130            environment: CssEnvironment,
131            font_metrics_provider,
132            default_computed_values,
133        }
134    }
135
136    /// Get the relevant environment to resolve `env()` functions.
137    #[inline]
138    pub fn environment(&self) -> &CssEnvironment {
139        &self.environment
140    }
141
142    /// Return the default computed values for this device.
143    pub fn default_computed_values(&self) -> &ComputedValues {
144        &self.default_computed_values
145    }
146
147    /// Get the font size of the root element (for rem)
148    pub fn root_font_size(&self) -> CSSPixelLength {
149        self.used_root_font_size.store(true, Ordering::Relaxed);
150        CSSPixelLength::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed)))
151    }
152
153    /// Set the font size of the root element (for rem), in zoom-independent CSS pixels.
154    pub fn set_root_font_size(&self, size: f32) {
155        self.root_font_size.store(size.to_bits(), Ordering::Relaxed)
156    }
157
158    /// Get the line height of the root element (for rlh)
159    pub fn root_line_height(&self) -> CSSPixelLength {
160        self.used_root_line_height.store(true, Ordering::Relaxed);
161        CSSPixelLength::new(f32::from_bits(
162            self.root_line_height.load(Ordering::Relaxed),
163        ))
164    }
165
166    /// Set the line height of the root element (for rlh), in zoom-independent CSS pixels.
167    pub fn set_root_line_height(&self, size: f32) {
168        self.root_line_height
169            .store(size.to_bits(), Ordering::Relaxed);
170    }
171
172    /// Returns the computed line-height for the font in a given computed values instance.
173    ///
174    /// If you pass down an element, then the used line-height is returned.
175    pub fn calc_line_height(
176        &self,
177        font: &crate::properties::style_structs::Font,
178        _writing_mode: WritingMode,
179        _element: Option<()>,
180    ) -> NonNegativeLength {
181        (match font.line_height {
182            // TODO: compute `normal` from the font metrics
183            LineHeight::Normal => CSSPixelLength::new(0.),
184            LineHeight::Number(number) => font.font_size.computed_size() * number.0,
185            LineHeight::Length(length) => length.0,
186        })
187        .into()
188    }
189
190    /// Get the quirks mode of the current device.
191    pub fn quirks_mode(&self) -> QuirksMode {
192        self.quirks_mode
193    }
194
195    /// Sets the body text color for the "inherit color from body" quirk.
196    ///
197    /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
198    pub fn set_body_text_color(&self, _color: AbsoluteColor) {
199        // Servo doesn't implement this quirk (yet)
200    }
201
202    /// Gets the base size given a generic font family.
203    pub fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length {
204        self.font_metrics_provider.base_size_for_generic(generic)
205    }
206
207    /// Whether a given animation name may be referenced from style.
208    pub fn animation_name_may_be_referenced(&self, _: &KeyframesName) -> bool {
209        // Assume it is, since we don't have any good way to prove it's not.
210        true
211    }
212
213    /// Returns whether we ever looked up the root font size of the Device.
214    pub fn used_root_font_size(&self) -> bool {
215        self.used_root_font_size.load(Ordering::Relaxed)
216    }
217
218    /// Returns whether we ever looked up the root line-height of the device.
219    pub fn used_root_line_height(&self) -> bool {
220        self.used_root_line_height.load(Ordering::Relaxed)
221    }
222
223    /// Returns whether font metrics have been queried.
224    pub fn used_font_metrics(&self) -> bool {
225        self.used_font_metrics.load(Ordering::Relaxed)
226    }
227
228    /// Get the viewport size on this [`Device`].
229    pub fn viewport_size(&self) -> Size2D<f32, CSSPixel> {
230        self.viewport_size
231    }
232
233    /// Set the viewport size on this [`Device`].
234    ///
235    /// Note that this does not update any associated `Stylist`. For this you must call
236    /// `Stylist::media_features_change_changed_style` and
237    /// `Stylist::force_stylesheet_origins_dirty`.
238    pub fn set_viewport_size(&mut self, viewport_size: Size2D<f32, CSSPixel>) {
239        self.viewport_size = viewport_size;
240    }
241
242    /// Returns the viewport size of the current device in app units, needed,
243    /// among other things, to resolve viewport units.
244    #[inline]
245    pub fn au_viewport_size(&self) -> UntypedSize2D<Au> {
246        Size2D::new(
247            Au::from_f32_px(self.viewport_size.width),
248            Au::from_f32_px(self.viewport_size.height),
249        )
250    }
251
252    /// Like the above, but records that we've used viewport units.
253    pub fn au_viewport_size_for_viewport_unit_resolution(
254        &self,
255        _: ViewportVariant,
256    ) -> UntypedSize2D<Au> {
257        self.used_viewport_units.store(true, Ordering::Relaxed);
258        // Servo doesn't have dynamic UA interfaces that affect the viewport,
259        // so we can just ignore the ViewportVariant.
260        self.au_viewport_size()
261    }
262
263    /// Whether viewport units were used since the last device change.
264    pub fn used_viewport_units(&self) -> bool {
265        self.used_viewport_units.load(Ordering::Relaxed)
266    }
267
268    /// Returns the number of app units per device pixel we're using currently.
269    pub fn app_units_per_device_pixel(&self) -> i32 {
270        (AU_PER_PX as f32 / self.device_pixel_ratio.0) as i32
271    }
272
273    /// Returns the device pixel ratio, ignoring the full zoom factor.
274    pub fn device_pixel_ratio_ignoring_full_zoom(&self) -> Scale<f32, CSSPixel, DevicePixel> {
275        self.device_pixel_ratio
276    }
277
278    /// Returns the device pixel ratio.
279    pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
280        self.device_pixel_ratio
281    }
282
283    /// Set a new device pixel ratio on this [`Device`].
284    ///
285    /// Note that this does not update any associated `Stylist`. For this you must call
286    /// `Stylist::media_features_change_changed_style` and
287    /// `Stylist::force_stylesheet_origins_dirty`.
288    pub fn set_device_pixel_ratio(
289        &mut self,
290        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
291    ) {
292        self.device_pixel_ratio = device_pixel_ratio;
293    }
294
295    /// Gets the size of the scrollbar in CSS pixels.
296    pub fn scrollbar_inline_size(&self) -> CSSPixelLength {
297        // TODO: implement this.
298        CSSPixelLength::new(0.0)
299    }
300
301    /// Queries font metrics using the [`FontMetricsProvider`] interface.
302    pub fn query_font_metrics(
303        &self,
304        vertical: bool,
305        font: &Font,
306        base_size: CSSPixelLength,
307        flags: QueryFontMetricsFlags,
308    ) -> FontMetrics {
309        self.used_font_metrics.store(true, Ordering::Relaxed);
310        self.font_metrics_provider
311            .query_font_metrics(vertical, font, base_size, flags)
312    }
313
314    /// Return the media type of the current device.
315    pub fn media_type(&self) -> MediaType {
316        self.media_type.clone()
317    }
318
319    /// Returns whether document colors are enabled.
320    pub fn forced_colors(&self) -> ForcedColors {
321        ForcedColors::None
322    }
323
324    /// Returns the default background color.
325    pub fn default_background_color(&self) -> AbsoluteColor {
326        AbsoluteColor::WHITE
327    }
328
329    /// Returns the default foreground color.
330    pub fn default_color(&self) -> AbsoluteColor {
331        AbsoluteColor::BLACK
332    }
333
334    /// Set the [`PrefersColorScheme`] value on this [`Device`].
335    ///
336    /// Note that this does not update any associated `Stylist`. For this you must call
337    /// `Stylist::media_features_change_changed_style` and
338    /// `Stylist::force_stylesheet_origins_dirty`.
339    pub fn set_color_scheme(&mut self, new_color_scheme: PrefersColorScheme) {
340        self.prefers_color_scheme = new_color_scheme;
341    }
342
343    /// Returns the color scheme of this [`Device`].
344    pub fn color_scheme(&self) -> PrefersColorScheme {
345        self.prefers_color_scheme
346    }
347
348    pub(crate) fn is_dark_color_scheme(&self, _: ColorSchemeFlags) -> bool {
349        false
350    }
351
352    /// Returns safe area insets
353    pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> {
354        SideOffsets2D::zero()
355    }
356
357    /// Returns true if the given MIME type is supported
358    pub fn is_supported_mime_type(&self, mime_type: &str) -> bool {
359        match mime_type.parse::<Mime>() {
360            Ok(m) => {
361                // Keep this in sync with 'image_classifer' from
362                // components/net/mime_classifier.rs
363                m == mime::IMAGE_BMP
364                    || m == mime::IMAGE_GIF
365                    || m == mime::IMAGE_PNG
366                    || m == mime::IMAGE_JPEG
367                    || m == "image/x-icon"
368                    || m == "image/webp"
369            },
370            _ => false,
371        }
372    }
373
374    /// Return whether the document is a chrome document.
375    #[inline]
376    pub fn chrome_rules_enabled_for_document(&self) -> bool {
377        false
378    }
379}
380
381/// https://drafts.csswg.org/mediaqueries-4/#width
382fn eval_width(context: &Context) -> CSSPixelLength {
383    CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
384}
385
386#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
387#[repr(u8)]
388enum Scan {
389    Progressive,
390    Interlace,
391}
392
393/// https://drafts.csswg.org/mediaqueries-4/#scan
394fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
395    // Since we doesn't support the 'tv' media type, the 'scan' feature never
396    // matches.
397    false
398}
399
400/// https://drafts.csswg.org/mediaqueries-4/#resolution
401fn eval_resolution(context: &Context) -> Resolution {
402    Resolution::from_dppx(context.device().device_pixel_ratio.0)
403}
404
405/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
406fn eval_device_pixel_ratio(context: &Context) -> f32 {
407    eval_resolution(context).dppx()
408}
409
410fn eval_prefers_color_scheme(context: &Context, query_value: Option<PrefersColorScheme>) -> bool {
411    match query_value {
412        Some(v) => context.device().prefers_color_scheme == v,
413        None => true,
414    }
415}
416
417/// A list with all the media features that Servo supports.
418pub static MEDIA_FEATURES: [QueryFeatureDescription; 6] = [
419    feature!(
420        atom!("width"),
421        AllowsRanges::Yes,
422        Evaluator::Length(eval_width),
423        FeatureFlags::empty(),
424    ),
425    feature!(
426        atom!("scan"),
427        AllowsRanges::No,
428        keyword_evaluator!(eval_scan, Scan),
429        FeatureFlags::empty(),
430    ),
431    feature!(
432        atom!("resolution"),
433        AllowsRanges::Yes,
434        Evaluator::Resolution(eval_resolution),
435        FeatureFlags::empty(),
436    ),
437    feature!(
438        atom!("device-pixel-ratio"),
439        AllowsRanges::Yes,
440        Evaluator::Float(eval_device_pixel_ratio),
441        FeatureFlags::WEBKIT_PREFIX,
442    ),
443    feature!(
444        atom!("-moz-device-pixel-ratio"),
445        AllowsRanges::Yes,
446        Evaluator::Float(eval_device_pixel_ratio),
447        FeatureFlags::empty(),
448    ),
449    feature!(
450        atom!("prefers-color-scheme"),
451        AllowsRanges::No,
452        keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
453        FeatureFlags::empty(),
454    ),
455];