rule_collector.rs (18201B)
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 //! Collects a series of applicable rules for a given element. 6 7 use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList}; 8 use crate::dom::{TElement, TNode, TShadowRoot}; 9 use crate::properties::{AnimationDeclarations, PropertyDeclarationBlock}; 10 use crate::rule_tree::{CascadeLevel, ShadowCascadeOrder}; 11 use crate::selector_map::SelectorMap; 12 use crate::selector_parser::PseudoElement; 13 use crate::shared_lock::Locked; 14 use crate::stylesheets::{layer_rule::LayerOrder, Origin}; 15 use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist}; 16 use selectors::matching::MatchingContext; 17 use servo_arc::ArcBorrow; 18 use smallvec::SmallVec; 19 20 /// This is a bit of a hack so <svg:use> matches the rules of the enclosing 21 /// tree. 22 /// 23 /// This function returns the containing shadow host ignoring <svg:use> shadow 24 /// trees, since those match the enclosing tree's rules. 25 /// 26 /// Only a handful of places need to really care about this. This is not a 27 /// problem for invalidation and that kind of stuff because they still don't 28 /// match rules based on elements outside of the shadow tree, and because the 29 /// <svg:use> subtrees are immutable and recreated each time the source tree 30 /// changes. 31 /// 32 /// We historically allow cross-document <svg:use> to have these rules applied, 33 /// but I think that's not great. Gecko is the only engine supporting that. 34 /// 35 /// See https://github.com/w3c/svgwg/issues/504 for the relevant spec 36 /// discussion. 37 #[inline] 38 pub fn containing_shadow_ignoring_svg_use<E: TElement>( 39 element: E, 40 ) -> Option<<E::ConcreteNode as TNode>::ConcreteShadowRoot> { 41 let mut shadow = element.containing_shadow()?; 42 loop { 43 let host = shadow.host(); 44 let host_is_svg_use_element = 45 host.is_svg_element() && host.local_name() == &**local_name!("use"); 46 if !host_is_svg_use_element { 47 return Some(shadow); 48 } 49 debug_assert!( 50 shadow.style_data().is_none(), 51 "We allow no stylesheets in <svg:use> subtrees" 52 ); 53 shadow = host.containing_shadow()?; 54 } 55 } 56 57 /// An object that we use with all the intermediate state needed for the 58 /// cascade. 59 /// 60 /// This is done basically to be able to organize the cascade in smaller 61 /// functions, and be able to reason about it easily. 62 pub struct RuleCollector<'a, 'b: 'a, E> 63 where 64 E: TElement, 65 { 66 element: E, 67 rule_hash_target: E, 68 stylist: &'a Stylist, 69 pseudo_elements: SmallVec<[PseudoElement; 1]>, 70 style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>, 71 smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>, 72 animation_declarations: AnimationDeclarations, 73 rule_inclusion: RuleInclusion, 74 rules: &'a mut ApplicableDeclarationList, 75 context: &'a mut MatchingContext<'b, E::Impl>, 76 matches_user_and_content_rules: bool, 77 matches_document_author_rules: bool, 78 in_sort_scope: bool, 79 } 80 81 impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E> 82 where 83 E: TElement, 84 { 85 /// Trivially construct a new collector. 86 pub fn new( 87 stylist: &'a Stylist, 88 element: E, 89 pseudo_elements: SmallVec<[PseudoElement; 1]>, 90 style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>, 91 smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>, 92 animation_declarations: AnimationDeclarations, 93 rule_inclusion: RuleInclusion, 94 rules: &'a mut ApplicableDeclarationList, 95 context: &'a mut MatchingContext<'b, E::Impl>, 96 ) -> Self { 97 let rule_hash_target = element.rule_hash_target(); 98 let matches_user_and_content_rules = rule_hash_target.matches_user_and_content_rules(); 99 100 debug_assert!(pseudo_elements.iter().all(|p| !p.is_precomputed())); 101 102 Self { 103 element, 104 rule_hash_target, 105 stylist, 106 pseudo_elements, 107 style_attribute, 108 smil_override, 109 animation_declarations, 110 rule_inclusion, 111 context, 112 rules, 113 matches_user_and_content_rules, 114 matches_document_author_rules: matches_user_and_content_rules, 115 in_sort_scope: false, 116 } 117 } 118 119 /// Sets up the state necessary to collect rules from a given DOM tree 120 /// (either the document tree, or a shadow tree). 121 /// 122 /// All rules in the same tree need to be matched together, and this 123 /// function takes care of sorting them by specificity and source order. 124 #[inline] 125 fn in_tree(&mut self, host: Option<E>, f: impl FnOnce(&mut Self)) { 126 debug_assert!(!self.in_sort_scope, "Nested sorting makes no sense"); 127 let start = self.rules.len(); 128 self.in_sort_scope = true; 129 let old_host = self.context.current_host.take(); 130 self.context.current_host = host.map(|e| e.opaque()); 131 f(self); 132 if start != self.rules.len() { 133 self.rules[start..].sort_unstable_by_key(|block| block.sort_key()); 134 } 135 self.context.current_host = old_host; 136 self.in_sort_scope = false; 137 } 138 139 #[inline] 140 fn in_shadow_tree(&mut self, host: E, f: impl FnOnce(&mut Self)) { 141 self.in_tree(Some(host), f); 142 } 143 144 fn collect_stylist_rules(&mut self, origin: Origin) { 145 let cascade_level = match origin { 146 Origin::UserAgent => CascadeLevel::UANormal, 147 Origin::User => CascadeLevel::UserNormal, 148 Origin::Author => CascadeLevel::same_tree_author_normal(), 149 }; 150 151 let cascade_data = self.stylist.cascade_data().borrow_for_origin(origin); 152 let map = match cascade_data.normal_rules(&self.pseudo_elements) { 153 Some(m) => m, 154 None => return, 155 }; 156 157 self.in_tree(None, |collector| { 158 collector.collect_rules_in_map(map, cascade_level, cascade_data); 159 }); 160 } 161 162 fn collect_user_agent_rules(&mut self) { 163 self.collect_stylist_rules(Origin::UserAgent); 164 #[cfg(feature = "gecko")] 165 self.collect_view_transition_dynamic_rules(); 166 } 167 168 #[cfg(feature = "gecko")] 169 fn collect_view_transition_dynamic_rules(&mut self) { 170 if !self 171 .pseudo_elements 172 .first() 173 .is_some_and(|p| p.is_named_view_transition()) 174 { 175 return; 176 } 177 let len_before_vt_rules = self.rules.len(); 178 self.element 179 .synthesize_view_transition_dynamic_rules(self.rules); 180 if cfg!(debug_assertions) && self.rules.len() != len_before_vt_rules { 181 for declaration in &self.rules[len_before_vt_rules..] { 182 assert_eq!(declaration.level(), CascadeLevel::UANormal); 183 } 184 } 185 } 186 187 fn collect_user_rules(&mut self) { 188 if !self.matches_user_and_content_rules { 189 return; 190 } 191 192 self.collect_stylist_rules(Origin::User); 193 } 194 195 /// Presentational hints. 196 /// 197 /// These go before author rules, but after user rules, see: 198 /// https://drafts.csswg.org/css-cascade/#preshint 199 fn collect_presentational_hints(&mut self) { 200 if !self.pseudo_elements.is_empty() { 201 return; 202 } 203 204 let length_before_preshints = self.rules.len(); 205 self.element 206 .synthesize_presentational_hints_for_legacy_attributes( 207 self.context.visited_handling(), 208 self.rules, 209 ); 210 if cfg!(debug_assertions) && self.rules.len() != length_before_preshints { 211 for declaration in &self.rules[length_before_preshints..] { 212 assert_eq!(declaration.level(), CascadeLevel::PresHints); 213 } 214 } 215 } 216 217 #[inline] 218 fn collect_rules_in_list( 219 &mut self, 220 part_rules: &[Rule], 221 cascade_level: CascadeLevel, 222 cascade_data: &CascadeData, 223 ) { 224 debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); 225 SelectorMap::get_matching_rules( 226 self.element, 227 part_rules, 228 &mut self.rules, 229 &mut self.context, 230 cascade_level, 231 cascade_data, 232 &self.stylist, 233 ); 234 } 235 236 #[inline] 237 fn collect_rules_in_map( 238 &mut self, 239 map: &SelectorMap<Rule>, 240 cascade_level: CascadeLevel, 241 cascade_data: &CascadeData, 242 ) { 243 debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); 244 map.get_all_matching_rules( 245 self.element, 246 self.rule_hash_target, 247 &mut self.rules, 248 &mut self.context, 249 cascade_level, 250 cascade_data, 251 &self.stylist, 252 ); 253 } 254 255 /// Collects the rules for the ::slotted pseudo-element and the :host 256 /// pseudo-class. 257 fn collect_host_and_slotted_rules(&mut self) { 258 let mut slots = SmallVec::<[_; 3]>::new(); 259 let mut current = self.rule_hash_target.assigned_slot(); 260 let mut shadow_cascade_order = ShadowCascadeOrder::for_outermost_shadow_tree(); 261 262 while let Some(slot) = current { 263 debug_assert!( 264 self.matches_user_and_content_rules, 265 "We should not slot NAC anywhere" 266 ); 267 slots.push(slot); 268 current = slot.assigned_slot(); 269 shadow_cascade_order.dec(); 270 } 271 272 self.collect_host_rules(shadow_cascade_order); 273 274 // Match slotted rules in reverse order, so that the outer slotted rules 275 // come before the inner rules (and thus have less priority). 276 for slot in slots.iter().rev() { 277 shadow_cascade_order.inc(); 278 279 let shadow = slot.containing_shadow().unwrap(); 280 let data = match shadow.style_data() { 281 Some(d) => d, 282 None => continue, 283 }; 284 let slotted_rules = match data.slotted_rules(&self.pseudo_elements) { 285 Some(r) => r, 286 None => continue, 287 }; 288 289 self.in_shadow_tree(shadow.host(), |collector| { 290 let cascade_level = CascadeLevel::AuthorNormal { 291 shadow_cascade_order, 292 }; 293 collector.collect_rules_in_map(slotted_rules, cascade_level, data); 294 }); 295 } 296 } 297 298 fn collect_rules_from_containing_shadow_tree(&mut self) { 299 if !self.matches_user_and_content_rules { 300 return; 301 } 302 303 let containing_shadow = containing_shadow_ignoring_svg_use(self.rule_hash_target); 304 let containing_shadow = match containing_shadow { 305 Some(s) => s, 306 None => return, 307 }; 308 309 self.matches_document_author_rules = false; 310 311 let cascade_data = match containing_shadow.style_data() { 312 Some(c) => c, 313 None => return, 314 }; 315 316 let cascade_level = CascadeLevel::same_tree_author_normal(); 317 self.in_shadow_tree(containing_shadow.host(), |collector| { 318 if let Some(map) = cascade_data.normal_rules(&collector.pseudo_elements) { 319 collector.collect_rules_in_map(map, cascade_level, cascade_data); 320 } 321 322 // Collect rules from :host::part() and such 323 let hash_target = collector.rule_hash_target; 324 if !hash_target.has_part_attr() { 325 return; 326 } 327 328 let part_rules = match cascade_data.part_rules(&collector.pseudo_elements) { 329 Some(p) => p, 330 None => return, 331 }; 332 333 hash_target.each_part(|part| { 334 if let Some(part_rules) = part_rules.get(&part.0) { 335 collector.collect_rules_in_list(part_rules, cascade_level, cascade_data); 336 } 337 }); 338 }); 339 } 340 341 /// Collects the rules for the :host pseudo-class. 342 fn collect_host_rules(&mut self, shadow_cascade_order: ShadowCascadeOrder) { 343 let shadow = match self.rule_hash_target.shadow_root() { 344 Some(s) => s, 345 None => return, 346 }; 347 348 let style_data = match shadow.style_data() { 349 Some(d) => d, 350 None => return, 351 }; 352 353 let host_rules = match style_data.featureless_host_rules(&self.pseudo_elements) { 354 Some(rules) => rules, 355 None => return, 356 }; 357 358 let rule_hash_target = self.rule_hash_target; 359 self.in_shadow_tree(rule_hash_target, |collector| { 360 let cascade_level = CascadeLevel::AuthorNormal { 361 shadow_cascade_order, 362 }; 363 debug_assert!(!collector.context.featureless(), "How?"); 364 collector.context.featureless = true; 365 collector.collect_rules_in_map(host_rules, cascade_level, style_data); 366 collector.context.featureless = false; 367 }); 368 } 369 370 fn collect_document_author_rules(&mut self) { 371 if !self.matches_document_author_rules { 372 return; 373 } 374 375 self.collect_stylist_rules(Origin::Author); 376 } 377 378 fn collect_part_rules_from_outer_trees(&mut self) { 379 if !self.rule_hash_target.has_part_attr() { 380 return; 381 } 382 383 let mut inner_shadow = match self.rule_hash_target.containing_shadow() { 384 Some(s) => s, 385 None => return, 386 }; 387 388 let mut shadow_cascade_order = ShadowCascadeOrder::for_innermost_containing_tree(); 389 390 let mut parts = SmallVec::<[_; 3]>::new(); 391 self.rule_hash_target.each_part(|p| parts.push(p.clone())); 392 393 loop { 394 if parts.is_empty() { 395 return; 396 } 397 398 let inner_shadow_host = inner_shadow.host(); 399 let outer_shadow = inner_shadow_host.containing_shadow(); 400 let cascade_data = match outer_shadow { 401 Some(shadow) => shadow.style_data(), 402 None => Some( 403 self.stylist 404 .cascade_data() 405 .borrow_for_origin(Origin::Author), 406 ), 407 }; 408 409 if let Some(cascade_data) = cascade_data { 410 if let Some(part_rules) = cascade_data.part_rules(&self.pseudo_elements) { 411 let containing_host = outer_shadow.map(|s| s.host()); 412 let cascade_level = CascadeLevel::AuthorNormal { 413 shadow_cascade_order, 414 }; 415 self.in_tree(containing_host, |collector| { 416 for p in &parts { 417 if let Some(part_rules) = part_rules.get(&p.0) { 418 collector.collect_rules_in_list( 419 part_rules, 420 cascade_level, 421 cascade_data, 422 ); 423 } 424 } 425 }); 426 shadow_cascade_order.inc(); 427 } 428 } 429 430 inner_shadow = match outer_shadow { 431 Some(s) => s, 432 None => break, // Nowhere to export to. 433 }; 434 435 let mut new_parts = SmallVec::new(); 436 for part in &parts { 437 inner_shadow_host.each_exported_part(part, |exported_part| { 438 new_parts.push(exported_part.clone()); 439 }); 440 } 441 parts = new_parts; 442 } 443 } 444 445 fn collect_style_attribute(&mut self) { 446 if let Some(sa) = self.style_attribute { 447 self.rules 448 .push(ApplicableDeclarationBlock::from_declarations( 449 sa.clone_arc(), 450 CascadeLevel::same_tree_author_normal(), 451 LayerOrder::style_attribute(), 452 )); 453 } 454 } 455 456 fn collect_animation_rules(&mut self) { 457 if let Some(so) = self.smil_override { 458 self.rules 459 .push(ApplicableDeclarationBlock::from_declarations( 460 so.clone_arc(), 461 CascadeLevel::SMILOverride, 462 LayerOrder::root(), 463 )); 464 } 465 466 // The animations sheet (CSS animations, script-generated 467 // animations, and CSS transitions that are no longer tied to CSS 468 // markup). 469 if let Some(anim) = self.animation_declarations.animations.take() { 470 self.rules 471 .push(ApplicableDeclarationBlock::from_declarations( 472 anim, 473 CascadeLevel::Animations, 474 LayerOrder::root(), 475 )); 476 } 477 478 // The transitions sheet (CSS transitions that are tied to CSS 479 // markup). 480 if let Some(anim) = self.animation_declarations.transitions.take() { 481 self.rules 482 .push(ApplicableDeclarationBlock::from_declarations( 483 anim, 484 CascadeLevel::Transitions, 485 LayerOrder::root(), 486 )); 487 } 488 } 489 490 /// Collects all the rules, leaving the result in `self.rules`. 491 /// 492 /// Note that `!important` rules are handled during rule tree insertion. 493 pub fn collect_all(mut self) { 494 self.collect_user_agent_rules(); 495 self.collect_user_rules(); 496 if self.rule_inclusion == RuleInclusion::DefaultOnly { 497 return; 498 } 499 self.collect_presentational_hints(); 500 // FIXME(emilio): Should the author styles enabled stuff avoid the 501 // presentational hints from getting pushed? See bug 1505770. 502 if self.stylist.author_styles_enabled() == AuthorStylesEnabled::No { 503 return; 504 } 505 self.collect_host_and_slotted_rules(); 506 self.collect_rules_from_containing_shadow_tree(); 507 self.collect_document_author_rules(); 508 self.collect_style_attribute(); 509 self.collect_part_rules_from_outer_trees(); 510 self.collect_animation_rules(); 511 } 512 }