mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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:
parent
ba5568a0a6
commit
f3e373bc62
19 changed files with 359 additions and 138 deletions
|
@ -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::<()>
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
[animationevent-pseudoelement.html]
|
||||
expected: TIMEOUT
|
||||
[AnimationEvent should have the correct pseudoElement memeber]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
[events-006.html]
|
||||
expected: TIMEOUT
|
||||
[transition padding-left on ::after]
|
||||
expected: NOTRUN
|
||||
|
||||
[transition padding-left on ::before]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue