Bug 1814785: Include the transform only for the frame itself in the a11y cache, rather than calculating it relative to an ancestor. r=morgan

Calculating it relative to an ancestor causes problems when the frame ancestry and the a11y ancestry diverge.
For example, this can happen for a transform which is also position: absolute.
In that case, the parent Accessible's frame is not an ancestor of the child Accessible's frame.
Since the transform is no longer relative to the ancestor, we no longer need to remove our parent-relative bounds before applying it.
However, we do need to ensure that we apply the transform *before* adding parent-relative bounds.

Differential Revision: https://phabricator.services.mozilla.com/D169059
This commit is contained in:
James Teh 2023-02-08 05:45:55 +00:00
Родитель 835ec5411a
Коммит 1dcf0a5946
4 изменённых файлов: 37 добавлений и 24 удалений

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

@ -53,6 +53,7 @@
#include "nsIContent.h"
#include "nsIFormControl.h"
#include "nsDisplayList.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsIFrame.h"
@ -3478,16 +3479,12 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
if (aCacheDomain & CacheDomain::TransformMatrix) {
bool transformed = false;
if (frame && frame->IsTransformed()) {
// We need to find a frame to make our transform relative to.
// It's important this frame have a corresponding accessible,
// because this transform is applied while walking the accessibility
// tree (in the parent process), not the frame tree.
nsIFrame* boundingFrame = FindNearestAccessibleAncestorFrame();
// This matrix is only valid when applied to CSSPixel points/rects
// in the coordinate space of `frame`. It also includes the translation
// to the parent space.
gfx::Matrix4x4Flagged mtx = nsLayoutUtils::GetTransformToAncestor(
RelativeTo{frame}, RelativeTo{boundingFrame}, nsIFrame::IN_CSS_UNITS);
// in the coordinate space of `frame`.
gfx::Matrix4x4 mtx = nsDisplayTransform::GetResultingTransformMatrix(
frame, nsPoint(0, 0), AppUnitsPerCSSPixel(),
nsDisplayTransform::INCLUDE_PERSPECTIVE |
nsDisplayTransform::OFFSET_BY_ORIGIN);
// We might get back the identity matrix. This can happen if there is no
// actual transform. For example, if an element has
// will-change: transform, nsIFrame::IsTransformed will return true, but
@ -3496,8 +3493,7 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
// point caching it.
transformed = !mtx.IsIdentity();
if (transformed) {
UniquePtr<gfx::Matrix4x4> ptr =
MakeUnique<gfx::Matrix4x4>(mtx.GetMatrix());
UniquePtr<gfx::Matrix4x4> ptr = MakeUnique<gfx::Matrix4x4>(mtx);
fields->SetAttribute(nsGkAtoms::transform, std::move(ptr));
}
}

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

@ -491,7 +491,7 @@ void RemoteAccessibleBase<Derived>::ApplyCrossDocOffset(nsRect& aBounds) const {
template <class Derived>
bool RemoteAccessibleBase<Derived>::ApplyTransform(
nsRect& aCumulativeBounds, const nsRect& aParentRelativeBounds) const {
nsRect& aCumulativeBounds) const {
// First, attempt to retrieve the transform from the cache.
Maybe<const UniquePtr<gfx::Matrix4x4>&> maybeTransform =
mCachedFields->GetAttribute<UniquePtr<gfx::Matrix4x4>>(
@ -500,13 +500,6 @@ bool RemoteAccessibleBase<Derived>::ApplyTransform(
return false;
}
// The transform matrix we cache is meant to operate on rects
// within the coordinate space of the frame to which the
// transform is applied (self-relative rects). We cache bounds
// relative to some ancestor. Remove the relative offset before
// transforming. The transform matrix will add it back in.
aCumulativeBounds.MoveBy(-aParentRelativeBounds.TopLeft());
auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix(
*(*maybeTransform));
@ -572,6 +565,10 @@ LayoutDeviceIntRect RemoteAccessibleBase<Derived>::BoundsWithOffset(
Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
if (maybeBounds) {
nsRect bounds = *maybeBounds;
// maybeBounds is parent-relative. However, the transform matrix we cache
// (if any) is meant to operate on self-relative rects. Therefore, make
// bounds self-relative until after we transform.
bounds.MoveTo(0, 0);
const DocAccessibleParent* topDoc = IsDoc() ? AsDoc() : nullptr;
if (aOffset.isSome()) {
@ -581,9 +578,11 @@ LayoutDeviceIntRect RemoteAccessibleBase<Derived>::BoundsWithOffset(
bounds.SetRectY(bounds.y + internalRect.y, internalRect.height);
}
ApplyCrossDocOffset(bounds);
Unused << ApplyTransform(bounds);
// Now apply the parent-relative offset.
bounds.MoveBy(maybeBounds->TopLeft());
Unused << ApplyTransform(bounds, *maybeBounds);
ApplyCrossDocOffset(bounds);
LayoutDeviceIntRect devPxBounds;
const Accessible* acc = Parent();
@ -635,11 +634,14 @@ LayoutDeviceIntRect RemoteAccessibleBase<Derived>::BoundsWithOffset(
encounteredFixedContainer = false;
}
if (!encounteredFixedContainer) {
// The transform matrix we cache (if any) is meant to operate on
// self-relative rects. Therefore, we must apply the transform before
// we make bounds parent-relative.
Unused << remoteAcc->ApplyTransform(bounds);
// Regardless of whether this is a doc, we should offset `bounds`
// by the bounds retrieved here. This is how we build screen
// coordinates from relative coordinates.
bounds.MoveBy(remoteBounds.X(), remoteBounds.Y());
Unused << remoteAcc->ApplyTransform(bounds, remoteBounds);
}
if (remoteAcc->IsFixedPos()) {

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

@ -428,8 +428,7 @@ class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase {
protected:
void SetParent(Derived* aParent);
Maybe<nsRect> RetrieveCachedBounds() const;
bool ApplyTransform(nsRect& aCumulativeBounds,
const nsRect& aParentRelativeBounds) const;
bool ApplyTransform(nsRect& aCumulativeBounds) const;
void ApplyScrollOffset(nsRect& aBounds) const;
void ApplyCrossDocOffset(nsRect& aBounds) const;
LayoutDeviceIntRect BoundsWithOffset(Maybe<nsRect> aOffset) const;

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

@ -114,3 +114,19 @@ addAccessibleTask(
},
{ topLevel: true, iframe: true, remoteIframe: true }
);
// Test translated, position: absolute Accessible in a container.
addAccessibleTask(
`
<div id="container">
<div id="transform" style="position: absolute; transform: translate(100px, 100px);">
<p id="p">test</p>
</div>
</div>
`,
async function(browser, docAcc) {
await testBoundsWithContent(docAcc, "transform", browser);
await testBoundsWithContent(docAcc, "p", browser);
},
{ topLevel: true, iframe: true, remoteIframe: true }
);