diff --git a/servo/components/script/layout_wrapper.rs b/servo/components/script/layout_wrapper.rs index 12d830e5fad6..3ddb1d2b5c9a 100644 --- a/servo/components/script/layout_wrapper.rs +++ b/servo/components/script/layout_wrapper.rs @@ -461,16 +461,16 @@ impl<'le> TElement for ServoLayoutElement<'le> { self.element.has_selector_flags(flags) } - fn has_animations(&self, _pseudo: Option<&PseudoElement>) -> bool { - panic!("this should be only called on gecko"); + fn has_animations(&self) -> bool { + unreachable!("this should be only called on gecko"); } - fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool { - panic!("this should be only called on gecko"); + fn has_css_animations(&self) -> bool { + unreachable!("this should be only called on gecko"); } - fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool { - panic!("this should be only called on gecko"); + fn has_css_transitions(&self) -> bool { + unreachable!("this should be only called on gecko"); } } diff --git a/servo/components/style/context.rs b/servo/components/style/context.rs index 01043637a0dd..581a7c12cd3a 100644 --- a/servo/components/style/context.rs +++ b/servo/components/style/context.rs @@ -20,7 +20,6 @@ use font_metrics::FontMetricsProvider; use matching::StyleSharingCandidateCache; use parking_lot::RwLock; #[cfg(feature = "gecko")] use properties::ComputedValues; -#[cfg(feature = "gecko")] use selector_parser::PseudoElement; use selectors::matching::ElementSelectorFlags; #[cfg(feature = "servo")] use servo_config::opts; use shared_lock::StylesheetGuards; @@ -270,7 +269,8 @@ impl TraversalStatistics { #[cfg(feature = "gecko")] bitflags! { - /// Represents which tasks are performed in a SequentialTask of UpdateAnimations. + /// Represents which tasks are performed in a SequentialTask of + /// UpdateAnimations. pub flags UpdateAnimationsTasks: u8 { /// Update CSS Animations. const CSS_ANIMATIONS = structs::UpdateAnimationsTasks_CSSAnimations, @@ -296,10 +296,8 @@ pub enum SequentialTask { /// of the non-animation style traversal, and updating the computed effect properties. #[cfg(feature = "gecko")] UpdateAnimations { - /// The target element. + /// The target element or pseudo-element. el: SendElement, - /// The target pseudo element. - pseudo: Option, /// The before-change style for transitions. We use before-change style as the initial /// value of its Keyframe. Required if |tasks| includes CSSTransitions. before_change_style: Option>, @@ -316,8 +314,8 @@ impl SequentialTask { match self { Unused(_) => unreachable!(), #[cfg(feature = "gecko")] - UpdateAnimations { el, pseudo, before_change_style, tasks } => { - unsafe { el.update_animations(pseudo.as_ref(), before_change_style, tasks) }; + UpdateAnimations { el, before_change_style, tasks } => { + unsafe { el.update_animations(before_change_style, tasks) }; } } } @@ -326,14 +324,14 @@ impl SequentialTask { /// a given (pseudo-)element. #[cfg(feature = "gecko")] pub fn update_animations(el: E, - pseudo: Option, before_change_style: Option>, tasks: UpdateAnimationsTasks) -> Self { use self::SequentialTask::*; - UpdateAnimations { el: unsafe { SendElement::new(el) }, - pseudo: pseudo, - before_change_style: before_change_style, - tasks: tasks } + UpdateAnimations { + el: unsafe { SendElement::new(el) }, + before_change_style: before_change_style, + tasks: tasks, + } } } diff --git a/servo/components/style/data.rs b/servo/components/style/data.rs index a1cc8b59f65f..4052f0b30a3a 100644 --- a/servo/components/style/data.rs +++ b/servo/components/style/data.rs @@ -532,6 +532,21 @@ impl ElementData { self.styles = Some(styles); } + /// Sets the computed element rules, and returns whether the rules changed. + pub fn set_primary_rules(&mut self, rules: StrongRuleNode) -> bool { + if !self.has_styles() { + self.set_styles(ElementStyles::new(ComputedStyle::new_partial(rules))); + return true; + } + + if self.styles().primary.rules == rules { + return false; + } + + self.styles_mut().primary.rules = rules; + true + } + /// Returns true if the Element has a RestyleData. pub fn has_restyle(&self) -> bool { self.restyle.is_some() diff --git a/servo/components/style/dom.rs b/servo/components/style/dom.rs index c937dba8bb3e..ffc46b0cc941 100644 --- a/servo/components/style/dom.rs +++ b/servo/components/style/dom.rs @@ -274,11 +274,20 @@ pub trait PresentationalHintsSynthetizer { where V: Push; } -/// The animation rules. The first one is for Animation cascade level, and the second one is for +/// The animation rules. +/// +/// The first one is for Animation cascade level, and the second one is for /// Transition cascade level. pub struct AnimationRules(pub Option>>, pub Option>>); +impl AnimationRules { + /// Returns whether these animation rules represents an actual rule or not. + pub fn is_empty(&self) -> bool { + self.0.is_none() && self.1.is_none() + } +} + /// The element trait, the main abstraction the style crate acts over. pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer { @@ -325,26 +334,25 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + } /// Get this element's animation rules. - fn get_animation_rules(&self, _pseudo: Option<&PseudoElement>) -> AnimationRules { + fn get_animation_rules(&self) -> AnimationRules { AnimationRules(None, None) } /// Get this element's animation rule by the cascade level. fn get_animation_rule_by_cascade(&self, - _pseudo: Option<&PseudoElement>, _cascade_level: CascadeLevel) -> Option>> { None } /// Get this element's animation rule. - fn get_animation_rule(&self, _pseudo: Option<&PseudoElement>) + fn get_animation_rule(&self) -> Option>> { None } /// Get this element's transition rule. - fn get_transition_rule(&self, _pseudo: Option<&PseudoElement>) + fn get_transition_rule(&self) -> Option>> { None } @@ -428,6 +436,18 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + /// anonymous content). fn is_native_anonymous(&self) -> bool { false } + /// Returns the pseudo-element implemented by this element, if any. + /// + /// Gecko traverses pseudo-elements during the style traversal, and we need + /// to know this so we can properly grab the pseudo-element style from the + /// parent element. + /// + /// Note that we still need to compute the pseudo-elements before-hand, + /// given otherwise we don't know if we need to create an element or not. + /// + /// Servo doesn't have to deal with this. + fn implemented_pseudo_element(&self) -> Option { None } + /// Atomically stores the number of children of this node that we will /// need to process during bottom-up traversal. fn store_children_to_process(&self, n: isize); @@ -469,21 +489,21 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + /// Creates a task to update various animation state on a given (pseudo-)element. #[cfg(feature = "gecko")] - fn update_animations(&self, _pseudo: Option<&PseudoElement>, + fn update_animations(&self, before_change_style: Option>, tasks: UpdateAnimationsTasks); /// Returns true if the element has relevant animations. Relevant /// animations are those animations that are affecting the element's style /// or are scheduled to do so in the future. - fn has_animations(&self, _pseudo: Option<&PseudoElement>) -> bool; + fn has_animations(&self) -> bool; /// Returns true if the element has a CSS animation. - fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool; + fn has_css_animations(&self) -> bool; /// Returns true if the element has a CSS transition (including running transitions and /// completed transitions). - fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool; + fn has_css_transitions(&self) -> bool; /// Returns true if the element has animation restyle hints. fn has_animation_restyle_hints(&self) -> bool { @@ -497,8 +517,7 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + /// Gets the current existing CSS transitions, by |property, end value| pairs in a HashMap. #[cfg(feature = "gecko")] - fn get_css_transitions_info(&self, - pseudo: Option<&PseudoElement>) + fn get_css_transitions_info(&self) -> HashMap>; /// Does a rough (and cheap) check for whether or not transitions might need to be updated that @@ -508,9 +527,9 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + /// reduce the possibility of false positives. #[cfg(feature = "gecko")] fn might_need_transitions_update(&self, - old_values: &Option<&Arc>, - new_values: &Arc, - pseudo: Option<&PseudoElement>) -> bool; + old_values: Option<&ComputedValues>, + new_values: &ComputedValues) + -> bool; /// Returns true if one of the transitions needs to be updated on this element. We check all /// the transition properties to make sure that updating transitions is necessary. @@ -518,17 +537,17 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + /// passed the same parameters. #[cfg(feature = "gecko")] fn needs_transitions_update(&self, - before_change_style: &Arc, - after_change_style: &Arc, - pseudo: Option<&PseudoElement>) -> bool; + before_change_style: &ComputedValues, + after_change_style: &ComputedValues) + -> bool; /// Returns true if we need to update transitions for the specified property on this element. #[cfg(feature = "gecko")] fn needs_transitions_update_per_property(&self, property: &TransitionProperty, combined_duration: f32, - before_change_style: &Arc, - after_change_style: &Arc, + before_change_style: &ComputedValues, + after_change_style: &ComputedValues, existing_transitions: &HashMap>) -> bool; diff --git a/servo/components/style/gecko/selector_parser.rs b/servo/components/style/gecko/selector_parser.rs index 95b7ab82ac5d..b87824fc33fe 100644 --- a/servo/components/style/gecko/selector_parser.rs +++ b/servo/components/style/gecko/selector_parser.rs @@ -54,6 +54,26 @@ pub const EAGER_PSEUDO_COUNT: usize = 2; impl PseudoElement { + /// Returns the kind of cascade type that a given pseudo is going to use. + /// + /// In Gecko we only compute ::before and ::after eagerly. We save the rules + /// for anonymous boxes separately, so we resolve them as precomputed + /// pseudos. + /// + /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`. + pub fn cascade_type(&self) -> PseudoElementCascadeType { + if self.is_eager() { + debug_assert!(!self.is_anon_box()); + return PseudoElementCascadeType::Eager + } + + if self.is_anon_box() { + return PseudoElementCascadeType::Precomputed + } + + PseudoElementCascadeType::Lazy + } + /// Gets the canonical index of this eagerly-cascaded pseudo-element. #[inline] pub fn eager_index(&self) -> usize { @@ -437,24 +457,9 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> { impl SelectorImpl { #[inline] - /// Returns the kind of cascade type that a given pseudo is going to use. - /// - /// In Gecko we only compute ::before and ::after eagerly. We save the rules - /// for anonymous boxes separately, so we resolve them as precomputed - /// pseudos. - /// - /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`. + /// Legacy alias for PseudoElement::cascade_type. pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType { - if pseudo.is_eager() { - debug_assert!(!pseudo.is_anon_box()); - return PseudoElementCascadeType::Eager - } - - if pseudo.is_anon_box() { - return PseudoElementCascadeType::Precomputed - } - - PseudoElementCascadeType::Lazy + pseudo.cascade_type() } /// A helper to traverse each eagerly cascaded pseudo-element, executing diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs index 30b9d7b78461..62a2d835d1a2 100644 --- a/servo/components/style/gecko/wrapper.rs +++ b/servo/components/style/gecko/wrapper.rs @@ -421,12 +421,15 @@ fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 { } fn get_animation_rule(element: &GeckoElement, - pseudo: Option<&PseudoElement>, cascade_level: CascadeLevel) -> Option>> { - let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); + // FIXME(emilio): This is quite dumb, why an RwLock, it's local to this + // function? + // + // Also, we should try to reuse the PDB, to avoid creating extra rule nodes. let animation_values = Arc::new(RwLock::new(AnimationValueMap::new())); - if unsafe { Gecko_GetAnimationRule(element.0, atom_ptr, cascade_level, + if unsafe { Gecko_GetAnimationRule(element.0, + cascade_level, HasArcFFI::arc_as_borrowed(&animation_values)) } { let shared_lock = &GLOBAL_STYLE_DATA.shared_lock; Some(Arc::new(shared_lock.wrap( @@ -531,30 +534,28 @@ impl<'le> TElement for GeckoElement<'le> { 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)) + fn get_animation_rules(&self) -> AnimationRules { + AnimationRules(self.get_animation_rule(), + self.get_transition_rule()) } - fn get_animation_rule_by_cascade(&self, - pseudo: Option<&PseudoElement>, - cascade_level: ServoCascadeLevel) + fn get_animation_rule_by_cascade(&self, cascade_level: ServoCascadeLevel) -> Option>> { match cascade_level { - ServoCascadeLevel::Animations => self.get_animation_rule(pseudo), - ServoCascadeLevel::Transitions => self.get_transition_rule(pseudo), + ServoCascadeLevel::Animations => self.get_animation_rule(), + ServoCascadeLevel::Transitions => self.get_transition_rule(), _ => panic!("Unsupported cascade level for getting the animation rule") } } - fn get_animation_rule(&self, pseudo: Option<&PseudoElement>) + fn get_animation_rule(&self) -> Option>> { - get_animation_rule(self, pseudo, CascadeLevel::Animations) + get_animation_rule(self, CascadeLevel::Animations) } - fn get_transition_rule(&self, pseudo: Option<&PseudoElement>) + fn get_transition_rule(&self) -> Option>> { - get_animation_rule(self, pseudo, CascadeLevel::Transitions) + get_animation_rule(self, CascadeLevel::Transitions) } fn get_state(&self) -> ElementState { @@ -589,7 +590,7 @@ impl<'le> TElement for GeckoElement<'le> { -> Option<&'a nsStyleContext> { let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); unsafe { - let context_ptr = Gecko_GetStyleContext(self.as_node().0, atom_ptr); + let context_ptr = Gecko_GetStyleContext(self.0, atom_ptr); context_ptr.as_ref() } } @@ -641,6 +642,22 @@ impl<'le> TElement for GeckoElement<'le> { self.flags() & (NODE_IS_NATIVE_ANONYMOUS as u32) != 0 } + fn implemented_pseudo_element(&self) -> Option { + if !self.is_native_anonymous() { + return None; + } + + let maybe_atom = + unsafe { bindings::Gecko_GetImplementedPseudo(self.0) }; + + if maybe_atom.is_null() { + return None; + } + + let atom = Atom::from(maybe_atom); + Some(PseudoElement::from_atom_unchecked(atom, /* anon_box = */ false)) + } + fn store_children_to_process(&self, _: isize) { // This is only used for bottom-up traversal, and is thus a no-op for Gecko. } @@ -673,37 +690,27 @@ impl<'le> TElement for GeckoElement<'le> { } fn update_animations(&self, - pseudo: Option<&PseudoElement>, before_change_style: Option>, tasks: UpdateAnimationsTasks) { - // We have to update animations even if the element has no computed style - // since it means the element is in a display:none subtree, we should destroy - // all CSS animations in display:none subtree. + // We have to update animations even if the element has no computed + // style since it means the element is in a display:none subtree, we + // should destroy all CSS animations in display:none subtree. let computed_data = self.borrow_data(); let computed_values = - computed_data.as_ref().map(|d| - pseudo.map_or_else(|| d.styles().primary.values(), - |p| d.styles().pseudos.get(p).unwrap().values()) - ); - let computed_values_opt = computed_values.map(|v| - *HasArcFFI::arc_as_borrowed(v) - ); - - let parent_element = if pseudo.is_none() { - self.parent_element() - } else { - Some(*self) - }; - let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data()); - let parent_values = parent_data.as_ref().map(|d| d.styles().primary.values()); - let parent_values_opt = parent_values.map(|v| - *HasArcFFI::arc_as_borrowed(v) - ); - - let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); - let before_change_values = before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v)); + computed_data.as_ref().map(|d| d.styles().primary.values()); + let computed_values_opt = + computed_values.map(|v| *HasArcFFI::arc_as_borrowed(v)); + let parent_element = self.parent_element(); + let parent_data = + parent_element.as_ref().and_then(|e| e.borrow_data()); + let parent_values = + parent_data.as_ref().map(|d| d.styles().primary.values()); + let parent_values_opt = + parent_values.map(|v| *HasArcFFI::arc_as_borrowed(v)); + let before_change_values = + before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v)); unsafe { - Gecko_UpdateAnimations(self.0, atom_ptr, + Gecko_UpdateAnimations(self.0, before_change_values, computed_values_opt, parent_values_opt, @@ -711,35 +718,31 @@ impl<'le> TElement for GeckoElement<'le> { } } - fn has_animations(&self, pseudo: Option<&PseudoElement>) -> bool { - let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); - unsafe { Gecko_ElementHasAnimations(self.0, atom_ptr) } + fn has_animations(&self) -> bool { + unsafe { Gecko_ElementHasAnimations(self.0) } } - fn has_css_animations(&self, pseudo: Option<&PseudoElement>) -> bool { - let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); - unsafe { Gecko_ElementHasCSSAnimations(self.0, atom_ptr) } + fn has_css_animations(&self) -> bool { + unsafe { Gecko_ElementHasCSSAnimations(self.0) } } - fn has_css_transitions(&self, pseudo: Option<&PseudoElement>) -> bool { - let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); - unsafe { Gecko_ElementHasCSSTransitions(self.0, atom_ptr) } + fn has_css_transitions(&self) -> bool { + unsafe { Gecko_ElementHasCSSTransitions(self.0) } } - fn get_css_transitions_info(&self, - pseudo: Option<&PseudoElement>) + fn get_css_transitions_info(&self) -> HashMap> { use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt; use gecko_bindings::bindings::Gecko_ElementTransitions_Length; use gecko_bindings::bindings::Gecko_ElementTransitions_PropertyAt; - let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); - let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0, atom_ptr) }; + let collection_length = + unsafe { Gecko_ElementTransitions_Length(self.0) }; let mut map = HashMap::with_capacity(collection_length); for i in 0..collection_length { let (property, raw_end_value) = unsafe { - (Gecko_ElementTransitions_PropertyAt(self.0, atom_ptr, i as usize).into(), - Gecko_ElementTransitions_EndValueAt(self.0, atom_ptr, i as usize)) + (Gecko_ElementTransitions_PropertyAt(self.0, i as usize).into(), + Gecko_ElementTransitions_EndValueAt(self.0, i as usize)) }; let end_value = AnimationValue::arc_from_borrowed(&raw_end_value); debug_assert!(end_value.is_some()); @@ -749,21 +752,21 @@ impl<'le> TElement for GeckoElement<'le> { } fn might_need_transitions_update(&self, - old_values: &Option<&Arc>, - new_values: &Arc, - pseudo: Option<&PseudoElement>) -> bool { + old_values: Option<&ComputedValues>, + new_values: &ComputedValues) -> bool { use properties::longhands::display::computed_value as display; - if old_values.is_none() { - return false; - } + let old_values = match old_values { + Some(v) => v, + None => return false, + }; - let ref new_box_style = new_values.get_box(); - let transition_not_running = !self.has_css_transitions(pseudo) && + let new_box_style = new_values.get_box(); + let transition_not_running = !self.has_css_transitions() && new_box_style.transition_property_count() == 1 && new_box_style.transition_combined_duration_at(0) <= 0.0f32; let new_display_style = new_box_style.clone_display(); - let old_display_style = old_values.map(|ref old| old.get_box().clone_display()).unwrap(); + let old_display_style = old_values.get_box().clone_display(); new_box_style.transition_property_count() > 0 && !transition_not_running && @@ -771,28 +774,31 @@ impl<'le> TElement for GeckoElement<'le> { old_display_style != display::T::none) } - // Detect if there are any changes that require us to update transitions. This is used as a - // more thoroughgoing check than the, cheaper might_need_transitions_update check. + // Detect if there are any changes that require us to update transitions. + // This is used as a more thoroughgoing check than the, cheaper + // might_need_transitions_update check. + // // The following logic shadows the logic used on the Gecko side - // (nsTransitionManager::DoUpdateTransitions) where we actually perform the update. + // (nsTransitionManager::DoUpdateTransitions) where we actually perform the + // update. + // // https://drafts.csswg.org/css-transitions/#starting fn needs_transitions_update(&self, - before_change_style: &Arc, - after_change_style: &Arc, - pseudo: Option<&PseudoElement>) -> bool { + before_change_style: &ComputedValues, + after_change_style: &ComputedValues) + -> bool { use gecko_bindings::structs::nsCSSPropertyID; use properties::{PropertyId, animated_properties}; use std::collections::HashSet; - debug_assert!(self.might_need_transitions_update(&Some(before_change_style), - after_change_style, - pseudo), + debug_assert!(self.might_need_transitions_update(Some(before_change_style), + after_change_style), "We should only call needs_transitions_update if \ might_need_transitions_update returns true"); - let ref after_change_box_style = after_change_style.get_box(); + let after_change_box_style = after_change_style.get_box(); let transitions_count = after_change_box_style.transition_property_count(); - let existing_transitions = self.get_css_transitions_info(pseudo); + let existing_transitions = self.get_css_transitions_info(); let mut transitions_to_keep = if !existing_transitions.is_empty() && (after_change_box_style.transition_nscsspropertyid_at(0) != nsCSSPropertyID::eCSSPropertyExtra_all_properties) { @@ -865,8 +871,8 @@ impl<'le> TElement for GeckoElement<'le> { fn needs_transitions_update_per_property(&self, property: &TransitionProperty, combined_duration: f32, - before_change_style: &Arc, - after_change_style: &Arc, + before_change_style: &ComputedValues, + after_change_style: &ComputedValues, existing_transitions: &HashMap>) -> bool { diff --git a/servo/components/style/gecko_bindings/bindings.rs b/servo/components/style/gecko_bindings/bindings.rs index bb8dd04c19f1..f0cbccc54dca 100644 --- a/servo/components/style/gecko_bindings/bindings.rs +++ b/servo/components/style/gecko_bindings/bindings.rs @@ -615,8 +615,7 @@ extern "C" { -> RawServoDeclarationBlockStrongBorrowedOrNull; } extern "C" { - pub fn Gecko_GetAnimationRule(aElement: RawGeckoElementBorrowed, - aPseudoTag: *mut nsIAtom, + pub fn Gecko_GetAnimationRule(aElementOrPseudo: RawGeckoElementBorrowed, aCascadeLevel: EffectCompositor_CascadeLevel, aAnimationValues: @@ -636,46 +635,42 @@ extern "C" { -> bool; } extern "C" { - pub fn Gecko_UpdateAnimations(aElement: RawGeckoElementBorrowed, - aPseudoTagOrNull: *mut nsIAtom, + pub fn Gecko_UpdateAnimations(aElementOrPseudo: RawGeckoElementBorrowed, aOldComputedValues: ServoComputedValuesBorrowedOrNull, aComputedValues: ServoComputedValuesBorrowedOrNull, aParentComputedValues: ServoComputedValuesBorrowedOrNull, - aTaskBits: UpdateAnimationsTasks); + aTasks: UpdateAnimationsTasks); } extern "C" { - pub fn Gecko_ElementHasAnimations(aElement: RawGeckoElementBorrowed, - aPseudoTagOrNull: *mut nsIAtom) -> bool; + pub fn Gecko_ElementHasAnimations(aElementOrPseudo: + RawGeckoElementBorrowed) -> bool; } extern "C" { - pub fn Gecko_ElementHasCSSAnimations(aElement: RawGeckoElementBorrowed, - aPseudoTagOrNull: *mut nsIAtom) + pub fn Gecko_ElementHasCSSAnimations(aElementOrPseudo: + RawGeckoElementBorrowed) -> bool; +} +extern "C" { + pub fn Gecko_ElementHasCSSTransitions(aElementOrPseudo: + RawGeckoElementBorrowed) -> bool; } extern "C" { - pub fn Gecko_ElementHasCSSTransitions(aElement: RawGeckoElementBorrowed, - aPseudoTagOrNull: *mut nsIAtom) - -> bool; -} -extern "C" { - pub fn Gecko_ElementTransitions_Length(aElement: RawGeckoElementBorrowed, - aPseudoTagOrNull: *mut nsIAtom) + pub fn Gecko_ElementTransitions_Length(aElementOrPseudo: + RawGeckoElementBorrowed) -> usize; } extern "C" { - pub fn Gecko_ElementTransitions_PropertyAt(aElement: + pub fn Gecko_ElementTransitions_PropertyAt(aElementOrPseudo: RawGeckoElementBorrowed, - aPseudoTagOrNull: *mut nsIAtom, aIndex: usize) -> nsCSSPropertyID; } extern "C" { - pub fn Gecko_ElementTransitions_EndValueAt(aElement: + pub fn Gecko_ElementTransitions_EndValueAt(aElementOrPseudo: RawGeckoElementBorrowed, - aPseudoTagOrNull: *mut nsIAtom, aIndex: usize) -> RawServoAnimationValueBorrowedOrNull; } @@ -860,10 +855,14 @@ extern "C" { RawGeckoElementBorrowed); } extern "C" { - pub fn Gecko_GetStyleContext(node: RawGeckoNodeBorrowed, + pub fn Gecko_GetStyleContext(element: RawGeckoElementBorrowed, aPseudoTagOrNull: *mut nsIAtom) -> *mut nsStyleContext; } +extern "C" { + pub fn Gecko_GetImplementedPseudo(element: RawGeckoElementBorrowed) + -> *mut nsIAtom; +} extern "C" { pub fn Gecko_CalcStyleDifference(oldstyle: *mut nsStyleContext, newstyle: ServoComputedValuesBorrowed) diff --git a/servo/components/style/matching.rs b/servo/components/style/matching.rs index 5d8b8b965c40..31641711e363 100644 --- a/servo/components/style/matching.rs +++ b/servo/components/style/matching.rs @@ -30,6 +30,15 @@ use sink::ForgetfulSink; use std::sync::Arc; use stylist::ApplicableDeclarationBlock; +/// The way a style should be inherited. +enum InheritMode { + /// Inherit from the parent element, as normal CSS dictates. + FromParentElement, + /// Inherit from the primary style, this is used while computing eager + /// pseudos, like ::before and ::after when we're traversing the parent. + FromPrimaryStyle, +} + /// Determines the amount of relations where we're going to share style. #[inline] fn relations_are_shareable(relations: &StyleRelations) -> bool { @@ -78,6 +87,8 @@ pub struct StyleSharingCandidateCache { pub enum CacheMiss { /// The parents don't match. Parent, + /// One element was NAC, while the other wasn't. + NativeAnonymousContent, /// The local name of the element and the candidate don't match. LocalName, /// The namespace of the element and the candidate don't match. @@ -137,6 +148,12 @@ fn element_matches_candidate(element: &E, miss!(Parent) } + if element.is_native_anonymous() { + debug_assert!(!candidate_element.is_native_anonymous(), + "Why inserting NAC into the cache?"); + miss!(NativeAnonymousContent) + } + if *element.get_local_name() != *candidate_element.get_local_name() { miss!(LocalName) } @@ -298,6 +315,11 @@ impl StyleSharingCandidateCache { } }; + if element.is_native_anonymous() { + debug!("Failing to insert into the cache: NAC"); + return; + } + // These are things we don't check in the candidate match because they // are either uncommon or expensive. if !relations_are_shareable(&relations) { @@ -312,7 +334,6 @@ impl StyleSharingCandidateCache { debug_assert!(hints.is_empty(), "Style relations should not be shareable!"); } - let box_style = style.get_box(); if box_style.specifies_transitions() { debug!("Failing to insert to the cache: transitions"); @@ -387,7 +408,7 @@ trait PrivateMatchMethods: TElement { font_metrics_provider: &FontMetricsProvider, rule_node: &StrongRuleNode, primary_style: &ComputedStyle, - is_pseudo: bool) + inherit_mode: InheritMode) -> Arc { let mut cascade_info = CascadeInfo::new(); let mut cascade_flags = CascadeFlags::empty(); @@ -398,24 +419,27 @@ trait PrivateMatchMethods: TElement { // Grab the inherited values. let parent_el; let parent_data; - let style_to_inherit_from = if !is_pseudo { - parent_el = self.parent_element(); - parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); - let parent_values = parent_data.as_ref().map(|d| { - // Sometimes Gecko eagerly styles things without processing - // pending restyles first. In general we'd like to avoid this, - // but there can be good reasons (for example, needing to - // construct a frame for some small piece of newly-added - // content in order to do something specific with that frame, - // but not wanting to flush all of layout). - debug_assert!(cfg!(feature = "gecko") || d.has_current_styles()); - d.styles().primary.values() - }); + let style_to_inherit_from = match inherit_mode { + InheritMode::FromParentElement => { + parent_el = self.parent_element(); + parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); + let parent_values = parent_data.as_ref().map(|d| { + // Sometimes Gecko eagerly styles things without processing + // pending restyles first. In general we'd like to avoid this, + // but there can be good reasons (for example, needing to + // construct a frame for some small piece of newly-added + // content in order to do something specific with that frame, + // but not wanting to flush all of layout). + debug_assert!(cfg!(feature = "gecko") || d.has_current_styles()); + d.styles().primary.values() + }); - parent_values - } else { - parent_el = Some(self.clone()); - Some(primary_style.values()) + parent_values + } + InheritMode::FromPrimaryStyle => { + parent_el = Some(self.clone()); + Some(primary_style.values()) + } }; let mut layout_parent_el = parent_el.clone(); @@ -433,15 +457,16 @@ trait PrivateMatchMethods: TElement { // Propagate the "can be fragmented" bit. It would be nice to // encapsulate this better. // - // Note that this is not needed for pseudos since we already do that - // when we resolve the non-pseudo style. - if !is_pseudo { - if let Some(ref p) = layout_parent_style { - let can_be_fragmented = - p.is_multicol() || - layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented(); - unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); } - } + // Note that this is technically not needed for pseudos since we already + // do that when we resolve the non-pseudo style, but it doesn't hurt + // anyway. + // + // TODO(emilio): This is servo-only, move somewhere else? + if let Some(ref p) = layout_parent_style { + let can_be_fragmented = + p.is_multicol() || + layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented(); + unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); } } // Invoke the cascade algorithm. @@ -463,16 +488,21 @@ trait PrivateMatchMethods: TElement { fn cascade_internal(&self, context: &StyleContext, primary_style: &ComputedStyle, - pseudo_style: Option<&ComputedStyle>) + eager_pseudo_style: Option<&ComputedStyle>) -> Arc { // Grab the rule node. - let rule_node = &pseudo_style.unwrap_or(primary_style).rules; + let rule_node = &eager_pseudo_style.unwrap_or(primary_style).rules; + let inherit_mode = if eager_pseudo_style.is_some() { + InheritMode::FromPrimaryStyle + } else { + InheritMode::FromParentElement + }; self.cascade_with_rules(context.shared, &context.thread_local.font_metrics_provider, rule_node, primary_style, - pseudo_style.is_some()) + inherit_mode) } /// Computes values and damage for the primary or pseudo style of an element, @@ -480,8 +510,9 @@ trait PrivateMatchMethods: TElement { fn cascade_primary_or_pseudo(&self, context: &mut StyleContext, data: &mut ElementData, - pseudo: Option<&PseudoElement>, - animate: bool) { + pseudo: Option<&PseudoElement>) { + debug_assert!(pseudo.is_none() || self.implemented_pseudo_element().is_none(), + "Pseudo-element-implementing elements can't have pseudos!"); // Collect some values. let (mut styles, restyle) = data.styles_and_restyle_mut(); let mut primary_style = &mut styles.primary; @@ -501,27 +532,65 @@ trait PrivateMatchMethods: TElement { }; // Compute the new values. - let mut new_values = self.cascade_internal(context, - primary_style, - pseudo_style.as_ref().map(|s| &**s)); + let mut new_values = match self.implemented_pseudo_element() { + Some(ref pseudo) => { + // This is an element-backed pseudo, just grab the styles from + // the parent if it's eager, and recascade otherwise. + // + // We also recascade if the eager pseudo-style has any animation + // rules, because we don't cascade those during the eager + // traversal. We could make that a bit better if the complexity + // cost is not too big, but given further restyles are posted + // directly to pseudo-elements, it doesn't seem worth the effort + // at a glance. + if pseudo.is_eager() && + self.get_animation_rules().is_empty() { + let parent = self.parent_element().unwrap(); - // Handle animations. - if animate && !context.shared.traversal_flags.for_animation_only() { + let parent_data = parent.borrow_data().unwrap(); + let pseudo_style = + parent_data.styles().pseudos.get(pseudo).unwrap(); + pseudo_style.values().clone() + } else { + self.cascade_internal(context, + primary_style, + None) + } + } + None => { + // Else it's an eager pseudo or a normal element, do the cascade + // work. + self.cascade_internal(context, + primary_style, + pseudo_style.as_ref().map(|s| &**s)) + } + }; + + // NB: Animations for pseudo-elements in Gecko are handled while + // traversing the pseudo-elements themselves. + if pseudo.is_none() && + !context.shared.traversal_flags.for_animation_only() { self.process_animations(context, &mut old_values, &mut new_values, - primary_style, - pseudo, - pseudo_style.as_ref().map(|s| &**s)); + primary_style); } // Accumulate restyle damage. if let Some(old) = old_values { - self.accumulate_damage(&context.shared, - restyle.unwrap(), - &old, - &new_values, - pseudo); + // ::before and ::after are element-backed in Gecko, so they do + // the damage calculation for themselves. + // + // FIXME(emilio): We have more element-backed stuff, and this is + // redundant for them right now. + if cfg!(feature = "servo") || + pseudo.map_or(true, |p| !p.is_before_or_after()) { + self.accumulate_damage(&context.shared, + restyle.unwrap(), + &old, + &new_values, + pseudo); + } } // Set the new computed values. @@ -534,11 +603,9 @@ trait PrivateMatchMethods: TElement { #[cfg(feature = "gecko")] fn get_after_change_style(&self, context: &mut StyleContext, - primary_style: &ComputedStyle, - pseudo_style: Option<&ComputedStyle>) + primary_style: &ComputedStyle) -> Option> { - let relevant_style = pseudo_style.unwrap_or(primary_style); - let rule_node = &relevant_style.rules; + let rule_node = &primary_style.rules; let without_transition_rules = context.shared.stylist.rule_tree.remove_transition_rule_if_applicable(rule_node); if without_transition_rules == *rule_node { @@ -551,21 +618,21 @@ trait PrivateMatchMethods: TElement { &context.thread_local.font_metrics_provider, &without_transition_rules, primary_style, - pseudo_style.is_some())) + InheritMode::FromParentElement)) } #[cfg(feature = "gecko")] fn needs_animations_update(&self, - old_values: &Option>, - new_values: &Arc, - pseudo: Option<&PseudoElement>) -> bool { - let ref new_box_style = new_values.get_box(); + old_values: Option<&Arc>, + new_values: &ComputedValues) + -> bool { + let new_box_style = new_values.get_box(); let has_new_animation_style = new_box_style.animation_name_count() >= 1 && new_box_style.animation_name_at(0).0.is_some(); - let has_animations = self.has_css_animations(pseudo); + let has_animations = self.has_css_animations(); - old_values.as_ref().map_or(has_new_animation_style, |ref old| { - let ref old_box_style = old.get_box(); + old_values.map_or(has_new_animation_style, |old| { + let old_box_style = old.get_box(); let old_display_style = old_box_style.clone_display(); let new_display_style = new_box_style.clone_display(); // FIXME: Bug 1344581: We still need to compare keyframe rules. @@ -584,40 +651,34 @@ trait PrivateMatchMethods: TElement { context: &mut StyleContext, old_values: &mut Option>, new_values: &mut Arc, - primary_style: &ComputedStyle, - pseudo: Option<&PseudoElement>, - pseudo_style: Option<&ComputedStyle>) { + primary_style: &ComputedStyle) { use context::{CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES}; use context::UpdateAnimationsTasks; - debug_assert_eq!(pseudo.is_some(), pseudo_style.is_some()); - let mut tasks = UpdateAnimationsTasks::empty(); - if self.needs_animations_update(old_values, new_values, pseudo) { + if self.needs_animations_update(old_values.as_ref(), new_values) { tasks.insert(CSS_ANIMATIONS); } - let before_change_style = if self.might_need_transitions_update(&old_values.as_ref(), - new_values, - pseudo) { - let after_change_style = if self.has_css_transitions(pseudo) { - self.get_after_change_style(context, primary_style, pseudo_style) + let before_change_style = if self.might_need_transitions_update(old_values.as_ref().map(|s| &**s), + new_values) { + let after_change_style = if self.has_css_transitions() { + self.get_after_change_style(context, primary_style) } else { None }; - // In order to avoid creating a SequentialTask for transitions which may not be updated, - // we check it per property to make sure Gecko side will really update transition. + // In order to avoid creating a SequentialTask for transitions which + // may not be updated, we check it per property to make sure Gecko + // side will really update transition. let needs_transitions_update = { - // We borrow new_values here, so need to add a scope to make sure we release it - // before assigning a new value to it. - let after_change_style_ref = match after_change_style { - Some(ref value) => value, - None => &new_values - }; + // We borrow new_values here, so need to add a scope to make + // sure we release it before assigning a new value to it. + let after_change_style_ref = + after_change_style.as_ref().unwrap_or(&new_values); + self.needs_transitions_update(old_values.as_ref().unwrap(), - after_change_style_ref, - pseudo) + after_change_style_ref) }; if needs_transitions_update { @@ -635,13 +696,12 @@ trait PrivateMatchMethods: TElement { None }; - if self.has_animations(pseudo) { + if self.has_animations() { tasks.insert(EFFECT_PROPERTIES); } if !tasks.is_empty() { let task = ::context::SequentialTask::update_animations(*self, - pseudo.cloned(), before_change_style, tasks); context.thread_local.tasks.push(task); @@ -653,11 +713,7 @@ trait PrivateMatchMethods: TElement { context: &mut StyleContext, old_values: &mut Option>, new_values: &mut Arc, - _primary_style: &ComputedStyle, - pseudo: Option<&PseudoElement>, - pseudo_style: Option<&ComputedStyle>) { - debug_assert_eq!(pseudo.is_some(), pseudo_style.is_some()); - + _primary_style: &ComputedStyle) { let possibly_expired_animations = &mut context.thread_local.current_element_info.as_mut().unwrap() .possibly_expired_animations; @@ -690,6 +746,10 @@ trait PrivateMatchMethods: TElement { } /// Computes and applies non-redundant damage. + /// + /// FIXME(emilio): Damage for non-::before and non-::after element-backed + /// pseudo-elements should be refactored to go on themselves (right now they + /// do, but we apply this twice). #[cfg(feature = "gecko")] fn accumulate_damage(&self, shared_context: &SharedStyleContext, @@ -716,7 +776,9 @@ trait PrivateMatchMethods: TElement { // for followup work to make the optimization here more optimal by considering // each bit individually. if !restyle.damage.contains(RestyleDamage::reconstruct()) { - let new_damage = self.compute_restyle_damage(&old_values, &new_values, pseudo); + let new_damage = self.compute_restyle_damage(&old_values, + &new_values, + pseudo); if !restyle.damage_handled.contains(new_damage) { restyle.damage |= new_damage; } @@ -813,7 +875,8 @@ pub enum StyleSharingBehavior { /// The public API that elements expose for selector matching. pub trait MatchMethods : TElement { - /// Performs selector matching and property cascading on an element and its eager pseudos. + /// Performs selector matching and property cascading on an element and its + /// eager pseudos. fn match_and_cascade(&self, context: &mut StyleContext, data: &mut ElementData, @@ -878,14 +941,54 @@ pub trait MatchMethods : TElement { relations: &mut StyleRelations) -> bool { + let implemented_pseudo = self.implemented_pseudo_element(); + if let Some(ref pseudo) = implemented_pseudo { + if pseudo.is_eager() { + // If it's an eager element-backed pseudo, just grab the matched + // rules from the parent, and update animations. + let parent = self.parent_element().unwrap(); + let parent_data = parent.borrow_data().unwrap(); + let pseudo_style = + parent_data.styles().pseudos.get(&pseudo).unwrap(); + let mut rules = pseudo_style.rules.clone(); + let animation_rules = self.get_animation_rules(); + + // Handle animations here. + if let Some(animation_rule) = animation_rules.0 { + let animation_rule_node = + context.shared.stylist.rule_tree + .update_rule_at_level(CascadeLevel::Animations, + Some(&animation_rule), + &mut rules, + &context.shared.guards); + if let Some(node) = animation_rule_node { + rules = node; + } + } + + if let Some(animation_rule) = animation_rules.1 { + let animation_rule_node = + context.shared.stylist.rule_tree + .update_rule_at_level(CascadeLevel::Transitions, + Some(&animation_rule), + &mut rules, + &context.shared.guards); + if let Some(node) = animation_rule_node { + rules = node; + } + } + + return data.set_primary_rules(rules); + } + } + let mut applicable_declarations = Vec::::with_capacity(16); 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 animation_rules = self.get_animation_rules(); let bloom = context.thread_local.bloom_filter.filter(); let map = &mut context.thread_local.selector_flags; @@ -899,22 +1002,15 @@ pub trait MatchMethods : TElement { style_attribute, smil_override, animation_rules, - None, + implemented_pseudo.as_ref(), &context.shared.guards, &mut applicable_declarations, &mut set_selector_flags); let primary_rule_node = compute_rule_node::(&stylist.rule_tree, &mut applicable_declarations); - if !data.has_styles() { - data.set_styles(ElementStyles::new(ComputedStyle::new_partial(primary_rule_node))); - rule_nodes_changed = true; - } else if data.styles().primary.rules != primary_rule_node { - data.styles_mut().primary.rules = primary_rule_node; - rule_nodes_changed = true; - } - rule_nodes_changed + return data.set_primary_rules(primary_rule_node); } /// Runs selector matching to (re)compute eager pseudo-element rule nodes for this @@ -927,9 +1023,13 @@ pub trait MatchMethods : TElement { data: &mut ElementData) -> bool { + if self.implemented_pseudo_element().is_some() { + // Element pseudos can't have any other pseudo. + return false; + } + let mut applicable_declarations = Vec::::with_capacity(16); - let mut rule_nodes_changed = false; let map = &mut context.thread_local.selector_flags; let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| { @@ -945,19 +1045,17 @@ pub trait MatchMethods : TElement { // Compute rule nodes for eagerly-cascaded pseudo-elements. let mut matches_different_pseudos = false; + let mut rule_nodes_changed = false; SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { let mut pseudos = &mut data.styles_mut().pseudos; debug_assert!(applicable_declarations.is_empty()); - let pseudo_animation_rules = if pseudo.is_before_or_after() { - self.get_animation_rules(Some(&pseudo)) - } else { - AnimationRules(None, None) - }; + // NB: We handle animation rules for ::before and ::after when + // traversing them. stylist.push_applicable_declarations(self, Some(bloom_filter), None, None, - pseudo_animation_rules, + AnimationRules(None, None), Some(&pseudo), &guards, &mut applicable_declarations, @@ -1073,9 +1171,11 @@ pub trait MatchMethods : TElement { } }; - // 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. + // 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()); @@ -1085,37 +1185,24 @@ pub trait MatchMethods : TElement { primary_rules); } - use data::EagerPseudoStyles; let mut replace_rule_node_for_animation = |level: CascadeLevel, - primary_rules: &mut StrongRuleNode, - pseudos: &mut EagerPseudoStyles| { - let animation_rule = self.get_animation_rule_by_cascade(None, level); + primary_rules: &mut StrongRuleNode| { + let animation_rule = self.get_animation_rule_by_cascade(level); replace_rule_node(level, animation_rule.as_ref(), primary_rules); - - for pseudo in pseudos.keys().iter().filter(|p| p.is_before_or_after()) { - let animation_rule = self.get_animation_rule_by_cascade(Some(&pseudo), level); - let pseudo_rules = &mut pseudos.get_mut(&pseudo).unwrap().rules; - replace_rule_node(level, - animation_rule.as_ref(), - pseudo_rules); - } }; // Apply Transition rules and Animation rules if the corresponding restyle hint // is contained. - let pseudos = &mut element_styles.pseudos; if hint.contains(RESTYLE_CSS_TRANSITIONS) { replace_rule_node_for_animation(CascadeLevel::Transitions, - primary_rules, - pseudos); + primary_rules); } if hint.contains(RESTYLE_CSS_ANIMATIONS) { replace_rule_node_for_animation(CascadeLevel::Animations, - primary_rules, - pseudos); + primary_rules); } } else if hint.contains(RESTYLE_STYLE_ATTRIBUTE) { let style_attribute = self.style_attribute(); @@ -1125,11 +1212,9 @@ pub trait MatchMethods : TElement { replace_rule_node(CascadeLevel::StyleAttributeImportant, style_attribute, primary_rules); - // The per-pseudo rule nodes never change in this path. } } - // The per-pseudo rule nodes never change in this path. rule_node_changed } @@ -1151,6 +1236,11 @@ pub trait MatchMethods : TElement { return StyleSharingResult::CannotShare } + if self.is_native_anonymous() { + debug!("{:?} Cannot share style: NAC", self); + return StyleSharingResult::CannotShare; + } + if self.style_attribute().is_some() { debug!("{:?} Cannot share style: element has style attribute", self); return StyleSharingResult::CannotShare @@ -1310,7 +1400,7 @@ pub trait MatchMethods : TElement { context: &mut StyleContext, mut data: &mut ElementData) { - self.cascade_primary_or_pseudo(context, &mut data, None, /* animate = */ true); + self.cascade_primary_or_pseudo(context, &mut data, None); } /// Performs the cascade for the element's eager pseudos. @@ -1325,9 +1415,7 @@ pub trait MatchMethods : TElement { // let us pass the mutable |data| to the cascade function. let matched_pseudos = data.styles().pseudos.keys(); for pseudo in matched_pseudos { - // Only ::before and ::after are animatable. - let animate = pseudo.is_before_or_after(); - self.cascade_primary_or_pseudo(context, data, Some(&pseudo), animate); + self.cascade_primary_or_pseudo(context, data, Some(&pseudo)); } } @@ -1352,7 +1440,7 @@ pub trait MatchMethods : TElement { font_metrics_provider, &without_animation_rules, primary_style, - pseudo_style.is_some()) + InheritMode::FromParentElement) } } diff --git a/servo/components/style/restyle_hints.rs b/servo/components/style/restyle_hints.rs index d1d733417e08..cbe10f3ed41a 100644 --- a/servo/components/style/restyle_hints.rs +++ b/servo/components/style/restyle_hints.rs @@ -17,7 +17,7 @@ use selector_parser::{AttrValue, NonTSPseudoClass, Snapshot, SelectorImpl}; use selectors::{Element, MatchAttr}; use selectors::matching::{ElementSelectorFlags, StyleRelations}; use selectors::matching::matches_selector; -use selectors::parser::{AttrSelector, Combinator, ComplexSelector, Component}; +use selectors::parser::{AttrSelector, Combinator, Component, Selector}; use selectors::parser::{SelectorInner, SelectorIter, SelectorMethods}; use selectors::visitor::SelectorVisitor; use std::clone::Clone; @@ -122,8 +122,7 @@ impl RestyleHint { #[cfg(feature = "gecko")] impl From for RestyleHint { fn from(raw: nsRestyleHint) -> Self { - use std::mem; - let raw_bits: u32 = unsafe { mem::transmute(raw) }; + let raw_bits: u32 = raw.0; // FIXME(bholley): Finish aligning the binary representations here and // then .expect() the result of the checked version. if Self::from_bits(raw_bits).is_none() { @@ -574,11 +573,10 @@ impl DependencySet { /// Adds a selector to this `DependencySet`, and returns whether it may need /// cache revalidation, that is, whether two siblings of the same "shape" /// may have different style due to this selector. - pub fn note_selector(&mut self, - base: &ComplexSelector) - -> bool - { - let mut next = Some(base.clone()); + pub fn note_selector(&mut self, selector: &Selector) -> bool { + let mut is_pseudo_element = selector.pseudo_element.is_some(); + + let mut next = Some(selector.inner.complex.clone()); let mut combinator = None; let mut needs_revalidation = false; @@ -590,6 +588,19 @@ impl DependencySet { needs_revalidation: false, }; + if is_pseudo_element { + // TODO(emilio): use more fancy restyle hints to avoid restyling + // the whole subtree when pseudos change. + // + // We currently need is_pseudo_element to handle eager pseudos + // (so the style the parent stores doesn't become stale), and + // restyle_descendants to handle all of them (::before and + // ::after, because we find them in the subtree, and other lazy + // pseudos for the same reason). + visitor.hint |= RESTYLE_SELF | RESTYLE_DESCENDANTS; + is_pseudo_element = false; + } + { // Visit all the simple selectors. let mut iter = current.iter(); diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index af0a4eb0e8ac..bf008ba75747 100644 --- a/servo/components/style/stylist.rs +++ b/servo/components/style/stylist.rs @@ -333,7 +333,7 @@ impl Stylist { for selector in &style_rule.selectors.0 { let needs_cache_revalidation = - self.dependencies.note_selector(&selector.inner.complex); + self.dependencies.note_selector(selector); if needs_cache_revalidation { self.selectors_for_cache_revalidation.push(selector.clone()); } @@ -646,7 +646,10 @@ impl Stylist { F: FnMut(&E, ElementSelectorFlags), { debug_assert!(!self.is_device_dirty); - debug_assert!(style_attribute.is_none() || pseudo_element.is_none(), + // Gecko definitely has pseudo-elements with style attributes, like + // ::-moz-color-swatch. + debug_assert!(cfg!(feature = "gecko") || + style_attribute.is_none() || pseudo_element.is_none(), "Style attributes do not apply to pseudo-elements"); debug_assert!(pseudo_element.as_ref().map_or(true, |p| !p.is_precomputed())); diff --git a/servo/components/style/traversal.rs b/servo/components/style/traversal.rs index e9f4e7c27754..831a9454a627 100644 --- a/servo/components/style/traversal.rs +++ b/servo/components/style/traversal.rs @@ -264,88 +264,118 @@ pub trait DomTraversal : Sync { return true; } - match node.as_element() { - None => Self::text_node_needs_traversal(node), - Some(el) => { - // If the element is native-anonymous and an ancestor frame will - // be reconstructed, the child and all its descendants will be - // destroyed. In that case, we don't need to traverse the subtree. - if el.is_native_anonymous() { - if let Some(parent) = el.parent_element() { - let parent_data = parent.borrow_data().unwrap(); - if let Some(r) = parent_data.get_restyle() { - if (r.damage | r.damage_handled()).contains(RestyleDamage::reconstruct()) { - debug!("Element {:?} is in doomed NAC subtree - culling traversal", el); - return false; - } + let el = match node.as_element() { + None => return Self::text_node_needs_traversal(node), + Some(el) => el, + }; + + // If the element is native-anonymous and an ancestor frame will + // be reconstructed, the child and all its descendants will be + // destroyed. In that case, we wouldn't need to traverse the + // subtree... + // + // Except if there could be transitions of pseudo-elements, in + // which + // case we still need to process them, unfortunately. + // + // We need to conservatively continue the traversal to style the + // pseudo-element in order to properly process potentially-new + // transitions that we won't see otherwise. + // + // But it may be that we no longer match, so detect that case + // and act appropriately here. + if el.is_native_anonymous() { + if let Some(parent) = el.parent_element() { + let parent_data = parent.borrow_data().unwrap(); + let going_to_reframe = parent_data.get_restyle().map_or(false, |r| { + (r.damage | r.damage_handled()) + .contains(RestyleDamage::reconstruct()) + }); + + let mut is_before_or_after_pseudo = false; + if let Some(pseudo) = el.implemented_pseudo_element() { + if pseudo.is_before_or_after() { + is_before_or_after_pseudo = true; + let still_match = + parent_data.styles().pseudos.get(&pseudo).is_some(); + + if !still_match { + debug_assert!(going_to_reframe, + "We're removing a pseudo, so we \ + should reframe!"); + return false; } } } - // In case of animation-only traversal we need to traverse - // the element if the element has animation only dirty - // descendants bit, animation-only restyle hint or recascade. - if traversal_flags.for_animation_only() { - if el.has_animation_only_dirty_descendants() { - return true; - } - - let data = match el.borrow_data() { - Some(d) => d, - None => return false, - }; - return data.get_restyle() - .map_or(false, |r| r.hint.has_animation_hint() || r.recascade); + if going_to_reframe && !is_before_or_after_pseudo { + debug!("Element {:?} is in doomed NAC subtree, \ + culling traversal", el); + return false; } - - // If the dirty descendants bit is set, we need to traverse no - // matter what. Skip examining the ElementData. - if el.has_dirty_descendants() { - return true; - } - - // Check the element data. If it doesn't exist, we need to visit - // the element. - let data = match el.borrow_data() { - Some(d) => d, - None => return true, - }; - - // If we don't have any style data, we need to visit the element. - if !data.has_styles() { - return true; - } - - // Check the restyle data. - if let Some(r) = data.get_restyle() { - // If we have a restyle hint or need to recascade, we need to - // visit the element. - // - // Note that this is different than checking has_current_styles(), - // since that can return true even if we have a restyle hint - // indicating that the element's descendants (but not necessarily - // the element) need restyling. - if !r.hint.is_empty() || r.recascade { - return true; - } - } - - // Servo uses the post-order traversal for flow construction, so - // we need to traverse any element with damage so that we can perform - // fixup / reconstruction on our way back up the tree. - // - // We also need to traverse nodes with explicit damage and no other - // restyle data, so that this damage can be cleared. - if (cfg!(feature = "servo") || - traversal_flags.for_reconstruct()) && - data.get_restyle().map_or(false, |r| r.damage != RestyleDamage::empty()) - { - return true; - } - - false - }, + } } + + // In case of animation-only traversal we need to traverse + // the element if the element has animation only dirty + // descendants bit, animation-only restyle hint or recascade. + if traversal_flags.for_animation_only() { + if el.has_animation_only_dirty_descendants() { + return true; + } + + let data = match el.borrow_data() { + Some(d) => d, + None => return false, + }; + return data.get_restyle() + .map_or(false, |r| r.hint.has_animation_hint() || r.recascade); + } + + // If the dirty descendants bit is set, we need to traverse no + // matter what. Skip examining the ElementData. + if el.has_dirty_descendants() { + return true; + } + + // Check the element data. If it doesn't exist, we need to visit + // the element. + let data = match el.borrow_data() { + Some(d) => d, + None => return true, + }; + + // If we don't have any style data, we need to visit the element. + if !data.has_styles() { + return true; + } + + // Check the restyle data. + if let Some(r) = data.get_restyle() { + // If we have a restyle hint or need to recascade, we need to + // visit the element. + // + // Note that this is different than checking has_current_styles(), + // since that can return true even if we have a restyle hint + // indicating that the element's descendants (but not necessarily + // the element) need restyling. + if !r.hint.is_empty() || r.recascade { + return true; + } + } + + // Servo uses the post-order traversal for flow construction, so + // we need to traverse any element with damage so that we can perform + // fixup / reconstruction on our way back up the tree. + // + // We also need to traverse nodes with explicit damage and no other + // restyle data, so that this damage can be cleared. + if (cfg!(feature = "servo") || traversal_flags.for_reconstruct()) && + data.get_restyle().map_or(false, |r| !r.damage.is_empty()) { + return true; + } + + false } /// Returns true if traversal of this element's children is allowed. We use @@ -396,7 +426,6 @@ pub trait DomTraversal : Sync { } return true; - } /// Helper for the traversal implementations to select the children that @@ -489,7 +518,9 @@ fn resolve_style_internal(context: &mut StyleContext, // Compute our style. context.thread_local.begin_element(element, &data); - element.match_and_cascade(context, &mut data, StyleSharingBehavior::Disallow); + element.match_and_cascade(context, + &mut data, + StyleSharingBehavior::Disallow); context.thread_local.end_element(element); // Conservatively mark us as having dirty descendants, since there might @@ -607,9 +638,13 @@ pub fn recalc_style_at(traversal: &D, }; debug_assert!(data.has_current_styles() || context.shared.traversal_flags.for_animation_only(), - "Should have computed style or haven't yet valid computed style in case of animation-only restyle"); - trace!("propagated_hint={:?}, inherited_style_changed={:?}", - propagated_hint, inherited_style_changed); + "Should have computed style or haven't yet valid computed \ + style in case of animation-only restyle"); + trace!("propagated_hint={:?}, inherited_style_changed={:?}, \ + is_display_none={:?}, implementing_pseudo={:?}", + propagated_hint, inherited_style_changed, + data.styles().is_display_none(), + element.implemented_pseudo_element()); let has_dirty_descendants_for_this_restyle = if context.shared.traversal_flags.for_animation_only() { @@ -664,7 +699,8 @@ pub fn recalc_style_at(traversal: &D, // The second case is when we are in a restyle for reconstruction, // where we won't need to perform a post-traversal to pick up any // change hints. - if data.styles().is_display_none() || context.shared.traversal_flags.for_reconstruct() { + if data.styles().is_display_none() || + context.shared.traversal_flags.for_reconstruct() { unsafe { element.unset_dirty_descendants(); } } } diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index cb532b47f237..16e19ad78218 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -23,7 +23,7 @@ use style::font_metrics::get_metrics_provider_for_product; use style::gecko::data::{PerDocumentStyleData, PerDocumentStyleDataImpl}; use style::gecko::global_style_data::{GLOBAL_STYLE_DATA, GlobalStyleData}; use style::gecko::restyle_damage::GeckoRestyleDamage; -use style::gecko::selector_parser::{SelectorImpl, PseudoElement}; +use style::gecko::selector_parser::PseudoElement; use style::gecko::traversal::RecalcStyleOnly; use style::gecko::wrapper::GeckoElement; use style::gecko_bindings::bindings; @@ -166,7 +166,8 @@ fn create_shared_context<'a>(global_style_data: &GlobalStyleData, } } -fn traverse_subtree(element: GeckoElement, raw_data: RawServoStyleSetBorrowed, +fn traverse_subtree(element: GeckoElement, + raw_data: RawServoStyleSetBorrowed, traversal_flags: TraversalFlags) { // When new content is inserted in a display:none subtree, we will call into // servo to try to style it. Detect that here and bail out. @@ -936,12 +937,15 @@ pub extern "C" fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed, } } -fn get_pseudo_style(guard: &SharedRwLockReadGuard, element: GeckoElement, pseudo_tag: *mut nsIAtom, - styles: &ElementStyles, doc_data: &PerDocumentStyleData) +fn get_pseudo_style(guard: &SharedRwLockReadGuard, + element: GeckoElement, + pseudo_tag: *mut nsIAtom, + styles: &ElementStyles, + doc_data: &PerDocumentStyleData) -> Option> { let pseudo = PseudoElement::from_atom_unchecked(Atom::from(pseudo_tag), false); - match SelectorImpl::pseudo_element_cascade_type(&pseudo) { + match pseudo.cascade_type() { PseudoElementCascadeType::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()), PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"), PseudoElementCascadeType::Lazy => {