mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 09:54:42 +00:00
f32815354c
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
184 lines
6.2 KiB
C++
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
|