Commit 723ff451 authored by Yu Han's avatar Yu Han Committed by Commit Bot

Adds ability to restore previous state in manual slot assignment when encountered exception.

Previous to this CL, during manual slot assignment, if an exception is
thrown, the state of the HTMLSlotElement is indeterminate.
HTMLSlotElement should be restored to the previous state where previous
assigned nodes are preserved.

This CL implements this behavior by doing the exception check of
assigned nodes before the actual assignment. Thus, if an exception
is encountered, the assign() function could just return, preserving
the previous state. However, this way of doing slot assignment adds
an extra loop for assigned nodes. I had tried to do it in
one loop. But that required saving the previous state of the Slot
and restoring it if exception is detected. I think the added
complexity isn't worth it for the little performance benefit.

In addition, I made sure that the state of the
candidate_assigned_slot_map_ is accurate after each slot
assignment recalc. The previous implementation could leave removed
nodes still in the map. Because in SlotAssignment:RecalcAssignment(),
the children traversal, NodeTraversal::ChildrenOf(owner_->host(),
wouldn't encounter removed nodes.

Bug: 869308
Change-Id: I3c8938f1c4360d664497b6d16a7753c545729e6f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2124293
Commit-Queue: Yu Han <yuzhehan@chromium.org>
Reviewed-by: default avatarMason Freed <masonfreed@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757723}
parent 2ac3eec3
...@@ -260,13 +260,18 @@ void SlotAssignment::RecalcAssignment() { ...@@ -260,13 +260,18 @@ void SlotAssignment::RecalcAssignment() {
FindSlotByName(HTMLSlotElement::UserAgentCustomAssignSlotName()); FindSlotByName(HTMLSlotElement::UserAgentCustomAssignSlotName());
} }
bool is_manual_slot_assignment = owner_->IsManualSlotting();
// Replaces candidate_assigned_slot_map_ after the loop, to avoid stale
// references resulting from calls to slot->DidRecalcAssignedNodes().
HeapHashMap<Member<Node>, Member<HTMLSlotElement>> candidate_map;
for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) { for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) {
if (!child.IsSlotable()) if (!child.IsSlotable())
continue; continue;
HTMLSlotElement* slot = nullptr; HTMLSlotElement* slot = nullptr;
if (!is_user_agent) { if (!is_user_agent) {
if (owner_->IsManualSlotting()) { if (is_manual_slot_assignment) {
if (auto* candidate_slot = candidate_assigned_slot_map_.at(&child)) { if (auto* candidate_slot = candidate_assigned_slot_map_.at(&child)) {
if (candidate_slot->ContainingShadowRoot() == owner_) { if (candidate_slot->ContainingShadowRoot() == owner_) {
slot = candidate_slot; slot = candidate_slot;
...@@ -300,6 +305,8 @@ void SlotAssignment::RecalcAssignment() { ...@@ -300,6 +305,8 @@ void SlotAssignment::RecalcAssignment() {
if (slot) { if (slot) {
slot->AppendAssignedNode(child); slot->AppendAssignedNode(child);
if (is_manual_slot_assignment)
candidate_map.Set(&child, slot);
} else { } else {
child.ClearFlatTreeNodeData(); child.ClearFlatTreeNodeData();
child.RemovedFromFlatTree(); child.RemovedFromFlatTree();
...@@ -314,6 +321,9 @@ void SlotAssignment::RecalcAssignment() { ...@@ -314,6 +321,9 @@ void SlotAssignment::RecalcAssignment() {
for (auto& slot : Slots()) for (auto& slot : Slots())
slot->DidRecalcAssignedNodes(); slot->DidRecalcAssignedNodes();
if (is_manual_slot_assignment)
candidate_assigned_slot_map_.swap(candidate_map);
} }
const HeapVector<Member<HTMLSlotElement>>& SlotAssignment::Slots() { const HeapVector<Member<HTMLSlotElement>>& SlotAssignment::Slots() {
......
...@@ -171,6 +171,21 @@ const HeapVector<Member<Element>> HTMLSlotElement::AssignedElementsForBinding( ...@@ -171,6 +171,21 @@ const HeapVector<Member<Element>> HTMLSlotElement::AssignedElementsForBinding(
return elements; return elements;
} }
bool HTMLSlotElement::CheckNodesValidity(HeapVector<Member<Node>> nodes,
ExceptionState& exception_state) {
auto* host = OwnerShadowHost();
for (auto& node : nodes) {
if (node->parentNode() != host) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"Node: '" + node->nodeName() +
"' is invalid for manual slot assignment.");
return false;
}
}
return true;
}
void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes, void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes,
ExceptionState& exception_state) { ExceptionState& exception_state) {
if (!SupportsAssignment() || !ContainingShadowRoot()->IsManualSlotting()) { if (!SupportsAssignment() || !ContainingShadowRoot()->IsManualSlotting()) {
...@@ -180,30 +195,19 @@ void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes, ...@@ -180,30 +195,19 @@ void HTMLSlotElement::assign(HeapVector<Member<Node>> nodes,
return; return;
} }
if (!CheckNodesValidity(nodes, exception_state))
return;
ContainingShadowRoot()->GetSlotAssignment().ClearCandidateNodes( ContainingShadowRoot()->GetSlotAssignment().ClearCandidateNodes(
assigned_nodes_candidates_); assigned_nodes_candidates_);
assigned_nodes_candidates_.clear(); assigned_nodes_candidates_.clear();
auto* host = OwnerShadowHost();
bool has_invalid_node = false;
for (auto& node : nodes) { for (auto& node : nodes) {
if (node->parentNode() != host) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"Node: '" + node->nodeName() +
"' is invalid for manual slot assignment.");
assigned_nodes_candidates_.clear();
has_invalid_node = true;
break;
}
// Before assignment, see if this node belongs to another slot. // Before assignment, see if this node belongs to another slot.
ContainingShadowRoot()->GetSlotAssignment().UpdateCandidateNodeAssignedSlot( ContainingShadowRoot()->GetSlotAssignment().UpdateCandidateNodeAssignedSlot(
*node, *this); *node, *this);
assigned_nodes_candidates_.AppendOrMoveToLast(node); assigned_nodes_candidates_.AppendOrMoveToLast(node);
} }
ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc();
if (!has_invalid_node)
ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc();
} }
void HTMLSlotElement::AppendAssignedNode(Node& host_child) { void HTMLSlotElement::AppendAssignedNode(Node& host_child) {
......
...@@ -138,6 +138,7 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement { ...@@ -138,6 +138,7 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement {
const HeapVector<Member<Node>>& new_slotted); const HeapVector<Member<Node>>& new_slotted);
void SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc(); void SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
bool CheckNodesValidity(HeapVector<Member<Node>> nodes, ExceptionState&);
// SlotAssignnment:recalc runs in tree order. Update to assigned order. // SlotAssignnment:recalc runs in tree order. Update to assigned order.
void UpdateManuallyAssignedNodesOrdering(); void UpdateManuallyAssignedNodesOrdering();
......
...@@ -6,7 +6,7 @@ PASS Imperative slot API can assign nodes in manual slot assignment. ...@@ -6,7 +6,7 @@ PASS Imperative slot API can assign nodes in manual slot assignment.
PASS Order of slotables is preserved in manual slot assignment. PASS Order of slotables is preserved in manual slot assignment.
PASS Previously assigned slotable is moved to new slot when it's reassigned. PASS Previously assigned slotable is moved to new slot when it's reassigned.
PASS Order and assignment of nodes are preserved during multiple assignment in a row. PASS Order and assignment of nodes are preserved during multiple assignment in a row.
FAIL Assigning invalid nodes causes exception and slot returns to its previous state. assert_array_equals: lengths differ, expected array [] length 0, got [Element node <div id="c1"></div>, Element node <div id="c2"></div>] length 2 PASS Assigning invalid nodes causes exception and slot returns to its previous state.
PASS Moving a slot to a new host, the slot loses its previously assigned slotables. PASS Moving a slot to a new host, the slot loses its previously assigned slotables.
PASS Moving a slot's tree order position within a shadow host has no impact on its assigned slotables. PASS Moving a slot's tree order position within a shadow host has no impact on its assigned slotables.
PASS Appending slotable to different host, it loses slot assignment. It can be re-assigned within a new host. PASS Appending slotable to different host, it loses slot assignment. It can be re-assigned within a new host.
......
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