Rewrite Shadow DOM distribution engine to support partial synchronous distribution for v1

This is a huge engine rewrite to support slotchange [1] events more strictly
and more efficiently.

The summary of the changes is:

1. Make Blink be more spec compliant, regarding slotchange event.

2. Get significant performance improvements.

   Now SlotAssignment has |slotname|-to-|slot| HashMap, via DocumentOrderedMap.
   This HashMap is updated in each insertion/removal/renaming of slots.

   Though DOM Standard [2] requires synchronous calculation of slot assignments
   in each DOM mutation to fire a slotchagne event at the end of microtask,
   this CL does not calculate it synchronously, as an important optimization,
   if we can be sure to delay it. Instead, we do a minimum necessary check to
   detect a slotchange, using the characteristics of slot assignments.

   Especially, appending a child to a shadow host, which happens a lot in
   Polymer, becomes much faster.

3. Make HTMLSlotElement a much smaller footprint element.

   The following member variables are removed:
     m_oldAssignedNodes, m_oldDistributedNodes, m_fallbackNodes, m_oldFallbackNodes,
     m_distributionState and m_assignmentState.

4. Make SlotAssignment::resolveDistribution much simpler because slotchange is
   already detected at early stage.

See the bug 610961 for details to know the basic ideas behind the scene.

The results of micro benchmarks are:

- PerformanceTests/ShadowDOM/v1-distribution.html => About 1.8x faster
  Time:
    Before: avg 3190.3 runs/s
    After:  avg 5885.6 runs/s

- PerformanceTests/ShadowDOM/v1-host-child-append.html => About 6.0x faster
  Time:
    Before: avg  51.6 runs/s
    After:  avg 306.4 runs/s

- PerformanceTests/ShadowDOM/v1-slot-append.html => About 3.4x faster
  Time:
    Before: avg 1647.4 runs/s
    After:  avg 5645.0 runs/s

This CL also reduced the memory usage in micro benchmarks because a slot element
becomes a smaller footprint element.

- PerformanceTests/ShadowDOM/v1-distribution.html
  JS Heap:
    Before: avg 21357790.4 bytes
    After:  avg  3136606.4 bytes

- PerformanceTests/ShadowDOM/v1-host-child-append.html
  JS Heap:
    Before: avg 5860745.6 bytes
    After:  avg 4165614.4 bytes

- PerformanceTests/ShadowDOM/v1-slot-append.html
  JS Heap:
    Before: avg 13256016 bytes
    After:  avg  3540172.8 bytes

Notes:
- Reducing the memory usage is not the primary motivation of this CL.
- These performance tests are being marked skipped. See crbug.com/579107
- This CL also fixes the following bugs. These tests no longer fail.
  - crbug.com/610588
  - imported/wpt/shadow-dom/HTMLSlotElement-interface.html

The future works are:

1. There is a still false-positive case for slotchange event.

   e.g. A preceding slot is inserted together with a following slot.  This would
   not be a significant issue, but should be fixed. As of now, we must pay a
   performance penalty to remove this kind of false-positive.

2. Add more layout tests and add W3C Web Platform tests to be upstreamed.

3. Optimize the performance more and more.

    e.g. It might be possible to optimize
    HTMLSlotElement::hasAssignedNodesSynchronously by using yet another data
    structure.

4. Support SlotAssignment for non shadow trees. e.g. a document tree.

    This is a low-priority task because a document tree is unlikely to have a
    slot.

5. Isolate NeedsRecalDistribution flag for v0 and v1.

    Currently, the same flag is used in both kinds of shadow trees, v0 and v1,
    to support both together in one page.

6. DocumentOrderedMap might not be optimized for our purpose.

    e.g. DocumentOrderedMap has to travers DOM Tree if conflicts happen.
    Just using Document::compareDocumentPosition() might be better, given that
    conflicts is unlikely to happen.

Links:

[1] slotchange event: https://github.com/w3c/webcomponents/issues/288
[2] Relevant DOM Standard sections: https://dom.spec.whatwg.org/
    4.2.2 Shadow tree  4.2.2.1 Slots
      4.2.2.2 Slotables
      4.2.2.3 Finding slots and slotables
      4.2.2.4 Assigning slotables and slots
      4.2.2.5 Signaling slot changes
    4.2.3 Mutation algorithms

BUG=531990,595287,610588,610961,579107

Review-Url: https://codereview.chromium.org/1995203002
Cr-Commit-Position: refs/heads/master@{#396443}
diff --git a/third_party/WebKit/LayoutTests/imported/wpt/shadow-dom/HTMLSlotElement-interface-expected.txt b/third_party/WebKit/LayoutTests/imported/wpt/shadow-dom/HTMLSlotElement-interface-expected.txt
deleted file mode 100644
index b176181..0000000
--- a/third_party/WebKit/LayoutTests/imported/wpt/shadow-dom/HTMLSlotElement-interface-expected.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-This is a testharness.js-based test.
-PASS HTMLSlotElement must be defined on window 
-PASS "name" attribute on HTMLSlotElement must reflect "name" attribute 
-PASS assignedNodes() on a HTMLSlotElement must return an empty array when the slot element is not in a tree or in a document tree 
-PASS assignedNodes({"flattened":false}) on a HTMLSlotElement must return an empty array when the slot element is not in a tree or in a document tree 
-PASS assignedNodes({"flattened":true}) on a HTMLSlotElement must return an empty array when the slot element is not in a tree or in a document tree 
-PASS assignedNodes() must return the list of assigned nodes when none of the assigned nodes themselves are slots 
-PASS assignedNodes({"flattened":false}) must return the list of assigned nodes when none of the assigned nodes themselves are slots 
-PASS assignedNodes({"flattened":true}) must return the list of assigned nodes when none of the assigned nodes themselves are slots 
-PASS assignedNodes() must update when slot and name attributes are modified 
-PASS assignedNodes({"flattened":false}) must update when slot and name attributes are modified 
-PASS assignedNodes({"flattened":true}) must update when slot and name attributes are modified 
-PASS assignedNodes must update when a default slot is introduced dynamically by a slot rename 
-PASS assignedNodes must update when a default slot is introduced dynamically by a slot rename 
-PASS assignedNodes must update when a default slot is introduced dynamically by a slot rename 
-FAIL assignedNodes must update when slot elements are inserted or removed assert_array_equals: assignedNodes on a detached formerly-default slot must return an empty array lengths differ, expected 0 got 3
-FAIL assignedNodes must update when slot elements are inserted or removed assert_array_equals: assignedNodes on a detached formerly-default slot must return an empty array lengths differ, expected 0 got 3
-FAIL assignedNodes must update when slot elements are inserted or removed assert_array_equals: assignedNodes on a detached formerly-default slot must return an empty array lengths differ, expected 0 got 3
-FAIL assignedNodes({flatten: true}) must return the distributed nodes, and assignedNodes() and assignedNodes({flatten: false}) must returned the assigned nodes assert_array_equals: assignedNodes() must return an empty array when the slot element is not in any tree lengths differ, expected 0 got 2
-Harness: the test ran to completion.
-
diff --git a/third_party/WebKit/LayoutTests/shadow-dom/slotchange-host-child-appended.html b/third_party/WebKit/LayoutTests/shadow-dom/slotchange-host-child-appended.html
new file mode 100644
index 0000000..449ae2c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/shadow-dom/slotchange-host-child-appended.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src='../resources/testharness.js'></script>
+<script src='../resources/testharnessreport.js'></script>
+<script src='resources/shadow-dom.js'></script>
+<div id='d1'>
+  <template data-mode='open' data-expose-as='d1_shadow'>
+    <slot name='d1-s1'></slot>
+  </template>
+  <div id='d2' slot='d1-s1'></div>
+</div>
+<script>
+'use strict';
+convertTemplatesToShadowRootsWithin(d1);
+removeWhiteSpaceOnlyTextNodes(d1);
+
+async_test((test) => {
+
+  const d1_s1 = d1_shadow.querySelector('slot');
+
+  assert_array_equals(d1_s1.assignedNodes(), [d2]);
+  assert_array_equals(d1_s1.assignedNodes({'flatten': true}), [d2]);
+
+  d1_s1.addEventListener('slotchange', (e) => {
+    test.step(() => {
+      assert_equals(e.target, d1_s1);
+      assert_array_equals(d1_s1.assignedNodes(), [d2, d3]);
+      assert_array_equals(d1_s1.assignedNodes({'flatten': true}), [d2, d3]);
+      assert_equals(e.scoped, true);
+      test.done();
+    });
+  });
+
+  const d3 = document.createElement('div');
+  d3.setAttribute('id', 'd3');
+  d3.setAttribute('slot', 'd1-s1');
+  d1.appendChild(d3);
+}, "slotchange event caused by appending a host child");
+</script>
diff --git a/third_party/WebKit/LayoutTests/shadow-dom/slotchange-node-removed.html b/third_party/WebKit/LayoutTests/shadow-dom/slotchange-node-removed.html
new file mode 100644
index 0000000..efcf92a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/shadow-dom/slotchange-node-removed.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<script src='../resources/testharness.js'></script>
+<script src='../resources/testharnessreport.js'></script>
+<script src='resources/shadow-dom.js'></script>
+<div id='d1'>
+  <template data-mode='open' data-expose-as='d1_shadow'>
+    <slot name='d1-s1'></slot>
+  </template>
+  <div id='d2' slot='d1-s1'></div>
+</div>
+<script>
+'use strict';
+convertTemplatesToShadowRootsWithin(d1);
+removeWhiteSpaceOnlyTextNodes(d1);
+
+async_test((test) => {
+
+  const d1_s1 = d1_shadow.querySelector('slot');
+
+  assert_array_equals(d1_s1.assignedNodes(), [d2]);
+  assert_array_equals(d1_s1.assignedNodes({'flatten': true}), [d2]);
+
+  d1_s1.addEventListener('slotchange', (e) => {
+    test.step(() => {
+      assert_equals(e.target, d1_s1);
+      assert_array_equals(d1_s1.assignedNodes(), []);
+      assert_array_equals(d1_s1.assignedNodes({'flatten': true}), []);
+      assert_equals(e.scoped, true);
+      test.done();
+    });
+  });
+
+  d2.remove();
+}, "slotchange event caused by removing a node");
+</script>
diff --git a/third_party/WebKit/LayoutTests/shadow-dom/slotchange-slotname-renamed.html b/third_party/WebKit/LayoutTests/shadow-dom/slotchange-slotname-renamed.html
new file mode 100644
index 0000000..db7f0be
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/shadow-dom/slotchange-slotname-renamed.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src='../resources/testharness.js'></script>
+<script src='../resources/testharnessreport.js'></script>
+<script src='resources/shadow-dom.js'></script>
+<!-- This is a micro benchmark to catch an unintentional regression.
+     If the reason of a regression is clear, it is okay.
+     We do not have to optimize the result of the benchmark. -->
+<div id='d1'>
+  <template data-mode='open' data-expose-as='d1_shadow'>
+    <slot name='d1-s1'></slot>
+  </template>
+  <div id='d2' slot='d1-s1'></div>
+</div>
+<script>
+'use strict';
+convertTemplatesToShadowRootsWithin(d1);
+removeWhiteSpaceOnlyTextNodes(d1);
+
+async_test((test) => {
+
+  const d1_s1 = d1_shadow.querySelector('slot');
+
+  assert_array_equals(d1_s1.assignedNodes(), [d2]);
+  assert_array_equals(d1_s1.assignedNodes({'flatten': true}), [d2]);
+
+  d1_s1.addEventListener('slotchange', (e) => {
+    test.step(() => {
+      assert_equals(e.target, d1_s1);
+      assert_array_equals(d1_s1.assignedNodes(), []);
+      assert_array_equals(d1_s1.assignedNodes({'flatten': true}), []);
+      assert_equals(e.scoped, true);
+      test.done();
+    });
+  });
+
+  d2.setAttribute('slot', 'non-exist');
+}, "slotchange event caused by renaming slot name");
+</script>
diff --git a/third_party/WebKit/PerformanceTests/ShadowDOM/v1-distribution.html b/third_party/WebKit/PerformanceTests/ShadowDOM/v1-distribution.html
index 9f392778..fcaa699 100644
--- a/third_party/WebKit/PerformanceTests/ShadowDOM/v1-distribution.html
+++ b/third_party/WebKit/PerformanceTests/ShadowDOM/v1-distribution.html
@@ -1,5 +1,8 @@
 <!DOCTYPE html>
 <script src="../resources/runner.js"></script>
+<!-- This is a micro benchmark to catch an unintentional regression.
+     If the reason of a regression is clear, it is okay.
+     We do not have to optimize the result of the benchmark. -->
 <div id="wrapper">
   <div id="host"></div>
 </div>
diff --git a/third_party/WebKit/PerformanceTests/ShadowDOM/v1-host-child-append.html b/third_party/WebKit/PerformanceTests/ShadowDOM/v1-host-child-append.html
new file mode 100644
index 0000000..c903c55
--- /dev/null
+++ b/third_party/WebKit/PerformanceTests/ShadowDOM/v1-host-child-append.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<script src="../resources/runner.js"></script>
+<!-- This is a micro benchmark to catch an unintentional regression.
+     If the reason of a regression is clear, it is okay.
+     We do not have to optimize the result of the benchmark. -->
+<div id="wrapper">
+  <div id="host"></div>
+</div>
+<script>
+'use strict';
+const numChildOfHost = 50;
+const numDivsInShadow = 20;
+const loops = 20;
+
+const slot1 = document.createElement('slot');
+slot1.setAttribute('name', 'slot1');
+const slot2 = document.createElement('slot');
+slot2.setAttribute('name', 'slot2');
+const shadowRoot = host.attachShadow({mode: 'open'});
+shadowRoot.appendChild(slot1);
+shadowRoot.appendChild(slot2);
+
+for (let i = 0; i < numDivsInShadow; ++i) {
+  let div = document.createElement('div');
+  shadowRoot.appendChild(div);
+}
+
+function run() {
+  for (let i = 0; i < loops; ++i) {
+    for (let j = 0; j < numChildOfHost; ++j) {
+      let div1 = document.createElement('div');
+      div1.setAttribute('slot', 'slot1');
+      host.appendChild(div1);
+      let div2 = document.createElement('div');
+      div2.setAttribute('slot', 'slot2');
+      host.appendChild(div2);
+    }
+    host.innerHTML = '';
+  }
+}
+
+PerfTestRunner.measureRunsPerSecond({
+  description: "Measure v1 distribution performance",
+  run: run,
+  done: () => {
+    wrapper.innerHTML = '';
+  }
+});
+</script>
diff --git a/third_party/WebKit/PerformanceTests/ShadowDOM/v1-slot-append.html b/third_party/WebKit/PerformanceTests/ShadowDOM/v1-slot-append.html
new file mode 100644
index 0000000..84ba432
--- /dev/null
+++ b/third_party/WebKit/PerformanceTests/ShadowDOM/v1-slot-append.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<script src="../resources/runner.js"></script>
+<!-- This is a micro benchmark to catch an unintentional regression.
+     If the reason of a regression is clear, it is okay.
+     We do not have to optimize the result of the benchmark. -->
+<div id="wrapper">
+  <div id="host"></div>
+</div>
+<script>
+'use strict';
+const numChildOfHost = 20;
+const numDivsInShadow = 20;
+const loops = 100;
+
+const slot1 = document.createElement('slot');
+slot1.setAttribute('name', 'slot1');
+const slot2 = document.createElement('slot');
+slot2.setAttribute('name', 'slot2');
+const shadowRoot = host.attachShadow({mode: 'open'});
+shadowRoot.appendChild(slot1);
+shadowRoot.appendChild(slot2);
+
+for (let i = 0; i < numDivsInShadow; ++i) {
+  let div = document.createElement('div');
+  shadowRoot.appendChild(div);
+}
+
+for (let i = 0; i < numChildOfHost; ++i) {
+  let div1 = document.createElement('div');
+  div1.setAttribute('slot', 'slot1');
+  host.appendChild(div1);
+  let div2 = document.createElement('div');
+  div2.setAttribute('slot', 'slot2');
+  host.appendChild(div2);
+}
+
+function run() {
+  for (let i = 0; i < loops; ++i) {
+    const slot3 = document.createElement('slot');
+    slot1.setAttribute('name', 'slot3');
+    const slot4 = document.createElement('slot');
+    slot1.setAttribute('name', 'slot1');
+    slot3.remove();
+    slot4.remove();
+  }
+}
+
+PerfTestRunner.measureRunsPerSecond({
+  description: "Measure v1 distribution performance",
+  run: run,
+  done: () => {
+    wrapper.innerHTML = '';
+  }
+});
+</script>
diff --git a/third_party/WebKit/PerformanceTests/Skipped b/third_party/WebKit/PerformanceTests/Skipped
index 8eec9a4..f018fe4 100644
--- a/third_party/WebKit/PerformanceTests/Skipped
+++ b/third_party/WebKit/PerformanceTests/Skipped
@@ -58,3 +58,5 @@
 # Times out on reference build - crbug.com/579107
 ShadowDOM/SlotDistibutedNextPrevious.html
 ShadowDOM/v1-distribution.html
+ShadowDOM/v1-host-child-append.html
+ShadowDOM/v1-slot-append.html
diff --git a/third_party/WebKit/Source/core/dom/ContainerNode.cpp b/third_party/WebKit/Source/core/dom/ContainerNode.cpp
index d509f3b3..a57cc70 100644
--- a/third_party/WebKit/Source/core/dom/ContainerNode.cpp
+++ b/third_party/WebKit/Source/core/dom/ContainerNode.cpp
@@ -692,6 +692,9 @@
 #endif
     DCHECK(!root.isShadowRoot());
 
+    if (document().containsV1ShadowTree())
+        root.checkSlotChangeAfterInserted();
+
     InspectorInstrumentation::didInsertDOMNode(&root);
 
     NodeVector postInsertionNotificationTargets;
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index 4c31f14..0271414 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -3840,6 +3840,11 @@
             frame->page()->dragCaretController().nodeWillBeRemoved(n);
         }
     }
+
+    if (containsV1ShadowTree()) {
+        for (Node& n : NodeTraversal::childrenOf(container))
+            n.checkSlotChangeBeforeRemoved();
+    }
 }
 
 void Document::nodeWillBeRemoved(Node& n)
@@ -3855,6 +3860,9 @@
         frame->selection().nodeWillBeRemoved(n);
         frame->page()->dragCaretController().nodeWillBeRemoved(n);
     }
+
+    if (containsV1ShadowTree())
+        n.checkSlotChangeBeforeRemoved();
 }
 
 void Document::dataWillChange(const CharacterData& characterData)
diff --git a/third_party/WebKit/Source/core/dom/Document.h b/third_party/WebKit/Source/core/dom/Document.h
index 6806557..863fb9d7 100644
--- a/third_party/WebKit/Source/core/dom/Document.h
+++ b/third_party/WebKit/Source/core/dom/Document.h
@@ -1082,6 +1082,8 @@
     ShadowCascadeOrder shadowCascadeOrder() const { return m_shadowCascadeOrder; }
     void setShadowCascadeOrder(ShadowCascadeOrder);
 
+    bool containsV1ShadowTree() const { return m_shadowCascadeOrder == ShadowCascadeOrder::ShadowCascadeV1; }
+
     void setRootScroller(Element*, ExceptionState&);
     Element* rootScroller();
 
diff --git a/third_party/WebKit/Source/core/dom/DocumentOrderedMap.cpp b/third_party/WebKit/Source/core/dom/DocumentOrderedMap.cpp
index 5c7822b..a7c01b5 100644
--- a/third_party/WebKit/Source/core/dom/DocumentOrderedMap.cpp
+++ b/third_party/WebKit/Source/core/dom/DocumentOrderedMap.cpp
@@ -35,6 +35,7 @@
 #include "core/dom/ElementTraversal.h"
 #include "core/dom/TreeScope.h"
 #include "core/html/HTMLMapElement.h"
+#include "core/html/HTMLSlotElement.h"
 
 namespace blink {
 
@@ -75,6 +76,11 @@
     return isHTMLMapElement(element) && toHTMLMapElement(element).getName() == key;
 }
 
+inline bool keyMatchesSlotName(const AtomicString& key, const Element& element)
+{
+    return isHTMLSlotElement(element) && toHTMLSlotElement(element).name() == key;
+}
+
 inline bool keyMatchesLowercasedMapName(const AtomicString& key, const Element& element)
 {
     return isHTMLMapElement(element) && toHTMLMapElement(element).getName().lower() == key;
@@ -195,6 +201,16 @@
     return get<keyMatchesMapName>(key, scope);
 }
 
+// TODO(hayato): Template get<> by return type.
+HTMLSlotElement* DocumentOrderedMap::getSlotByName(const AtomicString& key, const TreeScope* scope) const
+{
+    if (Element* slot = get<keyMatchesSlotName>(key, scope)) {
+        DCHECK(isHTMLSlotElement(slot));
+        return toHTMLSlotElement(slot);
+    }
+    return nullptr;
+}
+
 Element* DocumentOrderedMap::getElementByLowercasedMapName(const AtomicString& key, const TreeScope* scope) const
 {
     return get<keyMatchesLowercasedMapName>(key, scope);
diff --git a/third_party/WebKit/Source/core/dom/DocumentOrderedMap.h b/third_party/WebKit/Source/core/dom/DocumentOrderedMap.h
index 3555226..0908c05 100644
--- a/third_party/WebKit/Source/core/dom/DocumentOrderedMap.h
+++ b/third_party/WebKit/Source/core/dom/DocumentOrderedMap.h
@@ -42,6 +42,7 @@
 namespace blink {
 
 class Element;
+class HTMLSlotElement;
 class TreeScope;
 
 class DocumentOrderedMap : public GarbageCollected<DocumentOrderedMap> {
@@ -57,6 +58,7 @@
     Element* getElementById(const AtomicString&, const TreeScope*) const;
     const HeapVector<Member<Element>>& getAllElementsById(const AtomicString&, const TreeScope*) const;
     Element* getElementByMapName(const AtomicString&, const TreeScope*) const;
+    HTMLSlotElement* getSlotByName(const AtomicString&, const TreeScope*) const;
     Element* getElementByLowercasedMapName(const AtomicString&, const TreeScope*) const;
     Element* getElementByLabelForAttribute(const AtomicString&, const TreeScope*) const;
 
diff --git a/third_party/WebKit/Source/core/dom/Element.cpp b/third_party/WebKit/Source/core/dom/Element.cpp
index 092cbd4..ad733eb 100644
--- a/third_party/WebKit/Source/core/dom/Element.cpp
+++ b/third_party/WebKit/Source/core/dom/Element.cpp
@@ -78,6 +78,7 @@
 #include "core/dom/shadow/InsertionPoint.h"
 #include "core/dom/shadow/ShadowRoot.h"
 #include "core/dom/shadow/ShadowRootInit.h"
+#include "core/dom/shadow/SlotAssignment.h"
 #include "core/editing/EditingUtilities.h"
 #include "core/editing/FrameSelection.h"
 #include "core/editing/iterators/TextIterator.h"
@@ -1145,10 +1146,9 @@
         if (shouldInvalidateDistributionWhenAttributeChanged(parentElementShadow, name, newValue))
             parentElementShadow->setNeedsDistributionRecalc();
     }
-    if (name == HTMLNames::slotAttr && isChildOfV1ShadowHost()) {
-        parentElementShadow()->setNeedsDistributionRecalc();
-        if (oldValue != newValue)
-            parentElement()->shadowRootIfV1()->assignV1();
+    if (name == HTMLNames::slotAttr && oldValue != newValue) {
+        if (ShadowRoot* root = v1ShadowRootOfParent())
+            root->ensureSlotAssignment().hostChildSlotNameChanged(oldValue, newValue);
     }
 
     parseAttribute(name, oldValue, newValue);
@@ -1507,12 +1507,6 @@
 
     if (document().frame())
         document().frame()->eventHandler().elementRemoved(this);
-
-    if (HTMLSlotElement* slot = assignedSlot()) {
-        ShadowRoot* root = slot->containingShadowRoot();
-        if (root && root->isV1())
-            root->assignV1();
-    }
 }
 
 void Element::attach(const AttachContext& context)
@@ -2088,14 +2082,9 @@
     if (!change.byParser && change.isChildElementChange())
         checkForSiblingStyleChanges(change.type == ElementRemoved ? SiblingElementRemoved : SiblingElementInserted, change.siblingBeforeChange, change.siblingAfterChange);
 
-    if (ElementShadow* shadow = this->shadow()) {
+    // TODO(hayato): Confirm that we can skip this if a shadow tree is v1.
+    if (ElementShadow* shadow = this->shadow())
         shadow->setNeedsDistributionRecalc();
-        if (document().shadowCascadeOrder() == ShadowCascadeOrder::ShadowCascadeV1) {
-            ShadowRoot* root = isShadowHost(*this) && shadowRoot()->isV1() ? shadowRootIfV1() : isHTMLSlotElement(*this) ? containingShadowRoot() : nullptr;
-            if (root && root->isV1())
-                root->assignV1();
-        }
-    }
 }
 
 void Element::finishParsingChildren()
diff --git a/third_party/WebKit/Source/core/dom/Element.h b/third_party/WebKit/Source/core/dom/Element.h
index 02be63d..522b8c0 100644
--- a/third_party/WebKit/Source/core/dom/Element.h
+++ b/third_party/WebKit/Source/core/dom/Element.h
@@ -875,8 +875,6 @@
         setFlag(IsInShadowTreeFlag);
     if (childNeedsDistributionRecalc() && !insertionPoint->childNeedsDistributionRecalc())
         insertionPoint->markAncestorsWithChildNeedsDistributionRecalc();
-    if (document().shadowCascadeOrder() == ShadowCascadeOrder::ShadowCascadeV1)
-        updateAssignmentForInsertedInto(insertionPoint);
     return InsertionDone;
 }
 
diff --git a/third_party/WebKit/Source/core/dom/Node.cpp b/third_party/WebKit/Source/core/dom/Node.cpp
index 7cfded9..bdca750 100644
--- a/third_party/WebKit/Source/core/dom/Node.cpp
+++ b/third_party/WebKit/Source/core/dom/Node.cpp
@@ -59,6 +59,7 @@
 #include "core/dom/shadow/FlatTreeTraversal.h"
 #include "core/dom/shadow/InsertionPoint.h"
 #include "core/dom/shadow/ShadowRoot.h"
+#include "core/dom/shadow/SlotAssignment.h"
 #include "core/editing/EditingUtilities.h"
 #include "core/editing/markers/DocumentMarkerController.h"
 #include "core/events/Event.h"
@@ -635,12 +636,10 @@
     return *root;
 }
 
-#if DCHECK_IS_ON()
 bool Node::needsDistributionRecalc() const
 {
     return shadowIncludingRoot().childNeedsDistributionRecalc();
 }
-#endif
 
 void Node::updateDistribution()
 {
@@ -700,11 +699,6 @@
 void Node::markAncestorsWithChildNeedsDistributionRecalc()
 {
     ScriptForbiddenScope forbidScriptDuringRawIteration;
-    if (RuntimeEnabledFeatures::shadowDOMV1Enabled() && inShadowIncludingDocument() && !document().childNeedsDistributionRecalc()) {
-        // TODO(hayato): Support a non-document composed tree.
-        // TODO(hayato): Enqueue a task only if a 'slotchange' event listner is registered in the document composed tree.
-        Microtask::enqueueMicrotask(WTF::bind(&Document::updateDistribution, &document()));
-    }
     for (Node* node = this; node && !node->childNeedsDistributionRecalc(); node = node->parentOrShadowHostNode())
         node->setChildNeedsDistributionRecalc();
     document().scheduleLayoutTreeUpdateIfNeeded();
@@ -994,19 +988,13 @@
 
 AtomicString Node::slotName() const
 {
-    DCHECK(slottable());
+    DCHECK(isSlotable());
     if (isElementNode())
-        return normalizeSlotName(toElement(*this).fastGetAttribute(HTMLNames::slotAttr));
+        return HTMLSlotElement::normalizeSlotName(toElement(*this).fastGetAttribute(HTMLNames::slotAttr));
     DCHECK(isTextNode());
     return emptyAtom;
 }
 
-// static
-AtomicString Node::normalizeSlotName(const AtomicString& name)
-{
-    return (name.isNull() || name.isEmpty()) ? emptyAtom : name;
-}
-
 bool Node::isInV1ShadowTree() const
 {
     ShadowRoot* shadowRoot = containingShadowRoot();
@@ -1037,6 +1025,13 @@
     return parentShadow && !parentShadow->isV1();
 }
 
+ShadowRoot* Node::v1ShadowRootOfParent() const
+{
+    if (Element* parent = parentElement())
+        return parent->shadowRootIfV1();
+    return nullptr;
+}
+
 Element* Node::shadowHost() const
 {
     if (ShadowRoot* root = containingShadowRoot())
@@ -2247,20 +2242,18 @@
 
 HTMLSlotElement* Node::assignedSlot() const
 {
-    Element* parent = parentElement();
-    ShadowRoot* root = parent ? parent->youngestShadowRoot() : nullptr;
-    if (root && root->isV1())
-        return root->assignedSlotFor(*this);
+    if (ShadowRoot* root = v1ShadowRootOfParent())
+        return root->ensureSlotAssignment().findSlot(*this);
     return nullptr;
 }
 
 HTMLSlotElement* Node::assignedSlotForBinding()
 {
     updateDistribution();
-    Element* parent = parentElement();
-    ShadowRoot* root = parent ? parent->youngestShadowRoot() : nullptr;
-    if (root && root->type() == ShadowRootType::Open)
-        return root->assignedSlotFor(*this);
+    if (ShadowRoot* root = v1ShadowRootOfParent()) {
+        if (root->type() == ShadowRootType::Open)
+            return root->ensureSlotAssignment().findSlot(*this);
+    }
     return nullptr;
 }
 
@@ -2385,12 +2378,36 @@
         toElement(this)->pseudoStateChanged(CSSSelector::PseudoUnresolved);
 }
 
-void Node::updateAssignmentForInsertedInto(ContainerNode* insertionPoint)
+void Node::checkSlotChange()
 {
-    if (isShadowHost(insertionPoint)) {
-        ShadowRoot* root = insertionPoint->youngestShadowRoot();
-        if (root && root->isV1())
-            root->assignV1();
+    // Common check logic is used in both cases, "after inserted" and "before removed".
+    if (!isSlotable())
+        return;
+    if (ShadowRoot* root = v1ShadowRootOfParent()) {
+        // Relevant DOM Standard:
+        // https://dom.spec.whatwg.org/#concept-node-insert
+        // - 6.1.2: If parent is a shadow host and node is a slotable, then assign a slot for node.
+        // https://dom.spec.whatwg.org/#concept-node-remove
+        // - 10. If node is assigned, then run assign slotables for node’s assigned slot.
+
+        // Although DOM Standard requires "assign a slot for node / run assign slotables" at this timing,
+        // we skip it as an optimization.
+        if (HTMLSlotElement* slot = root->ensureSlotAssignment().findSlot(*this))
+            slot->enqueueSlotChangeEvent();
+    } else {
+        // Relevant DOM Standard:
+        // https://dom.spec.whatwg.org/#concept-node-insert
+        // - 6.1.3: If parent is a slot whose assigned nodes is the empty list, then run signal a slot change for parent.
+        // https://dom.spec.whatwg.org/#concept-node-remove
+        // - 11. If parent is a slot whose assigned nodes is the empty list, then run signal a slot change for parent.
+        Element* parent = parentElement();
+        if (parent && isHTMLSlotElement(parent)) {
+            HTMLSlotElement& parentSlot = toHTMLSlotElement(*parent);
+            if (ShadowRoot* root = containingShadowRoot()) {
+                if (root && root->isV1() && !parentSlot.hasAssignedNodesSlow())
+                    parentSlot.enqueueSlotChangeEvent();
+            }
+        }
     }
 }
 
diff --git a/third_party/WebKit/Source/core/dom/Node.h b/third_party/WebKit/Source/core/dom/Node.h
index acd595f..a9ee2d1 100644
--- a/third_party/WebKit/Source/core/dom/Node.h
+++ b/third_party/WebKit/Source/core/dom/Node.h
@@ -295,11 +295,10 @@
 
     bool canParticipateInFlatTree() const;
     bool isSlotOrActiveInsertionPoint() const;
-    bool slottable() const { return isElementNode() || isTextNode(); }
+    // A re-distribution across v0 and v1 shadow trees is not supported.
+    bool isSlotable() const { return isTextNode() || (isElementNode() && !isInsertionPoint()); }
     AtomicString slotName() const;
 
-    static AtomicString normalizeSlotName(const AtomicString&);
-
     bool hasCustomStyleCallbacks() const { return getFlag(HasCustomStyleCallbacksFlag); }
 
     // If this node is in a shadow tree, returns its shadow host. Otherwise, returns nullptr.
@@ -376,9 +375,7 @@
     void setNeedsStyleRecalc(StyleChangeType, const StyleChangeReasonForTracing&);
     void clearNeedsStyleRecalc();
 
-#if DCHECK_IS_ON()
     bool needsDistributionRecalc() const;
-#endif
 
     bool childNeedsDistributionRecalc() const { return getFlag(ChildNeedsDistributionRecalcFlag); }
     void setChildNeedsDistributionRecalc()  { setFlag(ChildNeedsDistributionRecalcFlag); }
@@ -490,7 +487,7 @@
     bool isInV0ShadowTree() const;
     bool isChildOfV1ShadowHost() const;
     bool isChildOfV0ShadowHost() const;
-    bool isSlotAssignable() const { return isTextNode() || isElementNode(); }
+    ShadowRoot* v1ShadowRootOfParent() const;
 
     bool isDocumentTypeNode() const { return getNodeType() == DOCUMENT_TYPE_NODE; }
     virtual bool childTypeAllowed(NodeType) const { return false; }
@@ -676,7 +673,8 @@
 
     bool isFinishedParsingChildren() const { return getFlag(IsFinishedParsingChildrenFlag); }
 
-    void updateAssignmentForInsertedInto(ContainerNode*);
+    void checkSlotChangeAfterInserted() { checkSlotChange(); }
+    void checkSlotChangeBeforeRemoved() { checkSlotChange(); }
 
     DECLARE_VIRTUAL_TRACE();
 
@@ -797,6 +795,8 @@
     // per-thread.
     virtual String debugNodeName() const;
 
+    void checkSlotChange();
+
     enum EditableLevel { Editable, RichlyEditable };
     bool hasEditableStyle(EditableLevel, UserSelectAllTreatment = UserSelectAllIsAlwaysNonEditable) const;
     bool isEditableToAccessibility(EditableLevel) const;
diff --git a/third_party/WebKit/Source/core/dom/shadow/ElementShadow.cpp b/third_party/WebKit/Source/core/dom/shadow/ElementShadow.cpp
index 597c4bf..1d312a8 100644
--- a/third_party/WebKit/Source/core/dom/shadow/ElementShadow.cpp
+++ b/third_party/WebKit/Source/core/dom/shadow/ElementShadow.cpp
@@ -254,9 +254,7 @@
 const InsertionPoint* ElementShadow::finalDestinationInsertionPointFor(const Node* key) const
 {
     DCHECK(key);
-#if DCHECK_IS_ON()
     DCHECK(!key->needsDistributionRecalc());
-#endif
     NodeToDestinationInsertionPoints::const_iterator it = m_nodeToInsertionPoints.find(key);
     return it == m_nodeToInsertionPoints.end() ? nullptr : it->value->last();
 }
@@ -264,9 +262,7 @@
 const DestinationInsertionPoints* ElementShadow::destinationInsertionPointsFor(const Node* key) const
 {
     DCHECK(key);
-#if DCHECK_IS_ON()
     DCHECK(!key->needsDistributionRecalc());
-#endif
     NodeToDestinationInsertionPoints::const_iterator it = m_nodeToInsertionPoints.find(key);
     return it == m_nodeToInsertionPoints.end() ? nullptr : it->value;
 }
diff --git a/third_party/WebKit/Source/core/dom/shadow/FlatTreeTraversal.h b/third_party/WebKit/Source/core/dom/shadow/FlatTreeTraversal.h
index 1c212c7..375a773 100644
--- a/third_party/WebKit/Source/core/dom/shadow/FlatTreeTraversal.h
+++ b/third_party/WebKit/Source/core/dom/shadow/FlatTreeTraversal.h
@@ -125,10 +125,8 @@
 
     static void assertPrecondition(const Node& node)
     {
-#if DCHECK_IS_ON()
         DCHECK(!node.needsDistributionRecalc());
         DCHECK(node.canParticipateInFlatTree());
-#endif
     }
 
     static void assertPostcondition(const Node* node)
diff --git a/third_party/WebKit/Source/core/dom/shadow/ShadowRoot.cpp b/third_party/WebKit/Source/core/dom/shadow/ShadowRoot.cpp
index aabea367..4d37956 100644
--- a/third_party/WebKit/Source/core/dom/shadow/ShadowRoot.cpp
+++ b/third_party/WebKit/Source/core/dom/shadow/ShadowRoot.cpp
@@ -61,7 +61,6 @@
     , m_registeredWithParentShadowRoot(false)
     , m_descendantInsertionPointsIsValid(false)
     , m_delegatesFocus(false)
-    , m_descendantSlotsIsValid(false)
 {
 }
 
@@ -107,16 +106,10 @@
 SlotAssignment& ShadowRoot::ensureSlotAssignment()
 {
     if (!m_slotAssignment)
-        m_slotAssignment = SlotAssignment::create();
+        m_slotAssignment = SlotAssignment::create(*this);
     return *m_slotAssignment;
 }
 
-HTMLSlotElement* ShadowRoot::assignedSlotFor(const Node& node) const
-{
-    DCHECK(m_slotAssignment);
-    return m_slotAssignment->assignedSlotFor(node);
-}
-
 Node* ShadowRoot::cloneNode(bool, ExceptionState& exceptionState)
 {
     exceptionState.throwDOMException(NotSupportedError, "ShadowRoot nodes are not clonable.");
@@ -300,63 +293,9 @@
     return *m_styleSheetList;
 }
 
-void ShadowRoot::didAddSlot()
-{
-    ensureSlotAssignment().didAddSlot();
-    invalidateDescendantSlots();
-}
-
-void ShadowRoot::didRemoveSlot()
-{
-    DCHECK(m_slotAssignment);
-    m_slotAssignment->didRemoveSlot();
-    invalidateDescendantSlots();
-}
-
-void ShadowRoot::invalidateDescendantSlots()
-{
-    DCHECK(m_slotAssignment);
-    m_descendantSlotsIsValid = false;
-    m_slotAssignment->clearDescendantSlots();
-}
-
-unsigned ShadowRoot::descendantSlotCount() const
-{
-    return m_slotAssignment ? m_slotAssignment->descendantSlotCount() : 0;
-}
-
-const HeapVector<Member<HTMLSlotElement>>& ShadowRoot::descendantSlots()
-{
-    DEFINE_STATIC_LOCAL(HeapVector<Member<HTMLSlotElement>>, emptyList, (new HeapVector<Member<HTMLSlotElement>>));
-    if (m_descendantSlotsIsValid) {
-        DCHECK(m_slotAssignment);
-        return m_slotAssignment->descendantSlots();
-    }
-    if (descendantSlotCount() == 0)
-        return emptyList;
-
-    DCHECK(m_slotAssignment);
-    HeapVector<Member<HTMLSlotElement>> slots;
-    slots.reserveCapacity(descendantSlotCount());
-    for (HTMLSlotElement& slot : Traversal<HTMLSlotElement>::descendantsOf(rootNode()))
-        slots.append(&slot);
-    m_slotAssignment->setDescendantSlots(slots);
-    m_descendantSlotsIsValid = true;
-    return m_slotAssignment->descendantSlots();
-}
-
-void ShadowRoot::assignV1()
-{
-    if (!m_slotAssignment)
-        m_slotAssignment = SlotAssignment::create();
-    m_slotAssignment->resolveAssignment(*this);
-}
-
 void ShadowRoot::distributeV1()
 {
-    if (!m_slotAssignment)
-        m_slotAssignment = SlotAssignment::create();
-    m_slotAssignment->resolveDistribution(*this);
+    ensureSlotAssignment().resolveDistribution();
 }
 
 DEFINE_TRACE(ShadowRoot)
diff --git a/third_party/WebKit/Source/core/dom/shadow/ShadowRoot.h b/third_party/WebKit/Source/core/dom/shadow/ShadowRoot.h
index eaa59e9..8a8b605 100644
--- a/third_party/WebKit/Source/core/dom/shadow/ShadowRoot.h
+++ b/third_party/WebKit/Source/core/dom/shadow/ShadowRoot.h
@@ -114,15 +114,8 @@
 
     SlotAssignment& ensureSlotAssignment();
 
-    void didAddSlot();
-    void didRemoveSlot();
-    const HeapVector<Member<HTMLSlotElement>>& descendantSlots();
-
-    void assignV1();
     void distributeV1();
 
-    HTMLSlotElement* assignedSlotFor(const Node&) const;
-
     Element* activeElement() const;
 
     String innerHTML() const;
@@ -157,19 +150,15 @@
     // ShadowRoots should never be cloned.
     Node* cloneNode(bool) override { return nullptr; }
 
-    void invalidateDescendantSlots();
-    unsigned descendantSlotCount() const;
-
     Member<ShadowRootRareDataV0> m_shadowRootRareDataV0;
     Member<StyleSheetList> m_styleSheetList;
     Member<SlotAssignment> m_slotAssignment;
-    unsigned m_numberOfStyles : 13;
+    unsigned m_numberOfStyles : 14;
     unsigned m_childShadowRootCount : 13;
     unsigned m_type : 2;
     unsigned m_registeredWithParentShadowRoot : 1;
     unsigned m_descendantInsertionPointsIsValid : 1;
     unsigned m_delegatesFocus : 1;
-    unsigned m_descendantSlotsIsValid : 1;
 };
 
 inline Element* ShadowRoot::activeElement() const
diff --git a/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp b/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp
index 712a29c..b5fed59 100644
--- a/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp
+++ b/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.cpp
@@ -14,9 +14,97 @@
 
 namespace blink {
 
-HTMLSlotElement* SlotAssignment::assignedSlotFor(const Node& node) const
+void SlotAssignment::slotAdded(HTMLSlotElement& slot)
 {
-    return m_assignment.get(const_cast<Node*>(&node));
+    // Relevant DOM Standard:
+    // https://dom.spec.whatwg.org/#concept-node-insert
+    // 6.4:  Run assign slotables for a tree with node's tree and a set containing each inclusive descendant of node that is a slot.
+
+    ++m_slotCount;
+    m_needsCollectSlots = true;
+
+    if (!m_slotMap->contains(slot.name())) {
+        m_slotMap->add(slot.name(), &slot);
+        return;
+    }
+
+    HTMLSlotElement& oldActive = *findSlotByName(slot.name());
+    DCHECK_NE(oldActive, slot);
+    m_slotMap->add(slot.name(), &slot);
+    if (findSlotByName(slot.name()) == oldActive)
+        return;
+    // |oldActive| is no longer an active slot.
+    if (oldActive.findHostChildWithSameSlotName())
+        oldActive.enqueueSlotChangeEvent();
+    // TODO(hayato): We should not enqeueue a slotchange event for |oldActive|
+    // if |oldActive| was inserted together with |slot|.
+    // This could happen if |oldActive| and |slot| are descendants of the inserted node, and
+    // |oldActive| is preceding |slot|.
+}
+
+void SlotAssignment::slotRemoved(HTMLSlotElement& slot)
+{
+    DCHECK_GT(m_slotCount, 0u);
+    --m_slotCount;
+    m_needsCollectSlots = true;
+
+    DCHECK(m_slotMap->contains(slot.name()));
+    HTMLSlotElement* oldActive = findSlotByName(slot.name());
+    m_slotMap->remove(slot.name(), &slot);
+    HTMLSlotElement* newActive = findSlotByName(slot.name());
+    if (newActive && newActive != oldActive) {
+        // |newActive| slot becomes an active slot.
+        if (newActive->findHostChildWithSameSlotName())
+            newActive->enqueueSlotChangeEvent();
+        // TODO(hayato): Prevent a false-positive slotchange.
+        // This could happen if more than one slots which have the same name are descendants of the removed node.
+    }
+}
+
+bool SlotAssignment::findHostChildBySlotName(const AtomicString& slotName) const
+{
+    // TODO(hayato): Avoid traversing children every time.
+    for (Node& child : NodeTraversal::childrenOf(m_owner->host())) {
+        if (!child.isSlotable())
+            continue;
+        if (child.slotName() == slotName)
+            return true;
+    }
+    return false;
+}
+
+void SlotAssignment::slotRenamed(const AtomicString& oldSlotName, HTMLSlotElement& slot)
+{
+    // |slot| has already new name. Thus, we can not use slot.hasAssignedNodesSynchronously.
+    bool hasAssignedNodesBefore = (findSlotByName(oldSlotName) == &slot) && findHostChildBySlotName(oldSlotName);
+
+    m_slotMap->remove(oldSlotName, &slot);
+    m_slotMap->add(slot.name(), &slot);
+
+    bool hasAssignedNodesAfter = slot.hasAssignedNodesSlow();
+
+    if (hasAssignedNodesBefore || hasAssignedNodesAfter)
+        slot.enqueueSlotChangeEvent();
+}
+
+void SlotAssignment::hostChildSlotNameChanged(const AtomicString& oldValue, const AtomicString& newValue)
+{
+    if (HTMLSlotElement* slot = findSlotByName(HTMLSlotElement::normalizeSlotName(oldValue))) {
+        slot->enqueueSlotChangeEvent();
+        m_owner->owner()->setNeedsDistributionRecalc();
+    }
+    if (HTMLSlotElement* slot = findSlotByName(HTMLSlotElement::normalizeSlotName(newValue))) {
+        slot->enqueueSlotChangeEvent();
+        m_owner->owner()->setNeedsDistributionRecalc();
+    }
+}
+
+SlotAssignment::SlotAssignment(ShadowRoot& owner)
+    : m_slotMap(DocumentOrderedMap::create())
+    , m_owner(&owner)
+    , m_needsCollectSlots(false)
+    , m_slotCount(0)
+{
 }
 
 static void detachNotAssignedNode(Node& node)
@@ -25,90 +113,72 @@
         node.lazyReattachIfAttached();
 }
 
-void SlotAssignment::resolveAssignment(ShadowRoot& shadowRoot)
+void SlotAssignment::resolveAssignment()
 {
-    m_assignment.clear();
+    for (Member<HTMLSlotElement> slot : slots())
+        slot->clearDistribution();
 
-    using Name2Slot = HeapHashMap<AtomicString, Member<HTMLSlotElement>>;
-    Name2Slot name2slot;
-
-    const HeapVector<Member<HTMLSlotElement>>& slots = shadowRoot.descendantSlots();
-
-    for (Member<HTMLSlotElement> slot : slots) {
-        slot->willUpdateAssignment();
-        slot->willUpdateFallback();
-        name2slot.add(slot->name(), slot.get());
-    }
-
-    for (Node& child : NodeTraversal::childrenOf(shadowRoot.host())) {
-        if (child.isInsertionPoint()) {
-            // A re-distribution across v0 and v1 shadow trees is not supported.
+    for (Node& child : NodeTraversal::childrenOf(m_owner->host())) {
+        if (!child.isSlotable()) {
             detachNotAssignedNode(child);
             continue;
         }
-        if (!child.slottable()) {
-            detachNotAssignedNode(child);
-            continue;
-        }
-        AtomicString slotName = child.slotName();
-        HTMLSlotElement* slot = name2slot.get(slotName);
+        HTMLSlotElement* slot = findSlotByName(child.slotName());
         if (slot)
-            assign(child, *slot);
+            slot->appendAssignedNode(child);
         else
             detachNotAssignedNode(child);
     }
-
-    for (auto slot = slots.rbegin(); slot != slots.rend(); ++slot)
-        (*slot)->updateFallbackNodes();
-
-    // For each slot, check if assigned nodes have changed
-    // If so, call fireSlotchange function
-    for (const auto& slot : slots)
-        slot->didUpdateAssignment();
 }
 
-void SlotAssignment::resolveDistribution(ShadowRoot& shadowRoot)
+void SlotAssignment::resolveDistribution()
 {
-    const HeapVector<Member<HTMLSlotElement>>& slots = shadowRoot.descendantSlots();
-    for (Member<HTMLSlotElement> slot : slots) {
-        slot->willUpdateDistribution();
-    }
+    resolveAssignment();
+    const HeapVector<Member<HTMLSlotElement>>& slots = this->slots();
 
-    for (auto slot : slots) {
-        for (auto node : slot->assignedNodes())
-            distribute(*node, *slot);
-    }
+    for (auto slot : slots)
+        slot->resolveDistributedNodes();
 
     // Update each slot's distribution in reverse tree order so that a child slot is visited before its parent slot.
     for (auto slot = slots.rbegin(); slot != slots.rend(); ++slot)
         (*slot)->updateDistributedNodesWithFallback();
-    for (const auto& slot : slots)
-        slot->didUpdateDistribution();
 }
 
-void SlotAssignment::assign(Node& hostChild, HTMLSlotElement& slot)
+const HeapVector<Member<HTMLSlotElement>>& SlotAssignment::slots()
 {
-    DCHECK(hostChild.isSlotAssignable());
-    m_assignment.add(&hostChild, &slot);
-    slot.appendAssignedNode(hostChild);
+    if (m_needsCollectSlots)
+        collectSlots();
+    return m_slots;
 }
 
-void SlotAssignment::distribute(Node& hostChild, HTMLSlotElement& slot)
+HTMLSlotElement* SlotAssignment::findSlot(const Node& node)
 {
-    DCHECK(hostChild.isSlotAssignable());
-    if (isHTMLSlotElement(hostChild))
-        slot.appendDistributedNodesFrom(toHTMLSlotElement(hostChild));
-    else
-        slot.appendDistributedNode(hostChild);
+    return node.isSlotable() ? findSlotByName(node.slotName()) : nullptr;
+}
 
-    if (slot.isChildOfV1ShadowHost())
-        slot.parentElementShadow()->setNeedsDistributionRecalc();
+HTMLSlotElement* SlotAssignment::findSlotByName(const AtomicString& slotName)
+{
+    return m_slotMap->getSlotByName(slotName, m_owner.get());
+}
+
+void SlotAssignment::collectSlots()
+{
+    DCHECK(m_needsCollectSlots);
+    m_slots.clear();
+
+    m_slots.reserveCapacity(m_slotCount);
+    for (HTMLSlotElement& slot : Traversal<HTMLSlotElement>::descendantsOf(*m_owner)) {
+        m_slots.append(&slot);
+    }
+    m_needsCollectSlots = false;
+    DCHECK_EQ(m_slots.size(), m_slotCount);
 }
 
 DEFINE_TRACE(SlotAssignment)
 {
-    visitor->trace(m_descendantSlots);
-    visitor->trace(m_assignment);
+    visitor->trace(m_slots);
+    visitor->trace(m_slotMap);
+    visitor->trace(m_owner);
 }
 
 } // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.h b/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.h
index 50c5b48..a227581 100644
--- a/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.h
+++ b/third_party/WebKit/Source/core/dom/shadow/SlotAssignment.h
@@ -5,7 +5,12 @@
 #ifndef SlotAssignment_h
 #define SlotAssignment_h
 
+// #include "core/dom/DocumentOrderedList.h"
+#include "core/dom/DocumentOrderedMap.h"
 #include "platform/heap/Handle.h"
+#include "wtf/HashMap.h"
+#include "wtf/text/AtomicString.h"
+#include "wtf/text/AtomicStringHash.h"
 
 namespace blink {
 
@@ -13,37 +18,52 @@
 class Node;
 class ShadowRoot;
 
+// TODO(hayato): Support SlotAssignment for non-shadow trees, e.g. a document tree.
 class SlotAssignment final : public GarbageCollected<SlotAssignment> {
 public:
-    static SlotAssignment* create()
+    static SlotAssignment* create(ShadowRoot& owner)
     {
-        return new SlotAssignment;
+        return new SlotAssignment(owner);
     }
 
-    HTMLSlotElement* assignedSlotFor(const Node&) const;
-    void resolveAssignment(ShadowRoot&);
-    void resolveDistribution(ShadowRoot&);
+    // Relevant DOM Standard: https://dom.spec.whatwg.org/#find-a-slot
+    HTMLSlotElement* findSlot(const Node&);
+    HTMLSlotElement* findSlotByName(const AtomicString& slotName);
 
-    void didAddSlot() { ++m_descendantSlotCount; }
-    void didRemoveSlot() { DCHECK_GT(m_descendantSlotCount, 0u); --m_descendantSlotCount; }
-    unsigned descendantSlotCount() const { return m_descendantSlotCount; }
+    // DOM Standaard defines these two procedures:
+    // 1. https://dom.spec.whatwg.org/#assign-a-slot
+    //    void assignSlot(const Node& slottable);
+    // 2. https://dom.spec.whatwg.org/#assign-slotables
+    //    void assignSlotables(HTMLSlotElement&);
+    // As an optimization, Blink does not implement these literally.
+    // Instead, provide alternative, HTMLSlotElement::hasAssignedNodesSlow()
+    // so that slotchange can be detected.
 
-    const HeapVector<Member<HTMLSlotElement>>& descendantSlots() const { return m_descendantSlots; }
+    void resolveDistribution();
+    const HeapVector<Member<HTMLSlotElement>>& slots();
 
-    void setDescendantSlots(HeapVector<Member<HTMLSlotElement>>& slots) { m_descendantSlots.swap(slots); }
-    void clearDescendantSlots() { m_descendantSlots.clear(); }
+    void slotAdded(HTMLSlotElement&);
+    void slotRemoved(HTMLSlotElement&);
+    void slotRenamed(const AtomicString& oldName, HTMLSlotElement&);
+    void hostChildSlotNameChanged(const AtomicString& oldValue, const AtomicString& newValue);
+
+    bool findHostChildBySlotName(const AtomicString& slotName) const;
 
     DECLARE_TRACE();
 
 private:
-    SlotAssignment() { };
+    explicit SlotAssignment(ShadowRoot& owner);
 
-    void assign(Node&, HTMLSlotElement&);
-    void distribute(Node&, HTMLSlotElement&);
+    void collectSlots();
 
-    unsigned m_descendantSlotCount = 0;
-    HeapVector<Member<HTMLSlotElement>> m_descendantSlots;
-    HeapHashMap<Member<Node>, Member<HTMLSlotElement>> m_assignment;
+    void resolveAssignment();
+    void distributeTo(Node&, HTMLSlotElement&);
+
+    HeapVector<Member<HTMLSlotElement>> m_slots;
+    Member<DocumentOrderedMap> m_slotMap;
+    WeakMember<ShadowRoot> m_owner;
+    unsigned m_needsCollectSlots : 1;
+    unsigned m_slotCount : 31;
 };
 
 } // namespace blink
diff --git a/third_party/WebKit/Source/core/html/HTMLSlotElement.cpp b/third_party/WebKit/Source/core/html/HTMLSlotElement.cpp
index 3314373..02e9279 100644
--- a/third_party/WebKit/Source/core/html/HTMLSlotElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLSlotElement.cpp
@@ -32,12 +32,12 @@
 
 #include "bindings/core/v8/Microtask.h"
 #include "core/HTMLNames.h"
-#include "core/dom/ElementTraversal.h"
 #include "core/dom/NodeTraversal.h"
 #include "core/dom/StyleChangeReason.h"
 #include "core/dom/StyleEngine.h"
 #include "core/dom/shadow/ElementShadow.h"
 #include "core/dom/shadow/InsertionPoint.h"
+#include "core/dom/shadow/SlotAssignment.h"
 #include "core/events/Event.h"
 #include "core/html/AssignedNodesOptions.h"
 
@@ -47,15 +47,25 @@
 
 inline HTMLSlotElement::HTMLSlotElement(Document& document)
     : HTMLElement(slotTag, document)
-    , m_distributionState(DistributionDone)
-    , m_assignmentState(AssignmentDone)
-    , m_slotchangeEventAdded(false)
 {
     setHasCustomStyleCallbacks();
 }
 
 DEFINE_NODE_FACTORY(HTMLSlotElement);
 
+// static
+AtomicString HTMLSlotElement::normalizeSlotName(const AtomicString& name)
+{
+    return (name.isNull() || name.isEmpty()) ? emptyAtom : name;
+}
+
+const HeapVector<Member<Node>>& HTMLSlotElement::assignedNodes()
+{
+    DCHECK(!needsDistributionRecalc());
+    DCHECK(isInShadowTree() || m_assignedNodes.isEmpty());
+    return m_assignedNodes;
+}
+
 const HeapVector<Member<Node>> HTMLSlotElement::assignedNodesForBinding(const AssignedNodesOptions& options)
 {
     updateDistribution();
@@ -66,86 +76,80 @@
 
 const HeapVector<Member<Node>>& HTMLSlotElement::getDistributedNodes()
 {
-    ASSERT(!needsDistributionRecalc());
+    DCHECK(!needsDistributionRecalc());
     if (isInShadowTree())
         return m_distributedNodes;
 
     // A slot is unlikely to be used outside of a shadow tree.
     // We do not need to optimize this case in most cases.
     // TODO(hayato): If this path causes a performance issue, we should move
-    // ShadowRootRaraDate::m_descendantSlots into TreeScopreRareData-ish and
+    // ShadowRoot::m_slotAssignment into TreeScopreRareData-ish and
     // update the distribution code so it considers a document tree too.
-    willUpdateDistribution();
-    for (Node& child : NodeTraversal::childrenOf(*this)) {
-        if (!child.isSlotAssignable())
+    clearDistribution();
+    Node* child = NodeTraversal::firstChild(*this);
+    while (child) {
+        if (!child->isSlotable()) {
+            child = NodeTraversal::nextSkippingChildren(*child, this);
             continue;
-        if (isHTMLSlotElement(child))
-            m_distributedNodes.appendVector(toHTMLSlotElement(child).getDistributedNodes());
-        else
-            m_distributedNodes.append(&child);
+        }
+        if (isHTMLSlotElement(child)) {
+            child = NodeTraversal::next(*child, this);
+        } else {
+            m_distributedNodes.append(child);
+            child = NodeTraversal::nextSkippingChildren(*child, this);
+        }
     }
-    didUpdateDistribution();
     return m_distributedNodes;
 }
 
-void HTMLSlotElement::appendAssignedNode(Node& node)
+void HTMLSlotElement::appendAssignedNode(Node& hostChild)
 {
-    ASSERT(m_assignmentState == AssignmentOnGoing);
-    m_assignedNodes.append(&node);
+    DCHECK(hostChild.isSlotable());
+    m_assignedNodes.append(&hostChild);
+}
+
+void HTMLSlotElement::resolveDistributedNodes()
+{
+    for (auto& node : m_assignedNodes) {
+        DCHECK(node->isSlotable());
+        if (isHTMLSlotElement(*node))
+            appendDistributedNodesFrom(toHTMLSlotElement(*node));
+        else
+            appendDistributedNode(*node);
+
+        if (isChildOfV1ShadowHost())
+            parentElementShadow()->setNeedsDistributionRecalc();
+    }
 }
 
 void HTMLSlotElement::appendDistributedNode(Node& node)
 {
-    ASSERT(m_distributionState == DistributionOnGoing);
     size_t size = m_distributedNodes.size();
     m_distributedNodes.append(&node);
     m_distributedIndices.set(&node, size);
 }
 
-void HTMLSlotElement::appendFallbackNode(Node& node)
-{
-    ASSERT(m_assignmentState == AssignmentOnGoing);
-    m_fallbackNodes.append(&node);
-}
-
 void HTMLSlotElement::appendDistributedNodesFrom(const HTMLSlotElement& other)
 {
-    ASSERT(m_distributionState == DistributionOnGoing);
     size_t index = m_distributedNodes.size();
     m_distributedNodes.appendVector(other.m_distributedNodes);
     for (const auto& node : other.m_distributedNodes)
         m_distributedIndices.set(node.get(), index++);
 }
 
-void HTMLSlotElement::willUpdateAssignment()
+void HTMLSlotElement::clearDistribution()
 {
-    ASSERT(m_assignmentState != AssignmentOnGoing);
-    m_assignmentState = AssignmentOnGoing;
-    m_oldAssignedNodes.swap(m_assignedNodes);
     m_assignedNodes.clear();
-}
-
-void HTMLSlotElement::willUpdateDistribution()
-{
-    ASSERT(m_distributionState != DistributionOnGoing);
-    m_distributionState = DistributionOnGoing;
-    m_oldDistributedNodes.swap(m_distributedNodes);
     m_distributedNodes.clear();
     m_distributedIndices.clear();
 }
 
-void HTMLSlotElement::willUpdateFallback()
-{
-    m_oldFallbackNodes.swap(m_fallbackNodes);
-    m_fallbackNodes.clear();
-}
-
 void HTMLSlotElement::dispatchSlotChangeEvent()
 {
+    m_slotchangeEventEnqueued = false;
     Event* event = Event::create(EventTypeNames::slotchange);
     event->setTarget(this);
     dispatchScopedEvent(event);
-    m_slotchangeEventAdded = false;
 }
 
 Node* HTMLSlotElement::distributedNodeNextTo(const Node& node) const
@@ -197,24 +201,20 @@
 {
     if (name == nameAttr) {
         if (ShadowRoot* root = containingShadowRoot()) {
-            root->owner()->willAffectSelector();
             if (root->isV1() && oldValue != newValue)
-                root->assignV1();
+                root->ensureSlotAssignment().slotRenamed(normalizeSlotName(oldValue), *this);
         }
     }
     HTMLElement::attributeChanged(name, oldValue, newValue, reason);
 }
 
-void HTMLSlotElement::childrenChanged(const ChildrenChange& change)
+static bool wasInShadowTreeBeforeInserted(HTMLSlotElement& slot, ContainerNode& insertionPoint)
 {
-    HTMLElement::childrenChanged(change);
-    if (ShadowRoot* root = containingShadowRoot()) {
-        if (ElementShadow* rootOwner = root->owner()) {
-            rootOwner->setNeedsDistributionRecalc();
-        }
-        if (m_assignedNodes.isEmpty() && root->isV1())
-            root->assignV1();
-    }
+    ShadowRoot* root1 = slot.containingShadowRoot();
+    ShadowRoot* root2 = insertionPoint.containingShadowRoot();
+    if (root1 && root2 && root1 == root2)
+        return false;
+    return root1;
 }
 
 Node::InsertionNotificationRequest HTMLSlotElement::insertedInto(ContainerNode* insertionPoint)
@@ -222,27 +222,36 @@
     HTMLElement::insertedInto(insertionPoint);
     ShadowRoot* root = containingShadowRoot();
     if (root) {
-        if (ElementShadow* rootOwner = root->owner())
-            rootOwner->setNeedsDistributionRecalc();
-        if (root == insertionPoint->treeScope().rootNode())
-            root->didAddSlot();
+        DCHECK(root->owner());
+        root->owner()->setNeedsDistributionRecalc();
+        // Relevant DOM Standard: https://dom.spec.whatwg.org/#concept-node-insert
+        // - 6.4:  Run assign slotables for a tree with node's tree and a set containing each inclusive descendant of node that is a slot.
+        if (!wasInShadowTreeBeforeInserted(*this, *insertionPoint))
+            root->ensureSlotAssignment().slotAdded(*this);
     }
 
     // We could have been distributed into in a detached subtree, make sure to
     // clear the distribution when inserted again to avoid cycles.
     clearDistribution();
 
-    if (root && root->isV1())
-        root->assignV1();
-
     return InsertionDone;
 }
 
+static ShadowRoot* containingShadowRootBeforeRemoved(Node& removedDescendant, ContainerNode& insertionPoint)
+{
+    if (ShadowRoot* root = removedDescendant.containingShadowRoot())
+        return root;
+    return insertionPoint.containingShadowRoot();
+}
+
 void HTMLSlotElement::removedFrom(ContainerNode* insertionPoint)
 {
-    ShadowRoot* root = containingShadowRoot();
-    if (!root)
-        root = insertionPoint->containingShadowRoot();
+    // `removedFrom` is called after the node is removed from the tree.
+    // That means:
+    // 1. If this slot is still in a tree scope, it means the slot has been in a shadow tree. An inclusive shadow-including ancestor of the shadow host was originally removed from its parent.
+    // 2. Or (this slot is now not in a tree scope), this slot's inclusive ancestor was orginally removed from its parent (== insertion point). This slot and the originally removed node was in the same tree.
+
+    ShadowRoot* root = containingShadowRootBeforeRemoved(*this, *insertionPoint);
     if (root) {
         if (ElementShadow* rootOwner = root->owner())
             rootOwner->setNeedsDistributionRecalc();
@@ -251,14 +260,11 @@
     // Since this insertion point is no longer visible from the shadow subtree, it need to clean itself up.
     clearDistribution();
 
-    ContainerNode& rootNode = insertionPoint->treeScope().rootNode();
-    if (root == &rootNode)
-        root->didRemoveSlot();
-    else if (rootNode.isShadowRoot() && toShadowRoot(rootNode).isV1())
-        toShadowRoot(rootNode).assignV1();
+    if (root && root->isV1() && root == insertionPoint->treeScope().rootNode()) {
+        // This slot was in a shadow tree and got disconnected from the shadow root.
+        root->ensureSlotAssignment().slotRemoved(*this);
+    }
 
-    if (root && root->isV1())
-        root->assignV1();
     HTMLElement::removedFrom(insertionPoint);
 }
 
@@ -271,93 +277,56 @@
         node->setNeedsStyleRecalc(LocalStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::PropagateInheritChangeToDistributedNodes));
 }
 
-void HTMLSlotElement::updateFallbackNodes()
-{
-    if (!m_fallbackNodes.isEmpty())
-        return;
-    for (auto& child : NodeTraversal::childrenOf(*this)) {
-        if (!child.isSlotAssignable())
-            continue;
-        // Insertion points are not supported as slots fallback
-        if (isActiveInsertionPoint(child))
-            continue;
-        appendFallbackNode(child);
-    }
-}
-
 void HTMLSlotElement::updateDistributedNodesWithFallback()
 {
     if (!m_distributedNodes.isEmpty())
         return;
-    for (auto node : m_fallbackNodes) {
-        if (isHTMLSlotElement(node))
-            appendDistributedNodesFrom(*toHTMLSlotElement(node));
+    for (auto& child : NodeTraversal::childrenOf(*this)) {
+        if (!child.isSlotable())
+            continue;
+        if (isHTMLSlotElement(child))
+            appendDistributedNodesFrom(toHTMLSlotElement(child));
         else
-            appendDistributedNode(*node);
+            appendDistributedNode(child);
     }
 }
 
-bool HTMLSlotElement::assignmentChanged()
+void HTMLSlotElement::enqueueSlotChangeEvent()
 {
-    ASSERT(m_assignmentState != AssignmentOnGoing);
-    if (m_assignmentState == AssignmentDone)
-        m_assignmentState = m_oldAssignedNodes == m_assignedNodes ? AssignmentUnchanged : AssignmentChanged;
-    return m_assignmentState == AssignmentChanged;
-}
+    if (!m_slotchangeEventEnqueued) {
+        Microtask::enqueueMicrotask(WTF::bind(&HTMLSlotElement::dispatchSlotChangeEvent, Persistent<HTMLSlotElement>(this)));
+        m_slotchangeEventEnqueued = true;
+    }
 
-bool HTMLSlotElement::distributionChanged()
-{
-    ASSERT(m_distributionState != DistributionOnGoing);
-    if (m_distributionState == DistributionDone)
-        m_distributionState = m_oldDistributedNodes == m_distributedNodes ? DistributionUnchanged : DistributionChanged;
-    return m_distributionState == DistributionChanged;
-}
+    ShadowRoot* root = containingShadowRoot();
+    DCHECK(root);
+    DCHECK(root->isV1());
+    root->owner()->setNeedsDistributionRecalc();
 
-bool HTMLSlotElement::fallbackChanged()
-{
-    return m_oldFallbackNodes == m_fallbackNodes;
-}
-
-void HTMLSlotElement::didUpdateAssignment()
-{
-    ASSERT(m_assignmentState == AssignmentOnGoing);
-    m_assignmentState = AssignmentDone;
-    if ((assignmentChanged() || fallbackChanged()) && !m_slotchangeEventAdded)
-        fireSlotChangeEvent();
-}
-
-void HTMLSlotElement::didUpdateDistribution()
-{
-    ASSERT(m_distributionState == DistributionOnGoing);
-    m_distributionState = DistributionDone;
-    if (isChildOfV1ShadowHost()) {
-        ElementShadow* shadow = parentElementShadow();
-        ASSERT(shadow);
-        if (!shadow->needsDistributionRecalc() && distributionChanged())
-            shadow->setNeedsDistributionRecalc();
+    if (ShadowRoot* parentShadowRoot = v1ShadowRootOfParent()) {
+        if (HTMLSlotElement* next = parentShadowRoot->ensureSlotAssignment().findSlot(*this))
+            next->enqueueSlotChangeEvent();
     }
 }
 
-void HTMLSlotElement::fireSlotChangeEvent()
+bool HTMLSlotElement::hasAssignedNodesSlow() const
 {
-    ASSERT(!m_slotchangeEventAdded);
-    Microtask::enqueueMicrotask(WTF::bind(&HTMLSlotElement::dispatchSlotChangeEvent, this));
-    m_slotchangeEventAdded = true;
-
-    Element* shadowHost = isShadowHost(parentElement()) ? parentElement() : nullptr;
-    // If this slot is assigned to another slot, fire slot change event of that slot too.
-    if (shadowHost && shadowHost->shadowRootIfV1()) {
-        if (HTMLSlotElement* assigned = assignedSlot()) {
-            if (!assigned->m_slotchangeEventAdded)
-                assigned->fireSlotChangeEvent();
-        }
-    }
+    ShadowRoot* root = containingShadowRoot();
+    DCHECK(root);
+    DCHECK(root->isV1());
+    SlotAssignment& assignment = root->ensureSlotAssignment();
+    if (assignment.findSlot(*this) != this)
+        return false;
+    return assignment.findHostChildBySlotName(name());
 }
 
-void HTMLSlotElement::clearDistribution()
+bool HTMLSlotElement::findHostChildWithSameSlotName() const
 {
-    willUpdateDistribution();
-    didUpdateDistribution();
+    ShadowRoot* root = containingShadowRoot();
+    DCHECK(root);
+    DCHECK(root->isV1());
+    SlotAssignment& assignment = root->ensureSlotAssignment();
+    return assignment.findHostChildBySlotName(name());
 }
 
 short HTMLSlotElement::tabIndex() const
@@ -369,11 +338,7 @@
 {
     visitor->trace(m_assignedNodes);
     visitor->trace(m_distributedNodes);
-    visitor->trace(m_fallbackNodes);
     visitor->trace(m_distributedIndices);
-    visitor->trace(m_oldAssignedNodes);
-    visitor->trace(m_oldDistributedNodes);
-    visitor->trace(m_oldFallbackNodes);
     HTMLElement::trace(visitor);
 }
 
diff --git a/third_party/WebKit/Source/core/html/HTMLSlotElement.h b/third_party/WebKit/Source/core/html/HTMLSlotElement.h
index 9c15524..9721bda 100644
--- a/third_party/WebKit/Source/core/html/HTMLSlotElement.h
+++ b/third_party/WebKit/Source/core/html/HTMLSlotElement.h
@@ -43,7 +43,7 @@
 public:
     DECLARE_NODE_FACTORY(HTMLSlotElement);
 
-    const HeapVector<Member<Node>>& assignedNodes() const { return m_assignedNodes; }
+    const HeapVector<Member<Node>>& assignedNodes();
     const HeapVector<Member<Node>>& getDistributedNodes();
     const HeapVector<Member<Node>> assignedNodesForBinding(const AssignedNodesOptions&);
 
@@ -54,19 +54,12 @@
     Node* distributedNodePreviousTo(const Node&) const;
 
     void appendAssignedNode(Node&);
-    void willUpdateAssignment();
+
+    void resolveDistributedNodes();
     void appendDistributedNode(Node&);
     void appendDistributedNodesFrom(const HTMLSlotElement& other);
-    void willUpdateDistribution();
-    void appendFallbackNode(Node&);
-    void willUpdateFallback();
 
-    void didUpdateAssignment();
-    void updateFallbackNodes();
     void updateDistributedNodesWithFallback();
-    void didUpdateDistribution();
-
-    void fireSlotChangeEvent();
 
     void attach(const AttachContext& = AttachContext()) final;
     void detach(const AttachContext& = AttachContext()) final;
@@ -76,47 +69,33 @@
     short tabIndex() const override;
     AtomicString name() const;
 
+    // This method can be slow because this has to traverse the children of a shadow host.
+    // This method should be used only when m_assignedNodes is dirty.
+    // e.g. To detect a slotchange event in DOM mutations.
+    bool hasAssignedNodesSlow() const;
+    bool findHostChildWithSameSlotName() const;
+
+    void enqueueSlotChangeEvent();
+
+    void clearDistribution();
+
+    static AtomicString normalizeSlotName(const AtomicString&);
+
     DECLARE_VIRTUAL_TRACE();
 
 private:
     HTMLSlotElement(Document&);
 
-    enum AssignmentState {
-        AssignmentOnGoing,
-        AssignmentDone,
-        AssignmentChanged,
-        AssignmentUnchanged
-    };
-
-    enum DistributionState {
-        DistributionOnGoing,
-        DistributionDone,
-        DistributionChanged,
-        DistributionUnchanged
-    };
-
-    void clearDistribution();
-    void childrenChanged(const ChildrenChange&) final;
     InsertionNotificationRequest insertedInto(ContainerNode*) final;
     void removedFrom(ContainerNode*) final;
     void willRecalcStyle(StyleRecalcChange) final;
 
     void dispatchSlotChangeEvent();
-    bool assignmentChanged();
-    bool distributionChanged();
-    bool fallbackChanged();
 
     HeapVector<Member<Node>> m_assignedNodes;
     HeapVector<Member<Node>> m_distributedNodes;
-    HeapVector<Member<Node>> m_fallbackNodes;
     HeapHashMap<Member<const Node>, size_t> m_distributedIndices;
-    HeapVector<Member<Node>> m_oldAssignedNodes;
-    // TODO(hayato): Remove m_oldDistibutedNodes and make SlotAssignment check the diffirence between old and new distributed nodes for each slot to save the memories.
-    HeapVector<Member<Node>> m_oldDistributedNodes;
-    HeapVector<Member<Node>> m_oldFallbackNodes;
-    DistributionState m_distributionState;
-    AssignmentState m_assignmentState;
-    bool m_slotchangeEventAdded;
+    bool m_slotchangeEventEnqueued = false;
 };
 
 } // namespace blink