Commit 31070df4 authored by Hayato Ito's avatar Hayato Ito Committed by Commit Bot

[Incremental Shadow DOM] Resolve assigned nodes incrementally

This is a part of Incremental Shadow DOM.
Resolve slot's assigned nodes incrementally, on-demand basis, instead of
relying on UpdateDistribution phase.

This functionality is guarded by IncrementalShadowDOM runtime flag.

This CL also introduces the utility function used only for testing.
The utility function can process "Mini-DSL" for declarative Shadow DOM.
That is useful for writing a complex composed tree.

Since SlotAssingmentTest is the only user of the function, it is inside
of anonymous namespace.

Bug: 776656
Change-Id: I3359f67eabe562261cdfe2467b17b6b5e46b3c72
Reviewed-on: https://chromium-review.googlesource.com/750941Reviewed-by: default avatarTakayoshi Kochi <kochi@chromium.org>
Commit-Queue: Hayato Ito <hayato@chromium.org>
Cr-Commit-Position: refs/heads/master@{#515883}
parent cd130094
...@@ -1458,6 +1458,7 @@ jumbo_source_set("unit_tests") { ...@@ -1458,6 +1458,7 @@ jumbo_source_set("unit_tests") {
"dom/events/EventTargetTest.cpp", "dom/events/EventTargetTest.cpp",
"dom/events/ListenerLeakTest.cpp", "dom/events/ListenerLeakTest.cpp",
"dom/ng/flat_tree_traversal_ng_test.cc", "dom/ng/flat_tree_traversal_ng_test.cc",
"dom/ng/slot_assignment_test.cpp",
"editing/CaretDisplayItemClientTest.cpp", "editing/CaretDisplayItemClientTest.cpp",
"editing/KeyboardTest.cpp", "editing/KeyboardTest.cpp",
"editing/LinkSelectionTest.cpp", "editing/LinkSelectionTest.cpp",
......
...@@ -2577,7 +2577,7 @@ void Element::ChildrenChanged(const ChildrenChange& change) { ...@@ -2577,7 +2577,7 @@ void Element::ChildrenChanged(const ChildrenChange& change) {
// TODO(hayato): Confirm that we can skip this if a shadow tree is v1. // TODO(hayato): Confirm that we can skip this if a shadow tree is v1.
if (ElementShadow* shadow = Shadow()) if (ElementShadow* shadow = Shadow())
shadow->SetNeedsDistributionRecalc(); shadow->SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
} }
void Element::FinishParsingChildren() { void Element::FinishParsingChildren() {
......
...@@ -125,7 +125,15 @@ void ElementShadow::Detach(const Node::AttachContext& context) { ...@@ -125,7 +125,15 @@ void ElementShadow::Detach(const Node::AttachContext& context) {
root->DetachLayoutTree(children_context); root->DetachLayoutTree(children_context);
} }
void ElementShadow::SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc() {
if (RuntimeEnabledFeatures::IncrementalShadowDOMEnabled() && IsV1())
YoungestShadowRoot().SetNeedsAssignmentRecalc();
else
SetNeedsDistributionRecalc();
}
void ElementShadow::SetNeedsDistributionRecalc() { void ElementShadow::SetNeedsDistributionRecalc() {
DCHECK(!(RuntimeEnabledFeatures::IncrementalShadowDOMEnabled() && IsV1()));
if (needs_distribution_recalc_) if (needs_distribution_recalc_)
return; return;
needs_distribution_recalc_ = true; needs_distribution_recalc_ = true;
......
...@@ -69,6 +69,7 @@ class CORE_EXPORT ElementShadow final : public GarbageCollected<ElementShadow>, ...@@ -69,6 +69,7 @@ class CORE_EXPORT ElementShadow final : public GarbageCollected<ElementShadow>,
void DistributeIfNeeded(); void DistributeIfNeeded();
void SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
void SetNeedsDistributionRecalc(); void SetNeedsDistributionRecalc();
bool NeedsDistributionRecalc() const { return needs_distribution_recalc_; } bool NeedsDistributionRecalc() const { return needs_distribution_recalc_; }
......
...@@ -744,7 +744,19 @@ bool Node::NeedsDistributionRecalc() const { ...@@ -744,7 +744,19 @@ bool Node::NeedsDistributionRecalc() const {
return ShadowIncludingRoot().ChildNeedsDistributionRecalc(); return ShadowIncludingRoot().ChildNeedsDistributionRecalc();
} }
bool Node::MayContainLegacyNodeTreeWhereDistributionShouldBeSupported() const {
if (!RuntimeEnabledFeatures::IncrementalShadowDOMEnabled())
return true;
if (isConnected() && !GetDocument().MayContainV0Shadow()) {
DCHECK(!GetDocument().ChildNeedsDistributionRecalc());
return false;
}
return true;
}
void Node::UpdateDistribution() { void Node::UpdateDistribution() {
if (!MayContainLegacyNodeTreeWhereDistributionShouldBeSupported())
return;
// Extra early out to avoid spamming traces. // Extra early out to avoid spamming traces.
if (isConnected() && !GetDocument().ChildNeedsDistributionRecalc()) if (isConnected() && !GetDocument().ChildNeedsDistributionRecalc())
return; return;
......
...@@ -515,6 +515,7 @@ class CORE_EXPORT Node : public EventTarget { ...@@ -515,6 +515,7 @@ class CORE_EXPORT Node : public EventTarget {
void SetNeedsStyleInvalidation(); void SetNeedsStyleInvalidation();
void UpdateDistribution(); void UpdateDistribution();
bool MayContainLegacyNodeTreeWhereDistributionShouldBeSupported() const;
void SetIsLink(bool f); void SetIsLink(bool f);
......
...@@ -232,6 +232,14 @@ void ShadowRoot::RemovedFrom(ContainerNode* insertion_point) { ...@@ -232,6 +232,14 @@ void ShadowRoot::RemovedFrom(ContainerNode* insertion_point) {
DocumentFragment::RemovedFrom(insertion_point); DocumentFragment::RemovedFrom(insertion_point);
} }
void ShadowRoot::SetNeedsAssignmentRecalc() {
DCHECK(RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
DCHECK(IsV1());
if (!slot_assignment_)
return;
return slot_assignment_->SetNeedsAssignmentRecalc();
}
void ShadowRoot::ChildrenChanged(const ChildrenChange& change) { void ShadowRoot::ChildrenChanged(const ChildrenChange& change) {
ContainerNode::ChildrenChanged(change); ContainerNode::ChildrenChanged(change);
......
...@@ -102,6 +102,8 @@ class CORE_EXPORT ShadowRoot final : public DocumentFragment, public TreeScope { ...@@ -102,6 +102,8 @@ class CORE_EXPORT ShadowRoot final : public DocumentFragment, public TreeScope {
InsertionNotificationRequest InsertedInto(ContainerNode*) override; InsertionNotificationRequest InsertedInto(ContainerNode*) override;
void RemovedFrom(ContainerNode*) override; void RemovedFrom(ContainerNode*) override;
void SetNeedsAssignmentRecalc();
// For V0 // For V0
ShadowRoot* YoungerShadowRoot() const; ShadowRoot* YoungerShadowRoot() const;
ShadowRoot* OlderShadowRoot() const; ShadowRoot* OlderShadowRoot() const;
......
...@@ -189,7 +189,33 @@ SlotAssignment::SlotAssignment(ShadowRoot& owner) ...@@ -189,7 +189,33 @@ SlotAssignment::SlotAssignment(ShadowRoot& owner)
DCHECK(owner.IsV1()); DCHECK(owner.IsV1());
} }
void SlotAssignment::ResolveAssignmentNg() {
DCHECK(RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
if (!needs_assignment_recalc_)
return;
needs_assignment_recalc_ = false;
for (Member<HTMLSlotElement> slot : Slots())
slot->ClearAssignedNodes();
for (Node& child : NodeTraversal::ChildrenOf(owner_->host())) {
if (!child.IsSlotable()) {
// TODO(hayato): Avoid LazyReattach
child.LazyReattachIfAttached();
continue;
}
if (HTMLSlotElement* slot = FindSlotByName(child.SlotName())) {
slot->AppendAssignedNode(child);
// TODO(hayato): Avoid LazyReattach
child.LazyReattachIfAttached();
}
}
}
void SlotAssignment::ResolveAssignment() { void SlotAssignment::ResolveAssignment() {
DCHECK(!RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
for (Member<HTMLSlotElement> slot : Slots()) for (Member<HTMLSlotElement> slot : Slots())
slot->SaveAndClearDistribution(); slot->SaveAndClearDistribution();
...@@ -207,6 +233,8 @@ void SlotAssignment::ResolveAssignment() { ...@@ -207,6 +233,8 @@ void SlotAssignment::ResolveAssignment() {
} }
void SlotAssignment::ResolveDistribution() { void SlotAssignment::ResolveDistribution() {
DCHECK(!RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
ResolveAssignment(); ResolveAssignment();
const HeapVector<Member<HTMLSlotElement>>& slots = Slots(); const HeapVector<Member<HTMLSlotElement>>& slots = Slots();
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "core/dom/TreeOrderedMap.h" #include "core/dom/TreeOrderedMap.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "platform/runtime_enabled_features.h"
#include "platform/wtf/HashMap.h" #include "platform/wtf/HashMap.h"
#include "platform/wtf/text/AtomicString.h" #include "platform/wtf/text/AtomicString.h"
#include "platform/wtf/text/AtomicStringHash.h" #include "platform/wtf/text/AtomicStringHash.h"
...@@ -47,6 +48,14 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> { ...@@ -47,6 +48,14 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> {
bool FindHostChildBySlotName(const AtomicString& slot_name) const; bool FindHostChildBySlotName(const AtomicString& slot_name) const;
void SetNeedsAssignmentRecalc() {
DCHECK(RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
needs_assignment_recalc_ = true;
}
// For Incremental Shadow DOM
void ResolveAssignmentNg();
void Trace(blink::Visitor*); void Trace(blink::Visitor*);
private: private:
...@@ -73,7 +82,8 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> { ...@@ -73,7 +82,8 @@ class SlotAssignment final : public GarbageCollected<SlotAssignment> {
Member<TreeOrderedMap> slot_map_; Member<TreeOrderedMap> slot_map_;
WeakMember<ShadowRoot> owner_; WeakMember<ShadowRoot> owner_;
unsigned needs_collect_slots_ : 1; unsigned needs_collect_slots_ : 1;
unsigned slot_count_ : 31; unsigned needs_assignment_recalc_ : 1; // For Incremental Shadow DOM
unsigned slot_count_ : 30;
}; };
} // namespace blink } // namespace blink
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/dom/FlatTreeTraversal.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/Node.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/ShadowRoot.h"
#include "core/html/HTMLCollection.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLSlotElement.h"
#include "core/testing/DummyPageHolder.h"
#include "platform/testing/RuntimeEnabledFeaturesTestHelpers.h"
#include "platform/wtf/Vector.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
// Copy elements in HTMLCollection into a vector because HTMLCollection is a
// live list and would be invalid if a mutation occurs.
HeapVector<Member<Element>> CollectFromHTMLCollection(
HTMLCollection& collection) {
HeapVector<Member<Element>> elements;
for (Element* element : collection)
elements.push_back(element);
return elements;
}
template <class T>
HeapVector<Member<Node>> CollectFromIterable(T iterable) {
HeapVector<Member<Node>> nodes;
for (auto& node : iterable)
nodes.push_back(&node);
return nodes;
}
// Process Mini-DSL for Declarative Shadow DOM.
// In Mini-DSL, <shadowroot> elements would behaves as if it were "Declarative
// Shadow DOM". This is usuful for constructing a composed tree in testing.
// Declarative Shadow DOM is under the dicussion. See
// https://github.com/whatwg/dom/issues/510. Once Declarative Shadow DOM is
// standardized and implemented, this DSL is no longer required.
void ConvertDeclarativeShadowDOMToShadowRoot(Element& element) {
HTMLCollection* shadow_root_collection =
element.getElementsByTagName("shadowroot");
if (!shadow_root_collection)
return;
for (Element* shadow_root :
CollectFromHTMLCollection(*shadow_root_collection)) {
Element* parent = shadow_root->parentElement();
DCHECK(parent);
parent->removeChild(shadow_root);
ShadowRoot& attached_shadow_root =
parent->AttachShadowRootInternal(ShadowRootType::kOpen);
for (Node* child :
CollectFromIterable(NodeTraversal::ChildrenOf(*shadow_root)))
attached_shadow_root.AppendChild(child);
}
}
void RemoveWhiteSpaceOnlyTextNode(ContainerNode& container) {
for (Node* descendant :
CollectFromIterable(NodeTraversal::InclusiveDescendantsOf(container))) {
if (Text* text = ToTextOrNull(descendant)) {
if (text->ContainsOnlyWhitespace())
text->remove();
} else if (Element* element = ToElementOrNull(descendant)) {
if (ShadowRoot* shadow_root = element->OpenShadowRoot())
RemoveWhiteSpaceOnlyTextNode(*shadow_root);
}
}
}
} // namespace
class SlotAssignmentTest : public ::testing::Test,
private ScopedIncrementalShadowDOMForTest {
public:
SlotAssignmentTest() : ScopedIncrementalShadowDOMForTest(true) {}
protected:
Document& GetDocument() const { return *document_; }
void SetBody(const char* html);
private:
void SetUp() override;
Persistent<Document> document_;
std::unique_ptr<DummyPageHolder> dummy_page_holder_;
};
void SlotAssignmentTest::SetUp() {
dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600));
document_ = &dummy_page_holder_->GetDocument();
DCHECK(document_);
}
void SlotAssignmentTest::SetBody(const char* html) {
Element* body = GetDocument().body();
body->SetInnerHTMLFromString(String::FromUTF8(html));
ConvertDeclarativeShadowDOMToShadowRoot(*body);
RemoveWhiteSpaceOnlyTextNode(*body);
}
TEST_F(SlotAssignmentTest, DeclarativeShadowDOM) {
SetBody(R"HTML(
<div id=host>
<shadowroot></shadowroot>
</div>
)HTML");
Element* host = GetDocument().QuerySelector("#host");
ASSERT_NE(nullptr, host);
ASSERT_NE(nullptr, host->OpenShadowRoot());
}
TEST_F(SlotAssignmentTest, NestedDeclarativeShadowDOM) {
SetBody(R"HTML(
<div id=host1>
<shadowroot>
<div id=host2>
<shadowroot></shadowroot>
</div>
</shadowroot>
</div>
)HTML");
Element* host1 = GetDocument().QuerySelector("#host1");
ASSERT_NE(nullptr, host1);
ShadowRoot* shadow_root1 = host1->OpenShadowRoot();
ASSERT_NE(nullptr, shadow_root1);
Element* host2 = shadow_root1->QuerySelector("#host2");
ASSERT_NE(nullptr, host2);
ShadowRoot* shadow_root2 = host2->OpenShadowRoot();
ASSERT_NE(nullptr, shadow_root2);
}
TEST_F(SlotAssignmentTest, AssignedNodesAreSet) {
SetBody(R"HTML(
<div id=host>
<shadowroot>
<slot></slot>
</shadowroot>
<div id='host-child'></div>
</div>
)HTML");
Element* host = GetDocument().QuerySelector("#host");
Element* host_child = GetDocument().QuerySelector("#host-child");
ShadowRoot* shadow_root = host->OpenShadowRoot();
HTMLSlotElement* slot =
ToHTMLSlotElementOrNull(shadow_root->QuerySelector("slot"));
ASSERT_NE(nullptr, slot);
EXPECT_EQ(slot, host_child->AssignedSlot());
HeapVector<Member<Node>> expected_nodes;
expected_nodes.push_back(host_child);
EXPECT_EQ(expected_nodes, slot->AssignedNodes());
}
} // namespace blink
...@@ -67,6 +67,15 @@ AtomicString HTMLSlotElement::NormalizeSlotName(const AtomicString& name) { ...@@ -67,6 +67,15 @@ AtomicString HTMLSlotElement::NormalizeSlotName(const AtomicString& name) {
} }
const HeapVector<Member<Node>>& HTMLSlotElement::AssignedNodes() const { const HeapVector<Member<Node>>& HTMLSlotElement::AssignedNodes() const {
if (RuntimeEnabledFeatures::IncrementalShadowDOMEnabled()) {
if (!SupportsAssignment()) {
DCHECK(assigned_nodes_.IsEmpty());
return assigned_nodes_;
}
ContainingShadowRoot()->GetSlotAssignment().ResolveAssignmentNg();
return assigned_nodes_;
}
DCHECK(!NeedsDistributionRecalc()); DCHECK(!NeedsDistributionRecalc());
DCHECK(IsInShadowTree() || assigned_nodes_.IsEmpty()); DCHECK(IsInShadowTree() || assigned_nodes_.IsEmpty());
return assigned_nodes_; return assigned_nodes_;
...@@ -118,6 +127,11 @@ void HTMLSlotElement::AppendDistributedNodesFrom(const HTMLSlotElement& other) { ...@@ -118,6 +127,11 @@ void HTMLSlotElement::AppendDistributedNodesFrom(const HTMLSlotElement& other) {
distributed_indices_.Set(node.Get(), index++); distributed_indices_.Set(node.Get(), index++);
} }
void HTMLSlotElement::ClearAssignedNodes() {
DCHECK(RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
assigned_nodes_.clear();
}
void HTMLSlotElement::ClearDistribution() { void HTMLSlotElement::ClearDistribution() {
assigned_nodes_.clear(); assigned_nodes_.clear();
distributed_nodes_.clear(); distributed_nodes_.clear();
...@@ -136,7 +150,9 @@ void HTMLSlotElement::DispatchSlotChangeEvent() { ...@@ -136,7 +150,9 @@ void HTMLSlotElement::DispatchSlotChangeEvent() {
} }
Node* HTMLSlotElement::AssignedNodeNextTo(const Node& node) const { Node* HTMLSlotElement::AssignedNodeNextTo(const Node& node) const {
DCHECK(RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
DCHECK(SupportsAssignment()); DCHECK(SupportsAssignment());
ContainingShadowRoot()->GetSlotAssignment().ResolveAssignmentNg();
// TODO(crbug.com/776656): Assert that assigned_nodes_ is up-to-date. // TODO(crbug.com/776656): Assert that assigned_nodes_ is up-to-date.
// TODO(crbug.com/776656): Use {node -> index} map to avoid O(N) lookup // TODO(crbug.com/776656): Use {node -> index} map to avoid O(N) lookup
size_t index = assigned_nodes_.Find(&node); size_t index = assigned_nodes_.Find(&node);
...@@ -147,7 +163,9 @@ Node* HTMLSlotElement::AssignedNodeNextTo(const Node& node) const { ...@@ -147,7 +163,9 @@ Node* HTMLSlotElement::AssignedNodeNextTo(const Node& node) const {
} }
Node* HTMLSlotElement::AssignedNodePreviousTo(const Node& node) const { Node* HTMLSlotElement::AssignedNodePreviousTo(const Node& node) const {
DCHECK(RuntimeEnabledFeatures::IncrementalShadowDOMEnabled());
DCHECK(SupportsAssignment()); DCHECK(SupportsAssignment());
ContainingShadowRoot()->GetSlotAssignment().ResolveAssignmentNg();
// TODO(crbug.com/776656): Assert that assigned_nodes_ is up-to-date. // TODO(crbug.com/776656): Assert that assigned_nodes_ is up-to-date.
// TODO(crbug.com/776656): Use {node -> index} map to avoid O(N) lookup // TODO(crbug.com/776656): Use {node -> index} map to avoid O(N) lookup
size_t index = assigned_nodes_.Find(&node); size_t index = assigned_nodes_.Find(&node);
...@@ -372,7 +390,7 @@ void HTMLSlotElement::DidSlotChangeAfterRemovedFromShadowTree() { ...@@ -372,7 +390,7 @@ void HTMLSlotElement::DidSlotChangeAfterRemovedFromShadowTree() {
void HTMLSlotElement::DidSlotChangeAfterRenaming() { void HTMLSlotElement::DidSlotChangeAfterRenaming() {
DCHECK(SupportsAssignment()); DCHECK(SupportsAssignment());
EnqueueSlotChangeEvent(); EnqueueSlotChangeEvent();
ContainingShadowRoot()->Owner()->SetNeedsDistributionRecalc(); SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
CheckSlotChange(SlotChangeType::kSuppressSlotChangeEvent); CheckSlotChange(SlotChangeType::kSuppressSlotChangeEvent);
} }
...@@ -384,11 +402,19 @@ void HTMLSlotElement::LazyReattachDistributedNodesNaive() { ...@@ -384,11 +402,19 @@ void HTMLSlotElement::LazyReattachDistributedNodesNaive() {
node->LazyReattachIfAttached(); node->LazyReattachIfAttached();
} }
void HTMLSlotElement::
SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc() {
if (RuntimeEnabledFeatures::IncrementalShadowDOMEnabled())
ContainingShadowRoot()->GetSlotAssignment().SetNeedsAssignmentRecalc();
else
ContainingShadowRoot()->Owner()->SetNeedsDistributionRecalc();
}
void HTMLSlotElement::DidSlotChange(SlotChangeType slot_change_type) { void HTMLSlotElement::DidSlotChange(SlotChangeType slot_change_type) {
DCHECK(SupportsAssignment()); DCHECK(SupportsAssignment());
if (slot_change_type == SlotChangeType::kSignalSlotChangeEvent) if (slot_change_type == SlotChangeType::kSignalSlotChangeEvent)
EnqueueSlotChangeEvent(); EnqueueSlotChangeEvent();
ContainingShadowRoot()->Owner()->SetNeedsDistributionRecalc(); SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
// Check slotchange recursively since this slotchange may cause another // Check slotchange recursively since this slotchange may cause another
// slotchange. // slotchange.
CheckSlotChange(SlotChangeType::kSuppressSlotChangeEvent); CheckSlotChange(SlotChangeType::kSuppressSlotChangeEvent);
......
...@@ -112,6 +112,9 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement { ...@@ -112,6 +112,9 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement {
void DispatchSlotChangeEvent(); void DispatchSlotChangeEvent();
void ClearSlotChangeEventEnqueued() { slotchange_event_enqueued_ = false; } void ClearSlotChangeEventEnqueued() { slotchange_event_enqueued_ = false; }
// For Incremental Shadow DOM
void ClearAssignedNodes();
static AtomicString NormalizeSlotName(const AtomicString&); static AtomicString NormalizeSlotName(const AtomicString&);
virtual void Trace(blink::Visitor*); virtual void Trace(blink::Visitor*);
...@@ -133,6 +136,8 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement { ...@@ -133,6 +136,8 @@ class CORE_EXPORT HTMLSlotElement final : public HTMLElement {
const HeapVector<Member<Node>>&, const HeapVector<Member<Node>>&,
const HeapVector<Member<Node>>&); const HeapVector<Member<Node>>&);
void SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
HeapVector<Member<Node>> assigned_nodes_; HeapVector<Member<Node>> assigned_nodes_;
HeapVector<Member<Node>> distributed_nodes_; HeapVector<Member<Node>> distributed_nodes_;
HeapVector<Member<Node>> old_distributed_nodes_; HeapVector<Member<Node>> old_distributed_nodes_;
......
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