void RootAccessible::HandlePopupShownEvent(Accessible* aAccessible) { roles::Role role = aAccessible->Role(); if (role == roles::MENUPOPUP) { // Don't fire menupopup events for combobox and autocomplete lists. nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aAccessible); return; } if (role == roles::TOOLTIP) { // There is a single <xul:tooltip> node which Mozilla moves around. // The accessible for it stays the same no matter where it moves. // AT's expect to get an EVENT_SHOW for the tooltip. // In event callback the tooltip's accessible will be ready. nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SHOW, aAccessible); return; } if (role == roles::COMBOBOX_LIST) { // Fire expanded state change event for comboboxes and autocompeletes. Accessible* combobox = aAccessible->Parent(); if (!combobox) return; if (combobox->IsCombobox() || combobox->IsAutoComplete()) { RefPtr<AccEvent> event = new AccStateChangeEvent(combobox, states::EXPANDED, true); if (event) nsEventShell::FireEvent(event); } } }
uint64_t XULMenuitemAccessible::NativeState() const { uint64_t state = Accessible::NativeState(); // Has Popup? if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) { state |= states::HASPOPUP; if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::open)) state |= states::EXPANDED; else state |= states::COLLAPSED; } // Checkable/checked? static Element::AttrValuesArray strings[] = { nsGkAtoms::radio, nsGkAtoms::checkbox, nullptr }; if (mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, strings, eCaseMatters) >= 0) { // Checkable? state |= states::CHECKABLE; // Checked? if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true, eCaseMatters)) state |= states::CHECKED; } // Combo box listitem bool isComboboxOption = (Role() == roles::COMBOBOX_OPTION); if (isComboboxOption) { // Is selected? bool isSelected = false; nsCOMPtr<nsIDOMXULSelectControlItemElement> item(do_QueryInterface(mContent)); NS_ENSURE_TRUE(item, state); item->GetSelected(&isSelected); // Is collapsed? bool isCollapsed = false; Accessible* parent = Parent(); if (parent && parent->State() & states::INVISIBLE) isCollapsed = true; if (isSelected) { state |= states::SELECTED; // Selected and collapsed? if (isCollapsed) { // Set selected option offscreen/invisible according to combobox state Accessible* grandParent = parent->Parent(); if (!grandParent) return state; NS_ASSERTION(grandParent->IsCombobox(), "grandparent of combobox listitem is not combobox"); uint64_t grandParentState = grandParent->State(); state &= ~(states::OFFSCREEN | states::INVISIBLE); state |= (grandParentState & states::OFFSCREEN) | (grandParentState & states::INVISIBLE) | (grandParentState & states::OPAQUE1); } // isCollapsed } // isSelected } // ROLE_COMBOBOX_OPTION return state; }
void RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode) { // Get popup accessible. There are cases when popup element isn't accessible // but an underlying widget is and behaves like popup, an example is // autocomplete popups. DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode); if (!document) return; Accessible* popup = document->GetAccessible(aPopupNode); if (!popup) { Accessible* popupContainer = document->GetContainerAccessible(aPopupNode); if (!popupContainer) return; uint32_t childCount = popupContainer->ChildCount(); for (uint32_t idx = 0; idx < childCount; idx++) { Accessible* child = popupContainer->GetChildAt(idx); if (child->IsAutoCompletePopup()) { popup = child; break; } } // No popup no events. Focus is managed by DOM. This is a case for // menupopups of menus on Linux since there are no accessible for popups. if (!popup) return; } // In case of autocompletes and comboboxes fire state change event for // expanded state. Note, HTML form autocomplete isn't a subject of state // change event because they aren't autocompletes strictly speaking. // When popup closes (except nested popups and menus) then fire focus event to // where it was. The focus event is expected even if popup didn't take a focus. static const uint32_t kNotifyOfFocus = 1; static const uint32_t kNotifyOfState = 2; uint32_t notifyOf = 0; // HTML select is target of popuphidding event. Otherwise get container // widget. No container widget means this is either tooltip or menupopup. // No events in the former case. Accessible* widget = nullptr; if (popup->IsCombobox()) { widget = popup; } else { widget = popup->ContainerWidget(); if (!widget) { if (!popup->IsMenuPopup()) return; widget = popup; } } if (popup->IsAutoCompletePopup()) { // No focus event for autocomplete because it's managed by // DOMMenuItemInactive events. if (widget->IsAutoComplete()) notifyOf = kNotifyOfState; } else if (widget->IsCombobox()) { // Fire focus for active combobox, otherwise the focus is managed by DOM // focus notifications. Always fire state change event. if (widget->IsActiveWidget()) notifyOf = kNotifyOfFocus; notifyOf |= kNotifyOfState; } else if (widget->IsMenuButton()) { // Can be a part of autocomplete. Accessible* compositeWidget = widget->ContainerWidget(); if (compositeWidget && compositeWidget->IsAutoComplete()) { widget = compositeWidget; notifyOf = kNotifyOfState; } // Autocomplete (like searchbar) can be inactive when popup hiddens notifyOf |= kNotifyOfFocus; } else if (widget == popup) { // Top level context menus and alerts. // Ignore submenus and menubar. When submenu is closed then sumbenu // container menuitem takes a focus via DOMMenuItemActive notification. // For menubars processing we listen DOMMenubarActive/Inactive // notifications. notifyOf = kNotifyOfFocus; } // Restore focus to where it was. if (notifyOf & kNotifyOfFocus) { FocusMgr()->ActiveItemChanged(nullptr); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("popuphiding", popup); #endif } // Fire expanded state change event. if (notifyOf & kNotifyOfState) { nsRefPtr<AccEvent> event = new AccStateChangeEvent(widget, states::EXPANDED, false); document->FireDelayedEvent(event); } }