Commit 03c1a559 authored by Kyoko Muto's avatar Kyoko Muto Committed by Commit Bot

Reimplement imperative shadowdom API for supporting slot change event

In this CL, we reuse hashset that control all the assigned slots
in the shadowroot for each node, added previous CL.
Thanks to the hashset we can find the assigned slot for each
node in O(1) order, instead of searching all the slot for each
shadow root when we call slot change event.

We will improve this implementation in another CL.

Result of perftests:
https://pinpoint-dot-chromeperf.appspot.com/job/151f9a47640000

Previous implementation CL: crrev.com/c/1179555

See design doc at
https://docs.google.com/document/d/1n2iDNkI5bF4B5eefxa0SRSyS_RVchvTNSqezvA4PyF4/edit?usp=sharing

Bug: 869308
Change-Id: I96fc4c50d9083eca199c7849db0097e73436983d
Reviewed-on: https://chromium-review.googlesource.com/1201513
Commit-Queue: Kyoko Muto <kymuto@google.com>
Reviewed-by: default avatarHayato Ito <hayato@chromium.org>
Cr-Commit-Position: refs/heads/master@{#592785}
parent d8fab434
...@@ -41,7 +41,39 @@ async_test((test) => { ...@@ -41,7 +41,39 @@ async_test((test) => {
n.s1.assign([n.c2]); n.s1.assign([n.c2]);
assert_array_equals(n.s2.assignedNodes(), []); assert_array_equals(n.s2.assignedNodes(), []);
},1); },1);
}, 'slotchange event: assign a child to a host.'); }, 'slotchange event: Assign a assigned child to another slot.');
async_test((test) => {
let n = createTestTree(test1);
n.s1.assign([n.c1]);
n.s2.assign([n.c1]);
setTimeout(function () {
doneIfSlotChange([n.s1,n.s2], test);
n.s1.assign([])
},1);
}, 'slotchange event: Deassign a child from a slot, and assigned to another slot.');
async_test((test) => {
let n = createTestTree(test1);
n.s1.assign([n.c1]);
setTimeout(function () {
doneIfSlotChange([n.s1], test);
n.s1.assign([n.c1,n.c2]);
},1);
}, 'slotchange event: Assign a new child to a slot.');
async_test((test) => {
let n = createTestTree(test1);
n.s1.assign([n.c1,n.c2]);
setTimeout(function () {
doneIfSlotChange([n.s1], test);
n.s1.assign([n.c1])
},1);
}, 'slotchange event: Deassign a child from a slot.');
async_test((test) => { async_test((test) => {
let n = createTestTree(test1); let n = createTestTree(test1);
...@@ -66,15 +98,25 @@ async_test((test) => { ...@@ -66,15 +98,25 @@ async_test((test) => {
doneIfSlotChange([n.s1], test); doneIfSlotChange([n.s1], test);
n.c1.remove(); n.c1.remove();
},1); },1);
}, 'slotchange event: Remove a child to a host.'); }, 'slotchange event: Remove a child from a host.');
async_test((test) => {
let n = createTestTree(test1);
let slot = document.createElement('slot');
slot.assign([n.c1])
setTimeout(function () {
doneIfSlotChange([slot], test);
n.shadowroot.appendChild(slot);
},1);
}, 'slotchange event: Insert a slot.');
async_test((test) => { async_test((test) => {
let n = createTestTree(test1); let n = createTestTree(test1);
n.s1.assign([n.c1]); n.s1.assign([n.c1]);
n.s2.assign([n.c1]);
setTimeout(function () { setTimeout(function () {
doneIfSlotChange([n.s2], test); doneIfSlotChange([n.s1], test);
n.s1.remove(); n.s1.remove();
},1); },1);
}, 'slotchange event: Remove a slot from a shadow tree.'); }, 'slotchange event: Remove a slot from a shadow tree.');
...@@ -84,7 +126,7 @@ async_test((test) => { ...@@ -84,7 +126,7 @@ async_test((test) => {
n.s1.assign([n.c1]); n.s1.assign([n.c1]);
let slot = document.createElement('slot'); let slot = document.createElement('slot');
slot.assign([n.c1]) slot.assign([n.c1])
assert_array_equals(slot.assignedNodes(), []); assert_array_equals(n.s1.assignedNodes(), [n.c1]);
setTimeout(function () { setTimeout(function () {
doneIfSlotChange([slot,n.s1], test); doneIfSlotChange([slot,n.s1], test);
...@@ -92,4 +134,66 @@ async_test((test) => { ...@@ -92,4 +134,66 @@ async_test((test) => {
assert_array_equals(n.s1.assignedNodes(), []); assert_array_equals(n.s1.assignedNodes(), []);
},1); },1);
}, 'slotchange event: Insert a slot before another slot.'); }, 'slotchange event: Insert a slot before another slot.');
async_test((test) => {
let n = createTestTree(test1);
n.s1.assign([n.c1]);
n.s2.assign([n.c1]);
setTimeout(function () {
doneIfSlotChange([n.s1,n.s2], test);
n.s1.remove();
},1);
}, 'slotchange event: Remove a preceding slot from a shadow tree.');
</script>
<div id="test2">
<div id="host1">
<template id="shadowroot" data-mode="open" data-slotting="manual">
<div id="host2">
<template id="shadowroot1" data-mode="open" data-slotting="manual">
<slot id="s3"></slot>
<slot id="s2"></slot>
</template>
<slot id="s1"></slot>
</div>
</template>
<div id="c1"></div>
</div>
</div>
<script>
async_test((test) => {
let n = createTestTree(test2);
n.s2.assign([n.s1]);
n.s3.assign([n.s1,n.c1]);
n.s1.assign([n.c1]);
assert_array_equals(n.s3.assignedNodes(), [n.s1]);
setTimeout(function () {
doneIfSlotChange([n.s1,n.s2,n.s3], test);
n.host2.insertBefore(n.s3,n.s1);
assert_array_equals(n.s2.assignedNodes(), [n.s1]);
},1);
}, 'slotchange event: Move a slot before another slot.');
async_test((test) => {
let n = createTestTree(test2);
n.s1.assign([n.c1]);
n.s2.assign([n.s1]);
setTimeout(function () {
doneIfSlotChange([n.s1,n.s2], test);
n.c1.remove();
},1);
}, 'slotchange event: A slot is assigned to another slot.');
async_test((test) => {
let n = createTestTree(test2);
n.s2.assign([n.s1]);
setTimeout(function () {
doneIfSlotChange([n.s2], test);
n.s1.remove();
},1);
}, 'slotchange event: Even if distributed nodes do not change, slotchange should be fired if assigned nodes are changed.');
</script> </script>
...@@ -5,139 +5,161 @@ See https://crbug.com/869308 ...@@ -5,139 +5,161 @@ See https://crbug.com/869308
--> -->
<script src="../resources/testharness.js"></script> <script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script> <script src="../resources/testharnessreport.js"></script>
<div id="host"> <script src="resources/shadow-dom.js"></script>
<div id="child1"></div> <div id="test1">
<div id="child2"></div> <div id="host"></div>
<div id="child3"></div> <div id="host1"></div>
<div id="host2"></div>
</div> </div>
<div id="host1">
</div>
</div>
<div id="host2">
</div>
</div>
<div id="host3">
</div>
<div id="child4"></div>
<div id="host4">
<div id="child5"></div>
</div>
</div>
<div id="host5">
</div>
<script> <script>
const host = document.querySelector('#host');
const host1 = document.querySelector('#host1');
const host2 = document.querySelector('#host2');
const host3 = document.querySelector('#host3');
const child1 = document.querySelector('#child1');
const child2 = document.querySelector('#child2');
const child3 = document.querySelector('#child3');
const child4 = document.querySelector('#child4');
const child5 = document.querySelector('#child5');
const shadow_root = host.attachShadow({ mode: 'open', slotting: 'manual' });
const shadow_root1 = host4.attachShadow({ mode: 'open' });
const shadow_root2 = host5.attachShadow({ mode: 'open', slotting: 'manual' });
const slot1 = document.createElement('slot');
const slot2 = document.createElement('slot');
const slot3 = 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(slot2);
shadow_root.appendChild(slot3);
shadow_root.appendChild(slot6);
shadow_root1.appendChild(slot4);
test(() => { test(() => {
assert_not_equals(host1.attachShadow({ mode: 'open', slotting: 'manual' }), let n = createTestTree(test1);
assert_not_equals(n.host.attachShadow({ mode: 'open', slotting: 'manual' }),
null, 'slotting manual should work'); null, 'slotting manual should work');
assert_not_equals(host2.attachShadow({ mode: 'open', slotting: 'auto' }), assert_not_equals(n.host1.attachShadow({ mode: 'open', slotting: 'auto' }),
null, 'slotting auto should work'); null, 'slotting auto should work');
assert_throws(new TypeError(), () => { assert_throws(new TypeError(), () => {
host3.attachShadow({ mode: 'open', slotting: 'exceptional' })}, n.host2.attachShadow({ mode: 'open', slotting: 'exceptional' })},
'others should throw exception'); 'others should throw exception');
}, 'attachShadow can take slotting parameter'); }, 'attachShadow can take slotting parameter');
</script>
<div id="test2">
<div id="host">
<template id="shadow_root" data-mode="open" data-slotting="manual">
<slot id="s1"></slot>
<slot id="s2"></slot>
<slot id="s3"></slot>
</template>
<div id="c1"></div>
<div id="c2"></div>
<div id="c3"></div>
</div>
<div id="c4"></div>
<div id="host4">
<template id="shadow_root1" data-mode="open" data-slotting="manual">
<slot id="s5" name="s5"></slot>
</template>
</div>
</div>
<script>
test(() => { test(() => {
assert_array_equals(slot2.assignedElements(), []); let n = createTestTree(test2);
assert_equals(child1.assignedSlot, null); assert_array_equals(n.s2.assignedElements(), []);
assert_equals(n.c1.assignedSlot, null);
slot2.assign([child1]); n.s2.assign([n.c1]);
assert_array_equals(slot2.assignedNodes(), [child1]); assert_array_equals(n.s2.assignedNodes(), [n.c1]);
assert_equals(child1.assignedSlot, slot2); assert_equals(n.c1.assignedSlot, n.s2);
slot1.assign([child2,child1]); n.s1.assign([n.c2,n.c1]);
assert_array_equals(slot1.assignedNodes(), [child1,child2]); assert_array_equals(n.s1.assignedNodes(), [n.c1,n.c2]);
assert_array_equals(slot2.assignedNodes(), []); assert_array_equals(n.s2.assignedNodes(), []);
assert_equals(child1.assignedSlot, slot1); assert_equals(n.c1.assignedSlot, n.s1);
assert_equals(child2.assignedSlot, slot1); assert_equals(n.c2.assignedSlot, n.s1);
slot1.assign([child2]); n.s1.assign([n.c2]);
assert_array_equals(slot1.assignedNodes(), [child2]); assert_array_equals(n.s1.assignedNodes(), [n.c2]);
assert_array_equals(slot2.assignedNodes(), [child1]); assert_array_equals(n.s2.assignedNodes(), [n.c1]);
}, 'assignedNodes/Slot can be used in manual slotting'); }, 'assignedNodes/Slot can be used in manual slotting');
test(() => { test(() => {
slot3.assign([child4]); let n = createTestTree(test2);
assert_array_equals(slot3.assignedNodes(), []); n.s1.assign([n.c4]);
assert_array_equals(n.s1.assignedNodes(), []);
host.appendChild(child4); n.host.appendChild(n.c4);
assert_array_equals(slot3.assignedNodes(), [child4]); assert_array_equals(n.s1.assignedNodes(), [n.c4]);
}, '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(() => {
slot5.assign([child1]); let n = createTestTree(test2);
assert_array_equals(slot5.assignedNodes(), []); n.s5.assign([n.c1]);
assert_array_equals(n.s5.assignedNodes(), []);
shadow_root.insertBefore(slot5,slot1); n.shadow_root.insertBefore(n.s5,n.s1);
assert_array_equals(slot5.assignedNodes(), [child1]); assert_array_equals(n.s5.assignedNodes(), [n.c1]);
}, '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(() => { test(() => {
slot1.assign([child2]); let n = createTestTree(test2);
slot2.assign([child2]); n.s1.assign([n.c1]);
assert_array_equals(slot2.assignedNodes(), []); n.s2.assign([n.c1]);
assert_array_equals(n.s2.assignedNodes(), []);
shadow_root.insertBefore(slot2,slot1); n.shadow_root.insertBefore(n.s2,n.s1);
assert_array_equals(slot2.assignedNodes(), [child2]); assert_array_equals(n.s2.assignedNodes(), [n.c1]);
}, 'A node should be assigned to a slot when the slot is inserted before the assigned slot'); }, 'A node should be assigned to a slot when the slot is inserted before the assigned slot');
test(() => { test(() => {
shadow_root.appendChild(slot1); let n = createTestTree(test2);
shadow_root.appendChild(slot2); n.s1.assign([n.c1]);
shadow_root.appendChild(slot6); n.s2.assign([n.c1]);
slot1.assign([child2]); n.s3.assign([n.c1]);
slot2.assign([child2]); n.shadow_root.insertBefore(n.s2,n.s1);
slot6.assign([child2]); n.shadow_root.insertBefore(n.s3,n.s1);
assert_array_equals(slot1.assignedNodes(), [child2]);
n.s2.remove();
shadow_root.insertBefore(slot6,slot2);
slot1.remove(); assert_array_equals(n.s1.assignedNodes(), []);
assert_array_equals(slot6.assignedNodes(), [child2]); assert_array_equals(n.s3.assignedNodes(), [n.c1]);
}, 'A slot should be assigned to a node even after the slot is inserted after the assigned slot, and the assigned slot are removed') }, '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(() => { test(() => {
shadow_root.insertBefore(slot1,slot2); let n = createTestTree(test2);
slot2.assign([child3,child3]); n.s1.assign([n.c1]);
assert_array_equals(slot2.assignedNodes(), [child3]); assert_array_equals(n.s1.assignedNodes(), [n.c1]);
n.shadow_root1.insertBefore(n.s1,n.s5);
assert_array_equals(n.s1.assignedNodes(), []);
n.s1.assign([]);
n.shadow_root.insertBefore(n.s1,n.s2);
slot1.assign([child3]); assert_array_equals(n.s1.assignedNodes(), []);
assert_array_equals(slot2.assignedNodes(), []); }, 'A slot should not be assigned to a node once after the slot is inserted in another shadow root, and assigned another slot.')
shadow_root2.appendChild(slot2); test(() => {
assert_array_equals(slot2.assignedNodes(), []); let n = createTestTree(test2);
n.shadow_root.insertBefore(n.s1,n.s2);
n.s2.assign([n.c1,n.c1]);
assert_array_equals(n.s2.assignedNodes(), [n.c1]);
n.s1.assign([n.c1]);
assert_array_equals(n.s2.assignedNodes(), []);
n.shadow_root1.appendChild(n.s2);
assert_array_equals(n.s2.assignedNodes(), []);
}, 'Same nodes should be ignored'); }, 'Same nodes should be ignored');
test(() => { test(() => {
assert_array_equals(slot4.assignedElements(), [child5]); let n = createTestTree(test2);
assert_equals(child5.assignedSlot, slot4); n.s1.assign([n.c3]);
n.s1.assign([n.c3]);
n.s1.remove();
assert_equals(n.c3.assignedSlot, null);
}, 'Same assignment should be ignored');
</script>
<div id="test3">
<div id="host1">
<template id="shadow_root" data-mode="open">
<slot id="s1" name="s1"></slot>
</template>
<div id="c1" slot="s1"></div>
</div>
</div>
<script>
test(() => {
let n = createTestTree(test3);
assert_array_equals(n.s1.assignedElements(), [n.c1]);
assert_equals(n.c1.assignedSlot, n.s1);
n.s1.assign([]);
assert_array_equals(n.s1.assignedElements(), [n.c1]);
assert_equals(n.c1.assignedSlot, n.s1);
}, 'Slotting API should not have any effect in auto mode');
slot4.assign([]);
assert_array_equals(slot4.assignedElements(), [child5]);
assert_equals(child5.assignedSlot, slot4);
}, 'slotting API should not have any effect in auto mode');
</script> </script>
...@@ -39,6 +39,7 @@ void SlotAssignment::DidAddSlot(HTMLSlotElement& slot) { ...@@ -39,6 +39,7 @@ void SlotAssignment::DidAddSlot(HTMLSlotElement& slot) {
needs_collect_slots_ = true; needs_collect_slots_ = true;
if (owner_->IsManualSlotting()) { if (owner_->IsManualSlotting()) {
DidAddSlotInternalInManualMode(slot); DidAddSlotInternalInManualMode(slot);
SetNeedsAssignmentRecalc();
return; return;
} }
...@@ -59,14 +60,10 @@ void SlotAssignment::DidRemoveSlot(HTMLSlotElement& slot) { ...@@ -59,14 +60,10 @@ void SlotAssignment::DidRemoveSlot(HTMLSlotElement& slot) {
DCHECK_GT(slot_count_, 0u); DCHECK_GT(slot_count_, 0u);
--slot_count_; --slot_count_;
if (owner_->IsManualSlotting()) { if (owner_->IsManualSlotting()) {
DCHECK(!needs_collect_slots_); CallSlotChangeAfterRemoved(slot);
CallSlotChangeIfNeeded(slot);
DeleteSlotInChildSlotMap(slot); DeleteSlotInChildSlotMap(slot);
SetNeedsAssignmentRecalc();
needs_collect_slots_ = true; needs_collect_slots_ = true;
// TODO(crbug.com/869308):Avoid calling Slots in order not to hit the
// DCHECK(!needs_collect_slots_)
Slots();
return; return;
} }
needs_collect_slots_ = true; needs_collect_slots_ = true;
...@@ -127,17 +124,10 @@ void SlotAssignment::DidAddSlotInternal(HTMLSlotElement& slot) { ...@@ -127,17 +124,10 @@ void SlotAssignment::DidAddSlotInternal(HTMLSlotElement& slot) {
} }
void SlotAssignment::DidAddSlotInternalInManualMode(HTMLSlotElement& slot) { void SlotAssignment::DidAddSlotInternalInManualMode(HTMLSlotElement& slot) {
for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) {
auto* change_slot = FindSlotChange(slot, child);
if (change_slot) {
slot.SignalSlotChange();
if (change_slot != slot)
change_slot->SignalSlotChange();
}
}
for (auto node : slot.AssignedNodesCandidate()) { for (auto node : slot.AssignedNodesCandidate()) {
InsertSlotInChildSlotMap(slot, *node); InsertSlotInChildSlotMap(slot, *node);
} }
CallSlotChangeIfNeeded(slot);
} }
void SlotAssignment::DidRemoveSlotInternal( void SlotAssignment::DidRemoveSlotInternal(
...@@ -272,8 +262,10 @@ void SlotAssignment::RecalcAssignment() { ...@@ -272,8 +262,10 @@ void SlotAssignment::RecalcAssignment() {
if (!is_user_agent) { if (!is_user_agent) {
if (owner_->IsManualSlotting()) { if (owner_->IsManualSlotting()) {
auto it = node_to_assigned_slot_candidate_in_tree_order_.find(&child); auto it = node_to_assigned_slot_candidate_in_tree_order_.find(&child);
if (it != node_to_assigned_slot_candidate_in_tree_order_.end()) if (it != node_to_assigned_slot_candidate_in_tree_order_.end()) {
slot = *it.Get()->value.begin(); DCHECK(!it.Get()->value.IsEmpty());
slot = it.Get()->value[0];
}
} else { } else {
slot = FindSlotByName(child.SlotName()); slot = FindSlotByName(child.SlotName());
} }
...@@ -393,30 +385,54 @@ HTMLSlotElement* SlotAssignment::FindSlotInUserAgentShadow( ...@@ -393,30 +385,54 @@ HTMLSlotElement* SlotAssignment::FindSlotInUserAgentShadow(
HTMLSlotElement* SlotAssignment::FindSlotChange(HTMLSlotElement& slot, HTMLSlotElement* SlotAssignment::FindSlotChange(HTMLSlotElement& slot,
Node& child) { Node& child) {
HTMLSlotElement* found_this_slot = nullptr; // Find the changed slot to call slot change event
for (auto a_slot : Slots()) {
if (a_slot == slot) { // There are the following 3 cases for addition:
found_this_slot = &slot; // Before: After: Return
continue; // case 1: [] -> [*slot*] *slot*
} // case 2: [old_active, ...] -> [*slot*, old_active, ...] old_active
if (a_slot->ContainsInAssignedNodesCandidates(child)) { // case 3: [old_active, ...] -> [old_active, ..., *slot*, ...] nullptr
if (found_this_slot) {
// case2 in DidRemoveSlotChange or DidAddSlotChange // Also, there are the following 3 cases for removal:
return a_slot; // Before: After: Return
// case 1: [*slot*] -> [] *slot*
// case 2: [*slot*, new_active, ...] -> [new_active, ...] new_active
// case 3: [new_active, ..., *slot*, ...] -> [new_active, ...] nullptr
auto it = node_to_assigned_slot_candidate_in_tree_order_.find(&child);
if (it != node_to_assigned_slot_candidate_in_tree_order_.end()) {
HeapVector<Member<HTMLSlotElement>>& assigned_slots = it.Get()->value;
if (assigned_slots[0] == slot) {
if (assigned_slots.size() == 1) {
// case1
return &slot;
} }
// case3 in DidRemoveSlotChange or DidAddSlotChange // case2
return nullptr; return assigned_slots[1];
} }
} }
// case1 in DidRemoveSlotChange or DidAddSlotChange or no slot for the child // case3 or no slot for the child
return found_this_slot; return nullptr;
} }
void SlotAssignment::CallSlotChangeIfNeeded(HTMLSlotElement& slot) { void SlotAssignment::CallSlotChangeIfNeeded(HTMLSlotElement& slot) {
for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) { for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) {
auto* change_slot = FindSlotChange(slot, child); auto* changed_slot = FindSlotChange(slot, child);
if (change_slot && change_slot != slot) if (changed_slot) {
change_slot->SignalSlotChange(); slot.SignalSlotChange();
if (changed_slot != slot)
changed_slot->SignalSlotChange();
}
}
}
void SlotAssignment::CallSlotChangeAfterRemoved(HTMLSlotElement& slot) {
for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) {
auto* changed_slot = FindSlotChange(slot, child);
if (changed_slot) {
slot.SignalSlotChangeAfterRemoved();
if (changed_slot != slot)
changed_slot->SignalSlotChange();
}
} }
} }
......
...@@ -44,6 +44,7 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> { ...@@ -44,6 +44,7 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> {
bool FindHostChildBySlotName(const AtomicString& slot_name) const; bool FindHostChildBySlotName(const AtomicString& slot_name) const;
void CallSlotChangeIfNeeded(HTMLSlotElement& slot); void CallSlotChangeIfNeeded(HTMLSlotElement& slot);
void CallSlotChangeAfterRemoved(HTMLSlotElement& slot);
HTMLSlotElement* FindSlotChange(HTMLSlotElement& slot, Node& child); HTMLSlotElement* FindSlotChange(HTMLSlotElement& slot, Node& child);
void DeleteSlotInChildSlotMap(HTMLSlotElement& slot); void DeleteSlotInChildSlotMap(HTMLSlotElement& slot);
......
...@@ -189,40 +189,56 @@ const HeapVector<Member<Element>> HTMLSlotElement::AssignedElementsForBinding( ...@@ -189,40 +189,56 @@ const HeapVector<Member<Element>> HTMLSlotElement::AssignedElementsForBinding(
return elements; return elements;
} }
bool HTMLSlotElement::IsAssignedNodeSameWithBefore( HeapVector<Member<Node>>
HeapVector<Member<Node>>& new_assigned_nodes, HTMLSlotElement::DeleteCommonAssignedNodeAndReturnAddedAssignedNode(
HeapHashSet<Member<Node>>& old_assigned_nodes) { const HeapVector<Member<Node>>& new_assigned_nodes) {
if (new_assigned_nodes.size() != old_assigned_nodes.size()) DCHECK(!assigned_nodes_candidates_.IsEmpty());
return false; if (new_assigned_nodes.IsEmpty())
for (auto node : old_assigned_nodes) { return new_assigned_nodes;
if (!new_assigned_nodes.Contains(*node)) HeapVector<Member<Node>> added_assigned_nodes = new_assigned_nodes;
return false; for (auto node : new_assigned_nodes) {
if (assigned_nodes_candidates_.Contains(node)) {
assigned_nodes_candidates_.erase(node);
added_assigned_nodes.EraseAt(added_assigned_nodes.Find(node));
}
} }
return true; return added_assigned_nodes;
} }
void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes) { void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes) {
// There are the following 2 groups for each of assignednodescandidates:
// Previous AssignedNodesCandidates: AssignedNodesCandidates:
// [{old_assigned_nodes} {same_nodes}]-> [{same_nodes} {new_assigned_nodes}]
// Classify above three groups and insert and call slot change for
// old_assigned_nodes
HeapVector<Member<Node>> added_assigned_nodes;
if (ContainingShadowRoot()) { if (ContainingShadowRoot()) {
SlotChangeType slot_change_type = if (!assigned_nodes_candidates_.IsEmpty()) {
IsAssignedNodeSameWithBefore(nodes, assigned_nodes_candidates_) added_assigned_nodes =
? SlotChangeType::kSuppressSlotChangeEvent DeleteCommonAssignedNodeAndReturnAddedAssignedNode(nodes);
: SlotChangeType::kSignalSlotChangeEvent; ContainingShadowRoot()->GetSlotAssignment().CallSlotChangeIfNeeded(*this);
DidSlotChange(slot_change_type); ContainingShadowRoot()->GetSlotAssignment().DeleteSlotInChildSlotMap(
ContainingShadowRoot()->GetSlotAssignment().DeleteSlotInChildSlotMap(*this); *this);
} else {
added_assigned_nodes = nodes;
}
} }
// Refrash all assigned_nodes_candidates_
assigned_nodes_candidates_.clear(); assigned_nodes_candidates_.clear();
for (Member<Node> child : nodes) { for (Member<Node> child : nodes) {
assigned_nodes_candidates_.insert(child); assigned_nodes_candidates_.insert(child);
} }
// Insert and call slot change for new_assigned_nodes
if (ContainingShadowRoot()) { if (ContainingShadowRoot()) {
ContainingShadowRoot()->GetSlotAssignment().CallSlotChangeIfNeeded(*this);
// 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) { for (auto child : added_assigned_nodes) {
ContainingShadowRoot()->GetSlotAssignment().InsertSlotInChildSlotMap( ContainingShadowRoot()->GetSlotAssignment().InsertSlotInChildSlotMap(
*this, *child); *this, *child);
} }
ContainingShadowRoot()->GetSlotAssignment().CallSlotChangeIfNeeded(*this);
} }
} }
...@@ -235,6 +251,10 @@ void HTMLSlotElement::SignalSlotChange() { ...@@ -235,6 +251,10 @@ void HTMLSlotElement::SignalSlotChange() {
DidSlotChange(SlotChangeType::kSignalSlotChangeEvent); DidSlotChange(SlotChangeType::kSignalSlotChangeEvent);
} }
void HTMLSlotElement::SignalSlotChangeAfterRemoved() {
DidSlotChangeAfterRemovedFromShadowTree();
}
const HeapVector<Member<Node>>& HTMLSlotElement::GetDistributedNodes() { const HeapVector<Member<Node>>& HTMLSlotElement::GetDistributedNodes() {
DCHECK(!RuntimeEnabledFeatures::IncrementalShadowDOMEnabled()); DCHECK(!RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
DCHECK(!NeedsDistributionRecalc()); DCHECK(!NeedsDistributionRecalc());
......
...@@ -53,15 +53,16 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement { ...@@ -53,15 +53,16 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement {
const HeapVector<Member<Element>> AssignedElementsForBinding( const HeapVector<Member<Element>> AssignedElementsForBinding(
const AssignedNodesOptions&); const AssignedNodesOptions&);
bool IsAssignedNodeSameWithBefore( HeapVector<Member<Node>> DeleteCommonAssignedNodeAndReturnAddedAssignedNode(
HeapVector<Member<Node>>& new_assigned_nodes, const HeapVector<Member<Node>>& new_assigned_nodes);
HeapHashSet<Member<Node>>& old_assigned_nodes);
void assign(HeapVector<Member<Node>> nodes); void assign(HeapVector<Member<Node>> nodes);
bool ContainsInAssignedNodesCandidates(Node&) const; bool ContainsInAssignedNodesCandidates(Node&) const;
void SignalSlotChange();
HeapHashSet<Member<Node>>& AssignedNodesCandidate() { HeapHashSet<Member<Node>>& AssignedNodesCandidate() {
return assigned_nodes_candidates_; return assigned_nodes_candidates_;
} }
void SignalSlotChange();
void SignalSlotChangeAfterRemoved();
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