Bug 343137. Multiple ARIA role support inconsistent with ARIA spec. r=surkov, sr=neil, a=dsicore

This commit is contained in:
aaronleventhal@moonset.net 2007-11-11 17:05:37 -08:00
parent b0d7ea2d92
commit 321a57847f
10 changed files with 177 additions and 95 deletions

View File

@ -40,8 +40,12 @@
interface nsIAccessible;
interface nsIAccessibleEvent;
%{C++
struct nsRoleMapEntry;
%}
[ptr] native nsRoleMapEntryPtr(nsRoleMapEntry);
[uuid(af05f83c-6fd2-48c1-b1c3-811857472421)]
[uuid(2d552ed0-3f38-4c7f-94c1-419de4d693ed)]
interface nsPIAccessible : nsISupports
{
/**
@ -107,5 +111,13 @@ interface nsPIAccessible : nsISupports
*/
void appendTextTo(out AString aString, in unsigned long aStartOffset,
in unsigned long aLength);
/**
* Set the ARIA role map entry for a new accessible.
* For a newly created accessible, specify which role map entry should be used.
* @param aRoleMapEntry The ARIA nsRoleMapEntry* for the accessible, or
* nsnull if none.
*/
void setRoleMapEntry(in nsRoleMapEntryPtr aRoleMapEntry);
};

View File

@ -151,6 +151,7 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
{eAria_checked, kBoolState, nsIAccessibleStates::STATE_CHECKED | nsIAccessibleStates::STATE_CHECKABLE},
{eAria_checked, "mixed", nsIAccessibleStates::STATE_MIXED | nsIAccessibleStates::STATE_CHECKABLE},
{eAria_checked, "false", nsIAccessibleStates::STATE_CHECKABLE}, kEndEntry},
{"presentation", nsIAccessibleRole::ROLE_NOTHING, eNameLabelOrTitle, eNoValue, kNoReqStates, kEndEntry},
{"progressbar", nsIAccessibleRole::ROLE_PROGRESSBAR, eNameLabelOrTitle, eHasValueMinMax, nsIAccessibleStates::STATE_READONLY,
{eAria_disabled, kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE}, kEndEntry},
{"radio", nsIAccessibleRole::ROLE_RADIOBUTTON, eNameOkFromChildren, eNoValue, kNoReqStates,
@ -211,7 +212,17 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
{eAria_checked, kBoolState, nsIAccessibleStates::STATE_CHECKED | nsIAccessibleStates::STATE_CHECKABLE},
{eAria_checked, "mixed", nsIAccessibleStates::STATE_MIXED | nsIAccessibleStates::STATE_CHECKABLE},
{eAria_checked, "false", nsIAccessibleStates::STATE_CHECKABLE},},
{nsnull, nsIAccessibleRole::ROLE_NOTHING, eNameLabelOrTitle, eNoValue, kNoReqStates, kEndEntry} // Last item
};
PRUint32 nsARIAMap::gWAIRoleMapLength = NS_ARRAY_LENGTH(nsARIAMap::gWAIRoleMap);
nsRoleMapEntry nsARIAMap::gLandmarkRoleMap = {
"",
nsIAccessibleRole::ROLE_NOTHING,
eNameLabelOrTitle,
eNoValue,
kNoReqStates,
kEndEntry
};
/**

View File

@ -134,6 +134,8 @@ struct nsARIAMap
static nsIAtom** gAriaAtomPtrsNS[eAria_none];
static nsIAtom** gAriaAtomPtrsHyphenated[eAria_none];
static nsRoleMapEntry gWAIRoleMap[];
static PRUint32 gWAIRoleMapLength;
static nsRoleMapEntry gLandmarkRoleMap;
static nsStateMapEntry gWAIUnivStateMap[];
};

View File

@ -47,7 +47,6 @@
#include "nsIDocShellTreeItem.h"
#include "nsIDocument.h"
#include "nsIDocumentViewer.h"
#include "nsIDOM3Node.h"
#include "nsIDOMCSSStyleDeclaration.h"
#include "nsIDOMCSSPrimitiveValue.h"
#include "nsIDOMDocument.h"
@ -844,66 +843,10 @@ nsAccessNode::GetARIARole(nsIContent *aContent, nsString& aRole)
{
aRole.Truncate();
PRBool allowPrefixLookup = PR_TRUE;
if (aContent->IsNodeOfType(nsINode::eHTML)) {
// Allow non-namespaced role attribute in HTML
if (!aContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::role, aRole)) {
return PR_FALSE;
}
nsCOMPtr<nsIDOMNSDocument> doc(do_QueryInterface(aContent->GetDocument()));
if (doc) {
nsAutoString mimeType;
doc->GetContentType(mimeType);
if (mimeType.EqualsLiteral("text/html")) {
allowPrefixLookup = PR_FALSE;
}
}
return aContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::role, aRole);
}
// In non-HTML content, use XHTML namespaced-role attribute
else if (!aContent->GetAttr(kNameSpaceID_XHTML, nsAccessibilityAtoms::role, aRole)) {
return PR_FALSE;
}
PRBool hasPrefix = (aRole.Find(":") >= 0);
if (!hasPrefix) {
// * No prefix* -- not a QName
// Just return entire string as long as prefix is not currently required
return PR_TRUE;
}
// Has prefix -- is a QName (role="prefix:rolename")
// Check hardcoded 'wairole:' prefix
NS_NAMED_LITERAL_STRING(hardcodedWairolePrefix, "wairole:");
if (StringBeginsWith(aRole, hardcodedWairolePrefix)) {
// The exact prefix "wairole:" is reserved to
// always indicate that we are using WAI roles.
aRole.Cut(0, hardcodedWairolePrefix.Length());
return PR_TRUE;
}
// Check for prefix mapped with xmlns:prefixname=""
nsAutoString prefix;
if (allowPrefixLookup) { // Not text/html, so we will try to find the WAIRole prefix
// QI to nsIDOM3Node causes some overhead. Unfortunately we need to do this each
// time there is a prefixed role attribute, because the prefix to namespace mappings
// can change within any subtree via the xmlns attribute
nsCOMPtr<nsIDOM3Node> dom3Node(do_QueryInterface(aContent));
if (dom3Node) {
// Look up exact prefix name for WAI Roles
NS_NAMED_LITERAL_STRING(kWAIRoles_Namespace, "http://www.w3.org/2005/01/wai-rdf/GUIRoleTaxonomy#");
dom3Node->LookupPrefix(kWAIRoles_Namespace, prefix);
prefix += ':';
PRUint32 length = prefix.Length();
if (length > 1 && StringBeginsWith(aRole, prefix)) {
// Is a QName (role="prefix:rolename"), and prefix matches WAI Role prefix
// Trim the WAI Role prefix off
aRole.Cut(0, length);
}
}
}
return PR_TRUE;
return aContent->GetAttr(kNameSpaceID_XHTML, nsAccessibilityAtoms::role, aRole);
}

View File

@ -40,6 +40,7 @@
#include "nsAccessibilityAtoms.h"
#include "nsAccessibilityService.h"
#include "nsAccessibilityUtils.h"
#include "nsARIAMap.h"
#include "nsCURILoader.h"
#include "nsDocAccessible.h"
#include "nsHTMLImageAccessibleWrap.h"
@ -1175,7 +1176,8 @@ NS_IMETHODIMP nsAccessibilityService::GetAccessibleInWeakShell(nsIDOMNode *aNode
}
nsresult nsAccessibilityService::InitAccessible(nsIAccessible *aAccessibleIn,
nsIAccessible **aAccessibleOut)
nsIAccessible **aAccessibleOut,
nsRoleMapEntry *aRoleMapEntry)
{
if (!aAccessibleIn) {
return NS_ERROR_FAILURE; // No accessible to init
@ -1186,6 +1188,9 @@ nsresult nsAccessibilityService::InitAccessible(nsIAccessible *aAccessibleIn,
NS_ASSERTION(privateAccessNode, "All accessibles must support nsPIAccessNode");
nsresult rv = privateAccessNode->Init(); // Add to cache, etc.
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsPIAccessible> privateAccessible =
do_QueryInterface(privateAccessNode);
privateAccessible->SetRoleMapEntry(aRoleMapEntry);
NS_ADDREF(*aAccessibleOut = aAccessibleIn);
}
return rv;
@ -1350,12 +1355,13 @@ NS_IMETHODIMP nsAccessibilityService::GetAccessible(nsIDOMNode *aNode,
return NS_OK;
}
frame->GetAccessible(getter_AddRefs(newAcc));
return InitAccessible(newAcc, aAccessible);
return InitAccessible(newAcc, aAccessible, nsnull);
}
nsAutoString role;
if (nsAccessNode::GetARIARole(content, role) && role.EqualsLiteral("presentation") && !content->IsFocusable()) {
// Only create accessible for role=":presentation" if it is focusable --
nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(aNode);
if (roleMapEntry && !nsCRT::strcmp(roleMapEntry->roleString, "presentation") &&
!content->IsFocusable()) { // For presentation only
// Only create accessible for role of "presentation" if it is focusable --
// in that case we need an accessible in case it gets focused, we
// don't want focus ever to be 'lost'
return NS_OK;
@ -1449,7 +1455,7 @@ NS_IMETHODIMP nsAccessibilityService::GetAccessible(nsIDOMNode *aNode,
if (!newAcc && content->Tag() != nsAccessibilityAtoms::body && content->GetParent() &&
(content->IsFocusable() ||
(isHTML && nsAccUtils::HasListener(content, NS_LITERAL_STRING("click"))) ||
HasUniversalAriaProperty(content, aWeakShell) || !role.IsEmpty())) {
HasUniversalAriaProperty(content, aWeakShell) || roleMapEntry)) {
// 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.
@ -1463,7 +1469,7 @@ NS_IMETHODIMP nsAccessibilityService::GetAccessible(nsIDOMNode *aNode,
}
}
return InitAccessible(newAcc, aAccessible);
return InitAccessible(newAcc, aAccessible, roleMapEntry);
}
PRBool

View File

@ -51,6 +51,7 @@ class nsObjectFrame;
class nsIDocShell;
class nsIPresShell;
class nsIContent;
struct nsRoleMapEntry;
class nsAccessibilityService : public nsIAccessibilityService,
public nsIObserver,
@ -99,8 +100,13 @@ private:
* every created accessible.
*
* @param aAccessibleIn - accessible to initialize.
* @param aAcccessibleOut - set to the same thing as aAccessibleIn, unless there was
* an error initializing the accessible, in which case
* it is set to nsnull
* @param aRoleMapEntry - The role map entry role the ARIA role or nsnull if none
*/
nsresult InitAccessible(nsIAccessible *aAccessibleIn, nsIAccessible **aAccessibleOut);
nsresult InitAccessible(nsIAccessible *aAccessibleIn, nsIAccessible **aAccessibleOut,
nsRoleMapEntry *aRoleMapEntry = nsnull);
/**
* Return accessible object for elements implementing nsIAccessibleProvider

View File

@ -48,6 +48,7 @@
#include "nsARIAMap.h"
#include "nsIDocument.h"
#include "nsIDOMAbstractView.h"
#include "nsIDOM3Node.h"
#include "nsIDOMDocument.h"
#include "nsIDOMDocumentView.h"
#include "nsIDOMDocumentXBL.h"
@ -67,6 +68,7 @@
#include "nsContentCID.h"
#include "nsComponentManagerUtils.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsWhitespaceTokenizer.h"
static NS_DEFINE_IID(kRangeCID, NS_RANGE_CID);
@ -194,7 +196,7 @@ nsAccUtils::SetAccAttrsForXULSelectControlItem(nsIDOMNode *aNode,
if (!itemAcc ||
nsAccessible::State(itemAcc) & nsIAccessibleStates::STATE_INVISIBLE) {
setSize--;
if (index < indexOf)
if (index < static_cast<PRUint32>(indexOf))
posInSet--;
}
}
@ -755,3 +757,77 @@ nsAccUtils::FindDescendantPointingToIDImpl(nsCString& aIdWithSpaces,
return nsnull;
}
const char *
nsAccUtils::TrimmedRole(const char *aRole, nsIContent *aContent)
{
const char kWaiRolePrefix[] = "wairole:";
const PRUint32 kWaiRolePrefixLen = NS_ARRAY_LENGTH(kWaiRolePrefix) - 1;
if (!PL_strncmp(aRole, kWaiRolePrefix, kWaiRolePrefixLen)) {
return aRole + kWaiRolePrefixLen;
}
//#ifdef ALLOW_PREFIX_LOOKUP
// Check if prefix was mapped via xmlns:[prefix] to the official WAI role namespace
char *colon = PL_strchr(aRole, ':');
if (colon) {
nsCOMPtr<nsIDOM3Node> dom3Node = do_QueryInterface(aContent);
if (dom3Node) {
// Look up exact prefix name for WAI Roles
nsAutoString prefix;
NS_NAMED_LITERAL_STRING(kWAIRoles_Namespace, "http://www.w3.org/2005/01/wai-rdf/GUIRoleTaxonomy#");
dom3Node->LookupPrefix(kWAIRoles_Namespace, prefix);
prefix += ':';
PRUint32 prefixLength = colon - aRole + 1;
if (!PL_strncmp(aRole, NS_LossyConvertUTF16toASCII(prefix).get(), prefixLength)) {
// Is a QName (role="prefix:rolename"), and prefix is mapped to WAI Role namespace
// Trim the prefix off
return aRole + prefixLength;
}
}
}
return aRole;
}
nsRoleMapEntry*
nsAccUtils::GetRoleMapEntry(nsIDOMNode *aNode)
{
nsIContent *content = nsAccessible::GetRoleContent(aNode);
if (!content) {
return nsnull;
}
nsAutoString roleString;
if (!nsAccessNode::GetARIARole(content, roleString)) {
return nsnull;
}
nsWhitespaceTokenizer tokenizer(roleString);
while (tokenizer.hasMoreTokens()) {
// Do a binary search through table for the next role in role list
const char *rawRole = NS_LossyConvertUTF16toASCII(tokenizer.nextToken()).get();
const char *trimmedRole = TrimmedRole(rawRole, content);
//#endif
PRInt32 low = 0;
PRInt32 high = nsARIAMap::gWAIRoleMapLength;
while (low <= high) {
PRInt32 index = low + ((high - low) / 2);
PRInt32 compare = PL_strcmp(trimmedRole, nsARIAMap::gWAIRoleMap[index].roleString);
if (compare == 0) {
// The role attribute maps to an entry in the role table
return &nsARIAMap::gWAIRoleMap[index];
}
if (compare < 0) {
high = index - 1;
}
else {
low = index + 1;
}
}
}
// Always use some entry if there is a role string
// To ensure an accessible object is created
return &nsARIAMap::gLandmarkRoleMap;
}

View File

@ -272,6 +272,23 @@ public:
EAriaProperty aProperty, nsAString& aValue,
PRUint32 aCheckFlags = 0);
/**
* Given a role string, return the role with any WAI role prefix trimmed off
* @param aRole The role to start with
* @param aContent What content nodes the role is set on
* @return The entire role if there is no prefix that is a WAI role prefix,
* or the role without the prefix, if it was mapped to WAI roles
*/
static const char *TrimmedRole(const char *aRole, nsIContent *aContent);
/**
* Get the role map entry for a given DOM node. This will use the first
* ARIA role if the role attribute provides a space delimited list of roles.
* @param aNode The DOM node to get the role map entry for
* @return A pointer to the role map entry for the ARIA role, or nsnull if none
*/
static nsRoleMapEntry* GetRoleMapEntry(nsIDOMNode *aNode);
/**
* Search element in neighborhood of the given element by tag name and
* attribute value that equals to ID attribute of the given element.

View File

@ -85,6 +85,7 @@
#include "nsIMutableArray.h"
#include "nsIObserverService.h"
#include "nsIServiceManager.h"
#include "nsWhitespaceTokenizer.h"
#ifdef NS_DEBUG
#include "nsIFrameDebug.h"
@ -272,6 +273,12 @@ nsAccessible::~nsAccessible()
{
}
NS_IMETHODIMP nsAccessible::SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry)
{
mRoleMapEntry = aRoleMapEntry;
return NS_OK;
}
NS_IMETHODIMP nsAccessible::GetName(nsAString& aName)
{
aName.Truncate();
@ -487,27 +494,6 @@ NS_IMETHODIMP nsAccessible::SetNextSibling(nsIAccessible *aNextSibling)
return NS_OK;
}
NS_IMETHODIMP nsAccessible::Init()
{
nsIContent *content = GetRoleContent(mDOMNode);
nsAutoString roleString;
if (content && GetARIARole(content, roleString)) {
nsCString utf8Role = NS_ConvertUTF16toUTF8(roleString); // For easy comparison
ToLowerCase(utf8Role);
PRUint32 index;
for (index = 0; nsARIAMap::gWAIRoleMap[index].roleString; index ++) {
if (utf8Role.Equals(nsARIAMap::gWAIRoleMap[index].roleString)) {
break; // The dynamic role attribute maps to an entry in our table
}
}
// Always use some entry if there is a role string
// If no match, we use the last entry which maps to ROLE_NOTHING
mRoleMapEntry = &nsARIAMap::gWAIRoleMap[index];
}
return nsAccessNodeWrap::Init();
}
nsIContent *nsAccessible::GetRoleContent(nsIDOMNode *aDOMNode)
{
// Given the DOM node for an acessible, return content node that
@ -2042,7 +2028,23 @@ nsAccessible::GetAttributes(nsIPersistentProperties **aAttributes)
// through this attribute
nsAutoString xmlRole;
if (GetARIARole(content, xmlRole)) {
attributes->SetStringProperty(NS_LITERAL_CSTRING("xml-roles"), xmlRole, oldValueUnused);
nsWhitespaceTokenizer tokenizer(xmlRole);
nsAutoString trimmedRoles;
while (tokenizer.hasMoreTokens()) {
// Trim off prefixes for WAI roles so they are easier for ATs to recognize --
// they will always appear the same, and the AT need not understand prefixes
const char *rawRole = NS_LossyConvertUTF16toASCII(tokenizer.nextToken()).get();
const char *trimmedRole = nsAccUtils::TrimmedRole(rawRole, content);
if (*trimmedRole) {
if (!trimmedRoles.IsEmpty()) {
trimmedRoles.AppendLiteral(" ");
}
trimmedRoles.Append(NS_ConvertASCIItoUTF16(trimmedRole));
}
}
if (!trimmedRoles.IsEmpty()) {
attributes->SetStringProperty(NS_LITERAL_CSTRING("xml-roles"), trimmedRoles, oldValueUnused);
}
}
// Make sure to keep these two arrays in sync

View File

@ -1,4 +1,4 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@ -114,7 +114,6 @@ public:
NS_DECL_NSIACCESSIBLEVALUE
// nsIAccessNode
NS_IMETHOD Init();
NS_IMETHOD Shutdown();
/**
@ -161,6 +160,15 @@ public:
return parent;
}
/**
* Return the nsIContent* to check for ARIA attributes on -- this may not always
* be the DOM node for the accessible. Specifically, for doc accessibles, it is not
* the document node, but either the root element or <body> in HTML.
* @param aDOMNode The DOM node for the accessible that may be affected by ARIA
* @return The nsIContent which may have ARIA markup
*/
static nsIContent *GetRoleContent(nsIDOMNode *aDOMNode);
protected:
PRBool MappedAttrState(nsIContent *aContent, PRUint32 *aStateInOut, nsStateMapEntry *aStateMapEntry);
virtual nsIFrame* GetBoundsFrame();
@ -181,7 +189,6 @@ protected:
static nsIContent *GetHTMLLabelContent(nsIContent *aForNode);
static nsIContent *GetLabelContent(nsIContent *aForNode);
static nsIContent *GetRoleContent(nsIDOMNode *aDOMNode);
// Name helpers
nsresult GetHTMLName(nsAString& _retval, PRBool aCanAggregateSubtree = PR_TRUE);