From 9cd23e3ae537c674f5c6eb34e565c4bcc6b0cb71 Mon Sep 17 00:00:00 2001 From: Callum Law Date: Tue, 30 Sep 2025 16:45:37 +1300 Subject: [PATCH] LibWeb: Compute and propagate tree-counting function resolution context Tree counting functions should be resolved at style computation time - to do this we will need to know the element's sibling count and index. This commit computes that information and propagates it to the various `StyleValue::to_computed_value` methods. --- .../LibWeb/CSS/CalculationResolutionContext.h | 2 ++ Libraries/LibWeb/CSS/StyleComputer.cpp | 24 +++++++++---- Libraries/LibWeb/CSS/StyleComputer.h | 2 +- .../CSS/StyleValues/ComputationContext.h | 6 ++++ Libraries/LibWeb/DOM/AbstractElement.cpp | 36 +++++++++++++++++++ Libraries/LibWeb/DOM/AbstractElement.h | 2 ++ Libraries/LibWeb/DOM/Element.cpp | 6 ++-- Libraries/LibWeb/Forward.h | 1 + .../HTML/Canvas/CanvasTextDrawingStyles.h | 11 ++++-- 9 files changed, 78 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/CSS/CalculationResolutionContext.h b/Libraries/LibWeb/CSS/CalculationResolutionContext.h index d8a4402927e..97f9cc8e018 100644 --- a/Libraries/LibWeb/CSS/CalculationResolutionContext.h +++ b/Libraries/LibWeb/CSS/CalculationResolutionContext.h @@ -19,12 +19,14 @@ struct CalculationResolutionContext { PercentageBasis percentage_basis {}; Optional length_resolution_context {}; + Optional tree_counting_function_resolution_context {}; static CalculationResolutionContext from_computation_context(ComputationContext const& computation_context, PercentageBasis percentage_basis = {}) { return { .percentage_basis = percentage_basis, .length_resolution_context = computation_context.length_resolution_context, + .tree_counting_function_resolution_context = computation_context.tree_counting_function_resolution_context }; } }; diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 7f8758e897b..6d14d1a3cf6 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -1068,10 +1068,12 @@ void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element }); } + auto tree_counting_function_resolution_context = abstract_element.tree_counting_function_resolution_context(); auto const& inheritance_parent = abstract_element.element_to_inherit_style_from(); auto inheritance_parent_has_computed_properties = inheritance_parent.has_value() && inheritance_parent->computed_properties(); ComputationContext font_computation_context { - .length_resolution_context = inheritance_parent_has_computed_properties ? Length::ResolutionContext::for_element(inheritance_parent.value()) : Length::ResolutionContext::for_window(*m_document->window()) + .length_resolution_context = inheritance_parent_has_computed_properties ? Length::ResolutionContext::for_element(inheritance_parent.value()) : Length::ResolutionContext::for_window(*m_document->window()), + .tree_counting_function_resolution_context = tree_counting_function_resolution_context }; if (auto const& font_size_specified_value = specified_values.get(PropertyID::FontSize); font_size_specified_value.has_value()) { @@ -1115,7 +1117,8 @@ void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element computed_properties.font_size(), computed_properties.first_available_computed_font().pixel_metrics(), inheritance_parent_has_computed_properties ? inheritance_parent->computed_properties()->line_height() : InitialValues::line_height() }, - .root_font_metrics = m_root_element_font_metrics } + .root_font_metrics = m_root_element_font_metrics }, + .tree_counting_function_resolution_context = tree_counting_function_resolution_context }; result.set(PropertyID::LineHeight, compute_line_height(*line_height_specified_value.value(), line_height_computation_context)); @@ -1126,6 +1129,7 @@ void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element .viewport_rect = viewport_rect(), .font_metrics = font_metrics, .root_font_metrics = m_root_element_font_metrics }, + .tree_counting_function_resolution_context = tree_counting_function_resolution_context }; // NOTE: This doesn't necessarily return the specified value if we reach into computed_properties but that @@ -2031,8 +2035,12 @@ void StyleComputer::compute_font(ComputedProperties& style, Optionalcomputed_properties()->font_size() : InitialValues::font_size(); auto inherited_math_depth = inheritance_parent_has_computed_properties ? inheritance_parent->computed_properties()->math_depth() : InitialValues::math_depth(); + + auto tree_counting_function_resolution_context = abstract_element.map([](auto abstract_element) { return abstract_element.tree_counting_function_resolution_context(); }).value_or({ .sibling_count = 1, .sibling_index = 1 }); + ComputationContext font_computation_context { - .length_resolution_context = inheritance_parent_has_computed_properties ? Length::ResolutionContext::for_element(inheritance_parent.value()) : Length::ResolutionContext::for_window(*m_document->window()) + .length_resolution_context = inheritance_parent_has_computed_properties ? Length::ResolutionContext::for_element(inheritance_parent.value()) : Length::ResolutionContext::for_window(*m_document->window()), + .tree_counting_function_resolution_context = tree_counting_function_resolution_context }; auto const& font_size_specified_value = style.property(PropertyID::FontSize, ComputedProperties::WithAnimationsApplied::No); @@ -2086,7 +2094,8 @@ void StyleComputer::compute_font(ComputedProperties& style, Optional(abstract_element->element()) ? line_height_font_metrics : m_root_element_font_metrics, - } + }, + .tree_counting_function_resolution_context = tree_counting_function_resolution_context }; auto const& line_height_specified_value = style.property(CSS::PropertyID::LineHeight, ComputedProperties::WithAnimationsApplied::No); @@ -2147,7 +2156,7 @@ Gfx::Font const& StyleComputer::initial_font() const return font; } -void StyleComputer::compute_property_values(ComputedProperties& style) const +void StyleComputer::compute_property_values(ComputedProperties& style, Optional abstract_element) const { Length::FontMetrics font_metrics { style.font_size(), @@ -2161,6 +2170,7 @@ void StyleComputer::compute_property_values(ComputedProperties& style) const .font_metrics = font_metrics, .root_font_metrics = m_root_element_font_metrics, }, + .tree_counting_function_resolution_context = abstract_element.map([](auto abstract_element) { return abstract_element.tree_counting_function_resolution_context(); }).value_or({ .sibling_count = 1, .sibling_index = 1 }) }; // NOTE: This doesn't necessarily return the specified value if we have already computed this property but that @@ -2392,7 +2402,7 @@ GC::Ref StyleComputer::create_document_style() const compute_math_depth(style, {}); compute_font(style, {}); - compute_property_values(style); + compute_property_values(style, {}); style->set_property(CSS::PropertyID::Width, CSS::LengthStyleValue::create(CSS::Length::make_px(viewport_rect().width()))); style->set_property(CSS::PropertyID::Height, CSS::LengthStyleValue::create(CSS::Length::make_px(viewport_rect().height()))); style->set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::Block))); @@ -2643,7 +2653,7 @@ GC::Ref StyleComputer::compute_properties(DOM::AbstractEleme compute_font(computed_style, abstract_element); // 4. Convert properties into their computed forms - compute_property_values(computed_style); + compute_property_values(computed_style, abstract_element); // FIXME: Support multiple entries for `animation` properties // Animation declarations [css-animations-2] diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index a2754c7dde3..0ff321f6195 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -191,7 +191,7 @@ public: [[nodiscard]] GC::Ref compute_properties(DOM::AbstractElement, CascadedProperties&) const; - void compute_property_values(ComputedProperties&) const; + void compute_property_values(ComputedProperties&, Optional) const; void compute_font(ComputedProperties&, Optional) const; [[nodiscard]] inline bool should_reject_with_ancestor_filter(Selector const&) const; diff --git a/Libraries/LibWeb/CSS/StyleValues/ComputationContext.h b/Libraries/LibWeb/CSS/StyleValues/ComputationContext.h index 96383ae4dc1..5410f6e4ac5 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ComputationContext.h +++ b/Libraries/LibWeb/CSS/StyleValues/ComputationContext.h @@ -10,8 +10,14 @@ namespace Web::CSS { +struct TreeCountingFunctionResolutionContext { + size_t sibling_count; + size_t sibling_index; +}; + struct ComputationContext { Length::ResolutionContext length_resolution_context; + Optional tree_counting_function_resolution_context {}; }; } diff --git a/Libraries/LibWeb/DOM/AbstractElement.cpp b/Libraries/LibWeb/DOM/AbstractElement.cpp index 2824e43171f..1cd2c5ef449 100644 --- a/Libraries/LibWeb/DOM/AbstractElement.cpp +++ b/Libraries/LibWeb/DOM/AbstractElement.cpp @@ -26,6 +26,42 @@ Document& AbstractElement::document() const return m_element->document(); } +CSS::TreeCountingFunctionResolutionContext AbstractElement::tree_counting_function_resolution_context() const +{ + // FIXME: When used on an element-backed pseudo-element which is also a real element, the tree counting functions + // resolve for that real element. For other pseudo elements, they resolve as if they were resolved against + // the originating element. It follows that for nested pseudo elements the resolution will recursively walk + // the originating elements until a real element is found. + + // FIXME: A tree counting function is a tree-scoped reference where it references an implicit tree-scoped name for + // the element it resolves against. This is done to not leak tree information to an outer tree. A tree + // counting function that is scoped to an outer tree relative to the element it resolves against, will alway + // resolve to 0. + auto const& element_to_resolve_tree_counting_function_against = element(); + + // The sibling-count() functional notation represents, as an , the total number of child elements in the + // parent of the element on which the notation is used. + auto const& parent = element_to_resolve_tree_counting_function_against.parent_element(); + + // If there is no parent we are the root node + if (!parent) + return { .sibling_count = 1, .sibling_index = 1 }; + + size_t count = 0; + size_t index = 0; + + for (auto const* child = parent->first_child_of_type(); child; child = child->next_element_sibling()) { + ++count; + if (child == &element_to_resolve_tree_counting_function_against) + index = count; + } + + return { + .sibling_count = count, + .sibling_index = index + }; +} + GC::Ptr AbstractElement::layout_node() { if (m_pseudo_element.has_value()) diff --git a/Libraries/LibWeb/DOM/AbstractElement.h b/Libraries/LibWeb/DOM/AbstractElement.h index e9917381978..88379e75a9e 100644 --- a/Libraries/LibWeb/DOM/AbstractElement.h +++ b/Libraries/LibWeb/DOM/AbstractElement.h @@ -27,6 +27,8 @@ public: GC::Ptr layout_node(); GC::Ptr layout_node() const { return const_cast(this)->layout_node(); } + CSS::TreeCountingFunctionResolutionContext tree_counting_function_resolution_context() const; + GC::Ptr parent_element() const; Optional element_to_inherit_style_from() const; Optional previous_in_tree_order() { return walk_layout_tree(WalkMethod::Previous); } diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index e9503cacd57..75f2a4117e2 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -844,8 +844,10 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_inherited_style() if (invalidation.is_none() && old_values_with_relative_units.is_empty()) return invalidation; - document().style_computer().compute_font(*computed_properties, AbstractElement { *this }); - document().style_computer().compute_property_values(*computed_properties); + AbstractElement abstract_element { *this }; + + document().style_computer().compute_font(*computed_properties, abstract_element); + document().style_computer().compute_property_values(*computed_properties, abstract_element); for (auto [property_id, old_value] : old_values_with_relative_units) { auto const& new_value = computed_properties->property(static_cast(property_id)); diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 141555ddd1f..4e2184ba339 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -401,6 +401,7 @@ struct CalculationResolutionContext; struct CSSStyleSheetInit; struct GridRepeatParams; struct StyleSheetIdentifier; +struct TreeCountingFunctionResolutionContext; } diff --git a/Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h b/Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h index 59e8f531fd4..be5e7a821be 100644 --- a/Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h +++ b/Libraries/LibWeb/HTML/Canvas/CanvasTextDrawingStyles.h @@ -82,6 +82,7 @@ public: // and with system fonts being computed to explicit values. // FIXME: with the 'line-height' component forced to 'normal' // FIXME: with the 'font-size' component converted to CSS pixels + // FIXME: Disallow tree counting functions if this is an offscreen canvas auto font_style_value_result = parse_css_value(CSS::Parser::ParsingParams {}, font, CSS::PropertyID::Font); // If the new value is syntactically incorrect (including using property-independent style sheet syntax like 'inherit' or 'initial'), then it must be ignored, without assigning a new font value. @@ -115,6 +116,7 @@ public: // FIXME: Investigate whether this is the correct resolution context (i.e. whether we should instead use // a font-size of 10px) for OffscreenCanvas auto length_resolution_context = CSS::Length::ResolutionContext::for_window(*document->window()); + Optional tree_counting_function_resolution_context; if constexpr (SameAs) { // NOTE: The canvas itself is considered the inheritance parent @@ -124,12 +126,17 @@ public: inherited_math_depth = canvas_element.computed_properties()->math_depth(); inherited_font_size = canvas_element.computed_properties()->font_size(); inherited_font_weight = canvas_element.computed_properties()->font_weight(); - length_resolution_context = CSS::Length::ResolutionContext::for_element(DOM::AbstractElement { canvas_element }); + + DOM::AbstractElement abstract_element { canvas_element }; + + length_resolution_context = CSS::Length::ResolutionContext::for_element(abstract_element); + tree_counting_function_resolution_context = abstract_element.tree_counting_function_resolution_context(); } } CSS::ComputationContext computation_context { - .length_resolution_context = length_resolution_context + .length_resolution_context = length_resolution_context, + .tree_counting_function_resolution_context = tree_counting_function_resolution_context }; auto const& computed_font_size = CSS::StyleComputer::compute_font_size(font_size, computed_math_depth, inherited_font_size, inherited_math_depth, computation_context);