/* -*- 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 "nsIAccessiblePivot.h" #include "AccEvent.h" #include "HyperTextAccessibleWrap.h" #include "nsClassHashtable.h" #include "nsDataHashtable.h" #include "nsIDocument.h" #include "nsIDocumentObserver.h" #include "nsIEditor.h" #include "nsIObserver.h" #include "nsIScrollPositionListener.h" #include "nsITimer.h" #include "nsIWeakReference.h" class nsAccessiblePivot; const uint32_t kDefaultCacheLength = 128; namespace mozilla { namespace a11y { class DocManager; class NotificationController; class DocAccessibleChild; class RelatedAccIterator; template class TNotification; class DocAccessible : public HyperTextAccessibleWrap, public nsIDocumentObserver, public nsIObserver, public nsIScrollPositionListener, public nsSupportsWeakReference, public nsIAccessiblePivotObserver { NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocAccessible, Accessible) NS_DECL_NSIOBSERVER NS_DECL_NSIACCESSIBLEPIVOTOBSERVER public: DocAccessible(nsIDocument* aDocument, nsIContent* aRootContent, nsIPresShell* aPresShell); // nsIScrollPositionListener virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) override {} virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) override; // nsIDocumentObserver NS_DECL_NSIDOCUMENTOBSERVER // Accessible virtual void Init(); virtual void Shutdown() override; virtual nsIFrame* GetFrame() const override; virtual nsINode* GetNode() const override { return mDocumentNode; } nsIDocument* DocumentNode() const { return mDocumentNode; } virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override; virtual void Description(nsString& aDescription) override; virtual Accessible* FocusedChild() override; virtual mozilla::a11y::role NativeRole() override; virtual uint64_t NativeState() override; virtual uint64_t NativeInteractiveState() const override; virtual bool NativelyUnavailable() const override; virtual void ApplyARIAState(uint64_t* aState) const override; virtual already_AddRefed Attributes() override; virtual void TakeFocus() override; #ifdef A11Y_LOG virtual nsresult HandleAccEvent(AccEvent* aEvent) override; #endif virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const 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 { mDocumentNode->GetTitle(aTitle); } /** * Return DOM document mime type. */ void MimeType(nsAString& aType) const { mDocumentNode->GetContentType(aType); } /** * Return DOM document type. */ void DocType(nsAString& aType) const; /** * Return virtual cursor associated with the document. */ nsIAccessiblePivot* VirtualCursor(); /** * Return presentation shell for this document accessible. */ nsIPresShell* PresShell() const { return mPresShell; } /** * Return the presentation shell's context. */ nsPresContext* PresContext() const { return mPresShell->GetPresContext(); } /** * Return true if associated DOM document was loaded and isn't unloading. */ bool IsContentLoaded() const { // eDOMLoaded flag check is used for error pages as workaround to make this // method return correct result since error pages do not receive 'pageshow' // event and as consequence nsIDocument::IsShowing() returns false. return mDocumentNode && mDocumentNode->IsVisible() && (mDocumentNode->IsShowing() || HasLoadState(eDOMLoaded)); } /** * 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, Accessible* aTarget); /** * Fire value change event on the given accessible if applicable. */ void MaybeNotifyOfValueChange(Accessible* aAccessible); /** * Get/set the anchor jump. */ Accessible* 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, Arg* aArg); /** * 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 */ Accessible* 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(). */ Accessible* GetAccessibleEvenIfNotInMap(nsINode* aNode) const; Accessible* 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. */ Accessible* 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. */ Accessible* GetAccessibleByUniqueIDInSubtree(void* aUniqueID); /** * Return an accessible for the given DOM node or container accessible if * the node is not accessible. */ Accessible* GetAccessibleOrContainer(nsINode* aNode) const; /** * Return a container accessible for the given DOM node. */ Accessible* GetContainerAccessible(nsINode* aNode) const { return aNode ? GetAccessibleOrContainer(aNode->GetParentNode()) : nullptr; } /** * Return an accessible for the given node or its first accessible descendant. */ Accessible* GetAccessibleOrDescendant(nsINode* aNode) const; /** * Return true if the given ID is referred by relation attribute. * * @note Different elements may share the same ID if they are hosted inside * XBL bindings. Be careful the result of this method may be senseless * while it's called for XUL elements (where XBL is used widely). */ bool IsDependentID(const nsAString& aID) const { return mDependentIDsHash.Get(aID, nullptr); } /** * 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(Accessible* aAccessible, nsRoleMapEntry* aRoleMapEntry); /** * Remove from document and shutdown the given accessible. */ void UnbindFromDocument(Accessible* aAccessible); /** * Notify the document accessible that content was inserted. */ void ContentInserted(nsIContent* aContainerNode, nsIContent* aStartChildNode, nsIContent* aEndChildNode); /** * Notify the document accessible that content was removed. */ void ContentRemoved(Accessible* aContainer, nsIContent* aChildNode) { // Update the whole tree of this document accessible when the container is // null (document element is removed). UpdateTreeOnRemoval((aContainer ? aContainer : this), aChildNode); } void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode) { ContentRemoved(GetAccessibleOrContainer(aContainerNode), aChildNode); } /** * 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); protected: virtual ~DocAccessible(); void LastRelease(); // Accessible virtual void CacheChildren() override; // 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(); /** * Process document load notification, fire document load and state busy * events if applicable. */ void ProcessLoad(); /** * Add/remove scroll listeners, @see nsIScrollPositionListener interface. */ void AddScrollListener(); void RemoveScrollListener(); /** * Append the given document accessible to this document's child document * accessibles. */ bool AppendChildDocument(DocAccessible* aChildDocument) { return mChildDocuments.AppendElement(aChildDocument); } /** * 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(dom::Element* aRelProviderElm, nsIAtom* 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(dom::Element* aRelProviderElm, nsIAtom* 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, nsIAtom* aAttribute); /** * Fire accessible events when attribute is changed. * * @param aAccessible [in] accessible the DOM attribute is changed for * @param aNameSpaceID [in] namespace of changed attribute * @param aAttribute [in] changed attribute */ void AttributeChangedImpl(Accessible* aAccessible, int32_t aNameSpaceID, nsIAtom* aAttribute); /** * Fire accessible events when ARIA attribute is changed. * * @param aAccessible [in] accesislbe the DOM attribute is changed for * @param aAttribute [in] changed attribute */ void ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute); /** * Process ARIA active-descendant attribute change. */ void ARIAActiveDescendantChanged(Accessible* aAccessible); /** * Update the accessible tree for inserted content. */ void ProcessContentInserted(Accessible* aContainer, const nsTArray >* 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(); /** * Update the tree on content insertion. */ void UpdateTreeOnInsertion(Accessible* aContainer); /** * Update the accessible tree for content removal. */ void UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode); /** * Helper for UpdateTreeOn methods. Go down to DOM subtree and updates * accessible tree. Return one of these flags. */ enum EUpdateTreeFlags { eNoAccessible = 0, eAccessible = 1, eAlertAccessible = 2 }; uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert, AccReorderEvent* aReorderEvent); /** * 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(Accessible* aRoot, Accessible** aFocusedAcc = nullptr); /** * Remove accessibles in subtree from node to accessible map. */ void UncacheChildrenInSubtree(Accessible* 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(Accessible* 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; /** * 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; } /* * Set the object responsible for communicating with the main process on * behalf of this document. */ void SetIPCDoc(DocAccessibleChild* aIPCDoc) { mIPCDoc = 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); protected: /** * State and property flags, kept by mDocFlags. */ enum { // Whether scroll listeners were added. eScrollInitialized = 1 << 0, // Whether the document is a tab document. eTabDocument = 1 << 1 }; /** * Cache of accessibles within this document accessible. */ AccessibleHashtable mAccessibleCache; nsDataHashtable, Accessible*> mNodeToAccessibleMap; nsIDocument* mDocumentNode; nsCOMPtr mScrollWatchTimer; uint16_t mScrollPositionChangedTicks; // Used for tracking scroll events /** * Bit mask of document load states (@see LoadState). */ uint32_t mLoadState : 3; /** * Bit mask of other states and props. */ uint32_t mDocFlags : 28; /** * 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. */ union { // ARIA attribute value nsIAtom* mARIAAttrOldValue; // True if the accessible state bit was on bool mStateBitWasOn; }; nsTArray > mChildDocuments; /** * The virtual cursor of the document. */ nsRefPtr mVirtualCursor; /** * A storage class for pairing content with one of its relation attributes. */ class AttrRelProvider { public: AttrRelProvider(nsIAtom* aRelAttr, nsIContent* aContent) : mRelAttr(aRelAttr), mContent(aContent) { } nsIAtom* mRelAttr; nsCOMPtr mContent; private: AttrRelProvider(); AttrRelProvider(const AttrRelProvider&); AttrRelProvider& operator =(const AttrRelProvider&); }; typedef nsTArray > AttrRelProviderArray; typedef nsClassHashtable DependentIDsHashtable; /** * The cache of IDs pointed by relation attributes. */ DependentIDsHashtable mDependentIDsHash; static PLDHashOperator CycleCollectorTraverseDepIDsEntry(const nsAString& aKey, AttrRelProviderArray* aProviders, void* aUserArg); friend class RelatedAccIterator; /** * Used for our caching algorithm. We store the list of nodes that should be * invalidated. * * @see ProcessInvalidationList */ nsTArray mInvalidationList; /** * Used to process notification from core and accessible events. */ nsRefPtr mNotificationController; friend class EventQueue; friend class NotificationController; private: nsIPresShell* mPresShell; // Exclusively owned by IPDL so don't manually delete it! DocAccessibleChild* mIPCDoc; }; inline DocAccessible* Accessible::AsDoc() { return IsDoc() ? static_cast(this) : nullptr; } } // namespace a11y } // namespace mozilla #endif