Bug 1205318 - make aria-owns loop alg more sophisticated, r=yzen

This commit is contained in:
Alexander Surkov 2015-09-29 15:17:40 -04:00
parent 1b90806f98
commit c8c132a9e4
6 changed files with 183 additions and 49 deletions

View File

@ -1613,34 +1613,21 @@ DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
mInvalidationList.AppendElement(dependentContent);
}
// Update ARIA owns cache.
if (relAttr == nsGkAtoms::aria_owns) {
// Dependent content cannot point to other aria-owns content or
// their parents. Ignore it if so.
// XXX: note, this alg may make invalid the scenario when X owns Y
// and Y owns Z, we should have something smarter to handle that.
bool isvalid = true;
for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
Accessible* owner = it.Key();
nsIContent* parentEl = owner->GetContent();
while (parentEl && parentEl != dependentContent) {
parentEl = parentEl->GetParent();
}
if (parentEl) {
isvalid = false;
break;
}
// ARIA owns cannot refer to itself or a parent. Ignore
// the element if so.
nsIContent* parentEl = relProviderEl;
while (parentEl && parentEl != dependentContent) {
parentEl = parentEl->GetParent();
}
if (isvalid) {
// ARIA owns also cannot refer to itself or a parent.
nsIContent* parentEl = relProviderEl;
while (parentEl && parentEl != dependentContent) {
parentEl = parentEl->GetParent();
}
if (parentEl) {
isvalid = false;
}
if (isvalid) {
if (!parentEl) {
// ARIA owns element cannot refer to an element in parents chain
// of other ARIA owns element (including that ARIA owns element)
// if it's inside of a dependent element subtree of that
// ARIA owns element. Applied recursively.
if (!IsInARIAOwnsLoop(relProviderEl, dependentContent)) {
nsTArray<nsIContent*>* list =
mARIAOwnsHash.LookupOrAdd(aRelProvider);
list->AppendElement(dependentContent);
@ -1759,6 +1746,45 @@ DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
}
}
bool
DocAccessible::IsInARIAOwnsLoop(nsIContent* aOwnerEl, nsIContent* aDependentEl)
{
// ARIA owns element cannot refer to an element in parents chain of other ARIA
// owns element (including that ARIA owns element) if it's inside of
// a dependent element subtree of that ARIA owns element.
for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
Accessible* otherOwner = it.Key();
nsIContent* parentEl = otherOwner->GetContent();
while (parentEl && parentEl != aDependentEl) {
parentEl = parentEl->GetParent();
}
// The dependent element of this ARIA owns element contains some other ARIA
// owns element, make sure this ARIA owns element is not in a subtree of
// a dependent element of that other ARIA owns element. If not then
// continue a check recursively.
if (parentEl) {
nsTArray<nsIContent*>* childEls = it.UserData();
for (uint32_t idx = 0; idx < childEls->Length(); idx++) {
nsIContent* childEl = childEls->ElementAt(idx);
nsIContent* parentEl = aOwnerEl;
while (parentEl && parentEl != childEl) {
parentEl = parentEl->GetParent();
}
if (parentEl) {
return true;
}
if (IsInARIAOwnsLoop(aOwnerEl, childEl)) {
return true;
}
}
}
}
return false;
}
bool
DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
nsIAtom* aAttribute)

View File

@ -436,6 +436,12 @@ protected:
void RemoveDependentIDsFor(Accessible* aRelProvider,
nsIAtom* aRelAttr = nullptr);
/**
* Return true if given ARIA owner element and its referred content make
* the loop closed.
*/
bool IsInARIAOwnsLoop(nsIContent* aOwnerEl, nsIContent* aDependentEl);
/**
* Update or recreate an accessible depending on a changed attribute.
*

View File

@ -454,11 +454,11 @@ function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags)
if (accTree.children.length != childCount) {
for (var i = 0; i < Math.max(accTree.children.length, childCount); i++) {
var accChild;
var accChild = null, testChild = null;
try {
testChild = accTree.children[i];
accChild = children.queryElementAt(i, nsIAccessible);
testChild = accTree.children[i];
if (!testChild) {
ok(false, prettyName(acc) + " has an extra child at index " + i +
" : " + prettyName(accChild));
@ -481,10 +481,10 @@ function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags)
}
info("Matching " + prettyName(accTree) + " and " + prettyName(acc) +
" child at index " + i + " : " + prettyName(accChild));
} catch (e) {
ok(false, prettyName(accTree) + " has an extra child at index " + i +
ok(false, prettyName(accTree) + " is expected to have a child at index " + i +
" : " + prettyName(testChild) + ", " + e);
throw e;
}
}
} else {

View File

@ -10,6 +10,7 @@ skip-if = true # Bug 561508
[test_aria_imgmap.html]
[test_aria_list.html]
[test_aria_menu.html]
[test_aria_owns.html]
[test_aria_presentation.html]
[test_aria_table.html]
[test_brokencontext.html]

View File

@ -0,0 +1,119 @@
<!DOCTYPE html>
<html>
<head>
<title>@aria-owns attribute testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
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">
////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////
//enableLogging("tree"); // debug stuff
var gQueue = null;
function doTest()
{
var tree =
{ SECTION: [ // t1_1
{ SECTION: [ // t1_2
// no kids, no loop
] }
] };
testAccessibleTree("t1_1", tree);
tree =
{ SECTION: [ // t2_1
{ SECTION: [ // t2_2
{ SECTION: [ // t2_3
// no kids, no loop
] }
] }
] };
testAccessibleTree("t2_1", tree);
tree =
{ SECTION: [ // t3_3
{ SECTION: [ // t3_1
{ SECTION: [ // t3_2
{ SECTION: [ // DOM child of t3_2
// no kids, no loop
] }
] }
] }
] };
testAccessibleTree("t3_3", tree);
tree =
{ SECTION: [ // t4_1
{ SECTION: [ // DOM child of t4_1
// no kids, no loop
] }
] };
testAccessibleTree("t4_1", tree);
tree =
{ SECTION: [ // t5_1
{ SECTION: [ // DOM child of t5_1
{ SECTION: [ // t5_2
{ SECTION: [ // DOM child of t5_2
{ SECTION: [ // t5_3
{ SECTION: [ // DOM child of t5_3
// no kids, no loop
]}
]}
]}
] }
] }
] };
testAccessibleTree("t5_1", tree);
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="t1_1" aria-owns="t1_2"></div>
<div id="t1_2" aria-owns="t1_1"></div>
<div id="t2_2" aria-owns="t2_3"></div>
<div id="t2_1" aria-owns="t2_2"></div>
<div id="t2_3" aria-owns="t2_1"></div>
<div id="t3_1" aria-owns="t3_2"></div>
<div id="t3_2">
<div aria-owns="t3_3"></div>
</div>
<div id="t3_3" aria-owns="t3_1"></div>
<div id="t4_1"><div aria-owns="t4_1"></div></div>
<div id="t5_1"><div aria-owns="t5_2"></div>
<div id="t5_2"><div aria-owns="t5_3"></div></div>
<div id="t5_3"><div aria-owns="t5_1"></div></div>
</body>
</html>

View File

@ -172,25 +172,13 @@
// Test
////////////////////////////////////////////////////////////////////////////
gA11yEventDumpToConsole = true;
enableLogging("tree"); // debug stuff
//gA11yEventDumpToConsole = true;
//enableLogging("tree"); // debug stuff
var gQueue = null;
function doTest()
{
// nested and recursive aria-owns
var tree =
{ SECTION: [ // container
{ SECTION: [ // child
{ SECTION: [ // mid div
{ SECTION: [] } // grandchild
] }
] }
] };
testAccessibleTree("container", tree);
// dynamic tests
gQueue = new eventQueue();
gQueue.push(new removeARIAOwns());
@ -214,12 +202,6 @@
<pre id="test">
</pre>
<div id="container" aria-owns="child" aria-label="container"></div>
<div id="child" aria-label="child">
<div aria-owns="grandchild" aria-label="midchild"></div>
</div>
<div id="grandchild" aria-owns="container" aria-label="grandchild"></div>
<div id="container2" aria-owns="t2_checkbox t2_button">
<div role="button" id="t2_button"></div>
<div role="checkbox" id="t2_checkbox">