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 "Accessible-inl.h"
8 #include "AccIterator.h"
9 #include "DocAccessible-inl.h"
10 #include "DocAccessibleChild.h"
11 #include "HTMLImageMapAccessible.h"
12 #include "nsAccCache.h"
13 #include "nsAccessiblePivot.h"
14 #include "nsAccUtils.h"
15 #include "nsEventShell.h"
16 #include "nsTextEquivUtils.h"
17 #include "Role.h"
18 #include "RootAccessible.h"
19 #include "TreeWalker.h"
20 #include "xpcAccessibleDocument.h"
21 
22 #include "nsIMutableArray.h"
23 #include "nsICommandManager.h"
24 #include "nsIDocShell.h"
25 #include "nsIDocument.h"
26 #include "nsIDOMCharacterData.h"
27 #include "nsIDOMDocument.h"
28 #include "nsPIDOMWindow.h"
29 #include "nsIEditingSession.h"
30 #include "nsIFrame.h"
31 #include "nsIInterfaceRequestorUtils.h"
32 #include "nsImageFrame.h"
33 #include "nsIPersistentProperties2.h"
34 #include "nsIPresShell.h"
35 #include "nsIServiceManager.h"
36 #include "nsViewManager.h"
37 #include "nsIScrollableFrame.h"
38 #include "nsUnicharUtils.h"
39 #include "nsIURI.h"
40 #include "nsIWebNavigation.h"
41 #include "nsFocusManager.h"
42 #include "mozilla/ArrayUtils.h"
43 #include "mozilla/Assertions.h"
44 #include "mozilla/EventStates.h"
45 #include "mozilla/HTMLEditor.h"
46 #include "mozilla/TextEditor.h"
47 #include "mozilla/dom/TabChild.h"
48 #include "mozilla/dom/DocumentType.h"
49 #include "mozilla/dom/Element.h"
50 #include "mozilla/dom/MutationEventBinding.h"
51 
52 using namespace mozilla;
53 using namespace mozilla::a11y;
54 
55 ////////////////////////////////////////////////////////////////////////////////
56 // Static member initialization
57 
58 static nsStaticAtom** kRelationAttrs[] = {&nsGkAtoms::aria_labelledby,
59                                           &nsGkAtoms::aria_describedby,
60                                           &nsGkAtoms::aria_details,
61                                           &nsGkAtoms::aria_owns,
62                                           &nsGkAtoms::aria_controls,
63                                           &nsGkAtoms::aria_flowto,
64                                           &nsGkAtoms::aria_errormessage,
65                                           &nsGkAtoms::_for,
66                                           &nsGkAtoms::control};
67 
68 static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
69 
70 ////////////////////////////////////////////////////////////////////////////////
71 // Constructor/desctructor
72 
DocAccessible(nsIDocument * aDocument,nsIPresShell * aPresShell)73 DocAccessible::DocAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell)
74     :  // XXX don't pass a document to the Accessible constructor so that we
75        // don't set mDoc until our vtable is fully setup.  If we set mDoc before
76        // setting up the vtable we will call Accessible::AddRef() but not the
77        // overrides of it for subclasses.  It is important to call those
78        // overrides to avoid confusing leak checking machinary.
79       HyperTextAccessibleWrap(nullptr, nullptr),
80       // XXX aaronl should we use an algorithm for the initial cache size?
81       mAccessibleCache(kDefaultCacheLength),
82       mNodeToAccessibleMap(kDefaultCacheLength),
83       mDocumentNode(aDocument),
84       mScrollPositionChangedTicks(0),
85       mLoadState(eTreeConstructionPending),
86       mDocFlags(0),
87       mLoadEventType(0),
88       mARIAAttrOldValue{nullptr},
89       mVirtualCursor(nullptr),
90       mPresShell(aPresShell),
91       mIPCDoc(nullptr) {
92   mGenericTypes |= eDocument;
93   mStateFlags |= eNotNodeMapEntry;
94   mDoc = this;
95 
96   MOZ_ASSERT(mPresShell, "should have been given a pres shell");
97   mPresShell->SetDocAccessible(this);
98 
99   // If this is a XUL Document, it should not implement nsHyperText
100   if (mDocumentNode && mDocumentNode->IsXULDocument())
101     mGenericTypes &= ~eHyperText;
102 }
103 
~DocAccessible()104 DocAccessible::~DocAccessible() {
105   NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
106 }
107 
108 ////////////////////////////////////////////////////////////////////////////////
109 // nsISupports
110 
111 NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
112 
113 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
114   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
115   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
116   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
117   for (auto iter = tmp->mDependentIDsHash.Iter(); !iter.Done(); iter.Next()) {
118     AttrRelProviderArray* providers = iter.UserData();
119 
120     for (int32_t jdx = providers->Length() - 1; jdx >= 0; jdx--) {
121       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
122           cb, "content of dependent ids hash entry of document accessible");
123 
124       AttrRelProvider* provider = (*providers)[jdx];
125       cb.NoteXPCOMChild(provider->mContent);
126 
127       NS_ASSERTION(provider->mContent->IsInUncomposedDoc(),
128                    "Referred content is not in document!");
129     }
130   }
131   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
132   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
133   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
134   for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
135     nsTArray<RefPtr<Accessible> >* ar = it.UserData();
136     for (uint32_t i = 0; i < ar->Length(); i++) {
137       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mARIAOwnsHash entry item");
138       cb.NoteXPCOMChild(ar->ElementAt(i));
139     }
140   }
141 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
142 
143 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
144   NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
145   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
146   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
147   tmp->mDependentIDsHash.Clear();
148   tmp->mNodeToAccessibleMap.Clear();
149   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
150   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
151   NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList)
152   tmp->mARIAOwnsHash.Clear();
153 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
154 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocAccessible)155 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocAccessible)
156   NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
157   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
158   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
159   NS_INTERFACE_MAP_ENTRY(nsIObserver)
160   NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
161 NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible)
162 
163 NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
164 NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
165 
166 ////////////////////////////////////////////////////////////////////////////////
167 // nsIAccessible
168 
169 ENameValueFlag DocAccessible::Name(nsString& aName) {
170   aName.Truncate();
171 
172   if (mParent) {
173     mParent->Name(aName);  // Allow owning iframe to override the name
174   }
175   if (aName.IsEmpty()) {
176     // Allow name via aria-labelledby or title attribute
177     Accessible::Name(aName);
178   }
179   if (aName.IsEmpty()) {
180     Title(aName);  // Try title element
181   }
182   if (aName.IsEmpty()) {  // Last resort: use URL
183     URL(aName);
184   }
185 
186   return eNameOK;
187 }
188 
189 // Accessible public method
NativeRole()190 role DocAccessible::NativeRole() {
191   nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
192   if (docShell) {
193     nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
194     docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
195     int32_t itemType = docShell->ItemType();
196     if (sameTypeRoot == docShell) {
197       // Root of content or chrome tree
198       if (itemType == nsIDocShellTreeItem::typeChrome)
199         return roles::CHROME_WINDOW;
200 
201       if (itemType == nsIDocShellTreeItem::typeContent) {
202 #ifdef MOZ_XUL
203         if (mDocumentNode && mDocumentNode->IsXULDocument())
204           return roles::APPLICATION;
205 #endif
206         return roles::DOCUMENT;
207       }
208     } else if (itemType == nsIDocShellTreeItem::typeContent) {
209       return roles::DOCUMENT;
210     }
211   }
212 
213   return roles::PANE;  // Fall back;
214 }
215 
Description(nsString & aDescription)216 void DocAccessible::Description(nsString& aDescription) {
217   if (mParent) mParent->Description(aDescription);
218 
219   if (HasOwnContent() && aDescription.IsEmpty()) {
220     nsTextEquivUtils::GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
221                                              aDescription);
222   }
223 }
224 
225 // Accessible public method
NativeState()226 uint64_t DocAccessible::NativeState() {
227   // Document is always focusable.
228   uint64_t state =
229       states::FOCUSABLE;  // keep in sync with NativeInteractiveState() impl
230   if (FocusMgr()->IsFocused(this)) state |= states::FOCUSED;
231 
232   // Expose stale state until the document is ready (DOM is loaded and tree is
233   // constructed).
234   if (!HasLoadState(eReady)) state |= states::STALE;
235 
236   // Expose state busy until the document and all its subdocuments is completely
237   // loaded.
238   if (!HasLoadState(eCompletelyLoaded)) state |= states::BUSY;
239 
240   nsIFrame* frame = GetFrame();
241   if (!frame || !frame->IsVisibleConsideringAncestors(
242                     nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
243     state |= states::INVISIBLE | states::OFFSCREEN;
244   }
245 
246   RefPtr<TextEditor> textEditor = GetEditor();
247   state |= textEditor ? states::EDITABLE : states::READONLY;
248 
249   return state;
250 }
251 
NativeInteractiveState() const252 uint64_t DocAccessible::NativeInteractiveState() const {
253   // Document is always focusable.
254   return states::FOCUSABLE;
255 }
256 
NativelyUnavailable() const257 bool DocAccessible::NativelyUnavailable() const { return false; }
258 
259 // Accessible public method
ApplyARIAState(uint64_t * aState) const260 void DocAccessible::ApplyARIAState(uint64_t* aState) const {
261   // Grab states from content element.
262   if (mContent) Accessible::ApplyARIAState(aState);
263 
264   // Allow iframe/frame etc. to have final state override via ARIA.
265   if (mParent) mParent->ApplyARIAState(aState);
266 }
267 
Attributes()268 already_AddRefed<nsIPersistentProperties> DocAccessible::Attributes() {
269   nsCOMPtr<nsIPersistentProperties> attributes =
270       HyperTextAccessibleWrap::Attributes();
271 
272   // No attributes if document is not attached to the tree or if it's a root
273   // document.
274   if (!mParent || IsRoot()) return attributes.forget();
275 
276   // Override ARIA object attributes from outerdoc.
277   aria::AttrIterator attribIter(mParent->GetContent());
278   nsAutoString name, value, unused;
279   while (attribIter.Next(name, value))
280     attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
281 
282   return attributes.forget();
283 }
284 
FocusedChild()285 Accessible* DocAccessible::FocusedChild() {
286   // Return an accessible for the current global focus, which does not have to
287   // be contained within the current document.
288   return FocusMgr()->FocusedAccessible();
289 }
290 
TakeFocus()291 void DocAccessible::TakeFocus() {
292   // Focus the document.
293   nsFocusManager* fm = nsFocusManager::GetFocusManager();
294   nsCOMPtr<nsIDOMElement> newFocus;
295   fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
296                 nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus));
297 }
298 
299 // HyperTextAccessible method
GetEditor() const300 already_AddRefed<TextEditor> DocAccessible::GetEditor() const {
301   // Check if document is editable (designMode="on" case). Otherwise check if
302   // the html:body (for HTML document case) or document element is editable.
303   if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
304       (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
305     return nullptr;
306 
307   nsCOMPtr<nsIDocShell> docShell = mDocumentNode->GetDocShell();
308   if (!docShell) {
309     return nullptr;
310   }
311 
312   nsCOMPtr<nsIEditingSession> editingSession;
313   docShell->GetEditingSession(getter_AddRefs(editingSession));
314   if (!editingSession) return nullptr;  // No editing session interface
315 
316   RefPtr<HTMLEditor> htmlEditor =
317       editingSession->GetHTMLEditorForWindow(mDocumentNode->GetWindow());
318   if (!htmlEditor) {
319     return nullptr;
320   }
321 
322   bool isEditable = false;
323   htmlEditor->GetIsDocumentEditable(&isEditable);
324   if (isEditable) {
325     return htmlEditor.forget();
326   }
327 
328   return nullptr;
329 }
330 
331 // DocAccessible public method
332 
URL(nsAString & aURL) const333 void DocAccessible::URL(nsAString& aURL) const {
334   nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
335   nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
336   nsAutoCString theURL;
337   if (webNav) {
338     nsCOMPtr<nsIURI> pURI;
339     webNav->GetCurrentURI(getter_AddRefs(pURI));
340     if (pURI) pURI->GetSpec(theURL);
341   }
342   CopyUTF8toUTF16(theURL, aURL);
343 }
344 
DocType(nsAString & aType) const345 void DocAccessible::DocType(nsAString& aType) const {
346 #ifdef MOZ_XUL
347   if (mDocumentNode->IsXULDocument()) {
348     aType.AssignLiteral("window");  // doctype not implemented for XUL at time
349                                     // of writing - causes assertion
350     return;
351   }
352 #endif
353   dom::DocumentType* docType = mDocumentNode->GetDoctype();
354   if (docType) docType->GetPublicId(aType);
355 }
356 
357 ////////////////////////////////////////////////////////////////////////////////
358 // Accessible
359 
Init()360 void DocAccessible::Init() {
361 #ifdef A11Y_LOG
362   if (logging::IsEnabled(logging::eDocCreate))
363     logging::DocCreate("document initialize", mDocumentNode, this);
364 #endif
365 
366   // Initialize notification controller.
367   mNotificationController = new NotificationController(this, mPresShell);
368 
369   // Mark the document accessible as loaded if its DOM document was loaded at
370   // this point (this can happen because a11y is started late or DOM document
371   // having no container was loaded.
372   if (mDocumentNode->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE)
373     mLoadState |= eDOMLoaded;
374 
375   AddEventListeners();
376 }
377 
Shutdown()378 void DocAccessible::Shutdown() {
379   if (!mPresShell)  // already shutdown
380     return;
381 
382 #ifdef A11Y_LOG
383   if (logging::IsEnabled(logging::eDocDestroy))
384     logging::DocDestroy("document shutdown", mDocumentNode, this);
385 #endif
386 
387   // Mark the document as shutdown before AT is notified about the document
388   // removal from its container (valid for root documents on ATK and due to
389   // some reason for MSAA, refer to bug 757392 for details).
390   mStateFlags |= eIsDefunct;
391 
392   if (mNotificationController) {
393     mNotificationController->Shutdown();
394     mNotificationController = nullptr;
395   }
396 
397   RemoveEventListeners();
398 
399   if (mParent) {
400     DocAccessible* parentDocument = mParent->Document();
401     if (parentDocument) parentDocument->RemoveChildDocument(this);
402 
403     mParent->RemoveChild(this);
404   }
405 
406   // Walk the array backwards because child documents remove themselves from the
407   // array as they are shutdown.
408   int32_t childDocCount = mChildDocuments.Length();
409   for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
410     mChildDocuments[idx]->Shutdown();
411 
412   mChildDocuments.Clear();
413 
414   // XXX thinking about ordering?
415   if (mIPCDoc) {
416     MOZ_ASSERT(IPCAccessibilityActive());
417     mIPCDoc->Shutdown();
418     MOZ_ASSERT(!mIPCDoc);
419   }
420 
421   if (mVirtualCursor) {
422     mVirtualCursor->RemoveObserver(this);
423     mVirtualCursor = nullptr;
424   }
425 
426   mPresShell->SetDocAccessible(nullptr);
427   mPresShell = nullptr;  // Avoid reentrancy
428 
429   mDependentIDsHash.Clear();
430   mNodeToAccessibleMap.Clear();
431 
432   for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
433     Accessible* accessible = iter.Data();
434     MOZ_ASSERT(accessible);
435     if (accessible && !accessible->IsDefunct()) {
436       // Unlink parent to avoid its cleaning overhead in shutdown.
437       accessible->mParent = nullptr;
438       accessible->Shutdown();
439     }
440     iter.Remove();
441   }
442 
443   HyperTextAccessibleWrap::Shutdown();
444 
445   GetAccService()->NotifyOfDocumentShutdown(this, mDocumentNode);
446   mDocumentNode = nullptr;
447 }
448 
GetFrame() const449 nsIFrame* DocAccessible::GetFrame() const {
450   nsIFrame* root = nullptr;
451   if (mPresShell) root = mPresShell->GetRootFrame();
452 
453   return root;
454 }
455 
456 // DocAccessible protected member
RelativeBounds(nsIFrame ** aRelativeFrame) const457 nsRect DocAccessible::RelativeBounds(nsIFrame** aRelativeFrame) const {
458   *aRelativeFrame = GetFrame();
459 
460   nsIDocument* document = mDocumentNode;
461   nsIDocument* parentDoc = nullptr;
462 
463   nsRect bounds;
464   while (document) {
465     nsIPresShell* presShell = document->GetShell();
466     if (!presShell) return nsRect();
467 
468     nsRect scrollPort;
469     nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
470     if (sf) {
471       scrollPort = sf->GetScrollPortRect();
472     } else {
473       nsIFrame* rootFrame = presShell->GetRootFrame();
474       if (!rootFrame) return nsRect();
475 
476       scrollPort = rootFrame->GetRect();
477     }
478 
479     if (parentDoc) {  // After first time thru loop
480       // XXXroc bogus code! scrollPort is relative to the viewport of
481       // this document, but we're intersecting rectangles derived from
482       // multiple documents and assuming they're all in the same coordinate
483       // system. See bug 514117.
484       bounds.IntersectRect(scrollPort, bounds);
485     } else {  // First time through loop
486       bounds = scrollPort;
487     }
488 
489     document = parentDoc = document->GetParentDocument();
490   }
491 
492   return bounds;
493 }
494 
495 // DocAccessible protected member
AddEventListeners()496 nsresult DocAccessible::AddEventListeners() {
497   nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
498 
499   // We want to add a command observer only if the document is content and has
500   // an editor.
501   if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
502     nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
503     if (commandManager)
504       commandManager->AddCommandObserver(this, "obs_documentCreated");
505   }
506 
507   SelectionMgr()->AddDocSelectionListener(mPresShell);
508 
509   // Add document observer.
510   mDocumentNode->AddObserver(this);
511   return NS_OK;
512 }
513 
514 // DocAccessible protected member
RemoveEventListeners()515 nsresult DocAccessible::RemoveEventListeners() {
516   // Remove listeners associated with content documents
517   // Remove scroll position listener
518   RemoveScrollListener();
519 
520   NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
521 
522   if (mDocumentNode) {
523     mDocumentNode->RemoveObserver(this);
524 
525     nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
526     NS_ASSERTION(docShell, "doc should support nsIDocShellTreeItem.");
527 
528     if (docShell) {
529       if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
530         nsCOMPtr<nsICommandManager> commandManager =
531             docShell->GetCommandManager();
532         if (commandManager) {
533           commandManager->RemoveCommandObserver(this, "obs_documentCreated");
534         }
535       }
536     }
537   }
538 
539   if (mScrollWatchTimer) {
540     mScrollWatchTimer->Cancel();
541     mScrollWatchTimer = nullptr;
542     NS_RELEASE_THIS();  // Kung fu death grip
543   }
544 
545   SelectionMgr()->RemoveDocSelectionListener(mPresShell);
546   return NS_OK;
547 }
548 
ScrollTimerCallback(nsITimer * aTimer,void * aClosure)549 void DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure) {
550   DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
551 
552   if (docAcc && docAcc->mScrollPositionChangedTicks &&
553       ++docAcc->mScrollPositionChangedTicks > 2) {
554     // Whenever scroll position changes, mScrollPositionChangeTicks gets reset
555     // to 1 We only want to fire accessibilty scroll event when scrolling stops
556     // or pauses Therefore, we wait for no scroll events to occur between 2
557     // ticks of this timer That indicates a pause in scrolling, so we fire the
558     // accessibilty scroll event
559     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
560 
561     docAcc->mScrollPositionChangedTicks = 0;
562     if (docAcc->mScrollWatchTimer) {
563       docAcc->mScrollWatchTimer->Cancel();
564       docAcc->mScrollWatchTimer = nullptr;
565       NS_RELEASE(docAcc);  // Release kung fu death grip
566     }
567   }
568 }
569 
570 ////////////////////////////////////////////////////////////////////////////////
571 // nsIScrollPositionListener
572 
ScrollPositionDidChange(nscoord aX,nscoord aY)573 void DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY) {
574   // Start new timer, if the timer cycles at least 1 full cycle without more
575   // scroll position changes, then the ::Notify() method will fire the
576   // accessibility event for scroll position changes
577   const uint32_t kScrollPosCheckWait = 50;
578   if (mScrollWatchTimer) {
579     mScrollWatchTimer->SetDelay(
580         kScrollPosCheckWait);  // Create new timer, to avoid leaks
581   } else {
582     NS_NewTimerWithFuncCallback(getter_AddRefs(mScrollWatchTimer),
583                                 ScrollTimerCallback, this, kScrollPosCheckWait,
584                                 nsITimer::TYPE_REPEATING_SLACK,
585                                 "a11y::DocAccessible::ScrollPositionDidChange");
586     if (mScrollWatchTimer) {
587       NS_ADDREF_THIS();  // Kung fu death grip
588     }
589   }
590   mScrollPositionChangedTicks = 1;
591 }
592 
593 ////////////////////////////////////////////////////////////////////////////////
594 // nsIObserver
595 
596 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)597 DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
598                        const char16_t* aData) {
599   if (!nsCRT::strcmp(aTopic, "obs_documentCreated")) {
600     // State editable will now be set, readonly is now clear
601     // Normally we only fire delayed events created from the node, not an
602     // accessible object. See the AccStateChangeEvent constructor for details
603     // about this exceptional case.
604     RefPtr<AccEvent> event =
605         new AccStateChangeEvent(this, states::EDITABLE, true);
606     FireDelayedEvent(event);
607   }
608 
609   return NS_OK;
610 }
611 
612 ////////////////////////////////////////////////////////////////////////////////
613 // nsIAccessiblePivotObserver
614 
615 NS_IMETHODIMP
OnPivotChanged(nsIAccessiblePivot * aPivot,nsIAccessible * aOldAccessible,int32_t aOldStart,int32_t aOldEnd,PivotMoveReason aReason,bool aIsFromUserInput)616 DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
617                               nsIAccessible* aOldAccessible, int32_t aOldStart,
618                               int32_t aOldEnd, PivotMoveReason aReason,
619                               bool aIsFromUserInput) {
620   RefPtr<AccEvent> event = new AccVCChangeEvent(
621       this, (aOldAccessible ? aOldAccessible->ToInternalAccessible() : nullptr),
622       aOldStart, aOldEnd, aReason,
623       aIsFromUserInput ? eFromUserInput : eNoUserInput);
624   nsEventShell::FireEvent(event);
625 
626   return NS_OK;
627 }
628 
629 ////////////////////////////////////////////////////////////////////////////////
630 // nsIDocumentObserver
631 
632 NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)633 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
634 NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(DocAccessible)
635 
636 void DocAccessible::AttributeWillChange(dom::Element* aElement,
637                                         int32_t aNameSpaceID,
638                                         nsAtom* aAttribute, int32_t aModType,
639                                         const nsAttrValue* aNewValue) {
640   Accessible* accessible = GetAccessible(aElement);
641   if (!accessible) {
642     if (aElement != mContent) return;
643 
644     accessible = this;
645   }
646 
647   // Update dependent IDs cache. Take care of elements that are accessible
648   // because dependent IDs cache doesn't contain IDs from non accessible
649   // elements.
650   if (aModType != dom::MutationEventBinding::ADDITION)
651     RemoveDependentIDsFor(accessible, aAttribute);
652 
653   if (aAttribute == nsGkAtoms::id) {
654     RelocateARIAOwnedIfNeeded(aElement);
655   }
656 
657   // Store the ARIA attribute old value so that it can be used after
658   // attribute change. Note, we assume there's no nested ARIA attribute
659   // changes. If this happens then we should end up with keeping a stack of
660   // old values.
661 
662   // XXX TODO: bugs 472142, 472143.
663   // Here we will want to cache whatever attribute values we are interested
664   // in, such as the existence of aria-pressed for button (so we know if we
665   // need to newly expose it as a toggle button) etc.
666   if (aAttribute == nsGkAtoms::aria_checked ||
667       aAttribute == nsGkAtoms::aria_pressed) {
668     mARIAAttrOldValue = (aModType != dom::MutationEventBinding::ADDITION)
669                             ? nsAccUtils::GetARIAToken(aElement, aAttribute)
670                             : nullptr;
671     return;
672   }
673 
674   if (aAttribute == nsGkAtoms::aria_disabled ||
675       aAttribute == nsGkAtoms::disabled)
676     mStateBitWasOn = accessible->Unavailable();
677 }
678 
NativeAnonymousChildListChange(nsIContent * aContent,bool aIsRemove)679 void DocAccessible::NativeAnonymousChildListChange(nsIContent* aContent,
680                                                    bool aIsRemove) {}
681 
AttributeChanged(dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)682 void DocAccessible::AttributeChanged(dom::Element* aElement,
683                                      int32_t aNameSpaceID, nsAtom* aAttribute,
684                                      int32_t aModType,
685                                      const nsAttrValue* aOldValue) {
686   NS_ASSERTION(!IsDefunct(),
687                "Attribute changed called on defunct document accessible!");
688 
689   // Proceed even if the element is not accessible because element may become
690   // accessible if it gets certain attribute.
691   if (UpdateAccessibleOnAttrChange(aElement, aAttribute)) return;
692 
693   // Ignore attribute change if the element doesn't have an accessible (at all
694   // or still) iff the element is not a root content of this document accessible
695   // (which is treated as attribute change on this document accessible).
696   // Note: we don't bail if all the content hasn't finished loading because
697   // these attributes are changing for a loaded part of the content.
698   Accessible* accessible = GetAccessible(aElement);
699   if (!accessible) {
700     if (mContent != aElement) return;
701 
702     accessible = this;
703   }
704 
705   MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
706              "DOM attribute change on an accessible detached from the tree");
707 
708   // Fire accessible events iff there's an accessible, otherwise we consider
709   // the accessible state wasn't changed, i.e. its state is initial state.
710   AttributeChangedImpl(accessible, aNameSpaceID, aAttribute);
711 
712   // Update dependent IDs cache. Take care of accessible elements because no
713   // accessible element means either the element is not accessible at all or
714   // its accessible will be created later. It doesn't make sense to keep
715   // dependent IDs for non accessible elements. For the second case we'll update
716   // dependent IDs cache when its accessible is created.
717   if (aModType == dom::MutationEventBinding::MODIFICATION ||
718       aModType == dom::MutationEventBinding::ADDITION) {
719     AddDependentIDsFor(accessible, aAttribute);
720   }
721 }
722 
723 // DocAccessible protected member
AttributeChangedImpl(Accessible * aAccessible,int32_t aNameSpaceID,nsAtom * aAttribute)724 void DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
725                                          int32_t aNameSpaceID,
726                                          nsAtom* aAttribute) {
727   // Fire accessible event after short timer, because we need to wait for
728   // DOM attribute & resulting layout to actually change. Otherwise,
729   // assistive technology will retrieve the wrong state/value/selection info.
730 
731   // XXX todo
732   // We still need to handle special HTML cases here
733   // For example, if an <img>'s usemap attribute is modified
734   // Otherwise it may just be a state change, for example an object changing
735   // its visibility
736   //
737   // XXX todo: report aria state changes for "undefined" literal value changes
738   // filed as bug 472142
739   //
740   // XXX todo:  invalidate accessible when aria state changes affect exposed
741   // role filed as bug 472143
742 
743   // Universal boolean properties that don't require a role. Fire the state
744   // change when disabled or aria-disabled attribute is set.
745   // Note. Checking the XUL or HTML namespace would not seem to gain us
746   // anything, because disabled attribute really is going to mean the same
747   // thing in any namespace.
748   // Note. We use the attribute instead of the disabled state bit because
749   // ARIA's aria-disabled does not affect the disabled state bit.
750   if (aAttribute == nsGkAtoms::disabled ||
751       aAttribute == nsGkAtoms::aria_disabled) {
752     // Do nothing if state wasn't changed (like @aria-disabled was removed but
753     // @disabled is still presented).
754     if (aAccessible->Unavailable() == mStateBitWasOn) return;
755 
756     RefPtr<AccEvent> enabledChangeEvent =
757         new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn);
758     FireDelayedEvent(enabledChangeEvent);
759 
760     RefPtr<AccEvent> sensitiveChangeEvent =
761         new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn);
762     FireDelayedEvent(sensitiveChangeEvent);
763     return;
764   }
765 
766   // Check for namespaced ARIA attribute
767   if (aNameSpaceID == kNameSpaceID_None) {
768     // Check for hyphenated aria-foo property?
769     if (StringBeginsWith(nsDependentAtomString(aAttribute),
770                          NS_LITERAL_STRING("aria-"))) {
771       ARIAAttributeChanged(aAccessible, aAttribute);
772     }
773   }
774 
775   // Fire name change and description change events. XXX: it's not complete and
776   // dupes the code logic of accessible name and description calculation, we do
777   // that for performance reasons.
778   if (aAttribute == nsGkAtoms::aria_label) {
779     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
780     return;
781   }
782 
783   if (aAttribute == nsGkAtoms::aria_describedby) {
784     FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
785     return;
786   }
787 
788   dom::Element* elm = aAccessible->GetContent()->AsElement();
789   if (aAttribute == nsGkAtoms::aria_labelledby &&
790       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
791     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
792     return;
793   }
794 
795   if (aAttribute == nsGkAtoms::alt &&
796       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
797       !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
798     FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
799     return;
800   }
801 
802   if (aAttribute == nsGkAtoms::title) {
803     if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
804         !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
805         !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
806       FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
807       return;
808     }
809 
810     if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby))
811       FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
812                        aAccessible);
813 
814     return;
815   }
816 
817   if (aAttribute == nsGkAtoms::aria_busy) {
818     bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
819                                  eCaseMatters);
820     RefPtr<AccEvent> event =
821         new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
822     FireDelayedEvent(event);
823     return;
824   }
825 
826   if (aAttribute == nsGkAtoms::id) {
827     RelocateARIAOwnedIfNeeded(elm);
828   }
829 
830   // ARIA or XUL selection
831   if ((aAccessible->GetContent()->IsXULElement() &&
832        aAttribute == nsGkAtoms::selected) ||
833       aAttribute == nsGkAtoms::aria_selected) {
834     Accessible* widget =
835         nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
836     if (widget) {
837       AccSelChangeEvent::SelChangeType selChangeType =
838           elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
839                            eCaseMatters)
840               ? AccSelChangeEvent::eSelectionAdd
841               : AccSelChangeEvent::eSelectionRemove;
842 
843       RefPtr<AccEvent> event =
844           new AccSelChangeEvent(widget, aAccessible, selChangeType);
845       FireDelayedEvent(event);
846     }
847 
848     return;
849   }
850 
851   if (aAttribute == nsGkAtoms::contenteditable) {
852     RefPtr<AccEvent> editableChangeEvent =
853         new AccStateChangeEvent(aAccessible, states::EDITABLE);
854     FireDelayedEvent(editableChangeEvent);
855     return;
856   }
857 
858   if (aAttribute == nsGkAtoms::value) {
859     if (aAccessible->IsProgress())
860       FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
861   }
862 }
863 
864 // DocAccessible protected member
ARIAAttributeChanged(Accessible * aAccessible,nsAtom * aAttribute)865 void DocAccessible::ARIAAttributeChanged(Accessible* aAccessible,
866                                          nsAtom* aAttribute) {
867   // Note: For universal/global ARIA states and properties we don't care if
868   // there is an ARIA role present or not.
869 
870   if (aAttribute == nsGkAtoms::aria_required) {
871     RefPtr<AccEvent> event =
872         new AccStateChangeEvent(aAccessible, states::REQUIRED);
873     FireDelayedEvent(event);
874     return;
875   }
876 
877   if (aAttribute == nsGkAtoms::aria_invalid) {
878     RefPtr<AccEvent> event =
879         new AccStateChangeEvent(aAccessible, states::INVALID);
880     FireDelayedEvent(event);
881     return;
882   }
883 
884   // The activedescendant universal property redirects accessible focus events
885   // to the element with the id that activedescendant points to. Make sure
886   // the tree up to date before processing.
887   if (aAttribute == nsGkAtoms::aria_activedescendant) {
888     mNotificationController->HandleNotification<DocAccessible, Accessible>(
889         this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
890 
891     return;
892   }
893 
894   // We treat aria-expanded as a global ARIA state for historical reasons
895   if (aAttribute == nsGkAtoms::aria_expanded) {
896     RefPtr<AccEvent> event =
897         new AccStateChangeEvent(aAccessible, states::EXPANDED);
898     FireDelayedEvent(event);
899     return;
900   }
901 
902   // For aria attributes like drag and drop changes we fire a generic attribute
903   // change event; at least until native API comes up with a more meaningful
904   // event.
905   uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
906   if (!(attrFlags & ATTR_BYPASSOBJ)) {
907     RefPtr<AccEvent> event =
908         new AccObjectAttrChangedEvent(aAccessible, aAttribute);
909     FireDelayedEvent(event);
910   }
911 
912   dom::Element* elm = aAccessible->GetContent()->AsElement();
913 
914   // Update aria-hidden flag for the whole subtree iff aria-hidden is changed
915   // on the root, i.e. ignore any affiliated aria-hidden changes in the subtree
916   // of top aria-hidden.
917   if (aAttribute == nsGkAtoms::aria_hidden) {
918     bool isDefined = aria::HasDefinedARIAHidden(elm);
919     if (isDefined != aAccessible->IsARIAHidden() &&
920         (!aAccessible->Parent() || !aAccessible->Parent()->IsARIAHidden())) {
921       aAccessible->SetARIAHidden(isDefined);
922 
923       RefPtr<AccEvent> event =
924           new AccObjectAttrChangedEvent(aAccessible, aAttribute);
925       FireDelayedEvent(event);
926     }
927     return;
928   }
929 
930   if (aAttribute == nsGkAtoms::aria_checked ||
931       (aAccessible->IsButton() && aAttribute == nsGkAtoms::aria_pressed)) {
932     const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked)
933                                 ? states::CHECKED
934                                 : states::PRESSED;
935     RefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
936     FireDelayedEvent(event);
937 
938     bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
939     bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
940                                     nsGkAtoms::mixed, eCaseMatters);
941     if (isMixed != wasMixed) {
942       RefPtr<AccEvent> event =
943           new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
944       FireDelayedEvent(event);
945     }
946     return;
947   }
948 
949   if (aAttribute == nsGkAtoms::aria_readonly) {
950     RefPtr<AccEvent> event =
951         new AccStateChangeEvent(aAccessible, states::READONLY);
952     FireDelayedEvent(event);
953     return;
954   }
955 
956   // Fire text value change event whenever aria-valuetext is changed.
957   if (aAttribute == nsGkAtoms::aria_valuetext) {
958     FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
959     return;
960   }
961 
962   // Fire numeric value change event when aria-valuenow is changed and
963   // aria-valuetext is empty
964   if (aAttribute == nsGkAtoms::aria_valuenow &&
965       (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
966        elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
967                         nsGkAtoms::_empty, eCaseMatters))) {
968     FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
969     return;
970   }
971 
972   if (aAttribute == nsGkAtoms::aria_current) {
973     RefPtr<AccEvent> event =
974         new AccStateChangeEvent(aAccessible, states::CURRENT);
975     FireDelayedEvent(event);
976     return;
977   }
978 
979   if (aAttribute == nsGkAtoms::aria_owns) {
980     mNotificationController->ScheduleRelocation(aAccessible);
981   }
982 }
983 
ARIAActiveDescendantChanged(Accessible * aAccessible)984 void DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible) {
985   nsIContent* elm = aAccessible->GetContent();
986   if (elm && elm->IsElement() && aAccessible->IsActiveWidget()) {
987     nsAutoString id;
988     if (elm->AsElement()->GetAttr(kNameSpaceID_None,
989                                   nsGkAtoms::aria_activedescendant, id)) {
990       dom::Element* activeDescendantElm = elm->OwnerDoc()->GetElementById(id);
991       if (activeDescendantElm) {
992         Accessible* activeDescendant = GetAccessible(activeDescendantElm);
993         if (activeDescendant) {
994           FocusMgr()->ActiveItemChanged(activeDescendant, false);
995 #ifdef A11Y_LOG
996           if (logging::IsEnabled(logging::eFocus))
997             logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
998                                               activeDescendant);
999 #endif
1000         }
1001       }
1002     }
1003   }
1004 }
1005 
ContentAppended(nsIContent * aFirstNewContent)1006 void DocAccessible::ContentAppended(nsIContent* aFirstNewContent) {}
1007 
ContentStateChanged(nsIDocument * aDocument,nsIContent * aContent,EventStates aStateMask)1008 void DocAccessible::ContentStateChanged(nsIDocument* aDocument,
1009                                         nsIContent* aContent,
1010                                         EventStates aStateMask) {
1011   Accessible* accessible = GetAccessible(aContent);
1012   if (!accessible) return;
1013 
1014   if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
1015     Accessible* widget = accessible->ContainerWidget();
1016     if (widget && widget->IsSelect()) {
1017       AccSelChangeEvent::SelChangeType selChangeType =
1018           aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED)
1019               ? AccSelChangeEvent::eSelectionAdd
1020               : AccSelChangeEvent::eSelectionRemove;
1021       RefPtr<AccEvent> event =
1022           new AccSelChangeEvent(widget, accessible, selChangeType);
1023       FireDelayedEvent(event);
1024       return;
1025     }
1026 
1027     RefPtr<AccEvent> event = new AccStateChangeEvent(
1028         accessible, states::CHECKED,
1029         aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
1030     FireDelayedEvent(event);
1031   }
1032 
1033   if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
1034     RefPtr<AccEvent> event =
1035         new AccStateChangeEvent(accessible, states::INVALID, true);
1036     FireDelayedEvent(event);
1037   }
1038 
1039   if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
1040     RefPtr<AccEvent> event =
1041         new AccStateChangeEvent(accessible, states::TRAVERSED, true);
1042     FireDelayedEvent(event);
1043   }
1044 }
1045 
DocumentStatesChanged(nsIDocument * aDocument,EventStates aStateMask)1046 void DocAccessible::DocumentStatesChanged(nsIDocument* aDocument,
1047                                           EventStates aStateMask) {}
1048 
CharacterDataWillChange(nsIContent * aContent,const CharacterDataChangeInfo &)1049 void DocAccessible::CharacterDataWillChange(nsIContent* aContent,
1050                                             const CharacterDataChangeInfo&) {}
1051 
CharacterDataChanged(nsIContent * aContent,const CharacterDataChangeInfo &)1052 void DocAccessible::CharacterDataChanged(nsIContent* aContent,
1053                                          const CharacterDataChangeInfo&) {}
1054 
ContentInserted(nsIContent * aChild)1055 void DocAccessible::ContentInserted(nsIContent* aChild) {}
1056 
ContentRemoved(nsIContent * aChildNode,nsIContent * aPreviousSiblingNode)1057 void DocAccessible::ContentRemoved(nsIContent* aChildNode,
1058                                    nsIContent* aPreviousSiblingNode) {
1059 #ifdef A11Y_LOG
1060   if (logging::IsEnabled(logging::eTree)) {
1061     logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
1062     logging::Node("container node", aChildNode->GetParent());
1063     logging::Node("content node", aChildNode);
1064     logging::MsgEnd();
1065   }
1066 #endif
1067   // This one and content removal notification from layout may result in
1068   // double processing of same subtrees. If it pops up in profiling, then
1069   // consider reusing a document node cache to reject these notifications early.
1070   ContentRemoved(aChildNode);
1071 }
1072 
ParentChainChanged(nsIContent * aContent)1073 void DocAccessible::ParentChainChanged(nsIContent* aContent) {}
1074 
1075   ////////////////////////////////////////////////////////////////////////////////
1076   // Accessible
1077 
1078 #ifdef A11Y_LOG
HandleAccEvent(AccEvent * aEvent)1079 nsresult DocAccessible::HandleAccEvent(AccEvent* aEvent) {
1080   if (logging::IsEnabled(logging::eDocLoad))
1081     logging::DocLoadEventHandled(aEvent);
1082 
1083   return HyperTextAccessible::HandleAccEvent(aEvent);
1084 }
1085 #endif
1086 
1087 ////////////////////////////////////////////////////////////////////////////////
1088 // Public members
1089 
GetNativeWindow() const1090 void* DocAccessible::GetNativeWindow() const {
1091   if (!mPresShell) return nullptr;
1092 
1093   nsViewManager* vm = mPresShell->GetViewManager();
1094   if (!vm) return nullptr;
1095 
1096   nsCOMPtr<nsIWidget> widget;
1097   vm->GetRootWidget(getter_AddRefs(widget));
1098   if (widget) return widget->GetNativeData(NS_NATIVE_WINDOW);
1099 
1100   return nullptr;
1101 }
1102 
GetAccessibleByUniqueIDInSubtree(void * aUniqueID)1103 Accessible* DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID) {
1104   Accessible* child = GetAccessibleByUniqueID(aUniqueID);
1105   if (child) return child;
1106 
1107   uint32_t childDocCount = mChildDocuments.Length();
1108   for (uint32_t childDocIdx = 0; childDocIdx < childDocCount; childDocIdx++) {
1109     DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
1110     child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
1111     if (child) return child;
1112   }
1113 
1114   return nullptr;
1115 }
1116 
GetAccessibleOrContainer(nsINode * aNode) const1117 Accessible* DocAccessible::GetAccessibleOrContainer(nsINode* aNode) const {
1118   if (!aNode || !aNode->GetComposedDoc()) return nullptr;
1119 
1120   for (nsINode* currNode = aNode; currNode;
1121        currNode = currNode->GetFlattenedTreeParentNode()) {
1122     if (Accessible* accessible = GetAccessible(currNode)) {
1123       return accessible;
1124     }
1125   }
1126 
1127   return nullptr;
1128 }
1129 
GetAccessibleOrDescendant(nsINode * aNode) const1130 Accessible* DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const {
1131   Accessible* acc = GetAccessible(aNode);
1132   if (acc) return acc;
1133 
1134   acc = GetContainerAccessible(aNode);
1135   if (acc) {
1136     uint32_t childCnt = acc->ChildCount();
1137     for (uint32_t idx = 0; idx < childCnt; idx++) {
1138       Accessible* child = acc->GetChildAt(idx);
1139       for (nsIContent* elm = child->GetContent();
1140            elm && elm != acc->GetContent();
1141            elm = elm->GetFlattenedTreeParent()) {
1142         if (elm == aNode) return child;
1143       }
1144     }
1145   }
1146 
1147   return nullptr;
1148 }
1149 
BindToDocument(Accessible * aAccessible,const nsRoleMapEntry * aRoleMapEntry)1150 void DocAccessible::BindToDocument(Accessible* aAccessible,
1151                                    const nsRoleMapEntry* aRoleMapEntry) {
1152   // Put into DOM node cache.
1153   if (aAccessible->IsNodeMapEntry())
1154     mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
1155 
1156   // Put into unique ID cache.
1157   mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible);
1158 
1159   aAccessible->SetRoleMapEntry(aRoleMapEntry);
1160 
1161   if (aAccessible->HasOwnContent()) {
1162     AddDependentIDsFor(aAccessible);
1163 
1164     nsIContent* content = aAccessible->GetContent();
1165     if (content->IsElement() && content->AsElement()->HasAttr(
1166                                     kNameSpaceID_None, nsGkAtoms::aria_owns)) {
1167       mNotificationController->ScheduleRelocation(aAccessible);
1168     }
1169   }
1170 }
1171 
UnbindFromDocument(Accessible * aAccessible)1172 void DocAccessible::UnbindFromDocument(Accessible* aAccessible) {
1173   NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
1174                "Unbinding the unbound accessible!");
1175 
1176   // Fire focus event on accessible having DOM focus if active item was removed
1177   // from the tree.
1178   if (FocusMgr()->IsActiveItem(aAccessible)) {
1179     FocusMgr()->ActiveItemChanged(nullptr);
1180 #ifdef A11Y_LOG
1181     if (logging::IsEnabled(logging::eFocus))
1182       logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
1183 #endif
1184   }
1185 
1186   // Remove an accessible from node-to-accessible map if it exists there.
1187   if (aAccessible->IsNodeMapEntry() &&
1188       mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
1189     mNodeToAccessibleMap.Remove(aAccessible->GetNode());
1190 
1191   aAccessible->mStateFlags |= eIsNotInDocument;
1192 
1193   // Update XPCOM part.
1194   xpcAccessibleDocument* xpcDoc = GetAccService()->GetCachedXPCDocument(this);
1195   if (xpcDoc) xpcDoc->NotifyOfShutdown(aAccessible);
1196 
1197   void* uniqueID = aAccessible->UniqueID();
1198 
1199   NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
1200   aAccessible->Shutdown();
1201 
1202   mAccessibleCache.Remove(uniqueID);
1203 }
1204 
ContentInserted(nsIContent * aContainerNode,nsIContent * aStartChildNode,nsIContent * aEndChildNode)1205 void DocAccessible::ContentInserted(nsIContent* aContainerNode,
1206                                     nsIContent* aStartChildNode,
1207                                     nsIContent* aEndChildNode) {
1208   // Ignore content insertions until we constructed accessible tree. Otherwise
1209   // schedule tree update on content insertion after layout.
1210   if (mNotificationController && HasLoadState(eTreeConstructed)) {
1211     // Update the whole tree of this document accessible when the container is
1212     // null (document element is inserted or removed).
1213     Accessible* container =
1214         aContainerNode ? AccessibleOrTrueContainer(aContainerNode) : this;
1215     if (container) {
1216       // Ignore notification if the container node is no longer in the DOM tree.
1217       mNotificationController->ScheduleContentInsertion(
1218           container, aStartChildNode, aEndChildNode);
1219     }
1220   }
1221 }
1222 
RecreateAccessible(nsIContent * aContent)1223 void DocAccessible::RecreateAccessible(nsIContent* aContent) {
1224 #ifdef A11Y_LOG
1225   if (logging::IsEnabled(logging::eTree)) {
1226     logging::MsgBegin("TREE", "accessible recreated");
1227     logging::Node("content", aContent);
1228     logging::MsgEnd();
1229   }
1230 #endif
1231 
1232   // XXX: we shouldn't recreate whole accessible subtree, instead we should
1233   // subclass hide and show events to handle them separately and implement their
1234   // coalescence with normal hide and show events. Note, in this case they
1235   // should be coalesced with normal show/hide events.
1236 
1237   nsIContent* parent = aContent->GetFlattenedTreeParent();
1238   ContentRemoved(aContent);
1239   ContentInserted(parent, aContent, aContent->GetNextSibling());
1240 }
1241 
ProcessInvalidationList()1242 void DocAccessible::ProcessInvalidationList() {
1243   // Invalidate children of container accessible for each element in
1244   // invalidation list. Allow invalidation list insertions while container
1245   // children are recached.
1246   for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
1247     nsIContent* content = mInvalidationList[idx];
1248     if (!HasAccessible(content) && content->HasID()) {
1249       Accessible* container = GetContainerAccessible(content);
1250       if (container) {
1251         // Check if the node is a target of aria-owns, and if so, don't process
1252         // it here and let DoARIAOwnsRelocation process it.
1253         AttrRelProviderArray* list =
1254             mDependentIDsHash.Get(nsDependentAtomString(content->GetID()));
1255         bool shouldProcess = !!list;
1256         if (shouldProcess) {
1257           for (uint32_t idx = 0; idx < list->Length(); idx++) {
1258             if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
1259               shouldProcess = false;
1260               break;
1261             }
1262           }
1263 
1264           if (shouldProcess) {
1265             ProcessContentInserted(container, content);
1266           }
1267         }
1268       }
1269     }
1270   }
1271 
1272   mInvalidationList.Clear();
1273 }
1274 
GetAccessibleEvenIfNotInMap(nsINode * aNode) const1275 Accessible* DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const {
1276   if (!aNode->IsContent() ||
1277       !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area))
1278     return GetAccessible(aNode);
1279 
1280   // XXX Bug 135040, incorrect when multiple images use the same map.
1281   nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
1282   nsImageFrame* imageFrame = do_QueryFrame(frame);
1283   if (imageFrame) {
1284     Accessible* parent = GetAccessible(imageFrame->GetContent());
1285     if (parent) {
1286       Accessible* area = parent->AsImageMap()->GetChildAccessibleFor(aNode);
1287       if (area) return area;
1288 
1289       return nullptr;
1290     }
1291   }
1292 
1293   return GetAccessible(aNode);
1294 }
1295 
1296 ////////////////////////////////////////////////////////////////////////////////
1297 // Protected members
1298 
NotifyOfLoading(bool aIsReloading)1299 void DocAccessible::NotifyOfLoading(bool aIsReloading) {
1300   // Mark the document accessible as loading, if it stays alive then we'll mark
1301   // it as loaded when we receive proper notification.
1302   mLoadState &= ~eDOMLoaded;
1303 
1304   if (!IsLoadEventTarget()) return;
1305 
1306   if (aIsReloading && !mLoadEventType) {
1307     // Fire reload and state busy events on existing document accessible while
1308     // event from user input flag can be calculated properly and accessible
1309     // is alive. When new document gets loaded then this one is destroyed.
1310     RefPtr<AccEvent> reloadEvent =
1311         new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
1312     nsEventShell::FireEvent(reloadEvent);
1313   }
1314 
1315   // Fire state busy change event. Use delayed event since we don't care
1316   // actually if event isn't delivered when the document goes away like a shot.
1317   RefPtr<AccEvent> stateEvent =
1318       new AccStateChangeEvent(this, states::BUSY, true);
1319   FireDelayedEvent(stateEvent);
1320 }
1321 
DoInitialUpdate()1322 void DocAccessible::DoInitialUpdate() {
1323   if (nsCoreUtils::IsTabDocument(mDocumentNode)) {
1324     mDocFlags |= eTabDocument;
1325     if (IPCAccessibilityActive()) {
1326       nsIDocShell* docShell = mDocumentNode->GetDocShell();
1327       if (RefPtr<dom::TabChild> tabChild = dom::TabChild::GetFrom(docShell)) {
1328         DocAccessibleChild* ipcDoc = new DocAccessibleChild(this, tabChild);
1329         SetIPCDoc(ipcDoc);
1330         if (IsRoot()) {
1331           tabChild->SetTopLevelDocAccessibleChild(ipcDoc);
1332         }
1333 
1334 #if defined(XP_WIN)
1335         IAccessibleHolder holder(CreateHolderFromAccessible(WrapNotNull(this)));
1336         MOZ_DIAGNOSTIC_ASSERT(!holder.IsNull());
1337         int32_t childID = AccessibleWrap::GetChildIDFor(this);
1338 #else
1339         int32_t holder = 0, childID = 0;
1340 #endif
1341         tabChild->SendPDocAccessibleConstructor(ipcDoc, nullptr, 0, childID,
1342                                                 holder);
1343       }
1344     }
1345   }
1346 
1347   mLoadState |= eTreeConstructed;
1348 
1349   // Set up a root element and ARIA role mapping.
1350   UpdateRootElIfNeeded();
1351 
1352   // Build initial tree.
1353   CacheChildrenInSubtree(this);
1354 #ifdef A11Y_LOG
1355   if (logging::IsEnabled(logging::eVerbose)) {
1356     logging::Tree("TREE", "Initial subtree", this);
1357   }
1358 #endif
1359 
1360   // Fire reorder event after the document tree is constructed. Note, since
1361   // this reorder event is processed by parent document then events targeted to
1362   // this document may be fired prior to this reorder event. If this is
1363   // a problem then consider to keep event processing per tab document.
1364   if (!IsRoot()) {
1365     RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
1366     ParentDocument()->FireDelayedEvent(reorderEvent);
1367   }
1368 
1369   if (IPCAccessibilityActive()) {
1370     DocAccessibleChild* ipcDoc = IPCDoc();
1371     MOZ_ASSERT(ipcDoc);
1372     if (ipcDoc) {
1373       for (auto idx = 0U; idx < mChildren.Length(); idx++) {
1374         ipcDoc->InsertIntoIpcTree(this, mChildren.ElementAt(idx), idx);
1375       }
1376     }
1377   }
1378 }
1379 
ProcessLoad()1380 void DocAccessible::ProcessLoad() {
1381   mLoadState |= eCompletelyLoaded;
1382 
1383 #ifdef A11Y_LOG
1384   if (logging::IsEnabled(logging::eDocLoad))
1385     logging::DocCompleteLoad(this, IsLoadEventTarget());
1386 #endif
1387 
1388   // Do not fire document complete/stop events for root chrome document
1389   // accessibles and for frame/iframe documents because
1390   // a) screen readers start working on focus event in the case of root chrome
1391   // documents
1392   // b) document load event on sub documents causes screen readers to act is if
1393   // entire page is reloaded.
1394   if (!IsLoadEventTarget()) return;
1395 
1396   // Fire complete/load stopped if the load event type is given.
1397   if (mLoadEventType) {
1398     RefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
1399     FireDelayedEvent(loadEvent);
1400 
1401     mLoadEventType = 0;
1402   }
1403 
1404   // Fire busy state change event.
1405   RefPtr<AccEvent> stateEvent =
1406       new AccStateChangeEvent(this, states::BUSY, false);
1407   FireDelayedEvent(stateEvent);
1408 }
1409 
AddDependentIDsFor(Accessible * aRelProvider,nsAtom * aRelAttr)1410 void DocAccessible::AddDependentIDsFor(Accessible* aRelProvider,
1411                                        nsAtom* aRelAttr) {
1412   dom::Element* relProviderEl = aRelProvider->Elm();
1413   if (!relProviderEl) return;
1414 
1415   for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
1416     nsAtom* relAttr = *kRelationAttrs[idx];
1417     if (aRelAttr && aRelAttr != relAttr) continue;
1418 
1419     if (relAttr == nsGkAtoms::_for) {
1420       if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
1421                                               nsGkAtoms::output))
1422         continue;
1423 
1424     } else if (relAttr == nsGkAtoms::control) {
1425       if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
1426                                              nsGkAtoms::description))
1427         continue;
1428     }
1429 
1430     IDRefsIterator iter(this, relProviderEl, relAttr);
1431     while (true) {
1432       const nsDependentSubstring id = iter.NextID();
1433       if (id.IsEmpty()) break;
1434 
1435       nsIContent* dependentContent = iter.GetElem(id);
1436       if (relAttr == nsGkAtoms::aria_owns && dependentContent &&
1437           !aRelProvider->IsAcceptableChild(dependentContent))
1438         continue;
1439 
1440       AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
1441       if (!providers) {
1442         providers = new AttrRelProviderArray();
1443         if (providers) {
1444           mDependentIDsHash.Put(id, providers);
1445         }
1446       }
1447 
1448       if (providers) {
1449         AttrRelProvider* provider = new AttrRelProvider(relAttr, relProviderEl);
1450         if (provider) {
1451           providers->AppendElement(provider);
1452 
1453           // We've got here during the children caching. If the referenced
1454           // content is not accessible then store it to pend its container
1455           // children invalidation (this happens immediately after the caching
1456           // is finished).
1457           if (dependentContent) {
1458             if (!HasAccessible(dependentContent)) {
1459               mInvalidationList.AppendElement(dependentContent);
1460             }
1461           }
1462         }
1463       }
1464     }
1465 
1466     // If the relation attribute is given then we don't have anything else to
1467     // check.
1468     if (aRelAttr) break;
1469   }
1470 
1471   // Make sure to schedule the tree update if needed.
1472   mNotificationController->ScheduleProcessing();
1473 }
1474 
RemoveDependentIDsFor(Accessible * aRelProvider,nsAtom * aRelAttr)1475 void DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
1476                                           nsAtom* aRelAttr) {
1477   dom::Element* relProviderElm = aRelProvider->Elm();
1478   if (!relProviderElm) return;
1479 
1480   for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
1481     nsAtom* relAttr = *kRelationAttrs[idx];
1482     if (aRelAttr && aRelAttr != *kRelationAttrs[idx]) continue;
1483 
1484     IDRefsIterator iter(this, relProviderElm, relAttr);
1485     while (true) {
1486       const nsDependentSubstring id = iter.NextID();
1487       if (id.IsEmpty()) break;
1488 
1489       AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
1490       if (providers) {
1491         for (uint32_t jdx = 0; jdx < providers->Length();) {
1492           AttrRelProvider* provider = (*providers)[jdx];
1493           if (provider->mRelAttr == relAttr &&
1494               provider->mContent == relProviderElm)
1495             providers->RemoveElement(provider);
1496           else
1497             jdx++;
1498         }
1499         if (providers->Length() == 0) mDependentIDsHash.Remove(id);
1500       }
1501     }
1502 
1503     // If the relation attribute is given then we don't have anything else to
1504     // check.
1505     if (aRelAttr) break;
1506   }
1507 }
1508 
UpdateAccessibleOnAttrChange(dom::Element * aElement,nsAtom * aAttribute)1509 bool DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
1510                                                  nsAtom* aAttribute) {
1511   if (aAttribute == nsGkAtoms::role) {
1512     // It is common for js libraries to set the role on the body element after
1513     // the document has loaded. In this case we just update the role map entry.
1514     if (mContent == aElement) {
1515       SetRoleMapEntry(aria::GetRoleMap(aElement));
1516       if (mIPCDoc) {
1517         mIPCDoc->SendRoleChangedEvent(Role());
1518       }
1519 
1520       return true;
1521     }
1522 
1523     // Recreate the accessible when role is changed because we might require a
1524     // different accessible class for the new role or the accessible may expose
1525     // a different sets of interfaces (COM restriction).
1526     RecreateAccessible(aElement);
1527 
1528     return true;
1529   }
1530 
1531   if (aAttribute == nsGkAtoms::href) {
1532     // Not worth the expense to ensure which namespace these are in. It doesn't
1533     // kill use to recreate the accessible even if the attribute was used in
1534     // the wrong namespace or an element that doesn't support it.
1535 
1536     // Make sure the accessible is recreated asynchronously to allow the content
1537     // to handle the attribute change.
1538     RecreateAccessible(aElement);
1539     return true;
1540   }
1541 
1542   if (aAttribute == nsGkAtoms::aria_multiselectable &&
1543       aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
1544     // This affects whether the accessible supports SelectAccessible.
1545     // COM says we cannot change what interfaces are supported on-the-fly,
1546     // so invalidate this object. A new one will be created on demand.
1547     RecreateAccessible(aElement);
1548 
1549     return true;
1550   }
1551 
1552   return false;
1553 }
1554 
UpdateRootElIfNeeded()1555 void DocAccessible::UpdateRootElIfNeeded() {
1556   dom::Element* rootEl = mDocumentNode->GetBodyElement();
1557   if (!rootEl) {
1558     rootEl = mDocumentNode->GetRootElement();
1559   }
1560   if (rootEl != mContent) {
1561     mContent = rootEl;
1562     SetRoleMapEntry(aria::GetRoleMap(rootEl));
1563     if (mIPCDoc) {
1564       mIPCDoc->SendRoleChangedEvent(Role());
1565     }
1566   }
1567 }
1568 
1569 /**
1570  * Content insertion helper.
1571  */
1572 class InsertIterator final {
1573  public:
InsertIterator(Accessible * aContext,const nsTArray<nsCOMPtr<nsIContent>> * aNodes)1574   InsertIterator(Accessible* aContext,
1575                  const nsTArray<nsCOMPtr<nsIContent> >* aNodes)
1576       : mChild(nullptr),
1577         mChildBefore(nullptr),
1578         mWalker(aContext),
1579         mNodes(aNodes),
1580         mNodesIdx(0) {
1581     MOZ_ASSERT(aContext, "No context");
1582     MOZ_ASSERT(aNodes, "No nodes to search for accessible elements");
1583     MOZ_COUNT_CTOR(InsertIterator);
1584   }
~InsertIterator()1585   ~InsertIterator() { MOZ_COUNT_DTOR(InsertIterator); }
1586 
Context() const1587   Accessible* Context() const { return mWalker.Context(); }
Child() const1588   Accessible* Child() const { return mChild; }
ChildBefore() const1589   Accessible* ChildBefore() const { return mChildBefore; }
Document() const1590   DocAccessible* Document() const { return mWalker.Document(); }
1591 
1592   /**
1593    * Iterates to a next accessible within the inserted content.
1594    */
1595   bool Next();
1596 
Rejected()1597   void Rejected() {
1598     mChild = nullptr;
1599     mChildBefore = nullptr;
1600   }
1601 
1602  private:
1603   Accessible* mChild;
1604   Accessible* mChildBefore;
1605   TreeWalker mWalker;
1606 
1607   const nsTArray<nsCOMPtr<nsIContent> >* mNodes;
1608   uint32_t mNodesIdx;
1609 };
1610 
Next()1611 bool InsertIterator::Next() {
1612   if (mNodesIdx > 0) {
1613     Accessible* nextChild = mWalker.Next();
1614     if (nextChild) {
1615       mChildBefore = mChild;
1616       mChild = nextChild;
1617       return true;
1618     }
1619   }
1620 
1621   while (mNodesIdx < mNodes->Length()) {
1622     // Ignore nodes that are not contained by the container anymore.
1623 
1624     // The container might be changed, for example, because of the subsequent
1625     // overlapping content insertion (i.e. other content was inserted between
1626     // this inserted content and its container or the content was reinserted
1627     // into different container of unrelated part of tree). To avoid a double
1628     // processing of the content insertion ignore this insertion notification.
1629     // Note, the inserted content might be not in tree at all at this point
1630     // what means there's no container. Ignore the insertion too.
1631     nsIContent* prevNode = mNodes->SafeElementAt(mNodesIdx - 1);
1632     nsIContent* node = mNodes->ElementAt(mNodesIdx++);
1633     Accessible* container = Document()->AccessibleOrTrueContainer(node);
1634     if (container != Context()) {
1635       continue;
1636     }
1637 
1638     // HTML comboboxes have no-content list accessible as an intermediate
1639     // containing all options.
1640     if (container->IsHTMLCombobox()) {
1641       container = container->FirstChild();
1642     }
1643 
1644     if (!container->IsAcceptableChild(node)) {
1645       continue;
1646     }
1647 
1648 #ifdef A11Y_LOG
1649     logging::TreeInfo("traversing an inserted node", logging::eVerbose,
1650                       "container", container, "node", node);
1651 #endif
1652 
1653     // If inserted nodes are siblings then just move the walker next.
1654     if (mChild && prevNode && prevNode->GetNextSibling() == node) {
1655       Accessible* nextChild = mWalker.Scope(node);
1656       if (nextChild) {
1657         mChildBefore = mChild;
1658         mChild = nextChild;
1659         return true;
1660       }
1661     } else {
1662       TreeWalker finder(container);
1663       if (finder.Seek(node)) {
1664         mChild = mWalker.Scope(node);
1665         if (mChild) {
1666           MOZ_ASSERT(!mChild->IsRelocated(), "child cannot be aria owned");
1667           mChildBefore = finder.Prev();
1668           return true;
1669         }
1670       }
1671     }
1672   }
1673 
1674   return false;
1675 }
1676 
ProcessContentInserted(Accessible * aContainer,const nsTArray<nsCOMPtr<nsIContent>> * aNodes)1677 void DocAccessible::ProcessContentInserted(
1678     Accessible* aContainer, const nsTArray<nsCOMPtr<nsIContent> >* aNodes) {
1679   // Process insertions if the container accessible is still in tree.
1680   if (!aContainer->IsInDocument()) {
1681     return;
1682   }
1683 
1684   // If new root content has been inserted then update it.
1685   if (aContainer == this) {
1686     UpdateRootElIfNeeded();
1687   }
1688 
1689   InsertIterator iter(aContainer, aNodes);
1690   if (!iter.Next()) {
1691     return;
1692   }
1693 
1694 #ifdef A11Y_LOG
1695   logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
1696 #endif
1697 
1698   TreeMutation mt(aContainer);
1699   do {
1700     Accessible* parent = iter.Child()->Parent();
1701     if (parent) {
1702       if (parent != aContainer) {
1703 #ifdef A11Y_LOG
1704         logging::TreeInfo("stealing accessible", 0, "old parent", parent,
1705                           "new parent", aContainer, "child", iter.Child(),
1706                           nullptr);
1707 #endif
1708         MOZ_ASSERT_UNREACHABLE("stealing accessible");
1709         continue;
1710       }
1711 
1712 #ifdef A11Y_LOG
1713       logging::TreeInfo("binding to same parent", logging::eVerbose, "parent",
1714                         aContainer, "child", iter.Child(), nullptr);
1715 #endif
1716       continue;
1717     }
1718 
1719     if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
1720 #ifdef A11Y_LOG
1721       logging::TreeInfo("accessible was inserted", 0, "container", aContainer,
1722                         "child", iter.Child(), nullptr);
1723 #endif
1724 
1725       CreateSubtree(iter.Child());
1726       mt.AfterInsertion(iter.Child());
1727       continue;
1728     }
1729 
1730     MOZ_ASSERT_UNREACHABLE("accessible was rejected");
1731     iter.Rejected();
1732   } while (iter.Next());
1733 
1734   mt.Done();
1735 
1736 #ifdef A11Y_LOG
1737   logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
1738 #endif
1739 
1740   FireEventsOnInsertion(aContainer);
1741 }
1742 
ProcessContentInserted(Accessible * aContainer,nsIContent * aNode)1743 void DocAccessible::ProcessContentInserted(Accessible* aContainer,
1744                                            nsIContent* aNode) {
1745   if (!aContainer->IsInDocument()) {
1746     return;
1747   }
1748 
1749 #ifdef A11Y_LOG
1750   logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
1751 #endif
1752 
1753 #ifdef A11Y_LOG
1754   logging::TreeInfo("traversing an inserted node", logging::eVerbose,
1755                     "container", aContainer, "node", aNode);
1756 #endif
1757 
1758   TreeWalker walker(aContainer);
1759   if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
1760     Accessible* child = GetAccessible(aNode);
1761     if (!child) {
1762       child = GetAccService()->CreateAccessible(aNode, aContainer);
1763     }
1764 
1765     if (child) {
1766       TreeMutation mt(aContainer);
1767       if (!aContainer->InsertAfter(child, walker.Prev())) {
1768         return;
1769       }
1770       CreateSubtree(child);
1771       mt.AfterInsertion(child);
1772       mt.Done();
1773 
1774       FireEventsOnInsertion(aContainer);
1775     }
1776   }
1777 
1778 #ifdef A11Y_LOG
1779   logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
1780 #endif
1781 }
1782 
FireEventsOnInsertion(Accessible * aContainer)1783 void DocAccessible::FireEventsOnInsertion(Accessible* aContainer) {
1784   // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
1785   // if it did.
1786   if (aContainer->IsAlert() || aContainer->IsInsideAlert()) {
1787     Accessible* ancestor = aContainer;
1788     do {
1789       if (ancestor->IsAlert()) {
1790         FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
1791         break;
1792       }
1793     } while ((ancestor = ancestor->Parent()));
1794   }
1795 }
1796 
ContentRemoved(Accessible * aChild)1797 void DocAccessible::ContentRemoved(Accessible* aChild) {
1798   Accessible* parent = aChild->Parent();
1799   MOZ_DIAGNOSTIC_ASSERT(parent, "Unattached accessible from tree");
1800 
1801 #ifdef A11Y_LOG
1802   logging::TreeInfo("process content removal", 0, "container", parent, "child",
1803                     aChild, nullptr);
1804 #endif
1805 
1806   // XXX: event coalescence may kill us
1807   RefPtr<Accessible> kungFuDeathGripChild(aChild);
1808 
1809   TreeMutation mt(parent);
1810   mt.BeforeRemoval(aChild);
1811 
1812   if (aChild->IsDefunct()) {
1813     MOZ_ASSERT_UNREACHABLE("Event coalescence killed the accessible");
1814     mt.Done();
1815     return;
1816   }
1817 
1818   MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Alive but unparented #1");
1819 
1820   if (aChild->IsRelocated()) {
1821     nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(parent);
1822     MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
1823     owned->RemoveElement(aChild);
1824     if (owned->Length() == 0) {
1825       mARIAOwnsHash.Remove(parent);
1826     }
1827   }
1828   MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Unparented #2");
1829   parent->RemoveChild(aChild);
1830   UncacheChildrenInSubtree(aChild);
1831 
1832   mt.Done();
1833 }
1834 
ContentRemoved(nsIContent * aContentNode)1835 void DocAccessible::ContentRemoved(nsIContent* aContentNode) {
1836   // If child node is not accessible then look for its accessible children.
1837   Accessible* acc = GetAccessible(aContentNode);
1838   if (acc) {
1839     ContentRemoved(acc);
1840   }
1841 
1842   dom::AllChildrenIterator iter =
1843       dom::AllChildrenIterator(aContentNode, nsIContent::eAllChildren, true);
1844   while (nsIContent* childNode = iter.GetNextChild()) {
1845     ContentRemoved(childNode);
1846   }
1847 }
1848 
RelocateARIAOwnedIfNeeded(nsIContent * aElement)1849 bool DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement) {
1850   if (!aElement->HasID()) return false;
1851 
1852   AttrRelProviderArray* list =
1853       mDependentIDsHash.Get(nsDependentAtomString(aElement->GetID()));
1854   if (list) {
1855     for (uint32_t idx = 0; idx < list->Length(); idx++) {
1856       if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
1857         Accessible* owner = GetAccessible(list->ElementAt(idx)->mContent);
1858         if (owner) {
1859           mNotificationController->ScheduleRelocation(owner);
1860           return true;
1861         }
1862       }
1863     }
1864   }
1865 
1866   return false;
1867 }
1868 
DoARIAOwnsRelocation(Accessible * aOwner)1869 void DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) {
1870   MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
1871   MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
1872 
1873 #ifdef A11Y_LOG
1874   logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
1875 #endif
1876 
1877   nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.LookupOrAdd(aOwner);
1878 
1879   IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
1880   uint32_t idx = 0;
1881   while (nsIContent* childEl = iter.NextElem()) {
1882     Accessible* child = GetAccessible(childEl);
1883     auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
1884 
1885     // Make an attempt to create an accessible if it wasn't created yet.
1886     if (!child) {
1887       if (aOwner->IsAcceptableChild(childEl)) {
1888         child = GetAccService()->CreateAccessible(childEl, aOwner);
1889         if (child) {
1890           TreeMutation imut(aOwner);
1891           aOwner->InsertChildAt(insertIdx, child);
1892           imut.AfterInsertion(child);
1893           imut.Done();
1894 
1895           child->SetRelocated(true);
1896           owned->InsertElementAt(idx, child);
1897           idx++;
1898 
1899           // Create subtree before adjusting the insertion index, since subtree
1900           // creation may alter children in the container.
1901           CreateSubtree(child);
1902           FireEventsOnInsertion(aOwner);
1903         }
1904       }
1905       continue;
1906     }
1907 
1908 #ifdef A11Y_LOG
1909     logging::TreeInfo("aria owns traversal", logging::eVerbose, "candidate",
1910                       child, nullptr);
1911 #endif
1912 
1913     if (owned->IndexOf(child) < idx) {
1914       continue;  // ignore second entry of same ID
1915     }
1916 
1917     // Same child on same position, no change.
1918     if (child->Parent() == aOwner) {
1919       int32_t indexInParent = child->IndexInParent();
1920 
1921       // The child is being placed in its current index,
1922       // eg. aria-owns='id1 id2 id3' is changed to aria-owns='id3 id2 id1'.
1923       if (indexInParent == static_cast<int32_t>(insertIdx)) {
1924         MOZ_ASSERT(child->IsRelocated(),
1925                    "A child, having an index in parent from aria ownded "
1926                    "indices range, has to be aria owned");
1927         MOZ_ASSERT(owned->ElementAt(idx) == child,
1928                    "Unexpected child in ARIA owned array");
1929         idx++;
1930         continue;
1931       }
1932 
1933       // The child is being inserted directly after its current index,
1934       // resulting in a no-move case. This will happen when a parent aria-owns
1935       // its last ordinal child:
1936       // <ul aria-owns='id2'><li id='id1'></li><li id='id2'></li></ul>
1937       if (indexInParent == static_cast<int32_t>(insertIdx) - 1) {
1938         MOZ_ASSERT(!child->IsRelocated(),
1939                    "Child should be in its ordinal position");
1940         child->SetRelocated(true);
1941         owned->InsertElementAt(idx, child);
1942         idx++;
1943         continue;
1944       }
1945     }
1946 
1947     MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
1948 
1949     // A new child is found, check for loops.
1950     if (child->Parent() != aOwner) {
1951       // Child is aria-owned by another container, skip.
1952       if (child->IsRelocated()) {
1953         continue;
1954       }
1955 
1956       Accessible* parent = aOwner;
1957       while (parent && parent != child && !parent->IsDoc()) {
1958         parent = parent->Parent();
1959       }
1960       // A referred child cannot be a parent of the owner.
1961       if (parent == child) {
1962         continue;
1963       }
1964     }
1965 
1966     if (MoveChild(child, aOwner, insertIdx)) {
1967       child->SetRelocated(true);
1968       MOZ_ASSERT(owned == mARIAOwnsHash.Get(aOwner));
1969       owned = mARIAOwnsHash.LookupOrAdd(aOwner);
1970       owned->InsertElementAt(idx, child);
1971       idx++;
1972     }
1973   }
1974 
1975   // Put back children that are not seized anymore.
1976   PutChildrenBack(owned, idx);
1977   if (owned->Length() == 0) {
1978     mARIAOwnsHash.Remove(aOwner);
1979   }
1980 }
1981 
PutChildrenBack(nsTArray<RefPtr<Accessible>> * aChildren,uint32_t aStartIdx)1982 void DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
1983                                     uint32_t aStartIdx) {
1984   MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
1985 
1986   for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
1987     Accessible* child = aChildren->ElementAt(idx);
1988     if (!child->IsInDocument()) {
1989       continue;
1990     }
1991 
1992     // Remove the child from the owner
1993     Accessible* owner = child->Parent();
1994     if (!owner) {
1995       NS_ERROR("Cannot put the child back. No parent, a broken tree.");
1996       continue;
1997     }
1998 
1999 #ifdef A11Y_LOG
2000     logging::TreeInfo("aria owns put child back", 0, "old parent", owner,
2001                       "child", child, nullptr);
2002 #endif
2003 
2004     // Unset relocated flag to find an insertion point for the child.
2005     child->SetRelocated(false);
2006 
2007     nsIContent* content = child->GetContent();
2008     int32_t idxInParent = -1;
2009     Accessible* origContainer =
2010         AccessibleOrTrueContainer(content->GetFlattenedTreeParentNode());
2011     if (origContainer) {
2012       TreeWalker walker(origContainer);
2013       if (walker.Seek(content)) {
2014         Accessible* prevChild = walker.Prev();
2015         if (prevChild) {
2016           idxInParent = prevChild->IndexInParent() + 1;
2017           MOZ_DIAGNOSTIC_ASSERT(origContainer == prevChild->Parent(),
2018                                 "Broken tree");
2019           origContainer = prevChild->Parent();
2020         } else {
2021           idxInParent = 0;
2022         }
2023       }
2024     }
2025 
2026     // The child may have already be in its ordinal place for 2 reasons:
2027     // 1. It was the last ordinal child, and the first aria-owned child.
2028     //    given:      <ul id="list" aria-owns="b"><li id="a"></li><li
2029     //    id="b"></li></ul> after load: $("list").setAttribute("aria-owns", "");
2030     // 2. The preceding adopted children were just reclaimed, eg:
2031     //    given:      <ul id="list"><li id="b"></li></ul>
2032     //    after load: $("list").setAttribute("aria-owns", "a b");
2033     //    later:      $("list").setAttribute("aria-owns", "");
2034     if (origContainer != owner || child->IndexInParent() != idxInParent) {
2035       DebugOnly<bool> moved = MoveChild(child, origContainer, idxInParent);
2036       MOZ_ASSERT(moved, "Failed to put child back.");
2037     } else {
2038       MOZ_ASSERT(!child->PrevSibling() || !child->PrevSibling()->IsRelocated(),
2039                  "No relocated child should appear before this one");
2040       MOZ_ASSERT(!child->NextSibling() || child->NextSibling()->IsRelocated(),
2041                  "No ordinal child should appear after this one");
2042     }
2043   }
2044 
2045   aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
2046 }
2047 
MoveChild(Accessible * aChild,Accessible * aNewParent,int32_t aIdxInParent)2048 bool DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
2049                               int32_t aIdxInParent) {
2050   MOZ_ASSERT(aChild, "No child");
2051   MOZ_ASSERT(aChild->Parent(), "No parent");
2052   MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
2053              "Wrong insertion point for a moving child");
2054 
2055   Accessible* curParent = aChild->Parent();
2056 
2057   if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
2058     return false;
2059   }
2060 
2061 #ifdef A11Y_LOG
2062   logging::TreeInfo("move child", 0, "old parent", curParent, "new parent",
2063                     aNewParent, "child", aChild, nullptr);
2064 #endif
2065 
2066   // Forget aria-owns info in case of ARIA owned element. The caller is expected
2067   // to update it if needed.
2068   if (aChild->IsRelocated()) {
2069     aChild->SetRelocated(false);
2070     nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(curParent);
2071     MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
2072     owned->RemoveElement(aChild);
2073     if (owned->Length() == 0) {
2074       mARIAOwnsHash.Remove(curParent);
2075     }
2076   }
2077 
2078   NotificationController::MoveGuard mguard(mNotificationController);
2079 
2080   if (curParent == aNewParent) {
2081     MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
2082     curParent->MoveChild(aIdxInParent, aChild);
2083 
2084 #ifdef A11Y_LOG
2085     logging::TreeInfo("move child: parent tree after", logging::eVerbose,
2086                       curParent);
2087 #endif
2088     return true;
2089   }
2090 
2091   MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
2092              "Wrong insertion point for a moving child");
2093 
2094   // If the child cannot be re-inserted into the tree, then make sure to remove
2095   // it from its present parent and then shutdown it.
2096   bool hasInsertionPoint =
2097       (aIdxInParent != -1) ||
2098       (aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()));
2099 
2100   TreeMutation rmut(curParent);
2101   rmut.BeforeRemoval(aChild, hasInsertionPoint && TreeMutation::kNoShutdown);
2102   curParent->RemoveChild(aChild);
2103   rmut.Done();
2104 
2105   // No insertion point for the child.
2106   if (!hasInsertionPoint) {
2107     return true;
2108   }
2109 
2110   TreeMutation imut(aNewParent);
2111   aNewParent->InsertChildAt(aIdxInParent, aChild);
2112   imut.AfterInsertion(aChild);
2113   imut.Done();
2114 
2115 #ifdef A11Y_LOG
2116   logging::TreeInfo("move child: old parent tree after", logging::eVerbose,
2117                     curParent);
2118   logging::TreeInfo("move child: new parent tree after", logging::eVerbose,
2119                     aNewParent);
2120 #endif
2121 
2122   return true;
2123 }
2124 
CacheChildrenInSubtree(Accessible * aRoot,Accessible ** aFocusedAcc)2125 void DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
2126                                            Accessible** aFocusedAcc) {
2127   // If the accessible is focused then report a focus event after all related
2128   // mutation events.
2129   if (aFocusedAcc && !*aFocusedAcc &&
2130       FocusMgr()->HasDOMFocus(aRoot->GetContent()))
2131     *aFocusedAcc = aRoot;
2132 
2133   Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot;
2134   if (root->KidsFromDOM()) {
2135     TreeMutation mt(root, TreeMutation::kNoEvents);
2136     TreeWalker walker(root);
2137     while (Accessible* child = walker.Next()) {
2138       if (child->IsBoundToParent()) {
2139         MoveChild(child, root, root->ChildCount());
2140         continue;
2141       }
2142 
2143       root->AppendChild(child);
2144       mt.AfterInsertion(child);
2145 
2146       CacheChildrenInSubtree(child, aFocusedAcc);
2147     }
2148     mt.Done();
2149   }
2150 
2151   // Fire events for ARIA elements.
2152   if (!aRoot->HasARIARole()) {
2153     return;
2154   }
2155 
2156   // XXX: we should delay document load complete event if the ARIA document
2157   // has aria-busy.
2158   roles::Role role = aRoot->ARIARole();
2159   if (!aRoot->IsDoc() &&
2160       (role == roles::DIALOG || role == roles::NON_NATIVE_DOCUMENT)) {
2161     FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
2162   }
2163 }
2164 
UncacheChildrenInSubtree(Accessible * aRoot)2165 void DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot) {
2166   aRoot->mStateFlags |= eIsNotInDocument;
2167   RemoveDependentIDsFor(aRoot);
2168 
2169   nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(aRoot);
2170   uint32_t count = aRoot->ContentChildCount();
2171   for (uint32_t idx = 0; idx < count; idx++) {
2172     Accessible* child = aRoot->ContentChildAt(idx);
2173 
2174     if (child->IsRelocated()) {
2175       MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
2176       owned->RemoveElement(child);
2177       if (owned->Length() == 0) {
2178         mARIAOwnsHash.Remove(aRoot);
2179         owned = nullptr;
2180       }
2181     }
2182 
2183     // Removing this accessible from the document doesn't mean anything about
2184     // accessibles for subdocuments, so skip removing those from the tree.
2185     if (!child->IsDoc()) {
2186       UncacheChildrenInSubtree(child);
2187     }
2188   }
2189 
2190   if (aRoot->IsNodeMapEntry() &&
2191       mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
2192     mNodeToAccessibleMap.Remove(aRoot->GetNode());
2193 }
2194 
ShutdownChildrenInSubtree(Accessible * aAccessible)2195 void DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible) {
2196   // Traverse through children and shutdown them before this accessible. When
2197   // child gets shutdown then it removes itself from children array of its
2198   // parent. Use jdx index to process the cases if child is not attached to the
2199   // parent and as result doesn't remove itself from its children.
2200   uint32_t count = aAccessible->ContentChildCount();
2201   for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
2202     Accessible* child = aAccessible->ContentChildAt(jdx);
2203     if (!child->IsBoundToParent()) {
2204       NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
2205       jdx++;
2206     }
2207 
2208     // Don't cross document boundaries. The outerdoc shutdown takes care about
2209     // its subdocument.
2210     if (!child->IsDoc()) ShutdownChildrenInSubtree(child);
2211   }
2212 
2213   UnbindFromDocument(aAccessible);
2214 }
2215 
IsLoadEventTarget() const2216 bool DocAccessible::IsLoadEventTarget() const {
2217   nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
2218   NS_ASSERTION(treeItem, "No document shell for document!");
2219 
2220   nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
2221   treeItem->GetParent(getter_AddRefs(parentTreeItem));
2222 
2223   // Not a root document.
2224   if (parentTreeItem) {
2225     // Return true if it's either:
2226     // a) tab document;
2227     nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
2228     treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
2229     if (parentTreeItem == rootTreeItem) return true;
2230 
2231     // b) frame/iframe document and its parent document is not in loading state
2232     // Note: we can get notifications while document is loading (and thus
2233     // while there's no parent document yet).
2234     DocAccessible* parentDoc = ParentDocument();
2235     return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
2236   }
2237 
2238   // It's content (not chrome) root document.
2239   return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
2240 }
2241