Bug 1822340 part 2: When hit testing using the cache, only match a TextLeafAccessible if one of its lines includes the requested point. r=morgan

Previously, we always used the Accessible's rect.
However, if the text wraps across lines, its rect might cover a wider area than the actual text.
That meant we were matching a wrapped text leaf when we shouldn't in some cases.
Now, we restrict text leaf matches to the rects occupied by text.

Differential Revision: https://phabricator.services.mozilla.com/D173097
This commit is contained in:
James Teh 2023-03-23 00:38:13 +00:00
Родитель 3151b74de4
Коммит ba7744a7e8
3 изменённых файлов: 70 добавлений и 1 удалений

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

@ -329,6 +329,65 @@ double RemoteAccessibleBase<Derived>::Step() const {
return UnspecifiedNaN<double>();
}
template <class Derived>
bool RemoteAccessibleBase<Derived>::ContainsPoint(int32_t aX, int32_t aY) {
if (!Bounds().Contains(aX, aY)) {
return false;
}
if (!IsTextLeaf()) {
return true;
}
// This is a text leaf. The text might wrap across lines, which means our
// rect might cover a wider area than the actual text. For example, if the
// text begins in the middle of the first line and wraps on to the second,
// the rect will cover the start of the first line and the end of the second.
auto lines = GetCachedTextLines();
if (!lines) {
// This means the text is empty or occupies a single line (but does not
// begin the line). In that case, the Bounds check above is sufficient,
// since there's only one rect.
return true;
}
uint32_t length = lines->Length();
MOZ_ASSERT(length > 0,
"Line starts shouldn't be in cache if there aren't any");
if (length == 0 || (length == 1 && (*lines)[0] == 0)) {
// This means the text begins and occupies a single line. Again, the Bounds
// check above is sufficient.
return true;
}
// Walk the lines of the text. Even if this text doesn't start at the
// beginning of a line (i.e. lines[0] > 0), we always want to consider its
// first line.
int32_t lineStart = 0;
for (uint32_t index = 0; index <= length; ++index) {
int32_t lineEnd;
if (index < length) {
int32_t nextLineStart = (*lines)[index];
if (nextLineStart == 0) {
// This Accessible starts at the beginning of a line. Here, we always
// treat 0 as the first line start anyway.
MOZ_ASSERT(index == 0);
continue;
}
lineEnd = nextLineStart - 1;
} else {
// This is the last line.
lineEnd = static_cast<int32_t>(nsAccUtils::TextLength(this)) - 1;
}
MOZ_ASSERT(lineEnd >= lineStart);
nsRect lineRect = GetCachedCharRect(lineStart);
if (lineEnd > lineStart) {
lineRect.UnionRect(lineRect, GetCachedCharRect(lineEnd));
}
if (BoundsWithOffset(Some(lineRect)).Contains(aX, aY)) {
return true;
}
lineStart = lineEnd + 1;
}
return false;
}
template <class Derived>
Accessible* RemoteAccessibleBase<Derived>::ChildAtPoint(
int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) {
@ -399,7 +458,7 @@ Accessible* RemoteAccessibleBase<Derived>::ChildAtPoint(
break;
}
if (acc->Bounds().Contains(aX, aY)) {
if (acc->ContainsPoint(aX, aY)) {
// Because our rects are in hittesting order, the
// first match we encounter is guaranteed to be the
// deepest match.

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

@ -440,6 +440,7 @@ class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase {
void ApplyCrossDocOffset(nsRect& aBounds) const;
LayoutDeviceIntRect BoundsWithOffset(Maybe<nsRect> aOffset) const;
bool IsFixedPos() const;
bool ContainsPoint(int32_t aX, int32_t aY);
virtual void ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
int32_t* aPosInSet) const override;

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

@ -101,6 +101,11 @@ async function runTests(browser, accDoc) {
containerWithInaccessibleChildP2,
containerWithInaccessibleChildP2.firstChild
);
info("Testing wrapped text");
const wrappedTextP = findAccessibleChildByID(accDoc, "wrappedTextP");
const wrappedTextA = findAccessibleChildByID(accDoc, "wrappedTextA");
await hitTest(browser, wrappedTextP, wrappedTextA, wrappedTextA.firstChild);
}
addAccessibleTask(
@ -142,6 +147,10 @@ addAccessibleTask(
<p aria-hidden="true">hi</p>
<p id="containerWithInaccessibleChild_p2">bye</p>
</div>
<p id="wrappedTextP" style="width: 3ch; font-family: monospace;">
<a id="wrappedTextA" href="https://example.com/">a</a>b cd
</p>
`,
runTests,
{