Bug 1571616 - Prune or reinsert accessibles in non-accessible container. r=Jamie

We need to visit the descendants of a container that has no accessible,
but has accessible children.

Also added a test for delayed removal where we can put all these nasty
cases.

Differential Revision: https://phabricator.services.mozilla.com/D41059

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Eitan Isaacson 2019-08-08 16:47:14 +00:00
Родитель 27fee7c535
Коммит 09554268a2
4 изменённых файлов: 235 добавлений и 99 удалений

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

@ -1308,6 +1308,7 @@ void DocAccessible::ContentInserted(nsIContent* aStartChildNode,
}
bool DocAccessible::PruneOrInsertSubtree(nsIContent* aRoot) {
bool insert = false;
// If we already have an accessible, check if we need to remove it, recreate
// it, or keep it in place.
Accessible* acc = GetAccessible(aRoot);
@ -1349,7 +1350,19 @@ bool DocAccessible::PruneOrInsertSubtree(nsIContent* aRoot) {
// 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)) {
return true;
// This may be a new subtree, the insertion process will recurse through
// its descendants.
if (!GetAccessibleOrDescendant(aRoot)) {
return true;
}
// Content is not an accessible, but has accessible descendants.
// We schedule this container for insertion strictly for the case where it
// itself now needs an accessible. We will still need to recurse into the
// descendant content to prune accessibles, and in all likelyness to
// insert accessibles since accessible insertions will likeley get missed
// in an existing subtree.
insert = true;
}
}
@ -1368,11 +1381,7 @@ bool DocAccessible::PruneOrInsertSubtree(nsIContent* aRoot) {
}
}
// If we get here we either already have an accessible we don't want to touch,
// or the content does not have a frame and is not display:contents.
MOZ_ASSERT(acc || (!aRoot->GetPrimaryFrame() &&
!nsCoreUtils::IsDisplayContents(aRoot)));
return false;
return insert;
}
void DocAccessible::RecreateAccessible(nsIContent* aContent) {

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

@ -21,6 +21,7 @@ support-files = test_bug1276857_subframe.html
[test_contextmenu.xul]
[test_cssoverflow.html]
[test_deck.xul]
[test_delayed_removal.html]
[test_doc.html]
[test_gencontent.html]
[test_general.html]

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

@ -0,0 +1,219 @@
<!DOCTYPE html>
<html>
<head>
<title>Test accessible delayed removal</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<style>
.gentext:before {
content: "START"
}
.gentext:after {
content: "END"
}
</style>
<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 hideDivFromInsideSpan() {
let msg = "hideDivFromInsideSpan";
info(msg);
let events = waitForOrderedEvents(
[[EVENT_HIDE, "div1"], [EVENT_REORDER, "span1"]], msg);
document.body.offsetTop; // Flush layout.
getNode("div1").style.display = "none";
await events;
testAccessibleTree("c1", { SECTION: [ { REGION: [] }, ] });
}
async function showDivFromInsideSpan() {
let msg = "showDivFromInsideSpan";
info(msg);
let events = waitForOrderedEvents(
[[EVENT_SHOW, "div2"], [EVENT_REORDER, "span2"]], msg);
document.body.offsetTop; // Flush layout.
getNode("div2").style.display = "block";
await events;
testAccessibleTree("c2",
{ SECTION: [ { REGION: [{ SECTION: [ { TEXT_LEAF: [] } ] }] }, ] });
}
async function removeDivFromInsideSpan() {
let msg = "removeDivFromInsideSpan";
info(msg);
let events = waitForOrderedEvents(
[[EVENT_HIDE, getNode("div3")], [EVENT_REORDER, "span3"]], msg);
document.body.offsetTop; // Flush layout.
getNode("div3").remove();
await events;
testAccessibleTree("c3", { SECTION: [ { REGION: [] }, ] });
}
// Test to see that generated content is inserted
async function addCSSGeneratedContent() {
let msg = "addCSSGeneratedContent";
let c4_child = getAccessible("c4_child");
info(msg);
let events = waitForOrderedEvents([
[EVENT_SHOW, evt => evt.accessible == c4_child.firstChild],
[EVENT_SHOW, evt => evt.accessible == c4_child.lastChild],
[EVENT_REORDER, c4_child]], msg);
document.body.offsetTop; // Flush layout.
getNode("c4_child").classList.add('gentext');
await events;
testAccessibleTree("c4", { SECTION: [ // container
{ SECTION: [ // inserted node
{ STATICTEXT: [] }, // :before
{ TEXT_LEAF: [] }, // primary text
{ STATICTEXT: [] }, // :after
] },
] });
}
// Test to see that generated content gets removed
async function removeCSSGeneratedContent() {
let msg = "removeCSSGeneratedContent";
let c5_child = getAccessible("c5_child");
info(msg);
let events = waitForEvents([
[EVENT_HIDE, c5_child.firstChild],
[EVENT_HIDE, c5_child.lastChild],
[EVENT_REORDER, c5_child]], msg);
document.body.offsetTop; // Flush layout.
getNode("c5_child").classList.remove('gentext');
await events;
testAccessibleTree("c5",{ SECTION: [ // container
{ SECTION: [ // inserted node
{ TEXT_LEAF: [] }, // primary text
] },
] });
}
// Test to see that a non-accessible intermediate container gets its accessible
// descendants removed and inserted correctly.
async function intermediateNonAccessibleContainers() {
let msg = "intermediateNonAccessibleContainers";
info(msg);
testAccessibleTree("c6",{ SECTION: [
{ SECTION: [
{ role: ROLE_PUSHBUTTON, name: "Hello" },
] },
] });
let events = waitForOrderedEvents(
[[EVENT_HIDE, "b1"], [EVENT_SHOW, "b2"], [EVENT_REORDER, "scrollarea"]], msg);
document.body.offsetTop; // Flush layout.
getNode("scrollarea").style.overflow = "auto";
document.querySelector("#scrollarea > div > div:first-child").style.display = "none";
document.querySelector("#scrollarea > div > div:last-child").style.display = "block";
await events;
testAccessibleTree("c6",{ SECTION: [
{ SECTION: [
{ role: ROLE_PUSHBUTTON, name: "Goodbye" },
] },
] });
}
// Test to see that the button gets reparented into the new accessible container.
async function intermediateNonAccessibleContainerBecomesAccessible() {
let msg = "intermediateNonAccessibleContainerBecomesAccessible";
info(msg);
testAccessibleTree("c7",{ SECTION: [
{ role: ROLE_PUSHBUTTON, name: "Hello" },
{ TEXT_LEAF: [] }
] });
let events = waitForOrderedEvents(
[[EVENT_HIDE, "b3"],
// b3 show event coalesced into its new container
[EVENT_SHOW, evt => evt.DOMNode.classList.contains('intermediate')],
[EVENT_REORDER, "c7"]], msg);
document.body.offsetTop; // Flush layout.
document.querySelector("#c7 > div").style.display = "block";
await events;
testAccessibleTree("c7",{ SECTION: [
{ SECTION: [ { role: ROLE_PUSHBUTTON, name: "Hello" } ] }
] });
}
async function doTest() {
await hideDivFromInsideSpan();
await showDivFromInsideSpan();
await removeDivFromInsideSpan();
await addCSSGeneratedContent();
await removeCSSGeneratedContent();
await intermediateNonAccessibleContainers();
await intermediateNonAccessibleContainerBecomesAccessible();
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="c1">
<span role="region" id="span1" aria-label="region"><div id="div1">hello</div></span>
</div>
<div id="c2">
<span role="region" id="span2" aria-label="region"><div id="div2" style="display: none">hello</div></span>
</div>
<div id="c3">
<span role="region" id="span3" aria-label="region"><div id="div3">hello</div></span>
</div>
<div id="c4"><div id="c4_child">text</div></div>
<div id="c5"><div id="c5_child" class="gentext">text</div></div>
<div id="c6">
<div id="scrollarea" style="overflow:hidden;">
<div><div><button id="b1">Hello</button></div><div style="display: none"><button id="b2">Goodbye</button></div></div>
</div>
</div>
<div id="c7">
<div style="display: inline;" class="intermediate">
<button id="b3">Hello</button>
</div>
</div>
<div id="eventdump"></div>
</body>
</html>

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

@ -133,93 +133,6 @@
};
}
// This tests a case where a container (c5) gets re-framed
// because an inline child has its block child set to display: none.
function hideBlockChildOfInline() {
this.eventSeq = [
new invokerChecker(EVENT_HIDE, "div"),
new invokerChecker(EVENT_REORDER, "span"),
];
this.invoke = function hideBlockChildOfInline_invoke() {
document.body.offsetTop; // Flush layout.
getNode("div").style.display = "none";
};
this.finalCheck = function hideBlockChildOfInline_finalCheck() {
var accTree =
{ SECTION: [ // container
{ REGION: [] },
] };
testAccessibleTree("c5", accTree);
};
this.getID = function hideBlockChildOfInline_getID() {
return "hide div from inside span.";
};
}
// This tests a case where a container (c5) gets re-framed
// because an inline child has its block child set to display: none.
function reinsertBlockChildOfInline() {
this.eventSeq = [
new invokerChecker(EVENT_REORDER, "span"),
];
this.invoke = function reinsertBlockChildOfInline_invoke() {
document.body.offsetTop; // Flush layout.
getNode("div").style.display = "block";
};
this.finalCheck = function reinsertBlockChildOfInline_finalCheck() {
var accTree =
{ SECTION: [ // container
{ REGION: [
{ SECTION: [
{ TEXT_LEAF: [] }
] }
] },
] };
testAccessibleTree("c5", accTree);
};
this.getID = function reinsertBlockChildOfInline_getID() {
return "reinsert div from inside span.";
};
}
// This tests a case where a container (c5) gets re-framed
// because an inline child has its block child removed.
function removeBlockChildOfInline() {
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("div")),
new invokerChecker(EVENT_REORDER, "span"),
];
this.invoke = function removeBlockChildOfInline_invoke() {
document.body.offsetTop; // Flush layout.
getNode("div").remove();
};
this.finalCheck = function removeBlockChildOfInline_finalCheck() {
var accTree =
{ SECTION: [ // container
{ REGION: [] },
] };
testAccessibleTree("c5", accTree);
};
this.getID = function removeBlockChildOfInline_getID() {
return "remove div from inside span.";
};
}
// //////////////////////////////////////////////////////////////////////////
// Do tests
@ -236,9 +149,6 @@
gQueue.push(new removeRemove("c2"));
gQueue.push(new insertInaccessibleAccessibleSiblings());
gQueue.push(new displayContentsInsertion());
gQueue.push(new hideBlockChildOfInline());
gQueue.push(new reinsertBlockChildOfInline());
gQueue.push(new removeBlockChildOfInline());
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -259,9 +169,6 @@
<div id="c3"><input type="button" value="button"></div>
<div id="c4"></div>
<div id="c5">
<span role="region" id="span" aria-label="region"><div id="div">hello</div></span>
</div>
</body>
</html>