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