style: Make style sharing look at XBL / Shadow DOM rules.

Bug: 1412251
Reviewed-by: bz
MozReview-Commit-ID: II6lk6OmSZU
This commit is contained in:
Emilio Cobos Álvarez 2017-10-28 01:48:08 +02:00
parent 76a71996f4
commit 0df912be93
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
5 changed files with 148 additions and 47 deletions

View file

@ -388,6 +388,15 @@ pub trait TElement
depth depth
} }
/// The style scope of this element is a node that represents which rules
/// apply to the element.
///
/// In Servo, where we don't know about Shadow DOM or XBL, the style scope
/// is always the document.
fn style_scope(&self) -> Self::ConcreteNode {
self.as_node().owner_doc().as_node()
}
/// Get this node's parent element from the perspective of a restyle /// Get this node's parent element from the perspective of a restyle
/// traversal. /// traversal.
fn traversal_parent(&self) -> Option<Self> { fn traversal_parent(&self) -> Option<Self> {
@ -738,14 +747,16 @@ pub trait TElement
None None
} }
/// Returns the rule hash target given an element. /// Return the element which we can use to look up rules in the selector
/// maps.
///
/// This is always the element itself, except in the case where we are an
/// element-backed pseudo-element, in which case we return the originating
/// element.
fn rule_hash_target(&self) -> Self { fn rule_hash_target(&self) -> Self {
let is_implemented_pseudo = let is_implemented_pseudo =
self.implemented_pseudo_element().is_some(); self.implemented_pseudo_element().is_some();
// NB: This causes use to rule has pseudo selectors based on the
// properties of the originating element (which is fine, given the
// find_first_from_right usage).
if is_implemented_pseudo { if is_implemented_pseudo {
self.closest_non_native_anonymous_ancestor().unwrap() self.closest_non_native_anonymous_ancestor().unwrap()
} else { } else {

View file

@ -518,9 +518,14 @@ impl<'le> GeckoElement<'le> {
.and_then(|s| unsafe { s.mExtendedSlots.mPtr.as_ref() }) .and_then(|s| unsafe { s.mExtendedSlots.mPtr.as_ref() })
} }
#[inline]
fn may_be_in_binding_manager(&self) -> bool {
self.flags() & (structs::NODE_MAY_BE_IN_BINDING_MNGR as u32) != 0
}
#[inline] #[inline]
fn get_xbl_binding(&self) -> Option<GeckoXBLBinding<'le>> { fn get_xbl_binding(&self) -> Option<GeckoXBLBinding<'le>> {
if self.flags() & (structs::NODE_MAY_BE_IN_BINDING_MNGR as u32) == 0 { if !self.may_be_in_binding_manager() {
return None; return None;
} }
@ -553,9 +558,11 @@ impl<'le> GeckoElement<'le> {
} else { } else {
let binding_parent = unsafe { let binding_parent = unsafe {
self.get_non_xul_xbl_binding_parent_raw_content().as_ref() self.get_non_xul_xbl_binding_parent_raw_content().as_ref()
}.map(GeckoNode::from_content) }.map(GeckoNode::from_content).and_then(|n| n.as_element());
.and_then(|n| n.as_element());
debug_assert!(binding_parent == unsafe { bindings::Gecko_GetBindingParent(self.0).map(GeckoElement) }); debug_assert!(binding_parent == unsafe {
bindings::Gecko_GetBindingParent(self.0).map(GeckoElement)
});
binding_parent binding_parent
} }
} }
@ -925,6 +932,28 @@ impl<'le> TElement for GeckoElement<'le> {
self.get_before_or_after_pseudo(/* is_before = */ false) self.get_before_or_after_pseudo(/* is_before = */ false)
} }
/// Ensure this accurately represents the rules that an element may ever
/// match, even in the native anonymous content case.
fn style_scope(&self) -> Self::ConcreteNode {
if self.implemented_pseudo_element().is_some() {
return self.closest_non_native_anonymous_ancestor().unwrap().style_scope();
}
if self.is_in_native_anonymous_subtree() {
return self.as_node().owner_doc().as_node();
}
if self.get_xbl_binding().is_some() {
return self.as_node();
}
if let Some(parent) = self.get_xbl_binding_parent() {
return parent.as_node();
}
self.as_node().owner_doc().as_node()
}
/// Execute `f` for each anonymous content child element (apart from /// Execute `f` for each anonymous content child element (apart from
/// ::before and ::after) whose originating element is `self`. /// ::before and ::after) whose originating element is `self`.
fn each_anonymous_content_child<F>(&self, mut f: F) fn each_anonymous_content_child<F>(&self, mut f: F)

View file

@ -6,7 +6,6 @@
//! quickly whether it's worth to share style, and whether two different //! quickly whether it's worth to share style, and whether two different
//! elements can indeed share the same style. //! elements can indeed share the same style.
use Atom;
use bloom::StyleBloom; use bloom::StyleBloom;
use context::{SelectorFlagsMap, SharedStyleContext}; use context::{SelectorFlagsMap, SharedStyleContext};
use dom::TElement; use dom::TElement;
@ -96,14 +95,16 @@ pub fn have_same_class<E>(target: &mut StyleSharingTarget<E>,
/// :first-child, etc, or on attributes that we don't check off-hand (pretty /// :first-child, etc, or on attributes that we don't check off-hand (pretty
/// much every attribute selector except `id` and `class`. /// much every attribute selector except `id` and `class`.
#[inline] #[inline]
pub fn revalidate<E>(target: &mut StyleSharingTarget<E>, pub fn revalidate<E>(
target: &mut StyleSharingTarget<E>,
candidate: &mut StyleSharingCandidate<E>, candidate: &mut StyleSharingCandidate<E>,
shared_context: &SharedStyleContext, shared_context: &SharedStyleContext,
bloom: &StyleBloom<E>, bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache, nth_index_cache: &mut NthIndexCache,
selector_flags_map: &mut SelectorFlagsMap<E>) selector_flags_map: &mut SelectorFlagsMap<E>,
-> bool ) -> bool
where E: TElement, where
E: TElement,
{ {
let stylist = &shared_context.stylist; let stylist = &shared_context.stylist;
@ -130,16 +131,25 @@ pub fn revalidate<E>(target: &mut StyleSharingTarget<E>,
/// Checks whether we might have rules for either of the two ids. /// Checks whether we might have rules for either of the two ids.
#[inline] #[inline]
pub fn may_have_rules_for_ids(shared_context: &SharedStyleContext, pub fn may_match_different_id_rules<E>(
element_id: Option<&Atom>, shared_context: &SharedStyleContext,
candidate_id: Option<&Atom>) -> bool element: E,
candidate: E,
) -> bool
where
E: TElement,
{ {
// We shouldn't be called unless the ids are different. let element_id = element.get_id();
debug_assert!(element_id.is_some() || candidate_id.is_some()); let candidate_id = candidate.get_id();
if element_id == candidate_id {
return false;
}
let stylist = &shared_context.stylist; let stylist = &shared_context.stylist;
let may_have_rules_for_element = match element_id { let may_have_rules_for_element = match element_id {
Some(id) => stylist.may_have_rules_for_id(id), Some(ref id) => stylist.may_have_rules_for_id(id, element),
None => false None => false
}; };
@ -148,7 +158,7 @@ pub fn may_have_rules_for_ids(shared_context: &SharedStyleContext,
} }
match candidate_id { match candidate_id {
Some(id) => stylist.may_have_rules_for_id(id), Some(ref id) => stylist.may_have_rules_for_id(id, candidate),
None => false None => false
} }
} }

View file

@ -206,9 +206,10 @@ impl ValidationData {
bloom: &StyleBloom<E>, bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache, nth_index_cache: &mut NthIndexCache,
bloom_known_valid: bool, bloom_known_valid: bool,
flags_setter: &mut F flags_setter: &mut F,
) -> &SmallBitVec ) -> &SmallBitVec
where E: TElement, where
E: TElement,
F: FnMut(&E, ElementSelectorFlags), F: FnMut(&E, ElementSelectorFlags),
{ {
if self.revalidation_match_results.is_none() { if self.revalidation_match_results.is_none() {
@ -230,10 +231,12 @@ impl ValidationData {
} }
}; };
self.revalidation_match_results = self.revalidation_match_results =
Some(stylist.match_revalidation_selectors(&element, Some(stylist.match_revalidation_selectors(
element,
bloom_to_use, bloom_to_use,
nth_index_cache, nth_index_cache,
flags_setter)); flags_setter,
));
} }
self.revalidation_match_results.as_ref().unwrap() self.revalidation_match_results.as_ref().unwrap()
@ -661,6 +664,15 @@ impl<E: TElement> StyleSharingCache<E> {
return None; return None;
} }
// Note that in the XBL case, we should be able to assert that the
// scopes are different, since two elements with different XBL bindings
// need to necessarily have different style (and thus children of them
// would never pass the parent check).
if target.element.style_scope() != candidate.element.style_scope() {
trace!("Miss: Different style scopes");
return None;
}
if *target.get_local_name() != *candidate.element.get_local_name() { if *target.get_local_name() != *candidate.element.get_local_name() {
trace!("Miss: Local Name"); trace!("Miss: Local Name");
return None; return None;
@ -690,16 +702,18 @@ impl<E: TElement> StyleSharingCache<E> {
return None; return None;
} }
let element_id = target.element.get_id();
let candidate_id = candidate.element.get_id();
if element_id != candidate_id {
// It's possible that there are no styles for either id. // It's possible that there are no styles for either id.
if checks::may_have_rules_for_ids(shared, element_id.as_ref(), let may_match_different_id_rules =
candidate_id.as_ref()) { checks::may_match_different_id_rules(
shared,
target.element,
candidate.element,
);
if may_match_different_id_rules {
trace!("Miss: ID Attr"); trace!("Miss: ID Attr");
return None; return None;
} }
}
if !checks::have_same_style_attribute(target, candidate) { if !checks::have_same_style_attribute(target, candidate) {
trace!("Miss: Style Attr"); trace!("Miss: Style Attr");

View file

@ -1409,10 +1409,29 @@ impl Stylist {
/// Given an id, returns whether there might be any rules for that id in any /// Given an id, returns whether there might be any rules for that id in any
/// of our rule maps. /// of our rule maps.
#[inline] #[inline]
pub fn may_have_rules_for_id(&self, id: &Atom) -> bool { pub fn may_have_rules_for_id<E>(
self.cascade_data &self,
.iter_origins() id: &Atom,
.any(|(d, _)| d.mapped_ids.might_contain_hash(id.get_hash())) element: E,
) -> bool
where
E: TElement,
{
let hash = id.get_hash();
for (data, _) in self.cascade_data.iter_origins() {
if data.mapped_ids.might_contain_hash(hash) {
return true;
}
}
let mut xbl_rules_may_contain = false;
element.each_xbl_stylist(|stylist| {
xbl_rules_may_contain = xbl_rules_may_contain ||
stylist.cascade_data.author.mapped_ids.might_contain_hash(hash)
});
xbl_rules_may_contain
} }
/// Returns the registered `@keyframes` animation for the specified name. /// Returns the registered `@keyframes` animation for the specified name.
@ -1428,7 +1447,7 @@ impl Stylist {
/// revalidation selectors. /// revalidation selectors.
pub fn match_revalidation_selectors<E, F>( pub fn match_revalidation_selectors<E, F>(
&self, &self,
element: &E, element: E,
bloom: Option<&BloomFilter>, bloom: Option<&BloomFilter>,
nth_index_cache: &mut NthIndexCache, nth_index_cache: &mut NthIndexCache,
flags_setter: &mut F flags_setter: &mut F
@ -1454,14 +1473,14 @@ impl Stylist {
let mut results = SmallBitVec::new(); let mut results = SmallBitVec::new();
for (data, _) in self.cascade_data.iter_origins() { for (data, _) in self.cascade_data.iter_origins() {
data.selectors_for_cache_revalidation.lookup( data.selectors_for_cache_revalidation.lookup(
*element, element,
self.quirks_mode, self.quirks_mode,
&mut |selector_and_hashes| { &mut |selector_and_hashes| {
results.push(matches_selector( results.push(matches_selector(
&selector_and_hashes.selector, &selector_and_hashes.selector,
selector_and_hashes.selector_offset, selector_and_hashes.selector_offset,
Some(&selector_and_hashes.hashes), Some(&selector_and_hashes.hashes),
element, &element,
&mut matching_context, &mut matching_context,
flags_setter flags_setter
)); ));
@ -1470,6 +1489,24 @@ impl Stylist {
); );
} }
element.each_xbl_stylist(|stylist| {
stylist.cascade_data.author.selectors_for_cache_revalidation.lookup(
element,
stylist.quirks_mode,
&mut |selector_and_hashes| {
results.push(matches_selector(
&selector_and_hashes.selector,
selector_and_hashes.selector_offset,
Some(&selector_and_hashes.hashes),
&element,
&mut matching_context,
flags_setter
));
true
}
);
});
results results
} }