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 +- dom/html/HTMLSlotElement.cpp | 21 +++++ dom/html/HTMLSlotElement.h | 3 + .../meta/shadow-dom/slotchange-event.html.ini | 99 ---------------------- .../meta/shadow-dom/slotchange.html.ini | 47 ---------- 9 files changed, 174 insertions(+), 160 deletions(-) delete mode 100644 testing/web-platform/meta/shadow-dom/slotchange-event.html.ini delete mode 100644 testing/web-platform/meta/shadow-dom/slotchange.html.ini 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() { diff --git a/dom/html/HTMLSlotElement.cpp b/dom/html/HTMLSlotElement.cpp index b13729a09..18b8ef87b 100644 --- a/dom/html/HTMLSlotElement.cpp +++ b/dom/html/HTMLSlotElement.cpp @@ -4,6 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/dom/DocGroup.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLSlotElementBinding.h" #include "mozilla/dom/HTMLUnknownElement.h" @@ -204,6 +205,26 @@ HTMLSlotElement::ClearAssignedNodes() mAssignedNodes.Clear(); } +void +HTMLSlotElement::EnqueueSlotChangeEvent() const +{ + DocGroup* docGroup = OwnerDoc()->GetDocGroup(); + if (!docGroup) { + return; + } + + docGroup->SignalSlotChange(this); +} + +void +HTMLSlotElement::FireSlotChangeEvent() +{ + nsContentUtils::DispatchTrustedEvent(OwnerDoc(), + static_cast(this), + NS_LITERAL_STRING("slotchange"), true, + false); +} + JSObject* HTMLSlotElement::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { diff --git a/dom/html/HTMLSlotElement.h b/dom/html/HTMLSlotElement.h index 4f8546200..0494a654e 100644 --- a/dom/html/HTMLSlotElement.h +++ b/dom/html/HTMLSlotElement.h @@ -62,6 +62,9 @@ public: void RemoveAssignedNode(nsINode* aNode); void ClearAssignedNodes(); + void EnqueueSlotChangeEvent() const; + void FireSlotChangeEvent(); + protected: virtual ~HTMLSlotElement(); virtual JSObject* diff --git a/testing/web-platform/meta/shadow-dom/slotchange-event.html.ini b/testing/web-platform/meta/shadow-dom/slotchange-event.html.ini deleted file mode 100644 index 434e7d9ba..000000000 --- a/testing/web-platform/meta/shadow-dom/slotchange-event.html.ini +++ /dev/null @@ -1,99 +0,0 @@ -[slotchange-event.html] - type: testharness - expected: ERROR - [slotchange event must fire on a default slot element inside an open shadow root in a document] - expected: FAIL - - [slotchange event must fire on a default slot element inside a closed shadow root in a document] - expected: FAIL - - [slotchange event must fire on a default slot element inside an open shadow root not in a document] - expected: FAIL - - [slotchange event must fire on a default slot element inside a closed shadow root not in a document] - expected: FAIL - - [slotchange event must fire on a named slot element insidean open shadow root in a document] - expected: FAIL - - [slotchange event must fire on a named slot element insidea closed shadow root in a document] - expected: FAIL - - [slotchange event must fire on a named slot element insidean open shadow root not in a document] - expected: FAIL - - [slotchange event must fire on a named slot element insidea closed shadow root not in a document] - expected: FAIL - - [slotchange event must not fire on a slot element inside an open shadow root in a document when another slot's assigned nodes change] - expected: FAIL - - [slotchange event must not fire on a slot element inside a closed shadow root in a document when another slot's assigned nodes change] - expected: FAIL - - [slotchange event must not fire on a slot element inside an open shadow root not in a document when another slot's assigned nodes change] - expected: FAIL - - [slotchange event must not fire on a slot element inside a closed shadow root not in a document when another slot's assigned nodes change] - expected: FAIL - - [slotchange event must not fire on a slot element inside an open shadow root in a document when the shadow host was mutated before the slot was inserted or after the slot was removed] - expected: FAIL - - [slotchange event must not fire on a slot element inside a closed shadow root in a document when the shadow host was mutated before the slot was inserted or after the slot was removed] - expected: FAIL - - [slotchange event must not fire on a slot element inside an open shadow root not in a document when the shadow host was mutated before the slot was inserted or after the slot was removed] - expected: FAIL - - [slotchange event must not fire on a slot element inside a closed shadow root not in a document when the shadow host was mutated before the slot was inserted or after the slot was removed] - expected: FAIL - - [slotchange event must fire on a slot element inside an open shadow root in a document even if the slot was removed immediately after the assigned nodes were mutated] - expected: FAIL - - [slotchange event must fire on a slot element inside a closed shadow root in a document even if the slot was removed immediately after the assigned nodes were mutated] - expected: FAIL - - [slotchange event must fire on a slot element inside an open shadow root not in a document even if the slot was removed immediately after the assigned nodes were mutated] - expected: FAIL - - [slotchange event must fire on a slot element inside a closed shadow root not in a document even if the slot was removed immediately after the assigned nodes were mutated] - expected: FAIL - - [slotchange event must fire on a slot element inside an open shadow root in a document when innerHTML modifies the children of the shadow host] - expected: FAIL - - [slotchange event must fire on a slot element inside a closed shadow root in a document when innerHTML modifies the children of the shadow host] - expected: FAIL - - [slotchange event must fire on a slot element inside an open shadow root not in a document when innerHTML modifies the children of the shadow host] - expected: FAIL - - [slotchange event must fire on a slot element inside a closed shadow root not in a document when innerHTML modifies the children of the shadow host] - expected: FAIL - - [slotchange event must fire on a slot element inside an open shadow root in a document when nested slots's contents change] - expected: FAIL - - [slotchange event must fire on a slot element inside a closed shadow root in a document when nested slots's contents change] - expected: FAIL - - [slotchange event must fire on a slot element inside an open shadow root not in a document when nested slots's contents change] - expected: FAIL - - [slotchange event must fire on a slot element inside a closed shadow root not in a document when nested slots's contents change] - expected: 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] - expected: 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] - expected: 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] - expected: 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] - expected: FAIL - diff --git a/testing/web-platform/meta/shadow-dom/slotchange.html.ini b/testing/web-platform/meta/shadow-dom/slotchange.html.ini deleted file mode 100644 index fff6fa28d..000000000 --- a/testing/web-platform/meta/shadow-dom/slotchange.html.ini +++ /dev/null @@ -1,47 +0,0 @@ -[slotchange.html] - type: testharness - [slotchange event: Append a child to a host.] - expected: FAIL - - [slotchange event: Remove a child from a host.] - expected: FAIL - - [slotchange event: Remove a child before adding an event listener.] - expected: FAIL - - [slotchange event: Change slot= attribute to make it un-assigned.] - expected: FAIL - - [slotchange event: Change slot's name= attribute so that none is assigned.] - expected: FAIL - - [slotchange event: Change slot= attribute to make it assigned.] - expected: FAIL - - [slotchange event: Change slot's name= attribute so that a node is assigned to the slot.] - expected: FAIL - - [slotchange event: Add a fallback content.] - expected: FAIL - - [slotchange event: Remove a fallback content.] - expected: FAIL - - [slotchange event: Add a fallback content to nested slots.] - expected: FAIL - - [slotchange event: Remove a fallback content from nested slots.] - expected: FAIL - - [slotchange event: Insert a slot before an existing slot.] - expected: FAIL - - [slotchange event: Remove a preceding slot.] - expected: FAIL - - [slotchange event: A slot is assigned to another slot.] - expected: FAIL - - [slotchange event: Even if distributed nodes do not change, slotchange should be fired if assigned nodes are changed.] - expected: FAIL - -- cgit v1.2.3