Bug 638684 - Accessible::GetFirstAvailableAccessible needs resuse the accessible tree walker, r=tbsaunde

This commit is contained in:
Alexander Surkov 2014-02-15 10:21:40 -05:00
Родитель 4296818d2c
Коммит 3d35730e1e
8 изменённых файлов: 114 добавлений и 66 удалений

Просмотреть файл

@ -9,7 +9,7 @@
#include "nsAccessibilityService.h"
#include "DocAccessible.h"
#include "nsINodeList.h"
#include "mozilla/dom/Element.h"
using namespace mozilla::a11y;
@ -39,9 +39,9 @@ struct WalkState
////////////////////////////////////////////////////////////////////////////////
TreeWalker::
TreeWalker(Accessible* aContext, nsIContent* aContent, bool aWalkCache) :
TreeWalker(Accessible* aContext, nsIContent* aContent, uint32_t aFlags) :
mDoc(aContext->Document()), mContext(aContext),
mWalkCache(aWalkCache), mState(nullptr)
mFlags(aFlags), mState(nullptr)
{
NS_ASSERTION(aContent, "No node for the accessible tree walker!");
@ -86,7 +86,8 @@ TreeWalker::NextChildInternal(bool aNoWalkUp)
mState->childIdx++;
bool isSubtreeHidden = false;
Accessible* accessible = mWalkCache ? mDoc->GetAccessible(childNode) :
Accessible* accessible = mFlags & eWalkCache ?
mDoc->GetAccessible(childNode) :
GetAccService()->GetOrCreateAccessible(childNode, mContext,
&isSubtreeHidden);
@ -95,9 +96,7 @@ TreeWalker::NextChildInternal(bool aNoWalkUp)
// Walk down into subtree to find accessibles.
if (!isSubtreeHidden) {
if (!PushState(childNode))
break;
PushState(childNode);
accessible = NextChildInternal(true);
if (accessible)
return accessible;
@ -105,9 +104,42 @@ TreeWalker::NextChildInternal(bool aNoWalkUp)
}
// No more children, get back to the parent.
nsIContent* anchorNode = mState->content;
PopState();
if (aNoWalkUp)
return nullptr;
return aNoWalkUp ? nullptr : NextChildInternal(false);
if (mState)
return NextChildInternal(false);
// If we traversed the whole subtree of the anchor node. Move to next node
// relative anchor node within the context subtree if possible.
if (mFlags != eWalkContextTree)
return nullptr;
while (anchorNode != mContext->GetNode()) {
nsINode* parentNode = anchorNode->GetFlattenedTreeParent();
if (!parentNode || !parentNode->IsElement())
return nullptr;
PushState(parentNode->AsElement());
mState->childList = mState->content->GetChildren(mChildFilter);
length = 0;
if (mState->childList)
mState->childList->GetLength(&length);
while (mState->childIdx < length) {
nsIContent* childNode = mState->childList->Item(mState->childIdx);
mState->childIdx++;
if (childNode == anchorNode)
return NextChildInternal(false);
}
PopState();
anchorNode = parentNode->AsElement();
}
return nullptr;
}
void
@ -118,15 +150,10 @@ TreeWalker::PopState()
mState = prevToLastState;
}
bool
void
TreeWalker::PushState(nsIContent* aContent)
{
WalkState* nextToLastState = new WalkState(aContent);
if (!nextToLastState)
return false;
nextToLastState->prevState = mState;
mState = nextToLastState;
return true;
}

Просмотреть файл

@ -6,6 +6,7 @@
#ifndef mozilla_a11y_TreeWalker_h_
#define mozilla_a11y_TreeWalker_h_
#include "mozilla/Attributes.h"
#include <stdint.h>
class nsIContent;
@ -21,11 +22,26 @@ struct WalkState;
/**
* This class is used to walk the DOM tree to create accessible tree.
*/
class TreeWalker
class TreeWalker MOZ_FINAL
{
public:
TreeWalker(Accessible* aContext, nsIContent* aNode, bool aWalkCache = false);
virtual ~TreeWalker();
enum {
// used to walk the existing tree of the given node
eWalkCache = 1,
// used to walk the context tree starting from given node
eWalkContextTree = 2 | eWalkCache
};
/**
* Constructor
*
* @param aContext [in] container accessible for the given node, used to
* define accessible context
* @param aNode [in] the node the search will be prepared relative to
* @param aFlags [in] flags (see enum above)
*/
TreeWalker(Accessible* aContext, nsIContent* aNode, uint32_t aFlags = 0);
~TreeWalker();
/**
* Return the next child accessible.
@ -59,7 +75,7 @@ private:
* @note State stack is used to navigate up/down the DOM subtree during
* accessible children search.
*/
bool PushState(nsIContent *aNode);
void PushState(nsIContent* aNode);
/**
* Pop state from stack.
@ -69,7 +85,7 @@ private:
DocAccessible* mDoc;
Accessible* mContext;
int32_t mChildFilter;
bool mWalkCache;
uint32_t mFlags;
WalkState* mState;
};

Просмотреть файл

@ -3060,40 +3060,6 @@ Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const
return child;
}
Accessible*
Accessible::GetFirstAvailableAccessible(nsINode *aStartNode) const
{
Accessible* accessible = mDoc->GetAccessible(aStartNode);
if (accessible)
return accessible;
nsCOMPtr<nsIDocument> doc = aStartNode->OwnerDoc();
nsCOMPtr<nsINode> currentNode = aStartNode;
ErrorResult rv;
nsRefPtr<dom::TreeWalker> walker =
doc->CreateTreeWalker(*GetNode(),
nsIDOMNodeFilter::SHOW_ELEMENT | nsIDOMNodeFilter::SHOW_TEXT,
nullptr, rv);
NS_ENSURE_TRUE(walker, nullptr);
walker->SetCurrentNode(*currentNode, rv);
if (rv.Failed())
return nullptr;
while (true) {
currentNode = walker->NextNode(rv);
if (!currentNode || rv.Failed())
return nullptr;
Accessible* accessible = mDoc->GetAccessible(currentNode);
if (accessible)
return accessible;
}
return nullptr;
}
double
Accessible::AttrNumericValue(nsIAtom* aAttr) const
{

Просмотреть файл

@ -888,16 +888,6 @@ protected:
// helper method to verify frames
static nsresult GetFullKeyName(const nsAString& aModifierName, const nsAString& aKeyName, nsAString& aStringOut);
/**
* Return an accessible for the given DOM node, or if that node isn't
* accessible, return the accessible for the next DOM node which has one
* (based on forward depth first search).
*
* @param aStartNode [in] the DOM node to start from
* @return the resulting accessible
*/
Accessible* GetFirstAvailableAccessible(nsINode* aStartNode) const;
//////////////////////////////////////////////////////////////////////////////
// Action helpers

Просмотреть файл

@ -1783,7 +1783,7 @@ DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
} else {
if (aIsInsert) {
TreeWalker walker(aContainer, aChildNode, true);
TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
while ((child = walker.NextChild()))
updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);

Просмотреть файл

@ -302,7 +302,16 @@ HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
// This <br> is the hacky "bogus node" used when there is no text in a control
return 0;
}
descendant = GetFirstAvailableAccessible(findNode);
descendant = mDoc->GetAccessible(findNode);
if (!descendant && findNode->IsContent()) {
Accessible* container = mDoc->GetContainerAccessible(findNode);
if (container) {
TreeWalker walker(container, findNode->AsContent(),
TreeWalker::eWalkContextTree);
descendant = walker.NextChild();
}
}
}
return TransformOffset(descendant, offset, aIsEndOffset);

Просмотреть файл

@ -58,7 +58,7 @@
testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
"hypertext3", kOk, kOk, kOk);
// XXX: see bug 638684.
// XXX: see bug 634202.
testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
"hypertext4", kTodo, kOk, kTodo);

Просмотреть файл

@ -109,6 +109,44 @@
}
}
function changeDOMSelection(aID, aNodeID1, aNodeOffset1,
aNodeID2, aNodeOffset2,
aStartOffset, aEndOffset)
{
this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
this.eventSeq = [
new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
];
this.invoke = function changeDOMSelection_invoke()
{
var sel = window.getSelection();
var range = document.createRange();
range.setStart(getNode(aNodeID1), aNodeOffset1);
range.setEnd(getNode(aNodeID2), aNodeOffset2);
sel.addRange(range);
}
this.finalCheck = function changeDOMSelection_finalCheck()
{
is(this.hyperText.selectionCount, 1,
"setSelectionBounds: Wrong selection count for " + aID);
var startOffset = {}, endOffset = {};
this.hyperText.getSelectionBounds(0, startOffset, endOffset);
is(startOffset.value, aStartOffset,
"setSelectionBounds: Wrong start offset for " + aID);
is(endOffset.value, aEndOffset,
"setSelectionBounds: Wrong end offset for " + aID);
}
this.getID = function changeDOMSelection_getID()
{
return "DOM selection change for " + aID;
}
}
function onfocusEventSeq(aID)
{
var caretMovedChecker =
@ -141,6 +179,7 @@
gQueue.push(new synthFocus("textarea", onfocusEventSeq("textarea")));
gQueue.push(new changeSelection("textarea", 1, 3));
gQueue.push(new changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0, 2, 2));
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -169,6 +208,7 @@
<p id="paragraph">hello</p>
<input id="textbox" value="hello"/>
<textarea id="textarea">hello</textarea>
<div id="c1">hi<span id="c1_span1"></span><span id="c1_span2"></span>hi</div>
</body>
</html>