style_resolver.rs (23875B)
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 //! Style resolution for a given element or pseudo-element. 6 7 use crate::applicable_declarations::ApplicableDeclarationList; 8 use crate::computed_value_flags::ComputedValueFlags; 9 use crate::context::{CascadeInputs, ElementCascadeInputs, StyleContext}; 10 use crate::data::{EagerPseudoStyles, ElementStyles}; 11 use crate::dom::TElement; 12 use crate::matching::MatchMethods; 13 use crate::properties::longhands::display::computed_value::T as Display; 14 use crate::properties::{ComputedValues, FirstLineReparenting}; 15 use crate::rule_tree::StrongRuleNode; 16 use crate::selector_parser::{PseudoElement, SelectorImpl}; 17 use crate::stylist::RuleInclusion; 18 use log::Level::Trace; 19 use selectors::matching::{ 20 IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode, 21 NeedsSelectorFlags, VisitedHandlingMode, 22 }; 23 use selectors::parser::PseudoElement as PseudoElementTrait; 24 use servo_arc::Arc; 25 26 /// Whether pseudo-elements should be resolved or not. 27 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 28 pub enum PseudoElementResolution { 29 /// Only resolve pseudo-styles if possibly applicable. 30 IfApplicable, 31 /// Force pseudo-element resolution. 32 Force, 33 } 34 35 /// A struct that takes care of resolving the style of a given element. 36 pub struct StyleResolverForElement<'a, 'ctx, 'le, E> 37 where 38 'ctx: 'a, 39 'le: 'ctx, 40 E: TElement + MatchMethods + 'le, 41 { 42 element: E, 43 context: &'a mut StyleContext<'ctx, E>, 44 rule_inclusion: RuleInclusion, 45 pseudo_resolution: PseudoElementResolution, 46 _marker: ::std::marker::PhantomData<&'le E>, 47 } 48 49 struct MatchingResults { 50 rule_node: StrongRuleNode, 51 flags: ComputedValueFlags, 52 has_starting_style: bool, 53 } 54 55 /// A style returned from the resolver machinery. 56 pub struct ResolvedStyle(pub Arc<ComputedValues>); 57 58 impl ResolvedStyle { 59 /// Convenience accessor for the style. 60 #[inline] 61 pub fn style(&self) -> &ComputedValues { 62 &*self.0 63 } 64 } 65 66 /// The primary style of an element or an element-backed pseudo-element. 67 pub struct PrimaryStyle { 68 /// The style itself. 69 pub style: ResolvedStyle, 70 /// Whether the style was reused from another element via the rule node (see 71 /// `StyleSharingCache::lookup_by_rules`). 72 pub reused_via_rule_node: bool, 73 /// The element may have matched rules inside @starting-style. 74 /// Basically, we don't apply @starting-style rules to |style|. This is a sugar to let us know 75 /// if we should resolve the element again for starting style, which is the after-change style 76 /// with @starting-style rules applied in addition. 77 pub may_have_starting_style: bool, 78 } 79 80 /// A set of style returned from the resolver machinery. 81 pub struct ResolvedElementStyles { 82 /// Primary style. 83 pub primary: PrimaryStyle, 84 /// Pseudo styles. 85 pub pseudos: EagerPseudoStyles, 86 } 87 88 impl ResolvedElementStyles { 89 /// Convenience accessor for the primary style. 90 pub fn primary_style(&self) -> &Arc<ComputedValues> { 91 &self.primary.style.0 92 } 93 94 /// Convenience mutable accessor for the style. 95 pub fn primary_style_mut(&mut self) -> &mut Arc<ComputedValues> { 96 &mut self.primary.style.0 97 } 98 99 /// Returns true if this element may have starting style rules. 100 #[inline] 101 pub fn may_have_starting_style(&self) -> bool { 102 self.primary.may_have_starting_style 103 } 104 } 105 106 impl PrimaryStyle { 107 /// Convenience accessor for the style. 108 pub fn style(&self) -> &ComputedValues { 109 &*self.style.0 110 } 111 } 112 113 impl From<ResolvedElementStyles> for ElementStyles { 114 fn from(r: ResolvedElementStyles) -> ElementStyles { 115 ElementStyles { 116 primary: Some(r.primary.style.0), 117 pseudos: r.pseudos, 118 } 119 } 120 } 121 122 pub(crate) fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R 123 where 124 E: TElement, 125 F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R, 126 { 127 let parent_el = element.inheritance_parent(); 128 let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); 129 let parent_style = parent_data.as_ref().map(|d| d.styles.primary()); 130 131 let mut layout_parent_el = parent_el.clone(); 132 let layout_parent_data; 133 let mut layout_parent_style = parent_style; 134 if parent_style.map_or(false, |s| s.is_display_contents()) { 135 layout_parent_el = Some(layout_parent_el.unwrap().layout_parent()); 136 layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap(); 137 layout_parent_style = Some(layout_parent_data.styles.primary()); 138 } 139 140 f( 141 parent_style.map(|x| &**x), 142 layout_parent_style.map(|s| &**s), 143 ) 144 } 145 146 fn layout_parent_style_for_pseudo<'a>( 147 primary_style: &'a PrimaryStyle, 148 layout_parent_style: Option<&'a ComputedValues>, 149 ) -> Option<&'a ComputedValues> { 150 if primary_style.style().is_display_contents() { 151 layout_parent_style 152 } else { 153 Some(primary_style.style()) 154 } 155 } 156 157 fn eager_pseudo_is_definitely_not_generated( 158 pseudo: &PseudoElement, 159 style: &ComputedValues, 160 ) -> bool { 161 if !pseudo.is_before_or_after() { 162 return false; 163 } 164 165 if !style 166 .flags 167 .intersects(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE) 168 && style.get_box().clone_display() == Display::None 169 { 170 return true; 171 } 172 173 if !style 174 .flags 175 .intersects(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE) 176 && style.ineffective_content_property() 177 { 178 return true; 179 } 180 181 false 182 } 183 184 impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E> 185 where 186 'ctx: 'a, 187 'le: 'ctx, 188 E: TElement + MatchMethods + 'le, 189 { 190 /// Trivially construct a new StyleResolverForElement. 191 pub fn new( 192 element: E, 193 context: &'a mut StyleContext<'ctx, E>, 194 rule_inclusion: RuleInclusion, 195 pseudo_resolution: PseudoElementResolution, 196 ) -> Self { 197 Self { 198 element, 199 context, 200 rule_inclusion, 201 pseudo_resolution, 202 _marker: ::std::marker::PhantomData, 203 } 204 } 205 206 /// Resolve just the style of a given element. 207 pub fn resolve_primary_style( 208 &mut self, 209 parent_style: Option<&ComputedValues>, 210 layout_parent_style: Option<&ComputedValues>, 211 include_starting_style: IncludeStartingStyle, 212 ) -> PrimaryStyle { 213 let primary_results = self.match_primary( 214 VisitedHandlingMode::AllLinksUnvisited, 215 include_starting_style, 216 ); 217 218 let inside_link = parent_style.map_or(false, |s| s.visited_style().is_some()); 219 220 let visited_rules = if self.context.shared.visited_styles_enabled 221 && (inside_link || self.element.is_link()) 222 { 223 let visited_matching_results = self.match_primary( 224 VisitedHandlingMode::RelevantLinkVisited, 225 IncludeStartingStyle::No, 226 ); 227 Some(visited_matching_results.rule_node) 228 } else { 229 None 230 }; 231 232 self.cascade_primary_style( 233 CascadeInputs { 234 rules: Some(primary_results.rule_node), 235 visited_rules, 236 flags: primary_results.flags, 237 }, 238 parent_style, 239 layout_parent_style, 240 include_starting_style, 241 primary_results.has_starting_style, 242 ) 243 } 244 245 fn cascade_primary_style( 246 &mut self, 247 inputs: CascadeInputs, 248 parent_style: Option<&ComputedValues>, 249 layout_parent_style: Option<&ComputedValues>, 250 include_starting_style: IncludeStartingStyle, 251 may_have_starting_style: bool, 252 ) -> PrimaryStyle { 253 // Before doing the cascade, check the sharing cache and see if we can 254 // reuse the style via rule node identity. 255 let may_reuse = self.element.matches_user_and_content_rules() 256 && parent_style.is_some() 257 && inputs.rules.is_some() 258 && include_starting_style == IncludeStartingStyle::No; 259 260 if may_reuse { 261 let cached = self.context.thread_local.sharing_cache.lookup_by_rules( 262 self.context.shared, 263 parent_style.unwrap(), 264 inputs.rules.as_ref().unwrap(), 265 inputs.visited_rules.as_ref(), 266 self.element, 267 ); 268 if let Some(mut primary_style) = cached { 269 self.context.thread_local.statistics.styles_reused += 1; 270 primary_style.reused_via_rule_node |= true; 271 return primary_style; 272 } 273 } 274 275 // No style to reuse. Cascade the style, starting with visited style 276 // if necessary. 277 PrimaryStyle { 278 style: self.cascade_style_and_visited( 279 inputs, 280 parent_style, 281 layout_parent_style, 282 /* pseudo = */ None, 283 ), 284 reused_via_rule_node: false, 285 may_have_starting_style, 286 } 287 } 288 289 /// Resolve the style of a given element, and all its eager pseudo-elements. 290 pub fn resolve_style( 291 &mut self, 292 parent_style: Option<&ComputedValues>, 293 layout_parent_style: Option<&ComputedValues>, 294 ) -> ResolvedElementStyles { 295 let primary_style = 296 self.resolve_primary_style(parent_style, layout_parent_style, IncludeStartingStyle::No); 297 298 let mut pseudo_styles = EagerPseudoStyles::default(); 299 300 if !self 301 .element 302 .implemented_pseudo_element() 303 .is_some_and(|p| !PseudoElementTrait::is_element_backed(&p)) 304 { 305 let layout_parent_style_for_pseudo = 306 layout_parent_style_for_pseudo(&primary_style, layout_parent_style); 307 SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { 308 let pseudo_style = self.resolve_pseudo_style( 309 &pseudo, 310 &primary_style, 311 layout_parent_style_for_pseudo, 312 ); 313 314 if let Some(style) = pseudo_style { 315 if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) 316 && eager_pseudo_is_definitely_not_generated(&pseudo, &style.0) 317 { 318 return; 319 } 320 pseudo_styles.set(&pseudo, style.0); 321 } 322 }) 323 } 324 325 ResolvedElementStyles { 326 primary: primary_style, 327 pseudos: pseudo_styles, 328 } 329 } 330 331 /// Resolve an element's styles with the default inheritance parent/layout 332 /// parents. 333 pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles { 334 with_default_parent_styles(self.element, |parent_style, layout_parent_style| { 335 self.resolve_style(parent_style, layout_parent_style) 336 }) 337 } 338 339 /// Cascade a set of rules, using the default parent for inheritance. 340 pub fn cascade_style_and_visited_with_default_parents( 341 &mut self, 342 inputs: CascadeInputs, 343 ) -> ResolvedStyle { 344 with_default_parent_styles(self.element, |parent_style, layout_parent_style| { 345 self.cascade_style_and_visited( 346 inputs, 347 parent_style, 348 layout_parent_style, 349 /* pseudo = */ None, 350 ) 351 }) 352 } 353 354 /// Cascade a set of rules for pseudo element, using the default parent for inheritance. 355 pub fn cascade_style_and_visited_for_pseudo_with_default_parents( 356 &mut self, 357 inputs: CascadeInputs, 358 pseudo: &PseudoElement, 359 primary_style: &PrimaryStyle, 360 ) -> ResolvedStyle { 361 with_default_parent_styles(self.element, |_, layout_parent_style| { 362 let layout_parent_style_for_pseudo = 363 layout_parent_style_for_pseudo(primary_style, layout_parent_style); 364 365 self.cascade_style_and_visited( 366 inputs, 367 Some(primary_style.style()), 368 layout_parent_style_for_pseudo, 369 Some(pseudo), 370 ) 371 }) 372 } 373 374 fn cascade_style_and_visited( 375 &mut self, 376 inputs: CascadeInputs, 377 parent_style: Option<&ComputedValues>, 378 layout_parent_style: Option<&ComputedValues>, 379 pseudo: Option<&PseudoElement>, 380 ) -> ResolvedStyle { 381 debug_assert!(pseudo.map_or(true, |p| p.is_eager())); 382 383 let mut conditions = Default::default(); 384 let values = self.context.shared.stylist.cascade_style_and_visited( 385 Some(self.element), 386 pseudo, 387 inputs, 388 &self.context.shared.guards, 389 parent_style, 390 layout_parent_style, 391 FirstLineReparenting::No, 392 /* try_tactic = */ &Default::default(), 393 Some(&self.context.thread_local.rule_cache), 394 &mut conditions, 395 ); 396 397 self.context.thread_local.rule_cache.insert_if_possible( 398 &self.context.shared.guards, 399 &values, 400 pseudo, 401 &conditions, 402 ); 403 404 ResolvedStyle(values) 405 } 406 407 /// Cascade the element and pseudo-element styles with the default parents. 408 pub fn cascade_styles_with_default_parents( 409 &mut self, 410 inputs: ElementCascadeInputs, 411 may_have_starting_style: bool, 412 ) -> ResolvedElementStyles { 413 with_default_parent_styles(self.element, move |parent_style, layout_parent_style| { 414 let primary_style = self.cascade_primary_style( 415 inputs.primary, 416 parent_style, 417 layout_parent_style, 418 IncludeStartingStyle::No, 419 may_have_starting_style, 420 ); 421 422 let mut pseudo_styles = EagerPseudoStyles::default(); 423 if let Some(mut pseudo_array) = inputs.pseudos.into_array() { 424 let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents() 425 { 426 layout_parent_style 427 } else { 428 Some(primary_style.style()) 429 }; 430 431 for (i, inputs) in pseudo_array.iter_mut().enumerate() { 432 if let Some(inputs) = inputs.take() { 433 let pseudo = PseudoElement::from_eager_index(i); 434 435 let style = self.cascade_style_and_visited( 436 inputs, 437 Some(primary_style.style()), 438 layout_parent_style_for_pseudo, 439 Some(&pseudo), 440 ); 441 442 if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) 443 && eager_pseudo_is_definitely_not_generated(&pseudo, &style.0) 444 { 445 continue; 446 } 447 448 pseudo_styles.set(&pseudo, style.0); 449 } 450 } 451 } 452 453 ResolvedElementStyles { 454 primary: primary_style, 455 pseudos: pseudo_styles, 456 } 457 }) 458 } 459 460 fn resolve_pseudo_style( 461 &mut self, 462 pseudo: &PseudoElement, 463 originating_element_style: &PrimaryStyle, 464 layout_parent_style: Option<&ComputedValues>, 465 ) -> Option<ResolvedStyle> { 466 let MatchingResults { 467 rule_node, 468 mut flags, 469 has_starting_style: _, 470 } = self.match_pseudo( 471 &originating_element_style.style.0, 472 pseudo, 473 VisitedHandlingMode::AllLinksUnvisited, 474 )?; 475 476 let mut visited_rules = None; 477 if originating_element_style.style().visited_style().is_some() { 478 visited_rules = self 479 .match_pseudo( 480 &originating_element_style.style.0, 481 pseudo, 482 VisitedHandlingMode::RelevantLinkVisited, 483 ) 484 .map(|results| { 485 flags |= results.flags; 486 results.rule_node 487 }); 488 } 489 490 Some(self.cascade_style_and_visited( 491 CascadeInputs { 492 rules: Some(rule_node), 493 visited_rules, 494 flags, 495 }, 496 Some(originating_element_style.style()), 497 layout_parent_style, 498 Some(pseudo), 499 )) 500 } 501 502 fn match_primary( 503 &mut self, 504 visited_handling: VisitedHandlingMode, 505 include_starting_style: IncludeStartingStyle, 506 ) -> MatchingResults { 507 debug!( 508 "Match primary for {:?}, visited: {:?}", 509 self.element, visited_handling 510 ); 511 let mut applicable_declarations = ApplicableDeclarationList::new(); 512 513 let bloom_filter = self.context.thread_local.bloom_filter.filter(); 514 let selector_caches = &mut self.context.thread_local.selector_caches; 515 let mut matching_context = MatchingContext::new_for_visited( 516 MatchingMode::Normal, 517 Some(bloom_filter), 518 selector_caches, 519 visited_handling, 520 include_starting_style, 521 self.context.shared.quirks_mode(), 522 NeedsSelectorFlags::Yes, 523 MatchingForInvalidation::No, 524 ); 525 526 let stylist = &self.context.shared.stylist; 527 // Compute the primary rule node. 528 stylist.push_applicable_declarations( 529 self.element, 530 None, 531 self.element.style_attribute(), 532 self.element.smil_override(), 533 self.element.animation_declarations(self.context.shared), 534 self.rule_inclusion, 535 &mut applicable_declarations, 536 &mut matching_context, 537 ); 538 539 // FIXME(emilio): This is a hack for animations, and should go away. 540 self.element.unset_dirty_style_attribute(); 541 542 let rule_node = stylist 543 .rule_tree() 544 .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards); 545 546 if log_enabled!(Trace) { 547 trace!("Matched rules for {:?}:", self.element); 548 for rn in rule_node.self_and_ancestors() { 549 let source = rn.style_source(); 550 if source.is_some() { 551 trace!(" > {:?}", source); 552 } 553 } 554 } 555 556 MatchingResults { 557 rule_node, 558 flags: matching_context.extra_data.cascade_input_flags, 559 has_starting_style: matching_context.has_starting_style, 560 } 561 } 562 563 fn match_pseudo( 564 &mut self, 565 originating_element_style: &ComputedValues, 566 pseudo_element: &PseudoElement, 567 visited_handling: VisitedHandlingMode, 568 ) -> Option<MatchingResults> { 569 debug!( 570 "Match pseudo {:?} for {:?}, visited: {:?}", 571 self.element, pseudo_element, visited_handling 572 ); 573 debug_assert!(pseudo_element.is_eager()); 574 575 let mut applicable_declarations = ApplicableDeclarationList::new(); 576 577 let stylist = &self.context.shared.stylist; 578 579 if !self 580 .element 581 .may_generate_pseudo(pseudo_element, originating_element_style) 582 { 583 return None; 584 } 585 586 let bloom_filter = self.context.thread_local.bloom_filter.filter(); 587 let selector_caches = &mut self.context.thread_local.selector_caches; 588 589 let mut matching_context = MatchingContext::<'_, E::Impl>::new_for_visited( 590 MatchingMode::ForStatelessPseudoElement, 591 Some(bloom_filter), 592 selector_caches, 593 visited_handling, 594 IncludeStartingStyle::No, 595 self.context.shared.quirks_mode(), 596 NeedsSelectorFlags::Yes, 597 MatchingForInvalidation::No, 598 ); 599 matching_context.extra_data.originating_element_style = Some(originating_element_style); 600 601 // NB: We handle animation rules for ::before and ::after when 602 // traversing them. 603 stylist.push_applicable_declarations( 604 self.element, 605 Some(pseudo_element), 606 None, 607 None, 608 /* animation_declarations = */ Default::default(), 609 self.rule_inclusion, 610 &mut applicable_declarations, 611 &mut matching_context, 612 ); 613 614 if applicable_declarations.is_empty() { 615 return None; 616 } 617 618 let rule_node = stylist 619 .rule_tree() 620 .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards); 621 622 Some(MatchingResults { 623 rule_node, 624 flags: matching_context.extra_data.cascade_input_flags, 625 has_starting_style: false, // We don't care. 626 }) 627 } 628 629 /// Resolve the starting style. 630 pub fn resolve_starting_style(&mut self) -> PrimaryStyle { 631 // Compute after-change style for the parent and the layout parent. 632 // Per spec, starting style inherits from the parent’s after-change style just like 633 // after-change style does. 634 let parent_el = self.element.inheritance_parent(); 635 let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); 636 let parent_style = parent_data.as_ref().map(|d| d.styles.primary()); 637 let parent_after_change_style = parent_style.and_then(|s| self.after_change_style(s)); 638 let parent_values = parent_after_change_style 639 .as_ref() 640 .or(parent_style) 641 .map(|x| &**x); 642 643 let mut layout_parent_el = parent_el.clone(); 644 let layout_parent_data; 645 let layout_parent_after_change_style; 646 let layout_parent_values = if parent_style.map_or(false, |s| s.is_display_contents()) { 647 layout_parent_el = Some(layout_parent_el.unwrap().layout_parent()); 648 layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap(); 649 let layout_parent_style = Some(layout_parent_data.styles.primary()); 650 layout_parent_after_change_style = 651 layout_parent_style.and_then(|s| self.after_change_style(s)); 652 layout_parent_after_change_style 653 .as_ref() 654 .or(layout_parent_style) 655 .map(|x| &**x) 656 } else { 657 parent_values 658 }; 659 660 self.resolve_primary_style( 661 parent_values, 662 layout_parent_values, 663 IncludeStartingStyle::Yes, 664 ) 665 } 666 667 /// If there is no transition rule in the ComputedValues, it returns None. 668 pub fn after_change_style( 669 &mut self, 670 primary_style: &Arc<ComputedValues>, 671 ) -> Option<Arc<ComputedValues>> { 672 let rule_node = primary_style.rules(); 673 let without_transition_rules = self 674 .context 675 .shared 676 .stylist 677 .rule_tree() 678 .remove_transition_rule_if_applicable(rule_node); 679 if without_transition_rules == *rule_node { 680 // We don't have transition rule in this case, so return None to let 681 // the caller use the original ComputedValues. 682 return None; 683 } 684 685 // FIXME(bug 868975): We probably need to transition visited style as 686 // well. 687 let inputs = CascadeInputs { 688 rules: Some(without_transition_rules), 689 visited_rules: primary_style.visited_rules().cloned(), 690 flags: primary_style.flags.for_cascade_inputs(), 691 }; 692 693 let style = self.cascade_style_and_visited_with_default_parents(inputs); 694 Some(style.0) 695 } 696 }