aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/script/layout_wrapper.rs12
-rw-r--r--components/style/context.rs22
-rw-r--r--components/style/data.rs15
-rw-r--r--components/style/dom.rs57
-rw-r--r--components/style/gecko/selector_parser.rs39
-rw-r--r--components/style/gecko/wrapper.rs164
-rw-r--r--components/style/matching.rs356
-rw-r--r--components/style/restyle_hints.rs27
-rw-r--r--components/style/stylist.rs7
-rw-r--r--components/style/traversal.rs188
10 files changed, 534 insertions, 353 deletions
diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs
index 12d830e5fad..3ddb1d2b5c9 100644
--- a/components/script/layout_wrapper.rs
+++ b/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/components/style/context.rs b/components/style/context.rs
index 01043637a0d..581a7c12cd3 100644
--- a/components/style/context.rs
+++ b/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<E: TElement> {
/// 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<E>,
- /// The target pseudo element.
- pseudo: Option<PseudoElement>,
/// 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<Arc<ComputedValues>>,
@@ -316,8 +314,8 @@ impl<E: TElement> SequentialTask<E> {
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<E: TElement> SequentialTask<E> {
/// a given (pseudo-)element.
#[cfg(feature = "gecko")]
pub fn update_animations(el: E,
- pseudo: Option<PseudoElement>,
before_change_style: Option<Arc<ComputedValues>>,
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/components/style/data.rs b/components/style/data.rs
index a1cc8b59f65..4052f0b30a3 100644
--- a/components/style/data.rs
+++ b/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/components/style/dom.rs b/components/style/dom.rs
index c937dba8bb3..ffc46b0cc94 100644
--- a/components/style/dom.rs
+++ b/components/style/dom.rs
@@ -274,11 +274,20 @@ pub trait PresentationalHintsSynthetizer {
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.
pub struct AnimationRules(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.
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<Arc<Locked<PropertyDeclarationBlock>>> {
None
}
/// Get this element's animation rule.
- fn get_animation_rule(&self, _pseudo: Option<&PseudoElement>)
+ fn get_animation_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
None
}
/// Get this element's transition rule.
- fn get_transition_rule(&self, _pseudo: Option<&PseudoElement>)
+ fn get_transition_rule(&self)
-> Option<Arc<Locked<PropertyDeclarationBlock>>> {
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<PseudoElement> { 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<Arc<ComputedValues>>,
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<TransitionProperty, Arc<AnimationValue>>;
/// 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<ComputedValues>>,
- new_values: &Arc<ComputedValues>,
- 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<ComputedValues>,
- after_change_style: &Arc<ComputedValues>,
- 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<ComputedValues>,
- after_change_style: &Arc<ComputedValues>,
+ before_change_style: &ComputedValues,
+ after_change_style: &ComputedValues,
existing_transitions: &HashMap<TransitionProperty,
Arc<AnimationValue>>)
-> bool;
diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs
index 95b7ab82ac5..b87824fc33f 100644
--- a/components/style/gecko/selector_parser.rs
+++ b/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/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs
index 30b9d7b7846..62a2d835d1a 100644
--- a/components/style/gecko/wrapper.rs
+++ b/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<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()));
- 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<Arc<Locked<PropertyDeclarationBlock>>> {
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<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>>> {
- 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<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) {
// 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<Arc<ComputedValues>>,
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<TransitionProperty, Arc<AnimationValue>> {
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<ComputedValues>>,
- new_values: &Arc<ComputedValues>,
- 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<ComputedValues>,
- after_change_style: &Arc<ComputedValues>,
- 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<ComputedValues>,
- after_change_style: &Arc<ComputedValues>,
+ before_change_style: &ComputedValues,
+ after_change_style: &ComputedValues,
existing_transitions: &HashMap<TransitionProperty,
Arc<AnimationValue>>)
-> bool {
diff --git a/components/style/matching.rs b/components/style/matching.rs
index 5d8b8b965c4..31641711e36 100644
--- a/components/style/matching.rs
+++ b/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<E: TElement> {
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<E: TElement>(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<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
// are either uncommon or expensive.
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!");
}
-
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<ComputedValues> {
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()
- });
-
- parent_values
- } else {
- parent_el = Some(self.clone());
- Some(primary_style.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
+ }
+ 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<Self>,
primary_style: &ComputedStyle,
- pseudo_style: Option<&ComputedStyle>)
+ eager_pseudo_style: Option<&ComputedStyle>)
-> Arc<ComputedValues> {
// 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<Self>,
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();
+
+ 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))
+ }
+ };
- // Handle animations.
- if animate && !context.shared.traversal_flags.for_animation_only() {
+ // 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<Self>,
- primary_style: &ComputedStyle,
- pseudo_style: Option<&ComputedStyle>)
+ primary_style: &ComputedStyle)
-> Option<Arc<ComputedValues>> {
- 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<Arc<ComputedValues>>,
- new_values: &Arc<ComputedValues>,
- pseudo: Option<&PseudoElement>) -> bool {
- let ref new_box_style = new_values.get_box();
+ old_values: Option<&Arc<ComputedValues>>,
+ 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<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>,
- 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<Self>,
old_values: &mut Option<Arc<ComputedValues>>,
new_values: &mut Arc<ComputedValues>,
- _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<Self>,
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::<ApplicableDeclarationBlock>::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::<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
@@ -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::<ApplicableDeclarationBlock>::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<Self>,
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/components/style/restyle_hints.rs b/components/style/restyle_hints.rs
index d1d733417e0..cbe10f3ed41 100644
--- a/components/style/restyle_hints.rs
+++ b/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<nsRestyleHint> 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<SelectorImpl>)
- -> bool
- {
- let mut next = Some(base.clone());
+ pub fn note_selector(&mut self, selector: &Selector<SelectorImpl>) -> 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/components/style/stylist.rs b/components/style/stylist.rs
index af0a4eb0e8a..bf008ba7574 100644
--- a/components/style/stylist.rs
+++ b/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/components/style/traversal.rs b/components/style/traversal.rs
index e9f4e7c2775..831a9454a62 100644
--- a/components/style/traversal.rs
+++ b/components/style/traversal.rs
@@ -264,88 +264,118 @@ pub trait DomTraversal<E: TElement> : 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;
- }
+ // 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;
+ }
- // 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,
- };
+ 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 we don't have any style data, we need to visit the element.
- if !data.has_styles() {
- return true;
- }
+ // 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 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;
- }
- }
+ // 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,
+ };
- // 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;
- }
+ // If we don't have any style data, we need to visit the element.
+ if !data.has_styles() {
+ return true;
+ }
- false
- },
+ // 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<E: TElement> : Sync {
}
return true;
-
}
/// 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.
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<E, D>(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<E, D>(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(); }
}
}