LibWeb: Allow style inheritance through slots
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

When a subtree is projected through a slot, its root now inherits style
from the slot's parent, rather than the parent of the unprojected root.

This fixes a ton of subtle issues, and is very noticeable on Reddit.
This commit is contained in:
Andreas Kling 2025-08-16 14:08:35 +02:00 committed by Andreas Kling
commit 9208a54b0b
Notes: github-actions[bot] 2025-08-16 19:04:51 +00:00
7 changed files with 64 additions and 34 deletions

View file

@ -81,6 +81,7 @@
#include <LibWeb/Fetch/Response.h>
#include <LibWeb/HTML/HTMLBRElement.h>
#include <LibWeb/HTML/HTMLHtmlElement.h>
#include <LibWeb/HTML/HTMLSlotElement.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/Layout/Node.h>
@ -180,8 +181,6 @@ OwnFontFaceKey::operator FontFaceKey() const
&& slope == other.slope;
}
static DOM::Element const* element_to_inherit_style_from(DOM::Element const*, Optional<CSS::PseudoElement>);
StyleComputer::StyleComputer(DOM::Document& document)
: m_document(document)
, m_default_font_metrics(16, Platform::FontPlugin::the().default_font(16)->pixel_metrics())
@ -1665,21 +1664,9 @@ GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element&
return cascaded_properties;
}
DOM::Element const* element_to_inherit_style_from(DOM::Element const* element, Optional<CSS::PseudoElement> pseudo_element)
{
// Pseudo-elements treat their originating element as their parent.
DOM::Element const* parent_element = nullptr;
if (pseudo_element.has_value()) {
parent_element = element;
} else if (element) {
parent_element = element->parent_or_shadow_host_element();
}
return parent_element;
}
NonnullRefPtr<StyleValue const> StyleComputer::get_inherit_value(CSS::PropertyID property_id, DOM::Element const* element, Optional<CSS::PseudoElement> pseudo_element)
{
auto* parent_element = element_to_inherit_style_from(element, pseudo_element);
auto parent_element = element ? element->element_to_inherit_style_from(pseudo_element) : nullptr;
if (!parent_element || !parent_element->computed_properties())
return property_initial_value(property_id);
@ -1751,7 +1738,7 @@ void StyleComputer::compute_defaulted_values(ComputedProperties& style, DOM::Ele
if (element && element->local_name() == HTML::TagNames::th
&& style.property(PropertyID::TextAlign).to_keyword() == Keyword::LibwebInheritOrCenter) {
auto const* parent_element = element;
while ((parent_element = element_to_inherit_style_from(parent_element, {}))) {
while ((parent_element = parent_element->element_to_inherit_style_from({}))) {
auto parent_computed = parent_element->computed_properties();
auto parent_cascaded = parent_element->cascaded_properties({});
if (!parent_computed || !parent_cascaded)
@ -1911,7 +1898,7 @@ CSSPixelFraction StyleComputer::absolute_size_mapping(Keyword keyword)
RefPtr<Gfx::FontCascadeList const> StyleComputer::compute_font_for_style_values(DOM::Element const* element, Optional<CSS::PseudoElement> pseudo_element, StyleValue const& font_family, StyleValue const& font_size, StyleValue const& font_style, StyleValue const& font_weight, StyleValue const& font_stretch, int math_depth) const
{
auto* parent_element = element_to_inherit_style_from(element, pseudo_element);
auto parent_element = element ? element->element_to_inherit_style_from(pseudo_element) : nullptr;
auto width = font_stretch.to_font_width();
auto weight = font_weight.to_font_weight();
@ -2180,7 +2167,7 @@ LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context(
{
auto normalize_value = [&](auto property_id, auto value) {
if (!value || value->is_inherit() || value->is_unset()) {
if (auto const* inheritance_parent = element_to_inherit_style_from(&element, pseudo_element)) {
if (auto const inheritance_parent = element.element_to_inherit_style_from(pseudo_element)) {
value = inheritance_parent->computed_properties()->property(property_id);
} else {
value = property_initial_value(property_id);
@ -2297,14 +2284,7 @@ static void compute_text_align(ComputedProperties& style, DOM::Element const& el
// value of start or end is interpreted against the parents direction value and results in a computed value of
// either left or right. Computes to start when specified on the root element.
if (style.property(PropertyID::TextAlign).to_keyword() == Keyword::MatchParent) {
// If it's a pseudo-element, then the "parent" is the originating element instead.
auto const* parent = [&]() -> DOM::Element const* {
if (pseudo_element.has_value())
return &element;
return element.parent_element();
}();
auto const parent = element.element_to_inherit_style_from(pseudo_element);
if (parent) {
auto const& parent_text_align = parent->computed_properties()->property(PropertyID::TextAlign);
auto const& parent_direction = parent->computed_properties()->direction();
@ -2355,7 +2335,7 @@ static BoxTypeTransformation required_box_type_transformation(ComputedProperties
// FIXME: Containment in a ruby container inlinifies the boxs display type, as described in [CSS-RUBY-1].
// NOTE: If we're computing style for a pseudo-element, the effective parent will be the originating element itself, not its parent.
auto parent = pseudo_element.has_value() ? GC::Ptr<DOM::Element const> { &element } : element.parent_element();
auto parent = element.element_to_inherit_style_from(pseudo_element);
// A parent with a grid or flex display value blockifies the boxs display type. [CSS-GRID-1] [CSS-FLEXBOX-1]
if (parent && parent->computed_properties()) {
@ -2597,10 +2577,10 @@ RefPtr<StyleValue const> StyleComputer::recascade_font_size_if_needed(
// Reconstruct the line of ancestor elements we need to inherit style from, and then do the cascade again
// but only for the font-size property.
Vector<DOM::Element&> ancestors;
Vector<DOM::Element const&> ancestors;
if (pseudo_element.has_value())
ancestors.append(element);
for (auto ancestor = element.parent_element(); ancestor; ancestor = ancestor->parent_element())
for (auto ancestor = element.element_to_inherit_style_from(pseudo_element); ancestor; ancestor = ancestor->element_to_inherit_style_from({}))
ancestors.append(*ancestor);
NonnullRefPtr<StyleValue const> new_font_size = CSS::LengthStyleValue::create(CSS::Length::make_px(default_monospace_font_size_in_px));
@ -2664,7 +2644,7 @@ GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::Element& elem
// FIXME: Logical properties should inherit from their parent's equivalent unmapped logical property.
if ((!value && is_inherited_property(property_id))
|| (value && value->is_inherit())) {
if (auto inheritance_parent = element_to_inherit_style_from(&element, pseudo_element)) {
if (auto const inheritance_parent = element.element_to_inherit_style_from(pseudo_element)) {
value = inheritance_parent->computed_properties()->property(property_id);
inherited = ComputedProperties::Inherited::Yes;
} else {
@ -3183,9 +3163,9 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_value_of_custom_property(
// Unset is the same as inherit for inherited properties, and by default all custom properties are inherited.
// FIXME: Support non-inherited registered custom properties.
if (value->is_inherit() || value->is_unset()) {
if (!abstract_element.parent_element())
if (!abstract_element.element_to_inherit_style_from())
return document.custom_property_initial_value(name);
auto inherited_value = DOM::AbstractElement { const_cast<DOM::Element&>(*abstract_element.parent_element()) }.get_custom_property(name);
auto inherited_value = DOM::AbstractElement { const_cast<DOM::Element&>(*abstract_element.element_to_inherit_style_from()) }.get_custom_property(name);
if (!inherited_value)
return document.custom_property_initial_value(name);
return inherited_value.release_nonnull();
@ -3235,9 +3215,9 @@ void StyleComputer::compute_math_depth(ComputedProperties& style, DOM::Element c
compute_defaulted_property_value(style, element, CSS::PropertyID::MathStyle, pseudo_element);
auto inherited_math_depth = [&]() {
if (!element || !element->parent_element())
if (!element || !element->element_to_inherit_style_from(pseudo_element))
return InitialValues::math_depth();
return element->parent_element()->computed_properties()->math_depth();
return element->element_to_inherit_style_from(pseudo_element)->computed_properties()->math_depth();
};
auto const& value = style.property(CSS::PropertyID::MathDepth);

View file

@ -40,6 +40,11 @@ GC::Ptr<Element const> AbstractElement::parent_element() const
return m_element->parent_element();
}
GC::Ptr<Element const> AbstractElement::element_to_inherit_style_from() const
{
return m_element->element_to_inherit_style_from(m_pseudo_element);
}
Optional<AbstractElement> AbstractElement::walk_layout_tree(WalkMethod walk_method)
{
GC::Ptr<Layout::Node> node = layout_node();

View file

@ -27,6 +27,7 @@ public:
GC::Ptr<Layout::NodeWithStyle const> layout_node() const { return const_cast<AbstractElement*>(this)->layout_node(); }
GC::Ptr<Element const> parent_element() const;
GC::Ptr<Element const> element_to_inherit_style_from() const;
Optional<AbstractElement> previous_in_tree_order() { return walk_layout_tree(WalkMethod::Previous); }
Optional<AbstractElement> previous_sibling_in_tree_order() { return walk_layout_tree(WalkMethod::PreviousSibling); }
bool is_before(AbstractElement const&) const;

View file

@ -4131,4 +4131,17 @@ GC::Ref<CSS::StylePropertyMapReadOnly> Element::computed_style_map()
return *m_computed_style_map_cache;
}
// The element to inherit style from.
// If a pseudo-element is specified, this will return the element itself.
// Otherwise, if this element is slotted somewhere, it will return the slot's element to inherit style from.
// Otherwise, it will return the parent or shadow host element of this element.
GC::Ptr<Element const> Element::element_to_inherit_style_from(Optional<CSS::PseudoElement> pseudo_element) const
{
if (pseudo_element.has_value())
return this;
while (auto const slot = assigned_slot_internal())
return slot->element_to_inherit_style_from({});
return parent_or_shadow_host_element();
}
}

View file

@ -233,6 +233,8 @@ public:
WebIDL::ExceptionOr<GC::Ref<DOM::DocumentFragment>> parse_fragment(StringView markup);
[[nodiscard]] GC::Ptr<Element const> element_to_inherit_style_from(Optional<CSS::PseudoElement>) const;
WebIDL::ExceptionOr<String> inner_html() const;
WebIDL::ExceptionOr<void> set_inner_html(StringView);

View file

@ -0,0 +1,20 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x54 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x38 children: not-inline
Box <main> at (13,13) content-size 774x28 flex-container(row) [FFC] children: not-inline
BlockContainer <foobar> at (13,13) content-size 52.5625x28 flex-item [BFC] children: not-inline
BlockContainer <div> at (18,18) content-size 42.5625x18 children: inline
frag 0 from TextNode start: 0, length: 5, rect: [18,18 42.5625x18] baseline: 13.796875
"story"
TextNode <#text>
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x54]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x38]
PaintableBox (Box<MAIN>) [8,8 784x38]
PaintableWithLines (BlockContainer<FOOBAR>) [13,13 52.5625x28]
PaintableWithLines (BlockContainer<DIV>) [13,13 52.5625x28]
TextPaintable (TextNode<#text>)
SC for Viewport<#document> [0,0 800x600] [children: 1] (z-index: auto)
SC for BlockContainer<HTML> [0,0 800x54] [children: 0] (z-index: auto)

View file

@ -0,0 +1,9 @@
<!doctype html><style>
* { outline: 1px solid black; }
div { border: 5px solid lime; }
</style><body><template shadowrootmode="open"><style>
main {
display: flex;
border: 5px solid orange;
}
</style><main><slot></slot></main></template><foobar><div>story