LibWeb/CSS: Add support for content to the ::marker pseudo-element

A ::marker pseudo-element is created for list item nodes (nodes
with display:list-item).

Before:
    - The content of the ::marker element is created magically from
    the value of the ordinal (for <ol>) or from a template (for <ul>).
    The style `content` is ignored for ::marker pseudo-elements.

After:
    - If a "list item node" has CSS `content` specified for its ::marker
    pseudo-element, use this to layout the pseudo-element,
    https://drafts.csswg.org/css-lists-3/#content-property
    - Otherwise, layout the list item node as before.
This commit is contained in:
Manuel Zahariev 2025-08-13 17:41:04 -07:00 committed by Sam Atkins
commit 9d77221c4d
Notes: github-actions[bot] 2025-10-10 11:03:28 +00:00
2 changed files with 56 additions and 19 deletions

View file

@ -7,6 +7,8 @@
#include <AK/TemporaryChange.h>
#include <LibWeb/CSS/Length.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/Dump.h>
#include <LibWeb/HTML/BrowsingContext.h>
@ -804,11 +806,17 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
resolve_used_height_if_treated_as_auto(box, available_space_for_height_resolution);
}
// This monster basically means: "a ListItemBox that does not have specified content in the ::marker pseudo-element".
// This happens for ::marker with content 'normal'.
// FIXME: We currently so not support ListItemBox-es generated by pseudo-elements. We will need to, eventually.
ListItemBox const* li_box = as_if<ListItemBox>(box);
bool is_list_item_box_without_css_content = li_box && (!(box.dom_node() && box.dom_node()->is_element() && as_if<DOM::Element const>(box.dom_node())->computed_properties(CSS::PseudoElement::Marker)->property(CSS::PropertyID::Content).is_content()));
// Before we insert the children of a list item we need to know the location of the marker.
// If we do not do this then left-floating elements inside the list item will push the marker to the right,
// in some cases even causing it to overlap with the non-floating content of the list.
CSSPixels left_space_before_children_formatted;
if (auto const* li_box = as_if<ListItemBox>(box)) {
if (is_list_item_box_without_css_content) {
// We need to ensure that our height and width are final before we calculate our left offset.
// Otherwise, the y at which we calculate the intrusion by floats might be incorrect.
ensure_sizes_correct_for_left_offset_calculation(*li_box);
@ -868,8 +876,10 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain
compute_inset(box, content_box_rect(block_container_state).size());
// Now that our children are formatted we place the ListItemBox with the left space we remembered.
if (auto const* li_box = as_if<ListItemBox>(box))
if (is_list_item_box_without_css_content)
// The marker pseudo-element will be created from a ListItemMarkerBox
layout_list_item_marker(*li_box, left_space_before_children_formatted);
// Otherwise, it will be a dealt with as a generic pseudo-element with the content of the ::marker pseudo-element.
bottom_of_lowest_margin_box = max(bottom_of_lowest_margin_box, box_state.offset.y() + box_state.content_height() + box_state.margin_box_bottom());