michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef mozilla_a11y_DocAccessible_h__ michael@0: #define mozilla_a11y_DocAccessible_h__ michael@0: michael@0: #include "nsIAccessibleDocument.h" michael@0: #include "nsIAccessiblePivot.h" michael@0: michael@0: #include "AccEvent.h" michael@0: #include "HyperTextAccessibleWrap.h" michael@0: michael@0: #include "nsClassHashtable.h" michael@0: #include "nsDataHashtable.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDocumentObserver.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIScrollPositionListener.h" michael@0: #include "nsITimer.h" michael@0: #include "nsIWeakReference.h" michael@0: michael@0: class nsAccessiblePivot; michael@0: michael@0: class nsIScrollableView; michael@0: michael@0: const uint32_t kDefaultCacheSize = 256; michael@0: michael@0: namespace mozilla { michael@0: namespace a11y { michael@0: michael@0: class DocManager; michael@0: class NotificationController; michael@0: class RelatedAccIterator; michael@0: template michael@0: class TNotification; michael@0: michael@0: class DocAccessible : public HyperTextAccessibleWrap, michael@0: public nsIAccessibleDocument, michael@0: public nsIDocumentObserver, michael@0: public nsIObserver, michael@0: public nsIScrollPositionListener, michael@0: public nsSupportsWeakReference, michael@0: public nsIAccessiblePivotObserver michael@0: { michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocAccessible, Accessible) michael@0: michael@0: NS_DECL_NSIACCESSIBLEDOCUMENT michael@0: michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: NS_DECL_NSIACCESSIBLEPIVOTOBSERVER michael@0: michael@0: public: michael@0: michael@0: DocAccessible(nsIDocument* aDocument, nsIContent* aRootContent, michael@0: nsIPresShell* aPresShell); michael@0: virtual ~DocAccessible(); michael@0: michael@0: // nsIAccessible michael@0: NS_IMETHOD TakeFocus(void); michael@0: michael@0: // nsIScrollPositionListener michael@0: virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) {} michael@0: virtual void ScrollPositionDidChange(nscoord aX, nscoord aY); michael@0: michael@0: // nsIDocumentObserver michael@0: NS_DECL_NSIDOCUMENTOBSERVER michael@0: michael@0: // Accessible michael@0: virtual void Init(); michael@0: virtual void Shutdown(); michael@0: virtual nsIFrame* GetFrame() const; michael@0: virtual nsINode* GetNode() const { return mDocumentNode; } michael@0: nsIDocument* DocumentNode() const { return mDocumentNode; } michael@0: michael@0: virtual mozilla::a11y::ENameValueFlag Name(nsString& aName); michael@0: virtual void Description(nsString& aDescription); michael@0: virtual Accessible* FocusedChild(); michael@0: virtual mozilla::a11y::role NativeRole(); michael@0: virtual uint64_t NativeState(); michael@0: virtual uint64_t NativeInteractiveState() const; michael@0: virtual bool NativelyUnavailable() const; michael@0: virtual void ApplyARIAState(uint64_t* aState) const; michael@0: virtual already_AddRefed Attributes(); michael@0: michael@0: #ifdef A11Y_LOG michael@0: virtual nsresult HandleAccEvent(AccEvent* aEvent); michael@0: #endif michael@0: michael@0: virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame); michael@0: michael@0: // HyperTextAccessible michael@0: virtual already_AddRefed GetEditor() const; michael@0: michael@0: // DocAccessible michael@0: michael@0: /** michael@0: * Return presentation shell for this document accessible. michael@0: */ michael@0: nsIPresShell* PresShell() const { return mPresShell; } michael@0: michael@0: /** michael@0: * Return the presentation shell's context. michael@0: */ michael@0: nsPresContext* PresContext() const { return mPresShell->GetPresContext(); } michael@0: michael@0: /** michael@0: * Return true if associated DOM document was loaded and isn't unloading. michael@0: */ michael@0: bool IsContentLoaded() const michael@0: { michael@0: // eDOMLoaded flag check is used for error pages as workaround to make this michael@0: // method return correct result since error pages do not receive 'pageshow' michael@0: // event and as consequence nsIDocument::IsShowing() returns false. michael@0: return mDocumentNode && mDocumentNode->IsVisible() && michael@0: (mDocumentNode->IsShowing() || HasLoadState(eDOMLoaded)); michael@0: } michael@0: michael@0: /** michael@0: * Document load states. michael@0: */ michael@0: enum LoadState { michael@0: // initial tree construction is pending michael@0: eTreeConstructionPending = 0, michael@0: // initial tree construction done michael@0: eTreeConstructed = 1, michael@0: // DOM document is loaded. michael@0: eDOMLoaded = 1 << 1, michael@0: // document is ready michael@0: eReady = eTreeConstructed | eDOMLoaded, michael@0: // document and all its subdocuments are ready michael@0: eCompletelyLoaded = eReady | 1 << 2 michael@0: }; michael@0: michael@0: /** michael@0: * Return true if the document has given document state. michael@0: */ michael@0: bool HasLoadState(LoadState aState) const michael@0: { return (mLoadState & static_cast(aState)) == michael@0: static_cast(aState); } michael@0: michael@0: /** michael@0: * Return a native window handler or pointer depending on platform. michael@0: */ michael@0: virtual void* GetNativeWindow() const; michael@0: michael@0: /** michael@0: * Return the parent document. michael@0: */ michael@0: DocAccessible* ParentDocument() const michael@0: { return mParent ? mParent->Document() : nullptr; } michael@0: michael@0: /** michael@0: * Return the child document count. michael@0: */ michael@0: uint32_t ChildDocumentCount() const michael@0: { return mChildDocuments.Length(); } michael@0: michael@0: /** michael@0: * Return the child document at the given index. michael@0: */ michael@0: DocAccessible* GetChildDocumentAt(uint32_t aIndex) const michael@0: { return mChildDocuments.SafeElementAt(aIndex, nullptr); } michael@0: michael@0: /** michael@0: * Fire accessible event asynchronously. michael@0: */ michael@0: void FireDelayedEvent(AccEvent* aEvent); michael@0: void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget); michael@0: michael@0: /** michael@0: * Fire value change event on the given accessible if applicable. michael@0: */ michael@0: void MaybeNotifyOfValueChange(Accessible* aAccessible); michael@0: michael@0: /** michael@0: * Get/set the anchor jump. michael@0: */ michael@0: Accessible* AnchorJump() michael@0: { return GetAccessibleOrContainer(mAnchorJumpElm); } michael@0: michael@0: void SetAnchorJump(nsIContent* aTargetNode) michael@0: { mAnchorJumpElm = aTargetNode; } michael@0: michael@0: /** michael@0: * Bind the child document to the tree. michael@0: */ michael@0: void BindChildDocument(DocAccessible* aDocument); michael@0: michael@0: /** michael@0: * Process the generic notification. michael@0: * michael@0: * @note The caller must guarantee that the given instance still exists when michael@0: * notification is processed. michael@0: * @see NotificationController::HandleNotification michael@0: */ michael@0: template michael@0: void HandleNotification(Class* aInstance, michael@0: typename TNotification::Callback aMethod, michael@0: Arg* aArg); michael@0: michael@0: /** michael@0: * Return the cached accessible by the given DOM node if it's in subtree of michael@0: * this document accessible or the document accessible itself, otherwise null. michael@0: * michael@0: * @return the accessible object michael@0: */ michael@0: Accessible* GetAccessible(nsINode* aNode) const; michael@0: michael@0: /** michael@0: * Return an accessible for the given node even if the node is not in michael@0: * document's node map cache (like HTML area element). michael@0: * michael@0: * XXX: it should be really merged with GetAccessible(). michael@0: */ michael@0: Accessible* GetAccessibleEvenIfNotInMap(nsINode* aNode) const; michael@0: Accessible* GetAccessibleEvenIfNotInMapOrContainer(nsINode* aNode) const; michael@0: michael@0: /** michael@0: * Return whether the given DOM node has an accessible or not. michael@0: */ michael@0: bool HasAccessible(nsINode* aNode) const michael@0: { return GetAccessible(aNode); } michael@0: michael@0: /** michael@0: * Return the cached accessible by the given unique ID within this document. michael@0: * michael@0: * @note the unique ID matches with the uniqueID() of Accessible michael@0: * michael@0: * @param aUniqueID [in] the unique ID used to cache the node. michael@0: */ michael@0: Accessible* GetAccessibleByUniqueID(void* aUniqueID) michael@0: { michael@0: return UniqueID() == aUniqueID ? michael@0: this : mAccessibleCache.GetWeak(aUniqueID); michael@0: } michael@0: michael@0: /** michael@0: * Return the cached accessible by the given unique ID looking through michael@0: * this and nested documents. michael@0: */ michael@0: Accessible* GetAccessibleByUniqueIDInSubtree(void* aUniqueID); michael@0: michael@0: /** michael@0: * Return an accessible for the given DOM node or container accessible if michael@0: * the node is not accessible. michael@0: */ michael@0: Accessible* GetAccessibleOrContainer(nsINode* aNode) const; michael@0: michael@0: /** michael@0: * Return a container accessible for the given DOM node. michael@0: */ michael@0: Accessible* GetContainerAccessible(nsINode* aNode) const michael@0: { michael@0: return aNode ? GetAccessibleOrContainer(aNode->GetParentNode()) : nullptr; michael@0: } michael@0: michael@0: /** michael@0: * Return an accessible for the given node or its first accessible descendant. michael@0: */ michael@0: Accessible* GetAccessibleOrDescendant(nsINode* aNode) const; michael@0: michael@0: /** michael@0: * Return true if the given ID is referred by relation attribute. michael@0: * michael@0: * @note Different elements may share the same ID if they are hosted inside michael@0: * XBL bindings. Be careful the result of this method may be senseless michael@0: * while it's called for XUL elements (where XBL is used widely). michael@0: */ michael@0: bool IsDependentID(const nsAString& aID) const michael@0: { return mDependentIDsHash.Get(aID, nullptr); } michael@0: michael@0: /** michael@0: * Initialize the newly created accessible and put it into document caches. michael@0: * michael@0: * @param aAccessible [in] created accessible michael@0: * @param aRoleMapEntry [in] the role map entry role the ARIA role or nullptr michael@0: * if none michael@0: */ michael@0: void BindToDocument(Accessible* aAccessible, nsRoleMapEntry* aRoleMapEntry); michael@0: michael@0: /** michael@0: * Remove from document and shutdown the given accessible. michael@0: */ michael@0: void UnbindFromDocument(Accessible* aAccessible); michael@0: michael@0: /** michael@0: * Notify the document accessible that content was inserted. michael@0: */ michael@0: void ContentInserted(nsIContent* aContainerNode, michael@0: nsIContent* aStartChildNode, michael@0: nsIContent* aEndChildNode); michael@0: michael@0: /** michael@0: * Notify the document accessible that content was removed. michael@0: */ michael@0: void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode); michael@0: michael@0: /** michael@0: * Updates accessible tree when rendered text is changed. michael@0: */ michael@0: void UpdateText(nsIContent* aTextNode); michael@0: michael@0: /** michael@0: * Recreate an accessible, results in hide/show events pair. michael@0: */ michael@0: void RecreateAccessible(nsIContent* aContent); michael@0: michael@0: protected: michael@0: michael@0: void LastRelease(); michael@0: michael@0: // Accessible michael@0: virtual void CacheChildren(); michael@0: michael@0: // DocAccessible michael@0: virtual nsresult AddEventListeners(); michael@0: virtual nsresult RemoveEventListeners(); michael@0: michael@0: /** michael@0: * Marks this document as loaded or loading. michael@0: */ michael@0: void NotifyOfLoad(uint32_t aLoadEventType); michael@0: void NotifyOfLoading(bool aIsReloading); michael@0: michael@0: friend class DocManager; michael@0: michael@0: /** michael@0: * Perform initial update (create accessible tree). michael@0: * Can be overridden by wrappers to prepare initialization work. michael@0: */ michael@0: virtual void DoInitialUpdate(); michael@0: michael@0: /** michael@0: * Process document load notification, fire document load and state busy michael@0: * events if applicable. michael@0: */ michael@0: void ProcessLoad(); michael@0: michael@0: /** michael@0: * Add/remove scroll listeners, @see nsIScrollPositionListener interface. michael@0: */ michael@0: void AddScrollListener(); michael@0: void RemoveScrollListener(); michael@0: michael@0: /** michael@0: * Append the given document accessible to this document's child document michael@0: * accessibles. michael@0: */ michael@0: bool AppendChildDocument(DocAccessible* aChildDocument) michael@0: { michael@0: return mChildDocuments.AppendElement(aChildDocument); michael@0: } michael@0: michael@0: /** michael@0: * Remove the given document accessible from this document's child document michael@0: * accessibles. michael@0: */ michael@0: void RemoveChildDocument(DocAccessible* aChildDocument) michael@0: { michael@0: mChildDocuments.RemoveElement(aChildDocument); michael@0: } michael@0: michael@0: /** michael@0: * Add dependent IDs pointed by accessible element by relation attribute to michael@0: * cache. If the relation attribute is missed then all relation attributes michael@0: * are checked. michael@0: * michael@0: * @param aRelProvider [in] accessible that element has relation attribute michael@0: * @param aRelAttr [in, optional] relation attribute michael@0: */ michael@0: void AddDependentIDsFor(dom::Element* aRelProviderElm, michael@0: nsIAtom* aRelAttr = nullptr); michael@0: michael@0: /** michael@0: * Remove dependent IDs pointed by accessible element by relation attribute michael@0: * from cache. If the relation attribute is absent then all relation michael@0: * attributes are checked. michael@0: * michael@0: * @param aRelProvider [in] accessible that element has relation attribute michael@0: * @param aRelAttr [in, optional] relation attribute michael@0: */ michael@0: void RemoveDependentIDsFor(dom::Element* aRelProviderElm, michael@0: nsIAtom* aRelAttr = nullptr); michael@0: michael@0: /** michael@0: * Update or recreate an accessible depending on a changed attribute. michael@0: * michael@0: * @param aElement [in] the element the attribute was changed on michael@0: * @param aAttribute [in] the changed attribute michael@0: * @return true if an action was taken on the attribute change michael@0: */ michael@0: bool UpdateAccessibleOnAttrChange(mozilla::dom::Element* aElement, michael@0: nsIAtom* aAttribute); michael@0: michael@0: /** michael@0: * Fire accessible events when attribute is changed. michael@0: * michael@0: * @param aAccessible [in] accessible the DOM attribute is changed for michael@0: * @param aNameSpaceID [in] namespace of changed attribute michael@0: * @param aAttribute [in] changed attribute michael@0: */ michael@0: void AttributeChangedImpl(Accessible* aAccessible, michael@0: int32_t aNameSpaceID, nsIAtom* aAttribute); michael@0: michael@0: /** michael@0: * Fire accessible events when ARIA attribute is changed. michael@0: * michael@0: * @param aAccessible [in] accesislbe the DOM attribute is changed for michael@0: * @param aAttribute [in] changed attribute michael@0: */ michael@0: void ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute); michael@0: michael@0: /** michael@0: * Process ARIA active-descendant attribute change. michael@0: */ michael@0: void ARIAActiveDescendantChanged(Accessible* aAccessible); michael@0: michael@0: /** michael@0: * Update the accessible tree for inserted content. michael@0: */ michael@0: void ProcessContentInserted(Accessible* aContainer, michael@0: const nsTArray >* aInsertedContent); michael@0: michael@0: /** michael@0: * Used to notify the document to make it process the invalidation list. michael@0: * michael@0: * While children are cached we may encounter the case there's no accessible michael@0: * for referred content by related accessible. Store these related nodes to michael@0: * invalidate their containers later. michael@0: */ michael@0: void ProcessInvalidationList(); michael@0: michael@0: /** michael@0: * Update the accessible tree for content insertion or removal. michael@0: */ michael@0: void UpdateTree(Accessible* aContainer, nsIContent* aChildNode, michael@0: bool aIsInsert); michael@0: michael@0: /** michael@0: * Helper for UpdateTree() method. Go down to DOM subtree and updates michael@0: * accessible tree. Return one of these flags. michael@0: */ michael@0: enum EUpdateTreeFlags { michael@0: eNoAccessible = 0, michael@0: eAccessible = 1, michael@0: eAlertAccessible = 2 michael@0: }; michael@0: michael@0: uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert, michael@0: AccReorderEvent* aReorderEvent); michael@0: michael@0: /** michael@0: * Create accessible tree. michael@0: * michael@0: * @param aRoot [in] a root of subtree to create michael@0: * @param aFocusedAcc [in, optional] a focused accessible under created michael@0: * subtree if any michael@0: */ michael@0: void CacheChildrenInSubtree(Accessible* aRoot, michael@0: Accessible** aFocusedAcc = nullptr); michael@0: michael@0: /** michael@0: * Remove accessibles in subtree from node to accessible map. michael@0: */ michael@0: void UncacheChildrenInSubtree(Accessible* aRoot); michael@0: michael@0: /** michael@0: * Shutdown any cached accessible in the subtree. michael@0: * michael@0: * @param aAccessible [in] the root of the subrtee to invalidate accessible michael@0: * child/parent refs in michael@0: */ michael@0: void ShutdownChildrenInSubtree(Accessible* aAccessible); michael@0: michael@0: /** michael@0: * Return true if the document is a target of document loading events michael@0: * (for example, state busy change or document reload events). michael@0: * michael@0: * Rules: The root chrome document accessible is never an event target michael@0: * (for example, Firefox UI window). If the sub document is loaded within its michael@0: * parent document then the parent document is a target only (aka events michael@0: * coalescence). michael@0: */ michael@0: bool IsLoadEventTarget() const; michael@0: michael@0: /** michael@0: * Used to fire scrolling end event after page scroll. michael@0: * michael@0: * @param aTimer [in] the timer object michael@0: * @param aClosure [in] the document accessible where scrolling happens michael@0: */ michael@0: static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure); michael@0: michael@0: protected: michael@0: michael@0: /** michael@0: * State and property flags, kept by mDocFlags. michael@0: */ michael@0: enum { michael@0: // Whether scroll listeners were added. michael@0: eScrollInitialized = 1 << 0, michael@0: michael@0: // Whether the document is a tab document. michael@0: eTabDocument = 1 << 1 michael@0: }; michael@0: michael@0: /** michael@0: * Cache of accessibles within this document accessible. michael@0: */ michael@0: AccessibleHashtable mAccessibleCache; michael@0: nsDataHashtable, Accessible*> michael@0: mNodeToAccessibleMap; michael@0: michael@0: nsIDocument* mDocumentNode; michael@0: nsCOMPtr mScrollWatchTimer; michael@0: uint16_t mScrollPositionChangedTicks; // Used for tracking scroll events michael@0: michael@0: /** michael@0: * Bit mask of document load states (@see LoadState). michael@0: */ michael@0: uint32_t mLoadState : 3; michael@0: michael@0: /** michael@0: * Bit mask of other states and props. michael@0: */ michael@0: uint32_t mDocFlags : 28; michael@0: michael@0: /** michael@0: * Type of document load event fired after the document is loaded completely. michael@0: */ michael@0: uint32_t mLoadEventType; michael@0: michael@0: /** michael@0: * Reference to anchor jump element. michael@0: */ michael@0: nsCOMPtr mAnchorJumpElm; michael@0: michael@0: /** michael@0: * A generic state (see items below) before the attribute value was changed. michael@0: * @see AttributeWillChange and AttributeChanged notifications. michael@0: */ michael@0: union { michael@0: // ARIA attribute value michael@0: nsIAtom* mARIAAttrOldValue; michael@0: michael@0: // True if the accessible state bit was on michael@0: bool mStateBitWasOn; michael@0: }; michael@0: michael@0: nsTArray > mChildDocuments; michael@0: michael@0: /** michael@0: * The virtual cursor of the document. michael@0: */ michael@0: nsRefPtr mVirtualCursor; michael@0: michael@0: /** michael@0: * A storage class for pairing content with one of its relation attributes. michael@0: */ michael@0: class AttrRelProvider michael@0: { michael@0: public: michael@0: AttrRelProvider(nsIAtom* aRelAttr, nsIContent* aContent) : michael@0: mRelAttr(aRelAttr), mContent(aContent) { } michael@0: michael@0: nsIAtom* mRelAttr; michael@0: nsCOMPtr mContent; michael@0: michael@0: private: michael@0: AttrRelProvider(); michael@0: AttrRelProvider(const AttrRelProvider&); michael@0: AttrRelProvider& operator =(const AttrRelProvider&); michael@0: }; michael@0: michael@0: typedef nsTArray > AttrRelProviderArray; michael@0: typedef nsClassHashtable michael@0: DependentIDsHashtable; michael@0: michael@0: /** michael@0: * The cache of IDs pointed by relation attributes. michael@0: */ michael@0: DependentIDsHashtable mDependentIDsHash; michael@0: michael@0: static PLDHashOperator michael@0: CycleCollectorTraverseDepIDsEntry(const nsAString& aKey, michael@0: AttrRelProviderArray* aProviders, michael@0: void* aUserArg); michael@0: michael@0: friend class RelatedAccIterator; michael@0: michael@0: /** michael@0: * Used for our caching algorithm. We store the list of nodes that should be michael@0: * invalidated. michael@0: * michael@0: * @see ProcessInvalidationList michael@0: */ michael@0: nsTArray mInvalidationList; michael@0: michael@0: /** michael@0: * Used to process notification from core and accessible events. michael@0: */ michael@0: nsRefPtr mNotificationController; michael@0: friend class EventQueue; michael@0: friend class NotificationController; michael@0: michael@0: private: michael@0: michael@0: nsIPresShell* mPresShell; michael@0: }; michael@0: michael@0: inline DocAccessible* michael@0: Accessible::AsDoc() michael@0: { michael@0: return IsDoc() ? static_cast(this) : nullptr; michael@0: } michael@0: michael@0: } // namespace a11y michael@0: } // namespace mozilla michael@0: michael@0: #endif