LibWeb: Ensure EventHandler visits its mouse selection target

We hold a raw pointer to the mouse selection target, which is a mixin-
style class inherited only by JS::Cell classes. By not visiting this
object, we sometime had a dangling reference to it after it had been
garbage collected.
This commit is contained in:
Timothy Flynn 2025-02-26 20:40:10 -05:00 committed by Sam Atkins
parent 18a160e0e9
commit d5be18617e
Notes: github-actions[bot] 2025-02-27 09:54:17 +00:00
7 changed files with 56 additions and 2 deletions

View file

@ -14,7 +14,8 @@
namespace Web::DOM {
class EditingHostManager : public JS::Cell
class EditingHostManager
: public JS::Cell
, public InputEventsTarget {
GC_CELL(EditingHostManager, JS::Cell);
GC_DECLARE_ALLOCATOR(EditingHostManager);
@ -45,6 +46,8 @@ public:
private:
EditingHostManager(GC::Ref<Document>);
virtual GC::Ref<JS::Cell> as_cell() override { return *this; }
GC::Ref<Document> m_document;
GC::Ptr<DOM::Node> m_active_contenteditable_element;
};

View file

@ -15,6 +15,8 @@ class InputEventsTarget {
public:
virtual ~InputEventsTarget() = default;
virtual GC::Ref<JS::Cell> as_cell() = 0;
virtual void handle_insert(String const&) = 0;
virtual void handle_return_key() = 0;

View file

@ -969,4 +969,9 @@ GC::Ptr<DOM::Position> FormAssociatedTextControlElement::cursor_position() const
return nullptr;
}
GC::Ref<JS::Cell> FormAssociatedTextControlElement::as_cell()
{
return form_associated_element_to_html_element();
}
}

View file

@ -160,7 +160,8 @@ enum class SelectionSource {
DOM,
};
class FormAssociatedTextControlElement : public FormAssociatedElement
class FormAssociatedTextControlElement
: public FormAssociatedElement
, public InputEventsTarget {
public:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
@ -231,6 +232,8 @@ protected:
void relevant_value_was_changed();
private:
virtual GC::Ref<JS::Cell> as_cell() override;
void collapse_selection_to_offset(size_t);
void selection_was_changed();

View file

@ -1375,6 +1375,9 @@ void EventHandler::visit_edges(JS::Cell::Visitor& visitor) const
{
m_drag_and_drop_event_handler->visit_edges(visitor);
visitor.visit(m_mouse_event_tracking_paintable);
if (m_mouse_selection_target)
visitor.visit(m_mouse_selection_target->as_cell());
}
Unicode::Segmenter& EventHandler::word_segmenter()

View file

@ -0,0 +1 @@
PASS (didn't crash)

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<iframe id="iframe"></iframe>
<script src="../include.js"></script>
<script>
const runTest = () => {
return new Promise(resolve => {
let iframe = document.getElementById("iframe");
iframe.onload = () => {
internals.movePointerTo(20, 40);
internals.mouseDown(20, 40);
internals.movePointerTo(60, 40);
iframe.onload = () => {
setTimeout(() => {
internals.movePointerTo(20, 40);
resolve();
});
};
iframe.src = "data:text/html,<p contenteditable>Text 2</p>";
};
iframe.src = "data:text/html,<p contenteditable>Text 1</p>";
});
};
asyncTest(async done => {
for (let i = 0; i < 10; ++i) {
await runTest();
internals.gc();
}
println("PASS (didn't crash)");
done();
});
</script>