Fix for bug 374790 - clean up accessible event firing, r=aaronlev, ginn.chen

This commit is contained in:
surkov.alexander@gmail.com 2007-04-09 21:37:52 -07:00
parent c3c85c9d19
commit 4fdfeeee01
2 changed files with 142 additions and 168 deletions

View File

@ -100,29 +100,75 @@ NS_IMETHODIMP nsRootAccessibleWrap::GetParent(nsIAccessible ** aParent)
nsresult nsRootAccessibleWrap::HandleEventWithTarget(nsIDOMEvent *aEvent,
nsIDOMNode *aTargetNode)
{
// first let the cross-platform code dispatch any events, before
// we start doing platform-specific things
nsRootAccessible::HandleEventWithTarget(aEvent, aTargetNode);
nsAutoString eventType;
aEvent->GetType(eventType);
nsAutoString localName;
aTargetNode->GetLocalName(localName);
if (eventType.LowerCaseEqualsLiteral("pagehide")) {
// nsRootAccessible::HandleEventWithTarget() has destoryed the accessible object
// we don't want to create it again
return NS_OK;
if (eventType.EqualsLiteral("pagehide")) {
nsRootAccessible::HandleEventWithTarget(aEvent, aTargetNode);
return NS_OK;
}
nsCOMPtr<nsIAccessible> accessible;
nsCOMPtr<nsIAccessibilityService> accService = GetAccService();
accService->GetAccessibleFor(aTargetNode, getter_AddRefs(accessible));
if (!accessible)
return NS_OK;
return NS_OK;
if (eventType.EqualsLiteral("popupshown")) {
nsRootAccessible::HandleEventWithTarget(aEvent, aTargetNode);
nsCOMPtr<nsIContent> content(do_QueryInterface(aTargetNode));
// 1) Don't fire focus events for tooltips, that wouldn't make any sense.
// 2) Don't fire focus events for autocomplete popups, because they
// come up automatically while the user is typing, and setting focus
// there would interrupt the user.
// If the AT wants to know about these popups it can track the ATK state
// change event we fire for ATK_STATE_INVISIBLE on the popup. This is
// fired as a result of the nsIAccessibleEvent::EVENT_MENUPOPUP_START we
// fire in the nsRootAccessible event handling for all popups.
if (!content->NodeInfo()->Equals(nsAccessibilityAtoms::tooltip,
kNameSpaceID_XUL) &&
!content->AttrValueIs(kNameSpaceID_None, nsAccessibilityAtoms::type,
NS_LITERAL_STRING("autocomplete"), eIgnoreCase)) {
FireAccessibleFocusEvent(accessible, aTargetNode, aEvent);
}
return NS_OK;
}
StateChange stateData;
nsCOMPtr<nsPIAccessible> privAcc(do_QueryInterface(accessible));
if (eventType.EqualsLiteral("CheckboxStateChange") || // it's a XUL <checkbox>
eventType.EqualsLiteral("RadioStateChange")) { // it's a XUL <radio>
stateData.state = State(accessible);
// prefPane tab is implemented as list items in A11y, so we need to
// check nsIAccessibleStates::STATE_SELECTED also
stateData.enable = (stateData.state &
(nsIAccessibleStates::STATE_CHECKED |
nsIAccessibleStates::STATE_SELECTED)) != 0;
stateData.state = nsIAccessibleStates::STATE_CHECKED;
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE,
accessible, &stateData);
// only fire focus event for checked radio
if (eventType.EqualsLiteral("RadioStateChange") &&
stateData.enable) {
FireAccessibleFocusEvent(accessible, aTargetNode, aEvent);
}
return NS_OK;
}
if (eventType.EqualsLiteral("OpenStateChange")) {
stateData.state = State(accessible); // collapsed/expanded changed
stateData.enable = (stateData.state & nsIAccessibleStates::STATE_EXPANDED) != 0;
stateData.state = nsIAccessibleStates::STATE_EXPANDED;
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE,
accessible, &stateData);
return NS_OK;
}
#ifdef MOZ_XUL
// If it's a tree element, need the currently selected item
nsCOMPtr<nsIAccessible> treeItemAccessible;
@ -147,9 +193,8 @@ nsresult nsRootAccessibleWrap::HandleEventWithTarget(nsIDOMEvent *aEvent,
}
}
#endif
StateChange stateData;
if (eventType.LowerCaseEqualsLiteral("focus")) {
if (eventType.EqualsLiteral("focus")) {
#ifdef MOZ_XUL
if (treeItemAccessible) { // use focused treeitem
privAcc = do_QueryInterface(treeItemAccessible);
@ -190,7 +235,7 @@ nsresult nsRootAccessibleWrap::HandleEventWithTarget(nsIDOMEvent *aEvent,
&stateData);
}
}
else if (eventType.LowerCaseEqualsLiteral("select")) {
else if (eventType.EqualsLiteral("select")) {
#ifdef MOZ_XUL
if (treeItemAccessible) { // it's a XUL <tree>
// use EVENT_FOCUS instead of EVENT_SELECTION_CHANGED
@ -204,65 +249,10 @@ nsresult nsRootAccessibleWrap::HandleEventWithTarget(nsIDOMEvent *aEvent,
// make GOK refresh "UI-Grab" window
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_REORDER, accessible, nsnull);
}
} else {
nsRootAccessible::HandleEventWithTarget(aEvent, aTargetNode);
}
else if (eventType.LowerCaseEqualsLiteral("checkboxstatechange") || // it's a XUL <checkbox>
eventType.LowerCaseEqualsLiteral("radiostatechange")) { // it's a XUL <radio>
stateData.state = State(accessible);
// prefPane tab is implemented as list items in A11y, so we need to
// check nsIAccessibleStates::STATE_SELECTED also
stateData.enable = (stateData.state &
(nsIAccessibleStates::STATE_CHECKED |
nsIAccessibleStates::STATE_SELECTED)) != 0;
stateData.state = nsIAccessibleStates::STATE_CHECKED;
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, accessible, &stateData);
// only fire focus event for checked radio
if (eventType.LowerCaseEqualsLiteral("radiostatechange") &&
stateData.enable) {
FireAccessibleFocusEvent(accessible, aTargetNode, aEvent);
}
}
else if (eventType.LowerCaseEqualsLiteral("openstatechange")) { // collapsed/expanded changed
stateData.state = State(accessible);
stateData.enable = (stateData.state & nsIAccessibleStates::STATE_EXPANDED) != 0;
stateData.state = nsIAccessibleStates::STATE_EXPANDED;
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, accessible, &stateData);
}
else if (eventType.LowerCaseEqualsLiteral("popuphiding")) {
// If accessible focus was inside popup that closes,
// then restore it to true current focus.
// This is the case when we've been getting DOMMenuItemActive events
// inside of a combo box that closes. The real focus is on the combo box.
if (!gLastFocusedNode) {
return NS_OK;
}
nsCOMPtr<nsIDOMNode> parentOfFocus;
gLastFocusedNode->GetParentNode(getter_AddRefs(parentOfFocus));
if (parentOfFocus != aTargetNode) {
return NS_OK;
}
// Focus was inside of popup that's being hidden
FireCurrentFocusEvent();
}
else if (eventType.LowerCaseEqualsLiteral("popupshown")) {
#ifdef MOZ_XUL
nsCOMPtr<nsIContent> content(do_QueryInterface(aTargetNode));
if (content->NodeInfo()->Equals(nsAccessibilityAtoms::tooltip, kNameSpaceID_XUL) ||
content->AttrValueIs(kNameSpaceID_None, nsAccessibilityAtoms::type,
NS_LITERAL_STRING("autocomplete"), eIgnoreCase)) {
// 1) Don't fire focus events for tooltips, that wouldn't make any sense.
// 2) Don't fire focus events for autocomplete popups, because they come up
// automatically while the user is typing, and setting focus there would
// interrupt the user.
// ------------------------------------------------------------------------
// If the AT wants to know about these popups it can track the ATK state change
// event we fire for ATK_STATE_INVISIBLE on the popup.
// This is fired as a result of the nsIAccessibleEvent::EVENT_MENUPOPUP_START
// we fire in the nsRootAccessible event handling for all popups.
return NS_OK;
}
#endif
FireAccessibleFocusEvent(accessible, aTargetNode, aEvent);
}
return NS_OK;
}

View File

@ -568,7 +568,7 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
aTargetNode->GetLocalName(localName);
#ifdef DEBUG_A11Y
// Very useful for debugging, please leave this here.
if (eventType.LowerCaseEqualsLiteral("alertactive")) {
if (eventType.EqualsLiteral("AlertActive")) {
printf("\ndebugging %s events for %s", NS_ConvertUTF16toUTF8(eventType).get(), NS_ConvertUTF16toUTF8(localName).get());
}
if (localName.LowerCaseEqualsLiteral("textbox")) {
@ -584,7 +584,7 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
nsIAccessibilityService *accService = GetAccService();
NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
if (eventType.LowerCaseEqualsLiteral("pagehide")) {
if (eventType.EqualsLiteral("pagehide")) {
// pagehide event can be fired under several conditions, such as HTML
// document going away, closing a window/dialog, and wizard page changing.
// We only destroy the accessible object when it's a document accessible,
@ -601,7 +601,7 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
return NS_OK;
}
if (eventType.LowerCaseEqualsLiteral("popupshown")) {
if (eventType.EqualsLiteral("popupshown")) {
// Fire menupopupstart events after a delay so that ancestor views
// are visible, otherwise an accessible cannot be created for the
// popup and the accessibility toolkit event can't be fired.
@ -620,7 +620,7 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
}
}
if (eventType.LowerCaseEqualsLiteral("domcontentloaded")) {
if (eventType.EqualsLiteral("DOMContentLoaded")) {
// Don't create the doc accessible until load scripts have a chance to set
// role attribute for <body> or <html> element, because the value of
// role attribute will be cached when the doc accessible is Init()'d
@ -642,9 +642,9 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
if (!accessible)
return NS_OK;
nsCOMPtr<nsIAccessible> treeItemAccessible;
#ifdef MOZ_XUL
// If it's a tree element, need the currently selected item
nsCOMPtr<nsIAccessible> treeItemAccessible;
if (localName.EqualsLiteral("tree")) {
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
do_QueryInterface(aTargetNode);
@ -669,83 +669,68 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
nsCOMPtr<nsPIAccessible> privAcc(do_QueryInterface(accessible));
#ifndef MOZ_ACCESSIBILITY_ATK
#ifdef MOZ_XUL
// tree event
if (eventType.LowerCaseEqualsLiteral("checkboxstatechange") ||
eventType.LowerCaseEqualsLiteral("openstatechange")) {
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE,
accessible, nsnull);
return NS_OK;
}
else if (treeItemAccessible) {
if (eventType.LowerCaseEqualsLiteral("focus")) {
FireAccessibleFocusEvent(accessible, aTargetNode, aEvent); // Tree has focus
}
else if (eventType.LowerCaseEqualsLiteral("dommenuitemactive")) {
FireAccessibleFocusEvent(treeItemAccessible, aTargetNode, aEvent, PR_TRUE);
}
else if (eventType.LowerCaseEqualsLiteral("namechange")) {
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE,
accessible, nsnull);
}
else if (eventType.LowerCaseEqualsLiteral("select")) {
// If multiselect tree, we should fire selectionadd or selection removed
if (gLastFocusedNode == aTargetNode) {
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel =
do_QueryInterface(aTargetNode);
nsAutoString selType;
multiSel->GetSelType(selType);
if (selType.IsEmpty() || !selType.EqualsLiteral("single")) {
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
accessible, nsnull);
// XXX We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE
// for each tree item. Perhaps each tree item will need to
// cache its selection state and fire an event after a DOM "select"
// event when that state changes.
// nsXULTreeAccessible::UpdateTreeSelection();
}
else {
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION,
treeItemAccessible, nsnull);
}
}
}
return NS_OK;
if (eventType.EqualsLiteral("CheckboxStateChange") ||
eventType.EqualsLiteral("OpenStateChange")) {
return privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE,
accessible, nsnull);
}
else
else if (treeItemAccessible && eventType.EqualsLiteral("select")) {
// If multiselect tree, we should fire selectionadd or selection removed
if (gLastFocusedNode == aTargetNode) {
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel =
do_QueryInterface(aTargetNode);
nsAutoString selType;
multiSel->GetSelType(selType);
if (selType.IsEmpty() || !selType.EqualsLiteral("single")) {
// XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE
// for each tree item. Perhaps each tree item will need to cache its
// selection state and fire an event after a DOM "select" event when
// that state changes. nsXULTreeAccessible::UpdateTreeSelection();
return privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
accessible, nsnull);
}
return privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION,
treeItemAccessible, nsnull);
}
}
else
#endif
if (eventType.LowerCaseEqualsLiteral("focus")) {
nsCOMPtr<nsIDOMXULSelectControlElement> selectControl =
do_QueryInterface(aTargetNode);
if (eventType.EqualsLiteral("focus")) {
// Keep a reference to the target node. We might want to change
// it to the individual radio button or selected item, and send
// the focus event to that.
nsCOMPtr<nsIDOMNode> focusedItem(aTargetNode);
if (selectControl) {
nsCOMPtr<nsIDOMXULMenuListElement> menuList =
if (!treeItemAccessible) {
nsCOMPtr<nsIDOMXULSelectControlElement> selectControl =
do_QueryInterface(aTargetNode);
if (!menuList) {
// Don't do this for menu lists, the items only get focused
// when the list is open, based on DOMMenuitemActive events
nsCOMPtr<nsIDOMXULSelectControlItemElement> selectedItem;
selectControl->GetSelectedItem(getter_AddRefs(selectedItem));
if (selectedItem) {
focusedItem = do_QueryInterface(selectedItem);
if (selectControl) {
nsCOMPtr<nsIDOMXULMenuListElement> menuList =
do_QueryInterface(aTargetNode);
if (!menuList) {
// Don't do this for menu lists, the items only get focused
// when the list is open, based on DOMMenuitemActive events
nsCOMPtr<nsIDOMXULSelectControlItemElement> selectedItem;
selectControl->GetSelectedItem(getter_AddRefs(selectedItem));
if (selectedItem)
focusedItem = do_QueryInterface(selectedItem);
if (!focusedItem)
return NS_OK;
accService->GetAccessibleInShell(focusedItem, eventShell,
getter_AddRefs(accessible));
if (!accessible)
return NS_OK;
}
if (!focusedItem)
return NS_OK;
accService->GetAccessibleInShell(focusedItem, eventShell,
getter_AddRefs(accessible));
if (!accessible)
return NS_OK;
}
}
FireAccessibleFocusEvent(accessible, focusedItem, aEvent);
}
else if (eventType.LowerCaseEqualsLiteral("namechange")) {
else if (eventType.EqualsLiteral("NameChange")) {
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE,
accessible, nsnull);
}
@ -753,7 +738,7 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_ALERT,
accessible, nsnull);
}
else if (eventType.LowerCaseEqualsLiteral("radiostatechange") ) {
else if (eventType.EqualsLiteral("RadioStateChange")) {
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE,
accessible, nsnull);
PRUint32 finalState = State(accessible);
@ -762,7 +747,7 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
FireAccessibleFocusEvent(accessible, aTargetNode, aEvent);
}
}
else if (eventType.LowerCaseEqualsLiteral("popuphiding")) {
else if (eventType.EqualsLiteral("popuphiding")) {
// If accessible focus was inside popup that closes,
// then restore it to true current focus.
// This is the case when we've been getting DOMMenuItemActive events
@ -785,38 +770,37 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
accessible, nsnull);
}
}
else
#endif
if (eventType.LowerCaseEqualsLiteral("dommenuitemactive")) {
nsCOMPtr<nsIAccessible> containerAccessible;
accessible->GetParent(getter_AddRefs(containerAccessible));
NS_ENSURE_TRUE(containerAccessible, NS_OK);
if (Role(containerAccessible) == nsIAccessibleRole::ROLE_MENUBAR) {
nsCOMPtr<nsPIAccessNode> menuBarAccessNode(do_QueryInterface(containerAccessible));
NS_ENSURE_TRUE(menuBarAccessNode, NS_ERROR_FAILURE);
nsCOMPtr<nsIMenuParent> menuParent = do_QueryInterface(menuBarAccessNode->GetFrame());
NS_ENSURE_TRUE(menuParent, NS_ERROR_FAILURE);
PRBool isActive;
menuParent->GetIsActive(isActive);
if (!isActive) {
// It is a top level menuitem
// Only fire focus event the menu bar is active
return NS_OK;
else if (eventType.EqualsLiteral("DOMMenuItemActive")) {
if (!treeItemAccessible) {
nsCOMPtr<nsIAccessible> containerAccessible;
accessible->GetParent(getter_AddRefs(containerAccessible));
NS_ENSURE_TRUE(containerAccessible, NS_OK);
if (Role(containerAccessible) == nsIAccessibleRole::ROLE_MENUBAR) {
nsCOMPtr<nsPIAccessNode> menuBarAccessNode(do_QueryInterface(containerAccessible));
NS_ENSURE_TRUE(menuBarAccessNode, NS_ERROR_FAILURE);
nsCOMPtr<nsIMenuParent> menuParent = do_QueryInterface(menuBarAccessNode->GetFrame());
NS_ENSURE_TRUE(menuParent, NS_ERROR_FAILURE);
PRBool isActive;
menuParent->GetIsActive(isActive);
if (!isActive) {
// It is a top level menuitem. Only fire focus event the menu bar
// is active.
return NS_OK;
}
} else {
// It is not top level menuitem
// Only fire focus event if it is not inside collapsed popup
if (State(containerAccessible) & nsIAccessibleStates::STATE_COLLAPSED)
return NS_OK;
}
}
else {
// It is not top level menuitem
// Only fire focus event if it is not inside collapsed popup
if (State(containerAccessible) & nsIAccessibleStates::STATE_COLLAPSED)
return NS_OK;
}
FireAccessibleFocusEvent(accessible, aTargetNode, aEvent, PR_TRUE);
}
else if (eventType.LowerCaseEqualsLiteral("dommenubaractive")) {
else if (eventType.EqualsLiteral("DOMMenuBarActive")) {
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENU_START,
accessible, nsnull);
}
else if (eventType.LowerCaseEqualsLiteral("dommenubarinactive")) {
else if (eventType.EqualsLiteral("DOMMenuBarInactive")) {
privAcc->FireToolkitEvent(nsIAccessibleEvent::EVENT_MENU_END,
accessible, nsnull);
FireCurrentFocusEvent();