diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /accessible/ipc | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
63 files changed, 16975 insertions, 0 deletions
diff --git a/accessible/ipc/DocAccessibleChildBase.cpp b/accessible/ipc/DocAccessibleChildBase.cpp new file mode 100644 index 0000000000..98c4a67c6a --- /dev/null +++ b/accessible/ipc/DocAccessibleChildBase.cpp @@ -0,0 +1,257 @@ +/* -*- 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/ipc/ProcessChild.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->IsTextLeaf() || acc->IsImage()) { + // Ideally, we'd set eActionable for any Accessible with an ancedstor + // action. However, that requires an ancestor walk which is too expensive + // here. eActionable is only used by ATK. For now, we only expose ancestor + // actions on text leaf and image Accessibles. This means that we don't + // support "click ancestor" for ATK. + if (acc->ActionCount()) { + genericTypes |= eActionable; + } + } else if (acc->HasPrimaryAction()) { + 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 || + StaticPrefs::accessibility_cache_enabled_AtStartup()); + SerializeTree(shownTree, data.NewTree()); + if (ipc::ProcessChild::ExpectingShutdown()) { + return; + } + 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)); + } + } + // The cache array might be empty if there were only moved Accessibles or if + // no Accessibles generated any cache data. + if (!cache.IsEmpty()) { + Unused << SendCache(CacheUpdateType::Initial, cache, !aSuppressShowEvent); + } + } +} + +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::RecvScrollTo( + const uint64_t& aID, const uint32_t& aScrollType) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + RefPtr<PresShell> presShell = acc->Document()->PresShellPtr(); + nsCOMPtr<nsIContent> content = acc->GetContent(); + nsCoreUtils::ScrollTo(presShell, content, aScrollType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChildBase::RecvTakeSelection( + const uint64_t& aID) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + acc->TakeSelection(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChildBase::RecvSetSelected( + const uint64_t& aID, const bool& aSelect) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + acc->SetSelected(aSelect); + } + + 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(); +} + +mozilla::ipc::IPCResult DocAccessibleChildBase::RecvDoActionAsync( + const uint64_t& aID, const uint8_t& aIndex) { + if (LocalAccessible* acc = IdToAccessible(aID)) { + Unused << acc->DoAction(aIndex); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChildBase::RecvSetCaretOffset( + const uint64_t& aID, const int32_t& aOffset) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole() && acc->IsValidOffset(aOffset)) { + acc->SetCaretOffset(aOffset); + } + 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)); +} + +HyperTextAccessible* DocAccessibleChildBase::IdToHyperTextAccessible( + const uint64_t& aID) const { + LocalAccessible* acc = IdToAccessible(aID); + return acc && acc->IsHyperText() ? acc->AsHyperText() : nullptr; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/DocAccessibleChildBase.h b/accessible/ipc/DocAccessibleChildBase.h new file mode 100644 index 0000000000..2573675048 --- /dev/null +++ b/accessible/ipc/DocAccessibleChildBase.h @@ -0,0 +1,114 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_DocAccessibleChildBase_h +#define mozilla_a11y_DocAccessibleChildBase_h + +#include "mozilla/a11y/DocAccessible.h" +#include "mozilla/a11y/PDocAccessibleChild.h" +#include "mozilla/Unused.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace a11y { + +class LocalAccessible; +class AccShowEvent; + +class DocAccessibleChildBase : public PDocAccessibleChild { + public: + explicit DocAccessibleChildBase(DocAccessible* aDoc) + : mDoc(aDoc), mIsRemoteConstructed(false) { + MOZ_COUNT_CTOR(DocAccessibleChildBase); + } + + ~DocAccessibleChildBase() { + // Shutdown() should have been called, but maybe it isn't if the process is + // killed? + MOZ_ASSERT(!mDoc); + if (mDoc) { + mDoc->SetIPCDoc(nullptr); + } + + MOZ_COUNT_DTOR(DocAccessibleChildBase); + } + + virtual void Shutdown() { + DetachDocument(); + SendShutdown(); + } + + /** + * Serializes a shown tree and sends it to the chrome process. + */ + void InsertIntoIpcTree(LocalAccessible* aParent, LocalAccessible* aChild, + uint32_t aIdxInParent, bool aSuppressShowEvent); + void ShowEvent(AccShowEvent* aShowEvent); + + virtual void ActorDestroy(ActorDestroyReason) override { + if (!mDoc) { + return; + } + + mDoc->SetIPCDoc(nullptr); + mDoc = nullptr; + } + + virtual mozilla::ipc::IPCResult RecvTakeFocus(const uint64_t& aID) override; + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual mozilla::ipc::IPCResult RecvScrollTo( + const uint64_t& aID, const uint32_t& aScrollType) override; + + virtual mozilla::ipc::IPCResult RecvTakeSelection( + const uint64_t& aID) override; + virtual mozilla::ipc::IPCResult RecvSetSelected(const uint64_t& aID, + const bool& aSelect) override; + + virtual mozilla::ipc::IPCResult RecvVerifyCache( + const uint64_t& aID, const uint64_t& aCacheDomain, + AccAttributes* aFields) override; + + virtual mozilla::ipc::IPCResult RecvDoActionAsync( + const uint64_t& aID, const uint8_t& aIndex) override; + + virtual mozilla::ipc::IPCResult RecvSetCaretOffset( + const uint64_t& aID, const int32_t& aOffset) override; + + protected: + static void FlattenTree(LocalAccessible* aRoot, + nsTArray<LocalAccessible*>& aTree); + + static void SerializeTree(nsTArray<LocalAccessible*>& aTree, + nsTArray<AccessibleData>& aData); + + virtual void MaybeSendShowEvent(ShowEventData& aData, bool aFromUser) { + Unused << SendShowEvent(aData, aFromUser); + } + + void DetachDocument() { + if (mDoc) { + mDoc->SetIPCDoc(nullptr); + mDoc = nullptr; + } + } + + bool IsConstructedInParentProcess() const { return mIsRemoteConstructed; } + void SetConstructedInParentProcess() { mIsRemoteConstructed = true; } + + LocalAccessible* IdToAccessible(const uint64_t& aID) const; + HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const; + + DocAccessible* mDoc; + bool mIsRemoteConstructed; + + friend void DocAccessible::DoInitialUpdate(); +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_DocAccessibleChildBase_h diff --git a/accessible/ipc/DocAccessibleParent.cpp b/accessible/ipc/DocAccessibleParent.cpp new file mode 100644 index 0000000000..54af3eb3c0 --- /dev/null +++ b/accessible/ipc/DocAccessibleParent.cpp @@ -0,0 +1,1465 @@ +/* -*- 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 "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/StaticPrefs_accessibility.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 "AccessibleWrap.h" +# include "Compatibility.h" +# include "mozilla/mscom/PassthruProxy.h" +# include "mozilla/mscom/Ptr.h" +# include "nsWinUtils.h" +#else +# include "mozilla/a11y/DocAccessiblePlatformExtParent.h" +#endif + +#if defined(ANDROID) +# define ACQUIRE_ANDROID_LOCK \ + MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); +#else +# define ACQUIRE_ANDROID_LOCK \ + do { \ + } while (0); +#endif + +namespace mozilla { + +#if defined(XP_WIN) +namespace mscom { +namespace detail { +// Needed by mscom::PassthruProxy::Wrap<IAccessible>. +template <> +struct VTableSizer<IAccessible> { + // 3 methods in IUnknown + 4 in IDispatch + 21 in IAccessible = 28 total + enum { Size = 28 }; +}; +} // namespace detail +} // namespace mscom +#endif // defined (XP_WIN) + +namespace a11y { +uint64_t DocAccessibleParent::sMaxDocID = 0; + +DocAccessibleParent::DocAccessibleParent() + : RemoteAccessible(this), + mParentDoc(kNoParentDoc), +#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::RecvShowEvent( + const ShowEventData& aData, const bool& aFromUser) { + ACQUIRE_ANDROID_LOCK + if (mShutdown) return IPC_OK(); + + MOZ_ASSERT(CheckDocTree()); + + if (aData.NewTree().IsEmpty()) { + return IPC_FAIL(this, "No children being added"); + } + + RemoteAccessible* parent = GetAccessible(aData.ID()); + + // 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 + } + + uint32_t newChildIdx = aData.Idx(); + if (newChildIdx > parent->ChildCount()) { + NS_ERROR("invalid index to add child at"); +#ifdef DEBUG + return IPC_FAIL(this, "invalid index"); +#else + return IPC_OK(); +#endif + } + + uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx); + MOZ_ASSERT(consumed == aData.NewTree().Length()); + + // XXX This shouldn't happen, but if we failed to add children then the below + // is pointless and can crash. + if (!consumed) { + return IPC_FAIL(this, "failed to add children"); + } + +#ifdef DEBUG + for (uint32_t i = 0; i < consumed; i++) { + uint64_t id = aData.NewTree()[i].ID(); + MOZ_ASSERT(mAccessibles.GetEntry(id)); + } +#endif + + MOZ_ASSERT(CheckDocTree()); + + // Just update, no events. + if (aData.EventSuppressed()) { + return IPC_OK(); + } + + RemoteAccessible* target = parent->RemoteChildAt(newChildIdx); + ProxyShowHideEvent(target, parent, true, aFromUser); + + if (!nsCoreUtils::AccEventObserversExist()) { + return IPC_OK(); + } + + uint32_t type = nsIAccessibleEvent::EVENT_SHOW; + xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target); + 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(); +} + +uint32_t DocAccessibleParent::AddSubtree( + RemoteAccessible* aParent, const nsTArray<a11y::AccessibleData>& aNewTree, + uint32_t aIdx, uint32_t aIdxInParent) { + if (aNewTree.Length() <= aIdx) { + NS_ERROR("bad index in serialized tree!"); + return 0; + } + + const AccessibleData& newChild = aNewTree[aIdx]; + + RemoteAccessible* newProxy; + if ((newProxy = GetAccessible(newChild.ID()))) { + // This is a move. Reuse the Accessible; don't destroy it. + MOZ_ASSERT(!newProxy->RemoteParent()); + aParent->AddChildAt(aIdxInParent, newProxy); + newProxy->SetParent(aParent); + } else { + newProxy = new RemoteAccessible( + newChild.ID(), aParent, this, newChild.Role(), newChild.Type(), + newChild.GenericTypes(), newChild.RoleMapEntryIndex()); + + aParent->AddChildAt(aIdxInParent, newProxy); + mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy; + ProxyCreated(newProxy); + +#if defined(XP_WIN) + if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) { + MsaaAccessible::GetFrom(newProxy)->SetID(newChild.MsaaID()); + } +#endif + + mPendingOOPChildDocs.RemoveIf([&](dom::BrowserBridgeParent* bridge) { + MOZ_ASSERT(bridge->GetBrowserParent(), + "Pending BrowserBridgeParent should be alive"); + if (bridge->GetEmbedderAccessibleId() != newChild.ID()) { + return false; + } + MOZ_ASSERT(bridge->GetEmbedderAccessibleDoc() == this); + if (DocAccessibleParent* childDoc = bridge->GetDocAccessibleParent()) { + AddChildDoc(childDoc, newChild.ID(), false); + } + return true; + }); + } + + if (newProxy->IsTableCell()) { + CachedTableAccessible::Invalidate(newProxy); + } + + DebugOnly<bool> isOuterDoc = newProxy->ChildCount() == 1; + + uint32_t accessibles = 1; + uint32_t kids = newChild.ChildrenCount(); + for (uint32_t i = 0; i < kids; i++) { + uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i); + if (!consumed) return 0; + + accessibles += consumed; + } + + MOZ_ASSERT((isOuterDoc && kids == 0) || newProxy->ChildCount() == kids); + + return accessibles; +} + +void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible* aAcc) { + 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); + if (aAcc->IsOuterDoc()) { + // Leave child documents alone. They are added and removed differently to + // normal children. + return; + } + // Some children might be removed. Handle children the same way. + for (RemoteAccessible* child : aAcc->mChildren) { + ShutdownOrPrepareForMove(child); + } + // Even if some children are kept, those will be re-attached when we handle + // the show event. For now, clear all of them. + aAcc->mChildren.Clear(); +} + +mozilla::ipc::IPCResult DocAccessibleParent::RecvHideEvent( + const uint64_t& aRootID, const bool& aFromUser) { + ACQUIRE_ANDROID_LOCK + if (mShutdown) return IPC_OK(); + + 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(); + ProxyShowHideEvent(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(); + } + + 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_FOCUS) { +#ifdef ANDROID + if (FocusMgr()) { + FocusMgr()->SetFocusedRemoteDoc(this); + } +#endif + mFocus = aAcc->ID(); + } + + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + if (aEventType == nsIAccessibleEvent::EVENT_REORDER || + aEventType == nsIAccessibleEvent::EVENT_INNER_REORDER) { + for (RemoteAccessible* child = aAcc->RemoteFirstChild(); child; + child = child->RemoteNextSibling()) { + child->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); + } + } + + ProxyEvent(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(); + } + + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + target->UpdateStateCache(aState, aEnabled); + } + ProxyStateChangeEvent(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, +#if defined(XP_WIN) + const LayoutDeviceIntRect& aCaretRect, +#endif // defined (XP_WIN) + const int32_t& aOffset, const bool& aIsSelectionCollapsed, + const bool& aIsAtEndOfLine, const int32_t& aGranularity) { + 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)); + } + +#if defined(XP_WIN) + ProxyCaretMoveEvent(proxy, aCaretRect, aGranularity); +#else + ProxyCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed, aGranularity); +#endif + + 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::RecvTextChangeEvent( + const uint64_t& aID, const nsAString& aStr, const int32_t& aStart, + const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) { + ACQUIRE_ANDROID_LOCK + if (mShutdown) { + return IPC_OK(); + } + + RemoteAccessible* target = GetAccessible(aID); + if (!target) { + NS_ERROR("text change event target is unknown!"); + return IPC_OK(); + } + + ProxyTextChangeEvent(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(); +} + +#if defined(XP_WIN) + +mozilla::ipc::IPCResult DocAccessibleParent::RecvSyncTextChangeEvent( + const uint64_t& aID, const nsAString& aStr, const int32_t& aStart, + const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) { + return RecvTextChangeEvent(aID, aStr, aStart, aLen, aIsInsert, aFromUser); +} + +#endif // defined(XP_WIN) + +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(); + } + + RemoteAccessible* target = GetAccessible(aID); + RemoteAccessible* widget = GetAccessible(aWidgetID); + if (!target || !widget) { + NS_ERROR("invalid id in selection event"); + return IPC_OK(); + } + + ProxySelectionEvent(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::RecvVirtualCursorChangeEvent( + const uint64_t& aID, const uint64_t& aOldPositionID, + const int32_t& aOldStartOffset, const int32_t& aOldEndOffset, + const uint64_t& aNewPositionID, const int32_t& aNewStartOffset, + const int32_t& aNewEndOffset, const int16_t& aReason, + const int16_t& aBoundaryType, const bool& aFromUser) { + ACQUIRE_ANDROID_LOCK + if (mShutdown) { + return IPC_OK(); + } + + RemoteAccessible* target = GetAccessible(aID); + RemoteAccessible* oldPosition = GetAccessible(aOldPositionID); + RemoteAccessible* newPosition = GetAccessible(aNewPositionID); + + if (!target) { + NS_ERROR("no proxy for event!"); + return IPC_OK(); + } + +#if defined(ANDROID) + ProxyVirtualCursorChangeEvent( + target, oldPosition, aOldStartOffset, aOldEndOffset, newPosition, + aNewStartOffset, aNewEndOffset, aReason, aBoundaryType, aFromUser); +#endif + + if (!nsCoreUtils::AccEventObserversExist()) { + return IPC_OK(); + } + + xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); + RefPtr<xpcAccVirtualCursorChangeEvent> event = + new xpcAccVirtualCursorChangeEvent( + nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, + GetXPCAccessible(target), doc, nullptr, aFromUser, + GetXPCAccessible(oldPosition), aOldStartOffset, aOldEndOffset, + GetXPCAccessible(newPosition), aNewStartOffset, aNewEndOffset, + aReason, aBoundaryType); + 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(); + } + + RemoteAccessible* target = GetAccessible(aID); + if (!target) { + NS_ERROR("no proxy for event!"); + return IPC_OK(); + } + +#if defined(ANDROID) + ProxyScrollingEvent(target, aType, aScrollX, aScrollY, aMaxScrollX, + aMaxScrollY); +#else + ProxyEvent(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, const bool& aDispatchShowEvent) { + 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 (aDispatchShowEvent && !aData.IsEmpty()) { + // We might need to dispatch a show event for an initial cache push. We + // should never dispatch a show event for a (non-initial) cache update. + MOZ_ASSERT(aUpdateType == CacheUpdateType::Initial); + RemoteAccessible* target = GetAccessible(aData.ElementAt(0).ID()); + if (!target) { + MOZ_ASSERT_UNREACHABLE("No remote found for initial cache push!"); + return IPC_OK(); + } + // We never dispatch a show event for the doc itself. + MOZ_ASSERT(!target->IsDoc() && target->RemoteParent()); + + ProxyShowHideEvent(target, target->RemoteParent(), true, false); + + if (nsCoreUtils::AccEventObserversExist()) { + xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target); + xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); + nsINode* node = nullptr; + RefPtr<xpcAccEvent> event = new xpcAccEvent( + nsIAccessibleEvent::EVENT_SHOW, xpcAcc, doc, node, false); + nsCoreUtils::DispatchAccEvent(std::move(event)); + } + } + + 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) + ProxyAnnouncementEvent(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(); + } + + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + mTextSelections.ClearAndRetainStorage(); + mTextSelections.AppendElements(aSelection); + } + +#ifdef MOZ_WIDGET_COCOA + ProxyTextSelectionChangeEvent(target, aSelection); +#else + ProxyEvent(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(); + } + + mRole = aRole; + mRoleMapEntryIndex = aRoleMapEntryIndex; + +#ifdef MOZ_WIDGET_COCOA + ProxyRoleChangedEvent(this, aRole, aRoleMapEntryIndex); +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleParent::RecvBindChildDoc( + 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); + 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_ASSERT(false, "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_ASSERT(false, + "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); + aChildDoc->mParentDoc = mActorID; + + if (aCreating) { + ProxyCreated(aChildDoc); + } + + if (aChildDoc->IsTopLevelInContentProcess()) { + // aChildDoc is an embedded document in a different content process to + // this document. + auto embeddedBrowser = + static_cast<dom::BrowserParent*>(aChildDoc->Manager()); + dom::BrowserBridgeParent* bridge = + embeddedBrowser->GetBrowserBridgeParent(); + if (bridge) { +#if defined(XP_WIN) + if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Send a COM proxy for the embedded document to the embedder process + // hosting the iframe. This will be returned as the child of the + // embedder OuterDocAccessible. + RefPtr<IDispatch> docAcc; + aChildDoc->GetCOMInterface((void**)getter_AddRefs(docAcc)); + MOZ_ASSERT(docAcc); + if (docAcc) { + RefPtr<IDispatch> docWrapped( + mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(docAcc))); + IDispatchHolder::COMPtrType docPtr( + mscom::ToProxyUniquePtr(std::move(docWrapped))); + IDispatchHolder docHolder(std::move(docPtr)); + if (bridge->SendSetEmbeddedDocAccessibleCOMProxy(docHolder)) { +# if defined(MOZ_SANDBOX) + aChildDoc->mDocProxyStream = docHolder.GetPreservedStream(); +# endif // defined(MOZ_SANDBOX) + } + } + // Send a COM proxy for the embedder OuterDocAccessible to the embedded + // document process. This will be returned as the parent of the + // embedded document. + aChildDoc->SendParentCOMProxy(outerDoc); + if (nsWinUtils::IsWindowEmulationStarted()) { + // The embedded document should use the same emulated window handle as + // its embedder. It will return the embedder document (not a window + // accessible) as the parent accessible, so we pass a null accessible + // when sending the window to the embedded document. + Unused << aChildDoc->SendEmulatedWindow( + reinterpret_cast<uintptr_t>(mEmulatedWindowHandle), nullptr); + } + // Send a COM proxy for the top level document to the embedded document + // process. This will be returned when the client calls QueryService + // with SID_IAccessibleContentDocument on an accessible in the embedded + // document. + DocAccessibleParent* topDoc = this; + while (DocAccessibleParent* parentDoc = topDoc->ParentDoc()) { + topDoc = parentDoc; + } + MOZ_ASSERT(topDoc && topDoc->IsTopLevel()); + RefPtr<IAccessible> topDocAcc; + topDoc->GetCOMInterface((void**)getter_AddRefs(topDocAcc)); + MOZ_ASSERT(topDocAcc); + if (topDocAcc) { + RefPtr<IAccessible> topDocWrapped( + mscom::PassthruProxy::Wrap<IAccessible>(WrapNotNull(topDocAcc))); + IAccessibleHolder::COMPtrType topDocPtr( + mscom::ToProxyUniquePtr(std::move(topDocWrapped))); + IAccessibleHolder topDocHolder(std::move(topDocPtr)); + if (aChildDoc->SendTopLevelDocCOMProxy(topDocHolder)) { +# if defined(MOZ_SANDBOX) + aChildDoc->mTopLevelDocProxyStream = + topDocHolder.GetPreservedStream(); +# endif // defined(MOZ_SANDBOX) + } + } + } + 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); + 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 (DocAccessibleParent* parentDoc = thisDoc->ParentDoc()) { + parentDoc->RemoveChildDoc(thisDoc); + } else if (IsTopLevel()) { + GetAccService()->RemoteDocShutdown(this); + } +} + +void DocAccessibleParent::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_ASSERT(CheckDocTree()); + if (!mShutdown) { + ACQUIRE_ANDROID_LOCK + Destroy(); + } +} + +DocAccessibleParent* DocAccessibleParent::ParentDoc() const { + if (mParentDoc == kNoParentDoc) { + return nullptr; + } + + return LiveDocs().Get(mParentDoc); +} + +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 { + IDispatchHolder hWndAccHolder; + + ::SetPropW(aHwnd, kPropNameDocAccParent, + reinterpret_cast<HANDLE>(thisRef.get())); + + thisRef->SetEmulatedWindowHandle(aHwnd); + + RefPtr<IAccessible> hwndAcc; + if (SUCCEEDED(::AccessibleObjectFromWindow( + aHwnd, OBJID_WINDOW, IID_IAccessible, getter_AddRefs(hwndAcc)))) { + RefPtr<IDispatch> wrapped( + mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(hwndAcc))); + hWndAccHolder.Set(IDispatchHolder::COMPtrType( + mscom::ToProxyUniquePtr(std::move(wrapped)))); + } + + Unused << thisRef->SendEmulatedWindow( + reinterpret_cast<uintptr_t>(thisRef->mEmulatedWindowHandle), + hWndAccHolder); + }); + + 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::SendParentCOMProxy(Accessible* aOuterDoc) { + // Make sure that we're not racing with a tab shutdown + auto tab = static_cast<dom::BrowserParent*>(Manager()); + MOZ_ASSERT(tab); + if (tab->IsDestroyed()) { + return; + } + + RefPtr<IDispatch> nativeAcc = + already_AddRefed<IDispatch>(MsaaAccessible::NativeAccessible(aOuterDoc)); + if (NS_WARN_IF(!nativeAcc)) { + // Couldn't get a COM proxy for the outer doc. That probably means it died, + // but the parent process hasn't received a message to remove it from the + // RemoteAccessible tree yet. + return; + } + + RefPtr<IDispatch> wrapped( + mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(nativeAcc))); + + IDispatchHolder::COMPtrType ptr(mscom::ToProxyUniquePtr(std::move(wrapped))); + IDispatchHolder holder(std::move(ptr)); + if (!PDocAccessibleParent::SendParentCOMProxy(holder)) { + return; + } + +# if defined(MOZ_SANDBOX) + mParentProxyStream = holder.GetPreservedStream(); +# endif // defined(MOZ_SANDBOX) +} + +void DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle) { + if (!aWindowHandle && mEmulatedWindowHandle && IsTopLevel()) { + ::DestroyWindow(mEmulatedWindowHandle); + } + mEmulatedWindowHandle = aWindowHandle; +} + +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(); + } + + mFocus = aID; + ProxyFocusEvent(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(); +} + +#endif // defined(XP_WIN) + +#if !defined(XP_WIN) +mozilla::ipc::IPCResult DocAccessibleParent::RecvBatch( + const uint64_t& aBatchType, nsTArray<BatchData>&& aData) { + // Only do something in Android. We can't ifdef the entire protocol out in + // the ipdl because it doesn't allow preprocessing. +# if defined(ANDROID) + if (mShutdown) { + return IPC_OK(); + } + nsTArray<RemoteAccessible*> proxies(aData.Length()); + for (size_t i = 0; i < aData.Length(); i++) { + DocAccessibleParent* doc = static_cast<DocAccessibleParent*>( + aData.ElementAt(i).Document().get_PDocAccessibleParent()); + MOZ_ASSERT(doc); + + if (doc->IsShutdown()) { + continue; + } + + RemoteAccessible* proxy = doc->GetAccessible(aData.ElementAt(i).ID()); + if (!proxy) { + MOZ_ASSERT_UNREACHABLE("No proxy found!"); + continue; + } + + proxies.AppendElement(proxy); + } + ProxyBatch(this, aBatchType, proxies, aData); +# endif // defined(XP_WIN) + return IPC_OK(); +} + +bool DocAccessibleParent::DeallocPDocAccessiblePlatformExtParent( + PDocAccessiblePlatformExtParent* aActor) { + delete aActor; + return true; +} + +PDocAccessiblePlatformExtParent* +DocAccessibleParent::AllocPDocAccessiblePlatformExtParent() { + return new DocAccessiblePlatformExtParent(); +} + +DocAccessiblePlatformExtParent* DocAccessibleParent::GetPlatformExtension() { + return static_cast<DocAccessiblePlatformExtParent*>( + SingleManagedOrNull(ManagedPDocAccessiblePlatformExtParent())); +} + +#endif // !defined(XP_WIN) + +void DocAccessibleParent::SelectionRanges(nsTArray<TextRange>* aRanges) const { + 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); +} + +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 RemoteAccessibleBase<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 += RemoteAccessibleBase::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 diff --git a/accessible/ipc/DocAccessibleParent.h b/accessible/ipc/DocAccessibleParent.h new file mode 100644 index 0000000000..c1f07c7979 --- /dev/null +++ b/accessible/ipc/DocAccessibleParent.h @@ -0,0 +1,442 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_DocAccessibleParent_h +#define mozilla_a11y_DocAccessibleParent_h + +#include "nsAccessibilityService.h" +#include "mozilla/a11y/PDocAccessibleParent.h" +#include "mozilla/a11y/RemoteAccessible.h" +#include "mozilla/dom/BrowserBridgeParent.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace dom { +class CanonicalBrowsingContext; +} + +namespace a11y { + +class TextRange; +class xpcAccessibleGeneric; + +#if !defined(XP_WIN) +class DocAccessiblePlatformExtParent; +#endif + +/* + * These objects live in the main process and comunicate with and represent + * an accessible document in a content process. + */ +class DocAccessibleParent : public RemoteAccessible, + public PDocAccessibleParent, + public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + private: + DocAccessibleParent(); + + public: + static already_AddRefed<DocAccessibleParent> New(); + + /** + * Set this as a top level document; i.e. it is not embedded by another remote + * document. This also means it is a top level document in its content + * process. + * Tab documents are top level documents. + */ + void SetTopLevel() { + mTopLevel = true; + mTopLevelInContentProcess = true; + } + bool IsTopLevel() const { return mTopLevel; } + + /** + * Set this as a top level document in its content process. + * Note that this could be an out-of-process iframe embedded by a remote + * embedder document. In that case, IsToplevel() will return false, but + * IsTopLevelInContentProcess() will return true. + */ + void SetTopLevelInContentProcess() { mTopLevelInContentProcess = true; } + bool IsTopLevelInContentProcess() const { return mTopLevelInContentProcess; } + + /** + * Determine whether this is an out-of-process iframe document, embedded by a + * remote embedder document. + */ + bool IsOOPIframeDoc() const { + return !mTopLevel && mTopLevelInContentProcess; + } + + bool IsShutdown() const { return mShutdown; } + + /** + * Mark this actor as shutdown without doing any cleanup. This should only + * be called on actors that have just been initialized, so probably only from + * RecvPDocAccessibleConstructor. + */ + void MarkAsShutdown() { + MOZ_ASSERT(mChildDocs.IsEmpty()); + MOZ_ASSERT(mAccessibles.Count() == 0); + MOZ_ASSERT(!mBrowsingContext); + mShutdown = true; + } + + void SetBrowsingContext(dom::CanonicalBrowsingContext* aBrowsingContext); + + dom::CanonicalBrowsingContext* GetBrowsingContext() const { + return mBrowsingContext; + } + + /* + * Called when a message from a document in a child process notifies the main + * process it is firing an event. + */ + virtual mozilla::ipc::IPCResult RecvEvent(const uint64_t& aID, + const uint32_t& aType) override; + + virtual mozilla::ipc::IPCResult RecvShowEvent(const ShowEventData& aData, + const bool& aFromUser) override; + virtual mozilla::ipc::IPCResult RecvHideEvent(const uint64_t& aRootID, + const bool& aFromUser) override; + mozilla::ipc::IPCResult RecvStateChangeEvent(const uint64_t& aID, + const uint64_t& aState, + const bool& aEnabled) final; + + mozilla::ipc::IPCResult RecvCaretMoveEvent( + const uint64_t& aID, +#if defined(XP_WIN) + const LayoutDeviceIntRect& aCaretRect, +#endif + const int32_t& aOffset, const bool& aIsSelectionCollapsed, + const bool& aIsAtEndOfLine, const int32_t& aGranularity) final; + + virtual mozilla::ipc::IPCResult RecvTextChangeEvent( + const uint64_t& aID, const nsAString& aStr, const int32_t& aStart, + const uint32_t& aLen, const bool& aIsInsert, + const bool& aFromUser) override; + +#if defined(XP_WIN) + virtual mozilla::ipc::IPCResult RecvSyncTextChangeEvent( + const uint64_t& aID, const nsAString& aStr, const int32_t& aStart, + const uint32_t& aLen, const bool& aIsInsert, + const bool& aFromUser) override; + + virtual mozilla::ipc::IPCResult RecvFocusEvent( + const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect) override; +#endif // defined(XP_WIN) + + virtual mozilla::ipc::IPCResult RecvSelectionEvent( + const uint64_t& aID, const uint64_t& aWidgetID, + const uint32_t& aType) override; + + virtual mozilla::ipc::IPCResult RecvVirtualCursorChangeEvent( + const uint64_t& aID, const uint64_t& aOldPositionID, + const int32_t& aOldStartOffset, const int32_t& aOldEndOffset, + const uint64_t& aNewPositionID, const int32_t& aNewStartOffset, + const int32_t& aNewEndOffset, const int16_t& aReason, + const int16_t& aBoundaryType, const bool& aFromUser) override; + + virtual mozilla::ipc::IPCResult 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) override; + + virtual mozilla::ipc::IPCResult RecvCache( + const mozilla::a11y::CacheUpdateType& aUpdateType, + nsTArray<CacheData>&& aData, const bool& aDispatchShowEvent) override; + + virtual mozilla::ipc::IPCResult RecvSelectedAccessiblesChanged( + nsTArray<uint64_t>&& aSelectedIDs, + nsTArray<uint64_t>&& aUnselectedIDs) override; + + virtual mozilla::ipc::IPCResult RecvAccessiblesWillMove( + nsTArray<uint64_t>&& aIDs) override; + +#if !defined(XP_WIN) + virtual mozilla::ipc::IPCResult RecvAnnouncementEvent( + const uint64_t& aID, const nsAString& aAnnouncement, + const uint16_t& aPriority) override; +#endif + + virtual mozilla::ipc::IPCResult RecvTextSelectionChangeEvent( + const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) override; + + mozilla::ipc::IPCResult RecvRoleChangedEvent( + const a11y::role& aRole, const uint8_t& aRoleMapEntryIndex) final; + + virtual mozilla::ipc::IPCResult RecvBindChildDoc( + PDocAccessibleParent* aChildDoc, const uint64_t& aID) override; + + void Unbind() { + if (DocAccessibleParent* parent = ParentDoc()) { + parent->RemoveChildDoc(this); + } + + SetParent(nullptr); + } + + virtual mozilla::ipc::IPCResult RecvShutdown() override; + void Destroy(); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + /* + * Return the main processes representation of the parent document (if any) + * of the document this object represents. + */ + DocAccessibleParent* ParentDoc() const; + static const uint64_t kNoParentDoc = UINT64_MAX; + + /** + * Called when a document in a content process notifies the main process of a + * new child document. + * Although this is called internally for OOP child documents, these should be + * added via the BrowserBridgeParent version of this method, as the parent id + * might not exist yet in that case. + */ + ipc::IPCResult AddChildDoc(DocAccessibleParent* aChildDoc, uint64_t aParentID, + bool aCreating = true); + + /** + * Called when a document in a content process notifies the main process of a + * new OOP child document. + */ + ipc::IPCResult AddChildDoc(dom::BrowserBridgeParent* aBridge); + + void RemovePendingOOPChildDoc(dom::BrowserBridgeParent* aBridge) { + mPendingOOPChildDocs.Remove(aBridge); + } + + /* + * Called when the document in the content process this object represents + * notifies the main process a child document has been removed. + */ + void RemoveChildDoc(DocAccessibleParent* aChildDoc) { + RemoteAccessible* parent = aChildDoc->RemoteParent(); + MOZ_ASSERT(parent); + if (parent) { + aChildDoc->RemoteParent()->ClearChildDoc(aChildDoc); + } + DebugOnly<bool> result = mChildDocs.RemoveElement(aChildDoc->mActorID); + aChildDoc->mParentDoc = kNoParentDoc; + MOZ_ASSERT(result); + } + + void RemoveAccessible(RemoteAccessible* aAccessible) { + MOZ_DIAGNOSTIC_ASSERT(mAccessibles.GetEntry(aAccessible->ID())); + mAccessibles.RemoveEntry(aAccessible->ID()); + } + + /** + * Return the accessible for given id. + */ + RemoteAccessible* GetAccessible(uintptr_t aID) { + if (!aID) return this; + + ProxyEntry* e = mAccessibles.GetEntry(aID); + return e ? e->mProxy : nullptr; + } + + const RemoteAccessible* GetAccessible(uintptr_t aID) const { + return const_cast<DocAccessibleParent*>(this)->GetAccessible(aID); + } + + size_t ChildDocCount() const { return mChildDocs.Length(); } + const DocAccessibleParent* ChildDocAt(size_t aIdx) const { + return const_cast<DocAccessibleParent*>(this)->ChildDocAt(aIdx); + } + DocAccessibleParent* ChildDocAt(size_t aIdx) { + return LiveDocs().Get(mChildDocs[aIdx]); + } + +#if defined(XP_WIN) + void MaybeInitWindowEmulation(); + + /** + * Note that an OuterDocAccessible can be created before the + * DocAccessibleParent or vice versa. Therefore, this must be conditionally + * called when either of these is created. + * @param aOuterDoc The OuterDocAccessible to be returned as the parent of + * this document. + */ + void SendParentCOMProxy(Accessible* aOuterDoc); + + /** + * Set emulated native window handle for a document. + * @param aWindowHandle emulated native window handle + */ + void SetEmulatedWindowHandle(HWND aWindowHandle); + HWND GetEmulatedWindowHandle() const { return mEmulatedWindowHandle; } +#endif + +#if !defined(XP_WIN) + virtual mozilla::ipc::IPCResult RecvBatch( + const uint64_t& aBatchType, nsTArray<BatchData>&& aData) override; + + virtual bool DeallocPDocAccessiblePlatformExtParent( + PDocAccessiblePlatformExtParent* aActor) override; + + virtual PDocAccessiblePlatformExtParent* + AllocPDocAccessiblePlatformExtParent() override; + + DocAccessiblePlatformExtParent* GetPlatformExtension(); +#endif + + // Accessible + virtual Accessible* Parent() const override { + if (IsTopLevel()) { + return OuterDocOfRemoteBrowser(); + } + return RemoteParent(); + } + + virtual int32_t IndexInParent() const override { + if (IsTopLevel() && OuterDocOfRemoteBrowser()) { + // An OuterDoc can only have 1 child. + return 0; + } + return RemoteAccessible::IndexInParent(); + } + + /** + * Get the focused Accessible in this document, if any. + */ + RemoteAccessible* GetFocusedAcc() const { + return const_cast<DocAccessibleParent*>(this)->GetAccessible(mFocus); + } + + /** + * Get the HyperText Accessible containing the caret and the offset of the + * caret within. If there is no caret in this document, returns + * {nullptr, -1}. + */ + std::pair<RemoteAccessible*, int32_t> GetCaret() const { + if (mCaretOffset == -1) { + return {nullptr, -1}; + } + RemoteAccessible* acc = + const_cast<DocAccessibleParent*>(this)->GetAccessible(mCaretId); + if (!acc) { + return {nullptr, -1}; + } + return {acc, mCaretOffset}; + } + + bool IsCaretAtEndOfLine() const { return mIsCaretAtEndOfLine; } + + virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const override; + + virtual Accessible* FocusedChild() override; + + void URL(nsAString& aURL) const; + void URL(nsACString& aURL) const; + + virtual Relation RelationByType(RelationType aType) const override; + + // Tracks cached reverse relations (ie. those not set explicitly by an + // attribute like aria-labelledby) for accessibles in this doc. This map is of + // the form: {accID, {relationType, [targetAccID, targetAccID, ...]}} + nsTHashMap<uint64_t, nsTHashMap<RelationType, nsTArray<uint64_t>>> + mReverseRelations; + + // Computed from the viewport cache, the accs referenced by these ids + // are currently on screen (making any acc not in this list offscreen). + nsTHashSet<uint64_t> mOnScreenAccessibles; + + static DocAccessibleParent* GetFrom(dom::BrowsingContext* aBrowsingContext); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) override; + + private: + ~DocAccessibleParent(); + + class ProxyEntry : public PLDHashEntryHdr { + public: + explicit ProxyEntry(const void*) : mProxy(nullptr) {} + ProxyEntry(ProxyEntry&& aOther) : mProxy(aOther.mProxy) { + aOther.mProxy = nullptr; + } + ~ProxyEntry() { delete mProxy; } + + typedef uint64_t KeyType; + typedef const void* KeyTypePointer; + + bool KeyEquals(const void* aKey) const { + return mProxy->ID() == (uint64_t)aKey; + } + + static const void* KeyToPointer(uint64_t aKey) { return (void*)aKey; } + + static PLDHashNumber HashKey(const void* aKey) { return (uint64_t)aKey; } + + enum { ALLOW_MEMMOVE = true }; + + RemoteAccessible* mProxy; + }; + + uint32_t AddSubtree(RemoteAccessible* aParent, + const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx, + uint32_t aIdxInParent); + [[nodiscard]] bool CheckDocTree() const; + xpcAccessibleGeneric* GetXPCAccessible(RemoteAccessible* aProxy); + + void FireEvent(RemoteAccessible* aAcc, const uint32_t& aType); + + /** + * If this Accessible is being moved, prepare it for reuse. Otherwise, it is + * being removed, so shut it down. + */ + void ShutdownOrPrepareForMove(RemoteAccessible* aAcc); + + nsTArray<uint64_t> mChildDocs; + uint64_t mParentDoc; + +#if defined(XP_WIN) + // The handle associated with the emulated window that contains this document + HWND mEmulatedWindowHandle; + +# if defined(MOZ_SANDBOX) + mscom::PreservedStreamPtr mParentProxyStream; + mscom::PreservedStreamPtr mDocProxyStream; + mscom::PreservedStreamPtr mTopLevelDocProxyStream; +# endif // defined(MOZ_SANDBOX) +#endif // defined(XP_WIN) + + /* + * Conceptually this is a map from IDs to proxies, but we store the ID in the + * proxy object so we can't use a real map. + */ + nsTHashtable<ProxyEntry> mAccessibles; + nsTHashSet<uint64_t> mMovingIDs; + uint64_t mActorID; + bool mTopLevel; + bool mTopLevelInContentProcess; + bool mShutdown; + RefPtr<dom::CanonicalBrowsingContext> mBrowsingContext; + + nsTHashSet<RefPtr<dom::BrowserBridgeParent>> mPendingOOPChildDocs; + + uint64_t mFocus; + uint64_t mCaretId; + int32_t mCaretOffset; + bool mIsCaretAtEndOfLine; + nsTArray<TextRangeData> mTextSelections; + + static uint64_t sMaxDocID; + static nsTHashMap<nsUint64HashKey, DocAccessibleParent*>& LiveDocs() { + static nsTHashMap<nsUint64HashKey, DocAccessibleParent*> sLiveDocs; + return sLiveDocs; + } +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/DocAccessibleTypes.ipdlh b/accessible/ipc/DocAccessibleTypes.ipdlh new file mode 100644 index 0000000000..df2ca043ea --- /dev/null +++ b/accessible/ipc/DocAccessibleTypes.ipdlh @@ -0,0 +1,19 @@ +/* -*- 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/. */ + +[RefCounted] using mozilla::a11y::AccAttributes from "mozilla/a11y/IPCTypes.h"; + +namespace mozilla { +namespace a11y { + +struct CacheData +{ + uint64_t ID; + AccAttributes Fields; +}; + +} +} diff --git a/accessible/ipc/IPCTypes.h b/accessible/ipc/IPCTypes.h new file mode 100644 index 0000000000..c044523551 --- /dev/null +++ b/accessible/ipc/IPCTypes.h @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#ifndef mozilla_a11y_IPCTypes_h +#define mozilla_a11y_IPCTypes_h + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/AccAttributes.h" +# include "mozilla/a11y/AccTypes.h" +# include "mozilla/a11y/CacheConstants.h" +# include "mozilla/a11y/Role.h" +# include "mozilla/a11y/AccGroupInfo.h" +# include "mozilla/GfxMessageUtils.h" +# include "ipc/EnumSerializer.h" +# include "ipc/IPCMessageUtilsSpecializations.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::a11y::role> + : public ContiguousEnumSerializerInclusive<mozilla::a11y::role, + mozilla::a11y::role::NOTHING, + mozilla::a11y::role::LAST_ROLE> { +}; + +template <> +struct ParamTraits<mozilla::a11y::AccType> + : public ContiguousEnumSerializerInclusive< + mozilla::a11y::AccType, mozilla::a11y::AccType::eNoType, + mozilla::a11y::AccType::eLastAccType> {}; + +template <> +struct ParamTraits<mozilla::a11y::AccGenericType> + : public BitFlagsEnumSerializer< + mozilla::a11y::AccGenericType, + mozilla::a11y::AccGenericType::eAllGenericTypes> {}; + +template <> +struct ParamTraits<mozilla::a11y::CacheUpdateType> + : public ContiguousEnumSerializerInclusive< + mozilla::a11y::CacheUpdateType, + mozilla::a11y::CacheUpdateType::Initial, + mozilla::a11y::CacheUpdateType::Update> {}; + +template <> +struct ParamTraits<mozilla::a11y::FontSize> { + typedef mozilla::a11y::FontSize paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mValue); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &(aResult->mValue)); + } +}; + +template <> +struct ParamTraits<mozilla::a11y::DeleteEntry> { + typedef mozilla::a11y::DeleteEntry paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mValue); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &(aResult->mValue)); + } +}; + +template <> +struct ParamTraits<mozilla::a11y::AccGroupInfo> { + typedef mozilla::a11y::AccGroupInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + MOZ_ASSERT_UNREACHABLE("Cannot serialize AccGroupInfo"); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + MOZ_ASSERT_UNREACHABLE("Cannot de-serialize AccGroupInfo"); + return false; + } +}; + +template <> +struct ParamTraits<mozilla::a11y::Color> { + typedef mozilla::a11y::Color paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mValue); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &(aResult->mValue)); + } +}; + +template <> +struct ParamTraits<mozilla::a11y::AccAttributes*> { + typedef mozilla::a11y::AccAttributes paramType; + + static void Write(MessageWriter* aWriter, const paramType* aParam) { + if (!aParam) { + WriteParam(aWriter, true); + return; + } + + WriteParam(aWriter, false); + uint32_t count = aParam->mData.Count(); + WriteParam(aWriter, count); + for (auto iter = aParam->mData.ConstIter(); !iter.Done(); iter.Next()) { + RefPtr<nsAtom> key = iter.Key(); + WriteParam(aWriter, key); + const paramType::AttrValueType& data = iter.Data(); + WriteParam(aWriter, data); + } + } + + static bool Read(MessageReader* aReader, RefPtr<paramType>* aResult) { + bool isNull = false; + if (!ReadParam(aReader, &isNull)) { + return false; + } + + if (isNull) { + *aResult = nullptr; + return true; + } + + *aResult = mozilla::MakeRefPtr<mozilla::a11y::AccAttributes>(); + uint32_t count; + if (!ReadParam(aReader, &count)) { + return false; + } + for (uint32_t i = 0; i < count; ++i) { + RefPtr<nsAtom> key; + if (!ReadParam(aReader, &key)) { + return false; + } + paramType::AttrValueType val(0); + if (!ReadParam(aReader, &val)) { + return false; + } + (*aResult)->mData.InsertOrUpdate(key, std::move(val)); + } + return true; + } +}; + +} // namespace IPC +#else +namespace mozilla { +namespace a11y { +typedef uint32_t role; +} // namespace a11y +} // namespace mozilla +#endif // ACCESSIBILITY + +/** + * Since IPDL does not support preprocessing, this header file allows us to + * define types used by PDocAccessible differently depending on platform. + */ + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + +// So that we don't include a bunch of other Windows junk. +# if !defined(COM_NO_WINDOWS_H) +# define COM_NO_WINDOWS_H +# endif // !defined(COM_NO_WINDOWS_H) + +// COM headers pull in MSXML which conflicts with our own XMLDocument class. +// This define excludes those conflicting definitions. +# if !defined(__XMLDocument_FWD_DEFINED__) +# define __XMLDocument_FWD_DEFINED__ +# endif // !defined(__XMLDocument_FWD_DEFINED__) + +# include <combaseapi.h> + +# include "mozilla/a11y/COMPtrTypes.h" + +// This define in rpcndr.h messes up our code, so we must undefine it after +// COMPtrTypes.h has been included. +# if defined(small) +# undef small +# endif // defined(small) + +#else + +namespace mozilla { +namespace a11y { + +typedef uint32_t IAccessibleHolder; +typedef uint32_t IDispatchHolder; +typedef uint32_t IHandlerControlHolder; + +} // namespace a11y +} // namespace mozilla + +#endif // defined(XP_WIN) && defined(ACCESSIBILITY) + +#if defined(MOZ_WIDGET_COCOA) +# if defined(ACCESSIBILITY) +# include "mozilla/a11y/PlatformExtTypes.h" +namespace IPC { + +template <> +struct ParamTraits<mozilla::a11y::EWhichRange> + : public ContiguousEnumSerializerInclusive< + mozilla::a11y::EWhichRange, mozilla::a11y::EWhichRange::eLeftWord, + mozilla::a11y::EWhichRange::eStyle> {}; + +template <> +struct ParamTraits<mozilla::a11y::EWhichPostFilter> + : public ContiguousEnumSerializerInclusive< + mozilla::a11y::EWhichPostFilter, + mozilla::a11y::EWhichPostFilter::eContainsText, + mozilla::a11y::EWhichPostFilter::eContainsText> {}; + +} // namespace IPC + +# else +namespace mozilla { +namespace a11y { +typedef uint32_t EWhichRange; +} // namespace a11y +} // namespace mozilla +# endif // defined(ACCESSIBILITY) +#endif // defined(MOZ_WIDGET_COCOA) + +#endif // mozilla_a11y_IPCTypes_h diff --git a/accessible/ipc/RemoteAccessibleBase.cpp b/accessible/ipc/RemoteAccessibleBase.cpp new file mode 100644 index 0000000000..b8ea930aa6 --- /dev/null +++ b/accessible/ipc/RemoteAccessibleBase.cpp @@ -0,0 +1,1775 @@ +/* -*- 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 "DocAccessible.h" +#include "mozilla/a11y/DocAccessibleParent.h" +#include "mozilla/a11y/DocManager.h" +#include "mozilla/a11y/Platform.h" +#include "mozilla/a11y/RemoteAccessibleBase.h" +#include "mozilla/a11y/RemoteAccessible.h" +#include "mozilla/a11y/Role.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/DocumentInlines.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/StaticPrefs_accessibility.h" +#include "mozilla/Unused.h" +#include "nsAccUtils.h" +#include "nsTextEquivUtils.h" +#include "Pivot.h" +#include "Relation.h" +#include "RelationType.h" +#include "xpcAccessibleDocument.h" + +#ifdef A11Y_LOG +# include "Logging.h" +# define VERIFY_CACHE(domain) \ + if (logging::IsEnabled(logging::eCache)) { \ + Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \ + } +#else +# define VERIFY_CACHE(domain) \ + do { \ + } while (0) + +#endif + +namespace mozilla { +namespace a11y { + +template <class Derived> +void RemoteAccessibleBase<Derived>::Shutdown() { + MOZ_DIAGNOSTIC_ASSERT(!IsDoc()); + xpcAccessibleDocument* xpcDoc = + GetAccService()->GetCachedXPCDocument(Document()); + if (xpcDoc) { + xpcDoc->NotifyOfShutdown(static_cast<Derived*>(this)); + } + + if (IsTable() || IsTableCell()) { + CachedTableAccessible::Invalidate(this); + } + + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Remove this acc's relation map from the doc's map of + // reverse relations. Prune forward relations associated with this + // acc's reverse relations. This also removes the acc's map of reverse + // rels from the mDoc's mReverseRelations. + PruneRelationsOnShutdown(); + } + + // XXX Ideally this wouldn't be necessary, but it seems OuterDoc + // accessibles can be destroyed before the doc they own. + uint32_t childCount = mChildren.Length(); + if (!IsOuterDoc()) { + for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown(); + } else { + if (childCount > 1) { + MOZ_CRASH("outer doc has too many documents!"); + } else if (childCount == 1) { + mChildren[0]->AsDoc()->Unbind(); + } + } + + mChildren.Clear(); + ProxyDestroyed(static_cast<Derived*>(this)); + mDoc->RemoveAccessible(static_cast<Derived*>(this)); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::SetChildDoc( + DocAccessibleParent* aChildDoc) { + MOZ_ASSERT(aChildDoc); + MOZ_ASSERT(mChildren.Length() == 0); + mChildren.AppendElement(aChildDoc); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::ClearChildDoc( + DocAccessibleParent* aChildDoc) { + MOZ_ASSERT(aChildDoc); + // This is possible if we're replacing one document with another: Doc 1 + // has not had a chance to remove itself, but was already replaced by Doc 2 + // in SetChildDoc(). This could result in two subsequent calls to + // ClearChildDoc() even though mChildren.Length() == 1. + MOZ_ASSERT(mChildren.Length() <= 1); + mChildren.RemoveElement(aChildDoc); +} + +template <class Derived> +uint32_t RemoteAccessibleBase<Derived>::EmbeddedChildCount() { + size_t count = 0, kids = mChildren.Length(); + for (size_t i = 0; i < kids; i++) { + if (mChildren[i]->IsEmbeddedObject()) { + count++; + } + } + + return count; +} + +template <class Derived> +int32_t RemoteAccessibleBase<Derived>::IndexOfEmbeddedChild( + Accessible* aChild) { + size_t index = 0, kids = mChildren.Length(); + for (size_t i = 0; i < kids; i++) { + if (mChildren[i]->IsEmbeddedObject()) { + if (mChildren[i] == aChild) { + return index; + } + + index++; + } + } + + return -1; +} + +template <class Derived> +Accessible* RemoteAccessibleBase<Derived>::EmbeddedChildAt(uint32_t aChildIdx) { + size_t index = 0, kids = mChildren.Length(); + for (size_t i = 0; i < kids; i++) { + if (!mChildren[i]->IsEmbeddedObject()) { + continue; + } + + if (index == aChildIdx) { + return mChildren[i]; + } + + index++; + } + + return nullptr; +} + +template <class Derived> +LocalAccessible* RemoteAccessibleBase<Derived>::OuterDocOfRemoteBrowser() + const { + auto tab = static_cast<dom::BrowserParent*>(mDoc->Manager()); + dom::Element* frame = tab->GetOwnerElement(); + NS_ASSERTION(frame, "why isn't the tab in a frame!"); + if (!frame) return nullptr; + + DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc()); + + return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::SetParent(Derived* aParent) { + if (!aParent) { + mParent = kNoParent; + } else { + MOZ_ASSERT(!IsDoc() || !aParent->IsDoc()); + mParent = aParent->ID(); + } +} + +template <class Derived> +Derived* RemoteAccessibleBase<Derived>::RemoteParent() const { + if (mParent == kNoParent) { + return nullptr; + } + + // if we are not a document then are parent is another proxy in the same + // document. That means we can just ask our document for the proxy with our + // parent id. + if (!IsDoc()) { + return Document()->GetAccessible(mParent); + } + + // If we are a top level document then our parent is not a proxy. + if (AsDoc()->IsTopLevel()) { + return nullptr; + } + + // Finally if we are a non top level document then our parent id is for a + // proxy in our parent document so get the proxy from there. + DocAccessibleParent* parentDoc = AsDoc()->ParentDoc(); + MOZ_ASSERT(parentDoc); + MOZ_ASSERT(mParent); + return parentDoc->GetAccessible(mParent); +} + +template <class Derived> +ENameValueFlag RemoteAccessibleBase<Derived>::Name(nsString& aName) const { + ENameValueFlag nameFlag = eNameOK; + if (mCachedFields) { + if (IsText()) { + mCachedFields->GetAttribute(nsGkAtoms::text, aName); + return eNameOK; + } + auto cachedNameFlag = + mCachedFields->GetAttribute<int32_t>(nsGkAtoms::explicit_name); + if (cachedNameFlag) { + nameFlag = static_cast<ENameValueFlag>(*cachedNameFlag); + } + if (mCachedFields->GetAttribute(nsGkAtoms::name, aName)) { + VERIFY_CACHE(CacheDomain::NameAndDescription); + return nameFlag; + } + } + + MOZ_ASSERT(aName.IsEmpty()); + aName.SetIsVoid(true); + return nameFlag; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::Description(nsString& aDescription) const { + if (mCachedFields) { + mCachedFields->GetAttribute(nsGkAtoms::description, aDescription); + VERIFY_CACHE(CacheDomain::NameAndDescription); + } +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::Value(nsString& aValue) const { + if (mCachedFields) { + if (mCachedFields->HasAttribute(nsGkAtoms::aria_valuetext)) { + mCachedFields->GetAttribute(nsGkAtoms::aria_valuetext, aValue); + VERIFY_CACHE(CacheDomain::Value); + return; + } + + if (HasNumericValue()) { + double checkValue = CurValue(); + if (!IsNaN(checkValue)) { + aValue.AppendFloat(checkValue); + } + return; + } + + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + // Value of textbox is a textified subtree. + if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) { + nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); + return; + } + + if (IsCombobox()) { + // For combo boxes, rely on selection state to determine the value. + const Accessible* option = + const_cast<RemoteAccessibleBase<Derived>*>(this)->GetSelectedItem(0); + if (option) { + option->Name(aValue); + } else { + // If no selected item, determine the value from descendant elements. + nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); + } + return; + } + + if (IsTextLeaf() || IsImage()) { + if (const Accessible* actionAcc = ActionAncestor()) { + if (const_cast<Accessible*>(actionAcc)->State() & states::LINKED) { + // Text and image descendants of links expose the link URL as the + // value. + return actionAcc->Value(aValue); + } + } + } + } +} + +template <class Derived> +double RemoteAccessibleBase<Derived>::CurValue() const { + if (mCachedFields) { + if (auto value = mCachedFields->GetAttribute<double>(nsGkAtoms::value)) { + VERIFY_CACHE(CacheDomain::Value); + return *value; + } + } + + return UnspecifiedNaN<double>(); +} + +template <class Derived> +double RemoteAccessibleBase<Derived>::MinValue() const { + if (mCachedFields) { + if (auto min = mCachedFields->GetAttribute<double>(nsGkAtoms::min)) { + VERIFY_CACHE(CacheDomain::Value); + return *min; + } + } + + return UnspecifiedNaN<double>(); +} + +template <class Derived> +double RemoteAccessibleBase<Derived>::MaxValue() const { + if (mCachedFields) { + if (auto max = mCachedFields->GetAttribute<double>(nsGkAtoms::max)) { + VERIFY_CACHE(CacheDomain::Value); + return *max; + } + } + + return UnspecifiedNaN<double>(); +} + +template <class Derived> +double RemoteAccessibleBase<Derived>::Step() const { + if (mCachedFields) { + if (auto step = mCachedFields->GetAttribute<double>(nsGkAtoms::step)) { + VERIFY_CACHE(CacheDomain::Value); + return *step; + } + } + + return UnspecifiedNaN<double>(); +} + +template <class Derived> +Accessible* RemoteAccessibleBase<Derived>::ChildAtPoint( + int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) { + if (IsOuterDoc() && aWhichChild == EWhichChildAtPoint::DirectChild) { + // This is an iframe, which is as deep as the viewport cache goes. The + // caller wants a direct child, which can only be the embedded document. + if (Bounds().Contains(aX, aY)) { + return RemoteFirstChild(); + } + return nullptr; + } + + RemoteAccessible* lastMatch = nullptr; + // If `this` is a document, use its viewport cache instead of + // the cache of its parent document. + if (DocAccessibleParent* doc = IsDoc() ? AsDoc() : mDoc) { + if (!doc->mCachedFields) { + // A client call might arrive after we've constructed doc but before we + // get a cache push for it. + return nullptr; + } + if (auto maybeViewportCache = + doc->mCachedFields->GetAttribute<nsTArray<uint64_t>>( + nsGkAtoms::viewport)) { + // The retrieved viewport cache contains acc IDs in hittesting order. + // That is, items earlier in the list have z-indexes that are larger than + // those later in the list. If you were to build a tree by z-index, where + // chilren have larger z indices than their parents, iterating this list + // is essentially a postorder tree traversal. + const nsTArray<uint64_t>& viewportCache = *maybeViewportCache; + + for (auto id : viewportCache) { + RemoteAccessible* acc = doc->GetAccessible(id); + if (!acc) { + // This can happen if the acc died in between + // pushing the viewport cache and doing this hittest + continue; + } + + if (acc->IsOuterDoc() && + aWhichChild == EWhichChildAtPoint::DeepestChild && + acc->Bounds().Contains(aX, aY)) { + // acc is an iframe, which is as deep as the viewport cache goes. This + // iframe contains the requested point. + RemoteAccessible* innerDoc = acc->RemoteFirstChild(); + if (innerDoc) { + MOZ_ASSERT(innerDoc->IsDoc()); + // Search the embedded document's viewport cache so we return the + // deepest descendant in that embedded document. + Accessible* deepestAcc = innerDoc->ChildAtPoint( + aX, aY, EWhichChildAtPoint::DeepestChild); + MOZ_ASSERT(!deepestAcc || deepestAcc->IsRemote()); + lastMatch = deepestAcc ? deepestAcc->AsRemote() : nullptr; + break; + } + // If there is no embedded document, the iframe itself is the deepest + // descendant. + lastMatch = acc; + break; + } + + if (acc == this) { + MOZ_ASSERT(!acc->IsOuterDoc()); + // Even though we're searching from the doc's cache + // this call shouldn't pass the boundary defined by + // the acc this call originated on. If we hit `this`, + // return our most recent match. + break; + } + + if (acc->Bounds().Contains(aX, aY)) { + // Because our rects are in hittesting order, the + // first match we encounter is guaranteed to be the + // deepest match. + lastMatch = acc; + break; + } + } + } + } + + if (aWhichChild == EWhichChildAtPoint::DirectChild && lastMatch) { + // lastMatch is the deepest match. Walk up to the direct child of this. + RemoteAccessible* parent = lastMatch->RemoteParent(); + for (;;) { + if (parent == this) { + break; + } + if (!parent || parent->IsDoc()) { + // `this` is not an ancestor of lastMatch. Ignore lastMatch. + lastMatch = nullptr; + break; + } + lastMatch = parent; + parent = parent->RemoteParent(); + } + } else if (aWhichChild == EWhichChildAtPoint::DeepestChild && lastMatch && + !IsDoc() && !IsAncestorOf(lastMatch)) { + // If we end up with a match that is not in the ancestor chain + // of the accessible this call originated on, we should ignore it. + // This can happen when the aX, aY given are outside `this`. + lastMatch = nullptr; + } + + if (!lastMatch && Bounds().Contains(aX, aY)) { + // Even though the hit target isn't inside `this`, the point is still + // within our bounds, so fall back to `this`. + return this; + } + + return lastMatch; +} + +template <class Derived> +Maybe<nsRect> RemoteAccessibleBase<Derived>::RetrieveCachedBounds() const { + if (!mCachedFields) { + return Nothing(); + } + + 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> +void RemoteAccessibleBase<Derived>::ApplyCrossDocOffset(nsRect& aBounds) const { + Accessible* parentAcc = Parent(); + if (!parentAcc || !parentAcc->IsRemote() || !parentAcc->IsOuterDoc()) { + return; + } + + if (!IsDoc()) { + // We should only apply cross-doc offsets to documents. If we're anything + // else, return early here. + return; + } + + Maybe<const nsTArray<int32_t>&> maybeOffset = + parentAcc->AsRemote()->mCachedFields->GetAttribute<nsTArray<int32_t>>( + nsGkAtoms::crossorigin); + if (!maybeOffset) { + return; + } + + MOZ_ASSERT(maybeOffset->Length() == 2); + const nsTArray<int32_t>& offset = *maybeOffset; + // Our retrieved value is in app units, so we don't need to do any + // unit conversion here. + aBounds.MoveBy(offset[0], offset[1]); +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::ApplyTransform( + nsRect& aCumulativeBounds, const nsRect& aParentRelativeBounds) const { + // First, attempt to retrieve the transform from the cache. + Maybe<const UniquePtr<gfx::Matrix4x4>&> maybeTransform = + mCachedFields->GetAttribute<UniquePtr<gfx::Matrix4x4>>( + nsGkAtoms::transform); + if (!maybeTransform) { + return false; + } + + // The transform matrix we cache is meant to operate on rects + // within the coordinate space of the frame to which the + // transform is applied (self-relative rects). We cache bounds + // relative to some ancestor. Remove the relative offset before + // transforming. The transform matrix will add it back in. + aCumulativeBounds.MoveBy(-aParentRelativeBounds.TopLeft()); + + auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix( + *(*maybeTransform)); + + // Our matrix is in CSS Pixels, so we need our rect to be in CSS + // Pixels too. Convert before applying. + auto boundsInPixels = CSSRect::FromAppUnits(aCumulativeBounds); + boundsInPixels = mtxInPixels.TransformBounds(boundsInPixels); + aCumulativeBounds = CSSRect::ToAppUnits(boundsInPixels); + + return true; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::ApplyScrollOffset(nsRect& aBounds) const { + Maybe<const nsTArray<int32_t>&> maybeScrollPosition = + mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::scrollPosition); + + if (!maybeScrollPosition || maybeScrollPosition->Length() != 2) { + return; + } + // Our retrieved value is in app units, so we don't need to do any + // unit conversion here. + const nsTArray<int32_t>& scrollPosition = *maybeScrollPosition; + + // Scroll position is an inverse representation of scroll offset (since the + // further the scroll bar moves down the page, the further the page content + // moves up/closer to the origin). + nsPoint scrollOffset(-scrollPosition[0], -scrollPosition[1]); + + aBounds.MoveBy(scrollOffset.x, scrollOffset.y); +} + +template <class Derived> +nsRect RemoteAccessibleBase<Derived>::BoundsInAppUnits() const { + if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()->Top()) { + if (dom::BrowserParent* bp = cbc->GetBrowserParent()) { + DocAccessibleParent* topDoc = bp->GetTopLevelDocAccessible(); + if (topDoc && topDoc->mCachedFields) { + auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>( + nsGkAtoms::_moz_device_pixel_ratio); + MOZ_ASSERT(appUnitsPerDevPixel); + return LayoutDeviceIntRect::ToAppUnits(Bounds(), *appUnitsPerDevPixel); + } + } + } + return LayoutDeviceIntRect::ToAppUnits(Bounds(), AppUnitsPerCSSPixel()); +} + +template <class Derived> +LayoutDeviceIntRect RemoteAccessibleBase<Derived>::BoundsWithOffset( + Maybe<nsRect> aOffset) const { + Maybe<nsRect> maybeBounds = RetrieveCachedBounds(); + if (maybeBounds) { + nsRect bounds = *maybeBounds; + const DocAccessibleParent* topDoc = IsDoc() ? AsDoc() : nullptr; + + if (aOffset.isSome()) { + // The rect we've passed in is in app units, so no conversion needed. + nsRect internalRect = *aOffset; + bounds.SetRectX(bounds.x + internalRect.x, internalRect.width); + bounds.SetRectY(bounds.y + internalRect.y, internalRect.height); + } + + ApplyCrossDocOffset(bounds); + + Unused << ApplyTransform(bounds, *maybeBounds); + + LayoutDeviceIntRect devPxBounds; + const Accessible* acc = Parent(); + while (acc && acc->IsRemote()) { + RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote(); + + if (Maybe<nsRect> maybeRemoteBounds = remoteAcc->RetrieveCachedBounds()) { + nsRect remoteBounds = *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. + 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). + Maybe<float> res = + remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>( + nsGkAtoms::resolution); + MOZ_ASSERT(res, "No cached document resolution found."); + bounds.ScaleRoundOut(res.valueOr(1.0f)); + + topDoc = remoteAcc->AsDoc(); + } + + // We don't account for the document offset of iframes when computing + // parent-relative bounds. Instead, we store this value separately on + // all iframes and apply it here. See the comments in + // LocalAccessible::BundleFieldsForCache where we set the + // nsGkAtoms::crossorigin attribute. + remoteAcc->ApplyCrossDocOffset(remoteBounds); + + // Apply scroll offset, if applicable. Only the contents of an + // element are affected by its scroll offset, which is why this call + // happens in this loop instead of both inside and outside of + // the loop (like ApplyTransform). + remoteAcc->ApplyScrollOffset(remoteBounds); + + // 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. + bounds.MoveBy(remoteBounds.X(), remoteBounds.Y()); + Unused << remoteAcc->ApplyTransform(bounds, remoteBounds); + } + acc = acc->Parent(); + } + + MOZ_ASSERT(topDoc); + if (topDoc) { + // We use the top documents app-units-per-dev-pixel even though + // theoretically nested docs can have different values. Practically, + // that isn't likely since we only offer zoom controls for the top + // document and all subdocuments inherit from it. + auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>( + nsGkAtoms::_moz_device_pixel_ratio); + MOZ_ASSERT(appUnitsPerDevPixel); + if (appUnitsPerDevPixel) { + // Convert our existing `bounds` rect from app units to dev pixels + devPxBounds = LayoutDeviceIntRect::FromAppUnitsToNearest( + bounds, *appUnitsPerDevPixel); + } + } + +#if !defined(ANDROID) + // This block is not thread safe because it queries a LocalAccessible. + // It is also not needed in Android since the only local accessible is + // the outer doc browser that has an offset of 0. + // acc could be null if the OuterDocAccessible died before the top level + // DocAccessibleParent. + if (LocalAccessible* localAcc = + acc ? const_cast<Accessible*>(acc)->AsLocal() : nullptr) { + // LocalAccessible::Bounds returns screen-relative bounds in + // dev pixels. + LayoutDeviceIntRect localBounds = localAcc->Bounds(); + + // 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()); + } +#endif + + return devPxBounds; + } + + return LayoutDeviceIntRect(); +} + +template <class Derived> +LayoutDeviceIntRect RemoteAccessibleBase<Derived>::Bounds() const { + return BoundsWithOffset(Nothing()); +} + +template <class Derived> +Relation RemoteAccessibleBase<Derived>::RelationByType( + RelationType aType) const { + // We are able to handle some relations completely in the + // parent process, without the help of the cache. Those + // relations are enumerated here. Other relations, whose + // types are stored in kRelationTypeAtoms, are processed + // below using the cache. + if (aType == RelationType::CONTAINING_TAB_PANE) { + if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()) { + if (dom::CanonicalBrowsingContext* topCbc = cbc->Top()) { + if (dom::BrowserParent* bp = topCbc->GetBrowserParent()) { + return Relation(bp->GetTopLevelDocAccessible()); + } + } + } + return Relation(); + } + + if (aType == RelationType::LINKS_TO && Role() == roles::LINK) { + Pivot p = Pivot(mDoc); + nsString href; + Value(href); + int32_t i = href.FindChar('#'); + int32_t len = static_cast<int32_t>(href.Length()); + if (i != -1 && i < (len - 1)) { + nsDependentSubstring anchorName = Substring(href, i + 1, len); + MustPruneSameDocRule rule; + Accessible* nameMatch = nullptr; + for (Accessible* match = p.Next(mDoc, rule); match; + match = p.Next(match, rule)) { + nsString currID; + match->DOMNodeID(currID); + MOZ_ASSERT(match->IsRemote()); + if (anchorName.Equals(currID)) { + return Relation(match->AsRemote()); + } + if (!nameMatch) { + nsString currName = match->AsRemote()->GetCachedHTMLNameAttribute(); + if (match->TagName() == nsGkAtoms::a && anchorName.Equals(currName)) { + // If we find an element with a matching ID, we should return + // that, but if we don't we should return the first anchor with + // a matching name. To avoid doing two traversals, store the first + // name match here. + nameMatch = match; + } + } + } + return nameMatch ? Relation(nameMatch->AsRemote()) : Relation(); + } + + return Relation(); + } + + // Handle ARIA tree, treegrid parent/child relations. Each of these cases + // relies on cached group info. To find the parent of an accessible, use the + // unified conceptual parent. + if (aType == RelationType::NODE_CHILD_OF) { + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM || + roleMapEntry->role == roles::LISTITEM || + roleMapEntry->role == roles::ROW)) { + if (const AccGroupInfo* groupInfo = + const_cast<RemoteAccessibleBase<Derived>*>(this) + ->GetOrCreateGroupInfo()) { + return Relation(groupInfo->ConceptualParent()); + } + } + return Relation(); + } + + // To find the children of a parent, provide an iterator through its items. + if (aType == RelationType::NODE_PARENT_OF) { + const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); + if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM || + roleMapEntry->role == roles::LISTITEM || + roleMapEntry->role == roles::ROW || + roleMapEntry->role == roles::OUTLINE || + roleMapEntry->role == roles::LIST || + roleMapEntry->role == roles::TREE_TABLE)) { + return Relation(new ItemIterator(this)); + } + return Relation(); + } + + if (aType == RelationType::MEMBER_OF) { + Relation rel = Relation(); + // HTML radio buttons with cached names should be grouped. + if (IsHTMLRadioButton()) { + nsString name = GetCachedHTMLNameAttribute(); + if (name.IsEmpty()) { + return rel; + } + + RemoteAccessible* ancestor = RemoteParent(); + while (ancestor && ancestor->Role() != roles::FORM && ancestor != mDoc) { + ancestor = ancestor->RemoteParent(); + } + if (ancestor) { + // Sometimes we end up with an unparented acc here, potentially + // because the acc is being moved. See bug 1807639. + // Pivot expects to be created with a non-null mRoot. + Pivot p = Pivot(ancestor); + PivotRadioNameRule rule(name); + Accessible* match = p.Next(ancestor, rule); + while (match) { + rel.AppendTarget(match->AsRemote()); + match = p.Next(match, rule); + } + } + return rel; + } + + if (IsARIARole(nsGkAtoms::radio)) { + // ARIA radio buttons should be grouped by their radio group + // parent, if one exists. + RemoteAccessible* currParent = RemoteParent(); + while (currParent && currParent->Role() != roles::RADIO_GROUP) { + currParent = currParent->RemoteParent(); + } + + if (currParent && currParent->Role() == roles::RADIO_GROUP) { + // If we found a radiogroup parent, search for all + // roles::RADIOBUTTON children and add them to our relation. + // This search will include the radio button this method + // was called from, which is expected. + Pivot p = Pivot(currParent); + PivotRoleRule rule(roles::RADIOBUTTON); + Accessible* match = p.Next(currParent, rule); + while (match) { + MOZ_ASSERT(match->IsRemote(), + "We should only be traversing the remote tree."); + rel.AppendTarget(match->AsRemote()); + match = p.Next(match, rule); + } + } + } + // By webkit's standard, aria radio buttons do not get grouped + // if they lack a group parent, so we return an empty + // relation here if the above check fails. + return rel; + } + + Relation rel; + if (!mCachedFields) { + return rel; + } + + for (const auto& data : kRelationTypeAtoms) { + if (data.mType != aType || + (data.mValidTag && TagName() != data.mValidTag)) { + continue; + } + + if (auto maybeIds = + mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom)) { + rel.AppendIter(new RemoteAccIterator(*maybeIds, Document())); + } + // Each relation type has only one relevant cached attribute, + // so break after we've handled the attr for this type, + // even if we didn't find any targets. + break; + } + + if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) { + if (auto reverseIdsEntry = accRelMapEntry.Data().Lookup(aType)) { + rel.AppendIter(new RemoteAccIterator(reverseIdsEntry.Data(), Document())); + } + } + + return rel; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::AppendTextTo(nsAString& aText, + uint32_t aStartOffset, + uint32_t aLength) { + if (IsText()) { + if (mCachedFields) { + if (auto text = mCachedFields->GetAttribute<nsString>(nsGkAtoms::text)) { + aText.Append(Substring(*text, aStartOffset, aLength)); + } + VERIFY_CACHE(CacheDomain::Text); + } + return; + } + + if (aStartOffset != 0 || aLength == 0) { + return; + } + + if (IsHTMLBr()) { + aText += kForcedNewLineChar; + } else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) { + // Expose the embedded object accessible as imaginary embedded object + // character if its parent hypertext accessible doesn't expose children to + // AT. + aText += kImaginaryEmbeddedObjectChar; + } else { + aText += kEmbeddedObjectChar; + } +} + +template <class Derived> +nsTArray<bool> RemoteAccessibleBase<Derived>::PreProcessRelations( + AccAttributes* aFields) { + nsTArray<bool> updateTracker(ArrayLength(kRelationTypeAtoms)); + for (auto const& data : kRelationTypeAtoms) { + if (data.mValidTag) { + // The relation we're currently processing only applies to particular + // elements. Check to see if we're one of them. + nsAtom* tag = TagName(); + if (!tag) { + // TagName() returns null on an initial cache push -- check aFields + // for a tag name instead. + if (auto maybeTag = + aFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::tag)) { + tag = *maybeTag; + } + } + MOZ_ASSERT( + tag || IsTextLeaf() || IsDoc(), + "Could not fetch tag via TagName() or from initial cache push!"); + if (tag != data.mValidTag) { + // If this rel doesn't apply to us, do no pre-processing. Also, + // note in our updateTracker that we should do no post-processing. + updateTracker.AppendElement(false); + continue; + } + } + + nsStaticAtom* const relAtom = data.mAtom; + auto newRelationTargets = + aFields->GetAttribute<nsTArray<uint64_t>>(relAtom); + bool shouldAddNewImplicitRels = + newRelationTargets && newRelationTargets->Length(); + + // Remove existing implicit relations if we need to perform an update, or + // if we've recieved a DeleteEntry(). Only do this if mCachedFields is + // initialized. If mCachedFields is not initialized, we still need to + // construct the update array so we correctly handle reverse rels in + // PostProcessRelations. + if ((shouldAddNewImplicitRels || + aFields->GetAttribute<DeleteEntry>(relAtom)) && + mCachedFields) { + if (auto maybeOldIDs = + mCachedFields->GetAttribute<nsTArray<uint64_t>>(relAtom)) { + for (uint64_t id : *maybeOldIDs) { + // For each target, fetch its reverse relation map + // We need to call `Lookup` here instead of `LookupOrInsert` because + // it's possible the ID we're querying is from an acc that has since + // been Shutdown(), and so has intentionally removed its reverse rels + // from the doc's reverse rel cache. + if (auto reverseRels = Document()->mReverseRelations.Lookup(id)) { + // Then fetch its reverse relation's ID list. This should be safe + // to do via LookupOrInsert because by the time we've gotten here, + // we know the acc and `this` are still alive in the doc. If we hit + // the following assert, we don't have parity on implicit/explicit + // rels and something is wrong. + nsTArray<uint64_t>& reverseRelIDs = + reverseRels->LookupOrInsert(data.mReverseType); + // There might be other reverse relations stored for this acc, so + // remove our ID instead of deleting the array entirely. + DebugOnly<bool> removed = reverseRelIDs.RemoveElement(ID()); + MOZ_ASSERT(removed, "Can't find old reverse relation"); + } + } + } + } + + updateTracker.AppendElement(shouldAddNewImplicitRels); + } + + return updateTracker; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::PostProcessRelations( + const nsTArray<bool>& aToUpdate) { + size_t updateCount = aToUpdate.Length(); + MOZ_ASSERT(updateCount == ArrayLength(kRelationTypeAtoms), + "Did not note update status for every relation type!"); + for (size_t i = 0; i < updateCount; i++) { + if (aToUpdate.ElementAt(i)) { + // Since kRelationTypeAtoms was used to generate aToUpdate, we + // know the ith entry of aToUpdate corresponds to the relation type in + // the ith entry of kRelationTypeAtoms. Fetch the related data here. + auto const& data = kRelationTypeAtoms[i]; + + const nsTArray<uint64_t>& newIDs = + *mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom); + for (uint64_t id : newIDs) { + nsTHashMap<RelationType, nsTArray<uint64_t>>& relations = + Document()->mReverseRelations.LookupOrInsert(id); + nsTArray<uint64_t>& ids = relations.LookupOrInsert(data.mReverseType); + ids.AppendElement(ID()); + } + } + } +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::PruneRelationsOnShutdown() { + auto reverseRels = mDoc->mReverseRelations.Lookup(ID()); + if (!reverseRels) { + return; + } + for (auto const& data : kRelationTypeAtoms) { + // Fetch the list of targets for this reverse relation + auto reverseTargetList = reverseRels->Lookup(data.mReverseType); + if (!reverseTargetList) { + continue; + } + for (uint64_t id : *reverseTargetList) { + // For each target, retrieve its corresponding forward relation target + // list + RemoteAccessible* affectedAcc = mDoc->GetAccessible(id); + if (!affectedAcc) { + // It's possible the affect acc also shut down, in which case + // we don't have anything to update. + continue; + } + if (auto forwardTargetList = + affectedAcc->mCachedFields + ->GetMutableAttribute<nsTArray<uint64_t>>(data.mAtom)) { + forwardTargetList->RemoveElement(ID()); + if (!forwardTargetList->Length()) { + // The ID we removed was the only thing in the list, so remove the + // entry from the cache entirely -- don't leave an empty array. + affectedAcc->mCachedFields->Remove(data.mAtom); + } + } + } + } + // Remove this ID from the document's map of reverse relations. + reverseRels.Remove(); +} + +template <class Derived> +uint32_t RemoteAccessibleBase<Derived>::GetCachedTextLength() { + MOZ_ASSERT(!HasChildren()); + if (!mCachedFields) { + return 0; + } + VERIFY_CACHE(CacheDomain::Text); + auto text = mCachedFields->GetAttribute<nsString>(nsGkAtoms::text); + if (!text) { + return 0; + } + return text->Length(); +} + +template <class Derived> +Maybe<const nsTArray<int32_t>&> +RemoteAccessibleBase<Derived>::GetCachedTextLines() { + MOZ_ASSERT(!HasChildren()); + if (!mCachedFields) { + return Nothing(); + } + VERIFY_CACHE(CacheDomain::Text); + return mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::line); +} + +template <class Derived> +Maybe<nsTArray<nsRect>> RemoteAccessibleBase<Derived>::GetCachedCharData() { + MOZ_ASSERT(IsText()); + if (!mCachedFields) { + return Nothing(); + } + + if (Maybe<const nsTArray<int32_t>&> maybeCharData = + mCachedFields->GetAttribute<nsTArray<int32_t>>( + nsGkAtoms::characterData)) { + const nsTArray<int32_t>& charData = *maybeCharData; + nsTArray<nsRect> rects; + for (int i = 0; i < static_cast<int32_t>(charData.Length()); i += 4) { + nsRect r(charData[i], charData[i + 1], charData[i + 2], charData[i + 3]); + rects.AppendElement(r); + } + return Some(std::move(rects)); + } + + return Nothing(); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::DOMNodeID(nsString& aID) const { + if (mCachedFields) { + mCachedFields->GetAttribute(nsGkAtoms::id, aID); + VERIFY_CACHE(CacheDomain::DOMNodeID); + } +} + +template <class Derived> +RefPtr<const AccAttributes> +RemoteAccessibleBase<Derived>::GetCachedTextAttributes() { + MOZ_ASSERT(IsText() || IsHyperText()); + if (mCachedFields) { + auto attrs = + mCachedFields->GetAttributeRefPtr<AccAttributes>(nsGkAtoms::style); + VERIFY_CACHE(CacheDomain::Text); + return attrs; + } + return nullptr; +} + +template <class Derived> +already_AddRefed<AccAttributes> +RemoteAccessibleBase<Derived>::DefaultTextAttributes() { + RefPtr<const AccAttributes> attrs = GetCachedTextAttributes(); + RefPtr<AccAttributes> result = new AccAttributes(); + if (attrs) { + attrs->CopyTo(result); + } + return result.forget(); +} + +template <class Derived> +RefPtr<const AccAttributes> +RemoteAccessibleBase<Derived>::GetCachedARIAAttributes() const { + if (mCachedFields) { + auto attrs = + mCachedFields->GetAttributeRefPtr<AccAttributes>(nsGkAtoms::aria); + VERIFY_CACHE(CacheDomain::ARIA); + return attrs; + } + return nullptr; +} + +template <class Derived> +nsString RemoteAccessibleBase<Derived>::GetCachedHTMLNameAttribute() const { + if (mCachedFields) { + if (auto maybeName = + mCachedFields->GetAttribute<nsString>(nsGkAtoms::attributeName)) { + return *maybeName; + } + } + return nsString(); +} + +template <class Derived> +uint64_t RemoteAccessibleBase<Derived>::State() { + uint64_t state = 0; + if (mCachedFields) { + if (auto rawState = + mCachedFields->GetAttribute<uint64_t>(nsGkAtoms::state)) { + VERIFY_CACHE(CacheDomain::State); + state = *rawState; + // Handle states that are derived from other states. + if (!(state & states::UNAVAILABLE)) { + state |= states::ENABLED | states::SENSITIVE; + } + if (state & states::EXPANDABLE && !(state & states::EXPANDED)) { + state |= states::COLLAPSED; + } + } + + ApplyImplicitState(state); + + auto* cbc = mDoc->GetBrowsingContext(); + if (cbc && !cbc->IsActive()) { + // If our browsing context is _not_ active, we're in a background tab + // and inherently offscreen. + state |= states::OFFSCREEN; + } else { + // If we're in an active browsing context, there are a few scenarios we + // need to address: + // - We are an iframe document in the visual viewport + // - We are an iframe document out of the visual viewport + // - We are non-iframe content in the visual viewport + // - We are non-iframe content out of the visual viewport + // We assume top level tab docs are on screen if their BC is active, so + // we don't need additional handling for them here. + if (!mDoc->IsTopLevel()) { + // Here we handle iframes and iframe content. + // We use an iframe's outer doc's position in the embedding document's + // viewport to determine if the iframe has been scrolled offscreen. + Accessible* docParent = mDoc->Parent(); + // In rare cases, we might not have an outer doc yet. Return if that's + // the case. + if (NS_WARN_IF(!docParent || !docParent->IsRemote())) { + return state; + } + + RemoteAccessible* outerDoc = docParent->AsRemote(); + DocAccessibleParent* embeddingDocument = outerDoc->Document(); + if (embeddingDocument && + !embeddingDocument->mOnScreenAccessibles.Contains(outerDoc->ID())) { + // Our embedding document's viewport cache doesn't contain the ID of + // our outer doc, so this iframe (and any of its content) is + // offscreen. + state |= states::OFFSCREEN; + } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) { + // Our embedding document's viewport cache contains the ID of our + // outer doc, but the iframe's viewport cache doesn't contain our ID. + // We are offscreen. + state |= states::OFFSCREEN; + } + } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) { + // We are top level tab content (but not a top level tab doc). + // If our tab doc's viewport cache doesn't contain our ID, we're + // offscreen. + state |= states::OFFSCREEN; + } + } + } + + return state; +} + +template <class Derived> +already_AddRefed<AccAttributes> RemoteAccessibleBase<Derived>::Attributes() { + RefPtr<AccAttributes> attributes = new AccAttributes(); + nsAccessibilityService* accService = GetAccService(); + if (!accService) { + // The service can be shut down before RemoteAccessibles. If it is shut + // down, we can't calculate some attributes. We're about to die anyway. + return attributes.forget(); + } + + if (mCachedFields) { + // We use GetAttribute instead of GetAttributeRefPtr because we need + // nsAtom, not const nsAtom. + if (auto tag = + mCachedFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::tag)) { + attributes->SetAttribute(nsGkAtoms::tag, *tag); + } + + GroupPos groupPos = GroupPosition(); + nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, groupPos.setSize, + groupPos.posInSet); + + bool hierarchical = false; + uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical); + if (itemCount) { + attributes->SetAttribute(nsGkAtoms::child_item_count, + static_cast<int32_t>(itemCount)); + } + + if (hierarchical) { + attributes->SetAttribute(nsGkAtoms::tree, true); + } + + if (auto inputType = mCachedFields->GetAttribute<RefPtr<nsAtom>>( + nsGkAtoms::textInputType)) { + attributes->SetAttribute(nsGkAtoms::textInputType, *inputType); + } + + if (RefPtr<nsAtom> display = DisplayStyle()) { + attributes->SetAttribute(nsGkAtoms::display, display); + } + + if (TableCellAccessibleBase* cell = AsTableCellBase()) { + TableAccessibleBase* table = cell->Table(); + uint32_t row = cell->RowIdx(); + uint32_t col = cell->ColIdx(); + int32_t cellIdx = table->CellIndexAt(row, col); + if (cellIdx != -1) { + attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx); + } + } + + if (bool layoutGuess = TableIsProbablyForLayout()) { + attributes->SetAttribute(nsGkAtoms::layout_guess, layoutGuess); + } + + accService->MarkupAttributes(this, attributes); + + const nsRoleMapEntry* roleMap = ARIARoleMap(); + nsAutoString role; + mCachedFields->GetAttribute(nsGkAtoms::role, role); + if (role.IsEmpty()) { + if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty) { + // Single, known role. + attributes->SetAttribute(nsGkAtoms::xmlroles, roleMap->roleAtom); + } else if (nsAtom* landmark = LandmarkRole()) { + // Landmark role from markup; e.g. HTML <main>. + attributes->SetAttribute(nsGkAtoms::xmlroles, landmark); + } + } else { + // Unknown role or multiple roles. + attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role)); + } + + if (roleMap) { + nsAutoString live; + if (nsAccUtils::GetLiveAttrValue(roleMap->liveAttRule, live)) { + attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live)); + } + } + + if (auto ariaAttrs = GetCachedARIAAttributes()) { + ariaAttrs->CopyTo(attributes); + } + + nsAccUtils::SetLiveContainerAttributes(attributes, this); + + nsString id; + DOMNodeID(id); + if (!id.IsEmpty()) { + attributes->SetAttribute(nsGkAtoms::id, std::move(id)); + } + } + + nsAutoString name; + if (Name(name) != eNameFromSubtree && !name.IsVoid()) { + attributes->SetAttribute(nsGkAtoms::explicit_name, true); + } + + // Expose the string value via the valuetext attribute. We test for the value + // interface because we don't want to expose traditional Value() information + // such as URLs on links and documents, or text in an input. + // XXX This is only needed for ATK, since other APIs have native ways to + // retrieve value text. We should probably move this into ATK specific code. + // For now, we do this because LocalAccessible does it. + if (HasNumericValue()) { + nsString valuetext; + Value(valuetext); + attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext)); + } + + return attributes.forget(); +} + +template <class Derived> +nsAtom* RemoteAccessibleBase<Derived>::TagName() const { + if (mCachedFields) { + if (auto tag = + mCachedFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::tag)) { + return *tag; + } + } + + return nullptr; +} + +template <class Derived> +already_AddRefed<nsAtom> RemoteAccessibleBase<Derived>::DisplayStyle() const { + if (mCachedFields) { + if (auto display = + mCachedFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::display)) { + RefPtr<nsAtom> result = *display; + return result.forget(); + } + } + return nullptr; +} + +template <class Derived> +float RemoteAccessibleBase<Derived>::Opacity() const { + if (mCachedFields) { + if (auto opacity = mCachedFields->GetAttribute<float>(nsGkAtoms::opacity)) { + return *opacity; + } + } + + return 1.0f; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::LiveRegionAttributes( + nsAString* aLive, nsAString* aRelevant, Maybe<bool>* aAtomic, + nsAString* aBusy) const { + if (!mCachedFields) { + return; + } + RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes(); + if (!attrs) { + return; + } + if (aLive) { + attrs->GetAttribute(nsGkAtoms::aria_live, *aLive); + } + if (aRelevant) { + attrs->GetAttribute(nsGkAtoms::aria_relevant, *aRelevant); + } + if (aAtomic) { + if (auto value = + attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::aria_atomic)) { + *aAtomic = Some(*value == nsGkAtoms::_true); + } + } + if (aBusy) { + attrs->GetAttribute(nsGkAtoms::aria_busy, *aBusy); + } +} + +template <class Derived> +Maybe<bool> RemoteAccessibleBase<Derived>::ARIASelected() const { + if (mCachedFields) { + return mCachedFields->GetAttribute<bool>(nsGkAtoms::aria_selected); + } + return Nothing(); +} + +template <class Derived> +nsAtom* RemoteAccessibleBase<Derived>::GetPrimaryAction() const { + if (mCachedFields) { + if (auto action = + mCachedFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::action)) { + return *action; + } + } + + return nullptr; +} + +template <class Derived> +uint8_t RemoteAccessibleBase<Derived>::ActionCount() const { + uint8_t actionCount = 0; + if (mCachedFields) { + if (HasPrimaryAction() || ActionAncestor()) { + actionCount++; + } + + if (mCachedFields->HasAttribute(nsGkAtoms::longdesc)) { + actionCount++; + } + VERIFY_CACHE(CacheDomain::Actions); + } + + return actionCount; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::ActionNameAt(uint8_t aIndex, + nsAString& aName) { + if (mCachedFields) { + aName.Truncate(); + nsAtom* action = GetPrimaryAction(); + bool hasActionAncestor = !action && ActionAncestor(); + + switch (aIndex) { + case 0: + if (action) { + action->ToString(aName); + } else if (hasActionAncestor) { + aName.AssignLiteral("click ancestor"); + } else if (mCachedFields->HasAttribute(nsGkAtoms::longdesc)) { + aName.AssignLiteral("showlongdesc"); + } + break; + case 1: + if ((action || hasActionAncestor) && + mCachedFields->HasAttribute(nsGkAtoms::longdesc)) { + aName.AssignLiteral("showlongdesc"); + } + break; + default: + break; + } + } + VERIFY_CACHE(CacheDomain::Actions); +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::DoAction(uint8_t aIndex) const { + if (ActionCount() < aIndex + 1) { + return false; + } + + Unused << mDoc->SendDoActionAsync(mID, aIndex); + return true; +} + +template <class Derived> +KeyBinding RemoteAccessibleBase<Derived>::AccessKey() const { + if (mCachedFields) { + if (auto value = + mCachedFields->GetAttribute<uint64_t>(nsGkAtoms::accesskey)) { + return KeyBinding(*value); + } + } + return KeyBinding(); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::SelectionRanges( + nsTArray<TextRange>* aRanges) const { + Document()->SelectionRanges(aRanges); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::ARIAGroupPosition( + int32_t* aLevel, int32_t* aSetSize, int32_t* aPosInSet) const { + if (!mCachedFields) { + return; + } + + if (aLevel) { + if (auto level = + mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_level)) { + *aLevel = *level; + } + } + if (aSetSize) { + if (auto setsize = + mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_setsize)) { + *aSetSize = *setsize; + } + } + if (aPosInSet) { + if (auto posinset = + mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_posinset)) { + *aPosInSet = *posinset; + } + } +} + +template <class Derived> +AccGroupInfo* RemoteAccessibleBase<Derived>::GetGroupInfo() const { + if (!mCachedFields) { + return nullptr; + } + + if (auto groupInfo = mCachedFields->GetAttribute<UniquePtr<AccGroupInfo>>( + nsGkAtoms::group)) { + return groupInfo->get(); + } + + return nullptr; +} + +template <class Derived> +AccGroupInfo* RemoteAccessibleBase<Derived>::GetOrCreateGroupInfo() { + AccGroupInfo* groupInfo = GetGroupInfo(); + if (groupInfo) { + return groupInfo; + } + + groupInfo = AccGroupInfo::CreateGroupInfo(this); + if (groupInfo) { + if (!mCachedFields) { + mCachedFields = new AccAttributes(); + } + + mCachedFields->SetAttribute(nsGkAtoms::group, groupInfo); + } + + return groupInfo; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::InvalidateGroupInfo() { + if (mCachedFields) { + mCachedFields->Remove(nsGkAtoms::group); + } +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::GetPositionAndSetSize(int32_t* aPosInSet, + int32_t* aSetSize) { + if (IsHTMLRadioButton()) { + *aSetSize = 0; + Relation rel = RelationByType(RelationType::MEMBER_OF); + while (Accessible* radio = rel.Next()) { + ++*aSetSize; + if (radio == this) { + *aPosInSet = *aSetSize; + } + } + return; + } + + Accessible::GetPositionAndSetSize(aPosInSet, aSetSize); +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::HasPrimaryAction() const { + return mCachedFields && mCachedFields->HasAttribute(nsGkAtoms::action); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::TakeFocus() const { + Unused << mDoc->SendTakeFocus(mID); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::ScrollTo(uint32_t aHow) const { + Unused << mDoc->SendScrollTo(mID, aHow); +} + +//////////////////////////////////////////////////////////////////////////////// +// SelectAccessible + +template <class Derived> +void RemoteAccessibleBase<Derived>::SelectedItems( + nsTArray<Accessible*>* aItems) { + Pivot p = Pivot(this); + PivotStateRule rule(states::SELECTED); + for (Accessible* selected = p.First(rule); selected; + selected = p.Next(selected, rule)) { + aItems->AppendElement(selected); + } +} + +template <class Derived> +uint32_t RemoteAccessibleBase<Derived>::SelectedItemCount() { + uint32_t count = 0; + Pivot p = Pivot(this); + PivotStateRule rule(states::SELECTED); + for (Accessible* selected = p.First(rule); selected; + selected = p.Next(selected, rule)) { + count++; + } + + return count; +} + +template <class Derived> +Accessible* RemoteAccessibleBase<Derived>::GetSelectedItem(uint32_t aIndex) { + uint32_t index = 0; + Accessible* selected = nullptr; + Pivot p = Pivot(this); + PivotStateRule rule(states::SELECTED); + for (selected = p.First(rule); selected && index < aIndex; + selected = p.Next(selected, rule)) { + index++; + } + + return selected; +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::IsItemSelected(uint32_t aIndex) { + uint32_t index = 0; + Accessible* selectable = nullptr; + Pivot p = Pivot(this); + PivotStateRule rule(states::SELECTABLE); + for (selectable = p.First(rule); selectable && index < aIndex; + selectable = p.Next(selectable, rule)) { + index++; + } + + return selectable && selectable->State() & states::SELECTED; +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::AddItemToSelection(uint32_t aIndex) { + uint32_t index = 0; + Accessible* selectable = nullptr; + Pivot p = Pivot(this); + PivotStateRule rule(states::SELECTABLE); + for (selectable = p.First(rule); selectable && index < aIndex; + selectable = p.Next(selectable, rule)) { + index++; + } + + if (selectable) selectable->SetSelected(true); + + return static_cast<bool>(selectable); +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::RemoveItemFromSelection(uint32_t aIndex) { + uint32_t index = 0; + Accessible* selectable = nullptr; + Pivot p = Pivot(this); + PivotStateRule rule(states::SELECTABLE); + for (selectable = p.First(rule); selectable && index < aIndex; + selectable = p.Next(selectable, rule)) { + index++; + } + + if (selectable) selectable->SetSelected(false); + + return static_cast<bool>(selectable); +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::SelectAll() { + if ((State() & states::MULTISELECTABLE) == 0) { + return false; + } + + bool success = false; + Accessible* selectable = nullptr; + Pivot p = Pivot(this); + PivotStateRule rule(states::SELECTABLE); + for (selectable = p.First(rule); selectable; + selectable = p.Next(selectable, rule)) { + success = true; + selectable->SetSelected(true); + } + return success; +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::UnselectAll() { + if ((State() & states::MULTISELECTABLE) == 0) { + return false; + } + + bool success = false; + Accessible* selectable = nullptr; + Pivot p = Pivot(this); + PivotStateRule rule(states::SELECTABLE); + for (selectable = p.First(rule); selectable; + selectable = p.Next(selectable, rule)) { + success = true; + selectable->SetSelected(false); + } + return success; +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::TakeSelection() { + Unused << mDoc->SendTakeSelection(mID); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::SetSelected(bool aSelect) { + Unused << mDoc->SendSetSelected(mID, aSelect); +} + +template <class Derived> +TableAccessibleBase* RemoteAccessibleBase<Derived>::AsTableBase() { + MOZ_ASSERT(StaticPrefs::accessibility_cache_enabled_AtStartup()); + if (IsTable()) { + return CachedTableAccessible::GetFrom(this); + } + return nullptr; +} + +template <class Derived> +TableCellAccessibleBase* RemoteAccessibleBase<Derived>::AsTableCellBase() { + MOZ_ASSERT(StaticPrefs::accessibility_cache_enabled_AtStartup()); + if (IsTableCell()) { + return CachedTableCellAccessible::GetFrom(this); + } + return nullptr; +} + +template <class Derived> +bool RemoteAccessibleBase<Derived>::TableIsProbablyForLayout() { + MOZ_ASSERT(StaticPrefs::accessibility_cache_enabled_AtStartup()); + if (mCachedFields) { + if (auto layoutGuess = + mCachedFields->GetAttribute<bool>(nsGkAtoms::layout_guess)) { + return *layoutGuess; + } + } + return false; +} + +template <class Derived> +nsTArray<int32_t>& RemoteAccessibleBase<Derived>::GetCachedHyperTextOffsets() { + if (mCachedFields) { + if (auto offsets = mCachedFields->GetMutableAttribute<nsTArray<int32_t>>( + nsGkAtoms::offset)) { + return *offsets; + } + } + nsTArray<int32_t> newOffsets; + if (!mCachedFields) { + mCachedFields = new AccAttributes(); + } + mCachedFields->SetAttribute(nsGkAtoms::offset, std::move(newOffsets)); + return *mCachedFields->GetMutableAttribute<nsTArray<int32_t>>( + nsGkAtoms::offset); +} + +template <class Derived> +void RemoteAccessibleBase<Derived>::SetCaretOffset(int32_t aOffset) { + Unused << mDoc->SendSetCaretOffset(mID, aOffset); +} + +template <class Derived> +Maybe<int32_t> RemoteAccessibleBase<Derived>::GetIntARIAAttr( + nsAtom* aAttrName) const { + if (RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes()) { + if (auto val = attrs->GetAttribute<int32_t>(aAttrName)) { + return val; + } + } + return Nothing(); +} + +template <class Derived> +size_t RemoteAccessibleBase<Derived>::SizeOfIncludingThis( + MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + +template <class Derived> +size_t RemoteAccessibleBase<Derived>::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) { + size_t size = 0; + + // Count attributes. + if (mCachedFields) { + size += mCachedFields->SizeOfIncludingThis(aMallocSizeOf); + } + + // We don't recurse into mChildren because they're already counted in their + // document's mAccessibles. + size += mChildren.ShallowSizeOfExcludingThis(aMallocSizeOf); + + return size; +} + +template class RemoteAccessibleBase<RemoteAccessible>; + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/RemoteAccessibleBase.h b/accessible/ipc/RemoteAccessibleBase.h new file mode 100644 index 0000000000..57407297b5 --- /dev/null +++ b/accessible/ipc/RemoteAccessibleBase.h @@ -0,0 +1,482 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_RemoteAccessibleBase_h +#define mozilla_a11y_RemoteAccessibleBase_h + +#include "mozilla/a11y/Accessible.h" +#include "mozilla/a11y/CacheConstants.h" +#include "mozilla/a11y/HyperTextAccessibleBase.h" +#include "mozilla/a11y/Role.h" +#include "AccAttributes.h" +#include "nsIAccessibleText.h" +#include "nsIAccessibleTypes.h" +#include "nsTArray.h" +#include "nsRect.h" +#include "LocalAccessible.h" + +namespace mozilla { +namespace a11y { + +class Attribute; +class DocAccessibleParent; +class RemoteAccessible; +enum class RelationType; + +/** + * The base type for an accessibility tree node that originated in the parent + * process. + */ +template <class Derived> +class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase { + public: + virtual ~RemoteAccessibleBase() { MOZ_ASSERT(!mWrapper); } + + virtual bool IsRemote() const override { return true; } + + void AddChildAt(uint32_t aIdx, Derived* aChild) { + mChildren.InsertElementAt(aIdx, aChild); + if (IsHyperText()) { + InvalidateCachedHyperTextOffsets(); + } + } + + virtual uint32_t ChildCount() const override { return mChildren.Length(); } + Derived* RemoteChildAt(uint32_t aIdx) const { + return mChildren.SafeElementAt(aIdx); + } + Derived* RemoteFirstChild() const { + return mChildren.Length() ? mChildren[0] : nullptr; + } + Derived* RemoteLastChild() const { + return mChildren.Length() ? mChildren[mChildren.Length() - 1] : nullptr; + } + Derived* RemotePrevSibling() const { + if (IsDoc()) { + // The normal code path doesn't work for documents because the parent + // might be a local OuterDoc, but IndexInParent() will return 1. + // A document is always a single child of an OuterDoc anyway. + return nullptr; + } + int32_t idx = IndexInParent(); + if (idx == -1) { + return nullptr; // No parent. + } + return idx > 0 ? RemoteParent()->mChildren[idx - 1] : nullptr; + } + Derived* RemoteNextSibling() const { + if (IsDoc()) { + // The normal code path doesn't work for documents because the parent + // might be a local OuterDoc, but IndexInParent() will return 1. + // A document is always a single child of an OuterDoc anyway. + return nullptr; + } + int32_t idx = IndexInParent(); + if (idx == -1) { + return nullptr; // No parent. + } + MOZ_ASSERT(idx >= 0); + size_t newIdx = idx + 1; + return newIdx < RemoteParent()->mChildren.Length() + ? RemoteParent()->mChildren[newIdx] + : nullptr; + } + + // Accessible hierarchy method overrides + + virtual Accessible* Parent() const override { return RemoteParent(); } + + virtual Accessible* ChildAt(uint32_t aIndex) const override { + return RemoteChildAt(aIndex); + } + + virtual Accessible* NextSibling() const override { + return RemoteNextSibling(); + } + + virtual Accessible* PrevSibling() const override { + return RemotePrevSibling(); + } + + // XXX evaluate if this is fast enough. + virtual int32_t IndexInParent() const override { + Derived* parent = RemoteParent(); + if (!parent) { + return -1; + } + return parent->mChildren.IndexOf(static_cast<const Derived*>(this)); + } + virtual uint32_t EmbeddedChildCount() override; + virtual int32_t IndexOfEmbeddedChild(Accessible* aChild) override; + virtual Accessible* EmbeddedChildAt(uint32_t aChildIdx) override; + + void Shutdown(); + + void SetChildDoc(DocAccessibleParent* aChildDoc); + void ClearChildDoc(DocAccessibleParent* aChildDoc); + + /** + * Remove The given child. + */ + void RemoveChild(Derived* aChild) { + mChildren.RemoveElement(aChild); + if (IsHyperText()) { + InvalidateCachedHyperTextOffsets(); + } + } + + /** + * Return the proxy for the parent of the wrapped accessible. + */ + Derived* RemoteParent() const; + + LocalAccessible* OuterDocOfRemoteBrowser() const; + + /** + * Get the role of the accessible we're proxying. + */ + virtual role Role() const override { return mRole; } + + /** + * Return true if this is an embedded object. + */ + bool IsEmbeddedObject() const { return !IsText(); } + + virtual bool IsLink() const override { + if (IsHTMLLink()) { + // XXX: HTML links always return true for IsLink. + return true; + } + + if (IsText()) { + return false; + } + + if (Accessible* parent = Parent()) { + return parent->IsHyperText(); + } + + return false; + } + + virtual bool HasNumericValue() const override { + // XXX: We combine the aria and native "has numeric value" field + // when we serialize the local accessible into eNumericValue. + return HasGenericType(eNumericValue); + } + + // Methods that potentially access a cache. + + virtual ENameValueFlag Name(nsString& aName) const override; + virtual void Description(nsString& aDescription) const override; + virtual void Value(nsString& aValue) const override; + + virtual double CurValue() const override; + virtual double MinValue() const override; + virtual double MaxValue() const override; + virtual double Step() const override; + + virtual Accessible* ChildAtPoint( + int32_t aX, int32_t aY, + LocalAccessible::EWhichChildAtPoint aWhichChild) override; + + virtual LayoutDeviceIntRect Bounds() const override; + + virtual nsRect BoundsInAppUnits() const override; + + virtual Relation RelationByType(RelationType aType) const override; + + virtual uint64_t State() override; + + virtual already_AddRefed<AccAttributes> Attributes() override; + + virtual nsAtom* TagName() const override; + + virtual already_AddRefed<nsAtom> DisplayStyle() const override; + + virtual float Opacity() const override; + + virtual void LiveRegionAttributes(nsAString* aLive, nsAString* aRelevant, + Maybe<bool>* aAtomic, + nsAString* aBusy) const override; + + virtual Maybe<bool> ARIASelected() const override; + + virtual uint8_t ActionCount() const override; + + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + virtual bool DoAction(uint8_t aIndex) const override; + + virtual KeyBinding AccessKey() const override; + + virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const override; + + virtual Maybe<int32_t> GetIntARIAAttr(nsAtom* aAttrName) const override; + + ////////////////////////////////////////////////////////////////////////////// + // SelectAccessible + + virtual void SelectedItems(nsTArray<Accessible*>* aItems) override; + + virtual uint32_t SelectedItemCount() override; + + virtual Accessible* GetSelectedItem(uint32_t aIndex) override; + + virtual bool IsItemSelected(uint32_t aIndex) override; + + virtual bool AddItemToSelection(uint32_t aIndex) override; + + virtual bool RemoveItemFromSelection(uint32_t aIndex) override; + + virtual bool SelectAll() override; + + virtual bool UnselectAll() override; + + virtual void TakeSelection() override; + + virtual void SetSelected(bool aSelect) override; + + // Methods that interact with content. + + virtual void TakeFocus() const override; + virtual void ScrollTo(uint32_t aHow) const override; + virtual void SetCaretOffset(int32_t aOffset) override; + + /** + * Allow the platform to store a pointers worth of data on us. + */ + uintptr_t GetWrapper() const { return mWrapper; } + void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; } + + virtual uint64_t ID() const override { return mID; } + + /** + * Return the document containing this proxy, or the proxy itself if it is a + * document. + */ + DocAccessibleParent* Document() const { return mDoc; } + + DocAccessibleParent* AsDoc() const { return IsDoc() ? mDoc : nullptr; } + + void ApplyCache(CacheUpdateType aUpdateType, AccAttributes* aFields) { + const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields); + if (auto maybeViewportCache = + aFields->GetAttribute<nsTArray<uint64_t>>(nsGkAtoms::viewport)) { + // Updating the viewport cache means the offscreen state of this + // document's accessibles has changed. Update the HashSet we use for + // checking offscreen state here. + MOZ_ASSERT(IsDoc(), + "Fetched the viewport cache from a non-doc accessible?"); + AsDoc()->mOnScreenAccessibles.Clear(); + for (auto id : *maybeViewportCache) { + AsDoc()->mOnScreenAccessibles.Insert(id); + } + } + + if (aUpdateType == CacheUpdateType::Initial) { + mCachedFields = aFields; + } else { + if (!mCachedFields) { + // The fields cache can be uninitialized if there were no cache-worthy + // fields in the initial cache push. + // We don't do a simple assign because we don't want to store the + // DeleteEntry entries. + mCachedFields = new AccAttributes(); + } + mCachedFields->Update(aFields); + } + + if (IsTextLeaf()) { + Derived* parent = RemoteParent(); + if (parent && parent->IsHyperText()) { + parent->InvalidateCachedHyperTextOffsets(); + } + } + + PostProcessRelations(relUpdatesNeeded); + } + + void UpdateStateCache(uint64_t aState, bool aEnabled) { + if (aState & kRemoteCalculatedStates) { + return; + } + uint64_t state = 0; + if (mCachedFields) { + if (auto oldState = + mCachedFields->GetAttribute<uint64_t>(nsGkAtoms::state)) { + state = *oldState; + } + } else { + mCachedFields = new AccAttributes(); + } + if (aEnabled) { + state |= aState; + } else { + state &= ~aState; + } + mCachedFields->SetAttribute(nsGkAtoms::state, state); + } + + void InvalidateGroupInfo(); + + virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0, + uint32_t aLength = UINT32_MAX) override; + + virtual bool TableIsProbablyForLayout(); + + /** + * Iterates through each atom in kRelationTypeAtoms, checking to see + * if it is present in aFields. If it is present (or if aFields contains + * a DeleteEntry() for this atom) and mCachedFields is initialized, + * fetches the old rel targets and removes their existing reverse relations + * stored in mReverseRelations. + * Returns an array of bools where the ith array entry corresponds + * to whether or not the rel at the ith entry of kRelationTypeAtoms + * requires a post-processing update. + */ + nsTArray<bool> PreProcessRelations(AccAttributes* aFields); + + /** + * Takes in the array returned from PreProcessRelations. + * For each entry requiring an update, fetches the new relation + * targets stored in mCachedFields and appropriately + * updates their reverse relations in mReverseRelations. + */ + void PostProcessRelations(const nsTArray<bool>& aToUpdate); + + /** + * This method is called during shutdown, before we clear our + * reverse rel map from the document's mReverseRelations cache. + * Here, we traverse our reverse relations, removing our ID from + * the corresponding forward relation's target list. This ensures + * the stored forward relations do not reference defunct accessibles. + */ + void PruneRelationsOnShutdown(); + + uint32_t GetCachedTextLength(); + Maybe<const nsTArray<int32_t>&> GetCachedTextLines(); + Maybe<nsTArray<nsRect>> GetCachedCharData(); + RefPtr<const AccAttributes> GetCachedTextAttributes(); + RefPtr<const AccAttributes> GetCachedARIAAttributes() const; + + nsString GetCachedHTMLNameAttribute() const; + + virtual HyperTextAccessibleBase* AsHyperTextBase() override { + return IsHyperText() ? static_cast<HyperTextAccessibleBase*>(this) + : nullptr; + } + + virtual TableAccessibleBase* AsTableBase() override; + virtual TableCellAccessibleBase* AsTableCellBase() override; + + virtual void DOMNodeID(nsString& aID) const override; + + // HyperTextAccessibleBase + virtual already_AddRefed<AccAttributes> DefaultTextAttributes() override; + + /** + * Invalidate cached HyperText offsets. This should be called whenever a + * child is added or removed or the text of a text leaf child is changed. + * Although GetChildOffset can either fully or partially invalidate the + * offsets cache, calculating which offset to invalidate is not worthwhile + * because a client might not even query offsets. This is in contrast to + * LocalAccessible, where the offsets are always needed to fire text change + * events. For RemoteAccessible, it's cheaper overall to just rebuild the + * offsets cache when a client next needs it. + */ + void InvalidateCachedHyperTextOffsets() { + if (mCachedFields) { + mCachedFields->Remove(nsGkAtoms::offset); + } + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf); + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf); + + protected: + RemoteAccessibleBase(uint64_t aID, Derived* aParent, + DocAccessibleParent* aDoc, role aRole, AccType aType, + AccGenericType aGenericTypes, uint8_t aRoleMapEntryIndex) + : Accessible(aType, aGenericTypes, aRoleMapEntryIndex), + mParent(aParent->ID()), + mDoc(aDoc), + mWrapper(0), + mID(aID), + mCachedFields(nullptr), + mRole(aRole) {} + + explicit RemoteAccessibleBase(DocAccessibleParent* aThisAsDoc) + : Accessible(), + mParent(kNoParent), + mDoc(aThisAsDoc), + mWrapper(0), + mID(0), + mCachedFields(nullptr), + mRole(roles::DOCUMENT) { + mGenericTypes = eDocument | eHyperText; + } + + protected: + void SetParent(Derived* aParent); + Maybe<nsRect> RetrieveCachedBounds() const; + bool ApplyTransform(nsRect& aCumulativeBounds, + const nsRect& aParentRelativeBounds) const; + void ApplyScrollOffset(nsRect& aBounds) const; + void ApplyCrossDocOffset(nsRect& aBounds) const; + LayoutDeviceIntRect BoundsWithOffset(Maybe<nsRect> aOffset) const; + + virtual void ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize, + int32_t* aPosInSet) const override; + + virtual AccGroupInfo* GetGroupInfo() const override; + + virtual AccGroupInfo* GetOrCreateGroupInfo() override; + + virtual void GetPositionAndSetSize(int32_t* aPosInSet, + int32_t* aSetSize) override; + + virtual bool HasPrimaryAction() const override; + + nsAtom* GetPrimaryAction() const; + + virtual nsTArray<int32_t>& GetCachedHyperTextOffsets() override; + + private: + uintptr_t mParent; + static const uintptr_t kNoParent = UINTPTR_MAX; + + friend Derived; + friend DocAccessibleParent; + friend TextLeafPoint; + friend HyperTextAccessibleBase; + friend class xpcAccessible; + friend class CachedTableCellAccessible; +#ifdef XP_WIN + friend class sdnAccessible; +#endif + + nsTArray<Derived*> mChildren; + DocAccessibleParent* mDoc; + uintptr_t mWrapper; + uint64_t mID; + + protected: + virtual const Accessible* Acc() const override { return this; } + + RefPtr<AccAttributes> mCachedFields; + + // XXX DocAccessibleParent gets to change this to change the role of + // documents. + role mRole : 27; +}; + +extern template class RemoteAccessibleBase<RemoteAccessible>; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/RemoteAccessibleShared.h b/accessible/ipc/RemoteAccessibleShared.h new file mode 100644 index 0000000000..f91b446b37 --- /dev/null +++ b/accessible/ipc/RemoteAccessibleShared.h @@ -0,0 +1,206 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_RemoteAccessibleShared_h +#define mozilla_a11y_RemoteAccessibleShared_h + +/** + * These are function declarations shared between win/RemoteAccessible.h and + * other/RemoteAccessible.h. + */ + +/* + * Return the states for the proxied accessible. + */ +virtual uint64_t State() override; + +/* + * Return the native states for the proxied accessible. + */ +uint64_t NativeState() const; + +/* + * Set aName to the name of the proxied accessible. + * Return the ENameValueFlag passed from Accessible::Name + */ +ENameValueFlag Name(nsString& aName) const override; + +/* + * Set aValue to the value of the proxied accessible. + */ +void Value(nsString& aValue) const override; + +/* + * Set aHelp to the help string of the proxied accessible. + */ +void Help(nsString& aHelp) const; + +/** + * Set aDesc to the description of the proxied accessible. + */ +void Description(nsString& aDesc) const override; + +/** + * Get the set of attributes on the proxied accessible. + */ +virtual already_AddRefed<AccAttributes> Attributes() override; + +virtual Relation RelationByType(RelationType aType) const override; + +bool IsSearchbox() const; + +virtual mozilla::a11y::GroupPos GroupPosition() override; +void ScrollToPoint(uint32_t aScrollType, int32_t aX, int32_t aY); + +void Announce(const nsString& aAnnouncement, uint16_t aPriority); + +int32_t CaretLineNumber(); +virtual int32_t CaretOffset() const override; + +virtual void TextSubstring(int32_t aStartOffset, int32_t aEndOfset, + nsAString& aText) const override; + +virtual void TextAfterOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, + nsAString& aText) override; + +virtual void TextAtOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, + nsAString& aText) override; + +virtual void TextBeforeOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, + nsAString& aText) override; + +char16_t CharAt(int32_t aOffset); + +virtual int32_t OffsetAtPoint(int32_t aX, int32_t aY, + uint32_t aCoordType) override; + +bool SetSelectionBoundsAt(int32_t aSelectionNum, int32_t aStartOffset, + int32_t aEndOffset); + +bool AddToSelection(int32_t aStartOffset, int32_t aEndOffset); + +bool RemoveFromSelection(int32_t aSelectionNum); + +void ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset, + uint32_t aScrollType); + +void ScrollSubstringToPoint(int32_t aStartOffset, int32_t aEndOffset, + uint32_t aCoordinateType, int32_t aX, int32_t aY); + +void Text(nsString* aText); + +void ReplaceText(const nsString& aText); + +bool InsertText(const nsString& aText, int32_t aPosition); + +bool CopyText(int32_t aStartPos, int32_t aEndPos); + +bool CutText(int32_t aStartPos, int32_t aEndPos); + +bool DeleteText(int32_t aStartPos, int32_t aEndPos); + +bool PasteText(int32_t aPosition); + +LayoutDeviceIntPoint ImagePosition(uint32_t aCoordType); + +LayoutDeviceIntSize ImageSize(); + +bool IsLinkValid(); + +uint32_t AnchorCount(bool* aOk); + +void AnchorURIAt(uint32_t aIndex, nsCString& aURI, bool* aOk); + +RemoteAccessible* AnchorAt(uint32_t aIndex); + +uint32_t LinkCount(); + +RemoteAccessible* LinkAt(const uint32_t& aIndex); + +RemoteAccessible* TableOfACell(); + +uint32_t ColIdx(); + +uint32_t RowIdx(); + +void GetPosition(uint32_t* aRowIdx, uint32_t* aColIdx); + +uint32_t ColExtent(); + +uint32_t RowExtent(); + +void GetColRowExtents(uint32_t* aColIdx, uint32_t* aRowIdx, + uint32_t* aColExtent, uint32_t* aRowExtent); + +void ColHeaderCells(nsTArray<RemoteAccessible*>* aCells); + +void RowHeaderCells(nsTArray<RemoteAccessible*>* aCells); + +bool IsCellSelected(); + +RemoteAccessible* TableCaption(); +void TableSummary(nsString& aSummary); +uint32_t TableColumnCount(); +uint32_t TableRowCount(); +RemoteAccessible* TableCellAt(uint32_t aRow, uint32_t aCol); +int32_t TableCellIndexAt(uint32_t aRow, uint32_t aCol); +int32_t TableColumnIndexAt(uint32_t aCellIndex); +int32_t TableRowIndexAt(uint32_t aCellIndex); +void TableRowAndColumnIndicesAt(uint32_t aCellIndex, int32_t* aRow, + int32_t* aCol); +uint32_t TableColumnExtentAt(uint32_t aRow, uint32_t aCol); +uint32_t TableRowExtentAt(uint32_t aRow, uint32_t aCol); +void TableColumnDescription(uint32_t aCol, nsString& aDescription); +void TableRowDescription(uint32_t aRow, nsString& aDescription); +bool TableColumnSelected(uint32_t aCol); +bool TableRowSelected(uint32_t aRow); +bool TableCellSelected(uint32_t aRow, uint32_t aCol); +uint32_t TableSelectedCellCount(); +uint32_t TableSelectedColumnCount(); +uint32_t TableSelectedRowCount(); +void TableSelectedCells(nsTArray<RemoteAccessible*>* aCellIDs); +void TableSelectedCellIndices(nsTArray<uint32_t>* aCellIndices); +void TableSelectedColumnIndices(nsTArray<uint32_t>* aColumnIndices); +void TableSelectedRowIndices(nsTArray<uint32_t>* aRowIndices); +void TableSelectColumn(uint32_t aCol); +void TableSelectRow(uint32_t aRow); +void TableUnselectColumn(uint32_t aCol); +void TableUnselectRow(uint32_t aRow); +RemoteAccessible* AtkTableColumnHeader(int32_t aCol); +RemoteAccessible* AtkTableRowHeader(int32_t aRow); + +void AtkKeyBinding(nsString& aBinding); + +double CurValue() const override; +double MinValue() const override; +double MaxValue() const override; +double Step() const override; +bool SetCurValue(double aValue); + +Accessible* ChildAtPoint( + int32_t aX, int32_t aY, + LocalAccessible::EWhichChildAtPoint aWhichChild) override; +LayoutDeviceIntRect Bounds() const override; +virtual nsIntRect BoundsInCSSPixels() const override; + +void Language(nsString& aLocale); +void DocType(nsString& aType); +void Title(nsString& aTitle); +void MimeType(nsString aMime); +void URLDocTypeMimeType(nsString& aURL, nsString& aDocType, + nsString& aMimeType); + +void Extents(bool aNeedsScreenCoords, int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight); + +virtual void DOMNodeID(nsString& aID) const override; + +#endif diff --git a/accessible/ipc/extension/android/DocAccessiblePlatformExtChild.cpp b/accessible/ipc/extension/android/DocAccessiblePlatformExtChild.cpp new file mode 100644 index 0000000000..ad29ae900c --- /dev/null +++ b/accessible/ipc/extension/android/DocAccessiblePlatformExtChild.cpp @@ -0,0 +1,75 @@ +/* -*- 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 "DocAccessiblePlatformExtChild.h" + +#include "DocAccessibleChild.h" +#include "AccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvPivotTo( + uint64_t aID, int32_t aGranularity, bool aForward, bool aInclusive) { + if (auto acc = IdToAccessibleWrap(aID)) { + acc->PivotTo(aGranularity, aForward, aInclusive); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvNavigateText( + uint64_t aID, int32_t aGranularity, int32_t aStartOffset, + int32_t aEndOffset, bool aForward, bool aSelect) { + if (auto acc = IdToAccessibleWrap(aID)) { + acc->NavigateText(aGranularity, aStartOffset, aEndOffset, aForward, + aSelect); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvSetSelection( + uint64_t aID, int32_t aStart, int32_t aEnd) { + if (auto acc = IdToAccessibleWrap(aID)) { + acc->SetSelection(aStart, aEnd); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvCut(uint64_t aID) { + if (auto acc = IdToAccessibleWrap(aID)) { + acc->Cut(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvCopy(uint64_t aID) { + if (auto acc = IdToAccessibleWrap(aID)) { + acc->Copy(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvPaste(uint64_t aID) { + if (auto acc = IdToAccessibleWrap(aID)) { + acc->Paste(); + } + + return IPC_OK(); +} + +AccessibleWrap* DocAccessiblePlatformExtChild::IdToAccessibleWrap( + const uint64_t& aID) const { + return static_cast<AccessibleWrap*>( + static_cast<DocAccessibleChild*>(Manager())->IdToAccessible(aID)); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/extension/android/DocAccessiblePlatformExtChild.h b/accessible/ipc/extension/android/DocAccessiblePlatformExtChild.h new file mode 100644 index 0000000000..60108213e1 --- /dev/null +++ b/accessible/ipc/extension/android/DocAccessiblePlatformExtChild.h @@ -0,0 +1,44 @@ +/* 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/. */ + +#ifndef mozilla_a11y_DocAccessiblePlatformExtChild_h +#define mozilla_a11y_DocAccessiblePlatformExtChild_h + +#include "mozilla/a11y/PDocAccessiblePlatformExtChild.h" + +namespace mozilla { +namespace a11y { + +class AccessibleWrap; +class DocAccessibleChild; + +class DocAccessiblePlatformExtChild : public PDocAccessiblePlatformExtChild { + public: + mozilla::ipc::IPCResult RecvPivotTo(uint64_t aID, int32_t aGranularity, + bool aForward, bool aInclusive); + + mozilla::ipc::IPCResult RecvNavigateText(uint64_t aID, int32_t aGranularity, + int32_t aStartOffset, + int32_t aEndOffset, bool aForward, + bool aSelect); + + mozilla::ipc::IPCResult RecvSetSelection(uint64_t aID, int32_t aStart, + int32_t aEnd); + + mozilla::ipc::IPCResult RecvCut(uint64_t aID); + + mozilla::ipc::IPCResult RecvCopy(uint64_t aID); + + mozilla::ipc::IPCResult RecvPaste(uint64_t aID); + + mozilla::ipc::IPCResult RecvExploreByTouch(uint64_t aID, float aX, float aY); + + private: + AccessibleWrap* IdToAccessibleWrap(const uint64_t& aID) const; +}; +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/extension/android/DocAccessiblePlatformExtParent.cpp b/accessible/ipc/extension/android/DocAccessiblePlatformExtParent.cpp new file mode 100644 index 0000000000..dbc16b7401 --- /dev/null +++ b/accessible/ipc/extension/android/DocAccessiblePlatformExtParent.cpp @@ -0,0 +1,45 @@ +/* -*- 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 "DocAccessiblePlatformExtParent.h" + +#include "AccessibleWrap.h" +#include "SessionAccessibility.h" + +#include "mozilla/a11y/DocAccessibleParent.h" + +namespace mozilla { +namespace a11y { + +mozilla::ipc::IPCResult DocAccessiblePlatformExtParent::RecvSetPivotBoundaries( + PDocAccessibleParent* aFirstDoc, uint64_t aFirst, + PDocAccessibleParent* aLastDoc, uint64_t aLast) { + MOZ_ASSERT(aFirstDoc); + MOZ_ASSERT(aLastDoc); + + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor( + static_cast<DocAccessibleParent*>(Manager())); + if (!sessionAcc) { + return IPC_OK(); + } + + RemoteAccessible* first = + static_cast<DocAccessibleParent*>(aFirstDoc)->GetAccessible(aFirst); + RemoteAccessible* last = + static_cast<DocAccessibleParent*>(aLastDoc)->GetAccessible(aLast); + + // We may not have proxy accessibles available yet for those accessibles + // in the parent process. + if (first && last) { + sessionAcc->UpdateAccessibleFocusBoundaries(first, last); + } + + return IPC_OK(); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/extension/android/DocAccessiblePlatformExtParent.h b/accessible/ipc/extension/android/DocAccessiblePlatformExtParent.h new file mode 100644 index 0000000000..b9278599bb --- /dev/null +++ b/accessible/ipc/extension/android/DocAccessiblePlatformExtParent.h @@ -0,0 +1,22 @@ +/* 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/. */ + +#ifndef mozilla_a11y_DocAccessiblePlatformExtParent_h +#define mozilla_a11y_DocAccessiblePlatformExtParent_h + +#include "mozilla/a11y/PDocAccessiblePlatformExtParent.h" + +namespace mozilla { +namespace a11y { +class DocAccessiblePlatformExtParent : public PDocAccessiblePlatformExtParent { + public: + mozilla::ipc::IPCResult RecvSetPivotBoundaries( + PDocAccessibleParent* aFirstDoc, uint64_t aFirst, + PDocAccessibleParent* aLastDoc, uint64_t aLast); +}; +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/extension/android/PDocAccessiblePlatformExt.ipdl b/accessible/ipc/extension/android/PDocAccessiblePlatformExt.ipdl new file mode 100644 index 0000000000..92b3d5f259 --- /dev/null +++ b/accessible/ipc/extension/android/PDocAccessiblePlatformExt.ipdl @@ -0,0 +1,35 @@ +/* -*- 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 protocol PDocAccessible; + +namespace mozilla { +namespace a11y { + +[ManualDealloc] +protocol PDocAccessiblePlatformExt { + manager PDocAccessible; + +child: + async __delete__(); + + async PivotTo(uint64_t aID, int32_t aGranularity, bool aForward, bool aInclusive); + + async NavigateText(uint64_t aID, int32_t aGranularity, int32_t aStartOffset, int32_t aEndOffset, bool aForward, bool aSelect); + + async SetSelection(uint64_t aID, int32_t aStart, int32_t aEnd); + + async Cut(uint64_t aID); + + async Copy(uint64_t aID); + + async Paste(uint64_t aID); + +parent: + async SetPivotBoundaries(PDocAccessible aFirstDoc, uint64_t aFirst, PDocAccessible aLastDoc, uint64_t aLast); + }; +} +} diff --git a/accessible/ipc/extension/android/moz.build b/accessible/ipc/extension/android/moz.build new file mode 100644 index 0000000000..50871e326b --- /dev/null +++ b/accessible/ipc/extension/android/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG["ACCESSIBILITY"]: + IPDL_SOURCES += ["PDocAccessiblePlatformExt.ipdl"] + + EXPORTS.mozilla.a11y += [ + "DocAccessiblePlatformExtChild.h", + "DocAccessiblePlatformExtParent.h", + ] + + SOURCES += [ + "DocAccessiblePlatformExtChild.cpp", + "DocAccessiblePlatformExtParent.cpp", + ] + + LOCAL_INCLUDES += [ + "/accessible/android", + "/accessible/generic", + "/accessible/ipc/other", + "/widget/android", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/accessible/ipc/extension/mac/DocAccessiblePlatformExtChild.cpp b/accessible/ipc/extension/mac/DocAccessiblePlatformExtChild.cpp new file mode 100644 index 0000000000..c435758240 --- /dev/null +++ b/accessible/ipc/extension/mac/DocAccessiblePlatformExtChild.cpp @@ -0,0 +1,281 @@ +/* -*- 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 "DocAccessiblePlatformExtChild.h" + +#include "AccAttributes.h" +#include "DocAccessibleChild.h" +#include "HyperTextAccessibleWrap.h" +#include "nsAccUtils.h" + +#define UNIQUE_ID(acc) \ + !acc || acc->Document() == acc ? 0 \ + : reinterpret_cast<uint64_t>(acc->UniqueID()) + +namespace mozilla { +namespace a11y { + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvRangeAt( + const uint64_t& aID, const int32_t& aOffset, const EWhichRange& aRangeType, + uint64_t* aStartContainer, int32_t* aStartOffset, uint64_t* aEndContainer, + int32_t* aEndOffset) { + *aStartContainer = 0; + *aStartOffset = 0; + *aEndContainer = 0; + *aEndOffset = 0; + + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + if (!acc) { + return IPC_OK(); + } + + HyperTextAccessible* startContainer = nullptr; + HyperTextAccessible* endContainer = nullptr; + + acc->RangeAt(aOffset, aRangeType, &startContainer, aStartOffset, + &endContainer, aEndOffset); + + MOZ_ASSERT(!startContainer || startContainer->Document() == acc->Document()); + MOZ_ASSERT(!endContainer || endContainer->Document() == acc->Document()); + + *aStartContainer = UNIQUE_ID(startContainer); + *aEndContainer = UNIQUE_ID(endContainer); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvNextClusterAt( + const uint64_t& aID, const int32_t& aOffset, uint64_t* aNextContainer, + int32_t* aNextOffset) { + *aNextContainer = 0; + *aNextOffset = 0; + + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + if (!acc) { + return IPC_OK(); + } + + HyperTextAccessible* nextContainer = nullptr; + + acc->NextClusterAt(aOffset, &nextContainer, aNextOffset); + + MOZ_ASSERT(!nextContainer || nextContainer->Document() == acc->Document()); + + *aNextContainer = UNIQUE_ID(nextContainer); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvPreviousClusterAt( + const uint64_t& aID, const int32_t& aOffset, uint64_t* aPrevContainer, + int32_t* aPrevOffset) { + *aPrevContainer = 0; + *aPrevOffset = 0; + + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + if (!acc) { + return IPC_OK(); + } + + HyperTextAccessible* prevContainer = nullptr; + + acc->PreviousClusterAt(aOffset, &prevContainer, aPrevOffset); + + MOZ_ASSERT(!prevContainer || prevContainer->Document() == acc->Document()); + + *aPrevContainer = UNIQUE_ID(prevContainer); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvTextForRange( + const uint64_t& aID, const int32_t& aStartOffset, + const uint64_t& aEndContainer, const int32_t& aEndOffset, nsString* aText) { + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + HyperTextAccessibleWrap* endContainer = + IdToHyperTextAccessibleWrap(aEndContainer); + if (!acc || !endContainer) { + return IPC_OK(); + } + + acc->TextForRange(*aText, aStartOffset, endContainer, aEndOffset); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvBoundsForRange( + const uint64_t& aID, const int32_t& aStartOffset, + const uint64_t& aEndContainer, const int32_t& aEndOffset, + LayoutDeviceIntRect* aBounds) { + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + HyperTextAccessibleWrap* endContainer = + IdToHyperTextAccessibleWrap(aEndContainer); + if (!acc || !endContainer) { + *aBounds = LayoutDeviceIntRect(); + return IPC_OK(); + } + + *aBounds = acc->BoundsForRange(aStartOffset, endContainer, aEndOffset); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvLengthForRange( + const uint64_t& aID, const int32_t& aStartOffset, + const uint64_t& aEndContainer, const int32_t& aEndOffset, + int32_t* aLength) { + *aLength = 0; + + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + HyperTextAccessibleWrap* endContainer = + IdToHyperTextAccessibleWrap(aEndContainer); + if (!acc || !endContainer) { + return IPC_OK(); + } + + *aLength = acc->LengthForRange(aStartOffset, endContainer, aEndOffset); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvOffsetAtIndex( + const uint64_t& aID, const int32_t& aIndex, uint64_t* aContainer, + int32_t* aOffset) { + *aContainer = 0; + *aOffset = 0; + + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + if (!acc) { + return IPC_OK(); + } + + HyperTextAccessible* container = nullptr; + + acc->OffsetAtIndex(aIndex, &container, aOffset); + + MOZ_ASSERT(!container || container->Document() == acc->Document()); + + *aContainer = UNIQUE_ID(container); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvRangeOfChild( + const uint64_t& aID, const uint64_t& aChild, int32_t* aStartOffset, + int32_t* aEndOffset) { + *aStartOffset = 0; + *aEndOffset = 0; + + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + LocalAccessible* child = + static_cast<DocAccessibleChild*>(Manager())->IdToAccessible(aChild); + if (!acc || !child) { + return IPC_OK(); + } + + acc->RangeOfChild(child, aStartOffset, aEndOffset); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvLeafAtOffset( + const uint64_t& aID, const int32_t& aOffset, uint64_t* aLeaf) { + *aLeaf = 0; + + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + if (!acc) { + return IPC_OK(); + } + + LocalAccessible* leaf = acc->LeafAtOffset(aOffset); + + MOZ_ASSERT(!leaf || leaf->Document() == acc->Document()); + + *aLeaf = UNIQUE_ID(leaf); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +DocAccessiblePlatformExtChild::RecvAttributedTextForRange( + const uint64_t& aID, const int32_t& aStartOffset, + const uint64_t& aEndContainer, const int32_t& aEndOffset, + nsTArray<TextAttributesRun>* aAttributes) { + HyperTextAccessibleWrap* acc = IdToHyperTextAccessibleWrap(aID); + HyperTextAccessibleWrap* endContainer = + IdToHyperTextAccessibleWrap(aEndContainer); + if (!acc || !endContainer) { + return IPC_OK(); + } + + nsTArray<nsString> texts; + nsTArray<LocalAccessible*> containers; + nsTArray<RefPtr<AccAttributes>> props; + + acc->AttributedTextForRange(texts, props, containers, aStartOffset, + endContainer, aEndOffset); + + MOZ_ASSERT(texts.Length() == props.Length() && + texts.Length() == containers.Length()); + + for (size_t i = 0; i < texts.Length(); i++) { + aAttributes->AppendElement(TextAttributesRun( + texts.ElementAt(i), UNIQUE_ID(containers.ElementAt(i)), + props.ElementAt(i))); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessiblePlatformExtChild::RecvSelectRange( + const uint64_t& aID, const int32_t& aStartOffset, + const uint64_t& aEndContainer, const int32_t& aEndOffset) { + RefPtr<HyperTextAccessibleWrap> acc = IdToHyperTextAccessibleWrap(aID); + RefPtr<HyperTextAccessibleWrap> endContainer = + IdToHyperTextAccessibleWrap(aEndContainer); + if (!acc || !endContainer) { + return IPC_OK(); + } + + acc->SelectRange(aStartOffset, endContainer, aEndOffset); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +DocAccessiblePlatformExtChild::RecvApplyPostSearchFilter( + const nsTArray<uint64_t>& aAccessibles, const int32_t& aLimit, + const EWhichPostFilter& aSearchKey, const nsString& aSearchText, + nsTArray<uint64_t>* aMatches) { + if (aSearchKey != EWhichPostFilter::eContainsText) { + return IPC_OK(); + } + + DocAccessibleChild* ipcDoc = static_cast<DocAccessibleChild*>(Manager()); + for (size_t i = 0; i < aAccessibles.Length(); i++) { + AccessibleWrap* acc = static_cast<AccessibleWrap*>( + ipcDoc->IdToAccessible(aAccessibles.ElementAt(i))); + if (!acc) { + continue; + } + + if (acc->ApplyPostFilter(aSearchKey, aSearchText)) { + aMatches->AppendElement(UNIQUE_ID(acc)); + } + } + + return IPC_OK(); +} + +HyperTextAccessibleWrap* +DocAccessiblePlatformExtChild::IdToHyperTextAccessibleWrap( + const uint64_t& aID) const { + return static_cast<HyperTextAccessibleWrap*>( + static_cast<DocAccessibleChild*>(Manager())->IdToHyperTextAccessible( + aID)); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/extension/mac/DocAccessiblePlatformExtChild.h b/accessible/ipc/extension/mac/DocAccessiblePlatformExtChild.h new file mode 100644 index 0000000000..c6f2580ccc --- /dev/null +++ b/accessible/ipc/extension/mac/DocAccessiblePlatformExtChild.h @@ -0,0 +1,87 @@ +/* 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/. */ + +#ifndef mozilla_a11y_DocAccessiblePlatformExtChild_h +#define mozilla_a11y_DocAccessiblePlatformExtChild_h + +#include "mozilla/a11y/PDocAccessiblePlatformExtChild.h" + +namespace mozilla { +namespace a11y { + +class HyperTextAccessibleWrap; +class DocAccessibleChild; + +class DocAccessiblePlatformExtChild : public PDocAccessiblePlatformExtChild { + public: + mozilla::ipc::IPCResult RecvRangeAt( + const uint64_t& aID, const int32_t& aOffset, + const EWhichRange& aRangeType, uint64_t* aStartContainer, + int32_t* aStartOffset, uint64_t* aEndContainer, int32_t* aEndOffset); + + mozilla::ipc::IPCResult RecvNextClusterAt(const uint64_t& aID, + const int32_t& aOffset, + uint64_t* aNextContainer, + int32_t* aNextOffset); + + mozilla::ipc::IPCResult RecvPreviousClusterAt(const uint64_t& aID, + const int32_t& aOffset, + uint64_t* aPrevContainer, + int32_t* aPrevOffset); + + mozilla::ipc::IPCResult RecvTextForRange(const uint64_t& aID, + const int32_t& aStartOffset, + const uint64_t& aEndContainer, + const int32_t& aEndOffset, + nsString* aText); + + mozilla::ipc::IPCResult RecvBoundsForRange(const uint64_t& aID, + const int32_t& aStartOffset, + const uint64_t& aEndContainer, + const int32_t& aEndOffset, + LayoutDeviceIntRect* aBounds); + + mozilla::ipc::IPCResult RecvLengthForRange(const uint64_t& aID, + const int32_t& aStartOffset, + const uint64_t& aEndContainer, + const int32_t& aEndOffset, + int32_t* aLength); + + mozilla::ipc::IPCResult RecvOffsetAtIndex(const uint64_t& aID, + const int32_t& aIndex, + uint64_t* aContainer, + int32_t* aOffset); + + mozilla::ipc::IPCResult RecvRangeOfChild(const uint64_t& aID, + const uint64_t& aChild, + int32_t* aStartOffset, + int32_t* aEndOffset); + + mozilla::ipc::IPCResult RecvLeafAtOffset(const uint64_t& aID, + const int32_t& aOffset, + uint64_t* aLeaf); + + mozilla::ipc::IPCResult RecvAttributedTextForRange( + const uint64_t& aID, const int32_t& aStartOffset, + const uint64_t& aEndContainer, const int32_t& aEndOffset, + nsTArray<TextAttributesRun>* aAttributes); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvSelectRange( + const uint64_t& aID, const int32_t& aStartOffset, + const uint64_t& aEndContainer, const int32_t& aEndOffset); + + mozilla::ipc::IPCResult RecvApplyPostSearchFilter( + const nsTArray<uint64_t>& aAccessibles, const int32_t& aLimit, + const EWhichPostFilter& aSearchKey, const nsString& aSearchText, + nsTArray<uint64_t>* aMatches); + + private: + HyperTextAccessibleWrap* IdToHyperTextAccessibleWrap( + const uint64_t& aID) const; +}; +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/extension/mac/DocAccessiblePlatformExtParent.h b/accessible/ipc/extension/mac/DocAccessiblePlatformExtParent.h new file mode 100644 index 0000000000..07e65c6e2d --- /dev/null +++ b/accessible/ipc/extension/mac/DocAccessiblePlatformExtParent.h @@ -0,0 +1,19 @@ +/* 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/. */ + +#ifndef mozilla_a11y_DocAccessiblePlatformExtParent_h +#define mozilla_a11y_DocAccessiblePlatformExtParent_h + +#include "mozilla/a11y/PDocAccessiblePlatformExtParent.h" + +namespace mozilla { +namespace a11y { +class DocAccessiblePlatformExtParent : public PDocAccessiblePlatformExtParent { + +}; +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/extension/mac/PDocAccessiblePlatformExt.ipdl b/accessible/ipc/extension/mac/PDocAccessiblePlatformExt.ipdl new file mode 100644 index 0000000000..82f9d18b47 --- /dev/null +++ b/accessible/ipc/extension/mac/PDocAccessiblePlatformExt.ipdl @@ -0,0 +1,73 @@ +/* -*- 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 protocol PDocAccessible; + +include "mozilla/GfxMessageUtils.h"; + +using mozilla::a11y::EWhichRange from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::EWhichPostFilter from "mozilla/a11y/IPCTypes.h"; +[RefCounted] using mozilla::a11y::AccAttributes from "mozilla/a11y/IPCTypes.h"; +using mozilla::LayoutDeviceIntRect from "Units.h"; + +namespace mozilla { +namespace a11y { + +struct TextAttributesRun { + nsString Text; + uint64_t ContainerID; + AccAttributes TextAttributes; +}; + + +[ManualDealloc, NestedUpTo=inside_sync] sync protocol PDocAccessiblePlatformExt { + manager PDocAccessible; + +child: + async __delete__(); + + [Nested=inside_sync] sync RangeAt(uint64_t aID, int32_t aOffset, EWhichRange aRangeType) + returns(uint64_t aStartContainer, int32_t aStartOffset, + uint64_t aEndContainer, int32_t aEndOffset); + + [Nested=inside_sync] sync NextClusterAt(uint64_t aID, int32_t aOffset) + returns(uint64_t aNextContainer, int32_t aNextOffset); + + [Nested=inside_sync] sync PreviousClusterAt(uint64_t aID, int32_t aOffset) + returns(uint64_t aNextContainer, int32_t aNextOffset); + + [Nested=inside_sync] sync TextForRange(uint64_t aID, int32_t aStartOffset, uint64_t aEndContainer, int32_t aEndOffset) + returns(nsString aText); + + [Nested=inside_sync] sync BoundsForRange(uint64_t aID, int32_t aStartOffset, uint64_t aEndContainer, int32_t aEndOffset) + returns(LayoutDeviceIntRect aRetVal); + + [Nested=inside_sync] sync LengthForRange(uint64_t aID, int32_t aStartOffset, uint64_t aEndContainer, int32_t aEndOffset) + returns(int32_t aLength); + + [Nested=inside_sync] sync OffsetAtIndex(uint64_t aID, int32_t aIndex) + returns(uint64_t aContainer, int32_t aOffset); + + [Nested=inside_sync] sync RangeOfChild(uint64_t aID, uint64_t aChild) + returns(int32_t aStartOffset, int32_t aEndOffset); + + [Nested=inside_sync] sync LeafAtOffset(uint64_t aID, int32_t aOffset) + returns(uint64_t aLeaf); + + [Nested=inside_sync] sync AttributedTextForRange(uint64_t aID, int32_t aStartOffset, uint64_t aEndContainer, int32_t aEndOffset) + returns(TextAttributesRun[] aAttributedText); + + async SelectRange(uint64_t aID, int32_t aStartOffset, uint64_t aEndContainer, int32_t aEndOffset); + + // A filter that can be applied to search predicate results. + [Nested=inside_sync] sync ApplyPostSearchFilter(uint64_t[] aAccessibles, int32_t aLimit, + EWhichPostFilter aSearchKey, nsString aSearchText) + returns(uint64_t[] aMatches); + +}; + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/extension/mac/moz.build b/accessible/ipc/extension/mac/moz.build new file mode 100644 index 0000000000..e7a7bf3bf8 --- /dev/null +++ b/accessible/ipc/extension/mac/moz.build @@ -0,0 +1,28 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG["ACCESSIBILITY"]: + IPDL_SOURCES += ["PDocAccessiblePlatformExt.ipdl"] + + EXPORTS.mozilla.a11y += [ + "DocAccessiblePlatformExtChild.h", + "DocAccessiblePlatformExtParent.h", + ] + + SOURCES += [ + "DocAccessiblePlatformExtChild.cpp", + ] + + LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/ipc/other", + "/accessible/mac", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/accessible/ipc/extension/moz.build b/accessible/ipc/extension/moz.build new file mode 100644 index 0000000000..3c82fd90b4 --- /dev/null +++ b/accessible/ipc/extension/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +toolkit = CONFIG["MOZ_WIDGET_TOOLKIT"] + +if toolkit == "android": + DIRS += ["android"] +elif toolkit == "cocoa": + DIRS += ["mac"] +else: + DIRS += ["other"] diff --git a/accessible/ipc/extension/other/DocAccessiblePlatformExtChild.h b/accessible/ipc/extension/other/DocAccessiblePlatformExtChild.h new file mode 100644 index 0000000000..6a6542bf71 --- /dev/null +++ b/accessible/ipc/extension/other/DocAccessiblePlatformExtChild.h @@ -0,0 +1,20 @@ +/* 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/. */ + +#ifndef mozilla_a11y_DocAccessiblePlatformExtChild_h +#define mozilla_a11y_DocAccessiblePlatformExtChild_h + +#include "mozilla/a11y/PDocAccessiblePlatformExtChild.h" + +namespace mozilla { +namespace a11y { + +class DocAccessibleChild; + +class DocAccessiblePlatformExtChild : public PDocAccessiblePlatformExtChild {}; +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/extension/other/DocAccessiblePlatformExtParent.h b/accessible/ipc/extension/other/DocAccessiblePlatformExtParent.h new file mode 100644 index 0000000000..e2e1cd907f --- /dev/null +++ b/accessible/ipc/extension/other/DocAccessiblePlatformExtParent.h @@ -0,0 +1,18 @@ +/* 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/. */ + +#ifndef mozilla_a11y_DocAccessiblePlatformExtParent_h +#define mozilla_a11y_DocAccessiblePlatformExtParent_h + +#include "mozilla/a11y/PDocAccessiblePlatformExtParent.h" + +namespace mozilla { +namespace a11y { +class DocAccessiblePlatformExtParent : public PDocAccessiblePlatformExtParent { +}; +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/extension/other/PDocAccessiblePlatformExt.ipdl b/accessible/ipc/extension/other/PDocAccessiblePlatformExt.ipdl new file mode 100644 index 0000000000..738505a743 --- /dev/null +++ b/accessible/ipc/extension/other/PDocAccessiblePlatformExt.ipdl @@ -0,0 +1,20 @@ +/* -*- 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 protocol PDocAccessible; + +namespace mozilla { +namespace a11y { + +[ManualDealloc] +protocol PDocAccessiblePlatformExt { + manager PDocAccessible; + +child: + async __delete__(); +}; + +}}
\ No newline at end of file diff --git a/accessible/ipc/extension/other/moz.build b/accessible/ipc/extension/other/moz.build new file mode 100644 index 0000000000..dbbc98702c --- /dev/null +++ b/accessible/ipc/extension/other/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG["ACCESSIBILITY"]: + IPDL_SOURCES += ["PDocAccessiblePlatformExt.ipdl"] + + EXPORTS.mozilla.a11y += [ + "DocAccessiblePlatformExtChild.h", + "DocAccessiblePlatformExtParent.h", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/accessible/ipc/moz.build b/accessible/ipc/moz.build new file mode 100644 index 0000000000..24c5bfadc0 --- /dev/null +++ b/accessible/ipc/moz.build @@ -0,0 +1,70 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG["OS_ARCH"] == "WINNT": + DIRS += ["win"] + LOCAL_INCLUDES += [ + "/accessible/ipc/win", + "/accessible/windows/ia2", + "/accessible/windows/msaa", + ] +else: + DIRS += ["other", "extension"] + LOCAL_INCLUDES += [ + "/accessible/ipc/other", + ] + if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + LOCAL_INCLUDES += [ + "/accessible/atk", + ] + elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + LOCAL_INCLUDES += [ + "/accessible/mac", + ] + elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + LOCAL_INCLUDES += [ + "/accessible/android", + ] + else: + LOCAL_INCLUDES += [ + "/accessible/other", + ] + +if CONFIG["ACCESSIBILITY"]: + IPDL_SOURCES += [ + "DocAccessibleTypes.ipdlh", + ] + +EXPORTS.mozilla.a11y += [ + "IPCTypes.h", +] + +if CONFIG["ACCESSIBILITY"]: + EXPORTS.mozilla.a11y += [ + "DocAccessibleChildBase.h", + "DocAccessibleParent.h", + "RemoteAccessibleBase.h", + "RemoteAccessibleShared.h", + ] + + UNIFIED_SOURCES += [ + "DocAccessibleChildBase.cpp", + "DocAccessibleParent.cpp", + "RemoteAccessibleBase.cpp", + ] + + LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/xpcom", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/accessible/ipc/other/DocAccessibleChild.cpp b/accessible/ipc/other/DocAccessibleChild.cpp new file mode 100644 index 0000000000..6a0987d07b --- /dev/null +++ b/accessible/ipc/other/DocAccessibleChild.cpp @@ -0,0 +1,1590 @@ +/* -*- 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 "DocAccessibleChild.h" + +#include "AccAttributes.h" +#include "nsAccessibilityService.h" +#include "LocalAccessible-inl.h" +#include "RemoteAccessible.h" +#include "Relation.h" +#include "HyperTextAccessible-inl.h" +#include "TextLeafAccessible.h" +#include "ImageAccessible.h" +#include "TableAccessible.h" +#include "TableCellAccessible.h" +#include "nsAccUtils.h" +#ifdef MOZ_ACCESSIBILITY_ATK +# include "AccessibleWrap.h" +#endif +#include "AccAttributes.h" +#include "mozilla/PresShell.h" +#include "mozilla/a11y/DocAccessiblePlatformExtChild.h" + +namespace mozilla { +namespace a11y { + +LocalAccessible* DocAccessibleChild::IdToAccessibleLink( + const uint64_t& aID) const { + LocalAccessible* acc = IdToAccessible(aID); + return acc && acc->IsLink() ? acc : nullptr; +} + +LocalAccessible* DocAccessibleChild::IdToAccessibleSelect( + const uint64_t& aID) const { + LocalAccessible* acc = IdToAccessible(aID); + return acc && acc->IsSelect() ? acc : nullptr; +} + +TextLeafAccessible* DocAccessibleChild::IdToTextLeafAccessible( + const uint64_t& aID) const { + LocalAccessible* acc = IdToAccessible(aID); + return acc && acc->IsTextLeaf() ? acc->AsTextLeaf() : nullptr; +} + +ImageAccessible* DocAccessibleChild::IdToImageAccessible( + const uint64_t& aID) const { + LocalAccessible* acc = IdToAccessible(aID); + return (acc && acc->IsImage()) ? acc->AsImage() : nullptr; +} + +TableCellAccessible* DocAccessibleChild::IdToTableCellAccessible( + const uint64_t& aID) const { + LocalAccessible* acc = IdToAccessible(aID); + return (acc && acc->IsTableCell()) ? acc->AsTableCell() : nullptr; +} + +TableAccessible* DocAccessibleChild::IdToTableAccessible( + const uint64_t& aID) const { + LocalAccessible* acc = IdToAccessible(aID); + return (acc && acc->IsTable()) ? acc->AsTable() : nullptr; +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvState(const uint64_t& aID, + uint64_t* aState) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) { + *aState = states::DEFUNCT; + return IPC_OK(); + } + + *aState = acc->State(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvNativeState(const uint64_t& aID, + uint64_t* aState) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) { + *aState = states::DEFUNCT; + return IPC_OK(); + } + + *aState = acc->NativeState(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvName(const uint64_t& aID, + nsString* aName, + uint32_t* aFlag) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) return IPC_OK(); + + *aFlag = acc->Name(*aName); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvValue(const uint64_t& aID, + nsString* aValue) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) { + return IPC_OK(); + } + + acc->Value(*aValue); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvHelp(const uint64_t& aID, + nsString* aHelp) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) { + return IPC_OK(); + } + + acc->Help(*aHelp); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvDescription(const uint64_t& aID, + nsString* aDesc) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) return IPC_OK(); + + acc->Description(*aDesc); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAttributes( + const uint64_t& aID, RefPtr<AccAttributes>* aAttributes) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) return IPC_OK(); + + *aAttributes = acc->Attributes(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvRelationByType( + const uint64_t& aID, const uint32_t& aType, nsTArray<uint64_t>* aTargets) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) return IPC_OK(); + + auto type = static_cast<RelationType>(aType); + Relation rel = acc->RelationByType(type); + while (LocalAccessible* target = rel.LocalNext()) { + aTargets->AppendElement(reinterpret_cast<uint64_t>(target->UniqueID())); + } + + return IPC_OK(); +} + +static void AddRelation(LocalAccessible* aAcc, RelationType aType, + nsTArray<RelationTargets>* aTargets) { + Relation rel = aAcc->RelationByType(aType); + nsTArray<uint64_t> targets; + while (LocalAccessible* target = rel.LocalNext()) { + targets.AppendElement(reinterpret_cast<uint64_t>(target->UniqueID())); + } + + if (!targets.IsEmpty()) { + RelationTargets* newRelation = aTargets->AppendElement( + RelationTargets(static_cast<uint32_t>(aType), nsTArray<uint64_t>())); + newRelation->Targets() = std::move(targets); + } +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvRelations( + const uint64_t& aID, nsTArray<RelationTargets>* aRelations) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) return IPC_OK(); + +#define RELATIONTYPE(gecko, s, a, m, i) \ + AddRelation(acc, RelationType::gecko, aRelations); + +#include "RelationTypeMap.h" +#undef RELATIONTYPE + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvIsSearchbox(const uint64_t& aID, + bool* aRetVal) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) return IPC_OK(); + + *aRetVal = acc->IsSearchbox(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvLandmarkRole( + const uint64_t& aID, nsString* aLandmark) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) { + return IPC_OK(); + } + + if (nsAtom* roleAtom = acc->LandmarkRole()) { + roleAtom->ToString(*aLandmark); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvGroupPosition( + const uint64_t& aID, int32_t* aLevel, int32_t* aSimilarItemsInGroup, + int32_t* aPositionInGroup) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + GroupPos groupPos = acc->GroupPosition(); + *aLevel = groupPos.level; + *aSimilarItemsInGroup = groupPos.setSize; + *aPositionInGroup = groupPos.posInSet; + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollToPoint( + const uint64_t& aID, const uint32_t& aScrollType, const int32_t& aX, + const int32_t& aY) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + acc->ScrollToPoint(aScrollType, aX, aY); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAnnounce( + const uint64_t& aID, const nsAString& aAnnouncement, + const uint16_t& aPriority) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + acc->Announce(aAnnouncement, aPriority); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvCaretLineNumber( + const uint64_t& aID, int32_t* aLineNumber) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + *aLineNumber = acc && acc->IsTextRole() ? acc->CaretLineNumber() : 0; + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvCaretOffset(const uint64_t& aID, + int32_t* aOffset) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + *aOffset = acc && acc->IsTextRole() ? acc->CaretOffset() : 0; + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvCharacterCount( + const uint64_t& aID, int32_t* aCount) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + *aCount = acc ? acc->CharacterCount() : 0; + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvSelectionCount( + const uint64_t& aID, int32_t* aCount) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + *aCount = acc ? acc->SelectionCount() : 0; + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTextSubstring( + const uint64_t& aID, const int32_t& aStartOffset, const int32_t& aEndOffset, + nsString* aText, bool* aValid) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) { + return IPC_OK(); + } + + TextLeafAccessible* leaf = acc->AsTextLeaf(); + if (leaf) { + if (aStartOffset != 0 || aEndOffset != -1) { + // We don't support fetching partial text from a leaf. + *aValid = false; + return IPC_OK(); + } + *aValid = true; + *aText = leaf->Text(); + return IPC_OK(); + } + + HyperTextAccessible* hyper = acc->AsHyperText(); + if (!hyper) { + return IPC_OK(); + } + + *aValid = hyper->IsValidRange(aStartOffset, aEndOffset); + hyper->TextSubstring(aStartOffset, aEndOffset, *aText); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvGetTextAfterOffset( + const uint64_t& aID, const int32_t& aOffset, const int32_t& aBoundaryType, + nsString* aText, int32_t* aStartOffset, int32_t* aEndOffset) { + *aStartOffset = 0; + *aEndOffset = 0; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc) { + acc->TextAfterOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset, + *aText); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvGetTextAtOffset( + const uint64_t& aID, const int32_t& aOffset, const int32_t& aBoundaryType, + nsString* aText, int32_t* aStartOffset, int32_t* aEndOffset) { + *aStartOffset = 0; + *aEndOffset = 0; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc) { + acc->TextAtOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset, *aText); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvGetTextBeforeOffset( + const uint64_t& aID, const int32_t& aOffset, const int32_t& aBoundaryType, + nsString* aText, int32_t* aStartOffset, int32_t* aEndOffset) { + *aStartOffset = 0; + *aEndOffset = 0; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc) { + acc->TextBeforeOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset, + *aText); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvCharAt(const uint64_t& aID, + const int32_t& aOffset, + uint16_t* aChar) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + *aChar = acc && acc->IsTextRole() + ? static_cast<uint16_t>(acc->CharAt(aOffset)) + : 0; + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTextAttributes( + const uint64_t& aID, const bool& aIncludeDefAttrs, const int32_t& aOffset, + RefPtr<AccAttributes>* aAttributes, int32_t* aStartOffset, + int32_t* aEndOffset) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (!acc || !acc->IsTextRole()) { + return IPC_OK(); + } + + *aAttributes = + acc->TextAttributes(aIncludeDefAttrs, aOffset, aStartOffset, aEndOffset); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvDefaultTextAttributes( + const uint64_t& aID, RefPtr<AccAttributes>* aAttributes) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (!acc || !acc->IsTextRole()) { + return IPC_OK(); + } + + *aAttributes = acc->DefaultTextAttributes(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTextBounds( + const uint64_t& aID, const int32_t& aStartOffset, const int32_t& aEndOffset, + const uint32_t& aCoordType, LayoutDeviceIntRect* aRetVal) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aRetVal = acc->TextBounds(aStartOffset, aEndOffset, aCoordType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvCharBounds( + const uint64_t& aID, const int32_t& aOffset, const uint32_t& aCoordType, + LayoutDeviceIntRect* aRetVal) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aRetVal = acc->CharBounds(aOffset, aCoordType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvOffsetAtPoint( + const uint64_t& aID, const int32_t& aX, const int32_t& aY, + const uint32_t& aCoordType, int32_t* aRetVal) { + *aRetVal = -1; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aRetVal = acc->OffsetAtPoint(aX, aY, aCoordType); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvSelectionBoundsAt( + const uint64_t& aID, const int32_t& aSelectionNum, bool* aSucceeded, + nsString* aData, int32_t* aStartOffset, int32_t* aEndOffset) { + *aSucceeded = false; + *aStartOffset = 0; + *aEndOffset = 0; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aSucceeded = + acc->SelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset); + if (*aSucceeded) { + acc->TextSubstring(*aStartOffset, *aEndOffset, *aData); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvSetSelectionBoundsAt( + const uint64_t& aID, const int32_t& aSelectionNum, + const int32_t& aStartOffset, const int32_t& aEndOffset, bool* aSucceeded) { + *aSucceeded = false; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aSucceeded = + acc->SetSelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAddToSelection( + const uint64_t& aID, const int32_t& aStartOffset, const int32_t& aEndOffset, + bool* aSucceeded) { + *aSucceeded = false; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aSucceeded = acc->AddToSelection(aStartOffset, aEndOffset); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvRemoveFromSelection( + const uint64_t& aID, const int32_t& aSelectionNum, bool* aSucceeded) { + *aSucceeded = false; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aSucceeded = acc->RemoveFromSelection(aSelectionNum); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollSubstringTo( + const uint64_t& aID, const int32_t& aStartOffset, const int32_t& aEndOffset, + const uint32_t& aScrollType) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc) { + acc->ScrollSubstringTo(aStartOffset, aEndOffset, aScrollType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollSubstringToPoint( + const uint64_t& aID, const int32_t& aStartOffset, const int32_t& aEndOffset, + const uint32_t& aCoordinateType, const int32_t& aX, const int32_t& aY) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc) { + acc->ScrollSubstringToPoint(aStartOffset, aEndOffset, aCoordinateType, aX, + aY); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvText(const uint64_t& aID, + nsString* aText) { + TextLeafAccessible* acc = IdToTextLeafAccessible(aID); + if (acc) { + *aText = acc->Text(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvReplaceText( + const uint64_t& aID, const nsAString& aText) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + acc->ReplaceText(aText); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvInsertText( + const uint64_t& aID, const nsAString& aText, const int32_t& aPosition, + bool* aValid) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aValid = acc->IsValidOffset(aPosition); + acc->InsertText(aText, aPosition); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvCopyText( + const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos, + bool* aValid) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + acc->CopyText(aStartPos, aEndPos); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvCutText( + const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos, + bool* aValid) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aValid = acc->IsValidRange(aStartPos, aEndPos); + acc->CutText(aStartPos, aEndPos); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvDeleteText( + const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos, + bool* aValid) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aValid = acc->IsValidRange(aStartPos, aEndPos); + acc->DeleteText(aStartPos, aEndPos); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvPasteText( + const uint64_t& aID, const int32_t& aPosition, bool* aValid) { + RefPtr<HyperTextAccessible> acc = IdToHyperTextAccessible(aID); + if (acc && acc->IsTextRole()) { + *aValid = acc->IsValidOffset(aPosition); + acc->PasteText(aPosition); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvImagePosition( + const uint64_t& aID, const uint32_t& aCoordType, + LayoutDeviceIntPoint* aRetVal) { + ImageAccessible* acc = IdToImageAccessible(aID); + if (acc) { + *aRetVal = acc->Position(aCoordType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvImageSize( + const uint64_t& aID, LayoutDeviceIntSize* aRetVal) { + ImageAccessible* acc = IdToImageAccessible(aID); + if (acc) { + *aRetVal = acc->Size(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvStartOffset(const uint64_t& aID, + uint32_t* aRetVal, + bool* aOk) { + LocalAccessible* acc = IdToAccessibleLink(aID); + if (acc) { + *aRetVal = acc->StartOffset(); + *aOk = true; + } else { + *aRetVal = 0; + *aOk = false; + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvEndOffset(const uint64_t& aID, + uint32_t* aRetVal, + bool* aOk) { + LocalAccessible* acc = IdToAccessibleLink(aID); + if (acc) { + *aRetVal = acc->EndOffset(); + *aOk = true; + } else { + *aRetVal = 0; + *aOk = false; + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvIsLinkValid(const uint64_t& aID, + bool* aRetVal) { + LocalAccessible* acc = IdToAccessibleLink(aID); + if (acc) { + *aRetVal = acc->IsLinkValid(); + } else { + *aRetVal = false; + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAnchorCount(const uint64_t& aID, + uint32_t* aRetVal, + bool* aOk) { + LocalAccessible* acc = IdToAccessibleLink(aID); + if (acc) { + *aRetVal = acc->AnchorCount(); + *aOk = true; + } else { + *aRetVal = 0; + *aOk = false; + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAnchorURIAt( + const uint64_t& aID, const uint32_t& aIndex, nsCString* aURI, bool* aOk) { + LocalAccessible* acc = IdToAccessibleLink(aID); + *aOk = false; + if (acc) { + nsCOMPtr<nsIURI> uri = acc->AnchorURIAt(aIndex); + if (uri) { + uri->GetSpec(*aURI); + *aOk = true; + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAnchorAt(const uint64_t& aID, + const uint32_t& aIndex, + uint64_t* aIDOfAnchor, + bool* aOk) { + *aIDOfAnchor = 0; + *aOk = false; + LocalAccessible* acc = IdToAccessibleLink(aID); + if (acc) { + LocalAccessible* anchor = acc->AnchorAt(aIndex); + if (anchor) { + *aIDOfAnchor = reinterpret_cast<uint64_t>(anchor->UniqueID()); + *aOk = true; + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvLinkCount(const uint64_t& aID, + uint32_t* aCount) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + *aCount = acc ? acc->LinkCount() : 0; + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvLinkAt(const uint64_t& aID, + const uint32_t& aIndex, + uint64_t* aIDOfLink, + bool* aOk) { + *aIDOfLink = 0; + *aOk = false; + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + if (acc) { + LocalAccessible* link = acc->LinkAt(aIndex); + if (link) { + *aIDOfLink = reinterpret_cast<uint64_t>(link->UniqueID()); + *aOk = true; + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvLinkIndexAtOffset( + const uint64_t& aID, const uint32_t& aOffset, int32_t* aIndex) { + HyperTextAccessible* acc = IdToHyperTextAccessible(aID); + *aIndex = acc ? acc->LinkIndexAtOffset(aOffset) : -1; + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableOfACell( + const uint64_t& aID, uint64_t* aTableID, bool* aOk) { + *aTableID = 0; + *aOk = false; + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + TableAccessible* table = acc->Table(); + if (table) { + *aTableID = reinterpret_cast<uint64_t>(table->AsAccessible()->UniqueID()); + *aOk = true; + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvColIdx(const uint64_t& aID, + uint32_t* aIndex) { + *aIndex = 0; + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + *aIndex = acc->ColIdx(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvRowIdx(const uint64_t& aID, + uint32_t* aIndex) { + *aIndex = 0; + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + *aIndex = acc->RowIdx(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvGetPosition(const uint64_t& aID, + uint32_t* aRowIdx, + uint32_t* aColIdx) { + *aColIdx = 0; + *aRowIdx = 0; + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + *aColIdx = acc->ColIdx(); + *aRowIdx = acc->RowIdx(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvGetColRowExtents( + const uint64_t& aID, uint32_t* aColIdx, uint32_t* aRowIdx, + uint32_t* aColExtent, uint32_t* aRowExtent) { + *aColIdx = 0; + *aRowIdx = 0; + *aColExtent = 0; + *aRowExtent = 0; + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + *aColIdx = acc->ColIdx(); + *aRowIdx = acc->RowIdx(); + *aColExtent = acc->ColExtent(); + *aRowExtent = acc->RowExtent(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvColExtent(const uint64_t& aID, + uint32_t* aExtent) { + *aExtent = 0; + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + *aExtent = acc->ColExtent(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvRowExtent(const uint64_t& aID, + uint32_t* aExtent) { + *aExtent = 0; + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + *aExtent = acc->RowExtent(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvColHeaderCells( + const uint64_t& aID, nsTArray<uint64_t>* aCells) { + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + AutoTArray<Accessible*, 10> headerCells; + acc->ColHeaderCells(&headerCells); + aCells->SetCapacity(headerCells.Length()); + for (Accessible* header : headerCells) { + aCells->AppendElement(header->ID()); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvRowHeaderCells( + const uint64_t& aID, nsTArray<uint64_t>* aCells) { + TableCellAccessible* acc = IdToTableCellAccessible(aID); + if (acc) { + AutoTArray<Accessible*, 10> headerCells; + acc->RowHeaderCells(&headerCells); + aCells->SetCapacity(headerCells.Length()); + for (Accessible* header : headerCells) { + aCells->AppendElement(header->ID()); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvIsCellSelected( + const uint64_t& aID, bool* aSelected) { + TableCellAccessible* acc = IdToTableCellAccessible(aID); + *aSelected = acc && acc->Selected(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableCaption( + const uint64_t& aID, uint64_t* aCaptionID, bool* aOk) { + *aCaptionID = 0; + *aOk = false; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + LocalAccessible* caption = acc->Caption(); + if (caption) { + *aCaptionID = reinterpret_cast<uint64_t>(caption->UniqueID()); + *aOk = true; + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSummary( + const uint64_t& aID, nsString* aSummary) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->Summary(*aSummary); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableColumnCount( + const uint64_t& aID, uint32_t* aColCount) { + *aColCount = 0; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aColCount = acc->ColCount(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableRowCount( + const uint64_t& aID, uint32_t* aRowCount) { + *aRowCount = 0; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aRowCount = acc->RowCount(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableCellAt( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + uint64_t* aCellID, bool* aOk) { + *aCellID = 0; + *aOk = false; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + LocalAccessible* cell = acc->CellAt(aRow, aCol); + if (cell) { + *aCellID = reinterpret_cast<uint64_t>(cell->UniqueID()); + *aOk = true; + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableCellIndexAt( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + int32_t* aIndex) { + *aIndex = -1; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aIndex = acc->CellIndexAt(aRow, aCol); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableColumnIndexAt( + const uint64_t& aID, const uint32_t& aCellIndex, int32_t* aCol) { + *aCol = -1; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aCol = acc->ColIndexAt(aCellIndex); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableRowIndexAt( + const uint64_t& aID, const uint32_t& aCellIndex, int32_t* aRow) { + *aRow = -1; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aRow = acc->RowIndexAt(aCellIndex); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableRowAndColumnIndicesAt( + const uint64_t& aID, const uint32_t& aCellIndex, int32_t* aRow, + int32_t* aCol) { + *aRow = -1; + *aCol = -1; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->RowAndColIndicesAt(aCellIndex, aRow, aCol); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableColumnExtentAt( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + uint32_t* aExtent) { + *aExtent = 0; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aExtent = acc->ColExtentAt(aRow, aCol); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableRowExtentAt( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + uint32_t* aExtent) { + *aExtent = 0; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aExtent = acc->RowExtentAt(aRow, aCol); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableColumnDescription( + const uint64_t& aID, const uint32_t& aCol, nsString* aDescription) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->ColDescription(aCol, *aDescription); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableRowDescription( + const uint64_t& aID, const uint32_t& aRow, nsString* aDescription) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->RowDescription(aRow, *aDescription); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableColumnSelected( + const uint64_t& aID, const uint32_t& aCol, bool* aSelected) { + *aSelected = false; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aSelected = acc->IsColSelected(aCol); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableRowSelected( + const uint64_t& aID, const uint32_t& aRow, bool* aSelected) { + *aSelected = false; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aSelected = acc->IsRowSelected(aRow); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableCellSelected( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + bool* aSelected) { + *aSelected = false; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aSelected = acc->IsCellSelected(aRow, aCol); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectedCellCount( + const uint64_t& aID, uint32_t* aSelectedCells) { + *aSelectedCells = 0; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aSelectedCells = acc->SelectedCellCount(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectedColumnCount( + const uint64_t& aID, uint32_t* aSelectedColumns) { + *aSelectedColumns = 0; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aSelectedColumns = acc->SelectedColCount(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectedRowCount( + const uint64_t& aID, uint32_t* aSelectedRows) { + *aSelectedRows = 0; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aSelectedRows = acc->SelectedRowCount(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectedCells( + const uint64_t& aID, nsTArray<uint64_t>* aCellIDs) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + AutoTArray<Accessible*, 30> cells; + acc->SelectedCells(&cells); + aCellIDs->SetCapacity(cells.Length()); + for (Accessible* cell : cells) { + aCellIDs->AppendElement(cell->ID()); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectedCellIndices( + const uint64_t& aID, nsTArray<uint32_t>* aCellIndices) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->SelectedCellIndices(aCellIndices); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectedColumnIndices( + const uint64_t& aID, nsTArray<uint32_t>* aColumnIndices) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->SelectedColIndices(aColumnIndices); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectedRowIndices( + const uint64_t& aID, nsTArray<uint32_t>* aRowIndices) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->SelectedRowIndices(aRowIndices); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectColumn( + const uint64_t& aID, const uint32_t& aCol) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->SelectCol(aCol); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableSelectRow( + const uint64_t& aID, const uint32_t& aRow) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->SelectRow(aRow); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableUnselectColumn( + const uint64_t& aID, const uint32_t& aCol) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->UnselectCol(aCol); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableUnselectRow( + const uint64_t& aID, const uint32_t& aRow) { + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + acc->UnselectRow(aRow); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTableIsProbablyForLayout( + const uint64_t& aID, bool* aForLayout) { + *aForLayout = false; + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + *aForLayout = acc->IsProbablyLayoutTable(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAtkTableColumnHeader( + const uint64_t& aID, const int32_t& aCol, uint64_t* aHeader, bool* aOk) { + *aHeader = 0; + *aOk = false; + +#ifdef MOZ_ACCESSIBILITY_ATK + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + Accessible* header = AccessibleWrap::GetColumnHeader(acc, aCol); + if (header) { + *aHeader = header->ID(); + *aOk = true; + } + } +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAtkTableRowHeader( + const uint64_t& aID, const int32_t& aRow, uint64_t* aHeader, bool* aOk) { + *aHeader = 0; + *aOk = false; + +#ifdef MOZ_ACCESSIBILITY_ATK + TableAccessible* acc = IdToTableAccessible(aID); + if (acc) { + Accessible* header = AccessibleWrap::GetRowHeader(acc, aRow); + if (header) { + *aHeader = header->ID(); + *aOk = true; + } + } +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvSelectedItems( + const uint64_t& aID, nsTArray<uint64_t>* aSelectedItemIDs) { + LocalAccessible* acc = IdToAccessibleSelect(aID); + if (acc) { + AutoTArray<Accessible*, 10> selectedItems; + acc->SelectedItems(&selectedItems); + aSelectedItemIDs->SetCapacity(selectedItems.Length()); + for (Accessible* item : selectedItems) { + aSelectedItemIDs->AppendElement( + reinterpret_cast<uint64_t>(item->AsLocal()->UniqueID())); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvSelectedItemCount( + const uint64_t& aID, uint32_t* aCount) { + *aCount = 0; + LocalAccessible* acc = IdToAccessibleSelect(aID); + if (acc) { + *aCount = acc->SelectedItemCount(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvGetSelectedItem( + const uint64_t& aID, const uint32_t& aIndex, uint64_t* aSelected, + bool* aOk) { + LocalAccessible* acc = IdToAccessibleSelect(aID); + if (acc) { + Accessible* selected = acc->GetSelectedItem(aIndex); + *aSelected = reinterpret_cast<uint64_t>(selected->AsLocal()->UniqueID()); + } + + *aOk = !!acc; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvIsItemSelected( + const uint64_t& aID, const uint32_t& aIndex, bool* aSelected) { + *aSelected = false; + LocalAccessible* acc = IdToAccessibleSelect(aID); + if (acc) { + *aSelected = acc->IsItemSelected(aIndex); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAddItemToSelection( + const uint64_t& aID, const uint32_t& aIndex, bool* aSuccess) { + *aSuccess = false; + LocalAccessible* acc = IdToAccessibleSelect(aID); + if (acc) { + *aSuccess = acc->AddItemToSelection(aIndex); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvRemoveItemFromSelection( + const uint64_t& aID, const uint32_t& aIndex, bool* aSuccess) { + *aSuccess = false; + LocalAccessible* acc = IdToAccessibleSelect(aID); + if (acc) { + *aSuccess = acc->RemoveItemFromSelection(aIndex); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvSelectAll(const uint64_t& aID, + bool* aSuccess) { + *aSuccess = false; + LocalAccessible* acc = IdToAccessibleSelect(aID); + if (acc) { + *aSuccess = acc->SelectAll(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvUnselectAll(const uint64_t& aID, + bool* aSuccess) { + *aSuccess = false; + LocalAccessible* acc = IdToAccessibleSelect(aID); + if (acc) { + *aSuccess = acc->UnselectAll(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvDoAction(const uint64_t& aID, + const uint8_t& aIndex, + bool* aSuccess) { + *aSuccess = false; + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + *aSuccess = acc->DoAction(aIndex); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvActionCount(const uint64_t& aID, + uint8_t* aCount) { + *aCount = 0; + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + *aCount = acc->ActionCount(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvActionNameAt( + const uint64_t& aID, const uint8_t& aIndex, nsString* aName) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + acc->ActionNameAt(aIndex, *aName); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAccessKey( + const uint64_t& aID, uint32_t* aKey, uint32_t* aModifierMask) { + *aKey = 0; + *aModifierMask = 0; + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + KeyBinding kb = acc->AccessKey(); + *aKey = kb.Key(); + *aModifierMask = kb.ModifierMask(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvAtkKeyBinding( + const uint64_t& aID, nsString* aResult) { +#ifdef MOZ_ACCESSIBILITY_ATK + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + AccessibleWrap::GetKeyBinding(acc, *aResult); + } +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvCurValue(const uint64_t& aID, + double* aValue) { + *aValue = UnspecifiedNaN<double>(); + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + *aValue = acc->CurValue(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvSetCurValue( + const uint64_t& aID, const double& aValue, bool* aRetVal) { + *aRetVal = false; + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + *aRetVal = acc->SetCurValue(aValue); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvMinValue(const uint64_t& aID, + double* aValue) { + *aValue = UnspecifiedNaN<double>(); + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + *aValue = acc->MinValue(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvMaxValue(const uint64_t& aID, + double* aValue) { + *aValue = UnspecifiedNaN<double>(); + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + *aValue = acc->MaxValue(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvStep(const uint64_t& aID, + double* aStep) { + *aStep = UnspecifiedNaN<double>(); + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + *aStep = acc->Step(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvLanguage(const uint64_t& aID, + nsString* aLocale) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + acc->Language(*aLocale); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvDocType(const uint64_t& aID, + nsString* aType) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc && acc->IsDoc()) { + acc->AsDoc()->DocType(*aType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvTitle(const uint64_t& aID, + nsString* aTitle) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc) { + mozilla::ErrorResult rv; + acc->GetContent()->GetTextContent(*aTitle, rv); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvMimeType(const uint64_t& aID, + nsString* aMime) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc && acc->IsDoc()) { + acc->AsDoc()->MimeType(*aMime); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvURLDocTypeMimeType( + const uint64_t& aID, nsString* aURL, nsString* aDocType, + nsString* aMimeType) { + LocalAccessible* acc = IdToAccessible(aID); + if (acc && acc->IsDoc()) { + DocAccessible* doc = acc->AsDoc(); + doc->URL(*aURL); + doc->DocType(*aDocType); + doc->MimeType(*aMimeType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvChildAtPoint( + const uint64_t& aID, const int32_t& aX, const int32_t& aY, + const uint32_t& aWhich, PDocAccessibleChild** aResultDoc, + uint64_t* aResultID) { + *aResultDoc = nullptr; + *aResultID = 0; + LocalAccessible* acc = IdToAccessible(aID); + if (acc && !acc->IsDefunct()) { + int32_t x = aX; + int32_t y = aY; + LocalAccessible* result = acc->LocalChildAtPoint( + x, y, static_cast<LocalAccessible::EWhichChildAtPoint>(aWhich)); + if (result) { + // LocalAccessible::ChildAtPoint can return a LocalAccessible from a + // descendant document. + DocAccessibleChild* resultDoc = result->Document()->IPCDoc(); + // We've sent the constructor for this document to the parent process. + // However, because the constructor is async, the parent process might + // get the result of this (sync) method before it runs the constructor. + // If we send this document in this case, the parent process will crash. + // Therefore, we only do this if the parent process has explicitly told + // us that the document has been constructed there. + if (resultDoc && resultDoc->IsConstructedInParentProcess()) { + *aResultDoc = resultDoc; + *aResultID = result->IsDoc() + ? 0 + : reinterpret_cast<uint64_t>(result->UniqueID()); + } + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvExtents( + const uint64_t& aID, const bool& aNeedsScreenCoords, int32_t* aX, + int32_t* aY, int32_t* aWidth, int32_t* aHeight) { + *aX = 0; + *aY = 0; + *aWidth = 0; + *aHeight = 0; + LocalAccessible* acc = IdToAccessible(aID); + if (acc && !acc->IsDefunct()) { + LayoutDeviceIntRect screenRect = acc->Bounds(); + if (!screenRect.IsEmpty()) { + if (aNeedsScreenCoords) { + LayoutDeviceIntPoint winCoords = + nsAccUtils::GetScreenCoordsForWindow(acc); + screenRect.x -= winCoords.x; + screenRect.y -= winCoords.y; + } + + *aWidth = screenRect.width; + *aHeight = screenRect.height; + } + // We should always report the position of our acc, even if + // the returned screenRect is empty. + *aX = screenRect.x; + *aY = screenRect.y; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvExtentsInCSSPixels( + const uint64_t& aID, int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) { + *aX = 0; + *aY = 0; + *aWidth = 0; + *aHeight = 0; + LocalAccessible* acc = IdToAccessible(aID); + if (acc && !acc->IsDefunct()) { + nsIntRect screenRect = acc->BoundsInCSSPixels(); + if (!screenRect.IsEmpty()) { + *aWidth = screenRect.width; + *aHeight = screenRect.height; + } + // We should always report the position of our acc, even if + // the returned screenRect is empty. + *aX = screenRect.x; + *aY = screenRect.y; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvDOMNodeID( + const uint64_t& aID, nsString* aDOMNodeID) { + LocalAccessible* acc = IdToAccessible(aID); + if (!acc) { + return IPC_OK(); + } + + nsIContent* content = acc->GetContent(); + if (!content) { + return IPC_OK(); + } + + nsAtom* id = content->GetID(); + if (id) { + id->ToString(*aDOMNodeID); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvConstructedInParentProcess() { + SetConstructedInParentProcess(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult DocAccessibleChild::RecvRestoreFocus() { + FocusMgr()->ForceFocusEvent(); + return IPC_OK(); +} + +bool DocAccessibleChild::DeallocPDocAccessiblePlatformExtChild( + PDocAccessiblePlatformExtChild* aActor) { + delete aActor; + return true; +} + +PDocAccessiblePlatformExtChild* +DocAccessibleChild::AllocPDocAccessiblePlatformExtChild() { + return new DocAccessiblePlatformExtChild(); +} + +DocAccessiblePlatformExtChild* DocAccessibleChild::GetPlatformExtension() { + return static_cast<DocAccessiblePlatformExtChild*>( + SingleManagedOrNull(ManagedPDocAccessiblePlatformExtChild())); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/other/DocAccessibleChild.h b/accessible/ipc/other/DocAccessibleChild.h new file mode 100644 index 0000000000..8973cb6eba --- /dev/null +++ b/accessible/ipc/other/DocAccessibleChild.h @@ -0,0 +1,465 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_DocAccessibleChild_h +#define mozilla_a11y_DocAccessibleChild_h + +#include "mozilla/a11y/DocAccessibleChildBase.h" + +namespace mozilla { +namespace a11y { + +class LocalAccessible; +class DocAccessiblePlatformExtChild; +class HyperTextAccessible; +class TextLeafAccessible; +class ImageAccessible; +class TableAccessible; +class TableCellAccessible; + +/* + * These objects handle content side communication for an accessible document, + * and their lifetime is the same as the document they represent. + */ +class DocAccessibleChild : public DocAccessibleChildBase { + friend DocAccessiblePlatformExtChild; + + public: + DocAccessibleChild(DocAccessible* aDoc, IProtocol* aManager) + : DocAccessibleChildBase(aDoc) { + MOZ_COUNT_CTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase); + SetManager(aManager); + } + + ~DocAccessibleChild() { + MOZ_COUNT_DTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase); + } + + virtual mozilla::ipc::IPCResult RecvConstructedInParentProcess() override; + virtual mozilla::ipc::IPCResult RecvRestoreFocus() override; + + /* + * Return the state for the accessible with given ID. + */ + virtual mozilla::ipc::IPCResult RecvState(const uint64_t& aID, + uint64_t* aState) override; + + /* + * Return the native state for the accessible with given ID. + */ + virtual mozilla::ipc::IPCResult RecvNativeState(const uint64_t& aID, + uint64_t* aState) override; + + /* + * Get the name for the accessible with given id. + */ + virtual mozilla::ipc::IPCResult RecvName(const uint64_t& aID, nsString* aName, + uint32_t* aFlag) override; + + virtual mozilla::ipc::IPCResult RecvValue(const uint64_t& aID, + nsString* aValue) override; + + virtual mozilla::ipc::IPCResult RecvHelp(const uint64_t& aID, + nsString* aHelp) override; + + /* + * Get the description for the accessible with given id. + */ + virtual mozilla::ipc::IPCResult RecvDescription(const uint64_t& aID, + nsString* aDesc) override; + virtual mozilla::ipc::IPCResult RecvRelationByType( + const uint64_t& aID, const uint32_t& aType, + nsTArray<uint64_t>* aTargets) override; + virtual mozilla::ipc::IPCResult RecvRelations( + const uint64_t& aID, nsTArray<RelationTargets>* aRelations) override; + + virtual mozilla::ipc::IPCResult RecvIsSearchbox(const uint64_t& aID, + bool* aRetVal) override; + + virtual mozilla::ipc::IPCResult RecvLandmarkRole( + const uint64_t& aID, nsString* aLandmark) override; + + virtual mozilla::ipc::IPCResult RecvGroupPosition( + const uint64_t& aID, int32_t* aLevel, int32_t* aSimilarItemsInGroup, + int32_t* aPositionInGroup) override; + + virtual mozilla::ipc::IPCResult RecvAttributes( + const uint64_t& aID, RefPtr<AccAttributes>* aAttributes) override; + virtual mozilla::ipc::IPCResult RecvScrollToPoint(const uint64_t& aID, + const uint32_t& aScrollType, + const int32_t& aX, + const int32_t& aY) override; + + virtual mozilla::ipc::IPCResult RecvAnnounce( + const uint64_t& aID, const nsAString& aAnnouncement, + const uint16_t& aPriority) override; + + virtual mozilla::ipc::IPCResult RecvCaretLineNumber( + const uint64_t& aID, int32_t* aLineNumber) override; + virtual mozilla::ipc::IPCResult RecvCaretOffset(const uint64_t& aID, + int32_t* aOffset) override; + + virtual mozilla::ipc::IPCResult RecvCharacterCount(const uint64_t& aID, + int32_t* aCount) override; + virtual mozilla::ipc::IPCResult RecvSelectionCount(const uint64_t& aID, + int32_t* aCount) override; + + virtual mozilla::ipc::IPCResult RecvTextSubstring(const uint64_t& aID, + const int32_t& aStartOffset, + const int32_t& aEndOffset, + nsString* aText, + bool* aValid) override; + + virtual mozilla::ipc::IPCResult RecvGetTextAfterOffset( + const uint64_t& aID, const int32_t& aOffset, const int32_t& aBoundaryType, + nsString* aText, int32_t* aStartOffset, int32_t* aEndOffset) override; + virtual mozilla::ipc::IPCResult RecvGetTextAtOffset( + const uint64_t& aID, const int32_t& aOffset, const int32_t& aBoundaryType, + nsString* aText, int32_t* aStartOffset, int32_t* aEndOffset) override; + virtual mozilla::ipc::IPCResult RecvGetTextBeforeOffset( + const uint64_t& aID, const int32_t& aOffset, const int32_t& aBoundaryType, + nsString* aText, int32_t* aStartOffset, int32_t* aEndOffset) override; + + virtual mozilla::ipc::IPCResult RecvCharAt(const uint64_t& aID, + const int32_t& aOffset, + uint16_t* aChar) override; + + virtual mozilla::ipc::IPCResult RecvTextAttributes( + const uint64_t& aID, const bool& aIncludeDefAttrs, const int32_t& aOffset, + RefPtr<AccAttributes>* aAttributes, int32_t* aStartOffset, + int32_t* aEndOffset) override; + + virtual mozilla::ipc::IPCResult RecvDefaultTextAttributes( + const uint64_t& aID, RefPtr<AccAttributes>* aAttributes) override; + + virtual mozilla::ipc::IPCResult RecvTextBounds( + const uint64_t& aID, const int32_t& aStartOffset, + const int32_t& aEndOffset, const uint32_t& aCoordType, + LayoutDeviceIntRect* aRetVal) override; + + virtual mozilla::ipc::IPCResult RecvCharBounds( + const uint64_t& aID, const int32_t& aOffset, const uint32_t& aCoordType, + LayoutDeviceIntRect* aRetVal) override; + + virtual mozilla::ipc::IPCResult RecvOffsetAtPoint(const uint64_t& aID, + const int32_t& aX, + const int32_t& aY, + const uint32_t& aCoordType, + int32_t* aRetVal) override; + + virtual mozilla::ipc::IPCResult RecvSelectionBoundsAt( + const uint64_t& aID, const int32_t& aSelectionNum, bool* aSucceeded, + nsString* aData, int32_t* aStartOffset, int32_t* aEndOffset) override; + + virtual mozilla::ipc::IPCResult RecvSetSelectionBoundsAt( + const uint64_t& aID, const int32_t& aSelectionNum, + const int32_t& aStartOffset, const int32_t& aEndOffset, + bool* aSucceeded) override; + + virtual mozilla::ipc::IPCResult RecvAddToSelection( + const uint64_t& aID, const int32_t& aStartOffset, + const int32_t& aEndOffset, bool* aSucceeded) override; + + virtual mozilla::ipc::IPCResult RecvRemoveFromSelection( + const uint64_t& aID, const int32_t& aSelectionNum, + bool* aSucceeded) override; + + virtual mozilla::ipc::IPCResult RecvScrollSubstringTo( + const uint64_t& aID, const int32_t& aStartOffset, + const int32_t& aEndOffset, const uint32_t& aScrollType) override; + + virtual mozilla::ipc::IPCResult RecvScrollSubstringToPoint( + const uint64_t& aID, const int32_t& aStartOffset, + const int32_t& aEndOffset, const uint32_t& aCoordinateType, + const int32_t& aX, const int32_t& aY) override; + + virtual mozilla::ipc::IPCResult RecvText(const uint64_t& aID, + nsString* aText) override; + + virtual mozilla::ipc::IPCResult RecvReplaceText( + const uint64_t& aID, const nsAString& aText) override; + + virtual mozilla::ipc::IPCResult RecvInsertText(const uint64_t& aID, + const nsAString& aText, + const int32_t& aPosition, + bool* aValid) override; + + virtual mozilla::ipc::IPCResult RecvCopyText(const uint64_t& aID, + const int32_t& aStartPos, + const int32_t& aEndPos, + bool* aValid) override; + + virtual mozilla::ipc::IPCResult RecvCutText(const uint64_t& aID, + const int32_t& aStartPos, + const int32_t& aEndPos, + bool* aValid) override; + + virtual mozilla::ipc::IPCResult RecvDeleteText(const uint64_t& aID, + const int32_t& aStartPos, + const int32_t& aEndPos, + bool* aValid) override; + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual mozilla::ipc::IPCResult RecvPasteText(const uint64_t& aID, + const int32_t& aPosition, + bool* aValid) override; + + virtual mozilla::ipc::IPCResult RecvImagePosition( + const uint64_t& aID, const uint32_t& aCoordType, + LayoutDeviceIntPoint* aRetVal) override; + + virtual mozilla::ipc::IPCResult RecvImageSize( + const uint64_t& aID, LayoutDeviceIntSize* aRetVal) override; + + virtual mozilla::ipc::IPCResult RecvStartOffset(const uint64_t& aID, + uint32_t* aRetVal, + bool* aOk) override; + virtual mozilla::ipc::IPCResult RecvEndOffset(const uint64_t& aID, + uint32_t* aRetVal, + bool* aOk) override; + virtual mozilla::ipc::IPCResult RecvIsLinkValid(const uint64_t& aID, + bool* aRetVal) override; + virtual mozilla::ipc::IPCResult RecvAnchorCount(const uint64_t& aID, + uint32_t* aRetVal, + bool* aOk) override; + virtual mozilla::ipc::IPCResult RecvAnchorURIAt(const uint64_t& aID, + const uint32_t& aIndex, + nsCString* aURI, + bool* aOk) override; + virtual mozilla::ipc::IPCResult RecvAnchorAt(const uint64_t& aID, + const uint32_t& aIndex, + uint64_t* aIDOfAnchor, + bool* aOk) override; + + virtual mozilla::ipc::IPCResult RecvLinkCount(const uint64_t& aID, + uint32_t* aCount) override; + + virtual mozilla::ipc::IPCResult RecvLinkAt(const uint64_t& aID, + const uint32_t& aIndex, + uint64_t* aIDOfLink, + bool* aOk) override; + + virtual mozilla::ipc::IPCResult RecvLinkIndexAtOffset( + const uint64_t& aID, const uint32_t& aOffset, int32_t* aIndex) override; + + virtual mozilla::ipc::IPCResult RecvTableOfACell(const uint64_t& aID, + uint64_t* aTableID, + bool* aOk) override; + + virtual mozilla::ipc::IPCResult RecvColIdx(const uint64_t& aID, + uint32_t* aIndex) override; + + virtual mozilla::ipc::IPCResult RecvRowIdx(const uint64_t& aID, + uint32_t* aIndex) override; + + virtual mozilla::ipc::IPCResult RecvColExtent(const uint64_t& aID, + uint32_t* aExtent) override; + + virtual mozilla::ipc::IPCResult RecvGetPosition(const uint64_t& aID, + uint32_t* aRowIdx, + uint32_t* aColIdx) override; + + virtual mozilla::ipc::IPCResult RecvGetColRowExtents( + const uint64_t& aID, uint32_t* aColIdx, uint32_t* aRowIdx, + uint32_t* aColExtent, uint32_t* aRowExtent) override; + + virtual mozilla::ipc::IPCResult RecvRowExtent(const uint64_t& aID, + uint32_t* aExtent) override; + + virtual mozilla::ipc::IPCResult RecvColHeaderCells( + const uint64_t& aID, nsTArray<uint64_t>* aCells) override; + + virtual mozilla::ipc::IPCResult RecvRowHeaderCells( + const uint64_t& aID, nsTArray<uint64_t>* aCells) override; + + virtual mozilla::ipc::IPCResult RecvIsCellSelected(const uint64_t& aID, + bool* aSelected) override; + + virtual mozilla::ipc::IPCResult RecvTableCaption(const uint64_t& aID, + uint64_t* aCaptionID, + bool* aOk) override; + virtual mozilla::ipc::IPCResult RecvTableSummary(const uint64_t& aID, + nsString* aSummary) override; + virtual mozilla::ipc::IPCResult RecvTableColumnCount( + const uint64_t& aID, uint32_t* aColCount) override; + virtual mozilla::ipc::IPCResult RecvTableRowCount( + const uint64_t& aID, uint32_t* aRowCount) override; + virtual mozilla::ipc::IPCResult RecvTableCellAt(const uint64_t& aID, + const uint32_t& aRow, + const uint32_t& aCol, + uint64_t* aCellID, + bool* aOk) override; + virtual mozilla::ipc::IPCResult RecvTableCellIndexAt( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + int32_t* aIndex) override; + virtual mozilla::ipc::IPCResult RecvTableColumnIndexAt( + const uint64_t& aID, const uint32_t& aCellIndex, int32_t* aCol) override; + virtual mozilla::ipc::IPCResult RecvTableRowIndexAt( + const uint64_t& aID, const uint32_t& aCellIndex, int32_t* aRow) override; + virtual mozilla::ipc::IPCResult RecvTableRowAndColumnIndicesAt( + const uint64_t& aID, const uint32_t& aCellIndex, int32_t* aRow, + int32_t* aCol) override; + virtual mozilla::ipc::IPCResult RecvTableColumnExtentAt( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + uint32_t* aExtent) override; + virtual mozilla::ipc::IPCResult RecvTableRowExtentAt( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + uint32_t* aExtent) override; + virtual mozilla::ipc::IPCResult RecvTableColumnDescription( + const uint64_t& aID, const uint32_t& aCol, + nsString* aDescription) override; + virtual mozilla::ipc::IPCResult RecvTableRowDescription( + const uint64_t& aID, const uint32_t& aRow, + nsString* aDescription) override; + virtual mozilla::ipc::IPCResult RecvTableColumnSelected( + const uint64_t& aID, const uint32_t& aCol, bool* aSelected) override; + virtual mozilla::ipc::IPCResult RecvTableRowSelected( + const uint64_t& aID, const uint32_t& aRow, bool* aSelected) override; + virtual mozilla::ipc::IPCResult RecvTableCellSelected( + const uint64_t& aID, const uint32_t& aRow, const uint32_t& aCol, + bool* aSelected) override; + virtual mozilla::ipc::IPCResult RecvTableSelectedCellCount( + const uint64_t& aID, uint32_t* aSelectedCells) override; + virtual mozilla::ipc::IPCResult RecvTableSelectedColumnCount( + const uint64_t& aID, uint32_t* aSelectedColumns) override; + virtual mozilla::ipc::IPCResult RecvTableSelectedRowCount( + const uint64_t& aID, uint32_t* aSelectedRows) override; + virtual mozilla::ipc::IPCResult RecvTableSelectedCells( + const uint64_t& aID, nsTArray<uint64_t>* aCellIDs) override; + virtual mozilla::ipc::IPCResult RecvTableSelectedCellIndices( + const uint64_t& aID, nsTArray<uint32_t>* aCellIndices) override; + virtual mozilla::ipc::IPCResult RecvTableSelectedColumnIndices( + const uint64_t& aID, nsTArray<uint32_t>* aColumnIndices) override; + virtual mozilla::ipc::IPCResult RecvTableSelectedRowIndices( + const uint64_t& aID, nsTArray<uint32_t>* aRowIndices) override; + virtual mozilla::ipc::IPCResult RecvTableSelectColumn( + const uint64_t& aID, const uint32_t& aCol) override; + virtual mozilla::ipc::IPCResult RecvTableSelectRow( + const uint64_t& aID, const uint32_t& aRow) override; + virtual mozilla::ipc::IPCResult RecvTableUnselectColumn( + const uint64_t& aID, const uint32_t& aCol) override; + virtual mozilla::ipc::IPCResult RecvTableUnselectRow( + const uint64_t& aID, const uint32_t& aRow) override; + virtual mozilla::ipc::IPCResult RecvTableIsProbablyForLayout( + const uint64_t& aID, bool* aForLayout) override; + virtual mozilla::ipc::IPCResult RecvAtkTableColumnHeader(const uint64_t& aID, + const int32_t& aCol, + uint64_t* aHeader, + bool* aOk) override; + virtual mozilla::ipc::IPCResult RecvAtkTableRowHeader(const uint64_t& aID, + const int32_t& aRow, + uint64_t* aHeader, + bool* aOk) override; + + virtual mozilla::ipc::IPCResult RecvSelectedItems( + const uint64_t& aID, nsTArray<uint64_t>* aSelectedItemIDs) override; + + virtual mozilla::ipc::IPCResult RecvSelectedItemCount( + const uint64_t& aID, uint32_t* aCount) override; + + virtual mozilla::ipc::IPCResult RecvGetSelectedItem(const uint64_t& aID, + const uint32_t& aIndex, + uint64_t* aSelected, + bool* aOk) override; + + virtual mozilla::ipc::IPCResult RecvIsItemSelected(const uint64_t& aID, + const uint32_t& aIndex, + bool* aSelected) override; + + virtual mozilla::ipc::IPCResult RecvAddItemToSelection( + const uint64_t& aID, const uint32_t& aIndex, bool* aSuccess) override; + + virtual mozilla::ipc::IPCResult RecvRemoveItemFromSelection( + const uint64_t& aID, const uint32_t& aIndex, bool* aSuccess) override; + + virtual mozilla::ipc::IPCResult RecvSelectAll(const uint64_t& aID, + bool* aSuccess) override; + + virtual mozilla::ipc::IPCResult RecvUnselectAll(const uint64_t& aID, + bool* aSuccess) override; + + virtual mozilla::ipc::IPCResult RecvDoAction(const uint64_t& aID, + const uint8_t& aIndex, + bool* aSuccess) override; + + virtual mozilla::ipc::IPCResult RecvActionCount(const uint64_t& aID, + uint8_t* aCount) override; + + virtual mozilla::ipc::IPCResult RecvActionNameAt(const uint64_t& aID, + const uint8_t& aIndex, + nsString* aName) override; + + virtual mozilla::ipc::IPCResult RecvAccessKey( + const uint64_t& aID, uint32_t* aKey, uint32_t* aModifierMask) override; + + virtual mozilla::ipc::IPCResult RecvAtkKeyBinding(const uint64_t& aID, + nsString* aResult) override; + + virtual mozilla::ipc::IPCResult RecvCurValue(const uint64_t& aID, + double* aValue) override; + + virtual mozilla::ipc::IPCResult RecvSetCurValue(const uint64_t& aID, + const double& aValue, + bool* aRetVal) override; + + virtual mozilla::ipc::IPCResult RecvMinValue(const uint64_t& aID, + double* aValue) override; + + virtual mozilla::ipc::IPCResult RecvMaxValue(const uint64_t& aID, + double* aValue) override; + + virtual mozilla::ipc::IPCResult RecvStep(const uint64_t& aID, + double* aStep) override; + + virtual mozilla::ipc::IPCResult RecvLanguage(const uint64_t& aID, + nsString* aLocale) override; + virtual mozilla::ipc::IPCResult RecvDocType(const uint64_t& aID, + nsString* aType) override; + virtual mozilla::ipc::IPCResult RecvTitle(const uint64_t& aID, + nsString* aTitle) override; + virtual mozilla::ipc::IPCResult RecvMimeType(const uint64_t& aID, + nsString* aMime) override; + virtual mozilla::ipc::IPCResult RecvURLDocTypeMimeType( + const uint64_t& aID, nsString* aURL, nsString* aDocType, + nsString* aMimeType) override; + + virtual mozilla::ipc::IPCResult RecvChildAtPoint( + const uint64_t& aID, const int32_t& aX, const int32_t& aY, + const uint32_t& aWhich, PDocAccessibleChild** aResultDoc, + uint64_t* aResultID) override; + + virtual mozilla::ipc::IPCResult RecvExtents(const uint64_t& aID, + const bool& aNeedsScreenCoords, + int32_t* aX, int32_t* aY, + int32_t* aWidth, + int32_t* aHeight) override; + virtual mozilla::ipc::IPCResult RecvExtentsInCSSPixels( + const uint64_t& aID, int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) override; + virtual mozilla::ipc::IPCResult RecvDOMNodeID(const uint64_t& aID, + nsString* aDOMNodeID) override; + + virtual bool DeallocPDocAccessiblePlatformExtChild( + PDocAccessiblePlatformExtChild* aActor) override; + + virtual PDocAccessiblePlatformExtChild* AllocPDocAccessiblePlatformExtChild() + override; + + DocAccessiblePlatformExtChild* GetPlatformExtension(); + + private: + LocalAccessible* IdToAccessibleLink(const uint64_t& aID) const; + LocalAccessible* IdToAccessibleSelect(const uint64_t& aID) const; + TextLeafAccessible* IdToTextLeafAccessible(const uint64_t& aID) const; + ImageAccessible* IdToImageAccessible(const uint64_t& aID) const; + TableCellAccessible* IdToTableCellAccessible(const uint64_t& aID) const; + TableAccessible* IdToTableAccessible(const uint64_t& aID) const; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/other/PDocAccessible.ipdl b/accessible/ipc/other/PDocAccessible.ipdl new file mode 100644 index 0000000000..13a9056cac --- /dev/null +++ b/accessible/ipc/other/PDocAccessible.ipdl @@ -0,0 +1,357 @@ +/* -*- 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 protocol PBrowser; +include protocol PDocAccessiblePlatformExt; + +include DocAccessibleTypes; + +include "mozilla/GfxMessageUtils.h"; + +using LayoutDeviceIntRect from "Units.h"; +using LayoutDeviceIntPoint from "Units.h"; +using LayoutDeviceIntSize from "Units.h"; +using mozilla::a11y::role from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::AccType from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::AccGenericType from "mozilla/a11y/IPCTypes.h"; +[RefCounted] using mozilla::a11y::AccAttributes from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::CacheUpdateType from "mozilla/a11y/IPCTypes.h"; +using mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h"; + +namespace mozilla { +namespace a11y { + +struct AccessibleData +{ + uint64_t ID; + role Role; + uint32_t ChildrenCount; + AccType Type; + AccGenericType GenericTypes; + uint8_t RoleMapEntryIndex; +}; + +union OriginDocument +{ + PDocAccessible; +}; + +struct BatchData +{ + OriginDocument Document; + uint64_t ID; + uint64_t State; + LayoutDeviceIntRect Bounds; + uint8_t ActionCount; + nsString Name; + nsString TextValue; + nsString DOMNodeID; + nsString Description; + double CurValue; + double MinValue; + double MaxValue; + double Step; + AccAttributes Attributes; +}; + +struct ShowEventData +{ + uint64_t ID; + uint32_t Idx; + AccessibleData[] NewTree; + bool EventSuppressed; +}; + +struct RelationTargets +{ + uint32_t Type; + uint64_t[] Targets; +}; + +struct TextRangeData +{ + uint64_t StartID; + uint64_t EndID; + int32_t StartOffset; + int32_t EndOffset; +}; + +[ManualDealloc, NestedUpTo=inside_sync, ChildImpl=virtual, ParentImpl=virtual] +sync protocol PDocAccessible +{ + manager PBrowser; + manages PDocAccessiblePlatformExt; + +parent: + async PDocAccessiblePlatformExt(); + async Shutdown(); + + /* + * Notify the parent process the document in the child process is firing an + * event. + */ + async Event(uint64_t aID, uint32_t type); + async ShowEvent(ShowEventData data, bool aFromuser); + async HideEvent(uint64_t aRootID, bool aFromUser); + async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled); + async CaretMoveEvent(uint64_t aID, int32_t aOffset, + bool aIsSelectionCollapsed, bool aIsAtEndOfLine, + int32_t aGranularity); + async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen, + bool aIsInsert, bool aFromUser); + async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType); + async RoleChangedEvent(role aRole, uint8_t aRoleMapEntryIndex); + async VirtualCursorChangeEvent(uint64_t aID, + uint64_t aOldPosition, + int32_t aOldStartOffset, int32_t aOldEndOffset, + uint64_t aPosition, + int32_t aStartOffset, int32_t aEndOffset, + int16_t aReason, int16_t aBoundaryType, + bool aFromUservcEvent); + async ScrollingEvent(uint64_t aID, uint64_t aType, + uint32_t aScrollX, uint32_t aScrollY, + uint32_t aMaxScrollX, uint32_t aMaxScrollY); + async AnnouncementEvent(uint64_t aID, + nsString aAnnouncement, + uint16_t aPriority); + async TextSelectionChangeEvent(uint64_t aID, TextRangeData[] aSelection); + + /* + * Tell the parent document to bind the existing document as a new child + * document. + */ + async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID); + + // Android + async Batch(uint64_t aBatchType, BatchData[] aData); + + /* + * Cache The World + * aDispatchShowEvent is true when a show event with the first accessible in + * the cache list as the target should be dispatched after the cache is + * populated. The show event will have a from-user flag value of false. + */ + async Cache(CacheUpdateType aUpdateType, CacheData[] aData, bool aDispatchShowEvent); + + /* + * Lists of accessibles that either gained or lost a selected state. + */ + async SelectedAccessiblesChanged(uint64_t[] aSelectedIDs, uint64_t[] aUnselectedIDs); + + /* + * Tell the parent process that the given Accessibles are about to be moved + * via subsequent hide and show events. + */ + async AccessiblesWillMove(uint64_t[] aIDs); + +child: + /* + * Notify the content process that the PDocAccessible has finished being + * constructed in the parent process. + */ + async ConstructedInParentProcess(); + + async __delete__(); + + /* + * Called as a result of focus shifting from chrome to content + * elements through keyboard navigation. + */ + async RestoreFocus(); + + // LocalAccessible + [Nested=inside_sync] sync State(uint64_t aID) returns(uint64_t states); + [Nested=inside_sync] sync NativeState(uint64_t aID) returns(uint64_t states); + [Nested=inside_sync] sync Name(uint64_t aID) returns(nsString name, uint32_t flag); + [Nested=inside_sync] sync Value(uint64_t aID) returns(nsString value); + [Nested=inside_sync] sync Help(uint64_t aID) returns(nsString help); + [Nested=inside_sync] sync Description(uint64_t aID) returns(nsString desc); + [Nested=inside_sync] sync Attributes(uint64_t aID) returns(AccAttributes attributes); + [Nested=inside_sync] sync RelationByType(uint64_t aID, uint32_t aRelationType) + returns(uint64_t[] targets); + [Nested=inside_sync] sync Relations(uint64_t aID) returns(RelationTargets[] relations); + [Nested=inside_sync] sync IsSearchbox(uint64_t aID) returns(bool retval); + [Nested=inside_sync] sync LandmarkRole(uint64_t aID) returns(nsString landmark); + [Nested=inside_sync] sync GroupPosition(uint64_t aID) + returns(int32_t groupLevel, int32_t similarItemsInGroup, int32_t positionInGroup); + async ScrollTo(uint64_t aID, uint32_t aScrollType); + async ScrollToPoint(uint64_t aID, uint32_t aScrollType, int32_t aX, + int32_t aY); + async Announce(uint64_t aID, nsString aAnnouncement, uint16_t aPriority); + + // AccessibleText + + // TextSubstring is getText in IDL. + [Nested=inside_sync] sync CaretLineNumber(uint64_t aID) returns(int32_t aLineNumber); + [Nested=inside_sync] sync CaretOffset(uint64_t aID) returns(int32_t aOffset); + async SetCaretOffset(uint64_t aID, int32_t aOffset); + [Nested=inside_sync] sync CharacterCount(uint64_t aID) returns(int32_t aCount); + [Nested=inside_sync] sync SelectionCount(uint64_t aID) returns(int32_t aCount); + [Nested=inside_sync] sync TextSubstring(uint64_t aID, int32_t aStartOffset, int32_t + aEndOffset) returns(nsString aText, bool aValid); + [Nested=inside_sync] sync GetTextAfterOffset(uint64_t aID, int32_t aOffset, int32_t aBoundaryType) + returns(nsString aText, int32_t aStartOffset, int32_t aEndOffset); + [Nested=inside_sync] sync GetTextAtOffset(uint64_t aID, int32_t aOffset, int32_t aBoundaryType) + returns(nsString aText, int32_t aStartOffset, int32_t aEndOffset); + + [Nested=inside_sync] sync GetTextBeforeOffset(uint64_t aID, int32_t aOffset, int32_t aBoundaryType) + returns(nsString aText, int32_t aStartOffset, int32_t aEndOffset); + [Nested=inside_sync] sync CharAt(uint64_t aID, int32_t aOffset) returns(uint16_t aChar); + + [Nested=inside_sync] sync TextAttributes(uint64_t aID, bool aIncludeDefAttrs, int32_t aOffset) + returns(AccAttributes aAttributes, int32_t aStartOffset, int32_t aEndOffset); + [Nested=inside_sync] sync DefaultTextAttributes(uint64_t aID) returns(AccAttributes aAttributes); + + [Nested=inside_sync] sync TextBounds(uint64_t aID, int32_t aStartOffset, int32_t aEndOffset, + uint32_t aCoordType) + returns(LayoutDeviceIntRect aRetVal); + [Nested=inside_sync] sync CharBounds(uint64_t aID, int32_t aOffset, uint32_t aCoordType) + returns(LayoutDeviceIntRect aRetVal); + + [Nested=inside_sync] sync OffsetAtPoint(uint64_t aID, int32_t aX, int32_t aY, uint32_t aCoordType) + returns(int32_t aRetVal); + + [Nested=inside_sync] sync SelectionBoundsAt(uint64_t aID, int32_t aSelectionNum) + returns(bool aSucceeded, nsString aData, int32_t aStartOffset, int32_t aEndOffset); + [Nested=inside_sync] sync SetSelectionBoundsAt(uint64_t aID, int32_t aSelectionNum, + int32_t aStartOffset, int32_t aEndOffset) + returns(bool aSucceeded); + [Nested=inside_sync] sync AddToSelection(uint64_t aID, int32_t aStartOffset, int32_t aEndOffset) + returns(bool aSucceeded); + [Nested=inside_sync] sync RemoveFromSelection(uint64_t aID, int32_t aSelectionNum) + returns(bool aSucceeded); + + async ScrollSubstringTo(uint64_t aID, int32_t aStartOffset, int32_t aEndOffset, + uint32_t aScrollType); + async ScrollSubstringToPoint(uint64_t aID, + int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aCoordinateType, + int32_t aX, int32_t aY); + + [Nested=inside_sync] sync Text(uint64_t aID) returns(nsString aText); + [Nested=inside_sync] sync ReplaceText(uint64_t aID, nsString aText); + [Nested=inside_sync] sync InsertText(uint64_t aID, nsString aText, int32_t aPosition) + returns(bool aValid); + [Nested=inside_sync] sync CopyText(uint64_t aID, int32_t aStartPos, int32_t aEndPos) + returns(bool aValid); + [Nested=inside_sync] sync CutText(uint64_t aID, int32_t aStartPos, int32_t aEndPos) + returns(bool aValid); + [Nested=inside_sync] sync DeleteText(uint64_t aID, int32_t aStartPos, int32_t aEndPos) + returns(bool aValid); + [Nested=inside_sync] sync PasteText(uint64_t aID, int32_t aPosition) + returns(bool aValid); + + [Nested=inside_sync] sync ImagePosition(uint64_t aID, uint32_t aCoordType) returns(LayoutDeviceIntPoint aRetVal); + [Nested=inside_sync] sync ImageSize(uint64_t aID) returns(LayoutDeviceIntSize aRetVal); + + [Nested=inside_sync] sync StartOffset(uint64_t aID) returns(uint32_t aRetVal, bool aOk); + [Nested=inside_sync] sync EndOffset(uint64_t aID) returns(uint32_t aRetVal, bool aOk); + [Nested=inside_sync] sync IsLinkValid(uint64_t aID) returns(bool aRetVal); + [Nested=inside_sync] sync AnchorCount(uint64_t aID) returns(uint32_t aRetVal, bool aOk); + [Nested=inside_sync] sync AnchorURIAt(uint64_t aID, uint32_t aIndex) returns(nsCString aURI, bool aOk); + [Nested=inside_sync] sync AnchorAt(uint64_t aID, uint32_t aIndex) returns(uint64_t aIDOfAnchor, bool aOk); + + [Nested=inside_sync] sync LinkCount(uint64_t aID) returns(uint32_t aCount); + [Nested=inside_sync] sync LinkAt(uint64_t aID, uint32_t aIndex) returns(uint64_t aIDOfLink, bool aOk); + [Nested=inside_sync] sync LinkIndexAtOffset(uint64_t aID, uint32_t aOffset) returns(int32_t aIndex); + + [Nested=inside_sync] sync TableOfACell(uint64_t aID) returns(uint64_t aTableID, bool aOk); + [Nested=inside_sync] sync ColIdx(uint64_t aID) returns(uint32_t aIndex); + [Nested=inside_sync] sync RowIdx(uint64_t aID) returns(uint32_t aIndex); + [Nested=inside_sync] sync GetPosition(uint64_t aID) returns(uint32_t aRow, uint32_t aCol); + [Nested=inside_sync] sync ColExtent(uint64_t aID) returns(uint32_t aExtent); + [Nested=inside_sync] sync RowExtent(uint64_t aID) returns(uint32_t aExtent); + [Nested=inside_sync] sync GetColRowExtents(uint64_t aID) + returns(uint32_t aCol, uint32_t aRow, uint32_t aColExtent, uint32_t aRowExtent); + [Nested=inside_sync] sync ColHeaderCells(uint64_t aID) returns(uint64_t[] aCells); + [Nested=inside_sync] sync RowHeaderCells(uint64_t aID) returns(uint64_t[] aCells); + [Nested=inside_sync] sync IsCellSelected(uint64_t aID) returns(bool aSelected); + + [Nested=inside_sync] sync TableCaption(uint64_t aID) returns(uint64_t aCaptionID, bool aOk); + [Nested=inside_sync] sync TableSummary(uint64_t aID) returns(nsString aSummary); + [Nested=inside_sync] sync TableColumnCount(uint64_t aID) returns(uint32_t aColCount); + [Nested=inside_sync] sync TableRowCount(uint64_t aID) returns(uint32_t aRowCount); + [Nested=inside_sync] sync TableCellAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint64_t aCellID, bool aOk); + [Nested=inside_sync] sync TableCellIndexAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(int32_t aIndex); + [Nested=inside_sync] sync TableColumnIndexAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aCol); + [Nested=inside_sync] sync TableRowIndexAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aRow); + [Nested=inside_sync] sync TableRowAndColumnIndicesAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aRow, int32_t aCol); + [Nested=inside_sync] sync TableColumnExtentAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint32_t aExtent); + [Nested=inside_sync] sync TableRowExtentAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint32_t aExtent); + [Nested=inside_sync] sync TableColumnDescription(uint64_t aID, uint32_t aCol) returns(nsString aDescription); + [Nested=inside_sync] sync TableRowDescription(uint64_t aID, uint32_t aRow) returns(nsString aDescription); + [Nested=inside_sync] sync TableColumnSelected(uint64_t aID, uint32_t aCol) returns(bool aSelected); + [Nested=inside_sync] sync TableRowSelected(uint64_t aID, uint32_t aRow) returns(bool aSelected); + [Nested=inside_sync] sync TableCellSelected(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(bool aSelected); + [Nested=inside_sync] sync TableSelectedCellCount(uint64_t aID) returns(uint32_t aSelectedCells); + [Nested=inside_sync] sync TableSelectedColumnCount(uint64_t aID) returns(uint32_t aSelectedColumns); + [Nested=inside_sync] sync TableSelectedRowCount(uint64_t aID) returns(uint32_t aSelectedRows); + [Nested=inside_sync] sync TableSelectedCells(uint64_t aID) returns(uint64_t[] aCellIDs); + [Nested=inside_sync] sync TableSelectedCellIndices(uint64_t aID) returns(uint32_t[] aCellIndeces); + [Nested=inside_sync] sync TableSelectedColumnIndices(uint64_t aID) returns(uint32_t[] aColumnIndeces); + [Nested=inside_sync] sync TableSelectedRowIndices(uint64_t aID) returns(uint32_t[] aRowIndeces); + [Nested=inside_sync] sync TableSelectColumn(uint64_t aID, uint32_t aCol); + [Nested=inside_sync] sync TableSelectRow(uint64_t aID, uint32_t aRow); + [Nested=inside_sync] sync TableUnselectColumn(uint64_t aID, uint32_t aCol); + [Nested=inside_sync] sync TableUnselectRow(uint64_t aID, uint32_t aRow); + [Nested=inside_sync] sync TableIsProbablyForLayout(uint64_t aID) returns(bool aForLayout); + [Nested=inside_sync] sync AtkTableColumnHeader(uint64_t aID, int32_t aCol) + returns(uint64_t aHeaderID, bool aOk); + [Nested=inside_sync] sync AtkTableRowHeader(uint64_t aID, int32_t aRow) + returns(uint64_t aHeaderID, bool aOk); + + [Nested=inside_sync] sync SelectedItems(uint64_t aID) returns(uint64_t[] aSelectedItemIDs); + [Nested=inside_sync] sync SelectedItemCount(uint64_t aID) returns(uint32_t aCount); + [Nested=inside_sync] sync GetSelectedItem(uint64_t aID, uint32_t aIndex) returns(uint64_t aSelected, bool aOk); + [Nested=inside_sync] sync IsItemSelected(uint64_t aID, uint32_t aIndex) returns(bool aSelected); + [Nested=inside_sync] sync AddItemToSelection(uint64_t aID, uint32_t aIndex) returns(bool aSuccess); + [Nested=inside_sync] sync RemoveItemFromSelection(uint64_t aID, uint32_t aIndex) returns(bool aSuccess); + [Nested=inside_sync] sync SelectAll(uint64_t aID) returns(bool aSuccess); + [Nested=inside_sync] sync UnselectAll(uint64_t aID) returns(bool aSuccess); + + async TakeSelection(uint64_t aID); + async SetSelected(uint64_t aID, bool aSelected); + + [Nested=inside_sync] sync DoAction(uint64_t aID, uint8_t aIndex) returns(bool aSuccess); + async DoActionAsync(uint64_t aID, uint8_t aIndex); + [Nested=inside_sync] sync ActionCount(uint64_t aID) returns(uint8_t aCount); + [Nested=inside_sync] sync ActionNameAt(uint64_t aID, uint8_t aIndex) returns(nsString aName); + [Nested=inside_sync] sync AccessKey(uint64_t aID) returns(uint32_t aKey, uint32_t aModifierMask); + [Nested=inside_sync] sync AtkKeyBinding(uint64_t aID) returns(nsString aResult); + + [Nested=inside_sync] sync CurValue(uint64_t aID) returns(double aValue); + [Nested=inside_sync] sync SetCurValue(uint64_t aID, double aValue) returns(bool aRetVal); + [Nested=inside_sync] sync MinValue(uint64_t aID) returns(double aValue); + [Nested=inside_sync] sync MaxValue(uint64_t aID) returns(double aValue); + [Nested=inside_sync] sync Step(uint64_t aID) returns(double aStep); + + async TakeFocus(uint64_t aID); + + [Nested=inside_sync] sync Language(uint64_t aID) returns(nsString aLocale); + [Nested=inside_sync] sync DocType(uint64_t aID) returns(nsString aType); + [Nested=inside_sync] sync Title(uint64_t aID) returns(nsString aTitle); + [Nested=inside_sync] sync MimeType(uint64_t aID) returns(nsString aMime); + [Nested=inside_sync] sync URLDocTypeMimeType(uint64_t aID) returns(nsString aURL, nsString aDocType, nsString aMimeType); + + [Nested=inside_sync] sync ChildAtPoint(uint64_t aID, int32_t aX, int32_t aY, uint32_t aWhich) + returns(nullable PDocAccessible aResultDoc, uint64_t aResultID); + + [Nested=inside_sync] sync Extents(uint64_t aID, bool aNeedsScreenCoords) + returns(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight); + [Nested=inside_sync] sync ExtentsInCSSPixels(uint64_t aID) + returns(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight); + [Nested=inside_sync] sync DOMNodeID(uint64_t aID) returns(nsString aDOMNodeID); + + /* + * Verify the cache. Used for testing purposes. + */ + async VerifyCache(uint64_t aID, uint64_t aCacheDomain, AccAttributes aFields); + +}; + +} +} diff --git a/accessible/ipc/other/RemoteAccessible.cpp b/accessible/ipc/other/RemoteAccessible.cpp new file mode 100644 index 0000000000..d720ff2530 --- /dev/null +++ b/accessible/ipc/other/RemoteAccessible.cpp @@ -0,0 +1,989 @@ +/* -*- 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 "RemoteAccessible.h" +#include "mozilla/a11y/DocAccessibleParent.h" +#include "DocAccessible.h" +#include "AccAttributes.h" +#include "mozilla/a11y/DocManager.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/Unused.h" +#include "mozilla/a11y/Platform.h" +#include "Relation.h" +#include "RelationType.h" +#include "mozilla/a11y/Role.h" +#include "mozilla/StaticPrefs_accessibility.h" + +namespace mozilla { +namespace a11y { + +uint64_t RemoteAccessible::State() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::State(); + } + uint64_t state = 0; + Unused << mDoc->SendState(mID, &state); + return state; +} + +uint64_t RemoteAccessible::NativeState() const { + uint64_t state = 0; + Unused << mDoc->SendNativeState(mID, &state); + return state; +} + +ENameValueFlag RemoteAccessible::Name(nsString& aName) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Name(aName); + } + + uint32_t flag = 0; + Unused << mDoc->SendName(mID, &aName, &flag); + return static_cast<ENameValueFlag>(flag); +} + +void RemoteAccessible::Value(nsString& aValue) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + RemoteAccessibleBase<RemoteAccessible>::Value(aValue); + return; + } + + Unused << mDoc->SendValue(mID, &aValue); +} + +void RemoteAccessible::Help(nsString& aHelp) const { + Unused << mDoc->SendHelp(mID, &aHelp); +} + +void RemoteAccessible::Description(nsString& aDesc) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + RemoteAccessibleBase<RemoteAccessible>::Description(aDesc); + return; + } + + Unused << mDoc->SendDescription(mID, &aDesc); +} + +already_AddRefed<AccAttributes> RemoteAccessible::Attributes() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Attributes(); + } + RefPtr<AccAttributes> attrs; + Unused << mDoc->SendAttributes(mID, &attrs); + return attrs.forget(); +} + +Relation RemoteAccessible::RelationByType(RelationType aType) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::RelationByType(aType); + } + + nsTArray<uint64_t> targetIDs; + Unused << mDoc->SendRelationByType(mID, static_cast<uint32_t>(aType), + &targetIDs); + return Relation(new RemoteAccIterator(std::move(targetIDs), Document())); +} + +void RemoteAccessible::Relations( + nsTArray<RelationType>* aTypes, + nsTArray<nsTArray<RemoteAccessible*>>* aTargetSets) const { + nsTArray<RelationTargets> ipcRelations; + Unused << mDoc->SendRelations(mID, &ipcRelations); + + size_t relationCount = ipcRelations.Length(); + aTypes->SetCapacity(relationCount); + aTargetSets->SetCapacity(relationCount); + for (size_t i = 0; i < relationCount; i++) { + uint32_t type = ipcRelations[i].Type(); + if (type > static_cast<uint32_t>(RelationType::LAST)) continue; + + size_t targetCount = ipcRelations[i].Targets().Length(); + nsTArray<RemoteAccessible*> targets(targetCount); + for (size_t j = 0; j < targetCount; j++) { + if (RemoteAccessible* proxy = + mDoc->GetAccessible(ipcRelations[i].Targets()[j])) { + targets.AppendElement(proxy); + } + } + + if (targets.IsEmpty()) continue; + + aTargetSets->AppendElement(std::move(targets)); + aTypes->AppendElement(static_cast<RelationType>(type)); + } +} + +bool RemoteAccessible::IsSearchbox() const { + bool retVal = false; + Unused << mDoc->SendIsSearchbox(mID, &retVal); + return retVal; +} + +nsAtom* RemoteAccessible::LandmarkRole() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::LandmarkRole(); + } + + nsString landmark; + Unused << mDoc->SendLandmarkRole(mID, &landmark); + return NS_GetStaticAtom(landmark); +} + +GroupPos RemoteAccessible::GroupPosition() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::GroupPosition(); + } + + GroupPos groupPos; + Unused << mDoc->SendGroupPosition(mID, &groupPos.level, &groupPos.setSize, + &groupPos.posInSet); + return groupPos; +} + +void RemoteAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX, + int32_t aY) { + Unused << mDoc->SendScrollToPoint(mID, aScrollType, aX, aY); +} + +void RemoteAccessible::Announce(const nsString& aAnnouncement, + uint16_t aPriority) { + Unused << mDoc->SendAnnounce(mID, aAnnouncement, aPriority); +} + +int32_t RemoteAccessible::CaretLineNumber() { + int32_t line = -1; + Unused << mDoc->SendCaretOffset(mID, &line); + return line; +} + +int32_t RemoteAccessible::CaretOffset() const { + int32_t offset = 0; + Unused << mDoc->SendCaretOffset(mID, &offset); + return offset; +} + +uint32_t RemoteAccessible::CharacterCount() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::CharacterCount(); + } + int32_t count = 0; + Unused << mDoc->SendCharacterCount(mID, &count); + return count; +} + +int32_t RemoteAccessible::SelectionCount() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::SelectionCount(); + } + int32_t count = 0; + Unused << mDoc->SendSelectionCount(mID, &count); + return count; +} + +void RemoteAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset, + nsAString& aText) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextSubstring( + aStartOffset, aEndOffset, aText); + } + + bool valid; + nsString text; + Unused << mDoc->SendTextSubstring(mID, aStartOffset, aEndOffset, &text, + &valid); + aText = std::move(text); +} + +void RemoteAccessible::TextAfterOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, + int32_t* aEndOffset, nsAString& aText) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextAfterOffset( + aOffset, aBoundaryType, aStartOffset, aEndOffset, aText); + } + nsString text; + Unused << mDoc->SendGetTextAfterOffset(mID, aOffset, aBoundaryType, &text, + aStartOffset, aEndOffset); + aText = std::move(text); +} + +void RemoteAccessible::TextAtOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, + nsAString& aText) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextAtOffset( + aOffset, aBoundaryType, aStartOffset, aEndOffset, aText); + } + nsString text; + Unused << mDoc->SendGetTextAtOffset(mID, aOffset, aBoundaryType, &text, + aStartOffset, aEndOffset); + aText = std::move(text); +} + +void RemoteAccessible::TextBeforeOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, + int32_t* aEndOffset, nsAString& aText) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextBeforeOffset( + aOffset, aBoundaryType, aStartOffset, aEndOffset, aText); + } + nsString text; + Unused << mDoc->SendGetTextBeforeOffset(mID, aOffset, aBoundaryType, &text, + aStartOffset, aEndOffset); + aText = std::move(text); +} + +char16_t RemoteAccessible::CharAt(int32_t aOffset) { + uint16_t retval = 0; + Unused << mDoc->SendCharAt(mID, aOffset, &retval); + return static_cast<char16_t>(retval); +} + +already_AddRefed<AccAttributes> RemoteAccessible::TextAttributes( + bool aIncludeDefAttrs, int32_t aOffset, int32_t* aStartOffset, + int32_t* aEndOffset) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextAttributes( + aIncludeDefAttrs, aOffset, aStartOffset, aEndOffset); + } + RefPtr<AccAttributes> attrs; + Unused << mDoc->SendTextAttributes(mID, aIncludeDefAttrs, aOffset, &attrs, + aStartOffset, aEndOffset); + return attrs.forget(); +} + +already_AddRefed<AccAttributes> RemoteAccessible::DefaultTextAttributes() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::DefaultTextAttributes(); + } + RefPtr<AccAttributes> attrs; + Unused << mDoc->SendDefaultTextAttributes(mID, &attrs); + return attrs.forget(); +} + +LayoutDeviceIntRect RemoteAccessible::TextBounds(int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aCoordType) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + MOZ_ASSERT(IsHyperText(), "is not hypertext?"); + return RemoteAccessibleBase<RemoteAccessible>::TextBounds( + aStartOffset, aEndOffset, aCoordType); + } + + LayoutDeviceIntRect rect; + Unused << mDoc->SendTextBounds(mID, aStartOffset, aEndOffset, aCoordType, + &rect); + return rect; +} + +LayoutDeviceIntRect RemoteAccessible::CharBounds(int32_t aOffset, + uint32_t aCoordType) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + MOZ_ASSERT(IsHyperText(), "is not hypertext?"); + return RemoteAccessibleBase<RemoteAccessible>::CharBounds(aOffset, + aCoordType); + } + + LayoutDeviceIntRect rect; + Unused << mDoc->SendCharBounds(mID, aOffset, aCoordType, &rect); + return rect; +} + +int32_t RemoteAccessible::OffsetAtPoint(int32_t aX, int32_t aY, + uint32_t aCoordType) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + MOZ_ASSERT(IsHyperText(), "is not hypertext?"); + return RemoteAccessibleBase<RemoteAccessible>::OffsetAtPoint(aX, aY, + aCoordType); + } + + int32_t retVal = -1; + Unused << mDoc->SendOffsetAtPoint(mID, aX, aY, aCoordType, &retVal); + return retVal; +} + +bool RemoteAccessible::SelectionBoundsAt(int32_t aSelectionNum, nsString& aData, + int32_t* aStartOffset, + int32_t* aEndOffset) { + bool retVal = false; + Unused << mDoc->SendSelectionBoundsAt(mID, aSelectionNum, &retVal, &aData, + aStartOffset, aEndOffset); + return retVal; +} + +bool RemoteAccessible::SetSelectionBoundsAt(int32_t aSelectionNum, + int32_t aStartOffset, + int32_t aEndOffset) { + bool retVal = false; + Unused << mDoc->SendSetSelectionBoundsAt(mID, aSelectionNum, aStartOffset, + aEndOffset, &retVal); + return retVal; +} + +bool RemoteAccessible::AddToSelection(int32_t aStartOffset, + int32_t aEndOffset) { + bool retVal = false; + Unused << mDoc->SendAddToSelection(mID, aStartOffset, aEndOffset, &retVal); + return retVal; +} + +bool RemoteAccessible::RemoveFromSelection(int32_t aSelectionNum) { + bool retVal = false; + Unused << mDoc->SendRemoveFromSelection(mID, aSelectionNum, &retVal); + return retVal; +} + +void RemoteAccessible::ScrollSubstringTo(int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aScrollType) { + Unused << mDoc->SendScrollSubstringTo(mID, aStartOffset, aEndOffset, + aScrollType); +} + +void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aCoordinateType, + int32_t aX, int32_t aY) { + Unused << mDoc->SendScrollSubstringToPoint(mID, aStartOffset, aEndOffset, + aCoordinateType, aX, aY); +} + +void RemoteAccessible::Text(nsString* aText) { + Unused << mDoc->SendText(mID, aText); +} + +void RemoteAccessible::ReplaceText(const nsString& aText) { + Unused << mDoc->SendReplaceText(mID, aText); +} + +bool RemoteAccessible::InsertText(const nsString& aText, int32_t aPosition) { + bool valid; + Unused << mDoc->SendInsertText(mID, aText, aPosition, &valid); + return valid; +} + +bool RemoteAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) { + bool valid; + Unused << mDoc->SendCopyText(mID, aStartPos, aEndPos, &valid); + return valid; +} + +bool RemoteAccessible::CutText(int32_t aStartPos, int32_t aEndPos) { + bool valid; + Unused << mDoc->SendCutText(mID, aStartPos, aEndPos, &valid); + return valid; +} + +bool RemoteAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) { + bool valid; + Unused << mDoc->SendDeleteText(mID, aStartPos, aEndPos, &valid); + return valid; +} + +bool RemoteAccessible::PasteText(int32_t aPosition) { + bool valid; + Unused << mDoc->SendPasteText(mID, aPosition, &valid); + return valid; +} + +uint32_t RemoteAccessible::StartOffset() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::StartOffset(); + } + uint32_t retVal = 0; + bool ok; + Unused << mDoc->SendStartOffset(mID, &retVal, &ok); + return retVal; +} + +uint32_t RemoteAccessible::EndOffset() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::EndOffset(); + } + bool ok; + uint32_t retVal = 0; + Unused << mDoc->SendEndOffset(mID, &retVal, &ok); + return retVal; +} + +bool RemoteAccessible::IsLinkValid() { + bool retVal = false; + Unused << mDoc->SendIsLinkValid(mID, &retVal); + return retVal; +} + +uint32_t RemoteAccessible::AnchorCount(bool* aOk) { + uint32_t retVal = 0; + Unused << mDoc->SendAnchorCount(mID, &retVal, aOk); + return retVal; +} + +void RemoteAccessible::AnchorURIAt(uint32_t aIndex, nsCString& aURI, + bool* aOk) { + Unused << mDoc->SendAnchorURIAt(mID, aIndex, &aURI, aOk); +} + +RemoteAccessible* RemoteAccessible::AnchorAt(uint32_t aIndex) { + uint64_t id = 0; + bool ok = false; + Unused << mDoc->SendAnchorAt(mID, aIndex, &id, &ok); + return ok ? mDoc->GetAccessible(id) : nullptr; +} + +uint32_t RemoteAccessible::LinkCount() { + uint32_t retVal = 0; + Unused << mDoc->SendLinkCount(mID, &retVal); + return retVal; +} + +RemoteAccessible* RemoteAccessible::LinkAt(const uint32_t& aIndex) { + uint64_t linkID = 0; + bool ok = false; + Unused << mDoc->SendLinkAt(mID, aIndex, &linkID, &ok); + return ok ? mDoc->GetAccessible(linkID) : nullptr; +} + +int32_t RemoteAccessible::LinkIndexAtOffset(uint32_t aOffset) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::LinkIndexAtOffset(aOffset); + } + int32_t retVal = -1; + Unused << mDoc->SendLinkIndexAtOffset(mID, aOffset, &retVal); + return retVal; +} + +RemoteAccessible* RemoteAccessible::TableOfACell() { + uint64_t tableID = 0; + bool ok = false; + Unused << mDoc->SendTableOfACell(mID, &tableID, &ok); + return ok ? mDoc->GetAccessible(tableID) : nullptr; +} + +uint32_t RemoteAccessible::ColIdx() { + uint32_t index = 0; + Unused << mDoc->SendColIdx(mID, &index); + return index; +} + +uint32_t RemoteAccessible::RowIdx() { + uint32_t index = 0; + Unused << mDoc->SendRowIdx(mID, &index); + return index; +} + +void RemoteAccessible::GetColRowExtents(uint32_t* aColIdx, uint32_t* aRowIdx, + uint32_t* aColExtent, + uint32_t* aRowExtent) { + Unused << mDoc->SendGetColRowExtents(mID, aColIdx, aRowIdx, aColExtent, + aRowExtent); +} + +void RemoteAccessible::GetPosition(uint32_t* aRowIdx, uint32_t* aColIdx) { + Unused << mDoc->SendGetPosition(mID, aRowIdx, aColIdx); +} + +uint32_t RemoteAccessible::ColExtent() { + uint32_t extent = 0; + Unused << mDoc->SendColExtent(mID, &extent); + return extent; +} + +uint32_t RemoteAccessible::RowExtent() { + uint32_t extent = 0; + Unused << mDoc->SendRowExtent(mID, &extent); + return extent; +} + +void RemoteAccessible::ColHeaderCells(nsTArray<RemoteAccessible*>* aCells) { + nsTArray<uint64_t> targetIDs; + Unused << mDoc->SendColHeaderCells(mID, &targetIDs); + + size_t targetCount = targetIDs.Length(); + for (size_t i = 0; i < targetCount; i++) { + aCells->AppendElement(mDoc->GetAccessible(targetIDs[i])); + } +} + +void RemoteAccessible::RowHeaderCells(nsTArray<RemoteAccessible*>* aCells) { + nsTArray<uint64_t> targetIDs; + Unused << mDoc->SendRowHeaderCells(mID, &targetIDs); + + size_t targetCount = targetIDs.Length(); + for (size_t i = 0; i < targetCount; i++) { + aCells->AppendElement(mDoc->GetAccessible(targetIDs[i])); + } +} + +bool RemoteAccessible::IsCellSelected() { + bool selected = false; + Unused << mDoc->SendIsCellSelected(mID, &selected); + return selected; +} + +RemoteAccessible* RemoteAccessible::TableCaption() { + uint64_t captionID = 0; + bool ok = false; + Unused << mDoc->SendTableCaption(mID, &captionID, &ok); + return ok ? mDoc->GetAccessible(captionID) : nullptr; +} + +void RemoteAccessible::TableSummary(nsString& aSummary) { + Unused << mDoc->SendTableSummary(mID, &aSummary); +} + +uint32_t RemoteAccessible::TableColumnCount() { + uint32_t count = 0; + Unused << mDoc->SendTableColumnCount(mID, &count); + return count; +} + +uint32_t RemoteAccessible::TableRowCount() { + uint32_t count = 0; + Unused << mDoc->SendTableRowCount(mID, &count); + return count; +} + +RemoteAccessible* RemoteAccessible::TableCellAt(uint32_t aRow, uint32_t aCol) { + uint64_t cellID = 0; + bool ok = false; + Unused << mDoc->SendTableCellAt(mID, aRow, aCol, &cellID, &ok); + return ok ? mDoc->GetAccessible(cellID) : nullptr; +} + +int32_t RemoteAccessible::TableCellIndexAt(uint32_t aRow, uint32_t aCol) { + int32_t index = 0; + Unused << mDoc->SendTableCellIndexAt(mID, aRow, aCol, &index); + return index; +} + +int32_t RemoteAccessible::TableColumnIndexAt(uint32_t aCellIndex) { + int32_t index = 0; + Unused << mDoc->SendTableColumnIndexAt(mID, aCellIndex, &index); + return index; +} + +int32_t RemoteAccessible::TableRowIndexAt(uint32_t aCellIndex) { + int32_t index = 0; + Unused << mDoc->SendTableRowIndexAt(mID, aCellIndex, &index); + return index; +} + +void RemoteAccessible::TableRowAndColumnIndicesAt(uint32_t aCellIndex, + int32_t* aRow, + int32_t* aCol) { + Unused << mDoc->SendTableRowAndColumnIndicesAt(mID, aCellIndex, aRow, aCol); +} + +uint32_t RemoteAccessible::TableColumnExtentAt(uint32_t aRow, uint32_t aCol) { + uint32_t extent = 0; + Unused << mDoc->SendTableColumnExtentAt(mID, aRow, aCol, &extent); + return extent; +} + +uint32_t RemoteAccessible::TableRowExtentAt(uint32_t aRow, uint32_t aCol) { + uint32_t extent = 0; + Unused << mDoc->SendTableRowExtentAt(mID, aRow, aCol, &extent); + return extent; +} + +void RemoteAccessible::TableColumnDescription(uint32_t aCol, + nsString& aDescription) { + Unused << mDoc->SendTableColumnDescription(mID, aCol, &aDescription); +} + +void RemoteAccessible::TableRowDescription(uint32_t aRow, + nsString& aDescription) { + Unused << mDoc->SendTableRowDescription(mID, aRow, &aDescription); +} + +bool RemoteAccessible::TableColumnSelected(uint32_t aCol) { + bool selected = false; + Unused << mDoc->SendTableColumnSelected(mID, aCol, &selected); + return selected; +} + +bool RemoteAccessible::TableRowSelected(uint32_t aRow) { + bool selected = false; + Unused << mDoc->SendTableRowSelected(mID, aRow, &selected); + return selected; +} + +bool RemoteAccessible::TableCellSelected(uint32_t aRow, uint32_t aCol) { + bool selected = false; + Unused << mDoc->SendTableCellSelected(mID, aRow, aCol, &selected); + return selected; +} + +uint32_t RemoteAccessible::TableSelectedCellCount() { + uint32_t count = 0; + Unused << mDoc->SendTableSelectedCellCount(mID, &count); + return count; +} + +uint32_t RemoteAccessible::TableSelectedColumnCount() { + uint32_t count = 0; + Unused << mDoc->SendTableSelectedColumnCount(mID, &count); + return count; +} + +uint32_t RemoteAccessible::TableSelectedRowCount() { + uint32_t count = 0; + Unused << mDoc->SendTableSelectedRowCount(mID, &count); + return count; +} + +void RemoteAccessible::TableSelectedCells( + nsTArray<RemoteAccessible*>* aCellIDs) { + AutoTArray<uint64_t, 30> cellIDs; + Unused << mDoc->SendTableSelectedCells(mID, &cellIDs); + aCellIDs->SetCapacity(cellIDs.Length()); + for (uint32_t i = 0; i < cellIDs.Length(); ++i) { + aCellIDs->AppendElement(mDoc->GetAccessible(cellIDs[i])); + } +} + +void RemoteAccessible::TableSelectedCellIndices( + nsTArray<uint32_t>* aCellIndices) { + Unused << mDoc->SendTableSelectedCellIndices(mID, aCellIndices); +} + +void RemoteAccessible::TableSelectedColumnIndices( + nsTArray<uint32_t>* aColumnIndices) { + Unused << mDoc->SendTableSelectedColumnIndices(mID, aColumnIndices); +} + +void RemoteAccessible::TableSelectedRowIndices( + nsTArray<uint32_t>* aRowIndices) { + Unused << mDoc->SendTableSelectedRowIndices(mID, aRowIndices); +} + +void RemoteAccessible::TableSelectColumn(uint32_t aCol) { + Unused << mDoc->SendTableSelectColumn(mID, aCol); +} + +void RemoteAccessible::TableSelectRow(uint32_t aRow) { + Unused << mDoc->SendTableSelectRow(mID, aRow); +} + +void RemoteAccessible::TableUnselectColumn(uint32_t aCol) { + Unused << mDoc->SendTableUnselectColumn(mID, aCol); +} + +void RemoteAccessible::TableUnselectRow(uint32_t aRow) { + Unused << mDoc->SendTableUnselectRow(mID, aRow); +} + +bool RemoteAccessible::TableIsProbablyForLayout() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TableIsProbablyForLayout(); + } + + bool forLayout = false; + Unused << mDoc->SendTableIsProbablyForLayout(mID, &forLayout); + return forLayout; +} + +RemoteAccessible* RemoteAccessible::AtkTableColumnHeader(int32_t aCol) { + uint64_t headerID = 0; + bool ok = false; + Unused << mDoc->SendAtkTableColumnHeader(mID, aCol, &headerID, &ok); + return ok ? mDoc->GetAccessible(headerID) : nullptr; +} + +RemoteAccessible* RemoteAccessible::AtkTableRowHeader(int32_t aRow) { + uint64_t headerID = 0; + bool ok = false; + Unused << mDoc->SendAtkTableRowHeader(mID, aRow, &headerID, &ok); + return ok ? mDoc->GetAccessible(headerID) : nullptr; +} + +void RemoteAccessible::SelectedItems(nsTArray<Accessible*>* aSelectedItems) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + RemoteAccessibleBase<RemoteAccessible>::SelectedItems(aSelectedItems); + return; + } + + AutoTArray<uint64_t, 10> itemIDs; + Unused << mDoc->SendSelectedItems(mID, &itemIDs); + aSelectedItems->SetCapacity(itemIDs.Length()); + for (size_t i = 0; i < itemIDs.Length(); ++i) { + aSelectedItems->AppendElement(mDoc->GetAccessible(itemIDs[i])); + } +} + +uint32_t RemoteAccessible::SelectedItemCount() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::SelectedItemCount(); + } + + uint32_t count = 0; + Unused << mDoc->SendSelectedItemCount(mID, &count); + return count; +} + +Accessible* RemoteAccessible::GetSelectedItem(uint32_t aIndex) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::GetSelectedItem(aIndex); + } + + uint64_t selectedItemID = 0; + bool ok = false; + Unused << mDoc->SendGetSelectedItem(mID, aIndex, &selectedItemID, &ok); + return ok ? mDoc->GetAccessible(selectedItemID) : nullptr; +} + +bool RemoteAccessible::IsItemSelected(uint32_t aIndex) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::IsItemSelected(aIndex); + } + + bool selected = false; + Unused << mDoc->SendIsItemSelected(mID, aIndex, &selected); + return selected; +} + +bool RemoteAccessible::AddItemToSelection(uint32_t aIndex) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::AddItemToSelection(aIndex); + } + + bool success = false; + Unused << mDoc->SendAddItemToSelection(mID, aIndex, &success); + return success; +} + +bool RemoteAccessible::RemoveItemFromSelection(uint32_t aIndex) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::RemoveItemFromSelection( + aIndex); + } + + bool success = false; + Unused << mDoc->SendRemoveItemFromSelection(mID, aIndex, &success); + return success; +} + +bool RemoteAccessible::SelectAll() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::SelectAll(); + } + + bool success = false; + Unused << mDoc->SendSelectAll(mID, &success); + return success; +} + +bool RemoteAccessible::UnselectAll() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::UnselectAll(); + } + + bool success = false; + Unused << mDoc->SendUnselectAll(mID, &success); + return success; +} + +bool RemoteAccessible::DoAction(uint8_t aIndex) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::DoAction(aIndex); + } + + bool success = false; + Unused << mDoc->SendDoAction(mID, aIndex, &success); + return success; +} + +uint8_t RemoteAccessible::ActionCount() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::ActionCount(); + } + + uint8_t count = 0; + Unused << mDoc->SendActionCount(mID, &count); + return count; +} + +void RemoteAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + RemoteAccessibleBase<RemoteAccessible>::ActionNameAt(aIndex, aName); + return; + } + + nsAutoString name; + Unused << mDoc->SendActionNameAt(mID, aIndex, &name); + + aName.Assign(name); +} + +KeyBinding RemoteAccessible::AccessKey() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::AccessKey(); + } + uint32_t key = 0; + uint32_t modifierMask = 0; + Unused << mDoc->SendAccessKey(mID, &key, &modifierMask); + return KeyBinding(key, modifierMask); +} + +void RemoteAccessible::AtkKeyBinding(nsString& aBinding) { + Unused << mDoc->SendAtkKeyBinding(mID, &aBinding); +} + +double RemoteAccessible::CurValue() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::CurValue(); + } + + double val = UnspecifiedNaN<double>(); + Unused << mDoc->SendCurValue(mID, &val); + return val; +} + +bool RemoteAccessible::SetCurValue(double aValue) { + bool success = false; + Unused << mDoc->SendSetCurValue(mID, aValue, &success); + return success; +} + +double RemoteAccessible::MinValue() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::MinValue(); + } + + double val = UnspecifiedNaN<double>(); + Unused << mDoc->SendMinValue(mID, &val); + return val; +} + +double RemoteAccessible::MaxValue() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::MaxValue(); + } + + double val = UnspecifiedNaN<double>(); + Unused << mDoc->SendMaxValue(mID, &val); + return val; +} + +double RemoteAccessible::Step() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Step(); + } + + double step = UnspecifiedNaN<double>(); + Unused << mDoc->SendStep(mID, &step); + return step; +} + +Accessible* RemoteAccessible::ChildAtPoint( + int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::ChildAtPoint(aX, aY, + aWhichChild); + } + + RemoteAccessible* target = this; + do { + if (target->IsOuterDoc()) { + if (target->ChildCount() == 0) { + // Return the OuterDoc if the requested point is within its bounds. + LayoutDeviceIntRect rect = target->Bounds(); + if (rect.Contains(aX, aY)) { + return target; + } + return nullptr; + } + DocAccessibleParent* childDoc = target->RemoteChildAt(0)->AsDoc(); + MOZ_ASSERT(childDoc); + if (childDoc->IsTopLevelInContentProcess()) { + // This is an OOP iframe. Remote calls can only work within their + // process, so they stop at OOP iframes. + if (aWhichChild == Accessible::EWhichChildAtPoint::DirectChild) { + // Return the child document if it's within the bounds of the iframe. + LayoutDeviceIntRect docRect = target->Bounds(); + if (docRect.Contains(aX, aY)) { + return childDoc; + } + return nullptr; + } + // We must continue the search from the child document. + target = childDoc; + } + } + PDocAccessibleParent* resultDoc = nullptr; + uint64_t resultID = 0; + Unused << target->mDoc->SendChildAtPoint(target->mID, aX, aY, + static_cast<uint32_t>(aWhichChild), + &resultDoc, &resultID); + // If resultDoc is null, this means there is no child at this point. + auto useDoc = static_cast<DocAccessibleParent*>(resultDoc); + target = resultDoc ? useDoc->GetAccessible(resultID) : nullptr; + } while (target && target->IsOuterDoc() && + aWhichChild == Accessible::EWhichChildAtPoint::DeepestChild); + return target; +} + +LayoutDeviceIntRect RemoteAccessible::Bounds() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Bounds(); + } + + LayoutDeviceIntRect rect; + Unused << mDoc->SendExtents(mID, false, &(rect.x), &(rect.y), &(rect.width), + &(rect.height)); + return rect; +} + +nsIntRect RemoteAccessible::BoundsInCSSPixels() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::BoundsInCSSPixels(); + } + + nsIntRect rect; + Unused << mDoc->SendExtentsInCSSPixels(mID, &rect.x, &rect.y, &rect.width, + &rect.height); + return rect; +} + +void RemoteAccessible::Language(nsString& aLocale) { + Unused << mDoc->SendLanguage(mID, &aLocale); +} + +void RemoteAccessible::DocType(nsString& aType) { + Unused << mDoc->SendDocType(mID, &aType); +} + +void RemoteAccessible::Title(nsString& aTitle) { + Unused << mDoc->SendTitle(mID, &aTitle); +} + +void RemoteAccessible::MimeType(nsString aMime) { + Unused << mDoc->SendMimeType(mID, &aMime); +} + +void RemoteAccessible::URLDocTypeMimeType(nsString& aURL, nsString& aDocType, + nsString& aMimeType) { + Unused << mDoc->SendURLDocTypeMimeType(mID, &aURL, &aDocType, &aMimeType); +} + +void RemoteAccessible::Extents(bool aNeedsScreenCoords, int32_t* aX, + int32_t* aY, int32_t* aWidth, int32_t* aHeight) { + Unused << mDoc->SendExtents(mID, aNeedsScreenCoords, aX, aY, aWidth, aHeight); +} + +void RemoteAccessible::DOMNodeID(nsString& aID) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::DOMNodeID(aID); + } + Unused << mDoc->SendDOMNodeID(mID, &aID); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/other/RemoteAccessible.h b/accessible/ipc/other/RemoteAccessible.h new file mode 100644 index 0000000000..57ba4db328 --- /dev/null +++ b/accessible/ipc/other/RemoteAccessible.h @@ -0,0 +1,107 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_RemoteAccessible_h +#define mozilla_a11y_RemoteAccessible_h + +#include "LocalAccessible.h" +#include "mozilla/a11y/RemoteAccessibleBase.h" +#include "mozilla/a11y/Role.h" +#include "nsIAccessibleText.h" +#include "nsIAccessibleTypes.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsRect.h" + +namespace mozilla { +namespace a11y { + +/** + * Functionality common across Linux and macOS for an accessibility tree node + * that originated in the parent process. + */ +class RemoteAccessible : public RemoteAccessibleBase<RemoteAccessible> { + public: + RemoteAccessible(uint64_t aID, RemoteAccessible* aParent, + DocAccessibleParent* aDoc, role aRole, AccType aType, + AccGenericType aGenericTypes, uint8_t aRoleMapEntryIndex) + : RemoteAccessibleBase(aID, aParent, aDoc, aRole, aType, aGenericTypes, + aRoleMapEntryIndex) { + MOZ_COUNT_CTOR(RemoteAccessible); + } + + MOZ_COUNTED_DTOR(RemoteAccessible) + +#include "mozilla/a11y/RemoteAccessibleShared.h" + + virtual uint32_t CharacterCount() const override; + + LayoutDeviceIntRect TextBounds( + int32_t aStartOffset, int32_t aEndOffset, + uint32_t aCoordType = + nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) override; + + LayoutDeviceIntRect CharBounds(int32_t aOffset, uint32_t aCoordType) override; + + virtual already_AddRefed<AccAttributes> TextAttributes( + bool aIncludeDefAttrs, int32_t aOffset, int32_t* aStartOffset, + int32_t* aEndOffset) override; + + virtual already_AddRefed<AccAttributes> DefaultTextAttributes() override; + + virtual uint32_t StartOffset() override; + virtual uint32_t EndOffset() override; + + virtual int32_t LinkIndexAtOffset(uint32_t aOffset) override; + + virtual bool DoAction(uint8_t aIndex) const override; + virtual uint8_t ActionCount() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual KeyBinding AccessKey() const override; + + virtual void SelectedItems(nsTArray<Accessible*>* aSelectedItems) override; + virtual uint32_t SelectedItemCount() override; + virtual Accessible* GetSelectedItem(uint32_t aIndex) override; + virtual bool IsItemSelected(uint32_t aIndex) override; + virtual bool AddItemToSelection(uint32_t aIndex) override; + virtual bool RemoveItemFromSelection(uint32_t aIndex) override; + virtual bool SelectAll() override; + virtual bool UnselectAll() override; + + virtual nsAtom* LandmarkRole() const override; + + virtual int32_t SelectionCount() override; + + using RemoteAccessibleBase<RemoteAccessible>::SelectionBoundsAt; + bool SelectionBoundsAt(int32_t aSelectionNum, nsString& aData, + int32_t* aStartOffset, int32_t* aEndOffset); + + virtual bool TableIsProbablyForLayout() override; + + /** + * Get all relations for this accessible. + */ + void Relations(nsTArray<RelationType>* aTypes, + nsTArray<nsTArray<RemoteAccessible*>>* aTargetSets) const; + + protected: + explicit RemoteAccessible(DocAccessibleParent* aThisAsDoc) + : RemoteAccessibleBase(aThisAsDoc) { + MOZ_COUNT_CTOR(RemoteAccessible); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// RemoteAccessible downcasting method + +inline RemoteAccessible* Accessible::AsRemote() { + return IsRemote() ? static_cast<RemoteAccessible*>(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/other/moz.build b/accessible/ipc/other/moz.build new file mode 100644 index 0000000000..2ea27c20c8 --- /dev/null +++ b/accessible/ipc/other/moz.build @@ -0,0 +1,51 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + + +if CONFIG["ACCESSIBILITY"]: + IPDL_SOURCES += [ + "PDocAccessible.ipdl", + ] + + EXPORTS.mozilla.a11y += [ + "DocAccessibleChild.h", + "RemoteAccessible.h", + ] + + SOURCES += [ + "DocAccessibleChild.cpp", + "RemoteAccessible.cpp", + ] + + LOCAL_INCLUDES += [ + "../../base", + "../../generic", + "../../xpcom", + ] + + if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + LOCAL_INCLUDES += [ + "/accessible/atk", + ] + elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + LOCAL_INCLUDES += [ + "/accessible/mac", + ] + elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + LOCAL_INCLUDES += [ + "/accessible/android", + ] + else: + LOCAL_INCLUDES += [ + "/accessible/other", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/accessible/ipc/win/COMPtrTypes.cpp b/accessible/ipc/win/COMPtrTypes.cpp new file mode 100644 index 0000000000..16b3a24cbe --- /dev/null +++ b/accessible/ipc/win/COMPtrTypes.cpp @@ -0,0 +1,75 @@ +/* -*- 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/COMPtrTypes.h" + +#include <utility> + +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/RefPtr.h" +#include "mozilla/a11y/LocalAccessible.h" +#include "mozilla/a11y/HandlerProvider.h" +#include "mozilla/a11y/Platform.h" +#include "mozilla/mscom/MainThreadHandoff.h" +#include "mozilla/mscom/Utils.h" +#include "nsXULAppAPI.h" + +using mozilla::mscom::MainThreadHandoff; +using mozilla::mscom::ProxyUniquePtr; +using mozilla::mscom::STAUniquePtr; + +namespace mozilla { +namespace a11y { + +IAccessibleHolder CreateHolderFromAccessible( + NotNull<LocalAccessible*> aAccToWrap) { + MOZ_ASSERT(NS_IsMainThread()); + + STAUniquePtr<IAccessible> iaToProxy; + aAccToWrap->GetNativeInterface(mscom::getter_AddRefs(iaToProxy)); + MOZ_DIAGNOSTIC_ASSERT(iaToProxy); + if (!iaToProxy) { + return nullptr; + } + + static const bool useHandler = + Preferences::GetBool("accessibility.handler.enabled", false) && + IsHandlerRegistered(); + + RefPtr<HandlerProvider> payload; + if (useHandler) { + payload = new HandlerProvider(IID_IAccessible, + mscom::ToInterceptorTargetPtr(iaToProxy)); + } + + ProxyUniquePtr<IAccessible> intercepted; + HRESULT hr = MainThreadHandoff::WrapInterface( + std::move(iaToProxy), payload, + (IAccessible**)mscom::getter_AddRefs(intercepted)); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + return nullptr; + } + + return IAccessibleHolder(std::move(intercepted)); +} + +IHandlerControlHolder CreateHolderFromHandlerControl( + mscom::ProxyUniquePtr<IHandlerControl> aHandlerControl) { + MOZ_ASSERT(aHandlerControl); + MOZ_ASSERT(XRE_IsContentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + if (!aHandlerControl) { + return nullptr; + } + + return IHandlerControlHolder(std::move(aHandlerControl)); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/COMPtrTypes.h b/accessible/ipc/win/COMPtrTypes.h new file mode 100644 index 0000000000..85c1f16f7c --- /dev/null +++ b/accessible/ipc/win/COMPtrTypes.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_COMPtrTypes_h +#define mozilla_a11y_COMPtrTypes_h + +#include "mozilla/a11y/AccessibleHandler.h" +#include "mozilla/mscom/ActCtxResource.h" +#include "mozilla/Attributes.h" +#include "mozilla/mscom/ActivationContext.h" +#include "mozilla/mscom/COMPtrHolder.h" +#include "mozilla/NotNull.h" + +#include <oleacc.h> + +namespace mozilla { +namespace a11y { + +class MOZ_RAII IAccessibleEnvironment : public mscom::ProxyStream::Environment { + public: + IAccessibleEnvironment() = default; + + bool Push() override { + mActCtxRgn = GetActCtx(); + return !!mActCtxRgn; + } + + bool Pop() override { return mActCtxRgn.Deactivate(); } + + private: + static const mscom::ActivationContext& GetActCtx() { + static const mscom::ActivationContext sActCtx( + mscom::ActCtxResource::GetAccessibilityResource()); + MOZ_DIAGNOSTIC_ASSERT(sActCtx); + return sActCtx; + } + + private: + mscom::ActivationContextRegion mActCtxRgn; +}; + +} // namespace a11y + +namespace mscom { +namespace detail { + +template <> +struct EnvironmentSelector<IAccessible> { + typedef a11y::IAccessibleEnvironment Type; +}; + +} // namespace detail +} // namespace mscom + +namespace a11y { + +typedef mozilla::mscom::COMPtrHolder<IAccessible, IID_IAccessible> + IAccessibleHolder; +typedef mozilla::mscom::COMPtrHolder<IDispatch, IID_IDispatch> IDispatchHolder; + +class LocalAccessible; + +IAccessibleHolder CreateHolderFromAccessible( + NotNull<LocalAccessible*> aAccToWrap); + +typedef mozilla::mscom::COMPtrHolder<IHandlerControl, IID_IHandlerControl> + IHandlerControlHolder; + +IHandlerControlHolder CreateHolderFromHandlerControl( + mscom::ProxyUniquePtr<IHandlerControl> aHandlerControl); + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_COMPtrTypes_h diff --git a/accessible/ipc/win/DocAccessibleChild.cpp b/accessible/ipc/win/DocAccessibleChild.cpp new file mode 100644 index 0000000000..3ecffb676c --- /dev/null +++ b/accessible/ipc/win/DocAccessibleChild.cpp @@ -0,0 +1,341 @@ +/* -*- 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 "DocAccessibleChild.h" + +#include "nsAccessibilityService.h" +#include "LocalAccessible-inl.h" +#include "mozilla/a11y/PlatformChild.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Document.h" +#include "mozilla/StaticPrefs_accessibility.h" +#include "RootAccessible.h" + +namespace mozilla { +namespace a11y { + +static StaticAutoPtr<PlatformChild> sPlatformChild; + +DocAccessibleChild::DocAccessibleChild(DocAccessible* aDoc, IProtocol* aManager) + : DocAccessibleChildBase(aDoc), mEmulatedWindowHandle(nullptr) { + MOZ_COUNT_CTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase); + if (!sPlatformChild) { + sPlatformChild = new PlatformChild(); + ClearOnShutdown(&sPlatformChild, ShutdownPhase::XPCOMShutdown); + } + + SetManager(aManager); + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // If the cache is enabled, we don't need to care whether this is + // constructed in the parent process. We must still set this flag because we + // defer sending any events unless it is set. + SetConstructedInParentProcess(); + } +} + +DocAccessibleChild::~DocAccessibleChild() { + MOZ_COUNT_DTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase); +} + +void DocAccessibleChild::Shutdown() { + if (IsConstructedInParentProcess()) { + DocAccessibleChildBase::Shutdown(); + return; + } + + PushDeferredEvent(MakeUnique<SerializedShutdown>(this)); + DetachDocument(); +} + +ipc::IPCResult DocAccessibleChild::RecvParentCOMProxy( + const IDispatchHolder& aParentCOMProxy) { + MOZ_ASSERT(!aParentCOMProxy.IsNull()); + mParentProxy.reset(const_cast<IDispatchHolder&>(aParentCOMProxy).Release()); + SetConstructedInParentProcess(); + + for (uint32_t i = 0, l = mDeferredEvents.Length(); i < l; ++i) { + mDeferredEvents[i]->Dispatch(); + } + + mDeferredEvents.Clear(); + + return IPC_OK(); +} + +ipc::IPCResult DocAccessibleChild::RecvEmulatedWindow( + const WindowsHandle& aEmulatedWindowHandle, + const IDispatchHolder& aEmulatedWindowCOMProxy) { + mEmulatedWindowHandle = reinterpret_cast<HWND>(aEmulatedWindowHandle); + if (!aEmulatedWindowCOMProxy.IsNull()) { + MOZ_ASSERT(!mEmulatedWindowProxy); + mEmulatedWindowProxy.reset( + const_cast<IDispatchHolder&>(aEmulatedWindowCOMProxy).Release()); + } + + return IPC_OK(); +} + +ipc::IPCResult DocAccessibleChild::RecvTopLevelDocCOMProxy( + const IAccessibleHolder& aCOMProxy) { + MOZ_ASSERT(!aCOMProxy.IsNull()); + mTopLevelDocProxy.reset(const_cast<IAccessibleHolder&>(aCOMProxy).Release()); + return IPC_OK(); +} + +HWND DocAccessibleChild::GetNativeWindowHandle() const { + if (mEmulatedWindowHandle) { + return mEmulatedWindowHandle; + } + + auto browser = static_cast<dom::BrowserChild*>(Manager()); + MOZ_ASSERT(browser); + // Iframe documents use the same window handle as their top level document. + auto topDoc = static_cast<DocAccessibleChild*>( + browser->GetTopLevelDocAccessibleChild()); + MOZ_ASSERT(topDoc); + if (topDoc != this && topDoc->mEmulatedWindowHandle) { + return topDoc->mEmulatedWindowHandle; + } + + return reinterpret_cast<HWND>(browser->GetNativeWindowHandle()); +} + +void DocAccessibleChild::PushDeferredEvent(UniquePtr<DeferredEvent> aEvent) { + DocAccessibleChild* topLevelIPCDoc = nullptr; + + if (mDoc && mDoc->IsRoot()) { + topLevelIPCDoc = this; + } else { + auto browserChild = static_cast<dom::BrowserChild*>(Manager()); + if (!browserChild) { + return; + } + + topLevelIPCDoc = static_cast<DocAccessibleChild*>( + browserChild->GetTopLevelDocAccessibleChild()); + } + + if (topLevelIPCDoc) { + topLevelIPCDoc->mDeferredEvents.AppendElement(std::move(aEvent)); + } +} + +bool DocAccessibleChild::SendEvent(const uint64_t& aID, const uint32_t& aType) { + if (IsConstructedInParentProcess()) { + return PDocAccessibleChild::SendEvent(aID, aType); + } + + PushDeferredEvent(MakeUnique<SerializedEvent>(this, aID, aType)); + return false; +} + +void DocAccessibleChild::MaybeSendShowEvent(ShowEventData& aData, + bool aFromUser) { + if (IsConstructedInParentProcess()) { + Unused << SendShowEvent(aData, aFromUser); + return; + } + + PushDeferredEvent(MakeUnique<SerializedShow>(this, aData, aFromUser)); +} + +bool DocAccessibleChild::SendHideEvent(const uint64_t& aRootID, + const bool& aFromUser) { + if (IsConstructedInParentProcess()) { + return PDocAccessibleChild::SendHideEvent(aRootID, aFromUser); + } + + PushDeferredEvent(MakeUnique<SerializedHide>(this, aRootID, aFromUser)); + return true; +} + +bool DocAccessibleChild::SendStateChangeEvent(const uint64_t& aID, + const uint64_t& aState, + const bool& aEnabled) { + if (IsConstructedInParentProcess()) { + return PDocAccessibleChild::SendStateChangeEvent(aID, aState, aEnabled); + } + + PushDeferredEvent( + MakeUnique<SerializedStateChange>(this, aID, aState, aEnabled)); + return true; +} + +LayoutDeviceIntRect DocAccessibleChild::GetCaretRectFor(const uint64_t& aID) { + LocalAccessible* target; + + if (aID) { + target = reinterpret_cast<LocalAccessible*>(aID); + } else { + target = mDoc; + } + + MOZ_ASSERT(target); + + HyperTextAccessible* text = target->AsHyperText(); + if (!text) { + return LayoutDeviceIntRect(); + } + + nsIWidget* widget = nullptr; + return text->GetCaretRect(&widget); +} + +bool DocAccessibleChild::SendFocusEvent(const uint64_t& aID) { + return SendFocusEvent(aID, GetCaretRectFor(aID)); +} + +bool DocAccessibleChild::SendFocusEvent(const uint64_t& aID, + const LayoutDeviceIntRect& aCaretRect) { + if (IsConstructedInParentProcess()) { + return PDocAccessibleChild::SendFocusEvent(aID, aCaretRect); + } + + PushDeferredEvent(MakeUnique<SerializedFocus>(this, aID, aCaretRect)); + return true; +} + +bool DocAccessibleChild::SendCaretMoveEvent(const uint64_t& aID, + const int32_t& aOffset, + const bool& aIsSelectionCollapsed, + const bool& aIsAtEndOfLine, + const int32_t& aGranularity) { + return SendCaretMoveEvent(aID, GetCaretRectFor(aID), aOffset, + aIsSelectionCollapsed, aIsAtEndOfLine, + aGranularity); +} + +bool DocAccessibleChild::SendCaretMoveEvent( + const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect, + const int32_t& aOffset, const bool& aIsSelectionCollapsed, + const bool& aIsAtEndOfLine, const int32_t& aGranularity) { + if (IsConstructedInParentProcess()) { + return PDocAccessibleChild::SendCaretMoveEvent( + aID, aCaretRect, aOffset, aIsSelectionCollapsed, aIsAtEndOfLine, + aGranularity); + } + + PushDeferredEvent(MakeUnique<SerializedCaretMove>( + this, aID, aCaretRect, aOffset, aIsSelectionCollapsed, aIsAtEndOfLine, + aGranularity)); + return true; +} + +bool DocAccessibleChild::SendTextChangeEvent( + const uint64_t& aID, const nsString& aStr, const int32_t& aStart, + const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser, + const bool aDoSync) { + if (IsConstructedInParentProcess()) { + if (aDoSync) { + // The AT is going to need to reenter content while the event is being + // dispatched synchronously. + return PDocAccessibleChild::SendSyncTextChangeEvent( + aID, aStr, aStart, aLen, aIsInsert, aFromUser); + } + return PDocAccessibleChild::SendTextChangeEvent(aID, aStr, aStart, aLen, + aIsInsert, aFromUser); + } + + PushDeferredEvent(MakeUnique<SerializedTextChange>( + this, aID, aStr, aStart, aLen, aIsInsert, aFromUser)); + return true; +} + +bool DocAccessibleChild::SendSelectionEvent(const uint64_t& aID, + const uint64_t& aWidgetID, + const uint32_t& aType) { + if (IsConstructedInParentProcess()) { + return PDocAccessibleChild::SendSelectionEvent(aID, aWidgetID, aType); + } + + PushDeferredEvent( + MakeUnique<SerializedSelection>(this, aID, aWidgetID, aType)); + return true; +} + +bool DocAccessibleChild::SendRoleChangedEvent(const a11y::role& aRole, + uint8_t aRoleMapEntryIndex) { + if (IsConstructedInParentProcess()) { + return PDocAccessibleChild::SendRoleChangedEvent(aRole, aRoleMapEntryIndex); + } + + PushDeferredEvent( + MakeUnique<SerializedRoleChanged>(this, aRole, aRoleMapEntryIndex)); + return true; +} + +bool DocAccessibleChild::SendScrollingEvent(const uint64_t& aID, + const uint64_t& aType, + const uint32_t& aScrollX, + const uint32_t& aScrollY, + const uint32_t& aMaxScrollX, + const uint32_t& aMaxScrollY) { + if (IsConstructedInParentProcess()) { + return PDocAccessibleChild::SendScrollingEvent( + aID, aType, aScrollX, aScrollY, aMaxScrollX, aMaxScrollY); + } + + PushDeferredEvent(MakeUnique<SerializedScrolling>( + this, aID, aType, aScrollX, aScrollY, aMaxScrollX, aMaxScrollY)); + return true; +} + +bool DocAccessibleChild::ConstructChildDocInParentProcess( + DocAccessibleChild* aNewChildDoc, uint64_t aUniqueID, uint32_t aMsaaID) { + if (IsConstructedInParentProcess()) { + // We may send the constructor immediately + auto browserChild = static_cast<dom::BrowserChild*>(Manager()); + MOZ_ASSERT(browserChild); + bool result = browserChild->SendPDocAccessibleConstructor( + aNewChildDoc, this, aUniqueID, + aNewChildDoc->mDoc->DocumentNode()->GetBrowsingContext(), aMsaaID, + IAccessibleHolder()); + if (result) { + aNewChildDoc->SetConstructedInParentProcess(); + } + return result; + } + + PushDeferredEvent(MakeUnique<SerializedChildDocConstructor>( + aNewChildDoc, this, aUniqueID, + aNewChildDoc->mDoc->DocumentNode()->GetBrowsingContext(), aMsaaID)); + return true; +} + +bool DocAccessibleChild::SendBindChildDoc(DocAccessibleChild* aChildDoc, + const uint64_t& aNewParentID) { + if (IsConstructedInParentProcess()) { + return DocAccessibleChildBase::SendBindChildDoc(aChildDoc, aNewParentID); + } + + PushDeferredEvent( + MakeUnique<SerializedBindChildDoc>(this, aChildDoc, aNewParentID)); + return true; +} + +ipc::IPCResult DocAccessibleChild::RecvRestoreFocus() { + FocusMgr()->ForceFocusEvent(); + return IPC_OK(); +} + +void DocAccessibleChild::SetEmbedderOnBridge(dom::BrowserBridgeChild* aBridge, + uint64_t aID) { + DocAccessibleChild* doc = aID ? this : nullptr; + if (IsConstructedInParentProcess()) { + MOZ_ASSERT(CanSend()); + aBridge->SetEmbedderAccessible(doc, aID); + return; + } + // Even though this doesn't fire an event, we must ensure this is sent in + // the correct order with insertions/removals, which are deferred until + // we are notified about parent process construction. Otherwise, the + // parent process might bind a child document to the wrong accessible if + // ids get reused. + PushDeferredEvent(MakeUnique<SerializedSetEmbedder>(aBridge, doc, aID)); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/DocAccessibleChild.h b/accessible/ipc/win/DocAccessibleChild.h new file mode 100644 index 0000000000..2f4eef00d7 --- /dev/null +++ b/accessible/ipc/win/DocAccessibleChild.h @@ -0,0 +1,392 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_DocAccessibleChild_h +#define mozilla_a11y_DocAccessibleChild_h + +#include "mozilla/a11y/COMPtrTypes.h" +#include "mozilla/a11y/DocAccessibleChildBase.h" +#include "mozilla/dom/BrowserBridgeChild.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/mscom/Ptr.h" + +namespace mozilla { +namespace a11y { + +/* + * These objects handle content side communication for an accessible document, + * and their lifetime is the same as the document they represent. + */ +class DocAccessibleChild : public DocAccessibleChildBase { + public: + DocAccessibleChild(DocAccessible* aDoc, IProtocol* aManager); + ~DocAccessibleChild(); + + virtual void Shutdown() override; + + virtual ipc::IPCResult RecvParentCOMProxy( + const IDispatchHolder& aParentCOMProxy) override; + virtual ipc::IPCResult RecvEmulatedWindow( + const WindowsHandle& aEmulatedWindowHandle, + const IDispatchHolder& aEmulatedWindowCOMProxy) override; + virtual ipc::IPCResult RecvTopLevelDocCOMProxy( + const IAccessibleHolder& aCOMProxy) override; + virtual ipc::IPCResult RecvRestoreFocus() override; + + HWND GetNativeWindowHandle() const; + IDispatch* GetEmulatedWindowIAccessible() const { + return mEmulatedWindowProxy.get(); + } + + IDispatch* GetParentIAccessible() const { return mParentProxy.get(); } + IAccessible* GetTopLevelDocIAccessible() const { + return mTopLevelDocProxy.get(); + } + + bool SendEvent(const uint64_t& aID, const uint32_t& type); + bool SendHideEvent(const uint64_t& aRootID, const bool& aFromUser); + bool SendStateChangeEvent(const uint64_t& aID, const uint64_t& aState, + const bool& aEnabled); + bool SendCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset, + const bool& aIsSelectionCollapsed, + const bool& aIsAtEndOfLine, + const int32_t& aGranularity); + bool SendCaretMoveEvent(const uint64_t& aID, + const LayoutDeviceIntRect& aCaretRect, + const int32_t& aOffset, + const bool& aIsSelectionCollapsed, + const bool& aIsAtEndOfLine, + const int32_t& aGranularity); + bool SendFocusEvent(const uint64_t& aID); + bool SendFocusEvent(const uint64_t& aID, + const LayoutDeviceIntRect& aCaretRect); + bool SendTextChangeEvent(const uint64_t& aID, const nsString& aStr, + const int32_t& aStart, const uint32_t& aLen, + const bool& aIsInsert, const bool& aFromUser, + const bool aDoSync = false); + bool SendSelectionEvent(const uint64_t& aID, const uint64_t& aWidgetID, + const uint32_t& aType); + bool SendRoleChangedEvent(const a11y::role& aRole, + uint8_t aRoleMapEntryIndex); + bool SendScrollingEvent(const uint64_t& aID, const uint64_t& aType, + const uint32_t& aScrollX, const uint32_t& aScrollY, + const uint32_t& aMaxScrollX, + const uint32_t& aMaxScrollY); + + bool ConstructChildDocInParentProcess(DocAccessibleChild* aNewChildDoc, + uint64_t aUniqueID, uint32_t aMsaaID); + + bool SendBindChildDoc(DocAccessibleChild* aChildDoc, + const uint64_t& aNewParentID); + + /** + * Set the embedder accessible on a BrowserBridgeChild to an accessible in + * this document. + * Sending this will be deferred if this DocAccessibleChild hasn't been + * constructed in the parent process yet. + */ + void SetEmbedderOnBridge(dom::BrowserBridgeChild* aBridge, uint64_t aID); + + protected: + virtual void MaybeSendShowEvent(ShowEventData& aData, + bool aFromUser) override; + + private: + void RemoveDeferredConstructor(); + + LayoutDeviceIntRect GetCaretRectFor(const uint64_t& aID); + + /** + * DocAccessibleChild should not fire events until it has asynchronously + * received the COM proxy for its parent. OTOH, content a11y may still be + * attempting to fire events during this window of time. If this object does + * not yet have its parent proxy, instead of immediately sending the events to + * our parent, we enqueue them to mDeferredEvents. As soon as + * RecvParentCOMProxy is called, we play back mDeferredEvents. + */ + struct DeferredEvent { + void Dispatch() { Dispatch(mTarget); } + + virtual ~DeferredEvent() {} + + protected: + explicit DeferredEvent(DocAccessibleChild* aTarget) : mTarget(aTarget) {} + + virtual void Dispatch(DocAccessibleChild* aIPCDoc) = 0; + + private: + DocAccessibleChild* mTarget; + }; + + void PushDeferredEvent(UniquePtr<DeferredEvent> aEvent); + + struct SerializedShow final : public DeferredEvent { + SerializedShow(DocAccessibleChild* aTarget, ShowEventData& aEventData, + bool aFromUser) + : DeferredEvent(aTarget), + mEventData(aEventData.ID(), aEventData.Idx(), + nsTArray<AccessibleData>(), aEventData.EventSuppressed()), + mFromUser(aFromUser) { + // Since IPDL doesn't generate a move constructor for ShowEventData, + // we move NewTree manually (ugh). We still construct with an empty + // NewTree above so that the compiler catches any changes made to the + // ShowEventData structure in IPDL. + mEventData.NewTree() = std::move(aEventData.NewTree()); + } + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendShowEvent(mEventData, mFromUser); + } + + ShowEventData mEventData; + bool mFromUser; + }; + + struct SerializedHide final : public DeferredEvent { + SerializedHide(DocAccessibleChild* aTarget, uint64_t aRootID, + bool aFromUser) + : DeferredEvent(aTarget), mRootID(aRootID), mFromUser(aFromUser) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendHideEvent(mRootID, mFromUser); + } + + uint64_t mRootID; + bool mFromUser; + }; + + struct SerializedStateChange final : public DeferredEvent { + SerializedStateChange(DocAccessibleChild* aTarget, uint64_t aID, + uint64_t aState, bool aEnabled) + : DeferredEvent(aTarget), + mID(aID), + mState(aState), + mEnabled(aEnabled) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendStateChangeEvent(mID, mState, mEnabled); + } + + uint64_t mID; + uint64_t mState; + bool mEnabled; + }; + + struct SerializedCaretMove final : public DeferredEvent { + SerializedCaretMove(DocAccessibleChild* aTarget, uint64_t aID, + const LayoutDeviceIntRect& aCaretRect, int32_t aOffset, + bool aIsSelectionCollapsed, bool aIsAtEndOfLine, + int32_t aGranularity) + : DeferredEvent(aTarget), + mID(aID), + mCaretRect(aCaretRect), + mOffset(aOffset), + mIsSelectionCollapsed(aIsSelectionCollapsed), + mIsAtEndOfLine(aIsAtEndOfLine), + mGranularity(aGranularity) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendCaretMoveEvent(mID, mCaretRect, mOffset, + mIsSelectionCollapsed, + mIsAtEndOfLine, mGranularity); + } + + uint64_t mID; + LayoutDeviceIntRect mCaretRect; + int32_t mOffset; + bool mIsSelectionCollapsed; + bool mIsAtEndOfLine; + int32_t mGranularity; + }; + + struct SerializedFocus final : public DeferredEvent { + SerializedFocus(DocAccessibleChild* aTarget, uint64_t aID, + const LayoutDeviceIntRect& aCaretRect) + : DeferredEvent(aTarget), mID(aID), mCaretRect(aCaretRect) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendFocusEvent(mID, mCaretRect); + } + + uint64_t mID; + LayoutDeviceIntRect mCaretRect; + }; + + struct SerializedTextChange final : public DeferredEvent { + SerializedTextChange(DocAccessibleChild* aTarget, uint64_t aID, + const nsString& aStr, int32_t aStart, uint32_t aLen, + bool aIsInsert, bool aFromUser) + : DeferredEvent(aTarget), + mID(aID), + mStr(aStr), + mStart(aStart), + mLen(aLen), + mIsInsert(aIsInsert), + mFromUser(aFromUser) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendTextChangeEvent(mID, mStr, mStart, mLen, mIsInsert, + mFromUser, false); + } + + uint64_t mID; + nsString mStr; + int32_t mStart; + uint32_t mLen; + bool mIsInsert; + bool mFromUser; + }; + + struct SerializedSelection final : public DeferredEvent { + SerializedSelection(DocAccessibleChild* aTarget, uint64_t aID, + uint64_t aWidgetID, uint32_t aType) + : DeferredEvent(aTarget), + mID(aID), + mWidgetID(aWidgetID), + mType(aType) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendSelectionEvent(mID, mWidgetID, mType); + } + + uint64_t mID; + uint64_t mWidgetID; + uint32_t mType; + }; + + struct SerializedRoleChanged final : public DeferredEvent { + explicit SerializedRoleChanged(DocAccessibleChild* aTarget, + a11y::role aRole, uint8_t aRoleMapEntryIndex) + : DeferredEvent(aTarget), + mRole(aRole), + mRoleMapEntryIndex(aRoleMapEntryIndex) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendRoleChangedEvent(mRole, mRoleMapEntryIndex); + } + + a11y::role mRole; + uint8_t mRoleMapEntryIndex; + }; + + struct SerializedScrolling final : public DeferredEvent { + explicit SerializedScrolling(DocAccessibleChild* aTarget, uint64_t aID, + uint64_t aType, uint32_t aScrollX, + uint32_t aScrollY, uint32_t aMaxScrollX, + uint32_t aMaxScrollY) + : DeferredEvent(aTarget), + mID(aID), + mType(aType), + mScrollX(aScrollX), + mScrollY(aScrollY), + mMaxScrollX(aMaxScrollX), + mMaxScrollY(aMaxScrollY) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendScrollingEvent(mID, mType, mScrollX, mScrollY, + mMaxScrollX, mMaxScrollY); + } + + uint64_t mID; + uint64_t mType; + uint32_t mScrollX; + uint32_t mScrollY; + uint32_t mMaxScrollX; + uint32_t mMaxScrollY; + }; + + struct SerializedEvent final : public DeferredEvent { + SerializedEvent(DocAccessibleChild* aTarget, uint64_t aID, uint32_t aType) + : DeferredEvent(aTarget), mID(aID), mType(aType) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { + Unused << aIPCDoc->SendEvent(mID, mType); + } + + uint64_t mID; + uint32_t mType; + }; + + struct SerializedChildDocConstructor final : public DeferredEvent { + SerializedChildDocConstructor(DocAccessibleChild* aIPCDoc, + DocAccessibleChild* aParentIPCDoc, + uint64_t aUniqueID, + dom::BrowsingContext* aBrowsingContext, + uint32_t aMsaaID) + : DeferredEvent(aParentIPCDoc), + mIPCDoc(aIPCDoc), + mUniqueID(aUniqueID), + mBrowsingContext(aBrowsingContext), + mMsaaID(aMsaaID) {} + + void Dispatch(DocAccessibleChild* aParentIPCDoc) override { + auto browserChild = + static_cast<dom::BrowserChild*>(aParentIPCDoc->Manager()); + MOZ_ASSERT(browserChild); + Unused << browserChild->SendPDocAccessibleConstructor( + mIPCDoc, aParentIPCDoc, mUniqueID, mBrowsingContext, mMsaaID, + IAccessibleHolder()); + mIPCDoc->SetConstructedInParentProcess(); + } + + DocAccessibleChild* mIPCDoc; + uint64_t mUniqueID; + // By the time we replay this, the document and its BrowsingContext might + // be dead, so we use MaybeDiscardedBrowsingContext here. Ideally, we just + // wouldn't replay this, but this is tricky because IPDL should manage the + // lifetime of DocAccessibleChild, which means we must send the constructor. + dom::MaybeDiscardedBrowsingContext mBrowsingContext; + uint32_t mMsaaID; + }; + + friend struct SerializedChildDocConstructor; + + struct SerializedBindChildDoc final : public DeferredEvent { + SerializedBindChildDoc(DocAccessibleChild* aParentDoc, + DocAccessibleChild* aChildDoc, uint64_t aNewParentID) + : DeferredEvent(aParentDoc), + mChildDoc(aChildDoc), + mNewParentID(aNewParentID) {} + + void Dispatch(DocAccessibleChild* aParentIPCDoc) override { + Unused << aParentIPCDoc->SendBindChildDoc(mChildDoc, mNewParentID); + } + + DocAccessibleChild* mChildDoc; + uint64_t mNewParentID; + }; + + struct SerializedShutdown final : public DeferredEvent { + explicit SerializedShutdown(DocAccessibleChild* aTarget) + : DeferredEvent(aTarget) {} + + void Dispatch(DocAccessibleChild* aIPCDoc) override { aIPCDoc->Shutdown(); } + }; + + struct SerializedSetEmbedder final : public DeferredEvent { + SerializedSetEmbedder(dom::BrowserBridgeChild* aBridge, + DocAccessibleChild* aDoc, uint64_t aID) + : DeferredEvent(aDoc), mBridge(aBridge), mID(aID) {} + + void Dispatch(DocAccessibleChild* aDoc) override { + mBridge->SetEmbedderAccessible(aDoc, mID); + } + + RefPtr<dom::BrowserBridgeChild> mBridge; + uint64_t mID; + }; + + mscom::ProxyUniquePtr<IDispatch> mParentProxy; + mscom::ProxyUniquePtr<IDispatch> mEmulatedWindowProxy; + mscom::ProxyUniquePtr<IAccessible> mTopLevelDocProxy; + nsTArray<UniquePtr<DeferredEvent>> mDeferredEvents; + HWND mEmulatedWindowHandle; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_DocAccessibleChild_h diff --git a/accessible/ipc/win/HandlerProvider.cpp b/accessible/ipc/win/HandlerProvider.cpp new file mode 100644 index 0000000000..6e8e5e52c3 --- /dev/null +++ b/accessible/ipc/win/HandlerProvider.cpp @@ -0,0 +1,974 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/HandlerProvider.h" + +#include <memory.h> + +#include <utility> + +#include "Accessible2_3.h" +#include "AccessibleApplication.h" +#include "AccessibleDocument.h" +#include "AccessibleEditableText.h" +#include "AccessibleImage.h" +#include "AccessibleRelation.h" +#include "AccessibleTable.h" +#include "AccessibleTable2.h" +#include "AccessibleTableCell.h" +#include "HandlerData.h" +#include "HandlerData_i.c" +#include "ISimpleDOM.h" +#include "mozilla/Assertions.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/a11y/AccessibleWrap.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/mscom/AgileReference.h" +#include "mozilla/mscom/FastMarshaler.h" +#include "mozilla/mscom/Interceptor.h" +#include "mozilla/mscom/MainThreadHandoff.h" +#include "mozilla/mscom/MainThreadInvoker.h" +#include "mozilla/mscom/Ptr.h" +#include "mozilla/mscom/StructStream.h" +#include "mozilla/mscom/Utils.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "uiautomation.h" + +namespace mozilla { +namespace a11y { + +HandlerProvider::HandlerProvider(REFIID aIid, + mscom::InterceptorTargetPtr<IUnknown> aTarget) + : mRefCnt(0), + mMutex("mozilla::a11y::HandlerProvider::mMutex"), + mTargetUnkIid(aIid), + mTargetUnk(std::move(aTarget)), + mPayloadMutex("mozilla::a11y::HandlerProvider::mPayloadMutex") {} + +HRESULT +HandlerProvider::QueryInterface(REFIID riid, void** ppv) { + if (!ppv) { + return E_INVALIDARG; + } + + if (riid == IID_IUnknown || riid == IID_IGeckoBackChannel) { + RefPtr<IUnknown> punk(static_cast<IGeckoBackChannel*>(this)); + punk.forget(ppv); + return S_OK; + } + + if (riid == IID_IMarshal) { + if (!mFastMarshalUnk) { + HRESULT hr = + mscom::FastMarshaler::Create(static_cast<IGeckoBackChannel*>(this), + getter_AddRefs(mFastMarshalUnk)); + if (FAILED(hr)) { + return hr; + } + } + + return mFastMarshalUnk->QueryInterface(riid, ppv); + } + + return E_NOINTERFACE; +} + +ULONG +HandlerProvider::AddRef() { return ++mRefCnt; } + +ULONG +HandlerProvider::Release() { + ULONG result = --mRefCnt; + if (!result) { + delete this; + } + return result; +} + +HRESULT +HandlerProvider::GetHandler(NotNull<CLSID*> aHandlerClsid) { + if (!IsTargetInterfaceCacheable()) { + return E_NOINTERFACE; + } + + *aHandlerClsid = CLSID_AccessibleHandler; + return S_OK; +} + +void HandlerProvider::GetAndSerializePayload( + const MutexAutoLock&, NotNull<mscom::IInterceptor*> aInterceptor) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (mSerializer) { + return; + } + + IA2PayloadPtr payload; + { // Scope for lock + MutexAutoLock lock(mPayloadMutex); + if (mPayload) { + // The payload was already built by prebuildPayload() called during a + // bulk fetch operation. + payload = std::move(mPayload); + } + } + + if (!payload) { + // We don't have a pre-built payload, so build it now. + payload.reset(new IA2Payload()); + if (!mscom::InvokeOnMainThread( + "HandlerProvider::BuildInitialIA2Data", this, + &HandlerProvider::BuildInitialIA2Data, + std::forward<NotNull<mscom::IInterceptor*>>(aInterceptor), + std::forward<StaticIA2Data*>(&payload->mStaticData), + std::forward<DynamicIA2Data*>(&payload->mDynamicData)) || + !payload->mDynamicData.mUniqueId) { + return; + } + } + + // But we set mGeckoBackChannel on the current thread which resides in the + // MTA. This is important to ensure that COM always invokes + // IGeckoBackChannel methods in an MTA background thread. + RefPtr<IGeckoBackChannel> payloadRef(this); + // AddRef/Release pair for this reference is handled by payloadRef + payload->mGeckoBackChannel = this; + + mSerializer = MakeUnique<mscom::StructToStream>(*payload, &IA2Payload_Encode); +} + +HRESULT +HandlerProvider::GetHandlerPayloadSize( + NotNull<mscom::IInterceptor*> aInterceptor, + NotNull<DWORD*> aOutPayloadSize) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!IsTargetInterfaceCacheable()) { + // No handler, so no payload for this instance. + return E_NOTIMPL; + } + + MutexAutoLock lock(mMutex); + + GetAndSerializePayload(lock, aInterceptor); + + if (!mSerializer || !(*mSerializer)) { + // Failed payload serialization is non-fatal + *aOutPayloadSize = mscom::StructToStream::GetEmptySize(); + return S_OK; + } + + *aOutPayloadSize = mSerializer->GetSize(); + return S_OK; +} + +void HandlerProvider::BuildStaticIA2Data( + NotNull<mscom::IInterceptor*> aInterceptor, StaticIA2Data* aOutData) { + MOZ_ASSERT(aOutData); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mTargetUnk); + MOZ_ASSERT(IsTargetInterfaceCacheable()); + + // Include interfaces the client is likely to request. + // This is cheap here and saves multiple cross-process calls later. + // These interfaces must be released in ReleaseStaticIA2DataInterfaces! + + // If the target is already an IAccessible2, this pointer is redundant. + // However, the target might be an IAccessibleHyperlink, etc., in which + // case the client will almost certainly QI for IAccessible2. + HRESULT hr = aInterceptor->GetInterceptorForIID(NEWEST_IA2_IID, + (void**)&aOutData->mIA2); + if (FAILED(hr)) { + // IA2 should always be present, so something has + // gone very wrong if this fails. + aOutData->mIA2 = nullptr; + return; + } + + // Some of these interfaces aren't present on all accessibles, + // so it's not a failure if these interfaces can't be fetched. + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleHypertext2, + (void**)&aOutData->mIAHypertext); + if (FAILED(hr)) { + aOutData->mIAHypertext = nullptr; + } + + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleHyperlink, + (void**)&aOutData->mIAHyperlink); + if (FAILED(hr)) { + aOutData->mIAHyperlink = nullptr; + } + + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleTable, + (void**)&aOutData->mIATable); + if (FAILED(hr)) { + aOutData->mIATable = nullptr; + } + + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleTable2, + (void**)&aOutData->mIATable2); + if (FAILED(hr)) { + aOutData->mIATable2 = nullptr; + } + + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleTableCell, + (void**)&aOutData->mIATableCell); + if (FAILED(hr)) { + aOutData->mIATableCell = nullptr; + } +} + +void HandlerProvider::BuildDynamicIA2Data(DynamicIA2Data* aOutIA2Data, + bool aMarshaledByCom) { + MOZ_ASSERT(aOutIA2Data); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsTargetInterfaceCacheable()); + + if (!mTargetUnk) { + return; + } + + RefPtr<NEWEST_IA2_INTERFACE> target; + HRESULT hr = + mTargetUnk.get()->QueryInterface(NEWEST_IA2_IID, getter_AddRefs(target)); + if (FAILED(hr)) { + return; + } + + hr = E_UNEXPECTED; + + auto hasFailed = [&hr]() -> bool { return FAILED(hr); }; + + auto cleanup = [aOutIA2Data, aMarshaledByCom]() -> void { + CleanupDynamicIA2Data(*aOutIA2Data, aMarshaledByCom); + }; + + mscom::ExecuteWhen<decltype(hasFailed), decltype(cleanup)> onFail(hasFailed, + cleanup); + + // When allocating memory to be returned to the client, you *must* use + // allocMem, not CoTaskMemAlloc! + auto allocMem = [aMarshaledByCom](size_t aSize) { + if (aMarshaledByCom) { + return ::CoTaskMemAlloc(aSize); + } + // We use midl_user_allocate rather than CoTaskMemAlloc because this + // struct is being marshaled by RPC, not COM. + return ::midl_user_allocate(aSize); + }; + + const VARIANT kChildIdSelf = {VT_I4}; + VARIANT varVal; + + hr = target->accLocation(&aOutIA2Data->mLeft, &aOutIA2Data->mTop, + &aOutIA2Data->mWidth, &aOutIA2Data->mHeight, + kChildIdSelf); + if (FAILED(hr)) { + return; + } + + hr = target->get_accRole(kChildIdSelf, &aOutIA2Data->mRole); + if (FAILED(hr)) { + return; + } + + hr = target->get_accState(kChildIdSelf, &varVal); + if (FAILED(hr)) { + return; + } + + aOutIA2Data->mState = varVal.lVal; + + hr = target->get_accKeyboardShortcut(kChildIdSelf, + &aOutIA2Data->mKeyboardShortcut); + if (FAILED(hr)) { + return; + } + + hr = target->get_accName(kChildIdSelf, &aOutIA2Data->mName); + if (FAILED(hr)) { + return; + } + + hr = target->get_accDescription(kChildIdSelf, &aOutIA2Data->mDescription); + if (FAILED(hr)) { + return; + } + + hr = target->get_accDefaultAction(kChildIdSelf, &aOutIA2Data->mDefaultAction); + if (FAILED(hr)) { + return; + } + + hr = target->get_accChildCount(&aOutIA2Data->mChildCount); + if (FAILED(hr)) { + return; + } + + hr = target->get_accValue(kChildIdSelf, &aOutIA2Data->mValue); + if (FAILED(hr)) { + return; + } + + hr = target->get_states(&aOutIA2Data->mIA2States); + if (FAILED(hr)) { + return; + } + + hr = target->get_attributes(&aOutIA2Data->mAttributes); + if (FAILED(hr)) { + return; + } + + HWND hwnd; + hr = target->get_windowHandle(&hwnd); + if (FAILED(hr)) { + return; + } + + aOutIA2Data->mHwnd = PtrToLong(hwnd); + + hr = target->get_locale(&aOutIA2Data->mIA2Locale); + if (FAILED(hr)) { + return; + } + + hr = target->role(&aOutIA2Data->mIA2Role); + if (FAILED(hr)) { + return; + } + + RefPtr<IAccessibleAction> action; + // It is not an error if this fails. + hr = mTargetUnk.get()->QueryInterface(IID_IAccessibleAction, + getter_AddRefs(action)); + if (SUCCEEDED(hr)) { + hr = action->nActions(&aOutIA2Data->mNActions); + if (FAILED(hr)) { + return; + } + } + + RefPtr<IAccessibleTableCell> cell; + // It is not an error if this fails. + hr = mTargetUnk.get()->QueryInterface(IID_IAccessibleTableCell, + getter_AddRefs(cell)); + if (SUCCEEDED(hr)) { + hr = cell->get_rowColumnExtents( + &aOutIA2Data->mRowIndex, &aOutIA2Data->mColumnIndex, + &aOutIA2Data->mRowExtent, &aOutIA2Data->mColumnExtent, + &aOutIA2Data->mCellIsSelected); + if (FAILED(hr)) { + return; + } + + // Because the same headers can apply to many cells, include the ids of + // header cells, rather than the actual objects. Otherwise, we might + // end up marshaling the same objects (and their payloads) many times. + IUnknown** headers = nullptr; + hr = cell->get_rowHeaderCells(&headers, &aOutIA2Data->mNRowHeaderCells); + if (FAILED(hr)) { + return; + } + if (aOutIA2Data->mNRowHeaderCells > 0) { + aOutIA2Data->mRowHeaderCellIds = static_cast<long*>( + allocMem(sizeof(long) * aOutIA2Data->mNRowHeaderCells)); + for (long i = 0; i < aOutIA2Data->mNRowHeaderCells; ++i) { + RefPtr<IAccessible2> headerAcc; + hr = headers[i]->QueryInterface(IID_IAccessible2, + getter_AddRefs(headerAcc)); + MOZ_ASSERT(SUCCEEDED(hr)); + headers[i]->Release(); + hr = headerAcc->get_uniqueID(&aOutIA2Data->mRowHeaderCellIds[i]); + MOZ_ASSERT(SUCCEEDED(hr)); + } + } + ::CoTaskMemFree(headers); + + hr = cell->get_columnHeaderCells(&headers, + &aOutIA2Data->mNColumnHeaderCells); + if (FAILED(hr)) { + return; + } + if (aOutIA2Data->mNColumnHeaderCells > 0) { + aOutIA2Data->mColumnHeaderCellIds = static_cast<long*>( + allocMem(sizeof(long) * aOutIA2Data->mNColumnHeaderCells)); + for (long i = 0; i < aOutIA2Data->mNColumnHeaderCells; ++i) { + RefPtr<IAccessible2> headerAcc; + hr = headers[i]->QueryInterface(IID_IAccessible2, + getter_AddRefs(headerAcc)); + MOZ_ASSERT(SUCCEEDED(hr)); + headers[i]->Release(); + hr = headerAcc->get_uniqueID(&aOutIA2Data->mColumnHeaderCellIds[i]); + MOZ_ASSERT(SUCCEEDED(hr)); + } + } + ::CoTaskMemFree(headers); + } + + // NB: get_uniqueID should be the final property retrieved in this method, + // as its presence is used to determine whether the rest of this data + // retrieval was successful. + hr = target->get_uniqueID(&aOutIA2Data->mUniqueId); +} + +void HandlerProvider::BuildInitialIA2Data( + NotNull<mscom::IInterceptor*> aInterceptor, StaticIA2Data* aOutStaticData, + DynamicIA2Data* aOutDynamicData) { + BuildStaticIA2Data(aInterceptor, aOutStaticData); + if (!aOutStaticData->mIA2) { + return; + } + BuildDynamicIA2Data(aOutDynamicData); +} + +bool HandlerProvider::IsTargetInterfaceCacheable() { + return MarshalAs(mTargetUnkIid) == NEWEST_IA2_IID || + mTargetUnkIid == IID_IAccessibleHyperlink; +} + +HRESULT +HandlerProvider::WriteHandlerPayload(NotNull<mscom::IInterceptor*> aInterceptor, + NotNull<IStream*> aStream) { + if (!IsTargetInterfaceCacheable()) { + // No handler, so no payload for this instance. + return E_NOTIMPL; + } + + MutexAutoLock lock(mMutex); + + if (!mSerializer || !(*mSerializer)) { + // Failed payload serialization is non-fatal + mscom::StructToStream emptyStruct; + return emptyStruct.Write(aStream); + } + + HRESULT hr = mSerializer->Write(aStream); + + mSerializer.reset(); + + return hr; +} + +REFIID +HandlerProvider::MarshalAs(REFIID aIid) { + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if (aIid == IID_IDispatch || aIid == IID_IAccessible || + aIid == IID_IAccessible2 || aIid == IID_IAccessible2_2 || + aIid == IID_IAccessible2_3) { + // This should always be the newest IA2 interface ID + return NEWEST_IA2_IID; + } + // Otherwise we juse return the identity. + return aIid; +} + +HRESULT +HandlerProvider::DisconnectHandlerRemotes() { + // If a handlerProvider call is pending on another thread, + // CoDisconnectObject won't release this HandlerProvider immediately. + // However, the interceptor and its target (mTargetUnk) might be destroyed. + mTargetUnk = nullptr; + + IUnknown* unk = static_cast<IGeckoBackChannel*>(this); + return ::CoDisconnectObject(unk, 0); +} + +HRESULT +HandlerProvider::IsInterfaceMaybeSupported(REFIID aIid) { + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if (aIid == IID_IUnknown || aIid == IID_IDispatch || + aIid == IID_IAccessible || aIid == IID_IServiceProvider || + aIid == IID_IEnumVARIANT || aIid == IID_IAccessible2 || + aIid == IID_IAccessible2_2 || aIid == IID_IAccessible2_3 || + aIid == IID_IAccessibleAction || aIid == IID_IAccessibleApplication || + aIid == IID_IAccessibleComponent || aIid == IID_IAccessibleDocument || + aIid == IID_IAccessibleEditableText || aIid == IID_IAccessibleHyperlink || + aIid == IID_IAccessibleHypertext || aIid == IID_IAccessibleHypertext2 || + aIid == IID_IAccessibleImage || aIid == IID_IAccessibleRelation || + aIid == IID_IAccessibleTable || aIid == IID_IAccessibleTable2 || + aIid == IID_IAccessibleTableCell || aIid == IID_IAccessibleText || + aIid == IID_IAccessibleValue || aIid == IID_ISimpleDOMNode || + aIid == IID_ISimpleDOMDocument || aIid == IID_ISimpleDOMText || + aIid == IID_IAccessibleEx || aIid == IID_IRawElementProviderSimple) { + return S_OK; + } + return E_NOINTERFACE; +} + +REFIID +HandlerProvider::GetEffectiveOutParamIid(REFIID aCallIid, ULONG aCallMethod) { + if (aCallIid == IID_IAccessibleTable || aCallIid == IID_IAccessibleTable2 || + aCallIid == IID_IAccessibleDocument || + aCallIid == IID_IAccessibleTableCell || + aCallIid == IID_IAccessibleRelation) { + return NEWEST_IA2_IID; + } + + // IAccessible2_2::accessibleWithCaret + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if ((aCallIid == IID_IAccessible2_2 || aCallIid == IID_IAccessible2_3) && + aCallMethod == 47) { + return NEWEST_IA2_IID; + } + + // IAccessible::get_accSelection + if ((aCallIid == IID_IAccessible || aCallIid == IID_IAccessible2 || + aCallIid == IID_IAccessible2_2 || aCallIid == IID_IAccessible2_3) && + aCallMethod == 19) { + return IID_IEnumVARIANT; + } + + MOZ_ASSERT(false); + return IID_IUnknown; +} + +HRESULT +HandlerProvider::NewInstance( + REFIID aIid, mscom::InterceptorTargetPtr<IUnknown> aTarget, + NotNull<mscom::IHandlerProvider**> aOutNewPayload) { + RefPtr<IHandlerProvider> newPayload( + new HandlerProvider(aIid, std::move(aTarget))); + newPayload.forget(aOutNewPayload.get()); + return S_OK; +} + +void HandlerProvider::SetHandlerControlOnMainThread( + DWORD aPid, mscom::ProxyUniquePtr<IHandlerControl> aCtrl) { + MOZ_ASSERT(NS_IsMainThread()); + + auto content = dom::ContentChild::GetSingleton(); + MOZ_ASSERT(content); + + IHandlerControlHolder holder( + CreateHolderFromHandlerControl(std::move(aCtrl))); + Unused << content->SendA11yHandlerControl(aPid, holder); +} + +HRESULT +HandlerProvider::put_HandlerControl(long aPid, IHandlerControl* aCtrl) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!aCtrl) { + return E_INVALIDARG; + } + + auto ptrProxy = mscom::ToProxyUniquePtr(aCtrl); + + if (!mscom::InvokeOnMainThread( + "HandlerProvider::SetHandlerControlOnMainThread", this, + &HandlerProvider::SetHandlerControlOnMainThread, + static_cast<DWORD>(aPid), std::move(ptrProxy))) { + return E_FAIL; + } + + return S_OK; +} + +HRESULT +HandlerProvider::Refresh(DynamicIA2Data* aOutData) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!mTargetUnk) { + return CO_E_OBJNOTCONNECTED; + } + + if (!mscom::InvokeOnMainThread("HandlerProvider::BuildDynamicIA2Data", this, + &HandlerProvider::BuildDynamicIA2Data, + std::forward<DynamicIA2Data*>(aOutData), + /* aMarshaledByCom */ true)) { + return E_FAIL; + } + + if (!aOutData->mUniqueId) { + // BuildDynamicIA2Data failed. + if (!mTargetUnk) { + // Even though we checked this before, the accessible can be shut down + // before BuildDynamicIA2Data executes on the main thread. + return CO_E_OBJNOTCONNECTED; + } + return E_FAIL; + } + + return S_OK; +} + +void HandlerProvider::PrebuildPayload( + NotNull<mscom::IInterceptor*> aInterceptor) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mPayloadMutex); + mPayload.reset(new IA2Payload()); + BuildInitialIA2Data(aInterceptor, &mPayload->mStaticData, + &mPayload->mDynamicData); + if (!mPayload->mDynamicData.mUniqueId) { + // Building the payload failed. + mPayload.reset(); + } +} + +template <typename Interface> +HRESULT HandlerProvider::ToWrappedObject(Interface** aObj) { + MOZ_ASSERT(NS_IsMainThread()); + mscom::STAUniquePtr<Interface> inObj(*aObj); + RefPtr<HandlerProvider> hprov = new HandlerProvider( + __uuidof(Interface), mscom::ToInterceptorTargetPtr(inObj)); + HRESULT hr = + mscom::MainThreadHandoff::WrapInterface(std::move(inObj), hprov, aObj); + if (FAILED(hr)) { + *aObj = nullptr; + return hr; + } + // Build the payload for this object now to avoid a cross-thread call when + // marshaling it later. + RefPtr<mscom::IInterceptor> interceptor; + hr = (*aObj)->QueryInterface(mscom::IID_IInterceptor, + getter_AddRefs(interceptor)); + MOZ_ASSERT(SUCCEEDED(hr)); + // Even though we created a new HandlerProvider, that won't be used if + // there's an existing Interceptor. Therefore, we must get the + // HandlerProvider from the Interceptor. + RefPtr<mscom::IInterceptorSink> interceptorSink; + interceptor->GetEventSink(getter_AddRefs(interceptorSink)); + MOZ_ASSERT(interceptorSink); + RefPtr<mscom::IMainThreadHandoff> handoff; + hr = interceptorSink->QueryInterface(mscom::IID_IMainThreadHandoff, + getter_AddRefs(handoff)); + // If a11y Interceptors stop using MainThreadHandoff as their event sink, we + // *really* want to know about it ASAP. + MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr), + "A11y Interceptor isn't using MainThreadHandoff"); + RefPtr<mscom::IHandlerProvider> usedIHprov; + handoff->GetHandlerProvider(getter_AddRefs(usedIHprov)); + MOZ_ASSERT(usedIHprov); + auto usedHprov = static_cast<HandlerProvider*>(usedIHprov.get()); + usedHprov->PrebuildPayload(WrapNotNull(interceptor)); + return hr; +} + +void HandlerProvider::GetAllTextInfoMainThread( + BSTR* aText, IAccessibleHyperlink*** aHyperlinks, long* aNHyperlinks, + IA2TextSegment** aAttribRuns, long* aNAttribRuns, HRESULT* result) { + MOZ_ASSERT(aText); + MOZ_ASSERT(aHyperlinks); + MOZ_ASSERT(aNHyperlinks); + MOZ_ASSERT(aAttribRuns); + MOZ_ASSERT(aNAttribRuns); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTargetUnk) { + *result = CO_E_OBJNOTCONNECTED; + return; + } + + RefPtr<IAccessibleHypertext2> ht; + HRESULT hr = + mTargetUnk->QueryInterface(IID_IAccessibleHypertext2, getter_AddRefs(ht)); + if (FAILED(hr)) { + *result = hr; + return; + } + + hr = ht->get_text(0, IA2_TEXT_OFFSET_LENGTH, aText); + if (FAILED(hr)) { + *result = hr; + return; + } + + if (hr == S_FALSE) { + // No text. + *aHyperlinks = nullptr; + *aNHyperlinks = 0; + *aAttribRuns = nullptr; + *aNAttribRuns = 0; + *result = S_FALSE; + return; + } + + hr = ht->get_hyperlinks(aHyperlinks, aNHyperlinks); + if (FAILED(hr)) { + *aHyperlinks = nullptr; + // -1 signals to the handler that it should call hyperlinks itself. + *aNHyperlinks = -1; + } + // We must wrap these hyperlinks in an interceptor. + for (long index = 0; index < *aNHyperlinks; ++index) { + ToWrappedObject(&(*aHyperlinks)[index]); + } + + // Fetch all attribute runs. + nsTArray<IA2TextSegment> attribRuns; + long end = 0; + long length = ::SysStringLen(*aText); + while (end < length) { + long offset = end; + long start; + BSTR attribs; + // The (exclusive) end of the last run is the start of the next run. + hr = ht->get_attributes(offset, &start, &end, &attribs); + // Bug 1421873: Gecko can return end <= offset in some rare cases, which + // isn't valid. This is perhaps because the text mutated during the loop + // for some reason, making this offset invalid. + if (FAILED(hr) || end <= offset) { + break; + } + attribRuns.AppendElement(IA2TextSegment({attribs, start, end})); + } + + // Put the attribute runs in a COM array. + *aNAttribRuns = attribRuns.Length(); + *aAttribRuns = static_cast<IA2TextSegment*>( + ::CoTaskMemAlloc(sizeof(IA2TextSegment) * *aNAttribRuns)); + for (long index = 0; index < *aNAttribRuns; ++index) { + (*aAttribRuns)[index] = attribRuns[index]; + } + + *result = S_OK; +} + +HRESULT +HandlerProvider::get_AllTextInfo(BSTR* aText, + IAccessibleHyperlink*** aHyperlinks, + long* aNHyperlinks, + IA2TextSegment** aAttribRuns, + long* aNAttribRuns) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!mTargetUnk) { + return CO_E_OBJNOTCONNECTED; + } + + HRESULT hr; + if (!mscom::InvokeOnMainThread( + "HandlerProvider::GetAllTextInfoMainThread", this, + &HandlerProvider::GetAllTextInfoMainThread, + std::forward<BSTR*>(aText), + std::forward<IAccessibleHyperlink***>(aHyperlinks), + std::forward<long*>(aNHyperlinks), + std::forward<IA2TextSegment**>(aAttribRuns), + std::forward<long*>(aNAttribRuns), std::forward<HRESULT*>(&hr))) { + return E_FAIL; + } + + return hr; +} + +void HandlerProvider::GetRelationsInfoMainThread(IARelationData** aRelations, + long* aNRelations, + HRESULT* hr) { + MOZ_ASSERT(aRelations); + MOZ_ASSERT(aNRelations); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTargetUnk) { + *hr = CO_E_OBJNOTCONNECTED; + return; + } + + RefPtr<NEWEST_IA2_INTERFACE> acc; + *hr = mTargetUnk.get()->QueryInterface(NEWEST_IA2_IID, getter_AddRefs(acc)); + if (FAILED(*hr)) { + return; + } + + *hr = acc->get_nRelations(aNRelations); + if (FAILED(*hr)) { + return; + } + + auto rawRels = MakeUnique<IAccessibleRelation*[]>(*aNRelations); + *hr = acc->get_relations(*aNRelations, rawRels.get(), aNRelations); + if (FAILED(*hr)) { + return; + } + + *aRelations = static_cast<IARelationData*>( + ::CoTaskMemAlloc(sizeof(IARelationData) * *aNRelations)); + for (long index = 0; index < *aNRelations; ++index) { + IAccessibleRelation* rawRel = rawRels[index]; + IARelationData& relData = (*aRelations)[index]; + *hr = rawRel->get_relationType(&relData.mType); + if (FAILED(*hr)) { + relData.mType = nullptr; + } + *hr = rawRel->get_nTargets(&relData.mNTargets); + if (FAILED(*hr)) { + relData.mNTargets = -1; + } + rawRel->Release(); + } + + *hr = S_OK; +} + +HRESULT +HandlerProvider::get_RelationsInfo(IARelationData** aRelations, + long* aNRelations) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!mTargetUnk) { + return CO_E_OBJNOTCONNECTED; + } + + HRESULT hr; + if (!mscom::InvokeOnMainThread( + "HandlerProvider::GetRelationsInfoMainThread", this, + &HandlerProvider::GetRelationsInfoMainThread, + std::forward<IARelationData**>(aRelations), + std::forward<long*>(aNRelations), std::forward<HRESULT*>(&hr))) { + return E_FAIL; + } + + return hr; +} + +// Helper function for GetAllChildrenMainThread. +static bool SetChildDataForTextLeaf(NEWEST_IA2_INTERFACE* acc, + AccChildData& data) { + const VARIANT kChildIdSelf = {VT_I4}; + VARIANT varVal; + + // 1. Check whether this is a text leaf. + + // 1.1. A text leaf always has ROLE_SYSTEM_TEXT or ROLE_SYSTEM_WHITESPACE. + HRESULT hr = acc->get_accRole(kChildIdSelf, &varVal); + if (FAILED(hr)) { + return false; + } + if (varVal.vt != VT_I4) { + return false; + } + long role = varVal.lVal; + if (role != ROLE_SYSTEM_TEXT && role != ROLE_SYSTEM_WHITESPACE) { + return false; + } + + // 1.2. A text leaf doesn't support IAccessibleText. + RefPtr<IAccessibleText> iaText; + hr = acc->QueryInterface(IID_IAccessibleText, getter_AddRefs(iaText)); + if (SUCCEEDED(hr)) { + return false; + } + + // 1.3. A text leaf doesn't have children. + long count; + hr = acc->get_accChildCount(&count); + if (FAILED(hr) || count != 0) { + return false; + } + + // 2. Update |data| with the data for this text leaf. + // Because marshaling objects is more expensive than marshaling other data, + // we just marshal the data we need for text leaf children, rather than + // marshaling the full accessible object. + + // |data| has already been zeroed, so we don't need to do anything if these + // calls fail. + acc->get_accName(kChildIdSelf, &data.mText); + data.mTextRole = role; + acc->get_uniqueID(&data.mTextId); + acc->get_accState(kChildIdSelf, &varVal); + data.mTextState = varVal.lVal; + acc->accLocation(&data.mTextLeft, &data.mTextTop, &data.mTextWidth, + &data.mTextHeight, kChildIdSelf); + + return true; +} + +void HandlerProvider::GetAllChildrenMainThread(AccChildData** aChildren, + ULONG* aNChildren, HRESULT* hr) { + MOZ_ASSERT(aChildren); + MOZ_ASSERT(aNChildren); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTargetUnk) { + *hr = CO_E_OBJNOTCONNECTED; + return; + } + + RefPtr<NEWEST_IA2_INTERFACE> acc; + *hr = mTargetUnk.get()->QueryInterface(NEWEST_IA2_IID, getter_AddRefs(acc)); + if (FAILED(*hr)) { + return; + } + + long count; + *hr = acc->get_accChildCount(&count); + if (FAILED(*hr)) { + return; + } + MOZ_ASSERT(count >= 0); + + if (count == 0) { + *aChildren = nullptr; + *aNChildren = 0; + return; + } + + RefPtr<IEnumVARIANT> enumVar; + *hr = mTargetUnk.get()->QueryInterface(IID_IEnumVARIANT, + getter_AddRefs(enumVar)); + if (FAILED(*hr)) { + return; + } + + auto rawChildren = MakeUnique<VARIANT[]>(count); + *hr = enumVar->Next((ULONG)count, rawChildren.get(), aNChildren); + if (FAILED(*hr)) { + *aChildren = nullptr; + *aNChildren = 0; + return; + } + + *aChildren = static_cast<AccChildData*>( + ::CoTaskMemAlloc(sizeof(AccChildData) * *aNChildren)); + for (ULONG index = 0; index < *aNChildren; ++index) { + (*aChildren)[index] = {}; + AccChildData& child = (*aChildren)[index]; + + MOZ_ASSERT(rawChildren[index].vt == VT_DISPATCH); + MOZ_ASSERT(rawChildren[index].pdispVal); + RefPtr<NEWEST_IA2_INTERFACE> childAcc; + *hr = rawChildren[index].pdispVal->QueryInterface(NEWEST_IA2_IID, + getter_AddRefs(childAcc)); + rawChildren[index].pdispVal->Release(); + MOZ_ASSERT(SUCCEEDED(*hr)); + if (FAILED(*hr)) { + continue; + } + + if (!SetChildDataForTextLeaf(childAcc, child)) { + // This isn't a text leaf. Marshal the accessible. + childAcc.forget(&child.mAccessible); + // We must wrap this accessible in an Interceptor. + ToWrappedObject(&child.mAccessible); + } + } + + *hr = S_OK; +} + +HRESULT +HandlerProvider::get_AllChildren(AccChildData** aChildren, ULONG* aNChildren) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + HRESULT hr; + if (!mscom::InvokeOnMainThread( + "HandlerProvider::GetAllChildrenMainThread", this, + &HandlerProvider::GetAllChildrenMainThread, + std::forward<AccChildData**>(aChildren), + std::forward<ULONG*>(aNChildren), std::forward<HRESULT*>(&hr))) { + return E_FAIL; + } + + return hr; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/HandlerProvider.h b/accessible/ipc/win/HandlerProvider.h new file mode 100644 index 0000000000..f517a0f991 --- /dev/null +++ b/accessible/ipc/win/HandlerProvider.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#ifndef mozilla_a11y_HandlerProvider_h +#define mozilla_a11y_HandlerProvider_h + +#include "mozilla/a11y/AccessibleHandler.h" +#include "mozilla/a11y/HandlerDataCleanup.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" +#include "mozilla/mscom/IHandlerProvider.h" +#include "mozilla/mscom/Ptr.h" +#include "mozilla/mscom/StructStream.h" +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" +#include "HandlerData.h" + +struct NEWEST_IA2_INTERFACE; + +namespace mozilla { + +namespace mscom { + +class StructToStream; + +} // namespace mscom + +namespace a11y { + +class HandlerProvider final : public IGeckoBackChannel, + public mscom::IHandlerProvider { + public: + HandlerProvider(REFIID aIid, mscom::InterceptorTargetPtr<IUnknown> aTarget); + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IHandlerProvider + STDMETHODIMP GetHandler(NotNull<CLSID*> aHandlerClsid) override; + STDMETHODIMP GetHandlerPayloadSize(NotNull<mscom::IInterceptor*> aInterceptor, + NotNull<DWORD*> aOutPayloadSize) override; + STDMETHODIMP WriteHandlerPayload(NotNull<mscom::IInterceptor*> aInterceptor, + NotNull<IStream*> aStream) override; + STDMETHODIMP_(REFIID) MarshalAs(REFIID aIid) override; + STDMETHODIMP DisconnectHandlerRemotes() override; + STDMETHODIMP IsInterfaceMaybeSupported(REFIID aIid) override; + STDMETHODIMP_(REFIID) + GetEffectiveOutParamIid(REFIID aCallIid, ULONG aCallMethod) override; + STDMETHODIMP NewInstance( + REFIID aIid, mscom::InterceptorTargetPtr<IUnknown> aTarget, + NotNull<mscom::IHandlerProvider**> aOutNewPayload) override; + + // IGeckoBackChannel + STDMETHODIMP put_HandlerControl(long aPid, IHandlerControl* aCtrl) override; + STDMETHODIMP Refresh(DynamicIA2Data* aOutData) override; + STDMETHODIMP get_AllTextInfo(BSTR* aText, IAccessibleHyperlink*** aHyperlinks, + long* aNHyperlinks, IA2TextSegment** aAttribRuns, + long* aNAttribRuns) override; + STDMETHODIMP get_RelationsInfo(IARelationData** aRelations, + long* aNRelations) override; + STDMETHODIMP get_AllChildren(AccChildData** aChildren, + ULONG* aNChildren) override; + + private: + ~HandlerProvider() = default; + + void SetHandlerControlOnMainThread( + DWORD aPid, mscom::ProxyUniquePtr<IHandlerControl> aCtrl); + void GetAndSerializePayload(const MutexAutoLock&, + NotNull<mscom::IInterceptor*> aInterceptor); + void BuildStaticIA2Data(NotNull<mscom::IInterceptor*> aInterceptor, + StaticIA2Data* aOutData); + /** + * Pass true for aMarshaledByCom if this struct is being directly marshaled as + * an out parameter of a COM method, currently only + * IGeckoBackChannel::Refresh. + * When aMarshaledByCom is false, this means the struct is being marshaled + * by RPC encoding functions. This means we must allocate memory differently, + * even though we're using this as part of a COM handler payload. + */ + void BuildDynamicIA2Data(DynamicIA2Data* aOutIA2Data, + bool aMarshaledByCom = false); + void BuildInitialIA2Data(NotNull<mscom::IInterceptor*> aInterceptor, + StaticIA2Data* aOutStaticData, + DynamicIA2Data* aOutDynamicData); + bool IsTargetInterfaceCacheable(); + + /** + * Build the payload for later marshaling. + * This is intended to be used during a bulk fetch operation and must only be + * called from the main thread. + */ + void PrebuildPayload(NotNull<mscom::IInterceptor*> aInterceptor); + + // Replace a raw object from the main thread with a wrapped, intercepted + // object suitable for calling from the MTA. + // The reference to the original object is adopted; i.e. you should not + // separately release it. + // This is intended for objects returned from method calls on the main thread. + template <typename Interface> + HRESULT ToWrappedObject(Interface** aObj); + void GetAllTextInfoMainThread(BSTR* aText, + IAccessibleHyperlink*** aHyperlinks, + long* aNHyperlinks, + IA2TextSegment** aAttribRuns, + long* aNAttribRuns, HRESULT* result); + void GetRelationsInfoMainThread(IARelationData** aRelations, + long* aNRelations, HRESULT* result); + void GetAllChildrenMainThread(AccChildData** aChildren, ULONG* aNChildren, + HRESULT* result); + + Atomic<uint32_t> mRefCnt; + Mutex mMutex MOZ_UNANNOTATED; // Protects mSerializer + const IID mTargetUnkIid; + mscom::InterceptorTargetPtr<IUnknown> + mTargetUnk; // Constant, main thread only + UniquePtr<mscom::StructToStream> mSerializer; + RefPtr<IUnknown> mFastMarshalUnk; + + struct IA2PayloadDeleter { + void operator()(IA2Payload* aPayload) { + // When CoMarshalInterface writes interfaces out to a stream, it AddRefs. + // Therefore, we must release our references after this. + ReleaseStaticIA2DataInterfaces(aPayload->mStaticData); + CleanupDynamicIA2Data(aPayload->mDynamicData); + delete aPayload; + } + }; + using IA2PayloadPtr = UniquePtr<IA2Payload, IA2PayloadDeleter>; + + // Used when the payload is built prior to marshaling the object by a bulk + // fetch operation. See prebuildPayload(). + IA2PayloadPtr mPayload; + Mutex mPayloadMutex MOZ_UNANNOTATED; // Protects mPayload +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerProvider_h diff --git a/accessible/ipc/win/PDocAccessible.ipdl b/accessible/ipc/win/PDocAccessible.ipdl new file mode 100644 index 0000000000..15a25a6000 --- /dev/null +++ b/accessible/ipc/win/PDocAccessible.ipdl @@ -0,0 +1,155 @@ +/* -*- 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 protocol PBrowser; + +include DocAccessibleTypes; + +include "mozilla/GfxMessageUtils.h"; + +using mozilla::a11y::role from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::IDispatchHolder from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::AccType from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::AccGenericType from "mozilla/a11y/IPCTypes.h"; +using mozilla::a11y::CacheUpdateType from "mozilla/a11y/IPCTypes.h"; +[RefCounted] using mozilla::a11y::AccAttributes from "mozilla/a11y/IPCTypes.h"; +using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h"; +using mozilla::LayoutDeviceIntRect from "Units.h"; +using mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h"; + +namespace mozilla { +namespace a11y { + +struct AccessibleData +{ + uint64_t ID; + int32_t MsaaID; + role Role; + uint32_t ChildrenCount; + AccType Type; + AccGenericType GenericTypes; + uint8_t RoleMapEntryIndex; +}; + +struct ShowEventData +{ + uint64_t ID; + uint32_t Idx; + AccessibleData[] NewTree; + bool EventSuppressed; +}; + +struct TextRangeData +{ + uint64_t StartID; + uint64_t EndID; + int32_t StartOffset; + int32_t EndOffset; +}; + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +sync protocol PDocAccessible +{ + manager PBrowser; + +parent: + async Shutdown(); + + /* + * Notify the parent process the document in the child process is firing an + * event. + */ + async Event(uint64_t aID, uint32_t type); + async ShowEvent(ShowEventData data, bool aFromUser); + async HideEvent(uint64_t aRootID, bool aFromUser); + async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled); + async CaretMoveEvent(uint64_t aID, LayoutDeviceIntRect aCaretRect, + int32_t aOffset, bool aIsAtEndOfLine, + bool aIsSelectionCollapsed, int32_t aGranularity); + async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen, + bool aIsInsert, bool aFromUser); + sync SyncTextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, + uint32_t aLen, bool aIsInsert, bool aFromUser); + async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType); + async RoleChangedEvent(role aRole, uint8_t aRoleMapEntryIndex); + async FocusEvent(uint64_t aID, LayoutDeviceIntRect aCaretRect); + async VirtualCursorChangeEvent(uint64_t aID, + uint64_t aOldPosition, + int32_t aOldStartOffset, int32_t aOldEndOffset, + uint64_t aPosition, + int32_t aStartOffset, int32_t aEndOffset, + int16_t aReason, int16_t aBoundaryType, + bool aFromUservcEvent); + async ScrollingEvent(uint64_t aID, uint64_t aType, + uint32_t aScrollX, uint32_t aScrollY, + uint32_t aMaxScrollX, uint32_t aMaxScrollY); + async TextSelectionChangeEvent(uint64_t aID, TextRangeData[] aSelection); + + /* + * Tell the parent document to bind the existing document as a new child + * document. + */ + async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID); + + /* + * Cache The World + * aDispatchShowEvent is true when a show event with the first accessible in + * the cache list as the target should be dispatched after the cache is + * populated. The show event will have a from-user flag value of false. + */ + async Cache(CacheUpdateType aUpdateType, CacheData[] aData, bool aDispatchShowEvent); + + /* + * Lists of accessibles that either gained or lost a selected state. + */ + async SelectedAccessiblesChanged(uint64_t[] aSelectedIDs, uint64_t[] aUnselectedIDs); + + /* + * Tell the parent process that the given Accessibles are about to be moved + * via subsequent hide and show events. + */ + async AccessiblesWillMove(uint64_t[] aIDs); + +child: + /** + * We use IDispatchHolder instead of IAccessibleHolder for the following two + * methods because of sandboxing. IAccessible::get_accParent returns the parent + * as an IDispatch. COM is not smart enough to understand that IAccessible is + * derived from IDispatch, so during marshaling it attempts to QueryInterface + * on the parent's proxy for IDispatch. This will fail with E_ACCESSDENIED + * thanks to the sandbox. We can avoid this entirely by just giving content + * the correct interface to begin with: IDispatch. + */ + async ParentCOMProxy(IDispatchHolder aParentCOMProxy); + async EmulatedWindow(WindowsHandle aEmulatedWindowHandle, + IDispatchHolder aEmulatedWindowCOMProxy); + async TopLevelDocCOMProxy(IAccessibleHolder aCOMProxy); + /* + * Called as a result of focus shifting from chrome to content + * elements through keyboard navigation. + */ + async RestoreFocus(); + + async TakeFocus(uint64_t aID); + async TakeSelection(uint64_t aID); + async SetSelected(uint64_t aID, bool aSelected); + async ScrollTo(uint64_t aID, uint32_t aScrollType); + + /* + * Verify the cache. Used for testing purposes. + */ + async VerifyCache(uint64_t aID, uint64_t aCacheDomain, AccAttributes aFields); + + async DoActionAsync(uint64_t aID, uint8_t aIndex); + + async SetCaretOffset(uint64_t aID, int32_t aOffset); + + async __delete__(); +}; + +} +} diff --git a/accessible/ipc/win/PlatformChild.cpp b/accessible/ipc/win/PlatformChild.cpp new file mode 100644 index 0000000000..31026bcf2d --- /dev/null +++ b/accessible/ipc/win/PlatformChild.cpp @@ -0,0 +1,87 @@ +/* -*- 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/AccessibleHandler.h" +#include "mozilla/a11y/PlatformChild.h" +#include "mozilla/mscom/ActCtxResource.h" +#include "mozilla/mscom/EnsureMTA.h" +#include "mozilla/mscom/InterceptorLog.h" + +#include "Accessible2.h" +#include "Accessible2_2.h" +#include "AccessibleHypertext2.h" +#include "AccessibleTable2.h" +#include "AccessibleTableCell.h" + +#include "AccessibleDocument_i.c" +#include "AccessibleHypertext2_i.c" + +namespace mozilla { +namespace a11y { + +/** + * Unfortunately the COM interceptor does not intrinsically handle array + * outparams. Instead we manually define the relevant metadata here, and + * register it in a call to mozilla::mscom::RegisterArrayData. + * @see mozilla::mscom::ArrayData + */ +static const mozilla::mscom::ArrayData sPlatformChildArrayData[] = { + {IID_IEnumVARIANT, 3, 1, VT_DISPATCH, IID_IDispatch, 2}, + {IID_IAccessible2, 30, 1, VT_UNKNOWN | VT_BYREF, IID_IAccessibleRelation, + 2}, + {IID_IAccessibleRelation, 7, 1, VT_UNKNOWN | VT_BYREF, NEWEST_IA2_IID, 2}, + {IID_IAccessible2_2, 48, 2, VT_UNKNOWN | VT_BYREF, NEWEST_IA2_IID, 3, + mozilla::mscom::ArrayData::Flag::eAllocatedByServer}, + {IID_IAccessibleTableCell, 4, 0, VT_UNKNOWN | VT_BYREF, NEWEST_IA2_IID, 1, + mozilla::mscom::ArrayData::Flag::eAllocatedByServer}, + {IID_IAccessibleTableCell, 7, 0, VT_UNKNOWN | VT_BYREF, NEWEST_IA2_IID, 1, + mozilla::mscom::ArrayData::Flag::eAllocatedByServer}, + {IID_IAccessibleHypertext2, 25, 0, VT_UNKNOWN | VT_BYREF, + IID_IAccessibleHyperlink, 1, + mozilla::mscom::ArrayData::Flag::eAllocatedByServer}, + {IID_IAccessibleTable2, 12, 0, VT_UNKNOWN | VT_BYREF, NEWEST_IA2_IID, 1, + mozilla::mscom::ArrayData::Flag::eAllocatedByServer}}; + +// Type libraries are thread-neutral, so we can register those from any +// apartment. OTOH, proxies must be registered from within the apartment where +// we intend to instantiate them. Therefore RegisterProxy() must be called +// via EnsureMTA. +PlatformChild::PlatformChild() + : mIA2Proxy(mozilla::mscom::RegisterProxy(L"ia2marshal.dll")), + mAccTypelib(mozilla::mscom::RegisterTypelib( + L"oleacc.dll", + mozilla::mscom::RegistrationFlags::eUseSystemDirectory)), + mMiscTypelib(mozilla::mscom::RegisterTypelib(L"Accessible.tlb")), + mSdnTypelib(mozilla::mscom::RegisterTypelib(L"AccessibleMarshal.dll")) { + auto actCtxResource = mscom::ActCtxResource::GetAccessibilityResource(); + + mozilla::mscom::MTADeletePtr<mozilla::mscom::ActivationContextRegion> + tmpActCtxMTA; + mozilla::mscom::EnsureMTA([actCtxResource, &tmpActCtxMTA]() -> void { + tmpActCtxMTA.reset( + new mozilla::mscom::ActivationContextRegion(actCtxResource)); + }); + mActCtxMTA = std::move(tmpActCtxMTA); + + mozilla::mscom::InterceptorLog::Init(); + mozilla::mscom::RegisterArrayData(sPlatformChildArrayData); + + UniquePtr<mozilla::mscom::RegisteredProxy> customProxy; + mozilla::mscom::EnsureMTA([&customProxy]() -> void { + customProxy = mozilla::mscom::RegisterProxy(); + }); + mCustomProxy = std::move(customProxy); + + // IA2 needs to be registered in both the main thread's STA as well as the MTA + UniquePtr<mozilla::mscom::RegisteredProxy> ia2ProxyMTA; + mozilla::mscom::EnsureMTA([&ia2ProxyMTA]() -> void { + ia2ProxyMTA = mozilla::mscom::RegisterProxy(L"ia2marshal.dll"); + }); + mIA2ProxyMTA = std::move(ia2ProxyMTA); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/PlatformChild.h b/accessible/ipc/win/PlatformChild.h new file mode 100644 index 0000000000..a175d6f453 --- /dev/null +++ b/accessible/ipc/win/PlatformChild.h @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_PlatformChild_h +#define mozilla_a11y_PlatformChild_h + +#include "mozilla/mscom/ActivationContext.h" +#include "mozilla/mscom/Ptr.h" +#include "mozilla/mscom/Registration.h" + +namespace mozilla { +namespace a11y { + +class PlatformChild { + public: + PlatformChild(); + + PlatformChild(PlatformChild&) = delete; + PlatformChild(PlatformChild&&) = delete; + PlatformChild& operator=(PlatformChild&) = delete; + PlatformChild& operator=(PlatformChild&&) = delete; + + private: + mscom::MTADeletePtr<mozilla::mscom::ActivationContextRegion> mActCtxMTA; + UniquePtr<mozilla::mscom::RegisteredProxy> mCustomProxy; + UniquePtr<mozilla::mscom::RegisteredProxy> mIA2Proxy; + UniquePtr<mozilla::mscom::RegisteredProxy> mIA2ProxyMTA; + UniquePtr<mozilla::mscom::RegisteredProxy> mAccTypelib; + UniquePtr<mozilla::mscom::RegisteredProxy> mMiscTypelib; + UniquePtr<mozilla::mscom::RegisteredProxy> mSdnTypelib; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_PlatformChild_h diff --git a/accessible/ipc/win/RemoteAccessible.cpp b/accessible/ipc/win/RemoteAccessible.cpp new file mode 100644 index 0000000000..f63dca49af --- /dev/null +++ b/accessible/ipc/win/RemoteAccessible.cpp @@ -0,0 +1,943 @@ +/* -*- 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 "Accessible2.h" +#include "RemoteAccessible.h" +#include "ia2AccessibleRelation.h" +#include "ia2AccessibleValue.h" +#include "IGeckoCustom.h" +#include "mozilla/a11y/DocAccessibleParent.h" +#include "DocAccessible.h" +#include "mozilla/a11y/DocManager.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/Unused.h" +#include "mozilla/a11y/Platform.h" +#include "Relation.h" +#include "RelationType.h" +#include "mozilla/a11y/Role.h" +#include "mozilla/StaticPrefs_accessibility.h" + +#include <comutil.h> + +static const VARIANT kChildIdSelf = {{{VT_I4}}}; + +namespace mozilla { +namespace a11y { + +bool RemoteAccessible::GetCOMInterface(void** aOutAccessible) const { + if (!aOutAccessible) { + return false; + } + + // This should never be called if the cache is enabled. We can't get a COM + // proxy from the content process in that case. Instead, the code below would + // return an MsaaAccessible from our process which would end up calling + // methods here in RemoteAccessible, causing infinite recursion. + MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup()); + if (!mCOMProxy && mSafeToRecurse) { + RemoteAccessible* thisPtr = const_cast<RemoteAccessible*>(this); + // See if we can lazily obtain a COM proxy + MsaaAccessible* msaa = MsaaAccessible::GetFrom(thisPtr); + bool isDefunct = false; + // NB: Don't pass CHILDID_SELF here, use the absolute MSAA ID. Otherwise + // GetIAccessibleFor will recurse into this function and we will just + // overflow the stack. + VARIANT realId = {{{VT_I4}}}; + realId.ulVal = msaa->GetExistingID(); + MOZ_DIAGNOSTIC_ASSERT(realId.ulVal != CHILDID_SELF); + thisPtr->mCOMProxy = msaa->GetIAccessibleFor(realId, &isDefunct); + } + + RefPtr<IAccessible> addRefed = mCOMProxy; + addRefed.forget(aOutAccessible); + return !!mCOMProxy; +} + +/** + * Specializations of this template map an IAccessible type to its IID + */ +template <typename Interface> +struct InterfaceIID {}; + +template <> +struct InterfaceIID<IAccessibleValue> { + static REFIID Value() { return IID_IAccessibleValue; } +}; + +template <> +struct InterfaceIID<IAccessibleText> { + static REFIID Value() { return IID_IAccessibleText; } +}; + +template <> +struct InterfaceIID<IAccessibleHyperlink> { + static REFIID Value() { return IID_IAccessibleHyperlink; } +}; + +template <> +struct InterfaceIID<IGeckoCustom> { + static REFIID Value() { return IID_IGeckoCustom; } +}; + +template <> +struct InterfaceIID<IAccessible2_2> { + static REFIID Value() { return IID_IAccessible2_2; } +}; + +/** + * Get the COM proxy for this proxy accessible and QueryInterface it with the + * correct IID + */ +template <typename Interface> +static already_AddRefed<Interface> QueryInterface( + const RemoteAccessible* aProxy) { + RefPtr<IAccessible> acc; + if (!aProxy->GetCOMInterface((void**)getter_AddRefs(acc))) { + return nullptr; + } + + RefPtr<Interface> acc2; + if (FAILED(acc->QueryInterface(InterfaceIID<Interface>::Value(), + (void**)getter_AddRefs(acc2)))) { + return nullptr; + } + + return acc2.forget(); +} + +static Maybe<uint64_t> GetIdFor(DocAccessibleParent* aDoc, + IUnknown* aCOMProxy) { + RefPtr<IGeckoCustom> custom; + if (FAILED(aCOMProxy->QueryInterface(IID_IGeckoCustom, + (void**)getter_AddRefs(custom)))) { + return Nothing(); + } + + uint64_t id; + if (FAILED(custom->get_ID(&id))) { + return Nothing(); + } + + return Some(id); +} + +static RemoteAccessible* GetProxyFor(DocAccessibleParent* aDoc, + IUnknown* aCOMProxy) { + if (auto id = GetIdFor(aDoc, aCOMProxy)) { + return aDoc->GetAccessible(*id); + } + return nullptr; +} + +ENameValueFlag RemoteAccessible::Name(nsString& aName) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Name(aName); + } + + /* The return values here exist only to match behvaiour required + * by the header declaration of this function. On Mac, we'd like + * to return the associated ENameValueFlag, but we don't have + * access to that here, so we return a dummy eNameOK value instead. + */ + aName.Truncate(); + RefPtr<IAccessible> acc; + if (!GetCOMInterface((void**)getter_AddRefs(acc))) { + return eNameOK; + } + + BSTR result; + HRESULT hr = acc->get_accName(kChildIdSelf, &result); + _bstr_t resultWrap(result, false); + if (FAILED(hr)) { + return eNameOK; + } + aName = (wchar_t*)resultWrap; + if (!resultWrap) { + aName.SetIsVoid(true); + } + return eNameOK; +} + +void RemoteAccessible::Value(nsString& aValue) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + RemoteAccessibleBase<RemoteAccessible>::Value(aValue); + return; + } + + aValue.Truncate(); + RefPtr<IAccessible> acc; + if (!GetCOMInterface((void**)getter_AddRefs(acc))) { + return; + } + + BSTR result; + HRESULT hr = acc->get_accValue(kChildIdSelf, &result); + _bstr_t resultWrap(result, false); + if (FAILED(hr)) { + return; + } + aValue = (wchar_t*)resultWrap; +} + +double RemoteAccessible::Step() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Step(); + } + + RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this); + if (!custom) { + return 0; + } + + double increment; + HRESULT hr = custom->get_minimumIncrement(&increment); + if (FAILED(hr)) { + return 0; + } + + return increment; +} + +void RemoteAccessible::Description(nsString& aDesc) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Description(aDesc); + } + + aDesc.Truncate(); + RefPtr<IAccessible> acc; + if (!GetCOMInterface((void**)getter_AddRefs(acc))) { + return; + } + + BSTR result; + HRESULT hr = acc->get_accDescription(kChildIdSelf, &result); + _bstr_t resultWrap(result, false); + if (FAILED(hr)) { + return; + } + aDesc = (wchar_t*)resultWrap; +} + +uint64_t RemoteAccessible::State() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::State(); + } + + RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this); + if (!custom) { + return 0; + } + + uint64_t state; + HRESULT hr = custom->get_mozState(&state); + if (FAILED(hr)) { + return 0; + } + return state; +} + +LayoutDeviceIntRect RemoteAccessible::Bounds() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Bounds(); + } + + LayoutDeviceIntRect rect; + + RefPtr<IAccessible> acc; + if (!GetCOMInterface((void**)getter_AddRefs(acc))) { + return rect; + } + + long left; + long top; + long width; + long height; + HRESULT hr = acc->accLocation(&left, &top, &width, &height, kChildIdSelf); + if (FAILED(hr)) { + return rect; + } + rect.SetRect(left, top, width, height); + return rect; +} + +nsIntRect RemoteAccessible::BoundsInCSSPixels() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::BoundsInCSSPixels(); + } + + RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this); + if (!custom) { + return nsIntRect(); + } + + nsIntRect rect; + Unused << custom->get_boundsInCSSPixels(&rect.x, &rect.y, &rect.width, + &rect.height); + return rect; +} + +void RemoteAccessible::Language(nsString& aLocale) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + aLocale.Truncate(); + return; + } + aLocale.Truncate(); + + RefPtr<IAccessible> acc; + if (!GetCOMInterface((void**)getter_AddRefs(acc))) { + return; + } + + RefPtr<IAccessible2> acc2; + if (FAILED(acc->QueryInterface(IID_IAccessible2, + (void**)getter_AddRefs(acc2)))) { + return; + } + + IA2Locale locale; + HRESULT hr = acc2->get_locale(&locale); + + _bstr_t langWrap(locale.language, false); + _bstr_t countryWrap(locale.country, false); + _bstr_t variantWrap(locale.variant, false); + + if (FAILED(hr)) { + return; + } + + // The remaining code should essentially be the inverse of the + // ia2Accessible::get_locale conversion to IA2Locale. + + if (!!variantWrap) { + aLocale = (wchar_t*)variantWrap; + return; + } + + if (!!langWrap) { + aLocale = (wchar_t*)langWrap; + if (!!countryWrap) { + aLocale += L"-"; + aLocale += (wchar_t*)countryWrap; + } + } +} + +static bool IsEscapedChar(const wchar_t c) { + return c == L'\\' || c == L':' || c == ',' || c == '=' || c == ';'; +} + +// XXX: This creates an all-strings AccAttributes, this isn't ideal +// but an OK temporary stop-gap before IPC goes full IPDL. +static bool ConvertBSTRAttributesToAccAttributes( + const nsAString& aStr, RefPtr<AccAttributes>& aAttrs) { + enum { eName = 0, eValue = 1, eNumStates } state; + nsString tokens[eNumStates]; + auto itr = aStr.BeginReading(), end = aStr.EndReading(); + + state = eName; + while (itr != end) { + switch (*itr) { + case L'\\': + // Skip the backslash so that we're looking at the escaped char + ++itr; + if (itr == end || !IsEscapedChar(*itr)) { + // Invalid state + return false; + } + break; + case L':': + if (state != eName) { + // Bad, should be looking at name + return false; + } + state = eValue; + ++itr; + continue; + case L';': { + if (state != eValue) { + // Bad, should be looking at value + return false; + } + state = eName; + RefPtr<nsAtom> nameAtom = NS_Atomize(tokens[eName]); + aAttrs->SetAttribute(nameAtom, std::move(tokens[eValue])); + tokens[eName].Truncate(); + ++itr; + continue; + } + default: + break; + } + tokens[state] += *itr; + ++itr; + } + return true; +} + +already_AddRefed<AccAttributes> RemoteAccessible::Attributes() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::Attributes(); + } + RefPtr<AccAttributes> attrsObj = new AccAttributes(); + RefPtr<IAccessible> acc; + if (!GetCOMInterface((void**)getter_AddRefs(acc))) { + return attrsObj.forget(); + } + + RefPtr<IAccessible2> acc2; + if (FAILED(acc->QueryInterface(IID_IAccessible2, + (void**)getter_AddRefs(acc2)))) { + return attrsObj.forget(); + } + + BSTR attrs; + HRESULT hr = acc2->get_attributes(&attrs); + _bstr_t attrsWrap(attrs, false); + if (FAILED(hr)) { + return attrsObj.forget(); + } + + ConvertBSTRAttributesToAccAttributes( + nsDependentString((wchar_t*)attrs, attrsWrap.length()), attrsObj); + return attrsObj.forget(); +} + +Relation RemoteAccessible::RelationByType(RelationType aType) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::RelationByType(aType); + } + + RefPtr<IAccessible2_2> acc = QueryInterface<IAccessible2_2>(this); + if (!acc) { + return Relation(); + } + + _bstr_t relationType; + for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) { + if (aType == sRelationTypePairs[idx].first) { + relationType = sRelationTypePairs[idx].second; + break; + } + } + + if (!relationType) { + return Relation(); + } + + IUnknown** targets; + long nTargets = 0; + HRESULT hr = + acc->get_relationTargetsOfType(relationType, 0, &targets, &nTargets); + if (FAILED(hr)) { + return Relation(); + } + + nsTArray<uint64_t> ids; + for (long idx = 0; idx < nTargets; idx++) { + IUnknown* target = targets[idx]; + if (auto id = GetIdFor(Document(), target)) { + ids.AppendElement(*id); + } + target->Release(); + } + CoTaskMemFree(targets); + + return Relation(new RemoteAccIterator(std::move(ids), Document())); +} + +double RemoteAccessible::CurValue() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::CurValue(); + } + + RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this); + if (!acc) { + return UnspecifiedNaN<double>(); + } + + VARIANT currentValue; + HRESULT hr = acc->get_currentValue(¤tValue); + if (FAILED(hr) || currentValue.vt != VT_R8) { + return UnspecifiedNaN<double>(); + } + + return currentValue.dblVal; +} + +bool RemoteAccessible::SetCurValue(double aValue) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + return false; + } + RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this); + if (!acc) { + return false; + } + + VARIANT currentValue; + VariantInit(¤tValue); + currentValue.vt = VT_R8; + currentValue.dblVal = aValue; + HRESULT hr = acc->setCurrentValue(currentValue); + return SUCCEEDED(hr); +} + +double RemoteAccessible::MinValue() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::MinValue(); + } + + RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this); + if (!acc) { + return UnspecifiedNaN<double>(); + } + + VARIANT minimumValue; + HRESULT hr = acc->get_minimumValue(&minimumValue); + if (FAILED(hr) || minimumValue.vt != VT_R8) { + return UnspecifiedNaN<double>(); + } + + return minimumValue.dblVal; +} + +double RemoteAccessible::MaxValue() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::MaxValue(); + } + + RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this); + if (!acc) { + return UnspecifiedNaN<double>(); + } + + VARIANT maximumValue; + HRESULT hr = acc->get_maximumValue(&maximumValue); + if (FAILED(hr) || maximumValue.vt != VT_R8) { + return UnspecifiedNaN<double>(); + } + + return maximumValue.dblVal; +} + +static IA2TextBoundaryType GetIA2TextBoundary( + AccessibleTextBoundary aGeckoBoundaryType) { + switch (aGeckoBoundaryType) { + case nsIAccessibleText::BOUNDARY_CHAR: + return IA2_TEXT_BOUNDARY_CHAR; + case nsIAccessibleText::BOUNDARY_WORD_START: + return IA2_TEXT_BOUNDARY_WORD; + case nsIAccessibleText::BOUNDARY_LINE_START: + return IA2_TEXT_BOUNDARY_LINE; + case nsIAccessibleText::BOUNDARY_PARAGRAPH: + return IA2_TEXT_BOUNDARY_PARAGRAPH; + default: + MOZ_CRASH(); + } +} + +int32_t RemoteAccessible::OffsetAtPoint(int32_t aX, int32_t aY, + uint32_t aCoordinateType) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::OffsetAtPoint( + aX, aY, aCoordinateType); + } + + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return -1; + } + + IA2CoordinateType coordType; + if (aCoordinateType == + nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) { + coordType = IA2_COORDTYPE_SCREEN_RELATIVE; + } else if (aCoordinateType == + nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE) { + coordType = IA2_COORDTYPE_PARENT_RELATIVE; + } else { + MOZ_CRASH("unsupported coord type"); + } + + long offset; + HRESULT hr = acc->get_offsetAtPoint( + static_cast<long>(aX), static_cast<long>(aY), coordType, &offset); + if (FAILED(hr)) { + return -1; + } + + return static_cast<int32_t>(offset); +} + +void RemoteAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset, + nsAString& aText) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextSubstring( + aStartOffset, aEndOffset, aText); + } + + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return; + } + + BSTR result; + HRESULT hr = acc->get_text(static_cast<long>(aStartOffset), + static_cast<long>(aEndOffset), &result); + if (FAILED(hr)) { + return; + } + + _bstr_t resultWrap(result, false); + aText = (wchar_t*)result; +} + +void RemoteAccessible::TextBeforeOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, + int32_t* aEndOffset, nsAString& aText) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextBeforeOffset( + aOffset, aBoundaryType, aStartOffset, aEndOffset, aText); + } + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return; + } + + BSTR result; + long start, end; + HRESULT hr = acc->get_textBeforeOffset( + aOffset, GetIA2TextBoundary(aBoundaryType), &start, &end, &result); + if (FAILED(hr)) { + return; + } + + _bstr_t resultWrap(result, false); + *aStartOffset = start; + *aEndOffset = end; + aText = (wchar_t*)result; +} + +void RemoteAccessible::TextAfterOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, + int32_t* aEndOffset, nsAString& aText) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextAfterOffset( + aOffset, aBoundaryType, aStartOffset, aEndOffset, aText); + } + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return; + } + + BSTR result; + long start, end; + HRESULT hr = acc->get_textAfterOffset( + aOffset, GetIA2TextBoundary(aBoundaryType), &start, &end, &result); + if (FAILED(hr)) { + return; + } + + _bstr_t resultWrap(result, false); + aText = (wchar_t*)result; + *aStartOffset = start; + *aEndOffset = end; +} + +void RemoteAccessible::TextAtOffset(int32_t aOffset, + AccessibleTextBoundary aBoundaryType, + int32_t* aStartOffset, int32_t* aEndOffset, + nsAString& aText) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TextAtOffset( + aOffset, aBoundaryType, aStartOffset, aEndOffset, aText); + } + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return; + } + + BSTR result; + long start, end; + HRESULT hr = acc->get_textAtOffset(aOffset, GetIA2TextBoundary(aBoundaryType), + &start, &end, &result); + if (FAILED(hr)) { + return; + } + + _bstr_t resultWrap(result, false); + aText = (wchar_t*)result; + *aStartOffset = start; + *aEndOffset = end; +} + +bool RemoteAccessible::AddToSelection(int32_t aStartOffset, + int32_t aEndOffset) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + return false; + } + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return false; + } + + return SUCCEEDED(acc->addSelection(static_cast<long>(aStartOffset), + static_cast<long>(aEndOffset))); +} + +bool RemoteAccessible::RemoveFromSelection(int32_t aSelectionNum) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + return false; + } + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return false; + } + + return SUCCEEDED(acc->removeSelection(static_cast<long>(aSelectionNum))); +} + +int32_t RemoteAccessible::CaretOffset() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::CaretOffset(); + } + + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return -1; + } + + long offset; + HRESULT hr = acc->get_caretOffset(&offset); + if (FAILED(hr)) { + return -1; + } + + return static_cast<int32_t>(offset); +} + +void RemoteAccessible::SetCaretOffset(int32_t aOffset) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::SetCaretOffset(aOffset); + } + + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return; + } + + acc->setCaretOffset(static_cast<long>(aOffset)); +} + +/** + * aScrollType should be one of the nsIAccessiblescrollType constants. + */ +void RemoteAccessible::ScrollSubstringTo(int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aScrollType) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + return; + } + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return; + } + + acc->scrollSubstringTo(static_cast<long>(aStartOffset), + static_cast<long>(aEndOffset), + static_cast<IA2ScrollType>(aScrollType)); +} + +/** + * aCoordinateType is one of the nsIAccessibleCoordinateType constants. + */ +void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset, + int32_t aEndOffset, + uint32_t aCoordinateType, + int32_t aX, int32_t aY) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + return; + } + RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this); + if (!acc) { + return; + } + + IA2CoordinateType coordType; + if (aCoordinateType == + nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) { + coordType = IA2_COORDTYPE_SCREEN_RELATIVE; + } else if (aCoordinateType == + nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE) { + coordType = IA2_COORDTYPE_PARENT_RELATIVE; + } else { + MOZ_CRASH("unsupported coord type"); + } + + acc->scrollSubstringToPoint(static_cast<long>(aStartOffset), + static_cast<long>(aEndOffset), coordType, + static_cast<long>(aX), static_cast<long>(aY)); +} + +bool RemoteAccessible::IsLinkValid() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + return false; + } + RefPtr<IAccessibleHyperlink> acc = QueryInterface<IAccessibleHyperlink>(this); + if (!acc) { + return false; + } + + boolean valid; + if (FAILED(acc->get_valid(&valid))) { + return false; + } + + return valid; +} + +uint32_t RemoteAccessible::AnchorCount(bool* aOk) { + *aOk = false; + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + return 0; + } + RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this); + if (!custom) { + return 0; + } + + long count; + if (FAILED(custom->get_anchorCount(&count))) { + return 0; + } + + *aOk = true; + return count; +} + +RemoteAccessible* RemoteAccessible::AnchorAt(uint32_t aIdx) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + // Not yet supported by the cache. + return nullptr; + } + RefPtr<IAccessibleHyperlink> link = + QueryInterface<IAccessibleHyperlink>(this); + if (!link) { + return nullptr; + } + + VARIANT anchor; + if (FAILED(link->get_anchor(aIdx, &anchor))) { + return nullptr; + } + + MOZ_ASSERT(anchor.vt == VT_UNKNOWN); + RemoteAccessible* proxyAnchor = GetProxyFor(Document(), anchor.punkVal); + anchor.punkVal->Release(); + return proxyAnchor; +} + +void RemoteAccessible::DOMNodeID(nsString& aID) const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::DOMNodeID(aID); + } + + aID.Truncate(); + RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this); + if (!custom) { + return; + } + + BSTR result; + HRESULT hr = custom->get_DOMNodeID(&result); + _bstr_t resultWrap(result, false); + if (FAILED(hr)) { + return; + } + aID = (wchar_t*)resultWrap; +} + +void RemoteAccessible::TakeFocus() const { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::TakeFocus(); + } + RefPtr<IAccessible> acc; + if (!GetCOMInterface((void**)getter_AddRefs(acc))) { + return; + } + acc->accSelect(SELFLAG_TAKEFOCUS, kChildIdSelf); +} + +Accessible* RemoteAccessible::ChildAtPoint( + int32_t aX, int32_t aY, Accessible::EWhichChildAtPoint aWhichChild) { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::ChildAtPoint(aX, aY, + aWhichChild); + } + + RefPtr<IAccessible2_2> target = QueryInterface<IAccessible2_2>(this); + if (!target) { + return nullptr; + } + DocAccessibleParent* doc = Document(); + RemoteAccessible* proxy = this; + // accHitTest only does direct children, but we might want the deepest child. + for (;;) { + VARIANT childVar; + if (FAILED(target->accHitTest(aX, aY, &childVar)) || + childVar.vt == VT_EMPTY) { + return nullptr; + } + if (childVar.vt == VT_I4 && childVar.lVal == CHILDID_SELF) { + break; + } + MOZ_ASSERT(childVar.vt == VT_DISPATCH && childVar.pdispVal); + target = nullptr; + childVar.pdispVal->QueryInterface(IID_IAccessible2_2, + getter_AddRefs(target)); + childVar.pdispVal->Release(); + if (!target) { + return nullptr; + } + // We can't always use GetProxyFor because it can't cross document + // boundaries. + if (proxy->ChildCount() == 1) { + proxy = proxy->RemoteChildAt(0); + if (proxy->IsDoc()) { + // We're crossing into a child document. + doc = proxy->AsDoc(); + } + } else { + proxy = GetProxyFor(doc, target); + } + if (aWhichChild == Accessible::EWhichChildAtPoint::DirectChild) { + break; + } + } + return proxy; +} + +GroupPos RemoteAccessible::GroupPosition() { + if (StaticPrefs::accessibility_cache_enabled_AtStartup()) { + return RemoteAccessibleBase<RemoteAccessible>::GroupPosition(); + } + + // This is only supported when cache is enabled. + return GroupPos(); +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/RemoteAccessible.h b/accessible/ipc/win/RemoteAccessible.h new file mode 100644 index 0000000000..d3aace3607 --- /dev/null +++ b/accessible/ipc/win/RemoteAccessible.h @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_RemoteAccessible_h +#define mozilla_a11y_RemoteAccessible_h + +#include "LocalAccessible.h" +#include "mozilla/a11y/RemoteAccessibleBase.h" +#include "mozilla/a11y/Role.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsRect.h" + +#include <oleacc.h> + +namespace mozilla { +namespace a11y { + +/** + * Windows specific functionality for an accessibility tree node that originated + * in the parent process. + */ +class RemoteAccessible : public RemoteAccessibleBase<RemoteAccessible> { + public: + RemoteAccessible(uint64_t aID, RemoteAccessible* aParent, + DocAccessibleParent* aDoc, role aRole, AccType aType, + AccGenericType aGenericTypes, uint8_t aRoleMapEntryIndex) + : RemoteAccessibleBase(aID, aParent, aDoc, aRole, aType, aGenericTypes, + aRoleMapEntryIndex) { + MOZ_COUNT_CTOR(RemoteAccessible); + } + + MOZ_COUNTED_DTOR(RemoteAccessible) + +#include "mozilla/a11y/RemoteAccessibleShared.h" + + virtual void TakeFocus() const override; + virtual void SetCaretOffset(int32_t aOffset) override; + + bool GetCOMInterface(void** aOutAccessible) const; + void SetCOMInterface(const RefPtr<IAccessible>& aIAccessible) { + if (aIAccessible) { + mCOMProxy = aIAccessible; + } else { + // If we were supposed to be receiving an interface (hence the call to + // this function), but the interface turns out to be null, then we're + // broken for some reason. + mSafeToRecurse = false; + } + } + + protected: + explicit RemoteAccessible(DocAccessibleParent* aThisAsDoc) + : RemoteAccessibleBase(aThisAsDoc) { + MOZ_COUNT_CTOR(RemoteAccessible); + } + + private: + RefPtr<IAccessible> mCOMProxy; + bool mSafeToRecurse = true; +}; + +//////////////////////////////////////////////////////////////////////////////// +// RemoteAccessible downcasting method + +inline RemoteAccessible* Accessible::AsRemote() { + return IsRemote() ? static_cast<RemoteAccessible*>(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/ipc/win/handler/AccessibleHandler.cpp b/accessible/ipc/win/handler/AccessibleHandler.cpp new file mode 100644 index 0000000000..bf70af4f35 --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandler.cpp @@ -0,0 +1,2189 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "AccessibleHandler.h" +#include "AccessibleHandlerControl.h" +#include "HandlerChildEnumerator.h" +#include "HandlerRelation.h" + +#include "Factory.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/a11y/HandlerDataCleanup.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/UniquePtr.h" + +#include <objbase.h> +#include <uiautomation.h> +#include <winreg.h> + +#include "AccessibleHypertext.h" +#include "AccessibleHypertext2.h" +#include "AccessibleRole.h" +#include "Accessible2_i.c" +#include "Accessible2_2_i.c" +#include "Accessible2_3_i.c" +#include "AccessibleAction_i.c" +#include "AccessibleHyperlink_i.c" +#include "AccessibleHypertext_i.c" +#include "AccessibleHypertext2_i.c" +#include "AccessibleTable_i.c" +#include "AccessibleTable2_i.c" +#include "AccessibleTableCell_i.c" +#include "AccessibleText_i.c" + +namespace mozilla { +namespace a11y { + +// Must be kept in sync with kClassNameTabContent in +// accessible/windows/msaa/nsWinUtils.h. +const WCHAR kEmulatedWindowClassName[] = L"MozillaContentWindowClass"; +const uint32_t kEmulatedWindowClassNameNChars = + sizeof(kEmulatedWindowClassName) / sizeof(WCHAR); +// Mask to get the content process portion of a Windows accessible unique id. +// This is bits 23 through 30 (LSB 0) of the id. This must be kept in sync +// with kNumContentProcessIDBits in accessible/windows/msaa/MsaaIdGenerator.cpp. +const uint32_t kIdContentProcessMask = 0x7F800000; + +static mscom::Factory<AccessibleHandler> sHandlerFactory; + +HRESULT +AccessibleHandler::Create(IUnknown* aOuter, REFIID aIid, void** aOutInterface) { + if (!aOutInterface || !aOuter || aIid != IID_IUnknown) { + return E_INVALIDARG; + } + + *aOutInterface = nullptr; + + HRESULT hr; + RefPtr<AccessibleHandler> handler(new AccessibleHandler(aOuter, &hr)); + if (FAILED(hr)) { + return hr; + } + + return handler->InternalQueryInterface(aIid, aOutInterface); +} + +AccessibleHandler::AccessibleHandler(IUnknown* aOuter, HRESULT* aResult) + : mscom::Handler(aOuter, aResult), + mDispatch(nullptr), + mIA2PassThru(nullptr), + mServProvPassThru(nullptr), + mIAHyperlinkPassThru(nullptr), + mIATableCellPassThru(nullptr), + mIAHypertextPassThru(nullptr), + mCachedData(), + mCachedDynamicDataMarshaledByCom(false), + mCacheGen(0), + mCachedHyperlinks(nullptr), + mCachedNHyperlinks(-1), + mCachedTextAttribRuns(nullptr), + mCachedNTextAttribRuns(-1), + mCachedRelations(nullptr), + mCachedNRelations(-1), + mIsEmulatedWindow(false) { + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton()); + MOZ_ASSERT(ctl); + if (!ctl) { + if (aResult) { + *aResult = E_UNEXPECTED; + } + return; + } + + mCacheGen = ctl->GetCacheGen(); +} + +AccessibleHandler::~AccessibleHandler() { + CleanupDynamicIA2Data(mCachedData.mDynamicData, + mCachedDynamicDataMarshaledByCom); + if (mCachedData.mGeckoBackChannel) { + mCachedData.mGeckoBackChannel->Release(); + } + ClearTextCache(); + ClearRelationCache(); +} + +HRESULT +AccessibleHandler::ResolveIA2() { + if (mIA2PassThru) { + return S_OK; + } + + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = proxy->QueryInterface(NEWEST_IA2_IID, + reinterpret_cast<void**>(&mIA2PassThru)); + if (SUCCEEDED(hr)) { + // mIA2PassThru is a weak reference (see comments in AccesssibleHandler.h) + mIA2PassThru->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::ResolveIAHyperlink() { + if (mIAHyperlinkPassThru) { + return S_OK; + } + + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = + proxy->QueryInterface(IID_IAccessibleHyperlink, + reinterpret_cast<void**>(&mIAHyperlinkPassThru)); + if (SUCCEEDED(hr)) { + // mIAHyperlinkPassThru is a weak reference + // (see comments in AccesssibleHandler.h) + mIAHyperlinkPassThru->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::ResolveIATableCell() { + if (mIATableCellPassThru) { + return S_OK; + } + + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = + proxy->QueryInterface(IID_IAccessibleTableCell, + reinterpret_cast<void**>(&mIATableCellPassThru)); + if (SUCCEEDED(hr)) { + // mIATableCellPassThru is a weak reference + // (see comments in AccesssibleHandler.h) + mIATableCellPassThru->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::ResolveIAHypertext() { + if (mIAHypertextPassThru) { + return S_OK; + } + + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = + proxy->QueryInterface(IID_IAccessibleHypertext2, + reinterpret_cast<void**>(&mIAHypertextPassThru)); + if (SUCCEEDED(hr)) { + // mIAHypertextPassThru is a weak reference + // (see comments in AccessibleHandler.h) + mIAHypertextPassThru->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::MaybeUpdateCachedData() { + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + + uint32_t gen = ctl->GetCacheGen(); + if (gen == mCacheGen) { + // Cache is already up to date. + return S_OK; + } + + if (!mCachedData.mGeckoBackChannel) { + return E_POINTER; + } + + // While we're making the outgoing COM call below, an incoming COM call can + // be handled which calls ReadHandlerPayload or re-enters this function. + // Therefore, we mustn't update the cached data directly lest it be mutated + // elsewhere before the outgoing COM call returns and cause corruption or + // memory leaks. Instead, pass a temporary struct and update the cached data + // only after this call completes. + DynamicIA2Data newData; + HRESULT hr = mCachedData.mGeckoBackChannel->Refresh(&newData); + if (SUCCEEDED(hr)) { + // Clean up the old data. + CleanupDynamicIA2Data(mCachedData.mDynamicData, + mCachedDynamicDataMarshaledByCom); + mCachedData.mDynamicData = newData; + mCachedDynamicDataMarshaledByCom = true; + // We just updated the cache, so update this object's cache generation + // so we only update the cache again after the next change. + mCacheGen = gen; + } + return hr; +} + +HRESULT +AccessibleHandler::GetAllTextInfo(BSTR* aText) { + MOZ_ASSERT(mCachedData.mGeckoBackChannel); + + ClearTextCache(); + + return mCachedData.mGeckoBackChannel->get_AllTextInfo( + aText, &mCachedHyperlinks, &mCachedNHyperlinks, &mCachedTextAttribRuns, + &mCachedNTextAttribRuns); +} + +void AccessibleHandler::ClearTextCache() { + if (mCachedNHyperlinks >= 0) { + // We cached hyperlinks, but the caller never retrieved them. + for (long index = 0; index < mCachedNHyperlinks; ++index) { + mCachedHyperlinks[index]->Release(); + } + // mCachedHyperlinks might already be null if there are no hyperlinks. + if (mCachedHyperlinks) { + ::CoTaskMemFree(mCachedHyperlinks); + mCachedHyperlinks = nullptr; + } + mCachedNHyperlinks = -1; + } + + if (mCachedTextAttribRuns) { + for (long index = 0; index < mCachedNTextAttribRuns; ++index) { + if (mCachedTextAttribRuns[index].text) { + // The caller never requested this attribute run. + ::SysFreeString(mCachedTextAttribRuns[index].text); + } + } + // This array is internal to us, so we must always free it. + ::CoTaskMemFree(mCachedTextAttribRuns); + mCachedTextAttribRuns = nullptr; + mCachedNTextAttribRuns = -1; + } +} + +HRESULT +AccessibleHandler::GetRelationsInfo() { + MOZ_ASSERT(mCachedData.mGeckoBackChannel); + + ClearRelationCache(); + + return mCachedData.mGeckoBackChannel->get_RelationsInfo(&mCachedRelations, + &mCachedNRelations); +} + +void AccessibleHandler::ClearRelationCache() { + if (mCachedNRelations == -1) { + // No cache; nothing to do. + return; + } + + // We cached relations, but the client never retrieved them. + if (mCachedRelations) { + for (long index = 0; index < mCachedNRelations; ++index) { + IARelationData& relData = mCachedRelations[index]; + if (relData.mType) { + ::SysFreeString(relData.mType); + } + } + // This array is internal to us, so we must always free it. + ::CoTaskMemFree(mCachedRelations); + mCachedRelations = nullptr; + } + mCachedNRelations = -1; +} + +HRESULT +AccessibleHandler::ResolveIDispatch() { + if (mDispatch) { + return S_OK; + } + + HRESULT hr; + + if (!mDispatchUnk) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + + RefPtr<ITypeInfo> typeinfo; + hr = ctl->GetHandlerTypeInfo(getter_AddRefs(typeinfo)); + if (FAILED(hr)) { + return hr; + } + + hr = ::CreateStdDispatch(GetOuter(), + static_cast<NEWEST_IA2_INTERFACE*>(this), typeinfo, + getter_AddRefs(mDispatchUnk)); + if (FAILED(hr)) { + return hr; + } + } + + hr = mDispatchUnk->QueryInterface(IID_IDispatch, + reinterpret_cast<void**>(&mDispatch)); + if (SUCCEEDED(hr)) { + // mDispatch is weak (see comments in AccessibleHandler.h) + mDispatch->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::QueryHandlerInterface(IUnknown* aProxyUnknown, REFIID aIid, + void** aOutInterface) { + MOZ_ASSERT(aProxyUnknown); + + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if (aIid == IID_IDispatch || aIid == IID_IAccessible2_3 || + aIid == IID_IAccessible2_2 || aIid == IID_IAccessible2 || + aIid == IID_IAccessible) { + RefPtr<NEWEST_IA2_INTERFACE> ia2(static_cast<NEWEST_IA2_INTERFACE*>(this)); + ia2.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IServiceProvider) { + RefPtr<IServiceProvider> svcProv(static_cast<IServiceProvider*>(this)); + svcProv.forget(aOutInterface); + return S_OK; + } + + if (HasPayload()) { + // The proxy manager caches interfaces marshaled in the payload + // and returns them on QI without a cross-process call. + // However, it doesn't know about interfaces which don't exist. + // We can determine this from the payload. + if (((aIid == IID_IAccessibleText || aIid == IID_IAccessibleHypertext || + aIid == IID_IAccessibleHypertext2) && + !mCachedData.mStaticData.mIAHypertext) || + ((aIid == IID_IAccessibleAction || aIid == IID_IAccessibleHyperlink) && + !mCachedData.mStaticData.mIAHyperlink) || + (aIid == IID_IAccessibleTable && !mCachedData.mStaticData.mIATable) || + (aIid == IID_IAccessibleTable2 && !mCachedData.mStaticData.mIATable2) || + (aIid == IID_IAccessibleTableCell && + !mCachedData.mStaticData.mIATableCell)) { + // We already know this interface is not available, so don't query + // the proxy, thus avoiding a pointless cross-process call. + // If we return E_NOINTERFACE here, mscom::Handler will try the COM + // proxy. S_FALSE signals that the proxy should not be tried. + return S_FALSE; + } + } + + if (aIid == IID_IAccessibleAction || aIid == IID_IAccessibleHyperlink) { + RefPtr<IAccessibleHyperlink> iaLink( + static_cast<IAccessibleHyperlink*>(this)); + iaLink.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IAccessibleTableCell) { + RefPtr<IAccessibleTableCell> iaCell( + static_cast<IAccessibleTableCell*>(this)); + iaCell.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IAccessibleText || aIid == IID_IAccessibleHypertext || + aIid == IID_IAccessibleHypertext2) { + RefPtr<IAccessibleHypertext2> iaHt( + static_cast<IAccessibleHypertext2*>(this)); + iaHt.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IProvideClassInfo) { + RefPtr<IProvideClassInfo> clsInfo(this); + clsInfo.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IEnumVARIANT && mCachedData.mGeckoBackChannel) { + if (mCachedData.mDynamicData.mChildCount == 0) { + return E_NOINTERFACE; + } + if (!mCachedData.mStaticData.mIAHypertext && + mCachedData.mDynamicData.mChildCount == 1) { + // This might be an OOP iframe. (We can't check the role because it might + // be overridden by ARIA.) HandlerChildEnumerator works fine for iframes + // rendered in the same content process. However, for out-of-process + // iframes, HandlerProvider::get_AllChildren (called by + // HandlerChildEnumerator) will fail. This is because we only send down + // an IDispatch COM proxy for the embedded document, but get_AllChildren + // will try to QueryInterface this to IAccessible2 to reduce QI calls + // from the parent process. Because the content process is sandboxed, + // it can't make the outgoing COM call to QI the proxy from IDispatch to + // IAccessible2 and so it fails. + // Since this Accessible only has one child anyway, we don't need the bulk + // fetch optimization offered by HandlerChildEnumerator or even + // IEnumVARIANT. Therefore, we explicitly tell the client this interface + // is not supported, which will cause the oleacc AccessibleChildren + // function to fall back to accChild. If we return E_NOINTERFACE here, + // mscom::Handler will try the COM proxy. S_FALSE signals that the proxy + // should not be tried. + return S_FALSE; + } + RefPtr<IEnumVARIANT> childEnum( + new HandlerChildEnumerator(this, mCachedData.mGeckoBackChannel)); + childEnum.forget(aOutInterface); + return S_OK; + } + + return E_NOINTERFACE; +} + +HRESULT +AccessibleHandler::ReadHandlerPayload(IStream* aStream, REFIID aIid) { + if (!aStream) { + return E_INVALIDARG; + } + + mscom::StructFromStream deserializer(aStream); + if (!deserializer) { + return E_FAIL; + } + if (deserializer.IsEmpty()) { + return S_FALSE; + } + + // QueryHandlerInterface might get called while we deserialize the payload, + // but that checks the interface pointers in the payload to determine what + // interfaces are available. Therefore, deserialize into a temporary struct + // and update mCachedData only after deserialization completes. + // The decoding functions can misbehave if their target memory is not zeroed + // beforehand, so ensure we do that. + IA2Payload newData{}; + if (!deserializer.Read(&newData, &IA2Payload_Decode)) { + return E_FAIL; + } + // Clean up the old data. + CleanupDynamicIA2Data(mCachedData.mDynamicData, + mCachedDynamicDataMarshaledByCom); + mCachedData = newData; + mCachedDynamicDataMarshaledByCom = false; + + // These interfaces have been aggregated into the proxy manager. + // The proxy manager will resolve these interfaces now on QI, + // so we can release these pointers. + // However, we don't null them out because we use their presence + // to determine whether the interface is available + // so as to avoid pointless cross-proc QI calls returning E_NOINTERFACE. + // Note that if pointers to other objects (in contrast to + // interfaces of *this* object) are added in future, we should not release + // those pointers. + ReleaseStaticIA2DataInterfaces(mCachedData.mStaticData); + + WCHAR className[kEmulatedWindowClassNameNChars]; + if (mCachedData.mDynamicData.mHwnd && + ::GetClassName( + reinterpret_cast<HWND>(uintptr_t(mCachedData.mDynamicData.mHwnd)), + className, kEmulatedWindowClassNameNChars) > 0 && + wcscmp(className, kEmulatedWindowClassName) == 0) { + mIsEmulatedWindow = true; + } + + if (!mCachedData.mGeckoBackChannel) { + return S_OK; + } + + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + + if (mCachedData.mDynamicData.mIA2Role == ROLE_SYSTEM_COLUMNHEADER || + mCachedData.mDynamicData.mIA2Role == ROLE_SYSTEM_ROWHEADER) { + // Because the same headers can apply to many cells, handler payloads + // include the ids of header cells, rather than potentially marshaling the + // same objects many times. We need to cache header cells here so we can + // get them by id later. + ctl->CacheAccessible(mCachedData.mDynamicData.mUniqueId, this); + } + + return ctl->Register(WrapNotNull(mCachedData.mGeckoBackChannel)); +} + +REFIID +AccessibleHandler::MarshalAs(REFIID aIid) { + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if (aIid == IID_IAccessible2_3 || aIid == IID_IAccessible2_2 || + aIid == IID_IAccessible2 || aIid == IID_IAccessible || + aIid == IID_IDispatch) { + return NEWEST_IA2_IID; + } + + return aIid; +} + +HRESULT +AccessibleHandler::GetMarshalInterface(REFIID aMarshalAsIid, + NotNull<IUnknown*> aProxy, + NotNull<IID*> aOutIid, + NotNull<IUnknown**> aOutUnk) { + if (aMarshalAsIid == NEWEST_IA2_IID) { + *aOutIid = IID_IAccessible; + } else { + *aOutIid = aMarshalAsIid; + } + + return aProxy->QueryInterface( + aMarshalAsIid, + reinterpret_cast<void**>(static_cast<IUnknown**>(aOutUnk))); +} + +HRESULT +AccessibleHandler::GetHandlerPayloadSize(REFIID aIid, DWORD* aOutPayloadSize) { + if (!aOutPayloadSize) { + return E_INVALIDARG; + } + + // If we're sending the payload to somebody else, we'd better make sure that + // it is up to date. If the cache update fails then we'll return a 0 payload + // size so that we don't transfer obsolete data. + if (FAILED(MaybeUpdateCachedData())) { + *aOutPayloadSize = mscom::StructToStream::GetEmptySize(); + return S_OK; + } + + mSerializer = + MakeUnique<mscom::StructToStream>(mCachedData, &IA2Payload_Encode); + if (!mSerializer) { + return E_FAIL; + } + + *aOutPayloadSize = mSerializer->GetSize(); + return S_OK; +} + +HRESULT +AccessibleHandler::WriteHandlerPayload(IStream* aStream, REFIID aIid) { + if (!aStream) { + return E_INVALIDARG; + } + + if (!mSerializer) { + return E_UNEXPECTED; + } + + HRESULT hr = mSerializer->Write(aStream); + mSerializer.reset(); + return hr; +} + +HRESULT +AccessibleHandler::QueryInterface(REFIID riid, void** ppv) { + return Handler::QueryInterface(riid, ppv); +} + +ULONG +AccessibleHandler::AddRef() { return Handler::AddRef(); } + +ULONG +AccessibleHandler::Release() { return Handler::Release(); } + +HRESULT +AccessibleHandler::GetTypeInfoCount(UINT* pctinfo) { + HRESULT hr = ResolveIDispatch(); + if (FAILED(hr)) { + return hr; + } + + return mDispatch->GetTypeInfoCount(pctinfo); +} + +HRESULT +AccessibleHandler::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { + HRESULT hr = ResolveIDispatch(); + if (FAILED(hr)) { + return hr; + } + + return mDispatch->GetTypeInfo(iTInfo, lcid, ppTInfo); +} + +HRESULT +AccessibleHandler::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) { + HRESULT hr = ResolveIDispatch(); + if (FAILED(hr)) { + return hr; + } + + return mDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId); +} + +HRESULT +AccessibleHandler::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS* pDispParams, + VARIANT* pVarResult, EXCEPINFO* pExcepInfo, + UINT* puArgErr) { + HRESULT hr = ResolveIDispatch(); + if (FAILED(hr)) { + return hr; + } + + return mDispatch->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, + pVarResult, pExcepInfo, puArgErr); +} + +#define BEGIN_CACHE_ACCESS \ + { \ + HRESULT hr; \ + if (FAILED(hr = MaybeUpdateCachedData())) { \ + return hr; \ + } \ + } + +#define GET_FIELD(member, assignTo) \ + { assignTo = mCachedData.mDynamicData.member; } + +#define GET_BSTR(member, assignTo) \ + { assignTo = CopyBSTR(mCachedData.mDynamicData.member); } + +/*** IAccessible ***/ + +HRESULT +AccessibleHandler::get_accParent(IDispatch** ppdispParent) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accParent(ppdispParent); +} + +HRESULT +AccessibleHandler::get_accChildCount(long* pcountChildren) { + if (!pcountChildren) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accChildCount(pcountChildren); + } + + BEGIN_CACHE_ACCESS; + if (mCachedData.mDynamicData.mIA2Role == ROLE_SYSTEM_DOCUMENT) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + if (ctl->IsA11ySuppressedForClipboardCopy()) { + // Bug 1798098: Windows Suggested Actions (introduced in Windows 11 + // 22H2) might walk the document a11y tree using UIA whenever anything + // is copied to the clipboard. This causes an unacceptable hang, + // particularly when the cache is disabled. Even though we lie about the + // selection in nSelections, it falls back to a normal tree walk on the + // document if it doesn't get a proper text selection. Prevent that by + // returning a 0 child count on the document. + *pcountChildren = 0; + return S_OK; + } + } + + GET_FIELD(mChildCount, *pcountChildren); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accChild(VARIANT varChild, IDispatch** ppdispChild) { + if (!ppdispChild) { + return E_INVALIDARG; + } + // Unlikely, but we might as well optimize for it + if (varChild.vt == VT_I4 && varChild.lVal == CHILDID_SELF) { + RefPtr<IDispatch> disp(this); + disp.forget(ppdispChild); + return S_OK; + } + + if (mIsEmulatedWindow && varChild.vt == VT_I4 && varChild.lVal < 0 && + (varChild.lVal & kIdContentProcessMask) != + (mCachedData.mDynamicData.mUniqueId & kIdContentProcessMask)) { + // Window emulation is enabled and the target id is in a different + // process to this accessible. + // When window emulation is enabled, each tab document gets its own HWND. + // OOP iframes get the same HWND as their tab document and fire events with + // that HWND. However, the root accessible for the HWND (the tab document) + // can't return accessibles for OOP iframes. Therefore, we must get the root + // accessible from the main HWND and call accChild on that instead. + // We don't need an oleacc proxy, so send WM_GETOBJECT directly instead of + // calling AccessibleObjectFromEvent. + HWND rootHwnd = GetParent( + reinterpret_cast<HWND>(uintptr_t(mCachedData.mDynamicData.mHwnd))); + MOZ_ASSERT(rootHwnd); + LRESULT lresult = ::SendMessage(rootHwnd, WM_GETOBJECT, 0, OBJID_CLIENT); + if (lresult > 0) { + RefPtr<IAccessible2_3> rootAcc; + HRESULT hr = ::ObjectFromLresult(lresult, IID_IAccessible2_3, 0, + getter_AddRefs(rootAcc)); + if (hr == S_OK) { + return rootAcc->get_accChild(varChild, ppdispChild); + } + } + } + + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accChild(varChild, ppdispChild); +} + +HRESULT +AccessibleHandler::get_accName(VARIANT varChild, BSTR* pszName) { + if (!pszName) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accName(varChild, pszName); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mName, *pszName); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accValue(VARIANT varChild, BSTR* pszValue) { + if (!pszValue) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accValue(varChild, pszValue); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mValue, *pszValue); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accDescription(VARIANT varChild, BSTR* pszDescription) { + if (!pszDescription) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accDescription(varChild, pszDescription); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mDescription, *pszDescription); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accRole(VARIANT varChild, VARIANT* pvarRole) { + if (!pvarRole) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accRole(varChild, pvarRole); + } + + BEGIN_CACHE_ACCESS; + return ::VariantCopy(pvarRole, &mCachedData.mDynamicData.mRole); +} + +HRESULT +AccessibleHandler::get_accState(VARIANT varChild, VARIANT* pvarState) { + if (!pvarState) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accState(varChild, pvarState); + } + + pvarState->vt = VT_I4; + BEGIN_CACHE_ACCESS; + GET_FIELD(mState, pvarState->lVal); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accHelp(VARIANT varChild, BSTR* pszHelp) { + // This matches what AccessibleWrap does + if (!pszHelp) { + return E_INVALIDARG; + } + *pszHelp = nullptr; + return S_FALSE; +} + +HRESULT +AccessibleHandler::get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) { + // This matches what AccessibleWrap does + if (!pszHelpFile || !pidTopic) { + return E_INVALIDARG; + } + *pszHelpFile = nullptr; + *pidTopic = 0; + return S_FALSE; +} + +HRESULT +AccessibleHandler::get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) { + if (!pszKeyboardShortcut) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accKeyboardShortcut(varChild, pszKeyboardShortcut); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mKeyboardShortcut, *pszKeyboardShortcut); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accFocus(VARIANT* pvarChild) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accFocus(pvarChild); +} + +HRESULT +AccessibleHandler::get_accSelection(VARIANT* pvarChildren) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accSelection(pvarChildren); +} + +HRESULT +AccessibleHandler::get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) { + if (!pszDefaultAction) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accDefaultAction(varChild, pszDefaultAction); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mDefaultAction, *pszDefaultAction); + return S_OK; +} + +HRESULT +AccessibleHandler::accSelect(long flagsSelect, VARIANT varChild) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accSelect(flagsSelect, varChild); +} + +HRESULT +AccessibleHandler::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) { + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, + varChild); + } + + if (!pxLeft || !pyTop || !pcxWidth || !pcyHeight) { + return E_INVALIDARG; + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mLeft, *pxLeft); + GET_FIELD(mTop, *pyTop); + GET_FIELD(mWidth, *pcxWidth); + GET_FIELD(mHeight, *pcyHeight); + return S_OK; +} + +HRESULT +AccessibleHandler::accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accNavigate(navDir, varStart, pvarEndUpAt); +} + +HRESULT +AccessibleHandler::accHitTest(long xLeft, long yTop, VARIANT* pvarChild) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accHitTest(xLeft, yTop, pvarChild); +} + +HRESULT +AccessibleHandler::accDoDefaultAction(VARIANT varChild) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accDoDefaultAction(varChild); +} + +HRESULT +AccessibleHandler::put_accName(VARIANT varChild, BSTR szName) { + // This matches AccessibleWrap + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::put_accValue(VARIANT varChild, BSTR szValue) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->put_accValue(varChild, szValue); +} + +/*** IAccessible2 ***/ + +HRESULT +AccessibleHandler::get_nRelations(long* nRelations) { + if (!nRelations) { + return E_INVALIDARG; + } + + HRESULT hr; + if (mCachedData.mGeckoBackChannel) { + // If the caller wants nRelations, they will almost certainly want the + // actual relations too. + hr = GetRelationsInfo(); + if (SUCCEEDED(hr)) { + *nRelations = mCachedNRelations; + return S_OK; + } + // We fall back to a normal call if this fails. + } + + hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_nRelations(nRelations); +} + +HRESULT +AccessibleHandler::get_relation(long relationIndex, + IAccessibleRelation** relation) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_relation(relationIndex, relation); +} + +HRESULT +AccessibleHandler::get_relations(long maxRelations, + IAccessibleRelation** relations, + long* nRelations) { + if (maxRelations == 0 || !relations || !nRelations) { + return E_INVALIDARG; + } + + // We currently only support retrieval of *all* cached relations at once. + if (mCachedNRelations != -1 && maxRelations >= mCachedNRelations) { + for (long index = 0; index < mCachedNRelations; ++index) { + IARelationData& relData = mCachedRelations[index]; + RefPtr<IAccessibleRelation> hrel(new HandlerRelation(this, relData)); + hrel.forget(&relations[index]); + } + *nRelations = mCachedNRelations; + // Clean up the cache, since we only cache for one call. + // We don't use ClearRelationCache here because that scans for data to free + // in the array and we don't we need that. The HandlerRelation instances + // will handle freeing of the data. + ::CoTaskMemFree(mCachedRelations); + mCachedRelations = nullptr; + mCachedNRelations = -1; + return S_OK; + } + + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_relations(maxRelations, relations, nRelations); +} + +HRESULT +AccessibleHandler::role(long* role) { + if (!role) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->role(role); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mIA2Role, *role); + return S_OK; +} + +HRESULT +AccessibleHandler::scrollTo(IA2ScrollType scrollType) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->scrollTo(scrollType); +} + +HRESULT +AccessibleHandler::scrollToPoint(IA2CoordinateType coordinateType, long x, + long y) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->scrollToPoint(coordinateType, x, y); +} + +HRESULT +AccessibleHandler::get_groupPosition(long* groupLevel, + long* similarItemsInGroup, + long* positionInGroup) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_groupPosition(groupLevel, similarItemsInGroup, + positionInGroup); +} + +HRESULT +AccessibleHandler::get_states(AccessibleStates* states) { + if (!states) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_states(states); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mIA2States, *states); + return S_OK; +} + +HRESULT +AccessibleHandler::get_extendedRole(BSTR* extendedRole) { + // This matches ia2Accessible + if (!extendedRole) { + return E_INVALIDARG; + } + *extendedRole = nullptr; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_localizedExtendedRole(BSTR* localizedExtendedRole) { + // This matches ia2Accessible + if (!localizedExtendedRole) { + return E_INVALIDARG; + } + *localizedExtendedRole = nullptr; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_nExtendedStates(long* nExtendedStates) { + // This matches ia2Accessible + if (!nExtendedStates) { + return E_INVALIDARG; + } + *nExtendedStates = 0; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_extendedStates(long maxExtendedStates, + BSTR** extendedStates, + long* nExtendedStates) { + // This matches ia2Accessible + if (!extendedStates || !nExtendedStates) { + return E_INVALIDARG; + } + *extendedStates = nullptr; + *nExtendedStates = 0; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_localizedExtendedStates(long maxLocalizedExtendedStates, + BSTR** localizedExtendedStates, + long* nLocalizedExtendedStates) { + // This matches ia2Accessible + if (!localizedExtendedStates || !nLocalizedExtendedStates) { + return E_INVALIDARG; + } + *localizedExtendedStates = nullptr; + *nLocalizedExtendedStates = 0; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_uniqueID(long* uniqueID) { + if (!uniqueID) { + return E_INVALIDARG; + } + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_uniqueID(uniqueID); + } + *uniqueID = mCachedData.mDynamicData.mUniqueId; + return S_OK; +} + +HRESULT +AccessibleHandler::get_windowHandle(HWND* windowHandle) { + if (!windowHandle) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_windowHandle(windowHandle); + } + + BEGIN_CACHE_ACCESS; + long hwnd = 0; + GET_FIELD(mHwnd, hwnd); + *windowHandle = reinterpret_cast<HWND>(uintptr_t(hwnd)); + return S_OK; +} + +HRESULT +AccessibleHandler::get_indexInParent(long* indexInParent) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_indexInParent(indexInParent); +} + +HRESULT +AccessibleHandler::get_locale(IA2Locale* locale) { + if (!locale) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_locale(locale); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mIA2Locale.language, locale->language); + GET_BSTR(mIA2Locale.country, locale->country); + GET_BSTR(mIA2Locale.variant, locale->variant); + return S_OK; +} + +HRESULT +AccessibleHandler::get_attributes(BSTR* attributes) { + if (!attributes) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_attributes(attributes); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mAttributes, *attributes); + return S_OK; +} + +/*** IAccessible2_2 ***/ + +HRESULT +AccessibleHandler::get_attribute(BSTR name, VARIANT* attribute) { + // Not yet implemented by ia2Accessible. + // Once ia2Accessible implements this, we could either pass it through + // or we could extract these individually from cached mAttributes. + // The latter should be considered if traffic warrants it. + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_accessibleWithCaret(IUnknown** accessible, + long* caretOffset) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accessibleWithCaret(accessible, caretOffset); +} + +HRESULT +AccessibleHandler::get_relationTargetsOfType(BSTR type, long maxTargets, + IUnknown*** targets, + long* nTargets) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_relationTargetsOfType(type, maxTargets, targets, + nTargets); +} + +/*** IAccessible2_3 ***/ + +HRESULT +AccessibleHandler::get_selectionRanges(IA2Range** ranges, long* nRanges) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_selectionRanges(ranges, nRanges); +} + +/*** IServiceProvider ***/ + +HRESULT +AccessibleHandler::QueryService(REFGUID aServiceId, REFIID aIid, + void** aOutInterface) { + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + /* We're taking advantage of the fact that we are implementing IA2 as part + of our own object to implement this just like a QI. */ + if (aIid == IID_IAccessible2_3 || aIid == IID_IAccessible2_2 || + aIid == IID_IAccessible2) { + RefPtr<NEWEST_IA2_INTERFACE> ia2(this); + ia2.forget(aOutInterface); + return S_OK; + } + + // JAWS uses QueryService for these, but QI will work just fine and we can + // thus avoid a cross-process call. More importantly, if QS is used, the + // handler won't get used for that object, so our caching won't be used. + if (aIid == IID_IAccessibleAction || aIid == IID_IAccessibleText) { + return InternalQueryInterface(aIid, aOutInterface); + } + + for (uint32_t i = 0; i < ArrayLength(kUnsupportedServices); ++i) { + if (aServiceId == kUnsupportedServices[i]) { + return E_NOINTERFACE; + } + } + + if (!mServProvPassThru) { + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = proxy->QueryInterface( + IID_IServiceProvider, reinterpret_cast<void**>(&mServProvPassThru)); + if (FAILED(hr)) { + return hr; + } + + // mServProvPassThru is a weak reference (see comments in + // AccessibleHandler.h) + mServProvPassThru->Release(); + } + + return mServProvPassThru->QueryService(aServiceId, aIid, aOutInterface); +} + +/*** IProvideClassInfo ***/ + +HRESULT +AccessibleHandler::GetClassInfo(ITypeInfo** aOutTypeInfo) { + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + + return ctl->GetHandlerTypeInfo(aOutTypeInfo); +} + +/*** IAccessibleAction ***/ + +HRESULT +AccessibleHandler::nActions(long* nActions) { + if (!nActions) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->nActions(nActions); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mNActions, *nActions); + return S_OK; +} + +HRESULT +AccessibleHandler::doAction(long actionIndex) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->doAction(actionIndex); +} + +HRESULT +AccessibleHandler::get_description(long actionIndex, BSTR* description) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_description(actionIndex, description); +} + +HRESULT +AccessibleHandler::get_keyBinding(long actionIndex, long nMaxBindings, + BSTR** keyBindings, long* nBindings) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_keyBinding(actionIndex, nMaxBindings, + keyBindings, nBindings); +} + +HRESULT +AccessibleHandler::get_name(long actionIndex, BSTR* name) { + if (!name) { + return E_INVALIDARG; + } + + if (HasPayload()) { + if (actionIndex >= mCachedData.mDynamicData.mNActions) { + // Action does not exist. + return E_INVALIDARG; + } + + if (actionIndex == 0) { + // same as accDefaultAction. + GET_BSTR(mDefaultAction, *name); + return S_OK; + } + } + + // At this point, there's either no payload or actionIndex is > 0. + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_name(actionIndex, name); +} + +HRESULT +AccessibleHandler::get_localizedName(long actionIndex, BSTR* localizedName) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_localizedName(actionIndex, localizedName); +} + +/*** IAccessibleHyperlink ***/ + +HRESULT +AccessibleHandler::get_anchor(long index, VARIANT* anchor) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_anchor(index, anchor); +} + +HRESULT +AccessibleHandler::get_anchorTarget(long index, VARIANT* anchorTarget) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_anchorTarget(index, anchorTarget); +} + +HRESULT +AccessibleHandler::get_startIndex(long* index) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_startIndex(index); +} + +HRESULT +AccessibleHandler::get_endIndex(long* index) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_endIndex(index); +} + +HRESULT +AccessibleHandler::get_valid(boolean* valid) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_valid(valid); +} + +/*** IAccessibleTableCell ***/ + +HRESULT +AccessibleHandler::get_columnExtent(long* nColumnsSpanned) { + if (!nColumnsSpanned) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_columnExtent(nColumnsSpanned); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mColumnExtent, *nColumnsSpanned); + return S_OK; +} + +HRESULT +AccessibleHandler::get_columnHeaderCells(IUnknown*** cellAccessibles, + long* nColumnHeaderCells) { + if (!cellAccessibles || !nColumnHeaderCells) { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + if (HasPayload()) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + *nColumnHeaderCells = mCachedData.mDynamicData.mNColumnHeaderCells; + *cellAccessibles = static_cast<IUnknown**>( + ::CoTaskMemAlloc(sizeof(IUnknown*) * *nColumnHeaderCells)); + long i; + for (i = 0; i < *nColumnHeaderCells; ++i) { + RefPtr<AccessibleHandler> headerAcc; + hr = ctl->GetCachedAccessible( + mCachedData.mDynamicData.mColumnHeaderCellIds[i], + getter_AddRefs(headerAcc)); + if (FAILED(hr)) { + break; + } + hr = headerAcc->QueryInterface(IID_IUnknown, + (void**)&(*cellAccessibles)[i]); + if (FAILED(hr)) { + break; + } + } + if (SUCCEEDED(hr)) { + return S_OK; + } + // If we failed to get any of the headers from the cache, don't use the + // cache at all. We need to clean up anything we did so far. + long failedHeader = i; + for (i = 0; i < failedHeader; ++i) { + (*cellAccessibles)[i]->Release(); + } + ::CoTaskMemFree(*cellAccessibles); + *cellAccessibles = nullptr; + } + + hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + + return mIATableCellPassThru->get_columnHeaderCells(cellAccessibles, + nColumnHeaderCells); +} + +HRESULT +AccessibleHandler::get_columnIndex(long* columnIndex) { + if (!columnIndex) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_columnIndex(columnIndex); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mColumnIndex, *columnIndex); + return S_OK; +} + +HRESULT +AccessibleHandler::get_rowExtent(long* nRowsSpanned) { + if (!nRowsSpanned) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_rowExtent(nRowsSpanned); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mRowExtent, *nRowsSpanned); + return S_OK; +} + +HRESULT +AccessibleHandler::get_rowHeaderCells(IUnknown*** cellAccessibles, + long* nRowHeaderCells) { + if (!cellAccessibles || !nRowHeaderCells) { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + if (HasPayload()) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + *nRowHeaderCells = mCachedData.mDynamicData.mNRowHeaderCells; + *cellAccessibles = static_cast<IUnknown**>( + ::CoTaskMemAlloc(sizeof(IUnknown*) * *nRowHeaderCells)); + long i; + for (i = 0; i < *nRowHeaderCells; ++i) { + RefPtr<AccessibleHandler> headerAcc; + hr = ctl->GetCachedAccessible( + mCachedData.mDynamicData.mRowHeaderCellIds[i], + getter_AddRefs(headerAcc)); + if (FAILED(hr)) { + break; + } + hr = headerAcc->QueryInterface(IID_IUnknown, + (void**)&(*cellAccessibles)[i]); + if (FAILED(hr)) { + break; + } + } + if (SUCCEEDED(hr)) { + return S_OK; + } + // If we failed to get any of the headers from the cache, don't use the + // cache at all. We need to clean up anything we did so far. + long failedHeader = i; + for (i = 0; i < failedHeader; ++i) { + (*cellAccessibles)[i]->Release(); + } + ::CoTaskMemFree(*cellAccessibles); + *cellAccessibles = nullptr; + } + + hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + + return mIATableCellPassThru->get_rowHeaderCells(cellAccessibles, + nRowHeaderCells); +} + +HRESULT +AccessibleHandler::get_rowIndex(long* rowIndex) { + if (!rowIndex) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_rowIndex(rowIndex); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mRowIndex, *rowIndex); + return S_OK; +} + +HRESULT +AccessibleHandler::get_isSelected(boolean* isSelected) { + if (!isSelected) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_isSelected(isSelected); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mCellIsSelected, *isSelected); + return S_OK; +} + +HRESULT +AccessibleHandler::get_rowColumnExtents(long* row, long* column, + long* rowExtents, long* columnExtents, + boolean* isSelected) { + if (!row || !column || !rowExtents || !columnExtents || !isSelected) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_rowColumnExtents( + row, column, rowExtents, columnExtents, isSelected); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mRowIndex, *row); + GET_FIELD(mColumnIndex, *column); + GET_FIELD(mRowExtent, *rowExtents); + GET_FIELD(mColumnExtent, *columnExtents); + GET_FIELD(mCellIsSelected, *isSelected); + return S_OK; +} + +HRESULT +AccessibleHandler::get_table(IUnknown** table) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + + return mIATableCellPassThru->get_table(table); +} + +/*** IAccessibleText ***/ + +HRESULT +AccessibleHandler::addSelection(long startOffset, long endOffset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->addSelection(startOffset, endOffset); +} + +HRESULT +AccessibleHandler::get_attributes(long offset, long* startOffset, + long* endOffset, BSTR* textAttributes) { + if (!startOffset || !endOffset || !textAttributes) { + return E_INVALIDARG; + } + + if (mCachedNTextAttribRuns >= 0) { + // We have cached attributes. + for (long index = 0; index < mCachedNTextAttribRuns; ++index) { + auto& attribRun = mCachedTextAttribRuns[index]; + if (attribRun.start <= offset && offset < attribRun.end) { + *startOffset = attribRun.start; + *endOffset = attribRun.end; + *textAttributes = attribRun.text; + // The caller will clean this up. + // (We only keep each cached attribute run for one call.) + attribRun.text = nullptr; + // The cache for this run is now invalid, so don't visit it again. + attribRun.end = 0; + return S_OK; + } + } + } + + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_attributes(offset, startOffset, endOffset, + textAttributes); +} + +HRESULT +AccessibleHandler::get_caretOffset(long* offset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_caretOffset(offset); +} + +HRESULT +AccessibleHandler::get_characterExtents(long offset, + enum IA2CoordinateType coordType, + long* x, long* y, long* width, + long* height) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_characterExtents(offset, coordType, x, y, + width, height); +} + +HRESULT +AccessibleHandler::get_nSelections(long* nSelections) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + hr = mIAHypertextPassThru->get_nSelections(nSelections); + if (SUCCEEDED(hr) && *nSelections == 0 && HasPayload()) { + BEGIN_CACHE_ACCESS; + if (mCachedData.mDynamicData.mIA2Role == ROLE_SYSTEM_DOCUMENT) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + if (ctl->IsA11ySuppressedForClipboardCopy()) { + // Bug 1798098: Windows Suggested Actions (introduced in Windows 11 + // 22H2) might walk the document a11y tree using UIA whenever anything + // is copied to the clipboard. This causes an unacceptable hang, + // particularly when the cache is disabled. It walks using + // IAccessibleText/IAccessibleHyperText if the document reports no + // selection, so we lie here and say that there is a selection even + // though there isn't. It will subsequently call get_selection, which + // will fail, but this hack here seems to be enough to avoid further + // text calls. + *nSelections = 1; + } + } + } + return hr; +} + +HRESULT +AccessibleHandler::get_offsetAtPoint(long x, long y, + enum IA2CoordinateType coordType, + long* offset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_offsetAtPoint(x, y, coordType, offset); +} + +HRESULT +AccessibleHandler::get_selection(long selectionIndex, long* startOffset, + long* endOffset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_selection(selectionIndex, startOffset, + endOffset); +} + +HRESULT +AccessibleHandler::get_text(long startOffset, long endOffset, BSTR* text) { + if (!text) { + return E_INVALIDARG; + } + + HRESULT hr; + if (mCachedData.mGeckoBackChannel && startOffset == 0 && + endOffset == IA2_TEXT_OFFSET_LENGTH) { + // If the caller is retrieving all text, they will probably want all + // hyperlinks and attributes as well. + hr = GetAllTextInfo(text); + if (SUCCEEDED(hr)) { + return hr; + } + // We fall back to a normal call if this fails. + } + + hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_text(startOffset, endOffset, text); +} + +HRESULT +AccessibleHandler::get_textBeforeOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_textBeforeOffset( + offset, boundaryType, startOffset, endOffset, text); +} + +HRESULT +AccessibleHandler::get_textAfterOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_textAfterOffset( + offset, boundaryType, startOffset, endOffset, text); +} + +HRESULT +AccessibleHandler::get_textAtOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_textAtOffset(offset, boundaryType, + startOffset, endOffset, text); +} + +HRESULT +AccessibleHandler::removeSelection(long selectionIndex) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->removeSelection(selectionIndex); +} + +HRESULT +AccessibleHandler::setCaretOffset(long offset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->setCaretOffset(offset); +} + +HRESULT +AccessibleHandler::setSelection(long selectionIndex, long startOffset, + long endOffset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->setSelection(selectionIndex, startOffset, + endOffset); +} + +HRESULT +AccessibleHandler::get_nCharacters(long* nCharacters) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_nCharacters(nCharacters); +} + +HRESULT +AccessibleHandler::scrollSubstringTo(long startIndex, long endIndex, + enum IA2ScrollType scrollType) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->scrollSubstringTo(startIndex, endIndex, + scrollType); +} + +HRESULT +AccessibleHandler::scrollSubstringToPoint(long startIndex, long endIndex, + enum IA2CoordinateType coordinateType, + long x, long y) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->scrollSubstringToPoint(startIndex, endIndex, + coordinateType, x, y); +} + +HRESULT +AccessibleHandler::get_newText(IA2TextSegment* newText) { + if (!newText) { + return E_INVALIDARG; + } + + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetSingleton()); + MOZ_ASSERT(ctl); + if (!ctl) { + return S_OK; + } + + long id; + HRESULT hr = this->get_uniqueID(&id); + if (FAILED(hr)) { + return hr; + } + + return ctl->GetNewText(id, WrapNotNull(newText)); +} + +HRESULT +AccessibleHandler::get_oldText(IA2TextSegment* oldText) { + if (!oldText) { + return E_INVALIDARG; + } + + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetSingleton()); + MOZ_ASSERT(ctl); + if (!ctl) { + return S_OK; + } + + long id; + HRESULT hr = this->get_uniqueID(&id); + if (FAILED(hr)) { + return hr; + } + + return ctl->GetOldText(id, WrapNotNull(oldText)); +} + +/*** IAccessibleHypertext ***/ + +HRESULT +AccessibleHandler::get_nHyperlinks(long* hyperlinkCount) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_nHyperlinks(hyperlinkCount); +} + +HRESULT +AccessibleHandler::get_hyperlink(long index, IAccessibleHyperlink** hyperlink) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_hyperlink(index, hyperlink); +} + +HRESULT +AccessibleHandler::get_hyperlinkIndex(long charIndex, long* hyperlinkIndex) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_hyperlinkIndex(charIndex, hyperlinkIndex); +} + +/*** IAccessibleHypertext2 ***/ + +HRESULT +AccessibleHandler::get_hyperlinks(IAccessibleHyperlink*** hyperlinks, + long* nHyperlinks) { + if (!hyperlinks || !nHyperlinks) { + return E_INVALIDARG; + } + + if (mCachedNHyperlinks >= 0) { + // We have cached hyperlinks. + *hyperlinks = mCachedHyperlinks; + *nHyperlinks = mCachedNHyperlinks; + // The client will clean these up. (We only keep the cache for one call.) + mCachedHyperlinks = nullptr; + mCachedNHyperlinks = -1; + return mCachedNHyperlinks == 0 ? S_FALSE : S_OK; + } + + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_hyperlinks(hyperlinks, nHyperlinks); +} + +} // namespace a11y +} // namespace mozilla + +extern "C" HRESULT __stdcall ProxyDllCanUnloadNow(); + +extern "C" HRESULT __stdcall DllCanUnloadNow() { + return mozilla::mscom::Module::CanUnload() && ProxyDllCanUnloadNow(); +} + +extern "C" HRESULT __stdcall ProxyDllGetClassObject(REFCLSID aClsid, + REFIID aIid, + LPVOID* aOutInterface); + +extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID aClsid, REFIID aIid, + LPVOID* aOutInterface) { + if (aClsid == CLSID_AccessibleHandler) { + return mozilla::a11y::sHandlerFactory.QueryInterface(aIid, aOutInterface); + } + return ProxyDllGetClassObject(aClsid, aIid, aOutInterface); +} + +extern "C" BOOL WINAPI ProxyDllMain(HINSTANCE aInstDll, DWORD aReason, + LPVOID aReserved); + +BOOL WINAPI DllMain(HINSTANCE aInstDll, DWORD aReason, LPVOID aReserved) { + if (aReason == DLL_PROCESS_ATTACH) { + DisableThreadLibraryCalls((HMODULE)aInstDll); + } + // This is required for ProxyDllRegisterServer to work correctly + return ProxyDllMain(aInstDll, aReason, aReserved); +} + +extern "C" HRESULT __stdcall ProxyDllRegisterServer(); + +extern "C" HRESULT __stdcall DllRegisterServer() { + HRESULT hr = mozilla::mscom::Handler::Register(CLSID_AccessibleHandler); + if (FAILED(hr)) { + return hr; + } + + return ProxyDllRegisterServer(); +} + +extern "C" HRESULT __stdcall ProxyDllUnregisterServer(); + +extern "C" HRESULT __stdcall DllUnregisterServer() { + HRESULT hr = mozilla::mscom::Handler::Unregister(CLSID_AccessibleHandler); + if (FAILED(hr)) { + return hr; + } + + return ProxyDllUnregisterServer(); +} + +extern "C" HRESULT __stdcall RegisterMsix() { + return mozilla::mscom::Handler::Register(CLSID_AccessibleHandler, + /* aMsixContainer */ true); +} diff --git a/accessible/ipc/win/handler/AccessibleHandler.def b/accessible/ipc/win/handler/AccessibleHandler.def new file mode 100644 index 0000000000..f1f3513b0e --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandler.def @@ -0,0 +1,12 @@ +;+# 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/. + +LIBRARY AccessibleHandler.dll + +EXPORTS DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + GetProxyDllInfo PRIVATE + RegisterMsix PRIVATE diff --git a/accessible/ipc/win/handler/AccessibleHandler.h b/accessible/ipc/win/handler/AccessibleHandler.h new file mode 100644 index 0000000000..f99091d6c3 --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandler.h @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#ifndef mozilla_a11y_AccessibleHandler_h +#define mozilla_a11y_AccessibleHandler_h + +#define NEWEST_IA2_BASENAME Accessible2_3 + +#define __GENIFACE(base) I##base +#define INTERFACEFOR(base) __GENIFACE(base) +#define NEWEST_IA2_INTERFACE INTERFACEFOR(NEWEST_IA2_BASENAME) + +#define __GENIID(iface) IID_##iface +#define IIDFOR(iface) __GENIID(iface) +#define NEWEST_IA2_IID IIDFOR(NEWEST_IA2_INTERFACE) + +#if defined(__midl) || defined(__WIDL__) + +import "Accessible2_3.idl"; + +#else + +# include "HandlerData.h" + +# include <windows.h> + +namespace mozilla { +namespace a11y { + +static const GUID kUnsupportedServices[] = { + // clang-format off + // Unknown, queried by Windows on devices with touch screens or similar devices + // connected. + {0x33f139ee, 0xe509, 0x47f7, {0xbf, 0x39, 0x83, 0x76, 0x44, 0xf7, 0x45, 0x76}}, + // Unknown, queried by Windows + {0xFDA075CF, 0x7C8B, 0x498C, { 0xB5, 0x14, 0xA9, 0xCB, 0x52, 0x1B, 0xBF, 0xB4 }}, + // Unknown, queried by Windows + {0x8EDAA462, 0x21F4, 0x4C87, { 0xA0, 0x12, 0xB3, 0xCD, 0xA3, 0xAB, 0x01, 0xFC }}, + // Unknown, queried by Windows + {0xacd46652, 0x829d, 0x41cb, { 0xa5, 0xfc, 0x17, 0xac, 0xf4, 0x36, 0x61, 0xac }}, + // SID_IsUIAutomationObject (undocumented), queried by Windows + {0xb96fdb85, 0x7204, 0x4724, { 0x84, 0x2b, 0xc7, 0x05, 0x9d, 0xed, 0xb9, 0xd0 }}, + // IIS_IsOleaccProxy (undocumented), queried by Windows + {0x902697FA, 0x80E4, 0x4560, {0x80, 0x2A, 0xA1, 0x3F, 0x22, 0xA6, 0x47, 0x09}}, + // IID_IHTMLElement, queried by JAWS + {0x3050F1FF, 0x98B5, 0x11CF, {0xBB, 0x82, 0x00, 0xAA, 0x00, 0xBD, 0xCE, 0x0B}} + // clang-format on +}; + +} +} // namespace mozilla + +# if !defined(MOZILLA_INTERNAL_API) + +# include "Accessible2_3.h" +# include "AccessibleHyperlink.h" +# include "AccessibleHypertext2.h" +# include "AccessibleTableCell.h" +# include "Handler.h" +# include "mozilla/mscom/StructStream.h" +# include "mozilla/UniquePtr.h" + +# include <ocidl.h> +# include <servprov.h> + +namespace mozilla { +namespace a11y { + +class AccessibleHandler final : public mscom::Handler, + public NEWEST_IA2_INTERFACE, + public IServiceProvider, + public IProvideClassInfo, + public IAccessibleHyperlink, + public IAccessibleTableCell, + public IAccessibleHypertext2 { + public: + static HRESULT Create(IUnknown* aOuter, REFIID aIid, void** aOutInterface); + + // mscom::Handler + HRESULT QueryHandlerInterface(IUnknown* aProxyUnknown, REFIID aIid, + void** aOutInterface) override; + HRESULT ReadHandlerPayload(IStream* aStream, REFIID aIid) override; + + REFIID MarshalAs(REFIID aRequestedIid) override; + HRESULT GetMarshalInterface(REFIID aMarshalAsIid, NotNull<IUnknown*> aProxy, + NotNull<IID*> aOutIid, + NotNull<IUnknown**> aOutUnk) override; + HRESULT GetHandlerPayloadSize(REFIID aIid, DWORD* aOutPayloadSize) override; + HRESULT WriteHandlerPayload(IStream* aStream, REFIID aIId) override; + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IDispatch + STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) override; + STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, + ITypeInfo** ppTInfo) override; + STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) override; + STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS* pDispParams, VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, UINT* puArgErr) override; + + // IAccessible + STDMETHODIMP get_accParent(IDispatch** ppdispParent) override; + STDMETHODIMP get_accChildCount(long* pcountChildren) override; + STDMETHODIMP get_accChild(VARIANT varChild, IDispatch** ppdispChild) override; + STDMETHODIMP get_accName(VARIANT varChild, BSTR* pszName) override; + STDMETHODIMP get_accValue(VARIANT varChild, BSTR* pszValue) override; + STDMETHODIMP get_accDescription(VARIANT varChild, + BSTR* pszDescription) override; + STDMETHODIMP get_accRole(VARIANT varChild, VARIANT* pvarRole) override; + STDMETHODIMP get_accState(VARIANT varChild, VARIANT* pvarState) override; + STDMETHODIMP get_accHelp(VARIANT varChild, BSTR* pszHelp) override; + STDMETHODIMP get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) override; + STDMETHODIMP get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) override; + STDMETHODIMP get_accFocus(VARIANT* pvarChild) override; + STDMETHODIMP get_accSelection(VARIANT* pvarChildren) override; + STDMETHODIMP get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) override; + STDMETHODIMP accSelect(long flagsSelect, VARIANT varChild) override; + STDMETHODIMP accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) override; + STDMETHODIMP accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) override; + STDMETHODIMP accHitTest(long xLeft, long yTop, VARIANT* pvarChild) override; + STDMETHODIMP accDoDefaultAction(VARIANT varChild) override; + STDMETHODIMP put_accName(VARIANT varChild, BSTR szName) override; + STDMETHODIMP put_accValue(VARIANT varChild, BSTR szValue) override; + + // IAccessible2 + STDMETHODIMP get_nRelations(long* nRelations) override; + STDMETHODIMP get_relation(long relationIndex, + IAccessibleRelation** relation) override; + STDMETHODIMP get_relations(long maxRelations, IAccessibleRelation** relations, + long* nRelations) override; + STDMETHODIMP role(long* role) override; + STDMETHODIMP scrollTo(IA2ScrollType scrollType) override; + STDMETHODIMP scrollToPoint(IA2CoordinateType coordinateType, long x, + long y) override; + STDMETHODIMP get_groupPosition(long* groupLevel, long* similarItemsInGroup, + long* positionInGroup) override; + STDMETHODIMP get_states(AccessibleStates* states) override; + STDMETHODIMP get_extendedRole(BSTR* extendedRole) override; + STDMETHODIMP get_localizedExtendedRole(BSTR* localizedExtendedRole) override; + STDMETHODIMP get_nExtendedStates(long* nExtendedStates) override; + STDMETHODIMP get_extendedStates(long maxExtendedStates, BSTR** extendedStates, + long* nExtendedStates) override; + STDMETHODIMP get_localizedExtendedStates( + long maxLocalizedExtendedStates, BSTR** localizedExtendedStates, + long* nLocalizedExtendedStates) override; + STDMETHODIMP get_uniqueID(long* uniqueID) override; + STDMETHODIMP get_windowHandle(HWND* windowHandle) override; + STDMETHODIMP get_indexInParent(long* indexInParent) override; + STDMETHODIMP get_locale(IA2Locale* locale) override; + STDMETHODIMP get_attributes(BSTR* attributes) override; + + // IAccessible2_2 + STDMETHODIMP get_attribute(BSTR name, VARIANT* attribute) override; + STDMETHODIMP get_accessibleWithCaret(IUnknown** accessible, + long* caretOffset) override; + STDMETHODIMP get_relationTargetsOfType(BSTR type, long maxTargets, + IUnknown*** targets, + long* nTargets) override; + + // IAccessible2_3 + STDMETHODIMP get_selectionRanges(IA2Range** ranges, long* nRanges) override; + + // IServiceProvider + STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aIid, + void** aOutInterface) override; + + // IProvideClassInfo + STDMETHODIMP GetClassInfo(ITypeInfo** aOutTypeInfo) override; + + // IAccessibleAction + STDMETHODIMP nActions(long* nActions) override; + STDMETHODIMP doAction(long actionIndex) override; + STDMETHODIMP get_description(long actionIndex, BSTR* description) override; + STDMETHODIMP get_keyBinding(long actionIndex, long nMaxBindings, + BSTR** keyBindings, long* nBindings) override; + STDMETHODIMP get_name(long actionIndex, BSTR* name) override; + STDMETHODIMP get_localizedName(long actionIndex, + BSTR* localizedName) override; + + // IAccessibleHyperlink + STDMETHODIMP get_anchor(long index, VARIANT* anchor) override; + STDMETHODIMP get_anchorTarget(long index, VARIANT* anchorTarget) override; + STDMETHODIMP get_startIndex(long* index) override; + STDMETHODIMP get_endIndex(long* index) override; + STDMETHODIMP get_valid(boolean* valid) override; + + // IAccessibleTableCell + STDMETHODIMP get_columnExtent(long* nColumnsSpanned) override; + STDMETHODIMP get_columnHeaderCells(IUnknown*** cellAccessibles, + long* nColumnHeaderCells) override; + STDMETHODIMP get_columnIndex(long* columnIndex) override; + STDMETHODIMP get_rowExtent(long* nRowsSpanned) override; + STDMETHODIMP get_rowHeaderCells(IUnknown*** cellAccessibles, + long* nRowHeaderCells) override; + STDMETHODIMP get_rowIndex(long* rowIndex) override; + STDMETHODIMP get_isSelected(boolean* isSelected) override; + STDMETHODIMP get_rowColumnExtents(long* row, long* column, long* rowExtents, + long* columnExtents, + boolean* isSelected) override; + STDMETHODIMP get_table(IUnknown** table) override; + + // IAccessibleText + STDMETHODIMP addSelection(long startOffset, long endOffset) override; + STDMETHODIMP get_attributes(long offset, long* startOffset, long* endOffset, + BSTR* textAttributes) override; + STDMETHODIMP get_caretOffset(long* offset) override; + STDMETHODIMP get_characterExtents(long offset, + enum IA2CoordinateType coordType, long* x, + long* y, long* width, + long* height) override; + STDMETHODIMP get_nSelections(long* nSelections) override; + STDMETHODIMP get_offsetAtPoint(long x, long y, + enum IA2CoordinateType coordType, + long* offset) override; + STDMETHODIMP get_selection(long selectionIndex, long* startOffset, + long* endOffset) override; + STDMETHODIMP get_text(long startOffset, long endOffset, BSTR* text) override; + STDMETHODIMP get_textBeforeOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) override; + STDMETHODIMP get_textAfterOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) override; + STDMETHODIMP get_textAtOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) override; + STDMETHODIMP removeSelection(long selectionIndex) override; + STDMETHODIMP setCaretOffset(long offset) override; + STDMETHODIMP setSelection(long selectionIndex, long startOffset, + long endOffset) override; + STDMETHODIMP get_nCharacters(long* nCharacters) override; + STDMETHODIMP scrollSubstringTo(long startIndex, long endIndex, + enum IA2ScrollType scrollType) override; + STDMETHODIMP scrollSubstringToPoint(long startIndex, long endIndex, + enum IA2CoordinateType coordinateType, + long x, long y) override; + STDMETHODIMP get_newText(IA2TextSegment* newText) override; + STDMETHODIMP get_oldText(IA2TextSegment* oldText) override; + + // IAccessibleHypertext + STDMETHODIMP get_nHyperlinks(long* hyperlinkCount) override; + STDMETHODIMP get_hyperlink(long index, + IAccessibleHyperlink** hyperlink) override; + STDMETHODIMP get_hyperlinkIndex(long charIndex, + long* hyperlinkIndex) override; + + // IAccessibleHypertext2 + STDMETHODIMP get_hyperlinks(IAccessibleHyperlink*** hyperlinks, + long* nHyperlinks) override; + + private: + AccessibleHandler(IUnknown* aOuter, HRESULT* aResult); + virtual ~AccessibleHandler(); + + HRESULT ResolveIA2(); + HRESULT ResolveIDispatch(); + HRESULT ResolveIAHyperlink(); + HRESULT ResolveIAHypertext(); + HRESULT ResolveIATableCell(); + HRESULT MaybeUpdateCachedData(); + HRESULT GetAllTextInfo(BSTR* aText); + void ClearTextCache(); + HRESULT GetRelationsInfo(); + void ClearRelationCache(); + + RefPtr<IUnknown> mDispatchUnk; + /** + * Handlers aggregate their proxies. This means that their proxies delegate + * their IUnknown implementation to us. + * + * mDispatchUnk and the result of Handler::GetProxy() are both strong + * references to the aggregated objects. OTOH, any interfaces that are QI'd + * from those aggregated objects have delegated unknowns. + * + * AddRef'ing an interface with a delegated unknown ends up incrementing the + * refcount of the *aggregator*. Since we are the aggregator of mDispatchUnk + * and of the wrapped proxy, holding a strong reference to any interfaces + * QI'd off of those objects would create a reference cycle. + * + * We may hold onto pointers to those references, but when we query them we + * must immediately Release() them to prevent these cycles. + * + * It is safe for us to use these raw pointers because the aggregated + * objects's lifetimes are proper subsets of our own lifetime. + */ + IDispatch* mDispatch; // weak + NEWEST_IA2_INTERFACE* mIA2PassThru; // weak + IServiceProvider* mServProvPassThru; // weak + IAccessibleHyperlink* mIAHyperlinkPassThru; // weak + IAccessibleTableCell* mIATableCellPassThru; // weak + IAccessibleHypertext2* mIAHypertextPassThru; // weak + IA2Payload mCachedData; + bool mCachedDynamicDataMarshaledByCom; + UniquePtr<mscom::StructToStream> mSerializer; + uint32_t mCacheGen; + IAccessibleHyperlink** mCachedHyperlinks; + long mCachedNHyperlinks; + IA2TextSegment* mCachedTextAttribRuns; + long mCachedNTextAttribRuns; + IARelationData* mCachedRelations; + long mCachedNRelations; + bool mIsEmulatedWindow; +}; + +inline static BSTR CopyBSTR(BSTR aSrc) { + if (!aSrc) { + return nullptr; + } + + return ::SysAllocStringLen(aSrc, ::SysStringLen(aSrc)); +} + +} // namespace a11y +} // namespace mozilla + +# endif // !defined(MOZILLA_INTERNAL_API) + +#endif // defined(__midl) + +#endif // mozilla_a11y_AccessibleHandler_h diff --git a/accessible/ipc/win/handler/AccessibleHandler.rc b/accessible/ipc/win/handler/AccessibleHandler.rc new file mode 100644 index 0000000000..3dac2f89d5 --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandler.rc @@ -0,0 +1,5 @@ +/* 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/. */ + +1 typelib HandlerData.tlb diff --git a/accessible/ipc/win/handler/AccessibleHandlerControl.cpp b/accessible/ipc/win/handler/AccessibleHandlerControl.cpp new file mode 100644 index 0000000000..0d0bd4d71d --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandlerControl.cpp @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "AccessibleHandlerControl.h" + +#include <utility> + +#include "AccessibleEventId.h" +#include "AccessibleHandler.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +mscom::SingletonFactory<AccessibleHandlerControl> gControlFactory; + +namespace detail { + +TextChange::TextChange() : mIA2UniqueId(0), mIsInsert(false), mText() {} + +TextChange::TextChange(long aIA2UniqueId, bool aIsInsert, + NotNull<IA2TextSegment*> aText) + : mIA2UniqueId(aIA2UniqueId), + mIsInsert(aIsInsert), + mText{BSTRCopy(aText->text), aText->start, aText->end} {} + +TextChange::TextChange(TextChange&& aOther) : mText() { + *this = std::move(aOther); +} + +TextChange::TextChange(const TextChange& aOther) : mText() { *this = aOther; } + +TextChange& TextChange::operator=(TextChange&& aOther) { + mIA2UniqueId = aOther.mIA2UniqueId; + mIsInsert = aOther.mIsInsert; + aOther.mIA2UniqueId = 0; + ::SysFreeString(mText.text); + mText = aOther.mText; + aOther.mText.text = nullptr; + return *this; +} + +TextChange& TextChange::operator=(const TextChange& aOther) { + mIA2UniqueId = aOther.mIA2UniqueId; + mIsInsert = aOther.mIsInsert; + ::SysFreeString(mText.text); + mText = {BSTRCopy(aOther.mText.text), aOther.mText.start, aOther.mText.end}; + return *this; +} + +TextChange::~TextChange() { ::SysFreeString(mText.text); } + +HRESULT +TextChange::GetOld(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldSegment) { + if (mIsInsert || aIA2UniqueId != mIA2UniqueId) { + return S_OK; + } + + return SegCopy(*aOutOldSegment, mText); +} + +HRESULT +TextChange::GetNew(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewSegment) { + if (!mIsInsert || aIA2UniqueId != mIA2UniqueId) { + return S_OK; + } + + return SegCopy(*aOutNewSegment, mText); +} + +/* static */ +BSTR TextChange::BSTRCopy(const BSTR& aIn) { + return ::SysAllocStringLen(aIn, ::SysStringLen(aIn)); +} + +/* static */ +HRESULT TextChange::SegCopy(IA2TextSegment& aDest, const IA2TextSegment& aSrc) { + aDest = {BSTRCopy(aSrc.text), aSrc.start, aSrc.end}; + if (aSrc.text && !aDest.text) { + return E_OUTOFMEMORY; + } + if (!::SysStringLen(aDest.text)) { + return S_FALSE; + } + return S_OK; +} + +} // namespace detail + +HRESULT +AccessibleHandlerControl::Create(AccessibleHandlerControl** aOutObject) { + if (!aOutObject) { + return E_INVALIDARG; + } + + RefPtr<AccessibleHandlerControl> ctl(new AccessibleHandlerControl()); + ctl.forget(aOutObject); + return S_OK; +} + +AccessibleHandlerControl::AccessibleHandlerControl() + : mIsRegistered(false), + mCacheGen(0), + mIA2Proxy(mscom::RegisterProxy(L"ia2marshal.dll")), + mHandlerProxy(mscom::RegisterProxy()) { + MOZ_ASSERT(mIA2Proxy); +} + +IMPL_IUNKNOWN1(AccessibleHandlerControl, IHandlerControl) + +HRESULT +AccessibleHandlerControl::Invalidate() { + ++mCacheGen; + // We can't just call mAccessibleCache.clear() because doing so would release + // remote objects, making remote COM calls. Since this is an STA, an incoming + // COM call might be handled which might marshal an AccessibleHandler, + // which in turn might add itself to mAccessibleCache. Since we'd be in the + // middle of mutating mAccessibleCache, that might cause a crash. Instead, + // swap mAccessibleCache into a temporary map first, which will empty + // mAccessibleCache without releasing remote objects. Once mAccessibleCache + // is empty, it's safe to let the temporary map be destroyed when it goes + // out of scope. Remote calls will be made, but nothing will re-enter + // the temporary map while it's being destroyed. + AccessibleCache oldCache; + mAccessibleCache.swap(oldCache); + return S_OK; +} + +HRESULT +AccessibleHandlerControl::OnTextChange(long aHwnd, long aIA2UniqueId, + VARIANT_BOOL aIsInsert, + IA2TextSegment* aText) { + if (!aText) { + return E_INVALIDARG; + } + + mTextChange = detail::TextChange(aIA2UniqueId, aIsInsert, WrapNotNull(aText)); + NotifyWinEvent(aIsInsert ? IA2_EVENT_TEXT_INSERTED : IA2_EVENT_TEXT_REMOVED, + reinterpret_cast<HWND>(static_cast<uintptr_t>(aHwnd)), + OBJID_CLIENT, aIA2UniqueId); + return S_OK; +} + +HRESULT +AccessibleHandlerControl::GetNewText(long aIA2UniqueId, + NotNull<IA2TextSegment*> aOutNewText) { + return mTextChange.GetNew(aIA2UniqueId, aOutNewText); +} + +HRESULT +AccessibleHandlerControl::GetOldText(long aIA2UniqueId, + NotNull<IA2TextSegment*> aOutOldText) { + return mTextChange.GetOld(aIA2UniqueId, aOutOldText); +} + +HRESULT +AccessibleHandlerControl::GetHandlerTypeInfo(ITypeInfo** aOutTypeInfo) { + if (!mHandlerProxy) { + return E_UNEXPECTED; + } + + return mHandlerProxy->GetTypeInfoForGuid(CLSID_AccessibleHandler, + aOutTypeInfo); +} + +HRESULT +AccessibleHandlerControl::Register(NotNull<IGeckoBackChannel*> aGecko) { + if (mIsRegistered) { + return S_OK; + } + + long pid = static_cast<long>(::GetCurrentProcessId()); + HRESULT hr = aGecko->put_HandlerControl(pid, this); + mIsRegistered = SUCCEEDED(hr); + MOZ_ASSERT(mIsRegistered); + return hr; +} + +void AccessibleHandlerControl::CacheAccessible(long aUniqueId, + AccessibleHandler* aAccessible) { + MOZ_ASSERT(aUniqueId && aAccessible); + mAccessibleCache[aUniqueId] = aAccessible; +} + +HRESULT AccessibleHandlerControl::GetCachedAccessible( + long aUniqueId, AccessibleHandler** aAccessible) { + MOZ_ASSERT(aUniqueId && aAccessible); + auto it = mAccessibleCache.find(aUniqueId); + if (it == mAccessibleCache.end()) { + return E_INVALIDARG; + } + RefPtr<AccessibleHandler> ref = it->second; + ref.forget(aAccessible); + return S_OK; +} + +HRESULT AccessibleHandlerControl::SuppressA11yForClipboardCopy() { + mA11yClipboardCopySuppressionStartTime = ::GetTickCount(); + return S_OK; +} + +bool AccessibleHandlerControl::IsA11ySuppressedForClipboardCopy() { + // Must be kept in sync with kSuppressTimeout in + // accessible/windows/msaa/Compatibility.cpp. + constexpr DWORD kSuppressTimeout = 1500; // ms + if (!mA11yClipboardCopySuppressionStartTime) { + return false; + } + return ::GetTickCount() - mA11yClipboardCopySuppressionStartTime < + kSuppressTimeout; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/handler/AccessibleHandlerControl.h b/accessible/ipc/win/handler/AccessibleHandlerControl.h new file mode 100644 index 0000000000..9a94ff9fe8 --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandlerControl.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#ifndef mozilla_a11y_AccessibleHandlerControl_h +# define mozilla_a11y_AccessibleHandlerControl_h + +# include <unordered_map> +# include "Factory.h" +# include "HandlerData.h" +# include "IUnknownImpl.h" +# include "mozilla/mscom/Registration.h" +# include "mozilla/NotNull.h" + +namespace mozilla { +namespace a11y { + +namespace detail { + +class TextChange final { + public: + TextChange(); + TextChange(long aIA2UniqueId, bool aIsInsert, NotNull<IA2TextSegment*> aText); + TextChange(TextChange&& aOther); + TextChange(const TextChange& aOther); + + TextChange& operator=(TextChange&& aOther); + TextChange& operator=(const TextChange& aOther); + + ~TextChange(); + + HRESULT GetOld(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldSegment); + HRESULT GetNew(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewSegment); + + private: + static BSTR BSTRCopy(const BSTR& aIn); + static HRESULT SegCopy(IA2TextSegment& aDest, const IA2TextSegment& aSrc); + + long mIA2UniqueId; + bool mIsInsert; + IA2TextSegment mText; +}; + +} // namespace detail + +class AccessibleHandler; + +class AccessibleHandlerControl final : public IHandlerControl { + public: + static HRESULT Create(AccessibleHandlerControl** aOutObject); + + DECL_IUNKNOWN + + // IHandlerControl + STDMETHODIMP Invalidate() override; + STDMETHODIMP OnTextChange(long aHwnd, long aIA2UniqueId, + VARIANT_BOOL aIsInsert, + IA2TextSegment* aText) override; + STDMETHODIMP SuppressA11yForClipboardCopy() override; + + uint32_t GetCacheGen() const { return mCacheGen; } + + HRESULT GetNewText(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewText); + HRESULT GetOldText(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldText); + + HRESULT GetHandlerTypeInfo(ITypeInfo** aOutTypeInfo); + + HRESULT Register(NotNull<IGeckoBackChannel*> aGecko); + + void CacheAccessible(long aUniqueId, AccessibleHandler* aAccessible); + HRESULT GetCachedAccessible(long aUniqueId, AccessibleHandler** aAccessible); + + bool IsA11ySuppressedForClipboardCopy(); + + private: + AccessibleHandlerControl(); + ~AccessibleHandlerControl() = default; + + bool mIsRegistered; + uint32_t mCacheGen; + detail::TextChange mTextChange; + UniquePtr<mscom::RegisteredProxy> mIA2Proxy; + UniquePtr<mscom::RegisteredProxy> mHandlerProxy; + // We can't use Gecko APIs in this dll, hence the use of std::unordered_map. + typedef std::unordered_map<long, RefPtr<AccessibleHandler>> AccessibleCache; + AccessibleCache mAccessibleCache; + // Time when SuppressA11yForClipboardCopy() was called, as returned by + // ::GetTickCount(). + DWORD mA11yClipboardCopySuppressionStartTime = 0; +}; + +extern mscom::SingletonFactory<AccessibleHandlerControl> gControlFactory; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_AccessibleHandlerControl_h diff --git a/accessible/ipc/win/handler/HandlerChildEnumerator.cpp b/accessible/ipc/win/handler/HandlerChildEnumerator.cpp new file mode 100644 index 0000000000..2062387bf6 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerChildEnumerator.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "HandlerChildEnumerator.h" +#include "HandlerTextLeaf.h" +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace a11y { + +HandlerChildEnumerator::HandlerChildEnumerator( + AccessibleHandler* aHandler, IGeckoBackChannel* aGeckoBackChannel) + : mHandler(aHandler), + mGeckoBackChannel(aGeckoBackChannel), + mChildCount(0), + mNextChild(0) { + MOZ_ASSERT(aHandler); + MOZ_ASSERT(aGeckoBackChannel); +} + +HandlerChildEnumerator::HandlerChildEnumerator( + const HandlerChildEnumerator& aEnumerator) + : mHandler(aEnumerator.mHandler), + mGeckoBackChannel(aEnumerator.mGeckoBackChannel), + mChildCount(aEnumerator.mChildCount), + mNextChild(aEnumerator.mNextChild) { + if (mChildCount == 0) { + return; + } + mChildren = MakeUnique<VARIANT[]>(mChildCount); + CopyMemory(mChildren.get(), aEnumerator.mChildren.get(), + sizeof(VARIANT) * mChildCount); + for (ULONG index = 0; index < mChildCount; ++index) { + mChildren[index].pdispVal->AddRef(); + } +} + +HandlerChildEnumerator::~HandlerChildEnumerator() { ClearCache(); } + +void HandlerChildEnumerator::ClearCache() { + if (!mChildren) { + return; + } + + for (ULONG index = 0; index < mChildCount; ++index) { + mChildren[index].pdispVal->Release(); + } + + mChildren = nullptr; + mChildCount = 0; +} + +HRESULT +HandlerChildEnumerator::MaybeCacheChildren() { + if (mChildren) { + // Already cached. + return S_OK; + } + + AccChildData* children; + HRESULT hr = mGeckoBackChannel->get_AllChildren(&children, &mChildCount); + if (FAILED(hr)) { + mChildCount = 0; + ClearCache(); + return hr; + } + + HWND hwnd = nullptr; + hr = mHandler->get_windowHandle(&hwnd); + MOZ_ASSERT(SUCCEEDED(hr)); + + RefPtr<IDispatch> parent; + hr = mHandler->QueryInterface(IID_IDispatch, getter_AddRefs(parent)); + MOZ_ASSERT(SUCCEEDED(hr)); + + mChildren = MakeUnique<VARIANT[]>(mChildCount); + for (ULONG index = 0; index < mChildCount; ++index) { + AccChildData& data = children[index]; + VARIANT& child = mChildren[index]; + if (data.mAccessible) { + RefPtr<IDispatch> disp; + hr = + data.mAccessible->QueryInterface(IID_IDispatch, getter_AddRefs(disp)); + data.mAccessible->Release(); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + child.vt = VT_EMPTY; + continue; + } + child.vt = VT_DISPATCH; + disp.forget(&child.pdispVal); + } else { + // Text leaf. + RefPtr<IDispatch> leaf(new HandlerTextLeaf(parent, index, hwnd, data)); + child.vt = VT_DISPATCH; + leaf.forget(&child.pdispVal); + } + } + + ::CoTaskMemFree(children); + return S_OK; +} + +IMPL_IUNKNOWN_QUERY_HEAD(HandlerChildEnumerator) +IMPL_IUNKNOWN_QUERY_IFACE(IEnumVARIANT) +IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mHandler) + +/*** IEnumVARIANT ***/ + +HRESULT +HandlerChildEnumerator::Clone(IEnumVARIANT** aPpEnum) { + RefPtr<HandlerChildEnumerator> newEnum(new HandlerChildEnumerator(*this)); + newEnum.forget(aPpEnum); + return S_OK; +} + +HRESULT +HandlerChildEnumerator::Next(ULONG aCelt, VARIANT* aRgVar, + ULONG* aPCeltFetched) { + if (!aRgVar || aCelt == 0) { + return E_INVALIDARG; + } + + HRESULT hr = MaybeCacheChildren(); + if (FAILED(hr)) { + return hr; + } + + for (ULONG index = 0; index < aCelt; ++index) { + if (mNextChild >= mChildCount) { + // Less elements remaining than were requested. + if (aPCeltFetched) { + *aPCeltFetched = index; + } + return S_FALSE; + } + aRgVar[index] = mChildren[mNextChild]; + aRgVar[index].pdispVal->AddRef(); + ++mNextChild; + } + *aPCeltFetched = aCelt; + return S_OK; +} + +HRESULT +HandlerChildEnumerator::Reset() { + mNextChild = 0; + ClearCache(); + return S_OK; +} + +HRESULT +HandlerChildEnumerator::Skip(ULONG aCelt) { + mNextChild += aCelt; + if (mNextChild > mChildCount) { + // Less elements remaining than the client requested to skip. + return S_FALSE; + } + return S_OK; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/handler/HandlerChildEnumerator.h b/accessible/ipc/win/handler/HandlerChildEnumerator.h new file mode 100644 index 0000000000..df0218c6ed --- /dev/null +++ b/accessible/ipc/win/handler/HandlerChildEnumerator.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#ifndef mozilla_a11y_HandlerChildEnumerator_h +# define mozilla_a11y_HandlerChildEnumerator_h + +# include "AccessibleHandler.h" +# include "IUnknownImpl.h" +# include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +class HandlerChildEnumerator final : public IEnumVARIANT { + public: + explicit HandlerChildEnumerator(AccessibleHandler* aHandler, + IGeckoBackChannel* aGeckoBackChannel); + + DECL_IUNKNOWN + + // IEnumVARIANT + STDMETHODIMP Clone(IEnumVARIANT** aPpEnum) override; + STDMETHODIMP Next(ULONG aCelt, VARIANT* aRgVar, + ULONG* aPCeltFetched) override; + STDMETHODIMP Reset() override; + STDMETHODIMP Skip(ULONG aCelt) override; + + private: + explicit HandlerChildEnumerator(const HandlerChildEnumerator& aEnumerator); + ~HandlerChildEnumerator(); + void ClearCache(); + HRESULT MaybeCacheChildren(); + + RefPtr<AccessibleHandler> mHandler; + RefPtr<IGeckoBackChannel> mGeckoBackChannel; + UniquePtr<VARIANT[]> mChildren; + ULONG mChildCount; + ULONG mNextChild; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerChildEnumerator_h diff --git a/accessible/ipc/win/handler/HandlerData.acf b/accessible/ipc/win/handler/HandlerData.acf new file mode 100644 index 0000000000..993240b063 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerData.acf @@ -0,0 +1,11 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +[explicit_handle] +interface HandlerData +{ + typedef [encode,decode] IA2Payload; +} diff --git a/accessible/ipc/win/handler/HandlerData.idl b/accessible/ipc/win/handler/HandlerData.idl new file mode 100644 index 0000000000..e449a88263 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerData.idl @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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-config.h" +#include "AccessibleHandler.h" +#include "HandlerDataUUID.h" + +import "ocidl.idl"; +import "servprov.idl"; + +import "Accessible2_3.idl"; +import "AccessibleHypertext2.idl"; +import "AccessibleHyperlink.idl"; +import "AccessibleTable.idl"; +import "AccessibleTable2.idl"; +import "AccessibleTableCell.idl"; + +typedef struct _StaticIA2Data +{ + NEWEST_IA2_INTERFACE* mIA2; + IAccessibleHypertext2* mIAHypertext; + IAccessibleHyperlink* mIAHyperlink; + IAccessibleTable* mIATable; + IAccessibleTable2* mIATable2; + IAccessibleTableCell* mIATableCell; +} StaticIA2Data; + +typedef struct _DynamicIA2Data +{ + // From IAccessible/IAccessible2 + VARIANT mRole; + long mState; + long mChildCount; + long mIA2Role; + AccessibleStates mIA2States; + long mLeft; + long mTop; + long mWidth; + long mHeight; + long mHwnd; + BSTR mKeyboardShortcut; + BSTR mName; + BSTR mDescription; + BSTR mDefaultAction; + BSTR mValue; + BSTR mAttributes; + IA2Locale mIA2Locale; + // From IAccessibleAction + long mNActions; + // From IAccessibleTableCell + long mRowIndex; + long mColumnIndex; + long mRowExtent; + long mColumnExtent; + boolean mCellIsSelected; + long mNRowHeaderCells; + [size_is(mNRowHeaderCells)] long* mRowHeaderCellIds; + long mNColumnHeaderCells; + [size_is(mNColumnHeaderCells)] long* mColumnHeaderCellIds; + // From IAccessible2 + long mUniqueId; +} DynamicIA2Data; + +interface IGeckoBackChannel; + +[uuid(2b0e83b3-fd1a-443f-9ed6-c00d39055b58)] +interface HandlerData +{ + typedef struct _IA2Payload + { + StaticIA2Data mStaticData; + DynamicIA2Data mDynamicData; + IGeckoBackChannel* mGeckoBackChannel; + } IA2Payload; +} + +[object, + uuid(IHANDLERCONTROL_IID), + async_uuid(ASYNCIHANDLERCONTROL_IID), + pointer_default(unique)] +interface IHandlerControl : IUnknown +{ + HRESULT Invalidate(); + HRESULT OnTextChange([in] long aHwnd, [in] long aIA2UniqueId, + [in] VARIANT_BOOL aIsInsert, + [in] IA2TextSegment* aText); + HRESULT SuppressA11yForClipboardCopy(); +} + +typedef struct _IARelationData +{ + BSTR mType; + long mNTargets; +} IARelationData; + +typedef struct _AccChildData +{ + NEWEST_IA2_INTERFACE* mAccessible; + BSTR mText; + long mTextRole; + long mTextId; + long mTextState; + long mTextLeft; + long mTextTop; + long mTextWidth; + long mTextHeight; +} AccChildData; + +[object, + uuid(IGECKOBACKCHANNEL_IID), + pointer_default(unique)] +interface IGeckoBackChannel : IUnknown +{ + [propput] HRESULT HandlerControl([in] long aPid, [in] IHandlerControl* aCtrl); + HRESULT Refresh([out] DynamicIA2Data* aOutData); + [propget] HRESULT AllTextInfo([out] BSTR* aText, + [out, size_is(,*aNHyperlinks)] IAccessibleHyperlink*** aHyperlinks, + [out] long* aNHyperlinks, + [out, size_is(,*aNAttribRuns)] IA2TextSegment** aAttribRuns, + [out] long* aNAttribRuns); + [propget] HRESULT RelationsInfo( + [out, size_is(,*aNRelations)] IARelationData** aRelations, + [out] long* aNRelations); + [propget] HRESULT AllChildren( + [out, size_is(,*aNChildren)] AccChildData** aChildren, + [out] ULONG* aNChildren); +} + +[uuid(1e545f07-f108-4912-9471-546827a80983)] +library AccessibleHandlerTypeLib +{ + importlib("stdole2.tlb"); + + /** + * This definition is required in order for the handler implementation to + * support IDispatch (aka Automation). This is used by interpreted language + * FFIs to discover which interfaces may be controlled via IDispatch. + * (In particular, the python FFI used by NVDA needs this). + * + * In reality, the only a11y interface that is Automation compliant is + * IAccessible; our remaining interfaces are not. + * + * Once the FFI knows that IAccessible is supported, the FFI queries for + * IAccessible and is then able to resolve non-automation interfaces from + * there. + */ + [uuid(HANDLER_CLSID)] + coclass AccessibleHandler + { + [default] interface IAccessible; + }; +}; diff --git a/accessible/ipc/win/handler/HandlerDataCleanup.h b/accessible/ipc/win/handler/HandlerDataCleanup.h new file mode 100644 index 0000000000..14d9995c19 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerDataCleanup.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#ifndef mozilla_a11y_HandlerDataCleanup_h +#define mozilla_a11y_HandlerDataCleanup_h + +#include <oleauto.h> + +namespace mozilla { +namespace a11y { + +inline void ReleaseStaticIA2DataInterfaces(StaticIA2Data& aData) { + // Only interfaces of the proxied object wrapped by this handler should be + // released here, never other objects! + // For example, if StaticIA2Data were to include accParent in future, + // that must not be released here. + if (aData.mIA2) { + aData.mIA2->Release(); + } + if (aData.mIAHypertext) { + aData.mIAHypertext->Release(); + } + if (aData.mIAHyperlink) { + aData.mIAHyperlink->Release(); + } + if (aData.mIATable) { + aData.mIATable->Release(); + } + if (aData.mIATable2) { + aData.mIATable2->Release(); + } + if (aData.mIATableCell) { + aData.mIATableCell->Release(); + } +} + +/** + * Pass true for aMarshaledByCom if this struct was directly marshaled as an + * out parameter of a COM method, currently only IGeckoBackChannel::Refresh. + */ +inline void CleanupDynamicIA2Data(DynamicIA2Data& aData, + bool aMarshaledByCom = false) { + // If freeing generic memory returned to the client, you *must* use freeMem, + // not CoTaskMemFree! + auto freeMem = [aMarshaledByCom](void* aMem) { + if (aMarshaledByCom) { + ::CoTaskMemFree(aMem); + } else { + ::midl_user_free(aMem); + } + }; + + ::VariantClear(&aData.mRole); + if (aData.mKeyboardShortcut) { + ::SysFreeString(aData.mKeyboardShortcut); + } + if (aData.mName) { + ::SysFreeString(aData.mName); + } + if (aData.mDescription) { + ::SysFreeString(aData.mDescription); + } + if (aData.mDefaultAction) { + ::SysFreeString(aData.mDefaultAction); + } + if (aData.mValue) { + ::SysFreeString(aData.mValue); + } + if (aData.mAttributes) { + ::SysFreeString(aData.mAttributes); + } + if (aData.mIA2Locale.language) { + ::SysFreeString(aData.mIA2Locale.language); + } + if (aData.mIA2Locale.country) { + ::SysFreeString(aData.mIA2Locale.country); + } + if (aData.mIA2Locale.variant) { + ::SysFreeString(aData.mIA2Locale.variant); + } + if (aData.mRowHeaderCellIds) { + freeMem(aData.mRowHeaderCellIds); + } + if (aData.mColumnHeaderCellIds) { + freeMem(aData.mColumnHeaderCellIds); + } +} + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerDataCleanup_h diff --git a/accessible/ipc/win/handler/HandlerDataUUID.h.in b/accessible/ipc/win/handler/HandlerDataUUID.h.in new file mode 100644 index 0000000000..234c3d6114 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerDataUUID.h.in @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +// We use different CLSIDs and IIDs depending on channel and officiality. +// This prevents handlers from installing overtop one another when multiple +// channels are present. Note that we only do this for the UUIDs that are +// written to the registry. +// The specific UUIDs are defined in branding configuration. + +#define HANDLER_CLSID @MOZ_HANDLER_CLSID@ +#define IHANDLERCONTROL_IID @MOZ_IHANDLERCONTROL_IID@ +#define ASYNCIHANDLERCONTROL_IID @MOZ_ASYNCIHANDLERCONTROL_IID@ +#define IGECKOBACKCHANNEL_IID @MOZ_IGECKOBACKCHANNEL_IID@ diff --git a/accessible/ipc/win/handler/HandlerRelation.cpp b/accessible/ipc/win/handler/HandlerRelation.cpp new file mode 100644 index 0000000000..8e03da5360 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerRelation.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "HandlerRelation.h" +#include "mozilla/Assertions.h" + +#include "AccessibleRelation_i.c" + +namespace mozilla { +namespace a11y { + +HandlerRelation::HandlerRelation(AccessibleHandler* aHandler, + IARelationData& aData) + : mHandler(aHandler), mData(aData), mTargets(nullptr) { + // This instance now owns any pointers, so ensure no one else can + // manipulate them. + aData.mType = nullptr; +} + +HandlerRelation::~HandlerRelation() { + if (mData.mType) { + ::SysFreeString(mData.mType); + } + + if (mTargets) { + for (long index = 0; index < mData.mNTargets; ++index) { + mTargets[index]->Release(); + } + ::CoTaskMemFree(mTargets); + mTargets = nullptr; + } +} + +HRESULT +HandlerRelation::GetTargets() { + if (mTargets) { + // Already cached. + return S_OK; + } + + // Marshaling all of the IAccessibleRelation objects across processes is + // slow, and the client probably only wants targets for a few of them. + // Therefore, we just use IAccessible2_2::relationTargetsOfType, passing + // the type we have cached. This is a bit inefficient because Gecko has + // to look up the relation twice, but it's significantly faster than + // marshaling the relation objects regardless. + return mHandler->get_relationTargetsOfType(mData.mType, 0, &mTargets, + &mData.mNTargets); +} + +IMPL_IUNKNOWN1(HandlerRelation, IAccessibleRelation) + +/*** IAccessibleRelation ***/ + +HRESULT +HandlerRelation::get_relationType(BSTR* aType) { + if (!aType) { + return E_INVALIDARG; + } + if (!mData.mType) { + return E_FAIL; + } + *aType = CopyBSTR(mData.mType); + return S_OK; +} + +HRESULT +HandlerRelation::get_localizedRelationType(BSTR* aLocalizedType) { + // This is not implemented as per ia2AccessibleRelation. + return E_NOTIMPL; +} + +HRESULT +HandlerRelation::get_nTargets(long* aNTargets) { + if (!aNTargets) { + return E_INVALIDARG; + } + if (mData.mNTargets == -1) { + return E_FAIL; + } + *aNTargets = mData.mNTargets; + return S_OK; +} + +HRESULT +HandlerRelation::get_target(long aIndex, IUnknown** aTarget) { + if (!aTarget) { + return E_INVALIDARG; + } + + HRESULT hr = GetTargets(); + if (FAILED(hr)) { + return hr; + } + if (aIndex >= mData.mNTargets) { + return E_INVALIDARG; + } + + *aTarget = mTargets[aIndex]; + (*aTarget)->AddRef(); + return S_OK; +} + +HRESULT +HandlerRelation::get_targets(long aMaxTargets, IUnknown** aTargets, + long* aNTargets) { + if (aMaxTargets == 0 || !aTargets || !aNTargets) { + return E_INVALIDARG; + } + + HRESULT hr = GetTargets(); + if (FAILED(hr)) { + return hr; + } + + if (mData.mNTargets > aMaxTargets) { + // Don't give back more targets than were requested. + *aNTargets = aMaxTargets; + } else { + *aNTargets = mData.mNTargets; + } + + for (long index = 0; index < *aNTargets; ++index) { + aTargets[index] = mTargets[index]; + aTargets[index]->AddRef(); + } + return S_OK; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/handler/HandlerRelation.h b/accessible/ipc/win/handler/HandlerRelation.h new file mode 100644 index 0000000000..e25b4ee189 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerRelation.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#ifndef mozilla_a11y_HandlerRelation_h +# define mozilla_a11y_HandlerRelation_h + +# include "AccessibleHandler.h" +# include "IUnknownImpl.h" +# include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +class HandlerRelation final : public IAccessibleRelation { + public: + explicit HandlerRelation(AccessibleHandler* aHandler, IARelationData& aData); + + DECL_IUNKNOWN + + // IAccessibleRelation + STDMETHODIMP get_relationType(BSTR* aType) override; + STDMETHODIMP get_localizedRelationType(BSTR* aLocalizedType) override; + STDMETHODIMP get_nTargets(long* aNTargets) override; + STDMETHODIMP get_target(long aIndex, IUnknown** aTarget) override; + STDMETHODIMP get_targets(long aMaxTargets, IUnknown** aTargets, + long* aNTargets) override; + + private: + virtual ~HandlerRelation(); + HRESULT GetTargets(); + RefPtr<AccessibleHandler> mHandler; + IARelationData mData; + IUnknown** mTargets; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerRelation_h diff --git a/accessible/ipc/win/handler/HandlerTextLeaf.cpp b/accessible/ipc/win/handler/HandlerTextLeaf.cpp new file mode 100644 index 0000000000..730aa88df9 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerTextLeaf.cpp @@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "HandlerTextLeaf.h" +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace a11y { + +HandlerTextLeaf::HandlerTextLeaf(IDispatch* aParent, long aIndexInParent, + HWND aHwnd, AccChildData& aData) + : mParent(aParent), + mIndexInParent(aIndexInParent), + mHwnd(aHwnd), + mData(aData) { + MOZ_ASSERT(aParent); +} + +HandlerTextLeaf::~HandlerTextLeaf() { + if (mData.mText) { + ::SysFreeString(mData.mText); + } +} + +IMPL_IUNKNOWN_QUERY_HEAD(HandlerTextLeaf) +IMPL_IUNKNOWN_QUERY_IFACE(IDispatch) +IMPL_IUNKNOWN_QUERY_IFACE(IAccessible) +IMPL_IUNKNOWN_QUERY_IFACE(IAccessible2) +IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider) +IMPL_IUNKNOWN_QUERY_TAIL + +/*** IDispatch ***/ + +HRESULT +HandlerTextLeaf::GetTypeInfoCount(UINT* pctinfo) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS* pDispParams, + VARIANT* pVarResult, EXCEPINFO* pExcepInfo, + UINT* puArgErr) { + return E_NOTIMPL; +} + +/*** IAccessible ***/ + +HRESULT +HandlerTextLeaf::get_accParent(IDispatch** ppdispParent) { + if (!ppdispParent) { + return E_INVALIDARG; + } + + RefPtr<IDispatch> parent(mParent); + parent.forget(ppdispParent); + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accChildCount(long* pcountChildren) { + if (!pcountChildren) { + return E_INVALIDARG; + } + + *pcountChildren = 0; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accChild(VARIANT varChild, IDispatch** ppdispChild) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accName(VARIANT varChild, BSTR* pszName) { + if (varChild.lVal != CHILDID_SELF || !pszName) { + return E_INVALIDARG; + } + + *pszName = CopyBSTR(mData.mText); + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accValue(VARIANT varChild, BSTR* pszValue) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accDescription(VARIANT varChild, BSTR* pszDescription) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accRole(VARIANT varChild, VARIANT* pvarRole) { + if (varChild.lVal != CHILDID_SELF || !pvarRole) { + return E_INVALIDARG; + } + + pvarRole->vt = VT_I4; + pvarRole->lVal = mData.mTextRole; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accState(VARIANT varChild, VARIANT* pvarState) { + if (varChild.lVal != CHILDID_SELF || !pvarState) { + return E_INVALIDARG; + } + + pvarState->vt = VT_I4; + pvarState->lVal = mData.mTextState; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accHelp(VARIANT varChild, BSTR* pszHelp) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accFocus(VARIANT* pvarChild) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_accSelection(VARIANT* pvarChildren) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::accSelect(long flagsSelect, VARIANT varChild) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) { + if (varChild.lVal != CHILDID_SELF || !pxLeft || !pyTop || !pcxWidth || + !pcyHeight) { + return E_INVALIDARG; + } + + *pxLeft = mData.mTextLeft; + *pyTop = mData.mTextTop; + *pcxWidth = mData.mTextWidth; + *pcyHeight = mData.mTextHeight; + return S_OK; +} + +HRESULT +HandlerTextLeaf::accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::accHitTest(long xLeft, long yTop, VARIANT* pvarChild) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::accDoDefaultAction(VARIANT varChild) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::put_accName(VARIANT varChild, BSTR szName) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::put_accValue(VARIANT varChild, BSTR szValue) { + return E_NOTIMPL; +} + +/*** IAccessible2 ***/ + +HRESULT +HandlerTextLeaf::get_nRelations(long* nRelations) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_relation(long relationIndex, + IAccessibleRelation** relation) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_relations(long maxRelations, + IAccessibleRelation** relations, + long* nRelations) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::role(long* role) { + if (!role) { + return E_INVALIDARG; + } + + *role = mData.mTextRole; + return S_OK; +} + +HRESULT +HandlerTextLeaf::scrollTo(IA2ScrollType scrollType) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::scrollToPoint(IA2CoordinateType coordinateType, long x, + long y) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_groupPosition(long* groupLevel, long* similarItemsInGroup, + long* positionInGroup) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_states(AccessibleStates* states) { + if (!states) { + return E_INVALIDARG; + } + + *states = 0; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_extendedRole(BSTR* extendedRole) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_localizedExtendedRole(BSTR* localizedExtendedRole) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_nExtendedStates(long* nExtendedStates) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_extendedStates(long maxExtendedStates, + BSTR** extendedStates, + long* nExtendedStates) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_localizedExtendedStates(long maxLocalizedExtendedStates, + BSTR** localizedExtendedStates, + long* nLocalizedExtendedStates) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_uniqueID(long* uniqueID) { + if (!uniqueID) { + return E_INVALIDARG; + } + + *uniqueID = mData.mTextId; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_windowHandle(HWND* windowHandle) { + if (!windowHandle) { + return E_INVALIDARG; + } + + *windowHandle = mHwnd; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_indexInParent(long* indexInParent) { + if (!indexInParent) { + return E_INVALIDARG; + } + + *indexInParent = mIndexInParent; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_locale(IA2Locale* locale) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_attributes(BSTR* attributes) { return E_NOTIMPL; } + +/*** IServiceProvider ***/ + +HRESULT +HandlerTextLeaf::QueryService(REFGUID aServiceId, REFIID aIid, + void** aOutInterface) { + if (aIid == IID_IAccessible2) { + RefPtr<IAccessible2> ia2(this); + ia2.forget(aOutInterface); + return S_OK; + } + + return E_INVALIDARG; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/handler/HandlerTextLeaf.h b/accessible/ipc/win/handler/HandlerTextLeaf.h new file mode 100644 index 0000000000..f1a31b9219 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerTextLeaf.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#ifndef mozilla_a11y_HandlerTextLeaf_h +# define mozilla_a11y_HandlerTextLeaf_h + +# include "AccessibleHandler.h" +# include "IUnknownImpl.h" +# include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +class HandlerTextLeaf final : public IAccessible2, public IServiceProvider { + public: + explicit HandlerTextLeaf(IDispatch* aParent, long aIndexInParent, HWND aHwnd, + AccChildData& aData); + + DECL_IUNKNOWN + + // IDispatch + STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) override; + STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, + ITypeInfo** ppTInfo) override; + STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) override; + STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS* pDispParams, VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, UINT* puArgErr) override; + + // IAccessible + STDMETHODIMP get_accParent(IDispatch** ppdispParent) override; + STDMETHODIMP get_accChildCount(long* pcountChildren) override; + STDMETHODIMP get_accChild(VARIANT varChild, IDispatch** ppdispChild) override; + STDMETHODIMP get_accName(VARIANT varChild, BSTR* pszName) override; + STDMETHODIMP get_accValue(VARIANT varChild, BSTR* pszValue) override; + STDMETHODIMP get_accDescription(VARIANT varChild, + BSTR* pszDescription) override; + STDMETHODIMP get_accRole(VARIANT varChild, VARIANT* pvarRole) override; + STDMETHODIMP get_accState(VARIANT varChild, VARIANT* pvarState) override; + STDMETHODIMP get_accHelp(VARIANT varChild, BSTR* pszHelp) override; + STDMETHODIMP get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) override; + STDMETHODIMP get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) override; + STDMETHODIMP get_accFocus(VARIANT* pvarChild) override; + STDMETHODIMP get_accSelection(VARIANT* pvarChildren) override; + STDMETHODIMP get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) override; + STDMETHODIMP accSelect(long flagsSelect, VARIANT varChild) override; + STDMETHODIMP accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) override; + STDMETHODIMP accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) override; + STDMETHODIMP accHitTest(long xLeft, long yTop, VARIANT* pvarChild) override; + STDMETHODIMP accDoDefaultAction(VARIANT varChild) override; + STDMETHODIMP put_accName(VARIANT varChild, BSTR szName) override; + STDMETHODIMP put_accValue(VARIANT varChild, BSTR szValue) override; + + // IAccessible2 + STDMETHODIMP get_nRelations(long* nRelations) override; + STDMETHODIMP get_relation(long relationIndex, + IAccessibleRelation** relation) override; + STDMETHODIMP get_relations(long maxRelations, IAccessibleRelation** relations, + long* nRelations) override; + STDMETHODIMP role(long* role) override; + STDMETHODIMP scrollTo(IA2ScrollType scrollType) override; + STDMETHODIMP scrollToPoint(IA2CoordinateType coordinateType, long x, + long y) override; + STDMETHODIMP get_groupPosition(long* groupLevel, long* similarItemsInGroup, + long* positionInGroup) override; + STDMETHODIMP get_states(AccessibleStates* states) override; + STDMETHODIMP get_extendedRole(BSTR* extendedRole) override; + STDMETHODIMP get_localizedExtendedRole(BSTR* localizedExtendedRole) override; + STDMETHODIMP get_nExtendedStates(long* nExtendedStates) override; + STDMETHODIMP get_extendedStates(long maxExtendedStates, BSTR** extendedStates, + long* nExtendedStates) override; + STDMETHODIMP get_localizedExtendedStates( + long maxLocalizedExtendedStates, BSTR** localizedExtendedStates, + long* nLocalizedExtendedStates) override; + STDMETHODIMP get_uniqueID(long* uniqueID) override; + STDMETHODIMP get_windowHandle(HWND* windowHandle) override; + STDMETHODIMP get_indexInParent(long* indexInParent) override; + STDMETHODIMP get_locale(IA2Locale* locale) override; + STDMETHODIMP get_attributes(BSTR* attributes) override; + + // IServiceProvider + STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aIid, + void** aOutInterface) override; + + private: + ~HandlerTextLeaf(); + + RefPtr<IDispatch> mParent; + long mIndexInParent; + HWND mHwnd; + AccChildData mData; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerTextLeaf_h diff --git a/accessible/ipc/win/handler/moz.build b/accessible/ipc/win/handler/moz.build new file mode 100644 index 0000000000..1242bb6b8c --- /dev/null +++ b/accessible/ipc/win/handler/moz.build @@ -0,0 +1,132 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SharedLibrary("AccessibleHandler") + +EXPORTS.mozilla.a11y += [ + "AccessibleHandler.h", + "HandlerDataCleanup.h", +] + +LOCAL_INCLUDES += [ + "/accessible/interfaces/ia2", + "/ipc/mscom/oop", +] + +# We want to generate distinct UUIDs on a per-channel basis, so we need +# finer granularity than the standard preprocessor definitions offer. +# This generated include allow us to separate local builds from automated +# builds, separate beta from release, and use different UUIDs in downstream +# projects such as Thunderbird. +CONFIGURE_SUBST_FILES += ["HandlerDataUUID.h"] +flags = [] + +GeneratedFile( + "HandlerData.h", + "HandlerData_p.c", + "HandlerData_i.c", + "HandlerData_c.c", + "HandlerData_dlldata.c", + "HandlerData.tlb", + inputs=["HandlerData.idl"], + script="/build/midl.py", + entry_point="midl", + flags=flags + + [ + "-I", + TOPOBJDIR, + "-I", + TOPOBJDIR + "/dist/include", + "-I", + TOPSRCDIR + "/other-licenses/ia2", + "-I", + SRCDIR, + "-I", + OBJDIR, + "-acf", + SRCDIR + "/HandlerData.acf", + "-dlldata", + OBJDIR + "/HandlerData_dlldata.c", + ], +) + +SOURCES += [ + "!HandlerData_c.c", + "!HandlerData_dlldata.c", + "!HandlerData_i.c", + "!HandlerData_p.c", + "AccessibleHandler.cpp", + "AccessibleHandlerControl.cpp", + "HandlerChildEnumerator.cpp", + "HandlerRelation.cpp", + "HandlerTextLeaf.cpp", +] + +EXPORTS += [ + "!HandlerData.h", + "!HandlerData_i.c", + "!HandlerDataUUID.h", +] + +# Give some symbols a unique name in each translation unit, to avoid +# collisions caused by https://llvm.org/pr41817. +if CONFIG["CC_TYPE"] == "clang-cl": + SOURCES["!HandlerData_p.c"].flags += [ + "-DHandlerData__MIDL_ProcFormatString=HandlerData__MIDL_ProcFormatString__HandlerData_p" + ] + SOURCES["!HandlerData_p.c"].flags += [ + "-DHandlerData__MIDL_TypeFormatString=HandlerData__MIDL_TypeFormatString__HandlerData_p" + ] + for p in ("dlldata", "c", "i", "p"): + SOURCES["!HandlerData_%s.c" % p].flags += [ + "-DUserMarshalRoutines=UserMarshalRoutines__HandlerData_%s" % p + ] + +DEFFILE = "AccessibleHandler.def" + +USE_LIBS += [ + "mscom_oop", +] + +OS_LIBS += [ + "advapi32", + "uuid", + "rpcrt4", + "oleacc", + "user32", +] + +RCINCLUDE = "AccessibleHandler.rc" + +# Suppress warnings from the MIDL generated code. +if CONFIG["CC_TYPE"] == "clang-cl": + CFLAGS += [ + "-Wno-extern-initializer", + "-Wno-incompatible-pointer-types", + "-Wno-missing-braces", + "-Wno-unused-const-variable", + ] + +# Since we are defining our own COM entry points (DllRegisterServer et al), +# but we still want to be able to delegate some work to the generated code, +# we add the prefix "Proxy" to all of the generated counterparts. +DEFINES["ENTRY_PREFIX"] = "Proxy" +DEFINES["REGISTER_PROXY_DLL"] = True +LIBRARY_DEFINES["MOZ_MSCOM_REMARSHAL_NO_HANDLER"] = True + +# This DLL may be loaded into other processes, so we need static libs for +# Windows 7 and Windows 8. +USE_STATIC_LIBS = True + +LIBRARY_DEFINES["UNICODE"] = True +LIBRARY_DEFINES["_UNICODE"] = True +LIBRARY_DEFINES["MOZ_NO_MOZALLOC"] = True +DisableStlWrapping() + +DELAYLOAD_DLLS += [ + "advapi32.dll", + "ktmw32.dll", +] diff --git a/accessible/ipc/win/moz.build b/accessible/ipc/win/moz.build new file mode 100644 index 0000000000..597f358f0b --- /dev/null +++ b/accessible/ipc/win/moz.build @@ -0,0 +1,63 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG["COMPILE_ENVIRONMENT"] and CONFIG["ACCESSIBILITY"]: + DIRS += [ + "handler", + "typelib", + ] + +if CONFIG["ACCESSIBILITY"]: + IPDL_SOURCES += ["PDocAccessible.ipdl"] + + EXPORTS.mozilla.a11y += [ + "COMPtrTypes.h", + "DocAccessibleChild.h", + "HandlerProvider.h", + "PlatformChild.h", + "RemoteAccessible.h", + ] + + SOURCES += [ + "!./handler/HandlerData_c.c", + "COMPtrTypes.cpp", + "DocAccessibleChild.cpp", + "HandlerProvider.cpp", + "PlatformChild.cpp", + "RemoteAccessible.cpp", + ] + + # Give some symbols a unique name in each translation unit, to avoid + # collisions caused by https://llvm.org/pr41817. + if CONFIG["CC_TYPE"] == "clang-cl": + SOURCES["!./handler/HandlerData_c.c"].flags += [ + "-DUserMarshalRoutines=UserMarshalRoutines__HandlerData_c" + ] + + LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/windows/ia2", + "/accessible/windows/msaa", + "/accessible/xpcom", + ] + + # Suppress warnings from the MIDL generated code. + if CONFIG["CC_TYPE"] == "clang-cl": + CFLAGS += [ + "-Wno-extern-initializer", + "-Wno-incompatible-pointer-types", + "-Wno-missing-braces", + "-Wno-unused-const-variable", + ] + + CXXFLAGS += [ + "-Wno-missing-braces", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/accessible/ipc/win/typelib/Accessible.idl b/accessible/ipc/win/typelib/Accessible.idl new file mode 100644 index 0000000000..10d310a316 --- /dev/null +++ b/accessible/ipc/win/typelib/Accessible.idl @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +import "oaidl.idl"; +import "servprov.idl"; + +[uuid(b4d37cda-0dac-45e6-b613-158a5eb94293)] +library Accessible +{ + interface IEnumVARIANT; + interface IServiceProvider; +}; diff --git a/accessible/ipc/win/typelib/moz.build b/accessible/ipc/win/typelib/moz.build new file mode 100644 index 0000000000..2992deda1a --- /dev/null +++ b/accessible/ipc/win/typelib/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +FINAL_TARGET_FILES += [ + "!Accessible.tlb", +] + +GeneratedFile( + "Accessible.tlb", + inputs=["Accessible.idl"], + script="/build/midl.py", + entry_point="midl", +) |