/* 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::logical_geometry::WritingMode;
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::{ColorSchemeFlags, ForcedColors, SystemColor};
use crate::values::specified::font::{
    QueryFontMetricsFlags, FONT_MEDIUM_CAP_PX, FONT_MEDIUM_CH_PX, FONT_MEDIUM_EX_PX,
    FONT_MEDIUM_IC_PX, FONT_MEDIUM_LINE_HEIGHT_PX, 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 parking_lot::RwLock;
use servo_arc::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use std::{cmp, fmt, mem};
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.
///
/// This structure also contains atomics used for computing root font-relative
/// units. These atomics use relaxed ordering, since 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).
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>,
    /// Current computed style of the root element, used for calculations of
    /// root font-relative units.
    root_style: RwLock<Arc<ComputedValues>>,
    /// Font size of the root element, used for rem units in other elements.
    root_font_size: AtomicU32,
    /// Line height of the root element, used for rlh units in other elements.
    root_line_height: AtomicU32,
    /// X-height of the root element, used for rex units in other elements.
    root_font_metrics_ex: AtomicU32,
    /// Cap-height of the root element, used for rcap units in other elements.
    root_font_metrics_cap: AtomicU32,
    /// Advance measure (ch) of the root element, used for rch units in other elements.
    root_font_metrics_ch: AtomicU32,
    /// Ideographic advance measure of the root element, used for ric units in other elements.
    root_font_metrics_ic: 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 the root line-height
    /// by using rlh units.
    used_root_line_height: AtomicBool,
    /// Whether any styles computed in the document relied on the root font metrics
    /// by using rcap, rch, rex, or ric units. This is a lock instead of an atomic
    /// in order to prevent concurrent writes to the root font metric values.
    used_root_font_metrics: RwLock<bool>,
    /// 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) };
        let default_values = ComputedValues::default_values(doc);
        let root_style = RwLock::new(Arc::clone(&default_values));
        Device {
            document,
            default_values: default_values,
            root_style: root_style,
            root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
            root_line_height: AtomicU32::new(FONT_MEDIUM_LINE_HEIGHT_PX.to_bits()),
            root_font_metrics_ex: AtomicU32::new(FONT_MEDIUM_EX_PX.to_bits()),
            root_font_metrics_cap: AtomicU32::new(FONT_MEDIUM_CAP_PX.to_bits()),
            root_font_metrics_ch: AtomicU32::new(FONT_MEDIUM_CH_PX.to_bits()),
            root_font_metrics_ic: AtomicU32::new(FONT_MEDIUM_IC_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_root_line_height: AtomicBool::new(false),
            used_root_font_metrics: RwLock::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,
        font: &crate::properties::style_structs::Font,
        writing_mode: WritingMode,
        element: Option<super::wrapper::GeckoElement>,
    ) -> NonNegativeLength {
        let pres_context = self.pres_context();
        let line_height = font.clone_line_height();
        let au = Au(unsafe {
            bindings::Gecko_CalcLineHeight(
                &line_height,
                pres_context.map_or(std::ptr::null(), |pc| pc),
                writing_mode.is_text_vertical(),
                &**font,
                element.map_or(std::ptr::null(), |e| e.0),
            )
        });
        NonNegativeLength::new(au.to_f32_px())
    }

    /// 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
    }

    /// Store a pointer to the root element's computed style, for use in
    /// calculation of root font-relative metrics.
    pub fn set_root_style(&self, style: &Arc<ComputedValues>) {
        *self.root_style.write() = style.clone();
    }

    /// 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), in zoom-independent CSS pixels.
    pub fn set_root_font_size(&self, size: f32) {
        self.root_font_size.store(size.to_bits(), Ordering::Relaxed)
    }

    /// Get the line height of the root element (for rlh)
    pub fn root_line_height(&self) -> Length {
        self.used_root_line_height.store(true, Ordering::Relaxed);
        Length::new(f32::from_bits(
            self.root_line_height.load(Ordering::Relaxed),
        ))
    }

    /// Set the line height of the root element (for rlh), in zoom-independent CSS pixels.
    pub fn set_root_line_height(&self, size: f32) {
        self.root_line_height
            .store(size.to_bits(), Ordering::Relaxed);
    }

    /// Get the x-height of the root element (for rex)
    pub fn root_font_metrics_ex(&self) -> Length {
        self.ensure_root_font_metrics_updated();
        Length::new(f32::from_bits(
            self.root_font_metrics_ex.load(Ordering::Relaxed),
        ))
    }

    /// Set the x-height of the root element (for rex), in zoom-independent CSS pixels.
    pub fn set_root_font_metrics_ex(&self, size: f32) -> bool {
        let size = size.to_bits();
        let previous = self.root_font_metrics_ex.swap(size, Ordering::Relaxed);
        previous != size
    }

    /// Get the cap-height of the root element (for rcap)
    pub fn root_font_metrics_cap(&self) -> Length {
        self.ensure_root_font_metrics_updated();
        Length::new(f32::from_bits(
            self.root_font_metrics_cap.load(Ordering::Relaxed),
        ))
    }

    /// Set the cap-height of the root element (for rcap), in zoom-independent CSS pixels.
    pub fn set_root_font_metrics_cap(&self, size: f32) -> bool {
        let size = size.to_bits();
        let previous = self.root_font_metrics_cap.swap(size, Ordering::Relaxed);
        previous != size
    }

    /// Get the advance measure of the root element (for rch)
    pub fn root_font_metrics_ch(&self) -> Length {
        self.ensure_root_font_metrics_updated();
        Length::new(f32::from_bits(
            self.root_font_metrics_ch.load(Ordering::Relaxed),
        ))
    }

    /// Set the advance measure of the root element (for rch), in zoom-independent CSS pixels.
    pub fn set_root_font_metrics_ch(&self, size: f32) -> bool {
        let size = size.to_bits();
        let previous = self.root_font_metrics_ch.swap(size, Ordering::Relaxed);
        previous != size
    }

    /// Get the ideographic advance measure of the root element (for ric)
    pub fn root_font_metrics_ic(&self) -> Length {
        self.ensure_root_font_metrics_updated();
        Length::new(f32::from_bits(
            self.root_font_metrics_ic.load(Ordering::Relaxed),
        ))
    }

    /// Set the ideographic advance measure of the root element (for ric), in zoom-independent CSS pixels.
    pub fn set_root_font_metrics_ic(&self, size: f32) -> bool {
        let size = size.to_bits();
        let previous = self.root_font_metrics_ic.swap(size, Ordering::Relaxed);
        previous != size
    }

    /// 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,
        flags: QueryFontMetricsFlags,
        track_usage: bool,
    ) -> FontMetrics {
        if track_usage {
            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, flags) };
        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
            },
        }
    }

    fn ensure_root_font_metrics_updated(&self) {
        let mut guard = self.used_root_font_metrics.write();
        let previously_computed = mem::replace(&mut *guard, true);
        if !previously_computed {
            self.update_root_font_metrics();
        }
    }

    /// Compute the root element's font metrics, and returns a bool indicating whether
    /// the font metrics have changed since the previous restyle.
    pub fn update_root_font_metrics(&self) -> bool {
        let root_style = self.root_style.read();
        let root_effective_zoom = (*root_style).effective_zoom;
        let root_font_size = (*root_style).get_font().clone_font_size().computed_size();

        let root_font_metrics = self.query_font_metrics(
            (*root_style).writing_mode.is_upright(),
            &(*root_style).get_font(),
            root_font_size,
            QueryFontMetricsFlags::USE_USER_FONT_SET
                | QueryFontMetricsFlags::NEEDS_CH
                | QueryFontMetricsFlags::NEEDS_IC,
            /* track_usage = */ false,
        );

        let mut root_font_metrics_changed = false;
        root_font_metrics_changed |= self.set_root_font_metrics_ex(
            root_effective_zoom.unzoom(root_font_metrics.x_height_or_default(root_font_size).px()),
        );
        root_font_metrics_changed |= self.set_root_font_metrics_ch(
            root_effective_zoom.unzoom(
                root_font_metrics
                    .zero_advance_measure_or_default(
                        root_font_size,
                        (*root_style).writing_mode.is_upright(),
                    )
                    .px(),
            ),
        );
        root_font_metrics_changed |= self.set_root_font_metrics_cap(
            root_effective_zoom.unzoom(root_font_metrics.cap_height_or_default().px()),
        );
        root_font_metrics_changed |= self.set_root_font_metrics_ic(
            root_effective_zoom.unzoom(root_font_metrics.ic_width_or_default(root_font_size).px()),
        );

        root_font_metrics_changed
    }

    /// 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_root_line_height.store(false, Ordering::Relaxed);
        self.used_root_font_metrics = RwLock::new(false);
        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)
    }

    /// Returns whether we ever looked up the root line-height of the device.
    pub fn used_root_line_height(&self) -> bool {
        self.used_root_line_height.load(Ordering::Relaxed)
    }

    /// Returns whether we ever looked up the root font metrics of the device.
    pub fn used_root_font_metrics(&self) -> bool {
        *self.used_root_font_metrics.read()
    }

    /// 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 app units per pixel at 1x full-zoom.
    fn app_units_per_device_pixel_at_unit_full_zoom(&self) -> i32 {
        match self.pres_context() {
            Some(pc) => unsafe { (*pc.mDeviceContext.mRawPtr).mAppUnitsPerDevPixelAtUnitFullZoom },
            None => AU_PER_PX,
        }
    }

    /// Returns the device pixel ratio, ignoring the full zoom factor.
    pub fn device_pixel_ratio_ignoring_full_zoom(&self) -> Scale<f32, CSSPixel, DevicePixel> {
        let au_per_px = AU_PER_PX as f32;
        Scale::new(au_per_px / self.app_units_per_device_pixel_at_unit_full_zoom() as f32)
    }

    /// 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 forced_colors(&self) -> ForcedColors {
        self.pres_context()
            .map_or(ForcedColors::None, |pc| pc.mForcedColors)
    }

    /// Computes a system color and returns it as an nscolor.
    pub(crate) fn system_nscolor(
        &self,
        system_color: SystemColor,
        color_scheme: ColorSchemeFlags,
    ) -> u32 {
        unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), &color_scheme) }
    }

    /// Returns whether the used color-scheme for `color-scheme` should be dark.
    pub(crate) fn is_dark_color_scheme(&self, color_scheme: ColorSchemeFlags) -> bool {
        unsafe { bindings::Gecko_IsDarkColorScheme(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 {
        convert_nscolor_to_absolute_color(
            self.system_nscolor(SystemColor::Canvas, ColorScheme::normal().bits),
        )
    }

    /// Returns the default foreground color.
    ///
    /// See above for looking at light colors only.
    pub fn default_color(&self) -> AbsoluteColor {
        convert_nscolor_to_absolute_color(
            self.system_nscolor(SystemColor::Canvastext, ColorScheme::normal().bits),
        )
    }

    /// 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()
    }
}
