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); } }
// RootAccessible protected void RootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent) { MOZ_ASSERT(aDOMEvent); nsDOMEvent* event = aDOMEvent->InternalDOMEvent(); nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(event->GetOriginalTarget()); nsAutoString eventType; aDOMEvent->GetType(eventType); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eDOMEvents)) logging::DOMEvent("processed", origTargetNode, eventType); #endif if (eventType.EqualsLiteral("popuphiding")) { HandlePopupHidingEvent(origTargetNode); return; } DocAccessible* targetDocument = GetAccService()-> GetDocAccessible(origTargetNode->OwnerDoc()); NS_ASSERTION(targetDocument, "No document while accessible is in document?!"); Accessible* accessible = targetDocument->GetAccessibleOrContainer(origTargetNode); if (!accessible) return; #ifdef MOZ_XUL XULTreeAccessible* treeAcc = accessible->AsXULTree(); if (treeAcc) { if (eventType.EqualsLiteral("TreeRowCountChanged")) { HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); return; } if (eventType.EqualsLiteral("TreeInvalidated")) { HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); return; } } #endif if (eventType.EqualsLiteral("RadioStateChange")) { uint64_t state = accessible->State(); // radiogroup in prefWindow is exposed as a list, // and panebutton is exposed as XULListitem in A11y. // XULListitemAccessible::GetStateInternal uses STATE_SELECTED in this case, // so we need to check states::SELECTED also. bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0; nsRefPtr<AccEvent> accEvent = new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); nsEventShell::FireEvent(accEvent); if (isEnabled) { FocusMgr()->ActiveItemChanged(accessible); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("RadioStateChange", accessible); #endif } return; } if (eventType.EqualsLiteral("CheckboxStateChange")) { uint64_t state = accessible->State(); bool isEnabled = !!(state & states::CHECKED); nsRefPtr<AccEvent> accEvent = new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); nsEventShell::FireEvent(accEvent); return; } Accessible* treeItemAcc = nullptr; #ifdef MOZ_XUL // If it's a tree element, need the currently selected item. if (treeAcc) { treeItemAcc = accessible->CurrentItem(); if (treeItemAcc) accessible = treeItemAcc; } if (treeItemAcc && eventType.EqualsLiteral("OpenStateChange")) { uint64_t state = accessible->State(); bool isEnabled = (state & states::EXPANDED) != 0; nsRefPtr<AccEvent> accEvent = new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled); nsEventShell::FireEvent(accEvent); return; } nsINode* targetNode = accessible->GetNode(); if (treeItemAcc && eventType.EqualsLiteral("select")) { // XXX: We shouldn't be based on DOM select event which doesn't provide us // any context info. We should integrate into nsTreeSelection instead. // If multiselect tree, we should fire selectionadd or selection removed if (FocusMgr()->HasDOMFocus(targetNode)) { nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel = do_QueryInterface(targetNode); nsAutoString selType; multiSel->GetSelType(selType); if (selType.IsEmpty() || !selType.EqualsLiteral("single")) { // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE // for each tree item. Perhaps each tree item will need to cache its // selection state and fire an event after a DOM "select" event when // that state changes. XULTreeAccessible::UpdateTreeSelection(); nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, accessible); return; } nsRefPtr<AccSelChangeEvent> selChangeEvent = new AccSelChangeEvent(treeAcc, treeItemAcc, AccSelChangeEvent::eSelectionAdd); nsEventShell::FireEvent(selChangeEvent); return; } } else #endif if (eventType.EqualsLiteral("AlertActive")) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible); } else if (eventType.EqualsLiteral("popupshown")) { HandlePopupShownEvent(accessible); } else if (eventType.EqualsLiteral("DOMMenuInactive")) { if (accessible->Role() == roles::MENUPOPUP) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, accessible); } } else if (eventType.EqualsLiteral("DOMMenuItemActive")) { FocusMgr()->ActiveItemChanged(accessible); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible); #endif } else if (eventType.EqualsLiteral("DOMMenuItemInactive")) { // Process DOMMenuItemInactive event for autocomplete only because this is // unique widget that may acquire focus from autocomplete popup while popup // stays open and has no active item. In case of XUL tree autocomplete // popup this event is fired for tree accessible. Accessible* widget = accessible->IsWidget() ? accessible : accessible->ContainerWidget(); if (widget && widget->IsAutoCompletePopup()) { FocusMgr()->ActiveItemChanged(nullptr); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("DOMMenuItemInactive", accessible); #endif } } else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always from user input nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START, accessible, eFromUserInput); // Notify of active item change when menubar gets active and if it has // current item. This is a case of mouseover (set current menuitem) and // mouse click (activate the menubar). If menubar doesn't have current item // (can be a case of menubar activation from keyboard) then ignore this // notification because later we'll receive DOMMenuItemActive event after // current menuitem is set. Accessible* activeItem = accessible->CurrentItem(); if (activeItem) { FocusMgr()->ActiveItemChanged(activeItem); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("DOMMenuBarActive", accessible); #endif } } else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always from user input nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END, accessible, eFromUserInput); FocusMgr()->ActiveItemChanged(nullptr); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) logging::ActiveItemChangeCausedBy("DOMMenuBarInactive", accessible); #endif } else if (eventType.EqualsLiteral("ValueChange")) { //We don't process 'ValueChange' events for progress meters since we listen //@value attribute change for them. if (!accessible->IsProgress()) { targetDocument->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, accessible); } } #ifdef DEBUG_DRAGDROPSTART else if (eventType.EqualsLiteral("mouseover")) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START, accessible); } #endif }