Bug 1409976 - Add slotchange event

* Add support for `slotchange` event
* Signal `slotchange` when slot's assigned nodes changes

Tag #1375
This commit is contained in:
Matt A. Tobin 2020-04-17 07:35:48 -04:00 committed by Roy Tam
commit af22b5cba5
9 changed files with 174 additions and 160 deletions

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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*

View file

@ -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()
{

View file

@ -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)
{

View file

@ -62,6 +62,9 @@ public:
void RemoveAssignedNode(nsINode* aNode);
void ClearAssignedNodes();
void EnqueueSlotChangeEvent() const;
void FireSlotChangeEvent();
protected:
virtual ~HTMLSlotElement();
virtual JSObject*

View file

@ -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

View file

@ -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