WebKit Bugzilla
Attachment 348839 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]
Fixes the bug
bug-189144-20180904123657.patch (text/plain), 105.45 KB, created by
Ryosuke Niwa
on 2018-09-04 12:36:59 PDT
(
hide
)
Description:
Fixes the bug
Filename:
MIME Type:
Creator:
Ryosuke Niwa
Created:
2018-09-04 12:36:59 PDT
Size:
105.45 KB
patch
obsolete
>Index: Source/WebCore/ChangeLog >=================================================================== >--- Source/WebCore/ChangeLog (revision 235590) >+++ Source/WebCore/ChangeLog (working copy) >@@ -1,3 +1,127 @@ >+2018-09-03 Ryosuke Niwa <rniwa@webkit.org> >+ >+ slotchange event doesn't get fired when inserting, removing, or renaming slot elements >+ https://bugs.webkit.org/show_bug.cgi?id=189144 >+ <rdar://problem/43871061> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ This patch implements `slotchange` event when a slot element is inserted, removed, or renamed in the DOM tree. >+ Let us consider each scenairo separately. >+ >+ Insertion (https://dom.spec.whatwg.org/#concept-node-insert): In this case, we must fire `slotchange` event on >+ slot elements whose assigned nodes have changed in the tree order. When there is at most one slot element for >+ each name, this can be done by simply checking whether each slot has assigned nodes or not. When there are more >+ than one slot element, however, the newly inserted slot element may now become the first slot of a given name, >+ and gain assined nodes while the previously first element loses its assigned nodes. To see if the newly inserted >+ slot element is the first of its kind, we must travere the DOM tree to check the order of that and the previously >+ first slot element. To do this, we resolve the slot elements before start inserting nodes in >+ executeNodeInsertionWithScriptAssertion via SlotAssignment::resolveSlotsBeforeNodeInsertionOrRemoval. Note that >+ when the DOM tree has at most one slot element of its kind, resolveSlotsBeforeNodeInsertionOrRemoval is a no-op >+ and addSlotElementByName continues to operate in O(1). Becasue addSlotElementByName is called on each inserted >+ slot element in the tree order, we do the tree traversal upon finding the first slot element which has more than >+ one of its kind in the current tree. In this case, we resolve all other slot elements and enqueues slotchange >+ event as needed to avoid doing the tree traversal more than once. >+ >+ Removal (https://dom.spec.whatwg.org/#concept-node-remove): In removal, we're concerned with removing the first >+ slot element of its kind. We must fire slotchange event on the remaining slot elements which became the first of >+ its kind after the removal as well as the ones which got removed from the tree if they had assigned nodes. >+ Furthermore, the DOM specification mandates that we first fire slotchange events in the tree from which a node >+ was removed and then in the removed subtree. Because we must only fire slotchange event on the first slot element >+ of its kind which has been removed, we must resolve the first slot elements of each kind before a node removal >+ in removeAllChildrenWithScriptAssertion and removeNodeWithScriptAssertion as we've done for insertion. Again, >+ in the case there was at most one slot element of each kind, resolveSlotsBeforeNodeInsertionOrRemoval is a no-op >+ and removeSlotElementByName would continue to operate in O(1). When there are multiple slot elements for a given >+ kind, we immediately enqueue slotchange event on the slot elements which newly became the first of its kind but >+ delay the enqueuing of slotchange event on the removed slot elements until removeSlotElementByName is called on >+ that element so that enqueuing of slotchange events on the slot elements still remaining in the in the tree would >+ happen before those which got removed as the DOM specification mandates. >+ >+ Rename (https://dom.spec.whatwg.org/#shadow-tree-slots): In the case the slot element's name content attribute >+ is changed, the renamed element might have become the first of its kind or ceased to be the first of its kind. >+ There could be two other slot elements appearing later in the tree order which might have gained or lost assigned >+ nodes as a result. In this case, we invoke the algorithms for removing & inserting the slot with a key difference: >+ we enqueue slotchange event on the renamed slot immediately if it has assigned nodes. >+ >+ To enqueue slotchange event in the tree order, this patch adds oldElement, which is a WeakPtr to a slot element, >+ to SlotAssignment::Slot. This WeakPtr is set to the slot element which used to have assigned nodes prior to the >+ node insertion, removal, or rename but no longer has after the mutation. This patch also adds a slot mutation >+ version number, m_slotMutationVersion, which is incremented whenever a node is about to be inserted or removed, >+ and slot resolution version, m_slotResolutionVersion, which is set to the current slot mutation version number >+ when the full slot resolution is triggered during slot mutations. They are used to avoid redundant tree traversals >+ in resolveSlotsAfterSlotMutation. This patch also makes m_needsToResolveSlotElements compiled in release builds >+ to avoid resolving slot elements when there is at most one slot element of each kind. >+ >+ For insertion, oldElement is set to the slot which used to be the first of its kind before getting set to a slot >+ element being inserted in resolveSlotsAfterSlotMutation. We enqueue slotchange event on the newly inserted slot >+ element at that point (1). Since the slot element which used to be the first of its kind appears after the newly >+ inserted slot element by definition, we're guaranteed to see this oldElement later in the tree traversal upon >+ which we enqueue slotchange event. Note that if this slot element was the first of its kind, then we're simply >+ hitting (1), which is O(1) and does not invoke the tree traversal. >+ >+ For removal, oldElement is set to the slot which used to be the first of its kind. Because this slot element is >+ getting removed, slotchange event must be enqueud after slotchange events have been enqueued on all slot elements >+ still remaining in the tree. To do this, we enqueue slotchange event immediately on the first slot element of >+ its kind during the tree traversal as we encounter it (2), and set oldElement to the previosuly-first-but-removed >+ slot element. slotchange event is enqueued on this slot element when removeSlotElementByName is later invoked via >+ HTMLSlotElement::removedFromAncestor which traverses each removed element in the tree order. Again, if this was >+ the last slot of its kind, we'd simply expedite (2) by enqueuing slotchange event during removeSlotElementByName, >+ which is O(1). >+ >+ When the DOM invokes the concept to replace all children (https://dom.spec.whatwg.org/#concept-node-replace-all), >+ however, this algorithm isn't sufficient because the removal of each child happens one after another. We would >+ either need to resolve slots between each removal, or treat the removal of all children as a single operation. >+ While the DOM specification currently specifies the former beavhior, this patch implements the latter behavior >+ to avoid useless work. See the DOM spec issue: https://github.com/w3c/webcomponents/issues/764 >+ >+ Test: fast/shadow-dom/slotchange-for-slot-mutation.html >+ >+ * dom/ContainerNode.cpp: >+ (WebCore::ContainerNode::removeAllChildrenWithScriptAssertion): Call resolveSlotsBeforeNodeInsertionOrRemoval >+ before start removing children. >+ (WebCore::ContainerNode::removeNodeWithScriptAssertion): Ditto. >+ (WebCore::executeNodeInsertionWithScriptAssertion): Ditto before inserting children. >+ * dom/ShadowRoot.cpp: >+ (WebCore::ShadowRoot::~ShadowRoot): Set m_hasBegunDeletingDetachedChildren to true. This flag is used to supress >+ slotchange events during the shadow tree destruction. >+ (WebCore::ShadowRoot::renameSlotElement): Added. >+ (WebCore::ShadowRoot::removeSlotElementByName): Added oldParentOfRemovedTree as an argument. >+ * dom/ShadowRoot.h: >+ (WebCore::ShadowRoot::shouldFireSlotchangeEvent): Added. >+ * dom/SlotAssignment.cpp: >+ (WebCore::findSlotElement): Added. >+ (WebCore::nextSlotElementSkippingSubtree): Added. >+ (WebCore::SlotAssignment::hasAssignedNodes): Added. Returns true if the slot of a given name has assigned nodes. >+ (WebCore::SlotAssignment::renameSlotElement): Added. >+ (WebCore::SlotAssignment::addSlotElementByName): Call resolveSlotsAfterSlotMutation when slotchange event needs >+ to be dispatched for the current slot and there are more than one slot elements. >+ (WebCore::SlotAssignment::removeSlotElementByName): Ditto. When the slot's oldElement is set to the current slot >+ element, meaning that this slot element used to have assigned nodes, then enqueue slotchange event. It also has >+ a special case for oldParentOfRemovedTree is null when renaming a slot element. In this case, we want to enqueue >+ slot change event immediately on the renamed slot element and any affected elements as in a node insertion since >+ removeSlotElementByName would never be called on a slot element which newly become the first of its kind after >+ a slot rename. >+ (WebCore::SlotAssignment::resolveSlotsAfterSlotMutation): Added. This is the slot resolution algorithm invoked >+ when there are more than one slot elements for a given name. It has two modes dealing with insertion & removal. >+ The insertion mode is also used for renaming a slot element. The firs >+ (WebCore::SlotAssignment::resolveSlotsBeforeNodeInsertionOrRemoval): Added. Resolves all slot elements prior to >+ inserting or removing nodes. In many cases, this should be a no-op since m_needsToResolveSlotElements is set to >+ true only when there are more than one slot element of its kind. >+ (WebCore::SlotAssignment::willRemoveAllChildren): Ditto. Also sets m_willBeRemovingAllChildren to true. >+ (WebCore::SlotAssignment::didChangeSlot): >+ (WebCore::SlotAssignment::resolveAllSlotElements): Use seenFirstElement instead of element to indicate whether >+ we have seen a slot element of given name for consistency with resolveSlotsAfterSlotMutation. >+ * dom/SlotAssignment.h: >+ (WebCore::SlotAssignment::Slot): Added oldElement and seenFirstElement. >+ (WebCore::SlotAssignment): Always compile m_needsToResolveSlotElements. Added m_willBeRemovingAllChildren, >+ m_slotMutationVersion, and m_slotResolutionVersion. >+ (WebCore::ShadowRoot::resolveSlotsBeforeNodeInsertionOrRemoval): Added. Calls the one in SlotAssignment. >+ (WebCore::ShadowRoot::willRemoveAllChildren): Ditto. >+ * html/HTMLSlotElement.cpp: >+ (WebCore::HTMLSlotElement::removedFromAncestor): >+ (WebCore::HTMLSlotElement::attributeChanged): Calls ShadowRoot::renameSlotElement instead of >+ removeSlotElementByName and addSlotElementByName pair. >+ > 2018-09-02 Zalan Bujtas <zalan@apple.com> > > REGRESSION (r191336): RenderFlexibleBox::adjustChildSizeForMinAndMax crashes in std::optional<>::value() >Index: Source/WebCore/dom/ContainerNode.cpp >=================================================================== >--- Source/WebCore/dom/ContainerNode.cpp (revision 235590) >+++ 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 @@ ALWAYS_INLINE NodeVector ContainerNode:: > 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 @@ ALWAYS_INLINE bool ContainerNode::remove > 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 @@ static ALWAYS_INLINE void executeNodeIns > 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 235590) >+++ Source/WebCore/dom/ShadowRoot.cpp (working copy) >@@ -82,6 +82,9 @@ ShadowRoot::~ShadowRoot() > // 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 @@ HTMLSlotElement* ShadowRoot::findAssigne > 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 @@ void ShadowRoot::addSlotElementByName(co > 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 235590) >+++ Source/WebCore/dom/ShadowRoot.h (working copy) >@@ -67,14 +67,18 @@ public: > Element* activeElement() const; > > ShadowRootMode mode() const { return m_type; } >+ bool shouldFireSlotchangeEvent() const { return m_type != ShadowRootMode::UserAgent && !m_hasBegunDeletingDetachedChildren; } > > 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 +107,7 @@ private: > 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 235590) >+++ Source/WebCore/dom/SlotAssignment.cpp (working copy) >@@ -48,6 +48,29 @@ static const AtomicString& slotNameFromS > return slotNameFromAttributeValue(downcast<Element>(child).attributeWithoutSynchronization(slotAttr)); > } > >+#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); >+} >+ > SlotAssignment::SlotAssignment() = default; > > SlotAssignment::~SlotAssignment() = default; >@@ -64,6 +87,23 @@ HTMLSlotElement* SlotAssignment::findAss > return findFirstSlotElement(*slot, shadowRoot); > } > >+inline bool SlotAssignment::hasAssignedNodes(ShadowRoot& shadowRoot, Slot& slot) >+{ >+ if (!m_slotAssignmentsIsValid) >+ assignSlots(shadowRoot); >+ return !slot.assignedNodes.isEmpty(); >+} >+ >+void SlotAssignment::renameSlotElement(HTMLSlotElement& slotElement, const AtomicString& oldName, const AtomicString& newName, ShadowRoot& shadowRoot) >+{ >+ ASSERT(m_slotElementsForConsistencyCheck.contains(&slotElement)); >+ >+ m_slotMutationVersion++; >+ >+ removeSlotElementByName(oldName, slotElement, nullptr, shadowRoot); >+ addSlotElementByName(newName, slotElement, shadowRoot); >+} >+ > void SlotAssignment::addSlotElementByName(const AtomicString& name, HTMLSlotElement& slotElement, ShadowRoot& shadowRoot) > { > #ifndef NDEBUG >@@ -74,29 +114,36 @@ void SlotAssignment::addSlotElementByNam > // FIXME: We should be able to do a targeted reconstruction. > shadowRoot.host()->invalidateStyleAndRenderersForSubtree(); > >- const AtomicString& slotName = slotNameFromAttributeValue(name); >+ auto& slotName = slotNameFromAttributeValue(name); > auto addResult = m_slots.ensure(slotName, [&] { > // Unlike named slots, assignSlots doesn't collect nodes assigned to the default slot > // to avoid always having a vector of all child nodes of a shadow host. > if (slotName == defaultSlotName()) > m_slotAssignmentsIsValid = false; >- > return std::make_unique<Slot>(); > }); >- > auto& slot = *addResult.iterator->value; >- if (!slot.hasSlotElements()) >+ bool needsSlotchangeEvent = shadowRoot.shouldFireSlotchangeEvent() && hasAssignedNodes(shadowRoot, slot); >+ >+ slot.elementCount++; >+ if (slot.elementCount == 1) { > slot.element = makeWeakPtr(slotElement); >- else { >+ if (needsSlotchangeEvent) >+ slotElement.enqueueSlotChangeEvent(); >+ return; >+ } >+ >+ if (!needsSlotchangeEvent) { >+ ASSERT(slot.element || m_needsToResolveSlotElements); > slot.element = nullptr; >-#ifndef NDEBUG > m_needsToResolveSlotElements = true; >-#endif >+ return; > } >- slot.elementCount++; >+ >+ resolveSlotsAfterSlotMutation(shadowRoot, SlotMutationType::Insertion); > } > >-void SlotAssignment::removeSlotElementByName(const AtomicString& name, HTMLSlotElement& slotElement, ShadowRoot& shadowRoot) >+void SlotAssignment::removeSlotElementByName(const AtomicString& name, HTMLSlotElement& slotElement, ContainerNode* oldParentOfRemovedTree, ShadowRoot& shadowRoot) > { > #ifndef NDEBUG > ASSERT(m_slotElementsForConsistencyCheck.contains(&slotElement)); >@@ -108,15 +155,111 @@ void SlotAssignment::removeSlotElementBy > > 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; >+ if (needsSlotchangeEvent && m_slotResolutionVersion != m_slotMutationVersion) >+ slotElement.enqueueSlotChangeEvent(); >+ return; >+ } >+ >+ if (!needsSlotchangeEvent) { >+ ASSERT(slot->element || m_needsToResolveSlotElements); > slot->element = nullptr; >-#ifndef NDEBUG > m_needsToResolveSlotElements = true; >+ return; >+ } >+ >+ bool elementWasRenamed = !oldParentOfRemovedTree; >+ if (elementWasRenamed && slot->element == &slotElement) >+ slotElement.enqueueSlotChangeEvent(); >+ >+ if (!slot->element) // A previous invocation to resolveSlotsAfterSlotMutation during this removal has updated this slot. >+ ASSERT(m_slotResolutionVersion == m_slotMutationVersion && !findSlotElement(shadowRoot, name)); >+ else { >+ resolveSlotsAfterSlotMutation(shadowRoot, elementWasRenamed ? SlotMutationType::Insertion : SlotMutationType::Removal, >+ m_willBeRemovingAllChildren ? oldParentOfRemovedTree : nullptr); >+ } >+ >+ if (slot->oldElement == &slotElement) { >+ slotElement.enqueueSlotChangeEvent(); >+ slot->oldElement = nullptr; >+ } >+} >+ >+void SlotAssignment::resolveSlotsAfterSlotMutation(ShadowRoot& shadowRoot, SlotMutationType mutationType, ContainerNode* subtreeToSkip) >+{ >+ if (m_slotResolutionVersion == m_slotMutationVersion) >+ return; >+ m_slotResolutionVersion = m_slotMutationVersion; >+ >+ ASSERT(!subtreeToSkip || mutationType == SlotMutationType::Removal); >+ m_needsToResolveSlotElements = false; >+ >+ for (auto& slot : m_slots.values()) >+ slot->seenFirstElement = false; >+ >+ unsigned slotCount = 0; >+ HTMLSlotElement* currentElement = nextSlotElementSkippingSubtree(shadowRoot, subtreeToSkip); >+ for (; currentElement; currentElement = nextSlotElementSkippingSubtree(*currentElement, subtreeToSkip)) { >+ auto& currentSlotName = slotNameFromAttributeValue(currentElement->attributeWithoutSynchronization(nameAttr)); >+ auto* currentSlot = m_slots.get(currentSlotName); >+ if (!currentSlot) { >+ // A new slot may have been inserted with this node but appears later in the tree order. >+ // Such a slot would go through the fast path in addSlotElementByName, >+ // and any subsequently inserted slot of the same name would not result in any slotchange or invokation of this function. >+ ASSERT(mutationType == SlotMutationType::Insertion); >+ continue; >+ } >+ if (currentSlot->seenFirstElement) { >+ if (mutationType == SlotMutationType::Insertion && currentSlot->oldElement == currentElement) { >+ currentElement->enqueueSlotChangeEvent(); >+ currentSlot->oldElement = nullptr; >+ } >+ continue; >+ } >+ currentSlot->seenFirstElement = true; >+ slotCount++; >+ ASSERT(currentSlot->element || !hasAssignedNodes(shadowRoot, *currentSlot)); >+ if (currentSlot->element != currentElement) { >+ if (hasAssignedNodes(shadowRoot, *currentSlot)) { >+ currentSlot->oldElement = WTFMove(currentSlot->element); >+ currentElement->enqueueSlotChangeEvent(); >+ } >+ currentSlot->element = makeWeakPtr(*currentElement); >+ } >+ } >+ >+ if (slotCount == m_slots.size()) >+ return; >+ >+ if (mutationType == SlotMutationType::Insertion) { >+ // This code path is taken only when continue above for !currentSlot is taken. >+ // i.e. there is a new slot being inserted into the tree but we have yet to invoke addSlotElementByName on it. >+#if !ASSERT_DISABLED >+ for (auto& entry : m_slots) >+ ASSERT(entry.value->seenFirstElement || !findSlotElement(shadowRoot, entry.key)); > #endif >+ return; >+ } >+ >+ 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->oldElement = WTFMove(slot->element); >+ slot->element = nullptr; > } >- ASSERT(slot->element || m_needsToResolveSlotElements); > } > > void SlotAssignment::slotFallbackDidChange(HTMLSlotElement& slotElement, ShadowRoot& shadowRoot) >@@ -129,6 +272,23 @@ void SlotAssignment::slotFallbackDidChan > 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 +305,8 @@ void SlotAssignment::didChangeSlot(const > > 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 +354,12 @@ HTMLSlotElement* SlotAssignment::findFir > > 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 +368,9 @@ void SlotAssignment::resolveAllSlotEleme > 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 235590) >+++ Source/WebCore/dom/SlotAssignment.h (working copy) >@@ -50,9 +50,12 @@ public: > > 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&); >@@ -72,10 +75,16 @@ private: > bool shouldResolveSlotElement() { return !element && elementCount; } > > WeakPtr<HTMLSlotElement> element; >+ WeakPtr<HTMLSlotElement> oldElement; > unsigned elementCount { 0 }; >+ bool seenFirstElement { false }; > Vector<Node*> assignedNodes; > }; >- >+ >+ bool hasAssignedNodes(ShadowRoot&, Slot&); >+ enum class SlotMutationType { Insertion, Removal }; >+ void resolveSlotsAfterSlotMutation(ShadowRoot&, SlotMutationType, ContainerNode* oldParentOfRemovedTree = nullptr); >+ > virtual const AtomicString& slotNameForHostChild(const Node&) const; > > HTMLSlotElement* findFirstSlotElement(Slot&, ShadowRoot&); >@@ -88,12 +97,27 @@ private: > > #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 235590) >+++ Source/WebCore/html/HTMLSlotElement.cpp (working copy) >@@ -69,7 +69,7 @@ void HTMLSlotElement::removedFromAncesto > 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 @@ void HTMLSlotElement::attributeChanged(c > 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/ChangeLog >=================================================================== >--- LayoutTests/ChangeLog (revision 235590) >+++ LayoutTests/ChangeLog (working copy) >@@ -1,3 +1,25 @@ >+2018-09-03 Ryosuke Niwa <rniwa@webkit.org> >+ >+ slotchange event doesn't get fired when inserting, removing, or renaming slot elements >+ https://bugs.webkit.org/show_bug.cgi?id=189144 >+ <rdar://problem/43871061> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Added a W3C style testharness.js test for inserting, removing, and renaming slot elements. >+ >+ It has 62 distinct test cases for closed/open shadow roots in connected and disconnected trees >+ for the total of 248 test cases. >+ >+ This test presumes the resolution of https://github.com/w3c/webcomponents/issues/764 in our favor. >+ >+ Chrome fails 48 test cases because it doesn't follow the tree order when dispatching slotchange event >+ on the previously first slot element, and Firefox fails 84 test cases because it fails to fire slotchange >+ in the tree order when a node is inserted. >+ >+ * fast/shadow-dom/slotchange-for-slot-mutation-expected.txt: Added. >+ * fast/shadow-dom/slotchange-for-slot-mutation.html: Added. >+ > 2018-09-02 Zalan Bujtas <zalan@apple.com> > > REGRESSION (r191336): RenderFlexibleBox::adjustChildSizeForMinAndMax crashes in std::optional<>::value() >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,250 @@ >+ >+PASS slotchange event should not fire when inserting a default slot element into a shadow host with no children in a connected closed mode shadow root >+PASS slotchange event should not fire when inserting a default slot element into a shadow host with no children in a disconnected closed mode shadow root >+PASS slotchange event should not fire when inserting a default slot element into a shadow host with no children in a connected open mode shadow root >+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 connected closed 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 closed mode shadow root >+PASS slotchange event should not fire when inserting a named slot element into a shadow host with no children in a connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed mode shadow root >+PASS slotchange event should not fire when inserting a default slot element after an existing default slot in a disconnected closed mode shadow root >+PASS slotchange event should not fire when inserting a default slot element after an existing default slot in a connected 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 connected closed 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 closed 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 connected 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 connected closed mode shadow root >+PASS slotchange event should not fire when inserting a default slot element between two existing default slots in a disconnected closed mode shadow root >+PASS slotchange event should not fire when inserting a default slot element between two existing default slots in a connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed mode shadow root >+PASS slotchange event should fire on the first default slot inserted in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on the first default slot inserted in the tree order in a connected 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 connected closed mode shadow root >+PASS slotchange event should fire on the first named slot inserted in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on the first named slot inserted in the tree order in a connected 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 connected closed 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 closed mode shadow root >+PASS slotchange event should fire on the first named slot of the same name inserted in the tree order in a connected 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 fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order in a connected closed mode shadow root >+PASS slotchange event should fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order in a connected open mode shadow root >+PASS slotchange event should fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on all newly inserted slots with assigned nodes and their previously-first counterparts in the tree order in a connected closed mode shadow root >+PASS slotchange event should fire on all newly inserted slots with assigned nodes and their previously-first counterparts in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on all newly inserted slots with assigned nodes and their previously-first counterparts in the tree order in a connected open mode shadow root >+PASS slotchange event should fire on all newly inserted slots with assigned nodes and their previously-first counterparts in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on all newly inserted slots with assigned nodes but not on those without in the tree order in a connected closed mode shadow root >+PASS slotchange event should fire on all newly inserted slots with assigned nodes but not on those without in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on all newly inserted slots with assigned nodes but not on those without in the tree order in a connected open mode shadow root >+PASS slotchange event should fire on all newly inserted slots with assigned nodes but not on those without 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 connected closed 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 closed mode shadow root >+PASS slotchange event should not fire when removing a default slot element into a shadow host with no children in a connected 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 connected closed 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 closed mode shadow root >+PASS slotchange event should not fire when removing a named slot element into a shadow host with no children in a connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed mode shadow root >+PASS slotchange event should not fire when removing a default slot element after an existing default slot in a disconnected closed mode shadow root >+PASS slotchange event should not fire when removing a default slot element after an existing default slot in a connected 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 connected closed mode shadow root >+PASS slotchange event should fire when removing the first default slot element even if it had duplicates in a disconnected closed mode shadow root >+PASS slotchange event should fire when removing the first default slot element even if it had duplicates in a connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed mode shadow root >+PASS slotchange event should fire when removing the first named slot element even if it had duplicates in a disconnected closed mode shadow root >+PASS slotchange event should fire when removing the first named slot element even if it had duplicates in a connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed mode shadow root >+PASS slotchange event should fire on the first default slot removed in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on the first default slot removed in the tree order in a connected 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 connected closed mode shadow root >+PASS slotchange event should fire on the first named slot removed in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on the first named slot removed in the tree order in a connected 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 connected closed 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 closed mode shadow root >+PASS slotchange event should fire on the first default slot removed in the tree order when replacing all children in a connected 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 connected closed 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 closed mode shadow root >+PASS slotchange event should fire on the first default slot removed in the tree order when replacing all children in a connected 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 removed in the tree order in a connected closed mode shadow root >+PASS slotchange event should fire on the first named slot of the same name removed in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on the first named slot of the same name removed in the tree order in a connected open mode shadow root >+PASS slotchange event should fire on the first named slot of the same name removed in the tree order 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 before a default slot in the tree order in a connected closed mode shadow root >+PASS slotchange event should fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order in a connected open mode shadow root >+PASS slotchange event should fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on all first slots of its kind with assigned nodes and their previously-first counterparts in the tree order in a connected closed mode shadow root >+PASS slotchange event should fire on all first slots of its kind with assigned nodes and their previously-first counterparts in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on all first slots of its kind with assigned nodes and their previously-first counterparts in the tree order in a connected open mode shadow root >+PASS slotchange event should fire on all first slots of its kind with assigned nodes and their previously-first counterparts in the tree order in a disconnected open mode shadow root >+PASS slotchange event should fire on all removed slots with assigned nodes but not on those without in the tree order in a connected closed mode shadow root >+PASS slotchange event should fire on all removed slots with assigned nodes but not on those without in the tree order in a disconnected closed mode shadow root >+PASS slotchange event should fire on all removed slots with assigned nodes but not on those without in the tree order in a connected open mode shadow root >+PASS slotchange event should fire on all removed slots with assigned nodes but not on those without 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed mode shadow root >+PASS slotchange event should not fire when renaming a named slot element when there are no assigned nodes in a disconnected closed mode shadow root >+PASS slotchange event should not fire when renaming a named slot element when there are no assigned nodes in a connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 connected closed 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 closed 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 connected 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 >+PASS slotchange event should fire on all three slot elements whose assigned nodes were affected in the tree order when renaming a slot element in a connected closed mode shadow root >+PASS slotchange event should fire on all three slot elements whose assigned nodes were affected in the tree order when renaming a slot element in a disconnected closed mode shadow root >+PASS slotchange event should fire on all three slot elements whose assigned nodes were affected in the tree order when renaming a slot element in a connected open mode shadow root >+PASS slotchange event should fire on all three slot elements whose assigned nodes were affected in the tree order when renaming a slot element in a disconnected open mode shadow root >+PASS slotchange event should fire on slot elements with assigned nodes in the tree order when renaming a slot element but not on those without in a connected closed mode shadow root >+PASS slotchange event should fire on slot elements with assigned nodes in the tree order when renaming a slot element but not on those without in a disconnected closed mode shadow root >+PASS slotchange event should fire on slot elements with assigned nodes in the tree order when renaming a slot element but not on those without in a connected open mode shadow root >+PASS slotchange event should fire on slot elements with assigned nodes in the tree order when renaming a slot element but not on those without in a disconnected open mode shadow root >+PASS slotchange event should only fire on the renamed slot when there is another slot with the same name earlier in the tree in a connected closed mode shadow root >+PASS slotchange event should only fire on the renamed slot when there is another slot with the same name earlier in the tree in a disconnected closed mode shadow root >+PASS slotchange event should only fire on the renamed slot when there is another slot with the same name earlier in the tree in a connected open mode shadow root >+PASS slotchange event should only fire on the renamed slot when there is another slot with the same name earlier in the tree 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,310 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<title>Shadow DOM: slotchange event when inserting, removing, or renaming a slot element</title> >+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> >+<link rel="help" href="https://dom.spec.whatwg.org/#shadow-tree-slots"> >+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-insert"> >+<link rel="help" href="https://dom.spec.whatwg.org/#concept-node-remove"> >+<link rel="help" href="https://dom.spec.whatwg.org/#assign-slotables-for-a-tree"> >+<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'); >+ >+generateTests('<span slot="a"></span><span slot="b"></span>', '<slot id="oldSlot" name="a"></slot><slot></slot>', >+ (shadowRoot) => shadowRoot.prepend(newSlot('a')), ['newSlot', 'oldSlot'], >+ 'slotchange event should fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order'); >+ >+generateTests('<span></span><span slot="a"></span>', '<slot id="oldDefault"></slot><slot id="oldA" name="a"></slot>', >+ (shadowRoot) => { >+ const ancestor = document.createElement('div'); >+ ancestor.innerHTML = `<span><slot id="newDefault1"></slot><b><slot id="newDefault2"></slot><slot id="newB" name="b"></slot><slot id="newA" name="a"></slot></b></span>`; >+ addEventListners(ancestor); >+ shadowRoot.prepend(ancestor); >+ }, ['newDefault1', 'newA', 'oldDefault', 'oldA'], >+ 'slotchange event should fire on all newly inserted slots with assigned nodes and their previously-first counterparts in the tree order'); >+ >+generateTests('<span></span><span slot="a"></span>', '<slot id="oldDefault"></slot><slot id="oldA" name="a"></slot><slot id="oldB" name="b"></slot>', >+ (shadowRoot) => { >+ const ancestor = document.createElement('div'); >+ ancestor.innerHTML = `<span><slot id="newDefault1"></slot><b><slot id="newDefault2"></slot><slot id="newB" name="b"></slot><slot id="newA" name="a"></slot></b></span>`; >+ addEventListners(ancestor); >+ shadowRoot.prepend(ancestor); >+ }, ['newDefault1', 'newA', 'oldDefault', 'oldA'], >+ 'slotchange event should fire on all newly inserted slots with assigned nodes but not on those without 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(), ['slot2', 'slot1'], >+ '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(), ['slot2', 'slot1'], >+ '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(), ['slot2', 'slot1'], >+ '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(), ['slot2', 'slot1'], >+ '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 removed in the tree order'); >+ >+generateTests('<span slot="a"></span><span slot="b"></span>', '<slot id="a1" name="a"></slot><slot id="a2" name="a"></slot><slot></slot>', >+ (shadowRoot) => shadowRoot.getElementById('a1').remove(), ['a2', 'a1'], >+ 'slotchange event should fire when inserting a named slot element before an existing slot of the same name before a default slot in the tree order'); >+ >+generateTests('<span></span><span slot="a"></span>', '<span><slot id="default1"></slot><b><slot id="b1" name="b"></slot>' >+ + '<slot id="a1" name="a"></slot></b></span><slot id="default2"></slot><slot id="a2" name="a"></slot>', >+ (shadowRoot) => shadowRoot.querySelector('span').remove(), ['default2', 'a2', 'default1', 'a1'], >+ 'slotchange event should fire on all first slots of its kind with assigned nodes and their previously-first counterparts in the tree order'); >+ >+generateTests('<span></span><span slot="a"></span>', '<span><slot id="default1"></slot><b><slot id="default2"></slot>' >+ + '<slot id="b1" name="b"></slot><slot id="a1" name="a"></slot></b></span>' >+ + '<slot id="default3"></slot><slot id="a2" name="a"></slot><slot id="b2" name="b"></slot>', >+ (shadowRoot) => shadowRoot.querySelector('span').remove(), ['default3', 'a2', 'default1', 'a1'], >+ 'slotchange event should fire on all removed slots with assigned nodes but not on those without 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'); >+ >+generateTests('<span slot="foo"></span><span slot="bar"></span>', >+ '<slot id="slot1" name="foo"></slot><slot id="slot2" name="bar"></slot><slot id="slot3" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot1').name = 'bar', ['slot1', 'slot2', 'slot3'], >+ 'slotchange event should fire on all three slot elements whose assigned nodes were affected in the tree order when renaming a slot element'); >+ >+generateTests('<span slot="bar"></span>', '<slot id="slot1" name="foo"></slot><slot id="slot2" name="bar"></slot><slot id="slot3" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot1').name = 'bar', ['slot1', 'slot2'], >+ 'slotchange event should fire on slot elements with assigned nodes in the tree order when renaming a slot element but not on those without'); >+ >+generateTests('<span slot="foo"></span><span slot="bar"></span>', >+ '<slot id="slot1" name="foo"></slot><slot id="slot2" name="bar"></slot><slot id="slot3" name="foo"></slot>', >+ (shadowRoot) => shadowRoot.getElementById('slot2').name = 'foo', ['slot2'], >+ 'slotchange event should only fire on the renamed slot when there is another slot with the same name earlier in the tree'); >+ >+</script> >+</body> >+</html> >Index: LayoutTests/imported/w3c/ChangeLog >=================================================================== >--- LayoutTests/imported/w3c/ChangeLog (revision 235604) >+++ LayoutTests/imported/w3c/ChangeLog (working copy) >@@ -1,3 +1,15 @@ >+2018-09-03 Ryosuke Niwa <rniwa@webkit.org> >+ >+ slotchange event doesn't get fired when inserting, removing, or renaming slot elements >+ https://bugs.webkit.org/show_bug.cgi?id=189144 >+ <rdar://problem/43871061> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * web-platform-tests/shadow-dom/slotchange-customelements-expected.txt: >+ * web-platform-tests/shadow-dom/slotchange-event-expected.txt: >+ * web-platform-tests/shadow-dom/slotchange-expected.txt: >+ > 2018-09-03 Andy Estes <aestes@apple.com> > > [Payment Request] Implement the MerchantValidationEvent constructor >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 235590) >+++ 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 235590) >+++ LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt (working copy) >@@ -1,9 +1,3 @@ >-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 >@@ -17,10 +11,10 @@ PASS slotchange event must not fire on a > 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 slo > 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 235590) >+++ 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
Flags:
koivisto
:
review+
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 189144
:
348477
|
348486
|
348490
|
348583
|
348594
|
348697
|
348699
|
348709
|
348710
|
348711
| 348839