diff --git a/accessible/src/base/nsAccessibilityAtomList.h b/accessible/src/base/nsAccessibilityAtomList.h index ab835813474..c31fcd93fb4 100755 --- a/accessible/src/base/nsAccessibilityAtomList.h +++ b/accessible/src/base/nsAccessibilityAtomList.h @@ -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 diff --git a/accessible/src/base/nsAccessible.cpp b/accessible/src/base/nsAccessible.cpp index 9576715d065..0631799b767 100644 --- a/accessible/src/base/nsAccessible.cpp +++ b/accessible/src/base/nsAccessible.cpp @@ -130,12 +130,23 @@ nsAccessible::~nsAccessible() { } -NS_IMETHODIMP nsAccessible::GetName(nsAString& _retval) +NS_IMETHODIMP nsAccessible::GetName(nsAString& aName) { - nsCOMPtr elt(do_QueryInterface(mDOMNode)); - if (elt) - return elt->GetAttribute(NS_LITERAL_STRING("title"), _retval); - return NS_ERROR_FAILURE; + nsCOMPtr 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 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 diff --git a/accessible/src/base/nsAccessible.h b/accessible/src/base/nsAccessible.h index c50281ec4f2..1352e4c9cb5 100644 --- a/accessible/src/base/nsAccessible.h +++ b/accessible/src/base/nsAccessible.h @@ -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 diff --git a/accessible/src/base/nsRootAccessible.cpp b/accessible/src/base/nsRootAccessible.cpp index f24cab9c572..3bc1079c937 100644 --- a/accessible/src/base/nsRootAccessible.cpp +++ b/accessible/src/base/nsRootAccessible.cpp @@ -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 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 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 content(do_QueryInterface(aNode)); + if (content) { + isHTML = content->IsContentOfType(nsIContent::eHTML); + } + else { + nsCOMPtr 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 privateAccessible; + + if (aRole == ROLE_MENUITEM) { + nsCOMPtr 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 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; diff --git a/accessible/src/base/nsRootAccessible.h b/accessible/src/base/nsRootAccessible.h index 94f5be58406..871c912f6d9 100644 --- a/accessible/src/base/nsRootAccessible.h +++ b/accessible/src/base/nsRootAccessible.h @@ -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 mAccService; nsCOMPtr mCaretAccessible; + nsCOMPtr mMenuAccessible; }; #endif