diff options
Diffstat (limited to 'accessible/generic/HyperTextAccessible.cpp')
-rw-r--r-- | accessible/generic/HyperTextAccessible.cpp | 2230 |
1 files changed, 2230 insertions, 0 deletions
diff --git a/accessible/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp new file mode 100644 index 000000000..059c27372 --- /dev/null +++ b/accessible/generic/HyperTextAccessible.cpp @@ -0,0 +1,2230 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=78: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HyperTextAccessible-inl.h" + +#include "Accessible-inl.h" +#include "nsAccessibilityService.h" +#include "nsIAccessibleTypes.h" +#include "DocAccessible.h" +#include "HTMLListAccessible.h" +#include "Role.h" +#include "States.h" +#include "TextAttrs.h" +#include "TextRange.h" +#include "TreeWalker.h" + +#include "nsCaret.h" +#include "nsContentUtils.h" +#include "nsFocusManager.h" +#include "nsIDOMRange.h" +#include "nsIEditingSession.h" +#include "nsContainerFrame.h" +#include "nsFrameSelection.h" +#include "nsILineIterator.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPersistentProperties2.h" +#include "nsIScrollableFrame.h" +#include "nsIServiceManager.h" +#include "nsITextControlElement.h" +#include "nsIMathMLFrame.h" +#include "nsTextFragment.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/dom/Element.h" +#include "mozilla/EventStates.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/MathAlgorithms.h" +#include "gfxSkipChars.h" +#include <algorithm> + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HyperTextAccessible +//////////////////////////////////////////////////////////////////////////////// + +HyperTextAccessible:: + HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) : + AccessibleWrap(aNode, aDoc) +{ + mType = eHyperTextType; + mGenericTypes |= eHyperText; +} + +NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessible, Accessible) + +role +HyperTextAccessible::NativeRole() +{ + a11y::role r = GetAccService()->MarkupRole(mContent); + if (r != roles::NOTHING) + return r; + + nsIFrame* frame = GetFrame(); + if (frame && frame->GetType() == nsGkAtoms::inlineFrame) + return roles::TEXT; + + return roles::TEXT_CONTAINER; +} + +uint64_t +HyperTextAccessible::NativeState() +{ + uint64_t states = AccessibleWrap::NativeState(); + + if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) { + states |= states::EDITABLE; + + } else if (mContent->IsHTMLElement(nsGkAtoms::article)) { + // We want <article> to behave like a document in terms of readonly state. + states |= states::READONLY; + } + + if (HasChildren()) + states |= states::SELECTABLE_TEXT; + + return states; +} + +nsIntRect +HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame, + uint32_t aStartRenderedOffset, + uint32_t aEndRenderedOffset) +{ + nsPresContext* presContext = mDoc->PresContext(); + if (aFrame->GetType() != nsGkAtoms::textFrame) { + return aFrame->GetScreenRectInAppUnits(). + ToNearestPixels(presContext->AppUnitsPerDevPixel()); + } + + // Substring must be entirely within the same text node. + int32_t startContentOffset, endContentOffset; + nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset); + NS_ENSURE_SUCCESS(rv, nsIntRect()); + rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset); + NS_ENSURE_SUCCESS(rv, nsIntRect()); + + nsIFrame *frame; + int32_t startContentOffsetInFrame; + // Get the right frame continuation -- not really a child, but a sibling of + // the primary frame passed in + rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false, + &startContentOffsetInFrame, &frame); + NS_ENSURE_SUCCESS(rv, nsIntRect()); + + nsRect screenRect; + while (frame && startContentOffset < endContentOffset) { + // Start with this frame's screen rect, which we will shrink based on + // the substring we care about within it. We will then add that frame to + // the total screenRect we are returning. + nsRect frameScreenRect = frame->GetScreenRectInAppUnits(); + + // Get the length of the substring in this frame that we want the bounds for + int32_t startFrameTextOffset, endFrameTextOffset; + frame->GetOffsets(startFrameTextOffset, endFrameTextOffset); + int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset; + int32_t seekLength = endContentOffset - startContentOffset; + int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength); + + // Add the point where the string starts to the frameScreenRect + nsPoint frameTextStartPoint; + rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint); + NS_ENSURE_SUCCESS(rv, nsIntRect()); + + // Use the point for the end offset to calculate the width + nsPoint frameTextEndPoint; + rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint); + NS_ENSURE_SUCCESS(rv, nsIntRect()); + + frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x); + frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x); + + screenRect.UnionRect(frameScreenRect, screenRect); + + // Get ready to loop back for next frame continuation + startContentOffset += frameSubStringLength; + startContentOffsetInFrame = 0; + frame = frame->GetNextContinuation(); + } + + return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel()); +} + +void +HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset, + nsAString& aText) +{ + aText.Truncate(); + + index_t startOffset = ConvertMagicOffset(aStartOffset); + index_t endOffset = ConvertMagicOffset(aEndOffset); + if (!startOffset.IsValid() || !endOffset.IsValid() || + startOffset > endOffset || endOffset > CharacterCount()) { + NS_ERROR("Wrong in offset"); + return; + } + + int32_t startChildIdx = GetChildIndexAtOffset(startOffset); + if (startChildIdx == -1) + return; + + int32_t endChildIdx = GetChildIndexAtOffset(endOffset); + if (endChildIdx == -1) + return; + + if (startChildIdx == endChildIdx) { + int32_t childOffset = GetChildOffset(startChildIdx); + if (childOffset == -1) + return; + + Accessible* child = GetChildAt(startChildIdx); + child->AppendTextTo(aText, startOffset - childOffset, + endOffset - startOffset); + return; + } + + int32_t startChildOffset = GetChildOffset(startChildIdx); + if (startChildOffset == -1) + return; + + Accessible* startChild = GetChildAt(startChildIdx); + startChild->AppendTextTo(aText, startOffset - startChildOffset); + + for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) { + Accessible* child = GetChildAt(childIdx); + child->AppendTextTo(aText); + } + + int32_t endChildOffset = GetChildOffset(endChildIdx); + if (endChildOffset == -1) + return; + + Accessible* endChild = GetChildAt(endChildIdx); + endChild->AppendTextTo(aText, 0, endOffset - endChildOffset); +} + +uint32_t +HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset, + bool aIsEndOffset) const +{ + if (!aNode) + return 0; + + uint32_t offset = 0; + nsINode* findNode = nullptr; + + if (aNodeOffset == -1) { + findNode = aNode; + + } else if (aNode->IsNodeOfType(nsINode::eTEXT)) { + // 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 + // We want the "skipped" offset into the text (rendered text without the extra whitespace) + nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); + NS_ENSURE_TRUE(frame, 0); + + nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset); + NS_ENSURE_SUCCESS(rv, 0); + + findNode = aNode; + + } else { + // findNode could be null if aNodeOffset == # of child nodes, which means + // one of two things: + // 1) there are no children, and the passed-in node is not mContent -- use + // parentContent for the node to find + // 2) there are no children and the passed-in node is mContent, which means + // we're an empty nsIAccessibleText + // 3) there are children and we're at the end of the children + + findNode = aNode->GetChildAt(aNodeOffset); + if (!findNode) { + if (aNodeOffset == 0) { + if (aNode == GetNode()) { + // Case #1: this accessible has no children and thus has empty text, + // we can only be at hypertext offset 0. + return 0; + } + + // Case #2: there are no children, we're at this node. + findNode = aNode; + } else if (aNodeOffset == static_cast<int32_t>(aNode->GetChildCount())) { + // Case #3: we're after the last child, get next node to this one. + for (nsINode* tmpNode = aNode; + !findNode && tmpNode && tmpNode != mContent; + tmpNode = tmpNode->GetParent()) { + findNode = tmpNode->GetNextSibling(); + } + } + } + } + + // 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) + Accessible* descendant = nullptr; + if (findNode) { + nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode)); + if (findContent && findContent->IsHTMLElement() && + findContent->NodeInfo()->Equals(nsGkAtoms::br) && + findContent->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::mozeditorbogusnode, + nsGkAtoms::_true, + eIgnoreCase)) { + // This <br> is the hacky "bogus node" used when there is no text in a control + return 0; + } + + descendant = mDoc->GetAccessible(findNode); + if (!descendant && findNode->IsContent()) { + Accessible* container = mDoc->GetContainerAccessible(findNode); + if (container) { + TreeWalker walker(container, findNode->AsContent(), + TreeWalker::eWalkContextTree); + descendant = walker.Next(); + if (!descendant) + descendant = container; + } + } + } + + return TransformOffset(descendant, offset, aIsEndOffset); +} + +uint32_t +HyperTextAccessible::TransformOffset(Accessible* aDescendant, + uint32_t aOffset, bool aIsEndOffset) const +{ + // From the descendant, go up and get the immediate child of this hypertext. + uint32_t offset = aOffset; + Accessible* descendant = aDescendant; + while (descendant) { + Accessible* parent = descendant->Parent(); + if (parent == this) + return GetChildOffset(descendant) + offset; + + // This offset no longer applies because the passed-in text object is not + // a child of the hypertext. This happens when there are nested hypertexts, + // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset + // to make it relative the hypertext. + // If the end offset is not supposed to be inclusive and the original point + // is not at 0 offset then the returned offset should be after an embedded + // character the original point belongs to. + if (aIsEndOffset) + offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0; + else + offset = 0; + + descendant = parent; + } + + // If the given a11y point cannot be mapped into offset relative this hypertext + // offset then return length as fallback value. + return CharacterCount(); +} + +/** + * GetElementAsContentOf() returns a content representing an element which is + * or includes aNode. + * + * XXX This method is enough to retrieve ::before or ::after pseudo element. + * So, if you want to use this for other purpose, you might need to check + * ancestors too. + */ +static nsIContent* GetElementAsContentOf(nsINode* aNode) +{ + if (aNode->IsElement()) { + return aNode->AsContent(); + } + nsIContent* parent = aNode->GetParent(); + return parent && parent->IsElement() ? parent : nullptr; +} + +bool +HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset, + nsRange* aRange) +{ + DOMPoint startPoint = OffsetToDOMPoint(aStartOffset); + if (!startPoint.node) + return false; + + // HyperTextAccessible manages pseudo elements generated by ::before or + // ::after. However, contents of them are not in the DOM tree normally. + // Therefore, they are not selectable and editable. So, when this creates + // a DOM range, it should not start from nor end in any pseudo contents. + + nsIContent* container = GetElementAsContentOf(startPoint.node); + DOMPoint startPointForDOMRange = + ClosestNotGeneratedDOMPoint(startPoint, container); + aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx); + + // If the caller wants collapsed range, let's collapse the range to its start. + if (aStartOffset == aEndOffset) { + aRange->Collapse(true); + return true; + } + + DOMPoint endPoint = OffsetToDOMPoint(aEndOffset); + if (!endPoint.node) + return false; + + if (startPoint.node != endPoint.node) { + container = GetElementAsContentOf(endPoint.node); + } + + DOMPoint endPointForDOMRange = + ClosestNotGeneratedDOMPoint(endPoint, container); + aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx); + return true; +} + +DOMPoint +HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset) +{ + // 0 offset is valid even if no children. In this case the associated editor + // is empty so return a DOM point for editor root element. + if (aOffset == 0) { + nsCOMPtr<nsIEditor> editor = GetEditor(); + if (editor) { + bool isEmpty = false; + editor->GetDocumentIsEmpty(&isEmpty); + if (isEmpty) { + nsCOMPtr<nsIDOMElement> editorRootElm; + editor->GetRootElement(getter_AddRefs(editorRootElm)); + + nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm)); + return DOMPoint(editorRoot, 0); + } + } + } + + int32_t childIdx = GetChildIndexAtOffset(aOffset); + if (childIdx == -1) + return DOMPoint(); + + Accessible* child = GetChildAt(childIdx); + int32_t innerOffset = aOffset - GetChildOffset(childIdx); + + // A text leaf case. + if (child->IsTextLeaf()) { + // The point is inside the text node. This is always true for any text leaf + // except a last child one. See assertion below. + if (aOffset < GetChildOffset(childIdx + 1)) { + nsIContent* content = child->GetContent(); + int32_t idx = 0; + if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(), + innerOffset, &idx))) + return DOMPoint(); + + return DOMPoint(content, idx); + } + + // Set the DOM point right after the text node. + MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount()); + innerOffset = 1; + } + + // Case of embedded object. The point is either before or after the element. + NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!"); + nsINode* node = child->GetNode(); + nsINode* parentNode = node->GetParentNode(); + return parentNode ? + DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) : + DOMPoint(); +} + +DOMPoint +HyperTextAccessible::ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint, + nsIContent* aElementContent) +{ + MOZ_ASSERT(aDOMPoint.node, "The node must not be null"); + + // ::before pseudo element + if (aElementContent && + aElementContent->IsGeneratedContentContainerForBefore()) { + MOZ_ASSERT(aElementContent->GetParent(), + "::before must have parent element"); + // The first child of its parent (i.e., immediately after the ::before) is + // good point for a DOM range. + return DOMPoint(aElementContent->GetParent(), 0); + } + + // ::after pseudo element + if (aElementContent && + aElementContent->IsGeneratedContentContainerForAfter()) { + MOZ_ASSERT(aElementContent->GetParent(), + "::after must have parent element"); + // The end of its parent (i.e., immediately before the ::after) is good + // point for a DOM range. + return DOMPoint(aElementContent->GetParent(), + aElementContent->GetParent()->GetChildCount()); + } + + return aDOMPoint; +} + +uint32_t +HyperTextAccessible::FindOffset(uint32_t aOffset, nsDirection aDirection, + nsSelectionAmount aAmount, + EWordMovementType aWordMovementType) +{ + NS_ASSERTION(aDirection == eDirPrevious || aAmount != eSelectBeginLine, + "eSelectBeginLine should only be used with eDirPrevious"); + + // Find a leaf accessible frame to start with. PeekOffset wants this. + HyperTextAccessible* text = this; + Accessible* child = nullptr; + int32_t innerOffset = aOffset; + + do { + int32_t childIdx = text->GetChildIndexAtOffset(innerOffset); + + // We can have an empty text leaf as our only child. Since empty text + // leaves are not accessible we then have no children, but 0 is a valid + // innerOffset. + if (childIdx == -1) { + NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?"); + return DOMPointToOffset(text->GetNode(), 0, aDirection == eDirNext); + } + + child = text->GetChildAt(childIdx); + + // HTML list items may need special processing because PeekOffset doesn't + // work with list bullets. + if (text->IsHTMLListItem()) { + HTMLLIAccessible* li = text->AsHTMLListItem(); + if (child == li->Bullet()) { + // XXX: the logic is broken for multichar bullets in moving by + // char/cluster/word cases. + if (text != this) { + return aDirection == eDirPrevious ? + TransformOffset(text, 0, false) : + TransformOffset(text, 1, true); + } + if (aDirection == eDirPrevious) + return 0; + + uint32_t nextOffset = GetChildOffset(1); + if (nextOffset == 0) + return 0; + + switch (aAmount) { + case eSelectLine: + case eSelectEndLine: + // Ask a text leaf next (if not empty) to the bullet for an offset + // since list item may be multiline. + return nextOffset < CharacterCount() ? + FindOffset(nextOffset, aDirection, aAmount, aWordMovementType) : + nextOffset; + + default: + return nextOffset; + } + } + } + + innerOffset -= text->GetChildOffset(childIdx); + + text = child->AsHyperText(); + } while (text); + + nsIFrame* childFrame = child->GetFrame(); + if (!childFrame) { + NS_ERROR("No child frame"); + return 0; + } + + int32_t innerContentOffset = innerOffset; + if (child->IsTextLeaf()) { + NS_ASSERTION(childFrame->GetType() == nsGkAtoms::textFrame, "Wrong frame!"); + RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset); + } + + nsIFrame* frameAtOffset = childFrame; + int32_t unusedOffsetInFrame = 0; + childFrame->GetChildFrameContainingOffset(innerContentOffset, true, + &unusedOffsetInFrame, + &frameAtOffset); + + const bool kIsJumpLinesOk = true; // okay to jump lines + const bool kIsScrollViewAStop = false; // do not stop at scroll views + const bool kIsKeyboardSelect = true; // is keyboard selection + const bool kIsVisualBidi = false; // use visual order for bidi text + nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset, + nsPoint(0, 0), kIsJumpLinesOk, kIsScrollViewAStop, + kIsKeyboardSelect, kIsVisualBidi, + false, aWordMovementType); + nsresult rv = frameAtOffset->PeekOffset(&pos); + + // PeekOffset fails on last/first lines of the text in certain cases. + if (NS_FAILED(rv) && aAmount == eSelectLine) { + pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine; + frameAtOffset->PeekOffset(&pos); + } + if (!pos.mResultContent) { + NS_ERROR("No result content!"); + return 0; + } + + // Turn the resulting DOM point into an offset. + uint32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent, + pos.mContentOffset, + aDirection == eDirNext); + + if (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 + if (hyperTextOffset == CharacterCount()) + return 0; + + // PeekOffset stops right before bullet so return 0 to workaround it. + if (IsHTMLListItem() && aAmount == eSelectBeginLine && + hyperTextOffset > 0) { + Accessible* prevOffsetChild = GetChildAtOffset(hyperTextOffset - 1); + if (prevOffsetChild == AsHTMLListItem()->Bullet()) + return 0; + } + } + + return hyperTextOffset; +} + +uint32_t +HyperTextAccessible::FindLineBoundary(uint32_t aOffset, + EWhichLineBoundary aWhichLineBoundary) +{ + // Note: empty last line doesn't have own frame (a previous line contains '\n' + // character instead) thus when it makes a difference we need to process this + // case separately (otherwise operations are performed on previous line). + switch (aWhichLineBoundary) { + case ePrevLineBegin: { + // Fetch a previous line and move to its start (as arrow up and home keys + // were pressed). + if (IsEmptyLastLineOffset(aOffset)) + return FindOffset(aOffset, eDirPrevious, eSelectBeginLine); + + uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine); + return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine); + } + + case ePrevLineEnd: { + if (IsEmptyLastLineOffset(aOffset)) + return aOffset - 1; + + // If offset is at first line then return 0 (first line start). + uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine); + if (tmpOffset == 0) + return 0; + + // Otherwise move to end of previous line (as arrow up and end keys were + // pressed). + tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine); + return FindOffset(tmpOffset, eDirNext, eSelectEndLine); + } + + case eThisLineBegin: + if (IsEmptyLastLineOffset(aOffset)) + return aOffset; + + // Move to begin of the current line (as home key was pressed). + return FindOffset(aOffset, eDirPrevious, eSelectBeginLine); + + case eThisLineEnd: + if (IsEmptyLastLineOffset(aOffset)) + return aOffset; + + // Move to end of the current line (as end key was pressed). + return FindOffset(aOffset, eDirNext, eSelectEndLine); + + case eNextLineBegin: { + if (IsEmptyLastLineOffset(aOffset)) + return aOffset; + + // Move to begin of the next line if any (arrow down and home keys), + // otherwise end of the current line (arrow down only). + uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine); + if (tmpOffset == CharacterCount()) + return tmpOffset; + + return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine); + } + + case eNextLineEnd: { + if (IsEmptyLastLineOffset(aOffset)) + return aOffset; + + // Move to next line end (as down arrow and end key were pressed). + uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine); + if (tmpOffset == CharacterCount()) + return tmpOffset; + + return FindOffset(tmpOffset, eDirNext, eSelectEndLine); + } + } + + return 0; +} + +void +HyperTextAccessible::TextBeforeOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, + nsAString& aText) +{ + *aStartOffset = *aEndOffset = 0; + aText.Truncate(); + + index_t convertedOffset = ConvertMagicOffset(aOffset); + if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) { + NS_ERROR("Wrong in offset!"); + return; + } + + uint32_t adjustedOffset = convertedOffset; + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) + adjustedOffset = AdjustCaretOffset(adjustedOffset); + + switch (aBoundaryType) { + case nsIAccessibleText::BOUNDARY_CHAR: + if (convertedOffset != 0) + CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset); + break; + + case nsIAccessibleText::BOUNDARY_WORD_START: { + // If the offset is a word start (except text length offset) then move + // backward to find a start offset (end offset is the given offset). + // Otherwise move backward twice to find both start and end offsets. + if (adjustedOffset == CharacterCount()) { + *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord); + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); + } else { + *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord); + *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord); + if (*aEndOffset != static_cast<int32_t>(adjustedOffset)) { + *aEndOffset = *aStartOffset; + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); + } + } + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + } + + case nsIAccessibleText::BOUNDARY_WORD_END: { + // Move word backward twice to find start and end offsets. + *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord); + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + } + + case nsIAccessibleText::BOUNDARY_LINE_START: + *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin); + *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + + case nsIAccessibleText::BOUNDARY_LINE_END: { + *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd); + int32_t tmpOffset = *aEndOffset; + // Adjust offset if line is wrapped. + if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset)) + tmpOffset--; + + *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + } + } +} + +void +HyperTextAccessible::TextAtOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, + nsAString& aText) +{ + *aStartOffset = *aEndOffset = 0; + aText.Truncate(); + + uint32_t adjustedOffset = ConvertMagicOffset(aOffset); + if (adjustedOffset == std::numeric_limits<uint32_t>::max()) { + NS_ERROR("Wrong given offset!"); + return; + } + + switch (aBoundaryType) { + case nsIAccessibleText::BOUNDARY_CHAR: + // Return no char if caret is at the end of wrapped line (case of no line + // end character). Returning a next line char is confusing for AT. + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine()) + *aStartOffset = *aEndOffset = adjustedOffset; + else + CharAt(adjustedOffset, aText, aStartOffset, aEndOffset); + break; + + case nsIAccessibleText::BOUNDARY_WORD_START: + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) + adjustedOffset = AdjustCaretOffset(adjustedOffset); + + *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord); + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + + case nsIAccessibleText::BOUNDARY_WORD_END: + // Ignore the spec and follow what WebKitGtk does because Orca expects it, + // i.e. return a next word at word end offset of the current word + // (WebKitGtk behavior) instead the current word (AKT spec). + *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord); + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + + case nsIAccessibleText::BOUNDARY_LINE_START: + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) + adjustedOffset = AdjustCaretOffset(adjustedOffset); + + *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin); + *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + + case nsIAccessibleText::BOUNDARY_LINE_END: + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) + adjustedOffset = AdjustCaretOffset(adjustedOffset); + + // In contrast to word end boundary we follow the spec here. + *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd); + *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + } +} + +void +HyperTextAccessible::TextAfterOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, + nsAString& aText) +{ + *aStartOffset = *aEndOffset = 0; + aText.Truncate(); + + index_t convertedOffset = ConvertMagicOffset(aOffset); + if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) { + NS_ERROR("Wrong in offset!"); + return; + } + + uint32_t adjustedOffset = convertedOffset; + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) + adjustedOffset = AdjustCaretOffset(adjustedOffset); + + switch (aBoundaryType) { + case nsIAccessibleText::BOUNDARY_CHAR: + // If caret is at the end of wrapped line (case of no line end character) + // then char after the offset is a first char at next line. + if (adjustedOffset >= CharacterCount()) + *aStartOffset = *aEndOffset = CharacterCount(); + else + CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset); + break; + + case nsIAccessibleText::BOUNDARY_WORD_START: + // Move word forward twice to find start and end offsets. + *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord); + *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + + case nsIAccessibleText::BOUNDARY_WORD_END: + // If the offset is a word end (except 0 offset) then move forward to find + // end offset (start offset is the given offset). Otherwise move forward + // twice to find both start and end offsets. + if (convertedOffset == 0) { + *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord); + *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord); + } else { + *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord); + *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); + if (*aStartOffset != static_cast<int32_t>(convertedOffset)) { + *aStartOffset = *aEndOffset; + *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord); + } + } + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + + case nsIAccessibleText::BOUNDARY_LINE_START: + *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin); + *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + + case nsIAccessibleText::BOUNDARY_LINE_END: + *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd); + *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd); + TextSubstring(*aStartOffset, *aEndOffset, aText); + break; + } +} + +already_AddRefed<nsIPersistentProperties> +HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset, + int32_t* aStartOffset, + int32_t* aEndOffset) +{ + // 1. Get each attribute and its ranges one after another. + // 2. As we get each new attribute, we pass the current start and end offsets + // as in/out parameters. In other words, as attributes are collected, + // the attribute range itself can only stay the same or get smaller. + + *aStartOffset = *aEndOffset = 0; + index_t offset = ConvertMagicOffset(aOffset); + if (!offset.IsValid() || offset > CharacterCount()) { + NS_ERROR("Wrong in offset!"); + return nullptr; + } + + nsCOMPtr<nsIPersistentProperties> attributes = + do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); + + Accessible* accAtOffset = GetChildAtOffset(offset); + if (!accAtOffset) { + // Offset 0 is correct offset when accessible has empty text. Include + // default attributes if they were requested, otherwise return empty set. + if (offset == 0) { + if (aIncludeDefAttrs) { + TextAttrsMgr textAttrsMgr(this); + textAttrsMgr.GetAttributes(attributes); + } + return attributes.forget(); + } + return nullptr; + } + + int32_t accAtOffsetIdx = accAtOffset->IndexInParent(); + uint32_t startOffset = GetChildOffset(accAtOffsetIdx); + uint32_t endOffset = GetChildOffset(accAtOffsetIdx + 1); + int32_t offsetInAcc = offset - startOffset; + + TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset, + accAtOffsetIdx); + textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset); + + // Compute spelling attributes on text accessible only. + nsIFrame *offsetFrame = accAtOffset->GetFrame(); + if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) { + int32_t nodeOffset = 0; + RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset); + + // Set 'misspelled' text attribute. + GetSpellTextAttr(accAtOffset->GetNode(), nodeOffset, + &startOffset, &endOffset, attributes); + } + + *aStartOffset = startOffset; + *aEndOffset = endOffset; + return attributes.forget(); +} + +already_AddRefed<nsIPersistentProperties> +HyperTextAccessible::DefaultTextAttributes() +{ + nsCOMPtr<nsIPersistentProperties> attributes = + do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); + + TextAttrsMgr textAttrsMgr(this); + textAttrsMgr.GetAttributes(attributes); + return attributes.forget(); +} + +int32_t +HyperTextAccessible::GetLevelInternal() +{ + if (mContent->IsHTMLElement(nsGkAtoms::h1)) + return 1; + if (mContent->IsHTMLElement(nsGkAtoms::h2)) + return 2; + if (mContent->IsHTMLElement(nsGkAtoms::h3)) + return 3; + if (mContent->IsHTMLElement(nsGkAtoms::h4)) + return 4; + if (mContent->IsHTMLElement(nsGkAtoms::h5)) + return 5; + if (mContent->IsHTMLElement(nsGkAtoms::h6)) + return 6; + + return AccessibleWrap::GetLevelInternal(); +} + +void +HyperTextAccessible::SetMathMLXMLRoles(nsIPersistentProperties* aAttributes) +{ + // Add MathML xmlroles based on the position inside the parent. + Accessible* parent = Parent(); + if (parent) { + switch (parent->Role()) { + case roles::MATHML_CELL: + case roles::MATHML_ENCLOSED: + case roles::MATHML_ERROR: + case roles::MATHML_MATH: + case roles::MATHML_ROW: + case roles::MATHML_SQUARE_ROOT: + case roles::MATHML_STYLE: + if (Role() == roles::MATHML_OPERATOR) { + // This is an operator inside an <mrow> (or an inferred <mrow>). + // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow + // XXX We should probably do something similar for MATHML_FENCED, but + // operators do not appear in the accessible tree. See bug 1175747. + nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetFrame()); + if (mathMLFrame) { + nsEmbellishData embellishData; + mathMLFrame->GetEmbellishData(embellishData); + if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData.flags)) { + if (!PrevSibling()) { + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + nsGkAtoms::open_fence); + } else if (!NextSibling()) { + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + nsGkAtoms::close_fence); + } + } + if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData.flags)) { + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + nsGkAtoms::separator_); + } + } + } + break; + case roles::MATHML_FRACTION: + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + IndexInParent() == 0 ? + nsGkAtoms::numerator : + nsGkAtoms::denominator); + break; + case roles::MATHML_ROOT: + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + IndexInParent() == 0 ? nsGkAtoms::base : + nsGkAtoms::root_index); + break; + case roles::MATHML_SUB: + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + IndexInParent() == 0 ? nsGkAtoms::base : + nsGkAtoms::subscript); + break; + case roles::MATHML_SUP: + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + IndexInParent() == 0 ? nsGkAtoms::base : + nsGkAtoms::superscript); + break; + case roles::MATHML_SUB_SUP: { + int32_t index = IndexInParent(); + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + index == 0 ? nsGkAtoms::base : + (index == 1 ? nsGkAtoms::subscript : + nsGkAtoms::superscript)); + } break; + case roles::MATHML_UNDER: + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + IndexInParent() == 0 ? nsGkAtoms::base : + nsGkAtoms::underscript); + break; + case roles::MATHML_OVER: + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + IndexInParent() == 0 ? nsGkAtoms::base : + nsGkAtoms::overscript); + break; + case roles::MATHML_UNDER_OVER: { + int32_t index = IndexInParent(); + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + index == 0 ? nsGkAtoms::base : + (index == 1 ? nsGkAtoms::underscript : + nsGkAtoms::overscript)); + } break; + case roles::MATHML_MULTISCRIPTS: { + // Get the <multiscripts> base. + nsIContent* child; + bool baseFound = false; + for (child = parent->GetContent()->GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child->IsMathMLElement()) { + baseFound = true; + break; + } + } + if (baseFound) { + nsIContent* content = GetContent(); + if (child == content) { + // We are the base. + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + nsGkAtoms::base); + } else { + // Browse the list of scripts to find us and determine our type. + bool postscript = true; + bool subscript = true; + for (child = child->GetNextSibling(); child; + child = child->GetNextSibling()) { + if (!child->IsMathMLElement()) + continue; + if (child->IsMathMLElement(nsGkAtoms::mprescripts_)) { + postscript = false; + subscript = true; + continue; + } + if (child == content) { + if (postscript) { + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + subscript ? + nsGkAtoms::subscript : + nsGkAtoms::superscript); + } else { + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles, + subscript ? + nsGkAtoms::presubscript : + nsGkAtoms::presuperscript); + } + break; + } + subscript = !subscript; + } + } + } + } break; + default: + break; + } + } +} + +already_AddRefed<nsIPersistentProperties> +HyperTextAccessible::NativeAttributes() +{ + nsCOMPtr<nsIPersistentProperties> attributes = + AccessibleWrap::NativeAttributes(); + + // 'formatting' attribute is deprecated, 'display' attribute should be + // instead. + nsIFrame *frame = GetFrame(); + if (frame && frame->GetType() == nsGkAtoms::blockFrame) { + nsAutoString unused; + attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"), + NS_LITERAL_STRING("block"), unused); + } + + if (FocusMgr()->IsFocused(this)) { + int32_t lineNumber = CaretLineNumber(); + if (lineNumber >= 1) { + nsAutoString strLineNumber; + strLineNumber.AppendInt(lineNumber); + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber); + } + } + + if (HasOwnContent()) { + GetAccService()->MarkupAttributes(mContent, attributes); + if (mContent->IsMathMLElement()) + SetMathMLXMLRoles(attributes); + } + + return attributes.forget(); +} + +nsIAtom* +HyperTextAccessible::LandmarkRole() const +{ + if (!HasOwnContent()) + return nullptr; + + // For the html landmark elements we expose them like we do ARIA landmarks to + // make AT navigation schemes "just work". + if (mContent->IsHTMLElement(nsGkAtoms::nav)) { + return nsGkAtoms::navigation; + } + + if (mContent->IsAnyOfHTMLElements(nsGkAtoms::header, + nsGkAtoms::footer)) { + // Only map header and footer if they are not descendants of an article + // or section tag. + nsIContent* parent = mContent->GetParent(); + while (parent) { + if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::section)) { + break; + } + parent = parent->GetParent(); + } + + // No article or section elements found. + if (!parent) { + if (mContent->IsHTMLElement(nsGkAtoms::header)) { + return nsGkAtoms::banner; + } + + if (mContent->IsHTMLElement(nsGkAtoms::footer)) { + return nsGkAtoms::contentinfo; + } + } + return nullptr; + } + + if (mContent->IsHTMLElement(nsGkAtoms::aside)) { + return nsGkAtoms::complementary; + } + + if (mContent->IsHTMLElement(nsGkAtoms::main)) { + return nsGkAtoms::main; + } + + return nullptr; +} + +int32_t +HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType) +{ + nsIFrame* hyperFrame = GetFrame(); + if (!hyperFrame) + return -1; + + nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, + this); + + nsPresContext* presContext = mDoc->PresContext(); + nsPoint coordsInAppUnits = + ToAppUnits(coords, presContext->AppUnitsPerDevPixel()); + + nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits(); + if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y)) + return -1; // Not found + + nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x, + coordsInAppUnits.y - frameScreenRect.y); + + // Go through the frames to check if each one has the point. + // When one does, add up the character offsets until we have a match + + // We have an point in an accessible child of this, now we need to add up the + // offsets before it to what we already have + int32_t offset = 0; + uint32_t childCount = ChildCount(); + for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { + Accessible* childAcc = mChildren[childIdx]; + + nsIFrame *primaryFrame = childAcc->GetFrame(); + NS_ENSURE_TRUE(primaryFrame, -1); + + nsIFrame *frame = primaryFrame; + while (frame) { + nsIContent *content = frame->GetContent(); + NS_ENSURE_TRUE(content, -1); + nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame); + nsSize frameSize = frame->GetSize(); + if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) { + // Finished + if (frame->GetType() == nsGkAtoms::textFrame) { + nsIFrame::ContentOffsets contentOffsets = + frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE); + if (contentOffsets.IsNull() || contentOffsets.content != content) { + return -1; // Not found + } + uint32_t addToOffset; + nsresult rv = ContentToRenderedOffset(primaryFrame, + contentOffsets.offset, + &addToOffset); + NS_ENSURE_SUCCESS(rv, -1); + offset += addToOffset; + } + return offset; + } + frame = frame->GetNextContinuation(); + } + + offset += nsAccUtils::TextLength(childAcc); + } + + return -1; // Not found +} + +nsIntRect +HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset, + uint32_t aCoordType) +{ + index_t startOffset = ConvertMagicOffset(aStartOffset); + index_t endOffset = ConvertMagicOffset(aEndOffset); + if (!startOffset.IsValid() || !endOffset.IsValid() || + startOffset > endOffset || endOffset > CharacterCount()) { + NS_ERROR("Wrong in offset"); + return nsIntRect(); + } + + + int32_t childIdx = GetChildIndexAtOffset(startOffset); + if (childIdx == -1) + return nsIntRect(); + + nsIntRect bounds; + int32_t prevOffset = GetChildOffset(childIdx); + int32_t offset1 = startOffset - prevOffset; + + while (childIdx < static_cast<int32_t>(ChildCount())) { + nsIFrame* frame = GetChildAt(childIdx++)->GetFrame(); + if (!frame) { + NS_NOTREACHED("No frame for a child!"); + continue; + } + + int32_t nextOffset = GetChildOffset(childIdx); + if (nextOffset >= static_cast<int32_t>(endOffset)) { + bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1, + endOffset - prevOffset)); + break; + } + + bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1, + nextOffset - prevOffset)); + + prevOffset = nextOffset; + offset1 = 0; + } + + nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this); + return bounds; +} + +already_AddRefed<nsIEditor> +HyperTextAccessible::GetEditor() const +{ + if (!mContent->HasFlag(NODE_IS_EDITABLE)) { + // If we're inside an editable container, then return that container's editor + Accessible* ancestor = Parent(); + while (ancestor) { + HyperTextAccessible* hyperText = ancestor->AsHyperText(); + if (hyperText) { + // Recursion will stop at container doc because it has its own impl + // of GetEditor() + return hyperText->GetEditor(); + } + + ancestor = ancestor->Parent(); + } + + return nullptr; + } + + nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent); + nsCOMPtr<nsIEditingSession> editingSession; + docShell->GetEditingSession(getter_AddRefs(editingSession)); + if (!editingSession) + return nullptr; // No editing session interface + + nsCOMPtr<nsIEditor> editor; + nsIDocument* docNode = mDoc->DocumentNode(); + editingSession->GetEditorForWindow(docNode->GetWindow(), + getter_AddRefs(editor)); + return editor.forget(); +} + +/** + * =================== Caret & Selection ====================== + */ + +nsresult +HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos) +{ + // Before setting the selection range, we need to ensure that the editor + // is initialized. (See bug 804927.) + // Otherwise, it's possible that lazy editor initialization will override + // the selection we set here and leave the caret at the end of the text. + // By calling GetEditor here, we ensure that editor initialization is + // completed before we set the selection. + nsCOMPtr<nsIEditor> editor = GetEditor(); + + bool isFocusable = InteractiveState() & states::FOCUSABLE; + + // If accessible is focusable then focus it before setting the selection to + // neglect control's selection changes on focus if any (for example, inputs + // that do select all on focus). + // some input controls + if (isFocusable) + TakeFocus(); + + dom::Selection* domSel = DOMSelection(); + NS_ENSURE_STATE(domSel); + + // Set up the selection. + for (int32_t idx = domSel->RangeCount() - 1; idx > 0; idx--) + domSel->RemoveRange(domSel->GetRangeAt(idx)); + SetSelectionBoundsAt(0, aStartPos, aEndPos); + + // When selection is done, move the focus to the selection if accessible is + // not focusable. That happens when selection is set within hypertext + // accessible. + if (isFocusable) + return NS_OK; + + nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager(); + if (DOMFocusManager) { + NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE); + nsIDocument* docNode = mDoc->DocumentNode(); + NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> window = docNode->GetWindow(); + nsCOMPtr<nsIDOMElement> result; + DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET, + nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result)); + } + + return NS_OK; +} + +int32_t +HyperTextAccessible::CaretOffset() const +{ + // Not focused focusable accessible except document accessible doesn't have + // a caret. + if (!IsDoc() && !FocusMgr()->IsFocused(this) && + (InteractiveState() & states::FOCUSABLE)) { + return -1; + } + + // Check cached value. + int32_t caretOffset = -1; + HyperTextAccessible* text = SelectionMgr()->AccessibleWithCaret(&caretOffset); + + // Use cached value if it corresponds to this accessible. + if (caretOffset != -1) { + if (text == this) + return caretOffset; + + nsINode* textNode = text->GetNode(); + // Ignore offset if cached accessible isn't a text leaf. + if (nsCoreUtils::IsAncestorOf(GetNode(), textNode)) + return TransformOffset(text, + textNode->IsNodeOfType(nsINode::eTEXT) ? caretOffset : 0, false); + } + + // No caret if the focused node is not inside this DOM node and this DOM node + // is not inside of focused node. + FocusManager::FocusDisposition focusDisp = + FocusMgr()->IsInOrContainsFocus(this); + if (focusDisp == FocusManager::eNone) + return -1; + + // Turn the focus node and offset of the selection into caret hypretext + // offset. + dom::Selection* domSel = DOMSelection(); + NS_ENSURE_TRUE(domSel, -1); + + nsINode* focusNode = domSel->GetFocusNode(); + uint32_t focusOffset = domSel->FocusOffset(); + + // No caret if this DOM node is inside of focused node but the selection's + // focus point is not inside of this DOM node. + if (focusDisp == FocusManager::eContainedByFocus) { + nsINode* resultNode = + nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset); + + nsINode* thisNode = GetNode(); + if (resultNode != thisNode && + !nsCoreUtils::IsAncestorOf(thisNode, resultNode)) + return -1; + } + + return DOMPointToOffset(focusNode, focusOffset); +} + +int32_t +HyperTextAccessible::CaretLineNumber() +{ + // Provide the line number for the caret, relative to the + // currently focused node. Use a 1-based index + RefPtr<nsFrameSelection> frameSelection = FrameSelection(); + if (!frameSelection) + return -1; + + dom::Selection* domSel = frameSelection->GetSelection(SelectionType::eNormal); + if (!domSel) + return - 1; + + nsINode* caretNode = domSel->GetFocusNode(); + if (!caretNode || !caretNode->IsContent()) + return -1; + + nsIContent* caretContent = caretNode->AsContent(); + if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent)) + return -1; + + int32_t returnOffsetUnused; + uint32_t caretOffset = domSel->FocusOffset(); + CaretAssociationHint hint = frameSelection->GetHint(); + nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset, + hint, &returnOffsetUnused); + NS_ENSURE_TRUE(caretFrame, -1); + + int32_t lineNumber = 1; + nsAutoLineIterator lineIterForCaret; + nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr; + while (caretFrame) { + if (hyperTextContent == caretFrame->GetContent()) { + return lineNumber; // Must be in a single line hyper text, there is no line iterator + } + nsContainerFrame *parentFrame = caretFrame->GetParent(); + if (!parentFrame) + break; + + // Add lines for the sibling frames before the caret + nsIFrame *sibling = parentFrame->PrincipalChildList().FirstChild(); + while (sibling && sibling != caretFrame) { + nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator(); + if (lineIterForSibling) { + // For the frames before that grab all the lines + int32_t addLines = lineIterForSibling->GetNumLines(); + lineNumber += addLines; + } + sibling = sibling->GetNextSibling(); + } + + // Get the line number relative to the container with lines + if (!lineIterForCaret) { // Add the caret line just once + lineIterForCaret = parentFrame->GetLineIterator(); + if (lineIterForCaret) { + // Ancestor of caret + int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame); + lineNumber += addLines; + } + } + + caretFrame = parentFrame; + } + + NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't"); + return lineNumber; +} + +LayoutDeviceIntRect +HyperTextAccessible::GetCaretRect(nsIWidget** aWidget) +{ + *aWidget = nullptr; + + RefPtr<nsCaret> caret = mDoc->PresShell()->GetCaret(); + NS_ENSURE_TRUE(caret, LayoutDeviceIntRect()); + + bool isVisible = caret->IsVisible(); + if (!isVisible) + return LayoutDeviceIntRect(); + + nsRect rect; + nsIFrame* frame = caret->GetGeometry(&rect); + if (!frame || rect.IsEmpty()) + return LayoutDeviceIntRect(); + + nsPoint offset; + // Offset from widget origin to the frame origin, which includes chrome + // on the widget. + *aWidget = frame->GetNearestWidget(offset); + NS_ENSURE_TRUE(*aWidget, LayoutDeviceIntRect()); + rect.MoveBy(offset); + + LayoutDeviceIntRect caretRect = LayoutDeviceIntRect::FromUnknownRect( + rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel())); + // ((content screen origin) - (content offset in the widget)) = widget origin on the screen + caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset()); + + // Correct for character size, so that caret always matches the size of + // the character. This is important for font size transitions, and is + // necessary because the Gecko caret uses the previous character's size as + // the user moves forward in the text by character. + nsIntRect charRect = CharBounds(CaretOffset(), + nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE); + if (!charRect.IsEmpty()) { + caretRect.height -= charRect.y - caretRect.y; + caretRect.y = charRect.y; + } + return caretRect; +} + +void +HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType, + nsTArray<nsRange*>* aRanges) +{ + // Ignore selection if it is not visible. + RefPtr<nsFrameSelection> frameSelection = FrameSelection(); + if (!frameSelection || + frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN) + return; + + dom::Selection* domSel = frameSelection->GetSelection(aSelectionType); + if (!domSel) + return; + + nsCOMPtr<nsINode> startNode = GetNode(); + + nsCOMPtr<nsIEditor> editor = GetEditor(); + if (editor) { + nsCOMPtr<nsIDOMElement> editorRoot; + editor->GetRootElement(getter_AddRefs(editorRoot)); + startNode = do_QueryInterface(editorRoot); + } + + if (!startNode) + return; + + uint32_t childCount = startNode->GetChildCount(); + nsresult rv = domSel-> + GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges); + NS_ENSURE_SUCCESS_VOID(rv); + + // Remove collapsed ranges + uint32_t numRanges = aRanges->Length(); + for (uint32_t idx = 0; idx < numRanges; idx ++) { + if ((*aRanges)[idx]->Collapsed()) { + aRanges->RemoveElementAt(idx); + --numRanges; + --idx; + } + } +} + +int32_t +HyperTextAccessible::SelectionCount() +{ + nsTArray<nsRange*> ranges; + GetSelectionDOMRanges(SelectionType::eNormal, &ranges); + return ranges.Length(); +} + +bool +HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum, + int32_t* aStartOffset, + int32_t* aEndOffset) +{ + *aStartOffset = *aEndOffset = 0; + + nsTArray<nsRange*> ranges; + GetSelectionDOMRanges(SelectionType::eNormal, &ranges); + + uint32_t rangeCount = ranges.Length(); + if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(rangeCount)) + return false; + + nsRange* range = ranges[aSelectionNum]; + + // Get start and end points. + nsINode* startNode = range->GetStartParent(); + nsINode* endNode = range->GetEndParent(); + int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset(); + + // Make sure start is before end, by swapping DOM points. This occurs when + // the user selects backwards in the text. + int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset, + startNode, startOffset); + if (rangeCompare < 0) { + nsINode* tempNode = startNode; + startNode = endNode; + endNode = tempNode; + int32_t tempOffset = startOffset; + startOffset = endOffset; + endOffset = tempOffset; + } + + if (!nsContentUtils::ContentIsDescendantOf(startNode, mContent)) + *aStartOffset = 0; + else + *aStartOffset = DOMPointToOffset(startNode, startOffset); + + if (!nsContentUtils::ContentIsDescendantOf(endNode, mContent)) + *aEndOffset = CharacterCount(); + else + *aEndOffset = DOMPointToOffset(endNode, endOffset, true); + return true; +} + +bool +HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum, + int32_t aStartOffset, + int32_t aEndOffset) +{ + index_t startOffset = ConvertMagicOffset(aStartOffset); + index_t endOffset = ConvertMagicOffset(aEndOffset); + if (!startOffset.IsValid() || !endOffset.IsValid() || + startOffset > endOffset || endOffset > CharacterCount()) { + NS_ERROR("Wrong in offset"); + return false; + } + + dom::Selection* domSel = DOMSelection(); + if (!domSel) + return false; + + RefPtr<nsRange> range; + uint32_t rangeCount = domSel->RangeCount(); + if (aSelectionNum == static_cast<int32_t>(rangeCount)) + range = new nsRange(mContent); + else + range = domSel->GetRangeAt(aSelectionNum); + + if (!range) + return false; + + if (!OffsetsToDOMRange(startOffset, endOffset, range)) + return false; + + // If new range was created then add it, otherwise notify selection listeners + // that existing selection range was changed. + if (aSelectionNum == static_cast<int32_t>(rangeCount)) + return NS_SUCCEEDED(domSel->AddRange(range)); + + domSel->RemoveRange(range); + return NS_SUCCEEDED(domSel->AddRange(range)); +} + +bool +HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum) +{ + dom::Selection* domSel = DOMSelection(); + if (!domSel) + return false; + + if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(domSel->RangeCount())) + return false; + + domSel->RemoveRange(domSel->GetRangeAt(aSelectionNum)); + return true; +} + +void +HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset, + uint32_t aScrollType) +{ + RefPtr<nsRange> range = new nsRange(mContent); + if (OffsetsToDOMRange(aStartOffset, aEndOffset, range)) + nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType); +} + +void +HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aCoordinateType, + int32_t aX, int32_t aY) +{ + nsIFrame *frame = GetFrame(); + if (!frame) + return; + + nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, + this); + + RefPtr<nsRange> range = new nsRange(mContent); + if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range)) + return; + + nsPresContext* presContext = frame->PresContext(); + nsPoint coordsInAppUnits = + ToAppUnits(coords, presContext->AppUnitsPerDevPixel()); + + bool initialScrolled = false; + nsIFrame *parentFrame = frame; + while ((parentFrame = parentFrame->GetParent())) { + nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame); + if (scrollableFrame) { + if (!initialScrolled) { + // Scroll substring to the given point. Turn the point into percents + // relative scrollable area to use nsCoreUtils::ScrollSubstringTo. + nsRect frameRect = parentFrame->GetScreenRectInAppUnits(); + nscoord offsetPointX = coordsInAppUnits.x - frameRect.x; + nscoord offsetPointY = coordsInAppUnits.y - frameRect.y; + + nsSize size(parentFrame->GetSize()); + + // avoid divide by zero + size.width = size.width ? size.width : 1; + size.height = size.height ? size.height : 1; + + int16_t hPercent = offsetPointX * 100 / size.width; + int16_t vPercent = offsetPointY * 100 / size.height; + + nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range, + nsIPresShell::ScrollAxis(vPercent), + nsIPresShell::ScrollAxis(hPercent)); + if (NS_FAILED(rv)) + return; + + initialScrolled = true; + } else { + // Substring was scrolled to the given point already inside its closest + // scrollable area. If there are nested scrollable areas then make + // sure we scroll lower areas to the given point inside currently + // traversed scrollable area. + nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords); + } + } + frame = parentFrame; + } +} + +void +HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const +{ + if (IsTextField()) { + aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0, + const_cast<HyperTextAccessible*>(this), CharacterCount()); + } else { + aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->CharacterCount()); + } +} + +void +HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const +{ + MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty"); + + dom::Selection* sel = DOMSelection(); + if (!sel) + return; + + aRanges->SetCapacity(sel->RangeCount()); + + for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) { + nsRange* DOMRange = sel->GetRangeAt(idx); + HyperTextAccessible* startParent = + nsAccUtils::GetTextContainer(DOMRange->GetStartParent()); + HyperTextAccessible* endParent = + nsAccUtils::GetTextContainer(DOMRange->GetEndParent()); + if (!startParent || !endParent) + continue; + + int32_t startOffset = + startParent->DOMPointToOffset(DOMRange->GetStartParent(), + DOMRange->StartOffset(), false); + int32_t endOffset = + endParent->DOMPointToOffset(DOMRange->GetEndParent(), + DOMRange->EndOffset(), true); + + TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc, + startParent, startOffset, endParent, endOffset); + *(aRanges->AppendElement()) = Move(tr); + } +} + +void +HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const +{ +} + +void +HyperTextAccessible::RangeByChild(Accessible* aChild, + a11y::TextRange& aRange) const +{ + HyperTextAccessible* ht = aChild->AsHyperText(); + if (ht) { + aRange.Set(mDoc, ht, 0, ht, ht->CharacterCount()); + return; + } + + Accessible* child = aChild; + Accessible* parent = nullptr; + while ((parent = child->Parent()) && !(ht = parent->AsHyperText())) + child = parent; + + // If no text then return collapsed text range, otherwise return a range + // containing the text enclosed by the given child. + if (ht) { + int32_t childIdx = child->IndexInParent(); + int32_t startOffset = ht->GetChildOffset(childIdx); + int32_t endOffset = child->IsTextLeaf() ? + ht->GetChildOffset(childIdx + 1) : startOffset; + aRange.Set(mDoc, ht, startOffset, ht, endOffset); + } +} + +void +HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY, + a11y::TextRange& aRange) const +{ + Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild); + if (!child) + return; + + Accessible* parent = nullptr; + while ((parent = child->Parent()) && !parent->IsHyperText()) + child = parent; + + // Return collapsed text range for the point. + if (parent) { + HyperTextAccessible* ht = parent->AsHyperText(); + int32_t offset = ht->GetChildOffset(child); + aRange.Set(mDoc, ht, offset, ht, offset); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Accessible public + +// Accessible protected +ENameValueFlag +HyperTextAccessible::NativeName(nsString& aName) +{ + // Check @alt attribute for invalid img elements. + bool hasImgAlt = false; + if (mContent->IsHTMLElement(nsGkAtoms::img)) { + hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName); + if (!aName.IsEmpty()) + return eNameOK; + } + + ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName); + if (!aName.IsEmpty()) + return nameFlag; + + // Get name from title attribute for HTML abbr and acronym elements making it + // a valid name from markup. Otherwise their name isn't picked up by recursive + // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP. + if (IsAbbreviation() && + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) + aName.CompressWhitespace(); + + return hasImgAlt ? eNoNameOnPurpose : eNameOK; +} + +void +HyperTextAccessible::Shutdown() +{ + mOffsets.Clear(); + AccessibleWrap::Shutdown(); +} + +bool +HyperTextAccessible::RemoveChild(Accessible* aAccessible) +{ + int32_t childIndex = aAccessible->IndexInParent(); + int32_t count = mOffsets.Length() - childIndex; + if (count > 0) + mOffsets.RemoveElementsAt(childIndex, count); + + return AccessibleWrap::RemoveChild(aAccessible); +} + +bool +HyperTextAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) +{ + int32_t count = mOffsets.Length() - aIndex; + if (count > 0 ) { + mOffsets.RemoveElementsAt(aIndex, count); + } + return AccessibleWrap::InsertChildAt(aIndex, aChild); +} + +Relation +HyperTextAccessible::RelationByType(RelationType aType) +{ + Relation rel = Accessible::RelationByType(aType); + + switch (aType) { + case RelationType::NODE_CHILD_OF: + if (HasOwnContent() && mContent->IsMathMLElement()) { + Accessible* parent = Parent(); + if (parent) { + nsIContent* parentContent = parent->GetContent(); + if (parentContent && + parentContent->IsMathMLElement(nsGkAtoms::mroot_)) { + // Add a relation pointing to the parent <mroot>. + rel.AppendTarget(parent); + } + } + } + break; + case RelationType::NODE_PARENT_OF: + if (HasOwnContent() && mContent->IsMathMLElement(nsGkAtoms::mroot_)) { + Accessible* base = GetChildAt(0); + Accessible* index = GetChildAt(1); + if (base && index) { + // Append the <mroot> children in the order index, base. + rel.AppendTarget(index); + rel.AppendTarget(base); + } + } + break; + default: + break; + } + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HyperTextAccessible public static + +nsresult +HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset, + uint32_t* aRenderedOffset) const +{ + if (!aFrame) { + // Current frame not rendered -- this can happen if text is set on + // something with display: none + *aRenderedOffset = 0; + return NS_OK; + } + + if (IsTextField()) { + *aRenderedOffset = aContentOffset; + return NS_OK; + } + + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, + "Need text frame for offset conversion"); + NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, + "Call on primary frame only"); + + nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset, + aContentOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, + nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); + *aRenderedOffset = text.mOffsetWithinNodeRenderedText; + + return NS_OK; +} + +nsresult +HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset, + int32_t* aContentOffset) const +{ + if (IsTextField()) { + *aContentOffset = aRenderedOffset; + return NS_OK; + } + + *aContentOffset = 0; + NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE); + + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, + "Need text frame for offset conversion"); + NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, + "Call on primary frame only"); + + nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset, + aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT, + nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); + *aContentOffset = text.mOffsetWithinNodeText; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// HyperTextAccessible public + +int32_t +HyperTextAccessible::GetChildOffset(uint32_t aChildIndex, + bool aInvalidateAfter) const +{ + if (aChildIndex == 0) { + if (aInvalidateAfter) + mOffsets.Clear(); + + return aChildIndex; + } + + int32_t count = mOffsets.Length() - aChildIndex; + if (count > 0) { + if (aInvalidateAfter) + mOffsets.RemoveElementsAt(aChildIndex, count); + + return mOffsets[aChildIndex - 1]; + } + + uint32_t lastOffset = mOffsets.IsEmpty() ? + 0 : mOffsets[mOffsets.Length() - 1]; + + while (mOffsets.Length() < aChildIndex) { + Accessible* child = mChildren[mOffsets.Length()]; + lastOffset += nsAccUtils::TextLength(child); + mOffsets.AppendElement(lastOffset); + } + + return mOffsets[aChildIndex - 1]; +} + +int32_t +HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const +{ + uint32_t lastOffset = 0; + const uint32_t offsetCount = mOffsets.Length(); + + if (offsetCount > 0) { + lastOffset = mOffsets[offsetCount - 1]; + if (aOffset < lastOffset) { + size_t index; + if (BinarySearch(mOffsets, 0, offsetCount, aOffset, &index)) { + return (index < (offsetCount - 1)) ? index + 1 : index; + } + + return (index == offsetCount) ? -1 : index; + } + } + + uint32_t childCount = ChildCount(); + while (mOffsets.Length() < childCount) { + Accessible* child = GetChildAt(mOffsets.Length()); + lastOffset += nsAccUtils::TextLength(child); + mOffsets.AppendElement(lastOffset); + if (aOffset < lastOffset) + return mOffsets.Length() - 1; + } + + if (aOffset == lastOffset) + return mOffsets.Length() - 1; + + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +// HyperTextAccessible protected + +nsresult +HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset, + Accessible* aAccessible, + DOMPoint* aPoint) +{ + NS_ENSURE_ARG(aAccessible); + + if (!aFrame) { + // If the given frame is null then set offset after the DOM node of the + // given accessible. + NS_ASSERTION(!aAccessible->IsDoc(), + "Shouldn't be called on document accessible!"); + + nsIContent* content = aAccessible->GetContent(); + NS_ASSERTION(content, "Shouldn't operate on defunct accessible!"); + + nsIContent* parent = content->GetParent(); + + aPoint->idx = parent->IndexOf(content) + 1; + aPoint->node = parent; + + } else if (aFrame->GetType() == nsGkAtoms::textFrame) { + nsIContent* content = aFrame->GetContent(); + NS_ENSURE_STATE(content); + + nsIFrame *primaryFrame = content->GetPrimaryFrame(); + nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx)); + NS_ENSURE_SUCCESS(rv, rv); + + aPoint->node = content; + + } else { + nsIContent* content = aFrame->GetContent(); + NS_ENSURE_STATE(content); + + nsIContent* parent = content->GetParent(); + NS_ENSURE_STATE(parent); + + aPoint->idx = parent->IndexOf(content); + aPoint->node = parent; + } + + return NS_OK; +} + +// HyperTextAccessible +void +HyperTextAccessible::GetSpellTextAttr(nsINode* aNode, + int32_t aNodeOffset, + uint32_t* aStartOffset, + uint32_t* aEndOffset, + nsIPersistentProperties* aAttributes) +{ + RefPtr<nsFrameSelection> fs = FrameSelection(); + if (!fs) + return; + + dom::Selection* domSel = fs->GetSelection(SelectionType::eSpellCheck); + if (!domSel) + return; + + int32_t rangeCount = domSel->RangeCount(); + if (rangeCount <= 0) + return; + + uint32_t startOffset = 0, endOffset = 0; + for (int32_t idx = 0; idx < rangeCount; idx++) { + nsRange* range = domSel->GetRangeAt(idx); + if (range->Collapsed()) + continue; + + // See if the point comes after the range in which case we must continue in + // case there is another range after this one. + nsINode* endNode = range->GetEndParent(); + int32_t endNodeOffset = range->EndOffset(); + if (nsContentUtils::ComparePoints(aNode, aNodeOffset, + endNode, endNodeOffset) >= 0) + continue; + + // At this point our point is either in this range or before it but after + // the previous range. So we check to see if the range starts before the + // point in which case the point is in the missspelled range, otherwise it + // must be before the range and after the previous one if any. + nsINode* startNode = range->GetStartParent(); + int32_t startNodeOffset = range->StartOffset(); + if (nsContentUtils::ComparePoints(startNode, startNodeOffset, aNode, + aNodeOffset) <= 0) { + startOffset = DOMPointToOffset(startNode, startNodeOffset); + + endOffset = DOMPointToOffset(endNode, endNodeOffset); + + if (startOffset > *aStartOffset) + *aStartOffset = startOffset; + + if (endOffset < *aEndOffset) + *aEndOffset = endOffset; + + if (aAttributes) { + nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid, + NS_LITERAL_STRING("spelling")); + } + + return; + } + + // This range came after the point. + endOffset = DOMPointToOffset(startNode, startNodeOffset); + + if (idx > 0) { + nsRange* prevRange = domSel->GetRangeAt(idx - 1); + startOffset = DOMPointToOffset(prevRange->GetEndParent(), + prevRange->EndOffset()); + } + + if (startOffset > *aStartOffset) + *aStartOffset = startOffset; + + if (endOffset < *aEndOffset) + *aEndOffset = endOffset; + + return; + } + + // We never found a range that ended after the point, therefore we know that + // the point is not in a range, that we do not need to compute an end offset, + // and that we should use the end offset of the last range to compute the + // start offset of the text attribute range. + nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1); + startOffset = DOMPointToOffset(prevRange->GetEndParent(), + prevRange->EndOffset()); + + if (startOffset > *aStartOffset) + *aStartOffset = startOffset; +} + +bool +HyperTextAccessible::IsTextRole() +{ + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry && + (roleMapEntry->role == roles::GRAPHIC || + roleMapEntry->role == roles::IMAGE_MAP || + roleMapEntry->role == roles::SLIDER || + roleMapEntry->role == roles::PROGRESSBAR || + roleMapEntry->role == roles::SEPARATOR)) + return false; + + return true; +} |