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
34namespace WebCore {
35
36using namespace HTMLNames;
37
38static const AtomString& slotNameFromAttributeValue(const AtomString& value)
39{
40 return value == nullAtom() ? SlotAssignment::defaultSlotName() : value;
41}
42
43static const AtomString& 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#if !ASSERT_DISABLED
52static HTMLSlotElement* findSlotElement(ShadowRoot& shadowRoot, const AtomString& slotName)
53{
54 for (auto& slotElement : descendantsOfType<HTMLSlotElement>(shadowRoot)) {
55 if (slotNameFromAttributeValue(slotElement.attributeWithoutSynchronization(nameAttr)) == slotName)
56 return &slotElement;
57 }
58 return nullptr;
59}
60#endif
61
62static HTMLSlotElement* nextSlotElementSkippingSubtree(ContainerNode& startingNode, ContainerNode* skippedSubtree)
63{
64 Node* node = &startingNode;
65 do {
66 if (UNLIKELY(node == skippedSubtree))
67 node = NodeTraversal::nextSkippingChildren(*node);
68 else
69 node = NodeTraversal::next(*node);
70 } while (node && !is<HTMLSlotElement>(node));
71 return downcast<HTMLSlotElement>(node);
72}
73
74SlotAssignment::SlotAssignment() = default;
75
76SlotAssignment::~SlotAssignment() = default;
77
78HTMLSlotElement* SlotAssignment::findAssignedSlot(const Node& node, ShadowRoot& shadowRoot)
79{
80 if (!is<Text>(node) && !is<Element>(node))
81 return nullptr;
82
83 auto* slot = m_slots.get(slotNameForHostChild(node));
84 if (!slot)
85 return nullptr;
86
87 return findFirstSlotElement(*slot, shadowRoot);
88}
89
90inline bool SlotAssignment::hasAssignedNodes(ShadowRoot& shadowRoot, Slot& slot)
91{
92 if (!m_slotAssignmentsIsValid)
93 assignSlots(shadowRoot);
94 return !slot.assignedNodes.isEmpty();
95}
96
97void SlotAssignment::renameSlotElement(HTMLSlotElement& slotElement, const AtomString& oldName, const AtomString& newName, ShadowRoot& shadowRoot)
98{
99 ASSERT(m_slotElementsForConsistencyCheck.contains(&slotElement));
100
101 m_slotMutationVersion++;
102
103 removeSlotElementByName(oldName, slotElement, nullptr, shadowRoot);
104 addSlotElementByName(newName, slotElement, shadowRoot);
105}
106
107void SlotAssignment::addSlotElementByName(const AtomString& name, HTMLSlotElement& slotElement, ShadowRoot& shadowRoot)
108{
109#ifndef NDEBUG
110 ASSERT(!m_slotElementsForConsistencyCheck.contains(&slotElement));
111 m_slotElementsForConsistencyCheck.add(&slotElement);
112#endif
113
114 // FIXME: We should be able to do a targeted reconstruction.
115 shadowRoot.host()->invalidateStyleAndRenderersForSubtree();
116
117 auto& slotName = slotNameFromAttributeValue(name);
118 auto addResult = m_slots.ensure(slotName, [&] {
119 // Unlike named slots, assignSlots doesn't collect nodes assigned to the default slot
120 // to avoid always having a vector of all child nodes of a shadow host.
121 if (slotName == defaultSlotName())
122 m_slotAssignmentsIsValid = false;
123 return std::make_unique<Slot>();
124 });
125 auto& slot = *addResult.iterator->value;
126 bool needsSlotchangeEvent = shadowRoot.shouldFireSlotchangeEvent() && hasAssignedNodes(shadowRoot, slot);
127
128 slot.elementCount++;
129 if (slot.elementCount == 1) {
130 slot.element = makeWeakPtr(slotElement);
131 if (needsSlotchangeEvent)
132 slotElement.enqueueSlotChangeEvent();
133 return;
134 }
135
136 if (!needsSlotchangeEvent) {
137 ASSERT(slot.element || m_needsToResolveSlotElements);
138 slot.element = nullptr;
139 m_needsToResolveSlotElements = true;
140 return;
141 }
142
143 resolveSlotsAfterSlotMutation(shadowRoot, SlotMutationType::Insertion);
144}
145
146void SlotAssignment::removeSlotElementByName(const AtomString& name, HTMLSlotElement& slotElement, ContainerNode* oldParentOfRemovedTreeForRemoval, ShadowRoot& shadowRoot)
147{
148#ifndef NDEBUG
149 ASSERT(m_slotElementsForConsistencyCheck.contains(&slotElement));
150 m_slotElementsForConsistencyCheck.remove(&slotElement);
151#endif
152
153 if (auto* host = shadowRoot.host()) // FIXME: We should be able to do a targeted reconstruction.
154 host->invalidateStyleAndRenderersForSubtree();
155
156 auto* slot = m_slots.get(slotNameFromAttributeValue(name));
157 RELEASE_ASSERT(slot && slot->hasSlotElements());
158 bool needsSlotchangeEvent = shadowRoot.shouldFireSlotchangeEvent() && hasAssignedNodes(shadowRoot, *slot);
159
160 slot->elementCount--;
161 if (!slot->elementCount) {
162 slot->element = nullptr;
163 if (needsSlotchangeEvent && m_slotResolutionVersion != m_slotMutationVersion)
164 slotElement.enqueueSlotChangeEvent();
165 return;
166 }
167
168 if (!needsSlotchangeEvent) {
169 ASSERT(slot->element || m_needsToResolveSlotElements);
170 slot->element = nullptr;
171 m_needsToResolveSlotElements = true;
172 return;
173 }
174
175 bool elementWasRenamed = !oldParentOfRemovedTreeForRemoval;
176 if (elementWasRenamed && slot->element == &slotElement)
177 slotElement.enqueueSlotChangeEvent();
178
179 // A previous invocation to resolveSlotsAfterSlotMutation during this removal has updated this slot.
180 ASSERT(slot->element || (m_slotResolutionVersion == m_slotMutationVersion && !findSlotElement(shadowRoot, name)));
181 if (slot->element) {
182 resolveSlotsAfterSlotMutation(shadowRoot, elementWasRenamed ? SlotMutationType::Insertion : SlotMutationType::Removal,
183 m_willBeRemovingAllChildren ? oldParentOfRemovedTreeForRemoval : nullptr);
184 }
185
186 if (slot->oldElement == &slotElement) {
187 slotElement.enqueueSlotChangeEvent();
188 slot->oldElement = nullptr;
189 }
190}
191
192void SlotAssignment::resolveSlotsAfterSlotMutation(ShadowRoot& shadowRoot, SlotMutationType mutationType, ContainerNode* subtreeToSkip)
193{
194 if (m_slotResolutionVersion == m_slotMutationVersion)
195 return;
196 m_slotResolutionVersion = m_slotMutationVersion;
197
198 ASSERT(!subtreeToSkip || mutationType == SlotMutationType::Removal);
199 m_needsToResolveSlotElements = false;
200
201 for (auto& slot : m_slots.values())
202 slot->seenFirstElement = false;
203
204 unsigned slotCount = 0;
205 HTMLSlotElement* currentElement = nextSlotElementSkippingSubtree(shadowRoot, subtreeToSkip);
206 for (; currentElement; currentElement = nextSlotElementSkippingSubtree(*currentElement, subtreeToSkip)) {
207 auto& currentSlotName = slotNameFromAttributeValue(currentElement->attributeWithoutSynchronization(nameAttr));
208 auto* currentSlot = m_slots.get(currentSlotName);
209 if (!currentSlot) {
210 // A new slot may have been inserted with this node but appears later in the tree order.
211 // Such a slot would go through the fast path in addSlotElementByName,
212 // and any subsequently inserted slot of the same name would not result in any slotchange or invokation of this function.
213 ASSERT(mutationType == SlotMutationType::Insertion);
214 continue;
215 }
216 if (currentSlot->seenFirstElement) {
217 if (mutationType == SlotMutationType::Insertion && currentSlot->oldElement == currentElement) {
218 currentElement->enqueueSlotChangeEvent();
219 currentSlot->oldElement = nullptr;
220 }
221 continue;
222 }
223 currentSlot->seenFirstElement = true;
224 slotCount++;
225 ASSERT(currentSlot->element || !hasAssignedNodes(shadowRoot, *currentSlot));
226 if (currentSlot->element != currentElement) {
227 if (hasAssignedNodes(shadowRoot, *currentSlot)) {
228 currentSlot->oldElement = WTFMove(currentSlot->element);
229 currentElement->enqueueSlotChangeEvent();
230 }
231 currentSlot->element = makeWeakPtr(*currentElement);
232 }
233 }
234
235 if (slotCount == m_slots.size())
236 return;
237
238 if (mutationType == SlotMutationType::Insertion) {
239 // This code path is taken only when continue above for !currentSlot is taken.
240 // i.e. there is a new slot being inserted into the tree but we have yet to invoke addSlotElementByName on it.
241#if !ASSERT_DISABLED
242 for (auto& entry : m_slots)
243 ASSERT(entry.value->seenFirstElement || !findSlotElement(shadowRoot, entry.key));
244#endif
245 return;
246 }
247
248 for (auto& slot : m_slots.values()) {
249 if (slot->seenFirstElement)
250 continue;
251 if (!slot->elementCount) {
252 // Taken the fast path for removal.
253 ASSERT(!slot->element);
254 continue;
255 }
256 // All slot elements have been removed for this slot.
257 slot->seenFirstElement = true;
258 ASSERT(slot->element);
259 if (hasAssignedNodes(shadowRoot, *slot))
260 slot->oldElement = WTFMove(slot->element);
261 slot->element = nullptr;
262 }
263}
264
265void SlotAssignment::slotFallbackDidChange(HTMLSlotElement& slotElement, ShadowRoot& shadowRoot)
266{
267 if (shadowRoot.mode() == ShadowRootMode::UserAgent)
268 return;
269
270 bool usesFallbackContent = !assignedNodesForSlot(slotElement, shadowRoot);
271 if (usesFallbackContent)
272 slotElement.enqueueSlotChangeEvent();
273}
274
275void SlotAssignment::resolveSlotsBeforeNodeInsertionOrRemoval(ShadowRoot& shadowRoot)
276{
277 ASSERT(shadowRoot.shouldFireSlotchangeEvent());
278 m_slotMutationVersion++;
279 m_willBeRemovingAllChildren = false;
280 if (m_needsToResolveSlotElements)
281 resolveAllSlotElements(shadowRoot);
282}
283
284void SlotAssignment::willRemoveAllChildren(ShadowRoot& shadowRoot)
285{
286 m_slotMutationVersion++;
287 m_willBeRemovingAllChildren = true;
288 if (m_needsToResolveSlotElements)
289 resolveAllSlotElements(shadowRoot);
290}
291
292void SlotAssignment::didChangeSlot(const AtomString& slotAttrValue, ShadowRoot& shadowRoot)
293{
294 auto& slotName = slotNameFromAttributeValue(slotAttrValue);
295 auto* slot = m_slots.get(slotName);
296 if (!slot)
297 return;
298
299 slot->assignedNodes.clear();
300 m_slotAssignmentsIsValid = false;
301
302 auto slotElement = makeRefPtr(findFirstSlotElement(*slot, shadowRoot));
303 if (!slotElement)
304 return;
305
306 shadowRoot.host()->invalidateStyleAndRenderersForSubtree();
307
308 if (shadowRoot.shouldFireSlotchangeEvent())
309 slotElement->enqueueSlotChangeEvent();
310}
311
312void SlotAssignment::hostChildElementDidChange(const Element& childElement, ShadowRoot& shadowRoot)
313{
314 didChangeSlot(childElement.attributeWithoutSynchronization(slotAttr), shadowRoot);
315}
316
317const Vector<Node*>* SlotAssignment::assignedNodesForSlot(const HTMLSlotElement& slotElement, ShadowRoot& shadowRoot)
318{
319 ASSERT(slotElement.containingShadowRoot() == &shadowRoot);
320 const AtomString& slotName = slotNameFromAttributeValue(slotElement.attributeWithoutSynchronization(nameAttr));
321 auto* slot = m_slots.get(slotName);
322 RELEASE_ASSERT(slot);
323
324 if (!m_slotAssignmentsIsValid)
325 assignSlots(shadowRoot);
326
327 if (slot->assignedNodes.isEmpty())
328 return nullptr;
329
330 RELEASE_ASSERT(slot->hasSlotElements());
331 if (slot->hasDuplicatedSlotElements() && findFirstSlotElement(*slot, shadowRoot) != &slotElement)
332 return nullptr;
333
334 return &slot->assignedNodes;
335}
336
337const AtomString& SlotAssignment::slotNameForHostChild(const Node& child) const
338{
339 return slotNameFromSlotAttribute(child);
340}
341
342HTMLSlotElement* SlotAssignment::findFirstSlotElement(Slot& slot, ShadowRoot& shadowRoot)
343{
344 if (slot.shouldResolveSlotElement())
345 resolveAllSlotElements(shadowRoot);
346
347#ifndef NDEBUG
348 ASSERT(!slot.element || m_slotElementsForConsistencyCheck.contains(slot.element.get()));
349 ASSERT(!!slot.element == !!slot.elementCount);
350#endif
351
352 return slot.element.get();
353}
354
355void SlotAssignment::resolveAllSlotElements(ShadowRoot& shadowRoot)
356{
357 ASSERT(m_needsToResolveSlotElements);
358 m_needsToResolveSlotElements = false;
359
360 // FIXME: It's inefficient to reset all values. We should be able to void this in common case.
361 for (auto& entry : m_slots)
362 entry.value->seenFirstElement = false;
363
364 unsigned slotCount = m_slots.size();
365 for (auto& slotElement : descendantsOfType<HTMLSlotElement>(shadowRoot)) {
366 auto& slotName = slotNameFromAttributeValue(slotElement.attributeWithoutSynchronization(nameAttr));
367
368 auto* slot = m_slots.get(slotName);
369 RELEASE_ASSERT(slot); // slot must have been created when a slot was inserted.
370
371 if (slot->seenFirstElement)
372 continue;
373 slot->seenFirstElement = true;
374
375 slot->element = makeWeakPtr(slotElement);
376 slotCount--;
377 if (!slotCount)
378 break;
379 }
380}
381
382void SlotAssignment::assignSlots(ShadowRoot& shadowRoot)
383{
384 ASSERT(!m_slotAssignmentsIsValid);
385 m_slotAssignmentsIsValid = true;
386
387 for (auto& entry : m_slots)
388 entry.value->assignedNodes.shrink(0);
389
390 auto& host = *shadowRoot.host();
391 for (auto* child = host.firstChild(); child; child = child->nextSibling()) {
392 if (!is<Text>(*child) && !is<Element>(*child))
393 continue;
394 auto slotName = slotNameForHostChild(*child);
395 assignToSlot(*child, slotName);
396 }
397
398 for (auto& entry : m_slots)
399 entry.value->assignedNodes.shrinkToFit();
400}
401
402void SlotAssignment::assignToSlot(Node& child, const AtomString& slotName)
403{
404 ASSERT(!slotName.isNull());
405 if (slotName == defaultSlotName()) {
406 auto defaultSlotEntry = m_slots.find(defaultSlotName());
407 if (defaultSlotEntry != m_slots.end())
408 defaultSlotEntry->value->assignedNodes.append(&child);
409 return;
410 }
411
412 auto addResult = m_slots.ensure(slotName, [] {
413 return std::make_unique<Slot>();
414 });
415 addResult.iterator->value->assignedNodes.append(&child);
416}
417
418}
419
420
421