/* 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 #include "mozilla/a11y/PDocAccessibleParent.h" #include "mozilla/a11y/RemoteAccessible.h" #include "mozilla/dom/BrowserBridgeParent.h" #include "nsAccessibilityService.h" #include "nsClassHashtable.h" #include "nsHashKeys.h" #include "nsIMemoryReporter.h" #include "nsISupportsImpl.h" namespace mozilla { namespace dom { class BrowserParent; class CanonicalBrowsingContext; class WindowContext; class WindowGlobalParent; } // namespace dom namespace a11y { class TextRange; class xpcAccessibleGeneric; /* * 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: // AddRef/Release are inherited from RemoteAccessible rather than declared // here, so that this class has only one mRefCnt (RemoteAccessible's); see // NS_IMPL_ADDREF_INHERITED/NS_IMPL_RELEASE_INHERITED in the .cpp. NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIMEMORYREPORTER private: DocAccessibleParent(); public: static already_AddRefed 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; } /** * Set whether this document is a static clone created for printing. We don't * expose print documents or their descendant Accessibles to platform * accessibility APIs; they are used purely to generate a tagged PDF. */ void SetIsPrintDoc(bool aIsPrintDoc) { mIsPrintDoc = aIsPrintDoc; } bool IsPrintDoc() const { return mIsPrintDoc; } /** * 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); mShutdown = true; } /** * Return the BrowsingContext of the WindowGlobal which manages this * document, or null if this document has been shut down. */ dom::CanonicalBrowsingContext* GetBrowsingContext() const; /** * Return our manager as a WindowGlobalParent. This document's manager is * always a WindowGlobalParent since PDocAccessible is managed by * PWindowGlobal. */ dom::WindowGlobalParent* Manager() const; /** * Return the BrowserParent for the PBrowser connection hosting this * document's content process, regardless of how deeply this document is * nested via in-process iframes. */ dom::BrowserParent* GetBrowserParent() const; /* * Called when a message from a document in a child process notifies the main * process it is firing an event. */ mozilla::ipc::IPCResult RecvEvent(const uint64_t& aID, const uint32_t& aType); mozilla::ipc::IPCResult RecvStateChangeEvent(const uint64_t& aID, const uint64_t& aState, const bool& aEnabled); mozilla::ipc::IPCResult RecvCaretMoveEvent( const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect, const int32_t& aOffset, const bool& aIsSelectionCollapsed, const bool& aIsAtEndOfLine, const int32_t& aGranularity, const bool& aFromUser, const bool& aSuppressEvent); mozilla::ipc::IPCResult RecvMutationEvents( nsTArray&& aData); mozilla::ipc::IPCResult RecvRequestAckMutationEvents(); mozilla::ipc::IPCResult RecvFocusEvent(const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect); mozilla::ipc::IPCResult RecvSelectionEvent(const uint64_t& aID, const uint64_t& aWidgetID, const uint32_t& aType); 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); mozilla::ipc::IPCResult RecvCache( const mozilla::a11y::CacheUpdateType& aUpdateType, nsTArray&& aData); mozilla::ipc::IPCResult RecvSelectedAccessiblesChanged( nsTArray&& aSelectedIDs, nsTArray&& aUnselectedIDs); mozilla::ipc::IPCResult RecvAccessiblesWillMove(nsTArray&& aIDs); mozilla::ipc::IPCResult RecvAnnouncementEvent(const uint64_t& aID, const nsAString& aAnnouncement, const uint16_t& aPriority); mozilla::ipc::IPCResult RecvTextSelectionChangeEvent( const uint64_t& aID, nsTArray&& aSelection); mozilla::ipc::IPCResult RecvRoleChangedEvent( const uint8_t& aRoleMapEntryIndex); mozilla::ipc::IPCResult RecvBindChildDoc( NotNull aChildDoc, const uint64_t& aID); void Unbind() { if (RemoteAccessible* parent = RemoteParent()) { DocAccessibleParent* parentDoc = parent->Document(); parent->ClearChildDoc(this); DebugOnly result = parentDoc->mChildDocs.RemoveElement(mActorID); MOZ_ASSERT(result); } SetParent(nullptr); } mozilla::ipc::IPCResult RecvShutdown(); 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; /** * 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); } /** * Drop this document's reference to aAccessible. This does not necessarily * destroy it; e.g. it might still be referenced by another node's * mChildren. */ 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.get() : nullptr; } const RemoteAccessible* GetAccessible(uintptr_t aID) const { return const_cast(this)->GetAccessible(aID); } size_t ChildDocCount() const { return mChildDocs.Length(); } const DocAccessibleParent* ChildDocAt(size_t aIdx) const { return const_cast(this)->ChildDocAt(aIdx); } DocAccessibleParent* ChildDocAt(size_t aIdx) { return LiveDocs().Get(mChildDocs[aIdx]); } #if defined(XP_WIN) void MaybeInitWindowEmulation(); /** * Set emulated native window handle for a document. * @param aWindowHandle emulated native window handle */ void SetEmulatedWindowHandle(HWND aWindowHandle); HWND GetEmulatedWindowHandle() const { return mEmulatedWindowHandle; } #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(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 GetCaret() const { if (mCaretOffset == -1) { return {nullptr, -1}; } RemoteAccessible* acc = const_cast(this)->GetAccessible(mCaretId); if (!acc) { return {nullptr, -1}; } return {acc, mCaretOffset}; } bool IsCaretAtEndOfLine() const { return mIsCaretAtEndOfLine; } mozilla::LayoutDeviceIntRect GetCachedCaretRect(); virtual void SelectionRanges(nsTArray* aRanges) const override; virtual Accessible* FocusedChild() override; void URL(nsAString& aURL) const; void URL(nsACString& aURL) const; void MimeType(nsAString& 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, {pointerToRelationDataAddress, [targetAccID, ...]}} nsTHashMap>> 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 mOnScreenAccessibles; #ifdef MOZ_WIDGET_COCOA // Bounds (in screen-relative Gecko device pixels) of the focused accessible. // This is used to suppress redundant notifications on viewport cache updates. // This field is updated: // - When a focus event is fired, changing the focused accessible // - When a viewport cache update is recieved, and the computed bounds for // the focused accessible differ from the last-cached bounds in this field. // Nothing() indicates we have not yet computed bounds for the focused acc. Maybe mFocusedAccBounds; #endif static DocAccessibleParent* GetFrom(dom::WindowContext* aWindowContext, bool aAllowShutdown = false); size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) override; /** * Return the cache domain set that should be used for accessibles in this * document. */ uint64_t EffectiveCacheDomains() const; /** * Return true if every domain in aRequiredCacheDomains is active for * accessibles in this document. */ bool DomainsAreActive(uint64_t aRequiredCacheDomains) const { return (aRequiredCacheDomains & ~EffectiveCacheDomains()) == 0; } /** * If any domain in aRequiredCacheDomains is not currently active for this * document, request that it become active and return true. If all required * domains are already active, return false. The caller should treat true as * "can't proceed right now": the fields aren't in the cache yet. */ bool RequestDomainsIfInactive(uint64_t aRequiredCacheDomains); mozilla::ipc::IPCResult RecvPrinting(); enum class AllowConstruction { Disallow, Allow, AllowButIgnore, }; AllowConstruction ShouldAllowConstruction() const; private: ~DocAccessibleParent(); class ProxyEntry : public PLDHashEntryHdr { public: explicit ProxyEntry(const void*) : mProxy(nullptr) {} ProxyEntry(ProxyEntry&& aOther) : mProxy(std::move(aOther.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 }; // A strong reference. See the comment on RemoveAccessible for how this // interacts with the tree's own mChildren/mParent references. RefPtr mProxy; }; RemoteAccessible* CreateAcc(const AccessibleData& aAccData); bool AttachChild(RemoteAccessible* aParent, uint32_t aIndex, RemoteAccessible* aChild); [[nodiscard]] bool CheckDocTree() const; xpcAccessibleGeneric* GetXPCAccessible(RemoteAccessible* aProxy); /** * Fire an event to both OS and XPCOM consumers. */ 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); mozilla::ipc::IPCResult ProcessShowEvent(nsTArray&& aNewTree, const bool& aEventSuppressed, const bool& aComplete, const bool& aFromUser); mozilla::ipc::IPCResult ProcessHideEvent(const uint64_t& aRootID, const bool& aFromUser); mozilla::ipc::IPCResult ProcessTextChangeEvent( const uint64_t& aID, const nsAString& aStr, const int32_t& aStart, const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser); nsTArray mChildDocs; #if defined(XP_WIN) // The handle associated with the emulated window that contains this document HWND mEmulatedWindowHandle; #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 mAccessibles; uint64_t mPendingShowChild = 0; uint64_t mPendingShowParent = 0; uint32_t mPendingShowIndex = 0; nsTHashSet mMovingIDs; uint64_t mActorID; bool mTopLevel : 1; bool mTopLevelInContentProcess : 1; bool mShutdown : 1; bool mIsPrintDoc : 1 = false; bool mIsInitialTreeDone : 1 = false; nsTHashSet> mPendingOOPChildDocs; uint64_t mFocus; uint64_t mCaretId; int32_t mCaretOffset; bool mIsCaretAtEndOfLine; LayoutDeviceIntRect mCaretRect; nsTArray mTextSelections; static uint64_t sMaxDocID; static nsTHashMap& LiveDocs() { static nsTHashMap sLiveDocs; return sLiveDocs; } }; } // namespace a11y } // namespace mozilla #endif