style: Add invalidation support for ::slotted().

Bug: 1424607
Reviewed-by: heycam
MozReview-Commit-ID: 8pIVUx27o7x
This commit is contained in:
Emilio Cobos Álvarez 2017-12-17 17:56:15 +01:00
parent 040379208e
commit b26f3280d2
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
7 changed files with 294 additions and 110 deletions

View file

@ -363,6 +363,10 @@ impl<'le> TElement for ServoLayoutElement<'le> {
LayoutIterator(self.as_node().dom_children())
}
fn is_html_element(&self) -> bool {
unsafe { self.element.is_html_element() }
}
fn style_attribute(&self) -> Option<ArcBorrow<StyleLocked<PropertyDeclarationBlock>>> {
unsafe {
(*self.element.style_attribute()).as_ref().map(|x| x.borrow_arc())

View file

@ -416,6 +416,20 @@ pub trait TElement
F: FnMut(Self),
{}
/// Return whether this element is an element in the HTML namespace.
fn is_html_element(&self) -> bool;
/// Returns whether this element is a <html:slot> element.
fn is_html_slot_element(&self) -> bool {
self.get_local_name() == &*local_name!("slot") &&
self.is_html_element()
}
/// Return the list of slotted nodes of this node.
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
&[]
}
/// For a given NAC element, return the closest non-NAC ancestor, which is
/// guaranteed to exist.
fn closest_non_native_anonymous_ancestor(&self) -> Option<Self> {

View file

@ -8,7 +8,8 @@
use Atom;
use context::QuirksMode;
use dom::{TDocument, TElement, TNode};
use invalidation::element::invalidator::{Invalidation, InvalidationProcessor, InvalidationVector};
use invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
use invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
use selectors::{Element, NthIndexCache, SelectorList};
use selectors::attr::CaseSensitivity;
use selectors::matching::{self, MatchingContext, MatchingMode};
@ -143,7 +144,7 @@ where
&mut self,
element: E,
self_invalidations: &mut InvalidationVector<'a>,
descendant_invalidations: &mut InvalidationVector<'a>,
descendant_invalidations: &mut DescendantInvalidationLists<'a>,
_sibling_invalidations: &mut InvalidationVector<'a>,
) -> bool {
// TODO(emilio): If the element is not a root element, and
@ -163,7 +164,7 @@ where
let target_vector =
if self.matching_context.scope_element.is_some() {
descendant_invalidations
&mut descendant_invalidations.dom_descendants
} else {
self_invalidations
};

View file

@ -610,10 +610,7 @@ impl<'le> GeckoElement<'le> {
self.as_node().node_info().mInner.mNamespaceID
}
fn is_html_element(&self) -> bool {
self.namespace_id() == (structs::root::kNameSpaceID_XHTML as i32)
}
#[inline]
fn is_xul_element(&self) -> bool {
self.namespace_id() == (structs::root::kNameSpaceID_XUL as i32)
}
@ -974,6 +971,40 @@ impl<'le> TElement for GeckoElement<'le> {
self.as_node().owner_doc().as_node()
}
#[inline]
fn is_html_element(&self) -> bool {
self.namespace_id() == (structs::root::kNameSpaceID_XHTML as i32)
}
/// Return the list of slotted nodes of this node.
#[inline]
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
if !self.is_html_slot_element() || !self.is_in_shadow_tree() {
return &[];
}
let slot: &structs::HTMLSlotElement = unsafe {
mem::transmute(self.0)
};
if cfg!(debug_assertions) {
let base: &RawGeckoElement = &slot._base._base._base._base;
assert_eq!(base as *const _, self.0 as *const _, "Bad cast");
}
let assigned_nodes: &[structs::RefPtr<structs::nsINode>] =
&*slot.mAssignedNodes;
debug_assert_eq!(
mem::size_of::<structs::RefPtr<structs::nsINode>>(),
mem::size_of::<Self::ConcreteNode>(),
"Bad cast!"
);
unsafe { mem::transmute(assigned_nodes) }
}
/// Execute `f` for each anonymous content child element (apart from
/// ::before and ::after) whose originating element is `self`.
fn each_anonymous_content_child<F>(&self, mut f: F)

View file

@ -13,7 +13,8 @@ use dom::TElement;
use element_state::ElementState;
use invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper};
use invalidation::element::invalidation_map::*;
use invalidation::element::invalidator::{InvalidationVector, Invalidation, InvalidationProcessor};
use invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector};
use invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
use invalidation::element::restyle_hints::RestyleHint;
use selector_map::SelectorMap;
use selector_parser::Snapshot;
@ -47,7 +48,7 @@ where
classes_removed: &'a SmallVec<[Atom; 8]>,
classes_added: &'a SmallVec<[Atom; 8]>,
state_changes: ElementState,
descendant_invalidations: &'a mut InvalidationVector<'selectors>,
descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>,
sibling_invalidations: &'a mut InvalidationVector<'selectors>,
invalidates_self: bool,
}
@ -109,7 +110,7 @@ where
&mut self,
element: E,
_self_invalidations: &mut InvalidationVector<'a>,
descendant_invalidations: &mut InvalidationVector<'a>,
descendant_invalidations: &mut DescendantInvalidationLists<'a>,
sibling_invalidations: &mut InvalidationVector<'a>,
) -> bool {
debug_assert!(element.has_snapshot(), "Why bothering?");
@ -236,7 +237,10 @@ where
//
// This number is completely made-up, but the page that made us add this
// code generated 1960+ invalidations (bug 1420741).
if descendant_invalidations.len() > 150 {
//
// We don't look at slotted_descendants because those don't propagate
// down more than one level anyway.
if descendant_invalidations.dom_descendants.len() > 150 {
self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS);
}
@ -509,36 +513,52 @@ where
}
fn note_dependency(&mut self, dependency: &'selectors Dependency) {
if dependency.affects_self() {
debug_assert!(self.dependency_may_be_relevant(dependency));
let invalidation_kind = dependency.invalidation_kind();
if matches!(invalidation_kind, DependencyInvalidationKind::Element) {
self.invalidates_self = true;
return;
}
if dependency.affects_descendants() {
debug_assert_ne!(dependency.selector_offset, 0);
debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
debug_assert!(!dependency.affects_later_siblings());
self.descendant_invalidations.push(Invalidation::new(
&dependency.selector,
dependency.selector.len() - dependency.selector_offset + 1,
));
} else if dependency.affects_later_siblings() {
debug_assert_ne!(dependency.selector_offset, 0);
debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
self.sibling_invalidations.push(Invalidation::new(
&dependency.selector,
dependency.selector.len() - dependency.selector_offset + 1,
));
debug_assert_ne!(dependency.selector_offset, 0);
debug_assert_ne!(
dependency.selector_offset,
dependency.selector.len()
);
let invalidation = Invalidation::new(
&dependency.selector,
dependency.selector.len() - dependency.selector_offset + 1,
);
match invalidation_kind {
DependencyInvalidationKind::Element => unreachable!(),
DependencyInvalidationKind::ElementAndDescendants => {
self.invalidates_self = true;
self.descendant_invalidations.dom_descendants.push(invalidation);
}
DependencyInvalidationKind::Descendants => {
self.descendant_invalidations.dom_descendants.push(invalidation);
}
DependencyInvalidationKind::Siblings => {
self.sibling_invalidations.push(invalidation);
}
DependencyInvalidationKind::SlottedElements => {
self.descendant_invalidations.slotted_descendants.push(invalidation);
}
}
}
/// Returns whether `dependency` may cause us to invalidate the style of
/// more elements than what we've already invalidated.
fn dependency_may_be_relevant(&self, dependency: &Dependency) -> bool {
if dependency.affects_descendants() || dependency.affects_later_siblings() {
return true;
match dependency.invalidation_kind() {
DependencyInvalidationKind::Element => !self.invalidates_self,
DependencyInvalidationKind::SlottedElements => self.element.is_html_slot_element(),
DependencyInvalidationKind::ElementAndDescendants |
DependencyInvalidationKind::Siblings |
DependencyInvalidationKind::Descendants => true,
}
debug_assert!(dependency.affects_self());
!self.invalidates_self
}
}

View file

@ -63,6 +63,25 @@ pub struct Dependency {
pub selector_offset: usize,
}
/// The kind of elements down the tree this dependency may affect.
#[derive(Debug, Eq, PartialEq)]
pub enum DependencyInvalidationKind {
/// This dependency may affect the element that changed itself.
Element,
/// This dependency affects the style of the element itself, and also the
/// style of its descendants.
///
/// TODO(emilio): Each time this feels more of a hack for eager pseudos...
ElementAndDescendants,
/// This dependency may affect descendants down the tree.
Descendants,
/// This dependency may affect siblings to the right of the element that
/// changed.
Siblings,
/// This dependency may affect slotted elements of the element that changed.
SlottedElements,
}
impl Dependency {
/// Returns the combinator to the right of the partial selector this
/// dependency represents.
@ -76,28 +95,19 @@ impl Dependency {
Some(self.selector.combinator_at_match_order(self.selector_offset - 1))
}
/// Whether this dependency affects the style of the element.
///
/// NOTE(emilio): pseudo-elements need to be here to account for eager
/// pseudos, since they just grab the style from the originating element.
///
/// TODO(emilio): We could look at the selector itself to see if it's an
/// eager pseudo, and return false here if not.
pub fn affects_self(&self) -> bool {
matches!(self.combinator(), None | Some(Combinator::PseudoElement))
}
/// Whether this dependency may affect style of any of our descendants.
pub fn affects_descendants(&self) -> bool {
matches!(self.combinator(), Some(Combinator::PseudoElement) |
Some(Combinator::Child) |
Some(Combinator::Descendant))
}
/// Whether this dependency may affect style of any of our later siblings.
pub fn affects_later_siblings(&self) -> bool {
matches!(self.combinator(), Some(Combinator::NextSibling) |
Some(Combinator::LaterSibling))
/// The kind of invalidation that this would generate.
pub fn invalidation_kind(&self) -> DependencyInvalidationKind {
match self.combinator() {
None => DependencyInvalidationKind::Element,
Some(Combinator::Child) |
Some(Combinator::Descendant) => DependencyInvalidationKind::Descendants,
Some(Combinator::LaterSibling) |
Some(Combinator::NextSibling) => DependencyInvalidationKind::Siblings,
// TODO(emilio): We could look at the selector itself to see if it's
// an eager pseudo, and return only Descendants here if not.
Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants,
Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements,
}
}
}

View file

@ -39,7 +39,7 @@ where
&mut self,
element: E,
self_invalidations: &mut InvalidationVector<'a>,
descendant_invalidations: &mut InvalidationVector<'a>,
descendant_invalidations: &mut DescendantInvalidationLists<'a>,
sibling_invalidations: &mut InvalidationVector<'a>,
) -> bool;
@ -58,6 +58,25 @@ where
fn invalidated_descendants(&mut self, element: E, child: E);
}
/// Different invalidation lists for descendants.
#[derive(Debug, Default)]
pub struct DescendantInvalidationLists<'a> {
/// Invalidations for normal DOM children and pseudo-elements.
///
/// TODO(emilio): Having a list of invalidations just for pseudo-elements
/// may save some work here and there.
pub dom_descendants: InvalidationVector<'a>,
/// Invalidations for slotted children of an element.
pub slotted_descendants: InvalidationVector<'a>,
}
impl<'a> DescendantInvalidationLists<'a> {
fn is_empty(&self) -> bool {
self.dom_descendants.is_empty() &&
self.slotted_descendants.is_empty()
}
}
/// The struct that takes care of encapsulating all the logic on where and how
/// element styles need to be invalidated.
pub struct TreeStyleInvalidator<'a, 'b, E, P: 'a>
@ -75,13 +94,22 @@ where
/// A vector of invalidations, optimized for small invalidation sets.
pub type InvalidationVector<'a> = SmallVec<[Invalidation<'a>; 10]>;
/// The kind of descendant invalidation we're processing.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum DescendantInvalidationKind {
/// A DOM descendant invalidation.
Dom,
/// A ::slotted() descendant invalidation.
Slotted,
}
/// The kind of invalidation we're processing.
///
/// We can use this to avoid pushing invalidations of the same kind to our
/// descendants or siblings.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum InvalidationKind {
Descendant,
Descendant(DescendantInvalidationKind),
Sibling,
}
@ -126,21 +154,27 @@ impl<'a> Invalidation<'a> {
//
// We should be able to do better here!
match self.selector.combinator_at_parse_order(self.offset - 1) {
Combinator::Descendant |
Combinator::LaterSibling |
Combinator::PseudoElement => true,
Combinator::SlotAssignment |
Combinator::NextSibling |
Combinator::Child => false,
_ => true,
}
}
fn kind(&self) -> InvalidationKind {
if self.offset == 0 {
return InvalidationKind::Descendant;
return InvalidationKind::Descendant(DescendantInvalidationKind::Dom);
}
if self.selector.combinator_at_parse_order(self.offset - 1).is_ancestor() {
InvalidationKind::Descendant
} else {
InvalidationKind::Sibling
match self.selector.combinator_at_parse_order(self.offset - 1) {
Combinator::Child |
Combinator::Descendant |
Combinator::PseudoElement => InvalidationKind::Descendant(DescendantInvalidationKind::Dom),
Combinator::SlotAssignment => InvalidationKind::Descendant(DescendantInvalidationKind::Slotted),
Combinator::NextSibling |
Combinator::LaterSibling => InvalidationKind::Sibling,
}
}
}
@ -230,7 +264,7 @@ where
debug!("StyleTreeInvalidator::invalidate({:?})", self.element);
let mut self_invalidations = InvalidationVector::new();
let mut descendant_invalidations = InvalidationVector::new();
let mut descendant_invalidations = DescendantInvalidationLists::default();
let mut sibling_invalidations = InvalidationVector::new();
let mut invalidated_self = self.processor.collect_invalidations(
@ -242,7 +276,7 @@ where
debug!("Collected invalidations (self: {}): ", invalidated_self);
debug!(" > self: {}, {:?}", self_invalidations.len(), self_invalidations);
debug!(" > descendants: {}, {:?}", descendant_invalidations.len(), descendant_invalidations);
debug!(" > descendants: {:?}", descendant_invalidations);
debug!(" > siblings: {}, {:?}", sibling_invalidations.len(), sibling_invalidations);
let invalidated_self_from_collection = invalidated_self;
@ -251,6 +285,7 @@ where
&self_invalidations,
&mut descendant_invalidations,
&mut sibling_invalidations,
DescendantInvalidationKind::Dom,
);
if invalidated_self && !invalidated_self_from_collection {
@ -260,7 +295,11 @@ where
let invalidated_descendants = self.invalidate_descendants(&descendant_invalidations);
let invalidated_siblings = self.invalidate_siblings(&mut sibling_invalidations);
InvalidationResult { invalidated_self, invalidated_descendants, invalidated_siblings }
InvalidationResult {
invalidated_self,
invalidated_descendants,
invalidated_siblings,
}
}
/// Go through later DOM siblings, invalidating style as needed using the
@ -286,7 +325,8 @@ where
self.processor,
);
let mut invalidations_for_descendants = InvalidationVector::new();
let mut invalidations_for_descendants =
DescendantInvalidationLists::default();
let invalidated_sibling =
sibling_invalidator.process_sibling_invalidations(
&mut invalidations_for_descendants,
@ -301,7 +341,7 @@ where
any_invalidated |=
sibling_invalidator.invalidate_descendants(
&invalidations_for_descendants
&invalidations_for_descendants,
);
if sibling_invalidations.is_empty() {
@ -324,7 +364,8 @@ where
let result = self.invalidate_child(
child,
invalidations,
&mut sibling_invalidations
&mut sibling_invalidations,
DescendantInvalidationKind::Dom,
);
// Roots of NAC subtrees can indeed generate sibling invalidations, but
@ -344,8 +385,10 @@ where
child: E,
invalidations: &[Invalidation<'b>],
sibling_invalidations: &mut InvalidationVector<'b>,
descendant_invalidation_kind: DescendantInvalidationKind,
) -> bool {
let mut invalidations_for_descendants = InvalidationVector::new();
let mut invalidations_for_descendants =
DescendantInvalidationLists::default();
let mut invalidated_child = false;
let invalidated_descendants = {
@ -366,13 +409,16 @@ where
invalidations,
&mut invalidations_for_descendants,
sibling_invalidations,
descendant_invalidation_kind,
);
if invalidated_child {
child_invalidator.processor.invalidated_self(child);
}
child_invalidator.invalidate_descendants(&invalidations_for_descendants)
child_invalidator.invalidate_descendants(
&invalidations_for_descendants,
)
};
// The child may not be a flattened tree child of the current element,
@ -418,10 +464,6 @@ where
//
// 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,
@ -431,15 +473,14 @@ where
child,
invalidations,
&mut sibling_invalidations,
DescendantInvalidationKind::Dom,
);
}
any_descendant
}
/// Given a descendant invalidation list, go through the current element's
/// descendants, and invalidate style on them.
fn invalidate_descendants(
fn invalidate_slotted_elements(
&mut self,
invalidations: &[Invalidation<'b>],
) -> bool {
@ -447,6 +488,83 @@ where
return false;
}
let mut any = false;
let mut sibling_invalidations = InvalidationVector::new();
let element = self.element;
for node in element.slotted_nodes() {
let element = match node.as_element() {
Some(e) => e,
None => continue,
};
any |= self.invalidate_child(
element,
invalidations,
&mut sibling_invalidations,
DescendantInvalidationKind::Slotted,
);
debug_assert!(
sibling_invalidations.is_empty(),
"::slotted() shouldn't have sibling combinators to the right, \
this makes no sense! {:?}",
sibling_invalidations
);
}
any
}
fn invalidate_non_slotted_descendants(
&mut self,
invalidations: &[Invalidation<'b>],
) -> bool {
if invalidations.is_empty() {
return false;
}
if self.processor.light_tree_only() {
let node = self.element.as_node();
return self.invalidate_dom_descendants_of(node, invalidations);
}
let mut any_descendant = false;
if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
any_descendant |=
self.invalidate_dom_descendants_of(anon_content, invalidations);
}
if let Some(before) = self.element.before_pseudo_element() {
any_descendant |=
self.invalidate_pseudo_element_or_nac(before, invalidations);
}
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);
any_descendant
}
/// Given the descendant invalidation lists, go through the current
/// element's descendants, and invalidate style on them.
fn invalidate_descendants(
&mut self,
invalidations: &DescendantInvalidationLists<'b>,
) -> bool {
if invalidations.is_empty() {
return false;
}
debug!("StyleTreeInvalidator::invalidate_descendants({:?})",
self.element);
debug!(" > {:?}", invalidations);
@ -465,35 +583,12 @@ where
}
}
if self.processor.light_tree_only() {
let node = self.element.as_node();
return self.invalidate_dom_descendants_of(node, invalidations);
}
let mut any_descendant = false;
if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
any_descendant |=
self.invalidate_dom_descendants_of(anon_content, 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);
}
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);
self.invalidate_non_slotted_descendants(&invalidations.dom_descendants);
any_descendant |=
self.invalidate_slotted_elements(&invalidations.slotted_descendants);
any_descendant
}
@ -511,7 +606,7 @@ where
/// Returns whether invalidated the current element's style.
fn process_sibling_invalidations(
&mut self,
descendant_invalidations: &mut InvalidationVector<'b>,
descendant_invalidations: &mut DescendantInvalidationLists<'b>,
sibling_invalidations: &mut InvalidationVector<'b>,
) -> bool {
let mut i = 0;
@ -547,8 +642,9 @@ where
fn process_descendant_invalidations(
&mut self,
invalidations: &[Invalidation<'b>],
descendant_invalidations: &mut InvalidationVector<'b>,
descendant_invalidations: &mut DescendantInvalidationLists<'b>,
sibling_invalidations: &mut InvalidationVector<'b>,
descendant_invalidation_kind: DescendantInvalidationKind,
) -> bool {
let mut invalidated = false;
@ -557,14 +653,19 @@ where
invalidation,
descendant_invalidations,
sibling_invalidations,
InvalidationKind::Descendant,
InvalidationKind::Descendant(descendant_invalidation_kind),
);
invalidated |= result.invalidated_self;
if invalidation.effective_for_next() {
let mut invalidation = invalidation.clone();
invalidation.matched_by_any_previous |= result.matched;
descendant_invalidations.push(invalidation.clone());
debug_assert_eq!(
descendant_invalidation_kind,
DescendantInvalidationKind::Dom,
"Slotted invalidations don't propagate."
);
descendant_invalidations.dom_descendants.push(invalidation);
}
}
@ -580,7 +681,7 @@ where
fn process_invalidation(
&mut self,
invalidation: &Invalidation<'b>,
descendant_invalidations: &mut InvalidationVector<'b>,
descendant_invalidations: &mut DescendantInvalidationLists<'b>,
sibling_invalidations: &mut InvalidationVector<'b>,
invalidation_kind: InvalidationKind,
) -> SingleInvalidationResult {
@ -732,8 +833,11 @@ where
already been matched before");
} else {
match next_invalidation_kind {
InvalidationKind::Descendant => {
descendant_invalidations.push(next_invalidation);
InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => {
descendant_invalidations.dom_descendants.push(next_invalidation);
}
InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => {
descendant_invalidations.slotted_descendants.push(next_invalidation);
}
InvalidationKind::Sibling => {
sibling_invalidations.push(next_invalidation);