Commit 01261f59 authored by Kyoko Muto's avatar Kyoko Muto Committed by Commit Bot

Support slot change event in slotting-manual mode in shadow tree

This CL makes slotting-manual mode support slot change event
which is already supported in auto mode.

BUG:869308

Change-Id: I106d1037036e5e34d552cf7ce17eacd8f453fc9c
Reviewed-on: https://chromium-review.googlesource.com/1179555Reviewed-by: default avatarHayato Ito <hayato@chromium.org>
Commit-Queue: Kyoko Muto <kymuto@google.com>
Cr-Commit-Position: refs/heads/master@{#585362}
parent 53201f58
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="resources/shadow-dom.js"></script>
<div id="test1">
<div id="host1">
<template id="shadowroot" data-mode="open" data-slotting="manual">
<slot id="s1" name="slot1"></slot>
<slot id="s2" name="slot2"></slot>
</template>
<div id="c1"></div>
<div id="c2"></div>
</div>
</div>
<script>
function doneIfSlotChange(slots, test) {
let fired = new Set();
for (let slot of slots) {
slot.addEventListener('slotchange', test.step_func((e) => {
assert_false(fired.has(slot.id));
fired.add(slot.id);
if (fired.size == slots.length) {
test.done();
}
}))
}
}
async_test((test) => {
let n = createTestTree(test1);
assert_array_equals(n.s2.assignedNodes(), []);
n.s2.assign([n.c2]);
assert_array_equals(n.s2.assignedNodes(), [n.c2]);
setTimeout(function () {
doneIfSlotChange([n.s1,n.s2], test);
assert_array_equals(n.s2.assignedNodes(), [n.c2]);
n.s1.assign([n.c2]);
assert_array_equals(n.s2.assignedNodes(), []);
},1);
}, 'slotchange event: assign a child to a host.');
async_test((test) => {
let n = createTestTree(test1);
assert_array_equals(n.s1.assignedNodes(), []);
let d1 = document.createElement('div');
n.s1.assign([d1]);
assert_array_equals(n.s1.assignedNodes(), []);
setTimeout(function () {
doneIfSlotChange([n.s1], test);
n.host1.appendChild(d1);
},1);
}, 'slotchange event: Append a child to a host.');
async_test((test) => {
let n = createTestTree(test1);
assert_array_equals(n.s1.assignedNodes(), []);
n.s1.assign([n.c1]);
assert_array_equals(n.s1.assignedNodes(), [n.c1]);
setTimeout(function () {
doneIfSlotChange([n.s1], test);
n.c1.remove();
},1);
}, 'slotchange event: Remove a child to a host.');
async_test((test) => {
let n = createTestTree(test1);
n.s1.assign([n.c1]);
n.s2.assign([n.c1]);
setTimeout(function () {
doneIfSlotChange([n.s2], test);
n.s1.remove();
},1);
}, 'slotchange event: Remove a slot from a shadow tree.');
async_test((test) => {
let n = createTestTree(test1);
n.s1.assign([n.c1]);
let slot = document.createElement('slot');
slot.assign([n.c1])
assert_array_equals(slot.assignedNodes(), []);
setTimeout(function () {
doneIfSlotChange([slot,n.s1], test);
n.shadowroot.insertBefore(slot, n.s1);
assert_array_equals(n.s1.assignedNodes(), []);
},1);
}, 'slotchange event: Insert a slot before another slot.');
</script>
...@@ -76,6 +76,10 @@ function createTestTree(node) { ...@@ -76,6 +76,10 @@ function createTestTree(node) {
if (template.getAttribute('data-mode') === 'v0') { if (template.getAttribute('data-mode') === 'v0') {
// For legacy Shadow DOM // For legacy Shadow DOM
shadowRoot = parent.createShadowRoot(); shadowRoot = parent.createShadowRoot();
} else if (template.getAttribute('data-slotting') === 'manual') {
shadowRoot =
parent.attachShadow({mode: template.getAttribute('data-mode'),
slotting: 'manual'});
} else { } else {
shadowRoot = shadowRoot =
parent.attachShadow({mode: template.getAttribute('data-mode')}); parent.attachShadow({mode: template.getAttribute('data-mode')});
......
...@@ -37,6 +37,10 @@ void SlotAssignment::DidAddSlot(HTMLSlotElement& slot) { ...@@ -37,6 +37,10 @@ void SlotAssignment::DidAddSlot(HTMLSlotElement& slot) {
++slot_count_; ++slot_count_;
needs_collect_slots_ = true; needs_collect_slots_ = true;
if (owner_->IsManualSlotting()) {
DidAddSlotInternalInManualMode(slot);
return;
}
DCHECK(!slot_map_->Contains(slot.GetName()) || DCHECK(!slot_map_->Contains(slot.GetName()) ||
GetCachedFirstSlotWithoutAccessingNodeTree(slot.GetName())); GetCachedFirstSlotWithoutAccessingNodeTree(slot.GetName()));
...@@ -54,6 +58,15 @@ void SlotAssignment::DidRemoveSlot(HTMLSlotElement& slot) { ...@@ -54,6 +58,15 @@ void SlotAssignment::DidRemoveSlot(HTMLSlotElement& slot) {
DCHECK_GT(slot_count_, 0u); DCHECK_GT(slot_count_, 0u);
--slot_count_; --slot_count_;
if (owner_->IsManualSlotting()) {
DCHECK(!needs_collect_slots_);
CallSlotChangeIfNeeded(slot);
needs_collect_slots_ = true;
// TODO(crbug.com/869308):Avoid calling Slots in order not to hit the
// DCHECK(!needs_collect_slots_)
Slots();
return;
}
needs_collect_slots_ = true; needs_collect_slots_ = true;
DCHECK(GetCachedFirstSlotWithoutAccessingNodeTree(slot.GetName())); DCHECK(GetCachedFirstSlotWithoutAccessingNodeTree(slot.GetName()));
...@@ -111,6 +124,17 @@ void SlotAssignment::DidAddSlotInternal(HTMLSlotElement& slot) { ...@@ -111,6 +124,17 @@ void SlotAssignment::DidAddSlotInternal(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();
}
}
}
void SlotAssignment::DidRemoveSlotInternal( void SlotAssignment::DidRemoveSlotInternal(
HTMLSlotElement& slot, HTMLSlotElement& slot,
const AtomicString& slot_name, const AtomicString& slot_name,
...@@ -365,6 +389,35 @@ HTMLSlotElement* SlotAssignment::FindSlotInUserAgentShadow( ...@@ -365,6 +389,35 @@ HTMLSlotElement* SlotAssignment::FindSlotInUserAgentShadow(
return user_agent_default_slot; return user_agent_default_slot;
} }
HTMLSlotElement* SlotAssignment::FindSlotChange(HTMLSlotElement& slot,
Node& child) {
HTMLSlotElement* found_this_slot = nullptr;
for (auto a_slot : Slots()) {
if (a_slot == slot) {
found_this_slot = &slot;
continue;
}
if (a_slot->ContainsInAssignedNodesCandidates(child)) {
if (found_this_slot) {
// case2 in DidRemoveSlotChange or DidAddSlotChange
return a_slot;
}
// case3 in DidRemoveSlotChange or DidAddSlotChange
return nullptr;
}
}
// case1 in DidRemoveSlotChange or DidAddSlotChange or no slot for the child
return found_this_slot;
}
void SlotAssignment::CallSlotChangeIfNeeded(HTMLSlotElement& slot) {
for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) {
auto* change_slot = FindSlotChange(slot, child);
if (change_slot && change_slot != slot)
change_slot->SignalSlotChange();
}
}
HTMLSlotElement* SlotAssignment::FindFirstAssignedSlot(Node& node) { HTMLSlotElement* SlotAssignment::FindFirstAssignedSlot(Node& node) {
for (auto slot : Slots()) { for (auto slot : Slots()) {
if (slot->ContainsInAssignedNodesCandidates(node)) if (slot->ContainsInAssignedNodesCandidates(node))
......
...@@ -43,6 +43,8 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> { ...@@ -43,6 +43,8 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> {
const AtomicString& new_value); const AtomicString& new_value);
bool FindHostChildBySlotName(const AtomicString& slot_name) const; bool FindHostChildBySlotName(const AtomicString& slot_name) const;
void CallSlotChangeIfNeeded(HTMLSlotElement& slot);
HTMLSlotElement* FindSlotChange(HTMLSlotElement& slot, Node& child);
void Trace(blink::Visitor*); void Trace(blink::Visitor*);
...@@ -71,6 +73,7 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> { ...@@ -71,6 +73,7 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> {
const AtomicString& slot_name); const AtomicString& slot_name);
void DidAddSlotInternal(HTMLSlotElement&); void DidAddSlotInternal(HTMLSlotElement&);
void DidAddSlotInternalInManualMode(HTMLSlotElement&);
void DidRemoveSlotInternal(HTMLSlotElement&, void DidRemoveSlotInternal(HTMLSlotElement&,
const AtomicString& slot_name, const AtomicString& slot_name,
SlotMutationType); SlotMutationType);
......
...@@ -189,12 +189,36 @@ const HeapVector<Member<Element>> HTMLSlotElement::AssignedElementsForBinding( ...@@ -189,12 +189,36 @@ const HeapVector<Member<Element>> HTMLSlotElement::AssignedElementsForBinding(
return elements; return elements;
} }
bool HTMLSlotElement::IsAssignedNodeSameWithBefore(
HeapVector<Member<Node>>& new_assigned_nodes,
HeapHashSet<Member<Node>>& old_assigned_nodes) {
if (new_assigned_nodes.size() != old_assigned_nodes.size())
return false;
for (auto node : old_assigned_nodes) {
if (!new_assigned_nodes.Contains(*node))
return false;
}
return true;
}
void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes) { void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes) {
ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc(); if (ContainingShadowRoot()) {
SlotChangeType slot_change_type =
IsAssignedNodeSameWithBefore(nodes, assigned_nodes_candidates_)
? SlotChangeType::kSuppressSlotChangeEvent
: SlotChangeType::kSignalSlotChangeEvent;
DidSlotChange(slot_change_type);
}
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);
} }
if (ContainingShadowRoot()) {
ContainingShadowRoot()->GetSlotAssignment().CallSlotChangeIfNeeded(*this);
// TODO(crbug.com/869308): Don't call SetNeedsAssignmentRecalc if we can
// detect all possible slotchange surely
ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc();
}
} }
bool HTMLSlotElement::ContainsInAssignedNodesCandidates( bool HTMLSlotElement::ContainsInAssignedNodesCandidates(
...@@ -202,6 +226,10 @@ bool HTMLSlotElement::ContainsInAssignedNodesCandidates( ...@@ -202,6 +226,10 @@ bool HTMLSlotElement::ContainsInAssignedNodesCandidates(
return assigned_nodes_candidates_.Contains(&host_child); return assigned_nodes_candidates_.Contains(&host_child);
} }
void HTMLSlotElement::SignalSlotChange() {
DidSlotChange(SlotChangeType::kSignalSlotChangeEvent);
}
const HeapVector<Member<Node>>& HTMLSlotElement::GetDistributedNodes() { const HeapVector<Member<Node>>& HTMLSlotElement::GetDistributedNodes() {
DCHECK(!RuntimeEnabledFeatures::IncrementalShadowDOMEnabled()); DCHECK(!RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
DCHECK(!NeedsDistributionRecalc()); DCHECK(!NeedsDistributionRecalc());
......
...@@ -53,8 +53,12 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement { ...@@ -53,8 +53,12 @@ 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>>& 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();
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