From c0b08af113aeb48e656a7a90de001748179cb40e Mon Sep 17 00:00:00 2001 From: "rniwa@webkit.org" Date: Wed, 5 Sep 2018 00:22:43 +0000 Subject: [PATCH] slotchange event doesn't get fired when inserting, removing, or renaming slot elements https://bugs.webkit.org/show_bug.cgi?id=189144 Reviewed by Antti Koivisto. LayoutTests/imported/w3c: * 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: Source/WebCore: This patch implements `slotchange` event when a slot element is inserted, removed, or renamed in the DOM tree. Let us consider each scenario 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 behavior, 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. LayoutTests: 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. git-svn-id: http://svn.webkit.org/repository/webkit/trunk@235650 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- LayoutTests/ChangeLog | 22 ++ .../slotchange-for-slot-mutation-expected.txt | 250 ++++++++++++++ .../slotchange-for-slot-mutation.html | 310 ++++++++++++++++++ LayoutTests/imported/w3c/ChangeLog | 12 + .../slotchange-customelements-expected.txt | 2 +- .../shadow-dom/slotchange-event-expected.txt | 22 +- .../shadow-dom/slotchange-expected.txt | 10 +- Source/WebCore/ChangeLog | 124 +++++++ Source/WebCore/dom/ContainerNode.cpp | 11 + Source/WebCore/dom/ShadowRoot.cpp | 14 +- Source/WebCore/dom/ShadowRoot.h | 7 +- Source/WebCore/dom/SlotAssignment.cpp | 198 +++++++++-- Source/WebCore/dom/SlotAssignment.h | 30 +- Source/WebCore/html/HTMLSlotElement.cpp | 8 +- 14 files changed, 967 insertions(+), 53 deletions(-) create mode 100644 LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation-expected.txt create mode 100644 LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation.html diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog index 5c016f5223448..61d11c3612079 100644 --- a/LayoutTests/ChangeLog +++ b/LayoutTests/ChangeLog @@ -1,3 +1,25 @@ +2018-09-04 Ryosuke Niwa + + slotchange event doesn't get fired when inserting, removing, or renaming slot elements + https://bugs.webkit.org/show_bug.cgi?id=189144 + + + Reviewed by Antti Koivisto. + + 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-04 Simon Fraser CSS reference filter that references a tiled feTurbulence is blank diff --git a/LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation-expected.txt b/LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation-expected.txt new file mode 100644 index 0000000000000..7b64ad54b0374 --- /dev/null +++ b/LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation-expected.txt @@ -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 + diff --git a/LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation.html b/LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation.html new file mode 100644 index 0000000000000..fed3e0fee2c34 --- /dev/null +++ b/LayoutTests/fast/shadow-dom/slotchange-for-slot-mutation.html @@ -0,0 +1,310 @@ + + + +Shadow DOM: slotchange event when inserting, removing, or renaming a slot element + + + + + + + + + +
+ + + diff --git a/LayoutTests/imported/w3c/ChangeLog b/LayoutTests/imported/w3c/ChangeLog index 873faa1f354f1..e53a49d174d4e 100644 --- a/LayoutTests/imported/w3c/ChangeLog +++ b/LayoutTests/imported/w3c/ChangeLog @@ -1,3 +1,15 @@ +2018-09-04 Ryosuke Niwa + + slotchange event doesn't get fired when inserting, removing, or renaming slot elements + https://bugs.webkit.org/show_bug.cgi?id=189144 + + + Reviewed by Antti Koivisto. + + * 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-04 Rob Buis Adjust XMLHttpRequest username/password precedence rules diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-customelements-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-customelements-expected.txt index 7f7664a711992..016d751ebc177 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-customelements-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-customelements-expected.txt @@ -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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt index 9c31efce7f29b..0ba26dedeee5d 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-event-expected.txt @@ -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 slot element inside an open shadow root PASS slotchange event must not fire on a slot element inside a closed shadow root in a document when another slot's assigned nodes change PASS slotchange event must not fire on a slot element inside an open shadow root not in a document when another slot's assigned nodes change PASS slotchange event must not fire on a slot element inside a closed shadow root not in a document when another slot's assigned nodes change -FAIL slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside an open shadow root in a document assert_equals: slotchange must be fired on a slot element if there is assigned nodes when the slot was inserted expected 1 but got 0 -FAIL slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside a closed shadow root in a document assert_equals: slotchange must be fired on a slot element if there is assigned nodes when the slot was inserted expected 1 but got 0 -FAIL slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside an open shadow root not in a document assert_equals: slotchange must be fired on a slot element if there is assigned nodes when the slot was inserted expected 1 but got 0 -FAIL slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside a closed shadow root not in a document assert_equals: slotchange must be fired on a slot element if there is assigned nodes when the slot was inserted expected 1 but got 0 +PASS slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside an open shadow root in a document +PASS slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside a closed shadow root in a document +PASS slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside an open shadow root not in a document +PASS slotchange event must fire on a slot element when a shadow host has a slotable and the slot was inserted and must not fire when the shadow host was mutated after the slot was removed inside a closed shadow root not in a document PASS slotchange event must fire on a slot element inside an open shadow root in a document even if the slot was removed immediately after the assigned nodes were mutated PASS slotchange event must fire on a slot element inside a closed shadow root in a document even if the slot was removed immediately after the assigned nodes were mutated PASS slotchange event must fire on a slot element inside an open shadow root not in a document even if the slot was removed immediately after the assigned nodes were mutated @@ -29,10 +23,10 @@ PASS slotchange event must fire on a slot element inside an open shadow root in 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 but got Element node -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 but got Element node -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 but got Element node -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 but got Element node +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 diff --git a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-expected.txt index c53a5070b845d..b4c56a3847852 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/shadow-dom/slotchange-expected.txt @@ -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. diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog index f0368cc3ad8dd..af9d63bd2eddc 100644 --- a/Source/WebCore/ChangeLog +++ b/Source/WebCore/ChangeLog @@ -1,3 +1,127 @@ +2018-09-04 Ryosuke Niwa + + slotchange event doesn't get fired when inserting, removing, or renaming slot elements + https://bugs.webkit.org/show_bug.cgi?id=189144 + + + Reviewed by Antti Koivisto. + + This patch implements `slotchange` event when a slot element is inserted, removed, or renamed in the DOM tree. + Let us consider each scenario 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 behavior, 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-04 Youenn Fablet Make LibWebRTCRtpSenderBackend own its libwebrtc RTP sender backend diff --git a/Source/WebCore/dom/ContainerNode.cpp b/Source/WebCore/dom/ContainerNode.cpp index 8b509f7723b1c..8c7747ee8144a 100644 --- a/Source/WebCore/dom/ContainerNode.cpp +++ b/Source/WebCore/dom/ContainerNode.cpp @@ -59,6 +59,7 @@ #include "SVGUseElement.h" #include "ScriptDisallowedScope.h" #include "SelectorQuery.h" +#include "SlotAssignment.h" #include "TemplateContentDocumentFragment.h" #include #include @@ -104,6 +105,9 @@ ALWAYS_INLINE NodeVector ContainerNode::removeAllChildrenWithScriptAssertion(Chi WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; ScriptDisallowedScope::InMainThread scriptDisallowedScope; + if (UNLIKELY(isShadowRoot() || isInShadowTree())) + containingShadowRoot()->willRemoveAllChildren(*this); + document().nodeChildrenWillBeRemoved(*this); while (RefPtr child = m_firstChild) { @@ -150,6 +154,9 @@ ALWAYS_INLINE bool ContainerNode::removeNodeWithScriptAssertion(Node& childToRem 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 executeNodeInsertionWithScriptAssertion(ContainerNode& NodeVector postInsertionNotificationTargets; { ScriptDisallowedScope::InMainThread scriptDisallowedScope; + + if (UNLIKELY(containerNode.isShadowRoot() || containerNode.isInShadowTree())) + containerNode.containingShadowRoot()->resolveSlotsBeforeNodeInsertionOrRemoval(); + doNodeInsertion(); ChildListMutationScope(containerNode).childAdded(child); postInsertionNotificationTargets = notifyChildNodeInserted(containerNode, child); diff --git a/Source/WebCore/dom/ShadowRoot.cpp b/Source/WebCore/dom/ShadowRoot.cpp index b0325348e1964..bedaadc62e279 100644 --- a/Source/WebCore/dom/ShadowRoot.cpp +++ b/Source/WebCore/dom/ShadowRoot.cpp @@ -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::findAssignedSlot(const Node& node) 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(const AtomicString& name, HTMLSlotElement& 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) diff --git a/Source/WebCore/dom/ShadowRoot.h b/Source/WebCore/dom/ShadowRoot.h index a273c261827a7..2bfc8865646a8 100644 --- a/Source/WebCore/dom/ShadowRoot.h +++ b/Source/WebCore/dom/ShadowRoot.h @@ -67,14 +67,18 @@ class ShadowRoot final : public DocumentFragment, public TreeScope { 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 @@ class ShadowRoot final : public DocumentFragment, public TreeScope { void removedFromAncestor(RemovalType, ContainerNode& insertionPoint) override; bool m_resetStyleInheritance { false }; + bool m_hasBegunDeletingDetachedChildren { false }; ShadowRootMode m_type { ShadowRootMode::UserAgent }; Element* m_host { nullptr }; diff --git a/Source/WebCore/dom/SlotAssignment.cpp b/Source/WebCore/dom/SlotAssignment.cpp index 9ffcfe0fde583..e39cfd49cdfdb 100644 --- a/Source/WebCore/dom/SlotAssignment.cpp +++ b/Source/WebCore/dom/SlotAssignment.cpp @@ -48,6 +48,29 @@ static const AtomicString& slotNameFromSlotAttribute(const Node& child) return slotNameFromAttributeValue(downcast(child).attributeWithoutSynchronization(slotAttr)); } +#if !ASSERT_DISABLED +static HTMLSlotElement* findSlotElement(ShadowRoot& shadowRoot, const AtomicString& slotName) +{ + for (auto& slotElement : descendantsOfType(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(node)); + return downcast(node); +} + SlotAssignment::SlotAssignment() = default; SlotAssignment::~SlotAssignment() = default; @@ -64,6 +87,23 @@ HTMLSlotElement* SlotAssignment::findAssignedSlot(const Node& node, ShadowRoot& 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::addSlotElementByName(const AtomicString& name, HTMLSlotElem // 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(); }); - 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* oldParentOfRemovedTreeForRemoval, ShadowRoot& shadowRoot) { #ifndef NDEBUG ASSERT(m_slotElementsForConsistencyCheck.contains(&slotElement)); @@ -108,15 +155,111 @@ void SlotAssignment::removeSlotElementByName(const AtomicString& name, HTMLSlotE 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 = !oldParentOfRemovedTreeForRemoval; + if (elementWasRenamed && slot->element == &slotElement) + slotElement.enqueueSlotChangeEvent(); + + // A previous invocation to resolveSlotsAfterSlotMutation during this removal has updated this slot. + ASSERT(slot->element || (m_slotResolutionVersion == m_slotMutationVersion && !findSlotElement(shadowRoot, name))); + if (slot->element) { + resolveSlotsAfterSlotMutation(shadowRoot, elementWasRenamed ? SlotMutationType::Insertion : SlotMutationType::Removal, + m_willBeRemovingAllChildren ? oldParentOfRemovedTreeForRemoval : 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::slotFallbackDidChange(HTMLSlotElement& slotElement, ShadowR 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 AtomicString& slotAttrValue, ShadowRoot 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::findFirstSlotElement(Slot& slot, ShadowRoot& sh 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(shadowRoot)) { @@ -212,9 +368,9 @@ void SlotAssignment::resolveAllSlotElements(ShadowRoot& shadowRoot) 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--; diff --git a/Source/WebCore/dom/SlotAssignment.h b/Source/WebCore/dom/SlotAssignment.h index cdf1e74a8420f..463673ca5907d 100644 --- a/Source/WebCore/dom/SlotAssignment.h +++ b/Source/WebCore/dom/SlotAssignment.h @@ -50,9 +50,12 @@ class SlotAssignment { 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* oldParentOfRemovedTreeForRemoval, 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 @@ class SlotAssignment { bool shouldResolveSlotElement() { return !element && elementCount; } WeakPtr element; + WeakPtr oldElement; unsigned elementCount { 0 }; + bool seenFirstElement { false }; Vector 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 @@ class SlotAssignment { #ifndef NDEBUG HashSet 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. diff --git a/Source/WebCore/html/HTMLSlotElement.cpp b/Source/WebCore/html/HTMLSlotElement.cpp index 2d3793915c839..8190846802596 100644 --- a/Source/WebCore/html/HTMLSlotElement.cpp +++ b/Source/WebCore/html/HTMLSlotElement.cpp @@ -69,7 +69,7 @@ void HTMLSlotElement::removedFromAncestor(RemovalType removalType, ContainerNode 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(const QualifiedName& name, const AtomicSt 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); } }