WebKit Bugzilla
Attachment 348697 Details for
Bug 189144
: slotchange event doesn't get fired when inserting, removing, or renaming slot elements
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
WIP4
wip189144d.patch (text/plain), 61.10 KB, created by
Ryosuke Niwa
on 2018-08-31 19:33:55 PDT
(
hide
)
Description:
WIP4
Filename:
MIME Type:
Creator:
Ryosuke Niwa
Created:
2018-08-31 19:33:55 PDT
Size:
61.10 KB
patch
obsolete
>Index: Source/WebCore/dom/ContainerNode.cpp >=================================================================== >--- Source/WebCore/dom/ContainerNode.cpp (revision 235483) >+++ Source/WebCore/dom/ContainerNode.cpp (working copy) >@@ -59,6 +59,7 @@ > #include "SVGUseElement.h" > #include "ScriptDisallowedScope.h" > #include "SelectorQuery.h" >+#include "SlotAssignment.h" > #include "TemplateContentDocumentFragment.h" > #include <algorithm> > #include <wtf/IsoMallocInlines.h> >@@ -104,6 +105,9 @@ > WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; > ScriptDisallowedScope::InMainThread scriptDisallowedScope; > >+ if (UNLIKELY(isShadowRoot() || isInShadowTree())) >+ containingShadowRoot()->willRemoveAllChildren(*this); >+ > document().nodeChildrenWillBeRemoved(*this); > > while (RefPtr<Node> child = m_firstChild) { >@@ -150,6 +154,9 @@ > WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; > ScriptDisallowedScope::InMainThread scriptDisallowedScope; > >+ if (UNLIKELY(isShadowRoot() || isInShadowTree())) >+ containingShadowRoot()->resolveSlotsBeforeNodeInsertionOrRemoval(); >+ > document().nodeWillBeRemoved(childToRemove); > > ASSERT_WITH_SECURITY_IMPLICATION(childToRemove.parentNode() == this); >@@ -181,6 +188,10 @@ > NodeVector postInsertionNotificationTargets; > { > ScriptDisallowedScope::InMainThread scriptDisallowedScope; >+ >+ if (UNLIKELY(containerNode.isShadowRoot() || containerNode.isInShadowTree())) >+ containerNode.containingShadowRoot()->resolveSlotsBeforeNodeInsertionOrRemoval(); >+ > doNodeInsertion(); > ChildListMutationScope(containerNode).childAdded(child); > postInsertionNotificationTargets = notifyChildNodeInserted(containerNode, child); >Index: Source/WebCore/dom/ShadowRoot.cpp >=================================================================== >--- Source/WebCore/dom/ShadowRoot.cpp (revision 235483) >+++ Source/WebCore/dom/ShadowRoot.cpp (working copy) >@@ -82,6 +82,9 @@ > // to access it Document reference after that. > willBeDeletedFrom(document()); > >+ ASSERT(!m_hasBegunDeletingDetachedChildren); >+ m_hasBegunDeletingDetachedChildren = true; >+ > // We must remove all of our children first before the TreeScope destructor > // runs so we don't go through Node::setTreeScopeRecursively for each child with a > // destructed tree scope in each descendant. >@@ -181,6 +184,12 @@ > return m_slotAssignment->findAssignedSlot(node, *this); > } > >+void ShadowRoot::renameSlotElement(HTMLSlotElement& slot, const AtomicString& oldName, const AtomicString& newName) >+{ >+ ASSERT(m_slotAssignment); >+ return m_slotAssignment->renameSlotElement(slot, oldName, newName, *this); >+} >+ > void ShadowRoot::addSlotElementByName(const AtomicString& name, HTMLSlotElement& slot) > { > ASSERT(&slot.rootNode() == this); >@@ -190,9 +199,10 @@ > return m_slotAssignment->addSlotElementByName(name, slot, *this); > } > >-void ShadowRoot::removeSlotElementByName(const AtomicString& name, HTMLSlotElement& slot) >+void ShadowRoot::removeSlotElementByName(const AtomicString& name, HTMLSlotElement& slot, ContainerNode& oldParentOfRemovedTree) > { >- return m_slotAssignment->removeSlotElementByName(name, slot, *this); >+ ASSERT(m_slotAssignment); >+ return m_slotAssignment->removeSlotElementByName(name, slot, &oldParentOfRemovedTree, *this); > } > > void ShadowRoot::slotFallbackDidChange(HTMLSlotElement& slot) >Index: Source/WebCore/dom/ShadowRoot.h >=================================================================== >--- Source/WebCore/dom/ShadowRoot.h (revision 235483) >+++ Source/WebCore/dom/ShadowRoot.h (working copy) >@@ -58,6 +58,11 @@ > bool resetStyleInheritance() const { return m_resetStyleInheritance; } > void setResetStyleInheritance(bool); > >+ bool hasBegunDeletingDetachedChildren() const { return m_hasBegunDeletingDetachedChildren; } >+ >+ ShadowRootMode mode() const { return m_type; } >+ bool shouldFireSlotchangeEvent() const { return m_type != ShadowRootMode::UserAgent && !m_hasBegunDeletingDetachedChildren; } >+ > Element* host() const { return m_host; } > void setHost(Element* host) { m_host = host; } > >@@ -66,15 +71,16 @@ > > Element* activeElement() const; > >- ShadowRootMode mode() const { return m_type; } >- > void removeAllEventListeners() override; > > HTMLSlotElement* findAssignedSlot(const Node&); > >+ void renameSlotElement(HTMLSlotElement&, const AtomicString& oldName, const AtomicString& newName); > void addSlotElementByName(const AtomicString&, HTMLSlotElement&); >- void removeSlotElementByName(const AtomicString&, HTMLSlotElement&); >+ void removeSlotElementByName(const AtomicString&, HTMLSlotElement&, ContainerNode& oldParentOfRemovedTree); > void slotFallbackDidChange(HTMLSlotElement&); >+ void resolveSlotsBeforeNodeInsertionOrRemoval(); >+ void willRemoveAllChildren(ContainerNode&); > > void didRemoveAllChildrenOfShadowHost(); > void didChangeDefaultSlot(); >@@ -103,6 +109,7 @@ > void removedFromAncestor(RemovalType, ContainerNode& insertionPoint) override; > > bool m_resetStyleInheritance { false }; >+ bool m_hasBegunDeletingDetachedChildren { false }; > ShadowRootMode m_type { ShadowRootMode::UserAgent }; > > Element* m_host { nullptr }; >Index: Source/WebCore/dom/SlotAssignment.cpp >=================================================================== >--- Source/WebCore/dom/SlotAssignment.cpp (revision 235483) >+++ Source/WebCore/dom/SlotAssignment.cpp (working copy) >@@ -64,6 +64,20 @@ > return findFirstSlotElement(*slot, shadowRoot); > } > >+void SlotAssignment::renameSlotElement(HTMLSlotElement& slotElement, const AtomicString& oldName, const AtomicString& newName, ShadowRoot& shadowRoot) >+{ >+ m_slotMutationVersion++; >+ removeSlotElementByName(oldName, slotElement, nullptr, shadowRoot); >+ addSlotElementByName(newName, slotElement, shadowRoot); >+} >+ >+inline bool SlotAssignment::hasAssignedNodes(ShadowRoot& shadowRoot, Slot& slot) >+{ >+ if (!m_slotAssignmentsIsValid) >+ assignSlots(shadowRoot); >+ return !slot.assignedNodes.isEmpty(); >+} >+ > void SlotAssignment::addSlotElementByName(const AtomicString& name, HTMLSlotElement& slotElement, ShadowRoot& shadowRoot) > { > #ifndef NDEBUG >@@ -83,21 +97,79 @@ > > return std::make_unique<Slot>(); > }); >+ auto& slot = *addResult.iterator->value; >+ bool needsSlotchangeEvent = shadowRoot.shouldFireSlotchangeEvent() && hasAssignedNodes(shadowRoot, slot); > >- auto& slot = *addResult.iterator->value; >- if (!slot.hasSlotElements()) >+ slot.elementCount++; >+ if (slot.elementCount == 1) { > slot.element = makeWeakPtr(slotElement); >- else { >+ if (needsSlotchangeEvent) >+ slotElement.enqueueSlotChangeEvent(); >+ return; >+ } >+ >+ auto* previouslyFirstSlot = slot.element.get(); >+ if (!previouslyFirstSlot) { >+ ASSERT(!needsSlotchangeEvent); >+ ASSERT(m_needsToResolveSlotElements); >+ return; >+ } >+ >+ if (!needsSlotchangeEvent) { > slot.element = nullptr; >-#ifndef NDEBUG > m_needsToResolveSlotElements = true; >-#endif >+ return; > } >- slot.elementCount++; >+ >+ if (m_slotResolutionVersion == m_slotMutationVersion) >+ return; >+ >+ m_slotResolutionVersion = m_slotMutationVersion; >+ m_needsToResolveSlotElements = false; >+ for (auto& slot : m_slots.values()) >+ slot->seenFirstElement = false; >+ for (auto& curentElement : descendantsOfType<HTMLSlotElement>(shadowRoot)) { >+ auto& currentSlotName = slotNameFromAttributeValue(curentElement.attributeWithoutSynchronization(nameAttr)); >+ auto* currentSlot = m_slots.get(currentSlotName); >+ if (!currentSlot) >+ continue; // A new slot may have been inserted with this node but later in the tree order. >+ if (slot.seenFirstElement) >+ continue; >+ slot.seenFirstElement = true; >+ ASSERT(currentSlot->element); >+ if (currentSlot->element != &curentElement) { >+ curentElement.enqueueSlotChangeEvent(); >+ currentSlot->element->enqueueSlotChangeEvent(); >+ currentSlot->element = makeWeakPtr(curentElement); >+ } >+ } > } > >-void SlotAssignment::removeSlotElementByName(const AtomicString& name, HTMLSlotElement& slotElement, ShadowRoot& shadowRoot) >+#if !ASSERT_DISABLED >+static HTMLSlotElement* findSlotElement(ShadowRoot& shadowRoot, const AtomicString& slotName) > { >+ for (auto& slotElement : descendantsOfType<HTMLSlotElement>(shadowRoot)) { >+ if (slotNameFromAttributeValue(slotElement.attributeWithoutSynchronization(nameAttr)) == slotName) >+ return &slotElement; >+ } >+ return nullptr; >+} >+#endif >+ >+static HTMLSlotElement* nextSlotElementSkippingSubtree(ContainerNode& startingNode, ContainerNode* skippedSubtree) >+{ >+ Node* node = &startingNode; >+ do { >+ if (UNLIKELY(node == skippedSubtree)) >+ node = NodeTraversal::nextSkippingChildren(*node); >+ else >+ node = NodeTraversal::next(*node); >+ } while (node && !is<HTMLSlotElement>(node)); >+ return downcast<HTMLSlotElement>(node); >+} >+ >+void SlotAssignment::removeSlotElementByName(const AtomicString& name, HTMLSlotElement& slotElement, ContainerNode* oldParentOfRemovedTree, ShadowRoot& shadowRoot) >+{ > #ifndef NDEBUG > ASSERT(m_slotElementsForConsistencyCheck.contains(&slotElement)); > m_slotElementsForConsistencyCheck.remove(&slotElement); >@@ -108,15 +180,98 @@ > > auto* slot = m_slots.get(slotNameFromAttributeValue(name)); > RELEASE_ASSERT(slot && slot->hasSlotElements()); >+ bool needsSlotchangeEvent = shadowRoot.shouldFireSlotchangeEvent() && hasAssignedNodes(shadowRoot, *slot); > > slot->elementCount--; >- if (slot->element == &slotElement) { >+ if (!slot->elementCount) { > slot->element = nullptr; >-#ifndef NDEBUG >+ if (needsSlotchangeEvent && m_slotResolutionVersion != m_slotMutationVersion) >+ slotElement.enqueueSlotChangeEvent(); >+ return; >+ } >+ >+ auto* previouslyFirstSlot = slot->element.get(); >+ if (!previouslyFirstSlot) { >+ ASSERT((!needsSlotchangeEvent && m_needsToResolveSlotElements) || (m_slotResolutionVersion == m_slotMutationVersion && !findSlotElement(shadowRoot, name))); >+ return; >+ } >+ >+ if (!needsSlotchangeEvent) { >+ slot->element = nullptr; > m_needsToResolveSlotElements = true; >-#endif >+ return; > } >- ASSERT(slot->element || m_needsToResolveSlotElements); >+ >+ if (m_slotResolutionVersion == m_slotMutationVersion) >+ return; >+ >+ m_slotResolutionVersion = m_slotMutationVersion; >+ m_needsToResolveSlotElements = false; >+ for (auto& slot : m_slots.values()) >+ slot->seenFirstElement = false; >+ unsigned slotCount = 0; >+ >+ if (!m_willBeRemovingAllChildren) >+ oldParentOfRemovedTree = nullptr; >+ HTMLSlotElement* nextSlotElement = nextSlotElementSkippingSubtree(shadowRoot, oldParentOfRemovedTree); >+ for (; nextSlotElement; nextSlotElement = nextSlotElementSkippingSubtree(*nextSlotElement, oldParentOfRemovedTree)) { >+ auto& curentElement = *nextSlotElement; >+ auto& currentSlotName = slotNameFromAttributeValue(curentElement.attributeWithoutSynchronization(nameAttr)); >+ auto* currentSlot = m_slots.get(currentSlotName); >+ if (!currentSlot) >+ continue; // A new slot may have been inserted with this node but later in the tree order. >+ if (slot->seenFirstElement) >+ continue; >+ slot->seenFirstElement = true; >+ slotCount++; >+ ASSERT(currentSlot->element); >+ if (currentSlot->element != &curentElement) { >+ // Removed element always gets the slotchange event first >+ // See https://dom.spec.whatwg.org/#concept-node-remove >+ if (hasAssignedNodes(shadowRoot, *slot)) { >+ currentSlot->element->enqueueSlotChangeEvent(); >+ curentElement.enqueueSlotChangeEvent(); >+ } >+ currentSlot->element = makeWeakPtr(curentElement); >+ } >+ } >+/* >+ for (auto& curentElement : descendantsOfType<HTMLSlotElement>(shadowRoot)) { >+ auto& currentSlotName = slotNameFromAttributeValue(curentElement.attributeWithoutSynchronization(nameAttr)); >+ auto* currentSlot = m_slots.get(currentSlotName); >+ if (!currentSlot) >+ continue; // A new slot may have been inserted with this node but later in the tree order. >+ if (slot->seenFirstElement) >+ continue; >+ slot->seenFirstElement = true; >+ slotCount++; >+ ASSERT(currentSlot->element); >+ if (currentSlot->element != &curentElement) { >+ // Removed element always gets the slotchange event first >+ // See https://dom.spec.whatwg.org/#concept-node-remove >+ if (hasAssignedNodes(shadowRoot, *slot)) { >+ currentSlot->element->enqueueSlotChangeEvent(); >+ curentElement.enqueueSlotChangeEvent(); >+ } >+ currentSlot->element = makeWeakPtr(curentElement); >+ } >+ }*/ >+ if (slotCount < m_slots.size()) { >+ for (auto& slot : m_slots.values()) { >+ if (slot->seenFirstElement) >+ continue; >+ if (!slot->elementCount) { // Taken the fast path for removal. >+ ASSERT(!slot->element); >+ continue; >+ } >+ // All slot elements have been removed for this slot. >+ slot->seenFirstElement = true; >+ ASSERT(slot->element); >+ if (hasAssignedNodes(shadowRoot, *slot)) >+ slot->element->enqueueSlotChangeEvent(); >+ slot->element = nullptr; >+ } >+ } > } > > void SlotAssignment::slotFallbackDidChange(HTMLSlotElement& slotElement, ShadowRoot& shadowRoot) >@@ -129,6 +284,23 @@ > slotElement.enqueueSlotChangeEvent(); > } > >+void SlotAssignment::resolveSlotsBeforeNodeInsertionOrRemoval(ShadowRoot& shadowRoot) >+{ >+ ASSERT(shadowRoot.shouldFireSlotchangeEvent()); >+ m_slotMutationVersion++; >+ m_willBeRemovingAllChildren = false; >+ if (m_needsToResolveSlotElements) >+ resolveAllSlotElements(shadowRoot); >+} >+ >+void SlotAssignment::willRemoveAllChildren(ShadowRoot& shadowRoot) >+{ >+ m_slotMutationVersion++; >+ m_willBeRemovingAllChildren = true; >+ if (m_needsToResolveSlotElements) >+ resolveAllSlotElements(shadowRoot); >+} >+ > void SlotAssignment::didChangeSlot(const AtomicString& slotAttrValue, ShadowRoot& shadowRoot) > { > auto& slotName = slotNameFromAttributeValue(slotAttrValue); >@@ -145,10 +317,8 @@ > > shadowRoot.host()->invalidateStyleAndRenderersForSubtree(); > >- if (shadowRoot.mode() == ShadowRootMode::UserAgent) >- return; >- >- slotElement->enqueueSlotChangeEvent(); >+ if (shadowRoot.shouldFireSlotchangeEvent()) >+ slotElement->enqueueSlotChangeEvent(); > } > > void SlotAssignment::hostChildElementDidChange(const Element& childElement, ShadowRoot& shadowRoot) >@@ -196,14 +366,12 @@ > > void SlotAssignment::resolveAllSlotElements(ShadowRoot& shadowRoot) > { >-#ifndef NDEBUG > ASSERT(m_needsToResolveSlotElements); > m_needsToResolveSlotElements = false; >-#endif > > // FIXME: It's inefficient to reset all values. We should be able to void this in common case. > for (auto& entry : m_slots) >- entry.value->element = nullptr; >+ entry.value->seenFirstElement = false; > > unsigned slotCount = m_slots.size(); > for (auto& slotElement : descendantsOfType<HTMLSlotElement>(shadowRoot)) { >@@ -212,9 +380,9 @@ > auto* slot = m_slots.get(slotName); > RELEASE_ASSERT(slot); // slot must have been created when a slot was inserted. > >- bool hasSeenSlotWithSameName = !!slot->element; >- if (hasSeenSlotWithSameName) >+ if (slot->seenFirstElement) > continue; >+ slot->seenFirstElement = true; > > slot->element = makeWeakPtr(slotElement); > slotCount--; >Index: Source/WebCore/dom/SlotAssignment.h >=================================================================== >--- Source/WebCore/dom/SlotAssignment.h (revision 235483) >+++ Source/WebCore/dom/SlotAssignment.h (working copy) >@@ -50,9 +50,12 @@ > > HTMLSlotElement* findAssignedSlot(const Node&, ShadowRoot&); > >+ void renameSlotElement(HTMLSlotElement&, const AtomicString& oldName, const AtomicString& newName, ShadowRoot&); > void addSlotElementByName(const AtomicString&, HTMLSlotElement&, ShadowRoot&); >- void removeSlotElementByName(const AtomicString&, HTMLSlotElement&, ShadowRoot&); >+ void removeSlotElementByName(const AtomicString&, HTMLSlotElement&, ContainerNode* oldParentOfRemovedTree, ShadowRoot&); > void slotFallbackDidChange(HTMLSlotElement&, ShadowRoot&); >+ void resolveSlotsBeforeNodeInsertionOrRemoval(ShadowRoot&); >+ void willRemoveAllChildren(ShadowRoot&); > > void didChangeSlot(const AtomicString&, ShadowRoot&); > void enqueueSlotChangeEvent(const AtomicString&, ShadowRoot&); >@@ -73,9 +76,12 @@ > > WeakPtr<HTMLSlotElement> element; > unsigned elementCount { 0 }; >+ bool seenFirstElement { false }; > Vector<Node*> assignedNodes; > }; >- >+ >+ bool hasAssignedNodes(ShadowRoot&, Slot&); >+ > virtual const AtomicString& slotNameForHostChild(const Node&) const; > > HTMLSlotElement* findFirstSlotElement(Slot&, ShadowRoot&); >@@ -88,12 +94,27 @@ > > #ifndef NDEBUG > HashSet<HTMLSlotElement*> m_slotElementsForConsistencyCheck; >- bool m_needsToResolveSlotElements { false }; > #endif > >+ bool m_needsToResolveSlotElements { false }; > bool m_slotAssignmentsIsValid { false }; >+ bool m_willBeRemovingAllChildren { false }; >+ unsigned m_slotMutationVersion { 0 }; >+ unsigned m_slotResolutionVersion { 0 }; > }; > >+inline void ShadowRoot::resolveSlotsBeforeNodeInsertionOrRemoval() >+{ >+ if (UNLIKELY(shouldFireSlotchangeEvent() && m_slotAssignment)) >+ m_slotAssignment->resolveSlotsBeforeNodeInsertionOrRemoval(*this); >+} >+ >+inline void ShadowRoot::willRemoveAllChildren(ContainerNode&) >+{ >+ if (UNLIKELY(shouldFireSlotchangeEvent() && m_slotAssignment)) >+ m_slotAssignment->willRemoveAllChildren(*this); >+} >+ > inline void ShadowRoot::didRemoveAllChildrenOfShadowHost() > { > if (m_slotAssignment) // FIXME: This is incorrect when there were no elements or text nodes removed. >Index: Source/WebCore/html/HTMLSlotElement.cpp >=================================================================== >--- Source/WebCore/html/HTMLSlotElement.cpp (revision 235483) >+++ Source/WebCore/html/HTMLSlotElement.cpp (working copy) >@@ -69,7 +69,7 @@ > if (removalType.treeScopeChanged && oldParentOfRemovedTree.isInShadowTree()) { > auto* oldShadowRoot = oldParentOfRemovedTree.containingShadowRoot(); > ASSERT(oldShadowRoot); >- oldShadowRoot->removeSlotElementByName(attributeWithoutSynchronization(nameAttr), *this); >+ oldShadowRoot->removeSlotElementByName(attributeWithoutSynchronization(nameAttr), *this, oldParentOfRemovedTree); > } > > HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree); >@@ -90,10 +90,8 @@ > HTMLElement::attributeChanged(name, oldValue, newValue, reason); > > if (isInShadowTree() && name == nameAttr) { >- if (auto shadowRoot = makeRefPtr(containingShadowRoot())) { >- shadowRoot->removeSlotElementByName(oldValue, *this); >- shadowRoot->addSlotElementByName(newValue, *this); >- } >+ if (auto shadowRoot = makeRefPtr(containingShadowRoot())) >+ shadowRoot->renameSlotElement(*this, oldValue, newValue); > } > } > >Index: LayoutTests/fast/shadow-dom/slotchange-event-bubbling.html >=================================================================== >--- LayoutTests/fast/shadow-dom/slotchange-event-bubbling.html (revision 235483) >+++ LayoutTests/fast/shadow-dom/slotchange-event-bubbling.html (working copy) >@@ -75,7 +75,7 @@ > test_slotchange_event_bubbles('open', true); > > function test_single_slotchange_event_for_nested_slots(outerMode, innerMode, connected) { >- promise_test(() => { >+ promise_test(async () => { > const outerHost = document.createElement('outer-host'); > if (connected) > document.body.appendChild(outerHost); >@@ -91,6 +91,8 @@ > const innerSlotParent = innerShadow.querySelector('div'); > const innerSlot = innerShadow.querySelector('slot'); > >+ await Promise.resolve(); >+ > const observer = create_slotchange_observer(); > observer.observe(outerSlot); > observer.observe(innerHost); >@@ -109,23 +111,23 @@ > outerHost.textContent = ' '; > > assert_array_equals(observer.takeLog(), [], 'slotchange event must not be fired synchronously'); >- return Promise.resolve().then(() => { >- const log = observer.takeLog(); >+ await Promise.resolve(); > >- const events = new Set(log.map((entry) => entry.event)); >- assert_equals(events.size, 1, 'Mutating the assigned content of a slot must fire exactly one slotchange event'); >+ const log = observer.takeLog(); > >- assert_slotchange_log(log[0], outerSlot, outerSlot, 'slotchange event must be dispatched at the slot element first'); >- assert_slotchange_log(log[1], innerSlot, outerSlot, 'slotchange event must bubble up from a slot element to its assigned slot'); >- assert_slotchange_log(log[2], innerSlotParent, outerSlot, 'slotchange event must bubble up to the parent node of a slot'); >- assert_slotchange_log(log[3], innerShadow, outerSlot, 'slotchange event must bubble up to the shadow root'); >- assert_slotchange_log(log[4], innerHost, outerSlot, >- 'slotchange event must bubble up to the shadow host if the host is a descendent of the tree in which the event was fired'); >- assert_slotchange_log(log[5], outerHostParent, outerSlot, >- 'slotchange event must bubble up to the parent of an inner shadow host'); >- assert_slotchange_log(log[6], outerShadow, outerSlot, 'slotchange event must bubble up to the shadow root'); >- assert_equals(log.length, 7, 'slotchange must not bubble beyond the shadow root in which the event was fired'); >- }); >+ const events = new Set(log.map((entry) => entry.event)); >+ assert_equals(events.size, 1, 'Mutating the assigned content of a slot must fire exactly one slotchange event'); >+ >+ assert_slotchange_log(log[0], outerSlot, outerSlot, 'slotchange event must be dispatched at the slot element first'); >+ assert_slotchange_log(log[1], innerSlot, outerSlot, 'slotchange event must bubble up from a slot element to its assigned slot'); >+ assert_slotchange_log(log[2], innerSlotParent, outerSlot, 'slotchange event must bubble up to the parent node of a slot'); >+ assert_slotchange_log(log[3], innerShadow, outerSlot, 'slotchange event must bubble up to the shadow root'); >+ assert_slotchange_log(log[4], innerHost, outerSlot, >+ 'slotchange event must bubble up to the shadow host if the host is a descendent of the tree in which the event was fired'); >+ assert_slotchange_log(log[5], outerHostParent, outerSlot, >+ 'slotchange event must bubble up to the parent of an inner shadow host'); >+ assert_slotchange_log(log[6], outerShadow, outerSlot, 'slotchange event must bubble up to the shadow root'); >+ assert_equals(log.length, 7, 'slotchange must not bubble beyond the shadow root in which the event was fired'); > }, `A single slotchange event must bubble from a ${connected ? 'connected' : 'disconnected'} ${innerMode}-mode shadow tree to` > + `a slot in its parent ${outerMode}-mode shadow tree`); > } >Index: LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation-expected.txt >=================================================================== >--- LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation-expected.txt (nonexistent) >+++ LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation-expected.txt (working copy) >@@ -0,0 +1,55 @@ >+ >+PASS slotchange event should not fire when inserting a default slot element into a shadow host with no children in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a named slot element into a shadow host with no children in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a default slot element when there is an element assigned to another slot in a disconnected open mode shadow root >+PASS slotchange event should fire when inserting a default slot element when there is an element assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should fire when inserting a default slot element when there is a Text node assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a named slot element when there is an element assigned to another slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a named slot element when there is an element assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should fire when inserting a named slot element when there is an element assigned to the slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a named slot element when there is a Text node assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a default slot element after an existing default slot in a disconnected open mode shadow root >+PASS slotchange event should fire when inserting a default slot element before an existing default slot in the tree order in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a default slot element between two existing default slots in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a named slot element after an existing slot of the same name in a disconnected open mode shadow root >+PASS slotchange event should fire when inserting a named slot element before an existing slot of the same name in the tree order in a disconnected open mode shadow root >+PASS slotchange event should not fire when inserting a named slot element between two existing slots of the same name in a disconnected open mode shadow root >+PASS slotchange event should fire when inserting the ancestor of a default slot element before an existing default slot in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire when inserting the ancestor of a named slot element before an existing named slot element in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on the first default slot inserted in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on the first named slot inserted in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on the first named slot of the same name inserted in the tree order in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a default slot element into a shadow host with no children in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a named slot element into a shadow host with no children in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a default slot element when there is an element assigned to another slot in a disconnected open mode shadow root >+PASS slotchange event should fire when removing a default slot element when there is an element assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should fire when removing a default slot element when there is a Text node assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a named slot element when there is an element assigned to another slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a named slot element when there is an element assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should fire when removing a named slot element when there is an element assigned to the slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a named slot element when there is a Text node assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a default slot element after an existing default slot in a disconnected open mode shadow root >+PASS slotchange event should fire when removing the first default slot element even if it had duplicates in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a duplicate default slot, which is the first child of a default slot element in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a named slot element after an existing named slot of the same name in a disconnected open mode shadow root >+PASS slotchange event should fire when removing the first named slot element even if it had duplicates in a disconnected open mode shadow root >+PASS slotchange event should not fire when removing a duplicate named slot, which is the first child of a named slot element of the same name in a disconnected open mode shadow root >+PASS slotchange event should fire when removing the ancestor of a default slot element before another default slot in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire when removing the ancestor of a named slot element before another slot of the same name in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on the first default slot removed in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on the first named slot removed in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on the first default slot removed in the tree order when replacing all children in a disconnected open mode shadow root >+PASS slotchange event should fire on the first default slot removed in the tree order when replacing all children in a disconnected open mode shadow root >+PASS slotchange event should fire on the first named slot of the same name inserted in the tree order in a disconnected open mode shadow root >+PASS slotchange event should not fire when renaming a default slot element to a named slot when there are no assigned nodes in a disconnected open mode shadow root >+PASS slotchange event should fire when renaming a default slot element to a named slot when there is an assigned node to the named slot in a disconnected open mode shadow root >+PASS slotchange event should fire when renaming a default slot element to a named slot when there is an assigned node to the default slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when renaming a named slot element to a default slot when there are no assigned nodes in a disconnected open mode shadow root >+PASS slotchange event should fire when renaming a named slot element to a default slot when there is a node assigned to the default slot in a disconnected open mode shadow root >+PASS slotchange event should fire when renaming a named slot element to a default slot when there is a node assigned to the named slot in a disconnected open mode shadow root >+PASS slotchange event should not fire when renaming a named slot element when there are no assigned nodes in a disconnected open mode shadow root >+PASS slotchange event should fire when renaming a named slot element when there is an assigned node to the slot of the new name in a disconnected open mode shadow root >+PASS slotchange event should fire when renaming a named slot element when there is an assigned node to the slot of the old name in a disconnected open mode shadow root >+PASS slotchange event should not fire when renaming the second slot element of a given name and there is no assigned node of the new name in a disconnected open mode shadow root >+PASS slotchange event should fire when renaming the slot element of a given name and there is an assigned node and a second slot element in a disconnected open mode shadow root >+ >Index: LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation.html >=================================================================== >--- LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation.html (nonexistent) >+++ LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation.html (working copy) >@@ -0,0 +1,258 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<title>Shadow DOM: slotchange event when inserting or removing a slot element</title> >+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> >+<link rel="help" href="https://dom.spec.whatwg.org/#signaling-slot-change"> >+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-insert"> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+</head> >+<body> >+<div id="log"></div> >+<script> >+ >+function generateTests(...args) { >+/* testMutatingSlot('closed', true, ...args); >+ testMutatingSlot('closed', false, ...args); >+ testMutatingSlot('open', true, ...args);*/ >+ testMutatingSlot('open', false, ...args); >+} >+ >+let slotchangeEventTargets; >+function testMutatingSlot(mode, connectedToDocument, hostContent, shadowContent, mutateSlot, expectedTargets, description) >+{ >+ promise_test(async function () { >+ const host = document.createElement('div'); >+ if (connectedToDocument) >+ document.body.appendChild(host); >+ if (hostContent) >+ host.innerHTML = hostContent; >+ >+ const shadowRoot = host.attachShadow({mode}); >+ slotchangeEventTargets = []; >+ if (shadowContent) { >+ shadowRoot.innerHTML = shadowContent; >+ addEventListners(shadowRoot); >+ } >+ >+ await Promise.resolve(); >+ slotchangeEventTargets.length = 0; >+ >+ mutateSlot(shadowRoot); >+ await Promise.resolve(); >+ >+ assert_array_equals(slotchangeEventTargets, expectedTargets); >+ >+ host.remove(); >+ }, description + ` in a ${connectedToDocument ? 'connected' : 'disconnected'} ${mode} mode shadow root`); >+} >+ >+function addEventListners(container) >+{ >+ for (const existingSlots of container.querySelectorAll('slot')) >+ existingSlots.addEventListener('slotchange', (event) => slotchangeEventTargets.push(event.target.id)); >+} >+ >+function newSlot(slotName) >+{ >+ const slot = document.createElement('slot'); >+ slot.id = 'newSlot'; >+ if (slotName) >+ slot.name = slotName; >+ slot.addEventListener('slotchange', (event) => slotchangeEventTargets.push(event.target.id)); >+ return slot; >+} >+ >+// Insertions >+ >+generateTests(null, null, (shadowRoot) => shadowRoot.append(newSlot()), [], >+ 'slotchange event should not fire when inserting a default slot element into a shadow host with no children'); >+generateTests(null, null, (shadowRoot) => shadowRoot.append(newSlot('foo')), [], >+ 'slotchange event should not fire when inserting a named slot element into a shadow host with no children'); >+ >+generateTests('<span slot="bar"></span>', null, (shadowRoot) => shadowRoot.append(newSlot()), [], >+ 'slotchange event should not fire when inserting a default slot element when there is an element assigned to another slot'); >+generateTests('<span></span>', null, (shadowRoot) => shadowRoot.append(newSlot()), ['newSlot'], >+ 'slotchange event should fire when inserting a default slot element when there is an element assigned to the default slot'); >+generateTests('hello', null, (shadowRoot) => shadowRoot.append(newSlot()), ['newSlot'], >+ 'slotchange event should fire when inserting a default slot element when there is a Text node assigned to the default slot'); >+ >+generateTests('<span slot="bar"></span>', null, (shadowRoot) => shadowRoot.append(newSlot('foo')), [], >+ 'slotchange event should not fire when inserting a named slot element when there is an element assigned to another slot'); >+generateTests('<span></span>', null, (shadowRoot) => shadowRoot.append(newSlot('foo')), [], >+ 'slotchange event should not fire when inserting a named slot element when there is an element assigned to the default slot'); >+generateTests('<span slot="foo"></span>', null, (shadowRoot) => shadowRoot.append(newSlot('foo')), ['newSlot'], >+ 'slotchange event should fire when inserting a named slot element when there is an element assigned to the slot'); >+generateTests('hello', null, (shadowRoot) => shadowRoot.append(newSlot('foo')), [], >+ 'slotchange event should not fire when inserting a named slot element when there is a Text node assigned to the default slot'); >+ >+generateTests('<span></span>', '<slot id="oldSlot"></slot>', >+ (shadowRoot) => shadowRoot.append(newSlot()), [], >+ 'slotchange event should not fire when inserting a default slot element after an existing default slot'); >+generateTests('<span></span>', '<slot id="oldSlot"></slot>', >+ (shadowRoot) => shadowRoot.prepend(newSlot()), ['newSlot', 'oldSlot'], >+ 'slotchange event should fire when inserting a default slot element before an existing default slot in the tree order'); >+generateTests('<span></span>', '<slot id="oldSlot1"></slot><slot id="oldSlot2"></slot>', >+ (shadowRoot) => shadowRoot.firstChild.after(newSlot()), [], >+ 'slotchange event should not fire when inserting a default slot element between two existing default slots'); >+ >+generateTests('<span slot="foo"></span>', '<slot id="oldSlot" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.append(newSlot('foo')), [], >+ 'slotchange event should not fire when inserting a named slot element after an existing slot of the same name'); >+generateTests('<span slot="foo"></span>', '<slot id="oldSlot" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.prepend(newSlot('foo')), ['newSlot', 'oldSlot'], >+ 'slotchange event should fire when inserting a named slot element before an existing slot of the same name in the tree order'); >+generateTests('<span name="foo"></span>', '<slot id="oldSlot1" name="foo"></slot><slot id="oldSlot2" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.firstChild.after(newSlot('foo')), [], >+ 'slotchange event should not fire when inserting a named slot element between two existing slots of the same name'); >+ >+generateTests('<span></span>', '<div><slot id="oldSlot"></slot></div>', >+ (shadowRoot) => { >+ const ancestor = document.createElement('div'); >+ ancestor.innerHTML = '<span><b><slot id="newSlot"></slot></b></span>'; >+ addEventListners(ancestor); >+ shadowRoot.prepend(ancestor); >+ }, ['newSlot', 'oldSlot'], >+ 'slotchange event should fire when inserting the ancestor of a default slot element before an existing default slot in the tree order'); >+generateTests('<span slot="foo"></span>', '<div><slot name="foo" id="oldSlot"></slot></div>', >+ (shadowRoot) => { >+ const ancestor = document.createElement('div'); >+ ancestor.innerHTML = '<span><b><slot id="newSlot" name="foo"></slot></b></span>'; >+ addEventListners(ancestor); >+ shadowRoot.prepend(ancestor); >+ }, ['newSlot', 'oldSlot'], >+ 'slotchange event should fire when inserting the ancestor of a named slot element before an existing named slot element in the tree order'); >+ >+generateTests('<span></span>', null, >+ (shadowRoot) => { >+ const ancestor = document.createElement('div'); >+ ancestor.innerHTML = '<p><slot id="slot1"></slot><b><slot id="slot2"></slot></b><slot id="slot3"></slot></p>'; >+ addEventListners(ancestor); >+ shadowRoot.append(ancestor); >+ }, ['slot1'], >+ 'slotchange event should fire on the first default slot inserted in the tree order'); >+generateTests('<span slot="foo"></span>', null, >+ (shadowRoot) => { >+ const ancestor = document.createElement('div'); >+ ancestor.innerHTML = '<p><slot id="slot1" name="foo"></slot><b><slot id="slot2" name="foo"></slot></b><slot id="slot3" name="foo"></slot></p>'; >+ addEventListners(ancestor); >+ shadowRoot.append(ancestor); >+ }, ['slot1'], >+ 'slotchange event should fire on the first named slot inserted in the tree order'); >+ >+generateTests('<span slot="b"></span>', null, >+ (shadowRoot) => { >+ const ancestor = document.createElement('div'); >+ ancestor.innerHTML = '<p><slot id="slot1" name="a"></slot><b><slot id="slot2" name="b"></slot></b><slot id="slot3" name="b"></slot></p>'; >+ addEventListners(ancestor); >+ shadowRoot.append(ancestor); >+ }, ['slot2'], >+ 'slotchange event should fire on the first named slot of the same name inserted in the tree order'); >+ >+ >+// Removals >+ >+generateTests(null, '<slot id="oldSlot"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), [], >+ 'slotchange event should not fire when removing a default slot element into a shadow host with no children'); >+generateTests(null, '<slot id="oldSlot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), [], >+ 'slotchange event should not fire when removing a named slot element into a shadow host with no children'); >+ >+generateTests('<span slot="bar"></span>', '<slot id="oldSlot"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), [], >+ 'slotchange event should not fire when removing a default slot element when there is an element assigned to another slot'); >+generateTests('<span></span>', '<slot id="oldSlot"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), ['oldSlot'], >+ 'slotchange event should fire when removing a default slot element when there is an element assigned to the default slot'); >+generateTests('hello', '<slot id="oldSlot"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), ['oldSlot'], >+ 'slotchange event should fire when removing a default slot element when there is a Text node assigned to the default slot'); >+ >+generateTests('<span slot="bar"></span>', '<slot id="oldSlot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), [], >+ 'slotchange event should not fire when removing a named slot element when there is an element assigned to another slot'); >+generateTests('<span></span>', '<slot id="oldSlot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), [], >+ 'slotchange event should not fire when removing a named slot element when there is an element assigned to the default slot'); >+generateTests('<span slot="foo"></span>', '<slot id="oldSlot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), ['oldSlot'], >+ 'slotchange event should fire when removing a named slot element when there is an element assigned to the slot'); >+generateTests('hello', '<slot id="oldSlot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').remove(), [], >+ 'slotchange event should not fire when removing a named slot element when there is a Text node assigned to the default slot'); >+ >+generateTests('<span></span>', '<slot id="slot1"></slot><slot id="slot2"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot2').remove(), [], >+ 'slotchange event should not fire when removing a default slot element after an existing default slot'); >+generateTests('<span></span>', '<slot id="slot1"></slot><slot id="slot2"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot1').remove(), ['slot1', 'slot2'], >+ 'slotchange event should fire when removing the first default slot element even if it had duplicates'); >+generateTests('<span></span>', '<slot id="slot1"><slot id="slot2"></slot></slot><slot id="slot3"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot2').remove(), [], >+ 'slotchange event should not fire when removing a duplicate default slot, which is the first child of a default slot element'); >+ >+generateTests('<span slot="foo"></span>', '<slot id="slot1" name="foo"></slot><slot id="slot2" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot2').remove(), [], >+ 'slotchange event should not fire when removing a named slot element after an existing named slot of the same name'); >+generateTests('<span slot="foo"></span>', '<slot id="slot1" name="foo"></slot><slot id="slot2" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot1').remove(), ['slot1', 'slot2'], >+ 'slotchange event should fire when removing the first named slot element even if it had duplicates'); >+generateTests('<span slot="foo"></span>', '<slot id="slot1" name="foo"><slot id="slot2" name="foo"></slot></slot><slot id="slot3" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot2').remove(), [], >+ 'slotchange event should not fire when removing a duplicate named slot, which is the first child of a named slot element of the same name'); >+ >+generateTests('<span></span>', '<div><span><b><slot id="slot1"></slot></b></span></div><slot id="slot2"></slot>', >+ (shadowRoot) => shadowRoot.querySelector('div').remove(), ['slot1', 'slot2'], >+ 'slotchange event should fire when removing the ancestor of a default slot element before another default slot in the tree order'); >+generateTests('<span></span>', '<div><span><b><slot id="slot1"></slot></b></span></div><slot id="slot2"></slot>', >+ (shadowRoot) => shadowRoot.querySelector('div').remove(), ['slot1', 'slot2'], >+ 'slotchange event should fire when removing the ancestor of a named slot element before another slot of the same name in the tree order'); >+ >+generateTests('<span></span>', '<div><p><slot id="slot1"></slot><b><slot id="slot2"></slot></b><slot id="slot3"></slot></p></div>', >+ (shadowRoot) => shadowRoot.querySelector('p').remove(), ['slot1'], >+ 'slotchange event should fire on the first default slot removed in the tree order'); >+generateTests('<span slot="foo"></span>', >+ '<div><p><slot id="slot1" name="foo"></slot><b><slot id="slot2" name="foo"></slot></b><slot id="slot3" name="foo"></slot></p></div>', >+ (shadowRoot) => shadowRoot.querySelector('p').remove(), ['slot1'], >+ 'slotchange event should fire on the first named slot removed in the tree order'); >+ >+generateTests('<span></span>', '<div><p><slot id="slot1"></slot><b><slot id="slot2"></slot></b><slot id="slot3"></slot></p></div>', >+ (shadowRoot) => shadowRoot.querySelector('p').textContent = '', ['slot1'], >+ 'slotchange event should fire on the first default slot removed in the tree order when replacing all children'); >+ >+generateTests('<span></span>', '<div><p><slot id="slot1"></slot><b><slot id="slot2"></slot></b><slot id="slot3"></slot></p></div>', >+ (shadowRoot) => shadowRoot.querySelector('p').textContent = '', ['slot1'], >+ 'slotchange event should fire on the first default slot removed in the tree order when replacing all children'); >+ >+generateTests('<span slot="b"></span>', >+ '<p><slot id="slot1" name="a"></slot><b><slot id="slot2" name="b"></slot></b><slot id="slot3" name="b"></slot></p>', >+ (shadowRoot) => shadowRoot.querySelector('p').remove(), ['slot2'], >+ 'slotchange event should fire on the first named slot of the same name inserted in the tree order'); >+ >+ >+// Changing the name >+ >+generateTests(null, '<slot id="slot"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = 'foo', [], >+ 'slotchange event should not fire when renaming a default slot element to a named slot when there are no assigned nodes'); >+generateTests('<span slot="foo"></span>', '<slot id="slot"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = 'foo', ['slot'], >+ 'slotchange event should fire when renaming a default slot element to a named slot when there is an assigned node to the named slot'); >+generateTests('hello', '<slot id="slot"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = 'foo', ['slot'], >+ 'slotchange event should fire when renaming a default slot element to a named slot when there is an assigned node to the default slot'); >+ >+generateTests(null, '<slot id="slot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = null, [], >+ 'slotchange event should not fire when renaming a named slot element to a default slot when there are no assigned nodes'); >+generateTests('<span></span>', '<slot id="slot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = '', ['slot'], >+ 'slotchange event should fire when renaming a named slot element to a default slot when there is a node assigned to the default slot'); >+generateTests('<span slot="foo"></span>', '<slot id="slot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = '', ['slot'], >+ 'slotchange event should fire when renaming a named slot element to a default slot when there is a node assigned to the named slot'); >+ >+generateTests(null, '<slot id="slot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = 'bar', [], >+ 'slotchange event should not fire when renaming a named slot element when there are no assigned nodes'); >+generateTests('<span slot="bar"></span>', '<slot id="slot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = 'bar', ['slot'], >+ 'slotchange event should fire when renaming a named slot element when there is an assigned node to the slot of the new name'); >+generateTests('<span slot="foo"></span>', '<slot id="slot" name="foo"></slot>', (shadowRoot) => shadowRoot.querySelector('slot').name = 'bar', ['slot'], >+ 'slotchange event should fire when renaming a named slot element when there is an assigned node to the slot of the old name'); >+ >+generateTests('<span slot="foo"></span>', '<slot id="slot1" name="foo"></slot><slot id="slot2" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot2').name = 'bar', [], >+ 'slotchange event should not fire when renaming the second slot element of a given name and there is no assigned node of the new name'); >+generateTests('<span slot="foo"></span>', '<slot id="slot1" name="foo"></slot><slot id="slot2" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot1').name = 'bar', ['slot1', 'slot2'], >+ 'slotchange event should fire when renaming the slot element of a given name and there is an assigned node and a second slot element'); >+ >+</script> >+</body> >+</html> >Index: LayoutTests/fast/shadow-dom/slotchange-in-fallback.html >=================================================================== >--- LayoutTests/fast/shadow-dom/slotchange-in-fallback.html (revision 235483) >+++ LayoutTests/fast/shadow-dom/slotchange-in-fallback.html (working copy) >@@ -19,14 +19,14 @@ > testMutatingSlot('open', false, ...args); > } > >-function testMutatingSlot(mode, connectedToDocument, shadowContent, slotName, prepareSlot, mutateSlot, description) >+function testMutatingSlot(mode, connectedToDocument, hostContent, slotName, prepareSlot, mutateSlot, description) > { > promise_test(async function () { > const host = document.createElement('div'); > if (connectedToDocument) > document.body.appendChild(host); >- if (shadowContent) >- host.innerHTML = shadowContent; >+ if (hostContent) >+ host.innerHTML = hostContent; > > const shadowRoot = host.attachShadow({mode}); > >@@ -44,9 +44,9 @@ > > mutateSlot(slot); > await Promise.resolve(); >- assert_equals(eventCount, shadowContent ? 0 : 1); >+ assert_equals(eventCount, hostContent ? 0 : 1); > >- slot.remove(); >+ host.remove(); > }, description + ` in a ${connectedToDocument ? 'connected' : 'disconnected'} ${mode} mode shadow root`); > } > >Index: LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-customelements-expected.txt >=================================================================== >--- LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-customelements-expected.txt (revision 235483) >+++ LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-customelements-expected.txt (working copy) >@@ -1,3 +1,3 @@ > >-FAIL slotchange must fire on initialization of custom elements with slotted children assert_true: expected true got false >+PASS slotchange must fire on initialization of custom elements with slotted children > >Index: LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt >=================================================================== >--- LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt (revision 235483) >+++ LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt (working copy) >@@ -1,10 +1,4 @@ >-CONSOLE MESSAGE: line 2659: Error: assert_equals: slotchange must not be fired on a slot element if the assigned nodes changed after the slot was removed expected 2 but got 0 >-CONSOLE MESSAGE: line 2659: Error: assert_equals: slotchange must not be fired on a slot element if the assigned nodes changed after the slot was removed expected 2 but got 0 >-CONSOLE MESSAGE: line 2659: Error: assert_equals: slotchange must not be fired on a slot element if the assigned nodes changed after the slot was removed expected 2 but got 0 >-CONSOLE MESSAGE: line 2659: Error: assert_equals: slotchange must not be fired on a slot element if the assigned nodes changed after the slot was removed expected 2 but got 0 > >-Harness Error (FAIL), message = Error: assert_equals: slotchange must not be fired on a slot element if the assigned nodes changed after the slot was removed expected 2 but got 0 >- > PASS slotchange event must fire on a default slot element inside an open shadow root in a document > PASS slotchange event must fire on a default slot element inside a closed shadow root in a document > PASS slotchange event must fire on a default slot element inside an open shadow root not in a document >@@ -17,10 +11,10 @@ > PASS slotchange event must not fire on a slot element inside a closed shadow root in a document when another slot's assigned nodes change > PASS 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 > PASS 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 >-FAIL slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside an open shadow root in a document assert_equals: slotchange must be fired on a slot element if there is assigned nodes when the slot was inserted expected 1 but got 0 >-FAIL slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside a closed shadow root in a document assert_equals: slotchange must be fired on a slot element if there is assigned nodes when the slot was inserted expected 1 but got 0 >-FAIL slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside an open shadow root not in a document assert_equals: slotchange must be fired on a slot element if there is assigned nodes when the slot was inserted expected 1 but got 0 >-FAIL slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside a closed shadow root not in a document assert_equals: slotchange must be fired on a slot element if there is assigned nodes when the slot was inserted expected 1 but got 0 >+PASS slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside an open shadow root in a document >+PASS slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside a closed shadow root in a document >+PASS slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside an open shadow root not in a document >+PASS slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside a closed shadow root not in a document > PASS 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 > PASS 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 > PASS 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 >@@ -29,10 +23,10 @@ > PASS 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 > PASS 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 > PASS 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 >-FAIL slotchange event must fire on a slot element inside an open shadow root in a document when nested slots's contents change assert_equals: slotchange event's target must be the inner slot element at 1st slotchange expected Element node <slot></slot> but got Element node <slot></slot> >-FAIL slotchange event must fire on a slot element inside a closed shadow root in a document when nested slots's contents change assert_equals: slotchange event's target must be the inner slot element at 1st slotchange expected Element node <slot></slot> but got Element node <slot></slot> >-FAIL slotchange event must fire on a slot element inside an open shadow root not in a document when nested slots's contents change assert_equals: slotchange event's target must be the inner slot element at 1st slotchange expected Element node <slot></slot> but got Element node <slot></slot> >-FAIL slotchange event must fire on a slot element inside a closed shadow root not in a document when nested slots's contents change assert_equals: slotchange event's target must be the inner slot element at 1st slotchange expected Element node <slot></slot> but got Element node <slot></slot> >+PASS slotchange event must fire on a slot element inside an open shadow root in a document when nested slots's contents change >+PASS slotchange event must fire on a slot element inside a closed shadow root in a document when nested slots's contents change >+PASS slotchange event must fire on a slot element inside an open shadow root not in a document when nested slots's contents change >+PASS slotchange event must fire on a slot element inside a closed shadow root not in a document when nested slots's contents change > PASS 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 > PASS 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 > PASS 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 >Index: LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-expected.txt >=================================================================== >--- LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-expected.txt (revision 235483) >+++ LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-expected.txt (working copy) >@@ -1,19 +1,17 @@ > >-Harness Error (TIMEOUT), message = null >- > PASS slotchange event: Append a child to a host. > PASS slotchange event: Remove a child from a host. > PASS slotchange event: Remove a child before adding an event listener. > PASS slotchange event: Change slot= attribute to make it un-assigned. >-TIMEOUT slotchange event: Change slot's name= attribute so that none is assigned. Test timed out >+PASS slotchange event: Change slot's name= attribute so that none is assigned. > PASS slotchange event: Change slot= attribute to make it assigned. >-TIMEOUT slotchange event: Change slot's name= attribute so that a node is assigned to the slot. Test timed out >+PASS slotchange event: Change slot's name= attribute so that a node is assigned to the slot. > PASS slotchange event: Add a fallback content. > PASS slotchange event: Remove a fallback content. > PASS slotchange event: Add a fallback content to nested slots. > PASS slotchange event: Remove a fallback content from nested slots. >-TIMEOUT slotchange event: Insert a slot before an existing slot. Test timed out >-TIMEOUT slotchange event: Remove a preceding slot. Test timed out >+PASS slotchange event: Insert a slot before an existing slot. >+PASS slotchange event: Remove a preceding slot. > PASS slotchange event: A slot is assigned to another slot. > PASS slotchange event: Even if distributed nodes do not change, slotchange should be fired if assigned nodes are changed. >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 189144
:
348477
|
348486
|
348490
|
348583
|
348594
|
348697
|
348699
|
348709
|
348710
|
348711
|
348839