Skip to main content

style/device/
servo.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-specific logic for [`Device`].
6
7use crate::color::AbsoluteColor;
8use crate::context::QuirksMode;
9use crate::substitution::env::CssEnvironment;
10use crate::font_metrics::FontMetrics;
11use crate::logical_geometry::WritingMode;
12use crate::media_queries::MediaType;
13use crate::properties::ComputedValues;
14use crate::properties::style_structs::Font;
15use crate::queries::values::PrefersColorScheme;
16use crate::servo::media_features::PointerCapabilities;
17use crate::values::KeyframesName;
18use crate::values::computed::font::GenericFontFamily;
19use crate::values::computed::{
20    CSSPixelLength, Length, LineHeight, LinkParameters, NonNegativeLength,
21};
22use crate::values::specified::ViewportVariant;
23use crate::values::specified::color::{ColorSchemeFlags, ForcedColors, SystemColor};
24use crate::values::specified::font::{
25    FONT_MEDIUM_CAP_PX, FONT_MEDIUM_CH_PX, FONT_MEDIUM_EX_PX, FONT_MEDIUM_IC_PX,
26    FONT_MEDIUM_LINE_HEIGHT_PX, FONT_MEDIUM_PX, QueryFontMetricsFlags,
27};
28use app_units::{AU_PER_PX, Au};
29use euclid::default::Size2D as UntypedSize2D;
30use euclid::{Scale, SideOffsets2D, Size2D};
31use malloc_size_of_derive::MallocSizeOf;
32use mime::Mime;
33use parking_lot::RwLock;
34use servo_arc::Arc;
35use std::fmt::Debug;
36use std::hash::{DefaultHasher, Hasher};
37use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
38use style_traits::{CSSPixel, DevicePixel};
39
40use crate::device::Device;
41
42/// A trait used to query font metrics in clients of Stylo. This is used by Device to
43/// query font metrics in a way that is specific to the client using Stylo.
44pub trait FontMetricsProvider: Debug + Sync {
45    /// Query the font metrics for the given font and the given base font size.
46    fn query_font_metrics(
47        &self,
48        vertical: bool,
49        font: &Font,
50        base_size: CSSPixelLength,
51        flags: QueryFontMetricsFlags,
52    ) -> FontMetrics;
53    /// Gets the base size given a generic font family.
54    fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length;
55}
56
57#[derive(Debug, MallocSizeOf)]
58pub(super) struct ExtraDeviceData {
59    /// Data which Stylo uses to evaluate media queries
60    media_data: MediaData,
61    /// An implementation of a trait which implements support for querying font metrics.
62    #[ignore_malloc_size_of = "Owned by embedder"]
63    font_metrics_provider: Box<dyn FontMetricsProvider>,
64}
65
66#[derive(Debug, MallocSizeOf, Clone, PartialEq)]
67/// Data which Stylo uses to evaluate media queries
68pub struct MediaData {
69    /// The current media type used by de device.
70    pub media_type: MediaType,
71    /// The current viewport size, in CSS pixels.
72    pub viewport_size: Size2D<f32, CSSPixel>,
73    /// The current screen size, in device pixels.
74    pub device_size: Size2D<f32, DevicePixel>,
75    /// The current device pixel ratio, from CSS pixels to device pixels.
76    pub device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
77    /// The current quirks mode.
78    #[ignore_malloc_size_of = "Pure stack type"]
79    pub quirks_mode: QuirksMode,
80    /// Whether the user prefers light mode or dark mode
81    #[ignore_malloc_size_of = "Pure stack type"]
82    pub prefers_color_scheme: PrefersColorScheme,
83    /// The capabilities of the primary pointer input
84    #[ignore_malloc_size_of = "Pure stack type"]
85    pub primary_pointer_capabilities: PointerCapabilities,
86    /// The union of the capabilities of all pointer inputs
87    #[ignore_malloc_size_of = "Pure stack type"]
88    pub all_pointer_capabilities: PointerCapabilities,
89}
90
91impl Device {
92    /// Trivially construct a new `Device`.
93    pub fn new(
94        default_values: Arc<ComputedValues>,
95        font_metrics_provider: Box<dyn FontMetricsProvider>,
96        submit_string: Box<str>,
97        media_data: MediaData,
98    ) -> Device {
99        let root_style = RwLock::new(Arc::clone(&default_values));
100        Device {
101            root_style,
102            root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
103            root_line_height: AtomicU32::new(FONT_MEDIUM_LINE_HEIGHT_PX.to_bits()),
104            root_font_metrics_ex: AtomicU32::new(FONT_MEDIUM_EX_PX.to_bits()),
105            root_font_metrics_cap: AtomicU32::new(FONT_MEDIUM_CAP_PX.to_bits()),
106            root_font_metrics_ch: AtomicU32::new(FONT_MEDIUM_CH_PX.to_bits()),
107            root_font_metrics_ic: AtomicU32::new(FONT_MEDIUM_IC_PX.to_bits()),
108            used_root_font_size: AtomicBool::new(false),
109            used_root_line_height: AtomicBool::new(false),
110            used_root_font_metrics: RwLock::new(false),
111            used_font_metrics: AtomicBool::new(false),
112            used_viewport_size: AtomicBool::new(false),
113            used_dynamic_viewport_size: AtomicBool::new(false),
114            environment: CssEnvironment,
115            default_values,
116            body_text_color: RwLock::new(AbsoluteColor::BLACK),
117            submit_string,
118            extra: ExtraDeviceData {
119                media_data,
120                font_metrics_provider,
121            },
122        }
123    }
124
125    /// Returns the computed line-height for the font in a given computed values instance.
126    ///
127    /// If you pass down an element, then the used line-height is returned.
128    pub fn calc_line_height(
129        &self,
130        font: &crate::properties::style_structs::Font,
131        _writing_mode: WritingMode,
132        _element: Option<()>,
133    ) -> NonNegativeLength {
134        (match font.line_height {
135            // TODO: compute `normal` from the font metrics
136            LineHeight::Normal => CSSPixelLength::new(0.),
137            LineHeight::Number(number) => font.font_size.computed_size() * number.0,
138            LineHeight::Length(length) => length.0,
139        })
140        .into()
141    }
142
143    /// Get the quirks mode of the current device.
144    pub fn quirks_mode(&self) -> QuirksMode {
145        self.extra.media_data.quirks_mode
146    }
147
148    /// Returns a hasher specific to this document, used to vary the random
149    /// base values of `random()` functions between document instances.
150    #[inline]
151    pub fn document_random_hasher(&self) -> impl Hasher {
152        // TODO: Implement a document-specific hasher (like Gecko in Bug 2081781).
153        DefaultHasher::new()
154    }
155
156    /// Gets the base size given a generic font family.
157    pub fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length {
158        self.extra
159            .font_metrics_provider
160            .base_size_for_generic(generic)
161    }
162
163    /// Whether a given animation name may be referenced from style.
164    pub fn animation_name_may_be_referenced(&self, _: &KeyframesName) -> bool {
165        // Assume it is, since we don't have any good way to prove it's not.
166        true
167    }
168
169    /// Get the [`MediaData`] associated with this [`Device`].
170    #[inline]
171    pub fn media_data(&self) -> &MediaData {
172        &self.extra.media_data
173    }
174
175    /// Mutate the [`MediaData`] on this [`Device`].
176    ///
177    /// Note that this does not update any associated `Stylist`. For this you must call
178    /// `Stylist::media_features_change_changed_style` and
179    /// `Stylist::force_stylesheet_origins_dirty`.
180    pub fn media_data_mut(&mut self) -> &mut MediaData {
181        &mut self.extra.media_data
182    }
183
184    /// Set the [`MediaData`] on this [`Device`].
185    ///
186    /// Note that this does not update any associated `Stylist`. For this you must call
187    /// `Stylist::media_features_change_changed_style` and
188    /// `Stylist::force_stylesheet_origins_dirty`.
189    pub fn set_media_data(&mut self, media_data: MediaData) {
190        self.extra.media_data = media_data;
191    }
192
193    /// Get the viewport size on this [`Device`].
194    pub fn viewport_size(&self) -> Size2D<f32, CSSPixel> {
195        self.extra.media_data.viewport_size
196    }
197
198    /// Set the viewport size on this [`Device`].
199    ///
200    /// Note that this does not update any associated `Stylist`. For this you must call
201    /// `Stylist::media_features_change_changed_style` and
202    /// `Stylist::force_stylesheet_origins_dirty`.
203    pub fn set_viewport_size(&mut self, viewport_size: Size2D<f32, CSSPixel>) {
204        self.extra.media_data.viewport_size = viewport_size;
205    }
206
207    /// Returns the viewport size of the current device in app units, needed,
208    /// among other things, to resolve viewport units.
209    #[inline]
210    pub fn au_viewport_size(&self) -> UntypedSize2D<Au> {
211        Size2D::new(
212            Au::from_f32_px(self.extra.media_data.viewport_size.width),
213            Au::from_f32_px(self.extra.media_data.viewport_size.height),
214        )
215    }
216
217    /// Like the above, but records that we've used viewport units.
218    pub fn au_viewport_size_for_viewport_unit_resolution(
219        &self,
220        _: ViewportVariant,
221    ) -> UntypedSize2D<Au> {
222        self.used_viewport_size.store(true, Ordering::Relaxed);
223        // Servo doesn't have dynamic UA interfaces that affect the viewport,
224        // so we can just ignore the ViewportVariant.
225        self.au_viewport_size()
226    }
227
228    /// Returns the number of app units per device pixel we're using currently.
229    pub fn app_units_per_device_pixel(&self) -> i32 {
230        (AU_PER_PX as f32 / self.extra.media_data.device_pixel_ratio.0) as i32
231    }
232
233    /// Returns the device pixel ratio, ignoring the full zoom factor.
234    pub fn device_pixel_ratio_ignoring_full_zoom(&self) -> Scale<f32, CSSPixel, DevicePixel> {
235        self.extra.media_data.device_pixel_ratio
236    }
237
238    /// Returns the device pixel ratio.
239    pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
240        self.extra.media_data.device_pixel_ratio
241    }
242
243    /// Set a new device pixel ratio on this [`Device`].
244    ///
245    /// Note that this does not update any associated `Stylist`. For this you must call
246    /// `Stylist::media_features_change_changed_style` and
247    /// `Stylist::force_stylesheet_origins_dirty`.
248    pub fn set_device_pixel_ratio(
249        &mut self,
250        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
251    ) {
252        self.extra.media_data.device_pixel_ratio = device_pixel_ratio;
253    }
254
255    /// Set the device size on this [`Device`] (e.g. the available screen dimensions).
256    ///
257    /// Note that this does not update any associated `Stylist`. For this you must call
258    /// `Stylist::media_features_change_changed_style` and
259    /// `Stylist::force_stylesheet_origins_dirty`.
260    pub fn set_device_size(&mut self, device_size: Size2D<f32, DevicePixel>) {
261        self.extra.media_data.device_size = device_size;
262    }
263
264    /// Returns the screen size of the current device in app units.
265    #[inline]
266    pub fn device_size(&self) -> Size2D<f32, DevicePixel> {
267        self.extra.media_data.device_size
268    }
269
270    /// Gets the size of the scrollbar in CSS pixels.
271    pub fn scrollbar_inline_size(&self) -> CSSPixelLength {
272        // TODO: implement this.
273        CSSPixelLength::new(0.0)
274    }
275
276    /// Queries font metrics using the [`FontMetricsProvider`] interface.
277    pub fn query_font_metrics(
278        &self,
279        vertical: bool,
280        font: &Font,
281        base_size: CSSPixelLength,
282        flags: QueryFontMetricsFlags,
283        track_usage: bool,
284    ) -> FontMetrics {
285        if track_usage {
286            self.used_font_metrics.store(true, Ordering::Relaxed);
287        }
288        self.extra
289            .font_metrics_provider
290            .query_font_metrics(vertical, font, base_size, flags)
291    }
292
293    /// Set the media type on this [`Device`].
294    ///
295    /// Note that this does not update any associated `Stylist`. For this you must call
296    /// `Stylist::media_features_change_changed_style` and
297    /// `Stylist::force_stylesheet_origins_dirty`.
298    pub fn set_media_type(&mut self, media_type: MediaType) {
299        self.extra.media_data.media_type = media_type;
300    }
301
302    /// Return the media type of the current device.
303    pub fn media_type(&self) -> MediaType {
304        self.extra.media_data.media_type.clone()
305    }
306
307    /// Returns whether document colors are enabled.
308    pub fn forced_colors(&self) -> ForcedColors {
309        ForcedColors::None
310    }
311
312    /// Returns the default background color.
313    pub fn default_background_color(&self) -> AbsoluteColor {
314        AbsoluteColor::WHITE
315    }
316
317    /// Returns the default foreground color.
318    pub fn default_color(&self) -> AbsoluteColor {
319        AbsoluteColor::BLACK
320    }
321
322    /// Set the [`PrefersColorScheme`] value on this [`Device`].
323    ///
324    /// Note that this does not update any associated `Stylist`. For this you must call
325    /// `Stylist::media_features_change_changed_style` and
326    /// `Stylist::force_stylesheet_origins_dirty`.
327    pub fn set_color_scheme(&mut self, new_color_scheme: PrefersColorScheme) {
328        self.extra.media_data.prefers_color_scheme = new_color_scheme;
329    }
330
331    /// Returns the color scheme of this [`Device`].
332    pub fn color_scheme(&self) -> PrefersColorScheme {
333        self.extra.media_data.prefers_color_scheme
334    }
335
336    /// Set the [`PointerCapbabilities`] value for the primary pointer on this [`Device`]
337    ///
338    /// Note that this does not update any associated `Stylist`. For this you must call
339    /// `Stylist::media_features_change_changed_style` and
340    /// `Stylist::force_stylesheet_origins_dirty`.
341    pub fn set_primary_pointer_capabilities(&mut self, capabilities: PointerCapabilities) {
342        self.extra.media_data.primary_pointer_capabilities = capabilities;
343    }
344
345    /// Returns the pointer capabilities of this [`Device`].
346    pub fn primary_pointer_capabilities(&self) -> PointerCapabilities {
347        self.extra.media_data.primary_pointer_capabilities
348    }
349
350    /// Set the [`PointerCapbabilities`] value for all pointers on this [`Device`]
351    ///
352    /// Note that this does not update any associated `Stylist`. For this you must call
353    /// `Stylist::media_features_change_changed_style` and
354    /// `Stylist::force_stylesheet_origins_dirty`.
355    pub fn set_all_pointer_capabilities(&mut self, capabilities: PointerCapabilities) {
356        self.extra.media_data.all_pointer_capabilities = capabilities;
357    }
358
359    /// Returns the pointer capabilities of this [`Device`].
360    pub fn all_pointer_capabilities(&self) -> PointerCapabilities {
361        self.extra.media_data.all_pointer_capabilities
362    }
363
364    pub(crate) fn is_dark_color_scheme(&self, color_scheme_flags: ColorSchemeFlags) -> bool {
365        // Inspired by
366        // https://searchfox.org/firefox-main/rev/0a7f146ccac85b8f413264042dcd764028d419ec/widget/nsXPLookAndFeel.cpp#1296
367        let supports_dark_mode = color_scheme_flags.contains(ColorSchemeFlags::DARK);
368        let supports_light_mode = color_scheme_flags.contains(ColorSchemeFlags::LIGHT);
369
370        // If only one is supported, then use dark mode if it was the supported one.
371        if supports_dark_mode != supports_light_mode {
372            return supports_dark_mode;
373        }
374
375        // If either both or none are supported, then use the preferred color scheme
376        // to determine whether the user wants dark mode.
377        return self.color_scheme() == PrefersColorScheme::Dark;
378    }
379
380    pub(crate) fn system_color(
381        &self,
382        system_color: SystemColor,
383        color_scheme_flags: ColorSchemeFlags,
384    ) -> AbsoluteColor {
385        fn srgb(r: u8, g: u8, b: u8) -> AbsoluteColor {
386            AbsoluteColor::srgb_legacy(r, g, b, 1f32)
387        }
388
389        // Refer to spec
390        // <https://www.w3.org/TR/css-color-4/#css-system-colors>
391        if self.is_dark_color_scheme(color_scheme_flags) {
392            match system_color {
393                SystemColor::Accentcolor => srgb(10, 132, 255),
394                SystemColor::Accentcolortext => srgb(255, 255, 255),
395                SystemColor::Activetext => srgb(255, 0, 0),
396                SystemColor::Linktext => srgb(158, 158, 255),
397                SystemColor::Visitedtext => srgb(208, 173, 240),
398                SystemColor::Buttonborder
399                // Deprecated system colors (CSS Color 4) mapped to Buttonborder.
400                | SystemColor::Activeborder
401                | SystemColor::Inactiveborder
402                | SystemColor::Threeddarkshadow
403                | SystemColor::Threedshadow
404                | SystemColor::Windowframe => srgb(255, 255, 255),
405                SystemColor::Buttonface
406                // Deprecated system colors (CSS Color 4) mapped to Buttonface.
407                | SystemColor::Buttonhighlight
408                | SystemColor::Buttonshadow
409                | SystemColor::Threedface
410                | SystemColor::Threedhighlight
411                | SystemColor::Threedlightshadow => srgb(107, 107, 107),
412                SystemColor::Buttontext => srgb(245, 245, 245),
413                SystemColor::Canvas
414                // Deprecated system colors (CSS Color 4) mapped to Canvas.
415                | SystemColor::Activecaption
416                | SystemColor::Appworkspace
417                | SystemColor::Background
418                | SystemColor::Inactivecaption
419                | SystemColor::Infobackground
420                | SystemColor::Menu
421                | SystemColor::Scrollbar
422                | SystemColor::Window => srgb(30, 30, 30),
423                SystemColor::Canvastext
424                // Deprecated system colors (CSS Color 4) mapped to Canvastext.
425                | SystemColor::Captiontext
426                | SystemColor::Infotext
427                | SystemColor::Menutext
428                | SystemColor::Windowtext => srgb(232, 232, 232),
429                SystemColor::Field => srgb(45, 45, 45),
430                SystemColor::Fieldtext => srgb(240, 240, 240),
431                SystemColor::Graytext
432                // Deprecated system colors (CSS Color 4) mapped to Graytext.
433                | SystemColor::Inactivecaptiontext => srgb(155, 155, 155),
434                SystemColor::Highlight => srgb(38, 79, 120),
435                SystemColor::Highlighttext => srgb(255, 255, 255),
436                SystemColor::Mark => srgb(102, 92, 0),
437                SystemColor::Marktext => srgb(255, 255, 255),
438                SystemColor::Selecteditem => srgb(153, 200, 255),
439                SystemColor::Selecteditemtext => srgb(59, 59, 59),
440            }
441        } else {
442            match system_color {
443                SystemColor::Accentcolor => srgb(0, 102, 204),
444                SystemColor::Accentcolortext => srgb(255, 255, 255),
445                SystemColor::Activetext => srgb(238, 0, 0),
446                SystemColor::Linktext => srgb(0, 0, 238),
447                SystemColor::Visitedtext => srgb(85, 26, 139),
448                SystemColor::Buttonborder
449                // Deprecated system colors (CSS Color 4) mapped to Buttonborder.
450                | SystemColor::Activeborder
451                | SystemColor::Inactiveborder
452                | SystemColor::Threeddarkshadow
453                | SystemColor::Threedshadow
454                | SystemColor::Windowframe => srgb(169, 169, 169),
455                SystemColor::Buttonface
456                // Deprecated system colors (CSS Color 4) mapped to Buttonface.
457                | SystemColor::Buttonhighlight
458                | SystemColor::Buttonshadow
459                | SystemColor::Threedface
460                | SystemColor::Threedhighlight
461                | SystemColor::Threedlightshadow => srgb(220, 220, 220),
462                SystemColor::Buttontext => srgb(0, 0, 0),
463                SystemColor::Canvas
464                // Deprecated system colors (CSS Color 4) mapped to Canvas.
465                | SystemColor::Activecaption
466                | SystemColor::Appworkspace
467                | SystemColor::Background
468                | SystemColor::Inactivecaption
469                | SystemColor::Infobackground
470                | SystemColor::Menu
471                | SystemColor::Scrollbar
472                | SystemColor::Window => srgb(255, 255, 255),
473                SystemColor::Canvastext
474                // Deprecated system colors (CSS Color 4) mapped to Canvastext.
475                | SystemColor::Captiontext
476                | SystemColor::Infotext
477                | SystemColor::Menutext
478                | SystemColor::Windowtext => srgb(0, 0, 0),
479                SystemColor::Field => srgb(255, 255, 255),
480                SystemColor::Fieldtext => srgb(0, 0, 0),
481                SystemColor::Graytext
482                // Deprecated system colors (CSS Color 4) mapped to Graytext.
483                | SystemColor::Inactivecaptiontext => srgb(109, 109, 109),
484                SystemColor::Highlight => srgb(0, 65, 198),
485                SystemColor::Highlighttext => srgb(0, 0, 0),
486                SystemColor::Mark => srgb(255, 235, 59),
487                SystemColor::Marktext => srgb(0, 0, 0),
488                SystemColor::Selecteditem => srgb(0, 102, 204),
489                SystemColor::Selecteditemtext => srgb(255, 255, 255),
490            }
491        }
492    }
493
494    /// Returns the current effective text zoom.
495    #[inline]
496    pub(super) fn text_zoom(&self) -> f32 {
497        // (Servo doesn't do text-zoom)
498        1.
499    }
500
501    /// Returns safe area insets
502    pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> {
503        SideOffsets2D::zero()
504    }
505
506    /// Returns true if the given MIME type is supported
507    pub fn is_supported_mime_type(&self, mime_type: &str) -> bool {
508        match mime_type.parse::<Mime>() {
509            Ok(m) => {
510                // Keep this in sync with 'image_classifer' from
511                // components/net/mime_classifier.rs
512                m == mime::IMAGE_BMP
513                    || m == mime::IMAGE_GIF
514                    || m == mime::IMAGE_PNG
515                    || m == mime::IMAGE_JPEG
516                    || m == "image/x-icon"
517                    || m == "image/webp"
518            },
519            _ => false,
520        }
521    }
522
523    /// Return whether the document is a chrome document.
524    #[inline]
525    pub fn chrome_rules_enabled_for_document(&self) -> bool {
526        false
527    }
528
529    /// Returns the link-parameters that have been set for this document.
530    /// <https://drafts.csswg.org/css-link-params-1/>
531    #[inline]
532    pub fn link_parameters(&self) -> Option<&LinkParameters> {
533        None
534    }
535}