mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 809751 - presentational table related elements referred or having global ARIA attributes must be accessible, r=tbsaunde
This commit is contained in:
parent
e4edf28611
commit
788e8fce72
@ -600,40 +600,40 @@ static const EStateRule sWAIUnivStateMap[] = {
|
||||
* @note ARIA attributes that don't have any flags are not included here
|
||||
*/
|
||||
nsAttributeCharacteristics nsARIAMap::gWAIUnivAttrMap[] = {
|
||||
{&nsGkAtoms::aria_activedescendant, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_atomic, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_busy, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_checked, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, /* exposes checkable obj attr */
|
||||
{&nsGkAtoms::aria_controls, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_describedby, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_disabled, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_dropeffect, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_expanded, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_flowto, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_grabbed, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_haspopup, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_hidden, ATTR_VALTOKEN },/* always expose obj attr */
|
||||
{&nsGkAtoms::aria_invalid, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_label, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_labelledby, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_level, ATTR_BYPASSOBJ }, /* handled via groupPosition */
|
||||
{&nsGkAtoms::aria_live, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_multiline, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_multiselectable, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_owns, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_orientation, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_posinset, ATTR_BYPASSOBJ }, /* handled via groupPosition */
|
||||
{&nsGkAtoms::aria_pressed, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_readonly, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_relevant, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_required, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_selected, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_setsize, ATTR_BYPASSOBJ }, /* handled via groupPosition */
|
||||
{&nsGkAtoms::aria_sort, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_valuenow, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_valuemin, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_valuemax, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_valuetext, ATTR_BYPASSOBJ }
|
||||
{&nsGkAtoms::aria_activedescendant, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_atomic, ATTR_VALTOKEN | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_busy, ATTR_VALTOKEN | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_checked, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, /* exposes checkable obj attr */
|
||||
{&nsGkAtoms::aria_controls, ATTR_BYPASSOBJ | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_describedby, ATTR_BYPASSOBJ | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_disabled, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_dropeffect, ATTR_VALTOKEN | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_expanded, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_flowto, ATTR_BYPASSOBJ | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_grabbed, ATTR_VALTOKEN | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_haspopup, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_hidden, ATTR_VALTOKEN | ATTR_GLOBAL },/* always expose obj attr */
|
||||
{&nsGkAtoms::aria_invalid, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_label, ATTR_BYPASSOBJ | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_labelledby, ATTR_BYPASSOBJ | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_level, ATTR_BYPASSOBJ }, /* handled via groupPosition */
|
||||
{&nsGkAtoms::aria_live, ATTR_VALTOKEN | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_multiline, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_multiselectable, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_owns, ATTR_BYPASSOBJ | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_orientation, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_posinset, ATTR_BYPASSOBJ }, /* handled via groupPosition */
|
||||
{&nsGkAtoms::aria_pressed, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_readonly, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_relevant, ATTR_BYPASSOBJ | ATTR_GLOBAL },
|
||||
{&nsGkAtoms::aria_required, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_selected, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_setsize, ATTR_BYPASSOBJ }, /* handled via groupPosition */
|
||||
{&nsGkAtoms::aria_sort, ATTR_VALTOKEN },
|
||||
{&nsGkAtoms::aria_valuenow, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_valuemin, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_valuemax, ATTR_BYPASSOBJ },
|
||||
{&nsGkAtoms::aria_valuetext, ATTR_BYPASSOBJ }
|
||||
};
|
||||
|
||||
uint32_t
|
||||
|
@ -96,13 +96,19 @@ const bool kUseNativeRole = false;
|
||||
* This means it either isn't mean't to be exposed as an object attribute, or
|
||||
* that it should, but is already handled in other code.
|
||||
*/
|
||||
const uint8_t ATTR_BYPASSOBJ = 0x0001;
|
||||
const uint8_t ATTR_BYPASSOBJ = 0x1 << 0;
|
||||
|
||||
/**
|
||||
* This mask indicates the attribute is expected to have an NMTOKEN or bool value.
|
||||
* (See for example usage in Accessible::Attributes())
|
||||
*/
|
||||
const uint8_t ATTR_VALTOKEN = 0x0010;
|
||||
const uint8_t ATTR_VALTOKEN = 0x1 << 1;
|
||||
|
||||
/**
|
||||
* Indicate the attribute is global state or property (refer to
|
||||
* http://www.w3.org/TR/wai-aria/states_and_properties#global_states).
|
||||
*/
|
||||
const uint8_t ATTR_GLOBAL = 0x1 << 2;
|
||||
|
||||
/**
|
||||
* Small footprint storage of persistent aria attribute characteristics.
|
||||
|
@ -78,6 +78,46 @@
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::a11y;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Statics
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Return true if the element must be accessible.
|
||||
*/
|
||||
static bool
|
||||
MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument)
|
||||
{
|
||||
if (aContent->GetPrimaryFrame()->IsFocusable())
|
||||
return true;
|
||||
|
||||
PRUint32 attrCount = aContent->GetAttrCount();
|
||||
for (PRUint32 attrIdx = 0; attrIdx < attrCount; attrIdx++) {
|
||||
const nsAttrName* attr = aContent->GetAttrNameAt(attrIdx);
|
||||
if (attr->NamespaceEquals(kNameSpaceID_None)) {
|
||||
nsIAtom* attrAtom = attr->Atom();
|
||||
nsDependentAtomString attrStr(attrAtom);
|
||||
if (!StringBeginsWith(attrStr, NS_LITERAL_STRING("aria-")))
|
||||
continue; // not ARIA
|
||||
|
||||
// A global state or a property and in case of token defined.
|
||||
uint8_t attrFlags = nsAccUtils::GetAttributeCharacteristics(attrAtom);
|
||||
if ((attrFlags & ATTR_GLOBAL) && (!(attrFlags & ATTR_VALTOKEN) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, attrAtom))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the given ID is referred by relation attribute then create an accessible
|
||||
// for it.
|
||||
nsAutoString id;
|
||||
if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty())
|
||||
return aDocument->IsDependentID(id);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsAccessibilityService
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -638,19 +678,6 @@ nsAccessibilityService::IsLogged(const nsAString& aModule, bool* aIsLogged)
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsAccessibilityService public
|
||||
|
||||
static bool HasRelatedContent(nsIContent *aContent)
|
||||
{
|
||||
nsAutoString id;
|
||||
if (!aContent || !nsCoreUtils::GetID(aContent, id) || id.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the given ID is referred by relation attribute then create an accessible
|
||||
// for it. Take care of HTML elements only for now.
|
||||
return aContent->IsHTML() &&
|
||||
nsAccUtils::GetDocAccessibleFor(aContent)->IsDependentID(id);
|
||||
}
|
||||
|
||||
Accessible*
|
||||
nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
|
||||
DocAccessible* aDoc,
|
||||
@ -777,8 +804,7 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
|
||||
// it is referenced by ARIA relationship then treat role="presentation" on
|
||||
// the element as the role is not there.
|
||||
if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::presentation)) {
|
||||
if (!content->IsFocusable() && !HasUniversalAriaProperty(content) &&
|
||||
!HasRelatedContent(content))
|
||||
if (!MustBeAccessible(content, aDoc))
|
||||
return nullptr;
|
||||
|
||||
roleMapEntry = nullptr;
|
||||
@ -825,7 +851,7 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
|
||||
"No accessible for parent table and it didn't have role of presentation");
|
||||
#endif
|
||||
|
||||
if (!roleMapEntry && !content->IsFocusable()) {
|
||||
if (!roleMapEntry && !MustBeAccessible(content, aDoc)) {
|
||||
// Table-related descendants of presentation table are also
|
||||
// presentation if they aren't focusable and have not explicit ARIA
|
||||
// role (don't create accessibles for them unless they need to fire
|
||||
@ -927,10 +953,8 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
|
||||
// We don't do this for <body>, <html>, <window>, <dialog> etc. which
|
||||
// correspond to the doc accessible and will be created in any case
|
||||
if (!newAcc && content->Tag() != nsGkAtoms::body && content->GetParent() &&
|
||||
(frame->IsFocusable() ||
|
||||
(isHTML && nsCoreUtils::HasClickListener(content)) ||
|
||||
HasUniversalAriaProperty(content) || roleMapEntry ||
|
||||
HasRelatedContent(content))) {
|
||||
(roleMapEntry || MustBeAccessible(content, aDoc) ||
|
||||
(isHTML && nsCoreUtils::HasClickListener(content)))) {
|
||||
// This content is focusable or has an interesting dynamic content accessibility property.
|
||||
// If it's interesting we need it in the accessibility hierarchy so that events or
|
||||
// other accessibles can point to it, or so that it can hold a state, etc.
|
||||
@ -1023,29 +1047,6 @@ nsAccessibilityService::Shutdown()
|
||||
gApplicationAccessible = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
nsAccessibilityService::HasUniversalAriaProperty(nsIContent *aContent)
|
||||
{
|
||||
// ARIA attributes that take token values (NMTOKEN, bool) are special cased
|
||||
// because of special value "undefined" (see HasDefinedARIAToken).
|
||||
return nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_atomic) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_busy) ||
|
||||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_controls) ||
|
||||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby) ||
|
||||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_disabled) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_dropeffect) ||
|
||||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_flowto) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_grabbed) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_haspopup) ||
|
||||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_hidden) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_invalid) ||
|
||||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) ||
|
||||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_live) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_owns) ||
|
||||
nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_relevant);
|
||||
}
|
||||
|
||||
already_AddRefed<Accessible>
|
||||
nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent,
|
||||
DocAccessible* aDoc)
|
||||
|
@ -231,15 +231,6 @@ private:
|
||||
*/
|
||||
static bool gIsShutdown;
|
||||
|
||||
/**
|
||||
* Does this content node have a universal ARIA property set on it?
|
||||
* A universal ARIA property is one that can be defined on any element even if there is no role.
|
||||
*
|
||||
* @param aContent The content node to test
|
||||
* @return true if there is a universal ARIA property set on the node
|
||||
*/
|
||||
bool HasUniversalAriaProperty(nsIContent *aContent);
|
||||
|
||||
friend nsAccessibilityService* GetAccService();
|
||||
friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr();
|
||||
friend mozilla::a11y::ApplicationAccessible* mozilla::a11y::ApplicationAcc();
|
||||
|
@ -16,29 +16,46 @@
|
||||
<script type="application/javascript">
|
||||
function doTest()
|
||||
{
|
||||
var accTree = {
|
||||
role: ROLE_GROUPING,
|
||||
children: [
|
||||
{ role: ROLE_TEXT_CONTAINER }, // pawn
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-atomic
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-busy
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-controls
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-describedby
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-disabled
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-dropeffect
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-flowto
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-grabbed
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-haspopup
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-invalid
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-label
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-labelledby
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-live
|
||||
{ role: ROLE_TEXT_CONTAINER }, // aria-owns
|
||||
{ role: ROLE_TEXT_CONTAINER } // aria-relevant
|
||||
]
|
||||
};
|
||||
var globalIds = [
|
||||
"atomic",
|
||||
"busy",
|
||||
"controls",
|
||||
"describedby",
|
||||
"disabled",
|
||||
"dropeffect",
|
||||
"flowto",
|
||||
"grabbed",
|
||||
"haspopup",
|
||||
"hidden",
|
||||
"invalid",
|
||||
"label",
|
||||
"labelledby",
|
||||
"live",
|
||||
"owns",
|
||||
"relevant"
|
||||
];
|
||||
|
||||
testAccessibleTree("global_aria_states_and_props", accTree);
|
||||
// Elements having ARIA global state or properties or referred by another
|
||||
// element must be accessible.
|
||||
ok(isAccessible("pawn"),
|
||||
"Must be accessible because referred by another elemnet.");
|
||||
|
||||
for (var idx = 0; idx < globalIds.length; idx++) {
|
||||
ok(isAccessible(globalIds[idx]),
|
||||
"Must be accessible becuase of " + "aria-" + globalIds[idx] +
|
||||
" presence");
|
||||
}
|
||||
|
||||
// Unfocusable elements, having ARIA global state or property with a valid
|
||||
// IDREF value, and an inherited presentation role.
|
||||
ok(!isAccessible("td_nothing"),
|
||||
"inherited presentation role takes a place");
|
||||
|
||||
for (var idx = 0; idx < globalIds.length; idx++) {
|
||||
ok(isAccessible("td_" + globalIds[idx]),
|
||||
"Inherited presentation role must be ignored becuase of " +
|
||||
"aria-" + globalIds[idx] + " presence");
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
@ -54,6 +71,11 @@
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=551978">
|
||||
Mozilla Bug 551978
|
||||
</a>
|
||||
<a target="_blank"
|
||||
title="Presentational table related elements referred or having global ARIA attributes must be accessible"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=809751">
|
||||
Mozilla Bug 809751
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
@ -72,10 +94,7 @@
|
||||
<span id="flowto" aria-flowto="pawn"></span>
|
||||
<span id="grabbed" aria-grabbed="false"></span>
|
||||
<span id="haspopup" aria-haspopup="false"></span>
|
||||
<!-- we use gecko to detect hidden-ness so we currently ignore aria-hidden
|
||||
we might want to special case for aria-hidden="false", but we would need
|
||||
a good case for it. aria-hidden is only in spec as an author aid to DOM
|
||||
based AT -->
|
||||
<span id="hidden" aria-hidden="true"></span>
|
||||
<span id="invalid" aria-invalid="false"></span>
|
||||
<span id="label" aria-label="hi"></span>
|
||||
<span id="labelledby" aria-labelledby="label"></span>
|
||||
@ -84,5 +103,26 @@
|
||||
<span id="relevant" aria-relevant="additions"></span>
|
||||
</div>
|
||||
|
||||
<table role="presentation">
|
||||
<tr>
|
||||
<td id="td_nothing"></td>
|
||||
<td id="td_atomic" aria-atomic="true"></td>
|
||||
<td id="td_busy" aria-busy="false"></td>
|
||||
<td id="td_controls" aria-controls="pawn"></td>
|
||||
<td id="td_describedby" aria-describedby="pawn"></td>
|
||||
<td id="td_disabled" aria-disabled="true"></td>
|
||||
<td id="td_dropeffect" aria-dropeffect="move"></td>
|
||||
<td id="td_flowto" aria-flowto="pawn"></td>
|
||||
<td id="td_grabbed" aria-grabbed="false"></td>
|
||||
<td id="td_haspopup" aria-haspopup="false"></td>
|
||||
<td id="td_hidden" aria-hidden="true"></td>
|
||||
<td id="td_invalid" aria-invalid="false"></td>
|
||||
<td id="td_label" aria-label="hi"></td>
|
||||
<td id="td_labelledby" aria-labelledby="label"></td>
|
||||
<td id="td_live" aria-live="polite"></td>
|
||||
<td id="td_owns" aria-owns="pawn"></td>
|
||||
<td id="td_relevant" aria-relevant="additions"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user