Add animation and transition support for pseudo-elements

This change extends the DocumentAnimationSet to hold animations for
pseudo-elements. Since pseudo-elements in Servo are not in the DOM like
in Gecko, they need to be handled a bit carefully in stylo.  When a
pseudo-element has an animation, recascade the style. Finally, this
change passes the pseudo-element string properly to animation events.

Fixes: #10316
This commit is contained in:
Martin Robinson 2020-06-15 11:51:23 +02:00
parent ba5568a0a6
commit f3e373bc62
19 changed files with 359 additions and 138 deletions

View file

@ -10,7 +10,7 @@
use crate::computed_value_flags::ComputedValueFlags;
use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
use crate::context::{SharedStyleContext, StyleContext};
use crate::data::ElementData;
use crate::data::{ElementData, ElementStyles};
use crate::dom::TElement;
#[cfg(feature = "servo")]
use crate::dom::TNode;
@ -90,13 +90,13 @@ enum CascadeVisitedMode {
trait PrivateMatchMethods: TElement {
fn replace_single_rule_node(
context: &mut StyleContext<Self>,
context: &SharedStyleContext,
level: CascadeLevel,
pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
path: &mut StrongRuleNode,
) -> bool {
let stylist = &context.shared.stylist;
let guards = &context.shared.guards;
let stylist = &context.stylist;
let guards = &context.guards;
let mut important_rules_changed = false;
let new_node = stylist.rule_tree().update_rule_at_level(
@ -143,13 +143,13 @@ trait PrivateMatchMethods: TElement {
if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) {
let style_attribute = self.style_attribute();
result |= Self::replace_single_rule_node(
context,
context.shared,
CascadeLevel::same_tree_author_normal(),
style_attribute,
primary_rules,
);
result |= Self::replace_single_rule_node(
context,
context.shared,
CascadeLevel::same_tree_author_important(),
style_attribute,
primary_rules,
@ -170,7 +170,7 @@ trait PrivateMatchMethods: TElement {
if replacements.contains(RestyleHint::RESTYLE_SMIL) {
Self::replace_single_rule_node(
context,
context.shared,
CascadeLevel::SMILOverride,
self.smil_override(),
primary_rules,
@ -179,7 +179,7 @@ trait PrivateMatchMethods: TElement {
if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) {
Self::replace_single_rule_node(
context,
context.shared,
CascadeLevel::Transitions,
self.transition_rule(&context.shared)
.as_ref()
@ -190,7 +190,7 @@ trait PrivateMatchMethods: TElement {
if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) {
Self::replace_single_rule_node(
context,
context.shared,
CascadeLevel::Animations,
self.animation_rule(&context.shared)
.as_ref()
@ -245,11 +245,12 @@ trait PrivateMatchMethods: TElement {
context: &mut StyleContext<Self>,
old_style: Option<&ComputedValues>,
new_style: &ComputedValues,
pseudo_element: Option<PseudoElement>,
) -> bool {
let new_box_style = new_style.get_box();
let new_style_specifies_animations = new_box_style.specifies_animations();
let has_animations = self.has_css_animations(&context.shared);
let has_animations = self.has_css_animations(&context.shared, pseudo_element);
if !new_style_specifies_animations && !has_animations {
return false;
}
@ -326,6 +327,7 @@ trait PrivateMatchMethods: TElement {
context: &StyleContext<Self>,
old_style: Option<&ComputedValues>,
new_style: &ComputedValues,
pseudo_element: Option<PseudoElement>,
) -> bool {
let old_style = match old_style {
Some(v) => v,
@ -333,7 +335,9 @@ trait PrivateMatchMethods: TElement {
};
let new_box_style = new_style.get_box();
if !self.has_css_transitions(context.shared) && !new_box_style.specifies_transitions() {
if !self.has_css_transitions(context.shared, pseudo_element) &&
!new_box_style.specifies_transitions()
{
return false;
}
@ -379,7 +383,7 @@ trait PrivateMatchMethods: TElement {
fn process_animations(
&self,
context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
old_styles: &mut ElementStyles,
new_styles: &mut ResolvedElementStyles,
restyle_hint: RestyleHint,
important_rules_changed: bool,
@ -401,7 +405,12 @@ trait PrivateMatchMethods: TElement {
// in addition to the unvisited styles.
let mut tasks = UpdateAnimationsTasks::empty();
if self.needs_animations_update(context, old_values.as_ref().map(|s| &**s), new_values) {
if self.needs_animations_update(
context,
old_values.as_ref().map(|s| &**s),
new_values,
/* pseudo_element = */ None,
) {
tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS);
}
@ -409,6 +418,7 @@ trait PrivateMatchMethods: TElement {
context,
old_values.as_ref().map(|s| &**s),
new_values,
/* pseudo_element = */ None,
) {
let after_change_style = if self.has_css_transitions(context.shared) {
self.after_change_style(context, new_values)
@ -464,57 +474,149 @@ trait PrivateMatchMethods: TElement {
fn process_animations(
&self,
context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
old_styles: &mut ElementStyles,
new_resolved_styles: &mut ResolvedElementStyles,
_restyle_hint: RestyleHint,
_important_rules_changed: bool,
) {
if !self.process_animations_for_style(
use crate::animation::AnimationSetKey;
use crate::dom::TDocument;
let style_changed = self.process_animations_for_style(
context,
old_values,
&mut old_styles.primary,
new_resolved_styles.primary_style_mut(),
) {
/* pseudo_element = */ None,
);
// If we have modified animation or transitions, we recascade style for this node.
if style_changed {
let mut rule_node = new_resolved_styles.primary_style().rules().clone();
let declarations = context.shared.animations.get_all_declarations(
&AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()),
context.shared.current_time_for_animations,
self.as_node().owner_doc().shared_lock(),
);
Self::replace_single_rule_node(
&context.shared,
CascadeLevel::Transitions,
declarations.transitions.as_ref().map(|a| a.borrow_arc()),
&mut rule_node,
);
Self::replace_single_rule_node(
&context.shared,
CascadeLevel::Animations,
declarations.animations.as_ref().map(|a| a.borrow_arc()),
&mut rule_node,
);
if rule_node != *new_resolved_styles.primary_style().rules() {
let inputs = CascadeInputs {
rules: Some(rule_node),
visited_rules: new_resolved_styles.primary_style().visited_rules().cloned(),
};
new_resolved_styles.primary.style = StyleResolverForElement::new(
*self,
context,
RuleInclusion::All,
PseudoElementResolution::IfApplicable,
)
.cascade_style_and_visited_with_default_parents(inputs);
}
}
self.process_animations_for_pseudo(
context,
old_styles,
new_resolved_styles,
PseudoElement::Before,
);
self.process_animations_for_pseudo(
context,
old_styles,
new_resolved_styles,
PseudoElement::After,
);
}
#[cfg(feature = "servo")]
fn process_animations_for_pseudo(
&self,
context: &mut StyleContext<Self>,
old_styles: &mut ElementStyles,
new_resolved_styles: &mut ResolvedElementStyles,
pseudo_element: PseudoElement,
) {
use crate::animation::AnimationSetKey;
use crate::dom::TDocument;
let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone());
let mut style = match new_resolved_styles.pseudos.get(&pseudo_element) {
Some(style) => Arc::clone(style),
None => {
context
.shared
.animations
.cancel_all_animations_for_key(&key);
return;
},
};
let mut old_style = old_styles.pseudos.get(&pseudo_element).cloned();
self.process_animations_for_style(
context,
&mut old_style,
&mut style,
Some(pseudo_element.clone()),
);
let declarations = context.shared.animations.get_all_declarations(
&key,
context.shared.current_time_for_animations,
self.as_node().owner_doc().shared_lock(),
);
if declarations.is_empty() {
return;
}
// If we have modified animation or transitions, we recascade style for this node.
let mut rule_node = new_resolved_styles.primary_style().rules().clone();
let mut rule_node = style.rules().clone();
Self::replace_single_rule_node(
context,
&context.shared,
CascadeLevel::Transitions,
self.transition_rule(&context.shared)
.as_ref()
.map(|a| a.borrow_arc()),
declarations.transitions.as_ref().map(|a| a.borrow_arc()),
&mut rule_node,
);
Self::replace_single_rule_node(
context,
&context.shared,
CascadeLevel::Animations,
self.animation_rule(&context.shared)
.as_ref()
.map(|a| a.borrow_arc()),
declarations.animations.as_ref().map(|a| a.borrow_arc()),
&mut rule_node,
);
// If these animations haven't modified the rule now, we can just exit early.
if rule_node == *new_resolved_styles.primary_style().rules() {
if rule_node == *style.rules() {
return;
}
let inputs = CascadeInputs {
rules: Some(rule_node),
visited_rules: new_resolved_styles.primary_style().visited_rules().cloned(),
visited_rules: style.visited_rules().cloned(),
};
let style = StyleResolverForElement::new(
let new_style = StyleResolverForElement::new(
*self,
context,
RuleInclusion::All,
PseudoElementResolution::IfApplicable,
)
.cascade_style_and_visited_with_default_parents(inputs);
.cascade_style_and_visited_for_pseudo_with_default_parents(
inputs,
&pseudo_element,
&new_resolved_styles.primary,
);
new_resolved_styles.primary.style = style;
new_resolved_styles
.pseudos
.set(&pseudo_element, new_style.0);
}
#[cfg(feature = "servo")]
@ -523,17 +625,24 @@ trait PrivateMatchMethods: TElement {
context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>,
pseudo_element: Option<PseudoElement>,
) -> bool {
use crate::animation::{AnimationSetKey, AnimationState};
// We need to call this before accessing the `ElementAnimationSet` from the
// map because this call will do a RwLock::read().
let needs_animations_update =
self.needs_animations_update(context, old_values.as_ref().map(|s| &**s), new_values);
let needs_animations_update = self.needs_animations_update(
context,
old_values.as_ref().map(|s| &**s),
new_values,
pseudo_element,
);
let might_need_transitions_update = self.might_need_transitions_update(
context,
old_values.as_ref().map(|s| &**s),
new_values,
pseudo_element,
);
let mut after_change_style = None;
@ -541,7 +650,7 @@ trait PrivateMatchMethods: TElement {
after_change_style = self.after_change_style(context, new_values);
}
let key = AnimationSetKey(self.as_node().opaque());
let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
let shared_context = context.shared;
let mut animation_set = shared_context
.animations
@ -761,7 +870,7 @@ pub trait MatchMethods: TElement {
self.process_animations(
context,
&mut data.styles.primary,
&mut data.styles,
&mut new_styles,
data.hint,
important_rules_changed,