gecko-dev/accessible/ipc/DocAccessibleChildBase.cpp
James Teh f32815354c Bug 1739050: If an Accessible is moved, reuse the RemoteAccessible and don't push the cache for it. r=eeejay
A move is sent to the parent process as a hide and then a show, even if the LocalAccessible wasn't recreated.
Previously, this meant that a new RemoteAccessible was created and the original RemoteAccessible was destroyed.
This was particularly problematic if the Accessible had focus and the client cached focus.
In that case, the client cache contained a dead Accessible, and since no focus event was fired (because it's the same Accessible in the content process), focus in the client was broken.
This also meant that there was a full cache push for a moved Accessible, which was wasteful.

To fix this:

1. Keep track of LocalAccessibles inserted during a tick.
2. Keep track of LocalAccessibles moved during a tick, including descendants. If a LocalAccessible was inserted during the same tick (1), don't track it as a move.
3. Before processing mutation events, tell the parent process about which Accessibles are about to be moved (2).
4. When sending a subtree to the parent process, don't send cache info for an Accessible which is being moved (2).
5. When the parent process receives a hide event, if there is an Accessible that is about to be moved (3), don't shut it down; allow it to be reused.
6. When the parent process receives a show event, if there is an Accessible that has an existing RemoteAccessible (5), reuse it.

Differential Revision: https://phabricator.services.mozilla.com/D132328
2021-12-22 01:33:52 +00:00

184 lines
6.2 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/a11y/DocAccessibleChildBase.h"
#include "mozilla/a11y/CacheConstants.h"
#include "mozilla/a11y/RemoteAccessible.h"
#include "mozilla/StaticPrefs_accessibility.h"
#include "LocalAccessible-inl.h"
#ifdef A11Y_LOG
# include "Logging.h"
#endif
namespace mozilla {
namespace a11y {
/* static */
void DocAccessibleChildBase::FlattenTree(LocalAccessible* aRoot,
nsTArray<LocalAccessible*>& aTree) {
MOZ_ASSERT(!aRoot->IsDoc(), "documents shouldn't be serialized");
aTree.AppendElement(aRoot);
// OuterDocAccessibles are special because we don't want to serialize the
// child doc here, we'll call PDocAccessibleConstructor in
// NotificationController.
uint32_t childCount = aRoot->IsOuterDoc() ? 0 : aRoot->ChildCount();
for (uint32_t i = 0; i < childCount; i++) {
FlattenTree(aRoot->LocalChildAt(i), aTree);
}
}
/* static */
void DocAccessibleChildBase::SerializeTree(nsTArray<LocalAccessible*>& aTree,
nsTArray<AccessibleData>& aData) {
for (LocalAccessible* acc : aTree) {
uint64_t id = reinterpret_cast<uint64_t>(acc->UniqueID());
#if defined(XP_WIN)
int32_t msaaId = StaticPrefs::accessibility_cache_enabled_AtStartup()
? 0
: MsaaAccessible::GetChildIDFor(acc);
#endif
a11y::role role = acc->Role();
uint32_t childCount = acc->IsOuterDoc() ? 0 : acc->ChildCount();
uint32_t genericTypes = acc->mGenericTypes;
if (acc->ARIAHasNumericValue()) {
// XXX: We need to do this because this requires a state check.
genericTypes |= eNumericValue;
}
if (acc->ActionCount()) {
genericTypes |= eActionable;
}
#if defined(XP_WIN)
aData.AppendElement(AccessibleData(
id, msaaId, role, childCount, static_cast<AccType>(acc->mType),
static_cast<AccGenericType>(genericTypes), acc->mRoleMapEntryIndex));
#else
aData.AppendElement(AccessibleData(
id, role, childCount, static_cast<AccType>(acc->mType),
static_cast<AccGenericType>(genericTypes), acc->mRoleMapEntryIndex));
#endif
}
}
void DocAccessibleChildBase::InsertIntoIpcTree(LocalAccessible* aParent,
LocalAccessible* aChild,
uint32_t aIdxInParent,
bool aSuppressShowEvent) {
uint64_t parentID =
aParent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(aParent->UniqueID());
nsTArray<LocalAccessible*> shownTree;
FlattenTree(aChild, shownTree);
ShowEventData data(parentID, aIdxInParent,
nsTArray<AccessibleData>(shownTree.Length()),
aSuppressShowEvent);
SerializeTree(shownTree, data.NewTree());
MaybeSendShowEvent(data, false);
if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
nsTArray<CacheData> cache(shownTree.Length());
for (LocalAccessible* acc : shownTree) {
if (mDoc->IsAccessibleBeingMoved(acc)) {
// Even though we send moves as a hide and a show, we don't want to
// push the cache again for moves.
continue;
}
RefPtr<AccAttributes> fields =
acc->BundleFieldsForCache(CacheDomain::All, CacheUpdateType::Initial);
if (fields->Count()) {
uint64_t id = reinterpret_cast<uint64_t>(acc->UniqueID());
cache.AppendElement(CacheData(id, fields));
}
}
Unused << SendCache(CacheUpdateType::Initial, cache, true);
}
}
void DocAccessibleChildBase::ShowEvent(AccShowEvent* aShowEvent) {
LocalAccessible* child = aShowEvent->GetAccessible();
InsertIntoIpcTree(aShowEvent->LocalParent(), child, child->IndexInParent(),
false);
}
mozilla::ipc::IPCResult DocAccessibleChildBase::RecvTakeFocus(
const uint64_t& aID) {
LocalAccessible* acc = IdToAccessible(aID);
if (acc) {
acc->TakeFocus();
}
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleChildBase::RecvVerifyCache(
const uint64_t& aID, const uint64_t& aCacheDomain, AccAttributes* aFields) {
#ifdef A11Y_LOG
LocalAccessible* acc = IdToAccessible(aID);
if (!acc) {
return IPC_OK();
}
RefPtr<AccAttributes> localFields =
acc->BundleFieldsForCache(aCacheDomain, CacheUpdateType::Update);
bool mismatches = false;
for (auto prop : *localFields) {
if (prop.Value<DeleteEntry>()) {
if (aFields->HasAttribute(prop.Name())) {
if (!mismatches) {
logging::MsgBegin("Mismatch!", "Local and remote values differ");
logging::AccessibleInfo("", acc);
mismatches = true;
}
nsAutoCString propName;
prop.Name()->ToUTF8String(propName);
nsAutoString val;
aFields->GetAttribute(prop.Name(), val);
logging::MsgEntry(
"Remote value for %s should be empty, but instead it is '%s'",
propName.get(), NS_ConvertUTF16toUTF8(val).get());
}
continue;
}
nsAutoString localVal;
prop.ValueAsString(localVal);
nsAutoString remoteVal;
aFields->GetAttribute(prop.Name(), remoteVal);
if (!localVal.Equals(remoteVal)) {
if (!mismatches) {
logging::MsgBegin("Mismatch!", "Local and remote values differ");
logging::AccessibleInfo("", acc);
mismatches = true;
}
nsAutoCString propName;
prop.Name()->ToUTF8String(propName);
logging::MsgEntry("Fields differ: %s '%s' != '%s'", propName.get(),
NS_ConvertUTF16toUTF8(remoteVal).get(),
NS_ConvertUTF16toUTF8(localVal).get());
}
}
if (mismatches) {
logging::MsgEnd();
}
#endif // A11Y_LOG
return IPC_OK();
}
LocalAccessible* DocAccessibleChildBase::IdToAccessible(
const uint64_t& aID) const {
if (!aID) return mDoc;
if (!mDoc) return nullptr;
return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID));
}
} // namespace a11y
} // namespace mozilla