mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 20:15:17 +00:00
LibWeb: Support both ::before/::after pseudo elements on button elements
This was mainly a matter of deferring the wrapping of the button's children until after its internal layout tree has been constructed. That way we don't lose any pseudo elements spawned along the way. Fixes #2397. Fixes #2399.
This commit is contained in:
parent
0f17ad9ebc
commit
4fa372564d
Notes:
github-actions[bot]
2025-02-03 15:00:37 +00:00
Author: https://github.com/awesomekling Commit: https://github.com/LadybirdBrowser/ladybird/commit/4fa372564d5 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3435
7 changed files with 124 additions and 80 deletions
|
@ -604,13 +604,89 @@ void TreeBuilder::update_layout_tree(DOM::Node& dom_node, TreeBuilder::Context&
|
|||
}
|
||||
}
|
||||
|
||||
if (should_create_layout_node)
|
||||
if (should_create_layout_node) {
|
||||
update_layout_tree_after_children(dom_node, *layout_node, context, element_has_content_visibility_hidden);
|
||||
wrap_in_button_layout_tree_if_needed(dom_node, *layout_node);
|
||||
|
||||
// If we completely finished inserting a block level element into an inline parent, we need to fix up the tree so
|
||||
// that we can maintain the invariant that all children are either inline or non-inline. We can't do this earlier,
|
||||
// because the restructuring adds new children after this node that become part of the ancestor stack.
|
||||
auto* layout_parent = layout_node->parent();
|
||||
if (layout_parent && layout_parent->display().is_inline_outside() && !display.is_contents()
|
||||
&& !display.is_inline_outside() && layout_parent->display().is_flow_inside() && !layout_node->is_out_of_flow())
|
||||
restructure_block_node_in_inline_parent(static_cast<NodeWithStyleAndBoxModelMetrics&>(*layout_node));
|
||||
}
|
||||
|
||||
dom_node.set_needs_layout_tree_update(false);
|
||||
dom_node.set_child_needs_layout_tree_update(false);
|
||||
}
|
||||
|
||||
void TreeBuilder::wrap_in_button_layout_tree_if_needed(DOM::Node& dom_node, GC::Ref<Layout::Node> layout_node)
|
||||
{
|
||||
auto is_button_layout = [&] {
|
||||
if (dom_node.is_html_button_element())
|
||||
return true;
|
||||
if (!dom_node.is_html_input_element())
|
||||
return false;
|
||||
// https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-button
|
||||
// An input element whose type attribute is in the Submit Button, Reset Button, or Button state, when it generates a CSS box, is expected to depict a button and use button layout
|
||||
auto const& input_element = static_cast<HTML::HTMLInputElement const&>(dom_node);
|
||||
if (input_element.is_button())
|
||||
return true;
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!is_button_layout)
|
||||
return;
|
||||
|
||||
auto display = layout_node->display();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/rendering.html#button-layout
|
||||
// If the computed value of 'inline-size' is 'auto', then the used value is the fit-content inline size.
|
||||
if (is_button_layout && dom_node.layout_node()->computed_values().width().is_auto()) {
|
||||
auto& computed_values = as<NodeWithStyle>(*dom_node.layout_node()).mutable_computed_values();
|
||||
computed_values.set_width(CSS::Size::make_fit_content());
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/rendering.html#button-layout
|
||||
// If the element is an input element, or if it is a button element and its computed value for
|
||||
// 'display' is not 'inline-grid', 'grid', 'inline-flex', or 'flex', then the element's box has
|
||||
// a child anonymous button content box with the following behaviors:
|
||||
if (is_button_layout && !display.is_grid_inside() && !display.is_flex_inside()) {
|
||||
auto& parent = *layout_node;
|
||||
|
||||
// If the box does not overflow in the vertical axis, then it is centered vertically.
|
||||
// FIXME: Only apply alignment when box overflows
|
||||
auto flex_computed_values = parent.computed_values().clone_inherited_values();
|
||||
auto& mutable_flex_computed_values = static_cast<CSS::MutableComputedValues&>(*flex_computed_values);
|
||||
mutable_flex_computed_values.set_display(CSS::Display { CSS::DisplayOutside::Block, CSS::DisplayInside::Flex });
|
||||
mutable_flex_computed_values.set_justify_content(CSS::JustifyContent::Center);
|
||||
mutable_flex_computed_values.set_flex_direction(CSS::FlexDirection::Column);
|
||||
mutable_flex_computed_values.set_height(CSS::Size::make_percentage(CSS::Percentage(100)));
|
||||
mutable_flex_computed_values.set_min_height(parent.computed_values().min_height());
|
||||
auto flex_wrapper = parent.heap().template allocate<BlockContainer>(parent.document(), nullptr, move(flex_computed_values));
|
||||
|
||||
auto content_box_computed_values = parent.computed_values().clone_inherited_values();
|
||||
auto content_box_wrapper = parent.heap().template allocate<BlockContainer>(parent.document(), nullptr, move(content_box_computed_values));
|
||||
content_box_wrapper->set_children_are_inline(parent.children_are_inline());
|
||||
|
||||
Vector<GC::Root<Node>> sequence;
|
||||
for (auto child = parent.first_child(); child; child = child->next_sibling()) {
|
||||
sequence.append(*child);
|
||||
}
|
||||
|
||||
for (auto& node : sequence) {
|
||||
parent.remove_child(*node);
|
||||
content_box_wrapper->append_child(*node);
|
||||
}
|
||||
|
||||
flex_wrapper->append_child(*content_box_wrapper);
|
||||
|
||||
parent.append_child(*flex_wrapper);
|
||||
parent.set_children_are_inline(false);
|
||||
}
|
||||
}
|
||||
|
||||
void TreeBuilder::update_layout_tree_before_children(DOM::Node& dom_node, GC::Ref<Layout::Node> layout_node, TreeBuilder::Context&, bool element_has_content_visibility_hidden)
|
||||
{
|
||||
// Add node for the ::before pseudo-element.
|
||||
|
@ -626,7 +702,6 @@ void TreeBuilder::update_layout_tree_after_children(DOM::Node& dom_node, GC::Ref
|
|||
{
|
||||
auto& document = dom_node.document();
|
||||
auto& style_computer = document.style_computer();
|
||||
auto display = layout_node->display();
|
||||
|
||||
if (is<ListItemBox>(*layout_node)) {
|
||||
auto& element = static_cast<DOM::Element&>(dom_node);
|
||||
|
@ -670,66 +745,6 @@ void TreeBuilder::update_layout_tree_after_children(DOM::Node& dom_node, GC::Ref
|
|||
layout_mask_or_clip_path(clip_path);
|
||||
}
|
||||
|
||||
auto is_button_layout = [&] {
|
||||
if (dom_node.is_html_button_element())
|
||||
return true;
|
||||
if (!dom_node.is_html_input_element())
|
||||
return false;
|
||||
// https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-button
|
||||
// An input element whose type attribute is in the Submit Button, Reset Button, or Button state, when it generates a CSS box, is expected to depict a button and use button layout
|
||||
auto const& input_element = static_cast<HTML::HTMLInputElement const&>(dom_node);
|
||||
if (input_element.is_button())
|
||||
return true;
|
||||
return false;
|
||||
}();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/rendering.html#button-layout
|
||||
// If the computed value of 'inline-size' is 'auto', then the used value is the fit-content inline size.
|
||||
if (is_button_layout && dom_node.layout_node()->computed_values().width().is_auto()) {
|
||||
auto& computed_values = as<NodeWithStyle>(*dom_node.layout_node()).mutable_computed_values();
|
||||
computed_values.set_width(CSS::Size::make_fit_content());
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/rendering.html#button-layout
|
||||
// If the element is an input element, or if it is a button element and its computed value for
|
||||
// 'display' is not 'inline-grid', 'grid', 'inline-flex', or 'flex', then the element's box has
|
||||
// a child anonymous button content box with the following behaviors:
|
||||
if (is_button_layout && !display.is_grid_inside() && !display.is_flex_inside()) {
|
||||
auto& parent = *dom_node.layout_node();
|
||||
|
||||
// If the box does not overflow in the vertical axis, then it is centered vertically.
|
||||
// FIXME: Only apply alignment when box overflows
|
||||
auto flex_computed_values = parent.computed_values().clone_inherited_values();
|
||||
auto& mutable_flex_computed_values = static_cast<CSS::MutableComputedValues&>(*flex_computed_values);
|
||||
mutable_flex_computed_values.set_display(CSS::Display { CSS::DisplayOutside::Block, CSS::DisplayInside::Flex });
|
||||
mutable_flex_computed_values.set_justify_content(CSS::JustifyContent::Center);
|
||||
mutable_flex_computed_values.set_flex_direction(CSS::FlexDirection::Column);
|
||||
mutable_flex_computed_values.set_height(CSS::Size::make_percentage(CSS::Percentage(100)));
|
||||
mutable_flex_computed_values.set_min_height(parent.computed_values().min_height());
|
||||
auto flex_wrapper = parent.heap().template allocate<BlockContainer>(parent.document(), nullptr, move(flex_computed_values));
|
||||
|
||||
auto content_box_computed_values = parent.computed_values().clone_inherited_values();
|
||||
auto content_box_wrapper = parent.heap().template allocate<BlockContainer>(parent.document(), nullptr, move(content_box_computed_values));
|
||||
content_box_wrapper->set_children_are_inline(parent.children_are_inline());
|
||||
|
||||
Vector<GC::Root<Node>> sequence;
|
||||
for (auto child = parent.first_child(); child; child = child->next_sibling()) {
|
||||
if (child->is_generated_for_before_pseudo_element())
|
||||
continue;
|
||||
sequence.append(*child);
|
||||
}
|
||||
|
||||
for (auto& node : sequence) {
|
||||
parent.remove_child(*node);
|
||||
content_box_wrapper->append_child(*node);
|
||||
}
|
||||
|
||||
flex_wrapper->append_child(*content_box_wrapper);
|
||||
|
||||
parent.append_child(*flex_wrapper);
|
||||
parent.set_children_are_inline(false);
|
||||
}
|
||||
|
||||
// Add nodes for the ::after pseudo-element.
|
||||
if (is<DOM::Element>(dom_node) && layout_node->can_have_children() && !element_has_content_visibility_hidden) {
|
||||
auto& element = static_cast<DOM::Element&>(dom_node);
|
||||
|
@ -737,14 +752,6 @@ void TreeBuilder::update_layout_tree_after_children(DOM::Node& dom_node, GC::Ref
|
|||
create_pseudo_element_if_needed(element, CSS::Selector::PseudoElement::Type::After, AppendOrPrepend::Append);
|
||||
pop_parent();
|
||||
}
|
||||
|
||||
// If we completely finished inserting a block level element into an inline parent, we need to fix up the tree so
|
||||
// that we can maintain the invariant that all children are either inline or non-inline. We can't do this earlier,
|
||||
// because the restructuring adds new children after this node that become part of the ancestor stack.
|
||||
auto* layout_parent = layout_node->parent();
|
||||
if (layout_parent && layout_parent->display().is_inline_outside() && !display.is_contents()
|
||||
&& !display.is_inline_outside() && layout_parent->display().is_flow_inside() && !layout_node->is_out_of_flow())
|
||||
restructure_block_node_in_inline_parent(static_cast<NodeWithStyleAndBoxModelMetrics&>(*layout_node));
|
||||
}
|
||||
|
||||
GC::Ptr<Layout::Node> TreeBuilder::build(DOM::Node& dom_node)
|
||||
|
|
|
@ -31,6 +31,7 @@ private:
|
|||
|
||||
void update_layout_tree_before_children(DOM::Node&, GC::Ref<Layout::Node>, Context&, bool element_has_content_visibility_hidden);
|
||||
void update_layout_tree_after_children(DOM::Node&, GC::Ref<Layout::Node>, Context&, bool element_has_content_visibility_hidden);
|
||||
void wrap_in_button_layout_tree_if_needed(DOM::Node&, GC::Ref<Layout::Node>);
|
||||
enum class MustCreateSubtree {
|
||||
No,
|
||||
Yes,
|
||||
|
|
|
@ -4,9 +4,9 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
|||
frag 0 from BlockContainer start: 0, length: 0, rect: [29,29 0x0] baseline: 42
|
||||
BlockContainer <button> at (29,29) content-size 0x0 positioned inline-block [BFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (29,29) content-size 0x0 flex-container(column) [FFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (29,29) content-size 0x0 [BFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (9,9) content-size 40x40 positioned [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> at (29,29) content-size 0x0 flex-item [BFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (9,9) content-size 40x40 positioned [BFC] children: inline
|
||||
TextNode <#text>
|
||||
TextNode <#text>
|
||||
|
||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||
|
@ -15,4 +15,4 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
|||
PaintableWithLines (BlockContainer<BUTTON>) [8,8 42x42]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [29,29 0x0]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [29,29 0x0]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [9,9 40x40]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [9,9 40x40]
|
||||
|
|
|
@ -8,8 +8,8 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
|||
frag 0 from TextNode start: 0, length: 14, rect: [13,10 414.703125x55] baseline: 42.484375
|
||||
"See more games"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> at (9,9) content-size 422.703125x57 positioned [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> at (9,9) content-size 422.703125x57 positioned [BFC] children: inline
|
||||
TextNode <#text>
|
||||
|
||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x75]
|
||||
|
@ -18,4 +18,4 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
|||
PaintableWithLines (BlockContainer(anonymous)) [13,10 414.703125x55]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [13,10 414.703125x55]
|
||||
TextPaintable (TextNode<#text>)
|
||||
PaintableWithLines (BlockContainer(anonymous)) [9,9 422.703125x57]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [9,9 422.703125x57]
|
||||
|
|
|
@ -3,19 +3,19 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
|||
BlockContainer <body> at (8,8) content-size 784x59 children: inline
|
||||
frag 0 from BlockContainer start: 0, length: 0, rect: [13,10 414.703125x55] baseline: 44.484375
|
||||
BlockContainer <button.button_button___eDCW> at (13,10) content-size 414.703125x55 positioned inline-block [BFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (9,9) content-size 422.703125x57 positioned [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> at (13,10) content-size 414.703125x55 flex-container(column) [FFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (13,10) content-size 414.703125x55 flex-item [BFC] children: inline
|
||||
frag 0 from TextNode start: 0, length: 14, rect: [13,10 414.703125x55] baseline: 42.484375
|
||||
"See more games"
|
||||
BlockContainer <(anonymous)> at (9,9) content-size 422.703125x57 positioned [BFC] children: inline
|
||||
TextNode <#text>
|
||||
TextNode <#text>
|
||||
|
||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x75]
|
||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x59]
|
||||
PaintableWithLines (BlockContainer<BUTTON>.button_button___eDCW) [8,8 424.703125x59]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [9,9 422.703125x57]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [13,10 414.703125x55]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [13,10 414.703125x55]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [9,9 422.703125x57]
|
||||
TextPaintable (TextNode<#text>)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||
BlockContainer <html> at (0,0) content-size 800x37 [BFC] children: not-inline
|
||||
BlockContainer <body> at (8,8) content-size 784x21 children: inline
|
||||
frag 0 from BlockContainer start: 0, length: 0, rect: [13,10 82x17] baseline: 15.296875
|
||||
BlockContainer <button> at (13,10) content-size 82x17 inline-block [BFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (13,10) content-size 82x17 flex-container(column) [FFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (13,10) content-size 82x17 flex-item [BFC] children: inline
|
||||
frag 0 from TextNode start: 0, length: 3, rect: [40.15625,10 27.640625x17] baseline: 13.296875
|
||||
"bar"
|
||||
InlineNode <(anonymous)>
|
||||
frag 0 from TextNode start: 0, length: 3, rect: [13,10 27.15625x17] baseline: 13.296875
|
||||
"foo"
|
||||
TextNode <#text>
|
||||
TextNode <#text>
|
||||
InlineNode <(anonymous)>
|
||||
frag 0 from TextNode start: 0, length: 3, rect: [67.796875,10 27.203125x17] baseline: 13.296875
|
||||
"baz"
|
||||
TextNode <#text>
|
||||
TextNode <#text>
|
||||
|
||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x37]
|
||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
||||
PaintableWithLines (BlockContainer<BUTTON>) [8,8 92x21]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [13,10 82x17]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [13,10 82x17]
|
||||
PaintableWithLines (InlineNode(anonymous))
|
||||
TextPaintable (TextNode<#text>)
|
||||
TextPaintable (TextNode<#text>)
|
||||
PaintableWithLines (InlineNode(anonymous))
|
||||
TextPaintable (TextNode<#text>)
|
|
@ -0,0 +1,5 @@
|
|||
<!doctype html><style>
|
||||
*, ::before, ::after { outline: 1px solid black; }
|
||||
button:before { content: "foo"; }
|
||||
button:after { content: "baz"; }
|
||||
</style><body><button>bar</button>
|
Loading…
Add table
Reference in a new issue