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-query device and expression representation.
6 
7 use crate::context::QuirksMode;
8 use crate::custom_properties::CssEnvironment;
9 use crate::gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
10 use crate::gecko_bindings::bindings;
11 use crate::gecko_bindings::structs;
12 use crate::media_queries::MediaType;
13 use crate::properties::ComputedValues;
14 use crate::string_cache::Atom;
15 use crate::values::computed::Length;
16 use crate::values::specified::font::FONT_MEDIUM_PX;
17 use crate::values::{CustomIdent, KeyframesName};
18 use app_units::{Au, AU_PER_PX};
19 use cssparser::RGBA;
20 use euclid::default::Size2D;
21 use euclid::{Scale, SideOffsets2D};
22 use servo_arc::Arc;
23 use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
24 use std::{cmp, fmt};
25 use style_traits::viewport::ViewportConstraints;
26 use style_traits::{CSSPixel, DevicePixel};
27 
28 /// The `Device` in Gecko wraps a pres context, has a default values computed,
29 /// and contains all the viewport rule state.
30 pub struct Device {
31     /// NB: The document owns the styleset, who owns the stylist, and thus the
32     /// `Device`, so having a raw document pointer here is fine.
33     document: *const structs::Document,
34     default_values: Arc<ComputedValues>,
35     /// The font size of the root element.
36     ///
37     /// This is set when computing the style of the root element, and used for
38     /// rem units in other elements.
39     ///
40     /// When computing the style of the root element, there can't be any other
41     /// style being computed at the same time, given we need the style of the
42     /// parent to compute everything else. So it is correct to just use a
43     /// relaxed atomic here.
44     root_font_size: AtomicU32,
45     /// The body text color, stored as an `nscolor`, used for the "tables
46     /// inherit from body" quirk.
47     ///
48     /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
49     body_text_color: AtomicUsize,
50     /// Whether any styles computed in the document relied on the root font-size
51     /// by using rem units.
52     used_root_font_size: AtomicBool,
53     /// Whether any styles computed in the document relied on the viewport size
54     /// by using vw/vh/vmin/vmax units.
55     used_viewport_size: AtomicBool,
56     /// The CssEnvironment object responsible of getting CSS environment
57     /// variables.
58     environment: CssEnvironment,
59 }
60 
61 impl fmt::Debug for Device {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result62     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63         use nsstring::nsCString;
64 
65         let mut doc_uri = nsCString::new();
66         unsafe {
67             bindings::Gecko_nsIURI_Debug(
68                 (*self.document()).mDocumentURI.raw::<structs::nsIURI>(),
69                 &mut doc_uri,
70             )
71         };
72 
73         f.debug_struct("Device")
74             .field("document_url", &doc_uri)
75             .finish()
76     }
77 }
78 
79 unsafe impl Sync for Device {}
80 unsafe impl Send for Device {}
81 
82 impl Device {
83     /// Trivially constructs a new `Device`.
new(document: *const structs::Document) -> Self84     pub fn new(document: *const structs::Document) -> Self {
85         assert!(!document.is_null());
86         let doc = unsafe { &*document };
87         let prefs = unsafe { &*bindings::Gecko_GetPrefSheetPrefs(doc) };
88         Device {
89             document,
90             default_values: ComputedValues::default_values(doc),
91             root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
92             body_text_color: AtomicUsize::new(prefs.mDefaultColor as usize),
93             used_root_font_size: AtomicBool::new(false),
94             used_viewport_size: AtomicBool::new(false),
95             environment: CssEnvironment,
96         }
97     }
98 
99     /// Get the relevant environment to resolve `env()` functions.
100     #[inline]
environment(&self) -> &CssEnvironment101     pub fn environment(&self) -> &CssEnvironment {
102         &self.environment
103     }
104 
105     /// Tells the device that a new viewport rule has been found, and stores the
106     /// relevant viewport constraints.
account_for_viewport_rule(&mut self, _constraints: &ViewportConstraints)107     pub fn account_for_viewport_rule(&mut self, _constraints: &ViewportConstraints) {
108         unreachable!("Gecko doesn't support @viewport");
109     }
110 
111     /// Whether any animation name may be referenced from the style of any
112     /// element.
animation_name_may_be_referenced(&self, name: &KeyframesName) -> bool113     pub fn animation_name_may_be_referenced(&self, name: &KeyframesName) -> bool {
114         let pc = match self.pres_context() {
115             Some(pc) => pc,
116             None => return false,
117         };
118 
119         unsafe {
120             bindings::Gecko_AnimationNameMayBeReferencedFromStyle(pc, name.as_atom().as_ptr())
121         }
122     }
123 
124     /// Returns the default computed values as a reference, in order to match
125     /// Servo.
default_computed_values(&self) -> &ComputedValues126     pub fn default_computed_values(&self) -> &ComputedValues {
127         &self.default_values
128     }
129 
130     /// Returns the default computed values as an `Arc`.
default_computed_values_arc(&self) -> &Arc<ComputedValues>131     pub fn default_computed_values_arc(&self) -> &Arc<ComputedValues> {
132         &self.default_values
133     }
134 
135     /// Get the font size of the root element (for rem)
root_font_size(&self) -> Length136     pub fn root_font_size(&self) -> Length {
137         self.used_root_font_size.store(true, Ordering::Relaxed);
138         Length::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed)))
139     }
140 
141     /// Set the font size of the root element (for rem)
set_root_font_size(&self, size: Length)142     pub fn set_root_font_size(&self, size: Length) {
143         self.root_font_size
144             .store(size.px().to_bits(), Ordering::Relaxed)
145     }
146 
147     /// The quirks mode of the document.
quirks_mode(&self) -> QuirksMode148     pub fn quirks_mode(&self) -> QuirksMode {
149         self.document().mCompatMode.into()
150     }
151 
152     /// Sets the body text color for the "inherit color from body" quirk.
153     ///
154     /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
set_body_text_color(&self, color: RGBA)155     pub fn set_body_text_color(&self, color: RGBA) {
156         self.body_text_color
157             .store(convert_rgba_to_nscolor(&color) as usize, Ordering::Relaxed)
158     }
159 
160     /// Returns the body text color.
body_text_color(&self) -> RGBA161     pub fn body_text_color(&self) -> RGBA {
162         convert_nscolor_to_rgba(self.body_text_color.load(Ordering::Relaxed) as u32)
163     }
164 
165     /// Gets the document pointer.
166     #[inline]
document(&self) -> &structs::Document167     pub fn document(&self) -> &structs::Document {
168         unsafe { &*self.document }
169     }
170 
171     /// Gets the pres context associated with this document.
172     #[inline]
pres_context(&self) -> Option<&structs::nsPresContext>173     pub fn pres_context(&self) -> Option<&structs::nsPresContext> {
174         unsafe {
175             self.document()
176                 .mPresShell
177                 .as_ref()?
178                 .mPresContext
179                 .mRawPtr
180                 .as_ref()
181         }
182     }
183 
184     /// Gets the preference stylesheet prefs for our document.
185     #[inline]
pref_sheet_prefs(&self) -> &structs::PreferenceSheet_Prefs186     pub fn pref_sheet_prefs(&self) -> &structs::PreferenceSheet_Prefs {
187         unsafe { &*bindings::Gecko_GetPrefSheetPrefs(self.document()) }
188     }
189 
190     /// Recreates the default computed values.
reset_computed_values(&mut self)191     pub fn reset_computed_values(&mut self) {
192         self.default_values = ComputedValues::default_values(self.document());
193     }
194 
195     /// Rebuild all the cached data.
rebuild_cached_data(&mut self)196     pub fn rebuild_cached_data(&mut self) {
197         self.reset_computed_values();
198         self.used_root_font_size.store(false, Ordering::Relaxed);
199         self.used_viewport_size.store(false, Ordering::Relaxed);
200     }
201 
202     /// Returns whether we ever looked up the root font size of the Device.
used_root_font_size(&self) -> bool203     pub fn used_root_font_size(&self) -> bool {
204         self.used_root_font_size.load(Ordering::Relaxed)
205     }
206 
207     /// Recreates all the temporary state that the `Device` stores.
208     ///
209     /// This includes the viewport override from `@viewport` rules, and also the
210     /// default computed values.
reset(&mut self)211     pub fn reset(&mut self) {
212         self.reset_computed_values();
213     }
214 
215     /// Returns whether this document is in print preview.
is_print_preview(&self) -> bool216     pub fn is_print_preview(&self) -> bool {
217         let pc = match self.pres_context() {
218             Some(pc) => pc,
219             None => return false,
220         };
221         pc.mType == structs::nsPresContext_nsPresContextType_eContext_PrintPreview
222     }
223 
224     /// Returns the current media type of the device.
media_type(&self) -> MediaType225     pub fn media_type(&self) -> MediaType {
226         let pc = match self.pres_context() {
227             Some(pc) => pc,
228             None => return MediaType::screen(),
229         };
230 
231         // Gecko allows emulating random media with mMediaEmulationData.mMedium.
232         let medium_to_use = if !pc.mMediaEmulationData.mMedium.mRawPtr.is_null() {
233             pc.mMediaEmulationData.mMedium.mRawPtr
234         } else {
235             pc.mMedium as *const structs::nsAtom as *mut _
236         };
237 
238         MediaType(CustomIdent(unsafe { Atom::from_raw(medium_to_use) }))
239     }
240 
241     // It may make sense to account for @page rule margins here somehow, however
242     // it's not clear how that'd work, see:
243     // https://github.com/w3c/csswg-drafts/issues/5437
page_size_minus_default_margin(&self, pc: &structs::nsPresContext) -> Size2D<Au>244     fn page_size_minus_default_margin(&self, pc: &structs::nsPresContext) -> Size2D<Au> {
245         debug_assert!(pc.mIsRootPaginatedDocument() != 0);
246         let area = &pc.mPageSize;
247         let margin = &pc.mDefaultPageMargin;
248         let width = area.width - margin.left - margin.right;
249         let height = area.height - margin.top - margin.bottom;
250         Size2D::new(Au(cmp::max(width, 0)), Au(cmp::max(height, 0)))
251     }
252 
253     /// Returns the current viewport size in app units.
au_viewport_size(&self) -> Size2D<Au>254     pub fn au_viewport_size(&self) -> Size2D<Au> {
255         let pc = match self.pres_context() {
256             Some(pc) => pc,
257             None => return Size2D::new(Au(0), Au(0)),
258         };
259 
260         if pc.mIsRootPaginatedDocument() != 0 {
261             return self.page_size_minus_default_margin(pc);
262         }
263 
264         let area = &pc.mVisibleArea;
265         Size2D::new(Au(area.width), Au(area.height))
266     }
267 
268     /// Returns the current viewport size in app units, recording that it's been
269     /// used for viewport unit resolution.
au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au>270     pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au> {
271         self.used_viewport_size.store(true, Ordering::Relaxed);
272         let pc = match self.pres_context() {
273             Some(pc) => pc,
274             None => return Size2D::new(Au(0), Au(0)),
275         };
276 
277         if pc.mIsRootPaginatedDocument() != 0 {
278             return self.page_size_minus_default_margin(pc);
279         }
280 
281         let size = &pc.mSizeForViewportUnits;
282         Size2D::new(Au(size.width), Au(size.height))
283     }
284 
285     /// Returns whether we ever looked up the viewport size of the Device.
used_viewport_size(&self) -> bool286     pub fn used_viewport_size(&self) -> bool {
287         self.used_viewport_size.load(Ordering::Relaxed)
288     }
289 
290     /// Returns the device pixel ratio.
device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel>291     pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
292         let pc = match self.pres_context() {
293             Some(pc) => pc,
294             None => return Scale::new(1.),
295         };
296 
297         if pc.mMediaEmulationData.mDPPX > 0.0 {
298             return Scale::new(pc.mMediaEmulationData.mDPPX);
299         }
300 
301         let au_per_dpx = pc.mCurAppUnitsPerDevPixel as f32;
302         let au_per_px = AU_PER_PX as f32;
303         Scale::new(au_per_px / au_per_dpx)
304     }
305 
306     /// Returns whether document colors are enabled.
307     #[inline]
use_document_colors(&self) -> bool308     pub fn use_document_colors(&self) -> bool {
309         let doc = self.document();
310         if doc.mIsBeingUsedAsImage() {
311             return true;
312         }
313         self.pref_sheet_prefs().mUseDocumentColors
314     }
315 
316     /// Returns the default background color.
default_background_color(&self) -> RGBA317     pub fn default_background_color(&self) -> RGBA {
318         convert_nscolor_to_rgba(self.pref_sheet_prefs().mDefaultBackgroundColor)
319     }
320 
321     /// Returns the default foreground color.
default_color(&self) -> RGBA322     pub fn default_color(&self) -> RGBA {
323         convert_nscolor_to_rgba(self.pref_sheet_prefs().mDefaultColor)
324     }
325 
326     /// Returns the current effective text zoom.
327     #[inline]
effective_text_zoom(&self) -> f32328     fn effective_text_zoom(&self) -> f32 {
329         let pc = match self.pres_context() {
330             Some(pc) => pc,
331             None => return 1.,
332         };
333         pc.mEffectiveTextZoom
334     }
335 
336     /// Applies text zoom to a font-size or line-height value (see nsStyleFont::ZoomText).
337     #[inline]
zoom_text(&self, size: Length) -> Length338     pub fn zoom_text(&self, size: Length) -> Length {
339         size.scale_by(self.effective_text_zoom())
340     }
341 
342     /// Un-apply text zoom.
343     #[inline]
unzoom_text(&self, size: Length) -> Length344     pub fn unzoom_text(&self, size: Length) -> Length {
345         size.scale_by(1. / self.effective_text_zoom())
346     }
347 
348     /// Returns safe area insets
safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel>349     pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> {
350         let pc = match self.pres_context() {
351             Some(pc) => pc,
352             None => return SideOffsets2D::zero(),
353         };
354         let mut top = 0.0;
355         let mut right = 0.0;
356         let mut bottom = 0.0;
357         let mut left = 0.0;
358         unsafe {
359             bindings::Gecko_GetSafeAreaInsets(pc, &mut top, &mut right, &mut bottom, &mut left)
360         };
361         SideOffsets2D::new(top, right, bottom, left)
362     }
363 
364     /// Returns true if the given MIME type is supported
is_supported_mime_type(&self, mime_type: &str) -> bool365     pub fn is_supported_mime_type(&self, mime_type: &str) -> bool {
366         unsafe {
367             bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32)
368         }
369     }
370 }
371