Auto merge of #16630 - emilio:nac, r=bholley,hiro

Make stylo traverse Native Anonymous Content, fixing a bunch of restyle bugs.

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16630)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-04-27 07:39:42 -05:00 committed by GitHub
commit c633e291c9
12 changed files with 568 additions and 384 deletions

View file

@ -461,16 +461,16 @@ impl<'le> TElement for ServoLayoutElement<'le> {
self.element.has_selector_flags(flags) self.element.has_selector_flags(flags)
} }
fn has_animations(&self, _pseudo: Option<&PseudoElement>) -> bool { fn has_animations(&self) -> bool {
panic!("this should be only called on gecko"); unreachable!("this should be only called on gecko");
} }
fn has_css_animations(&self, _pseudo: Option<&PseudoElement>) -> bool { fn has_css_animations(&self) -> bool {
panic!("this should be only called on gecko"); unreachable!("this should be only called on gecko");
} }
fn has_css_transitions(&self, _pseudo: Option<&PseudoElement>) -> bool { fn has_css_transitions(&self) -> bool {
panic!("this should be only called on gecko"); unreachable!("this should be only called on gecko");
} }
} }

View file

@ -20,7 +20,6 @@ use font_metrics::FontMetricsProvider;
use matching::StyleSharingCandidateCache; use matching::StyleSharingCandidateCache;
use parking_lot::RwLock; use parking_lot::RwLock;
#[cfg(feature = "gecko")] use properties::ComputedValues; #[cfg(feature = "gecko")] use properties::ComputedValues;
#[cfg(feature = "gecko")] use selector_parser::PseudoElement;
use selectors::matching::ElementSelectorFlags; use selectors::matching::ElementSelectorFlags;
#[cfg(feature = "servo")] use servo_config::opts; #[cfg(feature = "servo")] use servo_config::opts;
use shared_lock::StylesheetGuards; use shared_lock::StylesheetGuards;
@ -270,7 +269,8 @@ impl TraversalStatistics {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
bitflags! { 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 { pub flags UpdateAnimationsTasks: u8 {
/// Update CSS Animations. /// Update CSS Animations.
const CSS_ANIMATIONS = structs::UpdateAnimationsTasks_CSSAnimations, const CSS_ANIMATIONS = structs::UpdateAnimationsTasks_CSSAnimations,
@ -296,10 +296,8 @@ pub enum SequentialTask<E: TElement> {
/// of the non-animation style traversal, and updating the computed effect properties. /// of the non-animation style traversal, and updating the computed effect properties.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
UpdateAnimations { UpdateAnimations {
/// The target element. /// The target element or pseudo-element.
el: SendElement<E>, el: SendElement<E>,
/// The target pseudo element.
pseudo: Option<PseudoElement>,
/// The before-change style for transitions. We use before-change style as the initial /// The before-change style for transitions. We use before-change style as the initial
/// value of its Keyframe. Required if |tasks| includes CSSTransitions. /// value of its Keyframe. Required if |tasks| includes CSSTransitions.
before_change_style: Option<Arc<ComputedValues>>, before_change_style: Option<Arc<ComputedValues>>,
@ -316,8 +314,8 @@ impl<E: TElement> SequentialTask<E> {
match self { match self {
Unused(_) => unreachable!(), Unused(_) => unreachable!(),
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
UpdateAnimations { el, pseudo, before_change_style, tasks } => { UpdateAnimations { el, before_change_style, tasks } => {
unsafe { el.update_animations(pseudo.as_ref(), before_change_style, tasks) }; unsafe { el.update_animations(before_change_style, tasks) };
} }
} }
} }
@ -326,14 +324,14 @@ impl<E: TElement> SequentialTask<E> {
/// a given (pseudo-)element. /// a given (pseudo-)element.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub fn update_animations(el: E, pub fn update_animations(el: E,
pseudo: Option<PseudoElement>,
before_change_style: Option<Arc<ComputedValues>>, before_change_style: Option<Arc<ComputedValues>>,
tasks: UpdateAnimationsTasks) -> Self { tasks: UpdateAnimationsTasks) -> Self {
use self::SequentialTask::*; use self::SequentialTask::*;
UpdateAnimations { el: unsafe { SendElement::new(el) }, UpdateAnimations {
pseudo: pseudo, el: unsafe { SendElement::new(el) },
before_change_style: before_change_style, before_change_style: before_change_style,
tasks: tasks } tasks: tasks,
}
} }
} }

View file

@ -532,6 +532,21 @@ impl ElementData {
self.styles = Some(styles); 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. /// Returns true if the Element has a RestyleData.
pub fn has_restyle(&self) -> bool { pub fn has_restyle(&self) -> bool {
self.restyle.is_some() self.restyle.is_some()

View file

@ -274,11 +274,20 @@ pub trait PresentationalHintsSynthetizer {
where V: Push<ApplicableDeclarationBlock>; where V: Push<ApplicableDeclarationBlock>;
} }
/// 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. /// Transition cascade level.
pub struct AnimationRules(pub Option<Arc<Locked<PropertyDeclarationBlock>>>, pub struct AnimationRules(pub Option<Arc<Locked<PropertyDeclarationBlock>>>,
pub Option<Arc<Locked<PropertyDeclarationBlock>>>); pub Option<Arc<Locked<PropertyDeclarationBlock>>>);
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. /// The element trait, the main abstraction the style crate acts over.
pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
ElementExt + PresentationalHintsSynthetizer { ElementExt + PresentationalHintsSynthetizer {
@ -325,26 +334,25 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
} }
/// Get this element's animation rules. /// Get this element's animation rules.
fn get_animation_rules(&self, _pseudo: Option<&PseudoElement>) -> AnimationRules { fn get_animation_rules(&self) -> AnimationRules {
AnimationRules(None, None) AnimationRules(None, None)
} }
/// Get this element's animation rule by the cascade level. /// Get this element's animation rule by the cascade level.
fn get_animation_rule_by_cascade(&self, fn get_animation_rule_by_cascade(&self,
_pseudo: Option<&PseudoElement>,
_cascade_level: CascadeLevel) _cascade_level: CascadeLevel)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> { -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
None None
} }
/// Get this element's animation rule. /// Get this element's animation rule.
fn get_animation_rule(&self, _pseudo: Option<&PseudoElement>) fn get_animation_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> { -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
None None
} }
/// Get this element's transition rule. /// Get this element's transition rule.
fn get_transition_rule(&self, _pseudo: Option<&PseudoElement>) fn get_transition_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> { -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
None None
} }
@ -428,6 +436,18 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
/// anonymous content). /// anonymous content).
fn is_native_anonymous(&self) -> bool { false } 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<PseudoElement> { None }
/// Atomically stores the number of children of this node that we will /// Atomically stores the number of children of this node that we will
/// need to process during bottom-up traversal. /// need to process during bottom-up traversal.
fn store_children_to_process(&self, n: isize); 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. /// Creates a task to update various animation state on a given (pseudo-)element.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn update_animations(&self, _pseudo: Option<&PseudoElement>, fn update_animations(&self,
before_change_style: Option<Arc<ComputedValues>>, before_change_style: Option<Arc<ComputedValues>>,
tasks: UpdateAnimationsTasks); tasks: UpdateAnimationsTasks);
/// Returns true if the element has relevant animations. Relevant /// Returns true if the element has relevant animations. Relevant
/// animations are those animations that are affecting the element's style /// animations are those animations that are affecting the element's style
/// or are scheduled to do so in the future. /// 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. /// 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 /// Returns true if the element has a CSS transition (including running transitions and
/// completed transitions). /// 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. /// Returns true if the element has animation restyle hints.
fn has_animation_restyle_hints(&self) -> bool { 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. /// Gets the current existing CSS transitions, by |property, end value| pairs in a HashMap.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn get_css_transitions_info(&self, fn get_css_transitions_info(&self)
pseudo: Option<&PseudoElement>)
-> HashMap<TransitionProperty, Arc<AnimationValue>>; -> HashMap<TransitionProperty, Arc<AnimationValue>>;
/// Does a rough (and cheap) check for whether or not transitions might need to be updated that /// 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. /// reduce the possibility of false positives.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn might_need_transitions_update(&self, fn might_need_transitions_update(&self,
old_values: &Option<&Arc<ComputedValues>>, old_values: Option<&ComputedValues>,
new_values: &Arc<ComputedValues>, new_values: &ComputedValues)
pseudo: Option<&PseudoElement>) -> bool; -> bool;
/// Returns true if one of the transitions needs to be updated on this element. We check all /// 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. /// 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. /// passed the same parameters.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn needs_transitions_update(&self, fn needs_transitions_update(&self,
before_change_style: &Arc<ComputedValues>, before_change_style: &ComputedValues,
after_change_style: &Arc<ComputedValues>, after_change_style: &ComputedValues)
pseudo: Option<&PseudoElement>) -> bool; -> bool;
/// Returns true if we need to update transitions for the specified property on this element. /// Returns true if we need to update transitions for the specified property on this element.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn needs_transitions_update_per_property(&self, fn needs_transitions_update_per_property(&self,
property: &TransitionProperty, property: &TransitionProperty,
combined_duration: f32, combined_duration: f32,
before_change_style: &Arc<ComputedValues>, before_change_style: &ComputedValues,
after_change_style: &Arc<ComputedValues>, after_change_style: &ComputedValues,
existing_transitions: &HashMap<TransitionProperty, existing_transitions: &HashMap<TransitionProperty,
Arc<AnimationValue>>) Arc<AnimationValue>>)
-> bool; -> bool;

View file

@ -54,6 +54,26 @@ pub const EAGER_PSEUDO_COUNT: usize = 2;
impl PseudoElement { 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. /// Gets the canonical index of this eagerly-cascaded pseudo-element.
#[inline] #[inline]
pub fn eager_index(&self) -> usize { pub fn eager_index(&self) -> usize {
@ -437,24 +457,9 @@ impl<'a> ::selectors::Parser for SelectorParser<'a> {
impl SelectorImpl { impl SelectorImpl {
#[inline] #[inline]
/// Returns the kind of cascade type that a given pseudo is going to use. /// Legacy alias for PseudoElement::cascade_type.
///
/// 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 pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType { pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
if pseudo.is_eager() { pseudo.cascade_type()
debug_assert!(!pseudo.is_anon_box());
return PseudoElementCascadeType::Eager
}
if pseudo.is_anon_box() {
return PseudoElementCascadeType::Precomputed
}
PseudoElementCascadeType::Lazy
} }
/// A helper to traverse each eagerly cascaded pseudo-element, executing /// A helper to traverse each eagerly cascaded pseudo-element, executing

View file

@ -421,12 +421,15 @@ fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
} }
fn get_animation_rule(element: &GeckoElement, fn get_animation_rule(element: &GeckoElement,
pseudo: Option<&PseudoElement>,
cascade_level: CascadeLevel) cascade_level: CascadeLevel)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> { -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
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())); 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)) } { HasArcFFI::arc_as_borrowed(&animation_values)) } {
let shared_lock = &GLOBAL_STYLE_DATA.shared_lock; let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
Some(Arc::new(shared_lock.wrap( 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) declarations.map(|s| s.as_arc_opt()).unwrap_or(None)
} }
fn get_animation_rules(&self, pseudo: Option<&PseudoElement>) -> AnimationRules { fn get_animation_rules(&self) -> AnimationRules {
AnimationRules(self.get_animation_rule(pseudo), AnimationRules(self.get_animation_rule(),
self.get_transition_rule(pseudo)) self.get_transition_rule())
} }
fn get_animation_rule_by_cascade(&self, fn get_animation_rule_by_cascade(&self, cascade_level: ServoCascadeLevel)
pseudo: Option<&PseudoElement>,
cascade_level: ServoCascadeLevel)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> { -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
match cascade_level { match cascade_level {
ServoCascadeLevel::Animations => self.get_animation_rule(pseudo), ServoCascadeLevel::Animations => self.get_animation_rule(),
ServoCascadeLevel::Transitions => self.get_transition_rule(pseudo), ServoCascadeLevel::Transitions => self.get_transition_rule(),
_ => panic!("Unsupported cascade level for getting the animation rule") _ => panic!("Unsupported cascade level for getting the animation rule")
} }
} }
fn get_animation_rule(&self, pseudo: Option<&PseudoElement>) fn get_animation_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> { -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
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<Arc<Locked<PropertyDeclarationBlock>>> { -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
get_animation_rule(self, pseudo, CascadeLevel::Transitions) get_animation_rule(self, CascadeLevel::Transitions)
} }
fn get_state(&self) -> ElementState { fn get_state(&self) -> ElementState {
@ -589,7 +590,7 @@ impl<'le> TElement for GeckoElement<'le> {
-> Option<&'a nsStyleContext> { -> Option<&'a nsStyleContext> {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo);
unsafe { 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() context_ptr.as_ref()
} }
} }
@ -641,6 +642,22 @@ impl<'le> TElement for GeckoElement<'le> {
self.flags() & (NODE_IS_NATIVE_ANONYMOUS as u32) != 0 self.flags() & (NODE_IS_NATIVE_ANONYMOUS as u32) != 0
} }
fn implemented_pseudo_element(&self) -> Option<PseudoElement> {
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) { fn store_children_to_process(&self, _: isize) {
// This is only used for bottom-up traversal, and is thus a no-op for Gecko. // 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, fn update_animations(&self,
pseudo: Option<&PseudoElement>,
before_change_style: Option<Arc<ComputedValues>>, before_change_style: Option<Arc<ComputedValues>>,
tasks: UpdateAnimationsTasks) { tasks: UpdateAnimationsTasks) {
// We have to update animations even if the element has no computed style // We have to update animations even if the element has no computed
// since it means the element is in a display:none subtree, we should destroy // style since it means the element is in a display:none subtree, we
// all CSS animations in display:none subtree. // should destroy all CSS animations in display:none subtree.
let computed_data = self.borrow_data(); let computed_data = self.borrow_data();
let computed_values = let computed_values =
computed_data.as_ref().map(|d| computed_data.as_ref().map(|d| d.styles().primary.values());
pseudo.map_or_else(|| d.styles().primary.values(), let computed_values_opt =
|p| d.styles().pseudos.get(p).unwrap().values()) computed_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
); let parent_element = self.parent_element();
let computed_values_opt = computed_values.map(|v| let parent_data =
*HasArcFFI::arc_as_borrowed(v) 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_element = if pseudo.is_none() { let parent_values_opt =
self.parent_element() parent_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
} else { let before_change_values =
Some(*self) before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v));
};
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));
unsafe { unsafe {
Gecko_UpdateAnimations(self.0, atom_ptr, Gecko_UpdateAnimations(self.0,
before_change_values, before_change_values,
computed_values_opt, computed_values_opt,
parent_values_opt, parent_values_opt,
@ -711,35 +718,31 @@ impl<'le> TElement for GeckoElement<'le> {
} }
} }
fn has_animations(&self, pseudo: Option<&PseudoElement>) -> bool { fn has_animations(&self) -> bool {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); unsafe { Gecko_ElementHasAnimations(self.0) }
unsafe { Gecko_ElementHasAnimations(self.0, atom_ptr) }
} }
fn has_css_animations(&self, pseudo: Option<&PseudoElement>) -> bool { fn has_css_animations(&self) -> bool {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); unsafe { Gecko_ElementHasCSSAnimations(self.0) }
unsafe { Gecko_ElementHasCSSAnimations(self.0, atom_ptr) }
} }
fn has_css_transitions(&self, pseudo: Option<&PseudoElement>) -> bool { fn has_css_transitions(&self) -> bool {
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); unsafe { Gecko_ElementHasCSSTransitions(self.0) }
unsafe { Gecko_ElementHasCSSTransitions(self.0, atom_ptr) }
} }
fn get_css_transitions_info(&self, fn get_css_transitions_info(&self)
pseudo: Option<&PseudoElement>)
-> HashMap<TransitionProperty, Arc<AnimationValue>> { -> HashMap<TransitionProperty, Arc<AnimationValue>> {
use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt; use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt;
use gecko_bindings::bindings::Gecko_ElementTransitions_Length; use gecko_bindings::bindings::Gecko_ElementTransitions_Length;
use gecko_bindings::bindings::Gecko_ElementTransitions_PropertyAt; use gecko_bindings::bindings::Gecko_ElementTransitions_PropertyAt;
let atom_ptr = PseudoElement::ns_atom_or_null_from_opt(pseudo); let collection_length =
let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0, atom_ptr) }; unsafe { Gecko_ElementTransitions_Length(self.0) };
let mut map = HashMap::with_capacity(collection_length); let mut map = HashMap::with_capacity(collection_length);
for i in 0..collection_length { for i in 0..collection_length {
let (property, raw_end_value) = unsafe { let (property, raw_end_value) = unsafe {
(Gecko_ElementTransitions_PropertyAt(self.0, atom_ptr, i as usize).into(), (Gecko_ElementTransitions_PropertyAt(self.0, i as usize).into(),
Gecko_ElementTransitions_EndValueAt(self.0, atom_ptr, i as usize)) Gecko_ElementTransitions_EndValueAt(self.0, i as usize))
}; };
let end_value = AnimationValue::arc_from_borrowed(&raw_end_value); let end_value = AnimationValue::arc_from_borrowed(&raw_end_value);
debug_assert!(end_value.is_some()); debug_assert!(end_value.is_some());
@ -749,21 +752,21 @@ impl<'le> TElement for GeckoElement<'le> {
} }
fn might_need_transitions_update(&self, fn might_need_transitions_update(&self,
old_values: &Option<&Arc<ComputedValues>>, old_values: Option<&ComputedValues>,
new_values: &Arc<ComputedValues>, new_values: &ComputedValues) -> bool {
pseudo: Option<&PseudoElement>) -> bool {
use properties::longhands::display::computed_value as display; use properties::longhands::display::computed_value as display;
if old_values.is_none() { let old_values = match old_values {
return false; Some(v) => v,
} None => return false,
};
let ref new_box_style = new_values.get_box(); let new_box_style = new_values.get_box();
let transition_not_running = !self.has_css_transitions(pseudo) && let transition_not_running = !self.has_css_transitions() &&
new_box_style.transition_property_count() == 1 && new_box_style.transition_property_count() == 1 &&
new_box_style.transition_combined_duration_at(0) <= 0.0f32; new_box_style.transition_combined_duration_at(0) <= 0.0f32;
let new_display_style = new_box_style.clone_display(); 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 && new_box_style.transition_property_count() > 0 &&
!transition_not_running && !transition_not_running &&
@ -771,28 +774,31 @@ impl<'le> TElement for GeckoElement<'le> {
old_display_style != display::T::none) old_display_style != display::T::none)
} }
// Detect if there are any changes that require us to update transitions. This is used as a // Detect if there are any changes that require us to update transitions.
// more thoroughgoing check than the, cheaper might_need_transitions_update check. // 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 // 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 // https://drafts.csswg.org/css-transitions/#starting
fn needs_transitions_update(&self, fn needs_transitions_update(&self,
before_change_style: &Arc<ComputedValues>, before_change_style: &ComputedValues,
after_change_style: &Arc<ComputedValues>, after_change_style: &ComputedValues)
pseudo: Option<&PseudoElement>) -> bool { -> bool {
use gecko_bindings::structs::nsCSSPropertyID; use gecko_bindings::structs::nsCSSPropertyID;
use properties::{PropertyId, animated_properties}; use properties::{PropertyId, animated_properties};
use std::collections::HashSet; use std::collections::HashSet;
debug_assert!(self.might_need_transitions_update(&Some(before_change_style), debug_assert!(self.might_need_transitions_update(Some(before_change_style),
after_change_style, after_change_style),
pseudo),
"We should only call needs_transitions_update if \ "We should only call needs_transitions_update if \
might_need_transitions_update returns true"); 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 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() && let mut transitions_to_keep = if !existing_transitions.is_empty() &&
(after_change_box_style.transition_nscsspropertyid_at(0) != (after_change_box_style.transition_nscsspropertyid_at(0) !=
nsCSSPropertyID::eCSSPropertyExtra_all_properties) { nsCSSPropertyID::eCSSPropertyExtra_all_properties) {
@ -865,8 +871,8 @@ impl<'le> TElement for GeckoElement<'le> {
fn needs_transitions_update_per_property(&self, fn needs_transitions_update_per_property(&self,
property: &TransitionProperty, property: &TransitionProperty,
combined_duration: f32, combined_duration: f32,
before_change_style: &Arc<ComputedValues>, before_change_style: &ComputedValues,
after_change_style: &Arc<ComputedValues>, after_change_style: &ComputedValues,
existing_transitions: &HashMap<TransitionProperty, existing_transitions: &HashMap<TransitionProperty,
Arc<AnimationValue>>) Arc<AnimationValue>>)
-> bool { -> bool {

View file

@ -615,8 +615,7 @@ extern "C" {
-> RawServoDeclarationBlockStrongBorrowedOrNull; -> RawServoDeclarationBlockStrongBorrowedOrNull;
} }
extern "C" { extern "C" {
pub fn Gecko_GetAnimationRule(aElement: RawGeckoElementBorrowed, pub fn Gecko_GetAnimationRule(aElementOrPseudo: RawGeckoElementBorrowed,
aPseudoTag: *mut nsIAtom,
aCascadeLevel: aCascadeLevel:
EffectCompositor_CascadeLevel, EffectCompositor_CascadeLevel,
aAnimationValues: aAnimationValues:
@ -636,46 +635,42 @@ extern "C" {
-> bool; -> bool;
} }
extern "C" { extern "C" {
pub fn Gecko_UpdateAnimations(aElement: RawGeckoElementBorrowed, pub fn Gecko_UpdateAnimations(aElementOrPseudo: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom,
aOldComputedValues: aOldComputedValues:
ServoComputedValuesBorrowedOrNull, ServoComputedValuesBorrowedOrNull,
aComputedValues: aComputedValues:
ServoComputedValuesBorrowedOrNull, ServoComputedValuesBorrowedOrNull,
aParentComputedValues: aParentComputedValues:
ServoComputedValuesBorrowedOrNull, ServoComputedValuesBorrowedOrNull,
aTaskBits: UpdateAnimationsTasks); aTasks: UpdateAnimationsTasks);
} }
extern "C" { extern "C" {
pub fn Gecko_ElementHasAnimations(aElement: RawGeckoElementBorrowed, pub fn Gecko_ElementHasAnimations(aElementOrPseudo:
aPseudoTagOrNull: *mut nsIAtom) -> bool; RawGeckoElementBorrowed) -> bool;
} }
extern "C" { extern "C" {
pub fn Gecko_ElementHasCSSAnimations(aElement: RawGeckoElementBorrowed, pub fn Gecko_ElementHasCSSAnimations(aElementOrPseudo:
aPseudoTagOrNull: *mut nsIAtom) RawGeckoElementBorrowed) -> bool;
}
extern "C" {
pub fn Gecko_ElementHasCSSTransitions(aElementOrPseudo:
RawGeckoElementBorrowed)
-> bool; -> bool;
} }
extern "C" { extern "C" {
pub fn Gecko_ElementHasCSSTransitions(aElement: RawGeckoElementBorrowed, pub fn Gecko_ElementTransitions_Length(aElementOrPseudo:
aPseudoTagOrNull: *mut nsIAtom) RawGeckoElementBorrowed)
-> bool;
}
extern "C" {
pub fn Gecko_ElementTransitions_Length(aElement: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom)
-> usize; -> usize;
} }
extern "C" { extern "C" {
pub fn Gecko_ElementTransitions_PropertyAt(aElement: pub fn Gecko_ElementTransitions_PropertyAt(aElementOrPseudo:
RawGeckoElementBorrowed, RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom,
aIndex: usize) aIndex: usize)
-> nsCSSPropertyID; -> nsCSSPropertyID;
} }
extern "C" { extern "C" {
pub fn Gecko_ElementTransitions_EndValueAt(aElement: pub fn Gecko_ElementTransitions_EndValueAt(aElementOrPseudo:
RawGeckoElementBorrowed, RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom,
aIndex: usize) aIndex: usize)
-> RawServoAnimationValueBorrowedOrNull; -> RawServoAnimationValueBorrowedOrNull;
} }
@ -860,10 +855,14 @@ extern "C" {
RawGeckoElementBorrowed); RawGeckoElementBorrowed);
} }
extern "C" { extern "C" {
pub fn Gecko_GetStyleContext(node: RawGeckoNodeBorrowed, pub fn Gecko_GetStyleContext(element: RawGeckoElementBorrowed,
aPseudoTagOrNull: *mut nsIAtom) aPseudoTagOrNull: *mut nsIAtom)
-> *mut nsStyleContext; -> *mut nsStyleContext;
} }
extern "C" {
pub fn Gecko_GetImplementedPseudo(element: RawGeckoElementBorrowed)
-> *mut nsIAtom;
}
extern "C" { extern "C" {
pub fn Gecko_CalcStyleDifference(oldstyle: *mut nsStyleContext, pub fn Gecko_CalcStyleDifference(oldstyle: *mut nsStyleContext,
newstyle: ServoComputedValuesBorrowed) newstyle: ServoComputedValuesBorrowed)

View file

@ -30,6 +30,15 @@ use sink::ForgetfulSink;
use std::sync::Arc; use std::sync::Arc;
use stylist::ApplicableDeclarationBlock; 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. /// Determines the amount of relations where we're going to share style.
#[inline] #[inline]
fn relations_are_shareable(relations: &StyleRelations) -> bool { fn relations_are_shareable(relations: &StyleRelations) -> bool {
@ -78,6 +87,8 @@ pub struct StyleSharingCandidateCache<E: TElement> {
pub enum CacheMiss { pub enum CacheMiss {
/// The parents don't match. /// The parents don't match.
Parent, Parent,
/// One element was NAC, while the other wasn't.
NativeAnonymousContent,
/// The local name of the element and the candidate don't match. /// The local name of the element and the candidate don't match.
LocalName, LocalName,
/// The namespace of the element and the candidate don't match. /// The namespace of the element and the candidate don't match.
@ -137,6 +148,12 @@ fn element_matches_candidate<E: TElement>(element: &E,
miss!(Parent) 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() { if *element.get_local_name() != *candidate_element.get_local_name() {
miss!(LocalName) miss!(LocalName)
} }
@ -298,6 +315,11 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
} }
}; };
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 // These are things we don't check in the candidate match because they
// are either uncommon or expensive. // are either uncommon or expensive.
if !relations_are_shareable(&relations) { if !relations_are_shareable(&relations) {
@ -312,7 +334,6 @@ impl<E: TElement> StyleSharingCandidateCache<E> {
debug_assert!(hints.is_empty(), "Style relations should not be shareable!"); debug_assert!(hints.is_empty(), "Style relations should not be shareable!");
} }
let box_style = style.get_box(); let box_style = style.get_box();
if box_style.specifies_transitions() { if box_style.specifies_transitions() {
debug!("Failing to insert to the cache: transitions"); debug!("Failing to insert to the cache: transitions");
@ -387,7 +408,7 @@ trait PrivateMatchMethods: TElement {
font_metrics_provider: &FontMetricsProvider, font_metrics_provider: &FontMetricsProvider,
rule_node: &StrongRuleNode, rule_node: &StrongRuleNode,
primary_style: &ComputedStyle, primary_style: &ComputedStyle,
is_pseudo: bool) inherit_mode: InheritMode)
-> Arc<ComputedValues> { -> Arc<ComputedValues> {
let mut cascade_info = CascadeInfo::new(); let mut cascade_info = CascadeInfo::new();
let mut cascade_flags = CascadeFlags::empty(); let mut cascade_flags = CascadeFlags::empty();
@ -398,24 +419,27 @@ trait PrivateMatchMethods: TElement {
// Grab the inherited values. // Grab the inherited values.
let parent_el; let parent_el;
let parent_data; let parent_data;
let style_to_inherit_from = if !is_pseudo { let style_to_inherit_from = match inherit_mode {
parent_el = self.parent_element(); InheritMode::FromParentElement => {
parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); parent_el = self.parent_element();
let parent_values = parent_data.as_ref().map(|d| { parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
// Sometimes Gecko eagerly styles things without processing let parent_values = parent_data.as_ref().map(|d| {
// pending restyles first. In general we'd like to avoid this, // Sometimes Gecko eagerly styles things without processing
// but there can be good reasons (for example, needing to // pending restyles first. In general we'd like to avoid this,
// construct a frame for some small piece of newly-added // but there can be good reasons (for example, needing to
// content in order to do something specific with that frame, // construct a frame for some small piece of newly-added
// but not wanting to flush all of layout). // content in order to do something specific with that frame,
debug_assert!(cfg!(feature = "gecko") || d.has_current_styles()); // but not wanting to flush all of layout).
d.styles().primary.values() debug_assert!(cfg!(feature = "gecko") || d.has_current_styles());
}); d.styles().primary.values()
});
parent_values parent_values
} else { }
parent_el = Some(self.clone()); InheritMode::FromPrimaryStyle => {
Some(primary_style.values()) parent_el = Some(self.clone());
Some(primary_style.values())
}
}; };
let mut layout_parent_el = parent_el.clone(); 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 // Propagate the "can be fragmented" bit. It would be nice to
// encapsulate this better. // encapsulate this better.
// //
// Note that this is not needed for pseudos since we already do that // Note that this is technically not needed for pseudos since we already
// when we resolve the non-pseudo style. // do that when we resolve the non-pseudo style, but it doesn't hurt
if !is_pseudo { // anyway.
if let Some(ref p) = layout_parent_style { //
let can_be_fragmented = // TODO(emilio): This is servo-only, move somewhere else?
p.is_multicol() || if let Some(ref p) = layout_parent_style {
layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented(); let can_be_fragmented =
unsafe { self.as_node().set_can_be_fragmented(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. // Invoke the cascade algorithm.
@ -463,16 +488,21 @@ trait PrivateMatchMethods: TElement {
fn cascade_internal(&self, fn cascade_internal(&self,
context: &StyleContext<Self>, context: &StyleContext<Self>,
primary_style: &ComputedStyle, primary_style: &ComputedStyle,
pseudo_style: Option<&ComputedStyle>) eager_pseudo_style: Option<&ComputedStyle>)
-> Arc<ComputedValues> { -> Arc<ComputedValues> {
// Grab the rule node. // 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, self.cascade_with_rules(context.shared,
&context.thread_local.font_metrics_provider, &context.thread_local.font_metrics_provider,
rule_node, rule_node,
primary_style, primary_style,
pseudo_style.is_some()) inherit_mode)
} }
/// Computes values and damage for the primary or pseudo style of an element, /// 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, fn cascade_primary_or_pseudo(&self,
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
data: &mut ElementData, data: &mut ElementData,
pseudo: Option<&PseudoElement>, pseudo: Option<&PseudoElement>) {
animate: bool) { debug_assert!(pseudo.is_none() || self.implemented_pseudo_element().is_none(),
"Pseudo-element-implementing elements can't have pseudos!");
// Collect some values. // Collect some values.
let (mut styles, restyle) = data.styles_and_restyle_mut(); let (mut styles, restyle) = data.styles_and_restyle_mut();
let mut primary_style = &mut styles.primary; let mut primary_style = &mut styles.primary;
@ -501,27 +532,65 @@ trait PrivateMatchMethods: TElement {
}; };
// Compute the new values. // Compute the new values.
let mut new_values = self.cascade_internal(context, let mut new_values = match self.implemented_pseudo_element() {
primary_style, Some(ref pseudo) => {
pseudo_style.as_ref().map(|s| &**s)); // 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. let parent_data = parent.borrow_data().unwrap();
if animate && !context.shared.traversal_flags.for_animation_only() { 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, self.process_animations(context,
&mut old_values, &mut old_values,
&mut new_values, &mut new_values,
primary_style, primary_style);
pseudo,
pseudo_style.as_ref().map(|s| &**s));
} }
// Accumulate restyle damage. // Accumulate restyle damage.
if let Some(old) = old_values { if let Some(old) = old_values {
self.accumulate_damage(&context.shared, // ::before and ::after are element-backed in Gecko, so they do
restyle.unwrap(), // the damage calculation for themselves.
&old, //
&new_values, // FIXME(emilio): We have more element-backed stuff, and this is
pseudo); // 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. // Set the new computed values.
@ -534,11 +603,9 @@ trait PrivateMatchMethods: TElement {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn get_after_change_style(&self, fn get_after_change_style(&self,
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
primary_style: &ComputedStyle, primary_style: &ComputedStyle)
pseudo_style: Option<&ComputedStyle>)
-> Option<Arc<ComputedValues>> { -> Option<Arc<ComputedValues>> {
let relevant_style = pseudo_style.unwrap_or(primary_style); let rule_node = &primary_style.rules;
let rule_node = &relevant_style.rules;
let without_transition_rules = let without_transition_rules =
context.shared.stylist.rule_tree.remove_transition_rule_if_applicable(rule_node); context.shared.stylist.rule_tree.remove_transition_rule_if_applicable(rule_node);
if without_transition_rules == *rule_node { if without_transition_rules == *rule_node {
@ -551,21 +618,21 @@ trait PrivateMatchMethods: TElement {
&context.thread_local.font_metrics_provider, &context.thread_local.font_metrics_provider,
&without_transition_rules, &without_transition_rules,
primary_style, primary_style,
pseudo_style.is_some())) InheritMode::FromParentElement))
} }
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn needs_animations_update(&self, fn needs_animations_update(&self,
old_values: &Option<Arc<ComputedValues>>, old_values: Option<&Arc<ComputedValues>>,
new_values: &Arc<ComputedValues>, new_values: &ComputedValues)
pseudo: Option<&PseudoElement>) -> bool { -> bool {
let ref new_box_style = new_values.get_box(); let new_box_style = new_values.get_box();
let has_new_animation_style = new_box_style.animation_name_count() >= 1 && let has_new_animation_style = new_box_style.animation_name_count() >= 1 &&
new_box_style.animation_name_at(0).0.is_some(); 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| { old_values.map_or(has_new_animation_style, |old| {
let ref old_box_style = old.get_box(); let old_box_style = old.get_box();
let old_display_style = old_box_style.clone_display(); let old_display_style = old_box_style.clone_display();
let new_display_style = new_box_style.clone_display(); let new_display_style = new_box_style.clone_display();
// FIXME: Bug 1344581: We still need to compare keyframe rules. // FIXME: Bug 1344581: We still need to compare keyframe rules.
@ -584,40 +651,34 @@ trait PrivateMatchMethods: TElement {
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>, old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>, new_values: &mut Arc<ComputedValues>,
primary_style: &ComputedStyle, primary_style: &ComputedStyle) {
pseudo: Option<&PseudoElement>,
pseudo_style: Option<&ComputedStyle>) {
use context::{CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES}; use context::{CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES};
use context::UpdateAnimationsTasks; use context::UpdateAnimationsTasks;
debug_assert_eq!(pseudo.is_some(), pseudo_style.is_some());
let mut tasks = UpdateAnimationsTasks::empty(); 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); tasks.insert(CSS_ANIMATIONS);
} }
let before_change_style = if self.might_need_transitions_update(&old_values.as_ref(), let before_change_style = if self.might_need_transitions_update(old_values.as_ref().map(|s| &**s),
new_values, new_values) {
pseudo) { let after_change_style = if self.has_css_transitions() {
let after_change_style = if self.has_css_transitions(pseudo) { self.get_after_change_style(context, primary_style)
self.get_after_change_style(context, primary_style, pseudo_style)
} else { } else {
None None
}; };
// In order to avoid creating a SequentialTask for transitions which may not be updated, // In order to avoid creating a SequentialTask for transitions which
// we check it per property to make sure Gecko side will really update transition. // may not be updated, we check it per property to make sure Gecko
// side will really update transition.
let needs_transitions_update = { let needs_transitions_update = {
// We borrow new_values here, so need to add a scope to make sure we release it // We borrow new_values here, so need to add a scope to make
// before assigning a new value to it. // sure we release it before assigning a new value to it.
let after_change_style_ref = match after_change_style { let after_change_style_ref =
Some(ref value) => value, after_change_style.as_ref().unwrap_or(&new_values);
None => &new_values
};
self.needs_transitions_update(old_values.as_ref().unwrap(), self.needs_transitions_update(old_values.as_ref().unwrap(),
after_change_style_ref, after_change_style_ref)
pseudo)
}; };
if needs_transitions_update { if needs_transitions_update {
@ -635,13 +696,12 @@ trait PrivateMatchMethods: TElement {
None None
}; };
if self.has_animations(pseudo) { if self.has_animations() {
tasks.insert(EFFECT_PROPERTIES); tasks.insert(EFFECT_PROPERTIES);
} }
if !tasks.is_empty() { if !tasks.is_empty() {
let task = ::context::SequentialTask::update_animations(*self, let task = ::context::SequentialTask::update_animations(*self,
pseudo.cloned(),
before_change_style, before_change_style,
tasks); tasks);
context.thread_local.tasks.push(task); context.thread_local.tasks.push(task);
@ -653,11 +713,7 @@ trait PrivateMatchMethods: TElement {
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
old_values: &mut Option<Arc<ComputedValues>>, old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>, new_values: &mut Arc<ComputedValues>,
_primary_style: &ComputedStyle, _primary_style: &ComputedStyle) {
pseudo: Option<&PseudoElement>,
pseudo_style: Option<&ComputedStyle>) {
debug_assert_eq!(pseudo.is_some(), pseudo_style.is_some());
let possibly_expired_animations = let possibly_expired_animations =
&mut context.thread_local.current_element_info.as_mut().unwrap() &mut context.thread_local.current_element_info.as_mut().unwrap()
.possibly_expired_animations; .possibly_expired_animations;
@ -690,6 +746,10 @@ trait PrivateMatchMethods: TElement {
} }
/// Computes and applies non-redundant damage. /// 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")] #[cfg(feature = "gecko")]
fn accumulate_damage(&self, fn accumulate_damage(&self,
shared_context: &SharedStyleContext, shared_context: &SharedStyleContext,
@ -716,7 +776,9 @@ trait PrivateMatchMethods: TElement {
// for followup work to make the optimization here more optimal by considering // for followup work to make the optimization here more optimal by considering
// each bit individually. // each bit individually.
if !restyle.damage.contains(RestyleDamage::reconstruct()) { 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) { if !restyle.damage_handled.contains(new_damage) {
restyle.damage |= new_damage; restyle.damage |= new_damage;
} }
@ -813,7 +875,8 @@ pub enum StyleSharingBehavior {
/// The public API that elements expose for selector matching. /// The public API that elements expose for selector matching.
pub trait MatchMethods : TElement { 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, fn match_and_cascade(&self,
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
data: &mut ElementData, data: &mut ElementData,
@ -878,14 +941,54 @@ pub trait MatchMethods : TElement {
relations: &mut StyleRelations) relations: &mut StyleRelations)
-> bool -> 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 = let mut applicable_declarations =
Vec::<ApplicableDeclarationBlock>::with_capacity(16); Vec::<ApplicableDeclarationBlock>::with_capacity(16);
let stylist = &context.shared.stylist; let stylist = &context.shared.stylist;
let style_attribute = self.style_attribute(); let style_attribute = self.style_attribute();
let smil_override = self.get_smil_override(); let smil_override = self.get_smil_override();
let animation_rules = self.get_animation_rules(None); let animation_rules = self.get_animation_rules();
let mut rule_nodes_changed = false;
let bloom = context.thread_local.bloom_filter.filter(); let bloom = context.thread_local.bloom_filter.filter();
let map = &mut context.thread_local.selector_flags; let map = &mut context.thread_local.selector_flags;
@ -899,22 +1002,15 @@ pub trait MatchMethods : TElement {
style_attribute, style_attribute,
smil_override, smil_override,
animation_rules, animation_rules,
None, implemented_pseudo.as_ref(),
&context.shared.guards, &context.shared.guards,
&mut applicable_declarations, &mut applicable_declarations,
&mut set_selector_flags); &mut set_selector_flags);
let primary_rule_node = let primary_rule_node =
compute_rule_node::<Self>(&stylist.rule_tree, &mut applicable_declarations); compute_rule_node::<Self>(&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 /// Runs selector matching to (re)compute eager pseudo-element rule nodes for this
@ -927,9 +1023,13 @@ pub trait MatchMethods : TElement {
data: &mut ElementData) data: &mut ElementData)
-> bool -> bool
{ {
if self.implemented_pseudo_element().is_some() {
// Element pseudos can't have any other pseudo.
return false;
}
let mut applicable_declarations = let mut applicable_declarations =
Vec::<ApplicableDeclarationBlock>::with_capacity(16); Vec::<ApplicableDeclarationBlock>::with_capacity(16);
let mut rule_nodes_changed = false;
let map = &mut context.thread_local.selector_flags; let map = &mut context.thread_local.selector_flags;
let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| { 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. // Compute rule nodes for eagerly-cascaded pseudo-elements.
let mut matches_different_pseudos = false; let mut matches_different_pseudos = false;
let mut rule_nodes_changed = false;
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
let mut pseudos = &mut data.styles_mut().pseudos; let mut pseudos = &mut data.styles_mut().pseudos;
debug_assert!(applicable_declarations.is_empty()); debug_assert!(applicable_declarations.is_empty());
let pseudo_animation_rules = if pseudo.is_before_or_after() { // NB: We handle animation rules for ::before and ::after when
self.get_animation_rules(Some(&pseudo)) // traversing them.
} else {
AnimationRules(None, None)
};
stylist.push_applicable_declarations(self, stylist.push_applicable_declarations(self,
Some(bloom_filter), Some(bloom_filter),
None, None,
None, None,
pseudo_animation_rules, AnimationRules(None, None),
Some(&pseudo), Some(&pseudo),
&guards, &guards,
&mut applicable_declarations, &mut applicable_declarations,
@ -1073,9 +1171,11 @@ pub trait MatchMethods : TElement {
} }
}; };
// Animation restyle hints are processed prior to other restyle hints in the // Animation restyle hints are processed prior to other restyle
// animation-only traversal. Non-animation restyle hints will be processed in // hints in the animation-only traversal.
// a subsequent normal traversal. //
// Non-animation restyle hints will be processed in a subsequent
// normal traversal.
if hint.intersects(RestyleHint::for_animations()) { if hint.intersects(RestyleHint::for_animations()) {
debug_assert!(context.shared.traversal_flags.for_animation_only()); debug_assert!(context.shared.traversal_flags.for_animation_only());
@ -1085,37 +1185,24 @@ pub trait MatchMethods : TElement {
primary_rules); primary_rules);
} }
use data::EagerPseudoStyles;
let mut replace_rule_node_for_animation = |level: CascadeLevel, let mut replace_rule_node_for_animation = |level: CascadeLevel,
primary_rules: &mut StrongRuleNode, primary_rules: &mut StrongRuleNode| {
pseudos: &mut EagerPseudoStyles| { let animation_rule = self.get_animation_rule_by_cascade(level);
let animation_rule = self.get_animation_rule_by_cascade(None, level);
replace_rule_node(level, replace_rule_node(level,
animation_rule.as_ref(), animation_rule.as_ref(),
primary_rules); 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 // Apply Transition rules and Animation rules if the corresponding restyle hint
// is contained. // is contained.
let pseudos = &mut element_styles.pseudos;
if hint.contains(RESTYLE_CSS_TRANSITIONS) { if hint.contains(RESTYLE_CSS_TRANSITIONS) {
replace_rule_node_for_animation(CascadeLevel::Transitions, replace_rule_node_for_animation(CascadeLevel::Transitions,
primary_rules, primary_rules);
pseudos);
} }
if hint.contains(RESTYLE_CSS_ANIMATIONS) { if hint.contains(RESTYLE_CSS_ANIMATIONS) {
replace_rule_node_for_animation(CascadeLevel::Animations, replace_rule_node_for_animation(CascadeLevel::Animations,
primary_rules, primary_rules);
pseudos);
} }
} else if hint.contains(RESTYLE_STYLE_ATTRIBUTE) { } else if hint.contains(RESTYLE_STYLE_ATTRIBUTE) {
let style_attribute = self.style_attribute(); let style_attribute = self.style_attribute();
@ -1125,11 +1212,9 @@ pub trait MatchMethods : TElement {
replace_rule_node(CascadeLevel::StyleAttributeImportant, replace_rule_node(CascadeLevel::StyleAttributeImportant,
style_attribute, style_attribute,
primary_rules); 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 rule_node_changed
} }
@ -1151,6 +1236,11 @@ pub trait MatchMethods : TElement {
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
} }
if self.is_native_anonymous() {
debug!("{:?} Cannot share style: NAC", self);
return StyleSharingResult::CannotShare;
}
if self.style_attribute().is_some() { if self.style_attribute().is_some() {
debug!("{:?} Cannot share style: element has style attribute", self); debug!("{:?} Cannot share style: element has style attribute", self);
return StyleSharingResult::CannotShare return StyleSharingResult::CannotShare
@ -1310,7 +1400,7 @@ pub trait MatchMethods : TElement {
context: &mut StyleContext<Self>, context: &mut StyleContext<Self>,
mut data: &mut ElementData) 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. /// 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 us pass the mutable |data| to the cascade function.
let matched_pseudos = data.styles().pseudos.keys(); let matched_pseudos = data.styles().pseudos.keys();
for pseudo in matched_pseudos { for pseudo in matched_pseudos {
// Only ::before and ::after are animatable. self.cascade_primary_or_pseudo(context, data, Some(&pseudo));
let animate = pseudo.is_before_or_after();
self.cascade_primary_or_pseudo(context, data, Some(&pseudo), animate);
} }
} }
@ -1352,7 +1440,7 @@ pub trait MatchMethods : TElement {
font_metrics_provider, font_metrics_provider,
&without_animation_rules, &without_animation_rules,
primary_style, primary_style,
pseudo_style.is_some()) InheritMode::FromParentElement)
} }
} }

View file

@ -17,7 +17,7 @@ use selector_parser::{AttrValue, NonTSPseudoClass, Snapshot, SelectorImpl};
use selectors::{Element, MatchAttr}; use selectors::{Element, MatchAttr};
use selectors::matching::{ElementSelectorFlags, StyleRelations}; use selectors::matching::{ElementSelectorFlags, StyleRelations};
use selectors::matching::matches_selector; 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::parser::{SelectorInner, SelectorIter, SelectorMethods};
use selectors::visitor::SelectorVisitor; use selectors::visitor::SelectorVisitor;
use std::clone::Clone; use std::clone::Clone;
@ -122,8 +122,7 @@ impl RestyleHint {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
impl From<nsRestyleHint> for RestyleHint { impl From<nsRestyleHint> for RestyleHint {
fn from(raw: nsRestyleHint) -> Self { fn from(raw: nsRestyleHint) -> Self {
use std::mem; let raw_bits: u32 = raw.0;
let raw_bits: u32 = unsafe { mem::transmute(raw) };
// FIXME(bholley): Finish aligning the binary representations here and // FIXME(bholley): Finish aligning the binary representations here and
// then .expect() the result of the checked version. // then .expect() the result of the checked version.
if Self::from_bits(raw_bits).is_none() { 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 /// Adds a selector to this `DependencySet`, and returns whether it may need
/// cache revalidation, that is, whether two siblings of the same "shape" /// cache revalidation, that is, whether two siblings of the same "shape"
/// may have different style due to this selector. /// may have different style due to this selector.
pub fn note_selector(&mut self, pub fn note_selector(&mut self, selector: &Selector<SelectorImpl>) -> bool {
base: &ComplexSelector<SelectorImpl>) let mut is_pseudo_element = selector.pseudo_element.is_some();
-> bool
{ let mut next = Some(selector.inner.complex.clone());
let mut next = Some(base.clone());
let mut combinator = None; let mut combinator = None;
let mut needs_revalidation = false; let mut needs_revalidation = false;
@ -590,6 +588,19 @@ impl DependencySet {
needs_revalidation: false, 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. // Visit all the simple selectors.
let mut iter = current.iter(); let mut iter = current.iter();

View file

@ -333,7 +333,7 @@ impl Stylist {
for selector in &style_rule.selectors.0 { for selector in &style_rule.selectors.0 {
let needs_cache_revalidation = let needs_cache_revalidation =
self.dependencies.note_selector(&selector.inner.complex); self.dependencies.note_selector(selector);
if needs_cache_revalidation { if needs_cache_revalidation {
self.selectors_for_cache_revalidation.push(selector.clone()); self.selectors_for_cache_revalidation.push(selector.clone());
} }
@ -646,7 +646,10 @@ impl Stylist {
F: FnMut(&E, ElementSelectorFlags), F: FnMut(&E, ElementSelectorFlags),
{ {
debug_assert!(!self.is_device_dirty); 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"); "Style attributes do not apply to pseudo-elements");
debug_assert!(pseudo_element.as_ref().map_or(true, |p| !p.is_precomputed())); debug_assert!(pseudo_element.as_ref().map_or(true, |p| !p.is_precomputed()));

View file

@ -264,88 +264,118 @@ pub trait DomTraversal<E: TElement> : Sync {
return true; return true;
} }
match node.as_element() { let el = match node.as_element() {
None => Self::text_node_needs_traversal(node), None => return Self::text_node_needs_traversal(node),
Some(el) => { 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 don't need to traverse the subtree. // If the element is native-anonymous and an ancestor frame will
if el.is_native_anonymous() { // be reconstructed, the child and all its descendants will be
if let Some(parent) = el.parent_element() { // destroyed. In that case, we wouldn't need to traverse the
let parent_data = parent.borrow_data().unwrap(); // subtree...
if let Some(r) = parent_data.get_restyle() { //
if (r.damage | r.damage_handled()).contains(RestyleDamage::reconstruct()) { // Except if there could be transitions of pseudo-elements, in
debug!("Element {:?} is in doomed NAC subtree - culling traversal", el); // which
return false; // 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 if going_to_reframe && !is_before_or_after_pseudo {
// the element if the element has animation only dirty debug!("Element {:?} is in doomed NAC subtree, \
// descendants bit, animation-only restyle hint or recascade. culling traversal", el);
if traversal_flags.for_animation_only() { return false;
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 != 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 /// Returns true if traversal of this element's children is allowed. We use
@ -396,7 +426,6 @@ pub trait DomTraversal<E: TElement> : Sync {
} }
return true; return true;
} }
/// Helper for the traversal implementations to select the children that /// Helper for the traversal implementations to select the children that
@ -489,7 +518,9 @@ fn resolve_style_internal<E, F>(context: &mut StyleContext<E>,
// Compute our style. // Compute our style.
context.thread_local.begin_element(element, &data); 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); context.thread_local.end_element(element);
// Conservatively mark us as having dirty descendants, since there might // Conservatively mark us as having dirty descendants, since there might
@ -607,9 +638,13 @@ pub fn recalc_style_at<E, D>(traversal: &D,
}; };
debug_assert!(data.has_current_styles() || debug_assert!(data.has_current_styles() ||
context.shared.traversal_flags.for_animation_only(), context.shared.traversal_flags.for_animation_only(),
"Should have computed style or haven't yet valid computed style in case of animation-only restyle"); "Should have computed style or haven't yet valid computed \
trace!("propagated_hint={:?}, inherited_style_changed={:?}", style in case of animation-only restyle");
propagated_hint, inherited_style_changed); 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 = let has_dirty_descendants_for_this_restyle =
if context.shared.traversal_flags.for_animation_only() { if context.shared.traversal_flags.for_animation_only() {
@ -664,7 +699,8 @@ pub fn recalc_style_at<E, D>(traversal: &D,
// The second case is when we are in a restyle for reconstruction, // 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 // where we won't need to perform a post-traversal to pick up any
// change hints. // 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(); } unsafe { element.unset_dirty_descendants(); }
} }
} }

View file

@ -23,7 +23,7 @@ use style::font_metrics::get_metrics_provider_for_product;
use style::gecko::data::{PerDocumentStyleData, PerDocumentStyleDataImpl}; use style::gecko::data::{PerDocumentStyleData, PerDocumentStyleDataImpl};
use style::gecko::global_style_data::{GLOBAL_STYLE_DATA, GlobalStyleData}; use style::gecko::global_style_data::{GLOBAL_STYLE_DATA, GlobalStyleData};
use style::gecko::restyle_damage::GeckoRestyleDamage; 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::traversal::RecalcStyleOnly;
use style::gecko::wrapper::GeckoElement; use style::gecko::wrapper::GeckoElement;
use style::gecko_bindings::bindings; 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) { traversal_flags: TraversalFlags) {
// When new content is inserted in a display:none subtree, we will call into // 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. // 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, fn get_pseudo_style(guard: &SharedRwLockReadGuard,
styles: &ElementStyles, doc_data: &PerDocumentStyleData) element: GeckoElement,
pseudo_tag: *mut nsIAtom,
styles: &ElementStyles,
doc_data: &PerDocumentStyleData)
-> Option<Arc<ComputedValues>> -> Option<Arc<ComputedValues>>
{ {
let pseudo = PseudoElement::from_atom_unchecked(Atom::from(pseudo_tag), false); 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::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()),
PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"), PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
PseudoElementCascadeType::Lazy => { PseudoElementCascadeType::Lazy => {