matching.rs (44093B)
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 //! High-level interface to CSS selector matching. 6 7 #![allow(unsafe_code)] 8 #![deny(missing_docs)] 9 10 use crate::computed_value_flags::ComputedValueFlags; 11 #[cfg(feature = "servo")] 12 use crate::context::CascadeInputs; 13 use crate::context::{ElementCascadeInputs, QuirksMode}; 14 use crate::context::{SharedStyleContext, StyleContext}; 15 use crate::data::{ElementData, ElementStyles}; 16 use crate::dom::TElement; 17 #[cfg(feature = "servo")] 18 use crate::dom::TNode; 19 use crate::invalidation::element::restyle_hints::RestyleHint; 20 use crate::properties::longhands::display::computed_value::T as Display; 21 use crate::properties::ComputedValues; 22 use crate::properties::PropertyDeclarationBlock; 23 use crate::rule_tree::{CascadeLevel, StrongRuleNode}; 24 use crate::selector_parser::{PseudoElement, RestyleDamage}; 25 use crate::shared_lock::Locked; 26 use crate::style_resolver::StyleResolverForElement; 27 use crate::style_resolver::{PseudoElementResolution, ResolvedElementStyles}; 28 use crate::stylesheets::layer_rule::LayerOrder; 29 use crate::stylist::RuleInclusion; 30 use crate::traversal_flags::TraversalFlags; 31 use servo_arc::{Arc, ArcBorrow}; 32 33 /// Represents the result of comparing an element's old and new style. 34 #[derive(Debug)] 35 pub struct StyleDifference { 36 /// The resulting damage. 37 pub damage: RestyleDamage, 38 39 /// Whether any styles changed. 40 pub change: StyleChange, 41 } 42 43 /// Represents whether or not the style of an element has changed. 44 #[derive(Clone, Copy, Debug)] 45 pub enum StyleChange { 46 /// The style hasn't changed. 47 Unchanged, 48 /// The style has changed. 49 Changed { 50 /// Whether only reset structs changed. 51 reset_only: bool, 52 }, 53 } 54 55 /// Whether or not newly computed values for an element need to be cascaded to 56 /// children (or children might need to be re-matched, e.g., for container 57 /// queries). 58 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 59 pub enum ChildRestyleRequirement { 60 /// Old and new computed values were the same, or we otherwise know that 61 /// we won't bother recomputing style for children, so we can skip cascading 62 /// the new values into child elements. 63 CanSkipCascade = 0, 64 /// The same as `MustCascadeChildren`, but we only need to actually 65 /// recascade if the child inherits any explicit reset style. 66 MustCascadeChildrenIfInheritResetStyle = 1, 67 /// Old and new computed values were different, so we must cascade the 68 /// new values to children. 69 MustCascadeChildren = 2, 70 /// The same as `MustCascadeChildren`, but for the entire subtree. This is 71 /// used to handle root font-size updates needing to recascade the whole 72 /// document. 73 MustCascadeDescendants = 3, 74 /// We need to re-match the whole subttree. This is used to handle container 75 /// query relative unit changes for example. Container query size changes 76 /// also trigger re-match, but after layout. 77 MustMatchDescendants = 4, 78 } 79 80 /// Determines which styles are being cascaded currently. 81 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 82 enum CascadeVisitedMode { 83 /// Cascade the regular, unvisited styles. 84 Unvisited, 85 /// Cascade the styles used when an element's relevant link is visited. A 86 /// "relevant link" is the element being matched if it is a link or the 87 /// nearest ancestor link. 88 Visited, 89 } 90 91 trait PrivateMatchMethods: TElement { 92 fn replace_single_rule_node( 93 context: &SharedStyleContext, 94 level: CascadeLevel, 95 layer_order: LayerOrder, 96 pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, 97 path: &mut StrongRuleNode, 98 ) -> bool { 99 let stylist = &context.stylist; 100 let guards = &context.guards; 101 102 let mut important_rules_changed = false; 103 let new_node = stylist.rule_tree().update_rule_at_level( 104 level, 105 layer_order, 106 pdb, 107 path, 108 guards, 109 &mut important_rules_changed, 110 ); 111 if let Some(n) = new_node { 112 *path = n; 113 } 114 important_rules_changed 115 } 116 117 /// Updates the rule nodes without re-running selector matching, using just 118 /// the rule tree, for a specific visited mode. 119 /// 120 /// Returns true if an !important rule was replaced. 121 fn replace_rules_internal( 122 &self, 123 replacements: RestyleHint, 124 context: &mut StyleContext<Self>, 125 cascade_visited: CascadeVisitedMode, 126 cascade_inputs: &mut ElementCascadeInputs, 127 ) -> bool { 128 debug_assert!( 129 replacements.intersects(RestyleHint::replacements()) 130 && (replacements & !RestyleHint::replacements()).is_empty() 131 ); 132 133 let primary_rules = match cascade_visited { 134 CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(), 135 CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(), 136 }; 137 138 let primary_rules = match primary_rules { 139 Some(r) => r, 140 None => return false, 141 }; 142 143 if !context.shared.traversal_flags.for_animation_only() { 144 let mut result = false; 145 if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) { 146 let style_attribute = self.style_attribute(); 147 result |= Self::replace_single_rule_node( 148 context.shared, 149 CascadeLevel::same_tree_author_normal(), 150 LayerOrder::style_attribute(), 151 style_attribute, 152 primary_rules, 153 ); 154 result |= Self::replace_single_rule_node( 155 context.shared, 156 CascadeLevel::same_tree_author_important(), 157 LayerOrder::style_attribute(), 158 style_attribute, 159 primary_rules, 160 ); 161 // FIXME(emilio): Still a hack! 162 self.unset_dirty_style_attribute(); 163 } 164 return result; 165 } 166 167 // Animation restyle hints are processed prior to other restyle 168 // hints in the animation-only traversal. 169 // 170 // Non-animation restyle hints will be processed in a subsequent 171 // normal traversal. 172 if replacements.intersects(RestyleHint::for_animations()) { 173 debug_assert!(context.shared.traversal_flags.for_animation_only()); 174 175 if replacements.contains(RestyleHint::RESTYLE_SMIL) { 176 Self::replace_single_rule_node( 177 context.shared, 178 CascadeLevel::SMILOverride, 179 LayerOrder::root(), 180 self.smil_override(), 181 primary_rules, 182 ); 183 } 184 185 if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) { 186 Self::replace_single_rule_node( 187 context.shared, 188 CascadeLevel::Transitions, 189 LayerOrder::root(), 190 self.transition_rule(&context.shared) 191 .as_ref() 192 .map(|a| a.borrow_arc()), 193 primary_rules, 194 ); 195 } 196 197 if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) { 198 Self::replace_single_rule_node( 199 context.shared, 200 CascadeLevel::Animations, 201 LayerOrder::root(), 202 self.animation_rule(&context.shared) 203 .as_ref() 204 .map(|a| a.borrow_arc()), 205 primary_rules, 206 ); 207 } 208 } 209 210 false 211 } 212 213 /// If there is no transition rule in the ComputedValues, it returns None. 214 fn after_change_style( 215 &self, 216 context: &mut StyleContext<Self>, 217 primary_style: &Arc<ComputedValues>, 218 ) -> Option<Arc<ComputedValues>> { 219 // Actually `PseudoElementResolution` doesn't really matter. 220 StyleResolverForElement::new( 221 *self, 222 context, 223 RuleInclusion::All, 224 PseudoElementResolution::IfApplicable, 225 ) 226 .after_change_style(primary_style) 227 } 228 229 fn needs_animations_update( 230 &self, 231 context: &mut StyleContext<Self>, 232 old_style: Option<&ComputedValues>, 233 new_style: &ComputedValues, 234 pseudo_element: Option<PseudoElement>, 235 ) -> bool { 236 let new_ui_style = new_style.get_ui(); 237 let new_style_specifies_animations = new_ui_style.specifies_animations(); 238 239 let has_animations = self.has_css_animations(&context.shared, pseudo_element); 240 if !new_style_specifies_animations && !has_animations { 241 return false; 242 } 243 244 let old_style = match old_style { 245 Some(old) => old, 246 // If we have no old style but have animations, we may be a 247 // pseudo-element which was re-created without style changes. 248 // 249 // This can happen when we reframe the pseudo-element without 250 // restyling it (due to content insertion on a flex container or 251 // such, for example). See bug 1564366. 252 // 253 // FIXME(emilio): The really right fix for this is keeping the 254 // pseudo-element itself around on reframes, but that's a bit 255 // harder. If we do that we can probably remove quite a lot of the 256 // EffectSet complexity though, since right now it's stored on the 257 // parent element for pseudo-elements given we need to keep it 258 // around... 259 None => { 260 return new_style_specifies_animations || new_style.is_pseudo_style(); 261 }, 262 }; 263 264 let old_ui_style = old_style.get_ui(); 265 266 let keyframes_could_have_changed = context 267 .shared 268 .traversal_flags 269 .contains(TraversalFlags::ForCSSRuleChanges); 270 271 // If the traversal is triggered due to changes in CSS rules changes, we 272 // need to try to update all CSS animations on the element if the 273 // element has or will have CSS animation style regardless of whether 274 // the animation is running or not. 275 // 276 // TODO: We should check which @keyframes were added/changed/deleted and 277 // update only animations corresponding to those @keyframes. 278 if keyframes_could_have_changed { 279 return true; 280 } 281 282 // If the animations changed, well... 283 if !old_ui_style.animations_equals(new_ui_style) { 284 return true; 285 } 286 287 let old_display = old_style.clone_display(); 288 let new_display = new_style.clone_display(); 289 290 // If we were display: none, we may need to trigger animations. 291 if old_display == Display::None && new_display != Display::None { 292 return new_style_specifies_animations; 293 } 294 295 // If we are becoming display: none, we may need to stop animations. 296 if old_display != Display::None && new_display == Display::None { 297 return has_animations; 298 } 299 300 // We might need to update animations if writing-mode or direction 301 // changed, and any of the animations contained logical properties. 302 // 303 // We may want to be more granular, but it's probably not worth it. 304 if new_style.writing_mode != old_style.writing_mode { 305 return has_animations; 306 } 307 308 false 309 } 310 311 fn might_need_transitions_update( 312 &self, 313 context: &StyleContext<Self>, 314 old_style: Option<&ComputedValues>, 315 new_style: &ComputedValues, 316 pseudo_element: Option<PseudoElement>, 317 ) -> bool { 318 let old_style = match old_style { 319 Some(v) => v, 320 None => return false, 321 }; 322 323 if !self.has_css_transitions(context.shared, pseudo_element) 324 && !new_style.get_ui().specifies_transitions() 325 { 326 return false; 327 } 328 329 if old_style.clone_display().is_none() { 330 return false; 331 } 332 333 return true; 334 } 335 336 #[cfg(feature = "gecko")] 337 fn maybe_resolve_starting_style( 338 &self, 339 context: &mut StyleContext<Self>, 340 old_values: Option<&Arc<ComputedValues>>, 341 new_styles: &ResolvedElementStyles, 342 ) -> Option<Arc<ComputedValues>> { 343 // For both cases: 344 // 1. If we didn't see any starting-style rules for this given element during full matching. 345 // 2. If there is no transitions specified. 346 // We don't have to resolve starting style. 347 if !new_styles.may_have_starting_style() 348 || !new_styles.primary_style().get_ui().specifies_transitions() 349 { 350 return None; 351 } 352 353 // We resolve starting style only if we don't have before-change-style, or we change from 354 // display:none. 355 if old_values.is_some() 356 && !new_styles 357 .primary_style() 358 .is_display_property_changed_from_none(old_values.map(|s| &**s)) 359 { 360 return None; 361 } 362 363 // Note: Basically, we have to remove transition rules because the starting style for an 364 // element is the after-change style with @starting-style rules applied in addition. 365 // However, we expect there is no transition rules for this element when calling this 366 // function because we do this only when we don't have before-change style or we change 367 // from display:none. In these cases, it's unlikely to have running transitions on this 368 // element. 369 let mut resolver = StyleResolverForElement::new( 370 *self, 371 context, 372 RuleInclusion::All, 373 PseudoElementResolution::IfApplicable, 374 ); 375 376 let starting_style = resolver.resolve_starting_style().style; 377 if starting_style.style().clone_display().is_none() { 378 return None; 379 } 380 381 Some(starting_style.0) 382 } 383 384 /// Handle CSS Transitions. Returns None if we don't need to update transitions. And it returns 385 /// the before-change style per CSS Transitions spec. 386 /// 387 /// Note: The before-change style could be the computed values of all properties on the element 388 /// as of the previous style change event, or the starting style if we don't have the valid 389 /// before-change style there. 390 #[cfg(feature = "gecko")] 391 fn process_transitions( 392 &self, 393 context: &mut StyleContext<Self>, 394 old_values: Option<&Arc<ComputedValues>>, 395 new_styles: &mut ResolvedElementStyles, 396 ) -> Option<Arc<ComputedValues>> { 397 let starting_values = self.maybe_resolve_starting_style(context, old_values, new_styles); 398 let before_change_or_starting = if starting_values.is_some() { 399 starting_values.as_ref() 400 } else { 401 old_values 402 }; 403 let new_values = new_styles.primary_style_mut(); 404 405 if !self.might_need_transitions_update( 406 context, 407 before_change_or_starting.map(|s| &**s), 408 new_values, 409 /* pseudo_element = */ None, 410 ) { 411 return None; 412 } 413 414 let after_change_style = 415 if self.has_css_transitions(context.shared, /* pseudo_element = */ None) { 416 self.after_change_style(context, new_values) 417 } else { 418 None 419 }; 420 421 // In order to avoid creating a SequentialTask for transitions which 422 // may not be updated, we check it per property to make sure Gecko 423 // side will really update transition. 424 if !self.needs_transitions_update( 425 before_change_or_starting.unwrap(), 426 after_change_style.as_ref().unwrap_or(&new_values), 427 ) { 428 return None; 429 } 430 431 if let Some(values_without_transitions) = after_change_style { 432 *new_values = values_without_transitions; 433 } 434 435 // Move the new-created starting style, or clone the old values. 436 if starting_values.is_some() { 437 starting_values 438 } else { 439 old_values.cloned() 440 } 441 } 442 443 #[cfg(feature = "gecko")] 444 fn process_animations( 445 &self, 446 context: &mut StyleContext<Self>, 447 old_styles: &mut ElementStyles, 448 new_styles: &mut ResolvedElementStyles, 449 important_rules_changed: bool, 450 ) { 451 use crate::context::UpdateAnimationsTasks; 452 453 let old_values = &old_styles.primary; 454 if context.shared.traversal_flags.for_animation_only() && old_values.is_some() { 455 return; 456 } 457 458 // Bug 868975: These steps should examine and update the visited styles 459 // in addition to the unvisited styles. 460 461 let mut tasks = UpdateAnimationsTasks::empty(); 462 463 if old_values.as_deref().map_or_else( 464 || { 465 new_styles 466 .primary_style() 467 .get_ui() 468 .specifies_scroll_timelines() 469 }, 470 |old| { 471 !old.get_ui() 472 .scroll_timelines_equals(new_styles.primary_style().get_ui()) 473 }, 474 ) { 475 tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES); 476 } 477 478 if old_values.as_deref().map_or_else( 479 || { 480 new_styles 481 .primary_style() 482 .get_ui() 483 .specifies_view_timelines() 484 }, 485 |old| { 486 !old.get_ui() 487 .view_timelines_equals(new_styles.primary_style().get_ui()) 488 }, 489 ) { 490 tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES); 491 } 492 493 if self.needs_animations_update( 494 context, 495 old_values.as_deref(), 496 new_styles.primary_style(), 497 /* pseudo_element = */ None, 498 ) { 499 tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS); 500 } 501 502 let before_change_style = 503 self.process_transitions(context, old_values.as_ref(), new_styles); 504 if before_change_style.is_some() { 505 tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS); 506 } 507 508 if self.has_animations(&context.shared) { 509 tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES); 510 if important_rules_changed { 511 tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS); 512 } 513 if new_styles 514 .primary_style() 515 .is_display_property_changed_from_none(old_values.as_deref()) 516 { 517 tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE); 518 } 519 } 520 521 if !tasks.is_empty() { 522 let task = crate::context::SequentialTask::update_animations( 523 *self, 524 before_change_style, 525 tasks, 526 ); 527 context.thread_local.tasks.push(task); 528 } 529 } 530 531 #[cfg(feature = "servo")] 532 fn process_animations( 533 &self, 534 context: &mut StyleContext<Self>, 535 old_styles: &mut ElementStyles, 536 new_resolved_styles: &mut ResolvedElementStyles, 537 _important_rules_changed: bool, 538 ) { 539 use crate::animation::AnimationSetKey; 540 use crate::dom::TDocument; 541 542 let style_changed = self.process_animations_for_style( 543 context, 544 &mut old_styles.primary, 545 new_resolved_styles.primary_style_mut(), 546 /* pseudo_element = */ None, 547 ); 548 549 // If we have modified animation or transitions, we recascade style for this node. 550 if style_changed { 551 let primary_style = new_resolved_styles.primary_style(); 552 let mut rule_node = primary_style.rules().clone(); 553 let declarations = context.shared.animations.get_all_declarations( 554 &AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()), 555 context.shared.current_time_for_animations, 556 self.as_node().owner_doc().shared_lock(), 557 ); 558 Self::replace_single_rule_node( 559 &context.shared, 560 CascadeLevel::Transitions, 561 LayerOrder::root(), 562 declarations.transitions.as_ref().map(|a| a.borrow_arc()), 563 &mut rule_node, 564 ); 565 Self::replace_single_rule_node( 566 &context.shared, 567 CascadeLevel::Animations, 568 LayerOrder::root(), 569 declarations.animations.as_ref().map(|a| a.borrow_arc()), 570 &mut rule_node, 571 ); 572 573 if rule_node != *primary_style.rules() { 574 let inputs = CascadeInputs { 575 rules: Some(rule_node), 576 visited_rules: primary_style.visited_rules().cloned(), 577 flags: primary_style.flags.for_cascade_inputs(), 578 }; 579 580 new_resolved_styles.primary.style = StyleResolverForElement::new( 581 *self, 582 context, 583 RuleInclusion::All, 584 PseudoElementResolution::IfApplicable, 585 ) 586 .cascade_style_and_visited_with_default_parents(inputs); 587 } 588 } 589 590 self.process_animations_for_pseudo( 591 context, 592 old_styles, 593 new_resolved_styles, 594 PseudoElement::Before, 595 ); 596 self.process_animations_for_pseudo( 597 context, 598 old_styles, 599 new_resolved_styles, 600 PseudoElement::After, 601 ); 602 } 603 604 #[cfg(feature = "servo")] 605 fn process_animations_for_pseudo( 606 &self, 607 context: &mut StyleContext<Self>, 608 old_styles: &ElementStyles, 609 new_resolved_styles: &mut ResolvedElementStyles, 610 pseudo_element: PseudoElement, 611 ) { 612 use crate::animation::AnimationSetKey; 613 use crate::dom::TDocument; 614 615 let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone()); 616 let style = match new_resolved_styles.pseudos.get(&pseudo_element) { 617 Some(style) => Arc::clone(style), 618 None => { 619 context 620 .shared 621 .animations 622 .cancel_all_animations_for_key(&key); 623 return; 624 }, 625 }; 626 627 let old_style = old_styles.pseudos.get(&pseudo_element).cloned(); 628 self.process_animations_for_style( 629 context, 630 &old_style, 631 &style, 632 Some(pseudo_element.clone()), 633 ); 634 635 let declarations = context.shared.animations.get_all_declarations( 636 &key, 637 context.shared.current_time_for_animations, 638 self.as_node().owner_doc().shared_lock(), 639 ); 640 if declarations.is_empty() { 641 return; 642 } 643 644 let mut rule_node = style.rules().clone(); 645 Self::replace_single_rule_node( 646 &context.shared, 647 CascadeLevel::Transitions, 648 LayerOrder::root(), 649 declarations.transitions.as_ref().map(|a| a.borrow_arc()), 650 &mut rule_node, 651 ); 652 Self::replace_single_rule_node( 653 &context.shared, 654 CascadeLevel::Animations, 655 LayerOrder::root(), 656 declarations.animations.as_ref().map(|a| a.borrow_arc()), 657 &mut rule_node, 658 ); 659 if rule_node == *style.rules() { 660 return; 661 } 662 663 let inputs = CascadeInputs { 664 rules: Some(rule_node), 665 visited_rules: style.visited_rules().cloned(), 666 flags: style.flags.for_cascade_inputs(), 667 }; 668 669 let new_style = StyleResolverForElement::new( 670 *self, 671 context, 672 RuleInclusion::All, 673 PseudoElementResolution::IfApplicable, 674 ) 675 .cascade_style_and_visited_for_pseudo_with_default_parents( 676 inputs, 677 &pseudo_element, 678 &new_resolved_styles.primary, 679 ); 680 681 new_resolved_styles 682 .pseudos 683 .set(&pseudo_element, new_style.0); 684 } 685 686 #[cfg(feature = "servo")] 687 fn process_animations_for_style( 688 &self, 689 context: &mut StyleContext<Self>, 690 old_values: &Option<Arc<ComputedValues>>, 691 new_values: &Arc<ComputedValues>, 692 pseudo_element: Option<PseudoElement>, 693 ) -> bool { 694 use crate::animation::{AnimationSetKey, AnimationState}; 695 696 // We need to call this before accessing the `ElementAnimationSet` from the 697 // map because this call will do a RwLock::read(). 698 let needs_animations_update = self.needs_animations_update( 699 context, 700 old_values.as_deref(), 701 new_values, 702 pseudo_element, 703 ); 704 705 let might_need_transitions_update = self.might_need_transitions_update( 706 context, 707 old_values.as_deref(), 708 new_values, 709 pseudo_element, 710 ); 711 712 let mut after_change_style = None; 713 if might_need_transitions_update { 714 after_change_style = self.after_change_style(context, new_values); 715 } 716 717 let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); 718 let shared_context = context.shared; 719 let mut animation_set = shared_context 720 .animations 721 .sets 722 .write() 723 .remove(&key) 724 .unwrap_or_default(); 725 726 // Starting animations is expensive, because we have to recalculate the style 727 // for all the keyframes. We only want to do this if we think that there's a 728 // chance that the animations really changed. 729 if needs_animations_update { 730 let mut resolver = StyleResolverForElement::new( 731 *self, 732 context, 733 RuleInclusion::All, 734 PseudoElementResolution::IfApplicable, 735 ); 736 737 animation_set.update_animations_for_new_style::<Self>( 738 *self, 739 &shared_context, 740 &new_values, 741 &mut resolver, 742 ); 743 } 744 745 animation_set.update_transitions_for_new_style( 746 might_need_transitions_update, 747 &shared_context, 748 old_values.as_ref(), 749 after_change_style.as_ref().unwrap_or(new_values), 750 ); 751 752 // This should change the computed values in the style, so we don't need 753 // to mark this set as dirty. 754 animation_set 755 .transitions 756 .retain(|transition| transition.state != AnimationState::Finished); 757 758 animation_set 759 .animations 760 .retain(|animation| animation.state != AnimationState::Finished); 761 762 // If the ElementAnimationSet is empty, and don't store it in order to 763 // save memory and to avoid extra processing later. 764 let changed_animations = animation_set.dirty; 765 if !animation_set.is_empty() { 766 animation_set.dirty = false; 767 shared_context 768 .animations 769 .sets 770 .write() 771 .insert(key, animation_set); 772 } 773 774 changed_animations 775 } 776 777 /// Computes and applies non-redundant damage. 778 fn accumulate_damage_for( 779 &self, 780 shared_context: &SharedStyleContext, 781 damage: &mut RestyleDamage, 782 old_values: &ComputedValues, 783 new_values: &ComputedValues, 784 pseudo: Option<&PseudoElement>, 785 ) -> ChildRestyleRequirement { 786 debug!("accumulate_damage_for: {:?}", self); 787 debug_assert!(!shared_context 788 .traversal_flags 789 .contains(TraversalFlags::FinalAnimationTraversal)); 790 791 let difference = self.compute_style_difference(old_values, new_values, pseudo); 792 793 *damage |= difference.damage; 794 795 debug!(" > style difference: {:?}", difference); 796 797 // We need to cascade the children in order to ensure the correct 798 // propagation of inherited computed value flags. 799 if old_values.flags.maybe_inherited() != new_values.flags.maybe_inherited() { 800 debug!( 801 " > flags changed: {:?} != {:?}", 802 old_values.flags, new_values.flags 803 ); 804 return ChildRestyleRequirement::MustCascadeChildren; 805 } 806 807 if old_values.effective_zoom != new_values.effective_zoom { 808 // Zoom changes need to get propagated to children. 809 debug!( 810 " > zoom changed: {:?} != {:?}", 811 old_values.effective_zoom, new_values.effective_zoom 812 ); 813 return ChildRestyleRequirement::MustCascadeChildren; 814 } 815 816 match difference.change { 817 StyleChange::Unchanged => return ChildRestyleRequirement::CanSkipCascade, 818 StyleChange::Changed { reset_only } => { 819 // If inherited properties changed, the best we can do is 820 // cascade the children. 821 if !reset_only { 822 return ChildRestyleRequirement::MustCascadeChildren; 823 } 824 }, 825 } 826 827 let old_display = old_values.clone_display(); 828 let new_display = new_values.clone_display(); 829 830 if old_display != new_display { 831 // If we used to be a display: none element, and no longer are, our 832 // children need to be restyled because they're unstyled. 833 if old_display == Display::None { 834 return ChildRestyleRequirement::MustCascadeChildren; 835 } 836 // Blockification of children may depend on our display value, 837 // so we need to actually do the recascade. We could potentially 838 // do better, but it doesn't seem worth it. 839 if old_display.is_item_container() != new_display.is_item_container() { 840 return ChildRestyleRequirement::MustCascadeChildren; 841 } 842 // We may also need to blockify and un-blockify descendants if our 843 // display goes from / to display: contents, since the "layout 844 // parent style" changes. 845 if old_display.is_contents() || new_display.is_contents() { 846 return ChildRestyleRequirement::MustCascadeChildren; 847 } 848 // Line break suppression may also be affected if the display 849 // type changes from ruby to non-ruby. 850 #[cfg(feature = "gecko")] 851 { 852 if old_display.is_ruby_type() != new_display.is_ruby_type() { 853 return ChildRestyleRequirement::MustCascadeChildren; 854 } 855 } 856 } 857 858 // Children with justify-items: auto may depend on our 859 // justify-items property value. 860 // 861 // Similarly, we could potentially do better, but this really 862 // seems not common enough to care about. 863 #[cfg(feature = "gecko")] 864 { 865 use crate::values::specified::align::AlignFlags; 866 867 let old_justify_items = old_values.get_position().clone_justify_items(); 868 let new_justify_items = new_values.get_position().clone_justify_items(); 869 870 let was_legacy_justify_items = old_justify_items.computed.contains(AlignFlags::LEGACY); 871 872 let is_legacy_justify_items = new_justify_items.computed.contains(AlignFlags::LEGACY); 873 874 if is_legacy_justify_items != was_legacy_justify_items { 875 return ChildRestyleRequirement::MustCascadeChildren; 876 } 877 878 if was_legacy_justify_items && old_justify_items.computed != new_justify_items.computed 879 { 880 return ChildRestyleRequirement::MustCascadeChildren; 881 } 882 } 883 884 #[cfg(feature = "servo")] 885 { 886 // We may need to set or propagate the CAN_BE_FRAGMENTED bit 887 // on our children. 888 if old_values.is_multicol() != new_values.is_multicol() { 889 return ChildRestyleRequirement::MustCascadeChildren; 890 } 891 } 892 893 // We could prove that, if our children don't inherit reset 894 // properties, we can stop the cascade. 895 ChildRestyleRequirement::MustCascadeChildrenIfInheritResetStyle 896 } 897 } 898 899 impl<E: TElement> PrivateMatchMethods for E {} 900 901 /// The public API that elements expose for selector matching. 902 pub trait MatchMethods: TElement { 903 /// Returns the closest parent element that doesn't have a display: contents 904 /// style (and thus generates a box). 905 /// 906 /// This is needed to correctly handle blockification of flex and grid 907 /// items. 908 /// 909 /// Returns itself if the element has no parent. In practice this doesn't 910 /// happen because the root element is blockified per spec, but it could 911 /// happen if we decide to not blockify for roots of disconnected subtrees, 912 /// which is a kind of dubious behavior. 913 fn layout_parent(&self) -> Self { 914 let mut current = self.clone(); 915 loop { 916 current = match current.traversal_parent() { 917 Some(el) => el, 918 None => return current, 919 }; 920 921 let is_display_contents = current 922 .borrow_data() 923 .unwrap() 924 .styles 925 .primary() 926 .is_display_contents(); 927 928 if !is_display_contents { 929 return current; 930 } 931 } 932 } 933 934 /// Updates the styles with the new ones, diffs them, and stores the restyle 935 /// damage. 936 fn finish_restyle( 937 &self, 938 context: &mut StyleContext<Self>, 939 data: &mut ElementData, 940 mut new_styles: ResolvedElementStyles, 941 important_rules_changed: bool, 942 ) -> ChildRestyleRequirement { 943 use std::cmp; 944 945 self.process_animations( 946 context, 947 &mut data.styles, 948 &mut new_styles, 949 important_rules_changed, 950 ); 951 952 // First of all, update the styles. 953 let old_styles = data.set_styles(new_styles); 954 955 let new_primary_style = data.styles.primary.as_ref().unwrap(); 956 957 let mut restyle_requirement = ChildRestyleRequirement::CanSkipCascade; 958 let is_root = new_primary_style 959 .flags 960 .contains(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE); 961 let is_container = !new_primary_style 962 .get_box() 963 .clone_container_type() 964 .is_normal(); 965 if is_root || is_container { 966 let device = context.shared.stylist.device(); 967 let old_style = old_styles.primary.as_ref(); 968 let new_font_size = new_primary_style.get_font().clone_font_size(); 969 let old_font_size = old_style.map(|s| s.get_font().clone_font_size()); 970 971 // For line-height, we want the fully resolved value, as `normal` also depends on other 972 // font properties. 973 let new_line_height = device 974 .calc_line_height( 975 &new_primary_style.get_font(), 976 new_primary_style.writing_mode, 977 None, 978 ) 979 .0; 980 let old_line_height = old_style.map(|s| { 981 device 982 .calc_line_height(&s.get_font(), s.writing_mode, None) 983 .0 984 }); 985 986 // Update root font-relative units. If any of these unit values changed 987 // since last time, ensure that we recascade the entire tree. 988 if is_root { 989 debug_assert!(self.owner_doc_matches_for_testing(device)); 990 device.set_root_style(new_primary_style); 991 992 // Update root font size for rem units 993 if old_font_size != Some(new_font_size) { 994 let size = new_font_size.computed_size(); 995 device.set_root_font_size(new_primary_style.effective_zoom.unzoom(size.px())); 996 if device.used_root_font_size() { 997 restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants; 998 } 999 } 1000 1001 // Update root line height for rlh units 1002 if old_line_height != Some(new_line_height) { 1003 device.set_root_line_height( 1004 new_primary_style 1005 .effective_zoom 1006 .unzoom(new_line_height.px()), 1007 ); 1008 if device.used_root_line_height() { 1009 restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants; 1010 } 1011 } 1012 1013 // Update root font metrics for rcap, rch, rex, ric units. Since querying 1014 // font metrics can be an expensive call, they are only updated if these 1015 // units are used in the document. 1016 if device.used_root_font_metrics() && device.update_root_font_metrics() { 1017 restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants; 1018 } 1019 } 1020 1021 if is_container 1022 && (old_font_size.is_some_and(|old| old != new_font_size) 1023 || old_line_height.is_some_and(|old| old != new_line_height)) 1024 { 1025 // TODO(emilio): Maybe only do this if we were matched 1026 // against relative font sizes? 1027 // Also, maybe we should do this as well for font-family / 1028 // etc changes (for ex/ch/ic units to work correctly)? We 1029 // should probably do the optimization mentioned above if 1030 // so. 1031 restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; 1032 } 1033 } 1034 1035 if context.shared.stylist.quirks_mode() == QuirksMode::Quirks { 1036 if self.is_html_document_body_element() { 1037 // NOTE(emilio): We _could_ handle dynamic changes to it if it 1038 // changes and before we reach our children the cascade stops, 1039 // but we don't track right now whether we use the document body 1040 // color, and nobody else handles that properly anyway. 1041 let device = context.shared.stylist.device(); 1042 1043 // Needed for the "inherit from body" quirk. 1044 let text_color = new_primary_style.get_inherited_text().clone_color(); 1045 device.set_body_text_color(text_color); 1046 } 1047 } 1048 1049 // Don't accumulate damage if we're in the final animation traversal. 1050 if context 1051 .shared 1052 .traversal_flags 1053 .contains(TraversalFlags::FinalAnimationTraversal) 1054 { 1055 return ChildRestyleRequirement::MustCascadeChildren; 1056 } 1057 1058 // Also, don't do anything if there was no style. 1059 let old_primary_style = match old_styles.primary { 1060 Some(s) => s, 1061 None => return ChildRestyleRequirement::MustCascadeChildren, 1062 }; 1063 1064 let old_container_type = old_primary_style.clone_container_type(); 1065 let new_container_type = new_primary_style.clone_container_type(); 1066 if old_container_type != new_container_type && !new_container_type.is_size_container_type() 1067 { 1068 // Stopped being a size container. Re-evaluate container queries and units on all our descendants. 1069 // Changes into and between different size containment is handled in `UpdateContainerQueryStyles`. 1070 restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; 1071 } else if old_container_type.is_size_container_type() 1072 && !old_primary_style.is_display_contents() 1073 && new_primary_style.is_display_contents() 1074 { 1075 // Also re-evaluate when a container gets 'display: contents', since size queries will now evaluate to unknown. 1076 // Other displays like 'inline' will keep generating a box, so they are handled in `UpdateContainerQueryStyles`. 1077 restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; 1078 } 1079 1080 restyle_requirement = cmp::max( 1081 restyle_requirement, 1082 self.accumulate_damage_for( 1083 context.shared, 1084 &mut data.damage, 1085 &old_primary_style, 1086 new_primary_style, 1087 None, 1088 ), 1089 ); 1090 1091 if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() { 1092 // This is the common case; no need to examine pseudos here. 1093 return restyle_requirement; 1094 } 1095 1096 let pseudo_styles = old_styles 1097 .pseudos 1098 .as_array() 1099 .iter() 1100 .zip(data.styles.pseudos.as_array().iter()); 1101 1102 for (i, (old, new)) in pseudo_styles.enumerate() { 1103 match (old, new) { 1104 (&Some(ref old), &Some(ref new)) => { 1105 self.accumulate_damage_for( 1106 context.shared, 1107 &mut data.damage, 1108 old, 1109 new, 1110 Some(&PseudoElement::from_eager_index(i)), 1111 ); 1112 }, 1113 (&None, &None) => {}, 1114 _ => { 1115 // It's possible that we're switching from not having 1116 // ::before/::after at all to having styles for them but not 1117 // actually having a useful pseudo-element. Check for that 1118 // case. 1119 let pseudo = PseudoElement::from_eager_index(i); 1120 let new_pseudo_should_exist = 1121 new.as_ref().map_or(false, |s| pseudo.should_exist(s)); 1122 let old_pseudo_should_exist = 1123 old.as_ref().map_or(false, |s| pseudo.should_exist(s)); 1124 if new_pseudo_should_exist != old_pseudo_should_exist { 1125 data.damage |= RestyleDamage::reconstruct(); 1126 return restyle_requirement; 1127 } 1128 }, 1129 } 1130 } 1131 1132 restyle_requirement 1133 } 1134 1135 /// Updates the rule nodes without re-running selector matching, using just 1136 /// the rule tree. 1137 /// 1138 /// Returns true if an !important rule was replaced. 1139 fn replace_rules( 1140 &self, 1141 replacements: RestyleHint, 1142 context: &mut StyleContext<Self>, 1143 cascade_inputs: &mut ElementCascadeInputs, 1144 ) -> bool { 1145 let mut result = false; 1146 result |= self.replace_rules_internal( 1147 replacements, 1148 context, 1149 CascadeVisitedMode::Unvisited, 1150 cascade_inputs, 1151 ); 1152 result |= self.replace_rules_internal( 1153 replacements, 1154 context, 1155 CascadeVisitedMode::Visited, 1156 cascade_inputs, 1157 ); 1158 result 1159 } 1160 1161 /// Given the old and new style of this element, and whether it's a 1162 /// pseudo-element, compute the restyle damage used to determine which 1163 /// kind of layout or painting operations we'll need. 1164 fn compute_style_difference( 1165 &self, 1166 old_values: &ComputedValues, 1167 new_values: &ComputedValues, 1168 pseudo: Option<&PseudoElement>, 1169 ) -> StyleDifference { 1170 debug_assert!(pseudo.map_or(true, |p| p.is_eager())); 1171 #[cfg(feature = "gecko")] 1172 { 1173 RestyleDamage::compute_style_difference(old_values, new_values) 1174 } 1175 #[cfg(feature = "servo")] 1176 { 1177 RestyleDamage::compute_style_difference::<Self>(old_values, new_values) 1178 } 1179 } 1180 } 1181 1182 impl<E: TElement> MatchMethods for E {}