style: Get restyle hints right in presence of XBL.

Bug: 1371130
Reviewed-By: heycam
MozReview-Commit-ID: 56lMyXEYT1I
This commit is contained in:
Emilio Cobos Álvarez 2017-06-20 10:08:10 +02:00
parent 459b22a985
commit ae5a6c9c69
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
3 changed files with 158 additions and 113 deletions

View file

@ -587,6 +587,14 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
return data.restyle.hint.has_animation_hint() return data.restyle.hint.has_animation_hint()
} }
/// Returns the anonymous content for the current element's XBL binding,
/// given if any.
///
/// This is used in Gecko for XBL and shadow DOM.
fn xbl_binding_anonymous_content(&self) -> Option<Self::ConcreteNode> {
None
}
/// Gets declarations from XBL bindings from the element. Only gecko element could have this. /// Gets declarations from XBL bindings from the element. Only gecko element could have this.
fn get_declarations_from_xbl_bindings<V>(&self, fn get_declarations_from_xbl_bindings<V>(&self,
_pseudo_element: Option<&PseudoElement>, _pseudo_element: Option<&PseudoElement>,

View file

@ -36,7 +36,6 @@ use gecko_bindings::bindings::{Gecko_IsRootElement, Gecko_MatchesElement, Gecko_
use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags}; use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
use gecko_bindings::bindings::Gecko_ClassOrClassList; use gecko_bindings::bindings::Gecko_ClassOrClassList;
use gecko_bindings::bindings::Gecko_ElementHasAnimations; use gecko_bindings::bindings::Gecko_ElementHasAnimations;
use gecko_bindings::bindings::Gecko_ElementHasBindingWithAnonymousContent;
use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
use gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock; use gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock;
@ -275,7 +274,10 @@ impl<'ln> TNode for GeckoNode<'ln> {
} }
fn children_and_traversal_children_might_differ(&self) -> bool { fn children_and_traversal_children_might_differ(&self) -> bool {
self.as_element().map_or(false, |e| unsafe { Gecko_ElementHasBindingWithAnonymousContent(e.0) }) match self.as_element() {
Some(e) => e.xbl_binding_anonymous_content().is_some(),
None => false,
}
} }
fn opaque(&self) -> OpaqueNode { fn opaque(&self) -> OpaqueNode {
@ -376,6 +378,10 @@ impl<'lb> GeckoXBLBinding<'lb> {
unsafe { self.0.mNextBinding.mRawPtr.as_ref().map(GeckoXBLBinding) } unsafe { self.0.mNextBinding.mRawPtr.as_ref().map(GeckoXBLBinding) }
} }
fn anon_content(&self) -> *const nsIContent {
unsafe { self.0.mContent.raw::<nsIContent>() }
}
fn inherits_style(&self) -> bool { fn inherits_style(&self) -> bool {
unsafe { bindings::Gecko_XBLBinding_InheritsStyle(self.0) } unsafe { bindings::Gecko_XBLBinding_InheritsStyle(self.0) }
} }
@ -1039,6 +1045,19 @@ impl<'le> TElement for GeckoElement<'le> {
current.is_some() current.is_some()
} }
fn xbl_binding_anonymous_content(&self) -> Option<GeckoNode<'le>> {
if self.flags() & (structs::NODE_MAY_BE_IN_BINDING_MNGR as u32) == 0 {
return None;
}
let anon_content = match self.get_xbl_binding() {
Some(binding) => binding.anon_content(),
None => return None,
};
unsafe { anon_content.as_ref().map(GeckoNode::from_content) }
}
fn get_css_transitions_info(&self) fn get_css_transitions_info(&self)
-> HashMap<TransitionProperty, Arc<AnimationValue>> { -> HashMap<TransitionProperty, Arc<AnimationValue>> {
use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt; use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt;
@ -1380,6 +1399,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
type Impl = SelectorImpl; type Impl = SelectorImpl;
fn parent_element(&self) -> Option<Self> { fn parent_element(&self) -> Option<Self> {
// FIXME(emilio): This will need to jump across if the parent node is a
// shadow root to get the shadow host.
let parent_node = self.as_node().parent_node(); let parent_node = self.as_node().parent_node();
parent_node.and_then(|n| n.as_element()) parent_node.and_then(|n| n.as_element())
} }

View file

@ -274,6 +274,34 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
&mut self, &mut self,
child: E, child: E,
invalidations: &InvalidationVector invalidations: &InvalidationVector
) -> bool {
let mut sibling_invalidations = InvalidationVector::new();
let result = self.invalidate_child(
child,
invalidations,
&mut sibling_invalidations
);
// Roots of NAC subtrees can indeed generate sibling invalidations, but
// they can be just ignored, since they have no siblings.
debug_assert!(child.implemented_pseudo_element().is_none() ||
sibling_invalidations.is_empty(),
"pseudos can't generate sibling invalidations, since \
using them in other position that isn't the \
rightmost part of the selector is invalid \
(for now at least)");
result
}
/// Invalidate a child and recurse down invalidating its descendants if
/// needed.
fn invalidate_child(
&mut self,
child: E,
invalidations: &InvalidationVector,
sibling_invalidations: &mut InvalidationVector,
) -> bool { ) -> bool {
let mut child_data = child.mutate_data(); let mut child_data = child.mutate_data();
let child_data = child_data.as_mut().map(|d| &mut **d); let child_data = child_data.as_mut().map(|d| &mut **d);
@ -285,69 +313,93 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
); );
let mut invalidations_for_descendants = InvalidationVector::new(); let mut invalidations_for_descendants = InvalidationVector::new();
let mut sibling_invalidations = InvalidationVector::new(); let mut invalidated_child = false;
let invalidated = child_invalidator.process_descendant_invalidations( invalidated_child |=
invalidations, child_invalidator.process_sibling_invalidations(
&mut invalidations_for_descendants, &mut invalidations_for_descendants,
&mut sibling_invalidations, sibling_invalidations,
);
debug_assert!(child.implemented_pseudo_element().is_none() ||
sibling_invalidations.is_empty(),
"pseudos can't generate sibling invalidations, since \
using them in other position that isn't the \
rightmost part of the selector is invalid \
(for now at least)");
// For NAC roots, we can ignore sibling invalidations, since they don't
// have any siblings.
let invalidated_children =
child_invalidator.invalidate_descendants(
&invalidations_for_descendants
); );
invalidated || invalidated_children invalidated_child |=
child_invalidator.process_descendant_invalidations(
invalidations,
&mut invalidations_for_descendants,
sibling_invalidations,
);
// The child may not be a flattened tree child of the current element,
// but may be arbitrarily deep.
//
// Since we keep the traversal flags in terms of the flattened tree,
// we need to propagate it as appropriate.
if invalidated_child && child.parent_element() != Some(self.element) {
let mut current = child.traversal_parent();
while let Some(parent) = current.take() {
if parent == self.element {
break;
}
unsafe { parent.set_dirty_descendants() };
current = parent.traversal_parent();
}
}
let invalidated_descendants = child_invalidator.invalidate_descendants(
&invalidations_for_descendants
);
invalidated_child || invalidated_descendants
} }
fn invalidate_pseudo_elements_and_nac( fn invalidate_nac(
&mut self, &mut self,
invalidations: &InvalidationVector invalidations: &InvalidationVector,
) -> bool { ) -> bool {
let mut any_pseudo = false; let mut any_nac_root = false;
if let Some(before) = self.element.before_pseudo_element() {
any_pseudo |=
self.invalidate_pseudo_element_or_nac(before, invalidations);
}
if let Some(after) = self.element.after_pseudo_element() {
any_pseudo |=
self.invalidate_pseudo_element_or_nac(after, invalidations);
}
let element = self.element; let element = self.element;
element.each_anonymous_content_child(|pseudo| { element.each_anonymous_content_child(|nac| {
let invalidated = any_nac_root |=
self.invalidate_pseudo_element_or_nac(pseudo, invalidations); self.invalidate_pseudo_element_or_nac(nac, invalidations);
if invalidated {
let mut current = pseudo.traversal_parent();
while let Some(parent) = current.take() {
if parent == self.element {
break;
}
unsafe { parent.set_dirty_descendants() };
current = parent.traversal_parent();
}
}
any_pseudo |= invalidated;
}); });
any_pseudo any_nac_root
}
// NB: It's important that this operates on DOM children, which is what
// selector-matching operates on.
fn invalidate_dom_descendants_of(
&mut self,
parent: E::ConcreteNode,
invalidations: &InvalidationVector,
) -> bool {
let mut any_descendant = false;
let mut sibling_invalidations = InvalidationVector::new();
for child in parent.children() {
// TODO(emilio): We handle <xbl:children> fine, because they appear
// in selector-matching (note bug 1374247, though).
//
// This probably needs a shadow root check on `child` here, and
// recursing if that's the case.
//
// Also, what's the deal with HTML <content>? We don't need to
// support that for now, though we probably need to recurse into the
// distributed children too.
let child = match child.as_element() {
Some(e) => e,
None => continue,
};
any_descendant |= self.invalidate_child(
child,
invalidations,
&mut sibling_invalidations,
);
}
any_descendant
} }
/// Given a descendant invalidation list, go through the current element's /// Given a descendant invalidation list, go through the current element's
@ -373,72 +425,36 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
} }
} }
let mut sibling_invalidations = InvalidationVector::new(); let mut any_descendant = false;
let mut any_children = false; if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
any_descendant |=
// NB: DOM children! self.invalidate_dom_descendants_of(anon_content, invalidations);
for child in self.element.as_node().children() {
let child = match child.as_element() {
Some(e) => e,
None => continue,
};
let mut child_data = child.mutate_data();
let child_data = child_data.as_mut().map(|d| &mut **d);
let mut child_invalidator = TreeStyleInvalidator::new(
child,
child_data,
self.shared_context
);
let mut invalidations_for_descendants = InvalidationVector::new();
let mut invalidated_child = false;
invalidated_child |=
child_invalidator.process_sibling_invalidations(
&mut invalidations_for_descendants,
&mut sibling_invalidations,
);
invalidated_child |=
child_invalidator.process_descendant_invalidations(
invalidations,
&mut invalidations_for_descendants,
&mut sibling_invalidations,
);
// The child may not be a flattened tree child of the current
// element, but may be arbitrarily deep.
//
// Since we keep the traversal flags in terms of the flattened tree,
// we need to propagate it as appropriate.
if invalidated_child {
let mut current = child.traversal_parent();
while let Some(parent) = current.take() {
if parent == self.element {
break;
}
unsafe { parent.set_dirty_descendants() };
current = parent.traversal_parent();
}
}
any_children |= invalidated_child;
any_children |= child_invalidator.invalidate_descendants(
&invalidations_for_descendants
);
} }
any_children |= self.invalidate_pseudo_elements_and_nac(invalidations); // TODO(emilio): Having a list of invalidations just for pseudo-elements
// may save some work here and there.
if let Some(before) = self.element.before_pseudo_element() {
any_descendant |=
self.invalidate_pseudo_element_or_nac(before, invalidations);
}
if any_children { let node = self.element.as_node();
any_descendant |=
self.invalidate_dom_descendants_of(node, invalidations);
if let Some(after) = self.element.after_pseudo_element() {
any_descendant |=
self.invalidate_pseudo_element_or_nac(after, invalidations);
}
any_descendant |= self.invalidate_nac(invalidations);
if any_descendant {
unsafe { self.element.set_dirty_descendants() }; unsafe { self.element.set_dirty_descendants() };
} }
any_children any_descendant
} }
/// Process the given sibling invalidations coming from our previous /// Process the given sibling invalidations coming from our previous