diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc index 4dece285b7ab3..fa64b76eaab95 100644 --- a/third_party/blink/renderer/core/dom/node.cc +++ b/third_party/blink/renderer/core/dom/node.cc @@ -57,6 +57,7 @@ #include "third_party/blink/renderer/core/dom/events/event_dispatcher.h" #include "third_party/blink/renderer/core/dom/events/event_listener.h" #include "third_party/blink/renderer/core/dom/events/event_path.h" +#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h" #include "third_party/blink/renderer/core/dom/flat_tree_node_data.h" #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" #include "third_party/blink/renderer/core/dom/focus_params.h" @@ -771,12 +772,22 @@ void Node::moveBefore(Node* new_child, // impossible for nested `moveBefore()` calls to occur. Assert that no atomic // move is already in progress. DCHECK(!GetDocument().StatePreservingAtomicMoveInProgress()); - GetDocument().SetStatePreservingAtomicMoveInProgress(true); ContainerNode* old_parent = new_child->parentNode(); - insertBefore(new_child, ref_child, exception_state); - GetDocument().SetStatePreservingAtomicMoveInProgress(false); + // Any scoped event (e.g. `change` from SpinButtonElement::ReleaseCapture() + // reached via HTMLFieldSetElement::ChildrenChanged()) that is queued during + // the atomic move must not be dispatched while the + // StatePreservingAtomicMoveInProgress flag is set, otherwise author script + // can run inside the atomic move and remove iframes without disconnecting + // their content frames. Hold an outer EventQueueScope so deferred events + // only fire after the flag has been cleared. + { + EventQueueScope defer_scoped_events; + GetDocument().SetStatePreservingAtomicMoveInProgress(true); + insertBefore(new_child, ref_child, exception_state); + GetDocument().SetStatePreservingAtomicMoveInProgress(false); + } if (exception_state.HadException()) { return;