LibWeb: Remove shadow roots from elements that are removed from the DOM

We currently create a shadow tree once for each DOM element that renders
with a shadow tree (e.g. <input>, <details>). If such an element is
removed from the DOM, we must remove its shadow tree. Otherwise, the
shadow tree will refer to the old document in perpetuity.

If the node is added back to a DOM, then recreate the shadow tree.
This commit is contained in:
Timothy Flynn 2023-11-29 18:22:16 -05:00 committed by Sam Atkins
parent 3cde479416
commit ff48b7333c
Notes: sideshowbarker 2024-07-16 18:26:46 +09:00
12 changed files with 67 additions and 3 deletions

View file

@ -0,0 +1 @@
<details><summary></summary></details>

View file

@ -0,0 +1 @@
<input>

View file

@ -0,0 +1 @@
<textarea></textarea>

View file

@ -0,0 +1,9 @@
<div id=test></div>
<script src="../include.js"></script>
<script>
test(() => {
let element = document.getElementById('test');
element.innerHTML = "<details><summary></summary></details>";
println(element.innerHTML);
});
</script>

View file

@ -0,0 +1,9 @@
<div id=test></div>
<script src="../include.js"></script>
<script>
test(() => {
let element = document.getElementById('test');
element.innerHTML = "<input>";
println(element.innerHTML);
});
</script>

View file

@ -0,0 +1,9 @@
<div id=test></div>
<script src="../include.js"></script>
<script>
test(() => {
let element = document.getElementById('test');
element.innerHTML = "<textarea></textarea>";
println(element.innerHTML);
});
</script>

View file

@ -39,8 +39,17 @@ void HTMLDetailsElement::initialize(JS::Realm& realm)
{
Base::initialize(realm);
set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLDetailsElementPrototype>(realm, "HTMLDetailsElement"_fly_string));
}
create_shadow_tree(realm).release_value_but_fixme_should_propagate_errors();
void HTMLDetailsElement::inserted()
{
create_shadow_tree_if_needed().release_value_but_fixme_should_propagate_errors();
update_shadow_tree_slots();
}
void HTMLDetailsElement::removed_from(DOM::Node*)
{
set_shadow_root(nullptr);
}
void HTMLDetailsElement::attribute_changed(FlyString const& name, Optional<String> const& value)
@ -107,8 +116,13 @@ void HTMLDetailsElement::queue_a_details_toggle_event_task(String old_state, Str
}
// https://html.spec.whatwg.org/#the-details-and-summary-elements
WebIDL::ExceptionOr<void> HTMLDetailsElement::create_shadow_tree(JS::Realm& realm)
WebIDL::ExceptionOr<void> HTMLDetailsElement::create_shadow_tree_if_needed()
{
if (shadow_root_internal())
return {};
auto& realm = this->realm();
// The element is also expected to have an internal shadow tree with two slots.
auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm, document(), *this, Bindings::ShadowRootMode::Closed);
shadow_root->set_slot_assignment(Bindings::SlotAssignmentMode::Manual);
@ -130,6 +144,9 @@ WebIDL::ExceptionOr<void> HTMLDetailsElement::create_shadow_tree(JS::Realm& real
void HTMLDetailsElement::update_shadow_tree_slots()
{
if (!shadow_root_internal())
return;
Vector<HTMLSlotElement::SlottableHandle> summary_assignment;
Vector<HTMLSlotElement::SlottableHandle> descendants_assignment;
@ -159,6 +176,9 @@ void HTMLDetailsElement::update_shadow_tree_slots()
// https://html.spec.whatwg.org/#the-details-and-summary-elements:the-details-element-6
void HTMLDetailsElement::update_shadow_tree_style()
{
if (!shadow_root_internal())
return;
if (has_attribute(HTML::AttributeNames::open)) {
MUST(m_descendants_slot->set_attribute(HTML::AttributeNames::style, R"~~~(
display: block;

View file

@ -31,12 +31,14 @@ private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual void inserted() override;
virtual void removed_from(DOM::Node*) override;
virtual void children_changed() override;
virtual void attribute_changed(FlyString const& name, Optional<String> const& value) override;
void queue_a_details_toggle_event_task(String old_state, String new_state);
WebIDL::ExceptionOr<void> create_shadow_tree(JS::Realm&);
WebIDL::ExceptionOr<void> create_shadow_tree_if_needed();
void update_shadow_tree_slots();
void update_shadow_tree_style();

View file

@ -1003,6 +1003,11 @@ void HTMLInputElement::form_associated_element_was_inserted()
create_shadow_tree_if_needed();
}
void HTMLInputElement::form_associated_element_was_removed(DOM::Node*)
{
set_shadow_root(nullptr);
}
// https://html.spec.whatwg.org/multipage/input.html#radio-button-group
static bool is_in_same_radio_button_group(HTML::HTMLInputElement const& a, HTML::HTMLInputElement const& b)
{

View file

@ -139,6 +139,7 @@ public:
virtual void reset_algorithm() override;
virtual void form_associated_element_was_inserted() override;
virtual void form_associated_element_was_removed(DOM::Node*) override;
// ^HTMLElement
// https://html.spec.whatwg.org/multipage/forms.html#category-label

View file

@ -91,6 +91,11 @@ void HTMLTextAreaElement::form_associated_element_was_inserted()
create_shadow_tree_if_needed();
}
void HTMLTextAreaElement::form_associated_element_was_removed(DOM::Node*)
{
set_shadow_root(nullptr);
}
void HTMLTextAreaElement::create_shadow_tree_if_needed()
{
if (shadow_root_internal())

View file

@ -62,6 +62,7 @@ public:
virtual void reset_algorithm() override;
virtual void form_associated_element_was_inserted() override;
virtual void form_associated_element_was_removed(DOM::Node*) override;
virtual void children_changed() override;