1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
5 //! Style resolution for a given element or pseudo-element.
7 use crate::applicable_declarations::ApplicableDeclarationList;
8 use crate::computed_value_flags::ComputedValueFlags;
9 use crate::context::{CascadeInputs, ElementCascadeInputs, StyleContext};
10 use crate::data::{EagerPseudoStyles, ElementStyles};
11 use crate::dom::TElement;
12 use crate::matching::MatchMethods;
13 use crate::properties::longhands::display::computed_value::T as Display;
14 use crate::properties::{ComputedValues, FirstLineReparenting};
15 use crate::rule_tree::StrongRuleNode;
16 use crate::selector_parser::{PseudoElement, SelectorImpl};
17 use crate::stylist::RuleInclusion;
18 use log::Level::Trace;
19 use selectors::matching::{
20 MatchingForInvalidation, MatchingContext, MatchingMode, NeedsSelectorFlags,
21 RelativeSelectorMatchingState, VisitedHandlingMode,
25 /// Whether pseudo-elements should be resolved or not.
26 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
27 pub enum PseudoElementResolution {
28 /// Only resolve pseudo-styles if possibly applicable.
30 /// Force pseudo-element resolution.
34 /// A struct that takes care of resolving the style of a given element.
35 pub struct StyleResolverForElement<'a, 'ctx, 'le, E>
39 E: TElement + MatchMethods + 'le,
42 context: &'a mut StyleContext<'ctx, E>,
43 rule_inclusion: RuleInclusion,
44 pseudo_resolution: PseudoElementResolution,
45 _marker: ::std::marker::PhantomData<&'le E>,
48 struct MatchingResults {
49 rule_node: StrongRuleNode,
50 flags: ComputedValueFlags,
53 /// A style returned from the resolver machinery.
54 pub struct ResolvedStyle(pub Arc<ComputedValues>);
56 /// The primary style of an element or an element-backed pseudo-element.
57 pub struct PrimaryStyle {
59 pub style: ResolvedStyle,
60 /// Whether the style was reused from another element via the rule node (see
61 /// `StyleSharingCache::lookup_by_rules`).
62 pub reused_via_rule_node: bool,
65 /// A set of style returned from the resolver machinery.
66 pub struct ResolvedElementStyles {
68 pub primary: PrimaryStyle,
70 pub pseudos: EagerPseudoStyles,
73 impl ResolvedElementStyles {
74 /// Convenience accessor for the primary style.
75 pub fn primary_style(&self) -> &Arc<ComputedValues> {
79 /// Convenience mutable accessor for the style.
80 pub fn primary_style_mut(&mut self) -> &mut Arc<ComputedValues> {
81 &mut self.primary.style.0
86 /// Convenience accessor for the style.
87 pub fn style(&self) -> &ComputedValues {
92 impl From<ResolvedElementStyles> for ElementStyles {
93 fn from(r: ResolvedElementStyles) -> ElementStyles {
95 primary: Some(r.primary.style.0),
101 fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
104 F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R,
106 let parent_el = element.inheritance_parent();
107 let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
108 let parent_style = parent_data.as_ref().map(|d| d.styles.primary());
110 let mut layout_parent_el = parent_el.clone();
111 let layout_parent_data;
112 let mut layout_parent_style = parent_style;
113 if parent_style.map_or(false, |s| s.is_display_contents()) {
114 layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
115 layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
116 layout_parent_style = Some(layout_parent_data.styles.primary());
120 parent_style.map(|x| &**x),
121 layout_parent_style.map(|s| &**s),
125 fn layout_parent_style_for_pseudo<'a>(
126 primary_style: &'a PrimaryStyle,
127 layout_parent_style: Option<&'a ComputedValues>,
128 ) -> Option<&'a ComputedValues> {
129 if primary_style.style().is_display_contents() {
132 Some(primary_style.style())
136 fn eager_pseudo_is_definitely_not_generated(
137 pseudo: &PseudoElement,
138 style: &ComputedValues,
140 if !pseudo.is_before_or_after() {
146 .intersects(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE) &&
147 style.get_box().clone_display() == Display::None
154 .intersects(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE) &&
155 style.ineffective_content_property()
163 impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E>
167 E: TElement + MatchMethods + 'le,
169 /// Trivially construct a new StyleResolverForElement.
172 context: &'a mut StyleContext<'ctx, E>,
173 rule_inclusion: RuleInclusion,
174 pseudo_resolution: PseudoElementResolution,
181 _marker: ::std::marker::PhantomData,
185 /// Resolve just the style of a given element.
186 pub fn resolve_primary_style(
188 parent_style: Option<&ComputedValues>,
189 layout_parent_style: Option<&ComputedValues>,
191 let primary_results = self.match_primary(VisitedHandlingMode::AllLinksUnvisited);
193 let inside_link = parent_style.map_or(false, |s| s.visited_style().is_some());
195 let visited_rules = if self.context.shared.visited_styles_enabled &&
196 (inside_link || self.element.is_link())
198 let visited_matching_results =
199 self.match_primary(VisitedHandlingMode::RelevantLinkVisited);
200 Some(visited_matching_results.rule_node)
205 self.cascade_primary_style(
207 rules: Some(primary_results.rule_node),
209 flags: primary_results.flags,
216 fn cascade_primary_style(
218 inputs: CascadeInputs,
219 parent_style: Option<&ComputedValues>,
220 layout_parent_style: Option<&ComputedValues>,
222 // Before doing the cascade, check the sharing cache and see if we can
223 // reuse the style via rule node identity.
224 let may_reuse = self.element.matches_user_and_content_rules() &&
225 parent_style.is_some() &&
226 inputs.rules.is_some();
229 let cached = self.context.thread_local.sharing_cache.lookup_by_rules(
231 parent_style.unwrap(),
232 inputs.rules.as_ref().unwrap(),
233 inputs.visited_rules.as_ref(),
236 if let Some(mut primary_style) = cached {
237 self.context.thread_local.statistics.styles_reused += 1;
238 primary_style.reused_via_rule_node |= true;
239 return primary_style;
243 // No style to reuse. Cascade the style, starting with visited style
246 style: self.cascade_style_and_visited(
252 reused_via_rule_node: false,
256 /// Resolve the style of a given element, and all its eager pseudo-elements.
257 pub fn resolve_style(
259 parent_style: Option<&ComputedValues>,
260 layout_parent_style: Option<&ComputedValues>,
261 ) -> ResolvedElementStyles {
262 let primary_style = self.resolve_primary_style(parent_style, layout_parent_style);
264 let mut pseudo_styles = EagerPseudoStyles::default();
266 if !self.element.is_pseudo_element() {
267 let layout_parent_style_for_pseudo =
268 layout_parent_style_for_pseudo(&primary_style, layout_parent_style);
269 SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
270 let pseudo_style = self.resolve_pseudo_style(
273 layout_parent_style_for_pseudo,
276 if let Some(style) = pseudo_style {
277 if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) &&
278 eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
282 pseudo_styles.set(&pseudo, style.0);
287 ResolvedElementStyles {
288 primary: primary_style,
289 pseudos: pseudo_styles,
293 /// Resolve an element's styles with the default inheritance parent/layout
295 pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles {
296 with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
297 self.resolve_style(parent_style, layout_parent_style)
301 /// Cascade a set of rules, using the default parent for inheritance.
302 pub fn cascade_style_and_visited_with_default_parents(
304 inputs: CascadeInputs,
306 with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
307 self.cascade_style_and_visited(
316 /// Cascade a set of rules for pseudo element, using the default parent for inheritance.
317 pub fn cascade_style_and_visited_for_pseudo_with_default_parents(
319 inputs: CascadeInputs,
320 pseudo: &PseudoElement,
321 primary_style: &PrimaryStyle,
323 with_default_parent_styles(self.element, |_, layout_parent_style| {
324 let layout_parent_style_for_pseudo =
325 layout_parent_style_for_pseudo(primary_style, layout_parent_style);
327 self.cascade_style_and_visited(
329 Some(primary_style.style()),
330 layout_parent_style_for_pseudo,
336 fn cascade_style_and_visited(
338 inputs: CascadeInputs,
339 parent_style: Option<&ComputedValues>,
340 layout_parent_style: Option<&ComputedValues>,
341 pseudo: Option<&PseudoElement>,
343 debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
345 let implemented_pseudo = self.element.implemented_pseudo_element();
346 let pseudo = pseudo.or(implemented_pseudo.as_ref());
348 let mut conditions = Default::default();
349 let values = self.context.shared.stylist.cascade_style_and_visited(
353 &self.context.shared.guards,
354 pseudo.and(parent_style),
357 FirstLineReparenting::No,
358 Some(&self.context.thread_local.rule_cache),
362 self.context.thread_local.rule_cache.insert_if_possible(
363 &self.context.shared.guards,
369 ResolvedStyle(values)
372 /// Cascade the element and pseudo-element styles with the default parents.
373 pub fn cascade_styles_with_default_parents(
375 inputs: ElementCascadeInputs,
376 ) -> ResolvedElementStyles {
377 with_default_parent_styles(self.element, move |parent_style, layout_parent_style| {
379 self.cascade_primary_style(inputs.primary, parent_style, layout_parent_style);
381 let mut pseudo_styles = EagerPseudoStyles::default();
382 if let Some(mut pseudo_array) = inputs.pseudos.into_array() {
383 let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents()
387 Some(primary_style.style())
390 for (i, inputs) in pseudo_array.iter_mut().enumerate() {
391 if let Some(inputs) = inputs.take() {
392 let pseudo = PseudoElement::from_eager_index(i);
394 let style = self.cascade_style_and_visited(
396 Some(primary_style.style()),
397 layout_parent_style_for_pseudo,
401 if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) &&
402 eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
407 pseudo_styles.set(&pseudo, style.0);
412 ResolvedElementStyles {
413 primary: primary_style,
414 pseudos: pseudo_styles,
419 fn resolve_pseudo_style(
421 pseudo: &PseudoElement,
422 originating_element_style: &PrimaryStyle,
423 layout_parent_style: Option<&ComputedValues>,
424 ) -> Option<ResolvedStyle> {
425 let MatchingResults {
428 } = self.match_pseudo(
429 &originating_element_style.style.0,
431 VisitedHandlingMode::AllLinksUnvisited,
434 let mut visited_rules = None;
435 if originating_element_style.style().visited_style().is_some() {
438 &originating_element_style.style.0,
440 VisitedHandlingMode::RelevantLinkVisited,
443 flags |= results.flags;
448 Some(self.cascade_style_and_visited(
450 rules: Some(rule_node),
454 Some(originating_element_style.style()),
460 fn match_primary(&mut self, visited_handling: VisitedHandlingMode) -> MatchingResults {
462 "Match primary for {:?}, visited: {:?}",
463 self.element, visited_handling
465 let mut applicable_declarations = ApplicableDeclarationList::new();
467 let bloom_filter = self.context.thread_local.bloom_filter.filter();
468 let selector_caches = &mut self.context.thread_local.selector_caches;
469 let mut matching_context = MatchingContext::new_for_visited(
470 MatchingMode::Normal,
474 self.context.shared.quirks_mode(),
475 NeedsSelectorFlags::Yes,
476 MatchingForInvalidation::No,
479 let stylist = &self.context.shared.stylist;
480 let implemented_pseudo = self.element.implemented_pseudo_element();
481 // Compute the primary rule node.
482 stylist.push_applicable_declarations(
484 implemented_pseudo.as_ref(),
485 self.element.style_attribute(),
486 self.element.smil_override(),
487 self.element.animation_declarations(self.context.shared),
489 &mut applicable_declarations,
490 &mut matching_context,
493 // FIXME(emilio): This is a hack for animations, and should go away.
494 self.element.unset_dirty_style_attribute();
496 let rule_node = stylist
498 .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
500 if log_enabled!(Trace) {
501 trace!("Matched rules for {:?}:", self.element);
502 for rn in rule_node.self_and_ancestors() {
503 let source = rn.style_source();
504 if source.is_some() {
505 trace!(" > {:?}", source);
509 // This is a bit awkward - ideally, the flag is set directly where `considered_relative_selector`
510 // is; however, in that context, the implementation detail of `extra_data` is not visible, so
511 // it's done here. A trait for manipulating the flags is an option, but not worth it for a single flag.
512 match matching_context.considered_relative_selector {
513 RelativeSelectorMatchingState::None => (),
514 RelativeSelectorMatchingState::Considered => {
518 .insert(ComputedValueFlags::CONSIDERED_RELATIVE_SELECTOR);
520 RelativeSelectorMatchingState::ConsideredAnchor => {
521 matching_context.extra_data.cascade_input_flags.insert(
522 ComputedValueFlags::ANCHORS_RELATIVE_SELECTOR |
523 ComputedValueFlags::CONSIDERED_RELATIVE_SELECTOR,
530 flags: matching_context.extra_data.cascade_input_flags,
536 originating_element_style: &ComputedValues,
537 pseudo_element: &PseudoElement,
538 visited_handling: VisitedHandlingMode,
539 ) -> Option<MatchingResults> {
541 "Match pseudo {:?} for {:?}, visited: {:?}",
542 self.element, pseudo_element, visited_handling
544 debug_assert!(pseudo_element.is_eager());
546 !self.element.is_pseudo_element(),
547 "Element pseudos can't have any other eager pseudo."
550 let mut applicable_declarations = ApplicableDeclarationList::new();
552 let stylist = &self.context.shared.stylist;
556 .may_generate_pseudo(pseudo_element, originating_element_style)
561 let bloom_filter = self.context.thread_local.bloom_filter.filter();
562 let selector_caches = &mut self.context.thread_local.selector_caches;
564 let mut matching_context = MatchingContext::<'_, E::Impl>::new_for_visited(
565 MatchingMode::ForStatelessPseudoElement,
569 self.context.shared.quirks_mode(),
570 NeedsSelectorFlags::Yes,
571 MatchingForInvalidation::No,
573 matching_context.extra_data.originating_element_style = Some(originating_element_style);
575 // NB: We handle animation rules for ::before and ::after when
577 stylist.push_applicable_declarations(
579 Some(pseudo_element),
582 /* animation_declarations = */ Default::default(),
584 &mut applicable_declarations,
585 &mut matching_context,
588 if applicable_declarations.is_empty() {
592 let rule_node = stylist
594 .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
596 Some(MatchingResults {
598 flags: matching_context.extra_data.cascade_input_flags,