зеркало из https://github.com/mozilla/pjs.git
Bug 280920. DHTML menu accessibility. See example in bug (URL field). r=pkwarren, sr=jst
This commit is contained in:
Родитель
cd93d8a8df
Коммит
b5a42a7a1b
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче