diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /accessible/basetypes/HyperTextAccessibleBase.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/basetypes/HyperTextAccessibleBase.cpp')
-rw-r--r-- | accessible/basetypes/HyperTextAccessibleBase.cpp | 841 |
1 files changed, 841 insertions, 0 deletions
diff --git a/accessible/basetypes/HyperTextAccessibleBase.cpp b/accessible/basetypes/HyperTextAccessibleBase.cpp new file mode 100644 index 0000000000..c66180673b --- /dev/null +++ b/accessible/basetypes/HyperTextAccessibleBase.cpp @@ -0,0 +1,841 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "HyperTextAccessibleBase.h" + +#include "mozilla/a11y/Accessible.h" +#include "nsAccUtils.h" +#include "TextLeafRange.h" +#include "TextRange.h" + +namespace mozilla::a11y { + +int32_t HyperTextAccessibleBase::GetChildIndexAtOffset(uint32_t aOffset) const { + auto& offsets = + const_cast<HyperTextAccessibleBase*>(this)->GetCachedHyperTextOffsets(); + int32_t lastOffset = 0; + const uint32_t offsetCount = offsets.Length(); + + if (offsetCount > 0) { + lastOffset = offsets[offsetCount - 1]; + if (static_cast<int32_t>(aOffset) < lastOffset) { + // We've cached up to aOffset. + size_t index; + if (BinarySearch(offsets, 0, offsetCount, static_cast<int32_t>(aOffset), + &index)) { + // aOffset is the exclusive end of a child, so return the child before + // it. + return static_cast<int32_t>((index < offsetCount - 1) ? index + 1 + : index); + } + if (index == offsetCount) { + // aOffset is past the end of the text. + return -1; + } + // index points at the exclusive end after aOffset. + return static_cast<int32_t>(index); + } + } + + // We haven't yet cached up to aOffset. Find it, caching as we go. + const Accessible* thisAcc = Acc(); + uint32_t childCount = thisAcc->ChildCount(); + // Even though we're only caching up to aOffset, it's likely that we'll + // eventually cache offsets for all children. Pre-allocate thus to minimize + // re-allocations. + offsets.SetCapacity(childCount); + while (offsets.Length() < childCount) { + Accessible* child = thisAcc->ChildAt(offsets.Length()); + lastOffset += static_cast<int32_t>(nsAccUtils::TextLength(child)); + offsets.AppendElement(lastOffset); + if (static_cast<int32_t>(aOffset) < lastOffset) { + return static_cast<int32_t>(offsets.Length() - 1); + } + } + + if (static_cast<int32_t>(aOffset) == lastOffset) { + return static_cast<int32_t>(offsets.Length() - 1); + } + + return -1; +} + +Accessible* HyperTextAccessibleBase::GetChildAtOffset(uint32_t aOffset) const { + const Accessible* thisAcc = Acc(); + return thisAcc->ChildAt(GetChildIndexAtOffset(aOffset)); +} + +int32_t HyperTextAccessibleBase::GetChildOffset(const Accessible* aChild, + bool aInvalidateAfter) const { + const Accessible* thisAcc = Acc(); + if (aChild->Parent() != thisAcc) { + return -1; + } + int32_t index = aChild->IndexInParent(); + if (index == -1) { + return -1; + } + return GetChildOffset(index, aInvalidateAfter); +} + +int32_t HyperTextAccessibleBase::GetChildOffset(uint32_t aChildIndex, + bool aInvalidateAfter) const { + auto& offsets = + const_cast<HyperTextAccessibleBase*>(this)->GetCachedHyperTextOffsets(); + if (aChildIndex == 0) { + if (aInvalidateAfter) { + offsets.Clear(); + } + return 0; + } + + int32_t countCachedAfterChild = static_cast<int32_t>(offsets.Length()) - + static_cast<int32_t>(aChildIndex); + if (countCachedAfterChild > 0) { + // We've cached up to aChildIndex. + if (aInvalidateAfter) { + offsets.RemoveElementsAt(aChildIndex, countCachedAfterChild); + } + return offsets[aChildIndex - 1]; + } + + // We haven't yet cached up to aChildIndex. Find it, caching as we go. + const Accessible* thisAcc = Acc(); + // Even though we're only caching up to aChildIndex, it's likely that we'll + // eventually cache offsets for all children. Pre-allocate thus to minimize + // re-allocations. + offsets.SetCapacity(thisAcc->ChildCount()); + uint32_t lastOffset = offsets.IsEmpty() ? 0 : offsets[offsets.Length() - 1]; + while (offsets.Length() < aChildIndex) { + Accessible* child = thisAcc->ChildAt(offsets.Length()); + lastOffset += nsAccUtils::TextLength(child); + offsets.AppendElement(lastOffset); + } + + return offsets[aChildIndex - 1]; +} + +uint32_t HyperTextAccessibleBase::CharacterCount() const { + return GetChildOffset(Acc()->ChildCount()); +} + +index_t HyperTextAccessibleBase::ConvertMagicOffset(int32_t aOffset) const { + if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT) { + return CharacterCount(); + } + + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) { + return CaretOffset(); + } + + return aOffset; +} + +void HyperTextAccessibleBase::TextSubstring(int32_t aStartOffset, + int32_t aEndOffset, + nsAString& aText) const { + 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; + } + + const Accessible* thisAcc = Acc(); + if (startChildIdx == endChildIdx) { + int32_t childOffset = GetChildOffset(startChildIdx); + if (childOffset == -1) { + return; + } + + Accessible* child = thisAcc->ChildAt(startChildIdx); + child->AppendTextTo(aText, startOffset - childOffset, + endOffset - startOffset); + return; + } + + int32_t startChildOffset = GetChildOffset(startChildIdx); + if (startChildOffset == -1) { + return; + } + + Accessible* startChild = thisAcc->ChildAt(startChildIdx); + startChild->AppendTextTo(aText, startOffset - startChildOffset); + + for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; + childIdx++) { + Accessible* child = thisAcc->ChildAt(childIdx); + child->AppendTextTo(aText); + } + + int32_t endChildOffset = GetChildOffset(endChildIdx); + if (endChildOffset == -1) { + return; + } + + Accessible* endChild = thisAcc->ChildAt(endChildIdx); + endChild->AppendTextTo(aText, 0, endOffset - endChildOffset); +} + +bool HyperTextAccessibleBase::CharAt(int32_t aOffset, nsAString& aChar, + int32_t* aStartOffset, + int32_t* aEndOffset) { + MOZ_ASSERT(!aStartOffset == !aEndOffset, + "Offsets should be both defined or both undefined!"); + + int32_t childIdx = GetChildIndexAtOffset(aOffset); + if (childIdx == -1) { + return false; + } + + Accessible* child = Acc()->ChildAt(childIdx); + child->AppendTextTo(aChar, aOffset - GetChildOffset(childIdx), 1); + + if (aStartOffset && aEndOffset) { + *aStartOffset = aOffset; + *aEndOffset = aOffset + aChar.Length(); + } + return true; +} + +LayoutDeviceIntRect HyperTextAccessibleBase::CharBounds(int32_t aOffset, + uint32_t aCoordType) { + index_t offset = ConvertMagicOffset(aOffset); + if (!offset.IsValid() || offset > CharacterCount()) { + return LayoutDeviceIntRect(); + } + TextLeafPoint point = ToTextLeafPoint(static_cast<int32_t>(offset), false); + if (!point.mAcc) { + return LayoutDeviceIntRect(); + } + + LayoutDeviceIntRect bounds = point.CharBounds(); + if (!bounds.x && !bounds.y && bounds.IsZeroArea()) { + return bounds; + } + nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, Acc()); + return bounds; +} + +LayoutDeviceIntRect HyperTextAccessibleBase::TextBounds(int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aCoordType) { + LayoutDeviceIntRect result; + if (CharacterCount() == 0) { + result = Acc()->Bounds(); + nsAccUtils::ConvertScreenCoordsTo(&result.x, &result.y, aCoordType, Acc()); + return result; + } + + index_t startOffset = ConvertMagicOffset(aStartOffset); + index_t endOffset = ConvertMagicOffset(aEndOffset); + if (!startOffset.IsValid() || startOffset >= endOffset) { + return LayoutDeviceIntRect(); + } + + // Here's where things get complicated. We can't simply query the first + // and last character, and union their bounds. They might reside on different + // lines, and a simple union may yield an incorrect width. We + // should use the length of the longest spanned line for our width. + + TextLeafPoint startPoint = + ToTextLeafPoint(static_cast<int32_t>(startOffset), false); + TextLeafPoint endPoint = + ToTextLeafPoint(static_cast<int32_t>(endOffset), true); + if (!endPoint) { + // The caller provided an invalid offset. + return LayoutDeviceIntRect(); + } + + // Step backwards from the point returned by ToTextLeafPoint above. + // For our purposes, `endPoint` should be inclusive. + endPoint = + endPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious); + if (endPoint < startPoint) { + return result; + } + + if (endPoint == startPoint) { + result = startPoint.CharBounds(); + } else { + TextLeafRange range(startPoint, endPoint); + result = range.Bounds(); + } + + // Calls to TextLeafRange::Bounds() will construct screen coordinates. + // Perform any additional conversions here. + nsAccUtils::ConvertScreenCoordsTo(&result.x, &result.y, aCoordType, Acc()); + return result; +} + +int32_t HyperTextAccessibleBase::OffsetAtPoint(int32_t aX, int32_t aY, + uint32_t aCoordType) { + Accessible* thisAcc = Acc(); + LayoutDeviceIntPoint coords = + nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, thisAcc); + if (!thisAcc->Bounds().Contains(coords.x, coords.y)) { + // The requested point does not exist in this accessible. + // Check if we used fuzzy hittesting to get here and, if + // so, return 0 to indicate this text leaf is a valid match. + LayoutDeviceIntPoint p(aX, aY); + if (aCoordType != nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) { + p = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType, thisAcc); + } + if (Accessible* doc = nsAccUtils::DocumentFor(thisAcc)) { + Accessible* hittestMatch = doc->ChildAtPoint( + p.x, p.y, Accessible::EWhichChildAtPoint::DeepestChild); + if (hittestMatch && thisAcc == hittestMatch->Parent()) { + return 0; + } + } + return -1; + } + + TextLeafPoint startPoint = ToTextLeafPoint(0, false); + // As with TextBounds, we walk to the very end of the text contained in this + // hypertext and then step backwards to make our endPoint inclusive. + TextLeafPoint endPoint = + ToTextLeafPoint(static_cast<int32_t>(CharacterCount()), true); + endPoint = + endPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious); + TextLeafPoint point = startPoint; + // XXX: We should create a TextLeafRange object for this hypertext and move + // this search inside the TextLeafRange class. + // If there are no characters in this container, we might have moved endPoint + // before startPoint. In that case, we shouldn't try to move further forward, + // as that might result in an infinite loop. + if (startPoint <= endPoint) { + for (; !point.ContainsPoint(coords.x, coords.y) && point != endPoint; + point = + point.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirNext)) { + } + } + if (!point.ContainsPoint(coords.x, coords.y)) { + LayoutDeviceIntRect startRect = startPoint.CharBounds(); + if (coords.x < startRect.x || coords.y < startRect.y) { + // Bug 1816601: The point is within the container but above or to the left + // of the rectangle at offset 0. We should really return -1, but we've + // returned 0 for many years due to a bug. Some users have unfortunately + // come to rely on this, so perpetuate this here. + return 0; + } + return -1; + } + DebugOnly<bool> ok = false; + int32_t htOffset; + std::tie(ok, htOffset) = + TransformOffset(point.mAcc, point.mOffset, /* aIsEndOffset */ false); + MOZ_ASSERT(ok, "point should be a descendant of this"); + return htOffset; +} + +TextLeafPoint HyperTextAccessibleBase::ToTextLeafPoint(int32_t aOffset, + bool aDescendToEnd) { + Accessible* thisAcc = Acc(); + if (!thisAcc->HasChildren()) { + return TextLeafPoint(thisAcc, 0); + } + Accessible* child = GetChildAtOffset(aOffset); + if (!child) { + return TextLeafPoint(); + } + if (HyperTextAccessibleBase* childHt = child->AsHyperTextBase()) { + return childHt->ToTextLeafPoint( + aDescendToEnd ? static_cast<int32_t>(childHt->CharacterCount()) : 0, + aDescendToEnd); + } + int32_t offset = aOffset - GetChildOffset(child); + return TextLeafPoint(child, offset); +} + +std::pair<bool, int32_t> HyperTextAccessibleBase::TransformOffset( + Accessible* aDescendant, int32_t aOffset, bool aIsEndOffset) const { + const Accessible* thisAcc = Acc(); + // From the descendant, go up and get the immediate child of this hypertext. + int32_t offset = aOffset; + Accessible* descendant = aDescendant; + while (descendant) { + Accessible* parent = descendant->Parent(); + if (parent == thisAcc) { + return {true, 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; + } + + // The given a11y point cannot be mapped to an offset relative to this + // hypertext accessible. Return the start or the end depending on whether this + // is a start ofset or an end offset, thus clipping to the relevant endpoint. + return {false, aIsEndOffset ? static_cast<int32_t>(CharacterCount()) : 0}; +} + +void HyperTextAccessibleBase::AdjustOriginIfEndBoundary( + TextLeafPoint& aOrigin, AccessibleTextBoundary aBoundaryType, + bool aAtOffset) const { + if (aBoundaryType != nsIAccessibleText::BOUNDARY_LINE_END && + aBoundaryType != nsIAccessibleText::BOUNDARY_WORD_END) { + return; + } + TextLeafPoint actualOrig = + aOrigin.IsCaret() ? aOrigin.ActualizeCaret(/* aAdjustAtEndOfLine */ false) + : aOrigin; + if (aBoundaryType == nsIAccessibleText::BOUNDARY_LINE_END) { + if (!actualOrig.IsLineFeedChar()) { + return; + } + aOrigin = + actualOrig.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious); + } else { // BOUNDARY_WORD_END + if (aAtOffset) { + // For TextAtOffset with BOUNDARY_WORD_END, we follow WebKitGtk here and + // return the word which ends after the origin if the origin is a word end + // boundary. Also, if the caret is at the end of a line, our tests expect + // the word after the caret, not the word before. The reason for that + // is a mystery lost to history. We can do that by explicitly using the + // actualized caret without adjusting for end of line. + aOrigin = actualOrig; + return; + } + if (!actualOrig.IsSpace()) { + return; + } + TextLeafPoint prevChar = + actualOrig.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious); + if (prevChar != actualOrig && !prevChar.IsSpace()) { + // aOrigin is a word end boundary. + aOrigin = prevChar; + } + } +} + +void HyperTextAccessibleBase::TextBeforeOffset( + int32_t aOffset, AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, nsAString& aText) { + *aStartOffset = *aEndOffset = 0; + aText.Truncate(); + + if (aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_START || + aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_END) { + return; // Not implemented. + } + + uint32_t adjustedOffset = ConvertMagicOffset(aOffset); + if (adjustedOffset == std::numeric_limits<uint32_t>::max()) { + NS_ERROR("Wrong given offset!"); + return; + } + + if (aBoundaryType == nsIAccessibleText::BOUNDARY_CHAR) { + if (adjustedOffset > 0) { + CharAt(static_cast<int32_t>(adjustedOffset) - 1, aText, aStartOffset, + aEndOffset); + } + return; + } + + TextLeafPoint orig; + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) { + orig = TextLeafPoint::GetCaret(Acc()); + } else { + orig = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset)); + } + if (!orig) { + // This can happen if aOffset is invalid. + return; + } + AdjustOriginIfEndBoundary(orig, aBoundaryType); + TextLeafPoint end = + orig.FindBoundary(aBoundaryType, eDirPrevious, + TextLeafPoint::BoundaryFlags::eIncludeOrigin); + bool ok; + std::tie(ok, *aEndOffset) = TransformOffset(end.mAcc, end.mOffset, + /* aIsEndOffset */ true); + if (!ok) { + // There is no previous boundary inside this HyperText. + *aStartOffset = *aEndOffset = 0; + return; + } + TextLeafPoint start = end.FindBoundary(aBoundaryType, eDirPrevious); + // If TransformOffset fails because start is outside this HyperText, + // *aStartOffset will be 0, which is what we want. + std::tie(ok, *aStartOffset) = TransformOffset(start.mAcc, start.mOffset, + /* aIsEndOffset */ false); + TextSubstring(*aStartOffset, *aEndOffset, aText); +} + +void HyperTextAccessibleBase::TextAtOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, + int32_t* aEndOffset, + nsAString& aText) { + *aStartOffset = *aEndOffset = 0; + aText.Truncate(); + + if (aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_START || + aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_END) { + return; // Not implemented. + } + + uint32_t adjustedOffset = ConvertMagicOffset(aOffset); + if (adjustedOffset == std::numeric_limits<uint32_t>::max()) { + NS_ERROR("Wrong given offset!"); + return; + } + + if (aBoundaryType == nsIAccessibleText::BOUNDARY_CHAR) { + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) { + TextLeafPoint caret = TextLeafPoint::GetCaret(Acc()); + if (caret.IsCaretAtEndOfLine()) { + // The caret is at the end of the line. Return no character. + *aStartOffset = *aEndOffset = static_cast<int32_t>(adjustedOffset); + return; + } + } + CharAt(adjustedOffset, aText, aStartOffset, aEndOffset); + return; + } + + TextLeafPoint start, end; + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) { + start = TextLeafPoint::GetCaret(Acc()); + AdjustOriginIfEndBoundary(start, aBoundaryType, /* aAtOffset */ true); + end = start; + } else { + start = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset)); + Accessible* childAcc = GetChildAtOffset(adjustedOffset); + if (childAcc && childAcc->IsHyperText()) { + // We're searching for boundaries enclosing an embedded object. + // An embedded object might contain several boundaries itself. + // Thus, we must ensure we search for the end boundary from the last + // text in the subtree, not just the first. + // For example, if the embedded object is a link and it contains two + // words, but the second word expands beyond the link, we want to + // include the part of the second word which is outside of the link. + end = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset), + /* aDescendToEnd */ true); + } else { + AdjustOriginIfEndBoundary(start, aBoundaryType, + /* aAtOffset */ true); + end = start; + } + } + if (!start) { + // This can happen if aOffset is invalid. + return; + } + start = start.FindBoundary(aBoundaryType, eDirPrevious, + TextLeafPoint::BoundaryFlags::eIncludeOrigin); + bool ok; + std::tie(ok, *aStartOffset) = TransformOffset(start.mAcc, start.mOffset, + /* aIsEndOffset */ false); + end = end.FindBoundary(aBoundaryType, eDirNext); + std::tie(ok, *aEndOffset) = TransformOffset(end.mAcc, end.mOffset, + /* aIsEndOffset */ true); + TextSubstring(*aStartOffset, *aEndOffset, aText); +} + +void HyperTextAccessibleBase::TextAfterOffset( + int32_t aOffset, AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, nsAString& aText) { + *aStartOffset = *aEndOffset = 0; + aText.Truncate(); + + if (aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_START || + aBoundaryType == nsIAccessibleText::BOUNDARY_SENTENCE_END) { + return; // Not implemented. + } + + uint32_t adjustedOffset = ConvertMagicOffset(aOffset); + if (adjustedOffset == std::numeric_limits<uint32_t>::max()) { + NS_ERROR("Wrong given offset!"); + return; + } + + if (aBoundaryType == nsIAccessibleText::BOUNDARY_CHAR) { + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && adjustedOffset > 0 && + TextLeafPoint::GetCaret(Acc()).IsCaretAtEndOfLine()) { + --adjustedOffset; + } + uint32_t count = CharacterCount(); + if (adjustedOffset >= count) { + *aStartOffset = *aEndOffset = static_cast<int32_t>(count); + } else { + CharAt(static_cast<int32_t>(adjustedOffset) + 1, aText, aStartOffset, + aEndOffset); + } + return; + } + + TextLeafPoint orig; + if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) { + orig = TextLeafPoint::GetCaret(Acc()); + } else { + orig = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset), + /* aDescendToEnd */ true); + } + if (!orig) { + // This can happen if aOffset is invalid. + return; + } + AdjustOriginIfEndBoundary(orig, aBoundaryType); + TextLeafPoint start = orig.FindBoundary(aBoundaryType, eDirNext); + bool ok; + std::tie(ok, *aStartOffset) = TransformOffset(start.mAcc, start.mOffset, + /* aIsEndOffset */ false); + if (!ok) { + // There is no next boundary inside this HyperText. + *aStartOffset = *aEndOffset = static_cast<int32_t>(CharacterCount()); + return; + } + TextLeafPoint end = start.FindBoundary(aBoundaryType, eDirNext); + // If TransformOffset fails because end is outside this HyperText, + // *aEndOffset will be CharacterCount(), which is what we want. + std::tie(ok, *aEndOffset) = TransformOffset(end.mAcc, end.mOffset, + /* aIsEndOffset */ true); + TextSubstring(*aStartOffset, *aEndOffset, aText); +} + +int32_t HyperTextAccessibleBase::CaretOffset() const { + TextLeafPoint point = TextLeafPoint::GetCaret(const_cast<Accessible*>(Acc())) + .ActualizeCaret(/* aAdjustAtEndOfLine */ false); + if (point.mOffset == 0 && point.mAcc == Acc()) { + // If a text box is empty, there will be no children, so point.mAcc will be + // this HyperText. + return 0; + } + auto [ok, htOffset] = + TransformOffset(point.mAcc, point.mOffset, /* aIsEndOffset */ false); + if (!ok) { + // The caret is not within this HyperText. + return -1; + } + return htOffset; +} + +int32_t HyperTextAccessibleBase::CaretLineNumber() { + TextLeafPoint point = TextLeafPoint::GetCaret(const_cast<Accessible*>(Acc())) + .ActualizeCaret(/* aAdjustAtEndOfLine */ false); + if (point.mOffset == 0 && point.mAcc == Acc()) { + MOZ_ASSERT(CharacterCount() == 0); + // If a text box is empty, there will be no children, so point.mAcc will be + // this HyperText. + return 1; + } + + if (!point.mAcc || + (point.mAcc != Acc() && !Acc()->IsAncestorOf(point.mAcc))) { + // The caret is not within this HyperText. + return -1; + } + + TextLeafPoint firstPointInThis = TextLeafPoint(Acc(), 0); + int32_t lineNumber = 1; + for (TextLeafPoint line = point; line && firstPointInThis < line; + line = line.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_START, + eDirPrevious)) { + lineNumber++; + } + + return lineNumber; +} + +bool HyperTextAccessibleBase::IsValidOffset(int32_t aOffset) { + index_t offset = ConvertMagicOffset(aOffset); + return offset.IsValid() && offset <= CharacterCount(); +} + +bool HyperTextAccessibleBase::IsValidRange(int32_t aStartOffset, + int32_t aEndOffset) { + index_t startOffset = ConvertMagicOffset(aStartOffset); + index_t endOffset = ConvertMagicOffset(aEndOffset); + return startOffset.IsValid() && endOffset.IsValid() && + startOffset <= endOffset && endOffset <= CharacterCount(); +} + +uint32_t HyperTextAccessibleBase::LinkCount() { + return Acc()->EmbeddedChildCount(); +} + +Accessible* HyperTextAccessibleBase::LinkAt(uint32_t aIndex) { + return Acc()->EmbeddedChildAt(aIndex); +} + +int32_t HyperTextAccessibleBase::LinkIndexOf(Accessible* aLink) { + return Acc()->IndexOfEmbeddedChild(aLink); +} + +already_AddRefed<AccAttributes> HyperTextAccessibleBase::TextAttributes( + bool aIncludeDefAttrs, int32_t aOffset, int32_t* aStartOffset, + int32_t* aEndOffset) { + *aStartOffset = *aEndOffset = 0; + index_t offset = ConvertMagicOffset(aOffset); + if (!offset.IsValid() || offset > CharacterCount()) { + NS_ERROR("Wrong in offset!"); + return RefPtr{new AccAttributes()}.forget(); + } + + Accessible* originAcc = GetChildAtOffset(offset); + if (!originAcc) { + // 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) { + return DefaultTextAttributes(); + } + } + return RefPtr{new AccAttributes()}.forget(); + } + + if (!originAcc->IsText()) { + // This is an embedded object. One or more consecutive embedded objects + // form a single attrs run with no attributes. + *aStartOffset = aOffset; + *aEndOffset = aOffset + 1; + Accessible* parent = originAcc->Parent(); + if (!parent) { + return RefPtr{new AccAttributes()}.forget(); + } + int32_t originIdx = originAcc->IndexInParent(); + if (originIdx > 0) { + // Check for embedded objects before the origin. + for (uint32_t idx = originIdx - 1;; --idx) { + Accessible* sibling = parent->ChildAt(idx); + if (sibling->IsText()) { + break; + } + --*aStartOffset; + if (idx == 0) { + break; + } + } + } + // Check for embedded objects after the origin. + for (uint32_t idx = originIdx + 1;; ++idx) { + Accessible* sibling = parent->ChildAt(idx); + if (!sibling || sibling->IsText()) { + break; + } + ++*aEndOffset; + } + return RefPtr{new AccAttributes()}.forget(); + } + + TextLeafPoint origin = ToTextLeafPoint(static_cast<int32_t>(offset)); + TextLeafPoint start = + origin.FindTextAttrsStart(eDirPrevious, /* aIncludeOrigin */ true); + bool ok; + std::tie(ok, *aStartOffset) = TransformOffset(start.mAcc, start.mOffset, + /* aIsEndOffset */ false); + TextLeafPoint end = + origin.FindTextAttrsStart(eDirNext, /* aIncludeOrigin */ false); + std::tie(ok, *aEndOffset) = TransformOffset(end.mAcc, end.mOffset, + /* aIsEndOffset */ true); + return origin.GetTextAttributes(aIncludeDefAttrs); +} + +void HyperTextAccessibleBase::CroppedSelectionRanges( + nsTArray<TextRange>& aRanges) const { + SelectionRanges(&aRanges); + const Accessible* acc = Acc(); + aRanges.RemoveElementsBy([acc](auto& range) { + if (range.StartPoint() == range.EndPoint()) { + return true; // Collapsed, so remove this range. + } + // If this is the document, it contains all ranges, so there's no need to + // crop. + if (!acc->IsDoc()) { + // If we fail to crop, the range is outside acc, so remove it. + return !range.Crop(const_cast<Accessible*>(acc)); + } + return false; + }); +} + +int32_t HyperTextAccessibleBase::SelectionCount() { + nsTArray<TextRange> ranges; + CroppedSelectionRanges(ranges); + return static_cast<int32_t>(ranges.Length()); +} + +bool HyperTextAccessibleBase::SelectionBoundsAt(int32_t aSelectionNum, + int32_t* aStartOffset, + int32_t* aEndOffset) { + nsTArray<TextRange> ranges; + CroppedSelectionRanges(ranges); + if (aSelectionNum >= static_cast<int32_t>(ranges.Length())) { + return false; + } + TextRange& range = ranges[aSelectionNum]; + Accessible* thisAcc = Acc(); + if (range.StartContainer() == thisAcc) { + *aStartOffset = range.StartOffset(); + } else { + bool ok; + // range.StartContainer() isn't a text leaf, so don't use its offset. + std::tie(ok, *aStartOffset) = + TransformOffset(range.StartContainer(), 0, /* aDescendToEnd */ false); + } + if (range.EndContainer() == thisAcc) { + *aEndOffset = range.EndOffset(); + } else { + bool ok; + // range.EndContainer() isn't a text leaf, so don't use its offset. If + // range.EndOffset() is > 0, we want to include this container, so pas + // offset 1. + std::tie(ok, *aEndOffset) = + TransformOffset(range.EndContainer(), range.EndOffset() == 0 ? 0 : 1, + /* aDescendToEnd */ true); + } + return true; +} + +bool HyperTextAccessibleBase::SetSelectionBoundsAt(int32_t aSelectionNum, + int32_t aStartOffset, + int32_t aEndOffset) { + TextLeafRange range(ToTextLeafPoint(aStartOffset), + ToTextLeafPoint(aEndOffset, true)); + if (!range) { + NS_ERROR("Wrong in offset"); + return false; + } + + return range.SetSelection(aSelectionNum); +} + +void HyperTextAccessibleBase::ScrollSubstringTo(int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aScrollType) { + TextLeafRange range(ToTextLeafPoint(aStartOffset), + ToTextLeafPoint(aEndOffset, true)); + range.ScrollIntoView(aScrollType); +} + +} // namespace mozilla::a11y |