Index: third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp |
diff --git a/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp b/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp |
index a13e9fb731cedae1bc806c0054377f6922f66da2..9c906804626b93e85e67a8852e2a4a585f6364cd 100644 |
--- a/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp |
+++ b/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp |
@@ -14,88 +14,165 @@ |
namespace blink { |
-HTMLSlotElement* SlotAssignment::assignedSlotFor(const Node& node) const |
+void SlotEntry::add(HTMLSlotElement& slot) |
{ |
- return m_assignment.get(const_cast<Node*>(&node)); |
+ // Relevant DOM Standard: |
+ // https://dom.spec.whatwg.org/#concept-node-insert |
+ // 6.4: Run assign slotables for a tree with node's tree and a set containing each inclusive descendant of node that is a slot. |
+ if (m_slots.isEmpty()) { |
+ m_slots.add(&slot); |
+ return; |
+ } |
+ HTMLSlotElement& first= *firstSlot(); |
+ DCHECK_NE(first, slot); |
+ m_slots.add(&slot); |
+ if (firstSlot() == &first) |
+ return; |
+ // |first| is no longer an active slot. |
+ if (first.findHostChildWithSameSlotName()) |
kochi
2016/05/24 10:02:56
Can this be simply "if (findHostChildBySlotName(fi
|
+ first.enqueueSlotChangeEvent(); |
+ // TODO(hayato): We should not eneueue a slotchange event for |first| |
+ // if |first| was inserted together with |slot|. |
+ // This could happen if |first| and |slot| are descendants of the inserted node, and |
+ // |first| is preceding |slot|. |
kochi
2016/05/23 06:08:42
Maybe you can add a test case for this case, and n
hayato
2016/05/24 13:23:38
Yes, we should have a test. Let me add a test late
|
} |
-static void detachNotAssignedNode(Node& node) |
+void SlotEntry::remove(HTMLSlotElement& slot) |
{ |
- if (node.layoutObject()) |
- node.lazyReattachIfAttached(); |
+ DCHECK(m_slots.contains(&slot)); |
+ m_slots.remove(&slot); |
+ HTMLSlotElement* first = firstSlot(); |
+ if (first && first != &slot) { |
kochi
2016/05/23 06:08:42
Instead of |first != &slot|, you should check |old
esprehn
2016/05/23 06:41:00
No need for the &, you can compare references and
hayato
2016/05/24 13:23:38
Done
|
+ // |first| slot becomes an active slot. |
+ if (first->findHostChildWithSameSlotName()) |
+ first->enqueueSlotChangeEvent(); |
+ // TODO(hayato): Prevent a false-positve slotchange. |
+ // This could happen if more than one slots which have the same name are descendants of the removed node. |
+ } |
} |
-void SlotAssignment::resolveAssignment(ShadowRoot& shadowRoot) |
+DEFINE_TRACE(SlotEntry) |
{ |
- m_assignment.clear(); |
+ visitor->trace(m_slots); |
+} |
- using Name2Slot = HeapHashMap<AtomicString, Member<HTMLSlotElement>>; |
- Name2Slot name2slot; |
+HTMLSlotElement* SlotEntry::firstSlot() const |
+{ |
+ return size() ? toHTMLSlotElement(*m_slots.begin()) : nullptr; |
esprehn
2016/05/23 06:41:00
You want .first()
hayato
2016/05/24 13:23:39
Done
|
+} |
- const HeapVector<Member<HTMLSlotElement>>& slots = shadowRoot.descendantSlots(); |
+SlotEntry& SlotAssignment::entry(const AtomicString& name) |
+{ |
+ auto addResult = m_slotEntryMap.add(name, nullptr); |
+ if (addResult.isNewEntry) |
+ addResult.storedValue->value = SlotEntry::create(); |
+ return *addResult.storedValue->value; |
+} |
- for (Member<HTMLSlotElement> slot : slots) { |
- slot->willUpdateAssignment(); |
- slot->willUpdateFallback(); |
- name2slot.add(slot->name(), slot.get()); |
- } |
+void SlotAssignment::slotAdded(HTMLSlotElement& slot) |
+{ |
+ ++m_slotCount; |
+ entry(slot.name()).add(slot); |
esprehn
2016/05/23 06:41:00
findEntryByName ?
hayato
2016/05/24 13:23:39
Done. Because this method creates and return a new
|
+ m_needsCollectSlots = true; |
+} |
- for (Node& child : NodeTraversal::childrenOf(*shadowRoot.host())) { |
- if (child.isInsertionPoint()) { |
- // A re-distribution across v0 and v1 shadow trees is not supported. |
- detachNotAssignedNode(child); |
+void SlotAssignment::slotRemoved(HTMLSlotElement& slot) |
+{ |
+ DCHECK_GT(m_slotCount, 0u); |
+ --m_slotCount; |
+ entry(slot.name()).remove(slot); |
+ m_needsCollectSlots = true; |
+} |
+ |
+bool SlotAssignment::findHostChildBySlotName(const AtomicString& slotName) const |
+{ |
+ // TODO(hayato): Avoid traversing children every time. |
kochi
2016/05/23 06:08:42
Can we shortcut the traverse if any slot with |slo
hayato
2016/05/24 13:23:38
No. Slot's assigned nodes are not updated at this
kochi
2016/05/25 05:37:11
Acknowledged.
|
+ for (Node& child : NodeTraversal::childrenOf(*m_owner->host())) { |
+ if (!child.isSlotable()) |
continue; |
- } |
- if (!child.slottable()) { |
+ if (child.slotName() == slotName) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+void SlotAssignment::slotRenamed(const AtomicString& oldSlotName, HTMLSlotElement& slot) |
+{ |
+ // |slot| has already new name. Thus, we can not use slot.hasAssignedNodesSynchronously. |
+ bool hasAssignedNodesBefore = (findSlotByName(oldSlotName) == &slot) && findHostChildBySlotName(oldSlotName); |
+ |
+ entry(oldSlotName).remove(slot); |
kochi
2016/05/23 06:08:42
Shall we remove the entry from |m_slotEntryMap| wh
hayato
2016/05/24 13:23:39
We can. However, it is very rare. I am afraid that
kochi
2016/05/25 05:37:11
Even it's rare, do we (kind of) leak empty slot en
kochi
2016/05/25 06:40:26
Ah, it is no longer necessary because now we use
D
|
+ entry(slot.name()).add(slot); |
+ |
+ bool hasAssignedNodesAfter = slot.hasAssignedNodesSynchronously(); |
+ |
+ if (hasAssignedNodesBefore || hasAssignedNodesAfter) |
+ slot.enqueueSlotChangeEvent(); |
+} |
+ |
+void SlotAssignment::hostChildSlotNameChanged(const AtomicString& oldValue, const AtomicString& newValue) |
+{ |
+ if (HTMLSlotElement* slot = findSlotByName(HTMLSlotElement::normalizeSlotName(oldValue))) { |
+ slot->enqueueSlotChangeEvent(); |
+ m_owner->owner()->setNeedsDistributionRecalc(); |
+ } |
+ if (HTMLSlotElement* slot = findSlotByName(HTMLSlotElement::normalizeSlotName(newValue))) { |
+ slot->enqueueSlotChangeEvent(); |
+ m_owner->owner()->setNeedsDistributionRecalc(); |
+ } |
+} |
+ |
+SlotAssignment::SlotAssignment(ShadowRoot& owner) |
+ : m_owner(&owner) |
+{ |
+} |
+ |
+static void detachNotAssignedNode(Node& node) |
+{ |
+ if (node.layoutObject()) |
+ node.lazyReattachIfAttached(); |
+} |
+ |
+void SlotAssignment::resolveAssignment() |
+{ |
+ for (Member<HTMLSlotElement> slot : slots()) |
+ slot->clearDistribution(); |
+ |
+ for (Node& child : NodeTraversal::childrenOf(*m_owner->host())) { |
+ if (!child.isSlotable()) { |
detachNotAssignedNode(child); |
continue; |
} |
- AtomicString slotName = child.slotName(); |
- HTMLSlotElement* slot = name2slot.get(slotName); |
+ HTMLSlotElement* slot = findSlotByName(child.slotName()); |
if (slot) |
- assign(child, *slot); |
+ assignTo(child, *slot); |
else |
detachNotAssignedNode(child); |
} |
- |
- for (auto slot = slots.rbegin(); slot != slots.rend(); ++slot) |
- (*slot)->updateFallbackNodes(); |
- |
- // For each slot, check if assigned nodes have changed |
- // If so, call fireSlotchange function |
- for (const auto& slot : slots) |
- slot->didUpdateAssignment(); |
} |
-void SlotAssignment::resolveDistribution(ShadowRoot& shadowRoot) |
+void SlotAssignment::resolveDistribution() |
{ |
- const HeapVector<Member<HTMLSlotElement>>& slots = shadowRoot.descendantSlots(); |
- for (Member<HTMLSlotElement> slot : slots) { |
- slot->willUpdateDistribution(); |
- } |
- |
+ resolveAssignment(); |
+ const HeapVector<Member<HTMLSlotElement>>& slots = this->slots(); |
for (auto slot : slots) { |
for (auto node : slot->assignedNodes()) |
- distribute(*node, *slot); |
+ distributeTo(*node, *slot); |
} |
- |
// Update each slot's distribution in reverse tree order so that a child slot is visited before its parent slot. |
for (auto slot = slots.rbegin(); slot != slots.rend(); ++slot) |
(*slot)->updateDistributedNodesWithFallback(); |
- for (const auto& slot : slots) |
- slot->didUpdateDistribution(); |
} |
-void SlotAssignment::assign(Node& hostChild, HTMLSlotElement& slot) |
+void SlotAssignment::assignTo(Node& hostChild, HTMLSlotElement& slot) |
{ |
- DCHECK(hostChild.isSlotAssignable()); |
- m_assignment.add(&hostChild, &slot); |
+ DCHECK(hostChild.isSlotable()); |
slot.appendAssignedNode(hostChild); |
} |
-void SlotAssignment::distribute(Node& hostChild, HTMLSlotElement& slot) |
+void SlotAssignment::distributeTo(Node& hostChild, HTMLSlotElement& slot) |
{ |
- DCHECK(hostChild.isSlotAssignable()); |
+ DCHECK(hostChild.isSlotable()); |
if (isHTMLSlotElement(hostChild)) |
slot.appendDistributedNodesFrom(toHTMLSlotElement(hostChild)); |
else |
@@ -105,10 +182,41 @@ void SlotAssignment::distribute(Node& hostChild, HTMLSlotElement& slot) |
slot.parentElementShadow()->setNeedsDistributionRecalc(); |
} |
+const HeapVector<Member<HTMLSlotElement>>& SlotAssignment::slots() |
+{ |
+ if (m_needsCollectSlots) |
+ collectSlots(); |
+ return m_slots; |
+} |
+ |
+HTMLSlotElement* SlotAssignment::findSlot(const Node& node) |
+{ |
+ return node.isSlotable() ? findSlotByName(node.slotName()) : nullptr; |
+} |
+ |
+HTMLSlotElement* SlotAssignment::findSlotByName(const AtomicString& slotName) |
+{ |
+ return entry(slotName).firstSlot(); |
esprehn
2016/05/23 06:41:00
Hmm, we should just reuse the same map machinery w
hayato
2016/05/24 13:23:39
Ops. Thank you for letting me notice that. It look
|
+} |
+ |
+void SlotAssignment::collectSlots() |
+{ |
+ DCHECK(m_needsCollectSlots); |
+ m_slots.clear(); |
+ |
+ m_slots.reserveCapacity(m_slotCount); |
+ for (HTMLSlotElement& slot : Traversal<HTMLSlotElement>::descendantsOf(*m_owner)) { |
esprehn
2016/05/23 06:41:00
we can just use HTMLSlotElement::insertedInto and
hayato
2016/05/24 13:23:38
Yeah, but we need an ordered list of *all* slots,
|
+ m_slots.append(&slot); |
+ } |
+ m_needsCollectSlots = false; |
+ DCHECK_EQ(m_slots.size(), m_slotCount); |
+} |
+ |
DEFINE_TRACE(SlotAssignment) |
{ |
- visitor->trace(m_descendantSlots); |
- visitor->trace(m_assignment); |
+ visitor->trace(m_slots); |
+ visitor->trace(m_slotEntryMap); |
+ visitor->trace(m_owner); |
} |
} // namespace blink |