bug 387273. Incorrect starting offset exposed when selection spans multiple objects. r=surkov, r=david.bolter, a=dsicore

This commit is contained in:
aaronleventhal@moonset.net 2007-09-18 15:28:26 -07:00
Родитель 8ad53c0662
Коммит 166beb485a
2 изменённых файлов: 131 добавлений и 47 удалений

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

@ -58,6 +58,7 @@
#include "nsIFrame.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIPlaintextEditor.h"
#include "nsISelection2.h"
#include "nsIServiceManager.h"
#include "nsTextFragment.h"
#include "gfxSkipChars.h"
@ -105,7 +106,12 @@ nsresult nsHyperTextAccessible::QueryInterface(REFNSIID aIID, void** aInstancePt
if (aIID.Equals(NS_GET_IID(nsIAccessibleHyperText))) {
if (role == nsIAccessibleRole::ROLE_ENTRY ||
role == nsIAccessibleRole::ROLE_PASSWORD_TEXT) {
return NS_ERROR_NO_INTERFACE;
nsCOMPtr<nsIEditor> editor;
GetAssociatedEditor(getter_AddRefs(editor));
nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
if (peditor) {
return NS_ERROR_NO_INTERFACE; // No embedded objects ever in plain text
}
}
*aInstancePtr = static_cast<nsIAccessibleHyperText*>(this);
NS_ADDREF_THIS();
@ -533,7 +539,8 @@ NS_IMETHODIMP nsHyperTextAccessible::GetCharacterAtOffset(PRInt32 aOffset, PRUni
nsresult nsHyperTextAccessible::DOMPointToHypertextOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset,
PRInt32* aHyperTextOffset,
nsIAccessible **aFinalAccessible)
nsIAccessible **aFinalAccessible,
PRBool aIsEndOffset)
{
// 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.
@ -609,10 +616,19 @@ nsresult nsHyperTextAccessible::DOMPointToHypertextOffset(nsIDOMNode* aNode, PRI
// <div>abc<h1>def</h1>ghi</div>
// If the passed-in DOM point was not on a direct child of the hypertext, we will
// return the offset for that entire hypertext
// If the offset was after the first character of the passed in object, we will now use 1 for
if (aIsEndOffset) {
// Not inclusive, the indicated char comes at index before this offset
// If the end offset is after the first character of the passed in object, use 1 for
// addTextOffset, to put us after the embedded object char. We'll only treat the offset as
// before the embedded object char if we end at the very beginning of the child.
addTextOffset = addTextOffset > 0;
}
else {
// Start offset, inclusive
// Make sure the offset lands on the embedded object character in order to indicate
// the true inner offset is inside the subtree for that link
addTextOffset = (TextLength(descendantAccessible) == addTextOffset) ? 1 : 0;
}
descendantAccessible = parentAccessible;
}
@ -699,7 +715,9 @@ nsHyperTextAccessible::GetRelativeOffset(nsIPresShell *aPresShell,
NS_ENSURE_TRUE(resultNode, -1);
nsCOMPtr<nsIAccessible> finalAccessible;
rv = DOMPointToHypertextOffset(resultNode, pos.mContentOffset, &hyperTextOffset, getter_AddRefs(finalAccessible));
rv = DOMPointToHypertextOffset(resultNode, pos.mContentOffset, &hyperTextOffset,
getter_AddRefs(finalAccessible),
aDirection == eDirNext);
// If finalAccessible == nsnull, then DOMPointToHypertextOffset() searched through the hypertext
// children without finding the node/offset position
NS_ENSURE_SUCCESS(rv, -1);
@ -1371,49 +1389,89 @@ NS_IMETHODIMP nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset)
return DOMPointToHypertextOffset(caretNode, caretOffset, aCaretOffset);
}
nsresult nsHyperTextAccessible::GetSelections(nsISelectionController **aSelCon, nsISelection **aDomSel)
nsresult nsHyperTextAccessible::GetSelections(nsISelectionController **aSelCon,
nsISelection **aDomSel,
nsCOMArray<nsIDOMRange>* aRanges)
{
if (!mDOMNode) {
return NS_ERROR_FAILURE;
}
if (aSelCon) {
*aSelCon = nsnull;
}
if (aDomSel) {
*aDomSel = nsnull;
}
if (aRanges) {
aRanges->Clear();
}
nsCOMPtr<nsISelection> domSel;
nsCOMPtr<nsISelectionController> selCon;
nsCOMPtr<nsIEditor> editor;
GetAssociatedEditor(getter_AddRefs(editor));
if (editor) {
nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
if (peditor) {
// Case 1: plain text editor
// This is for form controls which have their own
// selection controller separate from the document, for example
// HTML:input, HTML:textarea, XUL:textbox, etc.
if (aSelCon) {
editor->GetSelectionController(aSelCon);
editor->GetSelectionController(getter_AddRefs(selCon));
NS_ENSURE_TRUE(*aSelCon, NS_ERROR_FAILURE);
}
if (aDomSel) {
editor->GetSelection(aDomSel);
NS_ENSURE_TRUE(*aDomSel, NS_ERROR_FAILURE);
editor->GetSelection(getter_AddRefs(domSel));
NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE);
}
else {
// Case 2: rich content subtree (can be rich editor)
// This uses the selection controller from the entire document
nsIFrame *frame = GetFrame();
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
return NS_OK;
// Get the selection and selection controller
frame->GetSelectionController(GetPresContext(),
getter_AddRefs(selCon));
NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
getter_AddRefs(domSel));
NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE);
}
nsIFrame *frame = GetFrame();
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
// Get the selection and selection controller
nsCOMPtr<nsISelectionController> selCon;
frame->GetSelectionController(GetPresContext(),
getter_AddRefs(selCon));
NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
if (aSelCon) {
NS_ADDREF(*aSelCon = selCon);
}
if (aDomSel) {
nsCOMPtr<nsISelection> domSel;
selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(domSel));
NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE);
NS_ADDREF(*aDomSel = domSel);
}
if (aRanges) {
nsCOMPtr<nsISelection2> selection2(do_QueryInterface(domSel));
NS_ENSURE_TRUE(selection2, NS_ERROR_FAILURE);
nsCOMPtr<nsIDOMNodeList> childNodes;
nsresult rv = mDOMNode->GetChildNodes(getter_AddRefs(childNodes));
NS_ENSURE_SUCCESS(rv, rv);
PRUint32 numChildren;
rv = childNodes->GetLength(&numChildren);
NS_ENSURE_SUCCESS(rv, rv);
rv = selection2->GetRangesForIntervalCOMArray(mDOMNode, 0,
mDOMNode, numChildren,
PR_TRUE, aRanges);
NS_ENSURE_SUCCESS(rv, rv);
// Remove collapsed ranges
PRInt32 numRanges = aRanges->Count();
for (PRInt32 count = 0; count < numRanges; count ++) {
PRBool isCollapsed;
(*aRanges)[count]->GetCollapsed(&isCollapsed);
if (isCollapsed) {
aRanges->RemoveObjectAt(count);
-- numRanges;
-- count;
}
}
}
return NS_OK;
}
@ -1424,18 +1482,13 @@ nsresult nsHyperTextAccessible::GetSelections(nsISelectionController **aSelCon,
NS_IMETHODIMP nsHyperTextAccessible::GetSelectionCount(PRInt32 *aSelectionCount)
{
nsCOMPtr<nsISelection> domSel;
nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel));
nsCOMArray<nsIDOMRange> ranges;
nsresult rv = GetSelections(nsnull, nsnull, &ranges);
NS_ENSURE_SUCCESS(rv, rv);
PRBool isSelectionCollapsed;
rv = domSel->GetIsCollapsed(&isSelectionCollapsed);
NS_ENSURE_SUCCESS(rv, rv);
*aSelectionCount = ranges.Count();
if (isSelectionCollapsed) {
*aSelectionCount = 0;
return NS_OK;
}
return domSel->GetRangeCount(aSelectionCount);
return NS_OK;
}
/*
@ -1446,35 +1499,49 @@ NS_IMETHODIMP nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum, P
*aStartOffset = *aEndOffset = 0;
nsCOMPtr<nsISelection> domSel;
nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel));
nsCOMArray<nsIDOMRange> ranges;
nsresult rv = GetSelections(nsnull, getter_AddRefs(domSel), &ranges);
NS_ENSURE_SUCCESS(rv, rv);
PRInt32 rangeCount;
domSel->GetRangeCount(&rangeCount);
PRInt32 rangeCount = ranges.Count();
if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
return NS_ERROR_INVALID_ARG;
nsCOMPtr<nsIDOMRange> range;
rv = domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMRange> range = ranges[aSelectionNum];
// Get start point
nsCOMPtr<nsIDOMNode> startNode;
range->GetStartContainer(getter_AddRefs(startNode));
PRInt32 startOffset;
range->GetStartOffset(&startOffset);
rv = DOMPointToHypertextOffset(startNode, startOffset, aStartOffset);
NS_ENSURE_SUCCESS(rv, rv);
// Get end point
nsCOMPtr<nsIDOMNode> endNode;
range->GetEndContainer(getter_AddRefs(endNode));
PRInt32 endOffset;
range->GetEndOffset(&endOffset);
if (startNode == endNode && startOffset == endOffset) {
// Shortcut for collapsed selection case (caret)
*aEndOffset = *aStartOffset;
return NS_OK;
PRInt16 rangeCompareResult;
rv = range->CompareBoundaryPoints(nsIDOMRange::START_TO_END, range, &rangeCompareResult);
NS_ENSURE_SUCCESS(rv, rv);
if (rangeCompareResult < 0) {
// Make sure start is before end, by swapping offsets
// This occurs when the user selects backwards in the text
startNode.swap(endNode);
PRInt32 tempOffset = startOffset;
startOffset = endOffset;
endOffset = tempOffset;
}
return DOMPointToHypertextOffset(endNode, endOffset, aEndOffset);
nsCOMPtr<nsIAccessible> startAccessible;
rv = DOMPointToHypertextOffset(startNode, startOffset, aStartOffset, getter_AddRefs(startAccessible));
NS_ENSURE_SUCCESS(rv, rv);
if (!startAccessible) {
*aStartOffset = 0; // Could not find start point within this hypertext, so starts before
}
return DOMPointToHypertextOffset(endNode, endOffset, aEndOffset, nsnull, PR_TRUE);
}
/*

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

@ -110,10 +110,18 @@ public:
* contained the offset, if it is within
* the current nsHyperTextAccessible,
* otherwise it is set to nsnull.
* @param aIsEndOffset - if PR_TRUE, then then this offset is not inclusive. The character
* indicated by the offset returned is at [offset - 1]. This means
* if the passed-in offset is really in a descendant, then the offset returned
* will come just after the relevant embedded object characer.
* If PR_FALSE, then the offset is inclusive. The character indicated
* by the offset returned is at [offset]. If the passed-in offset in inside a
* descendant, then the returned offset will be on the relevant embedded object char.
*/
nsresult DOMPointToHypertextOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset,
PRInt32 *aHypertextOffset,
nsIAccessible **aFinalAccessible = nsnull);
nsIAccessible **aFinalAccessible = nsnull,
PRBool aIsEndOffset = PR_FALSE);
protected:
/*
@ -184,7 +192,16 @@ protected:
nsIntRect GetBoundsForString(nsIFrame *aFrame, PRUint32 aStartRenderedOffset, PRUint32 aEndRenderedOffset);
// Selection helpers
nsresult GetSelections(nsISelectionController **aSelCon, nsISelection **aDomSel);
/**
* Get the relevant selection interfaces and ranges for the current hyper text
* @param aSelCon The selection controller for the current hyper text, or nsnull if not needed
* @param aDomSel The selection interface for the current hyper text, or nsnull if not needed
* @param aRanges The selected ranges within the current subtree, or nsnull if not needed
*/
nsresult GetSelections(nsISelectionController **aSelCon,
nsISelection **aDomSel = nsnull,
nsCOMArray<nsIDOMRange>* aRanges = nsnull);
nsresult SetSelectionRange(PRInt32 aStartPos, PRInt32 aEndPos);
};