Skip to content

Commit 6e17503

Browse files
AtkinsSJawesomekling
authored andcommitted
LibWeb: Add and remove MutationObservers at specified times
In the current spec, MutationObservers are explicitly added to the pending mutation observers list, and they are removed when that list is cleared in the "notify mutation observers" microtask. This solves some issues with slotchange events. As noted, we delay actually emptying the list of pending mutation observers until after we're finished with the "clone", because we can't actually copy or move the intrusive list. As far as I am aware, this should not affect behaviour because only one microtask can run at once.
1 parent 37f49d5 commit 6e17503

File tree

6 files changed

+39
-34
lines changed

6 files changed

+39
-34
lines changed

Libraries/LibWeb/Bindings/MainThreadVM.cpp

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -723,27 +723,36 @@ void queue_mutation_observer_microtask(DOM::Document const& document)
723723
surrounding_agent.mutation_observer_microtask_queued = true;
724724

725725
// 3. Queue a microtask to notify mutation observers.
726-
// NOTE: This uses the implied document concept. In the case of mutation observers, it is always done in a node context, so document should be that node's document.
726+
// NOTE: This uses the implied document concept. In the case of mutation observers, it is always done in a node
727+
// context, so document should be that node's document.
727728
HTML::queue_a_microtask(&document, GC::create_function(vm.heap(), [&surrounding_agent, &heap = document.heap()]() {
729+
// https://dom.spec.whatwg.org/#notify-mutation-observers
728730
// 1. Set the surrounding agent’s mutation observer microtask queued to false.
729731
surrounding_agent.mutation_observer_microtask_queued = false;
730732

731-
// 2. Let notifySet be a clone of the surrounding agent’s mutation observers.
733+
// 2. Let notifySet be a clone of the surrounding agent’s pending mutation observers.
732734
GC::RootVector<DOM::MutationObserver*> notify_set(heap);
733-
for (auto& observer : surrounding_agent.mutation_observers)
735+
for (auto& observer : surrounding_agent.pending_mutation_observers)
734736
notify_set.append(&observer);
735737

736-
// 3. Let signalSet be a clone of the surrounding agent’s signal slots.
737-
// 4. Empty the surrounding agent’s signal slots.
738+
// 3. Empty the surrounding agent’s pending mutation observers.
739+
// NB: We instead do this at the end of the microtask. Steps 2 and 3 are equivalent to moving
740+
// surrounding_agent.pending_mutation_observers, but it's unmovable. Actually copying the MutationObservers
741+
// causes issues, so for now, keep notify_set as pointers and defer this step until after we've finished
742+
// using the notify_set.
743+
744+
// 4. Let signalSet be a clone of the surrounding agent’s signal slots.
745+
// 5. Empty the surrounding agent’s signal slots.
738746
auto signal_set = move(surrounding_agent.signal_slots);
739747

740-
// 5. For each mo of notifySet:
748+
// 6. For each mo of notifySet:
741749
for (auto& mutation_observer : notify_set) {
742750
// 1. Let records be a clone of mo’s record queue.
743751
// 2. Empty mo’s record queue.
744752
auto records = mutation_observer->take_records();
745753

746-
// 3. For each node of mo’s node list, remove all transient registered observers whose observer is mo from node’s registered observer list.
754+
// 3. For each node of mo’s node list, remove all transient registered observers whose observer is mo from
755+
// node’s registered observer list.
747756
for (auto& node : mutation_observer->node_list()) {
748757
// FIXME: Is this correct?
749758
if (!node)
@@ -756,7 +765,8 @@ void queue_mutation_observer_microtask(DOM::Document const& document)
756765
}
757766
}
758767

759-
// 4. If records is not empty, then invoke mo’s callback with « records, mo » and "report", and with callback this value mo.
768+
// 4. If records is not empty, then invoke mo’s callback with « records, mo » and "report", and with
769+
// callback this value mo.
760770
if (!records.is_empty()) {
761771
auto& callback = mutation_observer->callback();
762772
auto& realm = callback.callback_context;
@@ -772,12 +782,15 @@ void queue_mutation_observer_microtask(DOM::Document const& document)
772782
}
773783
}
774784

775-
// 6. For each slot of signalSet, fire an event named slotchange, with its bubbles attribute set to true, at slot.
785+
// 7. For each slot of signalSet, fire an event named slotchange, with its bubbles attribute set to true, at slot.
776786
for (auto& slot : signal_set) {
777787
DOM::EventInit event_init;
778788
event_init.bubbles = true;
779789
slot->dispatch_event(DOM::Event::create(slot->realm(), HTML::EventNames::slotchange, event_init));
780790
}
791+
792+
// NB: Step 3, done later.
793+
surrounding_agent.pending_mutation_observers.clear();
781794
}));
782795
}
783796

Libraries/LibWeb/DOM/MutationObserver.cpp

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,10 @@ MutationObserver::MutationObserver(JS::Realm& realm, GC::Ptr<WebIDL::CallbackTyp
2626
: PlatformObject(realm)
2727
, m_callback(move(callback))
2828
{
29-
30-
// 1. Set this’s callback to callback.
31-
32-
// 2. Append this to this’s relevant agent’s mutation observers.
33-
HTML::relevant_similar_origin_window_agent(*this).mutation_observers.append(*this);
29+
// The new MutationObserver(callback) constructor steps are to set this’s callback to callback.
3430
}
3531

36-
MutationObserver::~MutationObserver()
37-
{
38-
}
39-
40-
void MutationObserver::finalize()
41-
{
42-
HTML::relevant_similar_origin_window_agent(*this).mutation_observers.remove(*this);
43-
}
32+
MutationObserver::~MutationObserver() = default;
4433

4534
void MutationObserver::initialize(JS::Realm& realm)
4635
{
@@ -119,7 +108,7 @@ WebIDL::ExceptionOr<void> MutationObserver::observe(Node& target, MutationObserv
119108
auto new_registered_observer = RegisteredObserver::create(*this, options);
120109
target.add_registered_observer(new_registered_observer);
121110

122-
// 2. Append target to this’s node list.
111+
// 2. Append a weak reference to target to this’s node list.
123112
m_node_list.append(target);
124113
}
125114

Libraries/LibWeb/DOM/MutationObserver.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ class MutationObserver final : public Bindings::PlatformObject {
5353

5454
virtual void initialize(JS::Realm&) override;
5555
virtual void visit_edges(Cell::Visitor&) override;
56-
virtual void finalize() override;
5756

5857
// https://dom.spec.whatwg.org/#concept-mo-callback
5958
GC::Ptr<WebIDL::CallbackType> m_callback;

Libraries/LibWeb/DOM/Node.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include <LibWeb/HTML/Navigable.h>
5454
#include <LibWeb/HTML/NavigableContainer.h>
5555
#include <LibWeb/HTML/Parser/HTMLParser.h>
56+
#include <LibWeb/HTML/Scripting/SimilarOriginWindowAgent.h>
5657
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
5758
#include <LibWeb/Infra/CharacterTypes.h>
5859
#include <LibWeb/Layout/Node.h>
@@ -2588,13 +2589,16 @@ void Node::queue_mutation_record(FlyString const& type, Optional<FlyString> cons
25882589
auto removed_nodes_list = StaticNodeList::create(realm(), move(removed_nodes));
25892590

25902591
// 4. For each observer → mappedOldValue of interestedObservers:
2591-
for (auto& interested_observer : interested_observers) {
2592+
for (auto& [observer, mapped_old_value] : interested_observers) {
25922593
// 1. Let record be a new MutationRecord object with its type set to type, target set to target, attributeName set to name, attributeNamespace set to namespace, oldValue set to mappedOldValue,
25932594
// addedNodes set to addedNodes, removedNodes set to removedNodes, previousSibling set to previousSibling, and nextSibling set to nextSibling.
2594-
auto record = MutationRecord::create(realm(), type, *this, added_nodes_list, removed_nodes_list, previous_sibling, next_sibling, string_attribute_name, string_attribute_namespace, /* mappedOldValue */ interested_observer.value);
2595+
auto record = MutationRecord::create(realm(), type, *this, added_nodes_list, removed_nodes_list, previous_sibling, next_sibling, string_attribute_name, string_attribute_namespace, mapped_old_value);
25952596

25962597
// 2. Enqueue record to observer’s record queue.
2597-
interested_observer.key->enqueue_record({}, move(record));
2598+
observer->enqueue_record({}, move(record));
2599+
2600+
// 3. Append observer to the surrounding agent’s pending mutation observers.
2601+
HTML::relevant_similar_origin_window_agent(*this).pending_mutation_observers.append(*observer);
25982602
}
25992603

26002604
// 5. Queue a mutation observer microtask.

Libraries/LibWeb/HTML/Scripting/SimilarOriginWindowAgent.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct SimilarOriginWindowAgent : public Agent {
2929

3030
// https://dom.spec.whatwg.org/#mutation-observer-list
3131
// Each similar-origin window agent also has pending mutation observers (a set of zero or more MutationObserver objects), which is initially empty.
32-
DOM::MutationObserver::List mutation_observers;
32+
DOM::MutationObserver::List pending_mutation_observers;
3333

3434
// https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reactions-stack
3535
// Each similar-origin window agent has a custom element reactions stack, which is initially empty.

Tests/LibWeb/Text/expected/wpt-import/shadow-dom/slotchange-event.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ Harness status: OK
22

33
Found 32 tests
44

5-
12 Pass
6-
20 Fail
5+
16 Pass
6+
16 Fail
77
Fail slotchange event must fire on a default slot element inside an open shadow root in a document
88
Fail slotchange event must fire on a default slot element inside a closed shadow root in a document
99
Fail slotchange event must fire on a default slot element inside an open shadow root not in a document
@@ -32,7 +32,7 @@ Pass slotchange event must fire on a slot element inside an open shadow root in
3232
Pass slotchange event must fire on a slot element inside a closed shadow root in a document when nested slots's contents change
3333
Pass slotchange event must fire on a slot element inside an open shadow root not in a document when nested slots's contents change
3434
Pass slotchange event must fire on a slot element inside a closed shadow root not in a document when nested slots's contents change
35-
Fail slotchange event must fire at the end of current microtask after mutation observers are invoked inside an open shadow root in a document when slots's contents change
36-
Fail slotchange event must fire at the end of current microtask after mutation observers are invoked inside a closed shadow root in a document when slots's contents change
37-
Fail slotchange event must fire at the end of current microtask after mutation observers are invoked inside an open shadow root not in a document when slots's contents change
38-
Fail slotchange event must fire at the end of current microtask after mutation observers are invoked inside a closed shadow root not in a document when slots's contents change
35+
Pass slotchange event must fire at the end of current microtask after mutation observers are invoked inside an open shadow root in a document when slots's contents change
36+
Pass slotchange event must fire at the end of current microtask after mutation observers are invoked inside a closed shadow root in a document when slots's contents change
37+
Pass slotchange event must fire at the end of current microtask after mutation observers are invoked inside an open shadow root not in a document when slots's contents change
38+
Pass slotchange event must fire at the end of current microtask after mutation observers are invoked inside a closed shadow root not in a document when slots's contents change

0 commit comments

Comments
 (0)