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.
This commit is contained in:
Callum Law 2025-09-30 16:45:37 +13:00 committed by Tim Ledbetter
commit 9cd23e3ae5
Notes: github-actions[bot] 2025-10-20 15:13:50 +00:00
9 changed files with 78 additions and 12 deletions

View file

@ -19,12 +19,14 @@ struct CalculationResolutionContext {
PercentageBasis percentage_basis {};
Optional<Length::ResolutionContext> length_resolution_context {};
Optional<TreeCountingFunctionResolutionContext> 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
};
}
};

View file

@ -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, Optional<DOM::Abstra
auto inherited_font_size = inheritance_parent_has_computed_properties ? inheritance_parent->computed_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<DOM::Abstra
.viewport_rect = viewport_rect(),
.font_metrics = line_height_font_metrics,
.root_font_metrics = abstract_element.has_value() && is<HTML::HTMLHtmlElement>(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<DOM::AbstractElement> 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<ComputedProperties> 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<ComputedProperties> 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]

View file

@ -191,7 +191,7 @@ public:
[[nodiscard]] GC::Ref<ComputedProperties> compute_properties(DOM::AbstractElement, CascadedProperties&) const;
void compute_property_values(ComputedProperties&) const;
void compute_property_values(ComputedProperties&, Optional<DOM::AbstractElement>) const;
void compute_font(ComputedProperties&, Optional<DOM::AbstractElement>) const;
[[nodiscard]] inline bool should_reject_with_ancestor_filter(Selector const&) const;

View file

@ -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<TreeCountingFunctionResolutionContext> tree_counting_function_resolution_context {};
};
}

View file

@ -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 <integer>, 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<DOM::Element>(); 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<Layout::NodeWithStyle> AbstractElement::layout_node()
{
if (m_pseudo_element.has_value())

View file

@ -27,6 +27,8 @@ public:
GC::Ptr<Layout::NodeWithStyle> layout_node();
GC::Ptr<Layout::NodeWithStyle const> layout_node() const { return const_cast<AbstractElement*>(this)->layout_node(); }
CSS::TreeCountingFunctionResolutionContext tree_counting_function_resolution_context() const;
GC::Ptr<Element const> parent_element() const;
Optional<AbstractElement> element_to_inherit_style_from() const;
Optional<AbstractElement> previous_in_tree_order() { return walk_layout_tree(WalkMethod::Previous); }

View file

@ -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<CSS::PropertyID>(property_id));

View file

@ -401,6 +401,7 @@ struct CalculationResolutionContext;
struct CSSStyleSheetInit;
struct GridRepeatParams;
struct StyleSheetIdentifier;
struct TreeCountingFunctionResolutionContext;
}

View file

@ -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<CSS::TreeCountingFunctionResolutionContext> tree_counting_function_resolution_context;
if constexpr (SameAs<CanvasType, HTML::HTMLCanvasElement>) {
// 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);