Chromium Code Reviews| 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 |