Bug 378540. GetTextAtOffset() broken for word boundaries. r=surkov, a=dsicore

This commit is contained in:
aaronleventhal@moonset.net 2007-09-18 14:36:41 -07:00
parent bcd455df72
commit a4c907ce3a
10 changed files with 91 additions and 42 deletions

View File

@ -66,12 +66,9 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
{"application", nsIAccessibleRole::ROLE_APPLICATION, eNameLabelOrTitle, eNoValue, kNoReqStates, kEndEntry},
{"button", nsIAccessibleRole::ROLE_PUSHBUTTON, eNameOkFromChildren, eNoValue, kNoReqStates,
{"disabled", kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE},
{"pressed", kBoolState, nsIAccessibleStates::STATE_PRESSED}, kEndEntry},
{"pressed", kBoolState, nsIAccessibleStates::STATE_PRESSED},
{"pressed", "mixed", nsIAccessibleStates::STATE_MIXED}, kEndEntry},
{"checkbox", nsIAccessibleRole::ROLE_CHECKBUTTON, eNameOkFromChildren, eNoValue, nsIAccessibleStates::STATE_CHECKABLE,
{"disabled", kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED},
{"readonly", kBoolState, nsIAccessibleStates::STATE_READONLY}, kEndEntry},
{"checkboxtristate", nsIAccessibleRole::ROLE_CHECKBUTTON, eNameOkFromChildren, eNoValue, nsIAccessibleStates::STATE_CHECKABLE,
{"disabled", kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED},
{"checked", "mixed", nsIAccessibleStates::STATE_MIXED},
@ -116,6 +113,7 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
{"selected", kBoolState, nsIAccessibleStates::STATE_SELECTED | nsIAccessibleStates::STATE_SELECTABLE},
{"selected", "false", nsIAccessibleStates::STATE_SELECTABLE},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED | nsIAccessibleStates::STATE_CHECKABLE},
{"checked", "mixed", nsIAccessibleStates::STATE_MIXED | nsIAccessibleStates::STATE_CHECKABLE},
{"checked", "false", nsIAccessibleStates::STATE_CHECKABLE}, kEndEntry},
{"menu", nsIAccessibleRole::ROLE_MENUPOPUP, eNameLabelOrTitle, eNoValue, kNoReqStates,
{"disabled", kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE}, kEndEntry},
@ -124,11 +122,12 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
{"menuitem", nsIAccessibleRole::ROLE_MENUITEM, eNameOkFromChildren, eNoValue, kNoReqStates,
{"disabled", kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED | nsIAccessibleStates::STATE_CHECKABLE},
{"checked", "mixed", nsIAccessibleStates::STATE_MIXED},
{"checked", "mixed", nsIAccessibleStates::STATE_MIXED | nsIAccessibleStates::STATE_CHECKABLE},
{"checked", "false", nsIAccessibleStates::STATE_CHECKABLE}, kEndEntry},
{"menuitemcheckbox", nsIAccessibleRole::ROLE_MENUITEM, eNameOkFromChildren, eNoValue, nsIAccessibleStates::STATE_CHECKABLE,
{"disabled", kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED }, kEndEntry},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED },
{"checked", "mixed", nsIAccessibleStates::STATE_MIXED}, kEndEntry},
{"menuitemradio", nsIAccessibleRole::ROLE_MENUITEM, eNameOkFromChildren, eNoValue, nsIAccessibleStates::STATE_CHECKABLE,
{"disabled", kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED }, kEndEntry},
@ -137,6 +136,7 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
{"selected", kBoolState, nsIAccessibleStates::STATE_SELECTED | nsIAccessibleStates::STATE_SELECTABLE},
{"selected", "false", nsIAccessibleStates::STATE_SELECTABLE},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED | nsIAccessibleStates::STATE_CHECKABLE},
{"checked", "mixed", nsIAccessibleStates::STATE_MIXED | nsIAccessibleStates::STATE_CHECKABLE},
{"checked", "false", nsIAccessibleStates::STATE_CHECKABLE}, kEndEntry},
{"progressbar", nsIAccessibleRole::ROLE_PROGRESSBAR, eNameLabelOrTitle, eHasValueMinMax, nsIAccessibleStates::STATE_READONLY,
{"disabled", kBoolState, nsIAccessibleStates::STATE_UNAVAILABLE}, kEndEntry},
@ -193,7 +193,7 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] =
{"expanded", kBoolState, nsIAccessibleStates::STATE_EXPANDED},
{"expanded", "false", nsIAccessibleStates::STATE_COLLAPSED},
{"checked", kBoolState, nsIAccessibleStates::STATE_CHECKED | nsIAccessibleStates::STATE_CHECKABLE},
{"checked", "mixed", nsIAccessibleStates::STATE_MIXED},
{"checked", "mixed", nsIAccessibleStates::STATE_MIXED | nsIAccessibleStates::STATE_CHECKABLE},
{"checked", "false", nsIAccessibleStates::STATE_CHECKABLE},},
{nsnull, nsIAccessibleRole::ROLE_NOTHING, eNameLabelOrTitle, eNoValue, kNoReqStates, kEndEntry} // Last item
};

View File

@ -164,7 +164,6 @@ ACCESSIBILITY_ATOM(editable, "editable")
ACCESSIBILITY_ATOM(_for, "for")
ACCESSIBILITY_ATOM(hidden, "hidden") // XUL tree columns
ACCESSIBILITY_ATOM(href, "href")
ACCESSIBILITY_ATOM(id, "id")
ACCESSIBILITY_ATOM(increment, "increment") // XUL
ACCESSIBILITY_ATOM(lang, "lang")
ACCESSIBILITY_ATOM(maxpos, "maxpos") // XUL

View File

@ -1423,7 +1423,7 @@ NS_IMETHODIMP nsAccessibilityService::GetAccessible(nsIDOMNode *aNode,
// We don't do this for <body>, <html>, <window>, <dialog> etc. which
// correspond to the doc accessible and will be created in any case
if (!newAcc && content->Tag() != nsAccessibilityAtoms::body && content->GetParent() &&
(content->IsFocusable() ||
(content->IsFocusable() || content->GetID() ||
(isHTML && nsAccUtils::HasListener(content, NS_LITERAL_STRING("click"))) ||
content->HasAttr(kNameSpaceID_WAIProperties, nsAccessibilityAtoms::describedby) ||
content->HasAttr(kNameSpaceID_WAIProperties, nsAccessibilityAtoms::labelledby) ||

View File

@ -402,3 +402,9 @@ nsAccUtils::GetDocShellTreeItemFor(nsIDOMNode *aNode)
return docShellTreeItem;
}
PRBool
nsAccUtils::GetID(nsIContent *aContent, nsAString& aID)
{
nsIAtom *idAttribute = aContent->GetIDAttributeName();
return idAttribute ? aContent->GetAttr(kNameSpaceID_None, idAttribute, aID) : PR_FALSE;
}

View File

@ -180,6 +180,14 @@ public:
*/
static already_AddRefed<nsIDocShellTreeItem>
GetDocShellTreeItemFor(nsIDOMNode *aNode);
/**
* Get the ID for an element, in some types of XML this may not be the ID attribute
* @param aContent Node to get the ID for
* @param aID Where to put ID string
* @return PR_TRUE if there is an ID set for this node
*/
static PRBool GetID(nsIContent *aContent, nsAString& aID);
};
#endif

View File

@ -1630,11 +1630,10 @@ nsIContent* nsAccessible::GetHTMLLabelContent(nsIContent *aForNode)
// for="control_id" attribute. To save computing time, only
// look for those inside of a form element
nsAutoString forId;
aForNode->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::id, forId);
// Actually we'll be walking down the content this time, with a depth first search
if (forId.IsEmpty()) {
if (!nsAccUtils::GetID(aForNode, forId)) {
break;
}
// Actually we'll be walking down the content this time, with a depth first search
return FindDescendantPointingToID(&forId, walkUpContent,
nsAccessibilityAtoms::_for);
}
@ -1701,10 +1700,8 @@ nsAccessible::FindNeighbourPointingToNode(nsIContent *aForNode,
PRUint32 aAncestorLevelsToSearch)
{
nsCOMPtr<nsIContent> binding;
nsAutoString controlID;
aForNode->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::id, controlID);
if (controlID.IsEmpty()) {
if (!nsAccUtils::GetID(aForNode, controlID)) {
binding = aForNode->GetBindingParent();
if (binding == aForNode)
return nsnull;
@ -2063,12 +2060,10 @@ nsAccessible::GetAttributes(nsIPersistentProperties **aAttributes)
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContent> content = GetRoleContent(mDOMNode);
if (content) {
nsAutoString id;
nsAutoString id;
if (content && nsAccUtils::GetID(content, id)) {
nsAutoString oldValueUnused;
if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::id, id)) {
attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, oldValueUnused);
}
attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, oldValueUnused);
// XXX In the future we may need to expose the dynamic content role inheritance chain
// through this attribute
nsAutoString xmlRole;

View File

@ -77,6 +77,8 @@
// nsDocAccessible //
//=============================//
PRUint32 nsDocAccessible::gLastFocusedAccessiblesState = 0;
//-----------------------------------------------------
// construction
//-----------------------------------------------------
@ -918,6 +920,23 @@ void
nsDocAccessible::AttributeChanged(nsIDocument *aDocument, nsIContent* aContent,
PRInt32 aNameSpaceID, nsIAtom* aAttribute,
PRInt32 aModType, PRUint32 aStateMask)
{
AttributeChangedImpl(aContent, aNameSpaceID, aAttribute);
// If it was the focused node, cache the new state
nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(aContent);
if (targetNode == gLastFocusedNode) {
nsCOMPtr<nsIAccessible> focusedAccessible;
GetAccService()->GetAccessibleFor(targetNode, getter_AddRefs(focusedAccessible));
if (focusedAccessible) {
gLastFocusedAccessiblesState = State(focusedAccessible);
}
}
}
void
nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID, nsIAtom* aAttribute)
{
// Fire accessible event after short timer, because we need to wait for
// DOM attribute & resulting layout to actually change. Otherwise,
@ -1087,21 +1106,33 @@ nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
return;
}
if (aAttribute == nsAccessibilityAtoms::checked) {
if (aAttribute == nsAccessibilityAtoms::checked ||
aAttribute == nsAccessibilityAtoms::pressed) {
const PRUint32 kState = (aAttribute == nsAccessibilityAtoms::checked) ?
nsIAccessibleStates::STATE_CHECKED :
nsIAccessibleStates::STATE_PRESSED;
nsCOMPtr<nsIAccessibleStateChangeEvent> event =
new nsAccStateChangeEvent(targetNode,
nsIAccessibleStates::STATE_CHECKED,
PR_FALSE);
FireDelayedAccessibleEvent(event);
return;
}
if (aAttribute == nsAccessibilityAtoms::pressed) {
nsCOMPtr<nsIAccessibleStateChangeEvent> event =
new nsAccStateChangeEvent(targetNode,
nsIAccessibleStates::STATE_PRESSED,
PR_FALSE);
new nsAccStateChangeEvent(targetNode, kState, PR_FALSE);
FireDelayedAccessibleEvent(event);
if (targetNode == gLastFocusedNode) {
// State changes for MIXED state currently only supported for focused item, because
// otherwise we would need access to the old attribute value in this listener.
// This is because we don't know if the previous value of aaa:checked or aaa:pressed was "mixed"
// without caching that info.
nsCOMPtr<nsIAccessible> accessible;
event->GetAccessible(getter_AddRefs(accessible));
if (accessible) {
PRBool wasMixed = (gLastFocusedAccessiblesState & nsIAccessibleStates::STATE_MIXED) != 0;
PRBool isMixed = (State(accessible) & nsIAccessibleStates::STATE_MIXED) != 0;
if (wasMixed != isMixed) {
nsCOMPtr<nsIAccessibleStateChangeEvent> event =
new nsAccStateChangeEvent(targetNode,
nsIAccessibleStates::STATE_MIXED,
PR_FALSE, isMixed);
FireDelayedAccessibleEvent(event);
}
}
}
return;
}

View File

@ -149,7 +149,16 @@ class nsDocAccessible : public nsHyperTextAccessibleWrap,
static void ScrollTimerCallback(nsITimer *aTimer, void *aClosure);
/**
* Fires accessible events when ARIA attribute is chaned.
* Fires accessible events when attribute is changed.
*
* @param aContent - node that attribute is changed for
* @param aNameSpaceID - namespace of changed attribute
* @param aAttribute - changed attribute
*/
void AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID, nsIAtom* aAttribute);
/**
* Fires accessible events when ARIA attribute is changed.
*
* @param aContent - node that attribute is changed for
* @param aAttribute - changed attribute
@ -195,6 +204,7 @@ class nsDocAccessible : public nsHyperTextAccessibleWrap,
protected:
PRBool mIsAnchor;
PRBool mIsAnchorJumped;
static PRUint32 gLastFocusedAccessiblesState;
private:
static void DocLoadCallback(nsITimer *aTimer, void *aClosure);

View File

@ -481,6 +481,7 @@ PRBool nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible,
return PR_FALSE;
}
gLastFocusedAccessiblesState = State(finalFocusAccessible);
PRUint32 role = Role(finalFocusAccessible);
if (role == nsIAccessibleRole::ROLE_MENUITEM) {
if (!mCurrentARIAMenubar) { // Entering menus

View File

@ -866,6 +866,12 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
amount, eDirNext, needsStart);
NS_ENSURE_TRUE(endOffset >= 0, NS_ERROR_FAILURE);
if (finalEndOffset == aOffset) {
if (aType == eGetAt && amount == eSelectWord) {
// Fix word error for the first character in word: PeekOffset() will return the previous word when
// aOffset points to the first character of the word, but accessibility APIs want the current word
// that the first character is in
return GetTextHelper(eGetAfter, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
}
// This happens sometimes when current character at finalStartOffset
// is an embedded object character representing another hypertext, that
// the AT really needs to dig into separately
@ -873,13 +879,6 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
}
}
// Fix word error for the first character in word: PeekOffset() will return the previous word when
// aOffset points to the first character of the word, but accessibility APIs want the current word
// that the first character is in
if (aType == eGetAt && amount == eSelectWord && aOffset == endOffset) {
return GetTextHelper(eGetAfter, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
}
*aStartOffset = finalStartOffset;
*aEndOffset = finalEndOffset;