1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "RootAccessible.h"
7 
8 #include "mozilla/ArrayUtils.h"
9 #include "mozilla/PresShell.h"  // for nsAccUtils::GetDocAccessibleFor()
10 
11 #define CreateEvent CreateEventA
12 
13 #include "LocalAccessible-inl.h"
14 #include "DocAccessible-inl.h"
15 #include "mozilla/a11y/DocAccessibleParent.h"
16 #include "nsAccessibilityService.h"
17 #include "nsAccUtils.h"
18 #include "nsCoreUtils.h"
19 #include "nsEventShell.h"
20 #include "Relation.h"
21 #include "Role.h"
22 #include "States.h"
23 #ifdef MOZ_XUL
24 #  include "XULTreeAccessible.h"
25 #endif
26 
27 #include "mozilla/dom/BindingUtils.h"
28 #include "mozilla/dom/CustomEvent.h"
29 #include "mozilla/dom/Element.h"
30 #include "mozilla/dom/ScriptSettings.h"
31 #include "mozilla/dom/BrowserHost.h"
32 
33 #include "nsIDocShellTreeOwner.h"
34 #include "mozilla/dom/Event.h"
35 #include "mozilla/dom/EventTarget.h"
36 #include "nsIDOMXULMultSelectCntrlEl.h"
37 #include "mozilla/dom/Document.h"
38 #include "nsIInterfaceRequestorUtils.h"
39 #include "nsIPropertyBag2.h"
40 #include "nsPIDOMWindow.h"
41 #include "nsIWebBrowserChrome.h"
42 #include "nsReadableUtils.h"
43 #include "nsFocusManager.h"
44 #include "nsGlobalWindow.h"
45 
46 #ifdef MOZ_XUL
47 #  include "nsIAppWindow.h"
48 #endif
49 
50 using namespace mozilla;
51 using namespace mozilla::a11y;
52 using namespace mozilla::dom;
53 
54 ////////////////////////////////////////////////////////////////////////////////
55 // nsISupports
56 
NS_IMPL_ISUPPORTS_INHERITED(RootAccessible,DocAccessible,nsIDOMEventListener)57 NS_IMPL_ISUPPORTS_INHERITED(RootAccessible, DocAccessible, nsIDOMEventListener)
58 
59 ////////////////////////////////////////////////////////////////////////////////
60 // Constructor/destructor
61 
62 RootAccessible::RootAccessible(Document* aDocument, PresShell* aPresShell)
63     : DocAccessibleWrap(aDocument, aPresShell) {
64   mType = eRootType;
65 }
66 
~RootAccessible()67 RootAccessible::~RootAccessible() {}
68 
69 ////////////////////////////////////////////////////////////////////////////////
70 // LocalAccessible
71 
Name(nsString & aName) const72 ENameValueFlag RootAccessible::Name(nsString& aName) const {
73   aName.Truncate();
74 
75   if (ARIARoleMap()) {
76     LocalAccessible::Name(aName);
77     if (!aName.IsEmpty()) return eNameOK;
78   }
79 
80   mDocumentNode->GetTitle(aName);
81   return eNameOK;
82 }
83 
84 // RootAccessible protected member
85 #ifdef MOZ_XUL
GetChromeFlags() const86 uint32_t RootAccessible::GetChromeFlags() const {
87   // Return the flag set for the top level window as defined
88   // by nsIWebBrowserChrome::CHROME_WINDOW_[FLAGNAME]
89   // Not simple: nsIAppWindow is not just a QI from nsIDOMWindow
90   nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
91   NS_ENSURE_TRUE(docShell, 0);
92   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
93   docShell->GetTreeOwner(getter_AddRefs(treeOwner));
94   NS_ENSURE_TRUE(treeOwner, 0);
95   nsCOMPtr<nsIAppWindow> appWin(do_GetInterface(treeOwner));
96   if (!appWin) {
97     return 0;
98   }
99   uint32_t chromeFlags;
100   appWin->GetChromeFlags(&chromeFlags);
101   return chromeFlags;
102 }
103 #endif
104 
NativeState() const105 uint64_t RootAccessible::NativeState() const {
106   uint64_t state = DocAccessibleWrap::NativeState();
107   if (state & states::DEFUNCT) return state;
108 
109 #ifdef MOZ_XUL
110   uint32_t chromeFlags = GetChromeFlags();
111   if (chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) {
112     state |= states::SIZEABLE;
113   }
114   // If it has a titlebar it's movable
115   // XXX unless it's minimized or maximized, but not sure
116   //     how to detect that
117   if (chromeFlags & nsIWebBrowserChrome::CHROME_TITLEBAR) {
118     state |= states::MOVEABLE;
119   }
120   if (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) state |= states::MODAL;
121 #endif
122 
123   nsFocusManager* fm = nsFocusManager::GetFocusManager();
124   if (fm && fm->GetActiveWindow() == mDocumentNode->GetWindow()) {
125     state |= states::ACTIVE;
126   }
127 
128   return state;
129 }
130 
131 const char* const kEventTypes[] = {
132 #ifdef DEBUG_DRAGDROPSTART
133     // Capture mouse over events and fire fake DRAGDROPSTART event to simplify
134     // debugging a11y objects with event viewers.
135     "mouseover",
136 #endif
137     // Fired when list or tree selection changes.
138     "select",
139     // Fired when value changes immediately, wether or not focused changed.
140     "ValueChange", "AlertActive", "TreeRowCountChanged", "TreeInvalidated",
141     // add ourself as a OpenStateChange listener (custom event fired in
142     // tree.xml)
143     "OpenStateChange",
144     // add ourself as a CheckboxStateChange listener (custom event fired in
145     // HTMLInputElement.cpp)
146     "CheckboxStateChange",
147     // add ourself as a RadioStateChange Listener (custom event fired in in
148     // HTMLInputElement.cpp & radio.js)
149     "RadioStateChange", "popupshown", "popuphiding", "DOMMenuInactive",
150     "DOMMenuItemActive", "DOMMenuItemInactive", "DOMMenuBarActive",
151     "DOMMenuBarInactive", "scroll"};
152 
AddEventListeners()153 nsresult RootAccessible::AddEventListeners() {
154   // EventTarget interface allows to register event listeners to
155   // receive untrusted events (synthetic events generated by untrusted code).
156   // For example, XBL bindings implementations for elements that are hosted in
157   // non chrome document fire untrusted events.
158   // We must use the window's parent target in order to receive events from
159   // iframes and shadow DOM; e.g. ValueChange events from a <select> in an
160   // iframe or shadow DOM. The root document itself doesn't receive these.
161   nsPIDOMWindowOuter* window = mDocumentNode->GetWindow();
162   nsCOMPtr<EventTarget> nstarget = window ? window->GetParentTarget() : nullptr;
163 
164   if (nstarget) {
165     for (const char *const *e = kEventTypes, *const *e_end =
166                                                  ArrayEnd(kEventTypes);
167          e < e_end; ++e) {
168       nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e), this,
169                                                true, true);
170       NS_ENSURE_SUCCESS(rv, rv);
171     }
172   }
173 
174   return DocAccessible::AddEventListeners();
175 }
176 
RemoveEventListeners()177 nsresult RootAccessible::RemoveEventListeners() {
178   nsPIDOMWindowOuter* window = mDocumentNode->GetWindow();
179   nsCOMPtr<EventTarget> target = window ? window->GetParentTarget() : nullptr;
180   if (target) {
181     for (const char *const *e = kEventTypes, *const *e_end =
182                                                  ArrayEnd(kEventTypes);
183          e < e_end; ++e) {
184       target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, true);
185     }
186   }
187 
188   // Do this before removing clearing caret accessible, so that it can use
189   // shutdown the caret accessible's selection listener
190   DocAccessible::RemoveEventListeners();
191   return NS_OK;
192 }
193 
194 ////////////////////////////////////////////////////////////////////////////////
195 // public
196 
DocumentActivated(DocAccessible * aDocument)197 void RootAccessible::DocumentActivated(DocAccessible* aDocument) {}
198 
199 ////////////////////////////////////////////////////////////////////////////////
200 // nsIDOMEventListener
201 
202 NS_IMETHODIMP
HandleEvent(Event * aDOMEvent)203 RootAccessible::HandleEvent(Event* aDOMEvent) {
204   MOZ_ASSERT(aDOMEvent);
205   if (IsDefunct()) {
206     // Even though we've been shut down, RemoveEventListeners might not have
207     // removed the event handlers on the window's parent target if GetWindow
208     // returned null, so we might still get events here in this case. We should
209     // just ignore these events.
210     return NS_OK;
211   }
212 
213   nsCOMPtr<nsINode> origTargetNode =
214       do_QueryInterface(aDOMEvent->GetOriginalTarget());
215   if (!origTargetNode) return NS_OK;
216 
217 #ifdef A11Y_LOG
218   if (logging::IsEnabled(logging::eDOMEvents)) {
219     nsAutoString eventType;
220     aDOMEvent->GetType(eventType);
221     logging::DOMEvent("handled", origTargetNode, eventType);
222   }
223 #endif
224 
225   DocAccessible* document =
226       GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc());
227 
228   if (document) {
229     nsAutoString eventType;
230     aDOMEvent->GetType(eventType);
231     if (eventType.EqualsLiteral("scroll")) {
232       // We don't put this in the notification queue for 2 reasons:
233       // 1. We will flood the queue with repetitive events.
234       // 2. Since this doesn't necessarily touch layout, we are not
235       //    guaranteed to have a WillRefresh tick any time soon.
236       document->HandleScroll(origTargetNode);
237     } else {
238       // Root accessible exists longer than any of its descendant documents so
239       // that we are guaranteed notification is processed before root accessible
240       // is destroyed.
241       // For shadow DOM, GetOriginalTarget on the Event returns null if we
242       // process the event async, so we must pass the target node as well.
243       document->HandleNotification<RootAccessible, Event, nsINode>(
244           this, &RootAccessible::ProcessDOMEvent, aDOMEvent, origTargetNode);
245     }
246   }
247 
248   return NS_OK;
249 }
250 
251 // RootAccessible protected
ProcessDOMEvent(Event * aDOMEvent,nsINode * aTarget)252 void RootAccessible::ProcessDOMEvent(Event* aDOMEvent, nsINode* aTarget) {
253   MOZ_ASSERT(aDOMEvent);
254   MOZ_ASSERT(aTarget);
255 
256   nsAutoString eventType;
257   aDOMEvent->GetType(eventType);
258 
259 #ifdef A11Y_LOG
260   if (logging::IsEnabled(logging::eDOMEvents)) {
261     logging::DOMEvent("processed", aTarget, eventType);
262   }
263 #endif
264 
265   if (eventType.EqualsLiteral("popuphiding")) {
266     HandlePopupHidingEvent(aTarget);
267     return;
268   }
269 
270   DocAccessible* targetDocument =
271       GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
272   if (!targetDocument) {
273     // Document has ceased to exist.
274     return;
275   }
276 
277   if (eventType.EqualsLiteral("popupshown") &&
278       (aTarget->IsXULElement(nsGkAtoms::tooltip) ||
279        aTarget->IsXULElement(nsGkAtoms::panel))) {
280     targetDocument->ContentInserted(aTarget->AsContent(),
281                                     aTarget->GetNextSibling());
282     return;
283   }
284 
285   LocalAccessible* accessible =
286       targetDocument->GetAccessibleOrContainer(aTarget);
287   if (!accessible) return;
288 
289 #ifdef MOZ_XUL
290   XULTreeAccessible* treeAcc = accessible->AsXULTree();
291   if (treeAcc) {
292     if (eventType.EqualsLiteral("TreeRowCountChanged")) {
293       HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc);
294       return;
295     }
296 
297     if (eventType.EqualsLiteral("TreeInvalidated")) {
298       HandleTreeInvalidatedEvent(aDOMEvent, treeAcc);
299       return;
300     }
301   }
302 #endif
303 
304   if (eventType.EqualsLiteral("RadioStateChange")) {
305     uint64_t state = accessible->State();
306     bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0;
307 
308     if (accessible->NeedsDOMUIEvent()) {
309       RefPtr<AccEvent> accEvent =
310           new AccStateChangeEvent(accessible, states::CHECKED, isEnabled);
311       nsEventShell::FireEvent(accEvent);
312     }
313 
314     if (isEnabled) {
315       FocusMgr()->ActiveItemChanged(accessible);
316 #ifdef A11Y_LOG
317       if (logging::IsEnabled(logging::eFocus)) {
318         logging::ActiveItemChangeCausedBy("RadioStateChange", accessible);
319       }
320 #endif
321     }
322 
323     return;
324   }
325 
326   if (eventType.EqualsLiteral("CheckboxStateChange")) {
327     if (accessible->NeedsDOMUIEvent()) {
328       uint64_t state = accessible->State();
329       bool isEnabled = !!(state & states::CHECKED);
330 
331       RefPtr<AccEvent> accEvent =
332           new AccStateChangeEvent(accessible, states::CHECKED, isEnabled);
333       nsEventShell::FireEvent(accEvent);
334     }
335     return;
336   }
337 
338   LocalAccessible* treeItemAcc = nullptr;
339 #ifdef MOZ_XUL
340   // If it's a tree element, need the currently selected item.
341   if (treeAcc) {
342     treeItemAcc = accessible->CurrentItem();
343     if (treeItemAcc) accessible = treeItemAcc;
344   }
345 
346   if (treeItemAcc && eventType.EqualsLiteral("OpenStateChange")) {
347     uint64_t state = accessible->State();
348     bool isEnabled = (state & states::EXPANDED) != 0;
349 
350     RefPtr<AccEvent> accEvent =
351         new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled);
352     nsEventShell::FireEvent(accEvent);
353     return;
354   }
355 
356   nsINode* targetNode = accessible->GetNode();
357   if (treeItemAcc && eventType.EqualsLiteral("select")) {
358     // XXX: We shouldn't be based on DOM select event which doesn't provide us
359     // any context info. We should integrate into nsTreeSelection instead.
360     // If multiselect tree, we should fire selectionadd or selection removed
361     if (FocusMgr()->HasDOMFocus(targetNode)) {
362       nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel =
363           targetNode->AsElement()->AsXULMultiSelectControl();
364       if (!multiSel) {
365         // This shouldn't be possible. All XUL trees should have
366         // nsIDOMXULMultiSelectControlElement, and the tree is focused, so it
367         // shouldn't be dying. Nevertheless, this sometimes happens in the wild
368         // (bug 1597043).
369         MOZ_ASSERT_UNREACHABLE(
370             "XUL tree doesn't have nsIDOMXULMultiSelectControlElement");
371         return;
372       }
373       nsAutoString selType;
374       multiSel->GetSelType(selType);
375       if (selType.IsEmpty() || !selType.EqualsLiteral("single")) {
376         // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE
377         // for each tree item. Perhaps each tree item will need to cache its
378         // selection state and fire an event after a DOM "select" event when
379         // that state changes. XULTreeAccessible::UpdateTreeSelection();
380         nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
381                                 accessible);
382         return;
383       }
384 
385       RefPtr<AccSelChangeEvent> selChangeEvent = new AccSelChangeEvent(
386           treeAcc, treeItemAcc, AccSelChangeEvent::eSelectionAdd);
387       nsEventShell::FireEvent(selChangeEvent);
388       return;
389     }
390   } else
391 #endif
392       if (eventType.EqualsLiteral("AlertActive")) {
393     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible);
394   } else if (eventType.EqualsLiteral("popupshown")) {
395     HandlePopupShownEvent(accessible);
396   } else if (eventType.EqualsLiteral("DOMMenuInactive")) {
397     if (accessible->Role() == roles::MENUPOPUP) {
398       nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
399                               accessible);
400     }
401   } else if (eventType.EqualsLiteral("DOMMenuItemActive")) {
402     FocusMgr()->ActiveItemChanged(accessible);
403 #ifdef A11Y_LOG
404     if (logging::IsEnabled(logging::eFocus)) {
405       logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible);
406     }
407 #endif
408   } else if (eventType.EqualsLiteral("DOMMenuItemInactive")) {
409     // Process DOMMenuItemInactive event for autocomplete only because this is
410     // unique widget that may acquire focus from autocomplete popup while popup
411     // stays open and has no active item. In case of XUL tree autocomplete
412     // popup this event is fired for tree accessible.
413     LocalAccessible* widget =
414         accessible->IsWidget() ? accessible : accessible->ContainerWidget();
415     if (widget && widget->IsAutoCompletePopup()) {
416       FocusMgr()->ActiveItemChanged(nullptr);
417 #ifdef A11Y_LOG
418       if (logging::IsEnabled(logging::eFocus)) {
419         logging::ActiveItemChangeCausedBy("DOMMenuItemInactive", accessible);
420       }
421 #endif
422     }
423   } else if (eventType.EqualsLiteral(
424                  "DOMMenuBarActive")) {  // Always from user input
425     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START, accessible,
426                             eFromUserInput);
427 
428     // Notify of active item change when menubar gets active and if it has
429     // current item. This is a case of mouseover (set current menuitem) and
430     // mouse click (activate the menubar). If menubar doesn't have current item
431     // (can be a case of menubar activation from keyboard) then ignore this
432     // notification because later we'll receive DOMMenuItemActive event after
433     // current menuitem is set.
434     LocalAccessible* activeItem = accessible->CurrentItem();
435     if (activeItem) {
436       FocusMgr()->ActiveItemChanged(activeItem);
437 #ifdef A11Y_LOG
438       if (logging::IsEnabled(logging::eFocus)) {
439         logging::ActiveItemChangeCausedBy("DOMMenuBarActive", accessible);
440       }
441 #endif
442     }
443   } else if (eventType.EqualsLiteral(
444                  "DOMMenuBarInactive")) {  // Always from user input
445     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END, accessible,
446                             eFromUserInput);
447 
448     FocusMgr()->ActiveItemChanged(nullptr);
449 #ifdef A11Y_LOG
450     if (logging::IsEnabled(logging::eFocus)) {
451       logging::ActiveItemChangeCausedBy("DOMMenuBarInactive", accessible);
452     }
453 #endif
454   } else if (accessible->NeedsDOMUIEvent() &&
455              eventType.EqualsLiteral("ValueChange")) {
456     uint32_t event = accessible->HasNumericValue()
457                          ? nsIAccessibleEvent::EVENT_VALUE_CHANGE
458                          : nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE;
459     targetDocument->FireDelayedEvent(event, accessible);
460   }
461 #ifdef DEBUG_DRAGDROPSTART
462   else if (eventType.EqualsLiteral("mouseover")) {
463     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START,
464                             accessible);
465   }
466 #endif
467 }
468 
469 ////////////////////////////////////////////////////////////////////////////////
470 // LocalAccessible
471 
Shutdown()472 void RootAccessible::Shutdown() {
473   // Called manually or by LocalAccessible::LastRelease()
474   if (HasShutdown()) {
475     return;
476   }
477   DocAccessibleWrap::Shutdown();
478 }
479 
RelationByType(RelationType aType) const480 Relation RootAccessible::RelationByType(RelationType aType) const {
481   if (!mDocumentNode || aType != RelationType::EMBEDS) {
482     return DocAccessibleWrap::RelationByType(aType);
483   }
484 
485   if (nsIDocShell* docShell = mDocumentNode->GetDocShell()) {
486     nsCOMPtr<nsIDocShellTreeOwner> owner;
487     docShell->GetTreeOwner(getter_AddRefs(owner));
488     if (owner) {
489       nsCOMPtr<nsIDocShellTreeItem> contentShell;
490       owner->GetPrimaryContentShell(getter_AddRefs(contentShell));
491       if (contentShell) {
492         return Relation(nsAccUtils::GetDocAccessibleFor(contentShell));
493       }
494     }
495   }
496 
497   return Relation();
498 }
499 
500 ////////////////////////////////////////////////////////////////////////////////
501 // Protected members
502 
HandlePopupShownEvent(LocalAccessible * aAccessible)503 void RootAccessible::HandlePopupShownEvent(LocalAccessible* aAccessible) {
504   roles::Role role = aAccessible->Role();
505 
506   if (role == roles::MENUPOPUP) {
507     // Don't fire menupopup events for combobox and autocomplete lists.
508     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
509                             aAccessible);
510     return;
511   }
512 
513   if (role == roles::COMBOBOX_LIST) {
514     // Fire expanded state change event for comboboxes and autocompeletes.
515     LocalAccessible* combobox = aAccessible->LocalParent();
516     if (!combobox) return;
517 
518     if (combobox->IsCombobox() || combobox->IsAutoComplete()) {
519       RefPtr<AccEvent> event =
520           new AccStateChangeEvent(combobox, states::EXPANDED, true);
521       nsEventShell::FireEvent(event);
522     }
523 
524     // If aria-activedescendant is present, redirect focus.
525     // This is needed for parent process <select> dropdowns, which use a
526     // menulist containing div elements instead of XUL menuitems. XUL menuitems
527     // fire DOMMenuItemActive events from layout instead.
528     MOZ_ASSERT(aAccessible->Elm());
529     if (aAccessible->Elm()->HasAttr(kNameSpaceID_None,
530                                     nsGkAtoms::aria_activedescendant)) {
531       LocalAccessible* activeDescendant = aAccessible->CurrentItem();
532       if (activeDescendant) {
533         FocusMgr()->ActiveItemChanged(activeDescendant, false);
534 #ifdef A11Y_LOG
535         if (logging::IsEnabled(logging::eFocus)) {
536           logging::ActiveItemChangeCausedBy("ARIA activedescendant on popup",
537                                             activeDescendant);
538         }
539 #endif
540       }
541     }
542   }
543 }
544 
HandlePopupHidingEvent(nsINode * aPopupNode)545 void RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode) {
546   DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode);
547   if (!document) return;
548 
549   if (aPopupNode->IsXULElement(nsGkAtoms::tooltip) ||
550       aPopupNode->IsXULElement(nsGkAtoms::panel)) {
551     document->ContentRemoved(aPopupNode->AsContent());
552     return;
553   }
554 
555   // Get popup accessible. There are cases when popup element isn't accessible
556   // but an underlying widget is and behaves like popup, an example is
557   // autocomplete popups.
558   LocalAccessible* popup = document->GetAccessible(aPopupNode);
559   if (!popup) {
560     LocalAccessible* popupContainer =
561         document->GetContainerAccessible(aPopupNode);
562     if (!popupContainer) return;
563 
564     uint32_t childCount = popupContainer->ChildCount();
565     for (uint32_t idx = 0; idx < childCount; idx++) {
566       LocalAccessible* child = popupContainer->LocalChildAt(idx);
567       if (child->IsAutoCompletePopup()) {
568         popup = child;
569         break;
570       }
571     }
572 
573     // No popup no events. Focus is managed by DOM. This is a case for
574     // menupopups of menus on Linux since there are no accessible for popups.
575     if (!popup) return;
576   }
577 
578   // In case of autocompletes and comboboxes fire state change event for
579   // expanded state. Note, HTML form autocomplete isn't a subject of state
580   // change event because they aren't autocompletes strictly speaking.
581   // When popup closes (except nested popups and menus) then fire focus event to
582   // where it was. The focus event is expected even if popup didn't take a
583   // focus.
584 
585   static const uint32_t kNotifyOfFocus = 1;
586   static const uint32_t kNotifyOfState = 2;
587   uint32_t notifyOf = 0;
588 
589   // HTML select is target of popuphidding event. Otherwise get container
590   // widget. No container widget means this is either tooltip or menupopup.
591   // No events in the former case.
592   LocalAccessible* widget = nullptr;
593   if (popup->IsCombobox()) {
594     widget = popup;
595   } else {
596     widget = popup->ContainerWidget();
597     if (!widget) {
598       if (!popup->IsMenuPopup()) return;
599 
600       widget = popup;
601     }
602   }
603 
604   if (popup->IsAutoCompletePopup()) {
605     // No focus event for autocomplete because it's managed by
606     // DOMMenuItemInactive events.
607     if (widget->IsAutoComplete()) notifyOf = kNotifyOfState;
608 
609   } else if (widget->IsCombobox()) {
610     // Fire focus for active combobox, otherwise the focus is managed by DOM
611     // focus notifications. Always fire state change event.
612     if (widget->IsActiveWidget()) notifyOf = kNotifyOfFocus;
613     notifyOf |= kNotifyOfState;
614 
615   } else if (widget->IsMenuButton()) {
616     // Can be a part of autocomplete.
617     LocalAccessible* compositeWidget = widget->ContainerWidget();
618     if (compositeWidget && compositeWidget->IsAutoComplete()) {
619       widget = compositeWidget;
620       notifyOf = kNotifyOfState;
621     }
622 
623     // Autocomplete (like searchbar) can be inactive when popup hiddens
624     notifyOf |= kNotifyOfFocus;
625 
626   } else if (widget == popup) {
627     // Top level context menus and alerts.
628     // Ignore submenus and menubar. When submenu is closed then sumbenu
629     // container menuitem takes a focus via DOMMenuItemActive notification.
630     // For menubars processing we listen DOMMenubarActive/Inactive
631     // notifications.
632     notifyOf = kNotifyOfFocus;
633   }
634 
635   // Restore focus to where it was.
636   if (notifyOf & kNotifyOfFocus) {
637     FocusMgr()->ActiveItemChanged(nullptr);
638 #ifdef A11Y_LOG
639     if (logging::IsEnabled(logging::eFocus)) {
640       logging::ActiveItemChangeCausedBy("popuphiding", popup);
641     }
642 #endif
643   }
644 
645   // Fire expanded state change event.
646   if (notifyOf & kNotifyOfState) {
647     RefPtr<AccEvent> event =
648         new AccStateChangeEvent(widget, states::EXPANDED, false);
649     document->FireDelayedEvent(event);
650   }
651 }
652 
653 #ifdef MOZ_XUL
GetPropertyBagFromEvent(Event * aEvent,nsIPropertyBag2 ** aPropertyBag)654 static void GetPropertyBagFromEvent(Event* aEvent,
655                                     nsIPropertyBag2** aPropertyBag) {
656   *aPropertyBag = nullptr;
657 
658   CustomEvent* customEvent = aEvent->AsCustomEvent();
659   if (!customEvent) return;
660 
661   AutoJSAPI jsapi;
662   if (!jsapi.Init(customEvent->GetParentObject())) return;
663 
664   JSContext* cx = jsapi.cx();
665   JS::Rooted<JS::Value> detail(cx);
666   customEvent->GetDetail(cx, &detail);
667   if (!detail.isObject()) return;
668 
669   JS::Rooted<JSObject*> detailObj(cx, &detail.toObject());
670 
671   nsresult rv;
672   nsCOMPtr<nsIPropertyBag2> propBag;
673   rv = UnwrapArg<nsIPropertyBag2>(cx, detailObj, getter_AddRefs(propBag));
674   if (NS_FAILED(rv)) return;
675 
676   propBag.forget(aPropertyBag);
677 }
678 
HandleTreeRowCountChangedEvent(Event * aEvent,XULTreeAccessible * aAccessible)679 void RootAccessible::HandleTreeRowCountChangedEvent(
680     Event* aEvent, XULTreeAccessible* aAccessible) {
681   nsCOMPtr<nsIPropertyBag2> propBag;
682   GetPropertyBagFromEvent(aEvent, getter_AddRefs(propBag));
683   if (!propBag) return;
684 
685   nsresult rv;
686   int32_t index, count;
687   rv = propBag->GetPropertyAsInt32(u"index"_ns, &index);
688   if (NS_FAILED(rv)) return;
689 
690   rv = propBag->GetPropertyAsInt32(u"count"_ns, &count);
691   if (NS_FAILED(rv)) return;
692 
693   aAccessible->InvalidateCache(index, count);
694 }
695 
HandleTreeInvalidatedEvent(Event * aEvent,XULTreeAccessible * aAccessible)696 void RootAccessible::HandleTreeInvalidatedEvent(
697     Event* aEvent, XULTreeAccessible* aAccessible) {
698   nsCOMPtr<nsIPropertyBag2> propBag;
699   GetPropertyBagFromEvent(aEvent, getter_AddRefs(propBag));
700   if (!propBag) return;
701 
702   int32_t startRow = 0, endRow = -1, startCol = 0, endCol = -1;
703   propBag->GetPropertyAsInt32(u"startrow"_ns, &startRow);
704   propBag->GetPropertyAsInt32(u"endrow"_ns, &endRow);
705   propBag->GetPropertyAsInt32(u"startcolumn"_ns, &startCol);
706   propBag->GetPropertyAsInt32(u"endcolumn"_ns, &endCol);
707 
708   aAccessible->TreeViewInvalidated(startRow, endRow, startCol, endCol);
709 }
710 #endif
711 
GetPrimaryRemoteTopLevelContentDoc() const712 RemoteAccessible* RootAccessible::GetPrimaryRemoteTopLevelContentDoc() const {
713   nsCOMPtr<nsIDocShellTreeOwner> owner;
714   mDocumentNode->GetDocShell()->GetTreeOwner(getter_AddRefs(owner));
715   NS_ENSURE_TRUE(owner, nullptr);
716 
717   nsCOMPtr<nsIRemoteTab> remoteTab;
718   owner->GetPrimaryRemoteTab(getter_AddRefs(remoteTab));
719   if (!remoteTab) {
720     return nullptr;
721   }
722 
723   auto tab = static_cast<dom::BrowserHost*>(remoteTab.get());
724   return tab->GetTopLevelDocAccessible();
725 }
726