1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 08:58:11 +00:00

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
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()) if (!is_visible())
return; return;
LayoutBox::render(context); LayoutBox::render(context, phase);
if (children_are_inline()) { // FIXME: Inline backgrounds etc.
for (auto& line_box : m_line_boxes) { if (phase == PaintPhase::Foreground) {
for (auto& fragment : line_box.fragments()) { if (children_are_inline()) {
if (context.should_show_line_box_borders()) for (auto& line_box : m_line_boxes) {
context.painter().draw_rect(enclosing_int_rect(fragment.absolute_rect()), Color::Green); for (auto& fragment : line_box.fragments()) {
fragment.render(context); 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 const char* class_name() const override { return "LayoutBlock"; }
virtual void layout(LayoutMode = LayoutMode::Default) override; virtual void layout(LayoutMode = LayoutMode::Default) override;
virtual void render(RenderingContext&) override; virtual void render(RenderingContext&, PaintPhase) override;
virtual LayoutNode& inline_wrapper() override; virtual LayoutNode& inline_wrapper() override;

View file

@ -31,9 +31,6 @@
#include <LibWeb/Layout/LayoutBlock.h> #include <LibWeb/Layout/LayoutBlock.h>
#include <LibWeb/Layout/LayoutBox.h> #include <LibWeb/Layout/LayoutBox.h>
//#define DRAW_BOXES_AROUND_LAYOUT_NODES
//#define DRAW_BOXES_AROUND_HOVERED_NODES
namespace Web { 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) 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()) if (!is_visible())
return; return;
@ -198,21 +195,14 @@ void LayoutBox::render(RenderingContext& context)
if (is_fixed_position()) if (is_fixed_position())
context.painter().translate(context.scroll_offset()); 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; Gfx::FloatRect padded_rect;
padded_rect.set_x(absolute_x() - box_model().padding().left.to_px(*this)); 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_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_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)); 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); auto bgcolor = style().property(CSS::PropertyID::BackgroundColor);
if (bgcolor.has_value() && bgcolor.value()->is_color()) { if (bgcolor.has_value() && bgcolor.value()->is_color()) {
context.painter().fill_rect(enclosing_int_rect(padded_rect), bgcolor.value()->to_color(document())); 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; if (phase == PaintPhase::Border) {
bordered_rect.set_x(padded_rect.x() - box_model().border().left.to_px(*this)); Gfx::FloatRect bordered_rect;
bordered_rect.set_width(padded_rect.width() + box_model().border().left.to_px(*this) + box_model().border().right.to_px(*this)); bordered_rect.set_x(padded_rect.x() - box_model().border().left.to_px(*this));
bordered_rect.set_y(padded_rect.y() - box_model().border().top.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_height(padded_rect.height() + box_model().border().top.to_px(*this) + box_model().border().bottom.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::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::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::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::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); 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); } void set_stacking_context(NonnullOwnPtr<StackingContext> context) { m_stacking_context = move(context); }
StackingContext* enclosing_stacking_context(); StackingContext* enclosing_stacking_context();
virtual void render(RenderingContext&) override; virtual void render(RenderingContext&, PaintPhase) override;
protected: protected:
LayoutBox(const Node* node, NonnullRefPtr<StyleProperties> style) LayoutBox(const Node* node, NonnullRefPtr<StyleProperties> style)

View file

@ -49,18 +49,21 @@ void LayoutCanvas::layout(LayoutMode layout_mode)
LayoutReplaced::layout(layout_mode); LayoutReplaced::layout(layout_mode);
} }
void LayoutCanvas::render(RenderingContext& context) void LayoutCanvas::render(RenderingContext& context, PaintPhase phase)
{ {
if (!is_visible()) if (!is_visible())
return; return;
// FIXME: This should be done at a different level. Also rect() does not include padding etc! LayoutReplaced::render(context, phase);
if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
return;
if (node().bitmap()) if (phase == PaintPhase::Foreground) {
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *node().bitmap(), node().bitmap()->rect()); // FIXME: This should be done at a different level. Also rect() does not include padding etc!
LayoutReplaced::render(context); 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 ~LayoutCanvas() override;
virtual void layout(LayoutMode = LayoutMode::Default) 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()); } 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 const char* class_name() const override { return "LayoutDocument"; }
virtual void layout(LayoutMode = LayoutMode::Default) override; 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; } const LayoutRange& selection() const { return m_selection; }
LayoutRange& selection() { return m_selection; } LayoutRange& selection() { return m_selection; }

View file

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

View file

@ -36,7 +36,7 @@ public:
LayoutFrame(const Element&, NonnullRefPtr<StyleProperties>); LayoutFrame(const Element&, NonnullRefPtr<StyleProperties>);
virtual ~LayoutFrame() override; virtual ~LayoutFrame() override;
virtual void render(RenderingContext&) override; virtual void render(RenderingContext&, PaintPhase) override;
virtual void layout(LayoutMode) override; virtual void layout(LayoutMode) override;
const HTMLIFrameElement& node() const { return static_cast<const HTMLIFrameElement&>(LayoutReplaced::node()); } 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); LayoutReplaced::layout(layout_mode);
} }
void LayoutImage::render(RenderingContext& context) void LayoutImage::render(RenderingContext& context, PaintPhase phase)
{ {
if (!is_visible()) if (!is_visible())
return; return;
@ -86,18 +86,20 @@ void LayoutImage::render(RenderingContext& context)
if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect()))) if (!context.viewport_rect().intersects(enclosing_int_rect(absolute_rect())))
return; return;
LayoutReplaced::render(context); LayoutReplaced::render(context, phase);
if (renders_as_alt_text()) { if (phase == PaintPhase::Foreground) {
auto& image_element = to<HTMLImageElement>(node()); if (renders_as_alt_text()) {
context.painter().set_font(Gfx::Font::default_font()); auto& image_element = to<HTMLImageElement>(node());
Gfx::StylePainter::paint_frame(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2); context.painter().set_font(Gfx::Font::default_font());
auto alt = image_element.alt(); Gfx::StylePainter::paint_frame(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
if (alt.is_empty()) auto alt = image_element.alt();
alt = image_element.src(); if (alt.is_empty())
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); alt = image_element.src();
} else if (m_image_loader.bitmap()) { 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);
context.painter().draw_scaled_bitmap(enclosing_int_rect(absolute_rect()), *m_image_loader.bitmap(), m_image_loader.bitmap()->rect()); } 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 ~LayoutImage() override;
virtual void layout(LayoutMode = LayoutMode::Default) 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()); } 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 }; Gfx::IntRect bullet_rect { 0, 0, 4, 4 };
bullet_rect.center_within(enclosing_int_rect(absolute_rect())); 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. // 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(); LayoutListItemMarker();
virtual ~LayoutListItemMarker() override; virtual ~LayoutListItemMarker() override;
virtual void render(RenderingContext&) override; virtual void render(RenderingContext&, PaintPhase) override;
private: private:
virtual const char* class_name() const override { return "LayoutListItemMarker"; } 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(); return nearest_block_ancestor();
} }
void LayoutNode::render(RenderingContext& context) void LayoutNode::render(RenderingContext& context, PaintPhase phase)
{ {
if (!is_visible()) if (!is_visible())
return; return;
@ -97,7 +97,7 @@ void LayoutNode::render(RenderingContext& context)
for_each_child([&](auto& child) { for_each_child([&](auto& child) {
if (child.is_box() && to<LayoutBox>(child).stacking_context()) if (child.is_box() && to<LayoutBox>(child).stacking_context())
return; return;
child.render(context); child.render(context, phase);
}); });
} }

View file

@ -171,7 +171,14 @@ public:
}; };
virtual void layout(LayoutMode); 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_absolutely_positioned() const;
bool is_fixed_position() const; bool is_fixed_position() const;

View file

@ -65,9 +65,4 @@ void LayoutWidget::update_widget()
widget().move_to(adjusted_widget_position); 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&); LayoutWidget(const Element&, GUI::Widget&);
virtual ~LayoutWidget() override; virtual ~LayoutWidget() override;
virtual void render(RenderingContext&) override;
GUI::Widget& widget() { return m_widget; } GUI::Widget& widget() { return m_widget; }
const GUI::Widget& widget() const { 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()) { if (!m_box.is_root()) {
m_box.render(context); m_box.render(context, phase);
} else { } else {
// NOTE: LayoutDocument::render() merely calls StackingContext::render() // NOTE: LayoutDocument::render() merely calls StackingContext::render()
// so we call its base class instead. // 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) { for (auto* child : m_children) {
child->render(context); child->render(context, phase);
} }
} }

View file

@ -27,7 +27,7 @@
#pragma once #pragma once
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibWeb/Forward.h> #include <LibWeb/Layout/LayoutNode.h>
namespace Web { namespace Web {
@ -40,7 +40,7 @@ public:
StackingContext* parent() { return m_parent; } StackingContext* parent() { return m_parent; }
const StackingContext* parent() const { return m_parent; } const StackingContext* parent() const { return m_parent; }
void render(RenderingContext&); void render(RenderingContext&, LayoutNode::PaintPhase);
void dump(int indent = 0) const; 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() }); 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_should_show_line_box_borders(m_should_show_line_box_borders);
context.set_viewport_rect(viewport_rect_in_content_coordinates()); 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) 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()); Web::RenderingContext context(painter, palette(), Gfx::IntPoint());
context.set_viewport_rect(content_rect); 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) void PageHost::set_viewport_rect(const Gfx::IntRect& rect)