summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2020-04-17 07:35:48 -0400
committerMatt A. Tobin <email@mattatobin.com>2020-04-17 07:35:48 -0400
commit3508e79b1fe7fc928eed2f3c7bf2d628c53fbf17 (patch)
treec793eb598816afa68a560c4224b06ca15b8ad55c
parentf164962a3dc53f4823dfff6c77e7972c72d4c61f (diff)
downloadUXP-3508e79b1fe7fc928eed2f3c7bf2d628c53fbf17.tar
UXP-3508e79b1fe7fc928eed2f3c7bf2d628c53fbf17.tar.gz
UXP-3508e79b1fe7fc928eed2f3c7bf2d628c53fbf17.tar.lz
UXP-3508e79b1fe7fc928eed2f3c7bf2d628c53fbf17.tar.xz
UXP-3508e79b1fe7fc928eed2f3c7bf2d628c53fbf17.zip
Bug 1409976 - Add `slotchange` event
* Add support for `slotchange` event * Signal `slotchange` when slot's assigned nodes changes Tag #1375
-rw-r--r--dom/base/DocGroup.cpp21
-rw-r--r--dom/base/DocGroup.h19
-rw-r--r--dom/base/ShadowRoot.cpp69
-rw-r--r--dom/base/nsDOMMutationObserver.cpp46
-rw-r--r--dom/base/nsDOMMutationObserver.h9
-rw-r--r--dom/html/HTMLSlotElement.cpp21
-rw-r--r--dom/html/HTMLSlotElement.h3
-rw-r--r--testing/web-platform/meta/shadow-dom/slotchange-event.html.ini99
-rw-r--r--testing/web-platform/meta/shadow-dom/slotchange.html.ini47
9 files changed, 174 insertions, 160 deletions
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<RefPtr<DocGroup>, 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<HTMLSlotElement*>(aSlot));
+
+ if (!sPendingDocGroups) {
+ // Queue a mutation observer compound microtask.
+ nsDOMMutationObserver::QueueMutationObserverMicroTask();
+ sPendingDocGroups = new AutoTArray<RefPtr<DocGroup>, 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<RefPtr<HTMLSlotElement>>& SignalSlotList() const
+ {
+ return mSignalSlotList;
+ }
+
+ void ClearSignalSlotList()
+ {
+ mSignalSlotList.Clear();
+ }
+
+ // List of DocGroups that has non-empty signal slot list.
+ static AutoTArray<RefPtr<DocGroup>, 2>* sPendingDocGroups;
+
private:
DocGroup(TabGroup* aTabGroup, const nsACString& aKey);
~DocGroup();
@@ -81,6 +99,7 @@ private:
RefPtr<TabGroup> mTabGroup;
nsTArray<nsIDocument*> mDocuments;
RefPtr<mozilla::dom::CustomElementReactionsStack> mReactionsStack;
+ nsTArray<RefPtr<HTMLSlotElement>> 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<RefPtr<nsINode>>& 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<MutationObserverMicroTask> 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<RefPtr<nsDOMMutationObserver> >* suppressedObservers = nullptr;
- while (sScheduledMutationObservers) {
+ // Let signalList be a copy of unit of related similar-origin browsing
+ // contexts' signal slot list.
+ nsTArray<RefPtr<HTMLSlotElement>> 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<RefPtr<nsDOMMutationObserver>, 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<nsIContent*>(this),
+ NS_LITERAL_STRING("slotchange"), true,
+ false);
+}
+
JSObject*
HTMLSlotElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> 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
-