summaryrefslogtreecommitdiffstats
path: root/servo/components/style/gecko/media_queries.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/gecko/media_queries.rs')
-rw-r--r--servo/components/style/gecko/media_queries.rs567
1 files changed, 567 insertions, 0 deletions
diff --git a/servo/components/style/gecko/media_queries.rs b/servo/components/style/gecko/media_queries.rs
new file mode 100644
index 0000000000..f27d78fd93
--- /dev/null
+++ b/servo/components/style/gecko/media_queries.rs
@@ -0,0 +1,567 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Gecko's media-query device and expression representation.
+
+use crate::color::AbsoluteColor;
+use crate::context::QuirksMode;
+use crate::custom_properties::CssEnvironment;
+use crate::font_metrics::FontMetrics;
+use crate::gecko::values::{convert_absolute_color_to_nscolor, convert_nscolor_to_absolute_color};
+use crate::gecko_bindings::bindings;
+use crate::gecko_bindings::structs;
+use crate::media_queries::MediaType;
+use crate::properties::ComputedValues;
+use crate::string_cache::Atom;
+use crate::values::computed::font::GenericFontFamily;
+use crate::values::computed::{ColorScheme, Length, NonNegativeLength};
+use crate::values::specified::color::SystemColor;
+use crate::values::specified::font::FONT_MEDIUM_PX;
+use crate::values::specified::ViewportVariant;
+use crate::values::{CustomIdent, KeyframesName};
+use app_units::{Au, AU_PER_PX};
+use euclid::default::Size2D;
+use euclid::{Scale, SideOffsets2D};
+use servo_arc::Arc;
+use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
+use std::{cmp, fmt};
+use style_traits::viewport::ViewportConstraints;
+use style_traits::{CSSPixel, DevicePixel};
+
+/// The `Device` in Gecko wraps a pres context, has a default values computed,
+/// and contains all the viewport rule state.
+pub struct Device {
+ /// NB: The document owns the styleset, who owns the stylist, and thus the
+ /// `Device`, so having a raw document pointer here is fine.
+ document: *const structs::Document,
+ default_values: Arc<ComputedValues>,
+ /// The font size of the root element.
+ ///
+ /// This is set when computing the style of the root element, and used for
+ /// rem units in other elements.
+ ///
+ /// When computing the style of the root element, there can't be any other
+ /// style being computed at the same time, given we need the style of the
+ /// parent to compute everything else. So it is correct to just use a
+ /// relaxed atomic here.
+ root_font_size: AtomicU32,
+ /// The body text color, stored as an `nscolor`, used for the "tables
+ /// inherit from body" quirk.
+ ///
+ /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
+ body_text_color: AtomicUsize,
+ /// Whether any styles computed in the document relied on the root font-size
+ /// by using rem units.
+ used_root_font_size: AtomicBool,
+ /// Whether any styles computed in the document relied on font metrics.
+ used_font_metrics: AtomicBool,
+ /// Whether any styles computed in the document relied on the viewport size
+ /// by using vw/vh/vmin/vmax units.
+ used_viewport_size: AtomicBool,
+ /// Whether any styles computed in the document relied on the viewport size
+ /// by using dvw/dvh/dvmin/dvmax units.
+ used_dynamic_viewport_size: AtomicBool,
+ /// The CssEnvironment object responsible of getting CSS environment
+ /// variables.
+ environment: CssEnvironment,
+}
+
+impl fmt::Debug for Device {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use nsstring::nsCString;
+
+ let mut doc_uri = nsCString::new();
+ unsafe {
+ bindings::Gecko_nsIURI_Debug((*self.document()).mDocumentURI.raw(), &mut doc_uri)
+ };
+
+ f.debug_struct("Device")
+ .field("document_url", &doc_uri)
+ .finish()
+ }
+}
+
+unsafe impl Sync for Device {}
+unsafe impl Send for Device {}
+
+impl Device {
+ /// Trivially constructs a new `Device`.
+ pub fn new(document: *const structs::Document) -> Self {
+ assert!(!document.is_null());
+ let doc = unsafe { &*document };
+ let prefs = unsafe { &*bindings::Gecko_GetPrefSheetPrefs(doc) };
+ Device {
+ document,
+ default_values: ComputedValues::default_values(doc),
+ root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
+ // This gets updated when we see the <body>, so it doesn't really
+ // matter which color-scheme we look at here.
+ body_text_color: AtomicUsize::new(prefs.mLightColors.mDefault as usize),
+ used_root_font_size: AtomicBool::new(false),
+ used_font_metrics: AtomicBool::new(false),
+ used_viewport_size: AtomicBool::new(false),
+ used_dynamic_viewport_size: AtomicBool::new(false),
+ environment: CssEnvironment,
+ }
+ }
+
+ /// Get the relevant environment to resolve `env()` functions.
+ #[inline]
+ pub fn environment(&self) -> &CssEnvironment {
+ &self.environment
+ }
+
+ /// Returns the computed line-height for the font in a given computed values instance.
+ ///
+ /// If you pass down an element, then the used line-height is returned.
+ pub fn calc_line_height(
+ &self,
+ line_height: &crate::values::computed::LineHeight,
+ vertical: bool,
+ font: &crate::properties::style_structs::Font,
+ element: Option<super::wrapper::GeckoElement>,
+ ) -> NonNegativeLength {
+ let pres_context = self.pres_context();
+ let au = Au(unsafe {
+ bindings::Gecko_CalcLineHeight(
+ line_height,
+ pres_context.map_or(std::ptr::null(), |pc| pc),
+ vertical,
+ &**font,
+ element.map_or(std::ptr::null(), |e| e.0),
+ )
+ });
+ NonNegativeLength::new(au.to_f32_px())
+ }
+
+ /// Tells the device that a new viewport rule has been found, and stores the
+ /// relevant viewport constraints.
+ pub fn account_for_viewport_rule(&mut self, _constraints: &ViewportConstraints) {
+ unreachable!("Gecko doesn't support @viewport");
+ }
+
+ /// Whether any animation name may be referenced from the style of any
+ /// element.
+ pub fn animation_name_may_be_referenced(&self, name: &KeyframesName) -> bool {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return false,
+ };
+
+ unsafe {
+ bindings::Gecko_AnimationNameMayBeReferencedFromStyle(pc, name.as_atom().as_ptr())
+ }
+ }
+
+ /// Returns the default computed values as a reference, in order to match
+ /// Servo.
+ pub fn default_computed_values(&self) -> &ComputedValues {
+ &self.default_values
+ }
+
+ /// Returns the default computed values as an `Arc`.
+ pub fn default_computed_values_arc(&self) -> &Arc<ComputedValues> {
+ &self.default_values
+ }
+
+ /// Get the font size of the root element (for rem)
+ pub fn root_font_size(&self) -> Length {
+ self.used_root_font_size.store(true, Ordering::Relaxed);
+ Length::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed)))
+ }
+
+ /// Set the font size of the root element (for rem)
+ pub fn set_root_font_size(&self, size: Length) {
+ self.root_font_size
+ .store(size.px().to_bits(), Ordering::Relaxed)
+ }
+
+ /// The quirks mode of the document.
+ pub fn quirks_mode(&self) -> QuirksMode {
+ self.document().mCompatMode.into()
+ }
+
+ /// Sets the body text color for the "inherit color from body" quirk.
+ ///
+ /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk>
+ pub fn set_body_text_color(&self, color: AbsoluteColor) {
+ self.body_text_color.store(
+ convert_absolute_color_to_nscolor(&color) as usize,
+ Ordering::Relaxed,
+ )
+ }
+
+ /// Gets the base size given a generic font family and a language.
+ pub fn base_size_for_generic(&self, language: &Atom, generic: GenericFontFamily) -> Length {
+ unsafe { bindings::Gecko_GetBaseSize(self.document(), language.as_ptr(), generic) }
+ }
+
+ /// Gets the size of the scrollbar in CSS pixels.
+ pub fn scrollbar_inline_size(&self) -> Length {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ // XXX: we could have a more reasonable default perhaps.
+ None => return Length::new(0.0),
+ };
+ Length::new(unsafe { bindings::Gecko_GetScrollbarInlineSize(pc) })
+ }
+
+ /// Queries font metrics
+ pub fn query_font_metrics(
+ &self,
+ vertical: bool,
+ font: &crate::properties::style_structs::Font,
+ base_size: Length,
+ in_media_query: bool,
+ retrieve_math_scales: bool,
+ ) -> FontMetrics {
+ self.used_font_metrics.store(true, Ordering::Relaxed);
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return Default::default(),
+ };
+ let gecko_metrics = unsafe {
+ bindings::Gecko_GetFontMetrics(
+ pc,
+ vertical,
+ &**font,
+ base_size,
+ // we don't use the user font set in a media query
+ !in_media_query,
+ retrieve_math_scales,
+ )
+ };
+ FontMetrics {
+ x_height: Some(gecko_metrics.mXSize),
+ zero_advance_measure: if gecko_metrics.mChSize.px() >= 0. {
+ Some(gecko_metrics.mChSize)
+ } else {
+ None
+ },
+ cap_height: if gecko_metrics.mCapHeight.px() >= 0. {
+ Some(gecko_metrics.mCapHeight)
+ } else {
+ None
+ },
+ ic_width: if gecko_metrics.mIcWidth.px() >= 0. {
+ Some(gecko_metrics.mIcWidth)
+ } else {
+ None
+ },
+ ascent: gecko_metrics.mAscent,
+ script_percent_scale_down: if gecko_metrics.mScriptPercentScaleDown > 0. {
+ Some(gecko_metrics.mScriptPercentScaleDown)
+ } else {
+ None
+ },
+ script_script_percent_scale_down: if gecko_metrics.mScriptScriptPercentScaleDown > 0. {
+ Some(gecko_metrics.mScriptScriptPercentScaleDown)
+ } else {
+ None
+ },
+ }
+ }
+
+ /// Returns the body text color.
+ pub fn body_text_color(&self) -> AbsoluteColor {
+ convert_nscolor_to_absolute_color(self.body_text_color.load(Ordering::Relaxed) as u32)
+ }
+
+ /// Gets the document pointer.
+ #[inline]
+ pub fn document(&self) -> &structs::Document {
+ unsafe { &*self.document }
+ }
+
+ /// Gets the pres context associated with this document.
+ #[inline]
+ pub fn pres_context(&self) -> Option<&structs::nsPresContext> {
+ unsafe {
+ self.document()
+ .mPresShell
+ .as_ref()?
+ .mPresContext
+ .mRawPtr
+ .as_ref()
+ }
+ }
+
+ /// Gets the preference stylesheet prefs for our document.
+ #[inline]
+ pub fn pref_sheet_prefs(&self) -> &structs::PreferenceSheet_Prefs {
+ unsafe { &*bindings::Gecko_GetPrefSheetPrefs(self.document()) }
+ }
+
+ /// Recreates the default computed values.
+ pub fn reset_computed_values(&mut self) {
+ self.default_values = ComputedValues::default_values(self.document());
+ }
+
+ /// Rebuild all the cached data.
+ pub fn rebuild_cached_data(&mut self) {
+ self.reset_computed_values();
+ self.used_root_font_size.store(false, Ordering::Relaxed);
+ self.used_font_metrics.store(false, Ordering::Relaxed);
+ self.used_viewport_size.store(false, Ordering::Relaxed);
+ self.used_dynamic_viewport_size
+ .store(false, Ordering::Relaxed);
+ }
+
+ /// Returns whether we ever looked up the root font size of the Device.
+ pub fn used_root_font_size(&self) -> bool {
+ self.used_root_font_size.load(Ordering::Relaxed)
+ }
+
+ /// Recreates all the temporary state that the `Device` stores.
+ ///
+ /// This includes the viewport override from `@viewport` rules, and also the
+ /// default computed values.
+ pub fn reset(&mut self) {
+ self.reset_computed_values();
+ }
+
+ /// Returns whether this document is in print preview.
+ pub fn is_print_preview(&self) -> bool {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return false,
+ };
+ pc.mType == structs::nsPresContext_nsPresContextType_eContext_PrintPreview
+ }
+
+ /// Returns the current media type of the device.
+ pub fn media_type(&self) -> MediaType {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return MediaType::screen(),
+ };
+
+ // Gecko allows emulating random media with mMediaEmulationData.mMedium.
+ let medium_to_use = if !pc.mMediaEmulationData.mMedium.mRawPtr.is_null() {
+ pc.mMediaEmulationData.mMedium.mRawPtr
+ } else {
+ pc.mMedium as *const structs::nsAtom as *mut _
+ };
+
+ MediaType(CustomIdent(unsafe { Atom::from_raw(medium_to_use) }))
+ }
+
+ // It may make sense to account for @page rule margins here somehow, however
+ // it's not clear how that'd work, see:
+ // https://github.com/w3c/csswg-drafts/issues/5437
+ fn page_size_minus_default_margin(&self, pc: &structs::nsPresContext) -> Size2D<Au> {
+ debug_assert!(pc.mIsRootPaginatedDocument() != 0);
+ let area = &pc.mPageSize;
+ let margin = &pc.mDefaultPageMargin;
+ let width = area.width - margin.left - margin.right;
+ let height = area.height - margin.top - margin.bottom;
+ Size2D::new(Au(cmp::max(width, 0)), Au(cmp::max(height, 0)))
+ }
+
+ /// Returns the current viewport size in app units.
+ pub fn au_viewport_size(&self) -> Size2D<Au> {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return Size2D::new(Au(0), Au(0)),
+ };
+
+ if pc.mIsRootPaginatedDocument() != 0 {
+ return self.page_size_minus_default_margin(pc);
+ }
+
+ let area = &pc.mVisibleArea;
+ Size2D::new(Au(area.width), Au(area.height))
+ }
+
+ /// Returns the current viewport size in app units, recording that it's been
+ /// used for viewport unit resolution.
+ pub fn au_viewport_size_for_viewport_unit_resolution(
+ &self,
+ variant: ViewportVariant,
+ ) -> Size2D<Au> {
+ self.used_viewport_size.store(true, Ordering::Relaxed);
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return Size2D::new(Au(0), Au(0)),
+ };
+
+ if pc.mIsRootPaginatedDocument() != 0 {
+ return self.page_size_minus_default_margin(pc);
+ }
+
+ match variant {
+ ViewportVariant::UADefault => {
+ let size = &pc.mSizeForViewportUnits;
+ Size2D::new(Au(size.width), Au(size.height))
+ },
+ ViewportVariant::Small => {
+ let size = &pc.mVisibleArea;
+ Size2D::new(Au(size.width), Au(size.height))
+ },
+ ViewportVariant::Large => {
+ let size = &pc.mVisibleArea;
+ // Looks like IntCoordTyped is treated as if it's u32 in Rust.
+ debug_assert!(
+ /* pc.mDynamicToolbarMaxHeight >=0 && */
+ pc.mDynamicToolbarMaxHeight < i32::MAX as u32
+ );
+ Size2D::new(
+ Au(size.width),
+ Au(size.height +
+ pc.mDynamicToolbarMaxHeight as i32 * pc.mCurAppUnitsPerDevPixel),
+ )
+ },
+ ViewportVariant::Dynamic => {
+ self.used_dynamic_viewport_size
+ .store(true, Ordering::Relaxed);
+ let size = &pc.mVisibleArea;
+ // Looks like IntCoordTyped is treated as if it's u32 in Rust.
+ debug_assert!(
+ /* pc.mDynamicToolbarHeight >=0 && */
+ pc.mDynamicToolbarHeight < i32::MAX as u32
+ );
+ Size2D::new(
+ Au(size.width),
+ Au(size.height +
+ (pc.mDynamicToolbarMaxHeight - pc.mDynamicToolbarHeight) as i32 *
+ pc.mCurAppUnitsPerDevPixel),
+ )
+ },
+ }
+ }
+
+ /// Returns whether we ever looked up the viewport size of the Device.
+ pub fn used_viewport_size(&self) -> bool {
+ self.used_viewport_size.load(Ordering::Relaxed)
+ }
+
+ /// Returns whether we ever looked up the dynamic viewport size of the Device.
+ pub fn used_dynamic_viewport_size(&self) -> bool {
+ self.used_dynamic_viewport_size.load(Ordering::Relaxed)
+ }
+
+ /// Returns whether font metrics have been queried.
+ pub fn used_font_metrics(&self) -> bool {
+ self.used_font_metrics.load(Ordering::Relaxed)
+ }
+
+ /// Returns whether visited styles are enabled.
+ pub fn visited_styles_enabled(&self) -> bool {
+ unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) }
+ }
+
+ /// Returns the number of app units per device pixel we're using currently.
+ pub fn app_units_per_device_pixel(&self) -> i32 {
+ match self.pres_context() {
+ Some(pc) => pc.mCurAppUnitsPerDevPixel,
+ None => AU_PER_PX,
+ }
+ }
+
+ /// Returns the device pixel ratio.
+ pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return Scale::new(1.),
+ };
+
+ if pc.mMediaEmulationData.mDPPX > 0.0 {
+ return Scale::new(pc.mMediaEmulationData.mDPPX);
+ }
+
+ let au_per_dpx = pc.mCurAppUnitsPerDevPixel as f32;
+ let au_per_px = AU_PER_PX as f32;
+ Scale::new(au_per_px / au_per_dpx)
+ }
+
+ /// Returns whether document colors are enabled.
+ #[inline]
+ pub fn use_document_colors(&self) -> bool {
+ let doc = self.document();
+ if doc.mIsBeingUsedAsImage() {
+ return true;
+ }
+ self.pref_sheet_prefs().mUseDocumentColors
+ }
+
+ /// Computes a system color and returns it as an nscolor.
+ pub(crate) fn system_nscolor(
+ &self,
+ system_color: SystemColor,
+ color_scheme: &ColorScheme,
+ ) -> u32 {
+ unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) }
+ }
+
+ /// Returns the default background color.
+ ///
+ /// This is only for forced-colors/high-contrast, so looking at light colors
+ /// is ok.
+ pub fn default_background_color(&self) -> AbsoluteColor {
+ let normal = ColorScheme::normal();
+ convert_nscolor_to_absolute_color(self.system_nscolor(SystemColor::Canvas, &normal))
+ }
+
+ /// Returns the default foreground color.
+ ///
+ /// See above for looking at light colors only.
+ pub fn default_color(&self) -> AbsoluteColor {
+ let normal = ColorScheme::normal();
+ convert_nscolor_to_absolute_color(self.system_nscolor(SystemColor::Canvastext, &normal))
+ }
+
+ /// Returns the current effective text zoom.
+ #[inline]
+ fn text_zoom(&self) -> f32 {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return 1.,
+ };
+ pc.mTextZoom
+ }
+
+ /// Applies text zoom to a font-size or line-height value (see nsStyleFont::ZoomText).
+ #[inline]
+ pub fn zoom_text(&self, size: Length) -> Length {
+ size.scale_by(self.text_zoom())
+ }
+
+ /// Un-apply text zoom.
+ #[inline]
+ pub fn unzoom_text(&self, size: Length) -> Length {
+ size.scale_by(1. / self.text_zoom())
+ }
+
+ /// Returns safe area insets
+ pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> {
+ let pc = match self.pres_context() {
+ Some(pc) => pc,
+ None => return SideOffsets2D::zero(),
+ };
+ let mut top = 0.0;
+ let mut right = 0.0;
+ let mut bottom = 0.0;
+ let mut left = 0.0;
+ unsafe {
+ bindings::Gecko_GetSafeAreaInsets(pc, &mut top, &mut right, &mut bottom, &mut left)
+ };
+ SideOffsets2D::new(top, right, bottom, left)
+ }
+
+ /// Returns true if the given MIME type is supported
+ pub fn is_supported_mime_type(&self, mime_type: &str) -> bool {
+ unsafe {
+ bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32)
+ }
+ }
+
+ /// Return whether the document is a chrome document.
+ ///
+ /// This check is consistent with how we enable chrome rules for chrome:// and resource://
+ /// stylesheets (and thus chrome:// documents).
+ #[inline]
+ pub fn chrome_rules_enabled_for_document(&self) -> bool {
+ self.document().mChromeRulesEnabled()
+ }
+}