1352 lines
42 KiB
C++
1352 lines
42 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 "ARIAMap.h"
|
|
#include "CachedTableAccessible.h"
|
|
#include "DocAccessibleParent.h"
|
|
#include "mozilla/a11y/Platform.h"
|
|
#include "mozilla/Components.h" // for mozilla::components
|
|
#include "mozilla/dom/BrowserBridgeParent.h"
|
|
#include "mozilla/dom/BrowserParent.h"
|
|
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
|
#include "mozilla/PerfStats.h"
|
|
#include "mozilla/ProfilerMarkers.h"
|
|
#include "nsAccessibilityService.h"
|
|
#include "xpcAccessibleDocument.h"
|
|
#include "xpcAccEvents.h"
|
|
#include "nsAccUtils.h"
|
|
#include "nsIIOService.h"
|
|
#include "TextRange.h"
|
|
#include "Relation.h"
|
|
#include "RootAccessible.h"
|
|
|
|
#if defined(XP_WIN)
|
|
# include "Compatibility.h"
|
|
# include "nsWinUtils.h"
|
|
#endif
|
|
|
|
#if defined(ANDROID)
|
|
# define ACQUIRE_ANDROID_LOCK \
|
|
MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
|
|
#else
|
|
# define ACQUIRE_ANDROID_LOCK \
|
|
do { \
|
|
} while (0);
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
|
|
namespace a11y {
|
|
uint64_t DocAccessibleParent::sMaxDocID = 0;
|
|
|
|
DocAccessibleParent::DocAccessibleParent()
|
|
: RemoteAccessible(this),
|
|
#if defined(XP_WIN)
|
|
mEmulatedWindowHandle(nullptr),
|
|
#endif // defined(XP_WIN)
|
|
mTopLevel(false),
|
|
mTopLevelInContentProcess(false),
|
|
mShutdown(false),
|
|
mFocus(0),
|
|
mCaretId(0),
|
|
mCaretOffset(-1),
|
|
mIsCaretAtEndOfLine(false) {
|
|
sMaxDocID++;
|
|
mActorID = sMaxDocID;
|
|
MOZ_ASSERT(!LiveDocs().Get(mActorID));
|
|
LiveDocs().InsertOrUpdate(mActorID, this);
|
|
}
|
|
|
|
DocAccessibleParent::~DocAccessibleParent() {
|
|
UnregisterWeakMemoryReporter(this);
|
|
LiveDocs().Remove(mActorID);
|
|
MOZ_ASSERT(mChildDocs.Length() == 0);
|
|
MOZ_ASSERT(!ParentDoc());
|
|
}
|
|
|
|
already_AddRefed<DocAccessibleParent> DocAccessibleParent::New() {
|
|
RefPtr<DocAccessibleParent> dap(new DocAccessibleParent());
|
|
// We need to do this with a non-zero reference count. The easiest way is to
|
|
// do it in this static method and hide the constructor.
|
|
RegisterWeakMemoryReporter(dap);
|
|
return dap.forget();
|
|
}
|
|
|
|
void DocAccessibleParent::SetBrowsingContext(
|
|
dom::CanonicalBrowsingContext* aBrowsingContext) {
|
|
mBrowsingContext = aBrowsingContext;
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::ProcessShowEvent(
|
|
nsTArray<AccessibleData>&& aNewTree, const bool& aEventSuppressed,
|
|
const bool& aComplete, const bool& aFromUser) {
|
|
AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::ProcessShowEvent", A11Y, {},
|
|
""_ns);
|
|
PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ProcessShowEvent>
|
|
autoRecording;
|
|
// DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
|
|
|
|
ACQUIRE_ANDROID_LOCK
|
|
|
|
MOZ_ASSERT(CheckDocTree());
|
|
|
|
if (aNewTree.IsEmpty()) {
|
|
return IPC_FAIL(this, "No children being added");
|
|
}
|
|
|
|
RemoteAccessible* root = nullptr;
|
|
RemoteAccessible* rootParent = nullptr;
|
|
RemoteAccessible* lastParent = this;
|
|
uint64_t lastParentID = 0;
|
|
for (const auto& accData : aNewTree) {
|
|
// Avoid repeated hash lookups when there are multiple children of the same
|
|
// parent.
|
|
RemoteAccessible* parent = accData.ParentID() == lastParentID
|
|
? lastParent
|
|
: GetAccessible(accData.ParentID());
|
|
// XXX This should really never happen, but sometimes we fail to fire the
|
|
// required show events.
|
|
if (!parent) {
|
|
NS_ERROR("adding child to unknown accessible");
|
|
#ifdef DEBUG
|
|
return IPC_FAIL(this, "unknown parent accessible");
|
|
#else
|
|
return IPC_OK();
|
|
#endif
|
|
}
|
|
lastParent = parent;
|
|
lastParentID = accData.ParentID();
|
|
|
|
uint32_t childIdx = accData.IndexInParent();
|
|
if (childIdx > parent->ChildCount()) {
|
|
NS_ERROR("invalid index to add child at");
|
|
#ifdef DEBUG
|
|
return IPC_FAIL(this, "invalid index");
|
|
#else
|
|
return IPC_OK();
|
|
#endif
|
|
}
|
|
|
|
RemoteAccessible* child = CreateAcc(accData);
|
|
if (!child) {
|
|
// This shouldn't happen.
|
|
return IPC_FAIL(this, "failed to add children");
|
|
}
|
|
if (!root && !mPendingShowChild) {
|
|
// This is the first Accessible, which is the root of the shown subtree.
|
|
root = child;
|
|
rootParent = parent;
|
|
}
|
|
// If this show event has been split across multiple messages and this is
|
|
// not the last message, don't attach the shown root to the tree yet.
|
|
// Otherwise, clients might crawl the incomplete subtree and they won't get
|
|
// mutation events for the remaining pieces.
|
|
if (aComplete || root != child) {
|
|
AttachChild(parent, childIdx, child);
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(CheckDocTree());
|
|
|
|
if (!aComplete && !mPendingShowChild) {
|
|
// This is the first message for a show event split across multiple
|
|
// messages. Save the show target for subsequent messages and return.
|
|
const auto& accData = aNewTree[0];
|
|
mPendingShowChild = accData.ID();
|
|
mPendingShowParent = accData.ParentID();
|
|
mPendingShowIndex = accData.IndexInParent();
|
|
return IPC_OK();
|
|
}
|
|
if (!aComplete) {
|
|
// This show event has been split into multiple messages, but this is
|
|
// neither the first nor the last message. There's nothing more to do here.
|
|
return IPC_OK();
|
|
}
|
|
MOZ_ASSERT(aComplete);
|
|
if (mPendingShowChild) {
|
|
// This is the last message for a show event split across multiple
|
|
// messages. Retrieve the saved show target, attach it to the tree and fire
|
|
// an event if appropriate.
|
|
rootParent = GetAccessible(mPendingShowParent);
|
|
MOZ_ASSERT(rootParent);
|
|
root = GetAccessible(mPendingShowChild);
|
|
MOZ_ASSERT(root);
|
|
AttachChild(rootParent, mPendingShowIndex, root);
|
|
mPendingShowChild = 0;
|
|
mPendingShowParent = 0;
|
|
mPendingShowIndex = 0;
|
|
}
|
|
|
|
// Just update, no events.
|
|
if (aEventSuppressed) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
{
|
|
// Scope for PerfStats
|
|
AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns);
|
|
PerfStats::AutoMetricRecording<
|
|
PerfStats::Metric::A11Y_PlatformShowHideEvent>
|
|
autoRecording;
|
|
// WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK:
|
|
// THIS CODE IS MEASURING TIMINGS.
|
|
PlatformShowHideEvent(root, rootParent, true, aFromUser);
|
|
}
|
|
|
|
if (nsCOMPtr<nsIObserverService> obsService =
|
|
services::GetObserverService()) {
|
|
obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
|
|
}
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root);
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
nsINode* node = nullptr;
|
|
RefPtr<xpcAccEvent> event =
|
|
new xpcAccEvent(type, xpcAcc, doc, node, aFromUser);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
RemoteAccessible* DocAccessibleParent::CreateAcc(
|
|
const AccessibleData& aAccData) {
|
|
RemoteAccessible* newProxy;
|
|
if ((newProxy = GetAccessible(aAccData.ID()))) {
|
|
// This is a move. Reuse the Accessible; don't destroy it.
|
|
MOZ_ASSERT(!newProxy->RemoteParent());
|
|
return newProxy;
|
|
}
|
|
|
|
if (!aria::IsRoleMapIndexValid(aAccData.RoleMapEntryIndex())) {
|
|
MOZ_ASSERT_UNREACHABLE("Invalid role map entry index");
|
|
return nullptr;
|
|
}
|
|
|
|
newProxy = new RemoteAccessible(aAccData.ID(), this, aAccData.Role(),
|
|
aAccData.Type(), aAccData.GenericTypes(),
|
|
aAccData.RoleMapEntryIndex());
|
|
mAccessibles.PutEntry(aAccData.ID())->mProxy = newProxy;
|
|
|
|
if (RefPtr<AccAttributes> fields = aAccData.CacheFields()) {
|
|
newProxy->ApplyCache(CacheUpdateType::Initial, fields);
|
|
}
|
|
|
|
return newProxy;
|
|
}
|
|
|
|
void DocAccessibleParent::AttachChild(RemoteAccessible* aParent,
|
|
uint32_t aIndex,
|
|
RemoteAccessible* aChild) {
|
|
aParent->AddChildAt(aIndex, aChild);
|
|
aChild->SetParent(aParent);
|
|
// ProxyCreated might have already been called if aChild is being moved.
|
|
if (!aChild->GetWrapper()) {
|
|
ProxyCreated(aChild);
|
|
}
|
|
if (aChild->IsTableCell()) {
|
|
CachedTableAccessible::Invalidate(aChild);
|
|
}
|
|
if (aChild->IsOuterDoc()) {
|
|
// We can only do this after ProxyCreated is called because it will fire an
|
|
// event on aChild.
|
|
mPendingOOPChildDocs.RemoveIf([&](dom::BrowserBridgeParent* bridge) {
|
|
MOZ_ASSERT(bridge->GetBrowserParent(),
|
|
"Pending BrowserBridgeParent should be alive");
|
|
if (bridge->GetEmbedderAccessibleId() != aChild->ID()) {
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(bridge->GetEmbedderAccessibleDoc() == this);
|
|
if (DocAccessibleParent* childDoc = bridge->GetDocAccessibleParent()) {
|
|
AddChildDoc(childDoc, aChild->ID(), false);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible* aAcc) {
|
|
// Children might be removed or moved. Handle them the same way. We do this
|
|
// before checking the moving IDs set in order to ensure that we handle moved
|
|
// descendants properly. Avoid descending into the children of outer documents
|
|
// for moves since they are added and removed differently to normal children.
|
|
if (!aAcc->IsOuterDoc()) {
|
|
// Even if some children are kept, those will be re-attached when we handle
|
|
// the show event. For now, clear all of them by moving them to a temporary.
|
|
auto children{std::move(aAcc->mChildren)};
|
|
for (RemoteAccessible* child : children) {
|
|
ShutdownOrPrepareForMove(child);
|
|
}
|
|
}
|
|
|
|
const uint64_t id = aAcc->ID();
|
|
if (!mMovingIDs.Contains(id)) {
|
|
// This Accessible is being removed.
|
|
aAcc->Shutdown();
|
|
return;
|
|
}
|
|
// This is a move. Moves are sent as a hide and then a show, but for a move,
|
|
// we want to keep the Accessible alive for reuse later.
|
|
if (aAcc->IsTable() || aAcc->IsTableCell()) {
|
|
// For table cells, it's important that we do this before the parent is
|
|
// cleared because CachedTableAccessible::Invalidate needs the ancestry.
|
|
CachedTableAccessible::Invalidate(aAcc);
|
|
}
|
|
if (aAcc->IsHyperText()) {
|
|
aAcc->InvalidateCachedHyperTextOffsets();
|
|
}
|
|
aAcc->SetParent(nullptr);
|
|
mMovingIDs.EnsureRemoved(id);
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::ProcessHideEvent(
|
|
const uint64_t& aRootID, const bool& aFromUser) {
|
|
AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::ProcessHideEvent", A11Y, {},
|
|
""_ns);
|
|
PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ProcessHideEvent>
|
|
autoRecording;
|
|
// DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
|
|
ACQUIRE_ANDROID_LOCK
|
|
|
|
MOZ_ASSERT(CheckDocTree());
|
|
|
|
// We shouldn't actually need this because mAccessibles shouldn't have an
|
|
// entry for the document itself, but it doesn't hurt to be explicit.
|
|
if (!aRootID) {
|
|
return IPC_FAIL(this, "Trying to hide entire document?");
|
|
}
|
|
|
|
ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
|
|
if (!rootEntry) {
|
|
NS_ERROR("invalid root being removed!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
RemoteAccessible* root = rootEntry->mProxy;
|
|
if (!root) {
|
|
NS_ERROR("invalid root being removed!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
RemoteAccessible* parent = root->RemoteParent();
|
|
{
|
|
// Scope for PerfStats
|
|
AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns);
|
|
PerfStats::AutoMetricRecording<
|
|
PerfStats::Metric::A11Y_PlatformShowHideEvent>
|
|
autoRecording;
|
|
// WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK:
|
|
// THIS CODE IS MEASURING TIMINGS.
|
|
PlatformShowHideEvent(root, parent, false, aFromUser);
|
|
}
|
|
|
|
RefPtr<xpcAccHideEvent> event = nullptr;
|
|
if (nsCoreUtils::AccEventObserversExist()) {
|
|
uint32_t type = nsIAccessibleEvent::EVENT_HIDE;
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root);
|
|
xpcAccessibleGeneric* xpcParent = GetXPCAccessible(parent);
|
|
RemoteAccessible* next = root->RemoteNextSibling();
|
|
xpcAccessibleGeneric* xpcNext = next ? GetXPCAccessible(next) : nullptr;
|
|
RemoteAccessible* prev = root->RemotePrevSibling();
|
|
xpcAccessibleGeneric* xpcPrev = prev ? GetXPCAccessible(prev) : nullptr;
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
nsINode* node = nullptr;
|
|
event = new xpcAccHideEvent(type, xpcAcc, doc, node, aFromUser, xpcParent,
|
|
xpcNext, xpcPrev);
|
|
}
|
|
|
|
parent->RemoveChild(root);
|
|
ShutdownOrPrepareForMove(root);
|
|
|
|
MOZ_ASSERT(CheckDocTree());
|
|
|
|
if (event) {
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvEvent(
|
|
const uint64_t& aID, const uint32_t& aEventType) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
if (aEventType == 0 || aEventType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) {
|
|
MOZ_ASSERT_UNREACHABLE("Invalid event");
|
|
return IPC_FAIL(this, "Invalid event");
|
|
}
|
|
|
|
RemoteAccessible* remote = GetAccessible(aID);
|
|
if (!remote) {
|
|
NS_ERROR("no proxy for event!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
FireEvent(remote, aEventType);
|
|
return IPC_OK();
|
|
}
|
|
|
|
void DocAccessibleParent::FireEvent(RemoteAccessible* aAcc,
|
|
const uint32_t& aEventType) {
|
|
if (aEventType == nsIAccessibleEvent::EVENT_REORDER ||
|
|
aEventType == nsIAccessibleEvent::EVENT_INNER_REORDER) {
|
|
uint32_t count = aAcc->ChildCount();
|
|
for (uint32_t c = 0; c < count; ++c) {
|
|
aAcc->RemoteChildAt(c)->InvalidateGroupInfo();
|
|
}
|
|
} else if (aEventType == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE &&
|
|
aAcc == this) {
|
|
// A DocAccessible gets the STALE state while it is still loading, but we
|
|
// don't fire a state change for that. That state might have been
|
|
// included in the initial cache push, so clear it here.
|
|
// We also clear the BUSY state here. Although we do fire a state change
|
|
// for that, we fire it after doc load complete. It doesn't make sense
|
|
// for the document to report BUSY after doc load complete and doing so
|
|
// confuses JAWS.
|
|
UpdateStateCache(states::STALE | states::BUSY, false);
|
|
}
|
|
|
|
PlatformEvent(aAcc, aEventType);
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return;
|
|
}
|
|
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(aAcc);
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
nsINode* node = nullptr;
|
|
bool fromUser = true; // XXX fix me
|
|
RefPtr<xpcAccEvent> event =
|
|
new xpcAccEvent(aEventType, xpcAcc, doc, node, fromUser);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvStateChangeEvent(
|
|
const uint64_t& aID, const uint64_t& aState, const bool& aEnabled) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
RemoteAccessible* target = GetAccessible(aID);
|
|
if (!target) {
|
|
NS_ERROR("we don't know about the target of a state change event!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
target->UpdateStateCache(aState, aEnabled);
|
|
if (nsCOMPtr<nsIObserverService> obsService =
|
|
services::GetObserverService()) {
|
|
obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
|
|
}
|
|
PlatformStateChangeEvent(target, aState, aEnabled);
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
uint32_t type = nsIAccessibleEvent::EVENT_STATE_CHANGE;
|
|
bool extra;
|
|
uint32_t state = nsAccUtils::To32States(aState, &extra);
|
|
bool fromUser = true; // XXX fix this
|
|
nsINode* node = nullptr; // XXX can we do better?
|
|
RefPtr<xpcAccStateChangeEvent> event = new xpcAccStateChangeEvent(
|
|
type, xpcAcc, doc, node, fromUser, state, extra, aEnabled);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvCaretMoveEvent(
|
|
const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect,
|
|
const int32_t& aOffset, const bool& aIsSelectionCollapsed,
|
|
const bool& aIsAtEndOfLine, const int32_t& aGranularity,
|
|
const bool& aFromUser) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
RemoteAccessible* proxy = GetAccessible(aID);
|
|
if (!proxy) {
|
|
NS_ERROR("unknown caret move event target!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
mCaretId = aID;
|
|
mCaretOffset = aOffset;
|
|
mIsCaretAtEndOfLine = aIsAtEndOfLine;
|
|
if (aIsSelectionCollapsed) {
|
|
// We don't fire selection events for collapsed selections, but we need to
|
|
// ensure we don't have a stale cached selection; e.g. when selecting
|
|
// forward and then unselecting backward.
|
|
mTextSelections.ClearAndRetainStorage();
|
|
mTextSelections.AppendElement(TextRangeData(aID, aID, aOffset, aOffset));
|
|
}
|
|
|
|
PlatformCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed, aGranularity,
|
|
aCaretRect, aFromUser);
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
nsINode* node = nullptr;
|
|
bool fromUser = true; // XXX fix me
|
|
uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED;
|
|
RefPtr<xpcAccCaretMoveEvent> event = new xpcAccCaretMoveEvent(
|
|
type, xpcAcc, doc, node, fromUser, aOffset, aIsSelectionCollapsed,
|
|
aIsAtEndOfLine, aGranularity);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::ProcessTextChangeEvent(
|
|
const uint64_t& aID, const nsAString& aStr, const int32_t& aStart,
|
|
const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
|
|
RemoteAccessible* target = GetAccessible(aID);
|
|
if (!target) {
|
|
NS_ERROR("text change event target is unknown!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
PlatformTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
uint32_t type = aIsInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED
|
|
: nsIAccessibleEvent::EVENT_TEXT_REMOVED;
|
|
nsINode* node = nullptr;
|
|
RefPtr<xpcAccTextChangeEvent> event = new xpcAccTextChangeEvent(
|
|
type, xpcAcc, doc, node, aFromUser, aStart, aLen, aIsInsert, aStr);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvMutationEvents(
|
|
nsTArray<MutationEventData>&& aData) {
|
|
// We do not use ACQUIRE_ANDROID_LOCK here since we call functions that do
|
|
// that for us. The lock is not re-entrant.
|
|
mozilla::ipc::IPCResult result = IPC_OK();
|
|
if (mShutdown) {
|
|
return result;
|
|
}
|
|
for (MutationEventData& data : aData) {
|
|
switch (data.type()) {
|
|
case MutationEventData::Type::TCacheEventData: {
|
|
CacheEventData& cacheEventData = data;
|
|
result = RecvCache(cacheEventData.UpdateType(),
|
|
std::move(cacheEventData.aData()));
|
|
break;
|
|
}
|
|
case MutationEventData::Type::TReorderEventData: {
|
|
ReorderEventData& reorderEventData = data;
|
|
result = RecvEvent(reorderEventData.ID(), reorderEventData.Type());
|
|
break;
|
|
}
|
|
case MutationEventData::Type::THideEventData: {
|
|
HideEventData& hideEventData = data;
|
|
result = ProcessHideEvent(hideEventData.ID(),
|
|
hideEventData.IsFromUserInput());
|
|
break;
|
|
}
|
|
case MutationEventData::Type::TShowEventData: {
|
|
ShowEventData& showEventData = data;
|
|
result = ProcessShowEvent(
|
|
std::move(showEventData.NewTree()), showEventData.EventSuppressed(),
|
|
showEventData.Complete(), showEventData.FromUser());
|
|
break;
|
|
}
|
|
case MutationEventData::Type::TTextChangeEventData: {
|
|
TextChangeEventData& textChangeEventData = data;
|
|
result = ProcessTextChangeEvent(
|
|
textChangeEventData.ID(), textChangeEventData.Str(),
|
|
textChangeEventData.Start(), textChangeEventData.Len(),
|
|
textChangeEventData.IsInsert(), textChangeEventData.FromUser());
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
if (!result) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvRequestAckMutationEvents() {
|
|
if (!mShutdown) {
|
|
Unused << SendAckMutationEvents();
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectionEvent(
|
|
const uint64_t& aID, const uint64_t& aWidgetID, const uint32_t& aType) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
if (aType == 0 || aType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) {
|
|
MOZ_ASSERT_UNREACHABLE("Invalid event");
|
|
return IPC_FAIL(this, "Invalid event");
|
|
}
|
|
|
|
RemoteAccessible* target = GetAccessible(aID);
|
|
RemoteAccessible* widget = GetAccessible(aWidgetID);
|
|
if (!target || !widget) {
|
|
NS_ERROR("invalid id in selection event");
|
|
return IPC_OK();
|
|
}
|
|
|
|
PlatformSelectionEvent(target, widget, aType);
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
xpcAccessibleGeneric* xpcTarget = GetXPCAccessible(target);
|
|
xpcAccessibleDocument* xpcDoc = GetAccService()->GetXPCDocument(this);
|
|
RefPtr<xpcAccEvent> event =
|
|
new xpcAccEvent(aType, xpcTarget, xpcDoc, nullptr, false);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvScrollingEvent(
|
|
const uint64_t& aID, const uint64_t& aType, const uint32_t& aScrollX,
|
|
const uint32_t& aScrollY, const uint32_t& aMaxScrollX,
|
|
const uint32_t& aMaxScrollY) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
if (aType == 0 || aType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) {
|
|
MOZ_ASSERT_UNREACHABLE("Invalid event");
|
|
return IPC_FAIL(this, "Invalid event");
|
|
}
|
|
|
|
RemoteAccessible* target = GetAccessible(aID);
|
|
if (!target) {
|
|
NS_ERROR("no proxy for event!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
#if defined(ANDROID)
|
|
PlatformScrollingEvent(target, aType, aScrollX, aScrollY, aMaxScrollX,
|
|
aMaxScrollY);
|
|
#else
|
|
PlatformEvent(target, aType);
|
|
#endif
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
nsINode* node = nullptr;
|
|
bool fromUser = true; // XXX: Determine if this was from user input.
|
|
RefPtr<xpcAccScrollingEvent> event =
|
|
new xpcAccScrollingEvent(aType, xpcAcc, doc, node, fromUser, aScrollX,
|
|
aScrollY, aMaxScrollX, aMaxScrollY);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvCache(
|
|
const mozilla::a11y::CacheUpdateType& aUpdateType,
|
|
nsTArray<CacheData>&& aData) {
|
|
AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::RecvCache", A11Y, {}, ""_ns);
|
|
PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_RecvCache>
|
|
autoRecording;
|
|
// DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
|
|
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
for (auto& entry : aData) {
|
|
RemoteAccessible* remote = GetAccessible(entry.ID());
|
|
if (!remote) {
|
|
MOZ_ASSERT_UNREACHABLE("No remote found!");
|
|
continue;
|
|
}
|
|
|
|
remote->ApplyCache(aUpdateType, entry.Fields());
|
|
}
|
|
|
|
if (nsCOMPtr<nsIObserverService> obsService =
|
|
services::GetObserverService()) {
|
|
obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectedAccessiblesChanged(
|
|
nsTArray<uint64_t>&& aSelectedIDs, nsTArray<uint64_t>&& aUnselectedIDs) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
for (auto& id : aSelectedIDs) {
|
|
RemoteAccessible* remote = GetAccessible(id);
|
|
if (!remote) {
|
|
MOZ_ASSERT_UNREACHABLE("No remote found!");
|
|
continue;
|
|
}
|
|
|
|
remote->UpdateStateCache(states::SELECTED, true);
|
|
}
|
|
|
|
for (auto& id : aUnselectedIDs) {
|
|
RemoteAccessible* remote = GetAccessible(id);
|
|
if (!remote) {
|
|
MOZ_ASSERT_UNREACHABLE("No remote found!");
|
|
continue;
|
|
}
|
|
|
|
remote->UpdateStateCache(states::SELECTED, false);
|
|
}
|
|
|
|
if (nsCOMPtr<nsIObserverService> obsService =
|
|
services::GetObserverService()) {
|
|
obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvAccessiblesWillMove(
|
|
nsTArray<uint64_t>&& aIDs) {
|
|
for (uint64_t id : aIDs) {
|
|
mMovingIDs.EnsureInserted(id);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
#if !defined(XP_WIN)
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvAnnouncementEvent(
|
|
const uint64_t& aID, const nsAString& aAnnouncement,
|
|
const uint16_t& aPriority) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
RemoteAccessible* target = GetAccessible(aID);
|
|
if (!target) {
|
|
NS_ERROR("no proxy for event!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
# if defined(ANDROID)
|
|
PlatformAnnouncementEvent(target, aAnnouncement, aPriority);
|
|
# endif
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
RefPtr<xpcAccAnnouncementEvent> event = new xpcAccAnnouncementEvent(
|
|
nsIAccessibleEvent::EVENT_ANNOUNCEMENT, xpcAcc, doc, nullptr, false,
|
|
aAnnouncement, aPriority);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
#endif // !defined(XP_WIN)
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent(
|
|
const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
RemoteAccessible* target = GetAccessible(aID);
|
|
if (!target) {
|
|
NS_ERROR("no proxy for event!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
mTextSelections.ClearAndRetainStorage();
|
|
mTextSelections.AppendElements(aSelection);
|
|
|
|
#ifdef MOZ_WIDGET_COCOA
|
|
AutoTArray<TextRange, 1> ranges;
|
|
SelectionRanges(&ranges);
|
|
PlatformTextSelectionChangeEvent(target, ranges);
|
|
#else
|
|
PlatformEvent(target, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED);
|
|
#endif
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
|
|
xpcAccessibleDocument* doc = nsAccessibilityService::GetXPCDocument(this);
|
|
nsINode* node = nullptr;
|
|
bool fromUser = true; // XXX fix me
|
|
RefPtr<xpcAccEvent> event =
|
|
new xpcAccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, xpcAcc,
|
|
doc, node, fromUser);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent(
|
|
const a11y::role& aRole, const uint8_t& aRoleMapEntryIndex) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
if (!aria::IsRoleMapIndexValid(aRoleMapEntryIndex)) {
|
|
MOZ_ASSERT_UNREACHABLE("Invalid role map entry index");
|
|
return IPC_FAIL(this, "Invalid role map entry index");
|
|
}
|
|
|
|
mRole = aRole;
|
|
mRoleMapEntryIndex = aRoleMapEntryIndex;
|
|
|
|
#ifdef MOZ_WIDGET_COCOA
|
|
PlatformRoleChangedEvent(this, aRole, aRoleMapEntryIndex);
|
|
#endif
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvBindChildDoc(
|
|
NotNull<PDocAccessibleParent*> aChildDoc, const uint64_t& aID) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
// One document should never directly be the child of another.
|
|
// We should always have at least an outer doc accessible in between.
|
|
MOZ_ASSERT(aID);
|
|
if (!aID) return IPC_FAIL(this, "ID is 0!");
|
|
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
MOZ_ASSERT(CheckDocTree());
|
|
|
|
auto childDoc = static_cast<DocAccessibleParent*>(aChildDoc.get());
|
|
childDoc->Unbind();
|
|
ipc::IPCResult result = AddChildDoc(childDoc, aID, false);
|
|
MOZ_ASSERT(result);
|
|
MOZ_ASSERT(CheckDocTree());
|
|
#ifdef DEBUG
|
|
if (!result) {
|
|
return result;
|
|
}
|
|
#else
|
|
result = IPC_OK();
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
ipc::IPCResult DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
|
|
uint64_t aParentID,
|
|
bool aCreating) {
|
|
// We do not use GetAccessible here because we want to be sure to not get the
|
|
// document it self.
|
|
ProxyEntry* e = mAccessibles.GetEntry(aParentID);
|
|
if (!e) {
|
|
#ifndef FUZZING_SNAPSHOT
|
|
// This diagnostic assert and the one down below expect a well-behaved
|
|
// child process. In IPC fuzzing, we directly fuzz parameters of each
|
|
// method over IPDL and the asserts are not valid under these conditions.
|
|
MOZ_DIAGNOSTIC_CRASH("Binding to nonexistent proxy!");
|
|
#endif
|
|
return IPC_FAIL(this, "binding to nonexistant proxy!");
|
|
}
|
|
|
|
RemoteAccessible* outerDoc = e->mProxy;
|
|
MOZ_ASSERT(outerDoc);
|
|
|
|
// OuterDocAccessibles are expected to only have a document as a child.
|
|
// However for compatibility we tolerate replacing one document with another
|
|
// here.
|
|
if (!outerDoc->IsOuterDoc() || outerDoc->ChildCount() > 1 ||
|
|
(outerDoc->ChildCount() == 1 && !outerDoc->RemoteChildAt(0)->IsDoc())) {
|
|
#ifndef FUZZING_SNAPSHOT
|
|
MOZ_DIAGNOSTIC_CRASH("Binding to parent that isn't a valid OuterDoc!");
|
|
#endif
|
|
return IPC_FAIL(this, "Binding to parent that isn't a valid OuterDoc!");
|
|
}
|
|
|
|
if (outerDoc->ChildCount() == 1) {
|
|
MOZ_ASSERT(outerDoc->RemoteChildAt(0)->AsDoc());
|
|
outerDoc->RemoteChildAt(0)->AsDoc()->Unbind();
|
|
}
|
|
|
|
aChildDoc->SetParent(outerDoc);
|
|
outerDoc->SetChildDoc(aChildDoc);
|
|
mChildDocs.AppendElement(aChildDoc->mActorID);
|
|
|
|
if (aCreating) {
|
|
ProxyCreated(aChildDoc);
|
|
}
|
|
|
|
if (aChildDoc->IsTopLevelInContentProcess()) {
|
|
// aChildDoc is an embedded document in a different content process to
|
|
// this document.
|
|
#if defined(XP_WIN)
|
|
if (nsWinUtils::IsWindowEmulationStarted()) {
|
|
aChildDoc->SetEmulatedWindowHandle(mEmulatedWindowHandle);
|
|
}
|
|
#endif // defined(XP_WIN)
|
|
// We need to fire a reorder event on the outer doc accessible.
|
|
// For same-process documents, this is fired by the content process, but
|
|
// this isn't possible when the document is in a different process to its
|
|
// embedder.
|
|
// FireEvent fires both OS and XPCOM events.
|
|
FireEvent(outerDoc, nsIAccessibleEvent::EVENT_REORDER);
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
ipc::IPCResult DocAccessibleParent::AddChildDoc(
|
|
dom::BrowserBridgeParent* aBridge) {
|
|
MOZ_ASSERT(aBridge->GetEmbedderAccessibleDoc() == this);
|
|
uint64_t parentId = aBridge->GetEmbedderAccessibleId();
|
|
MOZ_ASSERT(parentId);
|
|
if (!mAccessibles.GetEntry(parentId)) {
|
|
// Sometimes, this gets called before the embedder sends us the
|
|
// OuterDocAccessible. We must add the child when the OuterDocAccessible
|
|
// gets created later.
|
|
mPendingOOPChildDocs.Insert(aBridge);
|
|
return IPC_OK();
|
|
}
|
|
return AddChildDoc(aBridge->GetDocAccessibleParent(), parentId,
|
|
/* aCreating */ false);
|
|
}
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvShutdown() {
|
|
ACQUIRE_ANDROID_LOCK
|
|
Destroy();
|
|
|
|
auto mgr = static_cast<dom::BrowserParent*>(Manager());
|
|
if (!mgr->IsDestroyed()) {
|
|
if (!PDocAccessibleParent::Send__delete__(this)) {
|
|
return IPC_FAIL_NO_REASON(mgr);
|
|
}
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
void DocAccessibleParent::Destroy() {
|
|
// If we are already shutdown that is because our containing tab parent is
|
|
// shutting down in which case we don't need to do anything.
|
|
if (mShutdown) {
|
|
return;
|
|
}
|
|
|
|
mShutdown = true;
|
|
mBrowsingContext = nullptr;
|
|
|
|
#ifdef ANDROID
|
|
if (FocusMgr() && FocusMgr()->IsFocusedRemoteDoc(this)) {
|
|
FocusMgr()->SetFocusedRemoteDoc(nullptr);
|
|
}
|
|
#endif
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(LiveDocs().Contains(mActorID));
|
|
uint32_t childDocCount = mChildDocs.Length();
|
|
for (uint32_t i = 0; i < childDocCount; i++) {
|
|
for (uint32_t j = i + 1; j < childDocCount; j++) {
|
|
MOZ_DIAGNOSTIC_ASSERT(mChildDocs[i] != mChildDocs[j]);
|
|
}
|
|
}
|
|
|
|
// XXX This indirection through the hash map of live documents shouldn't be
|
|
// needed, but be paranoid for now.
|
|
int32_t actorID = mActorID;
|
|
for (uint32_t i = childDocCount - 1; i < childDocCount; i--) {
|
|
DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
|
|
MOZ_ASSERT(thisDoc);
|
|
if (!thisDoc) {
|
|
return;
|
|
}
|
|
|
|
thisDoc->ChildDocAt(i)->Destroy();
|
|
}
|
|
|
|
for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
|
|
RemoteAccessible* acc = iter.Get()->mProxy;
|
|
MOZ_ASSERT(acc != this);
|
|
if (acc->IsTable()) {
|
|
CachedTableAccessible::Invalidate(acc);
|
|
}
|
|
ProxyDestroyed(acc);
|
|
// mAccessibles owns acc, so removing it deletes acc.
|
|
iter.Remove();
|
|
}
|
|
|
|
DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
|
|
MOZ_ASSERT(thisDoc);
|
|
if (!thisDoc) {
|
|
return;
|
|
}
|
|
|
|
mChildren.Clear();
|
|
// The code above should have already completely cleared these, but to be
|
|
// extra safe make sure they are cleared here.
|
|
thisDoc->mAccessibles.Clear();
|
|
thisDoc->mChildDocs.Clear();
|
|
|
|
DocManager::NotifyOfRemoteDocShutdown(thisDoc);
|
|
thisDoc = LiveDocs().Get(actorID);
|
|
MOZ_ASSERT(thisDoc);
|
|
if (!thisDoc) {
|
|
return;
|
|
}
|
|
|
|
ProxyDestroyed(thisDoc);
|
|
thisDoc = LiveDocs().Get(actorID);
|
|
MOZ_ASSERT(thisDoc);
|
|
if (!thisDoc) {
|
|
return;
|
|
}
|
|
|
|
if (IsTopLevel()) {
|
|
GetAccService()->RemoteDocShutdown(this);
|
|
} else {
|
|
Unbind();
|
|
}
|
|
}
|
|
|
|
void DocAccessibleParent::ActorDestroy(ActorDestroyReason aWhy) {
|
|
MOZ_ASSERT(CheckDocTree());
|
|
if (!mShutdown) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
DocAccessibleParent* DocAccessibleParent::ParentDoc() const {
|
|
if (RemoteAccessible* parent = RemoteParent()) {
|
|
return parent->Document();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool DocAccessibleParent::CheckDocTree() const {
|
|
size_t childDocs = mChildDocs.Length();
|
|
for (size_t i = 0; i < childDocs; i++) {
|
|
const DocAccessibleParent* childDoc = ChildDocAt(i);
|
|
if (!childDoc || childDoc->ParentDoc() != this) return false;
|
|
|
|
if (!childDoc->CheckDocTree()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
xpcAccessibleGeneric* DocAccessibleParent::GetXPCAccessible(
|
|
RemoteAccessible* aProxy) {
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
MOZ_ASSERT(doc);
|
|
|
|
return doc->GetAccessible(aProxy);
|
|
}
|
|
|
|
#if defined(XP_WIN)
|
|
void DocAccessibleParent::MaybeInitWindowEmulation() {
|
|
if (!nsWinUtils::IsWindowEmulationStarted()) {
|
|
return;
|
|
}
|
|
|
|
// XXX get the bounds from the browserParent instead of poking at accessibles
|
|
// which might not exist yet.
|
|
LocalAccessible* outerDoc = OuterDocOfRemoteBrowser();
|
|
if (!outerDoc) {
|
|
return;
|
|
}
|
|
|
|
RootAccessible* rootDocument = outerDoc->RootAccessible();
|
|
MOZ_ASSERT(rootDocument);
|
|
|
|
bool isActive = true;
|
|
LayoutDeviceIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
|
|
if (Compatibility::IsDolphin()) {
|
|
rect = Bounds();
|
|
LayoutDeviceIntRect rootRect = rootDocument->Bounds();
|
|
rect.MoveToX(rootRect.X() - rect.X());
|
|
rect.MoveToY(rect.Y() - rootRect.Y());
|
|
|
|
auto browserParent = static_cast<dom::BrowserParent*>(Manager());
|
|
isActive = browserParent->GetDocShellIsActive();
|
|
}
|
|
|
|
// onCreate is guaranteed to be called synchronously by
|
|
// nsWinUtils::CreateNativeWindow, so this reference isn't really necessary.
|
|
// However, static analysis complains without it.
|
|
RefPtr<DocAccessibleParent> thisRef = this;
|
|
nsWinUtils::NativeWindowCreateProc onCreate([thisRef](HWND aHwnd) -> void {
|
|
::SetPropW(aHwnd, kPropNameDocAccParent,
|
|
reinterpret_cast<HANDLE>(thisRef.get()));
|
|
thisRef->SetEmulatedWindowHandle(aHwnd);
|
|
});
|
|
|
|
HWND parentWnd = reinterpret_cast<HWND>(rootDocument->GetNativeWindow());
|
|
DebugOnly<HWND> hWnd = nsWinUtils::CreateNativeWindow(
|
|
kClassNameTabContent, parentWnd, rect.X(), rect.Y(), rect.Width(),
|
|
rect.Height(), isActive, &onCreate);
|
|
MOZ_ASSERT(hWnd);
|
|
}
|
|
|
|
void DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle) {
|
|
if (!aWindowHandle && mEmulatedWindowHandle && IsTopLevel()) {
|
|
::DestroyWindow(mEmulatedWindowHandle);
|
|
}
|
|
mEmulatedWindowHandle = aWindowHandle;
|
|
}
|
|
#endif // defined(XP_WIN)
|
|
|
|
mozilla::ipc::IPCResult DocAccessibleParent::RecvFocusEvent(
|
|
const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect) {
|
|
ACQUIRE_ANDROID_LOCK
|
|
if (mShutdown) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
RemoteAccessible* proxy = GetAccessible(aID);
|
|
if (!proxy) {
|
|
NS_ERROR("no proxy for event!");
|
|
return IPC_OK();
|
|
}
|
|
|
|
#ifdef ANDROID
|
|
if (FocusMgr()) {
|
|
FocusMgr()->SetFocusedRemoteDoc(this);
|
|
}
|
|
#endif
|
|
|
|
mFocus = aID;
|
|
PlatformFocusEvent(proxy, aCaretRect);
|
|
|
|
if (!nsCoreUtils::AccEventObserversExist()) {
|
|
return IPC_OK();
|
|
}
|
|
|
|
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
|
|
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
|
|
nsINode* node = nullptr;
|
|
bool fromUser = true; // XXX fix me
|
|
RefPtr<xpcAccEvent> event = new xpcAccEvent(nsIAccessibleEvent::EVENT_FOCUS,
|
|
xpcAcc, doc, node, fromUser);
|
|
nsCoreUtils::DispatchAccEvent(std::move(event));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
void DocAccessibleParent::SelectionRanges(nsTArray<TextRange>* aRanges) const {
|
|
aRanges->SetCapacity(mTextSelections.Length());
|
|
for (const auto& data : mTextSelections) {
|
|
// Selection ranges should usually be in sync with the tree. However, tree
|
|
// and selection updates happen using separate IPDL calls, so it's possible
|
|
// for a client selection query to arrive between them. Thus, we validate
|
|
// the Accessibles and offsets here.
|
|
auto* startAcc =
|
|
const_cast<RemoteAccessible*>(GetAccessible(data.StartID()));
|
|
auto* endAcc = const_cast<RemoteAccessible*>(GetAccessible(data.EndID()));
|
|
if (!startAcc || !endAcc) {
|
|
continue;
|
|
}
|
|
uint32_t startCount = startAcc->CharacterCount();
|
|
if (startCount == 0 ||
|
|
data.StartOffset() > static_cast<int32_t>(startCount)) {
|
|
continue;
|
|
}
|
|
uint32_t endCount = endAcc->CharacterCount();
|
|
if (endCount == 0 || data.EndOffset() > static_cast<int32_t>(endCount)) {
|
|
continue;
|
|
}
|
|
aRanges->AppendElement(TextRange(const_cast<DocAccessibleParent*>(this),
|
|
startAcc, data.StartOffset(), endAcc,
|
|
data.EndOffset()));
|
|
}
|
|
}
|
|
|
|
Accessible* DocAccessibleParent::FocusedChild() {
|
|
LocalAccessible* outerDoc = OuterDocOfRemoteBrowser();
|
|
if (!outerDoc) {
|
|
return nullptr;
|
|
}
|
|
|
|
RootAccessible* rootDocument = outerDoc->RootAccessible();
|
|
return rootDocument->FocusedChild();
|
|
}
|
|
|
|
void DocAccessibleParent::URL(nsACString& aURL) const {
|
|
if (!mBrowsingContext) {
|
|
return;
|
|
}
|
|
nsCOMPtr<nsIURI> uri = mBrowsingContext->GetCurrentURI();
|
|
if (!uri) {
|
|
return;
|
|
}
|
|
// Let's avoid treating too long URI in the main process for avoiding
|
|
// memory fragmentation as far as possible.
|
|
if (uri->SchemeIs("data") || uri->SchemeIs("blob")) {
|
|
return;
|
|
}
|
|
nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service();
|
|
if (NS_WARN_IF(!io)) {
|
|
return;
|
|
}
|
|
nsCOMPtr<nsIURI> exposableURI;
|
|
if (NS_FAILED(io->CreateExposableURI(uri, getter_AddRefs(exposableURI))) ||
|
|
MOZ_UNLIKELY(!exposableURI)) {
|
|
return;
|
|
}
|
|
exposableURI->GetSpec(aURL);
|
|
}
|
|
|
|
void DocAccessibleParent::URL(nsAString& aURL) const {
|
|
nsAutoCString url;
|
|
URL(url);
|
|
CopyUTF8toUTF16(url, aURL);
|
|
}
|
|
|
|
void DocAccessibleParent::MimeType(nsAString& aMime) const {
|
|
if (mCachedFields) {
|
|
mCachedFields->GetAttribute(CacheKey::MimeType, aMime);
|
|
}
|
|
}
|
|
|
|
Relation DocAccessibleParent::RelationByType(RelationType aType) const {
|
|
// If the accessible is top-level, provide the NODE_CHILD_OF relation so that
|
|
// MSAA clients can easily get to true parent instead of getting to oleacc's
|
|
// ROLE_WINDOW accessible when window emulation is enabled which will prevent
|
|
// us from going up further (because it is system generated and has no idea
|
|
// about the hierarchy above it).
|
|
if (aType == RelationType::NODE_CHILD_OF && IsTopLevel()) {
|
|
return Relation(Parent());
|
|
}
|
|
|
|
return RemoteAccessible::RelationByType(aType);
|
|
}
|
|
|
|
DocAccessibleParent* DocAccessibleParent::GetFrom(
|
|
dom::BrowsingContext* aBrowsingContext) {
|
|
if (!aBrowsingContext) {
|
|
return nullptr;
|
|
}
|
|
|
|
dom::BrowserParent* bp = aBrowsingContext->Canonical()->GetBrowserParent();
|
|
if (!bp) {
|
|
return nullptr;
|
|
}
|
|
|
|
const ManagedContainer<PDocAccessibleParent>& docs =
|
|
bp->ManagedPDocAccessibleParent();
|
|
for (auto* key : docs) {
|
|
// Iterate over our docs until we find one with a browsing
|
|
// context that matches the one we passed in. Return that
|
|
// document.
|
|
auto* doc = static_cast<a11y::DocAccessibleParent*>(key);
|
|
if (doc->GetBrowsingContext() == aBrowsingContext) {
|
|
return doc;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
size_t DocAccessibleParent::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
|
|
size_t size = 0;
|
|
|
|
size += RemoteAccessible::SizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
size += mReverseRelations.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
for (auto i = mReverseRelations.Iter(); !i.Done(); i.Next()) {
|
|
size += i.Data().ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
for (auto j = i.Data().Iter(); !j.Done(); j.Next()) {
|
|
size += j.Data().ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
}
|
|
|
|
size += mOnScreenAccessibles.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
size += mChildDocs.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
size += mAccessibles.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
for (auto i = mAccessibles.Iter(); !i.Done(); i.Next()) {
|
|
size += i.Get()->mProxy->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
size += mPendingOOPChildDocs.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
// The mTextSelections array contains structs of integers. We can count them
|
|
// by counting the size of the array - there's no deep structure here.
|
|
size += mTextSelections.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
return size;
|
|
}
|
|
|
|
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOfAccessibilityCache);
|
|
|
|
NS_IMETHODIMP
|
|
DocAccessibleParent::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|
nsISupports* aData, bool aAnon) {
|
|
nsAutoCString path;
|
|
|
|
if (aAnon) {
|
|
path = nsPrintfCString("explicit/a11y/cache(%" PRIu64 ")", mActorID);
|
|
} else {
|
|
nsCString url;
|
|
URL(url);
|
|
url.ReplaceChar(
|
|
'/', '\\'); // Tell the memory reporter this is not a path seperator.
|
|
path = nsPrintfCString("explicit/a11y/cache(%s)", url.get());
|
|
}
|
|
|
|
aHandleReport->Callback(
|
|
/* process */ ""_ns, path, KIND_HEAP, UNITS_BYTES,
|
|
SizeOfIncludingThis(MallocSizeOfAccessibilityCache),
|
|
nsLiteralCString("Size of the accessability cache for this document."),
|
|
aData);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(DocAccessibleParent, nsIMemoryReporter);
|
|
|
|
} // namespace a11y
|
|
} // namespace mozilla
|