michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "HyperTextAccessible-inl.h" michael@0: michael@0: #include "Accessible-inl.h" michael@0: #include "nsAccessibilityService.h" michael@0: #include "nsIAccessibleTypes.h" michael@0: #include "DocAccessible.h" michael@0: #include "HTMLListAccessible.h" michael@0: #include "Role.h" michael@0: #include "States.h" michael@0: #include "TextAttrs.h" michael@0: #include "TextRange.h" michael@0: #include "TreeWalker.h" michael@0: michael@0: #include "nsCaret.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIEditingSession.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsFrameSelection.h" michael@0: #include "nsILineIterator.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIPersistentProperties2.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsITextControlElement.h" michael@0: #include "nsTextFragment.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "gfxSkipChars.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::a11y; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HyperTextAccessible michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: HyperTextAccessible:: michael@0: HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) : michael@0: AccessibleWrap(aNode, aDoc), xpcAccessibleHyperText() michael@0: { michael@0: mGenericTypes |= eHyperText; michael@0: } michael@0: michael@0: nsresult michael@0: HyperTextAccessible::QueryInterface(REFNSIID aIID, void** aInstancePtr) michael@0: { michael@0: xpcAccessibleHyperText::QueryInterface(aIID, aInstancePtr); michael@0: return *aInstancePtr ? NS_OK : Accessible::QueryInterface(aIID, aInstancePtr); michael@0: } michael@0: NS_IMPL_ADDREF_INHERITED(HyperTextAccessible, AccessibleWrap) michael@0: NS_IMPL_RELEASE_INHERITED(HyperTextAccessible, AccessibleWrap) michael@0: michael@0: role michael@0: HyperTextAccessible::NativeRole() michael@0: { michael@0: nsIAtom *tag = mContent->Tag(); michael@0: michael@0: if (tag == nsGkAtoms::dd) michael@0: return roles::DEFINITION; michael@0: michael@0: if (tag == nsGkAtoms::form) michael@0: return roles::FORM; michael@0: michael@0: if (tag == nsGkAtoms::blockquote || tag == nsGkAtoms::div || michael@0: tag == nsGkAtoms::section || tag == nsGkAtoms::nav) michael@0: return roles::SECTION; michael@0: michael@0: if (tag == nsGkAtoms::h1 || tag == nsGkAtoms::h2 || michael@0: tag == nsGkAtoms::h3 || tag == nsGkAtoms::h4 || michael@0: tag == nsGkAtoms::h5 || tag == nsGkAtoms::h6) michael@0: return roles::HEADING; michael@0: michael@0: if (tag == nsGkAtoms::article) michael@0: return roles::DOCUMENT; michael@0: michael@0: // Deal with html landmark elements michael@0: if (tag == nsGkAtoms::header) michael@0: return roles::HEADER; michael@0: michael@0: if (tag == nsGkAtoms::footer) michael@0: return roles::FOOTER; michael@0: michael@0: if (tag == nsGkAtoms::aside) michael@0: return roles::NOTE; michael@0: michael@0: // Treat block frames as paragraphs michael@0: nsIFrame *frame = GetFrame(); michael@0: if (frame && frame->GetType() == nsGkAtoms::blockFrame) michael@0: return roles::PARAGRAPH; michael@0: michael@0: return roles::TEXT_CONTAINER; // In ATK this works michael@0: } michael@0: michael@0: uint64_t michael@0: HyperTextAccessible::NativeState() michael@0: { michael@0: uint64_t states = AccessibleWrap::NativeState(); michael@0: michael@0: if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) { michael@0: states |= states::EDITABLE; michael@0: michael@0: } else if (mContent->Tag() == nsGkAtoms::article) { michael@0: // We want
to behave like a document in terms of readonly state. michael@0: states |= states::READONLY; michael@0: } michael@0: michael@0: if (HasChildren()) michael@0: states |= states::SELECTABLE_TEXT; michael@0: michael@0: return states; michael@0: } michael@0: michael@0: nsIntRect michael@0: HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame, michael@0: uint32_t aStartRenderedOffset, michael@0: uint32_t aEndRenderedOffset) michael@0: { michael@0: nsPresContext* presContext = mDoc->PresContext(); michael@0: if (aFrame->GetType() != nsGkAtoms::textFrame) { michael@0: return aFrame->GetScreenRectInAppUnits(). michael@0: ToNearestPixels(presContext->AppUnitsPerDevPixel()); michael@0: } michael@0: michael@0: // Substring must be entirely within the same text node. michael@0: int32_t startContentOffset, endContentOffset; michael@0: nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset); michael@0: NS_ENSURE_SUCCESS(rv, nsIntRect()); michael@0: rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset); michael@0: NS_ENSURE_SUCCESS(rv, nsIntRect()); michael@0: michael@0: nsIFrame *frame; michael@0: int32_t startContentOffsetInFrame; michael@0: // Get the right frame continuation -- not really a child, but a sibling of michael@0: // the primary frame passed in michael@0: rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false, michael@0: &startContentOffsetInFrame, &frame); michael@0: NS_ENSURE_SUCCESS(rv, nsIntRect()); michael@0: michael@0: nsRect screenRect; michael@0: while (frame && startContentOffset < endContentOffset) { michael@0: // Start with this frame's screen rect, which we will michael@0: // shrink based on the substring we care about within it. michael@0: // We will then add that frame to the total screenRect we michael@0: // are returning. michael@0: nsRect frameScreenRect = frame->GetScreenRectInAppUnits(); michael@0: michael@0: // Get the length of the substring in this frame that we want the bounds for michael@0: int32_t startFrameTextOffset, endFrameTextOffset; michael@0: frame->GetOffsets(startFrameTextOffset, endFrameTextOffset); michael@0: int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset; michael@0: int32_t seekLength = endContentOffset - startContentOffset; michael@0: int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength); michael@0: michael@0: // Add the point where the string starts to the frameScreenRect michael@0: nsPoint frameTextStartPoint; michael@0: rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint); michael@0: NS_ENSURE_SUCCESS(rv, nsIntRect()); michael@0: michael@0: // Use the point for the end offset to calculate the width michael@0: nsPoint frameTextEndPoint; michael@0: rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint); michael@0: NS_ENSURE_SUCCESS(rv, nsIntRect()); michael@0: michael@0: frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x); michael@0: frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x); michael@0: michael@0: screenRect.UnionRect(frameScreenRect, screenRect); michael@0: michael@0: // Get ready to loop back for next frame continuation michael@0: startContentOffset += frameSubStringLength; michael@0: startContentOffsetInFrame = 0; michael@0: frame = frame->GetNextContinuation(); michael@0: } michael@0: michael@0: return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel()); michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset, michael@0: nsAString& aText) michael@0: { michael@0: aText.Truncate(); michael@0: michael@0: int32_t startOffset = ConvertMagicOffset(aStartOffset); michael@0: int32_t endOffset = ConvertMagicOffset(aEndOffset); michael@0: michael@0: int32_t startChildIdx = GetChildIndexAtOffset(startOffset); michael@0: if (startChildIdx == -1) michael@0: return; michael@0: michael@0: int32_t endChildIdx = GetChildIndexAtOffset(endOffset); michael@0: if (endChildIdx == -1) michael@0: return; michael@0: michael@0: if (startChildIdx == endChildIdx) { michael@0: int32_t childOffset = GetChildOffset(startChildIdx); michael@0: if (childOffset == -1) michael@0: return; michael@0: michael@0: Accessible* child = GetChildAt(startChildIdx); michael@0: child->AppendTextTo(aText, startOffset - childOffset, michael@0: endOffset - startOffset); michael@0: return; michael@0: } michael@0: michael@0: int32_t startChildOffset = GetChildOffset(startChildIdx); michael@0: if (startChildOffset == -1) michael@0: return; michael@0: michael@0: Accessible* startChild = GetChildAt(startChildIdx); michael@0: startChild->AppendTextTo(aText, startOffset - startChildOffset); michael@0: michael@0: for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) { michael@0: Accessible* child = GetChildAt(childIdx); michael@0: child->AppendTextTo(aText); michael@0: } michael@0: michael@0: int32_t endChildOffset = GetChildOffset(endChildIdx); michael@0: if (endChildOffset == -1) michael@0: return; michael@0: michael@0: Accessible* endChild = GetChildAt(endChildIdx); michael@0: endChild->AppendTextTo(aText, 0, endOffset - endChildOffset); michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset, michael@0: bool aIsEndOffset) const michael@0: { michael@0: if (!aNode) michael@0: return 0; michael@0: michael@0: uint32_t offset = 0; michael@0: nsINode* findNode = nullptr; michael@0: michael@0: if (aNodeOffset == -1) { michael@0: findNode = aNode; michael@0: michael@0: } else if (aNode->IsNodeOfType(nsINode::eTEXT)) { michael@0: // For text nodes, aNodeOffset comes in as a character offset michael@0: // Text offset will be added at the end, if we find the offset in this hypertext michael@0: // We want the "skipped" offset into the text (rendered text without the extra whitespace) michael@0: nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame(); michael@0: NS_ENSURE_TRUE(frame, 0); michael@0: michael@0: nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset); michael@0: NS_ENSURE_SUCCESS(rv, 0); michael@0: // Get the child node and michael@0: findNode = aNode; michael@0: michael@0: } else { michael@0: // findNode could be null if aNodeOffset == # of child nodes, which means michael@0: // one of two things: michael@0: // 1) there are no children, and the passed-in node is not mContent -- use michael@0: // parentContent for the node to find michael@0: // 2) there are no children and the passed-in node is mContent, which means michael@0: // we're an empty nsIAccessibleText michael@0: // 3) there are children and we're at the end of the children michael@0: michael@0: findNode = aNode->GetChildAt(aNodeOffset); michael@0: if (!findNode) { michael@0: if (aNodeOffset == 0) { michael@0: if (aNode == GetNode()) { michael@0: // Case #1: this accessible has no children and thus has empty text, michael@0: // we can only be at hypertext offset 0. michael@0: return 0; michael@0: } michael@0: michael@0: // Case #2: there are no children, we're at this node. michael@0: findNode = aNode; michael@0: } else if (aNodeOffset == aNode->GetChildCount()) { michael@0: // Case #3: we're after the last child, get next node to this one. michael@0: for (nsINode* tmpNode = aNode; michael@0: !findNode && tmpNode && tmpNode != mContent; michael@0: tmpNode = tmpNode->GetParent()) { michael@0: findNode = tmpNode->GetNextSibling(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Get accessible for this findNode, or if that node isn't accessible, use the michael@0: // accessible for the next DOM node which has one (based on forward depth first search) michael@0: Accessible* descendant = nullptr; michael@0: if (findNode) { michael@0: nsCOMPtr findContent(do_QueryInterface(findNode)); michael@0: if (findContent && findContent->IsHTML() && michael@0: findContent->NodeInfo()->Equals(nsGkAtoms::br) && michael@0: findContent->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::mozeditorbogusnode, michael@0: nsGkAtoms::_true, michael@0: eIgnoreCase)) { michael@0: // This
is the hacky "bogus node" used when there is no text in a control michael@0: return 0; michael@0: } michael@0: michael@0: descendant = mDoc->GetAccessible(findNode); michael@0: if (!descendant && findNode->IsContent()) { michael@0: Accessible* container = mDoc->GetContainerAccessible(findNode); michael@0: if (container) { michael@0: TreeWalker walker(container, findNode->AsContent(), michael@0: TreeWalker::eWalkContextTree); michael@0: descendant = walker.NextChild(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return TransformOffset(descendant, offset, aIsEndOffset); michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::TransformOffset(Accessible* aDescendant, michael@0: int32_t aOffset, bool aIsEndOffset) const michael@0: { michael@0: // From the descendant, go up and get the immediate child of this hypertext. michael@0: int32_t offset = aOffset; michael@0: Accessible* descendant = aDescendant; michael@0: while (descendant) { michael@0: Accessible* parent = descendant->Parent(); michael@0: if (parent == this) michael@0: return GetChildOffset(descendant) + offset; michael@0: michael@0: // This offset no longer applies because the passed-in text object is not michael@0: // a child of the hypertext. This happens when there are nested hypertexts, michael@0: // e.g.
abc

def

ghi
. Thus we need to adjust the offset michael@0: // to make it relative the hypertext. michael@0: // If the end offset is not supposed to be inclusive and the original point michael@0: // is not at 0 offset then the returned offset should be after an embedded michael@0: // character the original point belongs to. michael@0: if (aIsEndOffset) michael@0: offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0; michael@0: else michael@0: offset = 0; michael@0: michael@0: descendant = parent; michael@0: } michael@0: michael@0: // If the given a11y point cannot be mapped into offset relative this hypertext michael@0: // offset then return length as fallback value. michael@0: return CharacterCount(); michael@0: } michael@0: michael@0: bool michael@0: HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset, michael@0: nsRange* aRange) michael@0: { michael@0: DOMPoint startPoint = OffsetToDOMPoint(aStartOffset); michael@0: if (!startPoint.node) michael@0: return false; michael@0: michael@0: aRange->SetStart(startPoint.node, startPoint.idx); michael@0: if (aStartOffset == aEndOffset) { michael@0: aRange->SetEnd(startPoint.node, startPoint.idx); michael@0: return true; michael@0: } michael@0: michael@0: DOMPoint endPoint = OffsetToDOMPoint(aEndOffset); michael@0: if (!endPoint.node) michael@0: return false; michael@0: michael@0: aRange->SetEnd(endPoint.node, endPoint.idx); michael@0: return true; michael@0: } michael@0: michael@0: DOMPoint michael@0: HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset) michael@0: { michael@0: // 0 offset is valid even if no children. In this case the associated editor michael@0: // is empty so return a DOM point for editor root element. michael@0: if (aOffset == 0) { michael@0: nsCOMPtr editor = GetEditor(); michael@0: if (editor) { michael@0: bool isEmpty = false; michael@0: editor->GetDocumentIsEmpty(&isEmpty); michael@0: if (isEmpty) { michael@0: nsCOMPtr editorRootElm; michael@0: editor->GetRootElement(getter_AddRefs(editorRootElm)); michael@0: michael@0: nsCOMPtr editorRoot(do_QueryInterface(editorRootElm)); michael@0: return DOMPoint(editorRoot, 0); michael@0: } michael@0: } michael@0: } michael@0: michael@0: int32_t childIdx = GetChildIndexAtOffset(aOffset); michael@0: if (childIdx == -1) michael@0: return DOMPoint(); michael@0: michael@0: Accessible* child = GetChildAt(childIdx); michael@0: int32_t innerOffset = aOffset - GetChildOffset(childIdx); michael@0: michael@0: // A text leaf case. The point is inside the text node. michael@0: if (child->IsTextLeaf()) { michael@0: nsIContent* content = child->GetContent(); michael@0: int32_t idx = 0; michael@0: if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(), michael@0: innerOffset, &idx))) michael@0: return DOMPoint(); michael@0: michael@0: return DOMPoint(content, idx); michael@0: } michael@0: michael@0: // Case of embedded object. The point is either before or after the element. michael@0: NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!"); michael@0: nsINode* node = child->GetNode(); michael@0: nsINode* parentNode = node->GetParentNode(); michael@0: return parentNode ? michael@0: DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) : michael@0: DOMPoint(); michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::FindOffset(int32_t aOffset, nsDirection aDirection, michael@0: nsSelectionAmount aAmount, michael@0: EWordMovementType aWordMovementType) michael@0: { michael@0: // Find a leaf accessible frame to start with. PeekOffset wants this. michael@0: HyperTextAccessible* text = this; michael@0: Accessible* child = nullptr; michael@0: int32_t innerOffset = aOffset; michael@0: michael@0: do { michael@0: int32_t childIdx = text->GetChildIndexAtOffset(innerOffset); michael@0: NS_ASSERTION(childIdx != -1, "Bad in offset!"); michael@0: if (childIdx == -1) michael@0: return -1; michael@0: michael@0: child = text->GetChildAt(childIdx); michael@0: michael@0: // HTML list items may need special processing because PeekOffset doesn't michael@0: // work with list bullets. michael@0: if (text->IsHTMLListItem()) { michael@0: HTMLLIAccessible* li = text->AsHTMLListItem(); michael@0: if (child == li->Bullet()) { michael@0: // It works only when the bullet is one single char. michael@0: if (aDirection == eDirPrevious) michael@0: return text != this ? TransformOffset(text, 0, false) : 0; michael@0: michael@0: if (aAmount == eSelectEndLine || aAmount == eSelectLine) { michael@0: if (text != this) michael@0: return TransformOffset(text, 1, true); michael@0: michael@0: // Ask a text leaf next (if not empty) to the bullet for an offset michael@0: // since list item may be multiline. michael@0: return aOffset + 1 < CharacterCount() ? michael@0: FindOffset(aOffset + 1, aDirection, aAmount, aWordMovementType) : 1; michael@0: } michael@0: michael@0: // Case of word and char boundaries. michael@0: return text != this ? TransformOffset(text, 1, true) : 1; michael@0: } michael@0: } michael@0: michael@0: innerOffset -= text->GetChildOffset(childIdx); michael@0: michael@0: text = child->AsHyperText(); michael@0: } while (text); michael@0: michael@0: nsIFrame* childFrame = child->GetFrame(); michael@0: NS_ENSURE_TRUE(childFrame, -1); michael@0: michael@0: int32_t innerContentOffset = innerOffset; michael@0: if (child->IsTextLeaf()) { michael@0: NS_ASSERTION(childFrame->GetType() == nsGkAtoms::textFrame, "Wrong frame!"); michael@0: RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset); michael@0: } michael@0: michael@0: nsIFrame* frameAtOffset = childFrame; michael@0: int32_t unusedOffsetInFrame = 0; michael@0: childFrame->GetChildFrameContainingOffset(innerContentOffset, true, michael@0: &unusedOffsetInFrame, michael@0: &frameAtOffset); michael@0: michael@0: const bool kIsJumpLinesOk = true; // okay to jump lines michael@0: const bool kIsScrollViewAStop = false; // do not stop at scroll views michael@0: const bool kIsKeyboardSelect = true; // is keyboard selection michael@0: const bool kIsVisualBidi = false; // use visual order for bidi text michael@0: nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset, michael@0: 0, kIsJumpLinesOk, kIsScrollViewAStop, michael@0: kIsKeyboardSelect, kIsVisualBidi, michael@0: aWordMovementType); michael@0: nsresult rv = frameAtOffset->PeekOffset(&pos); michael@0: michael@0: // PeekOffset fails on last/first lines of the text in certain cases. michael@0: if (NS_FAILED(rv) && aAmount == eSelectLine) { michael@0: pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine; michael@0: frameAtOffset->PeekOffset(&pos); michael@0: } michael@0: if (!pos.mResultContent) michael@0: return -1; michael@0: michael@0: // Turn the resulting DOM point into an offset. michael@0: int32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent, michael@0: pos.mContentOffset, michael@0: aDirection == eDirNext); michael@0: michael@0: if (aDirection == eDirPrevious) { michael@0: // If we reached the end during search, this means we didn't find the DOM point michael@0: // and we're actually at the start of the paragraph michael@0: if (hyperTextOffset == CharacterCount()) michael@0: return 0; michael@0: michael@0: // PeekOffset stops right before bullet so return 0 to workaround it. michael@0: if (IsHTMLListItem() && aAmount == eSelectBeginLine && hyperTextOffset == 1) michael@0: return 0; michael@0: } michael@0: michael@0: return hyperTextOffset; michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::FindLineBoundary(int32_t aOffset, michael@0: EWhichLineBoundary aWhichLineBoundary) michael@0: { michael@0: // Note: empty last line doesn't have own frame (a previous line contains '\n' michael@0: // character instead) thus when it makes a difference we need to process this michael@0: // case separately (otherwise operations are performed on previous line). michael@0: switch (aWhichLineBoundary) { michael@0: case ePrevLineBegin: { michael@0: // Fetch a previous line and move to its start (as arrow up and home keys michael@0: // were pressed). michael@0: if (IsEmptyLastLineOffset(aOffset)) michael@0: return FindOffset(aOffset, eDirPrevious, eSelectBeginLine); michael@0: michael@0: int32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine); michael@0: return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine); michael@0: } michael@0: michael@0: case ePrevLineEnd: { michael@0: if (IsEmptyLastLineOffset(aOffset)) michael@0: return aOffset - 1; michael@0: michael@0: // If offset is at first line then return 0 (first line start). michael@0: int32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine); michael@0: if (tmpOffset == 0) michael@0: return 0; michael@0: michael@0: // Otherwise move to end of previous line (as arrow up and end keys were michael@0: // pressed). michael@0: tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine); michael@0: return FindOffset(tmpOffset, eDirNext, eSelectEndLine); michael@0: } michael@0: michael@0: case eThisLineBegin: michael@0: if (IsEmptyLastLineOffset(aOffset)) michael@0: return aOffset; michael@0: michael@0: // Move to begin of the current line (as home key was pressed). michael@0: return FindOffset(aOffset, eDirPrevious, eSelectBeginLine); michael@0: michael@0: case eThisLineEnd: michael@0: if (IsEmptyLastLineOffset(aOffset)) michael@0: return aOffset; michael@0: michael@0: // Move to end of the current line (as end key was pressed). michael@0: return FindOffset(aOffset, eDirNext, eSelectEndLine); michael@0: michael@0: case eNextLineBegin: { michael@0: if (IsEmptyLastLineOffset(aOffset)) michael@0: return aOffset; michael@0: michael@0: // Move to begin of the next line if any (arrow down and home keys), michael@0: // otherwise end of the current line (arrow down only). michael@0: int32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine); michael@0: if (tmpOffset == CharacterCount()) michael@0: return tmpOffset; michael@0: michael@0: return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine); michael@0: } michael@0: michael@0: case eNextLineEnd: { michael@0: if (IsEmptyLastLineOffset(aOffset)) michael@0: return aOffset; michael@0: michael@0: // Move to next line end (as down arrow and end key were pressed). michael@0: int32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine); michael@0: if (tmpOffset != CharacterCount()) michael@0: return FindOffset(tmpOffset, eDirNext, eSelectEndLine); michael@0: return tmpOffset; michael@0: } michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::TextBeforeOffset(int32_t aOffset, michael@0: AccessibleTextBoundary aBoundaryType, michael@0: int32_t* aStartOffset, int32_t* aEndOffset, michael@0: nsAString& aText) michael@0: { michael@0: *aStartOffset = *aEndOffset = 0; michael@0: aText.Truncate(); michael@0: michael@0: int32_t convertedOffset = ConvertMagicOffset(aOffset); michael@0: if (convertedOffset < 0) { michael@0: NS_ERROR("Wrong given offset!"); michael@0: return; michael@0: } michael@0: michael@0: int32_t adjustedOffset = convertedOffset; michael@0: if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) michael@0: adjustedOffset = AdjustCaretOffset(adjustedOffset); michael@0: michael@0: switch (aBoundaryType) { michael@0: case BOUNDARY_CHAR: michael@0: if (convertedOffset != 0) michael@0: CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset); michael@0: break; michael@0: michael@0: case BOUNDARY_WORD_START: { michael@0: // If the offset is a word start (except text length offset) then move michael@0: // backward to find a start offset (end offset is the given offset). michael@0: // Otherwise move backward twice to find both start and end offsets. michael@0: if (adjustedOffset == CharacterCount()) { michael@0: *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord); michael@0: *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); michael@0: } else { michael@0: *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord); michael@0: *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord); michael@0: if (*aEndOffset != adjustedOffset) { michael@0: *aEndOffset = *aStartOffset; michael@0: *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); michael@0: } michael@0: } michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: } michael@0: michael@0: case BOUNDARY_WORD_END: { michael@0: // Move word backward twice to find start and end offsets. michael@0: *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord); michael@0: *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: } michael@0: michael@0: case BOUNDARY_LINE_START: michael@0: *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin); michael@0: *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: michael@0: case BOUNDARY_LINE_END: { michael@0: *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd); michael@0: int32_t tmpOffset = *aEndOffset; michael@0: // Adjust offset if line is wrapped. michael@0: if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset)) michael@0: tmpOffset--; michael@0: michael@0: *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::TextAtOffset(int32_t aOffset, michael@0: AccessibleTextBoundary aBoundaryType, michael@0: int32_t* aStartOffset, int32_t* aEndOffset, michael@0: nsAString& aText) michael@0: { michael@0: *aStartOffset = *aEndOffset = 0; michael@0: aText.Truncate(); michael@0: michael@0: int32_t adjustedOffset = ConvertMagicOffset(aOffset); michael@0: if (adjustedOffset < 0) { michael@0: NS_ERROR("Wrong given offset!"); michael@0: return; michael@0: } michael@0: michael@0: switch (aBoundaryType) { michael@0: case BOUNDARY_CHAR: michael@0: // Return no char if caret is at the end of wrapped line (case of no line michael@0: // end character). Returning a next line char is confusing for AT. michael@0: if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine()) michael@0: *aStartOffset = *aEndOffset = adjustedOffset; michael@0: else michael@0: CharAt(adjustedOffset, aText, aStartOffset, aEndOffset); michael@0: break; michael@0: michael@0: case BOUNDARY_WORD_START: michael@0: if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) michael@0: adjustedOffset = AdjustCaretOffset(adjustedOffset); michael@0: michael@0: *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord); michael@0: *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: michael@0: case BOUNDARY_WORD_END: michael@0: // Ignore the spec and follow what WebKitGtk does because Orca expects it, michael@0: // i.e. return a next word at word end offset of the current word michael@0: // (WebKitGtk behavior) instead the current word (AKT spec). michael@0: *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord); michael@0: *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: michael@0: case BOUNDARY_LINE_START: michael@0: if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) michael@0: adjustedOffset = AdjustCaretOffset(adjustedOffset); michael@0: michael@0: *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin); michael@0: *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: michael@0: case BOUNDARY_LINE_END: michael@0: if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) michael@0: adjustedOffset = AdjustCaretOffset(adjustedOffset); michael@0: michael@0: // In contrast to word end boundary we follow the spec here. michael@0: *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd); michael@0: *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::TextAfterOffset(int32_t aOffset, michael@0: AccessibleTextBoundary aBoundaryType, michael@0: int32_t* aStartOffset, int32_t* aEndOffset, michael@0: nsAString& aText) michael@0: { michael@0: *aStartOffset = *aEndOffset = 0; michael@0: aText.Truncate(); michael@0: michael@0: int32_t convertedOffset = ConvertMagicOffset(aOffset); michael@0: if (convertedOffset < 0) { michael@0: NS_ERROR("Wrong given offset!"); michael@0: return; michael@0: } michael@0: michael@0: int32_t adjustedOffset = convertedOffset; michael@0: if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) michael@0: adjustedOffset = AdjustCaretOffset(adjustedOffset); michael@0: michael@0: switch (aBoundaryType) { michael@0: case BOUNDARY_CHAR: michael@0: // If caret is at the end of wrapped line (case of no line end character) michael@0: // then char after the offset is a first char at next line. michael@0: if (adjustedOffset >= CharacterCount()) michael@0: *aStartOffset = *aEndOffset = CharacterCount(); michael@0: else michael@0: CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset); michael@0: break; michael@0: michael@0: case BOUNDARY_WORD_START: michael@0: // Move word forward twice to find start and end offsets. michael@0: *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord); michael@0: *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: michael@0: case BOUNDARY_WORD_END: michael@0: // If the offset is a word end (except 0 offset) then move forward to find michael@0: // end offset (start offset is the given offset). Otherwise move forward michael@0: // twice to find both start and end offsets. michael@0: if (convertedOffset == 0) { michael@0: *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord); michael@0: *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord); michael@0: } else { michael@0: *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord); michael@0: *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord); michael@0: if (*aStartOffset != convertedOffset) { michael@0: *aStartOffset = *aEndOffset; michael@0: *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord); michael@0: } michael@0: } michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: michael@0: case BOUNDARY_LINE_START: michael@0: *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin); michael@0: *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: michael@0: case BOUNDARY_LINE_END: michael@0: *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd); michael@0: *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd); michael@0: TextSubstring(*aStartOffset, *aEndOffset, aText); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset, michael@0: int32_t* aStartOffset, michael@0: int32_t* aEndOffset) michael@0: { michael@0: // 1. Get each attribute and its ranges one after another. michael@0: // 2. As we get each new attribute, we pass the current start and end offsets michael@0: // as in/out parameters. In other words, as attributes are collected, michael@0: // the attribute range itself can only stay the same or get smaller. michael@0: michael@0: *aStartOffset = *aEndOffset = 0; michael@0: nsCOMPtr attributes = michael@0: do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); michael@0: michael@0: int32_t offset = ConvertMagicOffset(aOffset); michael@0: Accessible* accAtOffset = GetChildAtOffset(offset); michael@0: if (!accAtOffset) { michael@0: // Offset 0 is correct offset when accessible has empty text. Include michael@0: // default attributes if they were requested, otherwise return empty set. michael@0: if (offset == 0) { michael@0: if (aIncludeDefAttrs) { michael@0: TextAttrsMgr textAttrsMgr(this); michael@0: textAttrsMgr.GetAttributes(attributes); michael@0: } michael@0: return attributes.forget(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: int32_t accAtOffsetIdx = accAtOffset->IndexInParent(); michael@0: int32_t startOffset = GetChildOffset(accAtOffsetIdx); michael@0: int32_t endOffset = GetChildOffset(accAtOffsetIdx + 1); michael@0: int32_t offsetInAcc = offset - startOffset; michael@0: michael@0: TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset, michael@0: accAtOffsetIdx); michael@0: textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset); michael@0: michael@0: // Compute spelling attributes on text accessible only. michael@0: nsIFrame *offsetFrame = accAtOffset->GetFrame(); michael@0: if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) { michael@0: int32_t nodeOffset = 0; michael@0: RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset); michael@0: michael@0: // Set 'misspelled' text attribute. michael@0: GetSpellTextAttribute(accAtOffset->GetNode(), nodeOffset, michael@0: &startOffset, &endOffset, attributes); michael@0: } michael@0: michael@0: *aStartOffset = startOffset; michael@0: *aEndOffset = endOffset; michael@0: return attributes.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: HyperTextAccessible::DefaultTextAttributes() michael@0: { michael@0: nsCOMPtr attributes = michael@0: do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID); michael@0: michael@0: TextAttrsMgr textAttrsMgr(this); michael@0: textAttrsMgr.GetAttributes(attributes); michael@0: return attributes.forget(); michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::GetLevelInternal() michael@0: { michael@0: nsIAtom *tag = mContent->Tag(); michael@0: if (tag == nsGkAtoms::h1) michael@0: return 1; michael@0: if (tag == nsGkAtoms::h2) michael@0: return 2; michael@0: if (tag == nsGkAtoms::h3) michael@0: return 3; michael@0: if (tag == nsGkAtoms::h4) michael@0: return 4; michael@0: if (tag == nsGkAtoms::h5) michael@0: return 5; michael@0: if (tag == nsGkAtoms::h6) michael@0: return 6; michael@0: michael@0: return AccessibleWrap::GetLevelInternal(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: HyperTextAccessible::NativeAttributes() michael@0: { michael@0: nsCOMPtr attributes = michael@0: AccessibleWrap::NativeAttributes(); michael@0: michael@0: // 'formatting' attribute is deprecated, 'display' attribute should be michael@0: // instead. michael@0: nsIFrame *frame = GetFrame(); michael@0: if (frame && frame->GetType() == nsGkAtoms::blockFrame) { michael@0: nsAutoString unused; michael@0: attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"), michael@0: NS_LITERAL_STRING("block"), unused); michael@0: } michael@0: michael@0: if (FocusMgr()->IsFocused(this)) { michael@0: int32_t lineNumber = CaretLineNumber(); michael@0: if (lineNumber >= 1) { michael@0: nsAutoString strLineNumber; michael@0: strLineNumber.AppendInt(lineNumber); michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber); michael@0: } michael@0: } michael@0: michael@0: if (!HasOwnContent()) michael@0: return attributes.forget(); michael@0: michael@0: // For the html landmark elements we expose them like we do aria landmarks to michael@0: // make AT navigation schemes "just work". michael@0: nsIAtom* tag = mContent->Tag(); michael@0: if (tag == nsGkAtoms::nav) { michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, michael@0: NS_LITERAL_STRING("navigation")); michael@0: } else if (tag == nsGkAtoms::section) { michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, michael@0: NS_LITERAL_STRING("region")); michael@0: } else if (tag == nsGkAtoms::header || tag == nsGkAtoms::footer) { michael@0: // Only map header and footer if they are not descendants michael@0: // of an article or section tag. michael@0: nsIContent* parent = mContent->GetParent(); michael@0: while (parent) { michael@0: if (parent->Tag() == nsGkAtoms::article || michael@0: parent->Tag() == nsGkAtoms::section) michael@0: break; michael@0: parent = parent->GetParent(); michael@0: } michael@0: michael@0: // No article or section elements found. michael@0: if (!parent) { michael@0: if (tag == nsGkAtoms::header) { michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, michael@0: NS_LITERAL_STRING("banner")); michael@0: } else if (tag == nsGkAtoms::footer) { michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, michael@0: NS_LITERAL_STRING("contentinfo")); michael@0: } michael@0: } michael@0: } else if (tag == nsGkAtoms::aside) { michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, michael@0: NS_LITERAL_STRING("complementary")); michael@0: } else if (tag == nsGkAtoms::article) { michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, michael@0: NS_LITERAL_STRING("article")); michael@0: } else if (tag == nsGkAtoms::main) { michael@0: nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, michael@0: NS_LITERAL_STRING("main")); michael@0: } michael@0: michael@0: return attributes.forget(); michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType) michael@0: { michael@0: nsIFrame* hyperFrame = GetFrame(); michael@0: if (!hyperFrame) michael@0: return -1; michael@0: michael@0: nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, michael@0: this); michael@0: michael@0: nsPresContext* presContext = mDoc->PresContext(); michael@0: nsPoint coordsInAppUnits = michael@0: coords.ToAppUnits(presContext->AppUnitsPerDevPixel()); michael@0: michael@0: nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits(); michael@0: if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y)) michael@0: return -1; // Not found michael@0: michael@0: nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x, michael@0: coordsInAppUnits.y - frameScreenRect.y); michael@0: michael@0: // Go through the frames to check if each one has the point. michael@0: // When one does, add up the character offsets until we have a match michael@0: michael@0: // We have an point in an accessible child of this, now we need to add up the michael@0: // offsets before it to what we already have michael@0: int32_t offset = 0; michael@0: uint32_t childCount = ChildCount(); michael@0: for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { michael@0: Accessible* childAcc = mChildren[childIdx]; michael@0: michael@0: nsIFrame *primaryFrame = childAcc->GetFrame(); michael@0: NS_ENSURE_TRUE(primaryFrame, -1); michael@0: michael@0: nsIFrame *frame = primaryFrame; michael@0: while (frame) { michael@0: nsIContent *content = frame->GetContent(); michael@0: NS_ENSURE_TRUE(content, -1); michael@0: nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame); michael@0: nsSize frameSize = frame->GetSize(); michael@0: if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) { michael@0: // Finished michael@0: if (frame->GetType() == nsGkAtoms::textFrame) { michael@0: nsIFrame::ContentOffsets contentOffsets = michael@0: frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE); michael@0: if (contentOffsets.IsNull() || contentOffsets.content != content) { michael@0: return -1; // Not found michael@0: } michael@0: uint32_t addToOffset; michael@0: nsresult rv = ContentToRenderedOffset(primaryFrame, michael@0: contentOffsets.offset, michael@0: &addToOffset); michael@0: NS_ENSURE_SUCCESS(rv, -1); michael@0: offset += addToOffset; michael@0: } michael@0: return offset; michael@0: } michael@0: frame = frame->GetNextContinuation(); michael@0: } michael@0: michael@0: offset += nsAccUtils::TextLength(childAcc); michael@0: } michael@0: michael@0: return -1; // Not found michael@0: } michael@0: michael@0: nsIntRect michael@0: HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset, michael@0: uint32_t aCoordType) michael@0: { michael@0: int32_t startOffset = ConvertMagicOffset(aStartOffset); michael@0: int32_t endOffset = ConvertMagicOffset(aEndOffset); michael@0: NS_ASSERTION(startOffset < endOffset, "Wrong bad in!"); michael@0: michael@0: int32_t childIdx = GetChildIndexAtOffset(startOffset); michael@0: if (childIdx == -1) michael@0: return nsIntRect(); michael@0: michael@0: nsIntRect bounds; michael@0: int32_t prevOffset = GetChildOffset(childIdx); michael@0: int32_t offset1 = startOffset - prevOffset; michael@0: michael@0: while (childIdx < ChildCount()) { michael@0: nsIFrame* frame = GetChildAt(childIdx++)->GetFrame(); michael@0: if (!frame) { michael@0: NS_NOTREACHED("No frame for a child!"); michael@0: continue; michael@0: } michael@0: michael@0: int32_t nextOffset = GetChildOffset(childIdx); michael@0: if (nextOffset >= endOffset) { michael@0: bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1, michael@0: endOffset - prevOffset)); michael@0: break; michael@0: } michael@0: michael@0: bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1, michael@0: nextOffset - prevOffset)); michael@0: michael@0: prevOffset = nextOffset; michael@0: offset1 = 0; michael@0: } michael@0: michael@0: nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this); michael@0: return bounds; michael@0: } michael@0: michael@0: already_AddRefed michael@0: HyperTextAccessible::GetEditor() const michael@0: { michael@0: if (!mContent->HasFlag(NODE_IS_EDITABLE)) { michael@0: // If we're inside an editable container, then return that container's editor michael@0: Accessible* ancestor = Parent(); michael@0: while (ancestor) { michael@0: HyperTextAccessible* hyperText = ancestor->AsHyperText(); michael@0: if (hyperText) { michael@0: // Recursion will stop at container doc because it has its own impl michael@0: // of GetEditor() michael@0: return hyperText->GetEditor(); michael@0: } michael@0: michael@0: ancestor = ancestor->Parent(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr docShell = nsCoreUtils::GetDocShellFor(mContent); michael@0: nsCOMPtr editingSession(do_GetInterface(docShell)); michael@0: if (!editingSession) michael@0: return nullptr; // No editing session interface michael@0: michael@0: nsCOMPtr editor; michael@0: nsIDocument* docNode = mDoc->DocumentNode(); michael@0: editingSession->GetEditorForWindow(docNode->GetWindow(), michael@0: getter_AddRefs(editor)); michael@0: return editor.forget(); michael@0: } michael@0: michael@0: /** michael@0: * =================== Caret & Selection ====================== michael@0: */ michael@0: michael@0: nsresult michael@0: HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos) michael@0: { michael@0: // Before setting the selection range, we need to ensure that the editor michael@0: // is initialized. (See bug 804927.) michael@0: // Otherwise, it's possible that lazy editor initialization will override michael@0: // the selection we set here and leave the caret at the end of the text. michael@0: // By calling GetEditor here, we ensure that editor initialization is michael@0: // completed before we set the selection. michael@0: nsCOMPtr editor = GetEditor(); michael@0: michael@0: bool isFocusable = InteractiveState() & states::FOCUSABLE; michael@0: michael@0: // If accessible is focusable then focus it before setting the selection to michael@0: // neglect control's selection changes on focus if any (for example, inputs michael@0: // that do select all on focus). michael@0: // some input controls michael@0: if (isFocusable) michael@0: TakeFocus(); michael@0: michael@0: dom::Selection* domSel = DOMSelection(); michael@0: NS_ENSURE_STATE(domSel); michael@0: michael@0: // Set up the selection. michael@0: for (int32_t idx = domSel->GetRangeCount() - 1; idx > 0; idx--) michael@0: domSel->RemoveRange(domSel->GetRangeAt(idx)); michael@0: SetSelectionBoundsAt(0, aStartPos, aEndPos); michael@0: michael@0: // When selection is done, move the focus to the selection if accessible is michael@0: // not focusable. That happens when selection is set within hypertext michael@0: // accessible. michael@0: if (isFocusable) michael@0: return NS_OK; michael@0: michael@0: nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager(); michael@0: if (DOMFocusManager) { michael@0: NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE); michael@0: nsIDocument* docNode = mDoc->DocumentNode(); michael@0: NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE); michael@0: nsCOMPtr window = docNode->GetWindow(); michael@0: nsCOMPtr result; michael@0: DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET, michael@0: nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::CaretOffset() const michael@0: { michael@0: // Not focused focusable accessible except document accessible doesn't have michael@0: // a caret. michael@0: if (!IsDoc() && !FocusMgr()->IsFocused(this) && michael@0: (InteractiveState() & states::FOCUSABLE)) { michael@0: return -1; michael@0: } michael@0: michael@0: // No caret if the focused node is not inside this DOM node and this DOM node michael@0: // is not inside of focused node. michael@0: FocusManager::FocusDisposition focusDisp = michael@0: FocusMgr()->IsInOrContainsFocus(this); michael@0: if (focusDisp == FocusManager::eNone) michael@0: return -1; michael@0: michael@0: // Turn the focus node and offset of the selection into caret hypretext michael@0: // offset. michael@0: dom::Selection* domSel = DOMSelection(); michael@0: NS_ENSURE_TRUE(domSel, -1); michael@0: michael@0: nsINode* focusNode = domSel->GetFocusNode(); michael@0: uint32_t focusOffset = domSel->FocusOffset(); michael@0: michael@0: // No caret if this DOM node is inside of focused node but the selection's michael@0: // focus point is not inside of this DOM node. michael@0: if (focusDisp == FocusManager::eContainedByFocus) { michael@0: nsINode* resultNode = michael@0: nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset); michael@0: michael@0: nsINode* thisNode = GetNode(); michael@0: if (resultNode != thisNode && michael@0: !nsCoreUtils::IsAncestorOf(thisNode, resultNode)) michael@0: return -1; michael@0: } michael@0: michael@0: return DOMPointToOffset(focusNode, focusOffset); michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::CaretLineNumber() michael@0: { michael@0: // Provide the line number for the caret, relative to the michael@0: // currently focused node. Use a 1-based index michael@0: nsRefPtr frameSelection = FrameSelection(); michael@0: if (!frameSelection) michael@0: return -1; michael@0: michael@0: dom::Selection* domSel = michael@0: frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL); michael@0: if (!domSel) michael@0: return - 1; michael@0: michael@0: nsINode* caretNode = domSel->GetFocusNode(); michael@0: if (!caretNode || !caretNode->IsContent()) michael@0: return -1; michael@0: michael@0: nsIContent* caretContent = caretNode->AsContent(); michael@0: if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent)) michael@0: return -1; michael@0: michael@0: int32_t returnOffsetUnused; michael@0: uint32_t caretOffset = domSel->FocusOffset(); michael@0: nsFrameSelection::HINT hint = frameSelection->GetHint(); michael@0: nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset, michael@0: hint, &returnOffsetUnused); michael@0: NS_ENSURE_TRUE(caretFrame, -1); michael@0: michael@0: int32_t lineNumber = 1; michael@0: nsAutoLineIterator lineIterForCaret; michael@0: nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr; michael@0: while (caretFrame) { michael@0: if (hyperTextContent == caretFrame->GetContent()) { michael@0: return lineNumber; // Must be in a single line hyper text, there is no line iterator michael@0: } michael@0: nsIFrame *parentFrame = caretFrame->GetParent(); michael@0: if (!parentFrame) michael@0: break; michael@0: michael@0: // Add lines for the sibling frames before the caret michael@0: nsIFrame *sibling = parentFrame->GetFirstPrincipalChild(); michael@0: while (sibling && sibling != caretFrame) { michael@0: nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator(); michael@0: if (lineIterForSibling) { michael@0: // For the frames before that grab all the lines michael@0: int32_t addLines = lineIterForSibling->GetNumLines(); michael@0: lineNumber += addLines; michael@0: } michael@0: sibling = sibling->GetNextSibling(); michael@0: } michael@0: michael@0: // Get the line number relative to the container with lines michael@0: if (!lineIterForCaret) { // Add the caret line just once michael@0: lineIterForCaret = parentFrame->GetLineIterator(); michael@0: if (lineIterForCaret) { michael@0: // Ancestor of caret michael@0: int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame); michael@0: lineNumber += addLines; michael@0: } michael@0: } michael@0: michael@0: caretFrame = parentFrame; michael@0: } michael@0: michael@0: NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't"); michael@0: return lineNumber; michael@0: } michael@0: michael@0: nsIntRect michael@0: HyperTextAccessible::GetCaretRect(nsIWidget** aWidget) michael@0: { michael@0: *aWidget = nullptr; michael@0: michael@0: nsRefPtr caret = mDoc->PresShell()->GetCaret(); michael@0: NS_ENSURE_TRUE(caret, nsIntRect()); michael@0: michael@0: nsISelection* caretSelection = caret->GetCaretDOMSelection(); michael@0: NS_ENSURE_TRUE(caretSelection, nsIntRect()); michael@0: michael@0: bool isVisible = false; michael@0: caret->GetCaretVisible(&isVisible); michael@0: if (!isVisible) michael@0: return nsIntRect(); michael@0: michael@0: nsRect rect; michael@0: nsIFrame* frame = caret->GetGeometry(caretSelection, &rect); michael@0: if (!frame || rect.IsEmpty()) michael@0: return nsIntRect(); michael@0: michael@0: nsPoint offset; michael@0: // Offset from widget origin to the frame origin, which includes chrome michael@0: // on the widget. michael@0: *aWidget = frame->GetNearestWidget(offset); michael@0: NS_ENSURE_TRUE(*aWidget, nsIntRect()); michael@0: rect.MoveBy(offset); michael@0: michael@0: nsIntRect caretRect; michael@0: caretRect = rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel()); michael@0: // ((content screen origin) - (content offset in the widget)) = widget origin on the screen michael@0: caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset()); michael@0: michael@0: // Correct for character size, so that caret always matches the size of michael@0: // the character. This is important for font size transitions, and is michael@0: // necessary because the Gecko caret uses the previous character's size as michael@0: // the user moves forward in the text by character. michael@0: nsIntRect charRect = CharBounds(CaretOffset(), michael@0: nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE); michael@0: if (!charRect.IsEmpty()) { michael@0: caretRect.height -= charRect.y - caretRect.y; michael@0: caretRect.y = charRect.y; michael@0: } michael@0: return caretRect; michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::GetSelectionDOMRanges(int16_t aType, michael@0: nsTArray* aRanges) michael@0: { michael@0: // Ignore selection if it is not visible. michael@0: nsRefPtr frameSelection = FrameSelection(); michael@0: if (!frameSelection || michael@0: frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN) michael@0: return; michael@0: michael@0: dom::Selection* domSel = frameSelection->GetSelection(aType); michael@0: if (!domSel) michael@0: return; michael@0: michael@0: nsCOMPtr startNode = GetNode(); michael@0: michael@0: nsCOMPtr editor = GetEditor(); michael@0: if (editor) { michael@0: nsCOMPtr editorRoot; michael@0: editor->GetRootElement(getter_AddRefs(editorRoot)); michael@0: startNode = do_QueryInterface(editorRoot); michael@0: } michael@0: michael@0: if (!startNode) michael@0: return; michael@0: michael@0: uint32_t childCount = startNode->GetChildCount(); michael@0: nsresult rv = domSel-> michael@0: GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: // Remove collapsed ranges michael@0: uint32_t numRanges = aRanges->Length(); michael@0: for (uint32_t idx = 0; idx < numRanges; idx ++) { michael@0: if ((*aRanges)[idx]->Collapsed()) { michael@0: aRanges->RemoveElementAt(idx); michael@0: --numRanges; michael@0: --idx; michael@0: } michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::SelectionCount() michael@0: { michael@0: nsTArray ranges; michael@0: GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges); michael@0: return ranges.Length(); michael@0: } michael@0: michael@0: bool michael@0: HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum, michael@0: int32_t* aStartOffset, michael@0: int32_t* aEndOffset) michael@0: { michael@0: *aStartOffset = *aEndOffset = 0; michael@0: michael@0: nsTArray ranges; michael@0: GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges); michael@0: michael@0: uint32_t rangeCount = ranges.Length(); michael@0: if (aSelectionNum < 0 || aSelectionNum >= rangeCount) michael@0: return false; michael@0: michael@0: nsRange* range = ranges[aSelectionNum]; michael@0: michael@0: // Get start and end points. michael@0: nsINode* startNode = range->GetStartParent(); michael@0: nsINode* endNode = range->GetEndParent(); michael@0: int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset(); michael@0: michael@0: // Make sure start is before end, by swapping DOM points. This occurs when michael@0: // the user selects backwards in the text. michael@0: int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset, michael@0: startNode, startOffset); michael@0: if (rangeCompare < 0) { michael@0: nsINode* tempNode = startNode; michael@0: startNode = endNode; michael@0: endNode = tempNode; michael@0: int32_t tempOffset = startOffset; michael@0: startOffset = endOffset; michael@0: endOffset = tempOffset; michael@0: } michael@0: michael@0: *aStartOffset = DOMPointToOffset(startNode, startOffset); michael@0: *aEndOffset = DOMPointToOffset(endNode, endOffset, true); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum, michael@0: int32_t aStartOffset, michael@0: int32_t aEndOffset) michael@0: { michael@0: int32_t startOffset = ConvertMagicOffset(aStartOffset); michael@0: int32_t endOffset = ConvertMagicOffset(aEndOffset); michael@0: michael@0: dom::Selection* domSel = DOMSelection(); michael@0: if (!domSel) michael@0: return false; michael@0: michael@0: nsRefPtr range; michael@0: uint32_t rangeCount = domSel->GetRangeCount(); michael@0: if (aSelectionNum == rangeCount) michael@0: range = new nsRange(mContent); michael@0: else michael@0: range = domSel->GetRangeAt(aSelectionNum); michael@0: michael@0: if (!range) michael@0: return false; michael@0: michael@0: if (!OffsetsToDOMRange(startOffset, endOffset, range)) michael@0: return false; michael@0: michael@0: // If new range was created then add it, otherwise notify selection listeners michael@0: // that existing selection range was changed. michael@0: if (aSelectionNum == rangeCount) michael@0: return NS_SUCCEEDED(domSel->AddRange(range)); michael@0: michael@0: domSel->RemoveRange(range); michael@0: return NS_SUCCEEDED(domSel->AddRange(range)); michael@0: } michael@0: michael@0: bool michael@0: HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum) michael@0: { michael@0: dom::Selection* domSel = DOMSelection(); michael@0: if (!domSel) michael@0: return false; michael@0: michael@0: if (aSelectionNum < 0 || aSelectionNum >= domSel->GetRangeCount()) michael@0: return false; michael@0: michael@0: domSel->RemoveRange(domSel->GetRangeAt(aSelectionNum)); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset, michael@0: uint32_t aScrollType) michael@0: { michael@0: nsRefPtr range = new nsRange(mContent); michael@0: if (OffsetsToDOMRange(aStartOffset, aEndOffset, range)) michael@0: nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType); michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset, michael@0: int32_t aEndOffset, michael@0: uint32_t aCoordinateType, michael@0: int32_t aX, int32_t aY) michael@0: { michael@0: nsIFrame *frame = GetFrame(); michael@0: if (!frame) michael@0: return; michael@0: michael@0: nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, michael@0: this); michael@0: michael@0: nsRefPtr range = new nsRange(mContent); michael@0: if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range)) michael@0: return; michael@0: michael@0: nsPresContext* presContext = frame->PresContext(); michael@0: nsPoint coordsInAppUnits = michael@0: coords.ToAppUnits(presContext->AppUnitsPerDevPixel()); michael@0: michael@0: bool initialScrolled = false; michael@0: nsIFrame *parentFrame = frame; michael@0: while ((parentFrame = parentFrame->GetParent())) { michael@0: nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame); michael@0: if (scrollableFrame) { michael@0: if (!initialScrolled) { michael@0: // Scroll substring to the given point. Turn the point into percents michael@0: // relative scrollable area to use nsCoreUtils::ScrollSubstringTo. michael@0: nsRect frameRect = parentFrame->GetScreenRectInAppUnits(); michael@0: nscoord offsetPointX = coordsInAppUnits.x - frameRect.x; michael@0: nscoord offsetPointY = coordsInAppUnits.y - frameRect.y; michael@0: michael@0: nsSize size(parentFrame->GetSize()); michael@0: michael@0: // avoid divide by zero michael@0: size.width = size.width ? size.width : 1; michael@0: size.height = size.height ? size.height : 1; michael@0: michael@0: int16_t hPercent = offsetPointX * 100 / size.width; michael@0: int16_t vPercent = offsetPointY * 100 / size.height; michael@0: michael@0: nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range, vPercent, hPercent); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: initialScrolled = true; michael@0: } else { michael@0: // Substring was scrolled to the given point already inside its closest michael@0: // scrollable area. If there are nested scrollable areas then make michael@0: // sure we scroll lower areas to the given point inside currently michael@0: // traversed scrollable area. michael@0: nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords); michael@0: } michael@0: } michael@0: frame = parentFrame; michael@0: } michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const michael@0: { michael@0: if (IsTextField()) { michael@0: aRange.Set(mDoc, const_cast(this), 0, michael@0: const_cast(this), ChildCount()); michael@0: } else { michael@0: aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->ChildCount()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::SelectionRanges(nsTArray* aRanges) const michael@0: { michael@0: NS_ASSERTION(aRanges->Length() != 0, "TextRange array supposed to be empty"); michael@0: michael@0: dom::Selection* sel = DOMSelection(); michael@0: if (!sel) michael@0: return; michael@0: michael@0: aRanges->SetCapacity(sel->RangeCount()); michael@0: michael@0: for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) { michael@0: nsRange* DOMRange = sel->GetRangeAt(idx); michael@0: HyperTextAccessible* startParent = michael@0: nsAccUtils::GetTextContainer(DOMRange->GetStartParent()); michael@0: HyperTextAccessible* endParent = michael@0: nsAccUtils::GetTextContainer(DOMRange->GetEndParent()); michael@0: if (!startParent || !endParent) michael@0: continue; michael@0: michael@0: int32_t startOffset = michael@0: startParent->DOMPointToOffset(DOMRange->GetStartParent(), michael@0: DOMRange->StartOffset(), false); michael@0: int32_t endOffset = michael@0: endParent->DOMPointToOffset(DOMRange->GetEndParent(), michael@0: DOMRange->EndOffset(), true); michael@0: michael@0: TextRange tr(IsTextField() ? const_cast(this) : mDoc, michael@0: startParent, startOffset, endParent, endOffset); michael@0: *(aRanges->AppendElement()) = Move(tr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::VisibleRanges(nsTArray* aRanges) const michael@0: { michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::RangeByChild(Accessible* aChild, michael@0: a11y::TextRange& aRange) const michael@0: { michael@0: aRange.Set(mDoc, aChild, 0, aChild, aChild->ChildCount()); michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY, michael@0: a11y::TextRange& aRange) const michael@0: { michael@0: Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild); michael@0: if (child) michael@0: aRange.Set(mDoc, child, 0, child, child->ChildCount()); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Accessible public michael@0: michael@0: // Accessible protected michael@0: ENameValueFlag michael@0: HyperTextAccessible::NativeName(nsString& aName) michael@0: { michael@0: // Check @alt attribute for invalid img elements. michael@0: bool hasImgAlt = false; michael@0: if (mContent->IsHTML(nsGkAtoms::img)) { michael@0: hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName); michael@0: if (!aName.IsEmpty()) michael@0: return eNameOK; michael@0: } michael@0: michael@0: ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName); michael@0: if (!aName.IsEmpty()) michael@0: return nameFlag; michael@0: michael@0: // Get name from title attribute for HTML abbr and acronym elements making it michael@0: // a valid name from markup. Otherwise their name isn't picked up by recursive michael@0: // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP. michael@0: if (IsAbbreviation() && michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) michael@0: aName.CompressWhitespace(); michael@0: michael@0: return hasImgAlt ? eNoNameOnPurpose : eNameOK; michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::InvalidateChildren() michael@0: { michael@0: mOffsets.Clear(); michael@0: michael@0: AccessibleWrap::InvalidateChildren(); michael@0: } michael@0: michael@0: bool michael@0: HyperTextAccessible::RemoveChild(Accessible* aAccessible) michael@0: { michael@0: int32_t childIndex = aAccessible->IndexInParent(); michael@0: int32_t count = mOffsets.Length() - childIndex; michael@0: if (count > 0) michael@0: mOffsets.RemoveElementsAt(childIndex, count); michael@0: michael@0: return Accessible::RemoveChild(aAccessible); michael@0: } michael@0: michael@0: void michael@0: HyperTextAccessible::CacheChildren() michael@0: { michael@0: // Trailing HTML br element don't play any difference. We don't need to expose michael@0: // it to AT (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=899433#c16 michael@0: // for details). michael@0: michael@0: TreeWalker walker(this, mContent); michael@0: Accessible* child = nullptr; michael@0: Accessible* lastChild = nullptr; michael@0: while ((child = walker.NextChild())) { michael@0: if (lastChild) michael@0: AppendChild(lastChild); michael@0: michael@0: lastChild = child; michael@0: } michael@0: michael@0: if (lastChild) { michael@0: if (lastChild->IsHTMLBr()) michael@0: Document()->UnbindFromDocument(lastChild); michael@0: else michael@0: AppendChild(lastChild); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HyperTextAccessible public static michael@0: michael@0: nsresult michael@0: HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset, michael@0: uint32_t* aRenderedOffset) const michael@0: { michael@0: if (!aFrame) { michael@0: // Current frame not rendered -- this can happen if text is set on michael@0: // something with display: none michael@0: *aRenderedOffset = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (IsTextField()) { michael@0: *aRenderedOffset = aContentOffset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, michael@0: "Need text frame for offset conversion"); michael@0: NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, michael@0: "Call on primary frame only"); michael@0: michael@0: gfxSkipChars skipChars; michael@0: gfxSkipCharsIterator iter; michael@0: // Only get info up to original offset, we know that will be larger than skipped offset michael@0: nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aContentOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t ourRenderedStart = iter.GetSkippedOffset(); michael@0: int32_t ourContentStart = iter.GetOriginalOffset(); michael@0: michael@0: *aRenderedOffset = iter.ConvertOriginalToSkipped(aContentOffset + ourContentStart) - michael@0: ourRenderedStart; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset, michael@0: int32_t* aContentOffset) const michael@0: { michael@0: if (IsTextField()) { michael@0: *aContentOffset = aRenderedOffset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aContentOffset = 0; michael@0: NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE); michael@0: michael@0: NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, michael@0: "Need text frame for offset conversion"); michael@0: NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, michael@0: "Call on primary frame only"); michael@0: michael@0: gfxSkipChars skipChars; michael@0: gfxSkipCharsIterator iter; michael@0: // We only need info up to skipped offset -- that is what we're converting to original offset michael@0: nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aRenderedOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t ourRenderedStart = iter.GetSkippedOffset(); michael@0: int32_t ourContentStart = iter.GetOriginalOffset(); michael@0: michael@0: *aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HyperTextAccessible public michael@0: michael@0: int32_t michael@0: HyperTextAccessible::GetChildOffset(uint32_t aChildIndex, michael@0: bool aInvalidateAfter) const michael@0: { michael@0: if (aChildIndex == 0) { michael@0: if (aInvalidateAfter) michael@0: mOffsets.Clear(); michael@0: michael@0: return aChildIndex; michael@0: } michael@0: michael@0: int32_t count = mOffsets.Length() - aChildIndex; michael@0: if (count > 0) { michael@0: if (aInvalidateAfter) michael@0: mOffsets.RemoveElementsAt(aChildIndex, count); michael@0: michael@0: return mOffsets[aChildIndex - 1]; michael@0: } michael@0: michael@0: uint32_t lastOffset = mOffsets.IsEmpty() ? michael@0: 0 : mOffsets[mOffsets.Length() - 1]; michael@0: michael@0: while (mOffsets.Length() < aChildIndex) { michael@0: Accessible* child = mChildren[mOffsets.Length()]; michael@0: lastOffset += nsAccUtils::TextLength(child); michael@0: mOffsets.AppendElement(lastOffset); michael@0: } michael@0: michael@0: return mOffsets[aChildIndex - 1]; michael@0: } michael@0: michael@0: int32_t michael@0: HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const michael@0: { michael@0: uint32_t lastOffset = 0; michael@0: uint32_t offsetCount = mOffsets.Length(); michael@0: if (offsetCount > 0) { michael@0: lastOffset = mOffsets[offsetCount - 1]; michael@0: if (aOffset < lastOffset) { michael@0: uint32_t low = 0, high = offsetCount; michael@0: while (high > low) { michael@0: uint32_t mid = (high + low) >> 1; michael@0: if (mOffsets[mid] == aOffset) michael@0: return mid < offsetCount - 1 ? mid + 1 : mid; michael@0: michael@0: if (mOffsets[mid] < aOffset) michael@0: low = mid + 1; michael@0: else michael@0: high = mid; michael@0: } michael@0: if (high == offsetCount) michael@0: return -1; michael@0: michael@0: return low; michael@0: } michael@0: } michael@0: michael@0: uint32_t childCount = ChildCount(); michael@0: while (mOffsets.Length() < childCount) { michael@0: Accessible* child = GetChildAt(mOffsets.Length()); michael@0: lastOffset += nsAccUtils::TextLength(child); michael@0: mOffsets.AppendElement(lastOffset); michael@0: if (aOffset < lastOffset) michael@0: return mOffsets.Length() - 1; michael@0: } michael@0: michael@0: if (aOffset == lastOffset) michael@0: return mOffsets.Length() - 1; michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // HyperTextAccessible protected michael@0: michael@0: nsresult michael@0: HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset, michael@0: Accessible* aAccessible, michael@0: DOMPoint* aPoint) michael@0: { michael@0: NS_ENSURE_ARG(aAccessible); michael@0: michael@0: if (!aFrame) { michael@0: // If the given frame is null then set offset after the DOM node of the michael@0: // given accessible. michael@0: NS_ASSERTION(!aAccessible->IsDoc(), michael@0: "Shouldn't be called on document accessible!"); michael@0: michael@0: nsIContent* content = aAccessible->GetContent(); michael@0: NS_ASSERTION(content, "Shouldn't operate on defunct accessible!"); michael@0: michael@0: nsIContent* parent = content->GetParent(); michael@0: michael@0: aPoint->idx = parent->IndexOf(content) + 1; michael@0: aPoint->node = parent; michael@0: michael@0: } else if (aFrame->GetType() == nsGkAtoms::textFrame) { michael@0: nsIContent* content = aFrame->GetContent(); michael@0: NS_ENSURE_STATE(content); michael@0: michael@0: nsIFrame *primaryFrame = content->GetPrimaryFrame(); michael@0: nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aPoint->node = content; michael@0: michael@0: } else { michael@0: nsIContent* content = aFrame->GetContent(); michael@0: NS_ENSURE_STATE(content); michael@0: michael@0: nsIContent* parent = content->GetParent(); michael@0: NS_ENSURE_STATE(parent); michael@0: michael@0: aPoint->idx = parent->IndexOf(content); michael@0: aPoint->node = parent; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // HyperTextAccessible michael@0: nsresult michael@0: HyperTextAccessible::GetSpellTextAttribute(nsINode* aNode, michael@0: int32_t aNodeOffset, michael@0: int32_t* aHTStartOffset, michael@0: int32_t* aHTEndOffset, michael@0: nsIPersistentProperties* aAttributes) michael@0: { michael@0: nsRefPtr fs = FrameSelection(); michael@0: if (!fs) michael@0: return NS_OK; michael@0: michael@0: dom::Selection* domSel = fs->GetSelection(nsISelectionController::SELECTION_SPELLCHECK); michael@0: if (!domSel) michael@0: return NS_OK; michael@0: michael@0: int32_t rangeCount = domSel->GetRangeCount(); michael@0: if (rangeCount <= 0) michael@0: return NS_OK; michael@0: michael@0: int32_t startHTOffset = 0, endHTOffset = 0; michael@0: for (int32_t idx = 0; idx < rangeCount; idx++) { michael@0: nsRange* range = domSel->GetRangeAt(idx); michael@0: if (range->Collapsed()) michael@0: continue; michael@0: michael@0: // See if the point comes after the range in which case we must continue in michael@0: // case there is another range after this one. michael@0: nsINode* endNode = range->GetEndParent(); michael@0: int32_t endOffset = range->EndOffset(); michael@0: if (nsContentUtils::ComparePoints(aNode, aNodeOffset, endNode, endOffset) >= 0) michael@0: continue; michael@0: michael@0: // At this point our point is either in this range or before it but after michael@0: // the previous range. So we check to see if the range starts before the michael@0: // point in which case the point is in the missspelled range, otherwise it michael@0: // must be before the range and after the previous one if any. michael@0: nsINode* startNode = range->GetStartParent(); michael@0: int32_t startOffset = range->StartOffset(); michael@0: if (nsContentUtils::ComparePoints(startNode, startOffset, aNode, michael@0: aNodeOffset) <= 0) { michael@0: startHTOffset = DOMPointToOffset(startNode, startOffset); michael@0: michael@0: endHTOffset = DOMPointToOffset(endNode, endOffset); michael@0: michael@0: if (startHTOffset > *aHTStartOffset) michael@0: *aHTStartOffset = startHTOffset; michael@0: michael@0: if (endHTOffset < *aHTEndOffset) michael@0: *aHTEndOffset = endHTOffset; michael@0: michael@0: if (aAttributes) { michael@0: nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid, michael@0: NS_LITERAL_STRING("spelling")); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This range came after the point. michael@0: endHTOffset = DOMPointToOffset(startNode, startOffset); michael@0: michael@0: if (idx > 0) { michael@0: nsRange* prevRange = domSel->GetRangeAt(idx - 1); michael@0: startHTOffset = DOMPointToOffset(prevRange->GetEndParent(), michael@0: prevRange->EndOffset()); michael@0: } michael@0: michael@0: if (startHTOffset > *aHTStartOffset) michael@0: *aHTStartOffset = startHTOffset; michael@0: michael@0: if (endHTOffset < *aHTEndOffset) michael@0: *aHTEndOffset = endHTOffset; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We never found a range that ended after the point, therefore we know that michael@0: // the point is not in a range, that we do not need to compute an end offset, michael@0: // and that we should use the end offset of the last range to compute the michael@0: // start offset of the text attribute range. michael@0: nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1); michael@0: startHTOffset = DOMPointToOffset(prevRange->GetEndParent(), michael@0: prevRange->EndOffset()); michael@0: michael@0: if (startHTOffset > *aHTStartOffset) michael@0: *aHTStartOffset = startHTOffset; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: HyperTextAccessible::IsTextRole() michael@0: { michael@0: if (mRoleMapEntry && michael@0: (mRoleMapEntry->role == roles::GRAPHIC || michael@0: mRoleMapEntry->role == roles::IMAGE_MAP || michael@0: mRoleMapEntry->role == roles::SLIDER || michael@0: mRoleMapEntry->role == roles::PROGRESSBAR || michael@0: mRoleMapEntry->role == roles::SEPARATOR)) michael@0: return false; michael@0: michael@0: return true; michael@0: }