stylesheet.rs (19077B)
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 use crate::context::QuirksMode; 6 use crate::derives::*; 7 use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; 8 use crate::media_queries::{Device, MediaList}; 9 use crate::parser::ParserContext; 10 use crate::shared_lock::{DeepCloneWithLock, Locked}; 11 use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard}; 12 use crate::stylesheets::loader::StylesheetLoader; 13 use crate::stylesheets::rule_parser::{State, TopLevelRuleParser}; 14 use crate::stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator}; 15 use crate::stylesheets::rules_iterator::{NestedRuleIterationCondition, RulesIterator}; 16 use crate::stylesheets::{ 17 CssRule, CssRules, CustomMediaEvaluator, CustomMediaMap, Origin, UrlExtraData, 18 }; 19 use crate::use_counters::UseCounters; 20 use crate::{Namespace, Prefix}; 21 use cssparser::{Parser, ParserInput, StyleSheetParser}; 22 #[cfg(feature = "gecko")] 23 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; 24 use rustc_hash::FxHashMap; 25 use servo_arc::Arc; 26 use std::ops::Deref; 27 use std::sync::atomic::{AtomicBool, Ordering}; 28 use style_traits::ParsingMode; 29 30 use super::scope_rule::ImplicitScopeRoot; 31 32 /// This structure holds the user-agent and user stylesheets. 33 pub struct UserAgentStylesheets { 34 /// The lock used for user-agent stylesheets. 35 pub shared_lock: SharedRwLock, 36 /// The user or user agent stylesheets. 37 pub user_or_user_agent_stylesheets: Vec<DocumentStyleSheet>, 38 /// The quirks mode stylesheet. 39 pub quirks_mode_stylesheet: DocumentStyleSheet, 40 } 41 42 /// A set of namespaces applying to a given stylesheet. 43 /// 44 /// The namespace id is used in gecko 45 #[derive(Clone, Debug, Default, MallocSizeOf)] 46 #[allow(missing_docs)] 47 pub struct Namespaces { 48 pub default: Option<Namespace>, 49 pub prefixes: FxHashMap<Prefix, Namespace>, 50 } 51 52 /// The contents of a given stylesheet. This effectively maps to a 53 /// StyleSheetInner in Gecko. 54 #[derive(Debug)] 55 pub struct StylesheetContents { 56 /// List of rules in the order they were found (important for 57 /// cascading order) 58 pub rules: Arc<Locked<CssRules>>, 59 /// The origin of this stylesheet. 60 pub origin: Origin, 61 /// The url data this stylesheet should use. 62 pub url_data: UrlExtraData, 63 /// The namespaces that apply to this stylesheet. 64 pub namespaces: Namespaces, 65 /// The quirks mode of this stylesheet. 66 pub quirks_mode: QuirksMode, 67 /// This stylesheet's source map URL. 68 pub source_map_url: Option<String>, 69 /// This stylesheet's source URL. 70 pub source_url: Option<String>, 71 /// The use counters of the original stylesheet. 72 pub use_counters: UseCounters, 73 74 /// We don't want to allow construction outside of this file, to guarantee 75 /// that all contents are created with Arc<>. 76 _forbid_construction: (), 77 } 78 79 impl StylesheetContents { 80 /// Parse a given CSS string, with a given url-data, origin, and 81 /// quirks mode. 82 pub fn from_str( 83 css: &str, 84 url_data: UrlExtraData, 85 origin: Origin, 86 shared_lock: &SharedRwLock, 87 stylesheet_loader: Option<&dyn StylesheetLoader>, 88 error_reporter: Option<&dyn ParseErrorReporter>, 89 quirks_mode: QuirksMode, 90 allow_import_rules: AllowImportRules, 91 sanitization_data: Option<&mut SanitizationData>, 92 ) -> Arc<Self> { 93 let use_counters = UseCounters::default(); 94 let (namespaces, rules, source_map_url, source_url) = Stylesheet::parse_rules( 95 css, 96 &url_data, 97 origin, 98 &shared_lock, 99 stylesheet_loader, 100 error_reporter, 101 quirks_mode, 102 Some(&use_counters), 103 allow_import_rules, 104 sanitization_data, 105 ); 106 107 Arc::new(Self { 108 rules: CssRules::new(rules, &shared_lock), 109 origin, 110 url_data, 111 namespaces, 112 quirks_mode, 113 source_map_url, 114 source_url, 115 use_counters, 116 _forbid_construction: (), 117 }) 118 } 119 120 /// Creates a new StylesheetContents with the specified pre-parsed rules, 121 /// origin, URL data, and quirks mode. 122 /// 123 /// Since the rules have already been parsed, and the intention is that 124 /// this function is used for read only User Agent style sheets, an empty 125 /// namespace map is used, and the source map and source URLs are set to 126 /// None. 127 /// 128 /// An empty namespace map should be fine, as it is only used for parsing, 129 /// not serialization of existing selectors. Since UA sheets are read only, 130 /// we should never need the namespace map. 131 pub fn from_shared_data( 132 rules: Arc<Locked<CssRules>>, 133 origin: Origin, 134 url_data: UrlExtraData, 135 quirks_mode: QuirksMode, 136 ) -> Arc<Self> { 137 debug_assert!(rules.is_static()); 138 Arc::new(Self { 139 rules, 140 origin, 141 url_data, 142 namespaces: Namespaces::default(), 143 quirks_mode, 144 source_map_url: None, 145 source_url: None, 146 use_counters: UseCounters::default(), 147 _forbid_construction: (), 148 }) 149 } 150 151 /// Returns a reference to the list of rules. 152 #[inline] 153 pub fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { 154 &self.rules.read_with(guard).0 155 } 156 157 /// Measure heap usage. 158 #[cfg(feature = "gecko")] 159 pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { 160 if self.rules.is_static() { 161 return 0; 162 } 163 // Measurement of other fields may be added later. 164 self.rules.unconditional_shallow_size_of(ops) 165 + self.rules.read_with(guard).size_of(guard, ops) 166 } 167 168 /// Return an iterator using the condition `C`. 169 #[inline] 170 pub fn iter_rules<'a, 'b, C, CMM>( 171 &'a self, 172 device: &'a Device, 173 custom_media: CMM, 174 guard: &'a SharedRwLockReadGuard<'b>, 175 ) -> RulesIterator<'a, 'b, C, CMM> 176 where 177 C: NestedRuleIterationCondition, 178 CMM: Deref<Target = CustomMediaMap>, 179 { 180 RulesIterator::new( 181 device, 182 self.quirks_mode, 183 custom_media, 184 guard, 185 self.rules(guard).iter(), 186 ) 187 } 188 189 /// Return an iterator over the effective rules within the style-sheet, as 190 /// according to the supplied `Device`. 191 #[inline] 192 pub fn effective_rules<'a, 'b, CMM: Deref<Target = CustomMediaMap>>( 193 &'a self, 194 device: &'a Device, 195 custom_media: CMM, 196 guard: &'a SharedRwLockReadGuard<'b>, 197 ) -> EffectiveRulesIterator<'a, 'b, CMM> { 198 self.iter_rules::<EffectiveRules, CMM>(device, custom_media, guard) 199 } 200 201 /// Perform a deep clone, of this stylesheet, with an explicit URL data if needed. 202 pub fn deep_clone( 203 &self, 204 lock: &SharedRwLock, 205 url_data: Option<&UrlExtraData>, 206 guard: &SharedRwLockReadGuard, 207 ) -> Arc<Self> { 208 // Make a deep clone of the rules, using the new lock. 209 let rules = self 210 .rules 211 .read_with(guard) 212 .deep_clone_with_lock(lock, guard); 213 214 let url_data = url_data.cloned().unwrap_or_else(|| self.url_data.clone()); 215 216 Arc::new(Self { 217 rules: Arc::new(lock.wrap(rules)), 218 quirks_mode: self.quirks_mode, 219 origin: self.origin, 220 url_data, 221 namespaces: self.namespaces.clone(), 222 source_map_url: self.source_map_url.clone(), 223 source_url: self.source_url.clone(), 224 use_counters: self.use_counters.clone(), 225 _forbid_construction: (), 226 }) 227 } 228 } 229 230 /// The structure servo uses to represent a stylesheet. 231 #[derive(Debug)] 232 pub struct Stylesheet { 233 /// The contents of this stylesheet. 234 pub contents: Locked<Arc<StylesheetContents>>, 235 /// The lock used for objects inside this stylesheet 236 pub shared_lock: SharedRwLock, 237 /// List of media associated with the Stylesheet. 238 pub media: Arc<Locked<MediaList>>, 239 /// Whether this stylesheet should be disabled. 240 pub disabled: AtomicBool, 241 } 242 243 /// A trait to represent a given stylesheet in a document. 244 pub trait StylesheetInDocument: ::std::fmt::Debug { 245 /// Get whether this stylesheet is enabled. 246 fn enabled(&self) -> bool; 247 248 /// Get the media associated with this stylesheet. 249 fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>; 250 251 /// Returns a reference to the contents of the stylesheet. 252 fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents; 253 254 /// Returns whether the style-sheet applies for the current device. 255 fn is_effective_for_device( 256 &self, 257 device: &Device, 258 custom_media: &CustomMediaMap, 259 guard: &SharedRwLockReadGuard, 260 ) -> bool { 261 let media = match self.media(guard) { 262 Some(m) => m, 263 None => return true, 264 }; 265 media.evaluate( 266 device, 267 self.contents(guard).quirks_mode, 268 &mut CustomMediaEvaluator::new(custom_media, guard), 269 ) 270 } 271 272 /// Return the implicit scope root for this stylesheet, if one exists. 273 fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot>; 274 } 275 276 impl StylesheetInDocument for Stylesheet { 277 fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { 278 Some(self.media.read_with(guard)) 279 } 280 281 fn enabled(&self) -> bool { 282 !self.disabled() 283 } 284 285 #[inline] 286 fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents { 287 self.contents.read_with(guard) 288 } 289 290 fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> { 291 None 292 } 293 } 294 295 /// A simple wrapper over an `Arc<Stylesheet>`, with pointer comparison, and 296 /// suitable for its use in a `StylesheetSet`. 297 #[derive(Clone, Debug)] 298 #[cfg_attr(feature = "servo", derive(MallocSizeOf))] 299 pub struct DocumentStyleSheet( 300 #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] pub Arc<Stylesheet>, 301 ); 302 303 impl PartialEq for DocumentStyleSheet { 304 fn eq(&self, other: &Self) -> bool { 305 Arc::ptr_eq(&self.0, &other.0) 306 } 307 } 308 309 impl StylesheetInDocument for DocumentStyleSheet { 310 fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { 311 self.0.media(guard) 312 } 313 314 fn enabled(&self) -> bool { 315 self.0.enabled() 316 } 317 318 #[inline] 319 fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents { 320 self.0.contents(guard) 321 } 322 323 fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> { 324 None 325 } 326 } 327 328 /// The kind of sanitization to use when parsing a stylesheet. 329 #[repr(u8)] 330 #[derive(Clone, Copy, Debug, PartialEq)] 331 pub enum SanitizationKind { 332 /// Perform no sanitization. 333 None, 334 /// Allow only @font-face, style rules, and @namespace. 335 Standard, 336 /// Allow everything but conditional rules. 337 NoConditionalRules, 338 } 339 340 /// Whether @import rules are allowed. 341 #[repr(u8)] 342 #[derive(Clone, Copy, Debug, PartialEq)] 343 pub enum AllowImportRules { 344 /// @import rules will be parsed. 345 Yes, 346 /// @import rules will not be parsed. 347 No, 348 } 349 350 impl SanitizationKind { 351 fn allows(self, rule: &CssRule) -> bool { 352 debug_assert_ne!(self, SanitizationKind::None); 353 // NOTE(emilio): If this becomes more complex (not filtering just by 354 // top-level rules), we should thread all the data through nested rules 355 // and such. But this doesn't seem necessary at the moment. 356 let is_standard = matches!(self, SanitizationKind::Standard); 357 match *rule { 358 CssRule::Document(..) | 359 CssRule::Media(..) | 360 CssRule::CustomMedia(..) | 361 CssRule::Supports(..) | 362 CssRule::Import(..) | 363 CssRule::Container(..) | 364 // TODO(emilio): Perhaps Layer should not be always sanitized? But 365 // we sanitize @media and co, so this seems safer for now. 366 CssRule::LayerStatement(..) | 367 CssRule::LayerBlock(..) | 368 // TODO(dshin): Same comment as Layer applies - shouldn't give away 369 // something like display size - erring on the side of "safe" for now. 370 CssRule::Scope(..) | 371 CssRule::StartingStyle(..) => false, 372 373 CssRule::FontFace(..) | 374 CssRule::Namespace(..) | 375 CssRule::Style(..) | 376 CssRule::NestedDeclarations(..) | 377 CssRule::PositionTry(..) => true, 378 379 CssRule::Keyframes(..) | 380 CssRule::Page(..) | 381 CssRule::Margin(..) | 382 CssRule::Property(..) | 383 CssRule::FontFeatureValues(..) | 384 CssRule::FontPaletteValues(..) | 385 CssRule::CounterStyle(..) => !is_standard, 386 } 387 } 388 } 389 390 /// A struct to hold the data relevant to style sheet sanitization. 391 #[derive(Debug)] 392 pub struct SanitizationData { 393 kind: SanitizationKind, 394 output: String, 395 } 396 397 impl SanitizationData { 398 /// Create a new input for sanitization. 399 #[inline] 400 pub fn new(kind: SanitizationKind) -> Option<Self> { 401 if matches!(kind, SanitizationKind::None) { 402 return None; 403 } 404 Some(Self { 405 kind, 406 output: String::new(), 407 }) 408 } 409 410 /// Take the sanitized output. 411 #[inline] 412 pub fn take(self) -> String { 413 self.output 414 } 415 } 416 417 impl Stylesheet { 418 fn parse_rules( 419 css: &str, 420 url_data: &UrlExtraData, 421 origin: Origin, 422 shared_lock: &SharedRwLock, 423 stylesheet_loader: Option<&dyn StylesheetLoader>, 424 error_reporter: Option<&dyn ParseErrorReporter>, 425 quirks_mode: QuirksMode, 426 use_counters: Option<&UseCounters>, 427 allow_import_rules: AllowImportRules, 428 mut sanitization_data: Option<&mut SanitizationData>, 429 ) -> (Namespaces, Vec<CssRule>, Option<String>, Option<String>) { 430 let mut input = ParserInput::new(css); 431 let mut input = Parser::new(&mut input); 432 433 let context = ParserContext::new( 434 origin, 435 url_data, 436 None, 437 ParsingMode::DEFAULT, 438 quirks_mode, 439 /* namespaces = */ Default::default(), 440 error_reporter, 441 use_counters, 442 ); 443 444 let mut rule_parser = TopLevelRuleParser { 445 shared_lock, 446 loader: stylesheet_loader, 447 context, 448 state: State::Start, 449 dom_error: None, 450 insert_rule_context: None, 451 allow_import_rules, 452 declaration_parser_state: Default::default(), 453 first_declaration_block: Default::default(), 454 wants_first_declaration_block: false, 455 error_reporting_state: Default::default(), 456 rules: Vec::new(), 457 }; 458 459 { 460 let mut iter = StyleSheetParser::new(&mut input, &mut rule_parser); 461 while let Some(result) = iter.next() { 462 match result { 463 Ok(rule_start) => { 464 // TODO(emilio, nesting): sanitize nested CSS rules, probably? 465 if let Some(ref mut data) = sanitization_data { 466 if let Some(ref rule) = iter.parser.rules.last() { 467 if !data.kind.allows(rule) { 468 iter.parser.rules.pop(); 469 continue; 470 } 471 } 472 let end = iter.input.position().byte_index(); 473 data.output.push_str(&css[rule_start.byte_index()..end]); 474 } 475 }, 476 Err((error, slice)) => { 477 let location = error.location; 478 let error = ContextualParseError::InvalidRule(slice, error); 479 iter.parser.context.log_css_error(location, error); 480 }, 481 } 482 } 483 } 484 485 let source_map_url = input.current_source_map_url().map(String::from); 486 let source_url = input.current_source_url().map(String::from); 487 ( 488 rule_parser.context.namespaces.into_owned(), 489 rule_parser.rules, 490 source_map_url, 491 source_url, 492 ) 493 } 494 495 /// Creates an empty stylesheet and parses it with a given base url, origin and media. 496 pub fn from_str( 497 css: &str, 498 url_data: UrlExtraData, 499 origin: Origin, 500 media: Arc<Locked<MediaList>>, 501 shared_lock: SharedRwLock, 502 stylesheet_loader: Option<&dyn StylesheetLoader>, 503 error_reporter: Option<&dyn ParseErrorReporter>, 504 quirks_mode: QuirksMode, 505 allow_import_rules: AllowImportRules, 506 ) -> Self { 507 // FIXME: Consider adding use counters to Servo? 508 let contents = StylesheetContents::from_str( 509 css, 510 url_data, 511 origin, 512 &shared_lock, 513 stylesheet_loader, 514 error_reporter, 515 quirks_mode, 516 allow_import_rules, 517 /* sanitized_output = */ None, 518 ); 519 520 Stylesheet { 521 contents: shared_lock.wrap(contents), 522 shared_lock, 523 media, 524 disabled: AtomicBool::new(false), 525 } 526 } 527 528 /// Returns whether the stylesheet has been explicitly disabled through the 529 /// CSSOM. 530 pub fn disabled(&self) -> bool { 531 self.disabled.load(Ordering::SeqCst) 532 } 533 534 /// Records that the stylesheet has been explicitly disabled through the 535 /// CSSOM. 536 /// 537 /// Returns whether the the call resulted in a change in disabled state. 538 /// 539 /// Disabled stylesheets remain in the document, but their rules are not 540 /// added to the Stylist. 541 pub fn set_disabled(&self, disabled: bool) -> bool { 542 self.disabled.swap(disabled, Ordering::SeqCst) != disabled 543 } 544 } 545 546 #[cfg(feature = "servo")] 547 impl Clone for Stylesheet { 548 fn clone(&self) -> Self { 549 // Create a new lock for our clone. 550 let lock = self.shared_lock.clone(); 551 let guard = self.shared_lock.read(); 552 553 // Make a deep clone of the media, using the new lock. 554 let media = self.media.read_with(&guard).clone(); 555 let media = Arc::new(lock.wrap(media)); 556 let contents = lock.wrap(Arc::new( 557 self.contents 558 .read_with(&guard) 559 .deep_clone_with_lock(&lock, &guard), 560 )); 561 562 Stylesheet { 563 contents, 564 media, 565 shared_lock: lock, 566 disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)), 567 } 568 } 569 }