diff --git a/Libraries/LibWeb/CSS/ComputedProperties.cpp b/Libraries/LibWeb/CSS/ComputedProperties.cpp index c3933632214..08567d387b2 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.cpp +++ b/Libraries/LibWeb/CSS/ComputedProperties.cpp @@ -935,7 +935,7 @@ ColumnSpan ComputedProperties::column_span() const return keyword_to_column_span(value.to_keyword()).release_value(); } -ComputedProperties::ContentDataAndQuoteNestingLevel ComputedProperties::content(DOM::Element& element, u32 initial_quote_nesting_level) const +ComputedProperties::ContentDataAndQuoteNestingLevel ComputedProperties::content(DOM::AbstractElement& element_reference, u32 initial_quote_nesting_level) const { auto const& value = property(PropertyID::Content); auto quotes_data = quotes(); @@ -999,7 +999,7 @@ ComputedProperties::ContentDataAndQuoteNestingLevel ComputedProperties::content( break; } } else if (item->is_counter()) { - builder.append(item->as_counter().resolve(element)); + builder.append(item->as_counter().resolve(element_reference)); } else { // TODO: Implement images, and other things. dbgln("`{}` is not supported in `content` (yet?)", item->to_string(SerializationMode::Normal)); @@ -1014,7 +1014,7 @@ ComputedProperties::ContentDataAndQuoteNestingLevel ComputedProperties::content( if (item->is_string()) { alt_text_builder.append(item->as_string().string_value()); } else if (item->is_counter()) { - alt_text_builder.append(item->as_counter().resolve(element)); + alt_text_builder.append(item->as_counter().resolve(element_reference)); } else { dbgln("`{}` is not supported in `content` alt-text (yet?)", item->to_string(SerializationMode::Normal)); } diff --git a/Libraries/LibWeb/CSS/ComputedProperties.h b/Libraries/LibWeb/CSS/ComputedProperties.h index 046a5aaa13a..ec85345a36d 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.h +++ b/Libraries/LibWeb/CSS/ComputedProperties.h @@ -96,7 +96,7 @@ public: ContentData content_data; u32 final_quote_nesting_level { 0 }; }; - ContentDataAndQuoteNestingLevel content(DOM::Element&, u32 initial_quote_nesting_level) const; + ContentDataAndQuoteNestingLevel content(DOM::AbstractElement&, u32 initial_quote_nesting_level) const; ContentVisibility content_visibility() const; Vector cursor() const; Variant tab_size() const; diff --git a/Libraries/LibWeb/CSS/CountersSet.cpp b/Libraries/LibWeb/CSS/CountersSet.cpp index e7d147ec059..810d9f3b09e 100644 --- a/Libraries/LibWeb/CSS/CountersSet.cpp +++ b/Libraries/LibWeb/CSS/CountersSet.cpp @@ -12,27 +12,30 @@ namespace Web::CSS { +void CountersSet::visit_edges(GC::Cell::Visitor& visitor) +{ + for (auto const& counter : m_counters) + counter.originating_element.visit(visitor); +} + // https://drafts.csswg.org/css-lists-3/#instantiate-counter -Counter& CountersSet::instantiate_a_counter(FlyString name, UniqueNodeID originating_element_id, bool reversed, Optional value) +Counter& CountersSet::instantiate_a_counter(FlyString name, DOM::AbstractElement const& element, bool reversed, Optional value) { // 1. Let counters be element’s CSS counters set. - auto* element = DOM::Node::from_unique_id(originating_element_id); // 2. Let innermost counter be the last counter in counters with the name name. // If innermost counter’s originating element is element or a previous sibling of element, // remove innermost counter from counters. auto innermost_counter = last_counter_with_name(name); if (innermost_counter.has_value()) { - auto* originating_node = DOM::Node::from_unique_id(innermost_counter->originating_element_id); - VERIFY(originating_node); - auto& innermost_element = as(*originating_node); + auto& innermost_element = innermost_counter->originating_element; - if (&innermost_element == element - || (innermost_element.parent() == element->parent() && innermost_element.is_before(*element))) { + if (innermost_element == element + || (innermost_element.parent_element() == element.parent_element() && innermost_element.is_before(element))) { m_counters.remove_first_matching([&innermost_counter](auto& it) { return it.name == innermost_counter->name - && it.originating_element_id == innermost_counter->originating_element_id; + && it.originating_element == innermost_counter->originating_element; }); } } @@ -41,7 +44,7 @@ Counter& CountersSet::instantiate_a_counter(FlyString name, UniqueNodeID origina // reversed being reversed, and initial value value (if given) m_counters.append({ .name = move(name), - .originating_element_id = originating_element_id, + .originating_element = element, .reversed = reversed, .value = value, }); @@ -50,7 +53,7 @@ Counter& CountersSet::instantiate_a_counter(FlyString name, UniqueNodeID origina } // https://drafts.csswg.org/css-lists-3/#propdef-counter-set -void CountersSet::set_a_counter(FlyString name, UniqueNodeID originating_element_id, CounterValue value) +void CountersSet::set_a_counter(FlyString name, DOM::AbstractElement const& element, CounterValue value) { if (auto existing_counter = last_counter_with_name(name); existing_counter.has_value()) { existing_counter->value = value; @@ -60,12 +63,12 @@ void CountersSet::set_a_counter(FlyString name, UniqueNodeID originating_element // If there is not currently a counter of the given name on the element, the element instantiates // a new counter of the given name with a starting value of 0 before setting or incrementing its value. // https://drafts.csswg.org/css-lists-3/#valdef-counter-set-counter-name-integer - auto& counter = instantiate_a_counter(name, originating_element_id, false, 0); + auto& counter = instantiate_a_counter(name, element, false, 0); counter.value = value; } // https://drafts.csswg.org/css-lists-3/#propdef-counter-increment -void CountersSet::increment_a_counter(FlyString name, UniqueNodeID originating_element_id, CounterValue amount) +void CountersSet::increment_a_counter(FlyString name, DOM::AbstractElement const& element, CounterValue amount) { if (auto existing_counter = last_counter_with_name(name); existing_counter.has_value()) { // FIXME: How should we handle existing counters with no value? Can that happen? @@ -77,7 +80,7 @@ void CountersSet::increment_a_counter(FlyString name, UniqueNodeID originating_e // If there is not currently a counter of the given name on the element, the element instantiates // a new counter of the given name with a starting value of 0 before setting or incrementing its value. // https://drafts.csswg.org/css-lists-3/#valdef-counter-set-counter-name-integer - auto& counter = instantiate_a_counter(name, originating_element_id, false, 0); + auto& counter = instantiate_a_counter(name, element, false, 0); counter.value->saturating_add(amount.value()); } @@ -90,10 +93,10 @@ Optional CountersSet::last_counter_with_name(FlyString const& name) return {}; } -Optional CountersSet::counter_with_same_name_and_creator(FlyString const& name, UniqueNodeID originating_element_id) +Optional CountersSet::counter_with_same_name_and_creator(FlyString const& name, DOM::AbstractElement const& element) { return m_counters.first_matching([&](auto& it) { - return it.name == name && it.originating_element_id == originating_element_id; + return it.name == name && it.originating_element == element; }); } @@ -118,13 +121,10 @@ void resolve_counters(DOM::AbstractElement& element_reference) if (style.display().is_none()) return; - // FIXME: Include the pseudo-element in the element ID - auto element_id = element_reference.element().unique_id(); - // 2. New counters are instantiated (counter-reset). auto counter_reset = style.counter_data(PropertyID::CounterReset); for (auto const& counter : counter_reset) - element_reference.ensure_counters_set().instantiate_a_counter(counter.name, element_id, counter.is_reversed, counter.value); + element_reference.ensure_counters_set().instantiate_a_counter(counter.name, element_reference, counter.is_reversed, counter.value); // FIXME: Take style containment into account // https://drafts.csswg.org/css-contain-2/#containment-style @@ -135,12 +135,12 @@ void resolve_counters(DOM::AbstractElement& element_reference) // 3. Counter values are incremented (counter-increment). auto counter_increment = style.counter_data(PropertyID::CounterIncrement); for (auto const& counter : counter_increment) - element_reference.ensure_counters_set().increment_a_counter(counter.name, element_id, *counter.value); + element_reference.ensure_counters_set().increment_a_counter(counter.name, element_reference, *counter.value); // 4. Counter values are explicitly set (counter-set). auto counter_set = style.counter_data(PropertyID::CounterSet); for (auto const& counter : counter_set) - element_reference.ensure_counters_set().set_a_counter(counter.name, element_id, *counter.value); + element_reference.ensure_counters_set().set_a_counter(counter.name, element_reference, *counter.value); // 5. Counter values are used (counter()/counters()). // NOTE: This happens when we process the `content` property. @@ -176,7 +176,7 @@ void inherit_counters(DOM::AbstractElement& element_reference) // or an empty CSS counters set otherwise. // For each counter of sibling counters, if element counters does not already contain a counter with // the same name, append a copy of counter to element counters. - if (auto* const sibling = element_reference.element().previous_sibling_of_type(); sibling && sibling->has_non_empty_counters_set()) { + if (auto sibling = element_reference.previous_sibling_in_tree_order(); sibling.has_value() && sibling->has_non_empty_counters_set()) { auto& sibling_counters = sibling->counters_set().release_value(); ensure_element_counters(); for (auto const& counter : sibling_counters.counters()) { @@ -188,12 +188,12 @@ void inherit_counters(DOM::AbstractElement& element_reference) // 4. Let value source be the CSS counters set of the element immediately preceding element in tree order. // For each source counter of value source, if element counters contains a counter with the same name // and creator, then set the value of that counter to source counter’s value. - if (auto* const previous = element_reference.element().previous_element_in_pre_order(); previous && previous->has_non_empty_counters_set()) { + if (auto const previous = element_reference.previous_in_tree_order(); previous.has_value() && previous->has_non_empty_counters_set()) { // NOTE: If element_counters is empty (AKA null) then we can skip this since nothing will match. if (element_counters) { auto& value_source = previous->counters_set().release_value(); for (auto const& source_counter : value_source.counters()) { - auto maybe_existing_counter = element_counters->counter_with_same_name_and_creator(source_counter.name, source_counter.originating_element_id); + auto maybe_existing_counter = element_counters->counter_with_same_name_and_creator(source_counter.name, source_counter.originating_element); if (maybe_existing_counter.has_value()) maybe_existing_counter->value = source_counter.value; } @@ -204,4 +204,15 @@ void inherit_counters(DOM::AbstractElement& element_reference) element_reference.set_counters_set(move(element_counters)); } +String CountersSet::dump() const +{ + StringBuilder builder; + builder.append("{\n"sv); + for (auto const& counter : m_counters) { + builder.appendff(" {} ({}) = {}\n", counter.name, counter.originating_element.debug_description(), counter.value); + } + builder.append('}'); + return builder.to_string_without_validation(); +} + } diff --git a/Libraries/LibWeb/CSS/CountersSet.h b/Libraries/LibWeb/CSS/CountersSet.h index 260a80720d8..9c657075039 100644 --- a/Libraries/LibWeb/CSS/CountersSet.h +++ b/Libraries/LibWeb/CSS/CountersSet.h @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace Web::CSS { @@ -22,7 +23,7 @@ using CounterValue = Checked; // https://drafts.csswg.org/css-lists-3/#counter struct Counter { FlyString name; - UniqueNodeID originating_element_id; // "creator" + DOM::AbstractElement originating_element; // "creator" bool reversed { false }; Optional value; }; @@ -33,17 +34,21 @@ public: CountersSet() = default; ~CountersSet() = default; - Counter& instantiate_a_counter(FlyString name, UniqueNodeID originating_element_id, bool reversed, Optional); - void set_a_counter(FlyString name, UniqueNodeID originating_element_id, CounterValue value); - void increment_a_counter(FlyString name, UniqueNodeID originating_element_id, CounterValue amount); + Counter& instantiate_a_counter(FlyString name, DOM::AbstractElement const&, bool reversed, Optional); + void set_a_counter(FlyString name, DOM::AbstractElement const&, CounterValue value); + void increment_a_counter(FlyString name, DOM::AbstractElement const&, CounterValue amount); void append_copy(Counter const&); Optional last_counter_with_name(FlyString const& name); - Optional counter_with_same_name_and_creator(FlyString const& name, UniqueNodeID originating_element_id); + Optional counter_with_same_name_and_creator(FlyString const& name, DOM::AbstractElement const&); Vector const& counters() const { return m_counters; } bool is_empty() const { return m_counters.is_empty(); } + void visit_edges(GC::Cell::Visitor&); + + String dump() const; + private: Vector m_counters; }; diff --git a/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp index f5870fbb314..3c739550568 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp @@ -1,15 +1,16 @@ /* * Copyright (c) 2018-2022, Andreas Kling * Copyright (c) 2021, Tobias Christiansen - * Copyright (c) 2024, Sam Atkins + * Copyright (c) 2024-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ -#include "CounterStyleValue.h" +#include #include #include #include +#include #include #include #include @@ -94,13 +95,13 @@ static String generate_a_counter_representation(CSSStyleValue const& counter_sty return MUST(String::formatted("{}", value)); } -String CounterStyleValue::resolve(DOM::Element& element) const +String CounterStyleValue::resolve(DOM::AbstractElement& element_reference) const { // "If no counter named exists on an element where counter() or counters() is used, // one is first instantiated with a starting value of 0." - auto& counters_set = element.ensure_counters_set(); + auto& counters_set = element_reference.ensure_counters_set(); if (!counters_set.last_counter_with_name(m_properties.counter_name).has_value()) - counters_set.instantiate_a_counter(m_properties.counter_name, element.unique_id(), false, 0); + counters_set.instantiate_a_counter(m_properties.counter_name, element_reference, false, 0); // counter( , ? ) // "Represents the value of the innermost counter in the element’s CSS counters set named diff --git a/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.h index d5ae7d510d6..b8725064d3a 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.h @@ -34,7 +34,7 @@ public: auto counter_style() const { return m_properties.counter_style; } auto join_string() const { return m_properties.join_string; } - String resolve(DOM::Element&) const; + String resolve(DOM::AbstractElement&) const; virtual String to_string(SerializationMode) const override; diff --git a/Libraries/LibWeb/DOM/AbstractElement.cpp b/Libraries/LibWeb/DOM/AbstractElement.cpp index 108b9efedc2..ad5143ddc17 100644 --- a/Libraries/LibWeb/DOM/AbstractElement.cpp +++ b/Libraries/LibWeb/DOM/AbstractElement.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace Web::DOM { @@ -20,6 +21,13 @@ void AbstractElement::visit(GC::Cell::Visitor& visitor) const visitor.visit(m_element); } +GC::Ptr AbstractElement::layout_node() +{ + if (m_pseudo_element.has_value()) + return m_element->get_pseudo_element_node(*m_pseudo_element); + return m_element->layout_node(); +} + GC::Ptr AbstractElement::parent_element() const { if (m_pseudo_element.has_value()) @@ -27,6 +35,39 @@ GC::Ptr AbstractElement::parent_element() const return m_element->parent_element(); } +Optional AbstractElement::walk_layout_tree(WalkMethod walk_method) +{ + GC::Ptr node = layout_node(); + if (!node) + return OptionalNone {}; + + while (true) { + switch (walk_method) { + case WalkMethod::Previous: + node = node->previous_in_pre_order(); + break; + case WalkMethod::PreviousSibling: + node = node->previous_sibling(); + break; + } + if (!node) + return OptionalNone {}; + + if (auto* previous_element = as_if(node->dom_node())) + return AbstractElement { *previous_element }; + + if (node->is_generated()) + return AbstractElement { *node->pseudo_element_generator(), node->generated_for_pseudo_element() }; + } +} + +bool AbstractElement::is_before(AbstractElement const& other) const +{ + auto this_node = layout_node(); + auto other_node = other.layout_node(); + return this_node && other_node && this_node->is_before(*other_node); +} + GC::Ptr AbstractElement::computed_properties() const { if (m_pseudo_element.has_value()) @@ -34,16 +75,46 @@ GC::Ptr AbstractElement::computed_properties() co return m_element->computed_properties(); } +bool AbstractElement::has_non_empty_counters_set() const +{ + if (m_pseudo_element.has_value()) + return m_element->get_pseudo_element(*m_pseudo_element)->has_non_empty_counters_set(); + return m_element->has_non_empty_counters_set(); +} + +Optional AbstractElement::counters_set() const +{ + if (m_pseudo_element.has_value()) + return m_element->get_pseudo_element(*m_pseudo_element)->counters_set(); + return m_element->counters_set(); +} + CSS::CountersSet& AbstractElement::ensure_counters_set() { - // FIXME: Handle pseudo-elements + if (m_pseudo_element.has_value()) + return m_element->get_pseudo_element(*m_pseudo_element)->ensure_counters_set(); return m_element->ensure_counters_set(); } void AbstractElement::set_counters_set(OwnPtr&& counters_set) { - // FIXME: Handle pseudo-elements - m_element->set_counters_set(move(counters_set)); + if (m_pseudo_element.has_value()) { + m_element->get_pseudo_element(*m_pseudo_element)->set_counters_set(move(counters_set)); + } else { + m_element->set_counters_set(move(counters_set)); + } +} + +String AbstractElement::debug_description() const +{ + if (m_pseudo_element.has_value()) { + StringBuilder builder; + builder.append(m_element->debug_description()); + builder.append("::"sv); + builder.append(CSS::pseudo_element_name(*m_pseudo_element)); + return builder.to_string_without_validation(); + } + return m_element->debug_description(); } } diff --git a/Libraries/LibWeb/DOM/AbstractElement.h b/Libraries/LibWeb/DOM/AbstractElement.h index da236ad7bee..b8b1e1bfa7c 100644 --- a/Libraries/LibWeb/DOM/AbstractElement.h +++ b/Libraries/LibWeb/DOM/AbstractElement.h @@ -21,15 +21,33 @@ public: Element const& element() const { return m_element; } Optional pseudo_element() const { return m_pseudo_element; } + GC::Ptr layout_node(); + GC::Ptr layout_node() const { return const_cast(this)->layout_node(); } + GC::Ptr parent_element() const; + Optional previous_in_tree_order() { return walk_layout_tree(WalkMethod::Previous); } + Optional previous_sibling_in_tree_order() { return walk_layout_tree(WalkMethod::PreviousSibling); } + bool is_before(AbstractElement const&) const; + GC::Ptr computed_properties() const; + bool has_non_empty_counters_set() const; + Optional counters_set() const; CSS::CountersSet& ensure_counters_set(); void set_counters_set(OwnPtr&&); void visit(GC::Cell::Visitor& visitor) const; + String debug_description() const; + bool operator==(AbstractElement const&) const = default; + private: + enum class WalkMethod : u8 { + Previous, + PreviousSibling, + }; + Optional walk_layout_tree(WalkMethod); + GC::Ref m_element; Optional m_pseudo_element; }; diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 05ed2226fa1..019dee2516e 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -128,6 +129,8 @@ void Element::visit_edges(Cell::Visitor& visitor) for (auto& registered_intersection_observers : *m_registered_intersection_observers) visitor.visit(registered_intersection_observers.observer); } + if (m_counters_set) + m_counters_set->visit_edges(visitor); } // https://dom.spec.whatwg.org/#dom-element-getattribute diff --git a/Libraries/LibWeb/DOM/Element.h b/Libraries/LibWeb/DOM/Element.h index 0c8e88273ab..2451bbc183e 100644 --- a/Libraries/LibWeb/DOM/Element.h +++ b/Libraries/LibWeb/DOM/Element.h @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include diff --git a/Libraries/LibWeb/DOM/PseudoElement.cpp b/Libraries/LibWeb/DOM/PseudoElement.cpp index 32aa4ab0beb..2553940edf5 100644 --- a/Libraries/LibWeb/DOM/PseudoElement.cpp +++ b/Libraries/LibWeb/DOM/PseudoElement.cpp @@ -21,6 +21,8 @@ void PseudoElement::visit_edges(JS::Cell::Visitor& visitor) visitor.visit(m_cascaded_properties); visitor.visit(m_computed_properties); visitor.visit(m_layout_node); + if (m_counters_set) + m_counters_set->visit_edges(visitor); } Optional PseudoElement::counters_set() const diff --git a/Libraries/LibWeb/Layout/TreeBuilder.cpp b/Libraries/LibWeb/Layout/TreeBuilder.cpp index 333dea86329..654bb9c95f8 100644 --- a/Libraries/LibWeb/Layout/TreeBuilder.cpp +++ b/Libraries/LibWeb/Layout/TreeBuilder.cpp @@ -189,7 +189,8 @@ void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Ps return; auto initial_quote_nesting_level = m_quote_nesting_level; - auto [pseudo_element_content, final_quote_nesting_level] = pseudo_element_style->content(element, initial_quote_nesting_level); + DOM::AbstractElement element_reference { element, pseudo_element }; + auto [pseudo_element_content, final_quote_nesting_level] = pseudo_element_style->content(element_reference, initial_quote_nesting_level); m_quote_nesting_level = final_quote_nesting_level; auto pseudo_element_display = pseudo_element_style->display(); // ::before and ::after only exist if they have content. `content: normal` computes to `none` for them. @@ -217,27 +218,37 @@ void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Ps static_cast(*pseudo_element_node).set_marker(list_item_marker); element.set_pseudo_element_node({}, CSS::PseudoElement::Marker, list_item_marker); pseudo_element_node->prepend_child(*list_item_marker); + + // FIXME: Support counters on element::pseudo::marker } pseudo_element_node->set_generated_for(pseudo_element, element); pseudo_element_node->set_initial_quote_nesting_level(initial_quote_nesting_level); - // FIXME: Handle images, and multiple values - if (pseudo_element_content.type == CSS::ContentData::Type::String) { - auto text = document.realm().create(document, pseudo_element_content.data); - auto text_node = document.heap().allocate(document, *text); - text_node->set_generated_for(pseudo_element, element); - - push_parent(*pseudo_element_node); - insert_node_into_inline_or_block_ancestor(*text_node, text_node->display(), AppendOrPrepend::Append); - pop_parent(); - } else { - TODO(); - } - element.set_pseudo_element_node({}, pseudo_element, pseudo_element_node); insert_node_into_inline_or_block_ancestor(*pseudo_element_node, pseudo_element_display, mode); pseudo_element_node->mutable_computed_values().set_content(pseudo_element_content); + + DOM::AbstractElement pseudo_element_reference { element, pseudo_element }; + CSS::resolve_counters(pseudo_element_reference); + // Now that we have counters, we can compute the content for real. Which is silly. + if (pseudo_element_content.type == CSS::ContentData::Type::String) { + auto [new_content, _] = pseudo_element_style->content(element_reference, initial_quote_nesting_level); + pseudo_element_node->mutable_computed_values().set_content(new_content); + + // FIXME: Handle images, and multiple values + if (new_content.type == CSS::ContentData::Type::String) { + auto text = document.realm().create(document, new_content.data); + auto text_node = document.heap().allocate(document, *text); + text_node->set_generated_for(pseudo_element, element); + + push_parent(*pseudo_element_node); + insert_node_into_inline_or_block_ancestor(*text_node, text_node->display(), AppendOrPrepend::Append); + pop_parent(); + } else { + TODO(); + } + } } // Block nodes inside inline nodes are allowed, but to maintain the invariant that either all layout children are @@ -479,8 +490,6 @@ void TreeBuilder::update_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& element.clear_pseudo_element_nodes({}); VERIFY(!element.needs_style_update()); style = element.computed_properties(); - DOM::AbstractElement element_reference { element }; - CSS::resolve_counters(element_reference); display = style->display(); if (display.is_none()) return; @@ -539,8 +548,15 @@ void TreeBuilder::update_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& auto prior_quote_nesting_level = m_quote_nesting_level; - if (should_create_layout_node) + if (should_create_layout_node) { + // Resolve counters now that we exist in the layout tree. + if (auto* element = as_if(dom_node)) { + DOM::AbstractElement element_reference { *element }; + CSS::resolve_counters(element_reference); + } + update_layout_tree_before_children(dom_node, *layout_node, context, element_has_content_visibility_hidden); + } if (should_create_layout_node || dom_node.child_needs_layout_tree_update()) { if ((dom_node.has_children() || shadow_root) && layout_node->can_have_children() && !element_has_content_visibility_hidden) { @@ -584,6 +600,9 @@ void TreeBuilder::update_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& top_layer_element->set_pseudo_element_node({}, CSS::PseudoElement::Backdrop, pseudo_element_node); pseudo_element_node->set_generated_for(CSS::PseudoElement::Backdrop, top_layer_element); insert_node_into_inline_or_block_ancestor(*pseudo_element_node, pseudo_element_display, AppendOrPrepend::Append); + + DOM::AbstractElement backdrop_reference { top_layer_element, CSS::PseudoElement::Backdrop }; + CSS::resolve_counters(backdrop_reference); }(); update_layout_tree(top_layer_element, context, should_create_layout_node ? MustCreateSubtree::Yes : MustCreateSubtree::No); } @@ -726,6 +745,8 @@ void TreeBuilder::update_layout_tree_after_children(DOM::Node& dom_node, GC::Ref element.set_pseudo_element_computed_properties(CSS::PseudoElement::Marker, marker_style); element.set_pseudo_element_node({}, CSS::PseudoElement::Marker, list_item_marker); layout_node->prepend_child(*list_item_marker); + DOM::AbstractElement marker_reference { element, CSS::PseudoElement::Marker }; + CSS::resolve_counters(marker_reference); } if (is(dom_node)) { diff --git a/Tests/LibWeb/Layout/expected/css/counters-on-pseudo-elements.txt b/Tests/LibWeb/Layout/expected/css/counters-on-pseudo-elements.txt new file mode 100644 index 00000000000..9709d475872 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/css/counters-on-pseudo-elements.txt @@ -0,0 +1,46 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x70 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x54 children: not-inline + BlockContainer at (8,8) content-size 784x18 children: inline + frag 0 from TextNode start: 0, length: 1, rect: [26.6875,8 9.34375x18] baseline: 13.796875 + "a" + InlineNode <(anonymous)> + frag 0 from TextNode start: 0, length: 3, rect: [8,8 18.6875x18] baseline: 13.796875 + "1. " + TextNode <#text> + TextNode <#text> + BlockContainer at (8,26) content-size 784x18 children: inline + frag 0 from TextNode start: 0, length: 1, rect: [29.15625,26 9.46875x18] baseline: 13.796875 + "b" + InlineNode <(anonymous)> + frag 0 from TextNode start: 0, length: 3, rect: [8,26 21.15625x18] baseline: 13.796875 + "2. " + TextNode <#text> + TextNode <#text> + BlockContainer at (8,44) content-size 784x18 children: inline + frag 0 from TextNode start: 0, length: 1, rect: [29.4375,44 8.890625x18] baseline: 13.796875 + "c" + InlineNode <(anonymous)> + frag 0 from TextNode start: 0, length: 3, rect: [8,44 21.4375x18] baseline: 13.796875 + "3. " + TextNode <#text> + TextNode <#text> + BlockContainer <(anonymous)> at (8,62) content-size 784x0 children: inline + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x70] + PaintableWithLines (BlockContainer) [8,8 784x54] + PaintableWithLines (BlockContainer
#a) [8,8 784x18] + PaintableWithLines (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
#b) [8,26 784x18] + PaintableWithLines (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer
#c) [8,44 784x18] + PaintableWithLines (InlineNode(anonymous)) + TextPaintable (TextNode<#text>) + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer(anonymous)) [8,62 784x0] diff --git a/Tests/LibWeb/Layout/input/css/counters-on-pseudo-elements.html b/Tests/LibWeb/Layout/input/css/counters-on-pseudo-elements.html new file mode 100644 index 00000000000..51b5a7f49e5 --- /dev/null +++ b/Tests/LibWeb/Layout/input/css/counters-on-pseudo-elements.html @@ -0,0 +1,10 @@ + +
a
b
c