Bug 1576690 - Prune de-slotted accessibles, or relocate them to new slot. r=Jamie,emilio
authorEitan Isaacson <eitan@monotonous.org>
Thu, 29 Aug 2019 16:14:48 +0000
changeset 551174 89963083c79dd5f7fbb1bcf90efe5f9985597db5
parent 551173 e379881b398923ed5c63aeaa582b80e4d39154f9
child 551175 92848bd0c1e006b7e61113d3206bc6656151e183
push id11865
push userbtara@mozilla.com
push dateMon, 02 Sep 2019 08:54:37 +0000
treeherdermozilla-beta@37f59c4671b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersJamie, emilio
bugs1576690
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1576690 - Prune de-slotted accessibles, or relocate them to new slot. r=Jamie,emilio This patch does several things: 1. If there is a change to a host or a slot, check the slottable elements to see if they are rendered in the tree. Remove them if not. 2. Check slot elements' fallback content if it is rendered and remove if not. 3. Allow accessibles to be reinserted into a different parent or index. Differential Revision: https://phabricator.services.mozilla.com/D43489
accessible/generic/DocAccessible.cpp
accessible/tests/mochitest/treeupdate/a11y.ini
accessible/tests/mochitest/treeupdate/test_shadow_slots.html
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1289,16 +1289,46 @@ void DocAccessible::ContentInserted(nsIC
     }
   }
 
   mNotificationController->ScheduleContentInsertion(container, list);
 }
 
 bool DocAccessible::PruneOrInsertSubtree(nsIContent* aRoot) {
   bool insert = false;
+
+  // In the case that we are, or are in, a shadow host, we need to assure
+  // some accessibles are removed if they are not rendered anymore.
+  nsIContent* shadowHost =
+      aRoot->GetShadowRoot() ? aRoot : aRoot->GetContainingShadowHost();
+  if (shadowHost) {
+    dom::ExplicitChildIterator iter(shadowHost);
+
+    // Check all explicit children in the host, if they are not slotted
+    // then remove their accessibles and subtrees.
+    while (nsIContent* childNode = iter.GetNextChild()) {
+      if (!childNode->GetPrimaryFrame() &&
+          !nsCoreUtils::IsDisplayContents(childNode)) {
+        ContentRemoved(childNode);
+      }
+    }
+
+    // If this is a slot, check to see if its fallback content is rendered,
+    // if not - remove it.
+    if (aRoot->IsHTMLElement(nsGkAtoms::slot)) {
+      for (nsIContent* childNode = aRoot->GetFirstChild(); childNode;
+           childNode = childNode->GetNextSibling()) {
+        if (!childNode->GetPrimaryFrame() &&
+            !nsCoreUtils::IsDisplayContents(childNode)) {
+          ContentRemoved(childNode);
+        }
+      }
+    }
+  }
+
   // If we already have an accessible, check if we need to remove it, recreate
   // it, or keep it in place.
   Accessible* acc = GetAccessible(aRoot);
   if (acc) {
     MOZ_ASSERT(aRoot == acc->GetContent(), "Accessible has differing content!");
 #ifdef A11Y_LOG
     if (logging::IsEnabled(logging::eTree)) {
       logging::MsgBegin(
@@ -1329,16 +1359,21 @@ bool DocAccessible::PruneOrInsertSubtree
     }
 
     // It is a broken image that is being reframed because it either got
     // or lost an `alt` tag that would rerender this node as text.
     if (frame && (acc->IsImage() != (frame->AccessibleType() == eImageType))) {
       ContentRemoved(aRoot);
       return true;
     }
+
+    // The accessible can be reparented or reordered in its parent.
+    // We schedule it for reinsertion. For example, a slotted element
+    // can change its slot attribute to a different slot.
+    insert = true;
   } else {
     // If there is no current accessible, and the node has a frame, or is
     // display:contents, schedule it for insertion.
     if (aRoot->GetPrimaryFrame() || nsCoreUtils::IsDisplayContents(aRoot)) {
       // This may be a new subtree, the insertion process will recurse through
       // its descendants.
       if (!GetAccessibleOrDescendant(aRoot)) {
         return true;
@@ -1757,16 +1792,17 @@ class InsertIterator final {
   }
 
  private:
   Accessible* mChild;
   Accessible* mChildBefore;
   TreeWalker mWalker;
 
   const nsTArray<nsCOMPtr<nsIContent> >* mNodes;
+  nsTHashtable<nsPtrHashKey<const nsIContent>> mProcessedNodes;
   uint32_t mNodesIdx;
 };
 
 bool InsertIterator::Next() {
   if (mNodesIdx > 0) {
     Accessible* nextChild = mWalker.Next();
     if (nextChild) {
       mChildBefore = mChild;
@@ -1782,17 +1818,25 @@ bool InsertIterator::Next() {
     // overlapping content insertion (i.e. other content was inserted between
     // this inserted content and its container or the content was reinserted
     // into different container of unrelated part of tree). To avoid a double
     // processing of the content insertion ignore this insertion notification.
     // Note, the inserted content might be not in tree at all at this point
     // what means there's no container. Ignore the insertion too.
     nsIContent* prevNode = mNodes->SafeElementAt(mNodesIdx - 1);
     nsIContent* node = mNodes->ElementAt(mNodesIdx++);
-    Accessible* container = Document()->AccessibleOrTrueContainer(node, true);
+    // Check to see if we already processed this node with this iterator.
+    // this can happen if we get two redundant insertions in the case of a
+    // text and frame insertion.
+    if (!mProcessedNodes.EnsureInserted(node)) {
+      continue;
+    }
+
+    Accessible* container =
+        Document()->AccessibleOrTrueContainer(node->GetParentNode(), true);
     if (container != Context()) {
       continue;
     }
 
     // HTML comboboxes have no-content list accessible as an intermediate
     // containing all options.
     if (container->IsHTMLCombobox()) {
       container = container->FirstChild();
@@ -1851,30 +1895,27 @@ void DocAccessible::ProcessContentInsert
 #ifdef A11Y_LOG
   logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
 #endif
 
   TreeMutation mt(aContainer);
   do {
     Accessible* parent = iter.Child()->Parent();
     if (parent) {
-      if (parent != aContainer) {
+      Accessible* previousSibling = iter.ChildBefore();
+      if (parent != aContainer ||
+          iter.Child()->PrevSibling() != previousSibling) {
 #ifdef A11Y_LOG
-        logging::TreeInfo("stealing accessible", 0, "old parent", parent,
+        logging::TreeInfo("relocating accessible", 0, "old parent", parent,
                           "new parent", aContainer, "child", iter.Child(),
                           nullptr);
 #endif
-        MOZ_ASSERT_UNREACHABLE("stealing accessible");
-        continue;
+        MoveChild(iter.Child(), aContainer,
+                  previousSibling ? previousSibling->IndexInParent() + 1 : 0);
       }
-
-#ifdef A11Y_LOG
-      logging::TreeInfo("binding to same parent", logging::eVerbose, "parent",
-                        aContainer, "child", iter.Child(), nullptr);
-#endif
       continue;
     }
 
     if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
 #ifdef A11Y_LOG
       logging::TreeInfo("accessible was inserted", 0, "container", aContainer,
                         "child", iter.Child(), nullptr);
 #endif
--- a/accessible/tests/mochitest/treeupdate/a11y.ini
+++ b/accessible/tests/mochitest/treeupdate/a11y.ini
@@ -30,13 +30,14 @@ support-files = test_bug1276857_subframe
 [test_list.html]
 [test_list_editabledoc.html]
 [test_listbox.xul]
 [test_menu.xul]
 [test_menubutton.xul]
 [test_optgroup.html]
 [test_recreation.html]
 [test_select.html]
+[test_shadow_slots.html]
 [test_shutdown.xul]
 [test_table.html]
 [test_textleaf.html]
 [test_visibility.html]
 [test_whitespace.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html
@@ -0,0 +1,467 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <title>Test shadow roots with slots</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+  <script type="application/javascript"
+          src="../promisified-events.js"></script>
+
+  <script type="application/javascript">
+    async function _dynamicShadowTest(name, mutationFunc, expectedTree, reorder_targets) {
+      info(name);
+
+      let container = getNode(name);
+      let host = container.querySelector('.host');
+
+      document.body.offsetTop;
+      let event = reorder_targets ?
+        waitForEvents(reorder_targets.map(target => [EVENT_REORDER, target, name])) :
+        waitForEvent(EVENT_REORDER, host, name);
+
+      mutationFunc(container, host);
+
+      await event;
+
+      testAccessibleTree(container, expectedTree);
+
+      return true;
+    }
+
+    async function attachFlatShadow() {
+      await _dynamicShadowTest("attachFlatShadow",
+        (container, host) => {
+          host.attachShadow({ mode: "open" })
+            .appendChild(container.querySelector('.shadowtree').content.cloneNode(true));
+        }, { SECTION: [{ SECTION: [{ name: "red"} ] }] });
+    }
+
+    async function attachOneDeepShadow() {
+      await _dynamicShadowTest("attachOneDeepShadow",
+        (container, host) => {
+          host.attachShadow({ mode: "open" })
+            .appendChild(container.querySelector('.shadowtree').content.cloneNode(true));
+        }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] });
+    }
+
+    async function changeSlotFlat() {
+      await _dynamicShadowTest("changeSlotFlat",
+        (container, host) => {
+          container.querySelector('.red').removeAttribute('slot');
+          container.querySelector('.green').setAttribute('slot', 'myslot');
+        }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+    }
+
+    async function changeSlotOneDeep() {
+      await _dynamicShadowTest("changeSlotOneDeep",
+        (container, host) => {
+          container.querySelector('.red').removeAttribute('slot');
+          container.querySelector('.green').setAttribute('slot', 'myslot');
+        }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]);
+    }
+
+    // Nested roots and slots
+    async function changeSlotNested() {
+      await _dynamicShadowTest("changeSlotNested",
+        (container, host) => {
+          testAccessibleTree(getNode("changeSlotNested"),
+            { SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] });
+          container.querySelector('.red').removeAttribute('slot');
+          container.querySelector('.green').setAttribute('slot', 'myslot');
+        }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]);
+    }
+
+    // Dynamic mutations to both shadow root and shadow host subtrees
+    // testing/web-platform/tests/css/css-scoping/shadow-assign-dynamic-001.html
+    async function assignSlotDynamic() {
+      await _dynamicShadowTest("assignSlotDynamic",
+        (container, host) => {
+          host.shadowRoot.appendChild(container.querySelector('.shadowtree').content.cloneNode(true));
+          host.appendChild(container.querySelector('.lighttree').content.cloneNode(true));
+        }, { SECTION: [{ SECTION: [{ name: "slot1"}, { name: "slot2" } ] }] });
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-001.html
+    async function shadowFallbackDynamic_1() {
+      await _dynamicShadowTest("shadowFallbackDynamic_1",
+        (container, host) => {
+          host.firstElementChild.remove();
+        }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-002.html
+    async function shadowFallbackDynamic_2() {
+      await _dynamicShadowTest("shadowFallbackDynamic_2",
+        (container, host) => {
+          host.firstElementChild.removeAttribute("slot");
+        }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-003.html
+    async function shadowFallbackDynamic_3() {
+      await _dynamicShadowTest("shadowFallbackDynamic_3",
+        (container, host) => {
+          host.appendChild(container.querySelector(".lighttree").content.cloneNode(true));
+        }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html
+    async function shadowFallbackDynamic_4() {
+      await _dynamicShadowTest("shadowFallbackDynamic_4",
+        (container, host) => {
+          host.shadowRoot.insertBefore(
+            container.querySelector(".moreshadowtree").
+              content.cloneNode(true), host.shadowRoot.firstChild);
+        }, { SECTION: [{ SECTION: [{ name: "slotparent2", children: [{ name: "green"} ] }, { name: "slotparent1" } ] }] });
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html
+    // This tests a case when the the slotted element would remain in the same accessible container
+    async function shadowFallbackDynamic_4_1() {
+      await _dynamicShadowTest("shadowFallbackDynamic_4_1",
+        (container, host) => {
+          host.shadowRoot.insertBefore(
+            container.querySelector(".moreshadowtree").
+              content.cloneNode(true), host.shadowRoot.firstChild);
+        }, { SECTION: [{ SECTION: [ { name: "green"}, { SEPARATOR: [] } ] }] });
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-005.html
+    async function shadowFallbackDynamic_5() {
+      await _dynamicShadowTest("shadowFallbackDynamic_5",
+        (container, host) => {
+          host.firstElementChild.setAttribute("slot", "myotherslot");
+        }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-002.html
+    async function shadowReassignDynamic_2() {
+      await _dynamicShadowTest("shadowReassignDynamic_2",
+        (container, host) => {
+          host.shadowRoot.querySelector("slot").setAttribute("name", "myslot");
+        }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-003.html
+    async function shadowReassignDynamic_3() {
+      await _dynamicShadowTest("shadowReassignDynamic_3",
+        (container, host) => {
+          testAccessibleTree(container, { SECTION: [{ SECTION: [{ name: "green"}, { name: "red", children: [ { PUSHBUTTON: [] }]} ] }] });
+          host.shadowRoot.querySelector("slot[name]").removeAttribute("name");
+
+        }, { SECTION: [{ SECTION: [{ name: "green", children: [ { PUSHBUTTON: [] }]}, { name: "red"} ] }] },
+        [evt => evt.accessible.name == "green", evt => evt.accessible.name == "red"]);
+    }
+
+    // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-004.html
+    async function shadowReassignDynamic_4() {
+      await _dynamicShadowTest("shadowReassignDynamic_4",
+        (container, host) => {
+          host.shadowRoot.getElementById("slot").remove();
+      }, { SECTION: [{ SECTION: [{ name: "green"} ] }] });
+
+    }
+
+    async function doTest() {
+      await attachFlatShadow();
+
+      await attachOneDeepShadow();
+
+      await changeSlotFlat();
+
+      await changeSlotOneDeep();
+
+      await changeSlotNested();
+
+      await assignSlotDynamic();
+
+      await shadowFallbackDynamic_1();
+
+      await shadowFallbackDynamic_2();
+
+      await shadowFallbackDynamic_3();
+
+      await shadowFallbackDynamic_4();
+
+      await shadowFallbackDynamic_4_1();
+
+      await shadowFallbackDynamic_5();
+
+      await shadowReassignDynamic_2();
+
+      await shadowReassignDynamic_3();
+
+      await shadowReassignDynamic_4();
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+  <div id="attachFlatShadow">
+    <template class="shadowtree">
+      <style>::slotted(div) { width: 100px; height: 100px }</style>
+      <slot name="myslot">FAIL</slot>
+    </template>
+    <section class="host">
+      <div style="background: green" aria-label="green"></div>
+      <div style="background: red" aria-label="red" slot="myslot"></div>
+    </section>
+  </div>
+
+  <div id="attachOneDeepShadow">
+    <template class="shadowtree">
+      <style>::slotted(div) { width: 100px; height: 100px }</style>
+      <div id="shadowdiv">
+        <slot name="myslot">FAIL</slot>
+      </div>
+    </template>
+    <section class="host">
+      <div style="background: green" aria-label="green"></div>
+      <div style="background: red" aria-label="red" slot="myslot"></div>
+    </section>
+  </div>
+
+  <div id="changeSlotFlat">
+    <template class="shadowtree">
+      <style>::slotted(div) { width: 100px; height: 100px }</style>
+      <slot name="myslot">FAIL</slot>
+    </template>
+    <section class="host">
+      <div class="green" style="background: green" aria-label="green"></div>
+      <div class="red" style="background: red" aria-label="red" slot="myslot"></div>
+    </section>
+    <script>
+      document.querySelector("#changeSlotFlat > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#changeSlotFlat > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="changeSlotOneDeep">
+    <template class="shadowtree">
+      <style>::slotted(div) { width: 100px; height: 100px }</style>
+      <div id="shadowdiv">
+        <slot name="myslot">FAIL</slot>
+      </div>
+    </template>
+    <section class="host">
+      <div class="green" style="background: green" aria-label="green"></div>
+      <div class="red" style="background: red" aria-label="red" slot="myslot"></div>
+    </section>
+    <script>
+      document.querySelector("#changeSlotOneDeep > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#changeSlotOneDeep > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+    <div id="changeSlotNested">
+    <template class="shadowtree outer">
+      <div id="shadowdiv">
+        <slot name="myslot">FAIL</slot>
+      </div>
+    </template>
+    <template class="shadowtree inner">
+      <style>::slotted(div) { width: 100px; height: 100px }</style>
+      <slot>FAIL</slot>
+    </template>
+    <section class="host">
+      <div class="green" style="background: green" aria-label="green"></div>
+      <div class="red" style="background: red" aria-label="red" slot="myslot"></div>
+    </section>
+    <script>
+      (function foo() {
+        let outerShadow =
+          document.querySelector("#changeSlotNested > .host").
+            attachShadow({ mode: "open" });
+        outerShadow.appendChild(
+          document.querySelector("#changeSlotNested > .shadowtree.outer").
+            content.cloneNode(true));
+        let innerShadow =
+          outerShadow.querySelector("#shadowdiv").
+            attachShadow({ mode: "open" });
+        innerShadow.appendChild(
+          document.querySelector("#changeSlotNested > .shadowtree.inner").
+            content.cloneNode(true));
+      })();
+    </script>
+  </div>
+
+  <div id="assignSlotDynamic">
+    <template class="shadowtree">
+      <style>::slotted(div) { width: 50px; height: 100px }</style>
+      <slot name="slot1">FAIL</slot>
+      <slot name="slot2">FAIL</slot>
+    </template>
+    <template class="lighttree">
+      <div aria-label="slot1" slot="slot1"></div>
+      <div aria-label="slot2" slot="slot2"></div>
+    </template>
+    <section class="host"></section>
+    <script>
+      document.querySelector("#assignSlotDynamic > .host").attachShadow({ mode: "open" });
+    </script>
+  </div>
+
+  <div id="shadowFallbackDynamic_1">
+    <template class="shadowtree">
+      <slot name="myslot">
+        <div aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+      </slot>
+    </template>
+    <section class="host"><span slot="myslot">FAIL</span></section>
+    <script>
+      document.querySelector("#shadowFallbackDynamic_1 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowFallbackDynamic_1 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="shadowFallbackDynamic_2">
+    <template class="shadowtree">
+      <slot name="myslot">
+        <div aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+      </slot>
+    </template>
+    <section class="host"><span slot="myslot">FAIL</span></section>
+    <script>
+      document.querySelector("#shadowFallbackDynamic_2 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowFallbackDynamic_2 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="shadowFallbackDynamic_3">
+    <template class="shadowtree">
+      <slot name="myslot">FAIL</slot>
+    </template>
+    <template class="lighttree">
+      <div aria-label="green" slot="myslot" style="width: 100px; height: 100px; background: green"></div>
+    </template>
+    <section class="host"></section>
+    <script>
+      document.querySelector("#shadowFallbackDynamic_3 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowFallbackDynamic_3 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="shadowFallbackDynamic_4">
+    <template class="shadowtree">
+      <div aria-label="slotparent1"><slot name="myslot"></slot></div>
+    </template>
+    <template class="moreshadowtree">
+      <div aria-label="slotparent2"><slot name="myslot">FAIL</slot></div>
+    </template>
+    <section class="host">
+      <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+    </section>
+    <script>
+      document.querySelector("#shadowFallbackDynamic_4 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowFallbackDynamic_4 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="shadowFallbackDynamic_4_1">
+    <template class="shadowtree">
+      <hr>
+      <slot name="myslot"></slot>
+    </template>
+    <template class="moreshadowtree">
+      <slot name="myslot">FAIL</slot>
+    </template>
+    <section class="host">
+      <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+    </section>
+    <script>
+      document.querySelector("#shadowFallbackDynamic_4_1 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowFallbackDynamic_4_1 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="shadowFallbackDynamic_5">
+    <template class="shadowtree">
+      <slot name="myslot"></slot>
+      <slot name="myotherslot">FAIL</slot>
+    </template>
+    <section class="host">
+      <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+    </section>
+    <script>
+      document.querySelector("#shadowFallbackDynamic_5 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowFallbackDynamic_5 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="shadowReassignDynamic_2">
+    <template class="shadowtree">
+      <style>::slotted(div) { width: 100px; height: 100px }</style>
+      <slot>FAIL</slot>
+    </template>
+    <section class="host">
+      <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div>
+    </section>
+    <script>
+      document.querySelector("#shadowReassignDynamic_2 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowReassignDynamic_2 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="shadowReassignDynamic_3">
+    <template class="shadowtree">
+      <div aria-label="green"><slot name="nomatch"></slot></div>
+      <div aria-label="red"><slot></slot></div>
+    </template>
+    <section class="host">
+      <div role="button"></div>
+    </section>
+    <script>
+      document.querySelector("#shadowReassignDynamic_3 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowReassignDynamic_3 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="shadowReassignDynamic_4">
+    <template class="shadowtree">
+      <style>::slotted(div),div { width: 100px; height: 100px }</style>
+      <slot id="slot"></slot>
+      <slot>
+        <div aria-label="red" style="background: red"></div>
+      </slot>
+    </template>
+    <section class="host">
+      <div aria-label="green" style="background: green"></div>
+    </section>
+    <script>
+      document.querySelector("#shadowReassignDynamic_4 > .host")
+        .attachShadow({ mode: "open" })
+        .appendChild(document.querySelector("#shadowReassignDynamic_4 > .shadowtree").content.cloneNode(true));
+    </script>
+  </div>
+
+  <div id="eventdump"></div>
+</body>
+</html>