LibWeb: Separate layout tree rendering into phases

CSS defines a very specific paint order. This patch starts steering us
towards respecting that by introducing the PaintPhase enum with values:

- Background
- Border
- Foreground
- Overlay (internal overlays used by inspector)

Basically, to get the right visual result, we have to render the page
multiple times, going one phase at a time.
This commit is contained in:
Andreas Kling 2020-06-18 18:57:35 +02:00
parent abe811104f
commit cfab53903f
Notes: sideshowbarker 2024-07-19 05:34:13 +09:00
22 changed files with 109 additions and 95 deletions

View file

@ -600,19 +600,22 @@ void LayoutBlock::compute_height()
}
}
void LayoutBlock::render(RenderingContext& context)
void LayoutBlock::render(RenderingContext& context, PaintPhase phase)
{
if (!is_visible())
return;
LayoutBox::render(context);
LayoutBox::render(context, phase);
if (children_are_inline()) {
for (auto& line_box : m_line_boxes) {
for (auto& fragment : line_box.fragments()) {
if (context.should_show_line_box_borders())
context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Green);
fragment.render(context);
// FIXME: Inline backgrounds etc.
if (phase == PaintPhase::Foreground) {
if (children_are_inline()) {
for (auto& line_box : m_line_boxes) {
for (auto& fragment : line_box.fragments()) {
if (context.should_show_line_box_borders())
context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Green);
fragment.render(context);
}
}
}
}

View file

@ -41,7 +41,7 @@ public:
virtual const char* class_name() const override { return "LayoutBlock"; }
virtual void layout(LayoutMode = LayoutMode::Default) override;
virtual void render(RenderingContext&) override;
virtual void render(RenderingContext&, PaintPhase) override;
virtual LayoutNode& inline_wrapper() override;

View file

@ -31,9 +31,6 @@
#include <LibWeb/Layout/LayoutBlock.h>
#include <LibWeb/Layout/LayoutBox.h>
//#define DRAW_BOXES_AROUND_LAYOUT_NODES
//#define DRAW_BOXES_AROUND_HOVERED_NODES
namespace Web {
void LayoutBox::paint_border(RenderingContext& context, Edge edge, const Gfx::FloatRect& rect, CSS::PropertyID style_property_id, CSS::PropertyID color_property_id, CSS::PropertyID width_property_id)
@ -189,7 +186,7 @@ void LayoutBox::paint_border(RenderingContext& context, Edge edge, const Gfx::Fl
}
}
void LayoutBox::render(RenderingContext& context)
void LayoutBox::render(RenderingContext& context, PaintPhase phase)
{
if (!is_visible())
return;
@ -198,21 +195,14 @@ void LayoutBox::render(RenderingContext& context)
if (is_fixed_position())
context.painter().translate(context.scroll_offset());
#ifdef DRAW_BOXES_AROUND_LAYOUT_NODES
context.painter().draw_rect(m_rect, Color::Blue);
#endif
#ifdef DRAW_BOXES_AROUND_HOVERED_NODES
if (!is_anonymous() && node() == document().hovered_node())
context.painter().draw_rect(m_rect, Color::Red);
#endif
Gfx::FloatRect padded_rect;
padded_rect.set_x(absolute_x() - box_model().padding().left.to_px(*this));
padded_rect.set_width(width() + box_model().padding().left.to_px(*this) + box_model().padding().right.to_px(*this));
padded_rect.set_y(absolute_y() - box_model().padding().top.to_px(*this));
padded_rect.set_height(height() + box_model().padding().top.to_px(*this) + box_model().padding().bottom.to_px(*this));
if (!is_body()) {
if (phase == PaintPhase::Background && !is_body()) {
// FIXME: We should paint the body here too, but that currently happens at the view layer.
auto bgcolor = style().property(CSS::PropertyID::BackgroundColor);
if (bgcolor.has_value() && bgcolor.value()->is_color()) {
context.painter().fill_rect(enclosing_int_rect(padded_rect), bgcolor.value()->to_color(document()));
@ -227,20 +217,22 @@ void LayoutBox::render(RenderingContext& context)
}
}
Gfx::FloatRect bordered_rect;
bordered_rect.set_x(padded_rect.x() - box_model().border().left.to_px(*this));
bordered_rect.set_width(padded_rect.width() + box_model().border().left.to_px(*this) + box_model().border().right.to_px(*this));
bordered_rect.set_y(padded_rect.y() - box_model().border().top.to_px(*this));
bordered_rect.set_height(padded_rect.height() + box_model().border().top.to_px(*this) + box_model().border().bottom.to_px(*this));
if (phase == PaintPhase::Border) {
Gfx::FloatRect bordered_rect;
bordered_rect.set_x(padded_rect.x() - box_model().border().left.to_px(*this));
bordered_rect.set_width(padded_rect.width() + box_model().border().left.to_px(*this) + box_model().border().right.to_px(*this));
bordered_rect.set_y(padded_rect.y() - box_model().border().top.to_px(*this));
bordered_rect.set_height(padded_rect.height() + box_model().border().top.to_px(*this) + box_model().border().bottom.to_px(*this));
paint_border(context, Edge::Left, bordered_rect, CSS::PropertyID::BorderLeftStyle, CSS::PropertyID::BorderLeftColor, CSS::PropertyID::BorderLeftWidth);
paint_border(context, Edge::Right, bordered_rect, CSS::PropertyID::BorderRightStyle, CSS::PropertyID::BorderRightColor, CSS::PropertyID::BorderRightWidth);
paint_border(context, Edge::Top, bordered_rect, CSS::PropertyID::BorderTopStyle, CSS::PropertyID::BorderTopColor, CSS::PropertyID::BorderTopWidth);
paint_border(context, Edge::Bottom, bordered_rect, CSS::PropertyID::BorderBottomStyle, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomWidth);
paint_border(context, Edge::Left, bordered_rect, CSS::PropertyID::BorderLeftStyle, CSS::PropertyID::BorderLeftColor, CSS::PropertyID::BorderLeftWidth);
paint_border(context, Edge::Right, bordered_rect, CSS::PropertyID::BorderRightStyle, CSS::PropertyID::BorderRightColor, CSS::PropertyID::BorderRightWidth);
paint_border(context, Edge::Top, bordered_rect, CSS::PropertyID::BorderTopStyle, CSS::PropertyID::BorderTopColor, CSS::PropertyID::BorderTopWidth);
paint_border(context, Edge::Bottom, bordered_rect, CSS::PropertyID::BorderBottomStyle, CSS::PropertyID::BorderBottomColor, CSS::PropertyID::BorderBottomWidth);
}
LayoutNodeWithStyleAndBoxModelMetrics::render(context);
LayoutNodeWithStyleAndBoxModelMetrics::render(context, phase);
if (node() && document().inspected_node() == node())
if (phase == PaintPhase::Overlay && node() && document().inspected_node() == node())
context.painter().draw_rect(enclosing_int_rect(absolute_rect()), Color::Magenta);
}

View file

@ -67,7 +67,7 @@ public:
void set_stacking_context(NonnullOwnPtr<StackingContext> context) { m_stacking_context = move(context); }
StackingContext* enclosing_stacking_context();
virtual void render(RenderingContext&) override;
virtual void render(RenderingContext&, PaintPhase) override;
protected:
LayoutBox(const Node* node, NonnullRefPtr<StyleProperties> style)

View file

@ -49,18 +49,21 @@ void LayoutCanvas::layout(LayoutMode layout_mode)
LayoutReplaced::layout(layout_mode);
}
void LayoutCanvas::render(RenderingContext& context)
void LayoutCanvas::render(RenderingContext& context, PaintPhase phase)
{
if (!is_visible())
return;
// FIXME: This should be done at a different level. Also rect() does not include padding etc!
if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
return;
LayoutReplaced::render(context, phase);
if (node().bitmap())
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *node().bitmap(), node().bitmap()->rect());
LayoutReplaced::render(context);
if (phase == PaintPhase::Foreground) {
// FIXME: This should be done at a different level. Also rect() does not include padding etc!
if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
return;
if (node().bitmap())
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *node().bitmap(), node().bitmap()->rect());
}
}
}

View file

@ -39,7 +39,7 @@ public:
virtual ~LayoutCanvas() override;
virtual void layout(LayoutMode = LayoutMode::Default) override;
virtual void render(RenderingContext&) override;
virtual void render(RenderingContext&, PaintPhase) override;
const HTMLCanvasElement& node() const { return static_cast<const HTMLCanvasElement&>(LayoutReplaced::node()); }

View file

@ -100,9 +100,17 @@ void LayoutDocument::did_set_viewport_rect(Badge<Frame>, const Gfx::IntRect& a_v
});
}
void LayoutDocument::render(RenderingContext& context)
void LayoutDocument::paint_all_phases(RenderingContext& context)
{
stacking_context()->render(context);
render(context, PaintPhase::Background);
render(context, PaintPhase::Border);
render(context, PaintPhase::Foreground);
render(context, PaintPhase::Overlay);
}
void LayoutDocument::render(RenderingContext& context, PaintPhase phase)
{
stacking_context()->render(context, phase);
}
}

View file

@ -40,7 +40,9 @@ public:
virtual const char* class_name() const override { return "LayoutDocument"; }
virtual void layout(LayoutMode = LayoutMode::Default) override;
virtual void render(RenderingContext&) override;
void paint_all_phases(RenderingContext&);
virtual void render(RenderingContext&, PaintPhase) override;
const LayoutRange& selection() const { return m_selection; }
LayoutRange& selection() { return m_selection; }

View file

@ -59,28 +59,30 @@ void LayoutFrame::layout(LayoutMode layout_mode)
LayoutReplaced::layout(layout_mode);
}
void LayoutFrame::render(RenderingContext& context)
void LayoutFrame::render(RenderingContext& context, PaintPhase phase)
{
LayoutReplaced::render(context);
LayoutReplaced::render(context, phase);
auto* hosted_document = node().hosted_document();
if (!hosted_document)
return;
auto* hosted_layout_tree = hosted_document->layout_node();
if (!hosted_layout_tree)
return;
if (phase == PaintPhase::Foreground) {
auto* hosted_document = node().hosted_document();
if (!hosted_document)
return;
auto* hosted_layout_tree = hosted_document->layout_node();
if (!hosted_layout_tree)
return;
context.painter().save();
auto old_viewport_rect = context.viewport_rect();
context.painter().save();
auto old_viewport_rect = context.viewport_rect();
context.painter().add_clip_rect(enclosing_int_rect(absolute_rect()));
context.painter().translate(absolute_x(), absolute_y());
context.painter().add_clip_rect(enclosing_int_rect(absolute_rect()));
context.painter().translate(absolute_x(), absolute_y());
context.set_viewport_rect({ {}, node().hosted_frame()->size() });
const_cast<LayoutDocument*>(hosted_layout_tree)->render(context);
context.set_viewport_rect({ {}, node().hosted_frame()->size() });
const_cast<LayoutDocument*>(hosted_layout_tree)->paint_all_phases(context);
context.set_viewport_rect(old_viewport_rect);
context.painter().restore();
context.set_viewport_rect(old_viewport_rect);
context.painter().restore();
}
}
void LayoutFrame::did_set_rect()

View file

@ -36,7 +36,7 @@ public:
LayoutFrame(const Element&, NonnullRefPtr<StyleProperties>);
virtual ~LayoutFrame() override;
virtual void render(RenderingContext&) override;
virtual void render(RenderingContext&, PaintPhase) override;
virtual void layout(LayoutMode) override;
const HTMLIFrameElement& node() const { return static_cast<const HTMLIFrameElement&>(LayoutReplaced::node()); }

View file

@ -77,7 +77,7 @@ void LayoutImage::layout(LayoutMode layout_mode)
LayoutReplaced::layout(layout_mode);
}
void LayoutImage::render(RenderingContext& context)
void LayoutImage::render(RenderingContext& context, PaintPhase phase)
{
if (!is_visible())
return;
@ -86,18 +86,20 @@ void LayoutImage::render(RenderingContext& context)
if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
return;
LayoutReplaced::render(context);
LayoutReplaced::render(context, phase);
if (renders_as_alt_text()) {
auto& image_element = to<HTMLImageElement>(node());
context.painter().set_font(Gfx::Font::default_font());
Gfx::StylePainter::paint_frame(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
auto alt = image_element.alt();
if (alt.is_empty())
alt = image_element.src();
context.painter().draw_text(enclosing_int_rect(absolute_rect()), alt, Gfx::TextAlignment::Center, style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black), Gfx::TextElision::Right);
} else if (m_image_loader.bitmap()) {
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *m_image_loader.bitmap(), m_image_loader.bitmap()->rect());
if (phase == PaintPhase::Foreground) {
if (renders_as_alt_text()) {
auto& image_element = to<HTMLImageElement>(node());
context.painter().set_font(Gfx::Font::default_font());
Gfx::StylePainter::paint_frame(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
auto alt = image_element.alt();
if (alt.is_empty())
alt = image_element.src();
context.painter().draw_text(enclosing_int_rect(absolute_rect()), alt, Gfx::TextAlignment::Center, style().color_or_fallback(CSS::PropertyID::Color, document(), Color::Black), Gfx::TextElision::Right);
} else if (m_image_loader.bitmap()) {
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *m_image_loader.bitmap(), m_image_loader.bitmap()->rect());
}
}
}

View file

@ -39,7 +39,7 @@ public:
virtual ~LayoutImage() override;
virtual void layout(LayoutMode = LayoutMode::Default) override;
virtual void render(RenderingContext&) override;
virtual void render(RenderingContext&, PaintPhase) override;
const Element& node() const { return static_cast<const Element&>(LayoutReplaced::node()); }

View file

@ -38,8 +38,10 @@ LayoutListItemMarker::~LayoutListItemMarker()
{
}
void LayoutListItemMarker::render(RenderingContext& context)
void LayoutListItemMarker::render(RenderingContext& context, PaintPhase phase)
{
if (phase != PaintPhase::Foreground)
return;
Gfx::IntRect bullet_rect { 0, 0, 4, 4 };
bullet_rect.center_within(enclosing_int_rect(absolute_rect()));
// FIXME: It would be nicer to not have to go via the parent here to get our inherited style.

View file

@ -35,7 +35,7 @@ public:
LayoutListItemMarker();
virtual ~LayoutListItemMarker() override;
virtual void render(RenderingContext&) override;
virtual void render(RenderingContext&, PaintPhase) override;
private:
virtual const char* class_name() const override { return "LayoutListItemMarker"; }

View file

@ -89,7 +89,7 @@ const LayoutBlock* LayoutNode::containing_block() const
return nearest_block_ancestor();
}
void LayoutNode::render(RenderingContext& context)
void LayoutNode::render(RenderingContext& context, PaintPhase phase)
{
if (!is_visible())
return;
@ -97,7 +97,7 @@ void LayoutNode::render(RenderingContext& context)
for_each_child([&](auto& child) {
if (child.is_box() && to<LayoutBox>(child).stacking_context())
return;
child.render(context);
child.render(context, phase);
});
}

View file

@ -171,7 +171,14 @@ public:
};
virtual void layout(LayoutMode);
virtual void render(RenderingContext&);
enum class PaintPhase {
Background,
Border,
Foreground,
Overlay,
};
virtual void render(RenderingContext&, PaintPhase);
bool is_absolutely_positioned() const;
bool is_fixed_position() const;

View file

@ -65,9 +65,4 @@ void LayoutWidget::update_widget()
widget().move_to(adjusted_widget_position);
}
void LayoutWidget::render(RenderingContext& context)
{
LayoutReplaced::render(context);
}
}

View file

@ -35,8 +35,6 @@ public:
LayoutWidget(const Element&, GUI::Widget&);
virtual ~LayoutWidget() override;
virtual void render(RenderingContext&) override;
GUI::Widget& widget() { return m_widget; }
const GUI::Widget& widget() const { return m_widget; }

View file

@ -47,17 +47,17 @@ StackingContext::StackingContext(LayoutBox& box, StackingContext* parent)
}
}
void StackingContext::render(RenderingContext& context)
void StackingContext::render(RenderingContext& context, LayoutNode::PaintPhase phase)
{
if (!m_box.is_root()) {
m_box.render(context);
m_box.render(context, phase);
} else {
// NOTE: LayoutDocument::render() merely calls StackingContext::render()
// so we call its base class instead.
to<LayoutDocument>(m_box).LayoutBlock::render(context);
to<LayoutDocument>(m_box).LayoutBlock::render(context, phase);
}
for (auto* child : m_children) {
child->render(context);
child->render(context, phase);
}
}

View file

@ -27,7 +27,7 @@
#pragma once
#include <AK/Vector.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Layout/LayoutNode.h>
namespace Web {
@ -40,7 +40,7 @@ public:
StackingContext* parent() { return m_parent; }
const StackingContext* parent() const { return m_parent; }
void render(RenderingContext&);
void render(RenderingContext&, LayoutNode::PaintPhase);
void dump(int indent = 0) const;

View file

@ -210,7 +210,7 @@ void PageView::paint_event(GUI::PaintEvent& event)
RenderingContext context(painter, palette(), { horizontal_scrollbar().value(), vertical_scrollbar().value() });
context.set_should_show_line_box_borders(m_should_show_line_box_borders);
context.set_viewport_rect(viewport_rect_in_content_coordinates());
layout_root()->render(context);
layout_root()->paint_all_phases(context);
}
void PageView::mousemove_event(GUI::MouseEvent& event)

View file

@ -88,7 +88,7 @@ void PageHost::paint(const Gfx::IntRect& content_rect, Gfx::Bitmap& target)
Web::RenderingContext context(painter, palette(), Gfx::IntPoint());
context.set_viewport_rect(content_rect);
layout_root->render(context);
layout_root->paint_all_phases(context);
}
void PageHost::set_viewport_rect(const Gfx::IntRect& rect)