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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Eitan Isaacson 2019-08-29 16:14:48 +00:00
Родитель 4760cf4b6c
Коммит c7e39034b6
3 изменённых файлов: 519 добавлений и 10 удалений

Просмотреть файл

@ -1294,6 +1294,36 @@ void DocAccessible::ContentInserted(nsIContent* aStartChildNode,
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);
@ -1334,6 +1364,11 @@ bool DocAccessible::PruneOrInsertSubtree(nsIContent* aRoot) {
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.
@ -1762,6 +1797,7 @@ class InsertIterator final {
TreeWalker mWalker;
const nsTArray<nsCOMPtr<nsIContent> >* mNodes;
nsTHashtable<nsPtrHashKey<const nsIContent>> mProcessedNodes;
uint32_t mNodesIdx;
};
@ -1787,7 +1823,15 @@ bool InsertIterator::Next() {
// 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;
}
@ -1856,20 +1900,17 @@ void DocAccessible::ProcessContentInserted(
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;
}

Просмотреть файл

@ -35,6 +35,7 @@ support-files = test_bug1276857_subframe.html
[test_optgroup.html]
[test_recreation.html]
[test_select.html]
[test_shadow_slots.html]
[test_shutdown.xul]
[test_table.html]
[test_textleaf.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>