1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifndef mozilla_a11y_DocAccessible_h__
7 #define mozilla_a11y_DocAccessible_h__
9 #include "HyperTextAccessible.h"
12 #include "nsClassHashtable.h"
13 #include "nsTHashMap.h"
14 #include "mozilla/UniquePtr.h"
15 #include "nsIDocumentObserver.h"
17 #include "nsTHashSet.h"
18 #include "nsWeakReference.h"
20 const uint32_t kDefaultCacheLength
= 128;
34 class NotificationController
;
35 class DocAccessibleChild
;
36 class RelatedAccIterator
;
37 template <class Class
, class... Args
>
41 * An accessibility tree node that originated in a content process and
42 * represents a document. Tabs, in-process iframes, and out-of-process iframes
43 * all use this class to represent the doc they contain.
45 class DocAccessible
: public HyperTextAccessible
,
46 public nsIDocumentObserver
,
47 public nsSupportsWeakReference
{
48 NS_DECL_ISUPPORTS_INHERITED
49 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocAccessible
, LocalAccessible
)
52 typedef mozilla::dom::Document Document
;
55 DocAccessible(Document
* aDocument
, PresShell
* aPresShell
);
57 // nsIDocumentObserver
58 NS_DECL_NSIDOCUMENTOBSERVER
62 virtual void Shutdown() override
;
63 virtual nsIFrame
* GetFrame() const override
;
64 virtual nsINode
* GetNode() const override
;
65 Document
* DocumentNode() const { return mDocumentNode
; }
67 virtual mozilla::a11y::ENameValueFlag
Name(nsString
& aName
) const override
;
68 virtual void Description(nsString
& aDescription
) const override
;
69 virtual Accessible
* FocusedChild() override
;
70 virtual mozilla::a11y::role
NativeRole() const override
;
71 virtual uint64_t NativeState() const override
;
72 virtual uint64_t NativeInteractiveState() const override
;
73 virtual bool NativelyUnavailable() const override
;
74 virtual void ApplyARIAState(uint64_t* aState
) const override
;
76 virtual void TakeFocus() const override
;
79 virtual nsresult
HandleAccEvent(AccEvent
* aEvent
) override
;
82 virtual nsRect
RelativeBounds(nsIFrame
** aRelativeFrame
) const override
;
85 virtual bool HasPrimaryAction() const override
;
86 virtual void ActionNameAt(uint8_t aIndex
, nsAString
& aName
) override
;
88 // HyperTextAccessible
89 virtual already_AddRefed
<EditorBase
> GetEditor() const override
;
94 * Return document URL.
96 void URL(nsAString
& aURL
) const;
99 * Return DOM document title.
101 void Title(nsString
& aTitle
) const;
104 * Return DOM document mime type.
106 void MimeType(nsAString
& aType
) const;
108 * Return DOM document type.
110 void DocType(nsAString
& aType
) const;
113 * Adds an entry to mQueuedCacheUpdates indicating aAcc requires
114 * a cache update on domain aNewDomain. If we've already queued an update
115 * for aAcc, aNewDomain is or'd with the existing domain(s)
116 * and the map is updated. Otherwise, the entry is simply inserted.
117 * This function also schedules processing on the controller.
118 * Note that this CANNOT be used for anything which fires events, since events
119 * must be fired after their associated cache update.
121 void QueueCacheUpdate(LocalAccessible
* aAcc
, uint64_t aNewDomain
);
124 * Walks the mDependentIDsHashes list for the given accessible and
125 * queues a CacheDomain::Relations cache update fore each related acc.
126 * We call this when we observe an ID mutation or when an acc is bound
129 void QueueCacheUpdateForDependentRelations(LocalAccessible
* aAcc
);
132 * Returns true if the instance has shutdown.
134 bool HasShutdown() const { return !mPresShell
; }
137 * Return presentation shell for this document accessible.
139 PresShell
* PresShellPtr() const {
140 MOZ_DIAGNOSTIC_ASSERT(!HasShutdown());
145 * Return the presentation shell's context.
147 nsPresContext
* PresContext() const;
150 * Return true if associated DOM document was loaded and isn't unloading.
152 bool IsContentLoaded() const;
154 bool IsHidden() const;
156 void SetViewportCacheDirty(bool aDirty
) { mViewportCacheDirty
= aDirty
; }
159 * Document load states.
162 // initial tree construction is pending
163 eTreeConstructionPending
= 0,
164 // initial tree construction done
165 eTreeConstructed
= 1,
166 // DOM document is loaded.
169 eReady
= eTreeConstructed
| eDOMLoaded
,
170 // document and all its subdocuments are ready
171 eCompletelyLoaded
= eReady
| 1 << 2
175 * Return true if the document has given document state.
177 bool HasLoadState(LoadState aState
) const {
178 return (mLoadState
& static_cast<uint32_t>(aState
)) ==
179 static_cast<uint32_t>(aState
);
183 * Return a native window handler or pointer depending on platform.
185 virtual void* GetNativeWindow() const;
188 * Return the parent document.
190 DocAccessible
* ParentDocument() const {
191 return mParent
? mParent
->Document() : nullptr;
195 * Return the child document count.
197 uint32_t ChildDocumentCount() const { return mChildDocuments
.Length(); }
200 * Return the child document at the given index.
202 DocAccessible
* GetChildDocumentAt(uint32_t aIndex
) const {
203 return mChildDocuments
.SafeElementAt(aIndex
, nullptr);
207 * Fire accessible event asynchronously.
209 void FireDelayedEvent(AccEvent
* aEvent
);
210 void FireDelayedEvent(uint32_t aEventType
, LocalAccessible
* aTarget
);
211 void FireEventsOnInsertion(LocalAccessible
* aContainer
);
214 * Fire value change event on the given accessible if applicable.
216 void MaybeNotifyOfValueChange(LocalAccessible
* aAccessible
);
219 * Get/set the anchor jump.
221 LocalAccessible
* AnchorJump() {
222 return GetAccessibleOrContainer(mAnchorJumpElm
);
225 void SetAnchorJump(nsIContent
* aTargetNode
) { mAnchorJumpElm
= aTargetNode
; }
228 * Bind the child document to the tree.
230 void BindChildDocument(DocAccessible
* aDocument
);
233 * Process the generic notification.
235 * @note The caller must guarantee that the given instance still exists when
236 * notification is processed.
237 * @see NotificationController::HandleNotification
239 template <class Class
, class... Args
>
240 void HandleNotification(
242 typename TNotification
<Class
, Args
...>::Callback aMethod
, Args
*... aArgs
);
245 * Return the cached accessible by the given DOM node if it's in subtree of
246 * this document accessible or the document accessible itself, otherwise null.
248 * @return the accessible object
250 LocalAccessible
* GetAccessible(nsINode
* aNode
) const;
253 * Return an accessible for the given node even if the node is not in
254 * document's node map cache (like HTML area element).
256 * XXX: it should be really merged with GetAccessible().
258 LocalAccessible
* GetAccessibleEvenIfNotInMap(nsINode
* aNode
) const;
259 LocalAccessible
* GetAccessibleEvenIfNotInMapOrContainer(nsINode
* aNode
) const;
262 * Return whether the given DOM node has an accessible or not.
264 bool HasAccessible(nsINode
* aNode
) const { return GetAccessible(aNode
); }
267 * Return the cached accessible by the given unique ID within this document.
269 * @note the unique ID matches with the uniqueID() of Accessible
271 * @param aUniqueID [in] the unique ID used to cache the node.
273 LocalAccessible
* GetAccessibleByUniqueID(void* aUniqueID
) {
274 return UniqueID() == aUniqueID
? this : mAccessibleCache
.GetWeak(aUniqueID
);
278 * Return the cached accessible by the given unique ID looking through
279 * this and nested documents.
281 LocalAccessible
* GetAccessibleByUniqueIDInSubtree(void* aUniqueID
);
284 * Return an accessible for the given DOM node or container accessible if
285 * the node is not accessible. If aNoContainerIfPruned is true it will return
286 * null if the node is in a pruned subtree (eg. aria-hidden or unselected deck
289 LocalAccessible
* GetAccessibleOrContainer(
290 nsINode
* aNode
, bool aNoContainerIfPruned
= false) const;
293 * Return a container accessible for the given DOM node.
295 LocalAccessible
* GetContainerAccessible(nsINode
* aNode
) const;
298 * Return an accessible for the given node if any, or an immediate accessible
301 LocalAccessible
* AccessibleOrTrueContainer(
302 nsINode
* aNode
, bool aNoContainerIfPruned
= false) const;
305 * Return an accessible for the given node or its first accessible descendant.
307 LocalAccessible
* GetAccessibleOrDescendant(nsINode
* aNode
) const;
310 * Returns aria-owns seized child at the given index.
312 LocalAccessible
* ARIAOwnedAt(LocalAccessible
* aParent
,
313 uint32_t aIndex
) const {
314 nsTArray
<RefPtr
<LocalAccessible
>>* children
= mARIAOwnsHash
.Get(aParent
);
316 return children
->SafeElementAt(aIndex
);
320 uint32_t ARIAOwnedCount(LocalAccessible
* aParent
) const {
321 nsTArray
<RefPtr
<LocalAccessible
>>* children
= mARIAOwnsHash
.Get(aParent
);
322 return children
? children
->Length() : 0;
326 * Return true if the given ID is referred by relation attribute.
328 bool IsDependentID(dom::Element
* aElement
, const nsAString
& aID
) const {
329 return GetRelProviders(aElement
, aID
);
333 * Initialize the newly created accessible and put it into document caches.
335 * @param aAccessible [in] created accessible
336 * @param aRoleMapEntry [in] the role map entry role the ARIA role or
339 void BindToDocument(LocalAccessible
* aAccessible
,
340 const nsRoleMapEntry
* aRoleMapEntry
);
343 * Remove from document and shutdown the given accessible.
345 void UnbindFromDocument(LocalAccessible
* aAccessible
);
348 * Notify the document accessible that content was inserted.
350 void ContentInserted(nsIContent
* aStartChildNode
, nsIContent
* aEndChildNode
);
353 * @see nsAccessibilityService::ScheduleAccessibilitySubtreeUpdate
355 void ScheduleTreeUpdate(nsIContent
* aContent
);
358 * Update the tree on content removal.
360 void ContentRemoved(LocalAccessible
* aAccessible
);
361 void ContentRemoved(nsIContent
* aContentNode
);
364 * Updates accessible tree when rendered text is changed.
366 void UpdateText(nsIContent
* aTextNode
);
369 * Recreate an accessible, results in hide/show events pair.
371 void RecreateAccessible(nsIContent
* aContent
);
374 * Schedule ARIA owned element relocation if needed. Return true if relocation
377 bool RelocateARIAOwnedIfNeeded(nsIContent
* aEl
);
380 * Return a notification controller associated with the document.
382 NotificationController
* Controller() const { return mNotificationController
; }
385 * If this document is in a content process return the object responsible for
386 * communicating with the main process for it.
388 DocAccessibleChild
* IPCDoc() const { return mIPCDoc
; }
391 * Notify the document that a DOM node has been scrolled. document will
392 * dispatch throttled accessibility events for scrolling, and a scroll-end
393 * event. This function also queues a cache update for ScrollPosition.
395 void HandleScroll(nsINode
* aTarget
);
398 * Retrieves the scroll frame (if it exists) for the given accessible
399 * and returns its scroll position and scroll range. If the given
400 * accessible is `this`, return the scroll position and range of
401 * the root scroll frame. Return values have been scaled by the
402 * PresShell's resolution.
404 std::pair
<nsPoint
, nsRect
> ComputeScrollData(LocalAccessible
* aAcc
);
407 * Only works in content process documents.
409 bool IsAccessibleBeingMoved(LocalAccessible
* aAcc
) {
410 return mMovedAccessibles
.Contains(aAcc
);
414 virtual ~DocAccessible();
419 virtual nsresult
AddEventListeners();
420 virtual nsresult
RemoveEventListeners();
423 * Marks this document as loaded or loading.
425 void NotifyOfLoad(uint32_t aLoadEventType
);
426 void NotifyOfLoading(bool aIsReloading
);
428 friend class DocManager
;
431 * Perform initial update (create accessible tree).
432 * Can be overridden by wrappers to prepare initialization work.
434 virtual void DoInitialUpdate();
437 * Updates root element and picks up ARIA role on it if any.
439 void UpdateRootElIfNeeded();
442 * Process document load notification, fire document load and state busy
443 * events if applicable.
448 * Append the given document accessible to this document's child document
451 bool AppendChildDocument(DocAccessible
* aChildDocument
) {
452 // XXX(Bug 1631371) Check if this should use a fallible operation as it
453 // pretended earlier, or change the return type to void.
454 mChildDocuments
.AppendElement(aChildDocument
);
459 * Remove the given document accessible from this document's child document
462 void RemoveChildDocument(DocAccessible
* aChildDocument
) {
463 mChildDocuments
.RemoveElement(aChildDocument
);
467 * Add dependent IDs pointed by accessible element by relation attribute to
468 * cache. If the relation attribute is missed then all relation attributes
471 * @param aRelProvider [in] accessible that element has relation attribute
472 * @param aRelAttr [in, optional] relation attribute
474 void AddDependentIDsFor(LocalAccessible
* aRelProvider
,
475 nsAtom
* aRelAttr
= nullptr);
478 * Remove dependent IDs pointed by accessible element by relation attribute
479 * from cache. If the relation attribute is absent then all relation
480 * attributes are checked.
482 * @param aRelProvider [in] accessible that element has relation attribute
483 * @param aRelAttr [in, optional] relation attribute
485 void RemoveDependentIDsFor(LocalAccessible
* aRelProvider
,
486 nsAtom
* aRelAttr
= nullptr);
489 * Update or recreate an accessible depending on a changed attribute.
491 * @param aElement [in] the element the attribute was changed on
492 * @param aAttribute [in] the changed attribute
493 * @return true if an action was taken on the attribute change
495 bool UpdateAccessibleOnAttrChange(mozilla::dom::Element
* aElement
,
499 * Process ARIA active-descendant attribute change.
501 void ARIAActiveDescendantChanged(LocalAccessible
* aAccessible
);
504 * Update the accessible tree for inserted content.
506 void ProcessContentInserted(
507 LocalAccessible
* aContainer
,
508 const nsTArray
<nsCOMPtr
<nsIContent
>>* aInsertedContent
);
509 void ProcessContentInserted(LocalAccessible
* aContainer
,
510 nsIContent
* aInsertedContent
);
513 * Used to notify the document to make it process the invalidation list.
515 * While children are cached we may encounter the case there's no accessible
516 * for referred content by related accessible. Store these related nodes to
517 * invalidate their containers later.
519 void ProcessInvalidationList();
522 * Process mPendingUpdates
524 void ProcessPendingUpdates();
527 * Called from NotificationController to process this doc's
528 * mQueuedCacheUpdates list. For each acc in the map, this function
529 * sends a cache update with its corresponding CacheDomain.
531 void ProcessQueuedCacheUpdates();
534 * Called from NotificationController before mutation events are processed to
535 * notify the parent process which Accessibles are being moved (if any).
537 void SendAccessiblesWillMove();
540 * Called from NotificationController after all mutation events have been
541 * processed to clear our data about Accessibles that were moved during this
544 void ClearMovedAccessibles() {
545 mMovedAccessibles
.Clear();
546 mInsertedAccessibles
.Clear();
550 * Steals or puts back accessible subtrees.
552 void DoARIAOwnsRelocation(LocalAccessible
* aOwner
);
555 * Moves children back under their original parents.
557 void PutChildrenBack(nsTArray
<RefPtr
<LocalAccessible
>>* aChildren
,
560 bool MoveChild(LocalAccessible
* aChild
, LocalAccessible
* aNewParent
,
561 int32_t aIdxInParent
);
564 * Create accessible tree.
566 * @param aRoot [in] a root of subtree to create
567 * @param aFocusedAcc [in, optional] a focused accessible under created
570 void CacheChildrenInSubtree(LocalAccessible
* aRoot
,
571 LocalAccessible
** aFocusedAcc
= nullptr);
572 void CreateSubtree(LocalAccessible
* aRoot
);
575 * Remove accessibles in subtree from node to accessible map.
577 void UncacheChildrenInSubtree(LocalAccessible
* aRoot
);
580 * Shutdown any cached accessible in the subtree.
582 * @param aAccessible [in] the root of the subrtee to invalidate accessible
583 * child/parent refs in
585 void ShutdownChildrenInSubtree(LocalAccessible
* aAccessible
);
588 * Return true if the document is a target of document loading events
589 * (for example, state busy change or document reload events).
591 * Rules: The root chrome document accessible is never an event target
592 * (for example, Firefox UI window). If the sub document is loaded within its
593 * parent document then the parent document is a target only (aka events
596 bool IsLoadEventTarget() const;
599 * Set the object responsible for communicating with the main process on
600 * behalf of this document.
602 void SetIPCDoc(DocAccessibleChild
* aIPCDoc
);
604 friend class DocAccessibleChild
;
607 * Used to fire scrolling end event after page scroll.
609 * @param aTimer [in] the timer object
610 * @param aClosure [in] the document accessible where scrolling happens
612 static void ScrollTimerCallback(nsITimer
* aTimer
, void* aClosure
);
614 void DispatchScrollingEvent(nsINode
* aTarget
, uint32_t aEventType
);
617 * Check if an id attribute change affects aria-activedescendant and handle
618 * the aria-activedescendant change if appropriate.
619 * If the currently focused element has aria-activedescendant and an
620 * element's id changes to match this, the id was probably moved from the
621 * previous active descendant, thus making this element the new active
622 * descendant. In that case, accessible focus must be changed accordingly.
624 void ARIAActiveDescendantIDMaybeMoved(LocalAccessible
* aAccessible
);
627 * Traverse content subtree and for each node do one of 3 things:
628 * 1. Check if content node has an accessible that should be removed and
630 * 2. Check if content node has an accessible that needs to be recreated.
631 * Remove it and schedule it for reinsertion.
632 * 3. Check if content node has no accessible but needs one. Schedule one for
635 * Returns true if the root node should be reinserted.
637 bool PruneOrInsertSubtree(nsIContent
* aRoot
);
641 * State and property flags, kept by mDocFlags.
644 // Whether the document is a top level content document in this process.
645 eTopLevelContentDocInProcess
= 1 << 0
649 * Cache of accessibles within this document accessible.
651 AccessibleHashtable mAccessibleCache
;
652 nsTHashMap
<nsPtrHashKey
<const nsINode
>, LocalAccessible
*>
653 mNodeToAccessibleMap
;
655 Document
* mDocumentNode
;
656 nsCOMPtr
<nsITimer
> mScrollWatchTimer
;
657 nsTHashMap
<nsPtrHashKey
<nsINode
>, TimeStamp
> mLastScrollingDispatch
;
660 * Bit mask of document load states (@see LoadState).
662 uint32_t mLoadState
: 3;
665 * Bit mask of other states and props.
667 uint32_t mDocFlags
: 27;
670 * Tracks whether we have seen changes to this document's content that
671 * indicate we should re-send the viewport cache we use for hittesting.
672 * This value is set in `BundleFieldsForCache` and processed in
673 * `ProcessQueuedCacheUpdates`.
675 bool mViewportCacheDirty
: 1;
678 * Type of document load event fired after the document is loaded completely.
680 uint32_t mLoadEventType
;
683 * Reference to anchor jump element.
685 nsCOMPtr
<nsIContent
> mAnchorJumpElm
;
688 * A generic state (see items below) before the attribute value was changed.
689 * @see AttributeWillChange and AttributeChanged notifications.
692 // Previous state bits before attribute change
693 uint64_t mPrevStateBits
;
695 nsTArray
<RefPtr
<DocAccessible
>> mChildDocuments
;
698 * A storage class for pairing content with one of its relation attributes.
700 class AttrRelProvider
{
702 AttrRelProvider(nsAtom
* aRelAttr
, nsIContent
* aContent
)
703 : mRelAttr(aRelAttr
), mContent(aContent
) {}
706 nsCOMPtr
<nsIContent
> mContent
;
710 AttrRelProvider(const AttrRelProvider
&);
711 AttrRelProvider
& operator=(const AttrRelProvider
&);
714 typedef nsTArray
<mozilla::UniquePtr
<AttrRelProvider
>> AttrRelProviders
;
715 typedef nsClassHashtable
<nsStringHashKey
, AttrRelProviders
>
716 DependentIDsHashtable
;
719 * Returns/creates/removes attribute relation providers associated with
720 * a DOM document if the element is in uncomposed document or associated
721 * with shadow DOM the element is in.
723 AttrRelProviders
* GetRelProviders(dom::Element
* aElement
,
724 const nsAString
& aID
) const;
725 AttrRelProviders
* GetOrCreateRelProviders(dom::Element
* aElement
,
726 const nsAString
& aID
);
727 void RemoveRelProvidersIfEmpty(dom::Element
* aElement
, const nsAString
& aID
);
730 * The cache of IDs pointed by relation attributes.
732 nsClassHashtable
<nsPtrHashKey
<dom::DocumentOrShadowRoot
>,
733 DependentIDsHashtable
>
736 friend class RelatedAccIterator
;
739 * Used for our caching algorithm. We store the list of nodes that should be
742 * @see ProcessInvalidationList
744 nsTArray
<RefPtr
<nsIContent
>> mInvalidationList
;
747 * Holds a list of aria-owns relocations.
749 nsClassHashtable
<nsPtrHashKey
<LocalAccessible
>,
750 nsTArray
<RefPtr
<LocalAccessible
>>>
754 * Keeps a list of pending subtrees to update post-refresh.
756 nsTArray
<RefPtr
<nsIContent
>> mPendingUpdates
;
759 * Used to process notification from core and accessible events.
761 RefPtr
<NotificationController
> mNotificationController
;
762 friend class EventTree
;
763 friend class NotificationController
;
766 void SetRoleMapEntryForDoc(dom::Element
* aElement
);
769 * This must be called whenever an Accessible is moved in a content process.
770 * It keeps track of Accessibles moved during this tick.
772 void TrackMovedAccessible(LocalAccessible
* aAcc
);
775 * For hidden subtrees, fire a name/description change event if the subtree
776 * is a target of aria-labelledby/describedby.
777 * This does nothing if it is called on a node which is not part of a hidden
778 * aria-labelledby/describedby target.
780 void MaybeHandleChangeToHiddenNameOrDescription(nsIContent
* aChild
);
782 PresShell
* mPresShell
;
784 // Exclusively owned by IPDL so don't manually delete it!
785 DocAccessibleChild
* mIPCDoc
;
787 // A hash map between LocalAccessibles and CacheDomains, tracking
788 // cache updates that have been queued during the current tick
789 // but not yet sent. It is possible for this map to contain a reference
790 // to the document it lives on. We clear the list in Shutdown() to
791 // avoid cyclical references.
792 nsTHashMap
<RefPtr
<LocalAccessible
>, uint64_t> mQueuedCacheUpdates
;
794 // A set of Accessibles moved during this tick. Only used in content
796 nsTHashSet
<RefPtr
<LocalAccessible
>> mMovedAccessibles
;
797 // A set of Accessibles inserted during this tick. Only used in content
798 // processes. This is needed to prevent insertions + moves of the same
799 // Accessible in the same tick from being tracked as moves.
800 nsTHashSet
<RefPtr
<LocalAccessible
>> mInsertedAccessibles
;
803 inline DocAccessible
* LocalAccessible::AsDoc() {
804 return IsDoc() ? static_cast<DocAccessible
*>(this) : nullptr;
808 } // namespace mozilla