Skip to content

Commit

Permalink
style: Get restyle hints right in presence of XBL.
Browse files Browse the repository at this point in the history
Bug: 1371130
Reviewed-By: heycam
MozReview-Commit-ID: 56lMyXEYT1I
  • Loading branch information
emilio committed Jun 20, 2017
1 parent 459b22a commit ae5a6c9
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 106 deletions.
8 changes: 8 additions & 0 deletions components/style/dom.rs
Expand Up @@ -587,6 +587,14 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
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.
fn get_declarations_from_xbl_bindings<V>(&self,
_pseudo_element: Option<&PseudoElement>,
Expand Down
25 changes: 23 additions & 2 deletions components/style/gecko/wrapper.rs
Expand Up @@ -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_ClassOrClassList;
use gecko_bindings::bindings::Gecko_ElementHasAnimations;
use gecko_bindings::bindings::Gecko_ElementHasBindingWithAnonymousContent;
use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
use gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock;
Expand Down Expand Up @@ -275,7 +274,10 @@ impl<'ln> TNode for GeckoNode<'ln> {
}

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 {
Expand Down Expand Up @@ -376,6 +378,10 @@ impl<'lb> GeckoXBLBinding<'lb> {
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 {
unsafe { bindings::Gecko_XBLBinding_InheritsStyle(self.0) }
}
Expand Down Expand Up @@ -1039,6 +1045,19 @@ impl<'le> TElement for GeckoElement<'le> {
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)
-> HashMap<TransitionProperty, Arc<AnimationValue>> {
use gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt;
Expand Down Expand Up @@ -1380,6 +1399,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
type Impl = SelectorImpl;

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();
parent_node.and_then(|n| n.as_element())
}
Expand Down
224 changes: 120 additions & 104 deletions components/style/invalidation/element/invalidator.rs
Expand Up @@ -275,79 +275,131 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
child: E,
invalidations: &InvalidationVector
) -> bool {
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 sibling_invalidations = InvalidationVector::new();

let invalidated = child_invalidator.process_descendant_invalidations(
let result = self.invalidate_child(
child,
invalidations,
&mut invalidations_for_descendants,
&mut sibling_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)");

// 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
result
}

fn invalidate_pseudo_elements_and_nac(
/// Invalidate a child and recurse down invalidating its descendants if
/// needed.
fn invalidate_child(
&mut self,
invalidations: &InvalidationVector
child: E,
invalidations: &InvalidationVector,
sibling_invalidations: &mut InvalidationVector,
) -> bool {
let mut any_pseudo = false;
let mut child_data = child.mutate_data();
let child_data = child_data.as_mut().map(|d| &mut **d);

if let Some(before) = self.element.before_pseudo_element() {
any_pseudo |=
self.invalidate_pseudo_element_or_nac(before, invalidations);
}
let mut child_invalidator = TreeStyleInvalidator::new(
child,
child_data,
self.shared_context
);

if let Some(after) = self.element.after_pseudo_element() {
any_pseudo |=
self.invalidate_pseudo_element_or_nac(after, invalidations);
}
let mut invalidations_for_descendants = InvalidationVector::new();
let mut invalidated_child = false;

let element = self.element;
element.each_anonymous_content_child(|pseudo| {
let invalidated =
self.invalidate_pseudo_element_or_nac(pseudo, invalidations);

if invalidated {
let mut current = pseudo.traversal_parent();
while let Some(parent) = current.take() {
if parent == self.element {
break;
}
invalidated_child |=
child_invalidator.process_sibling_invalidations(
&mut invalidations_for_descendants,
sibling_invalidations,
);

unsafe { parent.set_dirty_descendants() };
current = parent.traversal_parent();
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_nac(
&mut self,
invalidations: &InvalidationVector,
) -> bool {
let mut any_nac_root = false;

any_pseudo |= invalidated;
let element = self.element;
element.each_anonymous_content_child(|nac| {
any_nac_root |=
self.invalidate_pseudo_element_or_nac(nac, invalidations);
});

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
Expand All @@ -373,72 +425,36 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
}
}

let mut sibling_invalidations = InvalidationVector::new();

let mut any_children = false;

// NB: DOM children!
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 any_descendant = false;

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,
);
if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
any_descendant |=
self.invalidate_dom_descendants_of(anon_content, 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;
}
// 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);
}

unsafe { parent.set_dirty_descendants() };
current = parent.traversal_parent();
}
}
let node = self.element.as_node();
any_descendant |=
self.invalidate_dom_descendants_of(node, invalidations);

any_children |= invalidated_child;
any_children |= child_invalidator.invalidate_descendants(
&invalidations_for_descendants
);
if let Some(after) = self.element.after_pseudo_element() {
any_descendant |=
self.invalidate_pseudo_element_or_nac(after, invalidations);
}

any_children |= self.invalidate_pseudo_elements_and_nac(invalidations);
any_descendant |= self.invalidate_nac(invalidations);

if any_children {
if any_descendant {
unsafe { self.element.set_dirty_descendants() };
}

any_children
any_descendant
}

/// Process the given sibling invalidations coming from our previous
Expand Down

0 comments on commit ae5a6c9

Please sign in to comment.