mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-27 10:29:23 +00:00
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:
parent
24ace9f183
commit
9d77221c4d
Notes:
github-actions[bot]
2025-10-10 11:03:28 +00:00
Author: https://github.com/manuel-za
Commit: 9d77221c4d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5645
Reviewed-by: https://github.com/AtkinsSJ ✅
Reviewed-by: https://github.com/konradekk
2 changed files with 56 additions and 19 deletions
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
#include <AK/TemporaryChange.h>
|
#include <AK/TemporaryChange.h>
|
||||||
#include <LibWeb/CSS/Length.h>
|
#include <LibWeb/CSS/Length.h>
|
||||||
|
#include <LibWeb/CSS/PropertyID.h>
|
||||||
|
#include <LibWeb/DOM/Element.h>
|
||||||
#include <LibWeb/DOM/Node.h>
|
#include <LibWeb/DOM/Node.h>
|
||||||
#include <LibWeb/Dump.h>
|
#include <LibWeb/Dump.h>
|
||||||
#include <LibWeb/HTML/BrowsingContext.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);
|
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.
|
// 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,
|
// 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.
|
// in some cases even causing it to overlap with the non-floating content of the list.
|
||||||
CSSPixels left_space_before_children_formatted;
|
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.
|
// 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.
|
// Otherwise, the y at which we calculate the intrusion by floats might be incorrect.
|
||||||
ensure_sizes_correct_for_left_offset_calculation(*li_box);
|
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());
|
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.
|
// 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);
|
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());
|
bottom_of_lowest_margin_box = max(bottom_of_lowest_margin_box, box_state.offset.y() + box_state.content_height() + box_state.margin_box_bottom());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
* Copyright (c) 2022, MacDue <macdue@dueutil.tech>
|
||||||
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||||
* Copyright (c) 2025, Aziz B. Yesilyurt <abyesilyurt@gmail.com>
|
* Copyright (c) 2025, Aziz B. Yesilyurt <abyesilyurt@gmail.com>
|
||||||
|
* Copyright (c) 2025, Manuel Zahariev <manuel@duck.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
@ -11,6 +12,9 @@
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
#include <AK/TemporaryChange.h>
|
#include <AK/TemporaryChange.h>
|
||||||
#include <LibWeb/CSS/ComputedProperties.h>
|
#include <LibWeb/CSS/ComputedProperties.h>
|
||||||
|
#include <LibWeb/CSS/ComputedValues.h>
|
||||||
|
#include <LibWeb/CSS/Enums.h>
|
||||||
|
#include <LibWeb/CSS/PseudoElement.h>
|
||||||
#include <LibWeb/CSS/StyleComputer.h>
|
#include <LibWeb/CSS/StyleComputer.h>
|
||||||
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
|
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
|
||||||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||||
|
|
@ -269,6 +273,8 @@ void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Ps
|
||||||
auto [pseudo_element_content, final_quote_nesting_level] = pseudo_element_style->content(element_reference, initial_quote_nesting_level);
|
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;
|
m_quote_nesting_level = final_quote_nesting_level;
|
||||||
auto pseudo_element_display = pseudo_element_style->display();
|
auto pseudo_element_display = pseudo_element_style->display();
|
||||||
|
|
||||||
|
Optional<String> content_from_counter_style;
|
||||||
// ::before and ::after only exist if they have content. `content: normal` computes to `none` for them.
|
// ::before and ::after only exist if they have content. `content: normal` computes to `none` for them.
|
||||||
// We also don't create them if they are `display: none`.
|
// We also don't create them if they are `display: none`.
|
||||||
if (first_is_one_of(pseudo_element, CSS::PseudoElement::Before, CSS::PseudoElement::After)
|
if (first_is_one_of(pseudo_element, CSS::PseudoElement::Before, CSS::PseudoElement::After)
|
||||||
|
|
@ -277,14 +283,39 @@ void TreeBuilder::create_pseudo_element_if_needed(DOM::Element& element, CSS::Ps
|
||||||
|| pseudo_element_content.type == CSS::ContentData::Type::None))
|
|| pseudo_element_content.type == CSS::ContentData::Type::None))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// For ::marker with content or display 'none' -- do nothing.
|
||||||
|
if (pseudo_element == CSS::PseudoElement::Marker
|
||||||
|
&& (pseudo_element_display.is_none() || pseudo_element_content.type == CSS::ContentData::Type::None))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// For ::marker with content 'normal', create the marker pseudo-element from a ListItemMarkerBox
|
||||||
|
// FIXME: This + ListItemBox + ListItemMarkerBox will disappear once ::marker pseudo-elements with 'normal' content
|
||||||
|
// are rendered using the special list-item counter.
|
||||||
|
// See: https://github.com/LadybirdBrowser/ladybird/issues/4782
|
||||||
|
if (pseudo_element == CSS::PseudoElement::Marker && pseudo_element_content.type == CSS::ContentData::Type::Normal)
|
||||||
|
if (auto* list_box = as_if<ListItemBox>(*element.layout_node())) {
|
||||||
|
|
||||||
|
auto list_item_marker = document.heap().allocate<ListItemMarkerBox>(
|
||||||
|
document,
|
||||||
|
list_box->computed_values().list_style_type(),
|
||||||
|
list_box->computed_values().list_style_position(),
|
||||||
|
element,
|
||||||
|
*pseudo_element_style);
|
||||||
|
list_box->set_marker(list_item_marker);
|
||||||
|
element.set_computed_properties(CSS::PseudoElement::Marker, pseudo_element_style);
|
||||||
|
element.set_pseudo_element_node({}, CSS::PseudoElement::Marker, list_item_marker);
|
||||||
|
list_box->prepend_child(*list_item_marker);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto pseudo_element_node = DOM::Element::create_layout_node_for_display_type(document, pseudo_element_display, *pseudo_element_style, nullptr);
|
auto pseudo_element_node = DOM::Element::create_layout_node_for_display_type(document, pseudo_element_display, *pseudo_element_style, nullptr);
|
||||||
if (!pseudo_element_node)
|
if (!pseudo_element_node)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto& style_computer = document.style_computer();
|
|
||||||
|
|
||||||
// FIXME: This code actually computes style for element::marker, and shouldn't for element::pseudo::marker
|
// FIXME: This code actually computes style for element::marker, and shouldn't for element::pseudo::marker
|
||||||
if (is<ListItemBox>(*pseudo_element_node)) {
|
if (is<ListItemBox>(*pseudo_element_node)) {
|
||||||
|
auto& style_computer = document.style_computer();
|
||||||
|
|
||||||
auto marker_style = style_computer.compute_style({ element, CSS::PseudoElement::Marker });
|
auto marker_style = style_computer.compute_style({ element, CSS::PseudoElement::Marker });
|
||||||
auto list_item_marker = document.heap().allocate<ListItemMarkerBox>(
|
auto list_item_marker = document.heap().allocate<ListItemMarkerBox>(
|
||||||
document,
|
document,
|
||||||
|
|
@ -777,27 +808,13 @@ void TreeBuilder::update_layout_tree_before_children(DOM::Node& dom_node, GC::Re
|
||||||
auto& element = static_cast<DOM::Element&>(dom_node);
|
auto& element = static_cast<DOM::Element&>(dom_node);
|
||||||
push_parent(as<NodeWithStyle>(*layout_node));
|
push_parent(as<NodeWithStyle>(*layout_node));
|
||||||
create_pseudo_element_if_needed(element, CSS::PseudoElement::Before, AppendOrPrepend::Prepend);
|
create_pseudo_element_if_needed(element, CSS::PseudoElement::Before, AppendOrPrepend::Prepend);
|
||||||
|
|
||||||
pop_parent();
|
pop_parent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TreeBuilder::update_layout_tree_after_children(DOM::Node& dom_node, GC::Ref<Layout::Node> layout_node, TreeBuilder::Context& context, bool element_has_content_visibility_hidden)
|
void TreeBuilder::update_layout_tree_after_children(DOM::Node& dom_node, GC::Ref<Layout::Node> layout_node, TreeBuilder::Context& context, bool element_has_content_visibility_hidden)
|
||||||
{
|
{
|
||||||
auto& document = dom_node.document();
|
|
||||||
auto& style_computer = document.style_computer();
|
|
||||||
|
|
||||||
if (is<ListItemBox>(*layout_node)) {
|
|
||||||
auto& element = static_cast<DOM::Element&>(dom_node);
|
|
||||||
DOM::AbstractElement list_marker_pseudo { element, CSS::PseudoElement::Marker };
|
|
||||||
auto marker_style = style_computer.compute_style(list_marker_pseudo);
|
|
||||||
auto list_item_marker = document.heap().allocate<ListItemMarkerBox>(document, layout_node->computed_values().list_style_type(), layout_node->computed_values().list_style_position(), element, marker_style);
|
|
||||||
static_cast<ListItemBox&>(*layout_node).set_marker(list_item_marker);
|
|
||||||
element.set_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);
|
|
||||||
CSS::resolve_counters(list_marker_pseudo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is<SVG::SVGGraphicsElement>(dom_node)) {
|
if (is<SVG::SVGGraphicsElement>(dom_node)) {
|
||||||
auto& graphics_element = static_cast<SVG::SVGGraphicsElement&>(dom_node);
|
auto& graphics_element = static_cast<SVG::SVGGraphicsElement&>(dom_node);
|
||||||
// Create the layout tree for the SVG mask/clip paths as a child of the masked element.
|
// Create the layout tree for the SVG mask/clip paths as a child of the masked element.
|
||||||
|
|
@ -820,6 +837,16 @@ void TreeBuilder::update_layout_tree_after_children(DOM::Node& dom_node, GC::Ref
|
||||||
if (is<DOM::Element>(dom_node) && layout_node->can_have_children() && !element_has_content_visibility_hidden) {
|
if (is<DOM::Element>(dom_node) && layout_node->can_have_children() && !element_has_content_visibility_hidden) {
|
||||||
auto& element = static_cast<DOM::Element&>(dom_node);
|
auto& element = static_cast<DOM::Element&>(dom_node);
|
||||||
push_parent(as<NodeWithStyle>(*layout_node));
|
push_parent(as<NodeWithStyle>(*layout_node));
|
||||||
|
|
||||||
|
// https://drafts.csswg.org/css-lists-3/#marker-pseudo
|
||||||
|
// The marker box is generated by the ::marker pseudo-element of a list item as the list item’s first child,
|
||||||
|
// before the ::before pseudo-element (if it exists on the element). It is filled with content as defined
|
||||||
|
// in § 3.2 Generating Marker Contents.
|
||||||
|
// NOTE: This happens in update_layout_tree_after_children (and not in ..._before_...), since potential
|
||||||
|
// block container wrapper children are created after update_layout_tree_before_children.
|
||||||
|
if (layout_node->is_list_item_box())
|
||||||
|
create_pseudo_element_if_needed(element, CSS::PseudoElement::Marker, AppendOrPrepend::Prepend);
|
||||||
|
|
||||||
create_pseudo_element_if_needed(element, CSS::PseudoElement::After, AppendOrPrepend::Append);
|
create_pseudo_element_if_needed(element, CSS::PseudoElement::After, AppendOrPrepend::Append);
|
||||||
pop_parent();
|
pop_parent();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue