Bug 468418 - Expose level for nested lists in HTML, r=marcoz, aaronlev

This commit is contained in:
Alexander Surkov 2009-01-05 15:41:30 +08:00
Родитель 9714de8cfa
Коммит 5e4bb7fd66
6 изменённых файлов: 324 добавлений и 130 удалений

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

@ -2015,95 +2015,12 @@ nsAccessible::GetAttributes(nsIPersistentProperties **aAttributes)
}
}
// Level/setsize/posinset
// Group attributes (level/setsize/posinset)
if (!nsAccUtils::HasAccGroupAttrs(attributes)) {
// The role of an accessible can be pointed by ARIA attribute but ARIA
// posinset, level, setsize may be skipped. Therefore we calculate here
// these properties to map them into description.
// If accessible is invisible we don't want to calculate group ARIA
// attributes for it.
if ((role == nsIAccessibleRole::ROLE_LISTITEM ||
role == nsIAccessibleRole::ROLE_MENUITEM ||
role == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
role == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM ||
role == nsIAccessibleRole::ROLE_RADIOBUTTON ||
role == nsIAccessibleRole::ROLE_PAGETAB ||
role == nsIAccessibleRole::ROLE_OPTION ||
role == nsIAccessibleRole::ROLE_RADIOBUTTON ||
role == nsIAccessibleRole::ROLE_OUTLINEITEM) &&
0 == (nsAccUtils::State(this) & nsIAccessibleStates::STATE_INVISIBLE)) {
PRUint32 baseRole = role;
if (role == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
role == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
baseRole = nsIAccessibleRole::ROLE_MENUITEM;
nsCOMPtr<nsIAccessible> parent = GetParent();
NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
PRInt32 positionInGroup = 0;
PRInt32 setSize = 0;
nsCOMPtr<nsIAccessible> sibling, nextSibling;
parent->GetFirstChild(getter_AddRefs(sibling));
NS_ENSURE_TRUE(sibling, NS_ERROR_FAILURE);
PRBool foundCurrent = PR_FALSE;
PRUint32 siblingRole, siblingBaseRole;
while (sibling) {
sibling->GetFinalRole(&siblingRole);
siblingBaseRole = siblingRole;
if (siblingRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
siblingRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
siblingBaseRole = nsIAccessibleRole::ROLE_MENUITEM;
// If sibling is visible and has the same base role.
if (siblingBaseRole == baseRole &&
!(nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE)) {
++ setSize;
if (!foundCurrent) {
++ positionInGroup;
if (sibling == this)
foundCurrent = PR_TRUE;
}
}
// If the sibling is separator
if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR) {
if (foundCurrent) // the our group is ended
break;
// not our group, continue the searching
positionInGroup = 0;
setSize = 0;
}
sibling->GetNextSibling(getter_AddRefs(nextSibling));
sibling = nextSibling;
}
PRInt32 groupLevel = 0;
if (role == nsIAccessibleRole::ROLE_OUTLINEITEM) {
groupLevel = 1;
nsCOMPtr<nsIAccessible> nextParent;
while (parent) {
parent->GetFinalRole(&role);
if (role == nsIAccessibleRole::ROLE_OUTLINE)
break;
if (role == nsIAccessibleRole::ROLE_GROUPING)
++ groupLevel;
parent->GetParent(getter_AddRefs(nextParent));
parent.swap(nextParent);
}
}
nsAccUtils::SetAccGroupAttrs(attributes, groupLevel, positionInGroup,
setSize);
}
// Calculate group attributes based on accessible hierarhy if they weren't
// provided by ARIA or by accessible class implementation.
rv = ComputeGroupAttributes(role, attributes);
NS_ENSURE_SUCCESS(rv, rv);
}
// Expose all ARIA attributes
@ -3567,3 +3484,145 @@ nsAccessible::GetActionRule(PRUint32 aStates)
return eNoAction;
}
nsresult
nsAccessible::ComputeGroupAttributes(PRUint32 aRole,
nsIPersistentProperties *aAttributes)
{
// The role of an accessible can be specified by ARIA attribute but ARIA
// posinset, level, setsize may be skipped. As well this method is used
// for non ARIA accessibles to avoid GetAccessibleInternal() method
// implementation in subclasses. For example, it's being used to calculate
// group attributes for HTML li elements.
// If accessible is invisible we don't want to calculate group attributes for
// it.
if (nsAccUtils::State(this) & nsIAccessibleStates::STATE_INVISIBLE)
return NS_OK;
if (aRole != nsIAccessibleRole::ROLE_LISTITEM &&
aRole != nsIAccessibleRole::ROLE_MENUITEM &&
aRole != nsIAccessibleRole::ROLE_CHECK_MENU_ITEM &&
aRole != nsIAccessibleRole::ROLE_RADIO_MENU_ITEM &&
aRole != nsIAccessibleRole::ROLE_RADIOBUTTON &&
aRole != nsIAccessibleRole::ROLE_PAGETAB &&
aRole != nsIAccessibleRole::ROLE_OPTION &&
aRole != nsIAccessibleRole::ROLE_OUTLINEITEM)
return NS_OK;
PRUint32 baseRole = aRole;
if (aRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
aRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
baseRole = nsIAccessibleRole::ROLE_MENUITEM;
nsCOMPtr<nsIAccessible> parent = GetParent();
NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
// Compute 'posinset' and 'setsize' attributes.
PRInt32 positionInGroup = 0;
PRInt32 setSize = 0;
nsCOMPtr<nsIAccessible> sibling, nextSibling;
parent->GetFirstChild(getter_AddRefs(sibling));
NS_ENSURE_STATE(sibling);
PRBool foundCurrent = PR_FALSE;
PRUint32 siblingRole, siblingBaseRole;
while (sibling) {
siblingRole = nsAccUtils::Role(sibling);
siblingBaseRole = siblingRole;
if (siblingRole == nsIAccessibleRole::ROLE_CHECK_MENU_ITEM ||
siblingRole == nsIAccessibleRole::ROLE_RADIO_MENU_ITEM)
siblingBaseRole = nsIAccessibleRole::ROLE_MENUITEM;
// If sibling is visible and has the same base role.
if (siblingBaseRole == baseRole &&
!(nsAccUtils::State(sibling) & nsIAccessibleStates::STATE_INVISIBLE)) {
++ setSize;
if (!foundCurrent) {
++ positionInGroup;
if (sibling == this)
foundCurrent = PR_TRUE;
}
}
// If the sibling is separator
if (siblingRole == nsIAccessibleRole::ROLE_SEPARATOR) {
if (foundCurrent) // the our group is ended
break;
// not our group, continue the searching
positionInGroup = 0;
setSize = 0;
}
sibling->GetNextSibling(getter_AddRefs(nextSibling));
sibling = nextSibling;
}
// Compute 'level' attribute.
PRInt32 groupLevel = 0;
if (aRole == nsIAccessibleRole::ROLE_OUTLINEITEM) {
// Always expose 'level' attribute for 'outlineitem' accessible. The number
// of nested 'grouping' accessibles containing 'outlineitem' accessible is
// its level.
groupLevel = 1;
nsCOMPtr<nsIAccessible> nextParent;
while (parent) {
PRUint32 parentRole = nsAccUtils::Role(parent);
if (parentRole == nsIAccessibleRole::ROLE_OUTLINE)
break;
if (parentRole == nsIAccessibleRole::ROLE_GROUPING)
++ groupLevel;
parent->GetParent(getter_AddRefs(nextParent));
parent.swap(nextParent);
}
} else if (aRole == nsIAccessibleRole::ROLE_LISTITEM) {
// Expose 'level' attribute on nested lists. We assume nested list is a last
// child of listitem of parent list. We don't handle the case when nested
// lists have more complex structure, for example when there are accessibles
// between parent listitem and nested list.
// Calculate 'level' attribute based on number of parent listitems.
nsCOMPtr<nsIAccessible> nextParent;
while (parent) {
PRUint32 parentRole = nsAccUtils::Role(parent);
if (parentRole == nsIAccessibleRole::ROLE_LISTITEM)
++ groupLevel;
else if (parentRole != nsIAccessibleRole::ROLE_LIST)
break;
parent->GetParent(getter_AddRefs(nextParent));
parent.swap(nextParent);
}
if (groupLevel == 0) {
// If this listitem is on top of nested lists then expose 'level'
// attribute.
nsCOMPtr<nsIAccessible> parent = GetParent();
parent->GetFirstChild(getter_AddRefs(sibling));
while (sibling) {
nsCOMPtr<nsIAccessible> siblingChild;
sibling->GetLastChild(getter_AddRefs(siblingChild));
if (nsAccUtils::Role(siblingChild) == nsIAccessibleRole::ROLE_LIST) {
groupLevel = 1;
break;
}
sibling->GetNextSibling(getter_AddRefs(nextSibling));
sibling.swap(nextSibling);
}
} else
groupLevel++; // level is 1-index based
}
nsAccUtils::SetAccGroupAttrs(aAttributes, groupLevel, positionInGroup,
setSize);
return NS_OK;
}

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

@ -284,6 +284,17 @@ protected:
*/
PRUint32 GetActionRule(PRUint32 aStates);
/**
* Compute group attributes ('posinset', 'setsize' and 'level') based
* on accessible hierarchy. Used by GetAttributes() method if group attributes
* weren't provided by ARIA or by internal accessible implementation.
*
* @param aRole [in] role of this accessible
* @param aAttributes [in, out] object attributes
*/
nsresult ComputeGroupAttributes(PRUint32 aRole,
nsIPersistentProperties *aAttributes);
/**
* Fires platform accessible event. It's notification method only. It does
* change nothing on Gecko side. Mostly you should use

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

@ -64,7 +64,8 @@ _TEST_FILES =\
test_bug420863.html \
test_cssattrs.html \
test_events_caretmove.html \
$(warning test_groupattrs.xul temporarily disabled) \
test_groupattrs.xul \
test_groupattrs.html \
$(warning test_table_indexes.html temporarily disabled) \
test_nsIAccessible_actions.html \
$(warning test_nsIAccessible_actions.xul temporarily disabled) \

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

@ -4,15 +4,15 @@
/**
* Test object attributes.
*
* @param aID [in] the ID of DOM element having accessible
* @param aAccOrElmOrID [in] the ID, DOM node or accessible
* @param aAttrs [in] the map of expected object attributes
* (name/value pairs)
* @param aSkipUnexpectedAttrs [in] points this function doesn't fail if
* unexpected attribute is encountered
*/
function testAttrs(aID, aAttrs, aSkipUnexpectedAttrs)
function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs)
{
var accessible = getAccessible(aID);
var accessible = getAccessible(aAccOrElmOrID);
if (!accessible)
return;
@ -22,14 +22,35 @@ function testAttrs(aID, aAttrs, aSkipUnexpectedAttrs)
} catch (e) { }
if (!attrs) {
ok(false, "Can't get object attributes for " + aID);
ok(false, "Can't get object attributes for " + aAccOrElmOrID);
return;
}
var errorMsg = " for " + aID;
var errorMsg = " for " + aAccOrElmOrID;
compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs);
}
/**
* Test group object attributes (posinset, setsize and level)
*
* @param aAccOrElmOrID [in] the ID, DOM node or accessible
* @param aPosInSet [in] the value of 'posinset' attribute
* @param aSetSize [in] the value of 'setsize' attribute
* @param aLevel [in, optional] the value of 'level' attribute
*/
function testGroupAttrs(aAccOrElmOrID, aPosInSet, aSetSize, aLevel)
{
var attrs = {
"posinset": String(aPosInSet),
"setsize": String(aSetSize)
};
if (aLevel)
attrs["level"] = String(aLevel);
testAttrs(aAccOrElmOrID, attrs, true);
}
////////////////////////////////////////////////////////////////////////////////
// Text attributes.

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

@ -0,0 +1,120 @@
<html>
<head>
<title>Group attributes tests</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/attributes.js"></script>
<script type="application/javascript">
function doTest()
{
//////////////////////////////////////////////////////////////////////////
// HTML select
testGroupAttrs("opt1", 1, 2);
testGroupAttrs("opt2", 2, 2);
//////////////////////////////////////////////////////////////////////////
// HTML ul/ol
testGroupAttrs("li1", 1, 3);
testGroupAttrs("li2", 2, 3);
testGroupAttrs("li3", 3, 3);
//////////////////////////////////////////////////////////////////////////
// HTML ul/ol (nested lists)
testGroupAttrs("li4", 1, 3, 1);
testGroupAttrs("li5", 2, 3, 1);
testGroupAttrs("li6", 3, 3, 1);
testGroupAttrs("n_li4", 1, 3, 2);
testGroupAttrs("n_li5", 2, 3, 2);
testGroupAttrs("n_li6", 3, 3, 2);
//////////////////////////////////////////////////////////////////////////
// ARIA list
testGroupAttrs("li7", 1, 3);
testGroupAttrs("li8", 2, 3);
testGroupAttrs("li9", 3, 3);
//////////////////////////////////////////////////////////////////////////
// ARIA list (nested lists)
testGroupAttrs("li10", 1, 3, 1);
testGroupAttrs("li11", 2, 3, 1);
testGroupAttrs("li12", 3, 3, 1);
testGroupAttrs("n_li10", 1, 3, 2);
testGroupAttrs("n_li11", 2, 3, 2);
testGroupAttrs("n_li12", 3, 3, 2);
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=468418"
title="Expose level for nested lists in HTML">
Mozilla Bug 468418
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<select size="4">
<option id="opt1">option1</option>
<option id="opt2">option2</option>
</select>
<ul>
<li id="li1">Oranges</li>
<li id="li2">Apples</li>
<li id="li3">Bananas</li>
</ul>
<ol>
<li id="li4">Oranges</li>
<li id="li5">Apples</li>
<li id="li6">Bananas
<ul>
<li id="n_li4">Oranges</li>
<li id="n_li5">Apples</li>
<li id="n_li6">Bananas</li>
</ul>
</li>
</ol>
<span role="list">
<span role="listitem" id="li7">Oranges</span>
<span role="listitem" id="li8">Apples</span>
<span role="listitem" id="li9">Bananas</span>
</span>
<span role="list">
<span role="listitem" id="li10">Oranges</span>
<span role="listitem" id="li11">Apples</span>
<span role="listitem" id="li12">Bananas
<span role="list">
<span role="listitem" id="n_li10">Oranges</span>
<span role="listitem" id="n_li11">Apples</span>
<span role="listitem" id="n_li12">Bananas</span>
</span>
</span>
</span>
</body>
</html>

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

@ -18,19 +18,6 @@
<script type="application/javascript">
<![CDATA[
function testGroupAttrs(aID, aPosInSet, aSetSize, aLevel)
{
var attrs = {
"posinset": aPosInSet,
"setsize": aSetSize
};
if (aLevel)
attrs["level"] = aLevel;
testAttrs(aID, attrs, true);
}
function doTest()
{
//////////////////////////////////////////////////////////////////////////
@ -40,26 +27,24 @@
//////////////////////////////////////////////////////////////////////////
// xul:menu (bug 443881)
if (navigator.platform == "Win32") {
var menu1 = document.getElementById("menu_item1");
menu1.open = true;
window.setTimeout(function() {
var menu2 = document.getElementById("menu_item2");
menu2.open = true;
window.setTimeout(function() {
testGroupAttrs("menu_item1.1", "1", "1");
testGroupAttrs("menu_item1.2", "1", "3");
testGroupAttrs("menu_item1.4", "2", "3");
testGroupAttrs("menu_item2", "3", "3");
testGroupAttrs("menu_item2.1", "1", "2", "1");
testGroupAttrs("menu_item2.2", "2", "2", "1");
SimpleTest.finish();
}, 0);
}, 0);
}
var menu1 = document.getElementById("menu_item1");
menu1.open = true;
window.setTimeout(function() {
var menu2 = document.getElementById("menu_item2");
menu2.open = true;
window.setTimeout(function() {
testGroupAttrs("menu_item1.1", "1", "1");
testGroupAttrs("menu_item1.2", "1", "3");
testGroupAttrs("menu_item1.4", "2", "3");
testGroupAttrs("menu_item2", "3", "3");
testGroupAttrs("menu_item2.1", "1", "2", "1");
testGroupAttrs("menu_item2.2", "2", "2", "1");
SimpleTest.finish();
}, 200);
}, 200);
//////////////////////////////////////////////////////////////////////////
// ARIA menu (bug 441888)
@ -67,9 +52,6 @@
testGroupAttrs("aria-menuitemcheckbox", "2", "3");
testGroupAttrs("aria-menuitemradio", "3", "3");
testGroupAttrs("aria-menuitem2", "1", "1");
if (navigator.platform != "Win32")
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();