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