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