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 use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap}; 12 use crate::context::{SharedStyleContext, StyleContext}; 13 use crate::data::ElementData; 14 use crate::dom::TElement; 15 #[cfg(feature = "servo")] 16 use crate::dom::{OpaqueNode, TNode}; 17 use crate::invalidation::element::restyle_hints::RestyleHint; 18 use crate::properties::longhands::display::computed_value::T as Display; 19 use crate::properties::ComputedValues; 20 use crate::rule_tree::{CascadeLevel, StrongRuleNode}; 21 use crate::selector_parser::{PseudoElement, RestyleDamage}; 22 use crate::style_resolver::ResolvedElementStyles; 23 use crate::traversal_flags::TraversalFlags; 24 use selectors::matching::ElementSelectorFlags; 25 use servo_arc::{Arc, ArcBorrow}; 26 27 /// Represents the result of comparing an element's old and new style. 28 #[derive(Debug)] 29 pub struct StyleDifference { 30 /// The resulting damage. 31 pub damage: RestyleDamage, 32 33 /// Whether any styles changed. 34 pub change: StyleChange, 35 } 36 37 /// Represents whether or not the style of an element has changed. 38 #[derive(Clone, Copy, Debug)] 39 pub enum StyleChange { 40 /// The style hasn't changed. 41 Unchanged, 42 /// The style has changed. 43 Changed { 44 /// Whether only reset structs changed. 45 reset_only: bool, 46 }, 47 } 48 49 /// Whether or not newly computed values for an element need to be cascade 50 /// to children. 51 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 52 pub enum ChildCascadeRequirement { 53 /// Old and new computed values were the same, or we otherwise know that 54 /// we won't bother recomputing style for children, so we can skip cascading 55 /// the new values into child elements. 56 CanSkipCascade = 0, 57 /// The same as `MustCascadeChildren`, but we only need to actually 58 /// recascade if the child inherits any explicit reset style. 59 MustCascadeChildrenIfInheritResetStyle = 1, 60 /// Old and new computed values were different, so we must cascade the 61 /// new values to children. 62 MustCascadeChildren = 2, 63 /// The same as `MustCascadeChildren`, but for the entire subtree. This is 64 /// used to handle root font-size updates needing to recascade the whole 65 /// document. 66 MustCascadeDescendants = 3, 67 } 68 69 impl ChildCascadeRequirement { 70 /// Whether we can unconditionally skip the cascade. can_skip_cascade(&self) -> bool71 pub fn can_skip_cascade(&self) -> bool { 72 matches!(*self, ChildCascadeRequirement::CanSkipCascade) 73 } 74 } 75 76 /// Determines which styles are being cascaded currently. 77 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 78 enum CascadeVisitedMode { 79 /// Cascade the regular, unvisited styles. 80 Unvisited, 81 /// Cascade the styles used when an element's relevant link is visited. A 82 /// "relevant link" is the element being matched if it is a link or the 83 /// nearest ancestor link. 84 Visited, 85 } 86 87 trait PrivateMatchMethods: TElement { 88 /// Updates the rule nodes without re-running selector matching, using just 89 /// the rule tree, for a specific visited mode. 90 /// 91 /// Returns true if an !important rule was replaced. replace_rules_internal( &self, replacements: RestyleHint, context: &mut StyleContext<Self>, cascade_visited: CascadeVisitedMode, cascade_inputs: &mut ElementCascadeInputs, ) -> bool92 fn replace_rules_internal( 93 &self, 94 replacements: RestyleHint, 95 context: &mut StyleContext<Self>, 96 cascade_visited: CascadeVisitedMode, 97 cascade_inputs: &mut ElementCascadeInputs, 98 ) -> bool { 99 use crate::properties::PropertyDeclarationBlock; 100 use crate::shared_lock::Locked; 101 102 debug_assert!( 103 replacements.intersects(RestyleHint::replacements()) && 104 (replacements & !RestyleHint::replacements()).is_empty() 105 ); 106 107 let stylist = &context.shared.stylist; 108 let guards = &context.shared.guards; 109 110 let primary_rules = match cascade_visited { 111 CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(), 112 CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(), 113 }; 114 115 let primary_rules = match primary_rules { 116 Some(r) => r, 117 None => return false, 118 }; 119 120 let replace_rule_node = |level: CascadeLevel, 121 pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, 122 path: &mut StrongRuleNode| 123 -> bool { 124 let mut important_rules_changed = false; 125 let new_node = stylist.rule_tree().update_rule_at_level( 126 level, 127 pdb, 128 path, 129 guards, 130 &mut important_rules_changed, 131 ); 132 if let Some(n) = new_node { 133 *path = n; 134 } 135 important_rules_changed 136 }; 137 138 if !context.shared.traversal_flags.for_animation_only() { 139 let mut result = false; 140 if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) { 141 let style_attribute = self.style_attribute(); 142 result |= replace_rule_node( 143 CascadeLevel::same_tree_author_normal(), 144 style_attribute, 145 primary_rules, 146 ); 147 result |= replace_rule_node( 148 CascadeLevel::same_tree_author_important(), 149 style_attribute, 150 primary_rules, 151 ); 152 // FIXME(emilio): Still a hack! 153 self.unset_dirty_style_attribute(); 154 } 155 return result; 156 } 157 158 // Animation restyle hints are processed prior to other restyle 159 // hints in the animation-only traversal. 160 // 161 // Non-animation restyle hints will be processed in a subsequent 162 // normal traversal. 163 if replacements.intersects(RestyleHint::for_animations()) { 164 debug_assert!(context.shared.traversal_flags.for_animation_only()); 165 166 if replacements.contains(RestyleHint::RESTYLE_SMIL) { 167 replace_rule_node( 168 CascadeLevel::SMILOverride, 169 self.smil_override(), 170 primary_rules, 171 ); 172 } 173 174 if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) { 175 replace_rule_node( 176 CascadeLevel::Transitions, 177 self.transition_rule().as_ref().map(|a| a.borrow_arc()), 178 primary_rules, 179 ); 180 } 181 182 if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) { 183 replace_rule_node( 184 CascadeLevel::Animations, 185 self.animation_rule().as_ref().map(|a| a.borrow_arc()), 186 primary_rules, 187 ); 188 } 189 } 190 191 false 192 } 193 194 /// If there is no transition rule in the ComputedValues, it returns None. 195 #[cfg(feature = "gecko")] after_change_style( &self, context: &mut StyleContext<Self>, primary_style: &Arc<ComputedValues>, ) -> Option<Arc<ComputedValues>>196 fn after_change_style( 197 &self, 198 context: &mut StyleContext<Self>, 199 primary_style: &Arc<ComputedValues>, 200 ) -> Option<Arc<ComputedValues>> { 201 use crate::context::CascadeInputs; 202 use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; 203 use crate::stylist::RuleInclusion; 204 205 let rule_node = primary_style.rules(); 206 let without_transition_rules = context 207 .shared 208 .stylist 209 .rule_tree() 210 .remove_transition_rule_if_applicable(rule_node); 211 if without_transition_rules == *rule_node { 212 // We don't have transition rule in this case, so return None to let 213 // the caller use the original ComputedValues. 214 return None; 215 } 216 217 // FIXME(bug 868975): We probably need to transition visited style as 218 // well. 219 let inputs = CascadeInputs { 220 rules: Some(without_transition_rules), 221 visited_rules: primary_style.visited_rules().cloned(), 222 }; 223 224 // Actually `PseudoElementResolution` doesn't really matter. 225 let style = StyleResolverForElement::new( 226 *self, 227 context, 228 RuleInclusion::All, 229 PseudoElementResolution::IfApplicable, 230 ) 231 .cascade_style_and_visited_with_default_parents(inputs); 232 233 Some(style.0) 234 } 235 236 #[cfg(feature = "gecko")] needs_animations_update( &self, context: &mut StyleContext<Self>, old_style: Option<&ComputedValues>, new_style: &ComputedValues, ) -> bool237 fn needs_animations_update( 238 &self, 239 context: &mut StyleContext<Self>, 240 old_style: Option<&ComputedValues>, 241 new_style: &ComputedValues, 242 ) -> bool { 243 let new_box_style = new_style.get_box(); 244 let new_style_specifies_animations = new_box_style.specifies_animations(); 245 246 let has_animations = self.has_css_animations(); 247 if !new_style_specifies_animations && !has_animations { 248 return false; 249 } 250 251 let old_style = match old_style { 252 Some(old) => old, 253 // If we have no old style but have animations, we may be a 254 // pseudo-element which was re-created without style changes. 255 // 256 // This can happen when we reframe the pseudo-element without 257 // restyling it (due to content insertion on a flex container or 258 // such, for example). See bug 1564366. 259 // 260 // FIXME(emilio): The really right fix for this is keeping the 261 // pseudo-element itself around on reframes, but that's a bit 262 // harder. If we do that we can probably remove quite a lot of the 263 // EffectSet complexity though, since right now it's stored on the 264 // parent element for pseudo-elements given we need to keep it 265 // around... 266 None => { 267 return new_style_specifies_animations || new_style.is_pseudo_style(); 268 }, 269 }; 270 271 let old_box_style = old_style.get_box(); 272 273 let keyframes_could_have_changed = context 274 .shared 275 .traversal_flags 276 .contains(TraversalFlags::ForCSSRuleChanges); 277 278 // If the traversal is triggered due to changes in CSS rules changes, we 279 // need to try to update all CSS animations on the element if the 280 // element has or will have CSS animation style regardless of whether 281 // the animation is running or not. 282 // 283 // TODO: We should check which @keyframes were added/changed/deleted and 284 // update only animations corresponding to those @keyframes. 285 if keyframes_could_have_changed { 286 return true; 287 } 288 289 // If the animations changed, well... 290 if !old_box_style.animations_equals(new_box_style) { 291 return true; 292 } 293 294 let old_display = old_box_style.clone_display(); 295 let new_display = new_box_style.clone_display(); 296 297 // If we were display: none, we may need to trigger animations. 298 if old_display == Display::None && new_display != Display::None { 299 return new_style_specifies_animations; 300 } 301 302 // If we are becoming display: none, we may need to stop animations. 303 if old_display != Display::None && new_display == Display::None { 304 return has_animations; 305 } 306 307 // We might need to update animations if writing-mode or direction 308 // changed, and any of the animations contained logical properties. 309 // 310 // We may want to be more granular, but it's probably not worth it. 311 if new_style.writing_mode != old_style.writing_mode { 312 return has_animations; 313 } 314 315 false 316 } 317 318 /// Create a SequentialTask for resolving descendants in a SMIL display 319 /// property animation if the display property changed from none. 320 #[cfg(feature = "gecko")] handle_display_change_for_smil_if_needed( &self, context: &mut StyleContext<Self>, old_values: Option<&ComputedValues>, new_values: &ComputedValues, restyle_hints: RestyleHint, )321 fn handle_display_change_for_smil_if_needed( 322 &self, 323 context: &mut StyleContext<Self>, 324 old_values: Option<&ComputedValues>, 325 new_values: &ComputedValues, 326 restyle_hints: RestyleHint, 327 ) { 328 use crate::context::PostAnimationTasks; 329 330 if !restyle_hints.intersects(RestyleHint::RESTYLE_SMIL) { 331 return; 332 } 333 334 if new_values.is_display_property_changed_from_none(old_values) { 335 // When display value is changed from none to other, we need to 336 // traverse descendant elements in a subsequent normal 337 // traversal (we can't traverse them in this animation-only restyle 338 // since we have no way to know whether the decendants 339 // need to be traversed at the beginning of the animation-only 340 // restyle). 341 let task = ::context::SequentialTask::process_post_animation( 342 *self, 343 PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL, 344 ); 345 context.thread_local.tasks.push(task); 346 } 347 } 348 349 #[cfg(feature = "gecko")] process_animations( &self, context: &mut StyleContext<Self>, old_values: &mut Option<Arc<ComputedValues>>, new_values: &mut Arc<ComputedValues>, restyle_hint: RestyleHint, important_rules_changed: bool, )350 fn process_animations( 351 &self, 352 context: &mut StyleContext<Self>, 353 old_values: &mut Option<Arc<ComputedValues>>, 354 new_values: &mut Arc<ComputedValues>, 355 restyle_hint: RestyleHint, 356 important_rules_changed: bool, 357 ) { 358 use crate::context::UpdateAnimationsTasks; 359 360 if context.shared.traversal_flags.for_animation_only() { 361 self.handle_display_change_for_smil_if_needed( 362 context, 363 old_values.as_ref().map(|v| &**v), 364 new_values, 365 restyle_hint, 366 ); 367 return; 368 } 369 370 // Bug 868975: These steps should examine and update the visited styles 371 // in addition to the unvisited styles. 372 373 let mut tasks = UpdateAnimationsTasks::empty(); 374 if self.needs_animations_update(context, old_values.as_ref().map(|s| &**s), new_values) { 375 tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS); 376 } 377 378 let before_change_style = if self 379 .might_need_transitions_update(old_values.as_ref().map(|s| &**s), new_values) 380 { 381 let after_change_style = if self.has_css_transitions() { 382 self.after_change_style(context, new_values) 383 } else { 384 None 385 }; 386 387 // In order to avoid creating a SequentialTask for transitions which 388 // may not be updated, we check it per property to make sure Gecko 389 // side will really update transition. 390 let needs_transitions_update = { 391 // We borrow new_values here, so need to add a scope to make 392 // sure we release it before assigning a new value to it. 393 let after_change_style_ref = after_change_style.as_ref().unwrap_or(&new_values); 394 395 self.needs_transitions_update(old_values.as_ref().unwrap(), after_change_style_ref) 396 }; 397 398 if needs_transitions_update { 399 if let Some(values_without_transitions) = after_change_style { 400 *new_values = values_without_transitions; 401 } 402 tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS); 403 404 // We need to clone old_values into SequentialTask, so we can 405 // use it later. 406 old_values.clone() 407 } else { 408 None 409 } 410 } else { 411 None 412 }; 413 414 if self.has_animations() { 415 tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES); 416 if important_rules_changed { 417 tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS); 418 } 419 if new_values.is_display_property_changed_from_none(old_values.as_ref().map(|s| &**s)) { 420 tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE); 421 } 422 } 423 424 if !tasks.is_empty() { 425 let task = 426 ::context::SequentialTask::update_animations(*self, before_change_style, tasks); 427 context.thread_local.tasks.push(task); 428 } 429 } 430 431 #[cfg(feature = "servo")] process_animations( &self, context: &mut StyleContext<Self>, old_values: &mut Option<Arc<ComputedValues>>, new_values: &mut Arc<ComputedValues>, _restyle_hint: RestyleHint, _important_rules_changed: bool, )432 fn process_animations( 433 &self, 434 context: &mut StyleContext<Self>, 435 old_values: &mut Option<Arc<ComputedValues>>, 436 new_values: &mut Arc<ComputedValues>, 437 _restyle_hint: RestyleHint, 438 _important_rules_changed: bool, 439 ) { 440 use crate::animation; 441 442 let this_opaque = self.as_node().opaque(); 443 let mut expired_transitions = vec![]; 444 let shared_context = context.shared; 445 if let Some(ref mut old_values) = *old_values { 446 // We apply the expired transitions and animations to the old style 447 // here, because we later compare the old style to the new style in 448 // `start_transitions_if_applicable`. If the styles differ then it will 449 // cause the expired transition to restart. 450 // 451 // TODO(mrobinson): We should really be following spec behavior and calculate 452 // after-change-style and before-change-style here. 453 Self::collect_and_update_style_for_expired_transitions( 454 shared_context, 455 this_opaque, 456 old_values, 457 &mut expired_transitions, 458 ); 459 460 Self::update_style_for_animations( 461 shared_context, 462 this_opaque, 463 old_values, 464 &context.thread_local.font_metrics_provider, 465 ); 466 } 467 468 let new_animations_sender = &context.thread_local.new_animations_sender; 469 // Trigger any present animations if necessary. 470 animation::maybe_start_animations( 471 *self, 472 &shared_context, 473 new_animations_sender, 474 this_opaque, 475 &new_values, 476 ); 477 478 // Trigger transitions if necessary. This will set `new_values` to 479 // the starting value of the transition if it did trigger a transition. 480 if let Some(ref values) = old_values { 481 animation::update_transitions( 482 &shared_context, 483 new_animations_sender, 484 this_opaque, 485 &values, 486 new_values, 487 &expired_transitions, 488 ); 489 } 490 } 491 492 /// Computes and applies non-redundant damage. accumulate_damage_for( &self, shared_context: &SharedStyleContext, damage: &mut RestyleDamage, old_values: &ComputedValues, new_values: &ComputedValues, pseudo: Option<&PseudoElement>, ) -> ChildCascadeRequirement493 fn accumulate_damage_for( 494 &self, 495 shared_context: &SharedStyleContext, 496 damage: &mut RestyleDamage, 497 old_values: &ComputedValues, 498 new_values: &ComputedValues, 499 pseudo: Option<&PseudoElement>, 500 ) -> ChildCascadeRequirement { 501 debug!("accumulate_damage_for: {:?}", self); 502 debug_assert!(!shared_context 503 .traversal_flags 504 .contains(TraversalFlags::FinalAnimationTraversal)); 505 506 let difference = self.compute_style_difference(old_values, new_values, pseudo); 507 508 *damage |= difference.damage; 509 510 debug!(" > style difference: {:?}", difference); 511 512 // We need to cascade the children in order to ensure the correct 513 // propagation of inherited computed value flags. 514 if old_values.flags.maybe_inherited() != new_values.flags.maybe_inherited() { 515 debug!( 516 " > flags changed: {:?} != {:?}", 517 old_values.flags, new_values.flags 518 ); 519 return ChildCascadeRequirement::MustCascadeChildren; 520 } 521 522 match difference.change { 523 StyleChange::Unchanged => return ChildCascadeRequirement::CanSkipCascade, 524 StyleChange::Changed { reset_only } => { 525 // If inherited properties changed, the best we can do is 526 // cascade the children. 527 if !reset_only { 528 return ChildCascadeRequirement::MustCascadeChildren; 529 } 530 }, 531 } 532 533 let old_display = old_values.get_box().clone_display(); 534 let new_display = new_values.get_box().clone_display(); 535 536 if old_display != new_display { 537 // If we used to be a display: none element, and no longer are, our 538 // children need to be restyled because they're unstyled. 539 if old_display == Display::None { 540 return ChildCascadeRequirement::MustCascadeChildren; 541 } 542 // Blockification of children may depend on our display value, 543 // so we need to actually do the recascade. We could potentially 544 // do better, but it doesn't seem worth it. 545 if old_display.is_item_container() != new_display.is_item_container() { 546 return ChildCascadeRequirement::MustCascadeChildren; 547 } 548 // We may also need to blockify and un-blockify descendants if our 549 // display goes from / to display: contents, since the "layout 550 // parent style" changes. 551 if old_display.is_contents() || new_display.is_contents() { 552 return ChildCascadeRequirement::MustCascadeChildren; 553 } 554 // Line break suppression may also be affected if the display 555 // type changes from ruby to non-ruby. 556 #[cfg(feature = "gecko")] 557 { 558 if old_display.is_ruby_type() != new_display.is_ruby_type() { 559 return ChildCascadeRequirement::MustCascadeChildren; 560 } 561 } 562 } 563 564 // Children with justify-items: auto may depend on our 565 // justify-items property value. 566 // 567 // Similarly, we could potentially do better, but this really 568 // seems not common enough to care about. 569 #[cfg(feature = "gecko")] 570 { 571 use crate::values::specified::align::AlignFlags; 572 573 let old_justify_items = old_values.get_position().clone_justify_items(); 574 let new_justify_items = new_values.get_position().clone_justify_items(); 575 576 let was_legacy_justify_items = 577 old_justify_items.computed.0.contains(AlignFlags::LEGACY); 578 579 let is_legacy_justify_items = new_justify_items.computed.0.contains(AlignFlags::LEGACY); 580 581 if is_legacy_justify_items != was_legacy_justify_items { 582 return ChildCascadeRequirement::MustCascadeChildren; 583 } 584 585 if was_legacy_justify_items && old_justify_items.computed != new_justify_items.computed 586 { 587 return ChildCascadeRequirement::MustCascadeChildren; 588 } 589 } 590 591 #[cfg(feature = "servo")] 592 { 593 // We may need to set or propagate the CAN_BE_FRAGMENTED bit 594 // on our children. 595 if old_values.is_multicol() != new_values.is_multicol() { 596 return ChildCascadeRequirement::MustCascadeChildren; 597 } 598 } 599 600 // We could prove that, if our children don't inherit reset 601 // properties, we can stop the cascade. 602 ChildCascadeRequirement::MustCascadeChildrenIfInheritResetStyle 603 } 604 605 #[cfg(feature = "servo")] collect_and_update_style_for_expired_transitions( context: &SharedStyleContext, node: OpaqueNode, style: &mut Arc<ComputedValues>, expired_transitions: &mut Vec<crate::animation::PropertyAnimation>, )606 fn collect_and_update_style_for_expired_transitions( 607 context: &SharedStyleContext, 608 node: OpaqueNode, 609 style: &mut Arc<ComputedValues>, 610 expired_transitions: &mut Vec<crate::animation::PropertyAnimation>, 611 ) { 612 use crate::animation::Animation; 613 614 let mut all_expired_animations = context.expired_animations.write(); 615 if let Some(animations) = all_expired_animations.remove(&node) { 616 debug!("removing expired animations for {:?}", node); 617 for animation in animations { 618 debug!("Updating expired animation {:?}", animation); 619 // TODO: support animation-fill-mode 620 if let Animation::Transition(_, _, property_animation) = animation { 621 property_animation.update(Arc::make_mut(style), 1.0); 622 expired_transitions.push(property_animation); 623 } 624 } 625 } 626 } 627 628 #[cfg(feature = "servo")] update_style_for_animations( context: &SharedStyleContext, node: OpaqueNode, style: &mut Arc<ComputedValues>, font_metrics: &dyn crate::font_metrics::FontMetricsProvider, )629 fn update_style_for_animations( 630 context: &SharedStyleContext, 631 node: OpaqueNode, 632 style: &mut Arc<ComputedValues>, 633 font_metrics: &dyn crate::font_metrics::FontMetricsProvider, 634 ) { 635 use crate::animation::{self, Animation, AnimationUpdate}; 636 637 let mut all_running_animations = context.running_animations.write(); 638 let running_animations = match all_running_animations.get_mut(&node) { 639 Some(running_animations) => running_animations, 640 None => return, 641 }; 642 643 for running_animation in running_animations.iter_mut() { 644 let update = match *running_animation { 645 Animation::Transition(..) => continue, 646 Animation::Keyframes(..) => animation::update_style_for_animation::<Self>( 647 context, 648 running_animation, 649 style, 650 font_metrics, 651 ), 652 }; 653 654 match *running_animation { 655 Animation::Transition(..) => unreachable!(), 656 Animation::Keyframes(_, _, _, ref mut state) => match update { 657 AnimationUpdate::Regular => {}, 658 AnimationUpdate::AnimationCanceled => { 659 state.expired = true; 660 }, 661 }, 662 } 663 } 664 } 665 } 666 667 impl<E: TElement> PrivateMatchMethods for E {} 668 669 /// The public API that elements expose for selector matching. 670 pub trait MatchMethods: TElement { 671 /// Returns the closest parent element that doesn't have a display: contents 672 /// style (and thus generates a box). 673 /// 674 /// This is needed to correctly handle blockification of flex and grid 675 /// items. 676 /// 677 /// Returns itself if the element has no parent. In practice this doesn't 678 /// happen because the root element is blockified per spec, but it could 679 /// happen if we decide to not blockify for roots of disconnected subtrees, 680 /// which is a kind of dubious behavior. layout_parent(&self) -> Self681 fn layout_parent(&self) -> Self { 682 let mut current = self.clone(); 683 loop { 684 current = match current.traversal_parent() { 685 Some(el) => el, 686 None => return current, 687 }; 688 689 let is_display_contents = current 690 .borrow_data() 691 .unwrap() 692 .styles 693 .primary() 694 .is_display_contents(); 695 696 if !is_display_contents { 697 return current; 698 } 699 } 700 } 701 702 /// Updates the styles with the new ones, diffs them, and stores the restyle 703 /// damage. finish_restyle( &self, context: &mut StyleContext<Self>, data: &mut ElementData, mut new_styles: ResolvedElementStyles, important_rules_changed: bool, ) -> ChildCascadeRequirement704 fn finish_restyle( 705 &self, 706 context: &mut StyleContext<Self>, 707 data: &mut ElementData, 708 mut new_styles: ResolvedElementStyles, 709 important_rules_changed: bool, 710 ) -> ChildCascadeRequirement { 711 use std::cmp; 712 713 self.process_animations( 714 context, 715 &mut data.styles.primary, 716 &mut new_styles.primary.style.0, 717 data.hint, 718 important_rules_changed, 719 ); 720 721 // First of all, update the styles. 722 let old_styles = data.set_styles(new_styles); 723 724 let new_primary_style = data.styles.primary.as_ref().unwrap(); 725 726 let mut cascade_requirement = ChildCascadeRequirement::CanSkipCascade; 727 if new_primary_style 728 .flags 729 .contains(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE) 730 { 731 let device = context.shared.stylist.device(); 732 let new_font_size = new_primary_style.get_font().clone_font_size(); 733 734 if old_styles 735 .primary 736 .as_ref() 737 .map_or(true, |s| s.get_font().clone_font_size() != new_font_size) 738 { 739 debug_assert!(self.owner_doc_matches_for_testing(device)); 740 device.set_root_font_size(new_font_size.size().into()); 741 // If the root font-size changed since last time, and something 742 // in the document did use rem units, ensure we recascade the 743 // entire tree. 744 if device.used_root_font_size() { 745 cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants; 746 } 747 } 748 } 749 750 if context.shared.stylist.quirks_mode() == QuirksMode::Quirks { 751 if self.is_html_document_body_element() { 752 // NOTE(emilio): We _could_ handle dynamic changes to it if it 753 // changes and before we reach our children the cascade stops, 754 // but we don't track right now whether we use the document body 755 // color, and nobody else handles that properly anyway. 756 757 let device = context.shared.stylist.device(); 758 759 // Needed for the "inherit from body" quirk. 760 let text_color = new_primary_style.get_inherited_text().clone_color(); 761 device.set_body_text_color(text_color); 762 } 763 } 764 765 // Don't accumulate damage if we're in the final animation traversal. 766 if context 767 .shared 768 .traversal_flags 769 .contains(TraversalFlags::FinalAnimationTraversal) 770 { 771 return ChildCascadeRequirement::MustCascadeChildren; 772 } 773 774 // Also, don't do anything if there was no style. 775 let old_primary_style = match old_styles.primary { 776 Some(s) => s, 777 None => return ChildCascadeRequirement::MustCascadeChildren, 778 }; 779 780 cascade_requirement = cmp::max( 781 cascade_requirement, 782 self.accumulate_damage_for( 783 context.shared, 784 &mut data.damage, 785 &old_primary_style, 786 new_primary_style, 787 None, 788 ), 789 ); 790 791 if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() { 792 // This is the common case; no need to examine pseudos here. 793 return cascade_requirement; 794 } 795 796 let pseudo_styles = old_styles 797 .pseudos 798 .as_array() 799 .iter() 800 .zip(data.styles.pseudos.as_array().iter()); 801 802 for (i, (old, new)) in pseudo_styles.enumerate() { 803 match (old, new) { 804 (&Some(ref old), &Some(ref new)) => { 805 self.accumulate_damage_for( 806 context.shared, 807 &mut data.damage, 808 old, 809 new, 810 Some(&PseudoElement::from_eager_index(i)), 811 ); 812 }, 813 (&None, &None) => {}, 814 _ => { 815 // It's possible that we're switching from not having 816 // ::before/::after at all to having styles for them but not 817 // actually having a useful pseudo-element. Check for that 818 // case. 819 let pseudo = PseudoElement::from_eager_index(i); 820 let new_pseudo_should_exist = 821 new.as_ref().map_or(false, |s| pseudo.should_exist(s)); 822 let old_pseudo_should_exist = 823 old.as_ref().map_or(false, |s| pseudo.should_exist(s)); 824 if new_pseudo_should_exist != old_pseudo_should_exist { 825 data.damage |= RestyleDamage::reconstruct(); 826 return cascade_requirement; 827 } 828 }, 829 } 830 } 831 832 cascade_requirement 833 } 834 835 /// Applies selector flags to an element, deferring mutations of the parent 836 /// until after the traversal. 837 /// 838 /// TODO(emilio): This is somewhat inefficient, because it doesn't take 839 /// advantage of us knowing that the traversal is sequential. apply_selector_flags( &self, map: &mut SelectorFlagsMap<Self>, element: &Self, flags: ElementSelectorFlags, )840 fn apply_selector_flags( 841 &self, 842 map: &mut SelectorFlagsMap<Self>, 843 element: &Self, 844 flags: ElementSelectorFlags, 845 ) { 846 // Handle flags that apply to the element. 847 let self_flags = flags.for_self(); 848 if !self_flags.is_empty() { 849 if element == self { 850 // If this is the element we're styling, we have exclusive 851 // access to the element, and thus it's fine inserting them, 852 // even from the worker. 853 unsafe { 854 element.set_selector_flags(self_flags); 855 } 856 } else { 857 // Otherwise, this element is an ancestor of the current element 858 // we're styling, and thus multiple children could write to it 859 // if we did from here. 860 // 861 // Instead, we can read them, and post them if necessary as a 862 // sequential task in order for them to be processed later. 863 if !element.has_selector_flags(self_flags) { 864 map.insert_flags(*element, self_flags); 865 } 866 } 867 } 868 869 // Handle flags that apply to the parent. 870 let parent_flags = flags.for_parent(); 871 if !parent_flags.is_empty() { 872 if let Some(p) = element.parent_element() { 873 if !p.has_selector_flags(parent_flags) { 874 map.insert_flags(p, parent_flags); 875 } 876 } 877 } 878 } 879 880 /// Updates the rule nodes without re-running selector matching, using just 881 /// the rule tree. 882 /// 883 /// Returns true if an !important rule was replaced. replace_rules( &self, replacements: RestyleHint, context: &mut StyleContext<Self>, cascade_inputs: &mut ElementCascadeInputs, ) -> bool884 fn replace_rules( 885 &self, 886 replacements: RestyleHint, 887 context: &mut StyleContext<Self>, 888 cascade_inputs: &mut ElementCascadeInputs, 889 ) -> bool { 890 let mut result = false; 891 result |= self.replace_rules_internal( 892 replacements, 893 context, 894 CascadeVisitedMode::Unvisited, 895 cascade_inputs, 896 ); 897 result |= self.replace_rules_internal( 898 replacements, 899 context, 900 CascadeVisitedMode::Visited, 901 cascade_inputs, 902 ); 903 result 904 } 905 906 /// Given the old and new style of this element, and whether it's a 907 /// pseudo-element, compute the restyle damage used to determine which 908 /// kind of layout or painting operations we'll need. compute_style_difference( &self, old_values: &ComputedValues, new_values: &ComputedValues, pseudo: Option<&PseudoElement>, ) -> StyleDifference909 fn compute_style_difference( 910 &self, 911 old_values: &ComputedValues, 912 new_values: &ComputedValues, 913 pseudo: Option<&PseudoElement>, 914 ) -> StyleDifference { 915 debug_assert!(pseudo.map_or(true, |p| p.is_eager())); 916 RestyleDamage::compute_style_difference(old_values, new_values) 917 } 918 } 919 920 impl<E: TElement> MatchMethods for E {} 921