Bug 280920. DHTML menu accessibility. See example in bug (URL field). r=pkwarren, sr=jst

This commit is contained in:
aaronleventhal%moonset.net 2005-02-07 19:43:45 +00:00
Родитель f1c5943731
Коммит ca121afdd3
5 изменённых файлов: 183 добавлений и 43 удалений

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

@ -88,6 +88,7 @@ ACCESSIBILITY_ATOM(_for, "for")
ACCESSIBILITY_ATOM(id, "id")
ACCESSIBILITY_ATOM(name, "name")
ACCESSIBILITY_ATOM(tabindex, "tabindex")
ACCESSIBILITY_ATOM(title, "title")
// DHTML accessibility attributes
ACCESSIBILITY_ATOM(valuenow, "valuenow") // For DHTML widget values

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

@ -130,12 +130,23 @@ nsAccessible::~nsAccessible()
{
}
NS_IMETHODIMP nsAccessible::GetName(nsAString& _retval)
NS_IMETHODIMP nsAccessible::GetName(nsAString& aName)
{
nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(mDOMNode));
if (elt)
return elt->GetAttribute(NS_LITERAL_STRING("title"), _retval);
return NS_ERROR_FAILURE;
nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
if (!content) {
return NS_ERROR_FAILURE; // Node shut down
}
if (mRoleMapEntry && mRoleMapEntry->nameRule == eAggregateSubtree) {
// DHTML accessible name method for focusable items such as menuitem, treeitem, gridcell, etc.
nsresult rv = AppendFlatStringFromSubtree(content, &aName);
if (NS_SUCCEEDED(rv) && !aName.IsEmpty()) {
return rv;
}
}
content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::title, aName);
return NS_OK;
}
NS_IMETHODIMP nsAccessible::GetDescription(nsAString& aDescription)
@ -987,8 +998,8 @@ nsresult nsAccessible::AppendFlatStringFromContentNode(nsIContent *aContent, nsA
nsCOMPtr<nsIContent> appendedSubtreeStart(do_QueryInterface(mDOMNode));
if (parentContent && parentContent != appendedSubtreeStart) {
nsIFrame *frame;
nsresult rv = shell->GetPrimaryFrameFor(parentContent, &frame);
if (NS_SUCCEEDED(rv)) {
shell->GetPrimaryFrameFor(parentContent, &frame);
if (frame) {
// If this text is inside a block level frame (as opposed to span level), we need to add spaces around that
// block's text, so we don't get words jammed together in final name
// Extra spaces will be trimmed out later
@ -1302,28 +1313,31 @@ nsRoleMapEntry nsAccessible::gWAIRoleMap[] =
// Using RDF will also allow for role extensibility.
// XXX Should we store attribute names in this table as atoms instead of strings?
// Definition of nsRoleMapEntry and nsStateMapEntry contains comments explaining this table.
{"button", ROLE_PUSHBUTTON, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"checkbox", ROLE_CHECKBUTTON, 0, {"checked", "true", STATE_CHECKED}, {"readonly", 0, STATE_READONLY}, {0, 0, 0}},
{"checkbox-tristate", ROLE_CHECKBUTTON, 0, {"checked", "true", STATE_CHECKED}, {"checked", "mixed", STATE_MIXED}, {"readonly", 0, STATE_READONLY}},
{"icon", ROLE_ICON, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menu", ROLE_MENUPOPUP, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menubar", ROLE_MENUBAR, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menuitem", ROLE_MENUITEM, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menuitem-checkbox", ROLE_MENUITEM, 0, {"checked", "true", STATE_CHECKED}, {0, 0, 0}, {0, 0, 0}},
{"progress-meter", ROLE_PROGRESSBAR, STATE_READONLY, {"valuenow", "unknown", STATE_MIXED}, {0, 0, 0}, {0, 0, 0}},
{"grid", ROLE_TABLE, 0, {"readonly", 0, STATE_READONLY}, {"multiselect", 0, STATE_EXTSELECTABLE | STATE_MULTISELECTABLE}, {0, 0, 0}},
{"gridcell", ROLE_CELL, STATE_SELECTABLE, {"selected", 0, STATE_SELECTED}, {0, 0, 0}, {0, 0, 0}},
{"option", ROLE_LISTITEM, 0, {"selected", 0, STATE_SELECTED}, {0, 0, 0}, {0, 0, 0}},
{"secret-text", ROLE_PASSWORD_TEXT, STATE_PROTECTED, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // XXX Use ext state STATE_SINGLE_LINE
{"select", ROLE_LIST, 0, {"readonly", 0, STATE_READONLY}, {"multiselect", 0, STATE_EXTSELECTABLE | STATE_MULTISELECTABLE}, {0, 0, 0}},
{"slider", ROLE_SLIDER, 0, {"readonly", 0, STATE_READONLY}, {0, 0, 0}, {0, 0, 0}},
{"submit", ROLE_PUSHBUTTON, STATE_DEFAULT, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"textarea", ROLE_TEXT, 0, {"readonly", 0, STATE_READONLY}, {0, 0, 0}, {0, 0, 0}}, // XXX Use ext state STATE_MULTI_LINE
{"textfield", ROLE_TEXT, 0, {"readonly", 0, STATE_READONLY}, {0, 0, 0}, {0, 0, 0}}, // XXX Use ext state STATE_SINGLE_LINE
{"toolbar-icon", ROLE_PUSHBUTTON, 0, {"checked", "true", STATE_PRESSED}, {0, 0, 0}, {0, 0, 0}},
{"tree", ROLE_OUTLINE, 0, {"readonly", 0, STATE_READONLY}, {"multiselect", 0, STATE_EXTSELECTABLE | STATE_MULTISELECTABLE}, {0, 0, 0}},
{"treeitem", ROLE_OUTLINEITEM, 0, {"selected", 0, STATE_SELECTED}, {0, 0, 0}, {0, 0, 0}},
{nsnull, ROLE_NOTHING, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"button", ROLE_PUSHBUTTON, eAggregateSubtree, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"checkbox", ROLE_CHECKBUTTON, eAggregateSubtree, 0, {"checked", "true", STATE_CHECKED}, {"readonly", 0, STATE_READONLY}, {0, 0, 0}},
{"checkbox-tristate", ROLE_CHECKBUTTON, eAggregateSubtree, 0, {"checked", "true", STATE_CHECKED}, {"checked", "mixed", STATE_MIXED}, {"readonly", 0, STATE_READONLY}},
{"columnheader", ROLE_COLUMNHEADER, eAggregateSubtree, STATE_SELECTABLE, {"selected", 0, STATE_SELECTED}, {0, 0, 0}, {0, 0, 0}},
{"icon", ROLE_ICON, eAggregateSubtree, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menu", ROLE_MENUPOPUP, eTitleOnly, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menupopup", ROLE_MENUPOPUP, eTitleOnly, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menubar", ROLE_MENUBAR, eTitleOnly, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menuitem", ROLE_MENUITEM, eAggregateSubtree, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"menuitem-checkbox", ROLE_MENUITEM, eAggregateSubtree, 0, {"checked", "true", STATE_CHECKED}, {0, 0, 0}, {0, 0, 0}},
{"grid", ROLE_TABLE, eTitleOnly, 0, {"readonly", 0, STATE_READONLY}, {"multiselect", 0, STATE_EXTSELECTABLE | STATE_MULTISELECTABLE}, {0, 0, 0}},
{"gridcell", ROLE_CELL, eAggregateSubtree, STATE_SELECTABLE, {"selected", 0, STATE_SELECTED}, {0, 0, 0}, {0, 0, 0}},
{"option", ROLE_LISTITEM, eAggregateSubtree, STATE_SELECTABLE, {"selected", 0, STATE_SELECTED}, {0, 0, 0}, {0, 0, 0}},
{"progress-meter", ROLE_PROGRESSBAR, eTitleOnly, STATE_READONLY, {"valuenow", "unknown", STATE_MIXED}, {0, 0, 0}, {0, 0, 0}},
{"rowheader", ROLE_ROWHEADER, eAggregateSubtree, STATE_SELECTABLE, {"selected", 0, STATE_SELECTED}, {0, 0, 0}, {0, 0, 0}},
{"secret-text", ROLE_PASSWORD_TEXT, eTitleOnly, STATE_PROTECTED, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // XXX Use ext state STATE_SINGLE_LINE
{"select", ROLE_LIST, eTitleOnly, 0, {"readonly", 0, STATE_READONLY}, {"multiselect", 0, STATE_EXTSELECTABLE | STATE_MULTISELECTABLE}, {0, 0, 0}},
{"slider", ROLE_SLIDER, eTitleOnly, 0, {"readonly", 0, STATE_READONLY}, {0, 0, 0}, {0, 0, 0}},
{"submit", ROLE_PUSHBUTTON, eAggregateSubtree, STATE_DEFAULT, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
{"textarea", ROLE_TEXT, eTitleOnly, 0, {"readonly", 0, STATE_READONLY}, {0, 0, 0}, {0, 0, 0}}, // XXX Use ext state STATE_MULTI_LINE
{"textfield", ROLE_TEXT, eTitleOnly, 0, {"readonly", 0, STATE_READONLY}, {0, 0, 0}, {0, 0, 0}}, // XXX Use ext state STATE_SINGLE_LINE
{"toolbar-icon", ROLE_PUSHBUTTON, eAggregateSubtree, 0, {"checked", "true", STATE_PRESSED}, {0, 0, 0}, {0, 0, 0}},
{"tree", ROLE_OUTLINE, eTitleOnly, 0, {"readonly", 0, STATE_READONLY}, {"multiselect", 0, STATE_EXTSELECTABLE | STATE_MULTISELECTABLE}, {0, 0, 0}},
{"treeitem", ROLE_OUTLINEITEM, eAggregateSubtree, STATE_SELECTABLE, {"selected", 0, STATE_SELECTED}, {0, 0, 0}, {0, 0, 0}},
{nsnull, ROLE_NOTHING, eTitleOnly, 0, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}},
};
// XHTML 2 roles

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

@ -63,10 +63,16 @@ struct nsStateMapEntry
PRUint32 state; // OR state with this
};
enum ENameRule {
eAggregateSubtree, // Collect name from text & img descendents; use title if resulting name is "".
eTitleOnly // Use the title attribute for a name
};
struct nsRoleMapEntry
{
const char *roleString; // such as "button"
PRUint32 role; // use this role
ENameRule nameRule; // how to compute name
PRUint32 state; // always OR state with this
// For this role with a DOM attribute/value match definined in
// nsStateMapEntry.attributeName && .attributeValue, OR accessible state with

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

@ -324,22 +324,138 @@ NS_IMETHODIMP nsRootAccessible::GetCaretAccessible(nsIAccessible **aCaretAccessi
return NS_OK;
}
void nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *focusAccessible, nsIDOMNode *focusNode)
void nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible, nsIDOMNode *aNode)
{
if (focusAccessible && focusNode && gLastFocusedNode != focusNode) {
nsCOMPtr<nsPIAccessible> privateFocusAcc(do_QueryInterface(focusAccessible));
NS_IF_RELEASE(gLastFocusedNode);
gLastFocusedNode = nsnull;
PRUint32 role = ROLE_NOTHING;
focusAccessible->GetRole(&role);
if (role != ROLE_MENUITEM) {
// It must report all focus events on menu and list items
gLastFocusedNode = focusNode;
NS_ADDREF(gLastFocusedNode);
NS_ASSERTION(aAccessible, "Attempted to fire focus event for no accessible");
PRUint32 role = ROLE_NOTHING;
aAccessible->GetFinalRole(&role);
// Fire focus if it changes, but always fire focus events for menu items
PRBool fireFocus = gLastFocusedNode != aNode || (role == ROLE_MENUITEM);
NS_IF_RELEASE(gLastFocusedNode);
gLastFocusedNode = aNode;
NS_IF_ADDREF(gLastFocusedNode);
nsCOMPtr<nsPIAccessible> privateAccessible =
do_QueryInterface(aAccessible);
privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS,
aAccessible, nsnull);
if (mCaretAccessible)
mCaretAccessible->AttachNewSelectionListener(aNode);
// Special DHTML handling
PRBool isHTML; // If it is HTML we will check extra DHTML accesibility logic
nsCOMPtr<nsIContent> content(do_QueryInterface(aNode));
if (content) {
isHTML = content->IsContentOfType(nsIContent::eHTML);
}
else {
nsCOMPtr<nsIHTMLDocument> htmlDoc(do_QueryInterface(aNode));
isHTML = (htmlDoc != nsnull);
}
if (isHTML) {
FireDHTMLFocusRelatedEvents(aAccessible, role);
}
}
void nsRootAccessible::FireDHTMLFocusRelatedEvents(nsIAccessible *aAccessible, PRUint32 aRole)
{
// Rule set 1: special menu events
// Use focus events on DHTML menuitems to indicate when to fire menustart and
// menuend for menubars, as well as menupopupstart and menupopupend for popups
// How menupopupstart/menupopupend events are computed for firing:
// We keep track of the last popup the user was in mMenuAccessible.
// Store null there if the last thing that was focused was not a menuitem.
// If there's a menuitem focus it checks to see if you're still in that menu.
// If the new menu != mMenuAccessible, then a menupopupstart is fired
// Once something else besides a menuitem is focused, menupopupend is fired,.
// the menustart/menuend events are for a menubar.
// How menustart/menuend events (for menubars) are computed for firing:
// Starting from mMenuAccessible, walk up from its chain of menupopup parents
// until we're no longer in a menupopup. If that ancestor is a menubar, then fire
// a menustart or menuend event, depending on whether we're now focusing a menuitem
// or something else.
PRUint32 containerRole;
nsCOMPtr<nsPIAccessible> privateAccessible;
if (aRole == ROLE_MENUITEM) {
nsCOMPtr<nsIAccessible> parent;
aAccessible->GetParent(getter_AddRefs(parent));
parent->GetFinalRole(&containerRole);
// We must synthesize a menupopupstart event for DHTML when a menu item gets focused
// This could be a DHTML menu in which case there will be no DOMMenuActive event to help do this
PRUint32 event = 0;
if (containerRole == ROLE_MENUPOPUP) {
event = nsIAccessibleEvent::EVENT_MENUPOPUPSTART;
}
else if (containerRole == ROLE_MENUBAR) {
event = nsIAccessibleEvent::EVENT_MENUSTART;
}
if (event && mMenuAccessible != parent) {
mMenuAccessible = parent;
privateAccessible = do_QueryInterface(mMenuAccessible);
privateAccessible->FireToolkitEvent(event, mMenuAccessible, nsnull);
}
}
else if (mMenuAccessible) {
// We must synthesize a menupopupend event when a menu was focused and
// apparently loses focus (something else gets focus)
privateAccessible = do_QueryInterface(mMenuAccessible);
mMenuAccessible->GetFinalRole(&containerRole);
if (containerRole == ROLE_MENUPOPUP) {
privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUPEND,
mMenuAccessible, nsnull);
while (containerRole == ROLE_MENUPOPUP) {
nsIAccessible *current = mMenuAccessible;
current->GetParent(getter_AddRefs(mMenuAccessible));
if (!mMenuAccessible) {
break;
}
mMenuAccessible->GetRole(&containerRole);
}
// Will fire EVENT_MENUEND as well if parent of menu is menubar
if (containerRole != ROLE_MENUBAR) {
mMenuAccessible = nsnull;
}
privateAccessible = do_QueryInterface(mMenuAccessible);
}
if (mMenuAccessible) {
NS_ASSERTION(SameCOMIdentity(privateAccessible, mMenuAccessible),
"privateAccessible should be from same accessible instance as mMenuAccessible");
privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUEND,
mMenuAccessible, nsnull);
mMenuAccessible = nsnull;
}
}
// Rule set 2: selection events that mirror focus events
// Mirror selection events to focus, but only for widgets that are selectable
// but not a descendent of a multi-selectable widget
PRUint32 state;
aAccessible->GetFinalState(&state);
PRBool isMultiSelectOn = PR_TRUE;
if (state & STATE_SELECTABLE) {
nsCOMPtr<nsIAccessible> container = aAccessible;
while (0 == (state & STATE_MULTISELECTABLE)) {
nsIAccessible *current = container;
current->GetParent(getter_AddRefs(container));
if (!container || (NS_SUCCEEDED(container->GetFinalRole(&containerRole)) &&
containerRole == ROLE_PANE)) {
isMultiSelectOn = PR_FALSE;
break;
}
container->GetFinalState(&state);
}
if (!isMultiSelectOn) {
privateAccessible = do_QueryInterface(aAccessible);
privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION,
aAccessible, nsnull);
}
privateFocusAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS, focusAccessible, nsnull);
if (mCaretAccessible)
mCaretAccessible->AttachNewSelectionListener(focusNode);
}
}
@ -720,6 +836,7 @@ NS_IMETHODIMP nsRootAccessible::Shutdown()
if (!mWeakShell) {
return NS_OK; // Already shutdown
}
mMenuAccessible = nsnull;
mCaretAccessible = nsnull;
mAccService = nsnull;

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

@ -107,9 +107,11 @@ class nsRootAccessible : public nsDocAccessibleWrap,
nsresult RemoveEventListeners();
static void GetTargetNode(nsIDOMEvent *aEvent, nsIDOMNode **aTargetNode);
void FireAccessibleFocusEvent(nsIAccessible *focusAccessible, nsIDOMNode *focusNode);
void FireDHTMLFocusRelatedEvents(nsIAccessible *aFocusAccessible, PRUint32 aRole);
void GetChromeEventHandler(nsIDOMEventTarget **aChromeTarget);
nsCOMPtr<nsIAccessibilityService> mAccService;
nsCOMPtr<nsIAccessibleCaret> mCaretAccessible;
nsCOMPtr<nsIAccessible> mMenuAccessible;
};
#endif