LibWeb: Move mutation observers from IntrusiveList to GC::RootVector

We need to prevent these mutation observers from being garbage
collected, and since they are only part of SimilarOriginWindowAgent and
themselves as part of the intrusive list, nobody is visiting them.

Make the list of pending mutation observers a GC::RootVector so we keep
them alive until they have been processed in the microtask.

Restores 1400+ WPT subtest passes in `dom/nodes/Element-classlist.html`.
This commit is contained in:
Jelle Raaijmakers 2025-11-24 09:56:05 +01:00 committed by Sam Atkins
parent 150828af98
commit e281e3a274
Notes: github-actions[bot] 2025-11-24 12:46:48 +00:00
4 changed files with 6 additions and 19 deletions

View file

@ -731,15 +731,8 @@ void queue_mutation_observer_microtask(DOM::Document const& document)
surrounding_agent.mutation_observer_microtask_queued = false;
// 2. Let notifySet be a clone of the surrounding agents pending mutation observers.
GC::RootVector<DOM::MutationObserver*> notify_set(heap);
for (auto& observer : surrounding_agent.pending_mutation_observers)
notify_set.append(&observer);
// 3. Empty the surrounding agents pending mutation observers.
// NB: We instead do this at the end of the microtask. Steps 2 and 3 are equivalent to moving
// surrounding_agent.pending_mutation_observers, but it's unmovable. Actually copying the MutationObservers
// causes issues, so for now, keep notify_set as pointers and defer this step until after we've finished
// using the notify_set.
auto notify_set = move(surrounding_agent.pending_mutation_observers);
// 4. Let signalSet be a clone of the surrounding agents signal slots.
// 5. Empty the surrounding agents signal slots.
@ -788,9 +781,6 @@ void queue_mutation_observer_microtask(DOM::Document const& document)
event_init.bubbles = true;
slot->dispatch_event(DOM::Event::create(slot->realm(), HTML::EventNames::slotchange, event_init));
}
// NB: Step 3, done later.
surrounding_agent.pending_mutation_observers.clear();
}));
}

View file

@ -64,11 +64,6 @@ private:
// https://dom.spec.whatwg.org/#concept-mo-queue
Vector<GC::Ref<MutationRecord>> m_record_queue;
IntrusiveListNode<MutationObserver> m_list_node;
public:
using List = IntrusiveList<&MutationObserver::m_list_node>;
};
// https://dom.spec.whatwg.org/#registered-observer

View file

@ -14,7 +14,7 @@ namespace Web::HTML {
NonnullOwnPtr<SimilarOriginWindowAgent> SimilarOriginWindowAgent::create(GC::Heap& heap)
{
// See 'creating an agent' step in: https://html.spec.whatwg.org/multipage/webappapis.html#obtain-similar-origin-window-agent
auto agent = adopt_own(*new SimilarOriginWindowAgent(CanBlock::No));
auto agent = adopt_own(*new SimilarOriginWindowAgent(heap, CanBlock::No));
agent->event_loop = heap.allocate<HTML::EventLoop>(HTML::EventLoop::Type::Window);
return agent;
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2025, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -29,7 +30,7 @@ struct SimilarOriginWindowAgent : public Agent {
// https://dom.spec.whatwg.org/#mutation-observer-list
// Each similar-origin window agent also has pending mutation observers (a set of zero or more MutationObserver objects), which is initially empty.
DOM::MutationObserver::List pending_mutation_observers;
GC::RootVector<GC::Ref<DOM::MutationObserver>> pending_mutation_observers;
// https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reactions-stack
// Each similar-origin window agent has a custom element reactions stack, which is initially empty.
@ -45,8 +46,9 @@ struct SimilarOriginWindowAgent : public Agent {
Vector<GC::Root<DOM::Element>> const& current_element_queue() const { return custom_element_reactions_stack.element_queue_stack.last(); }
private:
explicit SimilarOriginWindowAgent(CanBlock can_block)
SimilarOriginWindowAgent(GC::Heap& heap, CanBlock can_block)
: Agent(can_block)
, pending_mutation_observers(heap)
{
}
};