LibWeb: Lay out the fieldset's rendered legend
This commit is contained in:
parent
6218f1a609
commit
81f8866606
Notes:
github-actions[bot]
2024-11-29 12:51:25 +00:00
Author: https://github.com/kostyafarber Commit: https://github.com/LadybirdBrowser/ladybird/commit/81f88666067 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2546 Reviewed-by: https://github.com/tcl3 ✅
12 changed files with 356 additions and 15 deletions
|
@ -619,6 +619,7 @@ set(SOURCES
|
|||
Painting/DisplayList.cpp
|
||||
Painting/DisplayListPlayerSkia.cpp
|
||||
Painting/DisplayListRecorder.cpp
|
||||
Painting/FieldSetPaintable.cpp
|
||||
Painting/GradientPainting.cpp
|
||||
Painting/ImagePaintable.cpp
|
||||
Painting/LabelablePaintable.cpp
|
||||
|
@ -833,12 +834,12 @@ compile_ipc(Worker/WebWorkerClient.ipc Worker/WebWorkerClientEndpoint.h)
|
|||
compile_ipc(Worker/WebWorkerServer.ipc Worker/WebWorkerServerEndpoint.h)
|
||||
|
||||
invoke_generator(
|
||||
"AriaRoles.cpp"
|
||||
Lagom::GenerateAriaRoles
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/ARIA/AriaRoles.json"
|
||||
"ARIA/AriaRoles.h"
|
||||
"ARIA/AriaRoles.cpp"
|
||||
arguments -j "${CMAKE_CURRENT_SOURCE_DIR}/ARIA/AriaRoles.json"
|
||||
"AriaRoles.cpp"
|
||||
Lagom::GenerateAriaRoles
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/ARIA/AriaRoles.json"
|
||||
"ARIA/AriaRoles.h"
|
||||
"ARIA/AriaRoles.cpp"
|
||||
arguments -j "${CMAKE_CURRENT_SOURCE_DIR}/ARIA/AriaRoles.json"
|
||||
)
|
||||
|
||||
generate_css_implementation()
|
||||
|
|
|
@ -665,6 +665,7 @@ namespace Web::Painting {
|
|||
class AudioPaintable;
|
||||
class ButtonPaintable;
|
||||
class CheckBoxPaintable;
|
||||
class FieldSetPaintable;
|
||||
class LabelablePaintable;
|
||||
class MediaPaintable;
|
||||
class Paintable;
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Layout/BlockFormattingContext.h>
|
||||
#include <LibWeb/Layout/Box.h>
|
||||
#include <LibWeb/Layout/FieldSetBox.h>
|
||||
#include <LibWeb/Layout/InlineFormattingContext.h>
|
||||
#include <LibWeb/Layout/LegendBox.h>
|
||||
#include <LibWeb/Layout/LineBuilder.h>
|
||||
#include <LibWeb/Layout/ListItemBox.h>
|
||||
#include <LibWeb/Layout/ListItemMarkerBox.h>
|
||||
|
@ -73,6 +75,38 @@ void BlockFormattingContext::run(AvailableSpace const& available_space)
|
|||
return;
|
||||
}
|
||||
|
||||
if (is<FieldSetBox>(root())) {
|
||||
if (root().children_are_inline())
|
||||
layout_inline_children(root(), available_space);
|
||||
else
|
||||
layout_block_level_children(root(), available_space);
|
||||
|
||||
auto const& fieldset_box = verify_cast<FieldSetBox>(root());
|
||||
if (!(fieldset_box.has_rendered_legend())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const* legend = root().first_child_of_type<LegendBox>();
|
||||
auto& legend_state = m_state.get_mutable(*legend);
|
||||
auto& fieldset_state = m_state.get_mutable(root());
|
||||
|
||||
// The element is expected to be positioned in the block-flow direction such that
|
||||
// its border box is centered over the border on the block-start side of the fieldset element.
|
||||
// FIXME: this should take writing modes into consideration.
|
||||
auto legend_height = legend_state.border_box_height();
|
||||
auto new_y = -((legend_height) / 2) - fieldset_state.padding_top;
|
||||
legend_state.set_content_offset({ legend_state.offset.x(), new_y });
|
||||
|
||||
// If the computed value of 'inline-size' is 'auto',
|
||||
// then the used value is the fit-content inline size.
|
||||
if (legend->computed_values().width().is_auto()) {
|
||||
auto width = calculate_fit_content_width(*legend, available_space);
|
||||
legend_state.set_content_width(width);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (root().children_are_inline())
|
||||
layout_inline_children(root(), available_space);
|
||||
else
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/HTMLLegendElement.h>
|
||||
#include <LibWeb/Layout/FieldSetBox.h>
|
||||
#include <LibWeb/Layout/LegendBox.h>
|
||||
#include <LibWeb/Painting/FieldSetPaintable.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
|
@ -22,13 +24,27 @@ FieldSetBox::~FieldSetBox() = default;
|
|||
bool FieldSetBox::has_rendered_legend() const
|
||||
{
|
||||
// https://html.spec.whatwg.org/#rendered-legend
|
||||
if (this->has_children() && this->first_child()->is_legend_box()) {
|
||||
auto* first_child = this->first_child();
|
||||
return first_child->computed_values().float_() == CSS::Float::None
|
||||
&& first_child->computed_values().position() != CSS::Positioning::Absolute
|
||||
&& first_child->computed_values().position() != CSS::Positioning::Fixed;
|
||||
bool has_rendered_legend = false;
|
||||
if (has_children()) {
|
||||
for_each_child_of_type<Box>([&](Box const& child) {
|
||||
if (child.is_anonymous())
|
||||
return IterationDecision::Continue;
|
||||
|
||||
if (!child.is_legend_box())
|
||||
return IterationDecision::Break;
|
||||
|
||||
has_rendered_legend = child.computed_values().float_() == CSS::Float::None
|
||||
&& child.computed_values().position() != CSS::Positioning::Absolute
|
||||
&& child.computed_values().position() != CSS::Positioning::Fixed;
|
||||
return IterationDecision::Break;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
return has_rendered_legend;
|
||||
}
|
||||
|
||||
GC::Ptr<Painting::Paintable> FieldSetBox::create_paintable() const
|
||||
{
|
||||
return Painting::FieldSetPaintable::create(*this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
|
||||
#include <LibWeb/Painting/FieldSetPaintable.h>
|
||||
namespace Web::Layout {
|
||||
|
||||
class FieldSetBox final : public BlockContainer {
|
||||
|
@ -22,10 +22,10 @@ public:
|
|||
DOM::Element& dom_node() { return static_cast<DOM::Element&>(*BlockContainer::dom_node()); }
|
||||
DOM::Element const& dom_node() const { return static_cast<DOM::Element const&>(*BlockContainer::dom_node()); }
|
||||
|
||||
void layout_legend() const;
|
||||
bool has_rendered_legend() const;
|
||||
virtual GC::Ptr<Painting::Paintable> create_paintable() const override;
|
||||
|
||||
private:
|
||||
bool has_rendered_legend() const;
|
||||
virtual bool is_fieldset_box() const final
|
||||
{
|
||||
return true;
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <LibWeb/HTML/HTMLLIElement.h>
|
||||
#include <LibWeb/HTML/HTMLOListElement.h>
|
||||
#include <LibWeb/HTML/HTMLSlotElement.h>
|
||||
#include <LibWeb/Layout/FieldSetBox.h>
|
||||
#include <LibWeb/Layout/ListItemBox.h>
|
||||
#include <LibWeb/Layout/ListItemMarkerBox.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
|
@ -76,6 +77,9 @@ static Layout::Node& insertion_parent_for_inline_node(Layout::NodeWithStyle& lay
|
|||
return *layout_parent.last_child();
|
||||
};
|
||||
|
||||
if (is<FieldSetBox>(layout_parent))
|
||||
return last_child_creating_anonymous_wrapper_if_needed(layout_parent);
|
||||
|
||||
if (layout_parent.display().is_inline_outside() && layout_parent.display().is_flow_inside())
|
||||
return layout_parent;
|
||||
|
||||
|
|
106
Libraries/LibWeb/Painting/FieldSetPaintable.cpp
Normal file
106
Libraries/LibWeb/Painting/FieldSetPaintable.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Kostya Farber <kostya.farber@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/Layout/LegendBox.h>
|
||||
#include <LibWeb/Painting/FieldSetPaintable.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
GC_DEFINE_ALLOCATOR(FieldSetPaintable);
|
||||
|
||||
GC::Ref<FieldSetPaintable> FieldSetPaintable::create(Layout::FieldSetBox const& layout_box)
|
||||
{
|
||||
return layout_box.heap().allocate<FieldSetPaintable>(layout_box);
|
||||
}
|
||||
|
||||
FieldSetPaintable::FieldSetPaintable(Layout::FieldSetBox const& layout_box)
|
||||
: PaintableBox(layout_box)
|
||||
{
|
||||
}
|
||||
|
||||
Layout::FieldSetBox& FieldSetPaintable::layout_box()
|
||||
{
|
||||
return static_cast<Layout::FieldSetBox&>(layout_node());
|
||||
}
|
||||
|
||||
Layout::FieldSetBox const& FieldSetPaintable::layout_box() const
|
||||
{
|
||||
return static_cast<Layout::FieldSetBox const&>(layout_node());
|
||||
}
|
||||
|
||||
void FieldSetPaintable::paint(PaintContext& context, PaintPhase phase) const
|
||||
{
|
||||
if (!is_visible())
|
||||
return;
|
||||
|
||||
if (phase != PaintPhase::Border) {
|
||||
PaintableBox::paint(context, phase);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(layout_box().has_rendered_legend())) {
|
||||
PaintableBox::paint(context, phase);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& display_list_recorder = context.display_list_recorder();
|
||||
|
||||
auto const* legend_box = layout_box().first_child_of_type<Layout::LegendBox>();
|
||||
auto const* const legend_paintable = legend_box->paintable_box();
|
||||
|
||||
auto legend_border_rect = context.rounded_device_rect(legend_paintable->absolute_border_box_rect());
|
||||
auto fieldset_border_rect = context.rounded_device_rect(absolute_border_box_rect());
|
||||
|
||||
BordersData borders_data = BordersData {
|
||||
.top = CSS::BorderData(),
|
||||
.right = box_model().border.right == 0 ? CSS::BorderData() : computed_values().border_right(),
|
||||
.bottom = box_model().border.bottom == 0 ? CSS::BorderData() : computed_values().border_bottom(),
|
||||
.left = box_model().border.left == 0 ? CSS::BorderData() : computed_values().border_left(),
|
||||
};
|
||||
|
||||
paint_all_borders(display_list_recorder, fieldset_border_rect, normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
|
||||
|
||||
auto top_border_data = box_model().border.top == 0 ? CSS::BorderData() : computed_values().border_top();
|
||||
auto top_border = context.enclosing_device_pixels(top_border_data.width).value();
|
||||
|
||||
// if fieldset has a rendered legend, the top border is not
|
||||
// expected to be painted behind the border box of the legend
|
||||
DevicePixelRect left_segment = {
|
||||
fieldset_border_rect.x(),
|
||||
fieldset_border_rect.y(),
|
||||
legend_border_rect.x() - fieldset_border_rect.x(),
|
||||
top_border
|
||||
};
|
||||
|
||||
DevicePixelRect right_segment = {
|
||||
legend_border_rect.right(),
|
||||
fieldset_border_rect.y(),
|
||||
fieldset_border_rect.right() - legend_border_rect.right(),
|
||||
top_border
|
||||
};
|
||||
|
||||
BordersData top_border_only = BordersData {
|
||||
.top = top_border_data,
|
||||
.right = CSS::BorderData(),
|
||||
.bottom = CSS::BorderData(),
|
||||
.left = CSS::BorderData(),
|
||||
};
|
||||
|
||||
display_list_recorder.save();
|
||||
display_list_recorder.add_clip_rect(left_segment.to_type<int>());
|
||||
paint_all_borders(display_list_recorder, fieldset_border_rect, normalized_border_radii_data().as_corners(context), top_border_only.to_device_pixels(context));
|
||||
display_list_recorder.restore();
|
||||
|
||||
display_list_recorder.save();
|
||||
display_list_recorder.add_clip_rect(right_segment.to_type<int>());
|
||||
paint_all_borders(
|
||||
display_list_recorder,
|
||||
fieldset_border_rect,
|
||||
normalized_border_radii_data().as_corners(context),
|
||||
top_border_only.to_device_pixels(context));
|
||||
display_list_recorder.restore();
|
||||
}
|
||||
|
||||
}
|
32
Libraries/LibWeb/Painting/FieldSetPaintable.h
Normal file
32
Libraries/LibWeb/Painting/FieldSetPaintable.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Kostya Farber <kostya.farber@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/Layout/FieldSetBox.h>
|
||||
#include <LibWeb/Painting/PaintContext.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
class FieldSetPaintable final : public PaintableBox {
|
||||
GC_CELL(FieldSetPaintable, PaintableBox);
|
||||
GC_DECLARE_ALLOCATOR(FieldSetPaintable);
|
||||
|
||||
public:
|
||||
static GC::Ref<FieldSetPaintable> create(Layout::FieldSetBox const&);
|
||||
|
||||
virtual void paint(PaintContext&, PaintPhase) const override;
|
||||
|
||||
private:
|
||||
Layout::FieldSetBox& layout_box();
|
||||
Layout::FieldSetBox const& layout_box() const;
|
||||
|
||||
explicit FieldSetPaintable(Layout::FieldSetBox const&);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
|
||||
BlockContainer <body> at (8,8) content-size 784x36.59375 children: not-inline
|
||||
FieldSetBox <fieldset> at (24,15.59375) content-size 752x17 [BFC] children: not-inline
|
||||
LegendBox <legend> at (26,1.5) content-size 36.328125x17 children: inline
|
||||
frag 0 from TextNode start: 0, length: 5, rect: [26,1.5 36.328125x17] baseline: 13.296875
|
||||
"login"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> at (8,44.59375) content-size 784x0 children: inline
|
||||
TextNode <#text>
|
||||
|
||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x36.59375] overflow: [8,1.5 784x51.09375]
|
||||
FieldSetPaintable (FieldSetBox<FIELDSET>) [10,8 780x36.59375] overflow: [12,1.5 776x51.09375]
|
||||
PaintableWithLines (LegendBox<LEGEND>) [24,1.5 40.328125x17]
|
||||
TextPaintable (TextNode<#text>)
|
||||
PaintableWithLines (BlockContainer(anonymous)) [8,44.59375 784x0]
|
|
@ -0,0 +1 @@
|
|||
<fieldset><legend>login</legend></fieldset>
|
|
@ -0,0 +1,66 @@
|
|||
Summary
|
||||
|
||||
Harness status: OK
|
||||
|
||||
Rerun
|
||||
|
||||
Found 56 tests
|
||||
|
||||
56 Pass
|
||||
Details
|
||||
Result Test Name MessagePass in-body: display
|
||||
Pass in-body: unicodeBidi
|
||||
Pass in-body: marginTop
|
||||
Pass in-body: marginRight
|
||||
Pass in-body: marginBottom
|
||||
Pass in-body: marginLeft
|
||||
Pass in-body: paddingTop
|
||||
Pass in-body: paddingRight
|
||||
Pass in-body: paddingBottom
|
||||
Pass in-body: paddingLeft
|
||||
Pass in-body: overflow
|
||||
Pass in-body: height
|
||||
Pass in-body: box-sizing
|
||||
Pass in-body: width
|
||||
Pass rendered-legend: display
|
||||
Pass rendered-legend: unicodeBidi
|
||||
Pass rendered-legend: marginTop
|
||||
Pass rendered-legend: marginRight
|
||||
Pass rendered-legend: marginBottom
|
||||
Pass rendered-legend: marginLeft
|
||||
Pass rendered-legend: paddingTop
|
||||
Pass rendered-legend: paddingRight
|
||||
Pass rendered-legend: paddingBottom
|
||||
Pass rendered-legend: paddingLeft
|
||||
Pass rendered-legend: overflow
|
||||
Pass rendered-legend: height
|
||||
Pass rendered-legend: box-sizing
|
||||
Pass rendered-legend: width
|
||||
Pass in-fieldset-second-child: display
|
||||
Pass in-fieldset-second-child: unicodeBidi
|
||||
Pass in-fieldset-second-child: marginTop
|
||||
Pass in-fieldset-second-child: marginRight
|
||||
Pass in-fieldset-second-child: marginBottom
|
||||
Pass in-fieldset-second-child: marginLeft
|
||||
Pass in-fieldset-second-child: paddingTop
|
||||
Pass in-fieldset-second-child: paddingRight
|
||||
Pass in-fieldset-second-child: paddingBottom
|
||||
Pass in-fieldset-second-child: paddingLeft
|
||||
Pass in-fieldset-second-child: overflow
|
||||
Pass in-fieldset-second-child: height
|
||||
Pass in-fieldset-second-child: box-sizing
|
||||
Pass in-fieldset-second-child: width
|
||||
Pass in-fieldset-descendant: display
|
||||
Pass in-fieldset-descendant: unicodeBidi
|
||||
Pass in-fieldset-descendant: marginTop
|
||||
Pass in-fieldset-descendant: marginRight
|
||||
Pass in-fieldset-descendant: marginBottom
|
||||
Pass in-fieldset-descendant: marginLeft
|
||||
Pass in-fieldset-descendant: paddingTop
|
||||
Pass in-fieldset-descendant: paddingRight
|
||||
Pass in-fieldset-descendant: paddingBottom
|
||||
Pass in-fieldset-descendant: paddingLeft
|
||||
Pass in-fieldset-descendant: overflow
|
||||
Pass in-fieldset-descendant: height
|
||||
Pass in-fieldset-descendant: box-sizing
|
||||
Pass in-fieldset-descendant: width
|
|
@ -0,0 +1,62 @@
|
|||
<!doctype html>
|
||||
<title>The legend element</title>
|
||||
<script src=../../../../resources/testharness.js></script>
|
||||
<script src=../../../../resources/testharnessreport.js></script>
|
||||
<style>
|
||||
#ref {
|
||||
display: block;
|
||||
unicode-bidi: isolate;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
/* TODO: uncomment this when these properties are widely supported
|
||||
padding-inline-start: 2px; padding-inline-end: 2px;
|
||||
*/
|
||||
}
|
||||
</style>
|
||||
|
||||
<legend id=in-body></legend>
|
||||
<fieldset>
|
||||
<legend id=rendered-legend></legend>
|
||||
<legend id=in-fieldset-second-child></legend>
|
||||
<div><legend id=in-fieldset-descendant></legend></div>
|
||||
</fieldset>
|
||||
<div id=ref></div>
|
||||
|
||||
<script>
|
||||
setup(() => {
|
||||
self.legends = [].slice.call(document.querySelectorAll('legend'));
|
||||
self.refStyle = getComputedStyle(document.getElementById('ref'));
|
||||
self.props = ['display',
|
||||
'unicodeBidi',
|
||||
'marginTop',
|
||||
'marginRight',
|
||||
'marginBottom',
|
||||
'marginLeft',
|
||||
'paddingTop',
|
||||
'paddingRight',
|
||||
'paddingBottom',
|
||||
'paddingLeft',
|
||||
'overflow',
|
||||
// Extra tests
|
||||
'height',
|
||||
'box-sizing',
|
||||
];
|
||||
});
|
||||
legends.forEach(legend => {
|
||||
const testStyle = getComputedStyle(legend);
|
||||
props.forEach(prop => {
|
||||
test(() => {
|
||||
assert_equals(testStyle[prop], refStyle[prop]);
|
||||
}, `${legend.id}: ${prop}`);
|
||||
});
|
||||
|
||||
// Test width separately since it differs outside fieldset vs. in fieldset vs. rendered legend
|
||||
test(() => {
|
||||
if (legend.id === 'rendered-legend') {
|
||||
assert_equals(testStyle.width, '0px');
|
||||
} else {
|
||||
assert_not_equals(testStyle.width, '0px');
|
||||
}
|
||||
}, `${legend.id}: width`);
|
||||
});
|
||||
</script>
|
Loading…
Add table
Reference in a new issue