Browse Source

LibWeb: Move mouse event and label logic from layout to painting tree

Input events have nothing to do with layout, so let's not send them to
layout nodes.

The job of Paintable starts to become clear. It represents a paintable
item that can be rendered into the viewport, which means it can also
be targeted by the mouse cursor.
Andreas Kling 3 năm trước cách đây
mục cha
commit
cb0c5390ff
35 tập tin đã thay đổi với 560 bổ sung429 xóa
  1. 2 0
      Userland/Libraries/LibWeb/CMakeLists.txt
  2. 7 0
      Userland/Libraries/LibWeb/DOM/Node.cpp
  3. 1 0
      Userland/Libraries/LibWeb/DOM/Node.h
  4. 4 0
      Userland/Libraries/LibWeb/Forward.h
  5. 2 2
      Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
  6. 2 2
      Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
  7. 0 11
      Userland/Libraries/LibWeb/Layout/BlockContainer.cpp
  8. 0 2
      Userland/Libraries/LibWeb/Layout/BlockContainer.h
  9. 0 75
      Userland/Libraries/LibWeb/Layout/ButtonBox.cpp
  10. 1 15
      Userland/Libraries/LibWeb/Layout/ButtonBox.h
  11. 0 84
      Userland/Libraries/LibWeb/Layout/CheckBox.cpp
  12. 1 15
      Userland/Libraries/LibWeb/Layout/CheckBox.h
  13. 11 13
      Userland/Libraries/LibWeb/Layout/Label.cpp
  14. 6 6
      Userland/Libraries/LibWeb/Layout/Label.h
  15. 22 0
      Userland/Libraries/LibWeb/Layout/LabelableNode.cpp
  16. 2 3
      Userland/Libraries/LibWeb/Layout/LabelableNode.h
  17. 0 26
      Userland/Libraries/LibWeb/Layout/Node.cpp
  18. 0 7
      Userland/Libraries/LibWeb/Layout/Node.h
  19. 0 87
      Userland/Libraries/LibWeb/Layout/RadioButton.cpp
  20. 1 17
      Userland/Libraries/LibWeb/Layout/RadioButton.h
  21. 0 35
      Userland/Libraries/LibWeb/Layout/TextNode.cpp
  22. 0 4
      Userland/Libraries/LibWeb/Layout/TextNode.h
  23. 14 13
      Userland/Libraries/LibWeb/Page/EventHandler.cpp
  24. 85 2
      Userland/Libraries/LibWeb/Painting/ButtonPaintable.cpp
  25. 15 2
      Userland/Libraries/LibWeb/Painting/ButtonPaintable.h
  26. 91 2
      Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp
  27. 15 2
      Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h
  28. 26 0
      Userland/Libraries/LibWeb/Painting/LabelablePaintable.cpp
  29. 27 0
      Userland/Libraries/LibWeb/Painting/LabelablePaintable.h
  30. 48 0
      Userland/Libraries/LibWeb/Painting/Paintable.cpp
  31. 22 0
      Userland/Libraries/LibWeb/Painting/Paintable.h
  32. 98 2
      Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp
  33. 17 2
      Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.h
  34. 35 0
      Userland/Libraries/LibWeb/Painting/TextPaintable.cpp
  35. 5 0
      Userland/Libraries/LibWeb/Painting/TextPaintable.h

+ 2 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -246,6 +246,7 @@ set(SOURCES
     Layout/InlineLevelIterator.cpp
     Layout/InlineLevelIterator.cpp
     Layout/InlineNode.cpp
     Layout/InlineNode.cpp
     Layout/Label.cpp
     Layout/Label.cpp
+    Layout/LabelableNode.cpp
     Layout/LayoutPosition.cpp
     Layout/LayoutPosition.cpp
     Layout/LineBox.cpp
     Layout/LineBox.cpp
     Layout/LineBoxFragment.cpp
     Layout/LineBoxFragment.cpp
@@ -289,6 +290,7 @@ set(SOURCES
     Painting/CheckBoxPaintable.cpp
     Painting/CheckBoxPaintable.cpp
     Painting/ImagePaintable.cpp
     Painting/ImagePaintable.cpp
     Painting/InlinePaintable.cpp
     Painting/InlinePaintable.cpp
+    Painting/LabelablePaintable.cpp
     Painting/MarkerPaintable.cpp
     Painting/MarkerPaintable.cpp
     Painting/NestedBrowsingContextPaintable.cpp
     Painting/NestedBrowsingContextPaintable.cpp
     Painting/PaintContext.cpp
     Painting/PaintContext.cpp

+ 7 - 0
Userland/Libraries/LibWeb/DOM/Node.cpp

@@ -1010,6 +1010,13 @@ size_t Node::length() const
     return child_count();
     return child_count();
 }
 }
 
 
+Painting::Paintable const* Node::paintable() const
+{
+    if (!layout_node())
+        return nullptr;
+    return layout_node()->paintable();
+}
+
 Painting::PaintableBox const* Node::paint_box() const
 Painting::PaintableBox const* Node::paint_box() const
 {
 {
     if (!layout_node())
     if (!layout_node())

+ 1 - 0
Userland/Libraries/LibWeb/DOM/Node.h

@@ -159,6 +159,7 @@ public:
     Layout::Node* layout_node() { return m_layout_node; }
     Layout::Node* layout_node() { return m_layout_node; }
 
 
     Painting::PaintableBox const* paint_box() const;
     Painting::PaintableBox const* paint_box() const;
+    Painting::Paintable const* paintable() const;
 
 
     void set_layout_node(Badge<Layout::Node>, Layout::Node*) const;
     void set_layout_node(Badge<Layout::Node>, Layout::Node*) const;
 
 

+ 4 - 0
Userland/Libraries/LibWeb/Forward.h

@@ -265,9 +265,13 @@ class PerformanceTiming;
 
 
 namespace Web::Painting {
 namespace Web::Painting {
 enum class PaintPhase;
 enum class PaintPhase;
+class ButtonPaintable;
+class CheckBoxPaintable;
+class LabelablePaintable;
 class Paintable;
 class Paintable;
 class PaintableBox;
 class PaintableBox;
 class PaintableWithLines;
 class PaintableWithLines;
+class TextPaintable;
 }
 }
 
 
 namespace Web::RequestIdleCallback {
 namespace Web::RequestIdleCallback {

+ 2 - 2
Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp

@@ -29,7 +29,7 @@ HTMLInputElement::~HTMLInputElement()
 {
 {
 }
 }
 
 
-void HTMLInputElement::did_click_button(Badge<Layout::ButtonBox>)
+void HTMLInputElement::did_click_button(Badge<Painting::ButtonPaintable>)
 {
 {
     // FIXME: This should be a PointerEvent.
     // FIXME: This should be a PointerEvent.
     dispatch_event(DOM::Event::create(EventNames::click));
     dispatch_event(DOM::Event::create(EventNames::click));
@@ -42,7 +42,7 @@ void HTMLInputElement::did_click_button(Badge<Layout::ButtonBox>)
     }
     }
 }
 }
 
 
-void HTMLInputElement::did_click_checkbox(Badge<Layout::CheckBox>)
+void HTMLInputElement::did_click_checkbox(Badge<Painting::CheckBoxPaintable>)
 {
 {
     // FIXME: This should be a PointerEvent.
     // FIXME: This should be a PointerEvent.
     auto click_event = DOM::Event::create(EventNames::click);
     auto click_event = DOM::Event::create(EventNames::click);

+ 2 - 2
Userland/Libraries/LibWeb/HTML/HTMLInputElement.h

@@ -73,8 +73,8 @@ public:
     };
     };
     void set_checked(bool, ChangeSource = ChangeSource::Programmatic, ShouldRunActivationBehavior = ShouldRunActivationBehavior::Yes);
     void set_checked(bool, ChangeSource = ChangeSource::Programmatic, ShouldRunActivationBehavior = ShouldRunActivationBehavior::Yes);
 
 
-    void did_click_button(Badge<Layout::ButtonBox>);
-    void did_click_checkbox(Badge<Layout::CheckBox>);
+    void did_click_button(Badge<Painting::ButtonPaintable>);
+    void did_click_checkbox(Badge<Painting::CheckBoxPaintable>);
 
 
     void did_edit_text_node(Badge<BrowsingContext>);
     void did_edit_text_node(Badge<BrowsingContext>);
 
 

+ 0 - 11
Userland/Libraries/LibWeb/Layout/BlockContainer.cpp

@@ -69,17 +69,6 @@ void BlockContainer::set_scroll_offset(const Gfx::FloatPoint& offset)
     set_needs_display();
     set_needs_display();
 }
 }
 
 
-bool BlockContainer::handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned int, unsigned int, int wheel_delta_x, int wheel_delta_y)
-{
-    if (!is_scrollable())
-        return false;
-    auto new_offset = m_scroll_offset;
-    new_offset.translate_by(wheel_delta_x, wheel_delta_y);
-    set_scroll_offset(new_offset);
-
-    return true;
-}
-
 Painting::PaintableWithLines const* BlockContainer::paint_box() const
 Painting::PaintableWithLines const* BlockContainer::paint_box() const
 {
 {
     return static_cast<Painting::PaintableWithLines const*>(Box::paint_box());
     return static_cast<Painting::PaintableWithLines const*>(Box::paint_box());

+ 0 - 2
Userland/Libraries/LibWeb/Layout/BlockContainer.h

@@ -35,8 +35,6 @@ public:
 
 
 private:
 private:
     virtual bool is_block_container() const final { return true; }
     virtual bool is_block_container() const final { return true; }
-    virtual bool wants_mouse_events() const override { return false; }
-    virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override;
 
 
     Gfx::FloatPoint m_scroll_offset;
     Gfx::FloatPoint m_scroll_offset;
 };
 };

+ 0 - 75
Userland/Libraries/LibWeb/Layout/ButtonBox.cpp

@@ -31,81 +31,6 @@ void ButtonBox::prepare_for_replaced_layout()
     set_intrinsic_height(font().glyph_height());
     set_intrinsic_height(font().glyph_height());
 }
 }
 
 
-void ButtonBox::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
-{
-    if (button != GUI::MouseButton::Primary || !dom_node().enabled())
-        return;
-
-    m_being_pressed = true;
-    set_needs_display();
-
-    m_tracking_mouse = true;
-    browsing_context().event_handler().set_mouse_event_tracking_layout_node(this);
-}
-
-void ButtonBox::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
-{
-    if (!m_tracking_mouse || button != GUI::MouseButton::Primary || !dom_node().enabled())
-        return;
-
-    // NOTE: Handling the click may run arbitrary JS, which could disappear this node.
-    NonnullRefPtr protected_this = *this;
-    NonnullRefPtr protected_browsing_context = browsing_context();
-
-    bool is_inside_node_or_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
-    if (!is_inside_node_or_label)
-        is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
-
-    if (is_inside_node_or_label)
-        dom_node().did_click_button({});
-
-    m_being_pressed = false;
-    m_tracking_mouse = false;
-
-    protected_browsing_context->event_handler().set_mouse_event_tracking_layout_node(nullptr);
-}
-
-void ButtonBox::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
-{
-    if (!m_tracking_mouse || !dom_node().enabled())
-        return;
-
-    bool is_inside_node_or_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
-    if (!is_inside_node_or_label)
-        is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
-
-    if (m_being_pressed == is_inside_node_or_label)
-        return;
-
-    m_being_pressed = is_inside_node_or_label;
-    set_needs_display();
-}
-
-void ButtonBox::handle_associated_label_mousedown(Badge<Label>)
-{
-    m_being_pressed = true;
-    set_needs_display();
-}
-
-void ButtonBox::handle_associated_label_mouseup(Badge<Label>)
-{
-    // NOTE: Handling the click may run arbitrary JS, which could disappear this node.
-    NonnullRefPtr protected_this = *this;
-    NonnullRefPtr protected_browsing_context = browsing_context();
-
-    dom_node().did_click_button({});
-    m_being_pressed = false;
-}
-
-void ButtonBox::handle_associated_label_mousemove(Badge<Label>, bool is_inside_node_or_label)
-{
-    if (m_being_pressed == is_inside_node_or_label)
-        return;
-
-    m_being_pressed = is_inside_node_or_label;
-    set_needs_display();
-}
-
 RefPtr<Painting::Paintable> ButtonBox::create_paintable() const
 RefPtr<Painting::Paintable> ButtonBox::create_paintable() const
 {
 {
     return Painting::ButtonPaintable::create(*this);
     return Painting::ButtonPaintable::create(*this);

+ 1 - 15
Userland/Libraries/LibWeb/Layout/ButtonBox.h

@@ -21,22 +21,8 @@ public:
     const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
 
 
-    bool being_pressed() const { return m_being_pressed; }
-
-    virtual RefPtr<Painting::Paintable> create_paintable() const override;
-
 private:
 private:
-    virtual bool wants_mouse_events() const override { return true; }
-    virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
-    virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
-    virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers) override;
-
-    virtual void handle_associated_label_mousedown(Badge<Label>) override;
-    virtual void handle_associated_label_mouseup(Badge<Label>) override;
-    virtual void handle_associated_label_mousemove(Badge<Label>, bool is_inside_node_or_label) override;
-
-    bool m_being_pressed { false };
-    bool m_tracking_mouse { false };
+    virtual RefPtr<Painting::Paintable> create_paintable() const override;
 };
 };
 
 
 }
 }

+ 0 - 84
Userland/Libraries/LibWeb/Layout/CheckBox.cpp

@@ -4,10 +4,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
 
 
-#include <LibGUI/Event.h>
 #include <LibGfx/Font.h>
 #include <LibGfx/Font.h>
-#include <LibGfx/Painter.h>
-#include <LibGfx/StylePainter.h>
 #include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/HTMLInputElement.h>
 #include <LibWeb/HTML/HTMLInputElement.h>
 #include <LibWeb/Layout/CheckBox.h>
 #include <LibWeb/Layout/CheckBox.h>
@@ -27,87 +24,6 @@ CheckBox::~CheckBox()
 {
 {
 }
 }
 
 
-void CheckBox::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
-{
-    if (button != GUI::MouseButton::Primary || !dom_node().enabled())
-        return;
-
-    m_being_pressed = true;
-    set_needs_display();
-
-    m_tracking_mouse = true;
-    browsing_context().event_handler().set_mouse_event_tracking_layout_node(this);
-}
-
-void CheckBox::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
-{
-    if (!m_tracking_mouse || button != GUI::MouseButton::Primary || !dom_node().enabled())
-        return;
-
-    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
-    NonnullRefPtr protect = *this;
-
-    bool is_inside_node_or_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
-    if (!is_inside_node_or_label)
-        is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
-
-    if (is_inside_node_or_label) {
-        dom_node().did_click_checkbox({});
-        dom_node().set_checked(!dom_node().checked(), HTML::HTMLInputElement::ChangeSource::User);
-    }
-
-    m_being_pressed = false;
-    m_tracking_mouse = false;
-    browsing_context().event_handler().set_mouse_event_tracking_layout_node(nullptr);
-}
-
-void CheckBox::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
-{
-    if (!m_tracking_mouse || !dom_node().enabled())
-        return;
-
-    bool is_inside_node_or_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
-    if (!is_inside_node_or_label)
-        is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
-
-    if (m_being_pressed == is_inside_node_or_label)
-        return;
-
-    m_being_pressed = is_inside_node_or_label;
-    set_needs_display();
-}
-
-void CheckBox::handle_associated_label_mousedown(Badge<Label>)
-{
-    if (!dom_node().enabled())
-        return;
-
-    m_being_pressed = true;
-    set_needs_display();
-}
-
-void CheckBox::handle_associated_label_mouseup(Badge<Label>)
-{
-    if (!dom_node().enabled())
-        return;
-
-    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
-    NonnullRefPtr protect = *this;
-
-    dom_node().did_click_checkbox({});
-    dom_node().set_checked(!dom_node().checked(), HTML::HTMLInputElement::ChangeSource::User);
-    m_being_pressed = false;
-}
-
-void CheckBox::handle_associated_label_mousemove(Badge<Label>, bool is_inside_node_or_label)
-{
-    if (m_being_pressed == is_inside_node_or_label || !dom_node().enabled())
-        return;
-
-    m_being_pressed = is_inside_node_or_label;
-    set_needs_display();
-}
-
 RefPtr<Painting::Paintable> CheckBox::create_paintable() const
 RefPtr<Painting::Paintable> CheckBox::create_paintable() const
 {
 {
     return Painting::CheckBoxPaintable::create(*this);
     return Painting::CheckBoxPaintable::create(*this);

+ 1 - 15
Userland/Libraries/LibWeb/Layout/CheckBox.h

@@ -19,22 +19,8 @@ public:
     const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
 
 
-    bool being_pressed() const { return m_being_pressed; }
-
-    virtual RefPtr<Painting::Paintable> create_paintable() const override;
-
 private:
 private:
-    virtual bool wants_mouse_events() const override { return true; }
-    virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
-    virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
-    virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers) override;
-
-    virtual void handle_associated_label_mousedown(Badge<Label>) override;
-    virtual void handle_associated_label_mouseup(Badge<Label>) override;
-    virtual void handle_associated_label_mousemove(Badge<Label>, bool is_inside_node_or_label) override;
-
-    bool m_being_pressed { false };
-    bool m_tracking_mouse { false };
+    virtual RefPtr<Painting::Paintable> create_paintable() const override;
 };
 };
 
 
 }
 }

+ 11 - 13
Userland/Libraries/LibWeb/Layout/Label.cpp

@@ -5,16 +5,14 @@
  */
  */
 
 
 #include <LibGUI/Event.h>
 #include <LibGUI/Event.h>
-#include <LibGfx/Painter.h>
 #include <LibGfx/StylePainter.h>
 #include <LibGfx/StylePainter.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/DOM/Element.h>
 #include <LibWeb/DOM/Element.h>
-#include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/Layout/InitialContainingBlock.h>
 #include <LibWeb/Layout/InitialContainingBlock.h>
 #include <LibWeb/Layout/Label.h>
 #include <LibWeb/Layout/Label.h>
 #include <LibWeb/Layout/LabelableNode.h>
 #include <LibWeb/Layout/LabelableNode.h>
 #include <LibWeb/Layout/TextNode.h>
 #include <LibWeb/Layout/TextNode.h>
-#include <LibWeb/Painting/Paintable.h>
+#include <LibWeb/Painting/LabelablePaintable.h>
 
 
 namespace Web::Layout {
 namespace Web::Layout {
 
 
@@ -27,18 +25,18 @@ Label::~Label()
 {
 {
 }
 }
 
 
-void Label::handle_mousedown_on_label(Badge<TextNode>, const Gfx::IntPoint&, unsigned button)
+void Label::handle_mousedown_on_label(Badge<Painting::TextPaintable>, Gfx::IntPoint const&, unsigned button)
 {
 {
     if (button != GUI::MouseButton::Primary)
     if (button != GUI::MouseButton::Primary)
         return;
         return;
 
 
     if (auto* control = labeled_control(); control)
     if (auto* control = labeled_control(); control)
-        control->handle_associated_label_mousedown({});
+        control->paintable()->handle_associated_label_mousedown({});
 
 
     m_tracking_mouse = true;
     m_tracking_mouse = true;
 }
 }
 
 
-void Label::handle_mouseup_on_label(Badge<TextNode>, const Gfx::IntPoint& position, unsigned button)
+void Label::handle_mouseup_on_label(Badge<Painting::TextPaintable>, Gfx::IntPoint const& position, unsigned button)
 {
 {
     if (!m_tracking_mouse || button != GUI::MouseButton::Primary)
     if (!m_tracking_mouse || button != GUI::MouseButton::Primary)
         return;
         return;
@@ -51,13 +49,13 @@ void Label::handle_mouseup_on_label(Badge<TextNode>, const Gfx::IntPoint& positi
         bool is_inside_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
         bool is_inside_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
 
 
         if (is_inside_control || is_inside_label)
         if (is_inside_control || is_inside_label)
-            control->handle_associated_label_mouseup({});
+            control->paintable()->handle_associated_label_mouseup({});
     }
     }
 
 
     m_tracking_mouse = false;
     m_tracking_mouse = false;
 }
 }
 
 
-void Label::handle_mousemove_on_label(Badge<TextNode>, const Gfx::IntPoint& position, unsigned)
+void Label::handle_mousemove_on_label(Badge<Painting::TextPaintable>, Gfx::IntPoint const& position, unsigned)
 {
 {
     if (!m_tracking_mouse)
     if (!m_tracking_mouse)
         return;
         return;
@@ -66,18 +64,18 @@ void Label::handle_mousemove_on_label(Badge<TextNode>, const Gfx::IntPoint& posi
         bool is_inside_control = enclosing_int_rect(control->paint_box()->absolute_rect()).contains(position);
         bool is_inside_control = enclosing_int_rect(control->paint_box()->absolute_rect()).contains(position);
         bool is_inside_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
         bool is_inside_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
 
 
-        control->handle_associated_label_mousemove({}, is_inside_control || is_inside_label);
+        control->paintable()->handle_associated_label_mousemove({}, is_inside_control || is_inside_label);
     }
     }
 }
 }
 
 
-bool Label::is_inside_associated_label(LabelableNode& control, const Gfx::IntPoint& position)
+bool Label::is_inside_associated_label(LabelableNode const& control, const Gfx::IntPoint& position)
 {
 {
     if (auto* label = label_for_control_node(control); label)
     if (auto* label = label_for_control_node(control); label)
         return enclosing_int_rect(label->paint_box()->absolute_rect()).contains(position);
         return enclosing_int_rect(label->paint_box()->absolute_rect()).contains(position);
     return false;
     return false;
 }
 }
 
 
-bool Label::is_associated_label_hovered(LabelableNode& control)
+bool Label::is_associated_label_hovered(LabelableNode const& control)
 {
 {
     if (auto* label = label_for_control_node(control); label) {
     if (auto* label = label_for_control_node(control); label) {
         if (label->document().hovered_node() == &label->dom_node())
         if (label->document().hovered_node() == &label->dom_node())
@@ -91,7 +89,7 @@ bool Label::is_associated_label_hovered(LabelableNode& control)
 }
 }
 
 
 // https://html.spec.whatwg.org/multipage/forms.html#labeled-control
 // https://html.spec.whatwg.org/multipage/forms.html#labeled-control
-Label* Label::label_for_control_node(LabelableNode& control)
+Label const* Label::label_for_control_node(LabelableNode const& control)
 {
 {
     if (!control.document().layout_node())
     if (!control.document().layout_node())
         return nullptr;
         return nullptr;
@@ -102,7 +100,7 @@ Label* Label::label_for_control_node(LabelableNode& control)
     // whose ID is equal to the value of the for attribute, and the first such element in tree order is
     // whose ID is equal to the value of the for attribute, and the first such element in tree order is
     // a labelable element, then that element is the label element's labeled control.
     // a labelable element, then that element is the label element's labeled control.
     if (auto id = control.dom_node().attribute(HTML::AttributeNames::id); !id.is_empty()) {
     if (auto id = control.dom_node().attribute(HTML::AttributeNames::id); !id.is_empty()) {
-        Label* label = nullptr;
+        Label const* label = nullptr;
 
 
         control.document().layout_node()->for_each_in_inclusive_subtree_of_type<Label>([&](auto& node) {
         control.document().layout_node()->for_each_in_inclusive_subtree_of_type<Label>([&](auto& node) {
             if (node.dom_node().for_() == id) {
             if (node.dom_node().for_() == id) {

+ 6 - 6
Userland/Libraries/LibWeb/Layout/Label.h

@@ -16,20 +16,20 @@ public:
     Label(DOM::Document&, HTML::HTMLLabelElement*, NonnullRefPtr<CSS::StyleProperties>);
     Label(DOM::Document&, HTML::HTMLLabelElement*, NonnullRefPtr<CSS::StyleProperties>);
     virtual ~Label() override;
     virtual ~Label() override;
 
 
-    static bool is_inside_associated_label(LabelableNode&, const Gfx::IntPoint&);
-    static bool is_associated_label_hovered(LabelableNode&);
+    static bool is_inside_associated_label(LabelableNode const&, Gfx::IntPoint const&);
+    static bool is_associated_label_hovered(LabelableNode const&);
 
 
     const HTML::HTMLLabelElement& dom_node() const { return static_cast<const HTML::HTMLLabelElement&>(*BlockContainer::dom_node()); }
     const HTML::HTMLLabelElement& dom_node() const { return static_cast<const HTML::HTMLLabelElement&>(*BlockContainer::dom_node()); }
     HTML::HTMLLabelElement& dom_node() { return static_cast<HTML::HTMLLabelElement&>(*BlockContainer::dom_node()); }
     HTML::HTMLLabelElement& dom_node() { return static_cast<HTML::HTMLLabelElement&>(*BlockContainer::dom_node()); }
 
 
-    void handle_mousedown_on_label(Badge<TextNode>, const Gfx::IntPoint&, unsigned button);
-    void handle_mouseup_on_label(Badge<TextNode>, const Gfx::IntPoint&, unsigned button);
-    void handle_mousemove_on_label(Badge<TextNode>, const Gfx::IntPoint&, unsigned button);
+    void handle_mousedown_on_label(Badge<Painting::TextPaintable>, const Gfx::IntPoint&, unsigned button);
+    void handle_mouseup_on_label(Badge<Painting::TextPaintable>, const Gfx::IntPoint&, unsigned button);
+    void handle_mousemove_on_label(Badge<Painting::TextPaintable>, const Gfx::IntPoint&, unsigned button);
 
 
 private:
 private:
     virtual bool is_label() const override { return true; }
     virtual bool is_label() const override { return true; }
 
 
-    static Label* label_for_control_node(LabelableNode&);
+    static Label const* label_for_control_node(LabelableNode const&);
     LabelableNode* labeled_control();
     LabelableNode* labeled_control();
 
 
     bool m_tracking_mouse { false };
     bool m_tracking_mouse { false };

+ 22 - 0
Userland/Libraries/LibWeb/Layout/LabelableNode.cpp

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/Layout/LabelableNode.h>
+#include <LibWeb/Painting/LabelablePaintable.h>
+
+namespace Web::Layout {
+
+Painting::LabelablePaintable* LabelableNode::paintable()
+{
+    return static_cast<Painting::LabelablePaintable*>(ReplacedBox::paintable());
+}
+
+Painting::LabelablePaintable const* LabelableNode::paintable() const
+{
+    return static_cast<Painting::LabelablePaintable const*>(ReplacedBox::paintable());
+}
+
+}

+ 2 - 3
Userland/Libraries/LibWeb/Layout/LabelableNode.h

@@ -13,9 +13,8 @@ namespace Web::Layout {
 
 
 class LabelableNode : public ReplacedBox {
 class LabelableNode : public ReplacedBox {
 public:
 public:
-    virtual void handle_associated_label_mousedown(Badge<Label>) { }
-    virtual void handle_associated_label_mouseup(Badge<Label>) { }
-    virtual void handle_associated_label_mousemove(Badge<Label>, [[maybe_unused]] bool is_inside_node_or_label) { }
+    Painting::LabelablePaintable* paintable();
+    Painting::LabelablePaintable const* paintable() const;
 
 
 protected:
 protected:
     LabelableNode(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style)
     LabelableNode(DOM::Document& document, DOM::Element& element, NonnullRefPtr<CSS::StyleProperties> style)

+ 0 - 26
Userland/Libraries/LibWeb/Layout/Node.cpp

@@ -514,32 +514,6 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& specified_style)
     }
     }
 }
 }
 
 
-void Node::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
-{
-}
-
-void Node::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
-{
-}
-
-void Node::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned)
-{
-}
-
-bool Node::handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned, unsigned, int wheel_delta_x, int wheel_delta_y)
-{
-    if (auto* containing_block = this->containing_block()) {
-        if (!containing_block->is_scrollable())
-            return false;
-        auto new_offset = containing_block->scroll_offset();
-        new_offset.translate_by(wheel_delta_x, wheel_delta_y);
-        containing_block->set_scroll_offset(new_offset);
-        return true;
-    }
-
-    return false;
-}
-
 bool Node::is_root_element() const
 bool Node::is_root_element() const
 {
 {
     if (is_anonymous())
     if (is_anonymous())

+ 0 - 7
Userland/Libraries/LibWeb/Layout/Node.h

@@ -83,13 +83,6 @@ public:
 
 
     bool is_inline_block() const;
     bool is_inline_block() const;
 
 
-    virtual bool wants_mouse_events() const { return false; }
-
-    virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
-    virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
-    virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers);
-    virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y);
-
     virtual void paint_fragment(PaintContext&, const LineBoxFragment&, Painting::PaintPhase) const { }
     virtual void paint_fragment(PaintContext&, const LineBoxFragment&, Painting::PaintPhase) const { }
 
 
     // These are used to optimize hot is<T> variants for some classes where dynamic_cast is too slow.
     // These are used to optimize hot is<T> variants for some classes where dynamic_cast is too slow.

+ 0 - 87
Userland/Libraries/LibWeb/Layout/RadioButton.cpp

@@ -27,93 +27,6 @@ RadioButton::~RadioButton()
 {
 {
 }
 }
 
 
-void RadioButton::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
-{
-    if (button != GUI::MouseButton::Primary || !dom_node().enabled())
-        return;
-
-    m_being_pressed = true;
-    set_needs_display();
-
-    m_tracking_mouse = true;
-    browsing_context().event_handler().set_mouse_event_tracking_layout_node(this);
-}
-
-void RadioButton::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
-{
-    if (!m_tracking_mouse || button != GUI::MouseButton::Primary || !dom_node().enabled())
-        return;
-
-    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
-    NonnullRefPtr protect = *this;
-
-    bool is_inside_node_or_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
-    if (!is_inside_node_or_label)
-        is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
-
-    if (is_inside_node_or_label)
-        set_checked_within_group();
-
-    m_being_pressed = false;
-    m_tracking_mouse = false;
-    browsing_context().event_handler().set_mouse_event_tracking_layout_node(nullptr);
-}
-
-void RadioButton::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
-{
-    if (!m_tracking_mouse || !dom_node().enabled())
-        return;
-
-    bool is_inside_node_or_label = enclosing_int_rect(paint_box()->absolute_rect()).contains(position);
-    if (!is_inside_node_or_label)
-        is_inside_node_or_label = Label::is_inside_associated_label(*this, position);
-
-    if (m_being_pressed == is_inside_node_or_label)
-        return;
-
-    m_being_pressed = is_inside_node_or_label;
-    set_needs_display();
-}
-
-void RadioButton::handle_associated_label_mousedown(Badge<Label>)
-{
-    m_being_pressed = true;
-    set_needs_display();
-}
-
-void RadioButton::handle_associated_label_mouseup(Badge<Label>)
-{
-    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
-    NonnullRefPtr protect = *this;
-
-    set_checked_within_group();
-    m_being_pressed = false;
-}
-
-void RadioButton::handle_associated_label_mousemove(Badge<Label>, bool is_inside_node_or_label)
-{
-    if (m_being_pressed == is_inside_node_or_label)
-        return;
-
-    m_being_pressed = is_inside_node_or_label;
-    set_needs_display();
-}
-
-void RadioButton::set_checked_within_group()
-{
-    if (dom_node().checked())
-        return;
-
-    dom_node().set_checked(true, HTML::HTMLInputElement::ChangeSource::User);
-    String name = dom_node().name();
-
-    document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
-        if (element.checked() && (element.layout_node() != this) && (element.name() == name))
-            element.set_checked(false, HTML::HTMLInputElement::ChangeSource::User);
-        return IterationDecision::Continue;
-    });
-}
-
 RefPtr<Painting::Paintable> RadioButton::create_paintable() const
 RefPtr<Painting::Paintable> RadioButton::create_paintable() const
 {
 {
     return Painting::RadioButtonPaintable::create(*this);
     return Painting::RadioButtonPaintable::create(*this);

+ 1 - 17
Userland/Libraries/LibWeb/Layout/RadioButton.h

@@ -19,24 +19,8 @@ public:
     const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     const HTML::HTMLInputElement& dom_node() const { return static_cast<const HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
     HTML::HTMLInputElement& dom_node() { return static_cast<HTML::HTMLInputElement&>(LabelableNode::dom_node()); }
 
 
-    virtual RefPtr<Painting::Paintable> create_paintable() const override;
-
-    bool being_pressed() const { return m_being_pressed; }
-
 private:
 private:
-    virtual bool wants_mouse_events() const override { return true; }
-    virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
-    virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
-    virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers) override;
-
-    virtual void handle_associated_label_mousedown(Badge<Label>) override;
-    virtual void handle_associated_label_mouseup(Badge<Label>) override;
-    virtual void handle_associated_label_mousemove(Badge<Label>, bool is_inside_node_or_label) override;
-
-    void set_checked_within_group();
-
-    bool m_being_pressed { false };
-    bool m_tracking_mouse { false };
+    virtual RefPtr<Painting::Paintable> create_paintable() const override;
 };
 };
 
 
 }
 }

+ 0 - 35
Userland/Libraries/LibWeb/Layout/TextNode.cpp

@@ -241,41 +241,6 @@ void TextNode::compute_text_for_rendering(bool collapse, bool previous_is_empty_
     m_text_for_rendering = builder.to_string();
     m_text_for_rendering = builder.to_string();
 }
 }
 
 
-bool TextNode::wants_mouse_events() const
-{
-    return first_ancestor_of_type<Label>();
-}
-
-void TextNode::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
-{
-    auto* label = first_ancestor_of_type<Label>();
-    if (!label)
-        return;
-    label->handle_mousedown_on_label({}, position, button);
-    browsing_context().event_handler().set_mouse_event_tracking_layout_node(this);
-}
-
-void TextNode::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
-{
-    auto* label = first_ancestor_of_type<Label>();
-    if (!label)
-        return;
-
-    // NOTE: Changing the state of the DOM node may run arbitrary JS, which could disappear this node.
-    NonnullRefPtr protect = *this;
-
-    label->handle_mouseup_on_label({}, position, button);
-    browsing_context().event_handler().set_mouse_event_tracking_layout_node(nullptr);
-}
-
-void TextNode::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
-{
-    auto* label = first_ancestor_of_type<Label>();
-    if (!label)
-        return;
-    label->handle_mousemove_on_label({}, position, button);
-}
-
 TextNode::ChunkIterator::ChunkIterator(StringView text, LayoutMode layout_mode, bool wrap_lines, bool respect_linebreaks)
 TextNode::ChunkIterator::ChunkIterator(StringView text, LayoutMode layout_mode, bool wrap_lines, bool respect_linebreaks)
     : m_layout_mode(layout_mode)
     : m_layout_mode(layout_mode)
     , m_wrap_lines(wrap_lines)
     , m_wrap_lines(wrap_lines)

+ 0 - 4
Userland/Libraries/LibWeb/Layout/TextNode.h

@@ -56,10 +56,6 @@ public:
 
 
 private:
 private:
     virtual bool is_text_node() const final { return true; }
     virtual bool is_text_node() const final { return true; }
-    virtual bool wants_mouse_events() const override;
-    virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
-    virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
-    virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
     void paint_cursor_if_needed(PaintContext&, const LineBoxFragment&) const;
     void paint_cursor_if_needed(PaintContext&, const LineBoxFragment&) const;
     void paint_text_decoration(Gfx::Painter&, LineBoxFragment const&) const;
     void paint_text_decoration(Gfx::Painter&, LineBoxFragment const&) const;
 
 

+ 14 - 13
Userland/Libraries/LibWeb/Page/EventHandler.cpp

@@ -16,6 +16,7 @@
 #include <LibWeb/Layout/InitialContainingBlock.h>
 #include <LibWeb/Layout/InitialContainingBlock.h>
 #include <LibWeb/Page/EventHandler.h>
 #include <LibWeb/Page/EventHandler.h>
 #include <LibWeb/Page/Page.h>
 #include <LibWeb/Page/Page.h>
+#include <LibWeb/Painting/Paintable.h>
 #include <LibWeb/UIEvents/EventNames.h>
 #include <LibWeb/UIEvents/EventNames.h>
 #include <LibWeb/UIEvents/KeyboardEvent.h>
 #include <LibWeb/UIEvents/KeyboardEvent.h>
 #include <LibWeb/UIEvents/MouseEvent.h>
 #include <LibWeb/UIEvents/MouseEvent.h>
@@ -123,9 +124,10 @@ bool EventHandler::handle_mousewheel(const Gfx::IntPoint& position, unsigned int
     // FIXME: Support wheel events in nested browsing contexts.
     // FIXME: Support wheel events in nested browsing contexts.
 
 
     auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
     auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
-    if (result.layout_node) {
-        if (result.layout_node->handle_mousewheel({}, position, buttons, modifiers, wheel_delta_x, wheel_delta_y))
-            return true;
+    if (result.layout_node
+        && result.layout_node->paintable()
+        && result.layout_node->paintable()->handle_mousewheel({}, position, buttons, modifiers, wheel_delta_x, wheel_delta_y)) {
+        return true;
     }
     }
 
 
     if (auto* page = m_browsing_context.page()) {
     if (auto* page = m_browsing_context.page()) {
@@ -142,7 +144,7 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button
         return false;
         return false;
 
 
     if (m_mouse_event_tracking_layout_node) {
     if (m_mouse_event_tracking_layout_node) {
-        m_mouse_event_tracking_layout_node->handle_mouseup({}, position, button, modifiers);
+        m_mouse_event_tracking_layout_node->paintable()->handle_mouseup({}, position, button, modifiers);
         return true;
         return true;
     }
     }
 
 
@@ -150,8 +152,8 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button
 
 
     auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
     auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
 
 
-    if (result.layout_node && result.layout_node->wants_mouse_events()) {
-        result.layout_node->handle_mouseup({}, position, button, modifiers);
+    if (result.layout_node && result.layout_node->paintable() && result.layout_node->paintable()->wants_mouse_events()) {
+        result.layout_node->paintable()->handle_mouseup({}, position, button, modifiers);
 
 
         // Things may have changed as a consequence of Layout::Node::handle_mouseup(). Hit test again.
         // Things may have changed as a consequence of Layout::Node::handle_mouseup(). Hit test again.
         if (!layout_root())
         if (!layout_root())
@@ -186,7 +188,7 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
         return false;
         return false;
 
 
     if (m_mouse_event_tracking_layout_node) {
     if (m_mouse_event_tracking_layout_node) {
-        m_mouse_event_tracking_layout_node->handle_mousedown({}, position, button, modifiers);
+        m_mouse_event_tracking_layout_node->paintable()->handle_mousedown({}, position, button, modifiers);
         return true;
         return true;
     }
     }
 
 
@@ -207,8 +209,8 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
         node = result.layout_node->dom_node();
         node = result.layout_node->dom_node();
         document->set_hovered_node(node);
         document->set_hovered_node(node);
 
 
-        if (result.layout_node->wants_mouse_events()) {
-            result.layout_node->handle_mousedown({}, position, button, modifiers);
+        if (result.layout_node->paintable() && result.layout_node->paintable()->wants_mouse_events()) {
+            result.layout_node->paintable()->handle_mousedown({}, position, button, modifiers);
             return true;
             return true;
         }
         }
 
 
@@ -304,7 +306,7 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt
         return false;
         return false;
 
 
     if (m_mouse_event_tracking_layout_node) {
     if (m_mouse_event_tracking_layout_node) {
-        m_mouse_event_tracking_layout_node->handle_mousemove({}, position, buttons, modifiers);
+        m_mouse_event_tracking_layout_node->paintable()->handle_mousemove({}, position, buttons, modifiers);
         return true;
         return true;
     }
     }
 
 
@@ -316,10 +318,9 @@ bool EventHandler::handle_mousemove(const Gfx::IntPoint& position, unsigned butt
     auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
     auto result = layout_root()->hit_test(position, Layout::HitTestType::Exact);
     const HTML::HTMLAnchorElement* hovered_link_element = nullptr;
     const HTML::HTMLAnchorElement* hovered_link_element = nullptr;
     if (result.layout_node) {
     if (result.layout_node) {
-
-        if (result.layout_node->wants_mouse_events()) {
+        if (result.layout_node->paintable() && result.layout_node->paintable()->wants_mouse_events()) {
             document.set_hovered_node(result.layout_node->dom_node());
             document.set_hovered_node(result.layout_node->dom_node());
-            result.layout_node->handle_mousemove({}, position, buttons, modifiers);
+            result.layout_node->paintable()->handle_mousemove({}, position, buttons, modifiers);
             // FIXME: It feels a bit aggressive to always update the cursor like this.
             // FIXME: It feels a bit aggressive to always update the cursor like this.
             if (auto* page = m_browsing_context.page())
             if (auto* page = m_browsing_context.page())
                 page->client().page_did_request_cursor_change(Gfx::StandardCursor::None);
                 page->client().page_did_request_cursor_change(Gfx::StandardCursor::None);

+ 85 - 2
Userland/Libraries/LibWeb/Painting/ButtonPaintable.cpp

@@ -4,8 +4,11 @@
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
 
 
+#include <LibGUI/Event.h>
+#include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/HTMLImageElement.h>
 #include <LibWeb/HTML/HTMLImageElement.h>
 #include <LibWeb/Layout/ButtonBox.h>
 #include <LibWeb/Layout/ButtonBox.h>
+#include <LibWeb/Layout/Label.h>
 #include <LibWeb/Painting/ButtonPaintable.h>
 #include <LibWeb/Painting/ButtonPaintable.h>
 
 
 namespace Web::Painting {
 namespace Web::Painting {
@@ -16,7 +19,7 @@ NonnullRefPtr<ButtonPaintable> ButtonPaintable::create(Layout::ButtonBox const&
 }
 }
 
 
 ButtonPaintable::ButtonPaintable(Layout::ButtonBox const& layout_box)
 ButtonPaintable::ButtonPaintable(Layout::ButtonBox const& layout_box)
-    : PaintableBox(layout_box)
+    : LabelablePaintable(layout_box)
 {
 {
 }
 }
 
 
@@ -25,6 +28,11 @@ Layout::ButtonBox const& ButtonPaintable::layout_box() const
     return static_cast<Layout::ButtonBox const&>(layout_node());
     return static_cast<Layout::ButtonBox const&>(layout_node());
 }
 }
 
 
+Layout::ButtonBox& ButtonPaintable::layout_box()
+{
+    return static_cast<Layout::ButtonBox&>(layout_node());
+}
+
 void ButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
 void ButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
 {
 {
     if (!is_visible())
     if (!is_visible())
@@ -34,10 +42,85 @@ void ButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
 
 
     if (phase == PaintPhase::Foreground) {
     if (phase == PaintPhase::Foreground) {
         auto text_rect = enclosing_int_rect(absolute_rect());
         auto text_rect = enclosing_int_rect(absolute_rect());
-        if (layout_box().being_pressed())
+        if (m_being_pressed)
             text_rect.translate_by(1, 1);
             text_rect.translate_by(1, 1);
         context.painter().draw_text(text_rect, layout_box().dom_node().value(), layout_box().font(), Gfx::TextAlignment::Center, computed_values().color());
         context.painter().draw_text(text_rect, layout_box().dom_node().value(), layout_box().font(), Gfx::TextAlignment::Center, computed_values().color());
     }
     }
 }
 }
 
 
+void ButtonPaintable::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
+{
+    if (button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
+        return;
+
+    m_being_pressed = true;
+    set_needs_display();
+
+    m_tracking_mouse = true;
+    browsing_context().event_handler().set_mouse_event_tracking_layout_node(&layout_box());
+}
+
+void ButtonPaintable::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
+{
+    if (!m_tracking_mouse || button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
+        return;
+
+    // NOTE: Handling the click may run arbitrary JS, which could disappear this node.
+    NonnullRefPtr protected_this = *this;
+    NonnullRefPtr protected_browsing_context = browsing_context();
+
+    bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
+    if (!is_inside_node_or_label)
+        is_inside_node_or_label = Layout::Label::is_inside_associated_label(layout_box(), position);
+
+    if (is_inside_node_or_label)
+        const_cast<Layout::ButtonBox&>(layout_box()).dom_node().did_click_button({});
+
+    m_being_pressed = false;
+    m_tracking_mouse = false;
+
+    protected_browsing_context->event_handler().set_mouse_event_tracking_layout_node(nullptr);
+}
+
+void ButtonPaintable::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
+{
+    if (!m_tracking_mouse || !layout_box().dom_node().enabled())
+        return;
+
+    bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
+    if (!is_inside_node_or_label)
+        is_inside_node_or_label = Layout::Label::is_inside_associated_label(layout_box(), position);
+
+    if (m_being_pressed == is_inside_node_or_label)
+        return;
+
+    m_being_pressed = is_inside_node_or_label;
+    set_needs_display();
+}
+
+void ButtonPaintable::handle_associated_label_mousedown(Badge<Layout::Label>)
+{
+    m_being_pressed = true;
+    set_needs_display();
+}
+
+void ButtonPaintable::handle_associated_label_mouseup(Badge<Layout::Label>)
+{
+    // NOTE: Handling the click may run arbitrary JS, which could disappear this node.
+    NonnullRefPtr protected_this = *this;
+    NonnullRefPtr protected_browsing_context = browsing_context();
+
+    layout_box().dom_node().did_click_button({});
+    m_being_pressed = false;
+}
+
+void ButtonPaintable::handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label)
+{
+    if (m_being_pressed == is_inside_node_or_label)
+        return;
+
+    m_being_pressed = is_inside_node_or_label;
+    set_needs_display();
+}
+
 }
 }

+ 15 - 2
Userland/Libraries/LibWeb/Painting/ButtonPaintable.h

@@ -7,20 +7,33 @@
 #pragma once
 #pragma once
 
 
 #include <LibWeb/Layout/ButtonBox.h>
 #include <LibWeb/Layout/ButtonBox.h>
-#include <LibWeb/Painting/Paintable.h>
+#include <LibWeb/Painting/LabelablePaintable.h>
 
 
 namespace Web::Painting {
 namespace Web::Painting {
 
 
-class ButtonPaintable final : public PaintableBox {
+class ButtonPaintable final : public LabelablePaintable {
 public:
 public:
     static NonnullRefPtr<ButtonPaintable> create(Layout::ButtonBox const&);
     static NonnullRefPtr<ButtonPaintable> create(Layout::ButtonBox const&);
 
 
     virtual void paint(PaintContext&, PaintPhase) const override;
     virtual void paint(PaintContext&, PaintPhase) const override;
 
 
     Layout::ButtonBox const& layout_box() const;
     Layout::ButtonBox const& layout_box() const;
+    Layout::ButtonBox& layout_box();
+
+    virtual bool wants_mouse_events() const override { return true; }
+    virtual void handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
+    virtual void handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
+    virtual void handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned buttons, unsigned modifiers) override;
 
 
 private:
 private:
     ButtonPaintable(Layout::ButtonBox const&);
     ButtonPaintable(Layout::ButtonBox const&);
+
+    virtual void handle_associated_label_mousedown(Badge<Layout::Label>) override;
+    virtual void handle_associated_label_mouseup(Badge<Layout::Label>) override;
+    virtual void handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label) override;
+
+    bool m_being_pressed { false };
+    bool m_tracking_mouse { false };
 };
 };
 
 
 }
 }

+ 91 - 2
Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp

@@ -4,9 +4,12 @@
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
 
 
+#include <LibGUI/Event.h>
 #include <LibGfx/StylePainter.h>
 #include <LibGfx/StylePainter.h>
+#include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/HTMLImageElement.h>
 #include <LibWeb/HTML/HTMLImageElement.h>
 #include <LibWeb/Layout/CheckBox.h>
 #include <LibWeb/Layout/CheckBox.h>
+#include <LibWeb/Layout/Label.h>
 #include <LibWeb/Painting/CheckBoxPaintable.h>
 #include <LibWeb/Painting/CheckBoxPaintable.h>
 
 
 namespace Web::Painting {
 namespace Web::Painting {
@@ -17,7 +20,7 @@ NonnullRefPtr<CheckBoxPaintable> CheckBoxPaintable::create(Layout::CheckBox cons
 }
 }
 
 
 CheckBoxPaintable::CheckBoxPaintable(Layout::CheckBox const& layout_box)
 CheckBoxPaintable::CheckBoxPaintable(Layout::CheckBox const& layout_box)
-    : PaintableBox(layout_box)
+    : LabelablePaintable(layout_box)
 {
 {
 }
 }
 
 
@@ -26,6 +29,11 @@ Layout::CheckBox const& CheckBoxPaintable::layout_box() const
     return static_cast<Layout::CheckBox const&>(layout_node());
     return static_cast<Layout::CheckBox const&>(layout_node());
 }
 }
 
 
+Layout::CheckBox& CheckBoxPaintable::layout_box()
+{
+    return static_cast<Layout::CheckBox&>(layout_node());
+}
+
 void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const
 void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const
 {
 {
     if (!is_visible())
     if (!is_visible())
@@ -34,7 +42,88 @@ void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const
     PaintableBox::paint(context, phase);
     PaintableBox::paint(context, phase);
 
 
     if (phase == PaintPhase::Foreground)
     if (phase == PaintPhase::Foreground)
-        Gfx::StylePainter::paint_check_box(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().enabled(), layout_box().dom_node().checked(), layout_box().being_pressed());
+        Gfx::StylePainter::paint_check_box(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().enabled(), layout_box().dom_node().checked(), m_being_pressed);
+}
+
+void CheckBoxPaintable::handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned)
+{
+    if (button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
+        return;
+
+    m_being_pressed = true;
+    set_needs_display();
+
+    m_tracking_mouse = true;
+    browsing_context().event_handler().set_mouse_event_tracking_layout_node(&layout_box());
+}
+
+void CheckBoxPaintable::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
+{
+    if (!m_tracking_mouse || button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
+        return;
+
+    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
+    NonnullRefPtr protect = *this;
+
+    bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
+    if (!is_inside_node_or_label)
+        is_inside_node_or_label = Layout::Label::is_inside_associated_label(layout_box(), position);
+
+    if (is_inside_node_or_label) {
+        layout_box().dom_node().did_click_checkbox({});
+        layout_box().dom_node().set_checked(!layout_box().dom_node().checked(), HTML::HTMLInputElement::ChangeSource::User);
+    }
+
+    m_being_pressed = false;
+    m_tracking_mouse = false;
+    browsing_context().event_handler().set_mouse_event_tracking_layout_node(nullptr);
+}
+
+void CheckBoxPaintable::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
+{
+    if (!m_tracking_mouse || !layout_box().dom_node().enabled())
+        return;
+
+    bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
+    if (!is_inside_node_or_label)
+        is_inside_node_or_label = Layout::Label::is_inside_associated_label(layout_box(), position);
+
+    if (m_being_pressed == is_inside_node_or_label)
+        return;
+
+    m_being_pressed = is_inside_node_or_label;
+    set_needs_display();
+}
+
+void CheckBoxPaintable::handle_associated_label_mousedown(Badge<Layout::Label>)
+{
+    if (!layout_box().dom_node().enabled())
+        return;
+
+    m_being_pressed = true;
+    set_needs_display();
+}
+
+void CheckBoxPaintable::handle_associated_label_mouseup(Badge<Layout::Label>)
+{
+    if (!layout_box().dom_node().enabled())
+        return;
+
+    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
+    NonnullRefPtr protect = *this;
+
+    layout_box().dom_node().did_click_checkbox({});
+    layout_box().dom_node().set_checked(!layout_box().dom_node().checked(), HTML::HTMLInputElement::ChangeSource::User);
+    m_being_pressed = false;
+}
+
+void CheckBoxPaintable::handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label)
+{
+    if (m_being_pressed == is_inside_node_or_label || !layout_box().dom_node().enabled())
+        return;
+
+    m_being_pressed = is_inside_node_or_label;
+    set_needs_display();
 }
 }
 
 
 }
 }

+ 15 - 2
Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h

@@ -7,20 +7,33 @@
 #pragma once
 #pragma once
 
 
 #include <LibWeb/Layout/CheckBox.h>
 #include <LibWeb/Layout/CheckBox.h>
-#include <LibWeb/Painting/Paintable.h>
+#include <LibWeb/Painting/LabelablePaintable.h>
 
 
 namespace Web::Painting {
 namespace Web::Painting {
 
 
-class CheckBoxPaintable final : public PaintableBox {
+class CheckBoxPaintable final : public LabelablePaintable {
 public:
 public:
     static NonnullRefPtr<CheckBoxPaintable> create(Layout::CheckBox const&);
     static NonnullRefPtr<CheckBoxPaintable> create(Layout::CheckBox const&);
 
 
     virtual void paint(PaintContext&, PaintPhase) const override;
     virtual void paint(PaintContext&, PaintPhase) const override;
 
 
     Layout::CheckBox const& layout_box() const;
     Layout::CheckBox const& layout_box() const;
+    Layout::CheckBox& layout_box();
+
+    virtual bool wants_mouse_events() const override { return true; }
+    virtual void handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
+    virtual void handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
+    virtual void handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned buttons, unsigned modifiers) override;
 
 
 private:
 private:
     CheckBoxPaintable(Layout::CheckBox const&);
     CheckBoxPaintable(Layout::CheckBox const&);
+
+    virtual void handle_associated_label_mousedown(Badge<Layout::Label>) override;
+    virtual void handle_associated_label_mouseup(Badge<Layout::Label>) override;
+    virtual void handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label) override;
+
+    bool m_being_pressed { false };
+    bool m_tracking_mouse { false };
 };
 };
 
 
 }
 }

+ 26 - 0
Userland/Libraries/LibWeb/Painting/LabelablePaintable.cpp

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/Painting/LabelablePaintable.h>
+
+namespace Web::Painting {
+
+Layout::LabelableNode const& LabelablePaintable::layout_box() const
+{
+    return static_cast<Layout::LabelableNode const&>(PaintableBox::layout_box());
+}
+
+Layout::LabelableNode& LabelablePaintable::layout_box()
+{
+    return static_cast<Layout::LabelableNode&>(PaintableBox::layout_box());
+}
+
+LabelablePaintable::LabelablePaintable(Layout::LabelableNode const& layout_node)
+    : PaintableBox(layout_node)
+{
+}
+
+}

+ 27 - 0
Userland/Libraries/LibWeb/Painting/LabelablePaintable.h

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/Layout/LabelableNode.h>
+#include <LibWeb/Painting/Paintable.h>
+
+namespace Web::Painting {
+
+class LabelablePaintable : public PaintableBox {
+public:
+    Layout::LabelableNode const& layout_box() const;
+    Layout::LabelableNode& layout_box();
+
+    virtual void handle_associated_label_mousedown(Badge<Layout::Label>) { }
+    virtual void handle_associated_label_mouseup(Badge<Layout::Label>) { }
+    virtual void handle_associated_label_mousemove(Badge<Layout::Label>, [[maybe_unused]] bool is_inside_node_or_label) { }
+
+protected:
+    LabelablePaintable(Layout::LabelableNode const&);
+};
+
+}

+ 48 - 0
Userland/Libraries/LibWeb/Painting/Paintable.cpp

@@ -287,4 +287,52 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
     }
     }
 }
 }
 
 
+void Paintable::handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
+{
+}
+
+void Paintable::handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
+{
+}
+
+void Paintable::handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned)
+{
+}
+
+bool Paintable::handle_mousewheel(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned, int wheel_delta_x, int wheel_delta_y)
+{
+    if (auto* containing_block = layout_node().containing_block()) {
+        if (!containing_block->is_scrollable())
+            return false;
+        auto new_offset = containing_block->scroll_offset();
+        new_offset.translate_by(wheel_delta_x, wheel_delta_y);
+        // FIXME: This const_cast is gross.
+        // FIXME: Scroll offset shouldn't live in the layout tree.
+        const_cast<Layout::BlockContainer*>(containing_block)->set_scroll_offset(new_offset);
+        return true;
+    }
+
+    return false;
+}
+
+bool PaintableWithLines::handle_mousewheel(Badge<EventHandler>, Gfx::IntPoint const&, unsigned, unsigned, int wheel_delta_x, int wheel_delta_y)
+{
+    if (!layout_box().is_scrollable())
+        return false;
+    auto new_offset = layout_box().scroll_offset();
+    new_offset.translate_by(wheel_delta_x, wheel_delta_y);
+    const_cast<Layout::BlockContainer&>(layout_box()).set_scroll_offset(new_offset);
+    return true;
+}
+
+Layout::BlockContainer const& PaintableWithLines::layout_box() const
+{
+    return static_cast<Layout::BlockContainer const&>(PaintableBox::layout_box());
+}
+
+Layout::BlockContainer& PaintableWithLines::layout_box()
+{
+    return static_cast<Layout::BlockContainer&>(PaintableBox::layout_box());
+}
+
 }
 }

+ 22 - 0
Userland/Libraries/LibWeb/Painting/Paintable.h

@@ -25,9 +25,22 @@ public:
     virtual void before_children_paint(PaintContext&, PaintPhase) const { }
     virtual void before_children_paint(PaintContext&, PaintPhase) const { }
     virtual void after_children_paint(PaintContext&, PaintPhase) const { }
     virtual void after_children_paint(PaintContext&, PaintPhase) const { }
 
 
+    virtual bool wants_mouse_events() const { return false; }
+    virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
+    virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers);
+    virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers);
+    virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y);
+
     Layout::Node const& layout_node() const { return m_layout_node; }
     Layout::Node const& layout_node() const { return m_layout_node; }
+    Layout::Node& layout_node() { return const_cast<Layout::Node&>(m_layout_node); }
+
     auto const& computed_values() const { return m_layout_node.computed_values(); }
     auto const& computed_values() const { return m_layout_node.computed_values(); }
 
 
+    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(); }
+
 protected:
 protected:
     explicit Paintable(Layout::Node const& layout_node)
     explicit Paintable(Layout::Node const& layout_node)
         : m_layout_node(layout_node)
         : m_layout_node(layout_node)
@@ -47,6 +60,7 @@ public:
 
 
     bool is_visible() const { return layout_box().is_visible(); }
     bool is_visible() const { return layout_box().is_visible(); }
 
 
+    Layout::Box& layout_box() { return static_cast<Layout::Box&>(Paintable::layout_node()); }
     Layout::Box const& layout_box() const { return static_cast<Layout::Box const&>(Paintable::layout_node()); }
     Layout::Box const& layout_box() const { return static_cast<Layout::Box const&>(Paintable::layout_node()); }
 
 
     auto const& box_model() const { return layout_box().box_model(); }
     auto const& box_model() const { return layout_box().box_model(); }
@@ -134,7 +148,10 @@ public:
     StackingContext* enclosing_stacking_context();
     StackingContext* enclosing_stacking_context();
 
 
     DOM::Node const* dom_node() const { return layout_box().dom_node(); }
     DOM::Node const* dom_node() const { return layout_box().dom_node(); }
+    DOM::Node* dom_node() { return layout_box().dom_node(); }
+
     DOM::Document const& document() const { return layout_box().document(); }
     DOM::Document const& document() const { return layout_box().document(); }
+    DOM::Document& document() { return layout_box().document(); }
 
 
     virtual void before_children_paint(PaintContext&, PaintPhase) const override;
     virtual void before_children_paint(PaintContext&, PaintPhase) const override;
     virtual void after_children_paint(PaintContext&, PaintPhase) const override;
     virtual void after_children_paint(PaintContext&, PaintPhase) const override;
@@ -160,6 +177,9 @@ public:
     }
     }
     virtual ~PaintableWithLines() override;
     virtual ~PaintableWithLines() override;
 
 
+    Layout::BlockContainer const& layout_box() const;
+    Layout::BlockContainer& layout_box();
+
     Vector<Layout::LineBox> const& line_boxes() const { return m_line_boxes; }
     Vector<Layout::LineBox> const& line_boxes() const { return m_line_boxes; }
     void set_line_boxes(Vector<Layout::LineBox>&& line_boxes) { m_line_boxes = move(line_boxes); }
     void set_line_boxes(Vector<Layout::LineBox>&& line_boxes) { m_line_boxes = move(line_boxes); }
 
 
@@ -175,6 +195,8 @@ public:
     }
     }
 
 
     virtual void paint(PaintContext&, PaintPhase) const override;
     virtual void paint(PaintContext&, PaintPhase) const override;
+    virtual bool wants_mouse_events() const override { return false; }
+    virtual bool handle_mousewheel(Badge<EventHandler>, const Gfx::IntPoint&, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override;
 
 
 private:
 private:
     PaintableWithLines(Layout::BlockContainer const&);
     PaintableWithLines(Layout::BlockContainer const&);

+ 98 - 2
Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp

@@ -4,8 +4,12 @@
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
 
 
+#include <LibGUI/Event.h>
 #include <LibGfx/StylePainter.h>
 #include <LibGfx/StylePainter.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/HTML/BrowsingContext.h>
 #include <LibWeb/HTML/HTMLImageElement.h>
 #include <LibWeb/HTML/HTMLImageElement.h>
+#include <LibWeb/Layout/Label.h>
 #include <LibWeb/Layout/RadioButton.h>
 #include <LibWeb/Layout/RadioButton.h>
 #include <LibWeb/Painting/RadioButtonPaintable.h>
 #include <LibWeb/Painting/RadioButtonPaintable.h>
 
 
@@ -17,7 +21,7 @@ NonnullRefPtr<RadioButtonPaintable> RadioButtonPaintable::create(Layout::RadioBu
 }
 }
 
 
 RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box)
 RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box)
-    : PaintableBox(layout_box)
+    : LabelablePaintable(layout_box)
 {
 {
 }
 }
 
 
@@ -26,6 +30,11 @@ Layout::RadioButton const& RadioButtonPaintable::layout_box() const
     return static_cast<Layout::RadioButton const&>(layout_node());
     return static_cast<Layout::RadioButton const&>(layout_node());
 }
 }
 
 
+Layout::RadioButton& RadioButtonPaintable::layout_box()
+{
+    return static_cast<Layout::RadioButton&>(layout_node());
+}
+
 void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
 void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
 {
 {
     if (!is_visible())
     if (!is_visible())
@@ -34,7 +43,94 @@ void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
     PaintableBox::paint(context, phase);
     PaintableBox::paint(context, phase);
 
 
     if (phase == PaintPhase::Foreground)
     if (phase == PaintPhase::Foreground)
-        Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().checked(), layout_box().being_pressed());
+        Gfx::StylePainter::paint_radio_button(context.painter(), enclosing_int_rect(absolute_rect()), context.palette(), layout_box().dom_node().checked(), m_being_pressed);
+}
+
+void RadioButtonPaintable::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned)
+{
+    if (button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
+        return;
+
+    m_being_pressed = true;
+    set_needs_display();
+
+    m_tracking_mouse = true;
+    browsing_context().event_handler().set_mouse_event_tracking_layout_node(&layout_box());
+}
+
+void RadioButtonPaintable::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
+{
+    if (!m_tracking_mouse || button != GUI::MouseButton::Primary || !layout_box().dom_node().enabled())
+        return;
+
+    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
+    NonnullRefPtr protect = *this;
+
+    bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
+    if (!is_inside_node_or_label)
+        is_inside_node_or_label = Layout::Label::is_inside_associated_label(layout_box(), position);
+
+    if (is_inside_node_or_label)
+        set_checked_within_group();
+
+    m_being_pressed = false;
+    m_tracking_mouse = false;
+    browsing_context().event_handler().set_mouse_event_tracking_layout_node(nullptr);
+}
+
+void RadioButtonPaintable::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned, unsigned)
+{
+    if (!m_tracking_mouse || !layout_box().dom_node().enabled())
+        return;
+
+    bool is_inside_node_or_label = enclosing_int_rect(absolute_rect()).contains(position);
+    if (!is_inside_node_or_label)
+        is_inside_node_or_label = Layout::Label::is_inside_associated_label(layout_box(), position);
+
+    if (m_being_pressed == is_inside_node_or_label)
+        return;
+
+    m_being_pressed = is_inside_node_or_label;
+    set_needs_display();
+}
+
+void RadioButtonPaintable::handle_associated_label_mousedown(Badge<Layout::Label>)
+{
+    m_being_pressed = true;
+    set_needs_display();
+}
+
+void RadioButtonPaintable::handle_associated_label_mouseup(Badge<Layout::Label>)
+{
+    // NOTE: Changing the checked state of the DOM node may run arbitrary JS, which could disappear this node.
+    NonnullRefPtr protect = *this;
+
+    set_checked_within_group();
+    m_being_pressed = false;
+}
+
+void RadioButtonPaintable::handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label)
+{
+    if (m_being_pressed == is_inside_node_or_label)
+        return;
+
+    m_being_pressed = is_inside_node_or_label;
+    set_needs_display();
+}
+
+void RadioButtonPaintable::set_checked_within_group()
+{
+    if (layout_box().dom_node().checked())
+        return;
+
+    layout_box().dom_node().set_checked(true, HTML::HTMLInputElement::ChangeSource::User);
+    String name = layout_box().dom_node().name();
+
+    document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
+        if (element.checked() && (element.paintable() != this) && (element.name() == name))
+            element.set_checked(false, HTML::HTMLInputElement::ChangeSource::User);
+        return IterationDecision::Continue;
+    });
 }
 }
 
 
 }
 }

+ 17 - 2
Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.h

@@ -7,20 +7,35 @@
 #pragma once
 #pragma once
 
 
 #include <LibWeb/Layout/RadioButton.h>
 #include <LibWeb/Layout/RadioButton.h>
-#include <LibWeb/Painting/Paintable.h>
+#include <LibWeb/Painting/LabelablePaintable.h>
 
 
 namespace Web::Painting {
 namespace Web::Painting {
 
 
-class RadioButtonPaintable final : public PaintableBox {
+class RadioButtonPaintable final : public LabelablePaintable {
 public:
 public:
     static NonnullRefPtr<RadioButtonPaintable> create(Layout::RadioButton const&);
     static NonnullRefPtr<RadioButtonPaintable> create(Layout::RadioButton const&);
 
 
     virtual void paint(PaintContext&, PaintPhase) const override;
     virtual void paint(PaintContext&, PaintPhase) const override;
 
 
     Layout::RadioButton const& layout_box() const;
     Layout::RadioButton const& layout_box() const;
+    Layout::RadioButton& layout_box();
+
+    virtual bool wants_mouse_events() const override { return true; }
+    virtual void handle_mousedown(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
+    virtual void handle_mouseup(Badge<EventHandler>, Gfx::IntPoint const&, unsigned button, unsigned modifiers) override;
+    virtual void handle_mousemove(Badge<EventHandler>, Gfx::IntPoint const&, unsigned buttons, unsigned modifiers) override;
 
 
 private:
 private:
     RadioButtonPaintable(Layout::RadioButton const&);
     RadioButtonPaintable(Layout::RadioButton const&);
+
+    virtual void handle_associated_label_mousedown(Badge<Layout::Label>) override;
+    virtual void handle_associated_label_mouseup(Badge<Layout::Label>) override;
+    virtual void handle_associated_label_mousemove(Badge<Layout::Label>, bool is_inside_node_or_label) override;
+
+    void set_checked_within_group();
+
+    bool m_being_pressed { false };
+    bool m_tracking_mouse { false };
 };
 };
 
 
 }
 }

+ 35 - 0
Userland/Libraries/LibWeb/Painting/TextPaintable.cpp

@@ -21,4 +21,39 @@ TextPaintable::TextPaintable(Layout::TextNode const& layout_node)
 {
 {
 }
 }
 
 
+bool TextPaintable::wants_mouse_events() const
+{
+    return layout_node().first_ancestor_of_type<Layout::Label>();
+}
+
+void TextPaintable::handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
+{
+    auto* label = layout_node().first_ancestor_of_type<Layout::Label>();
+    if (!label)
+        return;
+    const_cast<Layout::Label*>(label)->handle_mousedown_on_label({}, position, button);
+    const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_layout_node(&const_cast<Layout::TextNode&>(layout_node()));
+}
+
+void TextPaintable::handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
+{
+    auto* label = layout_node().first_ancestor_of_type<Layout::Label>();
+    if (!label)
+        return;
+
+    // NOTE: Changing the state of the DOM node may run arbitrary JS, which could disappear this node.
+    NonnullRefPtr protect = *this;
+
+    const_cast<Layout::Label*>(label)->handle_mouseup_on_label({}, position, button);
+    const_cast<HTML::BrowsingContext&>(browsing_context()).event_handler().set_mouse_event_tracking_layout_node(nullptr);
+}
+
+void TextPaintable::handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint& position, unsigned button, unsigned)
+{
+    auto* label = layout_node().first_ancestor_of_type<Layout::Label>();
+    if (!label)
+        return;
+    const_cast<Layout::Label*>(label)->handle_mousemove_on_label({}, position, button);
+}
+
 }
 }

+ 5 - 0
Userland/Libraries/LibWeb/Painting/TextPaintable.h

@@ -16,6 +16,11 @@ public:
 
 
     Layout::TextNode const& layout_node() const { return static_cast<Layout::TextNode const&>(Paintable::layout_node()); }
     Layout::TextNode const& layout_node() const { return static_cast<Layout::TextNode const&>(Paintable::layout_node()); }
 
 
+    virtual bool wants_mouse_events() const override;
+    virtual void handle_mousedown(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
+    virtual void handle_mouseup(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
+    virtual void handle_mousemove(Badge<EventHandler>, const Gfx::IntPoint&, unsigned button, unsigned modifiers) override;
+
 private:
 private:
     explicit TextPaintable(Layout::TextNode const&);
     explicit TextPaintable(Layout::TextNode const&);
 };
 };