LibWeb: Bring measuring of scrollable overflow closer to spec
Importantly, we now only consider overflow from descendants with explicltly visible overflow, and only from descendants that have the measured box as their containing block. Also, we now measure scrollable overflow for all boxes, not just scroll containers. This will allow us to fix a long-standing paint problem in the next commit.
This commit is contained in:
parent
f23baf0a2d
commit
bf25568703
Notes:
sideshowbarker
2024-07-16 23:34:49 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/bf25568703 Pull-request: https://github.com/SerenityOS/serenity/pull/19975
4 changed files with 67 additions and 44 deletions
|
@ -825,8 +825,8 @@ void dump_tree(StringBuilder& builder, Painting::Paintable const& paintable, boo
|
|||
auto const& paintable_box = static_cast<Painting::PaintableBox const&>(paintable);
|
||||
builder.appendff(" {}", paintable_box.absolute_border_box_rect());
|
||||
|
||||
if (paintable_box.has_overflow()) {
|
||||
builder.appendff(" overflow: {}", paintable_box.scrollable_overflow_rect().value());
|
||||
if (paintable_box.has_scrollable_overflow()) {
|
||||
builder.appendff(" overflow: {}", paintable_box.scrollable_overflow_rect());
|
||||
}
|
||||
}
|
||||
builder.append("\n"sv);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Layout/LayoutState.h>
|
||||
#include <LibWeb/Layout/TextNode.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
||||
|
@ -64,36 +65,70 @@ LayoutState::UsedValues const& LayoutState::get(NodeWithStyleAndBoxModelMetrics
|
|||
return *new_used_values_ptr;
|
||||
}
|
||||
|
||||
static void measure_scrollable_overflow(LayoutState const& state, Box const& box, CSSPixels& bottom_edge, CSSPixels& right_edge)
|
||||
// https://www.w3.org/TR/css-overflow-3/#scrollable-overflow
|
||||
static CSSPixelRect measure_scrollable_overflow(Box const& box)
|
||||
{
|
||||
auto const* maybe_box_state = state.used_values_per_layout_node.get(&box).value_or(nullptr);
|
||||
if (!maybe_box_state)
|
||||
return;
|
||||
auto const& box_state = *maybe_box_state;
|
||||
auto scroll_container_border_box = CSSPixelRect {
|
||||
box_state.offset.translated(-box_state.border_box_left(), -box_state.border_box_top()),
|
||||
{ box_state.border_box_width(), box_state.border_box_height() }
|
||||
};
|
||||
if (!box.paintable_box())
|
||||
return {};
|
||||
|
||||
bottom_edge = max(bottom_edge, scroll_container_border_box.bottom());
|
||||
right_edge = max(right_edge, scroll_container_border_box.right());
|
||||
auto& paintable_box = const_cast<Painting::PaintableBox&>(*box.paintable_box());
|
||||
|
||||
if (box.children_are_inline()) {
|
||||
if (!box_state.line_boxes.is_empty()) {
|
||||
bottom_edge = max(bottom_edge, scroll_container_border_box.y() + box_state.line_boxes.last().bottom());
|
||||
for (auto& line_box : box_state.line_boxes) {
|
||||
if (line_box.fragments().is_empty())
|
||||
continue;
|
||||
right_edge = max(right_edge, scroll_container_border_box.x() + line_box.fragments().last().width());
|
||||
}
|
||||
if (paintable_box.scrollable_overflow_rect().has_value())
|
||||
return paintable_box.scrollable_overflow_rect().value();
|
||||
|
||||
// The scrollable overflow area is the union of:
|
||||
|
||||
// - The scroll container’s own padding box.
|
||||
auto scrollable_overflow_rect = paintable_box.absolute_padding_box_rect();
|
||||
|
||||
// - All line boxes directly contained by the scroll container.
|
||||
if (box.is_block_container() && box.children_are_inline()) {
|
||||
auto const& line_boxes = verify_cast<Painting::PaintableWithLines>(*box.paintable_box()).line_boxes();
|
||||
for (auto const& line_box : line_boxes) {
|
||||
scrollable_overflow_rect = scrollable_overflow_rect.united(line_box.absolute_rect());
|
||||
}
|
||||
} else {
|
||||
// FIXME: Only check boxes for whom `box` is the containing block.
|
||||
box.for_each_child_of_type<Box>([&](Box const& child) {
|
||||
measure_scrollable_overflow(state, child, bottom_edge, right_edge);
|
||||
}
|
||||
|
||||
// - The border boxes of all boxes for which it is the containing block
|
||||
// and whose border boxes are positioned not wholly in the negative scrollable overflow region,
|
||||
// FIXME: accounting for transforms by projecting each box onto the plane of the element that establishes its 3D rendering context. [CSS3-TRANSFORMS]
|
||||
if (!box.children_are_inline()) {
|
||||
box.for_each_child_of_type<Box>([&box, &scrollable_overflow_rect](Box const& child) {
|
||||
if (!child.paintable_box())
|
||||
return IterationDecision::Continue;
|
||||
|
||||
auto child_border_box = child.paintable_box()->absolute_border_box_rect();
|
||||
// NOTE: Here we check that the child is not wholly in the negative scrollable overflow region.
|
||||
if (child_border_box.bottom() > 0 && child_border_box.right() > 0)
|
||||
scrollable_overflow_rect = scrollable_overflow_rect.united(child_border_box);
|
||||
|
||||
// - The scrollable overflow areas of all of the above boxes
|
||||
// (including zero-area boxes and accounting for transforms as described above),
|
||||
// provided they themselves have overflow: visible (i.e. do not themselves trap the overflow)
|
||||
// and that scrollable overflow is not already clipped (e.g. by the clip property or the contain property).
|
||||
if (is<Viewport>(box) || child.computed_values().overflow_x() == CSS::Overflow::Visible || child.computed_values().overflow_y() == CSS::Overflow::Visible) {
|
||||
auto child_scrollable_overflow = measure_scrollable_overflow(child);
|
||||
if (is<Viewport>(box) || child.computed_values().overflow_x() == CSS::Overflow::Visible)
|
||||
scrollable_overflow_rect.unite_horizontally(child_scrollable_overflow);
|
||||
if (is<Viewport>(box) || child.computed_values().overflow_y() == CSS::Overflow::Visible)
|
||||
scrollable_overflow_rect.unite_vertically(child_scrollable_overflow);
|
||||
}
|
||||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: - The margin areas of grid item and flex item boxes for which the box establishes a containing block.
|
||||
|
||||
// FIXME: - Additional padding added to the end-side of the scrollable overflow rectangle as necessary
|
||||
// to enable a scroll position that satisfies the requirements of place-content: end alignment.
|
||||
|
||||
paintable_box.set_overflow_data(Painting::PaintableBox::OverflowData {
|
||||
.scrollable_overflow_rect = scrollable_overflow_rect,
|
||||
.has_scrollable_overflow = !paintable_box.absolute_padding_box_rect().contains(scrollable_overflow_rect),
|
||||
});
|
||||
|
||||
return scrollable_overflow_rect;
|
||||
}
|
||||
|
||||
void LayoutState::commit()
|
||||
|
@ -158,20 +193,7 @@ void LayoutState::commit()
|
|||
if (!used_values.node().is_box())
|
||||
continue;
|
||||
auto const& box = static_cast<Layout::Box const&>(used_values.node());
|
||||
if (!box.is_scroll_container())
|
||||
continue;
|
||||
CSSPixels bottom_edge = 0;
|
||||
CSSPixels right_edge = 0;
|
||||
measure_scrollable_overflow(*this, box, bottom_edge, right_edge);
|
||||
|
||||
auto padding_box = box.paintable_box()->absolute_padding_box_rect();
|
||||
|
||||
if (bottom_edge > padding_box.height() || right_edge > padding_box.width()) {
|
||||
Painting::PaintableBox::OverflowData overflow_data;
|
||||
overflow_data.scrollable_overflow_rect = padding_box;
|
||||
overflow_data.scrollable_overflow_rect.set_size(right_edge, bottom_edge);
|
||||
const_cast<Painting::PaintableBox&>(*box.paintable_box()).set_overflow_data(overflow_data);
|
||||
}
|
||||
measure_scrollable_overflow(box);
|
||||
}
|
||||
|
||||
for (auto* text_node : text_nodes)
|
||||
|
|
|
@ -31,7 +31,8 @@ public:
|
|||
|
||||
struct OverflowData {
|
||||
CSSPixelRect scrollable_overflow_rect;
|
||||
CSSPixelPoint scroll_offset;
|
||||
bool has_scrollable_overflow { false };
|
||||
CSSPixelPoint scroll_offset {};
|
||||
};
|
||||
|
||||
CSSPixelRect absolute_rect() const;
|
||||
|
@ -95,9 +96,9 @@ public:
|
|||
CSSPixels absolute_y() const { return absolute_rect().y(); }
|
||||
CSSPixelPoint absolute_position() const { return absolute_rect().location(); }
|
||||
|
||||
bool has_overflow() const { return m_overflow_data.has_value(); }
|
||||
[[nodiscard]] bool has_scrollable_overflow() const { return m_overflow_data->has_scrollable_overflow; }
|
||||
|
||||
Optional<CSSPixelRect> scrollable_overflow_rect() const
|
||||
[[nodiscard]] Optional<CSSPixelRect> scrollable_overflow_rect() const
|
||||
{
|
||||
if (!m_overflow_data.has_value())
|
||||
return {};
|
||||
|
@ -106,7 +107,7 @@ public:
|
|||
|
||||
Optional<CSSPixelRect> calculate_overflow_clipped_rect() const;
|
||||
|
||||
void set_overflow_data(Optional<OverflowData> data) { m_overflow_data = move(data); }
|
||||
void set_overflow_data(OverflowData data) { m_overflow_data = move(data); }
|
||||
void set_containing_line_box_fragment(Optional<Layout::LineBoxFragmentCoordinate>);
|
||||
|
||||
StackingContext* stacking_context() { return m_stacking_context; }
|
||||
|
|
|
@ -168,7 +168,7 @@ void PageHost::page_did_layout()
|
|||
{
|
||||
auto* layout_root = this->layout_root();
|
||||
VERIFY(layout_root);
|
||||
if (layout_root->paintable_box()->has_overflow())
|
||||
if (layout_root->paintable_box()->has_scrollable_overflow())
|
||||
m_content_size = page().enclosing_device_rect(layout_root->paintable_box()->scrollable_overflow_rect().value()).size();
|
||||
else
|
||||
m_content_size = page().enclosing_device_rect(layout_root->paintable_box()->absolute_rect()).size();
|
||||
|
|
Loading…
Add table
Reference in a new issue