From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- accessible/generic/DocAccessible.h | 825 +++++++++++++++++++++++++++++++++++++ 1 file changed, 825 insertions(+) create mode 100644 accessible/generic/DocAccessible.h (limited to 'accessible/generic/DocAccessible.h') diff --git a/accessible/generic/DocAccessible.h b/accessible/generic/DocAccessible.h new file mode 100644 index 0000000000..52cbdd68cf --- /dev/null +++ b/accessible/generic/DocAccessible.h @@ -0,0 +1,825 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_DocAccessible_h__ +#define mozilla_a11y_DocAccessible_h__ + +#include "HyperTextAccessible.h" +#include "AccEvent.h" + +#include "nsClassHashtable.h" +#include "nsTHashMap.h" +#include "mozilla/UniquePtr.h" +#include "nsIDocumentObserver.h" +#include "nsITimer.h" +#include "nsTHashSet.h" +#include "nsWeakReference.h" + +const uint32_t kDefaultCacheLength = 128; + +namespace mozilla { + +class EditorBase; +class PresShell; + +namespace dom { +class Document; +} + +namespace a11y { + +class DocManager; +class NotificationController; +class DocAccessibleChild; +class RelatedAccIterator; +template +class TNotification; + +/** + * An accessibility tree node that originated in a content process and + * represents a document. Tabs, in-process iframes, and out-of-process iframes + * all use this class to represent the doc they contain. + */ +class DocAccessible : public HyperTextAccessible, + public nsIDocumentObserver, + public nsSupportsWeakReference { + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocAccessible, LocalAccessible) + + protected: + typedef mozilla::dom::Document Document; + + public: + DocAccessible(Document* aDocument, PresShell* aPresShell); + + // nsIDocumentObserver + NS_DECL_NSIDOCUMENTOBSERVER + + // LocalAccessible + virtual void Init(); + virtual void Shutdown() override; + virtual nsIFrame* GetFrame() const override; + virtual nsINode* GetNode() const override; + Document* DocumentNode() const { return mDocumentNode; } + + virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) const override; + virtual void Description(nsString& aDescription) const override; + virtual Accessible* FocusedChild() override; + virtual mozilla::a11y::role NativeRole() const override; + virtual uint64_t NativeState() const override; + virtual uint64_t NativeInteractiveState() const override; + virtual bool NativelyUnavailable() const override; + virtual void ApplyARIAState(uint64_t* aState) const override; + + virtual void TakeFocus() const override; + +#ifdef A11Y_LOG + virtual nsresult HandleAccEvent(AccEvent* aEvent) override; +#endif + + virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const override; + + // ActionAccessible + virtual bool HasPrimaryAction() const override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + + // HyperTextAccessible + virtual already_AddRefed GetEditor() const override; + + // DocAccessible + + /** + * Return document URL. + */ + void URL(nsAString& aURL) const; + + /** + * Return DOM document title. + */ + void Title(nsString& aTitle) const; + + /** + * Return DOM document mime type. + */ + void MimeType(nsAString& aType) const; + /** + * Return DOM document type. + */ + void DocType(nsAString& aType) const; + + /** + * Adds an entry to queued cache updates indicating aAcc requires + * a cache update on domain aNewDomain. If we've already queued an update + * for aAcc, aNewDomain is or'd with the existing domain(s) + * and the map is updated. Otherwise, the entry is simply inserted. + * This function also schedules processing on the controller. + * Note that this CANNOT be used for anything which fires events, since events + * must be fired after their associated cache update. + */ + void QueueCacheUpdate(LocalAccessible* aAcc, uint64_t aNewDomain); + + /** + * Walks the mDependentIDsHashes list for the given accessible and + * queues a CacheDomain::Relations cache update fore each related acc. + * We call this when we observe an ID mutation or when an acc is bound + * to its document. + */ + void QueueCacheUpdateForDependentRelations(LocalAccessible* aAcc); + + /** + * Returns true if the instance has shutdown. + */ + bool HasShutdown() const { return !mPresShell; } + + /** + * Return presentation shell for this document accessible. + */ + PresShell* PresShellPtr() const { + MOZ_DIAGNOSTIC_ASSERT(!HasShutdown()); + return mPresShell; + } + + /** + * Return the presentation shell's context. + */ + nsPresContext* PresContext() const; + + /** + * Return true if associated DOM document was loaded and isn't unloading. + */ + bool IsContentLoaded() const; + + bool IsHidden() const; + + void SetViewportCacheDirty(bool aDirty) { mViewportCacheDirty = aDirty; } + + /** + * Document load states. + */ + enum LoadState { + // initial tree construction is pending + eTreeConstructionPending = 0, + // initial tree construction done + eTreeConstructed = 1, + // DOM document is loaded. + eDOMLoaded = 1 << 1, + // document is ready + eReady = eTreeConstructed | eDOMLoaded, + // document and all its subdocuments are ready + eCompletelyLoaded = eReady | 1 << 2 + }; + + /** + * Return true if the document has given document state. + */ + bool HasLoadState(LoadState aState) const { + return (mLoadState & static_cast(aState)) == + static_cast(aState); + } + + /** + * Return a native window handler or pointer depending on platform. + */ + virtual void* GetNativeWindow() const; + + /** + * Return the parent document. + */ + DocAccessible* ParentDocument() const { + return mParent ? mParent->Document() : nullptr; + } + + /** + * Return the child document count. + */ + uint32_t ChildDocumentCount() const { return mChildDocuments.Length(); } + + /** + * Return the child document at the given index. + */ + DocAccessible* GetChildDocumentAt(uint32_t aIndex) const { + return mChildDocuments.SafeElementAt(aIndex, nullptr); + } + + /** + * Fire accessible event asynchronously. + */ + void FireDelayedEvent(AccEvent* aEvent); + void FireDelayedEvent(uint32_t aEventType, LocalAccessible* aTarget); + void FireEventsOnInsertion(LocalAccessible* aContainer); + + /** + * Fire value change event on the given accessible if applicable. + */ + void MaybeNotifyOfValueChange(LocalAccessible* aAccessible); + + /** + * Get/set the anchor jump. + */ + LocalAccessible* AnchorJump() { + return GetAccessibleOrContainer(mAnchorJumpElm); + } + + void SetAnchorJump(nsIContent* aTargetNode) { mAnchorJumpElm = aTargetNode; } + + /** + * Bind the child document to the tree. + */ + void BindChildDocument(DocAccessible* aDocument); + + /** + * Process the generic notification. + * + * @note The caller must guarantee that the given instance still exists when + * notification is processed. + * @see NotificationController::HandleNotification + */ + template + void HandleNotification( + Class* aInstance, + typename TNotification::Callback aMethod, Args*... aArgs); + + /** + * Return the cached accessible by the given DOM node if it's in subtree of + * this document accessible or the document accessible itself, otherwise null. + * + * @return the accessible object + */ + LocalAccessible* GetAccessible(nsINode* aNode) const; + + /** + * Return an accessible for the given node even if the node is not in + * document's node map cache (like HTML area element). + * + * XXX: it should be really merged with GetAccessible(). + */ + LocalAccessible* GetAccessibleEvenIfNotInMap(nsINode* aNode) const; + LocalAccessible* GetAccessibleEvenIfNotInMapOrContainer(nsINode* aNode) const; + + /** + * Return whether the given DOM node has an accessible or not. + */ + bool HasAccessible(nsINode* aNode) const { return GetAccessible(aNode); } + + /** + * Return the cached accessible by the given unique ID within this document. + * + * @note the unique ID matches with the uniqueID() of Accessible + * + * @param aUniqueID [in] the unique ID used to cache the node. + */ + LocalAccessible* GetAccessibleByUniqueID(void* aUniqueID) { + return UniqueID() == aUniqueID ? this : mAccessibleCache.GetWeak(aUniqueID); + } + + /** + * Return the cached accessible by the given unique ID looking through + * this and nested documents. + */ + LocalAccessible* GetAccessibleByUniqueIDInSubtree(void* aUniqueID); + + /** + * Return an accessible for the given DOM node or container accessible if + * the node is not accessible. If aNoContainerIfPruned is true it will return + * null if the node is in a pruned subtree (eg. aria-hidden or unselected deck + * panel) + */ + LocalAccessible* GetAccessibleOrContainer( + nsINode* aNode, bool aNoContainerIfPruned = false) const; + + /** + * Return a container accessible for the given DOM node. + */ + LocalAccessible* GetContainerAccessible(nsINode* aNode) const; + + /** + * Return an accessible for the given node if any, or an immediate accessible + * container for it. + */ + LocalAccessible* AccessibleOrTrueContainer( + nsINode* aNode, bool aNoContainerIfPruned = false) const; + + /** + * Return an accessible for the given node or its first accessible descendant. + */ + LocalAccessible* GetAccessibleOrDescendant(nsINode* aNode) const; + + /** + * Returns aria-owns seized child at the given index. + */ + LocalAccessible* ARIAOwnedAt(LocalAccessible* aParent, + uint32_t aIndex) const { + nsTArray>* children = mARIAOwnsHash.Get(aParent); + if (children) { + return children->SafeElementAt(aIndex); + } + return nullptr; + } + uint32_t ARIAOwnedCount(LocalAccessible* aParent) const { + nsTArray>* children = mARIAOwnsHash.Get(aParent); + return children ? children->Length() : 0; + } + + /** + * Return true if the given ID is referred by relation attribute. + */ + bool IsDependentID(dom::Element* aElement, const nsAString& aID) const { + return GetRelProviders(aElement, aID); + } + + /** + * Initialize the newly created accessible and put it into document caches. + * + * @param aAccessible [in] created accessible + * @param aRoleMapEntry [in] the role map entry role the ARIA role or + * nullptr if none + */ + void BindToDocument(LocalAccessible* aAccessible, + const nsRoleMapEntry* aRoleMapEntry); + + /** + * Remove from document and shutdown the given accessible. + */ + void UnbindFromDocument(LocalAccessible* aAccessible); + + /** + * Notify the document accessible that content was inserted. + */ + void ContentInserted(nsIContent* aStartChildNode, nsIContent* aEndChildNode); + + /** + * @see nsAccessibilityService::ScheduleAccessibilitySubtreeUpdate + */ + void ScheduleTreeUpdate(nsIContent* aContent); + + /** + * Update the tree on content removal. + */ + void ContentRemoved(LocalAccessible* aAccessible); + void ContentRemoved(nsIContent* aContentNode); + + /** + * Updates accessible tree when rendered text is changed. + */ + void UpdateText(nsIContent* aTextNode); + + /** + * Recreate an accessible, results in hide/show events pair. + */ + void RecreateAccessible(nsIContent* aContent); + + /** + * Schedule ARIA owned element relocation if needed. Return true if relocation + * was scheduled. + */ + bool RelocateARIAOwnedIfNeeded(nsIContent* aEl); + + /** + * Return a notification controller associated with the document. + */ + NotificationController* Controller() const { return mNotificationController; } + + /** + * If this document is in a content process return the object responsible for + * communicating with the main process for it. + */ + DocAccessibleChild* IPCDoc() const { return mIPCDoc; } + + /** + * Notify the document that a DOM node has been scrolled. document will + * dispatch throttled accessibility events for scrolling, and a scroll-end + * event. This function also queues a cache update for ScrollPosition. + */ + void HandleScroll(nsINode* aTarget); + + /** + * Retrieves the scroll frame (if it exists) for the given accessible + * and returns its scroll position and scroll range. If the given + * accessible is `this`, return the scroll position and range of + * the root scroll frame. Return values have been scaled by the + * PresShell's resolution. + */ + std::pair ComputeScrollData(LocalAccessible* aAcc); + + /** + * Only works in content process documents. + */ + bool IsAccessibleBeingMoved(LocalAccessible* aAcc) { + return mMovedAccessibles.Contains(aAcc); + } + + protected: + virtual ~DocAccessible(); + + void LastRelease(); + + // DocAccessible + virtual nsresult AddEventListeners(); + virtual nsresult RemoveEventListeners(); + + /** + * Marks this document as loaded or loading. + */ + void NotifyOfLoad(uint32_t aLoadEventType); + void NotifyOfLoading(bool aIsReloading); + + friend class DocManager; + + /** + * Perform initial update (create accessible tree). + * Can be overridden by wrappers to prepare initialization work. + */ + virtual void DoInitialUpdate(); + + /** + * Updates root element and picks up ARIA role on it if any. + */ + void UpdateRootElIfNeeded(); + + /** + * Process document load notification, fire document load and state busy + * events if applicable. + */ + void ProcessLoad(); + + /** + * Append the given document accessible to this document's child document + * accessibles. + */ + bool AppendChildDocument(DocAccessible* aChildDocument) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + mChildDocuments.AppendElement(aChildDocument); + return true; + } + + /** + * Remove the given document accessible from this document's child document + * accessibles. + */ + void RemoveChildDocument(DocAccessible* aChildDocument) { + mChildDocuments.RemoveElement(aChildDocument); + } + + /** + * Add dependent IDs pointed by accessible element by relation attribute to + * cache. If the relation attribute is missed then all relation attributes + * are checked. + * + * @param aRelProvider [in] accessible that element has relation attribute + * @param aRelAttr [in, optional] relation attribute + */ + void AddDependentIDsFor(LocalAccessible* aRelProvider, + nsAtom* aRelAttr = nullptr); + + /** + * Remove dependent IDs pointed by accessible element by relation attribute + * from cache. If the relation attribute is absent then all relation + * attributes are checked. + * + * @param aRelProvider [in] accessible that element has relation attribute + * @param aRelAttr [in, optional] relation attribute + */ + void RemoveDependentIDsFor(LocalAccessible* aRelProvider, + nsAtom* aRelAttr = nullptr); + + /** + * Update or recreate an accessible depending on a changed attribute. + * + * @param aElement [in] the element the attribute was changed on + * @param aAttribute [in] the changed attribute + * @return true if an action was taken on the attribute change + */ + bool UpdateAccessibleOnAttrChange(mozilla::dom::Element* aElement, + nsAtom* aAttribute); + + /** + * Process ARIA active-descendant attribute change. + */ + void ARIAActiveDescendantChanged(LocalAccessible* aAccessible); + + /** + * Update the accessible tree for inserted content. + */ + void ProcessContentInserted( + LocalAccessible* aContainer, + const nsTArray>* aInsertedContent); + void ProcessContentInserted(LocalAccessible* aContainer, + nsIContent* aInsertedContent); + + /** + * Used to notify the document to make it process the invalidation list. + * + * While children are cached we may encounter the case there's no accessible + * for referred content by related accessible. Store these related nodes to + * invalidate their containers later. + */ + void ProcessInvalidationList(); + + /** + * Process mPendingUpdates + */ + void ProcessPendingUpdates(); + + /** + * Called from NotificationController to process this doc's + * queued cache updates. For each acc in the map, this function + * sends a cache update with its corresponding CacheDomain. + */ + void ProcessQueuedCacheUpdates(); + + /** + * Called from NotificationController before mutation events are processed to + * notify the parent process which Accessibles are being moved (if any). + */ + void SendAccessiblesWillMove(); + + /** + * Called from NotificationController after all mutation events have been + * processed to clear our data about mutations during this tick. + */ + void ClearMutationData() { + mMovedAccessibles.Clear(); + mInsertedAccessibles.Clear(); + mRemovedNodes.Clear(); + } + + /** + * Steals or puts back accessible subtrees. + */ + void DoARIAOwnsRelocation(LocalAccessible* aOwner); + + /** + * Moves children back under their original parents. + */ + void PutChildrenBack(nsTArray>* aChildren, + uint32_t aStartIdx); + + bool MoveChild(LocalAccessible* aChild, LocalAccessible* aNewParent, + int32_t aIdxInParent); + + /** + * Create accessible tree. + * + * @param aRoot [in] a root of subtree to create + * @param aFocusedAcc [in, optional] a focused accessible under created + * subtree if any + */ + void CacheChildrenInSubtree(LocalAccessible* aRoot, + LocalAccessible** aFocusedAcc = nullptr); + void CreateSubtree(LocalAccessible* aRoot); + + /** + * Remove accessibles in subtree from node to accessible map. + */ + void UncacheChildrenInSubtree(LocalAccessible* aRoot); + + /** + * Shutdown any cached accessible in the subtree. + * + * @param aAccessible [in] the root of the subrtee to invalidate accessible + * child/parent refs in + */ + void ShutdownChildrenInSubtree(LocalAccessible* aAccessible); + + /** + * Return true if the document is a target of document loading events + * (for example, state busy change or document reload events). + * + * Rules: The root chrome document accessible is never an event target + * (for example, Firefox UI window). If the sub document is loaded within its + * parent document then the parent document is a target only (aka events + * coalescence). + */ + bool IsLoadEventTarget() const; + + /* + * Set the object responsible for communicating with the main process on + * behalf of this document. + */ + void SetIPCDoc(DocAccessibleChild* aIPCDoc); + + friend class DocAccessibleChild; + + /** + * Used to fire scrolling end event after page scroll. + * + * @param aTimer [in] the timer object + * @param aClosure [in] the document accessible where scrolling happens + */ + static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure); + + void DispatchScrollingEvent(nsINode* aTarget, uint32_t aEventType); + + /** + * Check if an id attribute change affects aria-activedescendant and handle + * the aria-activedescendant change if appropriate. + * If the currently focused element has aria-activedescendant and an + * element's id changes to match this, the id was probably moved from the + * previous active descendant, thus making this element the new active + * descendant. In that case, accessible focus must be changed accordingly. + */ + void ARIAActiveDescendantIDMaybeMoved(LocalAccessible* aAccessible); + + /** + * Traverse content subtree and for each node do one of 3 things: + * 1. Check if content node has an accessible that should be removed and + * remove it. + * 2. Check if content node has an accessible that needs to be recreated. + * Remove it and schedule it for reinsertion. + * 3. Check if content node has no accessible but needs one. Schedule one for + * insertion. + * + * Returns true if the root node should be reinserted. + */ + bool PruneOrInsertSubtree(nsIContent* aRoot); + + protected: + /** + * State and property flags, kept by mDocFlags. + */ + enum { + // Whether the document is a top level content document in this process. + eTopLevelContentDocInProcess = 1 << 0 + }; + + /** + * Cache of accessibles within this document accessible. + */ + AccessibleHashtable mAccessibleCache; + nsTHashMap, LocalAccessible*> + mNodeToAccessibleMap; + + Document* mDocumentNode; + nsCOMPtr mScrollWatchTimer; + nsTHashMap, TimeStamp> mLastScrollingDispatch; + + /** + * Bit mask of document load states (@see LoadState). + */ + uint32_t mLoadState : 3; + + /** + * Bit mask of other states and props. + */ + uint32_t mDocFlags : 27; + + /** + * Tracks whether we have seen changes to this document's content that + * indicate we should re-send the viewport cache we use for hittesting. + * This value is set in `BundleFieldsForCache` and processed in + * `ProcessQueuedCacheUpdates`. + */ + bool mViewportCacheDirty : 1; + + /** + * Type of document load event fired after the document is loaded completely. + */ + uint32_t mLoadEventType; + + /** + * Reference to anchor jump element. + */ + nsCOMPtr mAnchorJumpElm; + + /** + * A generic state (see items below) before the attribute value was changed. + * @see AttributeWillChange and AttributeChanged notifications. + */ + + // Previous state bits before attribute change + uint64_t mPrevStateBits; + + nsTArray> mChildDocuments; + + /** + * A storage class for pairing content with one of its relation attributes. + */ + class AttrRelProvider { + public: + AttrRelProvider(nsAtom* aRelAttr, nsIContent* aContent) + : mRelAttr(aRelAttr), mContent(aContent) {} + + nsAtom* mRelAttr; + nsCOMPtr mContent; + + private: + AttrRelProvider(); + AttrRelProvider(const AttrRelProvider&); + AttrRelProvider& operator=(const AttrRelProvider&); + }; + + typedef nsTArray> AttrRelProviders; + typedef nsClassHashtable + DependentIDsHashtable; + + /** + * Returns/creates/removes attribute relation providers associated with + * a DOM document if the element is in uncomposed document or associated + * with shadow DOM the element is in. + */ + AttrRelProviders* GetRelProviders(dom::Element* aElement, + const nsAString& aID) const; + AttrRelProviders* GetOrCreateRelProviders(dom::Element* aElement, + const nsAString& aID); + void RemoveRelProvidersIfEmpty(dom::Element* aElement, const nsAString& aID); + + /** + * The cache of IDs pointed by relation attributes. + */ + nsClassHashtable, + DependentIDsHashtable> + mDependentIDsHashes; + + friend class RelatedAccIterator; + + /** + * Used for our caching algorithm. We store the list of nodes that should be + * invalidated. + * + * @see ProcessInvalidationList + */ + nsTArray> mInvalidationList; + + /** + * Holds a list of aria-owns relocations. + */ + nsClassHashtable, + nsTArray>> + mARIAOwnsHash; + + /** + * Keeps a list of pending subtrees to update post-refresh. + */ + nsTArray> mPendingUpdates; + + /** + * Used to process notification from core and accessible events. + */ + RefPtr mNotificationController; + friend class EventTree; + friend class NotificationController; + + private: + void SetRoleMapEntryForDoc(dom::Element* aElement); + + /** + * This must be called whenever an Accessible is moved in a content process. + * It keeps track of Accessibles moved during this tick. + */ + void TrackMovedAccessible(LocalAccessible* aAcc); + + /** + * For hidden subtrees, fire a name/description change event if the subtree + * is a target of aria-labelledby/describedby. + * This does nothing if it is called on a node which is not part of a hidden + * aria-labelledby/describedby target. + */ + void MaybeHandleChangeToHiddenNameOrDescription(nsIContent* aChild); + + void MaybeFireEventsForChangedPopover(LocalAccessible* aAcc); + + PresShell* mPresShell; + + // Exclusively owned by IPDL so don't manually delete it! + // Cleared in ActorDestroy + DocAccessibleChild* mIPCDoc; + + // These data structures map between LocalAccessibles and CacheDomains, + // tracking cache updates that have been queued during the current tick but + // not yet sent. If there are a lot of nearby text cache updates (e.g. during + // a reflow), it is much more performant to process them in order because we + // then benefit from the layout line cursor. However, we still only want to + // process each LocalAccessible only once. Therefore, we use an array for + // ordering and a hash map to avoid duplicates, since Gecko has no ordered + // set data structure. The array contains pairs of LocalAccessible and cache + // domain. The hash map maps from LocalAccessible to the corresponding index + // in the array. These data structures must be kept in sync. It is possible + // for these to contain a reference to the document they live on. We clear + // them in Shutdown() to avoid cyclical references. + nsTArray, uint64_t>> + mQueuedCacheUpdatesArray; + nsTHashMap mQueuedCacheUpdatesHash; + + // A set of Accessibles moved during this tick. Only used in content + // processes. + nsTHashSet> mMovedAccessibles; + // A set of Accessibles inserted during this tick. Only used in content + // processes. This is needed to prevent insertions + moves of the same + // Accessible in the same tick from being tracked as moves. + nsTHashSet> mInsertedAccessibles; + // A set of DOM nodes removed during this tick. This avoids a lot of pointless + // recursive DOM traversals. + nsTHashSet mRemovedNodes; +}; + +inline DocAccessible* LocalAccessible::AsDoc() { + return IsDoc() ? static_cast(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif -- cgit v1.2.3