summaryrefslogtreecommitdiffstats
path: root/accessible/generic/HyperTextAccessible.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/generic/HyperTextAccessible.cpp')
-rw-r--r--accessible/generic/HyperTextAccessible.cpp2230
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;
+}