/* -*- 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 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(this)); DetachDocument(); } ipc::IPCResult DocAccessibleChild::RecvParentCOMProxy( const IDispatchHolder& aParentCOMProxy) { MOZ_ASSERT(!aParentCOMProxy.IsNull()); mParentProxy.reset(const_cast(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(aEmulatedWindowHandle); if (!aEmulatedWindowCOMProxy.IsNull()) { MOZ_ASSERT(!mEmulatedWindowProxy); mEmulatedWindowProxy.reset( const_cast(aEmulatedWindowCOMProxy).Release()); } return IPC_OK(); } ipc::IPCResult DocAccessibleChild::RecvTopLevelDocCOMProxy( const IAccessibleHolder& aCOMProxy) { MOZ_ASSERT(!aCOMProxy.IsNull()); mTopLevelDocProxy.reset(const_cast(aCOMProxy).Release()); return IPC_OK(); } HWND DocAccessibleChild::GetNativeWindowHandle() const { if (mEmulatedWindowHandle) { return mEmulatedWindowHandle; } auto browser = static_cast(Manager()); MOZ_ASSERT(browser); // Iframe documents use the same window handle as their top level document. auto topDoc = static_cast( browser->GetTopLevelDocAccessibleChild()); MOZ_ASSERT(topDoc); if (topDoc != this && topDoc->mEmulatedWindowHandle) { return topDoc->mEmulatedWindowHandle; } return reinterpret_cast(browser->GetNativeWindowHandle()); } void DocAccessibleChild::PushDeferredEvent(UniquePtr aEvent) { DocAccessibleChild* topLevelIPCDoc = nullptr; if (mDoc && mDoc->IsRoot()) { topLevelIPCDoc = this; } else { auto browserChild = static_cast(Manager()); if (!browserChild) { return; } topLevelIPCDoc = static_cast( 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(this, aID, aType)); return false; } void DocAccessibleChild::MaybeSendShowEvent(ShowEventData& aData, bool aFromUser) { if (IsConstructedInParentProcess()) { Unused << SendShowEvent(aData, aFromUser); return; } PushDeferredEvent(MakeUnique(this, aData, aFromUser)); } bool DocAccessibleChild::SendHideEvent(const uint64_t& aRootID, const bool& aFromUser) { if (IsConstructedInParentProcess()) { return PDocAccessibleChild::SendHideEvent(aRootID, aFromUser); } PushDeferredEvent(MakeUnique(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(this, aID, aState, aEnabled)); return true; } LayoutDeviceIntRect DocAccessibleChild::GetCaretRectFor(const uint64_t& aID) { LocalAccessible* target; if (aID) { target = reinterpret_cast(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(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( 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( 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(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(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( 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(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( 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(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(aBridge, doc, aID)); } } // namespace a11y } // namespace mozilla