use crate::color::AbsoluteColor;
use crate::context::QuirksMode;
use crate::custom_properties::CssEnvironment;
use crate::device::Device;
use crate::font_metrics::FontMetrics;
use crate::gecko::wrapper::GeckoElement;
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, Ordering};
use std::{cmp, fmt};
use style_traits::{CSSPixel, DevicePixel};
pub(super) struct ExtraDeviceData {
document: *const structs::Document,
}
unsafe impl Sync for Device {}
unsafe impl Send for Device {}
impl 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 {
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()),
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,
body_text_color: AtomicU32::new(prefs.mLightColors.mDefault),
extra: ExtraDeviceData { document },
}
}
pub fn calc_line_height(
&self,
font: &crate::properties::style_structs::Font,
writing_mode: WritingMode,
element: Option<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())
}
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())
}
}
pub fn quirks_mode(&self) -> QuirksMode {
self.document().mCompatMode.into()
}
pub fn base_size_for_generic(&self, language: &Atom, generic: GenericFontFamily) -> Length {
unsafe { bindings::Gecko_GetBaseSize(self.document(), language.as_ptr(), generic) }
}
pub fn scrollbar_inline_size(&self) -> Length {
let pc = match self.pres_context() {
Some(pc) => pc,
None => return Length::new(0.0),
};
Length::new(unsafe { bindings::Gecko_GetScrollbarInlineSize(pc) })
}
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
},
}
}
#[inline]
pub fn document(&self) -> &structs::Document {
unsafe { &*self.extra.document }
}
#[inline]
pub fn pres_context(&self) -> Option<&structs::nsPresContext> {
unsafe {
self.document()
.mPresShell
.as_ref()?
.mPresContext
.mRawPtr
.as_ref()
}
}
#[inline]
pub fn pref_sheet_prefs(&self) -> &structs::PreferenceSheet_Prefs {
unsafe { &*bindings::Gecko_GetPrefSheetPrefs(self.document()) }
}
pub fn reset_computed_values(&mut self) {
self.default_values = ComputedValues::default_values(self.document());
}
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);
}
pub fn reset(&mut self) {
self.reset_computed_values();
}
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
}
pub fn media_type(&self) -> MediaType {
let pc = match self.pres_context() {
Some(pc) => pc,
None => return MediaType::screen(),
};
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) }))
}
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)))
}
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))
}
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;
debug_assert!(
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;
debug_assert!(
pc.mDynamicToolbarHeight < i32::MAX as u32
);
Size2D::new(
Au(size.width),
Au(size.height
+ (pc.mDynamicToolbarMaxHeight - pc.mDynamicToolbarHeight) as i32
* pc.mCurAppUnitsPerDevPixel),
)
},
}
}
pub fn visited_styles_enabled(&self) -> bool {
unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) }
}
pub fn app_units_per_device_pixel(&self) -> i32 {
match self.pres_context() {
Some(pc) => pc.mCurAppUnitsPerDevPixel,
None => AU_PER_PX,
}
}
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,
}
}
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)
}
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)
}
#[inline]
pub fn forced_colors(&self) -> ForcedColors {
self.pres_context()
.map_or(ForcedColors::None, |pc| pc.mForcedColors)
}
pub(crate) fn system_nscolor(
&self,
system_color: SystemColor,
color_scheme: ColorSchemeFlags,
) -> u32 {
unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), &color_scheme) }
}
pub(crate) fn is_dark_color_scheme(&self, color_scheme: ColorSchemeFlags) -> bool {
unsafe { bindings::Gecko_IsDarkColorScheme(self.document(), &color_scheme) }
}
pub fn default_background_color(&self) -> AbsoluteColor {
AbsoluteColor::from_nscolor(
self.system_nscolor(SystemColor::Canvas, ColorScheme::normal().bits),
)
}
pub fn default_color(&self) -> AbsoluteColor {
AbsoluteColor::from_nscolor(
self.system_nscolor(SystemColor::Canvastext, ColorScheme::normal().bits),
)
}
#[inline]
fn text_zoom(&self) -> f32 {
let pc = match self.pres_context() {
Some(pc) => pc,
None => return 1.,
};
pc.mTextZoom
}
#[inline]
pub fn zoom_text(&self, size: Length) -> Length {
size.scale_by(self.text_zoom())
}
#[inline]
pub fn unzoom_text(&self, size: Length) -> Length {
size.scale_by(1. / self.text_zoom())
}
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)
}
pub fn is_supported_mime_type(&self, mime_type: &str) -> bool {
unsafe {
bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32)
}
}
#[inline]
pub fn chrome_rules_enabled_for_document(&self) -> bool {
self.document().mChromeRulesEnabled()
}
}
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()
}
}