Bug 1726227: Cache parent-relative accessible bounds, resolution in parent process r=Jamie,eeejay

Differential Revision: https://phabricator.services.mozilla.com/D123399
This commit is contained in:
Morgan Reschenberg
2021-10-05 20:44:46 +00:00
parent 3bd65731f0
commit 0b1e546f30
21 changed files with 331 additions and 13 deletions

View File

@@ -15,6 +15,7 @@ class CacheDomain {
static constexpr uint64_t NameAndDescription = ((uint64_t)0x1) << 0;
static constexpr uint64_t Value = ((uint64_t)0x1) << 1;
static constexpr uint64_t Bounds = ((uint64_t)0x1) << 2;
static constexpr uint64_t Resolution = ((uint64_t)0x1) << 3;
static constexpr uint64_t All = ~((uint64_t)0x0);
};

View File

@@ -928,6 +928,10 @@ void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
}
}
if (IPCAccessibilityActive() && mDocument) {
mDocument->ProcessBoundsChanged();
}
mObservingState = eRefreshObserving;
if (!mDocument) return;

View File

@@ -11,6 +11,7 @@
#include "ARIAGridAccessibleWrap.h"
#include "ARIAMap.h"
#include "DocAccessible-inl.h"
#include "DocAccessibleChild.h"
#include "FocusManager.h"
#include "HTMLCanvasAccessible.h"
#include "HTMLElementAccessibles.h"
@@ -49,7 +50,6 @@
#ifdef XP_WIN
# include "mozilla/a11y/Compatibility.h"
# include "mozilla/dom/ContentChild.h"
# include "mozilla/StaticPrefs_accessibility.h"
# include "mozilla/StaticPtr.h"
#endif
@@ -73,6 +73,7 @@
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/SVGGeometryFrame.h"
#include "nsDeckFrame.h"
@@ -383,6 +384,34 @@ void nsAccessibilityService::NotifyOfImageSizeAvailable(
}
}
void nsAccessibilityService::NotifyOfPossibleBoundsChange(
mozilla::PresShell* aPresShell, nsIContent* aContent) {
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
DocAccessible* document = GetDocAccessible(aPresShell);
if (document) {
LocalAccessible* accessible = document->GetAccessible(aContent);
if (accessible) {
document->MarkForBoundsProcessing(accessible);
document->Controller()->ScheduleProcessing();
}
}
}
}
void nsAccessibilityService::NotifyOfResolutionChange(
mozilla::PresShell* aPresShell, float aResolution) {
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
DocAccessible* document = GetDocAccessible(aPresShell);
if (document) {
nsTArray<mozilla::a11y::CacheData> data(1);
RefPtr<AccAttributes> fields = new AccAttributes();
fields->SetAttribute(nsGkAtoms::resolution, aResolution);
data.AppendElement(mozilla::a11y::CacheData(0, fields));
document->IPCDoc()->SendCache(CacheUpdateType::Update, data, true);
}
}
}
LocalAccessible* nsAccessibilityService::GetRootDocumentAccessible(
PresShell* aPresShell, bool aCanCreate) {
PresShell* presShell = aPresShell;

View File

@@ -230,6 +230,12 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
void NotifyOfImageSizeAvailable(mozilla::PresShell* aPresShell,
nsIContent* aContent);
void NotifyOfPossibleBoundsChange(mozilla::PresShell* aPresShell,
nsIContent* aContent);
void NotifyOfResolutionChange(mozilla::PresShell* aPresShell,
float aResolution);
// nsAccessibiltiyService
/**

View File

@@ -9,6 +9,7 @@
#include "mozilla/a11y/Role.h"
#include "mozilla/a11y/AccTypes.h"
#include "nsString.h"
#include "nsRect.h"
class nsAtom;
@@ -125,6 +126,8 @@ class Accessible {
virtual double MaxValue() const = 0;
virtual double Step() const = 0;
virtual nsIntRect Bounds() const = 0;
// Type "is" methods
bool IsDoc() const { return HasGenericType(eDocument); }

View File

@@ -908,6 +908,10 @@ void DocAccessible::ContentRemoved(nsIContent* aChildNode,
ContentRemoved(aChildNode);
}
void DocAccessible::MarkForBoundsProcessing(LocalAccessible* aAcc) {
mMaybeBoundsChanged.EnsureInserted(aAcc);
}
void DocAccessible::ParentChainChanged(nsIContent* aContent) {}
////////////////////////////////////////////////////////////////////////////////
@@ -1335,6 +1339,31 @@ void DocAccessible::ProcessInvalidationList() {
mInvalidationList.Clear();
}
void DocAccessible::ProcessBoundsChanged() {
if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
return;
}
nsTArray<CacheData> data;
for (auto* acc : mMaybeBoundsChanged) {
if (!acc->IsDefunct()) {
RefPtr<AccAttributes> fields = acc->BundleFieldsForCache(
CacheDomain::Bounds, CacheUpdateType::Update);
if (fields->Count()) {
data.AppendElement(CacheData(
acc->IsDoc() ? 0 : reinterpret_cast<uint64_t>(acc->UniqueID()),
fields));
}
}
}
mMaybeBoundsChanged.Clear();
if (data.Length()) {
IPCDoc()->SendCache(CacheUpdateType::Update, data, true);
}
}
LocalAccessible* DocAccessible::GetAccessibleEvenIfNotInMap(
nsINode* aNode) const {
if (!aNode->IsContent() ||

View File

@@ -17,6 +17,7 @@
#include "nsIDocumentObserver.h"
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsTHashSet.h"
#include "nsWeakReference.h"
class nsAccessiblePivot;
@@ -340,6 +341,13 @@ class DocAccessible : public HyperTextAccessibleWrap,
void ContentRemoved(LocalAccessible* aAccessible);
void ContentRemoved(nsIContent* aContentNode);
/*
* Add the given accessible to mMaybeBoundsChanged so we
* can (later) check if its bounds have changed, and update
* the cache appropriately.
*/
void MarkForBoundsProcessing(LocalAccessible* aAcc);
/**
* Updates accessible tree when rendered text is changed.
*/
@@ -482,6 +490,13 @@ class DocAccessible : public HyperTextAccessibleWrap,
*/
void ProcessInvalidationList();
/**
* Called from NotificationController to process this doc's
* mMaybeBoundsChanged list. Sends a cache update for each acc in this
* doc whose bounds have changed since reflow.
*/
void ProcessBoundsChanged();
/**
* Steals or puts back accessible subtrees.
*/
@@ -697,6 +712,8 @@ class DocAccessible : public HyperTextAccessibleWrap,
// Exclusively owned by IPDL so don't manually delete it!
DocAccessibleChild* mIPCDoc;
nsTHashSet<RefPtr<LocalAccessible>> mMaybeBoundsChanged;
};
inline DocAccessible* LocalAccessible::AsDoc() {

View File

@@ -115,6 +115,7 @@ LocalAccessible::LocalAccessible(nsIContent* aContent, DocAccessible* aDoc)
mDoc(aDoc),
mParent(nullptr),
mIndexInParent(-1),
mBounds(),
mStateFlags(0),
mContextFlags(0),
mReorderEventTarget(false),
@@ -618,6 +619,62 @@ LocalAccessible* LocalAccessible::LocalChildAtPoint(
return accessible;
}
nsRect LocalAccessible::ParentRelativeBounds() {
nsIFrame* boundingFrame = nullptr;
nsIFrame* frame = GetFrame();
if (frame && mContent) {
if (mContent->GetProperty(nsGkAtoms::hitregion) && mContent->IsElement()) {
// This is for canvas fallback content
// Find a canvas frame the found hit region is relative to.
nsIFrame* canvasFrame = frame->GetParent();
if (canvasFrame) {
canvasFrame = nsLayoutUtils::GetClosestFrameOfType(
canvasFrame, LayoutFrameType::HTMLCanvas);
}
if (canvasFrame) {
if (auto* canvas =
dom::HTMLCanvasElement::FromNode(canvasFrame->GetContent())) {
if (auto* context = canvas->GetCurrentContext()) {
nsRect bounds;
if (context->GetHitRegionRect(mContent->AsElement(), bounds)) {
return bounds;
}
}
}
}
}
if (mParent) {
boundingFrame = mParent->GetFrame();
}
if (!boundingFrame) {
// if we can't get the bounding frame, use the pres shell root
boundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
}
nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
frame, boundingFrame, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
if (unionRect.IsEmpty()) {
// If we end up with a 0x0 rect from above (or one with negative
// height/width) we should try using the ink overflow rect instead. If we
// use this rect, our relative bounds will match the bounds of what
// appears visually. We do this because some web authors (icloud.com for
// example) employ things like 0x0 buttons with visual overflow. Without
// this, such frames aren't navigable by screen readers.
nsRect overflow = frame->InkOverflowRectRelativeToSelf();
nsLayoutUtils::TransformRect(frame, boundingFrame, overflow);
return overflow;
}
return unionRect;
}
return nsRect();
}
nsRect LocalAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const {
nsIFrame* frame = GetFrame();
if (frame && mContent) {
@@ -3053,6 +3110,23 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
fields->SetAttribute(nsGkAtoms::step, Step());
}
if (aCacheDomain & CacheDomain::Bounds) {
nsRect newBoundsRect = ParentRelativeBounds();
if (mBounds.isNothing() || !newBoundsRect.IsEqualEdges(mBounds.value())) {
mBounds = Some(newBoundsRect);
nsTArray<int32_t> boundsArray(4);
boundsArray.AppendElement(newBoundsRect.x);
boundsArray.AppendElement(newBoundsRect.y);
boundsArray.AppendElement(newBoundsRect.width);
boundsArray.AppendElement(newBoundsRect.height);
fields->SetAttribute(nsGkAtoms::relativeBounds, std::move(boundsArray));
}
}
return fields.forget();
}

View File

@@ -478,7 +478,7 @@ class LocalAccessible : public nsISupports, public Accessible {
/**
* Return boundaries in screen coordinates.
*/
virtual nsIntRect Bounds() const;
virtual nsIntRect Bounds() const override;
/**
* Return boundaries in screen coordinates in CSS pixels.
@@ -490,6 +490,11 @@ class LocalAccessible : public nsISupports, public Accessible {
*/
virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const;
/**
* Return boundaries rect relative to the frame of the parent accessible.
*/
virtual nsRect ParentRelativeBounds();
/**
* Selects the accessible within its container if applicable.
*/
@@ -1079,6 +1084,7 @@ class LocalAccessible : public nsISupports, public Accessible {
LocalAccessible* mParent;
nsTArray<LocalAccessible*> mChildren;
int32_t mIndexInParent;
Maybe<nsRect> mBounds;
static const uint8_t kStateFlagsBits = 11;
static const uint8_t kContextFlagsBits = 3;

View File

@@ -13,6 +13,8 @@
#include "mozilla/a11y/Role.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/Unused.h"
#include "RelationType.h"
#include "xpcAccessibleDocument.h"
@@ -237,6 +239,114 @@ double RemoteAccessibleBase<Derived>::Step() const {
return UnspecifiedNaN<double>();
}
template <class Derived>
Maybe<nsRect> RemoteAccessibleBase<Derived>::RetrieveCachedBounds() const {
Maybe<const nsTArray<int32_t>&> maybeArray =
mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::relativeBounds);
if (maybeArray) {
const nsTArray<int32_t>& relativeBoundsArr = *maybeArray;
MOZ_ASSERT(relativeBoundsArr.Length() == 4,
"Incorrectly sized bounds array");
nsRect relativeBoundsRect(relativeBoundsArr[0], relativeBoundsArr[1],
relativeBoundsArr[2], relativeBoundsArr[3]);
return Some(relativeBoundsRect);
}
return Nothing();
}
template <class Derived>
nsIntRect RemoteAccessibleBase<Derived>::Bounds() const {
if (mCachedFields) {
Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
if (maybeBounds) {
nsRect bounds = *maybeBounds;
nsIntRect devPxBounds;
dom::CanonicalBrowsingContext* cbc =
static_cast<dom::BrowserParent*>(mDoc->Manager())
->GetBrowsingContext()
->Top();
dom::BrowserParent* bp = cbc->GetBrowserParent();
nsPresContext* presContext =
bp->GetOwnerElement()->OwnerDoc()->GetPresContext();
const Accessible* acc = this;
while (acc) {
if (LocalAccessible* localAcc =
const_cast<Accessible*>(acc)->AsLocal()) {
// LocalAccessible::Bounds returns screen-relative bounds in
// dev pixels.
nsIntRect localBounds = localAcc->Bounds();
// Convert our existing `bounds` rect from app units to dev pixels
devPxBounds =
bounds.ToNearestPixels(presContext->AppUnitsPerDevPixel());
// We factor in our zoom level before offsetting by
// `localBounds`, which has already taken zoom into account.
devPxBounds.ScaleRoundOut(cbc->GetFullZoom());
// The root document will always have an APZ resolution of 1,
// so we don't factor in its scale here. We also don't scale
// by GetFullZoom because LocalAccessible::Bounds already does
// that.
devPxBounds.MoveBy(localBounds.X(), localBounds.Y());
break;
}
RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote();
// Verify that remoteAcc is not `this`, since `bounds` was
// initialised to include this->RetrieveCachedBounds()
Maybe<nsRect> maybeRemoteBounds =
(remoteAcc == this) ? Nothing() : remoteAcc->RetrieveCachedBounds();
if (maybeRemoteBounds) {
// We need to take into account a non-1 resolution set on the
// presshell. This happens with async pinch zooming, among other
// things. We can't reliably query this value in the parent process,
// so we retrieve it from the document's cache.
Maybe<float> res;
if (remoteAcc->IsDoc()) {
// Apply the document's resolution to the bounds we've gathered
// thus far. We do this before applying the document's offset
// because document accs should not have their bounds scaled by
// their own resolution. They should be scaled by the resolution
// of their containing document (if any). We also skip this in the
// case that remoteAcc == this, since that implies `bounds` should
// be scaled relative to its parent doc.
res = remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>(
nsGkAtoms::resolution);
bounds.ScaleRoundOut(res.valueOr(1.0f));
}
// 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.
nsRect remoteBounds = *maybeRemoteBounds;
bounds.MoveBy(remoteBounds.X(), remoteBounds.Y());
}
acc = acc->Parent();
}
PresShell* presShell = presContext->PresShell();
// Our relative bounds are pulled from the coordinate space of the layout
// viewport, but we need them to be in the coordinate space of the visual
// viewport. We calculate the difference and translate our bounds here.
nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
presShell->GetLayoutViewportOffset();
devPxBounds.MoveBy(-(
viewportOffset.ToNearestPixels(presContext->AppUnitsPerDevPixel())));
return devPxBounds;
}
}
return nsIntRect();
}
template class RemoteAccessibleBase<RemoteAccessible>;
} // namespace a11y

View File

@@ -175,6 +175,8 @@ class RemoteAccessibleBase : public Accessible {
virtual double MaxValue() const override;
virtual double Step() const override;
virtual nsIntRect Bounds() const override;
/**
* Allow the platform to store a pointers worth of data on us.
*/
@@ -234,6 +236,7 @@ class RemoteAccessibleBase : public Accessible {
protected:
void SetParent(Derived* aParent);
Maybe<nsRect> RetrieveCachedBounds() const;
private:
uintptr_t mParent;

View File

@@ -250,7 +250,7 @@ RemoteAccessible* FocusedChild();
virtual Accessible* ChildAtPoint(
int32_t aX, int32_t aY,
LocalAccessible::EWhichChildAtPoint aWhichChild) override;
nsIntRect Bounds();
nsIntRect Bounds() const override;
nsIntRect BoundsInCSSPixels();
void Language(nsString& aLocale);

View File

@@ -15,6 +15,7 @@
#include "mozilla/a11y/Platform.h"
#include "RelationType.h"
#include "mozilla/a11y/Role.h"
#include "mozilla/StaticPrefs_accessibility.h"
namespace mozilla {
namespace a11y {
@@ -879,7 +880,11 @@ Accessible* RemoteAccessible::ChildAtPoint(
return target;
}
nsIntRect RemoteAccessible::Bounds() {
nsIntRect RemoteAccessible::Bounds() const {
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
return RemoteAccessibleBase<RemoteAccessible>::Bounds();
}
nsIntRect rect;
Unused << mDoc->SendExtents(mID, false, &(rect.x), &(rect.y), &(rect.width),
&(rect.height));

View File

@@ -219,7 +219,11 @@ uint64_t RemoteAccessible::State() const {
return state;
}
nsIntRect RemoteAccessible::Bounds() {
nsIntRect RemoteAccessible::Bounds() const {
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
return RemoteAccessibleBase<RemoteAccessible>::Bounds();
}
nsIntRect rect;
RefPtr<IAccessible> acc;

View File

@@ -39,7 +39,7 @@ async function runTests(browser, accDoc) {
await testContentBounds(browser, p2);
await testContentBounds(browser, area);
await invokeContentTask(browser, [], () => {
await SpecialPowers.spawn(browser, [], () => {
const { Layout } = ChromeUtils.import(
"chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
);

View File

@@ -46,7 +46,7 @@ async function runTests(browser, accDoc) {
await testTextNode("p2");
await testEmptyInputNode("i1");
await invokeContentTask(browser, [], () => {
await SpecialPowers.spawn(browser, [], () => {
const { Layout } = ChromeUtils.import(
"chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
);
@@ -55,7 +55,7 @@ async function runTests(browser, accDoc) {
await testTextNode("p1");
await invokeContentTask(browser, [], () => {
await SpecialPowers.spawn(browser, [], () => {
const { Layout } = ChromeUtils.import(
"chrome://mochitests/content/browser/accessible/tests/browser/Layout.jsm"
);

View File

@@ -280,26 +280,26 @@ function getBounds(aID, aDPR = window.devicePixelRatio) {
info(`DPR is: ${aDPR}`);
isWithin(
x.value / aDPR,
xInCSS.value,
x.value / aDPR,
1,
"X in CSS pixels is calculated correctly"
);
isWithin(
y.value / aDPR,
yInCSS.value,
y.value / aDPR,
1,
"Y in CSS pixels is calculated correctly"
);
isWithin(
width.value / aDPR,
widthInCSS.value,
width.value / aDPR,
1,
"Width in CSS pixels is calculated correctly"
);
isWithin(
height.value / aDPR,
heightInCSS.value,
height.value / aDPR,
1,
"Height in CSS pixels is calculated correctly"
);

View File

@@ -138,7 +138,6 @@
#include "nsIScrollableFrame.h"
#include "nsITimer.h"
#ifdef ACCESSIBILITY
# include "nsAccessibilityService.h"
# include "mozilla/a11y/DocAccessible.h"
# ifdef DEBUG
# include "mozilla/a11y/Logging.h"
@@ -5462,6 +5461,12 @@ void PresShell::SetRenderingState(const RenderingState& aState) {
mRenderingStateFlags = aState.mRenderingStateFlags;
mResolution = aState.mResolution;
#ifdef ACCESSIBILITY
if (nsAccessibilityService* accService =
PresShell::GetAccessibilityService()) {
accService->NotifyOfResolutionChange(this, GetResolution());
}
#endif
}
void PresShell::SynthesizeMouseMove(bool aFromScroll) {

View File

@@ -48,6 +48,9 @@
#include "nsTHashSet.h"
#include "nsThreadUtils.h"
#include "nsWeakReference.h"
#ifdef ACCESSIBILITY
# include "nsAccessibilityService.h"
#endif
class AutoPointerEventTargetUpdater;
class AutoWeakFrame;
@@ -1851,6 +1854,13 @@ class PresShell final : public nsStubDocumentObserver,
~AutoSaveRestoreRenderingState() {
mPresShell->mRenderingStateFlags = mOldState.mRenderingStateFlags;
mPresShell->mResolution = mOldState.mResolution;
#ifdef ACCESSIBILITY
if (nsAccessibilityService* accService =
PresShell::GetAccessibilityService()) {
accService->NotifyOfResolutionChange(mPresShell,
mPresShell->GetResolution());
}
#endif
}
PresShell* mPresShell;

View File

@@ -109,6 +109,10 @@
#include "nsStyleChangeList.h"
#include "nsWindowSizes.h"
#ifdef ACCESSIBILITY
# include "nsAccessibilityService.h"
#endif
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/CSSClipPathInstance.h"
#include "mozilla/EffectCompositor.h"
@@ -6690,6 +6694,13 @@ void nsIFrame::DidReflow(nsPresContext* aPresContext,
}
aPresContext->ReflowedFrame();
#ifdef ACCESSIBILITY
if (nsAccessibilityService* accService =
PresShell::GetAccessibilityService()) {
accService->NotifyOfPossibleBoundsChange(PresShell(), mContent);
}
#endif
}
void nsIFrame::FinishReflowWithAbsoluteFrames(nsPresContext* aPresContext,

View File

@@ -1030,6 +1030,7 @@ STATIC_ATOMS = [
Atom("rectangle", "rectangle"),
Atom("refresh", "refresh"),
Atom("rel", "rel"),
Atom("relativeBounds", "relative-bounds"),
Atom("rem", "rem"),
Atom("remote", "remote"),
Atom("removeelement", "removeelement"),