Commit c8c9ce1b authored by Kyoko Muto's avatar Kyoko Muto Committed by Commit Bot

Reimplement imperative shadowdom API for better performance

In this CL, we add hashset that control all the assigned slots
in the shadowroot for each node.
Thanks to this hashset we can find the assigned slot for each
node in O(1) order, instead of searching all the slot for each
shadow root.
Other changes about slot change event will be added in separate
CL.

Bug: 869308

See design doc at https://docs.google.com/document/d/1n2iDNkI5bF4B5eefxa0SRSyS_RVchvTNSqezvA4PyF4/edit#heading=h.dkp0e2job8nf

Change-Id: I60284316902417efd692988b449fe660af8349c5
Reviewed-on: https://chromium-review.googlesource.com/1195298
Commit-Queue: Kyoko Muto <kymuto@google.com>
Reviewed-by: default avatarHayato Ito <hayato@chromium.org>
Reviewed-by: default avatarRakina Zata Amni <rakina@chromium.org>
Cr-Commit-Position: refs/heads/master@{#592731}
parent 8655a4f8
...@@ -22,7 +22,9 @@ See https://crbug.com/869308 ...@@ -22,7 +22,9 @@ See https://crbug.com/869308
<div id="host4"> <div id="host4">
<div id="child5"></div> <div id="child5"></div>
</div> </div>
</div>
<div id="host5">
</div>
<script> <script>
const host = document.querySelector('#host'); const host = document.querySelector('#host');
...@@ -36,13 +38,17 @@ const child4 = document.querySelector('#child4'); ...@@ -36,13 +38,17 @@ const child4 = document.querySelector('#child4');
const child5 = document.querySelector('#child5'); const child5 = document.querySelector('#child5');
const shadow_root = host.attachShadow({ mode: 'open', slotting: 'manual' }); const shadow_root = host.attachShadow({ mode: 'open', slotting: 'manual' });
const shadow_root1 = host4.attachShadow({ mode: 'open' }); const shadow_root1 = host4.attachShadow({ mode: 'open' });
const shadow_root2 = host5.attachShadow({ mode: 'open', slotting: 'manual' });
const slot1 = document.createElement('slot'); const slot1 = document.createElement('slot');
const slot2 = document.createElement('slot'); const slot2 = document.createElement('slot');
const slot3 = document.createElement('slot'); const slot3 = document.createElement('slot');
const slot4 = document.createElement('slot'); const slot4 = document.createElement('slot');
const slot5 = document.createElement('slot');
const slot6 = document.createElement('slot');
shadow_root.appendChild(slot1); shadow_root.appendChild(slot1);
shadow_root.appendChild(slot2); shadow_root.appendChild(slot2);
shadow_root.appendChild(slot3); shadow_root.appendChild(slot3);
shadow_root.appendChild(slot6);
shadow_root1.appendChild(slot4); shadow_root1.appendChild(slot4);
test(() => { test(() => {
...@@ -84,13 +90,48 @@ test(() => { ...@@ -84,13 +90,48 @@ test(() => {
}, 'Nodes should be assigned to slot only when nodes are host\'s children'); }, 'Nodes should be assigned to slot only when nodes are host\'s children');
test(() => { test(() => {
slot3.assign([child1]); slot5.assign([child1]);
assert_array_equals(slot3.assignedNodes(), []); assert_array_equals(slot5.assignedNodes(), []);
shadow_root.insertBefore(slot3,slot1); shadow_root.insertBefore(slot5,slot1);
assert_array_equals(slot3.assignedNodes(), [child1]); assert_array_equals(slot5.assignedNodes(), [child1]);
}, 'Nodes should be assigned to a slot only when the slot is in host\'s shadowtree'); }, 'Nodes should be assigned to a slot only when the slot is in host\'s shadowtree');
test(() => {
slot1.assign([child2]);
slot2.assign([child2]);
assert_array_equals(slot2.assignedNodes(), []);
shadow_root.insertBefore(slot2,slot1);
assert_array_equals(slot2.assignedNodes(), [child2]);
}, 'A node should be assigned to a slot when the slot is inserted before the assigned slot');
test(() => {
shadow_root.appendChild(slot1);
shadow_root.appendChild(slot2);
shadow_root.appendChild(slot6);
slot1.assign([child2]);
slot2.assign([child2]);
slot6.assign([child2]);
assert_array_equals(slot1.assignedNodes(), [child2]);
shadow_root.insertBefore(slot6,slot2);
slot1.remove();
assert_array_equals(slot6.assignedNodes(), [child2]);
}, 'A slot should be assigned to a node even after the slot is inserted after the assigned slot, and the assigned slot are removed')
test(() => {
shadow_root.insertBefore(slot1,slot2);
slot2.assign([child3,child3]);
assert_array_equals(slot2.assignedNodes(), [child3]);
slot1.assign([child3]);
assert_array_equals(slot2.assignedNodes(), []);
shadow_root2.appendChild(slot2);
assert_array_equals(slot2.assignedNodes(), []);
}, 'Same nodes should be ignored');
test(() => { test(() => {
assert_array_equals(slot4.assignedElements(), [child5]); assert_array_equals(slot4.assignedElements(), [child5]);
assert_equals(child5.assignedSlot, slot4); assert_equals(child5.assignedSlot, slot4);
......
...@@ -61,6 +61,8 @@ void SlotAssignment::DidRemoveSlot(HTMLSlotElement& slot) { ...@@ -61,6 +61,8 @@ void SlotAssignment::DidRemoveSlot(HTMLSlotElement& slot) {
if (owner_->IsManualSlotting()) { if (owner_->IsManualSlotting()) {
DCHECK(!needs_collect_slots_); DCHECK(!needs_collect_slots_);
CallSlotChangeIfNeeded(slot); CallSlotChangeIfNeeded(slot);
DeleteSlotInChildSlotMap(slot);
needs_collect_slots_ = true; needs_collect_slots_ = true;
// TODO(crbug.com/869308):Avoid calling Slots in order not to hit the // TODO(crbug.com/869308):Avoid calling Slots in order not to hit the
// DCHECK(!needs_collect_slots_) // DCHECK(!needs_collect_slots_)
...@@ -133,6 +135,9 @@ void SlotAssignment::DidAddSlotInternalInManualMode(HTMLSlotElement& slot) { ...@@ -133,6 +135,9 @@ void SlotAssignment::DidAddSlotInternalInManualMode(HTMLSlotElement& slot) {
change_slot->SignalSlotChange(); change_slot->SignalSlotChange();
} }
} }
for (auto node : slot.AssignedNodesCandidate()) {
InsertSlotInChildSlotMap(slot, *node);
}
} }
void SlotAssignment::DidRemoveSlotInternal( void SlotAssignment::DidRemoveSlotInternal(
...@@ -266,12 +271,9 @@ void SlotAssignment::RecalcAssignment() { ...@@ -266,12 +271,9 @@ void SlotAssignment::RecalcAssignment() {
HTMLSlotElement* slot = nullptr; HTMLSlotElement* slot = nullptr;
if (!is_user_agent) { if (!is_user_agent) {
if (owner_->IsManualSlotting()) { if (owner_->IsManualSlotting()) {
for (auto a_slot : Slots()) { auto it = node_to_assigned_slot_candidate_in_tree_order_.find(&child);
if (a_slot->ContainsInAssignedNodesCandidates(child)) { if (it != node_to_assigned_slot_candidate_in_tree_order_.end())
slot = a_slot; slot = *it.Get()->value.begin();
break;
}
}
} else { } else {
slot = FindSlotByName(child.SlotName()); slot = FindSlotByName(child.SlotName());
} }
...@@ -418,11 +420,57 @@ void SlotAssignment::CallSlotChangeIfNeeded(HTMLSlotElement& slot) { ...@@ -418,11 +420,57 @@ void SlotAssignment::CallSlotChangeIfNeeded(HTMLSlotElement& slot) {
} }
} }
HTMLSlotElement* SlotAssignment::FindFirstAssignedSlot(Node& node) { void SlotAssignment::DeleteSlotInChildSlotMap(HTMLSlotElement& slot) {
for (auto slot : Slots()) { for (auto itr = slot.AssignedNodesCandidate().begin();
if (slot->ContainsInAssignedNodesCandidates(node)) itr != slot.AssignedNodesCandidate().end(); ++itr) {
return slot; auto it_child = node_to_assigned_slot_candidate_in_tree_order_.find(*itr);
if (it_child != node_to_assigned_slot_candidate_in_tree_order_.end()) {
HeapVector<Member<HTMLSlotElement>>& assigned_slots =
it_child.Get()->value;
auto position = assigned_slots.Find(slot);
if (assigned_slots.size() == 1) {
node_to_assigned_slot_candidate_in_tree_order_.erase(it_child);
continue;
}
it_child.Get()->value.EraseAt(position);
}
} }
}
void SlotAssignment::InsertSlotInChildSlotMap(HTMLSlotElement& new_slot,
Node& child) {
auto child_it = node_to_assigned_slot_candidate_in_tree_order_.find(&child);
if (child_it == node_to_assigned_slot_candidate_in_tree_order_.end()) {
HeapVector<Member<HTMLSlotElement>> assigned_slot_;
assigned_slot_.push_back(new_slot);
node_to_assigned_slot_candidate_in_tree_order_.Set(&child, assigned_slot_);
} else {
// Find the correct spot for the slot, and the assigned_slots's contents
// line up in tree order, so that the first assigned slot in tree order
// can be found for each child.
unsigned int assigned_slot_order = 0;
HeapVector<Member<HTMLSlotElement>>& assigned_slots = child_it.Get()->value;
for (auto slot : Slots()) {
if (slot == new_slot) {
if (slot != assigned_slots[assigned_slot_order])
assigned_slots.insert(assigned_slot_order, new_slot);
break;
}
if (slot == assigned_slots[assigned_slot_order]) {
assigned_slot_order++;
if (assigned_slot_order == assigned_slots.size()) {
assigned_slots.push_back(new_slot);
break;
}
}
}
}
}
HTMLSlotElement* SlotAssignment::FindFirstAssignedSlot(Node& node) {
auto it = node_to_assigned_slot_candidate_in_tree_order_.find(&node);
if (it != node_to_assigned_slot_candidate_in_tree_order_.end())
return *it.Get()->value.begin();
return nullptr; return nullptr;
} }
...@@ -452,6 +500,7 @@ void SlotAssignment::Trace(blink::Visitor* visitor) { ...@@ -452,6 +500,7 @@ void SlotAssignment::Trace(blink::Visitor* visitor) {
visitor->Trace(slots_); visitor->Trace(slots_);
visitor->Trace(slot_map_); visitor->Trace(slot_map_);
visitor->Trace(owner_); visitor->Trace(owner_);
visitor->Trace(node_to_assigned_slot_candidate_in_tree_order_);
} }
} // namespace blink } // namespace blink
...@@ -46,6 +46,9 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> { ...@@ -46,6 +46,9 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> {
void CallSlotChangeIfNeeded(HTMLSlotElement& slot); void CallSlotChangeIfNeeded(HTMLSlotElement& slot);
HTMLSlotElement* FindSlotChange(HTMLSlotElement& slot, Node& child); HTMLSlotElement* FindSlotChange(HTMLSlotElement& slot, Node& child);
void DeleteSlotInChildSlotMap(HTMLSlotElement& slot);
void InsertSlotInChildSlotMap(HTMLSlotElement& slot, Node& child);
void Trace(blink::Visitor*); void Trace(blink::Visitor*);
// For Incremental Shadow DOM // For Incremental Shadow DOM
...@@ -84,6 +87,8 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> { ...@@ -84,6 +87,8 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> {
HeapVector<Member<HTMLSlotElement>> slots_; HeapVector<Member<HTMLSlotElement>> slots_;
Member<TreeOrderedMap> slot_map_; Member<TreeOrderedMap> slot_map_;
WeakMember<ShadowRoot> owner_; WeakMember<ShadowRoot> owner_;
HeapHashMap<Member<Node>, HeapVector<Member<HTMLSlotElement>>>
node_to_assigned_slot_candidate_in_tree_order_;
unsigned needs_collect_slots_ : 1; unsigned needs_collect_slots_ : 1;
unsigned needs_assignment_recalc_ : 1; // For Incremental Shadow DOM unsigned needs_assignment_recalc_ : 1; // For Incremental Shadow DOM
unsigned slot_count_ : 30; unsigned slot_count_ : 30;
......
...@@ -208,6 +208,7 @@ void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes) { ...@@ -208,6 +208,7 @@ void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes) {
? SlotChangeType::kSuppressSlotChangeEvent ? SlotChangeType::kSuppressSlotChangeEvent
: SlotChangeType::kSignalSlotChangeEvent; : SlotChangeType::kSignalSlotChangeEvent;
DidSlotChange(slot_change_type); DidSlotChange(slot_change_type);
ContainingShadowRoot()->GetSlotAssignment().DeleteSlotInChildSlotMap(*this);
} }
assigned_nodes_candidates_.clear(); assigned_nodes_candidates_.clear();
for (Member<Node> child : nodes) { for (Member<Node> child : nodes) {
...@@ -218,6 +219,10 @@ void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes) { ...@@ -218,6 +219,10 @@ void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes) {
// TODO(crbug.com/869308): Don't call SetNeedsAssignmentRecalc if we can // TODO(crbug.com/869308): Don't call SetNeedsAssignmentRecalc if we can
// detect all possible slotchange surely // detect all possible slotchange surely
ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc(); ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc();
for (auto child : nodes) {
ContainingShadowRoot()->GetSlotAssignment().InsertSlotInChildSlotMap(
*this, *child);
}
} }
} }
......
...@@ -59,6 +59,9 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement { ...@@ -59,6 +59,9 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement {
void assign(HeapVector<Member<Node>> nodes); void assign(HeapVector<Member<Node>> nodes);
bool ContainsInAssignedNodesCandidates(Node&) const; bool ContainsInAssignedNodesCandidates(Node&) const;
void SignalSlotChange(); void SignalSlotChange();
HeapHashSet<Member<Node>>& AssignedNodesCandidate() {
return assigned_nodes_candidates_;
}
const HeapVector<Member<Node>> FlattenedAssignedNodes(); const HeapVector<Member<Node>> FlattenedAssignedNodes();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment