diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 0c01464ed43..2aebafa9d42 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -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() diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index d8625afea86..5ca69c94fb6 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -665,6 +665,7 @@ namespace Web::Painting { class AudioPaintable; class ButtonPaintable; class CheckBoxPaintable; +class FieldSetPaintable; class LabelablePaintable; class MediaPaintable; class Paintable; diff --git a/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index 8d8201208ba..280744171c6 100644 --- a/Libraries/LibWeb/Layout/BlockFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -12,7 +12,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -73,6 +75,38 @@ void BlockFormattingContext::run(AvailableSpace const& available_space) return; } + if (is(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(root()); + if (!(fieldset_box.has_rendered_legend())) { + return; + } + + auto const* legend = root().first_child_of_type(); + 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 diff --git a/Libraries/LibWeb/Layout/FieldSetBox.cpp b/Libraries/LibWeb/Layout/FieldSetBox.cpp index ab28b8e6164..458c5939ce9 100644 --- a/Libraries/LibWeb/Layout/FieldSetBox.cpp +++ b/Libraries/LibWeb/Layout/FieldSetBox.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include 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 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 FieldSetBox::create_paintable() const +{ + return Painting::FieldSetPaintable::create(*this); } } diff --git a/Libraries/LibWeb/Layout/FieldSetBox.h b/Libraries/LibWeb/Layout/FieldSetBox.h index 320bb9a24a2..6b9841c30ff 100644 --- a/Libraries/LibWeb/Layout/FieldSetBox.h +++ b/Libraries/LibWeb/Layout/FieldSetBox.h @@ -8,7 +8,7 @@ #include #include - +#include namespace Web::Layout { class FieldSetBox final : public BlockContainer { @@ -22,10 +22,10 @@ public: DOM::Element& dom_node() { return static_cast(*BlockContainer::dom_node()); } DOM::Element const& dom_node() const { return static_cast(*BlockContainer::dom_node()); } - void layout_legend() const; + bool has_rendered_legend() const; + virtual GC::Ptr create_paintable() const override; private: - bool has_rendered_legend() const; virtual bool is_fieldset_box() const final { return true; diff --git a/Libraries/LibWeb/Layout/TreeBuilder.cpp b/Libraries/LibWeb/Layout/TreeBuilder.cpp index 5311e30a1a1..dbceceaf140 100644 --- a/Libraries/LibWeb/Layout/TreeBuilder.cpp +++ b/Libraries/LibWeb/Layout/TreeBuilder.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,9 @@ static Layout::Node& insertion_parent_for_inline_node(Layout::NodeWithStyle& lay return *layout_parent.last_child(); }; + if (is(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; diff --git a/Libraries/LibWeb/Painting/FieldSetPaintable.cpp b/Libraries/LibWeb/Painting/FieldSetPaintable.cpp new file mode 100644 index 00000000000..2291f7d198d --- /dev/null +++ b/Libraries/LibWeb/Painting/FieldSetPaintable.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2024, Kostya Farber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::Painting { +GC_DEFINE_ALLOCATOR(FieldSetPaintable); + +GC::Ref FieldSetPaintable::create(Layout::FieldSetBox const& layout_box) +{ + return layout_box.heap().allocate(layout_box); +} + +FieldSetPaintable::FieldSetPaintable(Layout::FieldSetBox const& layout_box) + : PaintableBox(layout_box) +{ +} + +Layout::FieldSetBox& FieldSetPaintable::layout_box() +{ + return static_cast(layout_node()); +} + +Layout::FieldSetBox const& FieldSetPaintable::layout_box() const +{ + return static_cast(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(); + 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()); + 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()); + 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(); +} + +} diff --git a/Libraries/LibWeb/Painting/FieldSetPaintable.h b/Libraries/LibWeb/Painting/FieldSetPaintable.h new file mode 100644 index 00000000000..ed7d3daddf6 --- /dev/null +++ b/Libraries/LibWeb/Painting/FieldSetPaintable.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, Kostya Farber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::Painting { + +class FieldSetPaintable final : public PaintableBox { + GC_CELL(FieldSetPaintable, PaintableBox); + GC_DECLARE_ALLOCATOR(FieldSetPaintable); + +public: + static GC::Ref 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&); +}; + +} diff --git a/Tests/LibWeb/Layout/expected/fieldset-with-rendered-legend.txt b/Tests/LibWeb/Layout/expected/fieldset-with-rendered-legend.txt new file mode 100644 index 00000000000..8390c0c650d --- /dev/null +++ b/Tests/LibWeb/Layout/expected/fieldset-with-rendered-legend.txt @@ -0,0 +1,18 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x36.59375 children: not-inline + FieldSetBox
at (24,15.59375) content-size 752x17 [BFC] children: not-inline + LegendBox 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) [0,0 800x600] + PaintableWithLines (BlockContainer) [8,8 784x36.59375] overflow: [8,1.5 784x51.09375] + FieldSetPaintable (FieldSetBox
) [10,8 780x36.59375] overflow: [12,1.5 776x51.09375] + PaintableWithLines (LegendBox) [24,1.5 40.328125x17] + TextPaintable (TextNode<#text>) + PaintableWithLines (BlockContainer(anonymous)) [8,44.59375 784x0] diff --git a/Tests/LibWeb/Layout/input/fieldset-with-rendered-legend.html b/Tests/LibWeb/Layout/input/fieldset-with-rendered-legend.html new file mode 100644 index 00000000000..c6c84753a1c --- /dev/null +++ b/Tests/LibWeb/Layout/input/fieldset-with-rendered-legend.html @@ -0,0 +1 @@ +
login
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend.txt b/Tests/LibWeb/Text/expected/wpt-import/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend.txt new file mode 100644 index 00000000000..e8c0be8475c --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend.txt @@ -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 \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend.html b/Tests/LibWeb/Text/input/wpt-import/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend.html new file mode 100644 index 00000000000..7a395b9f07b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend.html @@ -0,0 +1,62 @@ + +The legend element + + + + + +
+ + +
+
+
+ +