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/. */
5 
6 #ifndef mozilla_a11y_DocAccessible_h__
7 #define mozilla_a11y_DocAccessible_h__
8 
9 #include "nsIAccessiblePivot.h"
10 
11 #include "HyperTextAccessibleWrap.h"
12 #include "AccEvent.h"
13 
14 #include "nsAutoPtr.h"
15 #include "nsClassHashtable.h"
16 #include "nsDataHashtable.h"
17 #include "nsIDocument.h"
18 #include "nsIDocumentObserver.h"
19 #include "nsIObserver.h"
20 #include "nsIScrollPositionListener.h"
21 #include "nsITimer.h"
22 #include "nsIWeakReference.h"
23 
24 class nsAccessiblePivot;
25 
26 const uint32_t kDefaultCacheLength = 128;
27 
28 namespace mozilla {
29 
30 class TextEditor;
31 
32 namespace a11y {
33 
34 class DocManager;
35 class NotificationController;
36 class DocAccessibleChild;
37 class RelatedAccIterator;
38 template <class Class, class... Args>
39 class TNotification;
40 
41 class DocAccessible : public HyperTextAccessibleWrap,
42                       public nsIDocumentObserver,
43                       public nsIObserver,
44                       public nsIScrollPositionListener,
45                       public nsSupportsWeakReference,
46                       public nsIAccessiblePivotObserver {
47   NS_DECL_ISUPPORTS_INHERITED
48   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocAccessible, Accessible)
49 
50   NS_DECL_NSIOBSERVER
51   NS_DECL_NSIACCESSIBLEPIVOTOBSERVER
52 
53  public:
54   DocAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell);
55 
56   // nsIScrollPositionListener
ScrollPositionWillChange(nscoord aX,nscoord aY)57   virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) override {}
58   virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) override;
59 
60   // nsIDocumentObserver
61   NS_DECL_NSIDOCUMENTOBSERVER
62 
63   // Accessible
64   virtual void Init();
65   virtual void Shutdown() override;
66   virtual nsIFrame* GetFrame() const override;
GetNode()67   virtual nsINode* GetNode() const override { return mDocumentNode; }
DocumentNode()68   nsIDocument* DocumentNode() const { return mDocumentNode; }
69 
70   virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override;
71   virtual void Description(nsString& aDescription) override;
72   virtual Accessible* FocusedChild() override;
73   virtual mozilla::a11y::role NativeRole() override;
74   virtual uint64_t NativeState() override;
75   virtual uint64_t NativeInteractiveState() const override;
76   virtual bool NativelyUnavailable() const override;
77   virtual void ApplyARIAState(uint64_t* aState) const override;
78   virtual already_AddRefed<nsIPersistentProperties> Attributes() override;
79 
80   virtual void TakeFocus() override;
81 
82 #ifdef A11Y_LOG
83   virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
84 #endif
85 
86   virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const override;
87 
88   // HyperTextAccessible
89   virtual already_AddRefed<TextEditor> GetEditor() const override;
90 
91   // DocAccessible
92 
93   /**
94    * Return document URL.
95    */
96   void URL(nsAString& aURL) const;
97 
98   /**
99    * Return DOM document title.
100    */
Title(nsString & aTitle)101   void Title(nsString& aTitle) const { mDocumentNode->GetTitle(aTitle); }
102 
103   /**
104    * Return DOM document mime type.
105    */
MimeType(nsAString & aType)106   void MimeType(nsAString& aType) const {
107     mDocumentNode->GetContentType(aType);
108   }
109 
110   /**
111    * Return DOM document type.
112    */
113   void DocType(nsAString& aType) const;
114 
115   /**
116    * Return virtual cursor associated with the document.
117    */
118   nsIAccessiblePivot* VirtualCursor();
119 
120   /**
121    * Return presentation shell for this document accessible.
122    */
PresShell()123   nsIPresShell* PresShell() const { return mPresShell; }
124 
125   /**
126    * Return the presentation shell's context.
127    */
PresContext()128   nsPresContext* PresContext() const { return mPresShell->GetPresContext(); }
129 
130   /**
131    * Return true if associated DOM document was loaded and isn't unloading.
132    */
IsContentLoaded()133   bool IsContentLoaded() const {
134     // eDOMLoaded flag check is used for error pages as workaround to make this
135     // method return correct result since error pages do not receive 'pageshow'
136     // event and as consequence nsIDocument::IsShowing() returns false.
137     return mDocumentNode && mDocumentNode->IsVisible() &&
138            (mDocumentNode->IsShowing() || HasLoadState(eDOMLoaded));
139   }
140 
IsHidden()141   bool IsHidden() const { return mDocumentNode->Hidden(); }
142 
143   /**
144    * Document load states.
145    */
146   enum LoadState {
147     // initial tree construction is pending
148     eTreeConstructionPending = 0,
149     // initial tree construction done
150     eTreeConstructed = 1,
151     // DOM document is loaded.
152     eDOMLoaded = 1 << 1,
153     // document is ready
154     eReady = eTreeConstructed | eDOMLoaded,
155     // document and all its subdocuments are ready
156     eCompletelyLoaded = eReady | 1 << 2
157   };
158 
159   /**
160    * Return true if the document has given document state.
161    */
HasLoadState(LoadState aState)162   bool HasLoadState(LoadState aState) const {
163     return (mLoadState & static_cast<uint32_t>(aState)) ==
164            static_cast<uint32_t>(aState);
165   }
166 
167   /**
168    * Return a native window handler or pointer depending on platform.
169    */
170   virtual void* GetNativeWindow() const;
171 
172   /**
173    * Return the parent document.
174    */
ParentDocument()175   DocAccessible* ParentDocument() const {
176     return mParent ? mParent->Document() : nullptr;
177   }
178 
179   /**
180    * Return the child document count.
181    */
ChildDocumentCount()182   uint32_t ChildDocumentCount() const { return mChildDocuments.Length(); }
183 
184   /**
185    * Return the child document at the given index.
186    */
GetChildDocumentAt(uint32_t aIndex)187   DocAccessible* GetChildDocumentAt(uint32_t aIndex) const {
188     return mChildDocuments.SafeElementAt(aIndex, nullptr);
189   }
190 
191   /**
192    * Fire accessible event asynchronously.
193    */
194   void FireDelayedEvent(AccEvent* aEvent);
195   void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget);
196   void FireEventsOnInsertion(Accessible* aContainer);
197 
198   /**
199    * Fire value change event on the given accessible if applicable.
200    */
201   void MaybeNotifyOfValueChange(Accessible* aAccessible);
202 
203   /**
204    * Get/set the anchor jump.
205    */
AnchorJump()206   Accessible* AnchorJump() { return GetAccessibleOrContainer(mAnchorJumpElm); }
207 
SetAnchorJump(nsIContent * aTargetNode)208   void SetAnchorJump(nsIContent* aTargetNode) { mAnchorJumpElm = aTargetNode; }
209 
210   /**
211    * Bind the child document to the tree.
212    */
213   void BindChildDocument(DocAccessible* aDocument);
214 
215   /**
216    * Process the generic notification.
217    *
218    * @note  The caller must guarantee that the given instance still exists when
219    *          notification is processed.
220    * @see   NotificationController::HandleNotification
221    */
222   template <class Class, class Arg>
223   void HandleNotification(Class* aInstance,
224                           typename TNotification<Class, Arg>::Callback aMethod,
225                           Arg* aArg);
226 
227   /**
228    * Return the cached accessible by the given DOM node if it's in subtree of
229    * this document accessible or the document accessible itself, otherwise null.
230    *
231    * @return the accessible object
232    */
GetAccessible(nsINode * aNode)233   Accessible* GetAccessible(nsINode* aNode) const {
234     return aNode == mDocumentNode ? const_cast<DocAccessible*>(this)
235                                   : mNodeToAccessibleMap.Get(aNode);
236   }
237 
238   /**
239    * Return an accessible for the given node even if the node is not in
240    * document's node map cache (like HTML area element).
241    *
242    * XXX: it should be really merged with GetAccessible().
243    */
244   Accessible* GetAccessibleEvenIfNotInMap(nsINode* aNode) const;
245   Accessible* GetAccessibleEvenIfNotInMapOrContainer(nsINode* aNode) const;
246 
247   /**
248    * Return whether the given DOM node has an accessible or not.
249    */
HasAccessible(nsINode * aNode)250   bool HasAccessible(nsINode* aNode) const { return GetAccessible(aNode); }
251 
252   /**
253    * Return the cached accessible by the given unique ID within this document.
254    *
255    * @note   the unique ID matches with the uniqueID() of Accessible
256    *
257    * @param  aUniqueID  [in] the unique ID used to cache the node.
258    */
GetAccessibleByUniqueID(void * aUniqueID)259   Accessible* GetAccessibleByUniqueID(void* aUniqueID) {
260     return UniqueID() == aUniqueID ? this : mAccessibleCache.GetWeak(aUniqueID);
261   }
262 
263   /**
264    * Return the cached accessible by the given unique ID looking through
265    * this and nested documents.
266    */
267   Accessible* GetAccessibleByUniqueIDInSubtree(void* aUniqueID);
268 
269   /**
270    * Return an accessible for the given DOM node or container accessible if
271    * the node is not accessible.
272    */
273   Accessible* GetAccessibleOrContainer(nsINode* aNode) const;
274 
275   /**
276    * Return a container accessible for the given DOM node.
277    */
GetContainerAccessible(nsINode * aNode)278   Accessible* GetContainerAccessible(nsINode* aNode) const {
279     return aNode ? GetAccessibleOrContainer(aNode->GetParentNode()) : nullptr;
280   }
281 
282   /**
283    * Return an accessible for the given node if any, or an immediate accessible
284    * container for it.
285    */
286   Accessible* AccessibleOrTrueContainer(nsINode* aNode) const;
287 
288   /**
289    * Return an accessible for the given node or its first accessible descendant.
290    */
291   Accessible* GetAccessibleOrDescendant(nsINode* aNode) const;
292 
293   /**
294    * Returns aria-owns seized child at the given index.
295    */
ARIAOwnedAt(Accessible * aParent,uint32_t aIndex)296   Accessible* ARIAOwnedAt(Accessible* aParent, uint32_t aIndex) const {
297     nsTArray<RefPtr<Accessible>>* children = mARIAOwnsHash.Get(aParent);
298     if (children) {
299       return children->SafeElementAt(aIndex);
300     }
301     return nullptr;
302   }
ARIAOwnedCount(Accessible * aParent)303   uint32_t ARIAOwnedCount(Accessible* aParent) const {
304     nsTArray<RefPtr<Accessible>>* children = mARIAOwnsHash.Get(aParent);
305     return children ? children->Length() : 0;
306   }
307 
308   /**
309    * Return true if the given ID is referred by relation attribute.
310    *
311    * @note Different elements may share the same ID if they are hosted inside
312    *       XBL bindings. Be careful the result of this method may be  senseless
313    *       while it's called for XUL elements (where XBL is used widely).
314    */
IsDependentID(const nsAString & aID)315   bool IsDependentID(const nsAString& aID) const {
316     return mDependentIDsHash.Get(aID, nullptr);
317   }
318 
319   /**
320    * Initialize the newly created accessible and put it into document caches.
321    *
322    * @param  aAccessible    [in] created accessible
323    * @param  aRoleMapEntry  [in] the role map entry role the ARIA role or
324    * nullptr if none
325    */
326   void BindToDocument(Accessible* aAccessible,
327                       const nsRoleMapEntry* aRoleMapEntry);
328 
329   /**
330    * Remove from document and shutdown the given accessible.
331    */
332   void UnbindFromDocument(Accessible* aAccessible);
333 
334   /**
335    * Notify the document accessible that content was inserted.
336    */
337   void ContentInserted(nsIContent* aContainerNode, nsIContent* aStartChildNode,
338                        nsIContent* aEndChildNode);
339 
340   /**
341    * Update the tree on content removal.
342    */
343   void ContentRemoved(Accessible* aAccessible);
344   void ContentRemoved(nsIContent* aContentNode);
345 
346   /**
347    * Updates accessible tree when rendered text is changed.
348    */
349   void UpdateText(nsIContent* aTextNode);
350 
351   /**
352    * Recreate an accessible, results in hide/show events pair.
353    */
354   void RecreateAccessible(nsIContent* aContent);
355 
356   /**
357    * Schedule ARIA owned element relocation if needed. Return true if relocation
358    * was scheduled.
359    */
360   bool RelocateARIAOwnedIfNeeded(nsIContent* aEl);
361 
362   /**
363    * Return a notification controller associated with the document.
364    */
Controller()365   NotificationController* Controller() const { return mNotificationController; }
366 
367   /**
368    * If this document is in a content process return the object responsible for
369    * communicating with the main process for it.
370    */
IPCDoc()371   DocAccessibleChild* IPCDoc() const { return mIPCDoc; }
372 
373  protected:
374   virtual ~DocAccessible();
375 
376   void LastRelease();
377 
378   // DocAccessible
379   virtual nsresult AddEventListeners();
380   virtual nsresult RemoveEventListeners();
381 
382   /**
383    * Marks this document as loaded or loading.
384    */
385   void NotifyOfLoad(uint32_t aLoadEventType);
386   void NotifyOfLoading(bool aIsReloading);
387 
388   friend class DocManager;
389 
390   /**
391    * Perform initial update (create accessible tree).
392    * Can be overridden by wrappers to prepare initialization work.
393    */
394   virtual void DoInitialUpdate();
395 
396   /**
397    * Updates root element and picks up ARIA role on it if any.
398    */
399   void UpdateRootElIfNeeded();
400 
401   /**
402    * Process document load notification, fire document load and state busy
403    * events if applicable.
404    */
405   void ProcessLoad();
406 
407   /**
408    * Add/remove scroll listeners, @see nsIScrollPositionListener interface.
409    */
410   void AddScrollListener();
411   void RemoveScrollListener();
412 
413   /**
414    * Append the given document accessible to this document's child document
415    * accessibles.
416    */
AppendChildDocument(DocAccessible * aChildDocument)417   bool AppendChildDocument(DocAccessible* aChildDocument) {
418     return mChildDocuments.AppendElement(aChildDocument);
419   }
420 
421   /**
422    * Remove the given document accessible from this document's child document
423    * accessibles.
424    */
RemoveChildDocument(DocAccessible * aChildDocument)425   void RemoveChildDocument(DocAccessible* aChildDocument) {
426     mChildDocuments.RemoveElement(aChildDocument);
427   }
428 
429   /**
430    * Add dependent IDs pointed by accessible element by relation attribute to
431    * cache. If the relation attribute is missed then all relation attributes
432    * are checked.
433    *
434    * @param aRelProvider [in] accessible that element has relation attribute
435    * @param aRelAttr     [in, optional] relation attribute
436    */
437   void AddDependentIDsFor(Accessible* aRelProvider, nsAtom* aRelAttr = nullptr);
438 
439   /**
440    * Remove dependent IDs pointed by accessible element by relation attribute
441    * from cache. If the relation attribute is absent then all relation
442    * attributes are checked.
443    *
444    * @param aRelProvider [in] accessible that element has relation attribute
445    * @param aRelAttr     [in, optional] relation attribute
446    */
447   void RemoveDependentIDsFor(Accessible* aRelProvider,
448                              nsAtom* aRelAttr = nullptr);
449 
450   /**
451    * Update or recreate an accessible depending on a changed attribute.
452    *
453    * @param aElement   [in] the element the attribute was changed on
454    * @param aAttribute [in] the changed attribute
455    * @return            true if an action was taken on the attribute change
456    */
457   bool UpdateAccessibleOnAttrChange(mozilla::dom::Element* aElement,
458                                     nsAtom* aAttribute);
459 
460   /**
461    * Fire accessible events when attribute is changed.
462    *
463    * @param aAccessible   [in] accessible the DOM attribute is changed for
464    * @param aNameSpaceID  [in] namespace of changed attribute
465    * @param aAttribute    [in] changed attribute
466    */
467   void AttributeChangedImpl(Accessible* aAccessible, int32_t aNameSpaceID,
468                             nsAtom* aAttribute);
469 
470   /**
471    * Fire accessible events when ARIA attribute is changed.
472    *
473    * @param aAccessible  [in] accesislbe the DOM attribute is changed for
474    * @param aAttribute   [in] changed attribute
475    */
476   void ARIAAttributeChanged(Accessible* aAccessible, nsAtom* aAttribute);
477 
478   /**
479    * Process ARIA active-descendant attribute change.
480    */
481   void ARIAActiveDescendantChanged(Accessible* aAccessible);
482 
483   /**
484    * Update the accessible tree for inserted content.
485    */
486   void ProcessContentInserted(
487       Accessible* aContainer,
488       const nsTArray<nsCOMPtr<nsIContent>>* aInsertedContent);
489   void ProcessContentInserted(Accessible* aContainer,
490                               nsIContent* aInsertedContent);
491 
492   /**
493    * Used to notify the document to make it process the invalidation list.
494    *
495    * While children are cached we may encounter the case there's no accessible
496    * for referred content by related accessible. Store these related nodes to
497    * invalidate their containers later.
498    */
499   void ProcessInvalidationList();
500 
501   /**
502    * Steals or puts back accessible subtrees.
503    */
504   void DoARIAOwnsRelocation(Accessible* aOwner);
505 
506   /**
507    * Moves children back under their original parents.
508    */
509   void PutChildrenBack(nsTArray<RefPtr<Accessible>>* aChildren,
510                        uint32_t aStartIdx);
511 
512   bool MoveChild(Accessible* aChild, Accessible* aNewParent,
513                  int32_t aIdxInParent);
514 
515   /**
516    * Create accessible tree.
517    *
518    * @param aRoot       [in] a root of subtree to create
519    * @param aFocusedAcc [in, optional] a focused accessible under created
520    *                      subtree if any
521    */
522   void CacheChildrenInSubtree(Accessible* aRoot,
523                               Accessible** aFocusedAcc = nullptr);
524   void CreateSubtree(Accessible* aRoot);
525 
526   /**
527    * Remove accessibles in subtree from node to accessible map.
528    */
529   void UncacheChildrenInSubtree(Accessible* aRoot);
530 
531   /**
532    * Shutdown any cached accessible in the subtree.
533    *
534    * @param aAccessible  [in] the root of the subrtee to invalidate accessible
535    *                      child/parent refs in
536    */
537   void ShutdownChildrenInSubtree(Accessible* aAccessible);
538 
539   /**
540    * Return true if the document is a target of document loading events
541    * (for example, state busy change or document reload events).
542    *
543    * Rules: The root chrome document accessible is never an event target
544    * (for example, Firefox UI window). If the sub document is loaded within its
545    * parent document then the parent document is a target only (aka events
546    * coalescence).
547    */
548   bool IsLoadEventTarget() const;
549 
550   /*
551    * Set the object responsible for communicating with the main process on
552    * behalf of this document.
553    */
SetIPCDoc(DocAccessibleChild * aIPCDoc)554   void SetIPCDoc(DocAccessibleChild* aIPCDoc) { mIPCDoc = aIPCDoc; }
555 
556   friend class DocAccessibleChildBase;
557 
558   /**
559    * Used to fire scrolling end event after page scroll.
560    *
561    * @param aTimer    [in] the timer object
562    * @param aClosure  [in] the document accessible where scrolling happens
563    */
564   static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure);
565 
566  protected:
567   /**
568    * State and property flags, kept by mDocFlags.
569    */
570   enum {
571     // Whether scroll listeners were added.
572     eScrollInitialized = 1 << 0,
573 
574     // Whether the document is a tab document.
575     eTabDocument = 1 << 1
576   };
577 
578   /**
579    * Cache of accessibles within this document accessible.
580    */
581   AccessibleHashtable mAccessibleCache;
582   nsDataHashtable<nsPtrHashKey<const nsINode>, Accessible*>
583       mNodeToAccessibleMap;
584 
585   nsIDocument* mDocumentNode;
586   nsCOMPtr<nsITimer> mScrollWatchTimer;
587   uint16_t mScrollPositionChangedTicks;  // Used for tracking scroll events
588 
589   /**
590    * Bit mask of document load states (@see LoadState).
591    */
592   uint32_t mLoadState : 3;
593 
594   /**
595    * Bit mask of other states and props.
596    */
597   uint32_t mDocFlags : 28;
598 
599   /**
600    * Type of document load event fired after the document is loaded completely.
601    */
602   uint32_t mLoadEventType;
603 
604   /**
605    * Reference to anchor jump element.
606    */
607   nsCOMPtr<nsIContent> mAnchorJumpElm;
608 
609   /**
610    * A generic state (see items below) before the attribute value was changed.
611    * @see AttributeWillChange and AttributeChanged notifications.
612    */
613   union {
614     // ARIA attribute value
615     nsAtom* mARIAAttrOldValue;
616 
617     // True if the accessible state bit was on
618     bool mStateBitWasOn;
619   };
620 
621   nsTArray<RefPtr<DocAccessible>> mChildDocuments;
622 
623   /**
624    * The virtual cursor of the document.
625    */
626   RefPtr<nsAccessiblePivot> mVirtualCursor;
627 
628   /**
629    * A storage class for pairing content with one of its relation attributes.
630    */
631   class AttrRelProvider {
632    public:
AttrRelProvider(nsAtom * aRelAttr,nsIContent * aContent)633     AttrRelProvider(nsAtom* aRelAttr, nsIContent* aContent)
634         : mRelAttr(aRelAttr), mContent(aContent) {}
635 
636     nsAtom* mRelAttr;
637     nsCOMPtr<nsIContent> mContent;
638 
639    private:
640     AttrRelProvider();
641     AttrRelProvider(const AttrRelProvider&);
642     AttrRelProvider& operator=(const AttrRelProvider&);
643   };
644 
645   /**
646    * The cache of IDs pointed by relation attributes.
647    */
648   typedef nsTArray<nsAutoPtr<AttrRelProvider>> AttrRelProviderArray;
649   nsClassHashtable<nsStringHashKey, AttrRelProviderArray> mDependentIDsHash;
650 
651   friend class RelatedAccIterator;
652 
653   /**
654    * Used for our caching algorithm. We store the list of nodes that should be
655    * invalidated.
656    *
657    * @see ProcessInvalidationList
658    */
659   nsTArray<RefPtr<nsIContent>> mInvalidationList;
660 
661   /**
662    * Holds a list of aria-owns relocations.
663    */
664   nsClassHashtable<nsPtrHashKey<Accessible>, nsTArray<RefPtr<Accessible>>>
665       mARIAOwnsHash;
666 
667   /**
668    * Used to process notification from core and accessible events.
669    */
670   RefPtr<NotificationController> mNotificationController;
671   friend class EventTree;
672   friend class NotificationController;
673 
674  private:
675   nsIPresShell* mPresShell;
676 
677   // Exclusively owned by IPDL so don't manually delete it!
678   DocAccessibleChild* mIPCDoc;
679 };
680 
AsDoc()681 inline DocAccessible* Accessible::AsDoc() {
682   return IsDoc() ? static_cast<DocAccessible*>(this) : nullptr;
683 }
684 
685 }  // namespace a11y
686 }  // namespace mozilla
687 
688 #endif
689