Bug 1737919 part 3: Support spelling errors for cached RemoteAccessibles. r=morgan

Even though spelling errors can cross Accessibles and are represented in the DOM as ranges, we cache them for each text leaf.
This is necessary so that we correctly update the offsets when text is inserted or removed from a leaf.
We cache them as an array of offsets, including both the start and end offset for each range.
We use -1 as the start offset to indicate when a spelling error starts in a previous Accessible.
When a spelling error starts in an Accessible but ends in a subsequent one, we simply don't include an end offset in the array.
This structure means we can easily query spelling error points or ranges with a binary search.

Differential Revision: https://phabricator.services.mozilla.com/D147243
This commit is contained in:
James Teh 2022-05-27 10:56:41 +00:00
Родитель e02ce0a43e
Коммит 3daa72eb49
4 изменённых файлов: 131 добавлений и 2 удалений

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

@ -25,6 +25,7 @@ class CacheDomain {
static constexpr uint64_t TransformMatrix = ((uint64_t)0x1) << 10;
static constexpr uint64_t ScrollPosition = ((uint64_t)0x1) << 11;
static constexpr uint64_t Table = ((uint64_t)0x1) << 12;
static constexpr uint64_t Spelling = ((uint64_t)0x1) << 13;
static constexpr uint64_t All = ~((uint64_t)0x0);
};

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

@ -1065,7 +1065,38 @@ bool TextLeafPoint::IsInSpellingError() const {
// spelling error.
return !domRanges.IsEmpty();
}
return false;
RemoteAccessible* acc = mAcc->AsRemote();
MOZ_ASSERT(acc);
if (!acc->mCachedFields) {
return false;
}
auto spellingErrors =
acc->mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::spelling);
if (!spellingErrors) {
return false;
}
size_t index;
const bool foundOrigin = BinarySearch(
*spellingErrors, 0, spellingErrors->Length(), mOffset, &index);
// In spellingErrors, even indices are start offsets, odd indices are end
// offsets.
const bool foundStart = index % 2 == 0;
if (foundOrigin) {
// mOffset is a spelling error boundary. If it's a start offset, we're in a
// spelling error.
return foundStart;
}
// index points at the next spelling error boundary after mOffset.
if (index == 0) {
return false; // No spelling errors before mOffset.
}
if (foundStart) {
// We're not in a spelling error because it starts after mOffset.
return false;
}
// A spelling error ends after mOffset.
return true;
}
TextLeafPoint TextLeafPoint::FindSpellingErrorSameAcc(
@ -1123,8 +1154,86 @@ TextLeafPoint TextLeafPoint::FindSpellingErrorSameAcc(
}
}
}
return TextLeafPoint();
}
return TextLeafPoint();
RemoteAccessible* acc = mAcc->AsRemote();
MOZ_ASSERT(acc);
if (!acc->mCachedFields) {
return TextLeafPoint();
}
auto spellingErrors =
acc->mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::spelling);
if (!spellingErrors) {
return TextLeafPoint();
}
size_t index;
if (BinarySearch(*spellingErrors, 0, spellingErrors->Length(), mOffset,
&index)) {
// mOffset is in spellingErrors.
if (aIncludeOrigin) {
return *this;
}
if (aDirection == eDirNext) {
// We don't want the origin, so move to the next spelling error boundary
// after mOffset.
++index;
}
}
// index points at the next spelling error boundary after mOffset.
if (aDirection == eDirNext) {
if (spellingErrors->Length() == index) {
return TextLeafPoint(); // No spelling error boundary after us.
}
return TextLeafPoint(mAcc, (*spellingErrors)[index]);
}
if (index == 0) {
return TextLeafPoint(); // No spelling error boundary before us.
}
// Decrement index so it points at a spelling error boundary before mOffset.
--index;
if ((*spellingErrors)[index] == -1) {
MOZ_ASSERT(index == 0);
// A spelling error starts before mAcc.
return TextLeafPoint();
}
return TextLeafPoint(mAcc, (*spellingErrors)[index]);
}
/* static */
nsTArray<int32_t> TextLeafPoint::GetSpellingErrorOffsets(
LocalAccessible* aAcc) {
nsINode* node = aAcc->GetNode();
auto domRanges = FindDOMSpellingErrors(
aAcc, 0, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT);
// Our offsets array will contain two offsets for each range: one for the
// start, one for the end. That is, the array is of the form:
// [r1start, r1end, r2start, r2end, ...]
nsTArray<int32_t> offsets(domRanges.Length() * 2);
for (nsRange* domRange : domRanges) {
if (domRange->GetStartContainer() == node) {
offsets.AppendElement(static_cast<int32_t>(ContentToRenderedOffset(
aAcc, static_cast<int32_t>(domRange->StartOffset()))));
} else {
// This range overlaps aAcc, but starts before it.
// This can only happen for the first range.
MOZ_ASSERT(domRange == *domRanges.begin() && offsets.IsEmpty());
// Using -1 here means this won't be treated as the start of a spelling
// error range, while still indicating that we're within a spelling error.
offsets.AppendElement(-1);
}
if (domRange->GetEndContainer() == node) {
offsets.AppendElement(static_cast<int32_t>(ContentToRenderedOffset(
aAcc, static_cast<int32_t>(domRange->EndOffset()))));
} else {
// This range overlaps aAcc, but ends after it.
// This can only happen for the last range.
MOZ_ASSERT(domRange == *domRanges.rbegin());
// We don't append -1 here because this would just make things harder for
// a binary search.
}
}
return offsets;
}
already_AddRefed<AccAttributes> TextLeafPoint::GetTextAttributesLocalAcc(

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

@ -15,6 +15,7 @@
namespace mozilla::a11y {
class Accessible;
class LocalAccessible;
/**
* Represents a point within accessible text.
@ -128,6 +129,13 @@ class TextLeafPoint final {
already_AddRefed<AccAttributes> GetTextAttributesLocalAcc(
bool aIncludeDefaults = true) const;
/**
* Get the offsets of all spelling errors in a given LocalAccessible. This
* should only be used when pushing the cache. Most callers will want
* FindTextAttrsStart instead.
*/
static nsTArray<int32_t> GetSpellingErrorOffsets(LocalAccessible* aAcc);
/**
* Find the start of a run of text attributes in a specific direction.
* A text attributes run is a span of text where the attributes are the same.

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

@ -3256,6 +3256,17 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
}
}
// If text changes, we must also update spelling errors.
if (aCacheDomain & (CacheDomain::Spelling | CacheDomain::Text) &&
IsTextLeaf()) {
auto spellingErrors = TextLeafPoint::GetSpellingErrorOffsets(this);
if (!spellingErrors.IsEmpty()) {
fields->SetAttribute(nsGkAtoms::spelling, std::move(spellingErrors));
} else if (aUpdateType == CacheUpdateType::Update) {
fields->SetAttribute(nsGkAtoms::spelling, DeleteEntry());
}
}
nsIFrame* frame = GetFrame();
if (aCacheDomain & (CacheDomain::Text | CacheDomain::Bounds) &&
!HasChildren()) {