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