aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2020-06-15 11:51:23 +0200
committerMartin Robinson <mrobinson@igalia.com>2020-06-16 16:33:55 +0200
commitf3e373bc623fd355cb08122d31a76f6548e6827a (patch)
tree94d416bc55b50b1e343ab02fad79a513d4cd418d
parentba5568a0a60cbd4bbedd3b766b7182824d75b131 (diff)
downloadservo-f3e373bc623fd355cb08122d31a76f6548e6827a.tar.gz
servo-f3e373bc623fd355cb08122d31a76f6548e6827a.zip
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
-rw-r--r--components/layout_2020/flow/root.rs10
-rw-r--r--components/layout_thread/dom_wrapper.rs24
-rw-r--r--components/layout_thread/lib.rs16
-rw-r--r--components/layout_thread_2020/dom_wrapper.rs24
-rw-r--r--components/script/animations.rs26
-rw-r--r--components/style/animation.rs93
-rw-r--r--components/style/dom.rs19
-rw-r--r--components/style/gecko/wrapper.rs4
-rw-r--r--components/style/matching.rs185
-rw-r--r--components/style/servo/selector_parser.rs2
-rw-r--r--components/style/style_resolver.rs38
-rw-r--r--tests/wpt/metadata-layout-2020/css/css-animations/animationevent-pseudoelement.html.ini5
-rw-r--r--tests/wpt/metadata-layout-2020/css/css-transitions/events-006.html.ini8
-rw-r--r--tests/wpt/metadata-layout-2020/css/css-transitions/non-rendered-element-002.html.ini5
-rw-r--r--tests/wpt/metadata-layout-2020/css/css-transitions/pseudo-elements-001.html.ini13
-rw-r--r--tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini6
-rw-r--r--tests/wpt/metadata/css/css-transitions/events-006.html.ini9
-rw-r--r--tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini4
-rw-r--r--tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini6
19 files changed, 359 insertions, 138 deletions
diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs
index 55c325a3cd0..0d346a34eeb 100644
--- a/components/layout_2020/flow/root.rs
+++ b/components/layout_2020/flow/root.rs
@@ -38,6 +38,7 @@ use servo_arc::Arc;
use style::animation::AnimationSetKey;
use style::dom::OpaqueNode;
use style::properties::ComputedValues;
+use style::selector_parser::PseudoElement;
use style::values::computed::Length;
use style_traits::CSSPixel;
@@ -449,9 +450,12 @@ impl FragmentTree {
pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet<AnimationSetKey>) {
self.find(|fragment, _| {
- if let Some(tag) = fragment.tag().as_ref() {
- set.remove(&AnimationSetKey(tag.node()));
- }
+ let (node, pseudo) = match fragment.tag()? {
+ Tag::Node(node) => (node, None),
+ Tag::BeforePseudo(node) => (node, Some(PseudoElement::Before)),
+ Tag::AfterPseudo(node) => (node, Some(PseudoElement::After)),
+ };
+ set.remove(&AnimationSetKey::new(node, pseudo));
None::<()>
});
}
diff --git a/components/layout_thread/dom_wrapper.rs b/components/layout_thread/dom_wrapper.rs
index 3d3f50317a9..f0d25ff8c49 100644
--- a/components/layout_thread/dom_wrapper.rs
+++ b/components/layout_thread/dom_wrapper.rs
@@ -476,7 +476,7 @@ impl<'le> TElement for ServoLayoutElement<'le> {
let node = self.as_node();
let document = node.owner_doc();
context.animations.get_animation_declarations(
- &AnimationSetKey(node.opaque()),
+ &AnimationSetKey::new_for_non_pseudo(node.opaque()),
context.current_time_for_animations,
document.style_shared_lock(),
)
@@ -489,7 +489,7 @@ impl<'le> TElement for ServoLayoutElement<'le> {
let node = self.as_node();
let document = node.owner_doc();
context.animations.get_transition_declarations(
- &AnimationSetKey(node.opaque()),
+ &AnimationSetKey::new_for_non_pseudo(node.opaque()),
context.current_time_for_animations,
document.style_shared_lock(),
)
@@ -613,16 +613,26 @@ impl<'le> TElement for ServoLayoutElement<'le> {
}
fn has_animations(&self, context: &SharedStyleContext) -> bool {
- return self.has_css_animations(context) || self.has_css_transitions(context);
+ // This is not used for pseudo elements currently so we can pass None.
+ return self.has_css_animations(context, /* pseudo_element = */ None) ||
+ self.has_css_transitions(context, /* pseudo_element = */ None);
}
- fn has_css_animations(&self, context: &SharedStyleContext) -> bool {
- let key = AnimationSetKey(self.as_node().opaque());
+ fn has_css_animations(
+ &self,
+ context: &SharedStyleContext,
+ pseudo_element: Option<PseudoElement>,
+ ) -> bool {
+ let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
context.animations.has_active_animations(&key)
}
- fn has_css_transitions(&self, context: &SharedStyleContext) -> bool {
- let key = AnimationSetKey(self.as_node().opaque());
+ fn has_css_transitions(
+ &self,
+ context: &SharedStyleContext,
+ pseudo_element: Option<PseudoElement>,
+ ) -> bool {
+ let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
context.animations.has_active_transitions(&key)
}
diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs
index 5930e85b08c..07cb7c5a4c8 100644
--- a/components/layout_thread/lib.rs
+++ b/components/layout_thread/lib.rs
@@ -109,7 +109,7 @@ use style::invalidation::element::restyle_hints::RestyleHint;
use style::logical_geometry::LogicalPoint;
use style::media_queries::{Device, MediaList, MediaType};
use style::properties::PropertyId;
-use style::selector_parser::SnapshotMap;
+use style::selector_parser::{PseudoElement, SnapshotMap};
use style::servo::restyle_damage::ServoRestyleDamage;
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
use style::stylesheets::{
@@ -1651,7 +1651,19 @@ impl LayoutThread {
fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet<AnimationSetKey>) {
flow.mutate_fragments(&mut |fragment| {
- invalid_nodes.remove(&AnimationSetKey(fragment.node));
+ // Ideally we'd only not cancel ::before and ::after animations if they
+ // were actually in the tree. At this point layout has lost information
+ // about whether or not they exist, but have had their fragments accumulated
+ // together.
+ invalid_nodes.remove(&AnimationSetKey::new_for_non_pseudo(fragment.node));
+ invalid_nodes.remove(&AnimationSetKey::new_for_pseudo(
+ fragment.node,
+ PseudoElement::Before,
+ ));
+ invalid_nodes.remove(&AnimationSetKey::new_for_pseudo(
+ fragment.node,
+ PseudoElement::After,
+ ));
});
for kid in flow.mut_base().children.iter_mut() {
traverse_flow(kid, invalid_nodes)
diff --git a/components/layout_thread_2020/dom_wrapper.rs b/components/layout_thread_2020/dom_wrapper.rs
index c8a7aa4dd05..d3c355c2fe7 100644
--- a/components/layout_thread_2020/dom_wrapper.rs
+++ b/components/layout_thread_2020/dom_wrapper.rs
@@ -484,7 +484,7 @@ impl<'le> TElement for ServoLayoutElement<'le> {
let node = self.as_node();
let document = node.owner_doc();
context.animations.get_animation_declarations(
- &AnimationSetKey(node.opaque()),
+ &AnimationSetKey::new_for_non_pseudo(node.opaque()),
context.current_time_for_animations,
document.style_shared_lock(),
)
@@ -497,7 +497,7 @@ impl<'le> TElement for ServoLayoutElement<'le> {
let node = self.as_node();
let document = node.owner_doc();
context.animations.get_transition_declarations(
- &AnimationSetKey(node.opaque()),
+ &AnimationSetKey::new_for_non_pseudo(node.opaque()),
context.current_time_for_animations,
document.style_shared_lock(),
)
@@ -621,16 +621,26 @@ impl<'le> TElement for ServoLayoutElement<'le> {
}
fn has_animations(&self, context: &SharedStyleContext) -> bool {
- return self.has_css_animations(context) || self.has_css_transitions(context);
+ // This is not used for pseudo elements currently so we can pass None.
+ return self.has_css_animations(context, /* pseudo_element = */ None) ||
+ self.has_css_transitions(context, /* pseudo_element = */ None);
}
- fn has_css_animations(&self, context: &SharedStyleContext) -> bool {
- let key = AnimationSetKey(self.as_node().opaque());
+ fn has_css_animations(
+ &self,
+ context: &SharedStyleContext,
+ pseudo_element: Option<PseudoElement>,
+ ) -> bool {
+ let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
context.animations.has_active_animations(&key)
}
- fn has_css_transitions(&self, context: &SharedStyleContext) -> bool {
- let key = AnimationSetKey(self.as_node().opaque());
+ fn has_css_transitions(
+ &self,
+ context: &SharedStyleContext,
+ pseudo_element: Option<PseudoElement>,
+ ) -> bool {
+ let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element);
context.animations.has_active_transitions(&key)
}
diff --git a/components/script/animations.rs b/components/script/animations.rs
index 0b35ba4ae1d..bddbac14966 100644
--- a/components/script/animations.rs
+++ b/components/script/animations.rs
@@ -19,6 +19,7 @@ use crate::dom::event::Event;
use crate::dom::node::{from_untrusted_node_address, window_from_node, Node, NodeDamage};
use crate::dom::transitionevent::TransitionEvent;
use crate::dom::window::Window;
+use cssparser::ToCss;
use fxhash::{FxHashMap, FxHashSet};
use libc::c_void;
use msg::constellation_msg::PipelineId;
@@ -29,6 +30,7 @@ use style::animation::{
KeyframesIterationState, Transition,
};
use style::dom::OpaqueNode;
+use style::selector_parser::PseudoElement;
/// The set of animations for a document.
#[derive(Default, JSTraceable, MallocSizeOf)]
@@ -66,7 +68,7 @@ impl Animations {
pub(crate) fn mark_animating_nodes_as_dirty(&self) {
let sets = self.sets.sets.read();
let rooted_nodes = self.rooted_nodes.borrow();
- for node in sets.keys().filter_map(|key| rooted_nodes.get(&key.0)) {
+ for node in sets.keys().filter_map(|key| rooted_nodes.get(&key.node)) {
node.dirty(NodeDamage::NodeStyleDamaged);
}
}
@@ -287,7 +289,7 @@ impl Animations {
let js_runtime = window.get_js_runtime().as_ref().unwrap().rt();
let mut rooted_nodes = self.rooted_nodes.borrow_mut();
for (key, set) in sets.iter() {
- let opaque_node = key.0;
+ let opaque_node = key.node;
if rooted_nodes.contains_key(&opaque_node) {
continue;
}
@@ -309,7 +311,7 @@ impl Animations {
// Unroot any nodes that we have rooted but are no longer tracking animations for.
fn unroot_unused_nodes(&self, sets: &FxHashMap<AnimationSetKey, ElementAnimationSet>) {
let pending_events = self.pending_events.borrow();
- let nodes: FxHashSet<OpaqueNode> = sets.keys().map(|key| key.0).collect();
+ let nodes: FxHashSet<OpaqueNode> = sets.keys().map(|key| key.node).collect();
self.rooted_nodes.borrow_mut().retain(|node, _| {
nodes.contains(&node) || pending_events.iter().any(|event| event.node == *node)
});
@@ -344,7 +346,8 @@ impl Animations {
.push(TransitionOrAnimationEvent {
pipeline_id,
event_type,
- node: key.0,
+ node: key.node,
+ pseudo_element: key.pseudo_element.clone(),
property_or_animation_name: transition
.property_animation
.property_id()
@@ -392,7 +395,8 @@ impl Animations {
.push(TransitionOrAnimationEvent {
pipeline_id,
event_type,
- node: key.0,
+ node: key.node,
+ pseudo_element: key.pseudo_element.clone(),
property_or_animation_name: animation.name.to_string(),
elapsed_time,
});
@@ -429,9 +433,13 @@ impl Animations {
cancelable: false,
};
- // TODO: Handle pseudo-elements properly
let property_or_animation_name =
DOMString::from(event.property_or_animation_name.clone());
+ let pseudo_element = event
+ .pseudo_element
+ .map_or_else(DOMString::new, |pseudo_element| {
+ DOMString::from(pseudo_element.to_css_string())
+ });
let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap();
let window = window_from_node(&*node);
@@ -440,7 +448,7 @@ impl Animations {
parent,
propertyName: property_or_animation_name,
elapsedTime: elapsed_time,
- pseudoElement: DOMString::new(),
+ pseudoElement: pseudo_element,
};
TransitionEvent::new(&window, event_atom, &event_init)
.upcast::<Event>()
@@ -450,7 +458,7 @@ impl Animations {
parent,
animationName: property_or_animation_name,
elapsedTime: elapsed_time,
- pseudoElement: DOMString::new(),
+ pseudoElement: pseudo_element,
};
AnimationEvent::new(&window, event_atom, &event_init)
.upcast::<Event>()
@@ -513,6 +521,8 @@ pub struct TransitionOrAnimationEvent {
pub event_type: TransitionOrAnimationEventType,
/// The address of the node which owns this transition.
pub node: OpaqueNode,
+ /// The pseudo element for this transition or animation, if applicable.
+ pub pseudo_element: Option<PseudoElement>,
/// The name of the property that is transitioning (in the case of a transition)
/// or the name of the animation (in the case of an animation).
pub property_or_animation_name: String,
diff --git a/components/style/animation.rs b/components/style/animation.rs
index ecb921a5e4a..ad5e41553fe 100644
--- a/components/style/animation.rs
+++ b/components/style/animation.rs
@@ -19,6 +19,7 @@ use crate::properties::{
PropertyDeclarationId,
};
use crate::rule_tree::CascadeLevel;
+use crate::selector_parser::PseudoElement;
use crate::shared_lock::{Locked, SharedRwLock};
use crate::style_resolver::StyleResolverForElement;
use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
@@ -1138,7 +1139,39 @@ impl ElementAnimationSet {
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
/// A key that is used to identify nodes in the `DocumentAnimationSet`.
-pub struct AnimationSetKey(pub OpaqueNode);
+pub struct AnimationSetKey {
+ /// The node for this `AnimationSetKey`.
+ pub node: OpaqueNode,
+ /// The pseudo element for this `AnimationSetKey`. If `None` this key will
+ /// refer to the main content for its node.
+ pub pseudo_element: Option<PseudoElement>,
+}
+
+impl AnimationSetKey {
+ /// Create a new key given a node and optional pseudo element.
+ pub fn new(node: OpaqueNode, pseudo_element: Option<PseudoElement>) -> Self {
+ AnimationSetKey {
+ node,
+ pseudo_element,
+ }
+ }
+
+ /// Create a new key for the main content of this node.
+ pub fn new_for_non_pseudo(node: OpaqueNode) -> Self {
+ AnimationSetKey {
+ node,
+ pseudo_element: None,
+ }
+ }
+
+ /// Create a new key for given node and pseudo element.
+ pub fn new_for_pseudo(node: OpaqueNode, pseudo_element: PseudoElement) -> Self {
+ AnimationSetKey {
+ node,
+ pseudo_element: Some(pseudo_element),
+ }
+ }
+}
#[derive(Clone, Debug, Default, MallocSizeOf)]
/// A set of animations for a document.
@@ -1154,8 +1187,7 @@ impl DocumentAnimationSet {
self.sets
.read()
.get(key)
- .map(|set| set.has_active_animation())
- .unwrap_or(false)
+ .map_or(false, |set| set.has_active_animation())
}
/// Return whether or not the provided node has active CSS transitions.
@@ -1163,8 +1195,7 @@ impl DocumentAnimationSet {
self.sets
.read()
.get(key)
- .map(|set| set.has_active_transition())
- .unwrap_or(false)
+ .map_or(false, |set| set.has_active_transition())
}
/// Return a locked PropertyDeclarationBlock with animation values for the given
@@ -1202,6 +1233,58 @@ impl DocumentAnimationSet {
Arc::new(shared_lock.wrap(block))
})
}
+
+ /// Get all the animation declarations for the given key, returning an empty
+ /// `AnimationAndTransitionDeclarations` if there are no animations.
+ pub(crate) fn get_all_declarations(
+ &self,
+ key: &AnimationSetKey,
+ time: f64,
+ shared_lock: &SharedRwLock,
+ ) -> AnimationAndTransitionDeclarations {
+ let sets = self.sets.read();
+ let set = match sets.get(key) {
+ Some(set) => set,
+ None => return Default::default(),
+ };
+
+ let animations = set.get_value_map_for_active_animations(time).map(|map| {
+ let block = PropertyDeclarationBlock::from_animation_value_map(&map);
+ Arc::new(shared_lock.wrap(block))
+ });
+ let transitions = set.get_value_map_for_active_transitions(time).map(|map| {
+ let block = PropertyDeclarationBlock::from_animation_value_map(&map);
+ Arc::new(shared_lock.wrap(block))
+ });
+ AnimationAndTransitionDeclarations {
+ animations,
+ transitions,
+ }
+ }
+
+ /// Cancel all animations for set at the given key.
+ pub(crate) fn cancel_all_animations_for_key(&self, key: &AnimationSetKey) {
+ if let Some(set) = self.sets.write().get_mut(key) {
+ set.cancel_all_animations();
+ }
+ }
+}
+
+/// A set of property declarations for a node, including animations and
+/// transitions.
+#[derive(Default)]
+pub struct AnimationAndTransitionDeclarations {
+ /// Declarations for animations.
+ pub animations: Option<Arc<Locked<PropertyDeclarationBlock>>>,
+ /// Declarations for transitions.
+ pub transitions: Option<Arc<Locked<PropertyDeclarationBlock>>>,
+}
+
+impl AnimationAndTransitionDeclarations {
+ /// Whether or not this `AnimationAndTransitionDeclarations` is empty.
+ pub(crate) fn is_empty(&self) -> bool {
+ self.animations.is_none() && self.transitions.is_none()
+ }
}
/// Kick off any new transitions for this node and return all of the properties that are
diff --git a/components/style/dom.rs b/components/style/dom.rs
index 9b61cfbd3af..83908cf00cd 100644
--- a/components/style/dom.rs
+++ b/components/style/dom.rs
@@ -750,12 +750,23 @@ pub trait TElement:
/// or are scheduled to do so in the future.
fn has_animations(&self, context: &SharedStyleContext) -> bool;
- /// Returns true if the element has a CSS animation.
- fn has_css_animations(&self, context: &SharedStyleContext) -> bool;
+ /// Returns true if the element has a CSS animation. The `context` and `pseudo_element`
+ /// arguments are only used by Servo, since it stores animations globally and pseudo-elements
+ /// are not in the DOM.
+ fn has_css_animations(
+ &self,
+ context: &SharedStyleContext,
+ pseudo_element: Option<PseudoElement>,
+ ) -> bool;
/// Returns true if the element has a CSS transition (including running transitions and
- /// completed transitions).
- fn has_css_transitions(&self, context: &SharedStyleContext) -> bool;
+ /// completed transitions). The `context` and `pseudo_element` arguments are only used
+ /// by Servo, since it stores animations globally and pseudo-elements are not in the DOM.
+ fn has_css_transitions(
+ &self,
+ context: &SharedStyleContext,
+ pseudo_element: Option<PseudoElement>,
+ ) -> bool;
/// Returns true if the element has animation restyle hints.
fn has_animation_restyle_hints(&self) -> bool {
diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs
index 4d680c3ff02..e301bdd1107 100644
--- a/components/style/gecko/wrapper.rs
+++ b/components/style/gecko/wrapper.rs
@@ -1520,11 +1520,11 @@ impl<'le> TElement for GeckoElement<'le> {
self.may_have_animations() && unsafe { Gecko_ElementHasAnimations(self.0) }
}
- fn has_css_animations(&self, _: &SharedStyleContext) -> bool {
+ fn has_css_animations(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool {
self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) }
}
- fn has_css_transitions(&self, _: &SharedStyleContext) -> bool {
+ fn has_css_transitions(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool {
self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) }
}
diff --git a/components/style/matching.rs b/components/style/matching.rs
index a8edf36829e..95742c68f4a 100644
--- a/components/style/matching.rs
+++ b/components/style/matching.rs
@@ -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,
diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs
index af4dd92fcf7..a3a3ca7c2b8 100644
--- a/components/style/servo/selector_parser.rs
+++ b/components/style/servo/selector_parser.rs
@@ -29,7 +29,7 @@ use style_traits::{ParseError, StyleParseErrorKind};
/// A pseudo-element, both public and private.
///
/// NB: If you add to this list, be sure to update `each_simple_pseudo_element` too.
-#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, ToShmem)]
#[allow(missing_docs)]
#[repr(usize)]
pub enum PseudoElement {
diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs
index 47b0ca53bd7..5e9da39b122 100644
--- a/components/style/style_resolver.rs
+++ b/components/style/style_resolver.rs
@@ -118,6 +118,17 @@ where
)
}
+fn layout_parent_style_for_pseudo<'a>(
+ primary_style: &'a PrimaryStyle,
+ layout_parent_style: Option<&'a ComputedValues>,
+) -> Option<&'a ComputedValues> {
+ if primary_style.style().is_display_contents() {
+ layout_parent_style
+ } else {
+ Some(primary_style.style())
+ }
+}
+
fn eager_pseudo_is_definitely_not_generated(
pseudo: &PseudoElement,
style: &ComputedValues,
@@ -246,11 +257,8 @@ where
let mut pseudo_styles = EagerPseudoStyles::default();
if !self.element.is_pseudo_element() {
- let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents() {
- layout_parent_style
- } else {
- Some(primary_style.style())
- };
+ let layout_parent_style_for_pseudo =
+ layout_parent_style_for_pseudo(&primary_style, layout_parent_style);
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
let pseudo_style = self.resolve_pseudo_style(
&pseudo,
@@ -298,6 +306,26 @@ where
})
}
+ /// Cascade a set of rules for pseudo element, using the default parent for inheritance.
+ pub(crate) fn cascade_style_and_visited_for_pseudo_with_default_parents(
+ &mut self,
+ inputs: CascadeInputs,
+ pseudo: &PseudoElement,
+ primary_style: &PrimaryStyle,
+ ) -> ResolvedStyle {
+ with_default_parent_styles(self.element, |_, layout_parent_style| {
+ let layout_parent_style_for_pseudo =
+ layout_parent_style_for_pseudo(primary_style, layout_parent_style);
+
+ self.cascade_style_and_visited(
+ inputs,
+ Some(primary_style.style()),
+ layout_parent_style_for_pseudo,
+ Some(pseudo),
+ )
+ })
+ }
+
fn cascade_style_and_visited(
&mut self,
inputs: CascadeInputs,
diff --git a/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-pseudoelement.html.ini b/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-pseudoelement.html.ini
deleted file mode 100644
index fa19a3fabd9..00000000000
--- a/tests/wpt/metadata-layout-2020/css/css-animations/animationevent-pseudoelement.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[animationevent-pseudoelement.html]
- expected: TIMEOUT
- [AnimationEvent should have the correct pseudoElement memeber]
- expected: TIMEOUT
-
diff --git a/tests/wpt/metadata-layout-2020/css/css-transitions/events-006.html.ini b/tests/wpt/metadata-layout-2020/css/css-transitions/events-006.html.ini
deleted file mode 100644
index 2fc7af21dd4..00000000000
--- a/tests/wpt/metadata-layout-2020/css/css-transitions/events-006.html.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[events-006.html]
- expected: TIMEOUT
- [transition padding-left on ::after]
- expected: NOTRUN
-
- [transition padding-left on ::before]
- expected: TIMEOUT
-
diff --git a/tests/wpt/metadata-layout-2020/css/css-transitions/non-rendered-element-002.html.ini b/tests/wpt/metadata-layout-2020/css/css-transitions/non-rendered-element-002.html.ini
deleted file mode 100644
index d0600a9c6b2..00000000000
--- a/tests/wpt/metadata-layout-2020/css/css-transitions/non-rendered-element-002.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[non-rendered-element-002.html]
- expected: TIMEOUT
- [Transitions on ::before/::after pseudo-elements are canceled when the content property is cleared]
- expected: TIMEOUT
-
diff --git a/tests/wpt/metadata-layout-2020/css/css-transitions/pseudo-elements-001.html.ini b/tests/wpt/metadata-layout-2020/css/css-transitions/pseudo-elements-001.html.ini
deleted file mode 100644
index d43cd5b241b..00000000000
--- a/tests/wpt/metadata-layout-2020/css/css-transitions/pseudo-elements-001.html.ini
+++ /dev/null
@@ -1,13 +0,0 @@
-[pseudo-elements-001.html]
- [transition padding-left on :before / values]
- expected: FAIL
-
- [transition padding-left on :after, changing content / values]
- expected: FAIL
-
- [transition padding-left on :before, changing content / values]
- expected: FAIL
-
- [transition padding-left on :after / values]
- expected: FAIL
-
diff --git a/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini b/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini
deleted file mode 100644
index c7f08fec5f7..00000000000
--- a/tests/wpt/metadata/css/css-animations/animationevent-pseudoelement.html.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[animationevent-pseudoelement.html]
- bug: https://github.com/servo/servo/issues/10316
- expected: TIMEOUT
- [AnimationEvent should have the correct pseudoElement memeber]
- expected: TIMEOUT
-
diff --git a/tests/wpt/metadata/css/css-transitions/events-006.html.ini b/tests/wpt/metadata/css/css-transitions/events-006.html.ini
deleted file mode 100644
index edacd476df2..00000000000
--- a/tests/wpt/metadata/css/css-transitions/events-006.html.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[events-006.html]
- bug: https://github.com/servo/servo/issues/10316
- expected: TIMEOUT
- [transition padding-left on ::after]
- expected: NOTRUN
-
- [transition padding-left on ::before]
- expected: TIMEOUT
-
diff --git a/tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini b/tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini
index 684f225e665..1f8c58a6bc8 100644
--- a/tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini
+++ b/tests/wpt/metadata/css/css-transitions/non-rendered-element-002.html.ini
@@ -1,8 +1,4 @@
[non-rendered-element-002.html]
- expected: TIMEOUT
- [Transitions on ::before/::after pseudo-elements are canceled when the content property is cleared]
- expected: TIMEOUT
-
[Transitions on ::marker pseudo-elements are canceled when the parent display type is no longer list-item]
expected: NOTRUN
diff --git a/tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini b/tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini
index d43cd5b241b..8bb346ac786 100644
--- a/tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini
+++ b/tests/wpt/metadata/css/css-transitions/pseudo-elements-001.html.ini
@@ -2,12 +2,6 @@
[transition padding-left on :before / values]
expected: FAIL
- [transition padding-left on :after, changing content / values]
- expected: FAIL
-
- [transition padding-left on :before, changing content / values]
- expected: FAIL
-
[transition padding-left on :after / values]
expected: FAIL