Bug 368191. Implement ARIA activedescendant property. r=david.bolter

git-svn-id: svn://10.0.0.236/trunk@221715 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
aaronleventhal%moonset.net
2007-03-10 23:11:04 +00:00
parent 23eda96f7b
commit 3b792cb35c
6 changed files with 103 additions and 50 deletions

View File

@@ -64,6 +64,7 @@
#include "nsIStringBundle.h"
#include "nsITimer.h"
#include "nsRootAccessible.h"
#include "nsIFocusController.h"
/* For documentation of the accessibility architecture,
* see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
@@ -664,4 +665,37 @@ void nsAccessNode::ClearCache(nsInterfaceHashtable<nsVoidHashKey, nsIAccessNode>
aCache.Enumerate(ClearCacheEntry, nsnull);
}
already_AddRefed<nsIDOMNode> nsAccessNode::GetCurrentFocus()
{
nsCOMPtr<nsIPresShell> shell = GetPresShellFor(mDOMNode);
NS_ENSURE_TRUE(shell, nsnull);
nsCOMPtr<nsIDocument> doc = shell->GetDocument();
NS_ENSURE_TRUE(doc, nsnull);
nsCOMPtr<nsPIDOMWindow> privateDOMWindow(do_QueryInterface(doc->GetWindow()));
if (!privateDOMWindow) {
return nsnull;
}
nsIFocusController *focusController = privateDOMWindow->GetRootFocusController();
if (!focusController) {
return nsnull;
}
nsCOMPtr<nsIDOMElement> focusedElement;
focusController->GetFocusedElement(getter_AddRefs(focusedElement));
if (!focusedElement) {
return nsnull;
}
nsIDOMNode *focusedNode;
focusedElement->QueryInterface(NS_GET_IID(nsIDOMNode), (void**)&focusedNode);
if (!focusedNode) {
// Document itself may have focus
nsCOMPtr<nsIDOMWindowInternal> focusedWinInternal;
focusController->GetFocusedWindow(getter_AddRefs(focusedWinInternal));
if (focusedWinInternal) {
nsCOMPtr<nsIDOMDocument> focusedDOMDocument;
focusedWinInternal->GetDocument(getter_AddRefs(focusedDOMDocument));
focusedDOMDocument->QueryInterface(NS_GET_IID(nsIDOMNode), (void**)&focusedNode);
}
}
return focusedNode;
}

View File

@@ -149,6 +149,7 @@ class nsAccessNode: public nsIAccessNode, public nsPIAccessNode
static nsIDOMNode *gLastFocusedNode;
static nsIAccessibilityService* GetAccService();
already_AddRefed<nsIDOMNode> GetCurrentFocus();
protected:
nsresult MakeAccessNode(nsIDOMNode *aNode, nsIAccessNode **aAccessNode);

View File

@@ -164,6 +164,7 @@ ACCESSIBILITY_ATOM(type, "type")
ACCESSIBILITY_ATOM(value, "value")
// ARIA (DHTML accessibility) attributes
ACCESSIBILITY_ATOM(activedescendant, "activedescendant")
ACCESSIBILITY_ATOM(checked, "checked")
ACCESSIBILITY_ATOM(droppable, "droppable")
ACCESSIBILITY_ATOM(expanded, "expanded")

View File

@@ -36,7 +36,7 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nsDocAccessible.h"
#include "nsRootAccessible.h"
#include "nsAccessibilityAtoms.h"
#include "nsAccessibleEventData.h"
#include "nsIAccessibilityService.h"
@@ -945,20 +945,35 @@ nsDocAccessible::AttributeChanged(nsIDocument *aDocument, nsIContent* aContent,
}
}
else if (aNameSpaceID == kNameSpaceID_WAIProperties) {
// DHTML accessibility attributes
if (!HasRoleAttribute(aContent)) {
// We don't care about DHTML state changes unless there is
// a DHTML role set for the element
// ARIA attributes
if (aAttribute == nsAccessibilityAtoms::disabled ||
aAttribute == nsAccessibilityAtoms::required ||
aAttribute == nsAccessibilityAtoms::invalid) {
// Universal boolean properties that don't require a role
eventType = nsIAccessibleEvent::EVENT_STATE_CHANGE;
}
else if (aAttribute == nsAccessibilityAtoms::activedescendant) {
// The activedescendant universal property redirects accessible focus events
// to the element with the id that activedescendant points to
nsCOMPtr<nsIDOMNode> currentFocus = GetCurrentFocus();
if (SameCOMIdentity(currentFocus, aContent)) {
nsRefPtr<nsRootAccessible> rootAcc = GetRootAccessible();
if (rootAcc) {
rootAcc->FireAccessibleFocusEvent(nsnull, currentFocus, nsnull, PR_TRUE);
}
}
return;
}
else if (!HasRoleAttribute(aContent)) {
// We don't care about these other ARIA attribute changes unless there is
// an ARIA role set for the element
// XXX we should check the role map to see if the changed
// property is relevant for that particular role
return;
}
if (aAttribute == nsAccessibilityAtoms::checked ||
aAttribute == nsAccessibilityAtoms::expanded) {
eventType = nsIAccessibleEvent::EVENT_STATE_CHANGE;
}
else if (aAttribute == nsAccessibilityAtoms::readonly ||
aAttribute == nsAccessibilityAtoms::disabled ||
aAttribute == nsAccessibilityAtoms::required ||
aAttribute == nsAccessibilityAtoms::invalid) {
aAttribute == nsAccessibilityAtoms::expanded ||
aAttribute == nsAccessibilityAtoms::readonly) {
eventType = nsIAccessibleEvent::EVENT_STATE_CHANGE;
}
else if (aAttribute == nsAccessibilityAtoms::valuenow) {

View File

@@ -443,8 +443,6 @@ void nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible,
nsIDOMEvent *aFocusEvent,
PRBool aForceEvent)
{
NS_ASSERTION(aAccessible, "Attempted to fire focus event for no accessible");
if (mCaretAccessible) {
nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(aFocusEvent));
if (nsevent) {
@@ -465,23 +463,51 @@ void nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible,
}
}
// Check for aaa:activedescendant, which changes which element has focus
nsCOMPtr<nsIDOMNode> finalFocusNode = aNode;
nsCOMPtr<nsIAccessible> finalFocusAccessible = aAccessible;
nsCOMPtr<nsIContent> finalFocusContent = do_QueryInterface(aNode);
if (finalFocusContent) {
nsAutoString id;
if (finalFocusContent->GetAttr(kNameSpaceID_WAIProperties, nsAccessibilityAtoms::activedescendant, id)) {
nsCOMPtr<nsIDOMDocument> domDoc;
aNode->GetOwnerDocument(getter_AddRefs(domDoc));
if (!domDoc) {
return;
}
nsCOMPtr<nsIDOMElement> relatedEl;
domDoc->GetElementById(id, getter_AddRefs(relatedEl));
finalFocusContent = do_QueryInterface(relatedEl);
if (!finalFocusContent) {
return;
}
GetAccService()->GetAccessibleFor(finalFocusNode, getter_AddRefs(finalFocusAccessible));
// XXX Deal with case where finalFocusNode is not in parent chain of original true focus (aNode).
// We need direction from ARIA spec -- should we just return because it's not valid?
// Or should we put focus on the original container node?
}
}
// Fire focus only if it changes, but always fire focus events when aForceEvent == PR_TRUE
if (gLastFocusedNode == aNode && !aForceEvent) {
if (gLastFocusedNode == finalFocusNode && !aForceEvent) {
return;
}
nsCOMPtr<nsPIAccessible> privateAccessible =
do_QueryInterface(aAccessible);
do_QueryInterface(finalFocusAccessible);
NS_ASSERTION(privateAccessible , "No nsPIAccessible for nsIAccessible");
if (!privateAccessible) {
return;
}
// Use focus events on DHTML menuitems to indicate when to fire menustart and menuend
// Special dynamic content handling
PRUint32 role = ROLE_NOTHING;
aAccessible->GetFinalRole(&role);
finalFocusAccessible->GetFinalRole(&role);
if (role == ROLE_MENUITEM) {
if (!mIsInDHTMLMenu) { // Entering menus
PRUint32 naturalRole; // The natural role is the role that this type of element normally has
aAccessible->GetRole(&naturalRole);
finalFocusAccessible->GetRole(&naturalRole);
if (role != naturalRole) { // Must be a DHTML menuitem
FireToolkitEvent(nsIAccessibleEvent::EVENT_MENUSTART, this, nsnull);
mIsInDHTMLMenu = ROLE_MENUITEM;
@@ -494,41 +520,16 @@ void nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible,
}
NS_IF_RELEASE(gLastFocusedNode);
gLastFocusedNode = aNode;
gLastFocusedNode = finalFocusNode;
NS_IF_ADDREF(gLastFocusedNode);
privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS,
aAccessible, nsnull);
finalFocusAccessible, nsnull);
}
void nsRootAccessible::FireCurrentFocusEvent()
{
nsCOMPtr<nsIDOMWindow> domWin;
GetWindow(getter_AddRefs(domWin));
nsCOMPtr<nsPIDOMWindow> privateDOMWindow(do_QueryInterface(domWin));
if (!privateDOMWindow) {
return;
}
nsIFocusController *focusController = privateDOMWindow->GetRootFocusController();
if (!focusController) {
return;
}
nsCOMPtr<nsIDOMElement> focusedElement;
focusController->GetFocusedElement(getter_AddRefs(focusedElement));
nsCOMPtr<nsIDOMNode> focusedNode(do_QueryInterface(focusedElement));
if (!focusedNode) {
// Document itself may have focus
nsCOMPtr<nsIDOMWindowInternal> focusedWinInternal;
focusController->GetFocusedWindow(getter_AddRefs(focusedWinInternal));
if (focusedWinInternal) {
nsCOMPtr<nsIDOMDocument> focusedDOMDocument;
focusedWinInternal->GetDocument(getter_AddRefs(focusedDOMDocument));
focusedNode = do_QueryInterface(focusedDOMDocument);
}
if (!focusedNode) {
return; // Could not get a focused document either
}
}
nsCOMPtr<nsIDOMNode> focusedNode = GetCurrentFocus();
// Simulate a focus event so that we can reuse code that fires focus for container children like treeitems
nsCOMPtr<nsIDOMDocumentEvent> docEvent = do_QueryInterface(mDocument);

View File

@@ -91,6 +91,11 @@ class nsRootAccessible : public nsDocAccessibleWrap,
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ROOTACCESSIBLE_IMPL_CID)
void FireAccessibleFocusEvent(nsIAccessible *focusAccessible,
nsIDOMNode *focusNode,
nsIDOMEvent *aFocusEvent,
PRBool aForceEvent = PR_FALSE);
private:
nsCOMPtr<nsITimer> mFireFocusTimer;
static void FireFocusCallback(nsITimer *aTimer, void *aClosure);
@@ -102,10 +107,6 @@ class nsRootAccessible : public nsDocAccessibleWrap,
nsIDOMNode* aTargetNode);
static void GetTargetNode(nsIDOMEvent *aEvent, nsIDOMNode **aTargetNode);
void TryFireEarlyLoadEvent(nsIDOMNode *aDocNode);
void FireAccessibleFocusEvent(nsIAccessible *focusAccessible,
nsIDOMNode *focusNode,
nsIDOMEvent *aFocusEvent,
PRBool aForceEvent = PR_FALSE);
void FireCurrentFocusEvent();
void GetChromeEventHandler(nsIDOMEventTarget **aChromeTarget);
#ifdef MOZ_XUL