diff --git a/components/style/dom.rs b/components/style/dom.rs index 645db055be7..c937dba8bb3 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -319,6 +319,11 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + /// Get this element's style attribute. fn style_attribute(&self) -> Option<&Arc>>; + /// Get this element's SMIL override declarations. + fn get_smil_override(&self) -> Option<&Arc>> { + None + } + /// Get this element's animation rules. fn get_animation_rules(&self, _pseudo: Option<&PseudoElement>) -> AnimationRules { AnimationRules(None, None) diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 948a87d4cf6..30b9d7b7846 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -38,6 +38,7 @@ use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; use gecko_bindings::bindings::Gecko_GetAnimationRule; use gecko_bindings::bindings::Gecko_GetExtraContentStyleDeclarations; use gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock; +use gecko_bindings::bindings::Gecko_GetSMILOverrideDeclarationBlock; use gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock; use gecko_bindings::bindings::Gecko_GetStyleContext; use gecko_bindings::bindings::Gecko_IsSignificantChild; @@ -525,6 +526,11 @@ impl<'le> TElement for GeckoElement<'le> { declarations.map(|s| s.as_arc_opt()).unwrap_or(None) } + fn get_smil_override(&self) -> Option<&Arc>> { + let declarations = unsafe { Gecko_GetSMILOverrideDeclarationBlock(self.0) }; + declarations.map(|s| s.as_arc_opt()).unwrap_or(None) + } + fn get_animation_rules(&self, pseudo: Option<&PseudoElement>) -> AnimationRules { AnimationRules(self.get_animation_rule(pseudo), self.get_transition_rule(pseudo)) diff --git a/components/style/gecko_bindings/bindings.rs b/components/style/gecko_bindings/bindings.rs index f4d1a515d4b..bb8dd04c19f 100644 --- a/components/style/gecko_bindings/bindings.rs +++ b/components/style/gecko_bindings/bindings.rs @@ -623,6 +623,11 @@ extern "C" { RawServoAnimationValueMapBorrowed) -> bool; } +extern "C" { + pub fn Gecko_GetSMILOverrideDeclarationBlock(element: + RawGeckoElementBorrowed) + -> RawServoDeclarationBlockStrongBorrowedOrNull; +} extern "C" { pub fn Gecko_StyleAnimationsEquals(arg1: RawGeckoStyleAnimationListBorrowed, diff --git a/components/style/matching.rs b/components/style/matching.rs index 201f6c50878..5d8b8b965c4 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -19,7 +19,8 @@ use dom::{AnimationRules, SendElement, TElement, TNode}; use font_metrics::FontMetricsProvider; use properties::{CascadeFlags, ComputedValues, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade}; use properties::longhands::display::computed_value as display; -use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS, RestyleHint}; +use restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS, RestyleHint}; +use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_SMIL}; use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode}; use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl}; use selectors::bloom::BloomFilter; @@ -882,6 +883,7 @@ pub trait MatchMethods : TElement { let stylist = &context.shared.stylist; let style_attribute = self.style_attribute(); + let smil_override = self.get_smil_override(); let animation_rules = self.get_animation_rules(None); let mut rule_nodes_changed = false; let bloom = context.thread_local.bloom_filter.filter(); @@ -895,6 +897,7 @@ pub trait MatchMethods : TElement { *relations = stylist.push_applicable_declarations(self, Some(bloom), style_attribute, + smil_override, animation_rules, None, &context.shared.guards, @@ -952,7 +955,9 @@ pub trait MatchMethods : TElement { }; stylist.push_applicable_declarations(self, Some(bloom_filter), - None, pseudo_animation_rules, + None, + None, + pseudo_animation_rules, Some(&pseudo), &guards, &mut applicable_declarations, @@ -1068,12 +1073,18 @@ pub trait MatchMethods : TElement { } }; - // RESTYLE_CSS_ANIMATIONS or RESTYLE_CSS_TRANSITIONS is processed prior to other - // restyle hints in the name of animation-only traversal. Rest of restyle hints - // will be processed in a subsequent normal traversal. + // Animation restyle hints are processed prior to other restyle hints in the + // animation-only traversal. Non-animation restyle hints will be processed in + // a subsequent normal traversal. if hint.intersects(RestyleHint::for_animations()) { debug_assert!(context.shared.traversal_flags.for_animation_only()); + if hint.contains(RESTYLE_SMIL) { + replace_rule_node(CascadeLevel::SMILOverride, + self.get_smil_override(), + primary_rules); + } + use data::EagerPseudoStyles; let mut replace_rule_node_for_animation = |level: CascadeLevel, primary_rules: &mut StrongRuleNode, @@ -1330,7 +1341,7 @@ pub trait MatchMethods : TElement { let relevant_style = pseudo_style.unwrap_or(primary_style); let rule_node = &relevant_style.rules; let without_animation_rules = - shared_context.stylist.rule_tree.remove_animation_and_transition_rules(rule_node); + shared_context.stylist.rule_tree.remove_animation_rules(rule_node); if without_animation_rules == *rule_node { // Note that unwrapping here is fine, because the style is // only incomplete during the styling process. diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index d28bbee910f..d1d733417e0 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -62,6 +62,11 @@ bitflags! { /// attribute has changed, and this change didn't have any other /// dependencies. const RESTYLE_STYLE_ATTRIBUTE = 0x40, + + /// Replace the style data coming from SMIL animations without updating + /// any other style data. This hint is only processed in animation-only + /// traversal which is prior to normal traversal. + const RESTYLE_SMIL = 0x80, } } @@ -95,19 +100,22 @@ pub fn assert_restyle_hints_match() { nsRestyleHint_eRestyle_CSSTransitions => RESTYLE_CSS_TRANSITIONS, nsRestyleHint_eRestyle_CSSAnimations => RESTYLE_CSS_ANIMATIONS, nsRestyleHint_eRestyle_StyleAttribute => RESTYLE_STYLE_ATTRIBUTE, + nsRestyleHint_eRestyle_StyleAttribute_Animations => RESTYLE_SMIL, } } impl RestyleHint { /// The subset hints that affect the styling of a single element during the /// traversal. + #[inline] pub fn for_self() -> Self { - RESTYLE_SELF | RESTYLE_STYLE_ATTRIBUTE | RESTYLE_CSS_ANIMATIONS | RESTYLE_CSS_TRANSITIONS + RESTYLE_SELF | RESTYLE_STYLE_ATTRIBUTE | Self::for_animations() } /// The subset hints that are used for animation restyle. + #[inline] pub fn for_animations() -> Self { - RESTYLE_CSS_ANIMATIONS | RESTYLE_CSS_TRANSITIONS + RESTYLE_SMIL | RESTYLE_CSS_ANIMATIONS | RESTYLE_CSS_TRANSITIONS } } diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index 128de7a67e2..fa6acaa92b5 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -254,19 +254,19 @@ impl RuleTree { path.parent().unwrap().clone() } - /// Returns new rule node without Animations and Transitions level rules. - pub fn remove_animation_and_transition_rules(&self, path: &StrongRuleNode) -> StrongRuleNode { - // Return a clone if there is neither animation nor transition level. + /// Returns new rule node without rules from declarative animations. + pub fn remove_animation_rules(&self, path: &StrongRuleNode) -> StrongRuleNode { + // Return a clone if there are no animation rules. if !path.has_animation_or_transition_rules() { return path.clone(); } - let iter = path.self_and_ancestors().take_while(|node| node.cascade_level() >= CascadeLevel::Animations); + let iter = path.self_and_ancestors().take_while( + |node| node.cascade_level() >= CascadeLevel::SMILOverride); let mut last = path; let mut children = vec![]; for node in iter { - if node.cascade_level() != CascadeLevel::Animations && - node.cascade_level() != CascadeLevel::Transitions { + if node.cascade_level().is_animation() { children.push((node.get().source.clone().unwrap(), node.cascade_level())); } last = node; @@ -301,6 +301,8 @@ pub enum CascadeLevel { AuthorNormal, /// Style attribute normal rules. StyleAttributeNormal, + /// SVG SMIL animations. + SMILOverride, /// CSS animations and script-generated animations. Animations, /// Author-supplied important rules. @@ -333,6 +335,7 @@ impl CascadeLevel { match *self { CascadeLevel::Transitions | CascadeLevel::Animations | + CascadeLevel::SMILOverride | CascadeLevel::StyleAttributeNormal | CascadeLevel::StyleAttributeImportant => true, _ => false, @@ -362,6 +365,17 @@ impl CascadeLevel { Importance::Normal } } + + /// Returns whether this cascade level represents an animation rules. + #[inline] + pub fn is_animation(&self) -> bool { + match *self { + CascadeLevel::SMILOverride | + CascadeLevel::Animations | + CascadeLevel::Transitions => true, + _ => false, + } + } } struct RuleNode { @@ -780,8 +794,8 @@ impl StrongRuleNode { /// Returns true if there is either animation or transition level rule. pub fn has_animation_or_transition_rules(&self) -> bool { self.self_and_ancestors() - .take_while(|node| node.cascade_level() >= CascadeLevel::Animations) - .any(|node| matches!(node.cascade_level(), CascadeLevel::Animations | CascadeLevel::Transitions)) + .take_while(|node| node.cascade_level() >= CascadeLevel::SMILOverride) + .any(|node| node.cascade_level().is_animation()) } } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 8ae9998c4cb..af0a4eb0e8a 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -510,6 +510,7 @@ impl Stylist { self.push_applicable_declarations(element, + None, None, None, AnimationRules(None, None), @@ -631,6 +632,7 @@ impl Stylist { element: &E, parent_bf: Option<&BloomFilter>, style_attribute: Option<&Arc>>, + smil_override: Option<&Arc>>, animation_rules: AnimationRules, pseudo_element: Option<&PseudoElement>, guards: &StylesheetGuards, @@ -711,7 +713,17 @@ impl Stylist { debug!("style attr: {:?}", relations); - // Step 5: Animations. + // Step 5: SMIL override. + // Declarations from SVG SMIL animation elements. + if let Some(so) = smil_override { + Push::push( + applicable_declarations, + ApplicableDeclarationBlock::from_declarations(so.clone(), + CascadeLevel::SMILOverride)); + } + debug!("SMIL: {:?}", relations); + + // Step 6: Animations. // The animations sheet (CSS animations, script-generated animations, // and CSS transitions that are no longer tied to CSS markup) if let Some(anim) = animation_rules.0 { @@ -722,7 +734,7 @@ impl Stylist { } debug!("animation: {:?}", relations); - // Step 6: Author-supplied `!important` rules. + // Step 7: Author-supplied `!important` rules. map.author.get_all_matching_rules(element, parent_bf, applicable_declarations, @@ -732,7 +744,7 @@ impl Stylist { debug!("author important: {:?}", relations); - // Step 7: `!important` style attributes. + // Step 8: `!important` style attributes. if let Some(sa) = style_attribute { if sa.read_with(guards.author).any_important() { relations |= AFFECTED_BY_STYLE_ATTRIBUTE; @@ -745,7 +757,7 @@ impl Stylist { debug!("style attr important: {:?}", relations); - // Step 8: User `!important` rules. + // Step 9: User `!important` rules. map.user.get_all_matching_rules(element, parent_bf, applicable_declarations, @@ -758,7 +770,7 @@ impl Stylist { debug!("skipping non-agent rules"); } - // Step 9: UA `!important` rules. + // Step 10: UA `!important` rules. map.user_agent.get_all_matching_rules(element, parent_bf, applicable_declarations, @@ -768,7 +780,7 @@ impl Stylist { debug!("UA important: {:?}", relations); - // Step 10: Transitions. + // Step 11: Transitions. // The transitions sheet (CSS transitions that are tied to CSS markup) if let Some(anim) = animation_rules.1 { Push::push( diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 7e463db9d13..71e05739818 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -1795,19 +1795,17 @@ pub extern "C" fn Servo_NoteExplicitHints(element: RawGeckoElementBorrowed, let damage = GeckoRestyleDamage::new(change_hint); debug!("Servo_NoteExplicitHints: {:?}, restyle_hint={:?}, change_hint={:?}", element, restyle_hint, change_hint); - debug_assert!(restyle_hint == structs::nsRestyleHint_eRestyle_CSSAnimations || - restyle_hint == structs::nsRestyleHint_eRestyle_CSSTransitions || - (restyle_hint.0 & (structs::nsRestyleHint_eRestyle_CSSAnimations.0 | - structs::nsRestyleHint_eRestyle_CSSTransitions.0)) == 0, - "eRestyle_CSSAnimations or eRestyle_CSSTransitions should only appear by itself"); + + let restyle_hint: RestyleHint = restyle_hint.into(); + debug_assert!(RestyleHint::for_animations().contains(restyle_hint) || + !RestyleHint::for_animations().intersects(restyle_hint), + "Animation restyle hints should not appear with non-animation restyle hints"); let mut maybe_data = element.mutate_data(); let maybe_restyle_data = maybe_data.as_mut().and_then(|d| unsafe { - maybe_restyle(d, element, restyle_hint == structs::nsRestyleHint_eRestyle_CSSAnimations || - restyle_hint == structs::nsRestyleHint_eRestyle_CSSTransitions) + maybe_restyle(d, element, restyle_hint.intersects(RestyleHint::for_animations())) }); if let Some(restyle_data) = maybe_restyle_data { - let restyle_hint: RestyleHint = restyle_hint.into(); restyle_data.hint.insert(&restyle_hint.into()); restyle_data.damage |= damage; } else {