1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "LocalAccessible-inl.h"
8 #include "AccIterator.h"
9 #include "AccAttributes.h"
10 #include "DocAccessible-inl.h"
11 #include "DocAccessibleChild.h"
12 #include "HTMLImageMapAccessible.h"
13 #include "nsAccCache.h"
14 #include "nsAccessiblePivot.h"
15 #include "nsAccUtils.h"
16 #include "nsDeckFrame.h"
17 #include "nsEventShell.h"
18 #include "nsLayoutUtils.h"
19 #include "nsTextEquivUtils.h"
20 #include "Pivot.h"
21 #include "Role.h"
22 #include "RootAccessible.h"
23 #include "TreeWalker.h"
24 #include "xpcAccessibleDocument.h"
25 
26 #include "nsCommandManager.h"
27 #include "nsContentUtils.h"
28 #include "nsIDocShell.h"
29 #include "mozilla/dom/Document.h"
30 #include "nsPIDOMWindow.h"
31 #include "nsIEditingSession.h"
32 #include "nsIFrame.h"
33 #include "nsIInterfaceRequestorUtils.h"
34 #include "nsImageFrame.h"
35 #include "nsViewManager.h"
36 #include "nsIScrollableFrame.h"
37 #include "nsUnicharUtils.h"
38 #include "nsIURI.h"
39 #include "nsIWebNavigation.h"
40 #include "nsFocusManager.h"
41 #include "nsTHashSet.h"
42 #include "mozilla/ArrayUtils.h"
43 #include "mozilla/Assertions.h"
44 #include "mozilla/EditorBase.h"
45 #include "mozilla/EventStates.h"
46 #include "mozilla/HTMLEditor.h"
47 #include "mozilla/PresShell.h"
48 #include "mozilla/StaticPrefs_accessibility.h"
49 #include "mozilla/dom/AncestorIterator.h"
50 #include "mozilla/dom/BrowserChild.h"
51 #include "mozilla/dom/DocumentType.h"
52 #include "mozilla/dom/Element.h"
53 #include "mozilla/dom/MutationEventBinding.h"
54 #include "mozilla/dom/UserActivation.h"
55 #include "HTMLElementAccessibles.h"
56 
57 using namespace mozilla;
58 using namespace mozilla::a11y;
59 
60 ////////////////////////////////////////////////////////////////////////////////
61 // Static member initialization
62 
63 static nsStaticAtom* const kRelationAttrs[] = {nsGkAtoms::aria_labelledby,
64                                                nsGkAtoms::aria_describedby,
65                                                nsGkAtoms::aria_details,
66                                                nsGkAtoms::aria_owns,
67                                                nsGkAtoms::aria_controls,
68                                                nsGkAtoms::aria_flowto,
69                                                nsGkAtoms::aria_errormessage,
70                                                nsGkAtoms::_for,
71                                                nsGkAtoms::control};
72 
73 static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
74 
75 ////////////////////////////////////////////////////////////////////////////////
76 // Constructor/desctructor
77 
DocAccessible(dom::Document * aDocument,PresShell * aPresShell)78 DocAccessible::DocAccessible(dom::Document* aDocument,
79                              PresShell* aPresShell)
80     :  // XXX don't pass a document to the LocalAccessible constructor so that
81        // we don't set mDoc until our vtable is fully setup.  If we set mDoc
82        // before setting up the vtable we will call LocalAccessible::AddRef()
83        // but not the overrides of it for subclasses.  It is important to call
84        // those overrides to avoid confusing leak checking machinary.
85       HyperTextAccessibleWrap(nullptr, nullptr),
86       // XXX aaronl should we use an algorithm for the initial cache size?
87       mAccessibleCache(kDefaultCacheLength),
88       mNodeToAccessibleMap(kDefaultCacheLength),
89       mDocumentNode(aDocument),
90       mLoadState(eTreeConstructionPending),
91       mDocFlags(0),
92       mLoadEventType(0),
93       mARIAAttrOldValue{nullptr},
94       mVirtualCursor(nullptr),
95       mPresShell(aPresShell),
96       mIPCDoc(nullptr) {
97   mGenericTypes |= eDocument;
98   mStateFlags |= eNotNodeMapEntry;
99   mDoc = this;
100 
101   MOZ_ASSERT(mPresShell, "should have been given a pres shell");
102   mPresShell->SetDocAccessible(this);
103 }
104 
~DocAccessible()105 DocAccessible::~DocAccessible() {
106   NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
107 }
108 
109 ////////////////////////////////////////////////////////////////////////////////
110 // nsISupports
111 
112 NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
113 
114 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible,
115                                                   LocalAccessible)
116   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
117   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
118   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
119   for (const auto& hashEntry : tmp->mDependentIDsHashes.Values()) {
120     for (const auto& providers : hashEntry->Values()) {
121       for (int32_t provIdx = providers->Length() - 1; provIdx >= 0; provIdx--) {
122         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
123             cb, "content of dependent ids hash entry of document accessible");
124 
125         const auto& provider = (*providers)[provIdx];
126         cb.NoteXPCOMChild(provider->mContent);
127       }
128     }
129   }
130   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
131   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
132   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
133   for (const auto& ar : tmp->mARIAOwnsHash.Values()) {
134     for (uint32_t i = 0; i < ar->Length(); i++) {
135       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mARIAOwnsHash entry item");
136       cb.NoteXPCOMChild(ar->ElementAt(i));
137     }
138   }
139 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
140 
141 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, LocalAccessible)
142   NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
143   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
144   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
145   tmp->mDependentIDsHashes.Clear();
146   tmp->mNodeToAccessibleMap.Clear();
147   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
148   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
149   NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList)
150   NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
151   tmp->mARIAOwnsHash.Clear();
152 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
153 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocAccessible)154 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocAccessible)
155   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
156   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
157   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
158   NS_INTERFACE_MAP_ENTRY(nsIObserver)
159   NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
160 NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible)
161 
162 NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
163 NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
164 
165 ////////////////////////////////////////////////////////////////////////////////
166 // nsIAccessible
167 
168 ENameValueFlag DocAccessible::Name(nsString& aName) const {
169   aName.Truncate();
170 
171   if (mParent) {
172     mParent->Name(aName);  // Allow owning iframe to override the name
173   }
174   if (aName.IsEmpty()) {
175     // Allow name via aria-labelledby or title attribute
176     LocalAccessible::Name(aName);
177   }
178   if (aName.IsEmpty()) {
179     Title(aName);  // Try title element
180   }
181   if (aName.IsEmpty()) {  // Last resort: use URL
182     URL(aName);
183   }
184 
185   return eNameOK;
186 }
187 
188 // LocalAccessible public method
NativeRole() const189 role DocAccessible::NativeRole() const {
190   nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
191   if (docShell) {
192     nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
193     docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
194     int32_t itemType = docShell->ItemType();
195     if (sameTypeRoot == docShell) {
196       // Root of content or chrome tree
197       if (itemType == nsIDocShellTreeItem::typeChrome) {
198         return roles::CHROME_WINDOW;
199       }
200 
201       if (itemType == nsIDocShellTreeItem::typeContent) {
202         return roles::DOCUMENT;
203       }
204     } else if (itemType == nsIDocShellTreeItem::typeContent) {
205       return roles::DOCUMENT;
206     }
207   }
208 
209   return roles::PANE;  // Fall back;
210 }
211 
Description(nsString & aDescription)212 void DocAccessible::Description(nsString& aDescription) {
213   if (mParent) mParent->Description(aDescription);
214 
215   if (HasOwnContent() && aDescription.IsEmpty()) {
216     nsTextEquivUtils::GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
217                                              aDescription);
218   }
219 }
220 
221 // LocalAccessible public method
NativeState() const222 uint64_t DocAccessible::NativeState() const {
223   // Document is always focusable.
224   uint64_t state =
225       states::FOCUSABLE;  // keep in sync with NativeInteractiveState() impl
226   if (FocusMgr()->IsFocused(this)) state |= states::FOCUSED;
227 
228   // Expose stale state until the document is ready (DOM is loaded and tree is
229   // constructed).
230   if (!HasLoadState(eReady)) state |= states::STALE;
231 
232   // Expose state busy until the document and all its subdocuments is completely
233   // loaded.
234   if (!HasLoadState(eCompletelyLoaded)) state |= states::BUSY;
235 
236   nsIFrame* frame = GetFrame();
237   if (!frame || !frame->IsVisibleConsideringAncestors(
238                     nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
239     state |= states::INVISIBLE | states::OFFSCREEN;
240   }
241 
242   RefPtr<EditorBase> editorBase = GetEditor();
243   state |= editorBase ? states::EDITABLE : states::READONLY;
244 
245   return state;
246 }
247 
NativeInteractiveState() const248 uint64_t DocAccessible::NativeInteractiveState() const {
249   // Document is always focusable.
250   return states::FOCUSABLE;
251 }
252 
NativelyUnavailable() const253 bool DocAccessible::NativelyUnavailable() const { return false; }
254 
255 // LocalAccessible public method
ApplyARIAState(uint64_t * aState) const256 void DocAccessible::ApplyARIAState(uint64_t* aState) const {
257   // Grab states from content element.
258   if (mContent) LocalAccessible::ApplyARIAState(aState);
259 
260   // Allow iframe/frame etc. to have final state override via ARIA.
261   if (mParent) mParent->ApplyARIAState(aState);
262 }
263 
Attributes()264 already_AddRefed<AccAttributes> DocAccessible::Attributes() {
265   RefPtr<AccAttributes> attributes = HyperTextAccessibleWrap::Attributes();
266 
267   // No attributes if document is not attached to the tree or if it's a root
268   // document.
269   if (!mParent || IsRoot()) return attributes.forget();
270 
271   // Override ARIA object attributes from outerdoc.
272   aria::AttrIterator attribIter(mParent->GetContent());
273   while (attribIter.Next()) {
274     nsAutoString value;
275     attribIter.AttrValue(value);
276     attributes->SetAttribute(attribIter.AttrName(), value);
277   }
278 
279   return attributes.forget();
280 }
281 
FocusedChild()282 LocalAccessible* DocAccessible::FocusedChild() {
283   // Return an accessible for the current global focus, which does not have to
284   // be contained within the current document.
285   return FocusMgr()->FocusedAccessible();
286 }
287 
TakeFocus() const288 void DocAccessible::TakeFocus() const {
289   // Focus the document.
290   nsFocusManager* fm = nsFocusManager::GetFocusManager();
291   RefPtr<dom::Element> newFocus;
292   dom::AutoHandlingUserInputStatePusher inputStatePusher(true);
293   fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
294                 nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus));
295 }
296 
297 // HyperTextAccessible method
GetEditor() const298 already_AddRefed<EditorBase> DocAccessible::GetEditor() const {
299   // Check if document is editable (designMode="on" case). Otherwise check if
300   // the html:body (for HTML document case) or document element is editable.
301   if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
302       (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE))) {
303     return nullptr;
304   }
305 
306   nsCOMPtr<nsIDocShell> docShell = mDocumentNode->GetDocShell();
307   if (!docShell) {
308     return nullptr;
309   }
310 
311   nsCOMPtr<nsIEditingSession> editingSession;
312   docShell->GetEditingSession(getter_AddRefs(editingSession));
313   if (!editingSession) return nullptr;  // No editing session interface
314 
315   RefPtr<HTMLEditor> htmlEditor =
316       editingSession->GetHTMLEditorForWindow(mDocumentNode->GetWindow());
317   if (!htmlEditor) {
318     return nullptr;
319   }
320 
321   bool isEditable = false;
322   htmlEditor->GetIsDocumentEditable(&isEditable);
323   if (isEditable) {
324     return htmlEditor.forget();
325   }
326 
327   return nullptr;
328 }
329 
330 // DocAccessible public method
331 
URL(nsAString & aURL) const332 void DocAccessible::URL(nsAString& aURL) const {
333   nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
334   nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
335   nsAutoCString theURL;
336   if (webNav) {
337     nsCOMPtr<nsIURI> pURI;
338     webNav->GetCurrentURI(getter_AddRefs(pURI));
339     if (pURI) pURI->GetSpec(theURL);
340   }
341   CopyUTF8toUTF16(theURL, aURL);
342 }
343 
Title(nsString & aTitle) const344 void DocAccessible::Title(nsString& aTitle) const {
345   mDocumentNode->GetTitle(aTitle);
346 }
347 
MimeType(nsAString & aType) const348 void DocAccessible::MimeType(nsAString& aType) const {
349   mDocumentNode->GetContentType(aType);
350 }
351 
DocType(nsAString & aType) const352 void DocAccessible::DocType(nsAString& aType) const {
353   dom::DocumentType* docType = mDocumentNode->GetDoctype();
354   if (docType) docType->GetPublicId(aType);
355 }
356 
357 ////////////////////////////////////////////////////////////////////////////////
358 // LocalAccessible
359 
Init()360 void DocAccessible::Init() {
361 #ifdef A11Y_LOG
362   if (logging::IsEnabled(logging::eDocCreate)) {
363     logging::DocCreate("document initialize", mDocumentNode, this);
364   }
365 #endif
366 
367   // Initialize notification controller.
368   mNotificationController = new NotificationController(this, mPresShell);
369 
370   // Mark the document accessible as loaded if its DOM document was loaded at
371   // this point (this can happen because a11y is started late or DOM document
372   // having no container was loaded.
373   if (mDocumentNode->GetReadyStateEnum() ==
374       dom::Document::READYSTATE_COMPLETE) {
375     mLoadState |= eDOMLoaded;
376   }
377 
378   AddEventListeners();
379 }
380 
Shutdown()381 void DocAccessible::Shutdown() {
382   if (!mPresShell) {  // already shutdown
383     return;
384   }
385 
386 #ifdef A11Y_LOG
387   if (logging::IsEnabled(logging::eDocDestroy)) {
388     logging::DocDestroy("document shutdown", mDocumentNode, this);
389   }
390 #endif
391 
392   // Mark the document as shutdown before AT is notified about the document
393   // removal from its container (valid for root documents on ATK and due to
394   // some reason for MSAA, refer to bug 757392 for details).
395   mStateFlags |= eIsDefunct;
396 
397   if (mNotificationController) {
398     mNotificationController->Shutdown();
399     mNotificationController = nullptr;
400   }
401 
402   RemoveEventListeners();
403 
404   // mParent->RemoveChild clears mParent, but we need to know whether we were a
405   // child later, so use a flag.
406   const bool isChild = !!mParent;
407   if (mParent) {
408     DocAccessible* parentDocument = mParent->Document();
409     if (parentDocument) parentDocument->RemoveChildDocument(this);
410 
411     mParent->RemoveChild(this);
412     MOZ_ASSERT(!mParent, "Parent has to be null!");
413   }
414 
415   mPresShell->SetDocAccessible(nullptr);
416   mPresShell = nullptr;  // Avoid reentrancy
417 
418   // Walk the array backwards because child documents remove themselves from the
419   // array as they are shutdown.
420   int32_t childDocCount = mChildDocuments.Length();
421   for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
422     mChildDocuments[idx]->Shutdown();
423   }
424 
425   mChildDocuments.Clear();
426 
427   // XXX thinking about ordering?
428   if (mIPCDoc) {
429     MOZ_ASSERT(IPCAccessibilityActive());
430     mIPCDoc->Shutdown();
431     MOZ_ASSERT(!mIPCDoc);
432   }
433 
434   if (mVirtualCursor) {
435     mVirtualCursor->RemoveObserver(this);
436     mVirtualCursor = nullptr;
437   }
438 
439   mDependentIDsHashes.Clear();
440   mNodeToAccessibleMap.Clear();
441 
442   mAnchorJumpElm = nullptr;
443   mInvalidationList.Clear();
444 
445   for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
446     LocalAccessible* accessible = iter.Data();
447     MOZ_ASSERT(accessible);
448     if (accessible && !accessible->IsDefunct()) {
449       // Unlink parent to avoid its cleaning overhead in shutdown.
450       accessible->mParent = nullptr;
451       accessible->Shutdown();
452     }
453     iter.Remove();
454   }
455 
456   HyperTextAccessibleWrap::Shutdown();
457 
458   MOZ_ASSERT(GetAccService());
459   GetAccService()->NotifyOfDocumentShutdown(
460       this, mDocumentNode,
461       // Make sure we don't shut down AccService while a parent document is
462       // still shutting down. The parent will allow service shutdown when it
463       // reaches this point.
464       /* aAllowServiceShutdown */ !isChild);
465   mDocumentNode = nullptr;
466 }
467 
GetFrame() const468 nsIFrame* DocAccessible::GetFrame() const {
469   nsIFrame* root = nullptr;
470   if (mPresShell) {
471     root = mPresShell->GetRootFrame();
472   }
473 
474   return root;
475 }
476 
GetNode() const477 nsINode* DocAccessible::GetNode() const { return mDocumentNode; }
478 
479 // DocAccessible protected member
RelativeBounds(nsIFrame ** aRelativeFrame) const480 nsRect DocAccessible::RelativeBounds(nsIFrame** aRelativeFrame) const {
481   *aRelativeFrame = GetFrame();
482 
483   dom::Document* document = mDocumentNode;
484   dom::Document* parentDoc = nullptr;
485 
486   nsRect bounds;
487   while (document) {
488     PresShell* presShell = document->GetPresShell();
489     if (!presShell) {
490       return nsRect();
491     }
492 
493     nsRect scrollPort;
494     nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
495     if (sf) {
496       scrollPort = sf->GetScrollPortRect();
497     } else {
498       nsIFrame* rootFrame = presShell->GetRootFrame();
499       if (!rootFrame) return nsRect();
500 
501       scrollPort = rootFrame->GetRect();
502     }
503 
504     if (parentDoc) {  // After first time thru loop
505       // XXXroc bogus code! scrollPort is relative to the viewport of
506       // this document, but we're intersecting rectangles derived from
507       // multiple documents and assuming they're all in the same coordinate
508       // system. See bug 514117.
509       bounds.IntersectRect(scrollPort, bounds);
510     } else {  // First time through loop
511       bounds = scrollPort;
512     }
513 
514     document = parentDoc = document->GetInProcessParentDocument();
515   }
516 
517   return bounds;
518 }
519 
520 // DocAccessible protected member
AddEventListeners()521 nsresult DocAccessible::AddEventListeners() {
522   nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
523 
524   // We want to add a command observer only if the document is content and has
525   // an editor.
526   if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
527     RefPtr<nsCommandManager> commandManager = docShell->GetCommandManager();
528     if (commandManager) {
529       commandManager->AddCommandObserver(this, "obs_documentCreated");
530     }
531   }
532 
533   SelectionMgr()->AddDocSelectionListener(mPresShell);
534 
535   // Add document observer.
536   mDocumentNode->AddObserver(this);
537   return NS_OK;
538 }
539 
540 // DocAccessible protected member
RemoveEventListeners()541 nsresult DocAccessible::RemoveEventListeners() {
542   // Remove listeners associated with content documents
543   NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
544 
545   if (mDocumentNode) {
546     mDocumentNode->RemoveObserver(this);
547 
548     nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
549     NS_ASSERTION(docShell, "doc should support nsIDocShellTreeItem.");
550 
551     if (docShell) {
552       if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
553         RefPtr<nsCommandManager> commandManager = docShell->GetCommandManager();
554         if (commandManager) {
555           commandManager->RemoveCommandObserver(this, "obs_documentCreated");
556         }
557       }
558     }
559   }
560 
561   if (mScrollWatchTimer) {
562     mScrollWatchTimer->Cancel();
563     mScrollWatchTimer = nullptr;
564     NS_RELEASE_THIS();  // Kung fu death grip
565   }
566 
567   SelectionMgr()->RemoveDocSelectionListener(mPresShell);
568   return NS_OK;
569 }
570 
ScrollTimerCallback(nsITimer * aTimer,void * aClosure)571 void DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure) {
572   DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
573 
574   if (docAcc) {
575     // Dispatch a scroll-end for all entries in table. They have not
576     // been scrolled in at least `kScrollEventInterval`.
577     for (auto iter = docAcc->mLastScrollingDispatch.Iter(); !iter.Done();
578          iter.Next()) {
579       docAcc->DispatchScrollingEvent(iter.Key(),
580                                      nsIAccessibleEvent::EVENT_SCROLLING_END);
581       iter.Remove();
582     }
583 
584     if (docAcc->mScrollWatchTimer) {
585       docAcc->mScrollWatchTimer = nullptr;
586       NS_RELEASE(docAcc);  // Release kung fu death grip
587     }
588   }
589 }
590 
HandleScroll(nsINode * aTarget)591 void DocAccessible::HandleScroll(nsINode* aTarget) {
592   const uint32_t kScrollEventInterval = 100;
593   // If we haven't dispatched a scrolling event for a target in at least
594   // kScrollEventInterval milliseconds, dispatch one now.
595   mLastScrollingDispatch.WithEntryHandle(aTarget, [&](auto&& lastDispatch) {
596     const TimeStamp now = TimeStamp::Now();
597 
598     if (!lastDispatch ||
599         (now - lastDispatch.Data()).ToMilliseconds() >= kScrollEventInterval) {
600       // We can't fire events on a document whose tree isn't constructed yet.
601       if (HasLoadState(eTreeConstructed)) {
602         DispatchScrollingEvent(aTarget, nsIAccessibleEvent::EVENT_SCROLLING);
603       }
604       lastDispatch.InsertOrUpdate(now);
605     }
606   });
607 
608   // If timer callback is still pending, push it 100ms into the future.
609   // When scrolling ends and we don't fire this callback anymore, the
610   // timer callback will fire and dispatch an EVENT_SCROLLING_END.
611   if (mScrollWatchTimer) {
612     mScrollWatchTimer->SetDelay(kScrollEventInterval);
613   } else {
614     NS_NewTimerWithFuncCallback(getter_AddRefs(mScrollWatchTimer),
615                                 ScrollTimerCallback, this, kScrollEventInterval,
616                                 nsITimer::TYPE_ONE_SHOT,
617                                 "a11y::DocAccessible::ScrollPositionDidChange");
618     if (mScrollWatchTimer) {
619       NS_ADDREF_THIS();  // Kung fu death grip
620     }
621   }
622 }
623 
624 ////////////////////////////////////////////////////////////////////////////////
625 // nsIObserver
626 
627 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)628 DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
629                        const char16_t* aData) {
630   if (!nsCRT::strcmp(aTopic, "obs_documentCreated")) {
631     // State editable will now be set, readonly is now clear
632     // Normally we only fire delayed events created from the node, not an
633     // accessible object. See the AccStateChangeEvent constructor for details
634     // about this exceptional case.
635     RefPtr<AccEvent> event =
636         new AccStateChangeEvent(this, states::EDITABLE, true);
637     FireDelayedEvent(event);
638   }
639 
640   return NS_OK;
641 }
642 
643 ////////////////////////////////////////////////////////////////////////////////
644 // nsIAccessiblePivotObserver
645 
646 NS_IMETHODIMP
OnPivotChanged(nsIAccessiblePivot * aPivot,nsIAccessible * aOldAccessible,int32_t aOldStart,int32_t aOldEnd,nsIAccessible * aNewAccessible,int32_t aNewStart,int32_t aNewEnd,PivotMoveReason aReason,TextBoundaryType aBoundaryType,bool aIsFromUserInput)647 DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
648                               nsIAccessible* aOldAccessible, int32_t aOldStart,
649                               int32_t aOldEnd, nsIAccessible* aNewAccessible,
650                               int32_t aNewStart, int32_t aNewEnd,
651                               PivotMoveReason aReason,
652                               TextBoundaryType aBoundaryType,
653                               bool aIsFromUserInput) {
654   RefPtr<AccEvent> event = new AccVCChangeEvent(
655       this, (aOldAccessible ? aOldAccessible->ToInternalAccessible() : nullptr),
656       aOldStart, aOldEnd,
657       (aNewAccessible ? aNewAccessible->ToInternalAccessible() : nullptr),
658       aNewStart, aNewEnd, aReason, aBoundaryType,
659       aIsFromUserInput ? eFromUserInput : eNoUserInput);
660   nsEventShell::FireEvent(event);
661 
662   return NS_OK;
663 }
664 
665 ////////////////////////////////////////////////////////////////////////////////
666 // nsIDocumentObserver
667 
668 NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)669 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
670 
671 void DocAccessible::AttributeWillChange(dom::Element* aElement,
672                                         int32_t aNameSpaceID,
673                                         nsAtom* aAttribute, int32_t aModType) {
674   LocalAccessible* accessible = GetAccessible(aElement);
675   if (!accessible) {
676     if (aElement != mContent) return;
677 
678     accessible = this;
679   }
680 
681   // Update dependent IDs cache. Take care of elements that are accessible
682   // because dependent IDs cache doesn't contain IDs from non accessible
683   // elements.
684   if (aModType != dom::MutationEvent_Binding::ADDITION) {
685     RemoveDependentIDsFor(accessible, aAttribute);
686   }
687 
688   if (aAttribute == nsGkAtoms::id) {
689     RelocateARIAOwnedIfNeeded(aElement);
690   }
691 
692   // Store the ARIA attribute old value so that it can be used after
693   // attribute change. Note, we assume there's no nested ARIA attribute
694   // changes. If this happens then we should end up with keeping a stack of
695   // old values.
696 
697   // XXX TODO: bugs 472142, 472143.
698   // Here we will want to cache whatever attribute values we are interested
699   // in, such as the existence of aria-pressed for button (so we know if we
700   // need to newly expose it as a toggle button) etc.
701   if (aAttribute == nsGkAtoms::aria_checked ||
702       aAttribute == nsGkAtoms::aria_pressed) {
703     mARIAAttrOldValue = (aModType != dom::MutationEvent_Binding::ADDITION)
704                             ? nsAccUtils::GetARIAToken(aElement, aAttribute)
705                             : nullptr;
706     return;
707   }
708 
709   if (aAttribute == nsGkAtoms::aria_disabled || aAttribute == nsGkAtoms::href ||
710       aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::tabindex ||
711       aAttribute == nsGkAtoms::contenteditable) {
712     mPrevStateBits = accessible->State();
713   }
714 }
715 
NativeAnonymousChildListChange(nsIContent * aContent,bool aIsRemove)716 void DocAccessible::NativeAnonymousChildListChange(nsIContent* aContent,
717                                                    bool aIsRemove) {
718   if (aIsRemove) {
719 #ifdef A11Y_LOG
720     if (logging::IsEnabled(logging::eTree)) {
721       logging::MsgBegin("TREE", "Anonymous content removed; doc: %p", this);
722       logging::Node("node", aContent);
723       logging::MsgEnd();
724     }
725 #endif
726 
727     ContentRemoved(aContent);
728   }
729 }
730 
AttributeChanged(dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)731 void DocAccessible::AttributeChanged(dom::Element* aElement,
732                                      int32_t aNameSpaceID, nsAtom* aAttribute,
733                                      int32_t aModType,
734                                      const nsAttrValue* aOldValue) {
735   NS_ASSERTION(!IsDefunct(),
736                "Attribute changed called on defunct document accessible!");
737 
738   // Proceed even if the element is not accessible because element may become
739   // accessible if it gets certain attribute.
740   if (UpdateAccessibleOnAttrChange(aElement, aAttribute)) return;
741 
742   // Update the accessible tree on aria-hidden change. Make sure to not create
743   // a tree under aria-hidden='true'.
744   if (aAttribute == nsGkAtoms::aria_hidden) {
745     if (aria::HasDefinedARIAHidden(aElement)) {
746       ContentRemoved(aElement);
747     } else {
748       ContentInserted(aElement, aElement->GetNextSibling());
749     }
750     return;
751   }
752 
753   // Ignore attribute change if the element doesn't have an accessible (at all
754   // or still) iff the element is not a root content of this document accessible
755   // (which is treated as attribute change on this document accessible).
756   // Note: we don't bail if all the content hasn't finished loading because
757   // these attributes are changing for a loaded part of the content.
758   LocalAccessible* accessible = GetAccessible(aElement);
759   if (!accessible) {
760     if (mContent != aElement) return;
761 
762     accessible = this;
763   }
764 
765   MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
766              "DOM attribute change on an accessible detached from the tree");
767 
768   // Fire accessible events iff there's an accessible, otherwise we consider
769   // the accessible state wasn't changed, i.e. its state is initial state.
770   AttributeChangedImpl(accessible, aNameSpaceID, aAttribute, aModType);
771 
772   // Update dependent IDs cache. Take care of accessible elements because no
773   // accessible element means either the element is not accessible at all or
774   // its accessible will be created later. It doesn't make sense to keep
775   // dependent IDs for non accessible elements. For the second case we'll update
776   // dependent IDs cache when its accessible is created.
777   if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
778       aModType == dom::MutationEvent_Binding::ADDITION) {
779     AddDependentIDsFor(accessible, aAttribute);
780   }
781 }
782 
783 // DocAccessible protected member
AttributeChangedImpl(LocalAccessible * aAccessible,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)784 void DocAccessible::AttributeChangedImpl(LocalAccessible* aAccessible,
785                                          int32_t aNameSpaceID,
786                                          nsAtom* aAttribute, int32_t aModType) {
787   // Fire accessible event after short timer, because we need to wait for
788   // DOM attribute & resulting layout to actually change. Otherwise,
789   // assistive technology will retrieve the wrong state/value/selection info.
790 
791   // XXX todo
792   // We still need to handle special HTML cases here
793   // For example, if an <img>'s usemap attribute is modified
794   // Otherwise it may just be a state change, for example an object changing
795   // its visibility
796   //
797   // XXX todo: report aria state changes for "undefined" literal value changes
798   // filed as bug 472142
799   //
800   // XXX todo:  invalidate accessible when aria state changes affect exposed
801   // role filed as bug 472143
802 
803   // Universal boolean properties that don't require a role. Fire the state
804   // change when disabled or aria-disabled attribute is set.
805   // Note. Checking the XUL or HTML namespace would not seem to gain us
806   // anything, because disabled attribute really is going to mean the same
807   // thing in any namespace.
808   // Note. We use the attribute instead of the disabled state bit because
809   // ARIA's aria-disabled does not affect the disabled state bit.
810   if (aAttribute == nsGkAtoms::disabled ||
811       aAttribute == nsGkAtoms::aria_disabled) {
812     // disabled can affect focusable state
813     aAccessible->MaybeFireFocusableStateChange(
814         (mPrevStateBits & states::FOCUSABLE) != 0);
815 
816     // Do nothing if state wasn't changed (like @aria-disabled was removed but
817     // @disabled is still presented).
818     uint64_t unavailableState = (aAccessible->State() & states::UNAVAILABLE);
819     if ((mPrevStateBits & states::UNAVAILABLE) == unavailableState) {
820       return;
821     }
822 
823     RefPtr<AccEvent> enabledChangeEvent = new AccStateChangeEvent(
824         aAccessible, states::ENABLED, !unavailableState);
825     FireDelayedEvent(enabledChangeEvent);
826 
827     RefPtr<AccEvent> sensitiveChangeEvent = new AccStateChangeEvent(
828         aAccessible, states::SENSITIVE, !unavailableState);
829     FireDelayedEvent(sensitiveChangeEvent);
830 
831     return;
832   }
833 
834   if (aAttribute == nsGkAtoms::tabindex) {
835     // Fire a focusable state change event if the previous state was different.
836     // It may be the same if tabindex is on a redundantly focusable element.
837     aAccessible->MaybeFireFocusableStateChange(
838         (mPrevStateBits & states::FOCUSABLE));
839     return;
840   }
841 
842   // When a details object has its open attribute changed
843   // we should fire a state-change event on the accessible of
844   // its main summary
845   if (aAttribute == nsGkAtoms::open) {
846     // FromDetails checks if the given accessible belongs to
847     // a details frame and also locates the accessible of its
848     // main summary.
849     if (HTMLSummaryAccessible* summaryAccessible =
850             HTMLSummaryAccessible::FromDetails(aAccessible)) {
851       RefPtr<AccEvent> expandedChangeEvent =
852           new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
853       FireDelayedEvent(expandedChangeEvent);
854       return;
855     }
856   }
857 
858   // Check for namespaced ARIA attribute
859   if (aNameSpaceID == kNameSpaceID_None) {
860     // Check for hyphenated aria-foo property?
861     if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) {
862       ARIAAttributeChanged(aAccessible, aAttribute);
863     }
864   }
865 
866   // Fire name change and description change events. XXX: it's not complete and
867   // dupes the code logic of accessible name and description calculation, we do
868   // that for performance reasons.
869   if (aAttribute == nsGkAtoms::aria_label) {
870     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
871     return;
872   }
873 
874   dom::Element* elm = aAccessible->GetContent()->AsElement();
875   if (aAttribute == nsGkAtoms::aria_describedby) {
876     FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
877     if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
878         aModType == dom::MutationEvent_Binding::ADDITION) {
879       // The subtrees of the new aria-describedby targets might be used to
880       // compute the description for aAccessible. Therefore, we need to set
881       // the eHasDescriptionDependent flag on all Accessibles in these subtrees.
882       IDRefsIterator iter(this, aAccessible->Elm(),
883                           nsGkAtoms::aria_describedby);
884       while (LocalAccessible* target = iter.Next()) {
885         Pivot pivot(target);
886         LocalAccInSameDocRule rule;
887         for (AccessibleOrProxy anchor(target); !anchor.IsNull();
888              anchor = pivot.Next(anchor, rule)) {
889           LocalAccessible* acc = anchor.AsAccessible();
890           MOZ_ASSERT(acc);
891           acc->mContextFlags |= eHasDescriptionDependent;
892         }
893       }
894     }
895     return;
896   }
897 
898   if (aAttribute == nsGkAtoms::aria_labelledby &&
899       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
900     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
901     if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
902         aModType == dom::MutationEvent_Binding::ADDITION) {
903       // The subtrees of the new aria-labelledby targets might be used to
904       // compute the name for aAccessible. Therefore, we need to set
905       // the eHasNameDependent flag on all Accessibles in these subtrees.
906       IDRefsIterator iter(this, aAccessible->Elm(), nsGkAtoms::aria_labelledby);
907       while (LocalAccessible* target = iter.Next()) {
908         Pivot pivot(target);
909         LocalAccInSameDocRule rule;
910         for (AccessibleOrProxy anchor(target); !anchor.IsNull();
911              anchor = pivot.Next(anchor, rule)) {
912           LocalAccessible* acc = anchor.AsAccessible();
913           MOZ_ASSERT(acc);
914           acc->mContextFlags |= eHasNameDependent;
915         }
916       }
917     }
918     return;
919   }
920 
921   if (aAttribute == nsGkAtoms::alt &&
922       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
923       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
924     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
925     return;
926   }
927 
928   if (aAttribute == nsGkAtoms::title) {
929     if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
930         !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
931         !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
932       FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
933       return;
934     }
935 
936     if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby)) {
937       FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
938                        aAccessible);
939     }
940 
941     return;
942   }
943 
944   // These attributes can change whether or not a table is a layout table.
945   // We currently cache that information on Mac, so we fire a
946   // EVENT_OBJECT_ATTRIBUTE_CHANGED, which Mac listens for, to invalidate.
947   if (aAccessible->IsTable() || aAccessible->IsTableRow() ||
948       aAccessible->IsTableCell()) {
949     if (aAttribute == nsGkAtoms::summary || aAttribute == nsGkAtoms::headers ||
950         aAttribute == nsGkAtoms::scope || aAttribute == nsGkAtoms::abbr) {
951       FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
952                        aAccessible);
953     }
954   }
955 
956   if (aAttribute == nsGkAtoms::aria_busy) {
957     bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
958                                  eCaseMatters);
959     RefPtr<AccEvent> event =
960         new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
961     FireDelayedEvent(event);
962     return;
963   }
964 
965   if (aAttribute == nsGkAtoms::aria_multiline) {
966     bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
967                                  eCaseMatters);
968     RefPtr<AccEvent> event =
969         new AccStateChangeEvent(aAccessible, states::MULTI_LINE, isOn);
970     FireDelayedEvent(event);
971     return;
972   }
973 
974   if (aAttribute == nsGkAtoms::id) {
975     RelocateARIAOwnedIfNeeded(elm);
976     ARIAActiveDescendantIDMaybeMoved(elm);
977   }
978 
979   // ARIA or XUL selection
980   if ((aAccessible->GetContent()->IsXULElement() &&
981        aAttribute == nsGkAtoms::selected) ||
982       aAttribute == nsGkAtoms::aria_selected) {
983     LocalAccessible* widget =
984         nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
985     if (widget) {
986       AccSelChangeEvent::SelChangeType selChangeType =
987           elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
988                            eCaseMatters)
989               ? AccSelChangeEvent::eSelectionAdd
990               : AccSelChangeEvent::eSelectionRemove;
991 
992       RefPtr<AccEvent> event =
993           new AccSelChangeEvent(widget, aAccessible, selChangeType);
994       FireDelayedEvent(event);
995     }
996 
997     return;
998   }
999 
1000   if (aAttribute == nsGkAtoms::contenteditable) {
1001     RefPtr<AccEvent> editableChangeEvent =
1002         new AccStateChangeEvent(aAccessible, states::EDITABLE);
1003     FireDelayedEvent(editableChangeEvent);
1004     // Fire a focusable state change event if the previous state was different.
1005     // It may be the same if contenteditable is set on a node that doesn't
1006     // support it. Like an <input>.
1007     aAccessible->MaybeFireFocusableStateChange(
1008         (mPrevStateBits & states::FOCUSABLE));
1009     return;
1010   }
1011 
1012   if (aAttribute == nsGkAtoms::value) {
1013     if (aAccessible->IsProgress()) {
1014       FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
1015     }
1016     return;
1017   }
1018 
1019   if (aModType == dom::MutationEvent_Binding::REMOVAL ||
1020       aModType == dom::MutationEvent_Binding::ADDITION) {
1021     if (aAttribute == nsGkAtoms::href) {
1022       if (aAccessible->IsHTMLLink() &&
1023           !nsCoreUtils::HasClickListener(aAccessible->GetContent())) {
1024         RefPtr<AccEvent> linkedChangeEvent =
1025             new AccStateChangeEvent(aAccessible, states::LINKED);
1026         FireDelayedEvent(linkedChangeEvent);
1027         // Fire a focusable state change event if the previous state was
1028         // different. It may be the same if there is tabindex on this link.
1029         aAccessible->MaybeFireFocusableStateChange(
1030             (mPrevStateBits & states::FOCUSABLE));
1031       }
1032     }
1033   }
1034 }
1035 
1036 // DocAccessible protected member
ARIAAttributeChanged(LocalAccessible * aAccessible,nsAtom * aAttribute)1037 void DocAccessible::ARIAAttributeChanged(LocalAccessible* aAccessible,
1038                                          nsAtom* aAttribute) {
1039   // Note: For universal/global ARIA states and properties we don't care if
1040   // there is an ARIA role present or not.
1041 
1042   if (aAttribute == nsGkAtoms::aria_required) {
1043     RefPtr<AccEvent> event =
1044         new AccStateChangeEvent(aAccessible, states::REQUIRED);
1045     FireDelayedEvent(event);
1046     return;
1047   }
1048 
1049   if (aAttribute == nsGkAtoms::aria_invalid) {
1050     RefPtr<AccEvent> event =
1051         new AccStateChangeEvent(aAccessible, states::INVALID);
1052     FireDelayedEvent(event);
1053     return;
1054   }
1055 
1056   // The activedescendant universal property redirects accessible focus events
1057   // to the element with the id that activedescendant points to. Make sure
1058   // the tree up to date before processing. In other words, when a node has just
1059   // been inserted, the tree won't be up to date yet, so we must always schedule
1060   // an async notification so that a newly inserted node will be present in
1061   // the tree.
1062   if (aAttribute == nsGkAtoms::aria_activedescendant) {
1063     mNotificationController
1064         ->ScheduleNotification<DocAccessible, LocalAccessible>(
1065             this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
1066     return;
1067   }
1068 
1069   // We treat aria-expanded as a global ARIA state for historical reasons
1070   if (aAttribute == nsGkAtoms::aria_expanded) {
1071     RefPtr<AccEvent> event =
1072         new AccStateChangeEvent(aAccessible, states::EXPANDED);
1073     FireDelayedEvent(event);
1074     return;
1075   }
1076 
1077   // For aria attributes like drag and drop changes we fire a generic attribute
1078   // change event; at least until native API comes up with a more meaningful
1079   // event.
1080   uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
1081   if (!(attrFlags & ATTR_BYPASSOBJ)) {
1082     RefPtr<AccEvent> event =
1083         new AccObjectAttrChangedEvent(aAccessible, aAttribute);
1084     FireDelayedEvent(event);
1085   }
1086 
1087   dom::Element* elm = aAccessible->GetContent()->AsElement();
1088 
1089   if (aAttribute == nsGkAtoms::aria_checked ||
1090       (aAccessible->IsButton() && aAttribute == nsGkAtoms::aria_pressed)) {
1091     const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked)
1092                                 ? states::CHECKED
1093                                 : states::PRESSED;
1094     RefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
1095     FireDelayedEvent(event);
1096 
1097     bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
1098     bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
1099                                     nsGkAtoms::mixed, eCaseMatters);
1100     if (isMixed != wasMixed) {
1101       RefPtr<AccEvent> event =
1102           new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
1103       FireDelayedEvent(event);
1104     }
1105     return;
1106   }
1107 
1108   if (aAttribute == nsGkAtoms::aria_readonly) {
1109     RefPtr<AccEvent> event =
1110         new AccStateChangeEvent(aAccessible, states::READONLY);
1111     FireDelayedEvent(event);
1112     return;
1113   }
1114 
1115   // Fire text value change event whenever aria-valuetext is changed.
1116   if (aAttribute == nsGkAtoms::aria_valuetext) {
1117     FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
1118     return;
1119   }
1120 
1121   // Fire numeric value change event when aria-valuenow is changed and
1122   // aria-valuetext is empty
1123   if (aAttribute == nsGkAtoms::aria_valuenow &&
1124       (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
1125        elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
1126                         nsGkAtoms::_empty, eCaseMatters))) {
1127     FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
1128     return;
1129   }
1130 
1131   if (aAttribute == nsGkAtoms::aria_current) {
1132     RefPtr<AccEvent> event =
1133         new AccStateChangeEvent(aAccessible, states::CURRENT);
1134     FireDelayedEvent(event);
1135     return;
1136   }
1137 
1138   if (aAttribute == nsGkAtoms::aria_haspopup) {
1139     RefPtr<AccEvent> event =
1140         new AccStateChangeEvent(aAccessible, states::HASPOPUP);
1141     FireDelayedEvent(event);
1142     return;
1143   }
1144 
1145   if (aAttribute == nsGkAtoms::aria_owns) {
1146     mNotificationController->ScheduleRelocation(aAccessible);
1147   }
1148 }
1149 
ARIAActiveDescendantChanged(LocalAccessible * aAccessible)1150 void DocAccessible::ARIAActiveDescendantChanged(LocalAccessible* aAccessible) {
1151   nsIContent* elm = aAccessible->GetContent();
1152   if (elm && elm->IsElement() && aAccessible->IsActiveWidget()) {
1153     nsAutoString id;
1154     if (elm->AsElement()->GetAttr(kNameSpaceID_None,
1155                                   nsGkAtoms::aria_activedescendant, id)) {
1156       dom::Element* activeDescendantElm = IDRefsIterator::GetElem(elm, id);
1157       if (activeDescendantElm) {
1158         LocalAccessible* activeDescendant = GetAccessible(activeDescendantElm);
1159         if (activeDescendant) {
1160           FocusMgr()->ActiveItemChanged(activeDescendant, false);
1161 #ifdef A11Y_LOG
1162           if (logging::IsEnabled(logging::eFocus)) {
1163             logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
1164                                               activeDescendant);
1165           }
1166 #endif
1167           return;
1168         }
1169       }
1170     }
1171 
1172     // aria-activedescendant was cleared or changed to a non-existent node.
1173     // Move focus back to the element itself.
1174     FocusMgr()->ActiveItemChanged(aAccessible, false);
1175 #ifdef A11Y_LOG
1176     if (logging::IsEnabled(logging::eFocus)) {
1177       logging::ActiveItemChangeCausedBy("ARIA activedescedant cleared",
1178                                         aAccessible);
1179     }
1180 #endif
1181   }
1182 }
1183 
ContentAppended(nsIContent * aFirstNewContent)1184 void DocAccessible::ContentAppended(nsIContent* aFirstNewContent) {}
1185 
ContentStateChanged(dom::Document * aDocument,nsIContent * aContent,EventStates aStateMask)1186 void DocAccessible::ContentStateChanged(dom::Document* aDocument,
1187                                         nsIContent* aContent,
1188                                         EventStates aStateMask) {
1189   LocalAccessible* accessible = GetAccessible(aContent);
1190   if (!accessible) return;
1191 
1192   if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
1193     LocalAccessible* widget = accessible->ContainerWidget();
1194     if (widget && widget->IsSelect()) {
1195       AccSelChangeEvent::SelChangeType selChangeType =
1196           aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED)
1197               ? AccSelChangeEvent::eSelectionAdd
1198               : AccSelChangeEvent::eSelectionRemove;
1199       RefPtr<AccEvent> event =
1200           new AccSelChangeEvent(widget, accessible, selChangeType);
1201       FireDelayedEvent(event);
1202       return;
1203     }
1204 
1205     RefPtr<AccEvent> event = new AccStateChangeEvent(
1206         accessible, states::CHECKED,
1207         aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
1208     FireDelayedEvent(event);
1209   }
1210 
1211   if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
1212     RefPtr<AccEvent> event =
1213         new AccStateChangeEvent(accessible, states::INVALID, true);
1214     FireDelayedEvent(event);
1215   }
1216 
1217   if (aStateMask.HasState(NS_EVENT_STATE_REQUIRED)) {
1218     RefPtr<AccEvent> event =
1219         new AccStateChangeEvent(accessible, states::REQUIRED);
1220     FireDelayedEvent(event);
1221   }
1222 
1223   if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
1224     RefPtr<AccEvent> event =
1225         new AccStateChangeEvent(accessible, states::TRAVERSED, true);
1226     FireDelayedEvent(event);
1227   }
1228 }
1229 
CharacterDataWillChange(nsIContent * aContent,const CharacterDataChangeInfo &)1230 void DocAccessible::CharacterDataWillChange(nsIContent* aContent,
1231                                             const CharacterDataChangeInfo&) {}
1232 
CharacterDataChanged(nsIContent * aContent,const CharacterDataChangeInfo &)1233 void DocAccessible::CharacterDataChanged(nsIContent* aContent,
1234                                          const CharacterDataChangeInfo&) {}
1235 
ContentInserted(nsIContent * aChild)1236 void DocAccessible::ContentInserted(nsIContent* aChild) {}
1237 
ContentRemoved(nsIContent * aChildNode,nsIContent * aPreviousSiblingNode)1238 void DocAccessible::ContentRemoved(nsIContent* aChildNode,
1239                                    nsIContent* aPreviousSiblingNode) {
1240 #ifdef A11Y_LOG
1241   if (logging::IsEnabled(logging::eTree)) {
1242     logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
1243     logging::Node("container node", aChildNode->GetParent());
1244     logging::Node("content node", aChildNode);
1245     logging::MsgEnd();
1246   }
1247 #endif
1248   // This one and content removal notification from layout may result in
1249   // double processing of same subtrees. If it pops up in profiling, then
1250   // consider reusing a document node cache to reject these notifications early.
1251   ContentRemoved(aChildNode);
1252 }
1253 
ParentChainChanged(nsIContent * aContent)1254 void DocAccessible::ParentChainChanged(nsIContent* aContent) {}
1255 
1256 ////////////////////////////////////////////////////////////////////////////////
1257 // LocalAccessible
1258 
1259 #ifdef A11Y_LOG
HandleAccEvent(AccEvent * aEvent)1260 nsresult DocAccessible::HandleAccEvent(AccEvent* aEvent) {
1261   if (logging::IsEnabled(logging::eDocLoad)) {
1262     logging::DocLoadEventHandled(aEvent);
1263   }
1264 
1265   return HyperTextAccessible::HandleAccEvent(aEvent);
1266 }
1267 #endif
1268 
1269 ////////////////////////////////////////////////////////////////////////////////
1270 // Public members
1271 
PresContext() const1272 nsPresContext* DocAccessible::PresContext() const {
1273   return mPresShell->GetPresContext();
1274 }
1275 
GetNativeWindow() const1276 void* DocAccessible::GetNativeWindow() const {
1277   if (!mPresShell) {
1278     return nullptr;
1279   }
1280 
1281   nsViewManager* vm = mPresShell->GetViewManager();
1282   if (!vm) return nullptr;
1283 
1284   nsCOMPtr<nsIWidget> widget;
1285   vm->GetRootWidget(getter_AddRefs(widget));
1286   if (widget) return widget->GetNativeData(NS_NATIVE_WINDOW);
1287 
1288   return nullptr;
1289 }
1290 
GetAccessibleByUniqueIDInSubtree(void * aUniqueID)1291 LocalAccessible* DocAccessible::GetAccessibleByUniqueIDInSubtree(
1292     void* aUniqueID) {
1293   LocalAccessible* child = GetAccessibleByUniqueID(aUniqueID);
1294   if (child) return child;
1295 
1296   uint32_t childDocCount = mChildDocuments.Length();
1297   for (uint32_t childDocIdx = 0; childDocIdx < childDocCount; childDocIdx++) {
1298     DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
1299     child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
1300     if (child) return child;
1301   }
1302 
1303   return nullptr;
1304 }
1305 
GetAccessibleOrContainer(nsINode * aNode,bool aNoContainerIfPruned) const1306 LocalAccessible* DocAccessible::GetAccessibleOrContainer(
1307     nsINode* aNode, bool aNoContainerIfPruned) const {
1308   if (!aNode || !aNode->GetComposedDoc()) {
1309     return nullptr;
1310   }
1311 
1312   nsINode* start = aNode;
1313   if (auto* shadowRoot = dom::ShadowRoot::FromNode(aNode)) {
1314     // This can happen, for example, when called within
1315     // SelectionManager::ProcessSelectionChanged due to focusing a direct
1316     // child of a shadow root.
1317     // GetFlattenedTreeParent works on children of a shadow root, but not the
1318     // shadow root itself.
1319     start = shadowRoot->GetHost();
1320     if (!start) {
1321       return nullptr;
1322     }
1323   }
1324 
1325   for (nsINode* currNode : dom::InclusiveFlatTreeAncestors(*start)) {
1326     // No container if is inside of aria-hidden subtree.
1327     if (aNoContainerIfPruned && currNode->IsElement() &&
1328         aria::HasDefinedARIAHidden(currNode->AsElement())) {
1329       return nullptr;
1330     }
1331 
1332     // Check if node is in an unselected deck panel
1333     if (aNoContainerIfPruned && currNode->IsXULElement()) {
1334       if (nsIFrame* frame = currNode->AsContent()->GetPrimaryFrame()) {
1335         nsDeckFrame* deckFrame = do_QueryFrame(frame->GetParent());
1336         if (deckFrame && deckFrame->GetSelectedBox() != frame) {
1337           // If deck is not a <tabpanels>, return null
1338           nsIContent* parentFrameContent = deckFrame->GetContent();
1339           if (!parentFrameContent ||
1340               !parentFrameContent->IsXULElement(nsGkAtoms::tabpanels)) {
1341             return nullptr;
1342           }
1343         }
1344       }
1345     }
1346 
1347     // Check if node is in zero-sized map
1348     if (aNoContainerIfPruned && currNode->IsHTMLElement(nsGkAtoms::map)) {
1349       if (nsIFrame* frame = currNode->AsContent()->GetPrimaryFrame()) {
1350         if (nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame->GetParent())
1351                 .IsEmpty()) {
1352           return nullptr;
1353         }
1354       }
1355     }
1356 
1357     if (LocalAccessible* accessible = GetAccessible(currNode)) {
1358       return accessible;
1359     }
1360   }
1361 
1362   return nullptr;
1363 }
1364 
GetContainerAccessible(nsINode * aNode) const1365 LocalAccessible* DocAccessible::GetContainerAccessible(nsINode* aNode) const {
1366   return aNode ? GetAccessibleOrContainer(aNode->GetFlattenedTreeParentNode())
1367                : nullptr;
1368 }
1369 
GetAccessibleOrDescendant(nsINode * aNode) const1370 LocalAccessible* DocAccessible::GetAccessibleOrDescendant(
1371     nsINode* aNode) const {
1372   LocalAccessible* acc = GetAccessible(aNode);
1373   if (acc) return acc;
1374 
1375   if (aNode == mContent || aNode == mDocumentNode->GetRootElement()) {
1376     // If the node is the doc's body or root element, return the doc accessible.
1377     return const_cast<DocAccessible*>(this);
1378   }
1379 
1380   acc = GetContainerAccessible(aNode);
1381   if (acc) {
1382     TreeWalker walker(acc, aNode->AsContent(),
1383                       TreeWalker::eWalkCache | TreeWalker::eScoped);
1384     return walker.Next();
1385   }
1386 
1387   return nullptr;
1388 }
1389 
BindToDocument(LocalAccessible * aAccessible,const nsRoleMapEntry * aRoleMapEntry)1390 void DocAccessible::BindToDocument(LocalAccessible* aAccessible,
1391                                    const nsRoleMapEntry* aRoleMapEntry) {
1392   // Put into DOM node cache.
1393   if (aAccessible->IsNodeMapEntry()) {
1394     mNodeToAccessibleMap.InsertOrUpdate(aAccessible->GetNode(), aAccessible);
1395   }
1396 
1397   // Put into unique ID cache.
1398   mAccessibleCache.InsertOrUpdate(aAccessible->UniqueID(), RefPtr{aAccessible});
1399 
1400   aAccessible->SetRoleMapEntry(aRoleMapEntry);
1401 
1402   if (aAccessible->HasOwnContent()) {
1403     AddDependentIDsFor(aAccessible);
1404 
1405     nsIContent* content = aAccessible->GetContent();
1406     if (content->IsElement() && content->AsElement()->HasAttr(
1407                                     kNameSpaceID_None, nsGkAtoms::aria_owns)) {
1408       mNotificationController->ScheduleRelocation(aAccessible);
1409     }
1410   }
1411 }
1412 
UnbindFromDocument(LocalAccessible * aAccessible)1413 void DocAccessible::UnbindFromDocument(LocalAccessible* aAccessible) {
1414   NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
1415                "Unbinding the unbound accessible!");
1416 
1417   // Fire focus event on accessible having DOM focus if last focus was removed
1418   // from the tree.
1419   if (FocusMgr()->WasLastFocused(aAccessible)) {
1420     FocusMgr()->ActiveItemChanged(nullptr);
1421 #ifdef A11Y_LOG
1422     if (logging::IsEnabled(logging::eFocus)) {
1423       logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
1424     }
1425 #endif
1426   }
1427 
1428   // Remove an accessible from node-to-accessible map if it exists there.
1429   if (aAccessible->IsNodeMapEntry() &&
1430       mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible) {
1431     mNodeToAccessibleMap.Remove(aAccessible->GetNode());
1432   }
1433 
1434   aAccessible->mStateFlags |= eIsNotInDocument;
1435 
1436   // Update XPCOM part.
1437   xpcAccessibleDocument* xpcDoc = GetAccService()->GetCachedXPCDocument(this);
1438   if (xpcDoc) xpcDoc->NotifyOfShutdown(aAccessible);
1439 
1440   void* uniqueID = aAccessible->UniqueID();
1441 
1442   NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
1443   aAccessible->Shutdown();
1444 
1445   mAccessibleCache.Remove(uniqueID);
1446 }
1447 
ContentInserted(nsIContent * aStartChildNode,nsIContent * aEndChildNode)1448 void DocAccessible::ContentInserted(nsIContent* aStartChildNode,
1449                                     nsIContent* aEndChildNode) {
1450   // Ignore content insertions until we constructed accessible tree. Otherwise
1451   // schedule tree update on content insertion after layout.
1452   if (!mNotificationController || !HasLoadState(eTreeConstructed)) {
1453     return;
1454   }
1455 
1456   // The frame constructor guarantees that only ranges with the same parent
1457   // arrive here in presence of dynamic changes to the page, see
1458   // nsCSSFrameConstructor::IssueSingleInsertNotifications' callers.
1459   nsINode* parent = aStartChildNode->GetFlattenedTreeParentNode();
1460   if (!parent) {
1461     return;
1462   }
1463 
1464   LocalAccessible* container = AccessibleOrTrueContainer(parent);
1465   if (!container) {
1466     return;
1467   }
1468 
1469   AutoTArray<nsCOMPtr<nsIContent>, 10> list;
1470   for (nsIContent* node = aStartChildNode; node != aEndChildNode;
1471        node = node->GetNextSibling()) {
1472     MOZ_ASSERT(parent == node->GetFlattenedTreeParentNode());
1473     if (PruneOrInsertSubtree(node)) {
1474       list.AppendElement(node);
1475     }
1476   }
1477 
1478   mNotificationController->ScheduleContentInsertion(container, list);
1479 }
1480 
PruneOrInsertSubtree(nsIContent * aRoot)1481 bool DocAccessible::PruneOrInsertSubtree(nsIContent* aRoot) {
1482   bool insert = false;
1483 
1484   // In the case that we are, or are in, a shadow host, we need to assure
1485   // some accessibles are removed if they are not rendered anymore.
1486   nsIContent* shadowHost =
1487       aRoot->GetShadowRoot() ? aRoot : aRoot->GetContainingShadowHost();
1488   if (shadowHost) {
1489     dom::ExplicitChildIterator iter(shadowHost);
1490 
1491     // Check all explicit children in the host, if they are not slotted
1492     // then remove their accessibles and subtrees.
1493     while (nsIContent* childNode = iter.GetNextChild()) {
1494       if (!childNode->GetPrimaryFrame() &&
1495           !nsCoreUtils::IsDisplayContents(childNode)) {
1496         ContentRemoved(childNode);
1497       }
1498     }
1499 
1500     // If this is a slot, check to see if its fallback content is rendered,
1501     // if not - remove it.
1502     if (aRoot->IsHTMLElement(nsGkAtoms::slot)) {
1503       for (nsIContent* childNode = aRoot->GetFirstChild(); childNode;
1504            childNode = childNode->GetNextSibling()) {
1505         if (!childNode->GetPrimaryFrame() &&
1506             !nsCoreUtils::IsDisplayContents(childNode)) {
1507           ContentRemoved(childNode);
1508         }
1509       }
1510     }
1511   }
1512 
1513   // If we already have an accessible, check if we need to remove it, recreate
1514   // it, or keep it in place.
1515   LocalAccessible* acc = GetAccessible(aRoot);
1516   if (acc) {
1517     MOZ_ASSERT(aRoot == acc->GetContent(),
1518                "LocalAccessible has differing content!");
1519 #ifdef A11Y_LOG
1520     if (logging::IsEnabled(logging::eTree)) {
1521       logging::MsgBegin(
1522           "TREE", "inserted content already has accessible; doc: %p", this);
1523       logging::Node("content node", aRoot);
1524       logging::AccessibleInfo("accessible node", acc);
1525       logging::MsgEnd();
1526     }
1527 #endif
1528 
1529     nsIFrame* frame = acc->GetFrame();
1530 
1531     // LocalAccessible has no frame and it's not display:contents. Remove it.
1532     // As well as removing the a11y subtree, we must also remove Accessibles
1533     // for DOM descendants, since some of these might be relocated Accessibles
1534     // and their DOM nodes are now hidden as well.
1535     if (!frame && !nsCoreUtils::IsDisplayContents(aRoot)) {
1536       ContentRemoved(aRoot);
1537       return false;
1538     }
1539 
1540     // If it's a XULLabel it was probably reframed because a `value` attribute
1541     // was added. The accessible creates its text leaf upon construction, so we
1542     // need to recreate. Remove it, and schedule for reconstruction.
1543     if (acc->IsXULLabel()) {
1544       ContentRemoved(acc);
1545       return true;
1546     }
1547 
1548     // It is a broken image that is being reframed because it either got
1549     // or lost an `alt` tag that would rerender this node as text.
1550     if (frame && (acc->IsImage() != (frame->AccessibleType() == eImageType))) {
1551       ContentRemoved(aRoot);
1552       return true;
1553     }
1554 
1555     // If the frame is an OuterDoc frame but this isn't an OuterDocAccessible,
1556     // we need to recreate the LocalAccessible. This can happen for embed or
1557     // object elements if their embedded content changes to be web content.
1558     if (frame && !acc->IsOuterDoc() &&
1559         frame->AccessibleType() == eOuterDocType) {
1560       ContentRemoved(aRoot);
1561       return true;
1562     }
1563 
1564     // If the content is focused, and is being re-framed, reset the selection
1565     // listener for the node because the previous selection listener is on the
1566     // old frame.
1567     if (aRoot->IsElement() && FocusMgr()->HasDOMFocus(aRoot)) {
1568       SelectionMgr()->SetControlSelectionListener(aRoot->AsElement());
1569     }
1570 
1571     // If the accessible is a table, or table part, its layout table
1572     // status may have changed. We need to invalidate the associated
1573     // cache, which listens for the following event.
1574     if (acc->IsTable() || acc->IsTableRow() || acc->IsTableCell()) {
1575       FireDelayedEvent(nsIAccessibleEvent::EVENT_TABLE_STYLING_CHANGED, acc);
1576     }
1577 
1578     // The accessible can be reparented or reordered in its parent.
1579     // We schedule it for reinsertion. For example, a slotted element
1580     // can change its slot attribute to a different slot.
1581     insert = true;
1582 
1583     // If the frame is invisible, remove it.
1584     // Normally, layout sends explicit a11y notifications for visibility
1585     // changes (see SendA11yNotifications in RestyleManager). However, if a
1586     // visibility change also reconstructs the frame, we must handle it here.
1587     if (frame && !frame->StyleVisibility()->IsVisible()) {
1588       ContentRemoved(aRoot);
1589       // There might be visible descendants, so we want to walk the subtree.
1590       // However, we know we don't want to reinsert this node, so we set insert
1591       // to false.
1592       insert = false;
1593     }
1594   } else {
1595     // If there is no current accessible, and the node has a frame, or is
1596     // display:contents, schedule it for insertion.
1597     if (aRoot->GetPrimaryFrame() || nsCoreUtils::IsDisplayContents(aRoot)) {
1598       // This may be a new subtree, the insertion process will recurse through
1599       // its descendants.
1600       if (!GetAccessibleOrDescendant(aRoot)) {
1601         return true;
1602       }
1603 
1604       // Content is not an accessible, but has accessible descendants.
1605       // We schedule this container for insertion strictly for the case where it
1606       // itself now needs an accessible. We will still need to recurse into the
1607       // descendant content to prune accessibles, and in all likelyness to
1608       // insert accessibles since accessible insertions will likeley get missed
1609       // in an existing subtree.
1610       insert = true;
1611     }
1612   }
1613 
1614   if (LocalAccessible* container = AccessibleOrTrueContainer(aRoot)) {
1615     AutoTArray<nsCOMPtr<nsIContent>, 10> list;
1616     dom::AllChildrenIterator iter =
1617         dom::AllChildrenIterator(aRoot, nsIContent::eAllChildren, true);
1618     while (nsIContent* childNode = iter.GetNextChild()) {
1619       if (PruneOrInsertSubtree(childNode)) {
1620         list.AppendElement(childNode);
1621       }
1622     }
1623 
1624     if (!list.IsEmpty()) {
1625       mNotificationController->ScheduleContentInsertion(container, list);
1626     }
1627   }
1628 
1629   return insert;
1630 }
1631 
RecreateAccessible(nsIContent * aContent)1632 void DocAccessible::RecreateAccessible(nsIContent* aContent) {
1633 #ifdef A11Y_LOG
1634   if (logging::IsEnabled(logging::eTree)) {
1635     logging::MsgBegin("TREE", "accessible recreated");
1636     logging::Node("content", aContent);
1637     logging::MsgEnd();
1638   }
1639 #endif
1640 
1641   // XXX: we shouldn't recreate whole accessible subtree, instead we should
1642   // subclass hide and show events to handle them separately and implement their
1643   // coalescence with normal hide and show events. Note, in this case they
1644   // should be coalesced with normal show/hide events.
1645   ContentRemoved(aContent);
1646   ContentInserted(aContent, aContent->GetNextSibling());
1647 }
1648 
ProcessInvalidationList()1649 void DocAccessible::ProcessInvalidationList() {
1650   // Invalidate children of container accessible for each element in
1651   // invalidation list. Allow invalidation list insertions while container
1652   // children are recached.
1653   for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
1654     nsIContent* content = mInvalidationList[idx];
1655     if (!HasAccessible(content) && content->HasID()) {
1656       LocalAccessible* container = GetContainerAccessible(content);
1657       if (container) {
1658         // Check if the node is a target of aria-owns, and if so, don't process
1659         // it here and let DoARIAOwnsRelocation process it.
1660         AttrRelProviders* list = GetRelProviders(
1661             content->AsElement(), nsDependentAtomString(content->GetID()));
1662         bool shouldProcess = !!list;
1663         if (shouldProcess) {
1664           for (uint32_t idx = 0; idx < list->Length(); idx++) {
1665             if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
1666               shouldProcess = false;
1667               break;
1668             }
1669           }
1670 
1671           if (shouldProcess) {
1672             ProcessContentInserted(container, content);
1673           }
1674         }
1675       }
1676     }
1677   }
1678 
1679   mInvalidationList.Clear();
1680 }
1681 
GetAccessibleEvenIfNotInMap(nsINode * aNode) const1682 LocalAccessible* DocAccessible::GetAccessibleEvenIfNotInMap(
1683     nsINode* aNode) const {
1684   if (!aNode->IsContent() ||
1685       !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area)) {
1686     return GetAccessible(aNode);
1687   }
1688 
1689   // XXX Bug 135040, incorrect when multiple images use the same map.
1690   nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
1691   nsImageFrame* imageFrame = do_QueryFrame(frame);
1692   if (imageFrame) {
1693     LocalAccessible* parent = GetAccessible(imageFrame->GetContent());
1694     if (parent) {
1695       LocalAccessible* area =
1696           parent->AsImageMap()->GetChildAccessibleFor(aNode);
1697       if (area) return area;
1698 
1699       return nullptr;
1700     }
1701   }
1702 
1703   return GetAccessible(aNode);
1704 }
1705 
1706 ////////////////////////////////////////////////////////////////////////////////
1707 // Protected members
1708 
NotifyOfLoading(bool aIsReloading)1709 void DocAccessible::NotifyOfLoading(bool aIsReloading) {
1710   // Mark the document accessible as loading, if it stays alive then we'll mark
1711   // it as loaded when we receive proper notification.
1712   mLoadState &= ~eDOMLoaded;
1713 
1714   if (!IsLoadEventTarget()) return;
1715 
1716   if (aIsReloading && !mLoadEventType &&
1717       // We can't fire events on a document whose tree isn't constructed yet.
1718       HasLoadState(eTreeConstructed)) {
1719     // Fire reload and state busy events on existing document accessible while
1720     // event from user input flag can be calculated properly and accessible
1721     // is alive. When new document gets loaded then this one is destroyed.
1722     RefPtr<AccEvent> reloadEvent =
1723         new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
1724     nsEventShell::FireEvent(reloadEvent);
1725   }
1726 
1727   // Fire state busy change event. Use delayed event since we don't care
1728   // actually if event isn't delivered when the document goes away like a shot.
1729   RefPtr<AccEvent> stateEvent =
1730       new AccStateChangeEvent(this, states::BUSY, true);
1731   FireDelayedEvent(stateEvent);
1732 }
1733 
DoInitialUpdate()1734 void DocAccessible::DoInitialUpdate() {
1735   if (nsCoreUtils::IsTopLevelContentDocInProcess(mDocumentNode)) {
1736     mDocFlags |= eTopLevelContentDocInProcess;
1737     if (IPCAccessibilityActive()) {
1738       nsIDocShell* docShell = mDocumentNode->GetDocShell();
1739       if (RefPtr<dom::BrowserChild> browserChild =
1740               dom::BrowserChild::GetFrom(docShell)) {
1741         // In content processes, top level content documents are always
1742         // RootAccessibles.
1743         MOZ_ASSERT(IsRoot());
1744         DocAccessibleChild* ipcDoc = IPCDoc();
1745         if (ipcDoc) {
1746           browserChild->SetTopLevelDocAccessibleChild(ipcDoc);
1747         } else {
1748           ipcDoc = new DocAccessibleChild(this, browserChild);
1749           SetIPCDoc(ipcDoc);
1750           // Subsequent initialization might depend on being able to get the
1751           // top level DocAccessibleChild, so set that as early as possible.
1752           browserChild->SetTopLevelDocAccessibleChild(ipcDoc);
1753 
1754 #if defined(XP_WIN)
1755           IAccessibleHolder holder;
1756           int32_t childID;
1757           if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
1758             childID = 0;
1759           } else {
1760             holder = CreateHolderFromAccessible(WrapNotNull(this));
1761             MOZ_ASSERT(!holder.IsNull());
1762             childID = MsaaAccessible::GetChildIDFor(this);
1763           }
1764 #else
1765           int32_t holder = 0, childID = 0;
1766 #endif
1767           browserChild->SendPDocAccessibleConstructor(ipcDoc, nullptr, 0,
1768                                                       childID, holder);
1769 #if !defined(XP_WIN)
1770           ipcDoc->SendPDocAccessiblePlatformExtConstructor();
1771 #endif
1772         }
1773 #if !defined(XP_WIN)
1774         // It's safe for us to mark top level documents as constructed in the
1775         // parent process without receiving an explicit message, since we can
1776         // never get queries for this document or descendants before parent
1777         // process construction is complete.
1778         ipcDoc->SetConstructedInParentProcess();
1779 #endif
1780       }
1781     }
1782   }
1783 
1784   mLoadState |= eTreeConstructed;
1785 
1786   // Set up a root element and ARIA role mapping.
1787   UpdateRootElIfNeeded();
1788 
1789   // Build initial tree.
1790   CacheChildrenInSubtree(this);
1791 #ifdef A11Y_LOG
1792   if (logging::IsEnabled(logging::eVerbose)) {
1793     logging::Tree("TREE", "Initial subtree", this);
1794   }
1795   if (logging::IsEnabled(logging::eTreeSize)) {
1796     logging::TreeSize("TREE SIZE", "Initial subtree", this);
1797   }
1798 #endif
1799 
1800   // Fire reorder event after the document tree is constructed. Note, since
1801   // this reorder event is processed by parent document then events targeted to
1802   // this document may be fired prior to this reorder event. If this is
1803   // a problem then consider to keep event processing per tab document.
1804   if (!IsRoot()) {
1805     RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(LocalParent());
1806     ParentDocument()->FireDelayedEvent(reorderEvent);
1807   }
1808 
1809   if (IPCAccessibilityActive()) {
1810     DocAccessibleChild* ipcDoc = IPCDoc();
1811     MOZ_ASSERT(ipcDoc);
1812     if (ipcDoc) {
1813       for (auto idx = 0U; idx < mChildren.Length(); idx++) {
1814         ipcDoc->InsertIntoIpcTree(this, mChildren.ElementAt(idx), idx);
1815       }
1816     }
1817   }
1818 }
1819 
ProcessLoad()1820 void DocAccessible::ProcessLoad() {
1821   mLoadState |= eCompletelyLoaded;
1822 
1823 #ifdef A11Y_LOG
1824   if (logging::IsEnabled(logging::eDocLoad)) {
1825     logging::DocCompleteLoad(this, IsLoadEventTarget());
1826   }
1827 #endif
1828 
1829   // Do not fire document complete/stop events for root chrome document
1830   // accessibles and for frame/iframe documents because
1831   // a) screen readers start working on focus event in the case of root chrome
1832   // documents
1833   // b) document load event on sub documents causes screen readers to act is if
1834   // entire page is reloaded.
1835   if (!IsLoadEventTarget()) return;
1836 
1837   // Fire complete/load stopped if the load event type is given.
1838   if (mLoadEventType) {
1839     RefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
1840     FireDelayedEvent(loadEvent);
1841 
1842     mLoadEventType = 0;
1843   }
1844 
1845   // Fire busy state change event.
1846   RefPtr<AccEvent> stateEvent =
1847       new AccStateChangeEvent(this, states::BUSY, false);
1848   FireDelayedEvent(stateEvent);
1849 }
1850 
AddDependentIDsFor(LocalAccessible * aRelProvider,nsAtom * aRelAttr)1851 void DocAccessible::AddDependentIDsFor(LocalAccessible* aRelProvider,
1852                                        nsAtom* aRelAttr) {
1853   dom::Element* relProviderEl = aRelProvider->Elm();
1854   if (!relProviderEl) return;
1855 
1856   for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
1857     nsStaticAtom* relAttr = kRelationAttrs[idx];
1858     if (aRelAttr && aRelAttr != relAttr) continue;
1859 
1860     if (relAttr == nsGkAtoms::_for) {
1861       if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
1862                                               nsGkAtoms::output)) {
1863         continue;
1864       }
1865 
1866     } else if (relAttr == nsGkAtoms::control) {
1867       if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
1868                                              nsGkAtoms::description)) {
1869         continue;
1870       }
1871     }
1872 
1873     IDRefsIterator iter(this, relProviderEl, relAttr);
1874     while (true) {
1875       const nsDependentSubstring id = iter.NextID();
1876       if (id.IsEmpty()) break;
1877 
1878       AttrRelProviders* providers = GetOrCreateRelProviders(relProviderEl, id);
1879       if (providers) {
1880         AttrRelProvider* provider = new AttrRelProvider(relAttr, relProviderEl);
1881         if (provider) {
1882           providers->AppendElement(provider);
1883 
1884           // We've got here during the children caching. If the referenced
1885           // content is not accessible then store it to pend its container
1886           // children invalidation (this happens immediately after the caching
1887           // is finished).
1888           nsIContent* dependentContent = iter.GetElem(id);
1889           if (dependentContent) {
1890             if (!HasAccessible(dependentContent)) {
1891               mInvalidationList.AppendElement(dependentContent);
1892             }
1893           }
1894         }
1895       }
1896     }
1897 
1898     // If the relation attribute is given then we don't have anything else to
1899     // check.
1900     if (aRelAttr) break;
1901   }
1902 
1903   // Make sure to schedule the tree update if needed.
1904   mNotificationController->ScheduleProcessing();
1905 }
1906 
RemoveDependentIDsFor(LocalAccessible * aRelProvider,nsAtom * aRelAttr)1907 void DocAccessible::RemoveDependentIDsFor(LocalAccessible* aRelProvider,
1908                                           nsAtom* aRelAttr) {
1909   dom::Element* relProviderElm = aRelProvider->Elm();
1910   if (!relProviderElm) return;
1911 
1912   for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
1913     nsStaticAtom* relAttr = kRelationAttrs[idx];
1914     if (aRelAttr && aRelAttr != kRelationAttrs[idx]) continue;
1915 
1916     IDRefsIterator iter(this, relProviderElm, relAttr);
1917     while (true) {
1918       const nsDependentSubstring id = iter.NextID();
1919       if (id.IsEmpty()) break;
1920 
1921       AttrRelProviders* providers = GetRelProviders(relProviderElm, id);
1922       if (providers) {
1923         providers->RemoveElementsBy(
1924             [relAttr, relProviderElm](const auto& provider) {
1925               return provider->mRelAttr == relAttr &&
1926                      provider->mContent == relProviderElm;
1927             });
1928 
1929         RemoveRelProvidersIfEmpty(relProviderElm, id);
1930       }
1931     }
1932 
1933     // If the relation attribute is given then we don't have anything else to
1934     // check.
1935     if (aRelAttr) break;
1936   }
1937 }
1938 
UpdateAccessibleOnAttrChange(dom::Element * aElement,nsAtom * aAttribute)1939 bool DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
1940                                                  nsAtom* aAttribute) {
1941   if (aAttribute == nsGkAtoms::role) {
1942     // It is common for js libraries to set the role on the body element after
1943     // the document has loaded. In this case we just update the role map entry.
1944     if (mContent == aElement) {
1945       SetRoleMapEntryForDoc(aElement);
1946       if (mIPCDoc) {
1947         mIPCDoc->SendRoleChangedEvent(Role());
1948       }
1949 
1950       return true;
1951     }
1952 
1953     // Recreate the accessible when role is changed because we might require a
1954     // different accessible class for the new role or the accessible may expose
1955     // a different sets of interfaces (COM restriction).
1956     RecreateAccessible(aElement);
1957 
1958     return true;
1959   }
1960 
1961   if (aAttribute == nsGkAtoms::aria_multiselectable &&
1962       aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
1963     // This affects whether the accessible supports SelectAccessible.
1964     // COM says we cannot change what interfaces are supported on-the-fly,
1965     // so invalidate this object. A new one will be created on demand.
1966     RecreateAccessible(aElement);
1967 
1968     return true;
1969   }
1970 
1971   if (aAttribute == nsGkAtoms::type) {
1972     // If the input[type] changes, we should recreate the accessible.
1973     RecreateAccessible(aElement);
1974     return true;
1975   }
1976 
1977   return false;
1978 }
1979 
UpdateRootElIfNeeded()1980 void DocAccessible::UpdateRootElIfNeeded() {
1981   dom::Element* rootEl = mDocumentNode->GetBodyElement();
1982   if (!rootEl) {
1983     rootEl = mDocumentNode->GetRootElement();
1984   }
1985   if (rootEl != mContent) {
1986     mContent = rootEl;
1987     SetRoleMapEntryForDoc(rootEl);
1988     if (mIPCDoc) {
1989       mIPCDoc->SendRoleChangedEvent(Role());
1990     }
1991   }
1992 }
1993 
1994 /**
1995  * Content insertion helper.
1996  */
1997 class InsertIterator final {
1998  public:
InsertIterator(LocalAccessible * aContext,const nsTArray<nsCOMPtr<nsIContent>> * aNodes)1999   InsertIterator(LocalAccessible* aContext,
2000                  const nsTArray<nsCOMPtr<nsIContent>>* aNodes)
2001       : mChild(nullptr),
2002         mChildBefore(nullptr),
2003         mWalker(aContext),
2004         mNodes(aNodes),
2005         mNodesIdx(0) {
2006     MOZ_ASSERT(aContext, "No context");
2007     MOZ_ASSERT(aNodes, "No nodes to search for accessible elements");
2008     MOZ_COUNT_CTOR(InsertIterator);
2009   }
MOZ_COUNTED_DTOR(InsertIterator)2010   MOZ_COUNTED_DTOR(InsertIterator)
2011 
2012   LocalAccessible* Context() const { return mWalker.Context(); }
Child() const2013   LocalAccessible* Child() const { return mChild; }
ChildBefore() const2014   LocalAccessible* ChildBefore() const { return mChildBefore; }
Document() const2015   DocAccessible* Document() const { return mWalker.Document(); }
2016 
2017   /**
2018    * Iterates to a next accessible within the inserted content.
2019    */
2020   bool Next();
2021 
Rejected()2022   void Rejected() {
2023     mChild = nullptr;
2024     mChildBefore = nullptr;
2025   }
2026 
2027  private:
2028   LocalAccessible* mChild;
2029   LocalAccessible* mChildBefore;
2030   TreeWalker mWalker;
2031 
2032   const nsTArray<nsCOMPtr<nsIContent>>* mNodes;
2033   nsTHashSet<nsPtrHashKey<const nsIContent>> mProcessedNodes;
2034   uint32_t mNodesIdx;
2035 };
2036 
Next()2037 bool InsertIterator::Next() {
2038   if (mNodesIdx > 0) {
2039     // If we already processed the first node in the mNodes list,
2040     // check if we can just use the walker to get its next sibling.
2041     LocalAccessible* nextChild = mWalker.Next();
2042     if (nextChild) {
2043       mChildBefore = mChild;
2044       mChild = nextChild;
2045       return true;
2046     }
2047   }
2048 
2049   while (mNodesIdx < mNodes->Length()) {
2050     nsIContent* node = mNodes->ElementAt(mNodesIdx++);
2051     // Check to see if we already processed this node with this iterator.
2052     // this can happen if we get two redundant insertions in the case of a
2053     // text and frame insertion.
2054     if (!mProcessedNodes.EnsureInserted(node)) {
2055       continue;
2056     }
2057 
2058     LocalAccessible* container = Document()->AccessibleOrTrueContainer(
2059         node->GetFlattenedTreeParentNode(), true);
2060     // Ignore nodes that are not contained by the container anymore.
2061     // The container might be changed, for example, because of the subsequent
2062     // overlapping content insertion (i.e. other content was inserted between
2063     // this inserted content and its container or the content was reinserted
2064     // into different container of unrelated part of tree). To avoid a double
2065     // processing of the content insertion ignore this insertion notification.
2066     // Note, the inserted content might be not in tree at all at this point
2067     // what means there's no container. Ignore the insertion too.
2068     if (container != Context()) {
2069       continue;
2070     }
2071 
2072     // HTML comboboxes have no-content list accessible as an intermediate
2073     // containing all options.
2074     if (container->IsHTMLCombobox()) {
2075       container = container->LocalFirstChild();
2076     }
2077 
2078     if (!container->IsAcceptableChild(node)) {
2079       continue;
2080     }
2081 
2082 #ifdef A11Y_LOG
2083     logging::TreeInfo("traversing an inserted node", logging::eVerbose,
2084                       "container", container, "node", node);
2085 #endif
2086 
2087     nsIContent* prevNode = mChild ? mChild->GetContent() : nullptr;
2088     if (prevNode && prevNode->GetNextSibling() == node) {
2089       // If inserted nodes are siblings then just move the walker next.
2090       LocalAccessible* nextChild = mWalker.Scope(node);
2091       if (nextChild) {
2092         mChildBefore = mChild;
2093         mChild = nextChild;
2094         return true;
2095       }
2096     } else {
2097       // Otherwise use a new walker to find this node in the container's
2098       // subtree, and retrieve its preceding sibling.
2099       TreeWalker finder(container);
2100       if (finder.Seek(node)) {
2101         mChild = mWalker.Scope(node);
2102         if (mChild) {
2103           MOZ_ASSERT(!mChild->IsRelocated(), "child cannot be aria owned");
2104           mChildBefore = finder.Prev();
2105           return true;
2106         }
2107       }
2108     }
2109   }
2110 
2111   return false;
2112 }
2113 
ProcessContentInserted(LocalAccessible * aContainer,const nsTArray<nsCOMPtr<nsIContent>> * aNodes)2114 void DocAccessible::ProcessContentInserted(
2115     LocalAccessible* aContainer, const nsTArray<nsCOMPtr<nsIContent>>* aNodes) {
2116   // Process insertions if the container accessible is still in tree.
2117   if (!aContainer->IsInDocument()) {
2118     return;
2119   }
2120 
2121   // If new root content has been inserted then update it.
2122   if (aContainer == this) {
2123     UpdateRootElIfNeeded();
2124   }
2125 
2126   InsertIterator iter(aContainer, aNodes);
2127   if (!iter.Next()) {
2128     return;
2129   }
2130 
2131 #ifdef A11Y_LOG
2132   logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
2133 #endif
2134 
2135   TreeMutation mt(aContainer);
2136   do {
2137     LocalAccessible* parent = iter.Child()->LocalParent();
2138     if (parent) {
2139       LocalAccessible* previousSibling = iter.ChildBefore();
2140       if (parent != aContainer ||
2141           iter.Child()->LocalPrevSibling() != previousSibling) {
2142         if (previousSibling && previousSibling->LocalParent() != aContainer) {
2143           // previousSibling hasn't been moved into aContainer yet.
2144           // previousSibling should be later in the insertion list, so the tree
2145           // will get adjusted when we process it later.
2146           MOZ_DIAGNOSTIC_ASSERT(parent == aContainer,
2147                                 "Child moving to new parent, but previous "
2148                                 "sibling in wrong parent");
2149           continue;
2150         }
2151 #ifdef A11Y_LOG
2152         logging::TreeInfo("relocating accessible", 0, "old parent", parent,
2153                           "new parent", aContainer, "child", iter.Child(),
2154                           nullptr);
2155 #endif
2156         MoveChild(iter.Child(), aContainer,
2157                   previousSibling ? previousSibling->IndexInParent() + 1 : 0);
2158       }
2159       continue;
2160     }
2161 
2162     if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
2163 #ifdef A11Y_LOG
2164       logging::TreeInfo("accessible was inserted", 0, "container", aContainer,
2165                         "child", iter.Child(), nullptr);
2166 #endif
2167 
2168       CreateSubtree(iter.Child());
2169       mt.AfterInsertion(iter.Child());
2170       continue;
2171     }
2172 
2173     MOZ_ASSERT_UNREACHABLE("accessible was rejected");
2174     iter.Rejected();
2175   } while (iter.Next());
2176 
2177   mt.Done();
2178 
2179 #ifdef A11Y_LOG
2180   logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
2181 #endif
2182 
2183   FireEventsOnInsertion(aContainer);
2184 }
2185 
ProcessContentInserted(LocalAccessible * aContainer,nsIContent * aNode)2186 void DocAccessible::ProcessContentInserted(LocalAccessible* aContainer,
2187                                            nsIContent* aNode) {
2188   if (!aContainer->IsInDocument()) {
2189     return;
2190   }
2191 
2192 #ifdef A11Y_LOG
2193   logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
2194 #endif
2195 
2196 #ifdef A11Y_LOG
2197   logging::TreeInfo("traversing an inserted node", logging::eVerbose,
2198                     "container", aContainer, "node", aNode);
2199 #endif
2200 
2201   TreeWalker walker(aContainer);
2202   if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
2203     LocalAccessible* child = GetAccessible(aNode);
2204     if (!child) {
2205       child = GetAccService()->CreateAccessible(aNode, aContainer);
2206     }
2207 
2208     if (child) {
2209       TreeMutation mt(aContainer);
2210       if (!aContainer->InsertAfter(child, walker.Prev())) {
2211         return;
2212       }
2213       CreateSubtree(child);
2214       mt.AfterInsertion(child);
2215       mt.Done();
2216 
2217       FireEventsOnInsertion(aContainer);
2218     }
2219   }
2220 
2221 #ifdef A11Y_LOG
2222   logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
2223 #endif
2224 }
2225 
FireEventsOnInsertion(LocalAccessible * aContainer)2226 void DocAccessible::FireEventsOnInsertion(LocalAccessible* aContainer) {
2227   // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
2228   // if it did.
2229   if (aContainer->IsAlert() || aContainer->IsInsideAlert()) {
2230     LocalAccessible* ancestor = aContainer;
2231     do {
2232       if (ancestor->IsAlert()) {
2233         FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
2234         break;
2235       }
2236     } while ((ancestor = ancestor->LocalParent()));
2237   }
2238 }
2239 
ContentRemoved(LocalAccessible * aChild)2240 void DocAccessible::ContentRemoved(LocalAccessible* aChild) {
2241   LocalAccessible* parent = aChild->LocalParent();
2242   MOZ_DIAGNOSTIC_ASSERT(parent, "Unattached accessible from tree");
2243 
2244 #ifdef A11Y_LOG
2245   logging::TreeInfo("process content removal", 0, "container", parent, "child",
2246                     aChild, nullptr);
2247 #endif
2248 
2249   // XXX: event coalescence may kill us
2250   RefPtr<LocalAccessible> kungFuDeathGripChild(aChild);
2251 
2252   TreeMutation mt(parent);
2253   mt.BeforeRemoval(aChild);
2254 
2255   if (aChild->IsDefunct()) {
2256     MOZ_ASSERT_UNREACHABLE("Event coalescence killed the accessible");
2257     mt.Done();
2258     return;
2259   }
2260 
2261   MOZ_DIAGNOSTIC_ASSERT(aChild->LocalParent(), "Alive but unparented #1");
2262 
2263   if (aChild->IsRelocated()) {
2264     nsTArray<RefPtr<LocalAccessible>>* owned = mARIAOwnsHash.Get(parent);
2265     MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
2266     owned->RemoveElement(aChild);
2267     if (owned->Length() == 0) {
2268       mARIAOwnsHash.Remove(parent);
2269     }
2270   }
2271   MOZ_DIAGNOSTIC_ASSERT(aChild->LocalParent(), "Unparented #2");
2272   parent->RemoveChild(aChild);
2273   UncacheChildrenInSubtree(aChild);
2274 
2275   mt.Done();
2276 }
2277 
ContentRemoved(nsIContent * aContentNode)2278 void DocAccessible::ContentRemoved(nsIContent* aContentNode) {
2279   // If child node is not accessible then look for its accessible children.
2280   LocalAccessible* acc = GetAccessible(aContentNode);
2281   if (acc) {
2282     ContentRemoved(acc);
2283   }
2284 
2285   dom::AllChildrenIterator iter =
2286       dom::AllChildrenIterator(aContentNode, nsIContent::eAllChildren, true);
2287   while (nsIContent* childNode = iter.GetNextChild()) {
2288     ContentRemoved(childNode);
2289   }
2290 
2291   // If this node has a shadow root, remove its explicit children too.
2292   // The host node may be removed after the shadow root was attached, and
2293   // before we asynchronously prune the light DOM and construct the shadow DOM.
2294   // If this is a case where the node does not have its own accessible, we will
2295   // not recurse into its current children, so we need to use an
2296   // ExplicitChildIterator in order to get its accessible children in the light
2297   // DOM, since they are not accessible anymore via AllChildrenIterator.
2298   if (aContentNode->GetShadowRoot()) {
2299     dom::ExplicitChildIterator iter = dom::ExplicitChildIterator(aContentNode);
2300     while (nsIContent* childNode = iter.GetNextChild()) {
2301       ContentRemoved(childNode);
2302     }
2303   }
2304 }
2305 
RelocateARIAOwnedIfNeeded(nsIContent * aElement)2306 bool DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement) {
2307   if (!aElement->HasID()) return false;
2308 
2309   AttrRelProviders* list = GetRelProviders(
2310       aElement->AsElement(), nsDependentAtomString(aElement->GetID()));
2311   if (list) {
2312     for (uint32_t idx = 0; idx < list->Length(); idx++) {
2313       if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
2314         LocalAccessible* owner = GetAccessible(list->ElementAt(idx)->mContent);
2315         if (owner) {
2316           mNotificationController->ScheduleRelocation(owner);
2317           return true;
2318         }
2319       }
2320     }
2321   }
2322 
2323   return false;
2324 }
2325 
DoARIAOwnsRelocation(LocalAccessible * aOwner)2326 void DocAccessible::DoARIAOwnsRelocation(LocalAccessible* aOwner) {
2327   MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
2328   MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
2329 
2330 #ifdef A11Y_LOG
2331   logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
2332 #endif
2333 
2334   nsTArray<RefPtr<LocalAccessible>>* owned =
2335       mARIAOwnsHash.GetOrInsertNew(aOwner);
2336 
2337   IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
2338   uint32_t idx = 0;
2339   while (nsIContent* childEl = iter.NextElem()) {
2340     LocalAccessible* child = GetAccessible(childEl);
2341     auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
2342 
2343     // Make an attempt to create an accessible if it wasn't created yet.
2344     if (!child) {
2345       // An owned child cannot be an ancestor of the owner.
2346       bool ok = true;
2347       bool check = true;
2348       for (LocalAccessible* parent = aOwner; parent && !parent->IsDoc();
2349            parent = parent->LocalParent()) {
2350         if (check) {
2351           if (parent->Elm()->IsInclusiveDescendantOf(childEl)) {
2352             ok = false;
2353             break;
2354           }
2355         }
2356         // We need to do the DOM descendant check again whenever the DOM
2357         // lineage changes. If parent is relocated, that means the next
2358         // ancestor will have a different DOM lineage.
2359         check = parent->IsRelocated();
2360       }
2361       if (!ok) {
2362         continue;
2363       }
2364 
2365       if (aOwner->IsAcceptableChild(childEl)) {
2366         child = GetAccService()->CreateAccessible(childEl, aOwner);
2367         if (child) {
2368           TreeMutation imut(aOwner);
2369           aOwner->InsertChildAt(insertIdx, child);
2370           imut.AfterInsertion(child);
2371           imut.Done();
2372 
2373           child->SetRelocated(true);
2374           owned->InsertElementAt(idx, child);
2375           idx++;
2376 
2377           // Create subtree before adjusting the insertion index, since subtree
2378           // creation may alter children in the container.
2379           CreateSubtree(child);
2380           FireEventsOnInsertion(aOwner);
2381         }
2382       }
2383       continue;
2384     }
2385 
2386 #ifdef A11Y_LOG
2387     logging::TreeInfo("aria owns traversal", logging::eVerbose, "candidate",
2388                       child, nullptr);
2389 #endif
2390 
2391     if (owned->IndexOf(child) < idx) {
2392       continue;  // ignore second entry of same ID
2393     }
2394 
2395     // Same child on same position, no change.
2396     if (child->LocalParent() == aOwner) {
2397       int32_t indexInParent = child->IndexInParent();
2398 
2399       // The child is being placed in its current index,
2400       // eg. aria-owns='id1 id2 id3' is changed to aria-owns='id3 id2 id1'.
2401       if (indexInParent == static_cast<int32_t>(insertIdx)) {
2402         MOZ_ASSERT(child->IsRelocated(),
2403                    "A child, having an index in parent from aria ownded "
2404                    "indices range, has to be aria owned");
2405         MOZ_ASSERT(owned->ElementAt(idx) == child,
2406                    "Unexpected child in ARIA owned array");
2407         idx++;
2408         continue;
2409       }
2410 
2411       // The child is being inserted directly after its current index,
2412       // resulting in a no-move case. This will happen when a parent aria-owns
2413       // its last ordinal child:
2414       // <ul aria-owns='id2'><li id='id1'></li><li id='id2'></li></ul>
2415       if (indexInParent == static_cast<int32_t>(insertIdx) - 1) {
2416         MOZ_ASSERT(!child->IsRelocated(),
2417                    "Child should be in its ordinal position");
2418         child->SetRelocated(true);
2419         owned->InsertElementAt(idx, child);
2420         idx++;
2421         continue;
2422       }
2423     }
2424 
2425     MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
2426 
2427     // A new child is found, check for loops.
2428     if (child->LocalParent() != aOwner) {
2429       // Child is aria-owned by another container, skip.
2430       if (child->IsRelocated()) {
2431         continue;
2432       }
2433 
2434       LocalAccessible* parent = aOwner;
2435       while (parent && parent != child && !parent->IsDoc()) {
2436         parent = parent->LocalParent();
2437       }
2438       // A referred child cannot be a parent of the owner.
2439       if (parent == child) {
2440         continue;
2441       }
2442     }
2443 
2444     if (MoveChild(child, aOwner, insertIdx)) {
2445       child->SetRelocated(true);
2446       MOZ_ASSERT(owned == mARIAOwnsHash.Get(aOwner));
2447       owned = mARIAOwnsHash.GetOrInsertNew(aOwner);
2448       owned->InsertElementAt(idx, child);
2449       idx++;
2450     }
2451   }
2452 
2453   // Put back children that are not seized anymore.
2454   PutChildrenBack(owned, idx);
2455   if (owned->Length() == 0) {
2456     mARIAOwnsHash.Remove(aOwner);
2457   }
2458 }
2459 
PutChildrenBack(nsTArray<RefPtr<LocalAccessible>> * aChildren,uint32_t aStartIdx)2460 void DocAccessible::PutChildrenBack(
2461     nsTArray<RefPtr<LocalAccessible>>* aChildren, uint32_t aStartIdx) {
2462   MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
2463 
2464   for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
2465     LocalAccessible* child = aChildren->ElementAt(idx);
2466     if (!child->IsInDocument()) {
2467       continue;
2468     }
2469 
2470     // Remove the child from the owner
2471     LocalAccessible* owner = child->LocalParent();
2472     if (!owner) {
2473       NS_ERROR("Cannot put the child back. No parent, a broken tree.");
2474       continue;
2475     }
2476 
2477 #ifdef A11Y_LOG
2478     logging::TreeInfo("aria owns put child back", 0, "old parent", owner,
2479                       "child", child, nullptr);
2480 #endif
2481 
2482     // Unset relocated flag to find an insertion point for the child.
2483     child->SetRelocated(false);
2484 
2485     nsIContent* content = child->GetContent();
2486     int32_t idxInParent = -1;
2487     LocalAccessible* origContainer =
2488         AccessibleOrTrueContainer(content->GetFlattenedTreeParentNode());
2489     if (origContainer) {
2490       TreeWalker walker(origContainer);
2491       if (walker.Seek(content)) {
2492         LocalAccessible* prevChild = walker.Prev();
2493         if (prevChild) {
2494           idxInParent = prevChild->IndexInParent() + 1;
2495           MOZ_DIAGNOSTIC_ASSERT(origContainer == prevChild->LocalParent(),
2496                                 "Broken tree");
2497           origContainer = prevChild->LocalParent();
2498         } else {
2499           idxInParent = 0;
2500         }
2501       }
2502     }
2503 
2504     // The child may have already be in its ordinal place for 2 reasons:
2505     // 1. It was the last ordinal child, and the first aria-owned child.
2506     //    given:      <ul id="list" aria-owns="b"><li id="a"></li><li
2507     //    id="b"></li></ul> after load: $("list").setAttribute("aria-owns", "");
2508     // 2. The preceding adopted children were just reclaimed, eg:
2509     //    given:      <ul id="list"><li id="b"></li></ul>
2510     //    after load: $("list").setAttribute("aria-owns", "a b");
2511     //    later:      $("list").setAttribute("aria-owns", "");
2512     if (origContainer != owner || child->IndexInParent() != idxInParent) {
2513       DebugOnly<bool> moved = MoveChild(child, origContainer, idxInParent);
2514       MOZ_ASSERT(moved, "Failed to put child back.");
2515     } else {
2516       MOZ_ASSERT(!child->LocalPrevSibling() ||
2517                      !child->LocalPrevSibling()->IsRelocated(),
2518                  "No relocated child should appear before this one");
2519       MOZ_ASSERT(!child->LocalNextSibling() ||
2520                      child->LocalNextSibling()->IsRelocated(),
2521                  "No ordinal child should appear after this one");
2522     }
2523   }
2524 
2525   aChildren->RemoveLastElements(aChildren->Length() - aStartIdx);
2526 }
2527 
MoveChild(LocalAccessible * aChild,LocalAccessible * aNewParent,int32_t aIdxInParent)2528 bool DocAccessible::MoveChild(LocalAccessible* aChild,
2529                               LocalAccessible* aNewParent,
2530                               int32_t aIdxInParent) {
2531   MOZ_ASSERT(aChild, "No child");
2532   MOZ_ASSERT(aChild->LocalParent(), "No parent");
2533   // We can't guarantee MoveChild works correctly for accessibilities storing
2534   // children outside mChildren.
2535   MOZ_ASSERT(
2536       aIdxInParent <= static_cast<int32_t>(aNewParent->mChildren.Length()),
2537       "Wrong insertion point for a moving child");
2538 
2539   LocalAccessible* curParent = aChild->LocalParent();
2540 
2541   if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
2542     return false;
2543   }
2544 
2545 #ifdef A11Y_LOG
2546   logging::TreeInfo("move child", 0, "old parent", curParent, "new parent",
2547                     aNewParent, "child", aChild, nullptr);
2548 #endif
2549 
2550   // Forget aria-owns info in case of ARIA owned element. The caller is expected
2551   // to update it if needed.
2552   if (aChild->IsRelocated()) {
2553     aChild->SetRelocated(false);
2554     nsTArray<RefPtr<LocalAccessible>>* owned = mARIAOwnsHash.Get(curParent);
2555     MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
2556     owned->RemoveElement(aChild);
2557     if (owned->Length() == 0) {
2558       mARIAOwnsHash.Remove(curParent);
2559     }
2560   }
2561 
2562   NotificationController::MoveGuard mguard(mNotificationController);
2563 
2564   if (curParent == aNewParent) {
2565     MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
2566     curParent->RelocateChild(aIdxInParent, aChild);
2567 
2568 #ifdef A11Y_LOG
2569     logging::TreeInfo("move child: parent tree after", logging::eVerbose,
2570                       curParent);
2571 #endif
2572     return true;
2573   }
2574 
2575   // If the child cannot be re-inserted into the tree, then make sure to remove
2576   // it from its present parent and then shutdown it.
2577   bool hasInsertionPoint =
2578       (aIdxInParent >= 0) &&
2579       (aIdxInParent <= static_cast<int32_t>(aNewParent->mChildren.Length()));
2580 
2581   TreeMutation rmut(curParent);
2582   rmut.BeforeRemoval(aChild, hasInsertionPoint && TreeMutation::kNoShutdown);
2583   curParent->RemoveChild(aChild);
2584   rmut.Done();
2585 
2586   // No insertion point for the child.
2587   if (!hasInsertionPoint) {
2588     return true;
2589   }
2590 
2591   TreeMutation imut(aNewParent);
2592   aNewParent->InsertChildAt(aIdxInParent, aChild);
2593   imut.AfterInsertion(aChild);
2594   imut.Done();
2595 
2596 #ifdef A11Y_LOG
2597   logging::TreeInfo("move child: old parent tree after", logging::eVerbose,
2598                     curParent);
2599   logging::TreeInfo("move child: new parent tree after", logging::eVerbose,
2600                     aNewParent);
2601 #endif
2602 
2603   return true;
2604 }
2605 
CacheChildrenInSubtree(LocalAccessible * aRoot,LocalAccessible ** aFocusedAcc)2606 void DocAccessible::CacheChildrenInSubtree(LocalAccessible* aRoot,
2607                                            LocalAccessible** aFocusedAcc) {
2608   // If the accessible is focused then report a focus event after all related
2609   // mutation events.
2610   if (aFocusedAcc && !*aFocusedAcc &&
2611       FocusMgr()->HasDOMFocus(aRoot->GetContent())) {
2612     *aFocusedAcc = aRoot;
2613   }
2614 
2615   LocalAccessible* root =
2616       aRoot->IsHTMLCombobox() ? aRoot->LocalFirstChild() : aRoot;
2617   if (root->KidsFromDOM()) {
2618     TreeMutation mt(root, TreeMutation::kNoEvents);
2619     TreeWalker walker(root);
2620     while (LocalAccessible* child = walker.Next()) {
2621       if (child->IsBoundToParent()) {
2622         MoveChild(child, root, root->mChildren.Length());
2623         continue;
2624       }
2625 
2626       root->AppendChild(child);
2627       mt.AfterInsertion(child);
2628 
2629       CacheChildrenInSubtree(child, aFocusedAcc);
2630     }
2631     mt.Done();
2632   }
2633 
2634   // Fire events for ARIA elements.
2635   if (!aRoot->HasARIARole()) {
2636     return;
2637   }
2638 
2639   // XXX: we should delay document load complete event if the ARIA document
2640   // has aria-busy.
2641   roles::Role role = aRoot->ARIARole();
2642   if (!aRoot->IsDoc() &&
2643       (role == roles::DIALOG || role == roles::NON_NATIVE_DOCUMENT)) {
2644     FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
2645   }
2646 }
2647 
UncacheChildrenInSubtree(LocalAccessible * aRoot)2648 void DocAccessible::UncacheChildrenInSubtree(LocalAccessible* aRoot) {
2649   aRoot->mStateFlags |= eIsNotInDocument;
2650   RemoveDependentIDsFor(aRoot);
2651 
2652   nsTArray<RefPtr<LocalAccessible>>* owned = mARIAOwnsHash.Get(aRoot);
2653   uint32_t count = aRoot->ContentChildCount();
2654   for (uint32_t idx = 0; idx < count; idx++) {
2655     LocalAccessible* child = aRoot->ContentChildAt(idx);
2656 
2657     if (child->IsRelocated()) {
2658       MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
2659       owned->RemoveElement(child);
2660       if (owned->Length() == 0) {
2661         mARIAOwnsHash.Remove(aRoot);
2662         owned = nullptr;
2663       }
2664     }
2665 
2666     // Removing this accessible from the document doesn't mean anything about
2667     // accessibles for subdocuments, so skip removing those from the tree.
2668     if (!child->IsDoc()) {
2669       UncacheChildrenInSubtree(child);
2670     }
2671   }
2672 
2673   if (aRoot->IsNodeMapEntry() &&
2674       mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot) {
2675     mNodeToAccessibleMap.Remove(aRoot->GetNode());
2676   }
2677 }
2678 
ShutdownChildrenInSubtree(LocalAccessible * aAccessible)2679 void DocAccessible::ShutdownChildrenInSubtree(LocalAccessible* aAccessible) {
2680   // Traverse through children and shutdown them before this accessible. When
2681   // child gets shutdown then it removes itself from children array of its
2682   // parent. Use jdx index to process the cases if child is not attached to the
2683   // parent and as result doesn't remove itself from its children.
2684   uint32_t count = aAccessible->ContentChildCount();
2685   for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
2686     LocalAccessible* child = aAccessible->ContentChildAt(jdx);
2687     if (!child->IsBoundToParent()) {
2688       NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
2689       jdx++;
2690     }
2691 
2692     // Don't cross document boundaries. The outerdoc shutdown takes care about
2693     // its subdocument.
2694     if (!child->IsDoc()) ShutdownChildrenInSubtree(child);
2695   }
2696 
2697   UnbindFromDocument(aAccessible);
2698 }
2699 
IsLoadEventTarget() const2700 bool DocAccessible::IsLoadEventTarget() const {
2701   nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
2702   NS_ASSERTION(treeItem, "No document shell for document!");
2703 
2704   nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
2705   treeItem->GetInProcessParent(getter_AddRefs(parentTreeItem));
2706 
2707   // Not a root document.
2708   if (parentTreeItem) {
2709     // Return true if it's either:
2710     // a) tab document;
2711     nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
2712     treeItem->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem));
2713     if (parentTreeItem == rootTreeItem) return true;
2714 
2715     // b) frame/iframe document and its parent document is not in loading state
2716     // Note: we can get notifications while document is loading (and thus
2717     // while there's no parent document yet).
2718     DocAccessible* parentDoc = ParentDocument();
2719     return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
2720   }
2721 
2722   // It's content (not chrome) root document.
2723   return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
2724 }
2725 
SetIPCDoc(DocAccessibleChild * aIPCDoc)2726 void DocAccessible::SetIPCDoc(DocAccessibleChild* aIPCDoc) {
2727   MOZ_ASSERT(!mIPCDoc || !aIPCDoc, "Clobbering an attached IPCDoc!");
2728   mIPCDoc = aIPCDoc;
2729 }
2730 
DispatchScrollingEvent(nsINode * aTarget,uint32_t aEventType)2731 void DocAccessible::DispatchScrollingEvent(nsINode* aTarget,
2732                                            uint32_t aEventType) {
2733   LocalAccessible* acc = GetAccessible(aTarget);
2734   if (!acc) {
2735     return;
2736   }
2737 
2738   nsIFrame* frame = acc->GetFrame();
2739   if (!frame) {
2740     // Although the accessible had a frame at scroll time, it may now be gone
2741     // because of display: contents.
2742     return;
2743   }
2744 
2745   LayoutDevicePoint scrollPoint;
2746   LayoutDeviceRect scrollRange;
2747   nsIScrollableFrame* sf = acc == this
2748                                ? mPresShell->GetRootScrollFrameAsScrollable()
2749                                : frame->GetScrollTargetFrame();
2750 
2751   // If there is no scrollable frame, it's likely a scroll in a popup, like
2752   // <select>. Just send an event with no scroll info. The scroll info
2753   // is currently only used on Android, and popups are rendered natively
2754   // there.
2755   if (sf) {
2756     int32_t appUnitsPerDevPixel =
2757         mPresShell->GetPresContext()->AppUnitsPerDevPixel();
2758     scrollPoint = LayoutDevicePoint::FromAppUnits(sf->GetScrollPosition(),
2759                                                   appUnitsPerDevPixel) *
2760                   mPresShell->GetResolution();
2761 
2762     scrollRange = LayoutDeviceRect::FromAppUnits(sf->GetScrollRange(),
2763                                                  appUnitsPerDevPixel);
2764     scrollRange.ScaleRoundOut(mPresShell->GetResolution());
2765   }
2766 
2767   RefPtr<AccEvent> event =
2768       new AccScrollingEvent(aEventType, acc, scrollPoint.x, scrollPoint.y,
2769                             scrollRange.width, scrollRange.height);
2770   nsEventShell::FireEvent(event);
2771 }
2772 
ARIAActiveDescendantIDMaybeMoved(dom::Element * aElm)2773 void DocAccessible::ARIAActiveDescendantIDMaybeMoved(dom::Element* aElm) {
2774   nsINode* focusNode = FocusMgr()->FocusedDOMNode();
2775   // The focused element must be within this document.
2776   if (!focusNode || focusNode->OwnerDoc() != mDocumentNode) {
2777     return;
2778   }
2779 
2780   dom::Element* focusElm = nullptr;
2781   if (focusNode == mDocumentNode) {
2782     // The document is focused, so look for aria-activedescendant on the
2783     // body/root.
2784     focusElm = Elm();
2785     if (!focusElm) {
2786       return;
2787     }
2788   } else {
2789     MOZ_ASSERT(focusNode->IsElement());
2790     focusElm = focusNode->AsElement();
2791   }
2792 
2793   // Check if the focus has aria-activedescendant and whether
2794   // it refers to the id just set on aElm.
2795   nsAutoString id;
2796   aElm->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
2797   if (!focusElm->AttrValueIs(kNameSpaceID_None,
2798                              nsGkAtoms::aria_activedescendant, id,
2799                              eCaseMatters)) {
2800     return;
2801   }
2802 
2803   // The aria-activedescendant target has probably changed.
2804   LocalAccessible* acc = GetAccessibleEvenIfNotInMapOrContainer(focusNode);
2805   if (!acc) {
2806     return;
2807   }
2808 
2809   // The active descendant might have just been inserted and may not be in the
2810   // tree yet. Therefore, schedule this async to ensure the tree is up to date.
2811   mNotificationController->ScheduleNotification<DocAccessible, LocalAccessible>(
2812       this, &DocAccessible::ARIAActiveDescendantChanged, acc);
2813 }
2814 
SetRoleMapEntryForDoc(dom::Element * aElement)2815 void DocAccessible::SetRoleMapEntryForDoc(dom::Element* aElement) {
2816   const nsRoleMapEntry* entry = aria::GetRoleMap(aElement);
2817   if (!entry || entry->role == roles::APPLICATION ||
2818       entry->role == roles::DIALOG ||
2819       // Role alert isn't valid on the body element according to the ARIA spec,
2820       // but it's useful for our UI; e.g. the WebRTC sharing indicator.
2821       (entry->role == roles::ALERT &&
2822        !nsCoreUtils::IsContentDocument(mDocumentNode))) {
2823     SetRoleMapEntry(entry);
2824     return;
2825   }
2826   // No other ARIA roles are valid on body elements.
2827   SetRoleMapEntry(nullptr);
2828 }
2829 
GetAccessible(nsINode * aNode) const2830 LocalAccessible* DocAccessible::GetAccessible(nsINode* aNode) const {
2831   return aNode == mDocumentNode ? const_cast<DocAccessible*>(this)
2832                                 : mNodeToAccessibleMap.Get(aNode);
2833 }
2834