Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
//! Style resolution for a given element or pseudo-element.
use crate::applicable_declarations::ApplicableDeclarationList;
use crate::computed_value_flags::ComputedValueFlags;
use crate::context::{CascadeInputs, ElementCascadeInputs, StyleContext};
use crate::data::{EagerPseudoStyles, ElementStyles};
use crate::dom::TElement;
use crate::matching::MatchMethods;
use crate::properties::longhands::display::computed_value::T as Display;
use crate::properties::{ComputedValues, FirstLineReparenting};
use crate::rule_tree::StrongRuleNode;
use crate::selector_parser::{PseudoElement, SelectorImpl};
use crate::stylist::RuleInclusion;
use log::Level::Trace;
use selectors::matching::{
IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode,
NeedsSelectorFlags, VisitedHandlingMode,
};
use servo_arc::Arc;
/// Whether pseudo-elements should be resolved or not.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PseudoElementResolution {
/// Only resolve pseudo-styles if possibly applicable.
IfApplicable,
/// Force pseudo-element resolution.
Force,
}
/// A struct that takes care of resolving the style of a given element.
pub struct StyleResolverForElement<'a, 'ctx, 'le, E>
where
'ctx: 'a,
'le: 'ctx,
E: TElement + MatchMethods + 'le,
{
element: E,
context: &'a mut StyleContext<'ctx, E>,
rule_inclusion: RuleInclusion,
pseudo_resolution: PseudoElementResolution,
_marker: ::std::marker::PhantomData<&'le E>,
}
struct MatchingResults {
rule_node: StrongRuleNode,
flags: ComputedValueFlags,
has_starting_style: bool,
}
/// A style returned from the resolver machinery.
pub struct ResolvedStyle(pub Arc<ComputedValues>);
impl ResolvedStyle {
/// Convenience accessor for the style.
#[inline]
pub fn style(&self) -> &ComputedValues {
&*self.0
}
}
/// The primary style of an element or an element-backed pseudo-element.
pub struct PrimaryStyle {
/// The style itself.
pub style: ResolvedStyle,
/// Whether the style was reused from another element via the rule node (see
/// `StyleSharingCache::lookup_by_rules`).
pub reused_via_rule_node: bool,
/// The element may have matched rules inside @starting-style.
/// Basically, we don't apply @starting-style rules to |style|. This is a sugar to let us know
/// if we should resolve the element again for starting style, which is the after-change style
/// with @starting-style rules applied in addition.
pub may_have_starting_style: bool,
}
/// A set of style returned from the resolver machinery.
pub struct ResolvedElementStyles {
/// Primary style.
pub primary: PrimaryStyle,
/// Pseudo styles.
pub pseudos: EagerPseudoStyles,
}
impl ResolvedElementStyles {
/// Convenience accessor for the primary style.
pub fn primary_style(&self) -> &Arc<ComputedValues> {
&self.primary.style.0
}
/// Convenience mutable accessor for the style.
pub fn primary_style_mut(&mut self) -> &mut Arc<ComputedValues> {
&mut self.primary.style.0
}
/// Returns true if this element may have starting style rules.
#[inline]
pub fn may_have_starting_style(&self) -> bool {
self.primary.may_have_starting_style
}
}
impl PrimaryStyle {
/// Convenience accessor for the style.
pub fn style(&self) -> &ComputedValues {
&*self.style.0
}
}
impl From<ResolvedElementStyles> for ElementStyles {
fn from(r: ResolvedElementStyles) -> ElementStyles {
ElementStyles {
primary: Some(r.primary.style.0),
pseudos: r.pseudos,
}
}
}
fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
where
E: TElement,
F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R,
{
let parent_el = element.inheritance_parent();
let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
let parent_style = parent_data.as_ref().map(|d| d.styles.primary());
let mut layout_parent_el = parent_el.clone();
let layout_parent_data;
let mut layout_parent_style = parent_style;
if parent_style.map_or(false, |s| s.is_display_contents()) {
layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
layout_parent_style = Some(layout_parent_data.styles.primary());
}
f(
parent_style.map(|x| &**x),
layout_parent_style.map(|s| &**s),
)
}
fn layout_parent_style_for_pseudo<'a>(
primary_style: &'a PrimaryStyle,
layout_parent_style: Option<&'a ComputedValues>,
) -> Option<&'a ComputedValues> {
if primary_style.style().is_display_contents() {
layout_parent_style
} else {
Some(primary_style.style())
}
}
fn eager_pseudo_is_definitely_not_generated(
pseudo: &PseudoElement,
style: &ComputedValues,
) -> bool {
if !pseudo.is_before_or_after() {
return false;
}
if !style
.flags
.intersects(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE) &&
style.get_box().clone_display() == Display::None
{
return true;
}
if !style
.flags
.intersects(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE) &&
style.ineffective_content_property()
{
return true;
}
false
}
impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E>
where
'ctx: 'a,
'le: 'ctx,
E: TElement + MatchMethods + 'le,
{
/// Trivially construct a new StyleResolverForElement.
pub fn new(
element: E,
context: &'a mut StyleContext<'ctx, E>,
rule_inclusion: RuleInclusion,
pseudo_resolution: PseudoElementResolution,
) -> Self {
Self {
element,
context,
rule_inclusion,
pseudo_resolution,
_marker: ::std::marker::PhantomData,
}
}
/// Resolve just the style of a given element.
pub fn resolve_primary_style(
&mut self,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
include_starting_style: IncludeStartingStyle,
) -> PrimaryStyle {
let primary_results = self.match_primary(
VisitedHandlingMode::AllLinksUnvisited,
include_starting_style,
);
let inside_link = parent_style.map_or(false, |s| s.visited_style().is_some());
let visited_rules = if self.context.shared.visited_styles_enabled &&
(inside_link || self.element.is_link())
{
let visited_matching_results = self.match_primary(
VisitedHandlingMode::RelevantLinkVisited,
IncludeStartingStyle::No,
);
Some(visited_matching_results.rule_node)
} else {
None
};
self.cascade_primary_style(
CascadeInputs {
rules: Some(primary_results.rule_node),
visited_rules,
flags: primary_results.flags,
},
parent_style,
layout_parent_style,
include_starting_style,
primary_results.has_starting_style,
)
}
fn cascade_primary_style(
&mut self,
inputs: CascadeInputs,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
include_starting_style: IncludeStartingStyle,
may_have_starting_style: bool,
) -> PrimaryStyle {
// Before doing the cascade, check the sharing cache and see if we can
// reuse the style via rule node identity.
let may_reuse = self.element.matches_user_and_content_rules() &&
parent_style.is_some() &&
inputs.rules.is_some() &&
include_starting_style == IncludeStartingStyle::No;
if may_reuse {
let cached = self.context.thread_local.sharing_cache.lookup_by_rules(
self.context.shared,
parent_style.unwrap(),
inputs.rules.as_ref().unwrap(),
inputs.visited_rules.as_ref(),
self.element,
);
if let Some(mut primary_style) = cached {
self.context.thread_local.statistics.styles_reused += 1;
primary_style.reused_via_rule_node |= true;
return primary_style;
}
}
// No style to reuse. Cascade the style, starting with visited style
// if necessary.
PrimaryStyle {
style: self.cascade_style_and_visited(
inputs,
parent_style,
layout_parent_style,
/* pseudo = */ None,
),
reused_via_rule_node: false,
may_have_starting_style,
}
}
/// Resolve the style of a given element, and all its eager pseudo-elements.
pub fn resolve_style(
&mut self,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
) -> ResolvedElementStyles {
let primary_style =
self.resolve_primary_style(parent_style, layout_parent_style, IncludeStartingStyle::No);
let mut pseudo_styles = EagerPseudoStyles::default();
if !self.element.is_pseudo_element() {
let layout_parent_style_for_pseudo =
layout_parent_style_for_pseudo(&primary_style, layout_parent_style);
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
let pseudo_style = self.resolve_pseudo_style(
&pseudo,
&primary_style,
layout_parent_style_for_pseudo,
);
if let Some(style) = pseudo_style {
if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) &&
eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
{
return;
}
pseudo_styles.set(&pseudo, style.0);
}
})
}
ResolvedElementStyles {
primary: primary_style,
pseudos: pseudo_styles,
}
}
/// Resolve an element's styles with the default inheritance parent/layout
/// parents.
pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles {
with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
self.resolve_style(parent_style, layout_parent_style)
})
}
/// Cascade a set of rules, using the default parent for inheritance.
pub fn cascade_style_and_visited_with_default_parents(
&mut self,
inputs: CascadeInputs,
) -> ResolvedStyle {
with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
self.cascade_style_and_visited(
inputs,
parent_style,
layout_parent_style,
/* pseudo = */ None,
)
})
}
/// Cascade a set of rules for pseudo element, using the default parent for inheritance.
pub fn cascade_style_and_visited_for_pseudo_with_default_parents(
&mut self,
inputs: CascadeInputs,
pseudo: &PseudoElement,
primary_style: &PrimaryStyle,
) -> ResolvedStyle {
with_default_parent_styles(self.element, |_, layout_parent_style| {
let layout_parent_style_for_pseudo =
layout_parent_style_for_pseudo(primary_style, layout_parent_style);
self.cascade_style_and_visited(
inputs,
Some(primary_style.style()),
layout_parent_style_for_pseudo,
Some(pseudo),
)
})
}
fn cascade_style_and_visited(
&mut self,
inputs: CascadeInputs,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
pseudo: Option<&PseudoElement>,
) -> ResolvedStyle {
debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
let implemented_pseudo = self.element.implemented_pseudo_element();
let pseudo = pseudo.or(implemented_pseudo.as_ref());
let mut conditions = Default::default();
let values = self.context.shared.stylist.cascade_style_and_visited(
Some(self.element),
pseudo,
inputs,
&self.context.shared.guards,
parent_style,
layout_parent_style,
FirstLineReparenting::No,
Some(&self.context.thread_local.rule_cache),
&mut conditions,
);
self.context.thread_local.rule_cache.insert_if_possible(
&self.context.shared.guards,
&values,
pseudo,
&conditions,
);
ResolvedStyle(values)
}
/// Cascade the element and pseudo-element styles with the default parents.
pub fn cascade_styles_with_default_parents(
&mut self,
inputs: ElementCascadeInputs,
may_have_starting_style: bool,
) -> ResolvedElementStyles {
with_default_parent_styles(self.element, move |parent_style, layout_parent_style| {
let primary_style = self.cascade_primary_style(
inputs.primary,
parent_style,
layout_parent_style,
IncludeStartingStyle::No,
may_have_starting_style,
);
let mut pseudo_styles = EagerPseudoStyles::default();
if let Some(mut pseudo_array) = inputs.pseudos.into_array() {
let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents()
{
layout_parent_style
} else {
Some(primary_style.style())
};
for (i, inputs) in pseudo_array.iter_mut().enumerate() {
if let Some(inputs) = inputs.take() {
let pseudo = PseudoElement::from_eager_index(i);
let style = self.cascade_style_and_visited(
inputs,
Some(primary_style.style()),
layout_parent_style_for_pseudo,
Some(&pseudo),
);
if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) &&
eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
{
continue;
}
pseudo_styles.set(&pseudo, style.0);
}
}
}
ResolvedElementStyles {
primary: primary_style,
pseudos: pseudo_styles,
}
})
}
fn resolve_pseudo_style(
&mut self,
pseudo: &PseudoElement,
originating_element_style: &PrimaryStyle,
layout_parent_style: Option<&ComputedValues>,
) -> Option<ResolvedStyle> {
let MatchingResults {
rule_node,
mut flags,
has_starting_style: _,
} = self.match_pseudo(
&originating_element_style.style.0,
pseudo,
VisitedHandlingMode::AllLinksUnvisited,
)?;
let mut visited_rules = None;
if originating_element_style.style().visited_style().is_some() {
visited_rules = self
.match_pseudo(
&originating_element_style.style.0,
pseudo,
VisitedHandlingMode::RelevantLinkVisited,
)
.map(|results| {
flags |= results.flags;
results.rule_node
});
}
Some(self.cascade_style_and_visited(
CascadeInputs {
rules: Some(rule_node),
visited_rules,
flags,
},
Some(originating_element_style.style()),
layout_parent_style,
Some(pseudo),
))
}
fn match_primary(
&mut self,
visited_handling: VisitedHandlingMode,
include_starting_style: IncludeStartingStyle,
) -> MatchingResults {
debug!(
"Match primary for {:?}, visited: {:?}",
self.element, visited_handling
);
let mut applicable_declarations = ApplicableDeclarationList::new();
let bloom_filter = self.context.thread_local.bloom_filter.filter();
let selector_caches = &mut self.context.thread_local.selector_caches;
let mut matching_context = MatchingContext::new_for_visited(
MatchingMode::Normal,
Some(bloom_filter),
selector_caches,
visited_handling,
include_starting_style,
self.context.shared.quirks_mode(),
NeedsSelectorFlags::Yes,
MatchingForInvalidation::No,
);
let stylist = &self.context.shared.stylist;
let implemented_pseudo = self.element.implemented_pseudo_element();
// Compute the primary rule node.
stylist.push_applicable_declarations(
self.element,
implemented_pseudo.as_ref(),
self.element.style_attribute(),
self.element.smil_override(),
self.element.animation_declarations(self.context.shared),
self.rule_inclusion,
&mut applicable_declarations,
&mut matching_context,
);
// FIXME(emilio): This is a hack for animations, and should go away.
self.element.unset_dirty_style_attribute();
let rule_node = stylist
.rule_tree()
.compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
if log_enabled!(Trace) {
trace!("Matched rules for {:?}:", self.element);
for rn in rule_node.self_and_ancestors() {
let source = rn.style_source();
if source.is_some() {
trace!(" > {:?}", source);
}
}
}
MatchingResults {
rule_node,
flags: matching_context.extra_data.cascade_input_flags,
has_starting_style: matching_context.has_starting_style,
}
}
fn match_pseudo(
&mut self,
originating_element_style: &ComputedValues,
pseudo_element: &PseudoElement,
visited_handling: VisitedHandlingMode,
) -> Option<MatchingResults> {
debug!(
"Match pseudo {:?} for {:?}, visited: {:?}",
self.element, pseudo_element, visited_handling
);
debug_assert!(pseudo_element.is_eager());
debug_assert!(
!self.element.is_pseudo_element(),
"Element pseudos can't have any other eager pseudo."
);
let mut applicable_declarations = ApplicableDeclarationList::new();
let stylist = &self.context.shared.stylist;
if !self
.element
.may_generate_pseudo(pseudo_element, originating_element_style)
{
return None;
}
let bloom_filter = self.context.thread_local.bloom_filter.filter();
let selector_caches = &mut self.context.thread_local.selector_caches;
let mut matching_context = MatchingContext::<'_, E::Impl>::new_for_visited(
MatchingMode::ForStatelessPseudoElement,
Some(bloom_filter),
selector_caches,
visited_handling,
IncludeStartingStyle::No,
self.context.shared.quirks_mode(),
NeedsSelectorFlags::Yes,
MatchingForInvalidation::No,
);
matching_context.extra_data.originating_element_style = Some(originating_element_style);
// NB: We handle animation rules for ::before and ::after when
// traversing them.
stylist.push_applicable_declarations(
self.element,
Some(pseudo_element),
None,
None,
/* animation_declarations = */ Default::default(),
self.rule_inclusion,
&mut applicable_declarations,
&mut matching_context,
);
if applicable_declarations.is_empty() {
return None;
}
let rule_node = stylist
.rule_tree()
.compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
Some(MatchingResults {
rule_node,
flags: matching_context.extra_data.cascade_input_flags,
has_starting_style: false, // We don't care.
})
}
/// Resolve the starting style.
pub fn resolve_starting_style(&mut self) -> PrimaryStyle {
// Compute after-change style for the parent and the layout parent.
// Per spec, starting style inherits from the parent’s after-change style just like
// after-change style does.
let parent_el = self.element.inheritance_parent();
let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
let parent_style = parent_data.as_ref().map(|d| d.styles.primary());
let parent_after_change_style =
parent_style.and_then(|s| self.after_change_style(s));
let parent_values = parent_after_change_style
.as_ref()
.or(parent_style)
.map(|x| &**x);
let mut layout_parent_el = parent_el.clone();
let layout_parent_data;
let layout_parent_after_change_style;
let layout_parent_values = if parent_style.map_or(false, |s| s.is_display_contents()) {
layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
let layout_parent_style = Some(layout_parent_data.styles.primary());
layout_parent_after_change_style =
layout_parent_style.and_then(|s| self.after_change_style(s));
layout_parent_after_change_style
.as_ref()
.or(layout_parent_style)
.map(|x| &**x)
} else {
parent_values
};
self.resolve_primary_style(
parent_values,
layout_parent_values,
IncludeStartingStyle::Yes,
)
}
/// If there is no transition rule in the ComputedValues, it returns None.
pub fn after_change_style(
&mut self,
primary_style: &Arc<ComputedValues>,
) -> Option<Arc<ComputedValues>> {
let rule_node = primary_style.rules();
let without_transition_rules = self.context
.shared
.stylist
.rule_tree()
.remove_transition_rule_if_applicable(rule_node);
if without_transition_rules == *rule_node {
// We don't have transition rule in this case, so return None to let
// the caller use the original ComputedValues.
return None;
}
// well.
let inputs = CascadeInputs {
rules: Some(without_transition_rules),
visited_rules: primary_style.visited_rules().cloned(),
flags: primary_style.flags.for_cascade_inputs(),
};
let style = self.cascade_style_and_visited_with_default_parents(inputs);
Some(style.0)
}
}