use crate::applicable_declarations::ApplicableDeclarationBlock;
use crate::bloom::StyleBloom;
use crate::computed_value_flags::ComputedValueFlags;
use crate::context::{CascadeInputs, SharedStyleContext, StyleContext};
use crate::dom::{SendElement, TElement, TShadowRoot};
use crate::properties::ComputedValues;
use crate::selector_map::RelevantAttributes;
use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
use crate::stylist::Stylist;
use crate::values::AtomIdent;
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use selectors::matching::{NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode};
use smallbitvec::SmallBitVec;
use smallvec::SmallVec;
use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use std::ptr::NonNull;
use uluru::LRUCache;
mod checks;
pub const SHARING_CACHE_SIZE: usize = 32;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpaqueComputedValues(NonNull<()>);
unsafe impl Send for OpaqueComputedValues {}
unsafe impl Sync for OpaqueComputedValues {}
impl OpaqueComputedValues {
fn from(cv: &ComputedValues) -> Self {
let p =
unsafe { NonNull::new_unchecked(cv as *const ComputedValues as *const () as *mut ()) };
OpaqueComputedValues(p)
}
fn eq(&self, cv: &ComputedValues) -> bool {
Self::from(cv) == *self
}
}
#[derive(Debug, Default)]
pub struct RevalidationResult {
pub selectors_matched: SmallBitVec,
pub relevant_attributes: RelevantAttributes,
}
#[derive(Debug, Default, PartialEq)]
pub struct ScopeRevalidationResult {
pub scopes_matched: SmallBitVec,
}
impl PartialEq for RevalidationResult {
fn eq(&self, other: &Self) -> bool {
if self.relevant_attributes != other.relevant_attributes {
return false;
}
debug_assert_eq!(self.selectors_matched.len(), other.selectors_matched.len());
self.selectors_matched == other.selectors_matched
}
}
#[derive(Debug, Default)]
pub struct ValidationData {
class_list: Option<SmallVec<[AtomIdent; 5]>>,
part_list: Option<SmallVec<[AtomIdent; 5]>>,
pres_hints: Option<SmallVec<[ApplicableDeclarationBlock; 5]>>,
parent_style_identity: Option<OpaqueComputedValues>,
revalidation_match_results: Option<RevalidationResult>,
}
impl ValidationData {
pub fn take(&mut self) -> Self {
mem::replace(self, Self::default())
}
pub fn pres_hints<E>(&mut self, element: E) -> &[ApplicableDeclarationBlock]
where
E: TElement,
{
self.pres_hints.get_or_insert_with(|| {
let mut pres_hints = SmallVec::new();
element.synthesize_presentational_hints_for_legacy_attributes(
VisitedHandlingMode::AllLinksUnvisited,
&mut pres_hints,
);
pres_hints
})
}
pub fn part_list<E>(&mut self, element: E) -> &[AtomIdent]
where
E: TElement,
{
if !element.has_part_attr() {
return &[];
}
self.part_list.get_or_insert_with(|| {
let mut list = SmallVec::<[_; 5]>::new();
element.each_part(|p| list.push(p.clone()));
if !list.spilled() {
list.sort_unstable_by_key(|a| a.get_hash());
}
list
})
}
pub fn class_list<E>(&mut self, element: E) -> &[AtomIdent]
where
E: TElement,
{
self.class_list.get_or_insert_with(|| {
let mut list = SmallVec::<[_; 5]>::new();
element.each_class(|c| list.push(c.clone()));
if !list.spilled() {
list.sort_unstable_by_key(|a| a.get_hash());
}
list
})
}
pub fn parent_style_identity<E>(&mut self, el: E) -> OpaqueComputedValues
where
E: TElement,
{
self.parent_style_identity
.get_or_insert_with(|| {
let parent = el.inheritance_parent().unwrap();
let values =
OpaqueComputedValues::from(parent.borrow_data().unwrap().styles.primary());
values
})
.clone()
}
#[inline]
fn revalidation_match_results<E>(
&mut self,
element: E,
stylist: &Stylist,
bloom: &StyleBloom<E>,
selector_caches: &mut SelectorCaches,
bloom_known_valid: bool,
needs_selector_flags: NeedsSelectorFlags,
) -> &RevalidationResult
where
E: TElement,
{
self.revalidation_match_results.get_or_insert_with(|| {
let bloom_to_use = if bloom_known_valid {
debug_assert_eq!(bloom.current_parent(), element.traversal_parent());
Some(bloom.filter())
} else {
if bloom.current_parent() == element.traversal_parent() {
Some(bloom.filter())
} else {
None
}
};
stylist.match_revalidation_selectors(
element,
bloom_to_use,
selector_caches,
needs_selector_flags,
)
})
}
}
#[derive(Debug)]
pub struct StyleSharingCandidate<E: TElement> {
element: E,
validation_data: ValidationData,
considered_nontrivial_scoped_style: bool,
}
struct FakeCandidate {
_element: usize,
_validation_data: ValidationData,
_may_contain_scoped_style: bool,
}
impl<E: TElement> Deref for StyleSharingCandidate<E> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.element
}
}
impl<E: TElement> StyleSharingCandidate<E> {
fn class_list(&mut self) -> &[AtomIdent] {
self.validation_data.class_list(self.element)
}
fn part_list(&mut self) -> &[AtomIdent] {
self.validation_data.part_list(self.element)
}
fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] {
self.validation_data.pres_hints(self.element)
}
fn parent_style_identity(&mut self) -> OpaqueComputedValues {
self.validation_data.parent_style_identity(self.element)
}
fn revalidation_match_results(
&mut self,
stylist: &Stylist,
bloom: &StyleBloom<E>,
selector_caches: &mut SelectorCaches,
) -> &RevalidationResult {
self.validation_data.revalidation_match_results(
self.element,
stylist,
bloom,
selector_caches,
false,
NeedsSelectorFlags::No,
)
}
fn scope_revalidation_results(
&mut self,
stylist: &Stylist,
selector_caches: &mut SelectorCaches,
) -> ScopeRevalidationResult {
stylist.revalidate_scopes(&self.element, selector_caches, NeedsSelectorFlags::No)
}
}
impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<E> {
fn eq(&self, other: &Self) -> bool {
self.element == other.element
}
}
pub struct StyleSharingTarget<E: TElement> {
element: E,
validation_data: ValidationData,
}
impl<E: TElement> Deref for StyleSharingTarget<E> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.element
}
}
impl<E: TElement> StyleSharingTarget<E> {
pub fn new(element: E) -> Self {
Self {
element: element,
validation_data: ValidationData::default(),
}
}
fn class_list(&mut self) -> &[AtomIdent] {
self.validation_data.class_list(self.element)
}
fn part_list(&mut self) -> &[AtomIdent] {
self.validation_data.part_list(self.element)
}
fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] {
self.validation_data.pres_hints(self.element)
}
fn parent_style_identity(&mut self) -> OpaqueComputedValues {
self.validation_data.parent_style_identity(self.element)
}
fn revalidation_match_results(
&mut self,
stylist: &Stylist,
bloom: &StyleBloom<E>,
selector_caches: &mut SelectorCaches,
) -> &RevalidationResult {
self.validation_data.revalidation_match_results(
self.element,
stylist,
bloom,
selector_caches,
true,
NeedsSelectorFlags::Yes,
)
}
fn scope_revalidation_results(
&mut self,
stylist: &Stylist,
selector_caches: &mut SelectorCaches,
) -> ScopeRevalidationResult {
stylist.revalidate_scopes(&self.element, selector_caches, NeedsSelectorFlags::Yes)
}
pub fn share_style_if_possible(
&mut self,
context: &mut StyleContext<E>,
) -> Option<ResolvedElementStyles> {
let cache = &mut context.thread_local.sharing_cache;
let shared_context = &context.shared;
let bloom_filter = &context.thread_local.bloom_filter;
let selector_caches = &mut context.thread_local.selector_caches;
if cache.dom_depth != bloom_filter.matching_depth() {
debug!(
"Can't share style, because DOM depth changed from {:?} to {:?}, element: {:?}",
cache.dom_depth,
bloom_filter.matching_depth(),
self.element
);
return None;
}
debug_assert_eq!(
bloom_filter.current_parent(),
self.element.traversal_parent()
);
cache.share_style_if_possible(shared_context, bloom_filter, selector_caches, self)
}
pub fn take_validation_data(&mut self) -> ValidationData {
self.validation_data.take()
}
}
struct SharingCacheBase<Candidate> {
entries: LRUCache<Candidate, SHARING_CACHE_SIZE>,
}
impl<Candidate> Default for SharingCacheBase<Candidate> {
fn default() -> Self {
Self {
entries: LRUCache::default(),
}
}
}
impl<Candidate> SharingCacheBase<Candidate> {
fn clear(&mut self) {
self.entries.clear();
}
fn is_empty(&self) -> bool {
self.entries.len() == 0
}
}
impl<E: TElement> SharingCache<E> {
fn insert(
&mut self,
element: E,
validation_data_holder: Option<&mut StyleSharingTarget<E>>,
considered_nontrivial_scoped_style: bool,
) {
let validation_data = match validation_data_holder {
Some(v) => v.take_validation_data(),
None => ValidationData::default(),
};
self.entries.insert(StyleSharingCandidate {
element,
validation_data,
considered_nontrivial_scoped_style,
});
}
}
type SharingCache<E> = SharingCacheBase<StyleSharingCandidate<E>>;
type TypelessSharingCache = SharingCacheBase<FakeCandidate>;
thread_local! {
static SHARING_CACHE_KEY: &'static AtomicRefCell<TypelessSharingCache> =
Box::leak(Default::default());
}
pub struct StyleSharingCache<E: TElement> {
cache_typeless: AtomicRefMut<'static, TypelessSharingCache>,
marker: PhantomData<SendElement<E>>,
dom_depth: usize,
}
impl<E: TElement> Drop for StyleSharingCache<E> {
fn drop(&mut self) {
self.clear();
}
}
impl<E: TElement> StyleSharingCache<E> {
#[allow(dead_code)]
fn cache(&self) -> &SharingCache<E> {
let base: &TypelessSharingCache = &*self.cache_typeless;
unsafe { mem::transmute(base) }
}
fn cache_mut(&mut self) -> &mut SharingCache<E> {
let base: &mut TypelessSharingCache = &mut *self.cache_typeless;
unsafe { mem::transmute(base) }
}
#[inline(never)]
pub fn new() -> Self {
assert_eq!(
mem::size_of::<SharingCache<E>>(),
mem::size_of::<TypelessSharingCache>()
);
assert_eq!(
mem::align_of::<SharingCache<E>>(),
mem::align_of::<TypelessSharingCache>()
);
let cache = SHARING_CACHE_KEY.with(|c| c.borrow_mut());
debug_assert!(cache.is_empty());
StyleSharingCache {
cache_typeless: cache,
marker: PhantomData,
dom_depth: 0,
}
}
pub fn insert_if_possible(
&mut self,
element: &E,
style: &PrimaryStyle,
validation_data_holder: Option<&mut StyleSharingTarget<E>>,
dom_depth: usize,
shared_context: &SharedStyleContext,
) {
let parent = match element.traversal_parent() {
Some(element) => element,
None => {
debug!("Failing to insert to the cache: no parent element");
return;
},
};
if !element.matches_user_and_content_rules() {
debug!("Failing to insert into the cache: no tree rules:");
return;
}
if element.has_animations(shared_context) {
debug!("Failing to insert to the cache: running animations");
return;
}
if element.smil_override().is_some() {
debug!("Failing to insert to the cache: SMIL");
return;
}
debug!(
"Inserting into cache: {:?} with parent {:?}",
element, parent
);
if self.dom_depth != dom_depth {
debug!(
"Clearing cache because depth changed from {:?} to {:?}, element: {:?}",
self.dom_depth, dom_depth, element
);
self.clear();
self.dom_depth = dom_depth;
}
self.cache_mut().insert(
*element,
validation_data_holder,
style
.style()
.flags
.intersects(ComputedValueFlags::CONSIDERED_NONTRIVIAL_SCOPED_STYLE),
);
}
pub fn clear(&mut self) {
self.cache_mut().clear();
}
fn share_style_if_possible(
&mut self,
shared_context: &SharedStyleContext,
bloom_filter: &StyleBloom<E>,
selector_caches: &mut SelectorCaches,
target: &mut StyleSharingTarget<E>,
) -> Option<ResolvedElementStyles> {
if shared_context.options.disable_style_sharing_cache {
debug!(
"{:?} Cannot share style: style sharing cache disabled",
target.element
);
return None;
}
if target.inheritance_parent().is_none() {
debug!(
"{:?} Cannot share style: element has no parent",
target.element
);
return None;
}
if !target.matches_user_and_content_rules() {
debug!("{:?} Cannot share style: content rules", target.element);
return None;
}
self.cache_mut().entries.lookup(|candidate| {
Self::test_candidate(
target,
candidate,
&shared_context,
bloom_filter,
selector_caches,
shared_context,
)
})
}
fn test_candidate(
target: &mut StyleSharingTarget<E>,
candidate: &mut StyleSharingCandidate<E>,
shared: &SharedStyleContext,
bloom: &StyleBloom<E>,
selector_caches: &mut SelectorCaches,
shared_context: &SharedStyleContext,
) -> Option<ResolvedElementStyles> {
debug_assert!(target.matches_user_and_content_rules());
if !checks::parents_allow_sharing(target, candidate) {
trace!("Miss: Parent");
return None;
}
if target.local_name() != candidate.element.local_name() {
trace!("Miss: Local Name");
return None;
}
if target.namespace() != candidate.element.namespace() {
trace!("Miss: Namespace");
return None;
}
if target.element.state() != candidate.state() {
trace!("Miss: User and Author State");
return None;
}
if target.is_link() != candidate.element.is_link() {
trace!("Miss: Link");
return None;
}
if target.element.containing_shadow() != candidate.element.containing_shadow() {
trace!("Miss: Different containing shadow roots");
return None;
}
if target.element.assigned_slot() != candidate.element.assigned_slot() {
trace!("Miss: Different assigned slots");
return None;
}
if target.implemented_pseudo_element() != candidate.implemented_pseudo_element() {
trace!("Miss: Element backed pseudo-element");
return None;
}
match (
target.element.shadow_root().and_then(|s| s.style_data()),
candidate.element.shadow_root().and_then(|s| s.style_data()),
) {
(Some(td), Some(cd)) if std::ptr::eq(td, cd) => {},
(None, None) => {},
_ => {
trace!("Miss: Different shadow root style data");
return None;
},
}
if target.element.has_animations(shared_context)
|| candidate.element.has_animations(shared_context)
{
trace!("Miss: Has Animations");
return None;
}
if target.element.smil_override().is_some() {
trace!("Miss: SMIL");
return None;
}
if target.matches_user_and_content_rules()
!= candidate.element.matches_user_and_content_rules()
{
trace!("Miss: User and Author Rules");
return None;
}
if checks::may_match_different_id_rules(shared, target.element, candidate.element) {
trace!("Miss: ID Attr");
return None;
}
if !checks::have_same_style_attribute(target, candidate, shared_context) {
trace!("Miss: Style Attr");
return None;
}
if !checks::have_same_class(target, candidate) {
trace!("Miss: Class");
return None;
}
if !checks::have_same_presentational_hints(target, candidate) {
trace!("Miss: Pres Hints");
return None;
}
if !checks::have_same_parts(target, candidate) {
trace!("Miss: Shadow parts");
return None;
}
if !checks::have_same_referenced_attrs(target, candidate) {
trace!("Miss: Attr references");
return None;
}
if !checks::revalidate(target, candidate, shared, bloom, selector_caches) {
trace!("Miss: Revalidation");
return None;
}
if candidate.considered_nontrivial_scoped_style
&& !checks::revalidate_scope(target, candidate, shared, selector_caches)
{
trace!("Miss: Active Scopes");
return None;
}
debug!(
"Sharing allowed between {:?} and {:?}",
target.element, candidate.element
);
Some(candidate.element.borrow_data().unwrap().share_styles())
}
pub fn lookup_by_rules(
&mut self,
shared_context: &SharedStyleContext,
inherited: &ComputedValues,
inputs: &CascadeInputs,
target: E,
) -> Option<PrimaryStyle> {
if shared_context.options.disable_style_sharing_cache {
return None;
}
self.cache_mut().entries.lookup(|candidate| {
debug_assert_ne!(candidate.element, target);
if !candidate.parent_style_identity().eq(inherited) {
return None;
}
if !checks::have_same_referenced_attrs(&StyleSharingTarget::new(target), candidate) {
return None;
}
let data = candidate.element.borrow_data().unwrap();
let style = data.styles.primary();
if style.rules.as_ref() != Some(&inputs.rules.as_ref().unwrap()) {
return None;
}
if style.visited_rules() != inputs.visited_rules.as_ref() {
return None;
}
if target.namespace() != candidate.element.namespace()
|| target.local_name() != candidate.element.local_name()
{
return None;
}
if data
.styles
.primary()
.flags
.intersects(ComputedValueFlags::USES_CONTAINER_UNITS)
&& candidate.element.traversal_parent() != target.traversal_parent()
{
return None;
}
if target.is_link() || candidate.element.is_link() {
return None;
}
let target_depends_on_style_queries = inputs
.flags
.contains(ComputedValueFlags::DEPENDS_ON_CONTAINER_STYLE_QUERY);
let candidate_depends_on_style_queries = style
.flags
.contains(ComputedValueFlags::DEPENDS_ON_CONTAINER_STYLE_QUERY);
if target_depends_on_style_queries != candidate_depends_on_style_queries {
let mut new_flags = inputs.flags | style.flags;
new_flags.set(
ComputedValueFlags::DEPENDS_ON_CONTAINER_STYLE_QUERY,
target_depends_on_style_queries,
);
return Some(PrimaryStyle {
style: data.clone_style_with_flags(new_flags),
reused_via_rule_node: true,
});
}
Some(data.share_primary_style())
})
}
}