From 3508e79b1fe7fc928eed2f3c7bf2d628c53fbf17 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 17 Apr 2020 07:35:48 -0400 Subject: Bug 1409976 - Add `slotchange` event * Add support for `slotchange` event * Signal `slotchange` when slot's assigned nodes changes Tag #1375 --- dom/base/DocGroup.cpp | 21 ++++++++++++ dom/base/DocGroup.h | 19 +++++++++++ dom/base/ShadowRoot.cpp | 69 ++++++++++++++++++++++++++++++++++---- dom/base/nsDOMMutationObserver.cpp | 46 ++++++++++++++++++++++++- dom/base/nsDOMMutationObserver.h | 9 ++--- 5 files changed, 150 insertions(+), 14 deletions(-) (limited to 'dom/base') diff --git a/dom/base/DocGroup.cpp b/dom/base/DocGroup.cpp index 30c058f0c..826a71812 100644 --- a/dom/base/DocGroup.cpp +++ b/dom/base/DocGroup.cpp @@ -6,10 +6,13 @@ #include "mozilla/StaticPtr.h" #include "mozilla/ClearOnShutdown.h" #include "nsIDocShell.h" +#include "nsDOMMutationObserver.h" namespace mozilla { namespace dom { +AutoTArray, 2>* DocGroup::sPendingDocGroups = nullptr; + /* static */ void DocGroup::GetKey(nsIPrincipal* aPrincipal, nsACString& aKey) { @@ -54,5 +57,23 @@ DocGroup::~DocGroup() NS_IMPL_ISUPPORTS(DocGroup, nsISupports) +void +DocGroup::SignalSlotChange(const HTMLSlotElement* aSlot) +{ + if (mSignalSlotList.Contains(aSlot)) { + return; + } + + mSignalSlotList.AppendElement(const_cast(aSlot)); + + if (!sPendingDocGroups) { + // Queue a mutation observer compound microtask. + nsDOMMutationObserver::QueueMutationObserverMicroTask(); + sPendingDocGroups = new AutoTArray, 2>; + } + + sPendingDocGroups->AppendElement(this); +} + } } diff --git a/dom/base/DocGroup.h b/dom/base/DocGroup.h index 5b8f627cc..7a5a99dce 100644 --- a/dom/base/DocGroup.h +++ b/dom/base/DocGroup.h @@ -15,6 +15,7 @@ #include "mozilla/RefPtr.h" #include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/HTMLSlotElement.h" namespace mozilla { namespace dom { @@ -73,6 +74,23 @@ public: return mDocuments.end(); } + // Append aSlot to the list of signal slot list, if it's not in it already + // list, and queue a mutation observer microtask. + void SignalSlotChange(const mozilla::dom::HTMLSlotElement* aSlot); + + const nsTArray>& SignalSlotList() const + { + return mSignalSlotList; + } + + void ClearSignalSlotList() + { + mSignalSlotList.Clear(); + } + + // List of DocGroups that has non-empty signal slot list. + static AutoTArray, 2>* sPendingDocGroups; + private: DocGroup(TabGroup* aTabGroup, const nsACString& aKey); ~DocGroup(); @@ -81,6 +99,7 @@ private: RefPtr mTabGroup; nsTArray mDocuments; RefPtr mReactionsStack; + nsTArray> mSignalSlotList; }; } // namespace dom diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp index e7f7ae93c..12f35c197 100644 --- a/dom/base/ShadowRoot.cpp +++ b/dom/base/ShadowRoot.cpp @@ -134,6 +134,7 @@ ShadowRoot::AddSlot(HTMLSlotElement* aSlot) return; } + bool doEnqueueSlotChange = false; if (oldSlot && oldSlot != currentSlot) { // Move assigned nodes from old slot to new slot. const nsTArray>& assignedNodes = oldSlot->AssignedNodes(); @@ -142,6 +143,12 @@ ShadowRoot::AddSlot(HTMLSlotElement* aSlot) oldSlot->RemoveAssignedNode(assignedNode); currentSlot->AppendAssignedNode(assignedNode); + doEnqueueSlotChange = true; + } + + if (doEnqueueSlotChange) { + oldSlot->EnqueueSlotChangeEvent(); + currentSlot->EnqueueSlotChangeEvent(); } } else { // Otherwise add appropriate nodes to this slot from the host. @@ -152,8 +159,13 @@ ShadowRoot::AddSlot(HTMLSlotElement* aSlot) child->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); if (child->IsSlotable() && slotName.Equals(name)) { currentSlot->AppendAssignedNode(child); + doEnqueueSlotChange = true; } } + + if (doEnqueueSlotChange) { + currentSlot->EnqueueSlotChangeEvent(); + } } } @@ -171,8 +183,13 @@ ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) if (currentSlots->Length() == 1) { MOZ_ASSERT(currentSlots->ElementAt(0) == aSlot); mSlotMap.Remove(name); - aSlot->ClearAssignedNodes(); + + if (aSlot->AssignedNodes().Length() > 0) { + aSlot->ClearAssignedNodes(); + aSlot->EnqueueSlotChangeEvent(); + } } else { + bool doEnqueueSlotChange = false; bool doReplaceSlot = currentSlots->ElementAt(0) == aSlot; currentSlots->RemoveElement(aSlot); HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0); @@ -186,6 +203,12 @@ ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) aSlot->RemoveAssignedNode(assignedNode); replacementSlot->AppendAssignedNode(assignedNode); + doEnqueueSlotChange = true; + } + + if (doEnqueueSlotChange) { + aSlot->EnqueueSlotChangeEvent(); + replacementSlot->EnqueueSlotChangeEvent(); } } } @@ -421,6 +444,12 @@ ShadowRoot::MaybeReassignElement(Element* aElement, const HTMLSlotElement* newSlot = AssignSlotFor(aElement); if (oldSlot != newSlot) { + if (oldSlot) { + oldSlot->EnqueueSlotChangeEvent(); + } + if (newSlot) { + newSlot->EnqueueSlotChangeEvent(); + } return true; } } @@ -587,11 +616,24 @@ ShadowRoot::ContentInserted(nsIDocument* aDocument, return; } - if (!aChild->IsSlotable() || aContainer != GetHost()) { + if (!aChild->IsSlotable()) { return; } - AssignSlotFor(aChild); + if (aContainer && aContainer == GetHost()) { + if (const HTMLSlotElement* slot = AssignSlotFor(aChild)) { + slot->EnqueueSlotChangeEvent(); + } + return; + } + + // If parent's root is a shadow root, and parent is a slot whose assigned + // nodes is the empty list, then run signal a slot change for parent. + HTMLSlotElement* slot = HTMLSlotElement::FromContentOrNull(aContainer); + if (slot && slot->GetContainingShadow() == this && + slot->AssignedNodes().IsEmpty()) { + slot->EnqueueSlotChangeEvent(); + } } void @@ -608,13 +650,26 @@ ShadowRoot::ContentRemoved(nsIDocument* aDocument, return; } - if (!aChild->IsSlotable() || aContainer != GetHost()) { + if (!aChild->IsSlotable()) { return; } - nsAutoString slotName; - aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); - UnassignSlotFor(aChild, slotName); + if (aContainer && aContainer == GetHost()) { + nsAutoString slotName; + aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); + if (const HTMLSlotElement* slot = UnassignSlotFor(aChild, slotName)) { + slot->EnqueueSlotChangeEvent(); + } + return; + } + + // If parent's root is a shadow root, and parent is a slot whose assigned + // nodes is the empty list, then run signal a slot change for parent. + HTMLSlotElement* slot = HTMLSlotElement::FromContentOrNull(aContainer); + if (slot && slot->GetContainingShadow() == this && + slot->AssignedNodes().IsEmpty()) { + slot->EnqueueSlotChangeEvent(); + } } nsresult diff --git a/dom/base/nsDOMMutationObserver.cpp b/dom/base/nsDOMMutationObserver.cpp index 4c4731c11..fbd64dc20 100644 --- a/dom/base/nsDOMMutationObserver.cpp +++ b/dom/base/nsDOMMutationObserver.cpp @@ -12,6 +12,7 @@ #include "mozilla/dom/Animation.h" #include "mozilla/dom/KeyframeEffectReadOnly.h" +#include "mozilla/dom/DocGroup.h" #include "nsContentUtils.h" #include "nsCSSPseudoElements.h" @@ -609,6 +610,28 @@ public: } }; +/* static */ void +nsDOMMutationObserver::QueueMutationObserverMicroTask() +{ + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (!ccjs) { + return; + } + + RefPtr momt = + new MutationObserverMicroTask(); + ccjs->DispatchMicroTaskRunnable(momt.forget()); +} + +void +nsDOMMutationObserver::HandleMutations(mozilla::AutoSlowOperation& aAso) +{ + if (sScheduledMutationObservers || + mozilla::dom::DocGroup::sPendingDocGroups) { + HandleMutationsInternal(aAso); + } +} + void nsDOMMutationObserver::RescheduleForRun() { @@ -887,7 +910,23 @@ nsDOMMutationObserver::HandleMutationsInternal(AutoSlowOperation& aAso) { nsTArray >* suppressedObservers = nullptr; - while (sScheduledMutationObservers) { + // Let signalList be a copy of unit of related similar-origin browsing + // contexts' signal slot list. + nsTArray> signalList; + if (DocGroup::sPendingDocGroups) { + for (uint32_t i = 0; i < DocGroup::sPendingDocGroups->Length(); ++i) { + DocGroup* docGroup = DocGroup::sPendingDocGroups->ElementAt(i); + signalList.AppendElements(docGroup->SignalSlotList()); + + // Empty unit of related similar-origin browsing contexts' signal slot + // list. + docGroup->ClearSignalSlotList(); + } + delete DocGroup::sPendingDocGroups; + DocGroup::sPendingDocGroups = nullptr; + } + + if (sScheduledMutationObservers) { AutoTArray, 4>* observers = sScheduledMutationObservers; sScheduledMutationObservers = nullptr; @@ -917,6 +956,11 @@ nsDOMMutationObserver::HandleMutationsInternal(AutoSlowOperation& aAso) delete suppressedObservers; suppressedObservers = nullptr; } + + // Fire slotchange event for each slot in signalList. + for (uint32_t i = 0; i < signalList.Length(); ++i) { + signalList[i]->FireSlotChangeEvent(); + } } nsDOMMutationRecord* diff --git a/dom/base/nsDOMMutationObserver.h b/dom/base/nsDOMMutationObserver.h index a8babc603..d6bdfb3e0 100644 --- a/dom/base/nsDOMMutationObserver.h +++ b/dom/base/nsDOMMutationObserver.h @@ -552,12 +552,9 @@ public: } // static methods - static void HandleMutations(mozilla::AutoSlowOperation& aAso) - { - if (sScheduledMutationObservers) { - HandleMutationsInternal(aAso); - } - } + static void QueueMutationObserverMicroTask(); + + static void HandleMutations(mozilla::AutoSlowOperation& aAso); static bool AllScheduledMutationObserversAreSuppressed() { -- cgit v1.2.3