9ffcfe0fde58326a80d7d675f2e2fff96b6202ea
[WebKit-https.git] / Source / WebCore / dom / SlotAssignment.cpp
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "SlotAssignment.h"
28
29
30 #include "HTMLSlotElement.h"
31 #include "ShadowRoot.h"
32 #include "TypedElementDescendantIterator.h"
33
34 namespace WebCore {
35
36 using namespace HTMLNames;
37
38 static const AtomicString& slotNameFromAttributeValue(const AtomicString& value)
39 {
40     return value == nullAtom() ? SlotAssignment::defaultSlotName() : value;
41 }
42
43 static const AtomicString& slotNameFromSlotAttribute(const Node& child)
44 {
45     if (is<Text>(child))
46         return SlotAssignment::defaultSlotName();
47
48     return slotNameFromAttributeValue(downcast<Element>(child).attributeWithoutSynchronization(slotAttr));
49 }
50
51 SlotAssignment::SlotAssignment() = default;
52
53 SlotAssignment::~SlotAssignment() = default;
54
55 HTMLSlotElement* SlotAssignment::findAssignedSlot(const Node& node, ShadowRoot& shadowRoot)
56 {
57     if (!is<Text>(node) && !is<Element>(node))
58         return nullptr;
59
60     auto* slot = m_slots.get(slotNameForHostChild(node));
61     if (!slot)
62         return nullptr;
63
64     return findFirstSlotElement(*slot, shadowRoot);
65 }
66
67 void SlotAssignment::addSlotElementByName(const AtomicString& name, HTMLSlotElement& slotElement, ShadowRoot& shadowRoot)
68 {
69 #ifndef NDEBUG
70     ASSERT(!m_slotElementsForConsistencyCheck.contains(&slotElement));
71     m_slotElementsForConsistencyCheck.add(&slotElement);
72 #endif
73
74     // FIXME: We should be able to do a targeted reconstruction.
75     shadowRoot.host()->invalidateStyleAndRenderersForSubtree();
76
77     const AtomicString& slotName = slotNameFromAttributeValue(name);
78     auto addResult = m_slots.ensure(slotName, [&] {
79         // Unlike named slots, assignSlots doesn't collect nodes assigned to the default slot
80         // to avoid always having a vector of all child nodes of a shadow host.
81         if (slotName == defaultSlotName())
82             m_slotAssignmentsIsValid = false;
83
84         return std::make_unique<Slot>();
85     });
86
87     auto& slot = *addResult.iterator->value;
88     if (!slot.hasSlotElements())
89         slot.element = makeWeakPtr(slotElement);
90     else {
91         slot.element = nullptr;
92 #ifndef NDEBUG
93         m_needsToResolveSlotElements = true;
94 #endif
95     }
96     slot.elementCount++;
97 }
98
99 void SlotAssignment::removeSlotElementByName(const AtomicString& name, HTMLSlotElement& slotElement, ShadowRoot& shadowRoot)
100 {
101 #ifndef NDEBUG
102     ASSERT(m_slotElementsForConsistencyCheck.contains(&slotElement));
103     m_slotElementsForConsistencyCheck.remove(&slotElement);
104 #endif
105
106     if (auto* host = shadowRoot.host()) // FIXME: We should be able to do a targeted reconstruction.
107         host->invalidateStyleAndRenderersForSubtree();
108
109     auto* slot = m_slots.get(slotNameFromAttributeValue(name));
110     RELEASE_ASSERT(slot && slot->hasSlotElements());
111
112     slot->elementCount--;
113     if (slot->element == &slotElement) {
114         slot->element = nullptr;
115 #ifndef NDEBUG
116         m_needsToResolveSlotElements = true;
117 #endif
118     }
119     ASSERT(slot->element || m_needsToResolveSlotElements);
120 }
121
122 void SlotAssignment::slotFallbackDidChange(HTMLSlotElement& slotElement, ShadowRoot& shadowRoot)
123 {
124     if (shadowRoot.mode() == ShadowRootMode::UserAgent)
125         return;
126
127     bool usesFallbackContent = !assignedNodesForSlot(slotElement, shadowRoot);
128     if (usesFallbackContent)
129         slotElement.enqueueSlotChangeEvent();
130 }
131
132 void SlotAssignment::didChangeSlot(const AtomicString& slotAttrValue, ShadowRoot& shadowRoot)
133 {
134     auto& slotName = slotNameFromAttributeValue(slotAttrValue);
135     auto* slot = m_slots.get(slotName);
136     if (!slot)
137         return;
138     
139     slot->assignedNodes.clear();
140     m_slotAssignmentsIsValid = false;
141
142     auto slotElement = makeRefPtr(findFirstSlotElement(*slot, shadowRoot));
143     if (!slotElement)
144         return;
145
146     shadowRoot.host()->invalidateStyleAndRenderersForSubtree();
147
148     if (shadowRoot.mode() == ShadowRootMode::UserAgent)
149         return;
150
151     slotElement->enqueueSlotChangeEvent();
152 }
153
154 void SlotAssignment::hostChildElementDidChange(const Element& childElement, ShadowRoot& shadowRoot)
155 {
156     didChangeSlot(childElement.attributeWithoutSynchronization(slotAttr), shadowRoot);
157 }
158
159 const Vector<Node*>* SlotAssignment::assignedNodesForSlot(const HTMLSlotElement& slotElement, ShadowRoot& shadowRoot)
160 {
161     ASSERT(slotElement.containingShadowRoot() == &shadowRoot);
162     const AtomicString& slotName = slotNameFromAttributeValue(slotElement.attributeWithoutSynchronization(nameAttr));
163     auto* slot = m_slots.get(slotName);
164     RELEASE_ASSERT(slot);
165
166     if (!m_slotAssignmentsIsValid)
167         assignSlots(shadowRoot);
168
169     if (slot->assignedNodes.isEmpty())
170         return nullptr;
171
172     RELEASE_ASSERT(slot->hasSlotElements());
173     if (slot->hasDuplicatedSlotElements() && findFirstSlotElement(*slot, shadowRoot) != &slotElement)
174         return nullptr;
175
176     return &slot->assignedNodes;
177 }
178
179 const AtomicString& SlotAssignment::slotNameForHostChild(const Node& child) const
180 {
181     return slotNameFromSlotAttribute(child);
182 }
183
184 HTMLSlotElement* SlotAssignment::findFirstSlotElement(Slot& slot, ShadowRoot& shadowRoot)
185 {
186     if (slot.shouldResolveSlotElement())
187         resolveAllSlotElements(shadowRoot);
188
189 #ifndef NDEBUG
190     ASSERT(!slot.element || m_slotElementsForConsistencyCheck.contains(slot.element.get()));
191     ASSERT(!!slot.element == !!slot.elementCount);
192 #endif
193
194     return slot.element.get();
195 }
196
197 void SlotAssignment::resolveAllSlotElements(ShadowRoot& shadowRoot)
198 {
199 #ifndef NDEBUG
200     ASSERT(m_needsToResolveSlotElements);
201     m_needsToResolveSlotElements = false;
202 #endif
203
204     // FIXME: It's inefficient to reset all values. We should be able to void this in common case.
205     for (auto& entry : m_slots)
206         entry.value->element = nullptr;
207
208     unsigned slotCount = m_slots.size();
209     for (auto& slotElement : descendantsOfType<HTMLSlotElement>(shadowRoot)) {
210         auto& slotName = slotNameFromAttributeValue(slotElement.attributeWithoutSynchronization(nameAttr));
211
212         auto* slot = m_slots.get(slotName);
213         RELEASE_ASSERT(slot); // slot must have been created when a slot was inserted.
214
215         bool hasSeenSlotWithSameName = !!slot->element;
216         if (hasSeenSlotWithSameName)
217             continue;
218
219         slot->element = makeWeakPtr(slotElement);
220         slotCount--;
221         if (!slotCount)
222             break;
223     }
224 }
225
226 void SlotAssignment::assignSlots(ShadowRoot& shadowRoot)
227 {
228     ASSERT(!m_slotAssignmentsIsValid);
229     m_slotAssignmentsIsValid = true;
230
231     for (auto& entry : m_slots)
232         entry.value->assignedNodes.shrink(0);
233
234     auto& host = *shadowRoot.host();
235     for (auto* child = host.firstChild(); child; child = child->nextSibling()) {
236         if (!is<Text>(*child) && !is<Element>(*child))
237             continue;
238         auto slotName = slotNameForHostChild(*child);
239         assignToSlot(*child, slotName);
240     }
241
242     for (auto& entry : m_slots)
243         entry.value->assignedNodes.shrinkToFit();
244 }
245
246 void SlotAssignment::assignToSlot(Node& child, const AtomicString& slotName)
247 {
248     ASSERT(!slotName.isNull());
249     if (slotName == defaultSlotName()) {
250         auto defaultSlotEntry = m_slots.find(defaultSlotName());
251         if (defaultSlotEntry != m_slots.end())
252             defaultSlotEntry->value->assignedNodes.append(&child);
253         return;
254     }
255
256     auto addResult = m_slots.ensure(slotName, [] {
257         return std::make_unique<Slot>();
258     });
259     addResult.iterator->value->assignedNodes.append(&child);
260 }
261
262 }
263
264