Bug 366438. Fix accessible line break algorithm. r=surkov

git-svn-id: svn://10.0.0.236/trunk@221386 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
aaronleventhal%moonset.net 2007-03-06 13:44:42 +00:00
parent 0db6a6e6bf
commit 5b571fea75
6 changed files with 198 additions and 138 deletions

View File

@ -92,6 +92,9 @@
#include "nsITimer.h"
#include "nsIMutableArray.h"
#include "nsIPersistentProperties2.h"
#include "nsIDOMTreeWalker.h"
#include "nsIDOMDocumentTraversal.h"
#include "nsIDOMNodeFilter.h"
#ifdef NS_DEBUG
#include "nsIFrameDebug.h"
@ -2896,3 +2899,39 @@ PRInt32 nsAccessible::TextLength(nsIAccessible *aAccessible)
}
return textLength;
}
already_AddRefed<nsIAccessible>
nsAccessible::GetFirstAvailableAccessible(nsIDOMNode *aStartNode, PRBool aRequireLeaf)
{
nsIAccessibilityService *accService = GetAccService();
nsCOMPtr<nsIAccessible> accessible;
nsCOMPtr<nsIDOMTreeWalker> walker;
nsCOMPtr<nsIDOMNode> currentNode(aStartNode);
while (currentNode) {
accService->GetAccessibleInWeakShell(currentNode, mWeakShell, getter_AddRefs(accessible)); // AddRef'd
if (accessible && (!aRequireLeaf || IsLeaf(accessible))) {
nsIAccessible *retAccessible = accessible;
NS_ADDREF(retAccessible);
return retAccessible;
}
if (!walker) {
// Instantiate walker lazily since we won't need it in 90% of the cases
// where the first DOM node we're given provides an accessible
nsCOMPtr<nsIDOMDocument> document;
currentNode->GetOwnerDocument(getter_AddRefs(document));
nsCOMPtr<nsIDOMDocumentTraversal> trav = do_QueryInterface(document);
NS_ASSERTION(trav, "No DOM document traversal for document");
NS_ENSURE_TRUE(trav, nsnull);
trav->CreateTreeWalker(mDOMNode, nsIDOMNodeFilter::SHOW_ELEMENT | nsIDOMNodeFilter::SHOW_TEXT,
nsnull, PR_FALSE, getter_AddRefs(walker));
NS_ENSURE_TRUE(walker, nsnull);
walker->SetCurrentNode(currentNode);
}
walker->NextNode(getter_AddRefs(currentNode));
}
return nsnull;
}

View File

@ -152,6 +152,7 @@ public:
static PRBool IsText(nsIAccessible *aAcc) { PRUint32 role = Role(aAcc); return role == ROLE_TEXT_LEAF || role == ROLE_STATICTEXT; }
static PRBool IsEmbeddedObject(nsIAccessible *aAcc) { PRUint32 role = Role(aAcc); return role != ROLE_TEXT_LEAF && role != ROLE_WHITESPACE && role != ROLE_STATICTEXT; }
static PRInt32 TextLength(nsIAccessible *aAccessible);
static PRBool IsLeaf(nsIAccessible *aAcc) { PRInt32 numChildren; aAcc->GetChildCount(&numChildren); return numChildren > 0; }
already_AddRefed<nsIAccessible> GetParent() {
nsIAccessible *parent = nsnull;
@ -202,6 +203,15 @@ protected:
already_AddRefed<nsIAccessible> GetNextWithState(nsIAccessible *aStart, PRUint32 matchState);
/**
* 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, the DOM node to start from
* @param aRequireLeaf, only accept leaf accessible nodes
* @return the resulting accessible
*/
already_AddRefed<nsIAccessible> GetFirstAvailableAccessible(nsIDOMNode *aStartNode, PRBool aRequireLeaf = PR_FALSE);
// Selection helpers
static already_AddRefed<nsIAccessible> GetMultiSelectFor(nsIDOMNode *aNode);

View File

@ -472,63 +472,75 @@ NS_IMETHODIMP nsHyperTextAccessible::GetCharacterAtOffset(PRInt32 aOffset, PRUni
return NS_OK;
}
nsresult nsHyperTextAccessible::DOMPointToOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset, PRInt32* aResult)
nsresult nsHyperTextAccessible::DOMPointToOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset, PRInt32* aResult,
nsIAccessible **aFinalAccessible)
{
// Turn a DOM Node and offset into an offset into this hypertext.
// On failure, return null. On success, return the DOM node which contains the offset.
NS_ENSURE_ARG_POINTER(aResult);
*aResult = 0;
NS_ENSURE_ARG_POINTER(aNode);
NS_ENSURE_TRUE(aNodeOffset >= 0, NS_ERROR_INVALID_ARG);
NS_ENSURE_ARG_POINTER(aFinalAccessible);
if (aFinalAccessible) {
*aFinalAccessible = nsnull;
}
PRInt32 addTextOffset = 0;
nsCOMPtr<nsIDOMNode> findNode;
unsigned short nodeType;
aNode->GetNodeType(&nodeType);
if (nodeType != nsIDOMNode::TEXT_NODE) {
// If not text, aNodeOffset is a child
// aNode != mDOMNode for nsDocAccessible case, where
// aNode is for <body> and mDOMNode is doc element
nsCOMPtr<nsINode> hyperTextNode(do_QueryInterface(aNode));
NS_ENSURE_TRUE(hyperTextNode, NS_ERROR_FAILURE);
nsCOMPtr<nsIAccessible> childAccessible;
// Find first accessible child starting at aNodeOffset. We need to do this because
// the node at aNodeOffset might not be an accessible
while (!childAccessible) {
nsIContent *childContent = hyperTextNode->GetChildAt(aNodeOffset ++);
nsCOMPtr<nsIDOMNode> childNode(do_QueryInterface(childContent));
if (!childNode) {
break; // Caret at end of hyper text
}
GetAccService()->GetAccessibleFor(childNode, getter_AddRefs(childAccessible));
}
// Loop through, adding offsets until we reach childAccessible
// If childAccessible is null we will end up adding up the entire length of
// the hypertext, which is good -- it just means our offset node
// came after the last accessible child's node
nsCOMPtr<nsIAccessible> accessible;
while (NextChild(accessible) && accessible != childAccessible) {
*aResult += TextLength(accessible);
}
return NS_OK;
if (nodeType == nsIDOMNode::TEXT_NODE) {
// For text nodes, aNodeOffset comes in as a character offset
// Text offset will be added at the end, if we find the offset in this hypertext
addTextOffset = aNodeOffset;
// Get the child node and
findNode = aNode;
}
else {
// For non-text nodes, aNodeOffset comes in as a child node index
nsCOMPtr<nsIContent> parentContent(do_QueryInterface(aNode));
// Should not happen, but better to protect against crash if doc node is somehow passed in
NS_ENSURE_TRUE(parentContent, NS_ERROR_FAILURE);
// findNode could be null if aNodeOffset == # of child nodes, which means we're at the end of the children
findNode = do_QueryInterface(parentContent->GetChildAt(aNodeOffset));
}
// This is text, and aNodeOffset is the nth character in the text
*aResult = aNodeOffset;
#ifdef DEBUG
// aNode should be a descendant of mDOMNode (the DOM node for this accessible object)
// Corralary: the first accessible in the parent chain should be |this|
if (findNode) {
nsCOMPtr<nsIAccessibleDocument> docAccessible(GetDocAccessible());
nsCOMPtr<nsIAccessible> parentAccessible;
docAccessible->GetAccessibleInParentChain(findNode, getter_AddRefs(parentAccessible));
NS_ASSERTION(parentAccessible == this, "Accessible ancestor is not |this|");
}
#endif
// Get accessible for this findNode, or if that node isn't accessible, use the
// accessible for the next DOM node which has one (based on forward depth first search)
nsCOMPtr<nsIAccessible> childAccessible;
if (findNode) {
childAccessible = GetFirstAvailableAccessible(findNode);
}
// Loop through, adding offsets until we reach childAccessible
// If childAccessible is null we will end up adding up the entire length of
// the hypertext, which is good -- it just means our offset node
// came after the last accessible child's node
nsCOMPtr<nsIAccessible> accessible;
while (NextChild(accessible)) {
nsCOMPtr<nsPIAccessNode> accessNode(do_QueryInterface(accessible));
nsIFrame *frame = accessNode->GetFrame();
if (!frame) {
return NS_ERROR_FAILURE;
}
if (frame && SameCOMIdentity(frame->GetContent(), aNode)) {
return NS_OK;
}
while (NextChild(accessible) && accessible != childAccessible) {
*aResult += TextLength(accessible);
}
return NS_ERROR_FAILURE;
if (accessible) {
*aResult += addTextOffset;
NS_ASSERTION(accessible == childAccessible, "These should be equal whenever we exit loop and accessible != nsnull");
if (aFinalAccessible && (NextChild(accessible) || addTextOffset < TextLength(childAccessible))) {
// If not at end of last text node, we will return the accessible we were in
NS_ADDREF(*aFinalAccessible = childAccessible);
}
}
return NS_OK;
}
PRInt32 nsHyperTextAccessible::GetRelativeOffset(nsIPresShell *aPresShell, nsIFrame *aFromFrame, PRInt32 aFromOffset,
@ -538,33 +550,55 @@ PRInt32 nsHyperTextAccessible::GetRelativeOffset(nsIPresShell *aPresShell, nsIFr
const PRBool kIsScrollViewAStop = PR_FALSE; // do not stop at scroll views
const PRBool kIsKeyboardSelect = PR_TRUE; // is keyboard selection
const PRBool kIsVisualBidi = PR_FALSE; // use visual order for bidi text
const PRInt32 kDesiredX = aPresShell->GetRootFrame()->GetSize().width;
EWordMovementType wordMovementType = aNeedsStart ? eStartWord : eEndWord;
if (aAmount == eSelectLine && aDirection == eDirNext) {
aAmount = eSelectEndLine; // Select forward to end of line
if (aAmount == eSelectLine) {
aAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
}
// Ask layout for the new node and offset, after moving the appropriate amount
nsPeekOffsetStruct pos;
pos.SetData(aAmount, aDirection, aFromOffset, kDesiredX,
kIsJumpLinesOk,
pos.SetData(aAmount, aDirection, aFromOffset, 0, kIsJumpLinesOk,
kIsScrollViewAStop, kIsKeyboardSelect, kIsVisualBidi,
wordMovementType);
nsresult rv = aFromFrame->PeekOffset(&pos);
NS_ENSURE_SUCCESS(rv, -1);
PRInt32 resultOffset = pos.mContentOffset;
nsIContent *resultContent = nsnull;
if (NS_SUCCEEDED(rv)) {
resultContent = pos.mResultContent;
}
nsCOMPtr<nsIDOMNode> resultNode = do_QueryInterface(resultContent);
// Turn the resulting node and offset into a hyperTextOffset
PRInt32 hyperTextOffset;
if (!resultContent || NS_FAILED(DOMPointToOffset(resultNode, resultOffset, &hyperTextOffset))) {
if (aDirection == eDirNext) {
GetCharacterCount(&hyperTextOffset);
nsCOMPtr<nsIDOMNode> resultNode = do_QueryInterface(pos.mResultContent);
NS_ENSURE_TRUE(resultNode, -1);
nsCOMPtr<nsIAccessible> finalAccessible;
rv = DOMPointToOffset(resultNode, pos.mContentOffset, &hyperTextOffset, getter_AddRefs(finalAccessible));
// If finalAccessible == nsnull, then DOMPointToOffset() searched through the hypertext
// children without finding the node/offset position
NS_ENSURE_SUCCESS(rv, -1);
if (!finalAccessible && aDirection == eDirPrevious) {
// If we reached the end during search, this means we didn't find the DOM point
// and we're actually at the start of the paragraph
hyperTextOffset = 0;
}
else if (aAmount == eSelectBeginLine) {
// For line selection with needsStart, set start of line exactly to line break
if (!aNeedsStart && hyperTextOffset > 0) {
-- hyperTextOffset;
}
else {
hyperTextOffset = 0;
}
else if (aAmount == eSelectEndLine && finalAccessible) {
// If not at very end of hypertext, we may need change the end of line offset by 1,
// to make sure we are in the right place relative to the line ending
if (Role(finalAccessible) == ROLE_WHITESPACE) { // Landed on <br> hard line break
// if aNeedsStart, set end of line exactly 1 character past line break
// XXX It would be cleaner if we did not have to have the hard line break check,
// and just got the correct results from PeekOffset() for the <br> case -- the returned offset should
// come after the new line, as it does in other cases.
++ hyperTextOffset; // Get past hard line break
}
// We are now 1 character past the line break
if (!aNeedsStart) {
-- hyperTextOffset;
}
}
@ -629,6 +663,7 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
// Newlines are considered at the end of a line,
// Since getting the BOUNDARY_LINE_START gets the text from the line-start
// to the next line-start, the newline is included at the end of the string
needsStart = PR_TRUE;
amount = eSelectLine;
break;
case BOUNDARY_LINE_END:
@ -659,21 +694,13 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
// If aType == eGetAt we'll change both the start and end offset from
// the original offset
PRInt32 startForwardSearchOffset;
if (aType == eGetAfter) {
startForwardSearchOffset = startOffset = aOffset;
startOffset = aOffset;
}
else {
startOffset = GetRelativeOffset(presShell, startFrame, startOffset,
amount, eDirPrevious, needsStart);
startForwardSearchOffset = startOffset;
if (amount == eSelectLine) {
++ startForwardSearchOffset; // Avoid getting the previous line
if (aBoundaryType == BOUNDARY_LINE_START && startOffset > 0) {
// Start line fixup: don't include \n at start of string
++ startOffset;
}
}
NS_ENSURE_TRUE(startOffset >= 0, NS_ERROR_FAILURE);
}
if (aType == eGetBefore) {
@ -682,33 +709,14 @@ nsresult nsHyperTextAccessible::GetTextHelper(EGetTextType aType, nsAccessibleTe
else {
// Start moving forward from the start so that we don't get
// 2 words/lines if the offset occured on whitespace boundary
endOffset = startForwardSearchOffset; // Passed by reference
nsIFrame *endFrame = GetPosAndText(startForwardSearchOffset, endOffset);
endOffset = startOffset; // Passed by reference to GetPosAndText()
nsIFrame *endFrame = GetPosAndText(startOffset, endOffset);
if (!endFrame) {
return NS_ERROR_FAILURE;
}
endOffset = GetRelativeOffset(presShell, endFrame, endOffset, amount,
eDirNext, needsStart);
if (amount == eSelectLine &&
endFrame->GetType() == nsAccessibilityAtoms::textFrame) {
PRInt32 startFrameOffsetUnused, endFrameOffset;
if (NS_SUCCEEDED(endFrame->GetOffsets(startFrameOffsetUnused, endFrameOffset))) {
nsCOMPtr<nsIDOMNode> endNode = do_QueryInterface(endFrame->GetContent());
nsCOMPtr<nsIAccessible> endAccessible;
if (endNode) {
GetAccService()->GetAccessibleFor(endNode, getter_AddRefs(endAccessible));
if (endAccessible && NextChild(endAccessible) &&
Role(endAccessible) == ROLE_WHITESPACE) {
++ endOffset; // Make sure endOffset comes after <br>
}
}
// End line fixup: include \n for <br> at end of string,
// only if aBoundaryType == BOUNDARY_LINE_START
if (aBoundaryType == BOUNDARY_LINE_END) {
-- endOffset;
}
}
}
NS_ENSURE_TRUE(endOffset >= 0, NS_ERROR_FAILURE);
}
// Fix word error for the first character in word: PeekOffset() will return the previous word when

View File

@ -82,14 +82,60 @@ public:
protected:
PRBool IsHyperText();
/*
* This does the work for nsIAccessibleText::GetText[At|Before|After]Offset
* @param aType, eGetBefore, eGetAt, eGetAfter
* @param aBoundaryType, char/word-start/word-end/line-start/line-end/paragraph/attribute
* @param aOffset, offset into the hypertext to start from
* @param *aStartOffset, the resulting start offset for the returned substring
* @param *aEndOffset, the resulting end offset for the returned substring
* @param aText, the resulting substring
* @return success/failure code
*/
nsresult GetTextHelper(EGetTextType aType, nsAccessibleTextBoundary aBoundaryType,
PRInt32 aOffset, PRInt32 *aStartOffset, PRInt32 *aEndOffset,
nsAString & aText);
/**
* Used by GetPosAndText to move backward/forward from a given point by word/line/etc.
* @param aPresShell, the current presshell we're moving in
* @param aFromFrame, the starting frame we're moving from
* @param aFromOffset, the starting offset we're moving from
* @param aAmount, how much are we moving (word/line/etc.) ?
* @param aDirection, forward or backward?
* @param aNeedsStart, for word and line cases, are we basing this on the start or end?
* @return, the resulting offset into this hypertext
*/
PRInt32 GetRelativeOffset(nsIPresShell *aPresShell, nsIFrame *aFromFrame, PRInt32 aFromOffset,
nsSelectionAmount amount, nsDirection direction, PRBool aNeedsStart);
nsSelectionAmount aAmount, nsDirection aDirection, PRBool aNeedsStart);
/**
* Given a start offset and end offset, get substring information. Different info is returned depending
* on what optional paramters are provided.
* @param aStartOffset, the start offset into the hyper text. This is also an out parameter used to return
* the offset into the start frame's text content (start frame is the @return)
* @param aEndOffset, the endoffset into the hyper text. This is also an out parameter used to return
* the offset into the end frame's text content
* @param aText (optional), return the substring's text
* @param aEndFrame (optional), return the end frame for this substring
* @param aBoundsRect (optional), return the bounds rectangle for this substring
* @return the start frame for this substring
*/
nsIFrame* GetPosAndText(PRInt32& aStartOffset, PRInt32& aEndOffset, nsAString *aText = nsnull,
nsIFrame **aEndFrame = nsnull, nsIntRect *aBoundsRect = nsnull);
nsresult DOMPointToOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset, PRInt32 *aResult);
/**
* Turn a DOM Node and offset into a character offset into this hypertext. Will look for closest match
* when the DOM node does not have an accessible object associated with it.
* Will return an offset for the end of the string if the node is not found.
* @param aNode, the node to look for
* @param aNodeOffset, the offset to look for
* @param aResult, the character offset into the current nsHyperTextAccessible
* @param aFinalAccessible (optional), returns the accessible child which contained the offset,
* if it is within the current nsHyperTextAccessible, otherwise
* it is set to nsnull.
* @return failure/success code
*/
nsresult DOMPointToOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset, PRInt32 *aResultOffset,
nsIAccessible **aFinalAccessible = nsnull);
nsIntRect GetBoundsForString(nsIFrame *aFrame, PRInt32 aStartOffset, PRInt32 aLength);
// Editor helpers, subclasses of nsHyperTextAccessible may have editor

View File

@ -42,9 +42,6 @@
#include "nsIAccessibleEvent.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeNode.h"
#include "nsIDOMDocumentTraversal.h"
#include "nsIDOMNodeFilter.h"
#include "nsIDOMTreeWalker.h"
#include "nsIFrame.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPresShell.h"
@ -287,43 +284,6 @@ PRInt32 nsDocAccessibleWrap::GetChildIDFor(nsIAccessible* aAccessible)
return - NS_PTR_TO_INT32(uniqueID);
}
already_AddRefed<nsIAccessible>
nsDocAccessibleWrap::GetFirstLeafAccessible(nsIDOMNode *aStartNode)
{
nsCOMPtr<nsIAccessibilityService> accService(do_GetService("@mozilla.org/accessibilityService;1"));
nsCOMPtr<nsIAccessible> accessible;
nsCOMPtr<nsIDOMTreeWalker> walker;
nsCOMPtr<nsIDOMNode> currentNode(aStartNode);
while (currentNode) {
accService->GetAccessibleInWeakShell(currentNode, mWeakShell, getter_AddRefs(accessible)); // AddRef'd
if (accessible) {
PRInt32 numChildren;
accessible->GetChildCount(&numChildren);
if (numChildren == 0) {
nsIAccessible *leafAccessible = accessible;
NS_ADDREF(leafAccessible);
return leafAccessible; // It's a leaf accessible, return it
}
}
if (!walker) {
// Instantiate walker lazily since we won't need it in 90% of the cases
// where the first DOM node we're given provides an accessible
nsCOMPtr<nsIDOMDocumentTraversal> trav = do_QueryInterface(mDocument);
NS_ASSERTION(trav, "No DOM document traversal for document");
trav->CreateTreeWalker(mDOMNode,
nsIDOMNodeFilter::SHOW_ELEMENT | nsIDOMNodeFilter::SHOW_TEXT,
nsnull, PR_FALSE, getter_AddRefs(walker));
NS_ENSURE_TRUE(walker, nsnull);
walker->SetCurrentNode(currentNode);
}
walker->NextNode(getter_AddRefs(currentNode));
}
return nsnull;
}
NS_IMETHODIMP nsDocAccessibleWrap::FireAnchorJumpEvent()
{
// Staying on the same page, jumping to a named anchor
@ -353,7 +313,7 @@ NS_IMETHODIMP nsDocAccessibleWrap::FireAnchorJumpEvent()
focusNode = mDOMNode; // Moved to top, so event is for 1st leaf after root
}
nsCOMPtr<nsIAccessible> accessible = GetFirstLeafAccessible(focusNode);
nsCOMPtr<nsIAccessible> accessible = GetFirstAvailableAccessible(focusNode, PR_TRUE);
nsCOMPtr<nsPIAccessible> privateAccessible = do_QueryInterface(accessible);
if (privateAccessible) {
privateAccessible->FireToolkitEvent(nsIAccessibleEvent::EVENT_SCROLLINGSTART,

View File

@ -92,9 +92,6 @@ public:
NS_IMETHOD Shutdown();
NS_IMETHOD FireToolkitEvent(PRUint32 aEvent, nsIAccessible* aAccessible, void* aData);
NS_IMETHOD FireAnchorJumpEvent();
private:
already_AddRefed<nsIAccessible> GetFirstLeafAccessible(nsIDOMNode *aStartNode);
};
#endif