Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "core/dom/shadow/SlotAssignment.h" | 5 #include "core/dom/shadow/SlotAssignment.h" |
| 6 | 6 |
| 7 #include "core/HTMLNames.h" | 7 #include "core/HTMLNames.h" |
| 8 #include "core/dom/ElementTraversal.h" | 8 #include "core/dom/ElementTraversal.h" |
| 9 #include "core/dom/NodeTraversal.h" | 9 #include "core/dom/NodeTraversal.h" |
| 10 #include "core/dom/shadow/ElementShadow.h" | 10 #include "core/dom/shadow/ElementShadow.h" |
| 11 #include "core/dom/shadow/InsertionPoint.h" | 11 #include "core/dom/shadow/InsertionPoint.h" |
| 12 #include "core/dom/shadow/ShadowRoot.h" | 12 #include "core/dom/shadow/ShadowRoot.h" |
| 13 #include "core/html/HTMLSlotElement.h" | 13 #include "core/html/HTMLSlotElement.h" |
| 14 | 14 |
| 15 namespace blink { | 15 namespace blink { |
| 16 | 16 |
| 17 HTMLSlotElement* SlotAssignment::assignedSlotFor(const Node& node) const | 17 void SlotEntry::add(HTMLSlotElement& slot) |
| 18 { | 18 { |
| 19 return m_assignment.get(const_cast<Node*>(&node)); | 19 // Relevant DOM Standard: |
| 20 // https://dom.spec.whatwg.org/#concept-node-insert | |
| 21 // 6.4: Run assign slotables for a tree with node's tree and a set containi ng each inclusive descendant of node that is a slot. | |
| 22 if (m_slots.isEmpty()) { | |
| 23 m_slots.add(&slot); | |
| 24 return; | |
| 25 } | |
| 26 HTMLSlotElement& first= *firstSlot(); | |
| 27 DCHECK_NE(first, slot); | |
| 28 m_slots.add(&slot); | |
| 29 if (firstSlot() == &first) | |
| 30 return; | |
| 31 // |first| is no longer an active slot. | |
| 32 if (first.findHostChildWithSameSlotName()) | |
|
kochi
2016/05/24 10:02:56
Can this be simply "if (findHostChildBySlotName(fi
| |
| 33 first.enqueueSlotChangeEvent(); | |
| 34 // TODO(hayato): We should not eneueue a slotchange event for |first| | |
| 35 // if |first| was inserted together with |slot|. | |
| 36 // This could happen if |first| and |slot| are descendants of the inserted n ode, and | |
| 37 // |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
| |
| 38 } | |
| 39 | |
| 40 void SlotEntry::remove(HTMLSlotElement& slot) | |
| 41 { | |
| 42 DCHECK(m_slots.contains(&slot)); | |
| 43 m_slots.remove(&slot); | |
| 44 HTMLSlotElement* first = firstSlot(); | |
| 45 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
| |
| 46 // |first| slot becomes an active slot. | |
| 47 if (first->findHostChildWithSameSlotName()) | |
| 48 first->enqueueSlotChangeEvent(); | |
| 49 // TODO(hayato): Prevent a false-positve slotchange. | |
| 50 // This could happen if more than one slots which have the same name are descendants of the removed node. | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 DEFINE_TRACE(SlotEntry) | |
| 55 { | |
| 56 visitor->trace(m_slots); | |
| 57 } | |
| 58 | |
| 59 HTMLSlotElement* SlotEntry::firstSlot() const | |
| 60 { | |
| 61 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
| |
| 62 } | |
| 63 | |
| 64 SlotEntry& SlotAssignment::entry(const AtomicString& name) | |
| 65 { | |
| 66 auto addResult = m_slotEntryMap.add(name, nullptr); | |
| 67 if (addResult.isNewEntry) | |
| 68 addResult.storedValue->value = SlotEntry::create(); | |
| 69 return *addResult.storedValue->value; | |
| 70 } | |
| 71 | |
| 72 void SlotAssignment::slotAdded(HTMLSlotElement& slot) | |
| 73 { | |
| 74 ++m_slotCount; | |
| 75 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
| |
| 76 m_needsCollectSlots = true; | |
| 77 } | |
| 78 | |
| 79 void SlotAssignment::slotRemoved(HTMLSlotElement& slot) | |
| 80 { | |
| 81 DCHECK_GT(m_slotCount, 0u); | |
| 82 --m_slotCount; | |
| 83 entry(slot.name()).remove(slot); | |
| 84 m_needsCollectSlots = true; | |
| 85 } | |
| 86 | |
| 87 bool SlotAssignment::findHostChildBySlotName(const AtomicString& slotName) const | |
| 88 { | |
| 89 // 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.
| |
| 90 for (Node& child : NodeTraversal::childrenOf(*m_owner->host())) { | |
| 91 if (!child.isSlotable()) | |
| 92 continue; | |
| 93 if (child.slotName() == slotName) | |
| 94 return true; | |
| 95 } | |
| 96 return false; | |
| 97 } | |
| 98 | |
| 99 void SlotAssignment::slotRenamed(const AtomicString& oldSlotName, HTMLSlotElemen t& slot) | |
| 100 { | |
| 101 // |slot| has already new name. Thus, we can not use slot.hasAssignedNodesSy nchronously. | |
| 102 bool hasAssignedNodesBefore = (findSlotByName(oldSlotName) == &slot) && find HostChildBySlotName(oldSlotName); | |
| 103 | |
| 104 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
| |
| 105 entry(slot.name()).add(slot); | |
| 106 | |
| 107 bool hasAssignedNodesAfter = slot.hasAssignedNodesSynchronously(); | |
| 108 | |
| 109 if (hasAssignedNodesBefore || hasAssignedNodesAfter) | |
| 110 slot.enqueueSlotChangeEvent(); | |
| 111 } | |
| 112 | |
| 113 void SlotAssignment::hostChildSlotNameChanged(const AtomicString& oldValue, cons t AtomicString& newValue) | |
| 114 { | |
| 115 if (HTMLSlotElement* slot = findSlotByName(HTMLSlotElement::normalizeSlotNam e(oldValue))) { | |
| 116 slot->enqueueSlotChangeEvent(); | |
| 117 m_owner->owner()->setNeedsDistributionRecalc(); | |
| 118 } | |
| 119 if (HTMLSlotElement* slot = findSlotByName(HTMLSlotElement::normalizeSlotNam e(newValue))) { | |
| 120 slot->enqueueSlotChangeEvent(); | |
| 121 m_owner->owner()->setNeedsDistributionRecalc(); | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 SlotAssignment::SlotAssignment(ShadowRoot& owner) | |
| 126 : m_owner(&owner) | |
| 127 { | |
| 20 } | 128 } |
| 21 | 129 |
| 22 static void detachNotAssignedNode(Node& node) | 130 static void detachNotAssignedNode(Node& node) |
| 23 { | 131 { |
| 24 if (node.layoutObject()) | 132 if (node.layoutObject()) |
| 25 node.lazyReattachIfAttached(); | 133 node.lazyReattachIfAttached(); |
| 26 } | 134 } |
| 27 | 135 |
| 28 void SlotAssignment::resolveAssignment(ShadowRoot& shadowRoot) | 136 void SlotAssignment::resolveAssignment() |
| 29 { | 137 { |
| 30 m_assignment.clear(); | 138 for (Member<HTMLSlotElement> slot : slots()) |
| 139 slot->clearDistribution(); | |
| 31 | 140 |
| 32 using Name2Slot = HeapHashMap<AtomicString, Member<HTMLSlotElement>>; | 141 for (Node& child : NodeTraversal::childrenOf(*m_owner->host())) { |
| 33 Name2Slot name2slot; | 142 if (!child.isSlotable()) { |
| 34 | |
| 35 const HeapVector<Member<HTMLSlotElement>>& slots = shadowRoot.descendantSlot s(); | |
| 36 | |
| 37 for (Member<HTMLSlotElement> slot : slots) { | |
| 38 slot->willUpdateAssignment(); | |
| 39 slot->willUpdateFallback(); | |
| 40 name2slot.add(slot->name(), slot.get()); | |
| 41 } | |
| 42 | |
| 43 for (Node& child : NodeTraversal::childrenOf(*shadowRoot.host())) { | |
| 44 if (child.isInsertionPoint()) { | |
| 45 // A re-distribution across v0 and v1 shadow trees is not supported. | |
| 46 detachNotAssignedNode(child); | 143 detachNotAssignedNode(child); |
| 47 continue; | 144 continue; |
| 48 } | 145 } |
| 49 if (!child.slottable()) { | 146 HTMLSlotElement* slot = findSlotByName(child.slotName()); |
| 50 detachNotAssignedNode(child); | |
| 51 continue; | |
| 52 } | |
| 53 AtomicString slotName = child.slotName(); | |
| 54 HTMLSlotElement* slot = name2slot.get(slotName); | |
| 55 if (slot) | 147 if (slot) |
| 56 assign(child, *slot); | 148 assignTo(child, *slot); |
| 57 else | 149 else |
| 58 detachNotAssignedNode(child); | 150 detachNotAssignedNode(child); |
| 59 } | 151 } |
| 60 | |
| 61 for (auto slot = slots.rbegin(); slot != slots.rend(); ++slot) | |
| 62 (*slot)->updateFallbackNodes(); | |
| 63 | |
| 64 // For each slot, check if assigned nodes have changed | |
| 65 // If so, call fireSlotchange function | |
| 66 for (const auto& slot : slots) | |
| 67 slot->didUpdateAssignment(); | |
| 68 } | 152 } |
| 69 | 153 |
| 70 void SlotAssignment::resolveDistribution(ShadowRoot& shadowRoot) | 154 void SlotAssignment::resolveDistribution() |
| 71 { | 155 { |
| 72 const HeapVector<Member<HTMLSlotElement>>& slots = shadowRoot.descendantSlot s(); | 156 resolveAssignment(); |
| 73 for (Member<HTMLSlotElement> slot : slots) { | 157 const HeapVector<Member<HTMLSlotElement>>& slots = this->slots(); |
| 74 slot->willUpdateDistribution(); | |
| 75 } | |
| 76 | |
| 77 for (auto slot : slots) { | 158 for (auto slot : slots) { |
| 78 for (auto node : slot->assignedNodes()) | 159 for (auto node : slot->assignedNodes()) |
| 79 distribute(*node, *slot); | 160 distributeTo(*node, *slot); |
| 80 } | 161 } |
| 81 | |
| 82 // Update each slot's distribution in reverse tree order so that a child slo t is visited before its parent slot. | 162 // Update each slot's distribution in reverse tree order so that a child slo t is visited before its parent slot. |
| 83 for (auto slot = slots.rbegin(); slot != slots.rend(); ++slot) | 163 for (auto slot = slots.rbegin(); slot != slots.rend(); ++slot) |
| 84 (*slot)->updateDistributedNodesWithFallback(); | 164 (*slot)->updateDistributedNodesWithFallback(); |
| 85 for (const auto& slot : slots) | |
| 86 slot->didUpdateDistribution(); | |
| 87 } | 165 } |
| 88 | 166 |
| 89 void SlotAssignment::assign(Node& hostChild, HTMLSlotElement& slot) | 167 void SlotAssignment::assignTo(Node& hostChild, HTMLSlotElement& slot) |
| 90 { | 168 { |
| 91 DCHECK(hostChild.isSlotAssignable()); | 169 DCHECK(hostChild.isSlotable()); |
| 92 m_assignment.add(&hostChild, &slot); | |
| 93 slot.appendAssignedNode(hostChild); | 170 slot.appendAssignedNode(hostChild); |
| 94 } | 171 } |
| 95 | 172 |
| 96 void SlotAssignment::distribute(Node& hostChild, HTMLSlotElement& slot) | 173 void SlotAssignment::distributeTo(Node& hostChild, HTMLSlotElement& slot) |
| 97 { | 174 { |
| 98 DCHECK(hostChild.isSlotAssignable()); | 175 DCHECK(hostChild.isSlotable()); |
| 99 if (isHTMLSlotElement(hostChild)) | 176 if (isHTMLSlotElement(hostChild)) |
| 100 slot.appendDistributedNodesFrom(toHTMLSlotElement(hostChild)); | 177 slot.appendDistributedNodesFrom(toHTMLSlotElement(hostChild)); |
| 101 else | 178 else |
| 102 slot.appendDistributedNode(hostChild); | 179 slot.appendDistributedNode(hostChild); |
| 103 | 180 |
| 104 if (slot.isChildOfV1ShadowHost()) | 181 if (slot.isChildOfV1ShadowHost()) |
| 105 slot.parentElementShadow()->setNeedsDistributionRecalc(); | 182 slot.parentElementShadow()->setNeedsDistributionRecalc(); |
| 106 } | 183 } |
| 107 | 184 |
| 185 const HeapVector<Member<HTMLSlotElement>>& SlotAssignment::slots() | |
| 186 { | |
| 187 if (m_needsCollectSlots) | |
| 188 collectSlots(); | |
| 189 return m_slots; | |
| 190 } | |
| 191 | |
| 192 HTMLSlotElement* SlotAssignment::findSlot(const Node& node) | |
| 193 { | |
| 194 return node.isSlotable() ? findSlotByName(node.slotName()) : nullptr; | |
| 195 } | |
| 196 | |
| 197 HTMLSlotElement* SlotAssignment::findSlotByName(const AtomicString& slotName) | |
| 198 { | |
| 199 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
| |
| 200 } | |
| 201 | |
| 202 void SlotAssignment::collectSlots() | |
| 203 { | |
| 204 DCHECK(m_needsCollectSlots); | |
| 205 m_slots.clear(); | |
| 206 | |
| 207 m_slots.reserveCapacity(m_slotCount); | |
| 208 for (HTMLSlotElement& slot : Traversal<HTMLSlotElement>::descendantsOf(*m_ow ner)) { | |
|
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,
| |
| 209 m_slots.append(&slot); | |
| 210 } | |
| 211 m_needsCollectSlots = false; | |
| 212 DCHECK_EQ(m_slots.size(), m_slotCount); | |
| 213 } | |
| 214 | |
| 108 DEFINE_TRACE(SlotAssignment) | 215 DEFINE_TRACE(SlotAssignment) |
| 109 { | 216 { |
| 110 visitor->trace(m_descendantSlots); | 217 visitor->trace(m_slots); |
| 111 visitor->trace(m_assignment); | 218 visitor->trace(m_slotEntryMap); |
| 219 visitor->trace(m_owner); | |
| 112 } | 220 } |
| 113 | 221 |
| 114 } // namespace blink | 222 } // namespace blink |
| OLD | NEW |