1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-28 07:57:46 +00:00

LibWeb: Make the paint tree GC-allocated

This simplifies the ownership model between DOM/layout/paint nodes
immensely by deferring to the garbage collector for figuring out what's
live and what's not.
This commit is contained in:
Andreas Kling 2023-01-11 12:51:49 +01:00
parent 35ba13802d
commit 4d401bf796
64 changed files with 148 additions and 108 deletions

View file

@ -13,9 +13,9 @@
namespace Web::Painting {
NonnullRefPtr<ButtonPaintable> ButtonPaintable::create(Layout::ButtonBox const& layout_box)
JS::NonnullGCPtr<ButtonPaintable> ButtonPaintable::create(Layout::ButtonBox const& layout_box)
{
return adopt_ref(*new ButtonPaintable(layout_box));
return layout_box.heap().allocate_without_realm<ButtonPaintable>(layout_box);
}
ButtonPaintable::ButtonPaintable(Layout::ButtonBox const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class ButtonPaintable final : public LabelablePaintable {
JS_CELL(ButtonPaintable, LabelablePaintable);
public:
static NonnullRefPtr<ButtonPaintable> create(Layout::ButtonBox const&);
static JS::NonnullGCPtr<ButtonPaintable> create(Layout::ButtonBox const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -8,9 +8,9 @@
namespace Web::Painting {
NonnullRefPtr<CanvasPaintable> CanvasPaintable::create(Layout::CanvasBox const& layout_box)
JS::NonnullGCPtr<CanvasPaintable> CanvasPaintable::create(Layout::CanvasBox const& layout_box)
{
return adopt_ref(*new CanvasPaintable(layout_box));
return layout_box.heap().allocate_without_realm<CanvasPaintable>(layout_box);
}
CanvasPaintable::CanvasPaintable(Layout::CanvasBox const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class CanvasPaintable final : public PaintableBox {
JS_CELL(CanvasPaintable, PaintableBox);
public:
static NonnullRefPtr<CanvasPaintable> create(Layout::CanvasBox const&);
static JS::NonnullGCPtr<CanvasPaintable> create(Layout::CanvasBox const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -14,9 +14,9 @@
namespace Web::Painting {
NonnullRefPtr<CheckBoxPaintable> CheckBoxPaintable::create(Layout::CheckBox const& layout_box)
JS::NonnullGCPtr<CheckBoxPaintable> CheckBoxPaintable::create(Layout::CheckBox const& layout_box)
{
return adopt_ref(*new CheckBoxPaintable(layout_box));
return layout_box.heap().allocate_without_realm<CheckBoxPaintable>(layout_box);
}
CheckBoxPaintable::CheckBoxPaintable(Layout::CheckBox const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class CheckBoxPaintable final : public LabelablePaintable {
JS_CELL(CheckBoxPaintable, LabelablePaintable);
public:
static NonnullRefPtr<CheckBoxPaintable> create(Layout::CheckBox const&);
static JS::NonnullGCPtr<CheckBoxPaintable> create(Layout::CheckBox const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -13,9 +13,9 @@
namespace Web::Painting {
NonnullRefPtr<ImagePaintable> ImagePaintable::create(Layout::ImageBox const& layout_box)
JS::NonnullGCPtr<ImagePaintable> ImagePaintable::create(Layout::ImageBox const& layout_box)
{
return adopt_ref(*new ImagePaintable(layout_box));
return layout_box.heap().allocate_without_realm<ImagePaintable>(layout_box);
}
ImagePaintable::ImagePaintable(Layout::ImageBox const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class ImagePaintable final : public PaintableBox {
JS_CELL(ImagePaintable, PaintableBox);
public:
static NonnullRefPtr<ImagePaintable> create(Layout::ImageBox const&);
static JS::NonnullGCPtr<ImagePaintable> create(Layout::ImageBox const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -14,9 +14,9 @@
namespace Web::Painting {
NonnullRefPtr<InlinePaintable> InlinePaintable::create(Layout::InlineNode const& layout_node)
JS::NonnullGCPtr<InlinePaintable> InlinePaintable::create(Layout::InlineNode const& layout_node)
{
return adopt_ref(*new InlinePaintable(layout_node));
return layout_node.heap().allocate_without_realm<InlinePaintable>(layout_node);
}
InlinePaintable::InlinePaintable(Layout::InlineNode const& layout_node)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class InlinePaintable final : public Paintable {
JS_CELL(InlinePaintable, Paintable);
public:
static NonnullRefPtr<InlinePaintable> create(Layout::InlineNode const&);
static JS::NonnullGCPtr<InlinePaintable> create(Layout::InlineNode const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -81,10 +81,6 @@ void LabelablePaintable::handle_associated_label_mousedown(Badge<Layout::Label>)
void LabelablePaintable::handle_associated_label_mouseup(Badge<Layout::Label>)
{
// NOTE: Handling the click may run arbitrary JS, which could disappear this node.
NonnullRefPtr protected_this = *this;
JS::NonnullGCPtr protected_browsing_context { browsing_context() };
set_being_pressed(false);
}

View file

@ -16,6 +16,8 @@ namespace Web::Painting {
// FIXME: Splinter this into FormAssociatedLabelablePaintable once
// ProgressPaintable switches over to this.
class LabelablePaintable : public PaintableBox {
JS_CELL(LabelablePaintable, PaintableBox);
public:
Layout::FormAssociatedLabelableNode const& layout_box() const;
Layout::FormAssociatedLabelableNode& layout_box();

View file

@ -11,9 +11,9 @@
namespace Web::Painting {
NonnullRefPtr<MarkerPaintable> MarkerPaintable::create(Layout::ListItemMarkerBox const& layout_box)
JS::NonnullGCPtr<MarkerPaintable> MarkerPaintable::create(Layout::ListItemMarkerBox const& layout_box)
{
return adopt_ref(*new MarkerPaintable(layout_box));
return layout_box.heap().allocate_without_realm<MarkerPaintable>(layout_box);
}
MarkerPaintable::MarkerPaintable(Layout::ListItemMarkerBox const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class MarkerPaintable final : public PaintableBox {
JS_CELL(MarkerPaintable, PaintableBox);
public:
static NonnullRefPtr<MarkerPaintable> create(Layout::ListItemMarkerBox const&);
static JS::NonnullGCPtr<MarkerPaintable> create(Layout::ListItemMarkerBox const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -13,9 +13,9 @@
namespace Web::Painting {
NonnullRefPtr<NestedBrowsingContextPaintable> NestedBrowsingContextPaintable::create(Layout::FrameBox const& layout_box)
JS::NonnullGCPtr<NestedBrowsingContextPaintable> NestedBrowsingContextPaintable::create(Layout::FrameBox const& layout_box)
{
return adopt_ref(*new NestedBrowsingContextPaintable(layout_box));
return layout_box.heap().allocate_without_realm<NestedBrowsingContextPaintable>(layout_box);
}
NestedBrowsingContextPaintable::NestedBrowsingContextPaintable(Layout::FrameBox const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class NestedBrowsingContextPaintable final : public PaintableBox {
JS_CELL(NestedBrowsingContextPaintable, PaintableBox);
public:
static NonnullRefPtr<NestedBrowsingContextPaintable> create(Layout::FrameBox const&);
static JS::NonnullGCPtr<NestedBrowsingContextPaintable> create(Layout::FrameBox const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -10,6 +10,12 @@
namespace Web::Painting {
void Paintable::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_layout_node);
}
Paintable::DispatchEventOfSameName Paintable::handle_mousedown(Badge<EventHandler>, CSSPixelPoint, unsigned, unsigned)
{
return DispatchEventOfSameName::Yes;
@ -48,7 +54,7 @@ Optional<HitTestResult> Paintable::hit_test(CSSPixelPoint, HitTestType) const
Paintable const* Paintable::first_child() const
{
auto* layout_child = m_layout_node.first_child();
auto* layout_child = m_layout_node->first_child();
for (; layout_child && !layout_child->paintable(); layout_child = layout_child->next_sibling())
;
return layout_child ? layout_child->paintable() : nullptr;
@ -56,7 +62,7 @@ Paintable const* Paintable::first_child() const
Paintable const* Paintable::next_sibling() const
{
auto* layout_node = m_layout_node.next_sibling();
auto* layout_node = m_layout_node->next_sibling();
for (; layout_node && !layout_node->paintable(); layout_node = layout_node->next_sibling())
;
return layout_node ? layout_node->paintable() : nullptr;
@ -64,7 +70,7 @@ Paintable const* Paintable::next_sibling() const
Paintable const* Paintable::last_child() const
{
auto* layout_child = m_layout_node.last_child();
auto* layout_child = m_layout_node->last_child();
for (; layout_child && !layout_child->paintable(); layout_child = layout_child->previous_sibling())
;
return layout_child ? layout_child->paintable() : nullptr;
@ -72,7 +78,7 @@ Paintable const* Paintable::last_child() const
Paintable const* Paintable::previous_sibling() const
{
auto* layout_node = m_layout_node.previous_sibling();
auto* layout_node = m_layout_node->previous_sibling();
for (; layout_node && !layout_node->paintable(); layout_node = layout_node->previous_sibling())
;
return layout_node ? layout_node->paintable() : nullptr;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -28,7 +28,7 @@ enum class PaintPhase {
};
struct HitTestResult {
NonnullRefPtr<Painting::Paintable> paintable;
JS::Handle<Painting::Paintable> paintable;
int index_in_node { 0 };
enum InternalPosition {
@ -48,9 +48,8 @@ enum class HitTestType {
TextCursor, // Clicking past the right/bottom edge of text will still hit the text
};
class Paintable : public RefCounted<Paintable> {
AK_MAKE_NONMOVABLE(Paintable);
AK_MAKE_NONCOPYABLE(Paintable);
class Paintable : public JS::Cell {
JS_CELL(Paintable, Cell);
public:
virtual ~Paintable() = default;
@ -108,24 +107,24 @@ public:
virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y);
Layout::Node const& layout_node() const { return m_layout_node; }
Layout::Node& layout_node() { return const_cast<Layout::Node&>(m_layout_node); }
Layout::Node& layout_node() { return const_cast<Layout::Node&>(*m_layout_node); }
DOM::Node* dom_node() { return layout_node().dom_node(); }
DOM::Node const* dom_node() const { return layout_node().dom_node(); }
auto const& computed_values() const { return m_layout_node.computed_values(); }
auto const& computed_values() const { return m_layout_node->computed_values(); }
bool visible_for_hit_testing() const { return computed_values().pointer_events() != CSS::PointerEvents::None; }
HTML::BrowsingContext const& browsing_context() const { return m_layout_node.browsing_context(); }
HTML::BrowsingContext const& browsing_context() const { return m_layout_node->browsing_context(); }
HTML::BrowsingContext& browsing_context() { return layout_node().browsing_context(); }
void set_needs_display() const { const_cast<Layout::Node&>(m_layout_node).set_needs_display(); }
void set_needs_display() const { const_cast<Layout::Node&>(*m_layout_node).set_needs_display(); }
Layout::BlockContainer const* containing_block() const
{
if (!m_containing_block.has_value())
m_containing_block = const_cast<Layout::Node&>(m_layout_node).containing_block();
m_containing_block = m_layout_node->containing_block();
return *m_containing_block;
}
@ -138,8 +137,10 @@ protected:
{
}
virtual void visit_edges(Cell::Visitor&) override;
private:
Layout::Node const& m_layout_node;
JS::NonnullGCPtr<Layout::Node> m_layout_node;
Optional<Layout::BlockContainer*> mutable m_containing_block;
};
@ -154,6 +155,6 @@ inline DOM::Node const* HitTestResult::dom_node() const
}
template<>
inline bool Paintable::fast_is<PaintableBox>() const { return m_layout_node.is_box(); }
inline bool Paintable::fast_is<PaintableBox>() const { return m_layout_node->is_box(); }
}

View file

@ -19,9 +19,14 @@
namespace Web::Painting {
NonnullRefPtr<PaintableBox> PaintableBox::create(Layout::Box const& layout_box)
JS::NonnullGCPtr<PaintableWithLines> PaintableWithLines::create(Layout::BlockContainer const& block_container)
{
return adopt_ref(*new PaintableBox(layout_box));
return block_container.heap().allocate_without_realm<PaintableWithLines>(block_container);
}
JS::NonnullGCPtr<PaintableBox> PaintableBox::create(Layout::Box const& layout_box)
{
return layout_box.heap().allocate_without_realm<PaintableBox>(layout_box);
}
PaintableBox::PaintableBox(Layout::Box const& layout_box)
@ -679,7 +684,7 @@ Optional<HitTestResult> PaintableBox::hit_test(CSSPixelPoint position, HitTestTy
continue;
return result;
}
return HitTestResult { *this };
return HitTestResult { const_cast<PaintableBox&>(*this) };
}
Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, HitTestType type) const
@ -700,7 +705,7 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
if (fragment_absolute_rect.contains(position)) {
if (is<Layout::BlockContainer>(fragment.layout_node()) && fragment.layout_node().paintable())
return fragment.layout_node().paintable()->hit_test(position, type);
return HitTestResult { *fragment.layout_node().paintable(), fragment.text_index_at(position.x()) };
return HitTestResult { const_cast<Paintable&>(const_cast<Paintable&>(*fragment.layout_node().paintable())), fragment.text_index_at(position.x()) };
}
// If we reached this point, the position is not within the fragment. However, the fragment start or end might be the place to place the cursor.
@ -709,14 +714,14 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
// We arbitrarily choose to consider the end of the line above and ignore the beginning of the line below.
// If we knew the direction of selection, we could make a better choice.
if (fragment_absolute_rect.bottom() <= position.y()) { // fully below the fragment
last_good_candidate = HitTestResult { *fragment.layout_node().paintable(), fragment.start() + fragment.length() };
last_good_candidate = HitTestResult { const_cast<Paintable&>(*fragment.layout_node().paintable()), fragment.start() + fragment.length() };
} else if (fragment_absolute_rect.top() <= position.y()) { // vertically within the fragment
if (position.x() < fragment_absolute_rect.left()) { // left of the fragment
if (!last_good_candidate.has_value()) { // first fragment of the line
last_good_candidate = HitTestResult { *fragment.layout_node().paintable(), fragment.start() };
last_good_candidate = HitTestResult { const_cast<Paintable&>(*fragment.layout_node().paintable()), fragment.start() };
}
} else { // right of the fragment
last_good_candidate = HitTestResult { *fragment.layout_node().paintable(), fragment.start() + fragment.length() };
last_good_candidate = HitTestResult { const_cast<Paintable&>(*fragment.layout_node().paintable()), fragment.start() + fragment.length() };
}
}
}
@ -725,7 +730,7 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
if (type == HitTestType::TextCursor && last_good_candidate.has_value())
return last_good_candidate;
if (is_visible() && absolute_border_box_rect().contains(position.x(), position.y()))
return HitTestResult { *this };
return HitTestResult { const_cast<PaintableWithLines&>(*this) };
return {};
}

View file

@ -14,8 +14,10 @@
namespace Web::Painting {
class PaintableBox : public Paintable {
JS_CELL(PaintableBox, Paintable);
public:
static NonnullRefPtr<PaintableBox> create(Layout::Box const&);
static JS::NonnullGCPtr<PaintableBox> create(Layout::Box const&);
virtual ~PaintableBox();
virtual void paint(PaintContext&, PaintPhase) const override;
@ -162,11 +164,10 @@ private:
};
class PaintableWithLines : public PaintableBox {
JS_CELL(PaintableWithLines, PaintableBox);
public:
static NonnullRefPtr<PaintableWithLines> create(Layout::BlockContainer const& block_container)
{
return adopt_ref(*new PaintableWithLines(block_container));
}
static JS::NonnullGCPtr<PaintableWithLines> create(Layout::BlockContainer const&);
virtual ~PaintableWithLines() override;
Layout::BlockContainer const& layout_box() const;

View file

@ -9,9 +9,9 @@
namespace Web::Painting {
NonnullRefPtr<ProgressPaintable> ProgressPaintable::create(Layout::Progress const& layout_box)
JS::NonnullGCPtr<ProgressPaintable> ProgressPaintable::create(Layout::Progress const& layout_box)
{
return adopt_ref(*new ProgressPaintable(layout_box));
return layout_box.heap().allocate_without_realm<ProgressPaintable>(layout_box);
}
ProgressPaintable::ProgressPaintable(Layout::Progress const& layout_box)

View file

@ -15,8 +15,10 @@ namespace Web::Painting {
// LabelablePaintable should be split into FormAssociatedLabelablePaintable once this
// happens.
class ProgressPaintable final : public PaintableBox {
JS_CELL(ProgressPaintable, PaintableBox);
public:
static NonnullRefPtr<ProgressPaintable> create(Layout::Progress const&);
static JS::NonnullGCPtr<ProgressPaintable> create(Layout::Progress const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -15,9 +15,9 @@
namespace Web::Painting {
NonnullRefPtr<RadioButtonPaintable> RadioButtonPaintable::create(Layout::RadioButton const& layout_box)
JS::NonnullGCPtr<RadioButtonPaintable> RadioButtonPaintable::create(Layout::RadioButton const& layout_box)
{
return adopt_ref(*new RadioButtonPaintable(layout_box));
return layout_box.heap().allocate_without_realm<RadioButtonPaintable>(layout_box);
}
RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class RadioButtonPaintable final : public LabelablePaintable {
JS_CELL(RadioButtonPaintable, LabelablePaintable);
public:
static NonnullRefPtr<RadioButtonPaintable> create(Layout::RadioButton const&);
static JS::NonnullGCPtr<RadioButtonPaintable> create(Layout::RadioButton const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -11,9 +11,9 @@
namespace Web::Painting {
NonnullRefPtr<SVGGeometryPaintable> SVGGeometryPaintable::create(Layout::SVGGeometryBox const& layout_box)
JS::NonnullGCPtr<SVGGeometryPaintable> SVGGeometryPaintable::create(Layout::SVGGeometryBox const& layout_box)
{
return adopt_ref(*new SVGGeometryPaintable(layout_box));
return layout_box.heap().allocate_without_realm<SVGGeometryPaintable>(layout_box);
}
SVGGeometryPaintable::SVGGeometryPaintable(Layout::SVGGeometryBox const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class SVGGeometryPaintable : public SVGGraphicsPaintable {
JS_CELL(SVGGeometryPaintable, SVGGraphicsPaintable);
public:
static NonnullRefPtr<SVGGeometryPaintable> create(Layout::SVGGeometryBox const&);
static JS::NonnullGCPtr<SVGGeometryPaintable> create(Layout::SVGGeometryBox const&);
virtual void paint(PaintContext&, PaintPhase) const override;

View file

@ -12,6 +12,8 @@
namespace Web::Painting {
class SVGGraphicsPaintable : public SVGPaintable {
JS_CELL(SVGGraphicsPaintable, SVGPaintable);
public:
virtual void before_children_paint(PaintContext&, PaintPhase) const override;

View file

@ -12,6 +12,8 @@
namespace Web::Painting {
class SVGPaintable : public PaintableBox {
JS_CELL(SVGPaintable, PaintableBox);
public:
virtual void before_children_paint(PaintContext&, PaintPhase) const override;
virtual void after_children_paint(PaintContext&, PaintPhase) const override;

View file

@ -9,9 +9,9 @@
namespace Web::Painting {
NonnullRefPtr<SVGSVGPaintable> SVGSVGPaintable::create(Layout::SVGSVGBox const& layout_box)
JS::NonnullGCPtr<SVGSVGPaintable> SVGSVGPaintable::create(Layout::SVGSVGBox const& layout_box)
{
return adopt_ref(*new SVGSVGPaintable(layout_box));
return layout_box.heap().allocate_without_realm<SVGSVGPaintable>(layout_box);
}
SVGSVGPaintable::SVGSVGPaintable(Layout::SVGSVGBox const& layout_box)

View file

@ -12,8 +12,10 @@
namespace Web::Painting {
class SVGSVGPaintable : public PaintableBox {
JS_CELL(SVGSVGPaintable, PaintableBox);
public:
static NonnullRefPtr<SVGSVGPaintable> create(Layout::SVGSVGBox const&);
static JS::NonnullGCPtr<SVGSVGPaintable> create(Layout::SVGSVGBox const&);
virtual void before_children_paint(PaintContext&, PaintPhase) const override;
virtual void after_children_paint(PaintContext&, PaintPhase) const override;

View file

@ -578,7 +578,7 @@ Optional<HitTestResult> StackingContext::hit_test(CSSPixelPoint position, HitTes
// 1. the background and borders of the element forming the stacking context.
if (paintable().absolute_border_box_rect().contains(transformed_position.x().value(), transformed_position.y().value())) {
return HitTestResult {
.paintable = paintable(),
.paintable = const_cast<PaintableBox&>(paintable()),
};
}

View file

@ -12,9 +12,9 @@
namespace Web::Painting {
NonnullRefPtr<TextPaintable> TextPaintable::create(Layout::TextNode const& layout_node)
JS::NonnullGCPtr<TextPaintable> TextPaintable::create(Layout::TextNode const& layout_node)
{
return adopt_ref(*new TextPaintable(layout_node));
return layout_node.heap().allocate_without_realm<TextPaintable>(layout_node);
}
TextPaintable::TextPaintable(Layout::TextNode const& layout_node)

View file

@ -11,8 +11,10 @@
namespace Web::Painting {
class TextPaintable : public Paintable {
JS_CELL(TextPaintable, Paintable);
public:
static NonnullRefPtr<TextPaintable> create(Layout::TextNode const&);
static JS::NonnullGCPtr<TextPaintable> create(Layout::TextNode const&);
Layout::TextNode const& layout_node() const { return static_cast<Layout::TextNode const&>(Paintable::layout_node()); }