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 //! The main cascading algorithm of the style system.
7 use crate::applicable_declarations::CascadePriority;
8 use crate::color::AbsoluteColor;
9 use crate::computed_value_flags::ComputedValueFlags;
10 use crate::custom_properties::{
11 CustomPropertiesBuilder, DeferFontRelativeCustomPropertyResolution,
13 use crate::dom::TElement;
14 use crate::font_metrics::FontMetricsOrientation;
15 use crate::logical_geometry::WritingMode;
16 use crate::properties::{
17 property_counts, CSSWideKeyword, ComputedValues, DeclarationImportanceIterator, Importance,
18 LonghandId, LonghandIdSet, PrioritaryPropertyId, PropertyDeclaration, PropertyDeclarationId,
19 PropertyFlags, ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY,
21 use crate::rule_cache::{RuleCache, RuleCacheConditions};
22 use crate::rule_tree::{CascadeLevel, StrongRuleNode};
23 use crate::selector_parser::PseudoElement;
24 use crate::shared_lock::StylesheetGuards;
25 use crate::style_adjuster::StyleAdjuster;
26 use crate::stylesheets::container_rule::ContainerSizeQuery;
27 use crate::stylesheets::{layer_rule::LayerOrder, Origin};
28 use crate::stylist::Stylist;
29 use crate::values::specified::length::FontBaseSize;
30 use crate::values::{computed, specified};
31 use fxhash::FxHashMap;
33 use smallvec::SmallVec;
37 /// Whether we're resolving a style with the purposes of reparenting for ::first-line.
38 #[derive(Copy, Clone)]
39 #[allow(missing_docs)]
40 pub enum FirstLineReparenting<'a> {
43 /// The style we're re-parenting for ::first-line. ::first-line only affects inherited
44 /// properties so we use this to avoid some work and also ensure correctness by copying the
45 /// reset structs from this style.
46 style_to_reparent: &'a ComputedValues,
50 /// Performs the CSS cascade, computing new styles for an element from its parent style.
52 /// The arguments are:
54 /// * `device`: Used to get the initial viewport and other external state.
56 /// * `rule_node`: The rule node in the tree that represent the CSS rules that
59 /// * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
61 /// Returns the computed values.
62 /// * `flags`: Various flags.
66 pseudo: Option<&PseudoElement>,
67 rule_node: &StrongRuleNode,
68 guards: &StylesheetGuards,
69 parent_style: Option<&ComputedValues>,
70 layout_parent_style: Option<&ComputedValues>,
71 first_line_reparenting: FirstLineReparenting,
72 visited_rules: Option<&StrongRuleNode>,
73 cascade_input_flags: ComputedValueFlags,
74 rule_cache: Option<&RuleCache>,
75 rule_cache_conditions: &mut RuleCacheConditions,
77 ) -> Arc<ComputedValues>
88 first_line_reparenting,
89 CascadeMode::Unvisited { visited_rules },
92 rule_cache_conditions,
97 struct DeclarationIterator<'a> {
98 // Global to the iteration.
99 guards: &'a StylesheetGuards<'a>,
100 restriction: Option<PropertyFlags>,
101 // The rule we're iterating over.
102 current_rule_node: Option<&'a StrongRuleNode>,
104 declarations: DeclarationImportanceIterator<'a>,
106 importance: Importance,
107 priority: CascadePriority,
110 impl<'a> DeclarationIterator<'a> {
113 rule_node: &'a StrongRuleNode,
114 guards: &'a StylesheetGuards,
115 pseudo: Option<&PseudoElement>,
117 let restriction = pseudo.and_then(|p| p.property_restriction());
118 let mut iter = Self {
120 current_rule_node: Some(rule_node),
121 origin: Origin::UserAgent,
122 importance: Importance::Normal,
123 priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()),
124 declarations: DeclarationImportanceIterator::default(),
127 iter.update_for_node(rule_node);
131 fn update_for_node(&mut self, node: &'a StrongRuleNode) {
132 self.priority = node.cascade_priority();
133 let level = self.priority.cascade_level();
134 self.origin = level.origin();
135 self.importance = level.importance();
136 let guard = match self.origin {
137 Origin::Author => self.guards.author,
138 Origin::User | Origin::UserAgent => self.guards.ua_or_user,
140 self.declarations = match node.style_source() {
141 Some(source) => source.read(guard).declaration_importance_iter(),
142 None => DeclarationImportanceIterator::default(),
147 impl<'a> Iterator for DeclarationIterator<'a> {
148 type Item = (&'a PropertyDeclaration, CascadePriority);
151 fn next(&mut self) -> Option<Self::Item> {
153 if let Some((decl, importance)) = self.declarations.next_back() {
154 if self.importance != importance {
158 if let Some(restriction) = self.restriction {
159 // decl.id() is either a longhand or a custom
160 // property. Custom properties are always allowed, but
161 // longhands are only allowed if they have our
162 // restriction flag set.
163 if let PropertyDeclarationId::Longhand(id) = decl.id() {
164 if !id.flags().contains(restriction) && self.origin != Origin::UserAgent {
170 return Some((decl, self.priority));
173 let next_node = self.current_rule_node.take()?.parent()?;
174 self.current_rule_node = Some(next_node);
175 self.update_for_node(next_node);
182 pseudo: Option<&PseudoElement>,
183 rule_node: &StrongRuleNode,
184 guards: &StylesheetGuards,
185 parent_style: Option<&ComputedValues>,
186 layout_parent_style: Option<&ComputedValues>,
187 first_line_reparenting: FirstLineReparenting,
188 cascade_mode: CascadeMode,
189 cascade_input_flags: ComputedValueFlags,
190 rule_cache: Option<&RuleCache>,
191 rule_cache_conditions: &mut RuleCacheConditions,
193 ) -> Arc<ComputedValues>
202 DeclarationIterator::new(rule_node, guards, pseudo),
205 first_line_reparenting,
209 rule_cache_conditions,
214 /// Whether we're cascading for visited or unvisited styles.
215 #[derive(Clone, Copy)]
216 pub enum CascadeMode<'a, 'b> {
217 /// We're cascading for unvisited styles.
219 /// The visited rules that should match the visited style.
220 visited_rules: Option<&'a StrongRuleNode>,
222 /// We're cascading for visited styles.
224 /// The cascade for our unvisited style.
225 unvisited_context: &'a computed::Context<'b>,
229 fn iter_declarations<'builder, 'decls: 'builder>(
230 iter: impl Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
231 declarations: &mut Declarations<'decls>,
232 mut custom_builder: Option<&mut CustomPropertiesBuilder<'builder, 'decls>>,
234 for (declaration, priority) in iter {
235 if let PropertyDeclaration::Custom(ref declaration) = *declaration {
236 if let Some(ref mut builder) = custom_builder {
237 builder.cascade(declaration, priority);
240 let id = declaration.id().as_longhand().unwrap();
241 declarations.note_declaration(declaration, priority, id);
242 if let Some(ref mut builder) = custom_builder {
243 if let PropertyDeclaration::WithVariables(ref v) = declaration {
244 builder.note_potentially_cyclic_non_custom_dependency(id, v);
251 /// NOTE: This function expects the declaration with more priority to appear
253 pub fn apply_declarations<'a, E, I>(
254 stylist: &'a Stylist,
255 pseudo: Option<&'a PseudoElement>,
256 rules: &StrongRuleNode,
257 guards: &StylesheetGuards,
259 parent_style: Option<&'a ComputedValues>,
260 layout_parent_style: Option<&ComputedValues>,
261 first_line_reparenting: FirstLineReparenting<'a>,
262 cascade_mode: CascadeMode,
263 cascade_input_flags: ComputedValueFlags,
264 rule_cache: Option<&'a RuleCache>,
265 rule_cache_conditions: &'a mut RuleCacheConditions,
267 ) -> Arc<ComputedValues>
270 I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>,
272 debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
273 let device = stylist.device();
274 let inherited_style = parent_style.unwrap_or(device.default_computed_values());
275 let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root());
277 let container_size_query =
278 ContainerSizeQuery::for_option_element(element, Some(inherited_style), pseudo.is_some());
280 let mut context = computed::Context::new(
281 // We'd really like to own the rules here to avoid refcount traffic, but
282 // animation's usage of `apply_declarations` make this tricky. See bug
292 stylist.quirks_mode(),
293 rule_cache_conditions,
294 container_size_query,
297 context.style().add_flags(cascade_input_flags);
299 let using_cached_reset_properties;
300 let ignore_colors = !context.builder.device.use_document_colors();
301 let mut cascade = Cascade::new(first_line_reparenting, ignore_colors);
302 let mut declarations = Default::default();
303 let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
304 let properties_to_apply = match cascade_mode {
305 CascadeMode::Visited { unvisited_context } => {
306 context.builder.custom_properties = unvisited_context.builder.custom_properties.clone();
307 context.builder.writing_mode = unvisited_context.builder.writing_mode;
308 // We never insert visited styles into the cache so we don't need to try looking it up.
309 // It also wouldn't be super-profitable, only a handful :visited properties are
311 using_cached_reset_properties = false;
312 // TODO(bug 1859385): If we match the same rules when visited and unvisited, we could
313 // try to avoid gathering the declarations. That'd be:
314 // unvisited_context.builder.rules.as_ref() == Some(rules)
315 iter_declarations(iter, &mut declarations, None);
317 LonghandIdSet::visited_dependent()
319 CascadeMode::Unvisited { visited_rules } => {
320 let deferred_custom_properties = {
321 let mut builder = CustomPropertiesBuilder::new(stylist, &mut context);
322 iter_declarations(iter, &mut declarations, Some(&mut builder));
323 // Detect cycles, remove properties participating in them, and resolve properties, except:
324 // * Registered custom properties that depend on font-relative properties (Resolved)
325 // when prioritary properties are resolved), and
326 // * Any property that, in turn, depend on properties like above.
327 builder.build(DeferFontRelativeCustomPropertyResolution::Yes)
330 // Resolve prioritary properties - Guaranteed to not fall into a cycle with existing custom
332 cascade.apply_prioritary_properties(&mut context, &declarations, &mut shorthand_cache);
334 // Resolve the deferred custom properties.
335 if let Some(deferred) = deferred_custom_properties {
336 CustomPropertiesBuilder::build_deferred(deferred, stylist, &mut context);
339 if let Some(visited_rules) = visited_rules {
340 cascade.compute_visited_style_if_needed(
350 using_cached_reset_properties = cascade.try_to_use_cached_reset_properties(
351 &mut context.builder,
356 if using_cached_reset_properties {
357 LonghandIdSet::late_group_only_inherited()
359 LonghandIdSet::late_group()
364 cascade.apply_non_prioritary_properties(
366 &declarations.longhand_declarations,
367 &mut shorthand_cache,
368 &properties_to_apply,
371 cascade.finished_applying_properties(&mut context.builder);
373 std::mem::drop(cascade);
375 context.builder.clear_modified_reset();
377 if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
378 StyleAdjuster::new(&mut context.builder)
379 .adjust(layout_parent_style.unwrap_or(inherited_style), element);
382 if context.builder.modified_reset() || using_cached_reset_properties {
383 // If we adjusted any reset structs, we can't cache this ComputedValues.
385 // Also, if we re-used existing reset structs, don't bother caching it back again. (Aside
386 // from being wasted effort, it will be wrong, since context.rule_cache_conditions won't be
387 // set appropriately if we didn't compute those reset properties.)
388 context.rule_cache_conditions.borrow_mut().set_uncacheable();
391 context.builder.build()
394 /// For ignored colors mode, we sometimes want to do something equivalent to
395 /// "revert-or-initial", where we `revert` for a given origin, but then apply a
396 /// given initial value if nothing in other origins did override it.
398 /// This is a bit of a clunky way of achieving this.
399 type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>;
401 fn tweak_when_ignoring_colors(
402 context: &computed::Context,
403 longhand_id: LonghandId,
405 declaration: &mut Cow<PropertyDeclaration>,
406 declarations_to_apply_unless_overridden: &mut DeclarationsToApplyUnlessOverriden,
408 use crate::values::computed::ToComputedValue;
409 use crate::values::specified::Color;
411 if !longhand_id.ignored_when_document_colors_disabled() {
415 let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent);
416 if is_ua_or_user_rule {
420 // Always honor colors if forced-color-adjust is set to none.
423 .get_inherited_text()
424 .clone_forced_color_adjust();
425 if forced == computed::ForcedColorAdjust::None {
429 // Don't override background-color on ::-moz-color-swatch. It is set as an
430 // author style (via the style attribute), but it's pretty important for it
431 // to show up for obvious reasons :)
435 .map_or(false, |p| p.is_color_swatch()) &&
436 longhand_id == LonghandId::BackgroundColor
441 fn alpha_channel(color: &Color, context: &computed::Context) -> f32 {
442 // We assume here currentColor is opaque.
444 .to_computed_value(context)
445 .resolve_to_absolute(&AbsoluteColor::BLACK)
449 // A few special-cases ahead.
450 match **declaration {
451 // Honor CSS-wide keywords like unset / revert / initial...
452 PropertyDeclaration::CSSWideKeyword(..) => return,
453 PropertyDeclaration::BackgroundColor(ref color) => {
454 // We honor system colors and transparent colors unconditionally.
456 // NOTE(emilio): We honor transparent unconditionally, like we do
457 // for color, even though it causes issues like bug 1625036. The
458 // reasoning is that the conditions that trigger that (having
459 // mismatched widget and default backgrounds) are both uncommon, and
460 // broken in other applications as well, and not honoring
461 // transparent makes stuff uglier or break unconditionally
462 // (bug 1666059, bug 1755713).
463 if color.honored_in_forced_colors_mode(/* allow_transparent = */ true) {
466 // For background-color, we revert or initial-with-preserved-alpha
467 // otherwise, this is needed to preserve semi-transparent
469 let alpha = alpha_channel(color, context);
473 let mut color = context.builder.device.default_background_color();
475 declarations_to_apply_unless_overridden
476 .push(PropertyDeclaration::BackgroundColor(color.into()))
478 PropertyDeclaration::Color(ref color) => {
479 // We honor color: transparent and system colors.
482 .honored_in_forced_colors_mode(/* allow_transparent = */ true)
486 // If the inherited color would be transparent, but we would
487 // override this with a non-transparent color, then override it with
488 // the default color. Otherwise just let it inherit through.
491 .get_parent_inherited_text()
496 let color = context.builder.device.default_color();
497 declarations_to_apply_unless_overridden.push(PropertyDeclaration::Color(
498 specified::ColorPropertyValue(color.into()),
502 // We honor url background-images if backplating.
503 #[cfg(feature = "gecko")]
504 PropertyDeclaration::BackgroundImage(ref bkg) => {
505 use crate::values::generics::image::Image;
506 if static_prefs::pref!("browser.display.permit_backplate") {
510 .all(|image| matches!(*image, Image::Url(..) | Image::None))
517 // We honor system colors more generally for all colors.
519 // We used to honor transparent but that causes accessibility
520 // regressions like bug 1740924.
522 // NOTE(emilio): This doesn't handle caret-color and accent-color
523 // because those use a slightly different syntax (<color> | auto for
526 // That's probably fine though, as using a system color for
527 // caret-color doesn't make sense (using currentColor is fine), and
528 // we ignore accent-color in high-contrast-mode anyways.
529 if let Some(color) = declaration.color_value() {
530 if color.honored_in_forced_colors_mode(/* allow_transparent = */ false) {
537 *declaration.to_mut() =
538 PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert);
541 /// We track the index only for prioritary properties. For other properties we can just iterate.
542 type DeclarationIndex = u16;
544 /// "Prioritary" properties are properties that other properties depend on in one way or another.
546 /// We keep track of their position in the declaration vector, in order to be able to cascade them
547 /// separately in precise order.
548 #[derive(Copy, Clone)]
549 struct PrioritaryDeclarationPosition {
550 // DeclarationIndex::MAX signals no index.
551 most_important: DeclarationIndex,
552 least_important: DeclarationIndex,
555 impl Default for PrioritaryDeclarationPosition {
556 fn default() -> Self {
558 most_important: DeclarationIndex::MAX,
559 least_important: DeclarationIndex::MAX,
564 #[derive(Copy, Clone)]
565 struct Declaration<'a> {
566 decl: &'a PropertyDeclaration,
567 priority: CascadePriority,
568 next_index: DeclarationIndex,
571 /// The set of property declarations from our rules.
573 struct Declarations<'a> {
574 /// Whether we have any prioritary property. This is just a minor optimization.
575 has_prioritary_properties: bool,
576 /// A list of all the applicable longhand declarations.
577 longhand_declarations: SmallVec<[Declaration<'a>; 32]>,
578 /// The prioritary property position data.
579 prioritary_positions: [PrioritaryDeclarationPosition; property_counts::PRIORITARY],
582 impl<'a> Declarations<'a> {
583 fn note_prioritary_property(&mut self, id: PrioritaryPropertyId) {
584 let new_index = self.longhand_declarations.len();
585 if new_index >= DeclarationIndex::MAX as usize {
586 // This prioritary property is past the amount of declarations we can track. Let's give
587 // up applying it to prevent getting confused.
591 self.has_prioritary_properties = true;
592 let new_index = new_index as DeclarationIndex;
593 let position = &mut self.prioritary_positions[id as usize];
594 if position.most_important == DeclarationIndex::MAX {
595 // We still haven't seen this property, record the current position as the most
597 position.most_important = new_index;
599 // Let the previous item in the list know about us.
600 self.longhand_declarations[position.least_important as usize].next_index = new_index;
602 position.least_important = new_index;
607 decl: &'a PropertyDeclaration,
608 priority: CascadePriority,
611 if let Some(id) = PrioritaryPropertyId::from_longhand(id) {
612 self.note_prioritary_property(id);
614 self.longhand_declarations.push(Declaration {
623 first_line_reparenting: FirstLineReparenting<'b>,
626 author_specified: LonghandIdSet,
627 reverted_set: LonghandIdSet,
628 reverted: FxHashMap<LonghandId, (CascadePriority, bool)>,
629 declarations_to_apply_unless_overridden: DeclarationsToApplyUnlessOverriden,
632 impl<'b> Cascade<'b> {
633 fn new(first_line_reparenting: FirstLineReparenting<'b>, ignore_colors: bool) -> Self {
635 first_line_reparenting,
637 seen: LonghandIdSet::default(),
638 author_specified: LonghandIdSet::default(),
639 reverted_set: Default::default(),
640 reverted: Default::default(),
641 declarations_to_apply_unless_overridden: Default::default(),
645 fn substitute_variables_if_needed<'cache, 'decl>(
647 context: &mut computed::Context,
648 shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
649 declaration: &'decl PropertyDeclaration,
650 ) -> Cow<'decl, PropertyDeclaration>
654 let declaration = match *declaration {
655 PropertyDeclaration::WithVariables(ref declaration) => declaration,
656 ref d => return Cow::Borrowed(d),
659 if !declaration.id.inherited() {
660 context.rule_cache_conditions.borrow_mut().set_uncacheable();
662 // NOTE(emilio): We only really need to add the `display` /
663 // `content` flag if the CSS variable has not been specified on our
664 // declarations, but we don't have that information at this point,
665 // and it doesn't seem like an important enough optimization to
667 match declaration.id {
668 LonghandId::Display => {
671 .add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE);
673 LonghandId::Content => {
676 .add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE);
683 context.builder.stylist.is_some(),
684 "Need a Stylist to substitute variables!"
686 declaration.value.substitute_variables(
688 context.builder.custom_properties(),
689 context.builder.stylist.unwrap(),
695 fn apply_one_prioritary_property(
697 context: &mut computed::Context,
698 decls: &Declarations,
699 cache: &mut ShorthandsWithPropertyReferencesCache,
700 id: PrioritaryPropertyId,
702 let mut index = decls.prioritary_positions[id as usize].most_important;
703 if index == DeclarationIndex::MAX {
707 let longhand_id = id.to_longhand();
709 !longhand_id.is_logical(),
710 "That could require more book-keeping"
713 let decl = decls.longhand_declarations[index as usize];
714 self.apply_one_longhand(context, longhand_id, decl.decl, decl.priority, cache);
715 if self.seen.contains(longhand_id) {
716 return true; // Common case, we're done.
719 self.reverted_set.contains(longhand_id),
720 "How else can we fail to apply a prioritary property?"
723 decl.next_index == 0 || decl.next_index > index,
724 "should make progress! {} -> {}",
728 index = decl.next_index;
736 fn apply_prioritary_properties(
738 context: &mut computed::Context,
739 decls: &Declarations,
740 cache: &mut ShorthandsWithPropertyReferencesCache,
742 // Keeps apply_one_prioritary_property calls readable, considering the repititious
746 self.apply_one_prioritary_property(
750 PrioritaryPropertyId::$prop,
755 if !decls.has_prioritary_properties {
759 let has_writing_mode = apply!(WritingMode) | apply!(Direction) | apply!(TextOrientation);
760 if has_writing_mode {
761 self.compute_writing_mode(context);
765 self.compute_zoom(context);
768 // Compute font-family.
769 let has_font_family = apply!(FontFamily);
770 let has_lang = apply!(XLang);
772 self.recompute_initial_font_family_if_needed(&mut context.builder);
775 self.prioritize_user_fonts_if_needed(&mut context.builder);
778 // Compute font-size.
779 if apply!(XTextScale) {
780 self.unzoom_fonts_if_needed(&mut context.builder);
782 let has_font_size = apply!(FontSize);
783 let has_math_depth = apply!(MathDepth);
784 let has_min_font_size_ratio = apply!(MozMinFontSizeRatio);
786 if has_math_depth && has_font_size {
787 self.recompute_math_font_size_if_needed(context);
789 if has_lang || has_font_family {
790 self.recompute_keyword_font_size_if_needed(context);
792 if has_font_size || has_min_font_size_ratio || has_lang || has_font_family {
793 self.constrain_font_size_if_needed(&mut context.builder);
796 // Compute the rest of the first-available-font-affecting properties.
800 apply!(FontSizeAdjust);
803 apply!(ForcedColorAdjust);
805 // Compute the line height.
809 fn apply_non_prioritary_properties(
811 context: &mut computed::Context,
812 longhand_declarations: &[Declaration],
813 shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
814 properties_to_apply: &LonghandIdSet,
816 debug_assert!(!properties_to_apply.contains_any(LonghandIdSet::prioritary_properties()));
817 debug_assert!(self.declarations_to_apply_unless_overridden.is_empty());
818 for declaration in &*longhand_declarations {
819 let mut longhand_id = declaration.decl.id().as_longhand().unwrap();
820 if !properties_to_apply.contains(longhand_id) {
823 debug_assert!(PrioritaryPropertyId::from_longhand(longhand_id).is_none());
824 let is_logical = longhand_id.is_logical();
826 let wm = context.builder.writing_mode;
828 .rule_cache_conditions
830 .set_writing_mode_dependency(wm);
831 longhand_id = longhand_id.to_physical(wm);
833 self.apply_one_longhand(
837 declaration.priority,
841 if !self.declarations_to_apply_unless_overridden.is_empty() {
842 debug_assert!(self.ignore_colors);
843 for declaration in std::mem::take(&mut self.declarations_to_apply_unless_overridden) {
844 let longhand_id = declaration.id().as_longhand().unwrap();
845 debug_assert!(!longhand_id.is_logical());
846 if !self.seen.contains(longhand_id) {
848 self.do_apply_declaration(context, longhand_id, &declaration);
855 fn apply_one_longhand(
857 context: &mut computed::Context,
858 longhand_id: LonghandId,
859 declaration: &PropertyDeclaration,
860 priority: CascadePriority,
861 cache: &mut ShorthandsWithPropertyReferencesCache,
863 debug_assert!(!longhand_id.is_logical());
864 let origin = priority.cascade_level().origin();
865 if self.seen.contains(longhand_id) {
869 if self.reverted_set.contains(longhand_id) {
870 if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&longhand_id) {
871 if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
877 let mut declaration = self.substitute_variables_if_needed(context, cache, declaration);
879 // When document colors are disabled, do special handling of
880 // properties that are marked as ignored in that mode.
881 if self.ignore_colors {
882 tweak_when_ignoring_colors(
887 &mut self.declarations_to_apply_unless_overridden,
891 let is_unset = match declaration.get_css_wide_keyword() {
892 Some(keyword) => match keyword {
893 CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => {
894 let origin_revert = keyword == CSSWideKeyword::Revert;
895 // We intentionally don't want to insert it into `self.seen`, `reverted` takes
896 // care of rejecting other declarations as needed.
897 self.reverted_set.insert(longhand_id);
898 self.reverted.insert(longhand_id, (priority, origin_revert));
901 CSSWideKeyword::Unset => true,
902 CSSWideKeyword::Inherit => longhand_id.inherited(),
903 CSSWideKeyword::Initial => !longhand_id.inherited(),
908 self.seen.insert(longhand_id);
909 if origin == Origin::Author {
910 self.author_specified.insert(longhand_id);
917 unsafe { self.do_apply_declaration(context, longhand_id, &declaration) }
921 unsafe fn do_apply_declaration(
923 context: &mut computed::Context,
924 longhand_id: LonghandId,
925 declaration: &PropertyDeclaration,
927 debug_assert!(!longhand_id.is_logical());
928 // We could (and used to) use a pattern match here, but that bloats this
929 // function to over 100K of compiled code!
931 // To improve i-cache behavior, we outline the individual functions and
932 // use virtual dispatch instead.
933 (CASCADE_PROPERTY[longhand_id as usize])(&declaration, context);
936 fn compute_zoom(&self, context: &mut computed::Context) {
937 context.builder.effective_zoom = context
939 .inherited_effective_zoom()
940 .compute_effective(context.builder.specified_zoom());
943 fn compute_writing_mode(&self, context: &mut computed::Context) {
944 context.builder.writing_mode = WritingMode::new(context.builder.get_inherited_box())
947 fn compute_visited_style_if_needed<E>(
949 context: &mut computed::Context,
951 parent_style: Option<&ComputedValues>,
952 layout_parent_style: Option<&ComputedValues>,
953 visited_rules: &StrongRuleNode,
954 guards: &StylesheetGuards,
958 let is_link = context.builder.pseudo.is_none() && element.unwrap().is_link();
960 macro_rules! visited_parent {
965 $parent.map(|p| p.visited_style().unwrap_or(p))
970 // We could call apply_declarations directly, but that'd cause
971 // another instantiation of this function which is not great.
972 let style = cascade_rules(
973 context.builder.stylist.unwrap(),
974 context.builder.pseudo,
977 visited_parent!(parent_style),
978 visited_parent!(layout_parent_style),
979 self.first_line_reparenting,
980 CascadeMode::Visited {
981 unvisited_context: &*context,
983 // Cascade input flags don't matter for the visited style, they are
984 // in the main (unvisited) style.
986 // The rule cache doesn't care about caching :visited
987 // styles, we cache the unvisited style instead. We still do
988 // need to set the caching dependencies properly if present
989 // though, so the cache conditions need to match.
991 &mut *context.rule_cache_conditions.borrow_mut(),
994 context.builder.visited_style = Some(style);
997 fn finished_applying_properties(&self, builder: &mut StyleBuilder) {
998 #[cfg(feature = "gecko")]
1000 if let Some(bg) = builder.get_background_if_mutated() {
1004 if let Some(svg) = builder.get_svg_if_mutated() {
1011 .contains_any(LonghandIdSet::border_background_properties())
1013 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND);
1016 if self.author_specified.contains(LonghandId::FontFamily) {
1017 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY);
1020 if self.author_specified.contains(LonghandId::Color) {
1021 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_TEXT_COLOR);
1024 if self.author_specified.contains(LonghandId::LetterSpacing) {
1025 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING);
1028 if self.author_specified.contains(LonghandId::WordSpacing) {
1029 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING);
1034 .contains(LonghandId::FontSynthesisWeight)
1036 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT);
1041 .contains(LonghandId::FontSynthesisStyle)
1043 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE);
1046 #[cfg(feature = "servo")]
1048 if let Some(font) = builder.get_font_if_mutated() {
1049 font.compute_font_hash();
1054 fn try_to_use_cached_reset_properties(
1056 builder: &mut StyleBuilder<'b>,
1057 cache: Option<&'b RuleCache>,
1058 guards: &StylesheetGuards,
1060 let style = match self.first_line_reparenting {
1061 FirstLineReparenting::Yes { style_to_reparent } => style_to_reparent,
1062 FirstLineReparenting::No => {
1063 let Some(cache) = cache else { return false };
1064 let Some(style) = cache.find(guards, builder) else {
1071 builder.copy_reset_from(style);
1073 // We're using the same reset style as another element, and we'll skip
1074 // applying the relevant properties. So we need to do the relevant
1075 // bookkeeping here to keep these bits correct.
1077 // Note that the border/background properties are non-inherited, so we
1078 // don't need to do anything else other than just copying the bits over.
1080 // When using this optimization, we also need to copy whether the old
1081 // style specified viewport units / used font-relative lengths, this one
1082 // would as well. It matches the same rules, so it is the right thing
1083 // to do anyways, even if it's only used on inherited properties.
1084 let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND |
1085 ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS |
1086 ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS |
1087 ComputedValueFlags::USES_CONTAINER_UNITS |
1088 ComputedValueFlags::USES_VIEWPORT_UNITS;
1089 builder.add_flags(style.flags & bits_to_copy);
1094 /// The initial font depends on the current lang group so we may need to
1095 /// recompute it if the language changed.
1097 #[cfg(feature = "gecko")]
1098 fn recompute_initial_font_family_if_needed(&self, builder: &mut StyleBuilder) {
1099 use crate::gecko_bindings::bindings;
1100 use crate::values::computed::font::FontFamily;
1102 let default_font_type = {
1103 let font = builder.get_font();
1105 if !font.mFont.family.is_initial {
1109 let default_font_type = unsafe {
1110 bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
1111 builder.device.document(),
1112 font.mLanguage.mRawPtr,
1116 let initial_generic = font.mFont.family.families.single_generic();
1118 initial_generic.is_some(),
1119 "Initial font should be just one generic font"
1121 if initial_generic == Some(default_font_type) {
1128 // NOTE: Leaves is_initial untouched.
1129 builder.mutate_font().mFont.family.families =
1130 FontFamily::generic(default_font_type).families.clone();
1133 /// Prioritize user fonts if needed by pref.
1135 #[cfg(feature = "gecko")]
1136 fn prioritize_user_fonts_if_needed(&self, builder: &mut StyleBuilder) {
1137 use crate::gecko_bindings::bindings;
1139 // Check the use_document_fonts setting for content, but for chrome
1140 // documents they're treated as always enabled.
1141 if static_prefs::pref!("browser.display.use_document_fonts") != 0 ||
1142 builder.device.chrome_rules_enabled_for_document()
1147 let default_font_type = {
1148 let font = builder.get_font();
1150 if font.mFont.family.is_system_font {
1154 if !font.mFont.family.families.needs_user_font_prioritization() {
1159 bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
1160 builder.device.document(),
1161 font.mLanguage.mRawPtr,
1166 let font = builder.mutate_font();
1170 .prioritize_first_generic_or_prepend(default_font_type);
1173 /// Some keyword sizes depend on the font family and language.
1174 #[cfg(feature = "gecko")]
1175 fn recompute_keyword_font_size_if_needed(&self, context: &mut computed::Context) {
1176 use crate::values::computed::ToComputedValue;
1178 if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) {
1183 let font = context.builder.get_font();
1184 let info = font.clone_font_size().keyword_info;
1185 let new_size = match info.kw {
1186 specified::FontSizeKeyword::None => return,
1188 context.for_non_inherited_property = false;
1189 specified::FontSize::Keyword(info).to_computed_value(context)
1193 if font.mScriptUnconstrainedSize == new_size.computed_size {
1200 context.builder.mutate_font().set_font_size(new_size);
1203 /// Some properties, plus setting font-size itself, may make us go out of
1204 /// our minimum font-size range.
1205 #[cfg(feature = "gecko")]
1206 fn constrain_font_size_if_needed(&self, builder: &mut StyleBuilder) {
1207 use crate::gecko_bindings::bindings;
1208 use crate::values::generics::NonNegative;
1210 let min_font_size = {
1211 let font = builder.get_font();
1212 let min_font_size = unsafe {
1213 bindings::Gecko_nsStyleFont_ComputeMinSize(&**font, builder.device.document())
1216 if font.mFont.size.0 >= min_font_size {
1220 NonNegative(min_font_size)
1223 builder.mutate_font().mFont.size = min_font_size;
1226 /// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up
1227 /// the struct when this happens by unzooming its contained font values, which will have been
1228 /// zoomed in the parent.
1230 /// FIXME(emilio): Why doing this _before_ handling font-size? That sounds wrong.
1231 #[cfg(feature = "gecko")]
1232 fn unzoom_fonts_if_needed(&self, builder: &mut StyleBuilder) {
1233 debug_assert!(self.seen.contains(LonghandId::XTextScale));
1235 let parent_text_scale = builder.get_parent_font().clone__x_text_scale();
1236 let text_scale = builder.get_font().clone__x_text_scale();
1237 if parent_text_scale == text_scale {
1241 parent_text_scale.text_zoom_enabled(),
1242 text_scale.text_zoom_enabled(),
1243 "There's only one value that disables it"
1246 !text_scale.text_zoom_enabled(),
1247 "We only ever disable text zoom (in svg:text), never enable it"
1249 let device = builder.device;
1250 builder.mutate_font().unzoom_fonts(device);
1253 /// Special handling of font-size: math (used for MathML).
1254 /// https://w3c.github.io/mathml-core/#the-math-script-level-property
1255 /// TODO: Bug: 1548471: MathML Core also does not specify a script min size
1256 /// should we unship that feature or standardize it?
1257 #[cfg(feature = "gecko")]
1258 fn recompute_math_font_size_if_needed(&self, context: &mut computed::Context) {
1259 use crate::values::generics::NonNegative;
1261 // Do not do anything if font-size: math or math-depth is not set.
1262 if context.builder.get_font().clone_font_size().keyword_info.kw !=
1263 specified::FontSizeKeyword::Math
1268 const SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE: f32 = 0.71;
1270 // Helper function that calculates the scale factor applied to font-size
1271 // when math-depth goes from parent_math_depth to computed_math_depth.
1272 // This function is essentially a modification of the MathML3's formula
1273 // 0.71^(parent_math_depth - computed_math_depth) so that a scale factor
1274 // of parent_script_percent_scale_down is applied when math-depth goes
1275 // from 0 to 1 and parent_script_script_percent_scale_down is applied
1276 // when math-depth goes from 0 to 2. This is also a straightforward
1277 // implementation of the specification's algorithm:
1278 // https://w3c.github.io/mathml-core/#the-math-script-level-property
1279 fn scale_factor_for_math_depth_change(
1280 parent_math_depth: i32,
1281 computed_math_depth: i32,
1282 parent_script_percent_scale_down: Option<f32>,
1283 parent_script_script_percent_scale_down: Option<f32>,
1285 let mut a = parent_math_depth;
1286 let mut b = computed_math_depth;
1287 let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE;
1288 let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c);
1289 let scale_between_0_and_2 =
1290 parent_script_script_percent_scale_down.unwrap_or_else(|| c * c);
1292 let mut invert_scale_factor = false;
1297 mem::swap(&mut a, &mut b);
1298 invert_scale_factor = true;
1301 if a <= 0 && b >= 2 {
1302 s *= scale_between_0_and_2;
1305 s *= scale_between_0_and_2 / scale_between_0_and_1;
1308 s *= scale_between_0_and_1;
1311 s *= (c as f32).powi(e);
1312 if invert_scale_factor {
1313 1.0 / s.max(f32::MIN_POSITIVE)
1319 let (new_size, new_unconstrained_size) = {
1320 let builder = &context.builder;
1321 let font = builder.get_font();
1322 let parent_font = builder.get_parent_font();
1324 let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth);
1330 let mut min = parent_font.mScriptMinSize;
1331 if font.mXTextScale.text_zoom_enabled() {
1332 min = builder.device.zoom_text(min);
1335 // Calculate scale factor following MathML Core's algorithm.
1337 // Script scale factors are independent of orientation.
1338 let font_metrics = context.query_font_metrics(
1339 FontBaseSize::InheritedStyle,
1340 FontMetricsOrientation::Horizontal,
1341 /* retrieve_math_scales = */ true,
1343 scale_factor_for_math_depth_change(
1344 parent_font.mMathDepth as i32,
1345 font.mMathDepth as i32,
1346 font_metrics.script_percent_scale_down,
1347 font_metrics.script_script_percent_scale_down,
1351 let parent_size = parent_font.mSize.0;
1352 let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0;
1353 let new_size = parent_size.scale_by(scale);
1354 let new_unconstrained_size = parent_unconstrained_size.scale_by(scale);
1357 // The parent size can be smaller than scriptminsize, e.g. if it
1358 // was specified explicitly. Don't scale in this case, but we
1359 // don't want to set it to scriptminsize either since that will
1361 if parent_size <= min {
1362 (parent_size, new_unconstrained_size)
1364 (min.max(new_size), new_unconstrained_size)
1367 // If the new unconstrained size is larger than the min size,
1368 // this means we have escaped the grasp of scriptminsize and can
1369 // revert to using the unconstrained size.
1370 // However, if the new size is even larger (perhaps due to usage
1371 // of em units), use that instead.
1373 new_size.min(new_unconstrained_size.max(min)),
1374 new_unconstrained_size,
1378 let font = context.builder.mutate_font();
1379 font.mFont.size = NonNegative(new_size);
1380 font.mSize = NonNegative(new_size);
1381 font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size);