Bug 1794974: Part 2: Add ability to query and send individual cache domains, r=Jamie

This revision adds the capability of querying and sending information about
individual cache domains. It introduces the concept of active cache domains to
the accessibility service: cache domains that we think clients need. Conversely,
cache domains that clients don't need are inactive, and we avoid doing any work
to push information about those domains. This revision adds an IPC mechanism for
setting cache domains. It adds a way for content process documents to enable,
gather, and send to the parent process information from all of their accessibles
that's newly needed. It adds a way to instantiate new accessibility services
with a predetermined set of cache domains. It adds a mechanism for local accs to
use in BundleFieldsForCache, but doesn't implement it yet (see next revision).

Differential Revision: https://phabricator.services.mozilla.com/D220036
This commit is contained in:
Nathan LaPre 2024-09-09 23:02:19 +00:00
parent b32ecd1bf9
commit 08aae2056f
13 changed files with 214 additions and 29 deletions

View File

@ -15,6 +15,7 @@ namespace a11y {
class CacheDomain {
public:
static constexpr uint64_t None = 0;
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;
@ -36,6 +37,7 @@ class CacheDomain {
// Used for MathML.
static constexpr uint64_t InnerHTML = ((uint64_t)0x1) << 17;
#endif
static constexpr uint64_t TextBounds = ((uint64_t)0x1) << 18;
static constexpr uint64_t All = ~((uint64_t)0x0);
};
@ -235,10 +237,10 @@ class CacheKey {
// AccAttributes, CacheDomain::Text
// Text attributes; font, etc.
static constexpr nsStaticAtom* TextAttributes = nsGkAtoms::style;
// nsTArray<int32_t, 4 * n>, CacheDomain::Text | CacheDomain::Bounds
// nsTArray<int32_t, 4 * n>, CacheDomain::TextBounds
// The bounds of each character in a text leaf.
static constexpr nsStaticAtom* TextBounds = nsGkAtoms::characterData;
// nsTArray<int32_t>, CacheDomain::Text | CacheDomain::Bounds
// nsTArray<int32_t>, CacheDomain::TextBounds
// The text offsets where new lines start.
static constexpr nsStaticAtom* TextLineStarts = nsGkAtoms::line;
// nsString, CacheDomain::Value

View File

@ -162,10 +162,12 @@ class DocManager : public nsIWebProgressListener,
*/
void ClearDocCache();
protected:
typedef nsRefPtrHashtable<nsPtrHashKey<const dom::Document>, DocAccessible>
DocAccessibleHashtable;
DocAccessibleHashtable mDocAccessibleCache;
private:
typedef nsRefPtrHashtable<nsPtrHashKey<const DocAccessible>,
xpcAccessibleDocument>
XPCDocumentHashtable;

View File

@ -34,6 +34,7 @@
#include "nsServiceManagerUtils.h"
#include "nsTextFormatter.h"
#include "OuterDocAccessible.h"
#include "Pivot.h"
#include "mozilla/a11y/Role.h"
#ifdef MOZ_ACCESSIBILITY_ATK
# include "RootAccessibleWrap.h"
@ -61,6 +62,7 @@
#include "nsTreeUtils.h"
#include "mozilla/a11y/AccTypes.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/DOMStringList.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/dom/HTMLTableElement.h"
@ -137,6 +139,22 @@ static LocalAccessible* MaybeCreateSpecificARIAAccessible(
return nullptr;
}
// Send a request to all content processes that they build and send back
// information about the given cache domains.
static bool SendCacheDomainRequestToAllContentProcesses(
uint64_t aCacheDomains) {
if (!XRE_IsParentProcess()) {
return false;
}
bool sentAll = true;
nsTArray<ContentParent*> contentParents;
ContentParent::GetAll(contentParents);
for (auto* parent : contentParents) {
sentAll = sentAll && parent->SendSetCacheDomains(aCacheDomains);
}
return sentAll;
}
/**
* Return true if the element must be a generic Accessible, even if it has been
* marked presentational with role="presentation", etc. MustBeAccessible causes
@ -431,6 +449,8 @@ ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr;
xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible =
nullptr;
uint32_t nsAccessibilityService::gConsumers = 0;
uint64_t nsAccessibilityService::gCacheDomains =
nsAccessibilityService::kDefaultCacheDomains;
nsAccessibilityService::nsAccessibilityService()
: mHTMLMarkupMap(ArrayLength(sHTMLMarkupMapList)),
@ -1489,7 +1509,7 @@ mozilla::Monitor& nsAccessibilityService::GetAndroidMonitor() {
////////////////////////////////////////////////////////////////////////////////
// nsAccessibilityService private
bool nsAccessibilityService::Init() {
bool nsAccessibilityService::Init(uint64_t aCacheDomains) {
AUTO_PROFILER_MARKER_TEXT("nsAccessibilityService::Init", A11Y, {}, ""_ns);
// DO NOT ADD CODE ABOVE HERE: THIS CODE IS MEASURING TIMINGS.
@ -1552,6 +1572,10 @@ bool nsAccessibilityService::Init() {
// Now its safe to start platform accessibility.
if (XRE_IsParentProcess()) PlatformInit();
// Set the active accessibility cache domains. We might want to modify the
// domains that we activate based on information about the instantiator.
gCacheDomains = ::GetCacheDomainsForKnownClients(aCacheDomains);
statistics::A11yInitialized();
static const char16_t kInitIndicator[] = {'1', 0};
@ -1826,6 +1850,48 @@ void nsAccessibilityService::GetConsumers(nsAString& aString) {
aString.Assign(json);
}
void nsAccessibilityService::SetCacheDomains(uint64_t aCacheDomains) {
if (XRE_IsParentProcess()) {
const DebugOnly<bool> requestSent =
SendCacheDomainRequestToAllContentProcesses(aCacheDomains);
MOZ_ASSERT(requestSent,
"Could not send cache domain request to content processes.");
gCacheDomains = aCacheDomains;
return;
}
// Bail out if we're not a content process.
if (!XRE_IsContentProcess()) {
return;
}
// Anything not enabled already but enabled now is a newly-enabled domain.
const uint64_t newDomains = ~gCacheDomains & aCacheDomains;
// Queue cache updates on all accessibles in all documents within this
// process.
if (newDomains != CacheDomain::None) {
for (const RefPtr<DocAccessible>& doc : mDocAccessibleCache.Values()) {
MOZ_ASSERT(doc, "DocAccessible in cache is null!");
doc->QueueCacheUpdate(doc.get(), newDomains, true);
Pivot pivot(doc.get());
LocalAccInSameDocRule rule;
for (Accessible* anchor = doc.get(); anchor;
anchor = pivot.Next(anchor, rule)) {
LocalAccessible* acc = anchor->AsLocal();
// Note: Queueing changes for domains that aren't yet active. The
// domains will become active at the end of the function.
doc->QueueCacheUpdate(acc, newDomains, true);
}
// Process queued cache updates immediately.
doc->ProcessQueuedCacheUpdates(newDomains);
}
}
gCacheDomains = aCacheDomains;
}
void nsAccessibilityService::NotifyOfConsumersChange() {
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
@ -1854,15 +1920,22 @@ const mozilla::a11y::MarkupMapInfo* nsAccessibilityService::GetMarkupMapInfoFor(
return mHTMLMarkupMap.Get(aAcc->TagName());
}
nsAccessibilityService* GetOrCreateAccService(uint32_t aNewConsumer) {
nsAccessibilityService* GetOrCreateAccService(uint32_t aNewConsumer,
uint64_t aCacheDomains) {
// Do not initialize accessibility if it is force disabled.
if (PlatformDisabledState() == ePlatformIsDisabled) {
return nullptr;
}
if (!nsAccessibilityService::gAccessibilityService) {
uint64_t cacheDomains = aCacheDomains;
if (aNewConsumer == nsAccessibilityService::eXPCOM) {
// When instantiated via XPCOM, cache all accessibility information.
cacheDomains = CacheDomain::All;
}
RefPtr<nsAccessibilityService> service = new nsAccessibilityService();
if (!service->Init()) {
if (!service->Init(cacheDomains)) {
service->Shutdown();
return nullptr;
}

View File

@ -6,6 +6,7 @@
#ifndef __nsAccessibilityService_h__
#define __nsAccessibilityService_h__
#include "mozilla/a11y/CacheConstants.h"
#include "mozilla/a11y/DocManager.h"
#include "mozilla/a11y/FocusManager.h"
#include "mozilla/a11y/Platform.h"
@ -103,6 +104,10 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
typedef mozilla::a11y::LocalAccessible LocalAccessible;
typedef mozilla::a11y::DocAccessible DocAccessible;
static const uint64_t kDefaultCacheDomains =
mozilla::a11y::CacheDomain::NameAndDescription |
mozilla::a11y::CacheDomain::State;
// nsIListenerChangeListener
NS_IMETHOD ListenersChanged(nsIArray* aEventChanges) override;
@ -270,6 +275,15 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
static bool ShouldCreateImgAccessible(mozilla::dom::Element* aElement,
DocAccessible* aDocument);
/*
* Set the currently-active cache domains.
*/
void SetCacheDomains(uint64_t aCacheDomains);
bool CacheDomainIsActive(uint64_t aCacheDomain) const {
return (gCacheDomains & aCacheDomain) != mozilla::a11y::CacheDomain::None;
}
/**
* Creates an accessible for the given DOM node.
*
@ -331,6 +345,8 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
ePlatformAPI = 1 << 2,
};
static uint64_t GetActiveCacheDomains() { return gCacheDomains; }
#if defined(ANDROID)
static mozilla::Monitor& GetAndroidMonitor();
#endif
@ -346,7 +362,7 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
/**
* Initialize accessibility service.
*/
bool Init();
bool Init(uint64_t aCacheDomains = kDefaultCacheDomains);
/**
* Shutdowns accessibility service.
@ -395,6 +411,11 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
*/
static uint32_t gConsumers;
/**
* Contains the currently active cache domains.
*/
static uint64_t gCacheDomains;
// Can be weak because all atoms are known static
using MarkupMap = nsTHashMap<nsAtom*, const mozilla::a11y::MarkupMapInfo*>;
MarkupMap mHTMLMarkupMap;
@ -420,7 +441,7 @@ class nsAccessibilityService final : public mozilla::a11y::DocManager,
nsTHashMap<nsAtom*, const mozilla::a11y::XULMarkupMapInfo*> mXULMarkupMap;
friend nsAccessibilityService* GetAccService();
friend nsAccessibilityService* GetOrCreateAccService(uint32_t);
friend nsAccessibilityService* GetOrCreateAccService(uint32_t, uint64_t);
friend void MaybeShutdownAccService(uint32_t);
friend void mozilla::a11y::PrefChanged(const char*, void*);
friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr();
@ -442,7 +463,8 @@ inline nsAccessibilityService* GetAccService() {
* Return accessibility service instance; creating one if necessary.
*/
nsAccessibilityService* GetOrCreateAccService(
uint32_t aNewConsumer = nsAccessibilityService::ePlatformAPI);
uint32_t aNewConsumer = nsAccessibilityService::ePlatformAPI,
uint64_t aCacheDomains = nsAccessibilityService::GetActiveCacheDomains());
/**
* Shutdown accessibility service if needed.

View File

@ -354,8 +354,26 @@ void DocAccessible::DocType(nsAString& aType) const {
if (docType) docType->GetPublicId(aType);
}
void DocAccessible::QueueCacheUpdate(LocalAccessible* aAcc,
uint64_t aNewDomain) {
// Certain cache domain updates might require updating other cache domains.
// This function takes the given cache domains and returns those cache domains
// plus any other required associated cache domains. Made for use with
// QueueCacheUpdate.
static uint64_t GetCacheDomainsQueueUpdateSuperset(uint64_t aCacheDomains) {
// Text domain updates imply updates to the TextOffsetAttributes and
// TextBounds domains.
if (aCacheDomains & CacheDomain::Text) {
aCacheDomains |= CacheDomain::TextOffsetAttributes;
aCacheDomains |= CacheDomain::TextBounds;
}
// Bounds domain updates imply updates to the TextBounds domain.
if (aCacheDomains & CacheDomain::Bounds) {
aCacheDomains |= CacheDomain::TextBounds;
}
return aCacheDomains;
}
void DocAccessible::QueueCacheUpdate(LocalAccessible* aAcc, uint64_t aNewDomain,
bool aBypassActiveDomains) {
if (!mIPCDoc) {
return;
}
@ -378,9 +396,32 @@ void DocAccessible::QueueCacheUpdate(LocalAccessible* aAcc,
// LocalAccessible twice.
return entry.Insert(index);
});
// We may need to bypass the active domain restriction when populating domains
// for the first time. In that case, queue cache updates regardless of domain.
if (aBypassActiveDomains) {
auto& [arrayAcc, domain] = mQueuedCacheUpdatesArray[arrayIndex];
MOZ_ASSERT(arrayAcc == aAcc);
domain |= aNewDomain;
Controller()->ScheduleProcessing();
return;
}
// Potentially queue updates for required related domains.
const uint64_t newDomains = GetCacheDomainsQueueUpdateSuperset(aNewDomain);
// Only queue cache updates for domains that are active.
const uint64_t domainsToUpdate =
nsAccessibilityService::GetActiveCacheDomains() & newDomains;
// Avoid queueing cache updates if we have no domains to update.
if (domainsToUpdate == CacheDomain::None) {
return;
}
auto& [arrayAcc, domain] = mQueuedCacheUpdatesArray[arrayIndex];
MOZ_ASSERT(arrayAcc == aAcc);
domain |= aNewDomain;
domain |= domainsToUpdate;
Controller()->ScheduleProcessing();
}
@ -1526,7 +1567,7 @@ void DocAccessible::ProcessInvalidationList() {
mInvalidationList.Clear();
}
void DocAccessible::ProcessQueuedCacheUpdates() {
void DocAccessible::ProcessQueuedCacheUpdates(uint64_t aInitialDomains) {
AUTO_PROFILER_MARKER_TEXT("DocAccessible::ProcessQueuedCacheUpdates", A11Y,
{}, ""_ns);
PerfStats::AutoMetricRecording<
@ -1537,8 +1578,8 @@ void DocAccessible::ProcessQueuedCacheUpdates() {
nsTArray<CacheData> data;
for (auto [acc, domain] : mQueuedCacheUpdatesArray) {
if (acc && acc->IsInDocument() && !acc->IsDefunct()) {
RefPtr<AccAttributes> fields =
acc->BundleFieldsForCache(domain, CacheUpdateType::Update);
RefPtr<AccAttributes> fields = acc->BundleFieldsForCache(
domain, CacheUpdateType::Update, aInitialDomains);
if (fields->Count()) {
data.AppendElement(CacheData(
@ -1698,7 +1739,8 @@ void DocAccessible::DoInitialUpdate() {
// Send an initial update for this document and its attributes. Each acc
// contained in this doc will have its initial update sent in
// `InsertIntoIpcTree`.
SendCache(CacheDomain::All, CacheUpdateType::Initial);
SendCache(nsAccessibilityService::GetActiveCacheDomains(),
CacheUpdateType::Initial);
for (auto idx = 0U; idx < mChildren.Length(); idx++) {
ipcDoc->InsertIntoIpcTree(mChildren.ElementAt(idx), true);

View File

@ -8,6 +8,7 @@
#include "HyperTextAccessible.h"
#include "AccEvent.h"
#include "nsAccessibilityService.h"
#include "nsClassHashtable.h"
#include "nsTHashMap.h"
@ -118,7 +119,8 @@ class DocAccessible : public HyperTextAccessible,
* Note that this CANNOT be used for anything which fires events, since events
* must be fired after their associated cache update.
*/
void QueueCacheUpdate(LocalAccessible* aAcc, uint64_t aNewDomain);
void QueueCacheUpdate(LocalAccessible* aAcc, uint64_t aNewDomain,
bool aBypassActiveDomains = false);
/**
* Walks the dependent ids and elements maps for the given accessible and
@ -559,8 +561,10 @@ class DocAccessible : public HyperTextAccessible,
* Called from NotificationController to process this doc's
* queued cache updates. For each acc in the map, this function
* sends a cache update with its corresponding CacheDomain.
* Each domain bit in aInitialDomains indicates that this is the first push
* for that cache domain.
*/
void ProcessQueuedCacheUpdates();
void ProcessQueuedCacheUpdates(uint64_t aInitialDomains = 0);
/**
* Called from NotificationController before mutation events are processed to
@ -817,6 +821,12 @@ class DocAccessible : public HyperTextAccessible,
friend class EventTree;
friend class NotificationController;
/*
* The accessibility service may need to process queued cache updates outside
* of the regular NotificationController flow.
*/
friend class ::nsAccessibilityService;
private:
void SetRoleMapEntryForDoc(dom::Element* aElement);

View File

@ -3337,6 +3337,15 @@ void LocalAccessible::SendCache(uint64_t aCacheDomain,
return;
}
// Only send cache updates for domains that are active.
const uint64_t domainsToSend =
nsAccessibilityService::GetActiveCacheDomains() & aCacheDomain;
// Avoid sending cache updates if we have no domains to update.
if (domainsToSend == CacheDomain::None) {
return;
}
DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
if (!ipcDoc) {
// This means DocAccessible::DoInitialUpdate hasn't been called yet, which
@ -3347,7 +3356,7 @@ void LocalAccessible::SendCache(uint64_t aCacheDomain,
}
RefPtr<AccAttributes> fields =
BundleFieldsForCache(aCacheDomain, aUpdateType);
BundleFieldsForCache(domainsToSend, aUpdateType);
if (!fields->Count()) {
return;
}
@ -3369,7 +3378,8 @@ void LocalAccessible::SendCache(uint64_t aCacheDomain,
}
already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
uint64_t aCacheDomain, CacheUpdateType aUpdateType) {
uint64_t aCacheDomain, CacheUpdateType aUpdateType,
uint64_t aInitialDomains) {
RefPtr<AccAttributes> fields = new AccAttributes();
// Caching name for text leaf Accessibles is redundant, since their name is

View File

@ -9,6 +9,7 @@
#include "mozilla/ComputedStyle.h"
#include "mozilla/a11y/Accessible.h"
#include "mozilla/a11y/AccTypes.h"
#include "mozilla/a11y/CacheConstants.h"
#include "mozilla/a11y/RelationType.h"
#include "mozilla/a11y/States.h"
@ -715,7 +716,8 @@ class LocalAccessible : public nsISupports, public Accessible {
virtual bool IsRemote() const override { return false; }
already_AddRefed<AccAttributes> BundleFieldsForCache(
uint64_t aCacheDomain, CacheUpdateType aUpdateType);
uint64_t aCacheDomain, CacheUpdateType aUpdateType,
uint64_t aInitialDomains = CacheDomain::None);
/**
* Push fields to cache.

View File

@ -60,8 +60,9 @@ AccessibleData DocAccessibleChild::SerializeAcc(LocalAccessible* aAcc) {
// Even though we send moves as a hide and a show, we don't want to
// push the cache again for moves.
if (!aAcc->Document()->IsAccessibleBeingMoved(aAcc)) {
fields =
aAcc->BundleFieldsForCache(CacheDomain::All, CacheUpdateType::Initial);
fields = aAcc->BundleFieldsForCache(
nsAccessibilityService::GetActiveCacheDomains(),
CacheUpdateType::Initial);
if (fields->Count() == 0) {
fields = nullptr;
}

View File

@ -2557,11 +2557,11 @@ mozilla::ipc::IPCResult ContentChild::RecvFlushMemory(const nsString& reason) {
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvActivateA11y() {
mozilla::ipc::IPCResult ContentChild::RecvActivateA11y(uint64_t aCacheDomains) {
#ifdef ACCESSIBILITY
// Start accessibility in content process if it's running in chrome
// process.
GetOrCreateAccService(nsAccessibilityService::eMainProcess);
GetOrCreateAccService(nsAccessibilityService::eMainProcess, aCacheDomains);
#endif // ACCESSIBILITY
return IPC_OK();
}
@ -2575,6 +2575,18 @@ mozilla::ipc::IPCResult ContentChild::RecvShutdownA11y() {
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvSetCacheDomains(
uint64_t aCacheDomains) {
#ifdef ACCESSIBILITY
nsAccessibilityService* accService = GetAccService();
if (!accService) {
return IPC_FAIL(this, "Accessibility service should exist");
}
accService->SetCacheDomains(aCacheDomains);
#endif
return IPC_OK();
}
mozilla::ipc::IPCResult ContentChild::RecvApplicationForeground() {
// Rebroadcast the "application-foreground"
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();

View File

@ -346,8 +346,9 @@ class ContentChild final : public PContentChild,
mozilla::ipc::IPCResult RecvFlushMemory(const nsString& reason);
mozilla::ipc::IPCResult RecvActivateA11y();
mozilla::ipc::IPCResult RecvActivateA11y(uint64_t aCacheDomains);
mozilla::ipc::IPCResult RecvShutdownA11y();
mozilla::ipc::IPCResult RecvSetCacheDomains(uint64_t aCacheDomains);
mozilla::ipc::IPCResult RecvApplicationForeground();
mozilla::ipc::IPCResult RecvApplicationBackground();

View File

@ -1617,7 +1617,7 @@ void ContentParent::Init() {
// If accessibility is running in chrome process then start it in content
// process.
if (GetAccService()) {
Unused << SendActivateA11y();
Unused << SendActivateA11y(nsAccessibilityService::GetActiveCacheDomains());
}
#endif // #ifdef ACCESSIBILITY
@ -3808,7 +3808,8 @@ ContentParent::Observe(nsISupports* aSubject, const char* aTopic,
if (*aData == '1') {
// Make sure accessibility is running in content process when
// accessibility gets initiated in chrome process.
Unused << SendActivateA11y();
Unused << SendActivateA11y(
nsAccessibilityService::GetActiveCacheDomains());
} else {
// If possible, shut down accessibility in content process when
// accessibility gets shutdown in chrome process.

View File

@ -764,15 +764,22 @@ child:
async UnlinkGhosts();
/**
* Start accessibility engine in content process.
* Start accessibility engine in content process. aDomains specifies the
* cache domains for which content processes should send info.
*/
async ActivateA11y();
async ActivateA11y(uint64_t aDomains);
/**
* Shutdown accessibility engine in content process (if not in use).
*/
async ShutdownA11y();
/**
* Set accessibility cache domains, effectively requesting more or less
* cached information from the content process.
*/
async SetCacheDomains(uint64_t aCacheDomains);
async AppInfo(nsCString version, nsCString buildID, nsCString name, nsCString UAName,
nsCString ID, nsCString vendor, nsCString sourceURL, nsCString updateURL);