michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "RootAccessible.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #define CreateEvent CreateEventA michael@0: #include "nsIDOMDocument.h" michael@0: michael@0: #include "Accessible-inl.h" michael@0: #include "DocAccessible-inl.h" michael@0: #include "nsAccessibilityService.h" michael@0: #include "nsAccUtils.h" michael@0: #include "nsCoreUtils.h" michael@0: #include "nsEventShell.h" michael@0: #include "Relation.h" michael@0: #include "Role.h" michael@0: #include "States.h" michael@0: #ifdef MOZ_XUL michael@0: #include "XULTreeAccessible.h" michael@0: #endif michael@0: michael@0: #include "mozilla/dom/Element.h" michael@0: michael@0: #include "nsIAccessibleRelation.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsIDocShellTreeOwner.h" michael@0: #include "mozilla/dom/Event.h" michael@0: #include "mozilla/dom/EventTarget.h" michael@0: #include "nsIDOMCustomEvent.h" michael@0: #include "nsIDOMXULMultSelectCntrlEl.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIPropertyBag2.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIWebBrowserChrome.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsFocusManager.h" michael@0: michael@0: #ifdef MOZ_XUL michael@0: #include "nsIXULDocument.h" michael@0: #include "nsIXULWindow.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::a11y; michael@0: using namespace mozilla::dom; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsISupports michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(RootAccessible, DocAccessible, nsIAccessibleDocument) michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Constructor/destructor michael@0: michael@0: RootAccessible:: michael@0: RootAccessible(nsIDocument* aDocument, nsIContent* aRootContent, michael@0: nsIPresShell* aPresShell) : michael@0: DocAccessibleWrap(aDocument, aRootContent, aPresShell) michael@0: { michael@0: mType = eRootType; michael@0: } michael@0: michael@0: RootAccessible::~RootAccessible() michael@0: { michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Accessible michael@0: michael@0: ENameValueFlag michael@0: RootAccessible::Name(nsString& aName) michael@0: { michael@0: aName.Truncate(); michael@0: michael@0: if (mRoleMapEntry) { michael@0: Accessible::Name(aName); michael@0: if (!aName.IsEmpty()) michael@0: return eNameOK; michael@0: } michael@0: michael@0: mDocumentNode->GetTitle(aName); michael@0: return eNameOK; michael@0: } michael@0: michael@0: role michael@0: RootAccessible::NativeRole() michael@0: { michael@0: // If it's a or , use roles::DIALOG instead michael@0: dom::Element* rootElm = mDocumentNode->GetRootElement(); michael@0: if (rootElm && (rootElm->Tag() == nsGkAtoms::dialog || michael@0: rootElm->Tag() == nsGkAtoms::wizard)) michael@0: return roles::DIALOG; michael@0: michael@0: return DocAccessibleWrap::NativeRole(); michael@0: } michael@0: michael@0: // RootAccessible protected member michael@0: #ifdef MOZ_XUL michael@0: uint32_t michael@0: RootAccessible::GetChromeFlags() michael@0: { michael@0: // Return the flag set for the top level window as defined michael@0: // by nsIWebBrowserChrome::CHROME_WINDOW_[FLAGNAME] michael@0: // Not simple: nsIXULWindow is not just a QI from nsIDOMWindow michael@0: nsCOMPtr docShell = nsCoreUtils::GetDocShellFor(mDocumentNode); michael@0: NS_ENSURE_TRUE(docShell, 0); michael@0: nsCOMPtr treeOwner; michael@0: docShell->GetTreeOwner(getter_AddRefs(treeOwner)); michael@0: NS_ENSURE_TRUE(treeOwner, 0); michael@0: nsCOMPtr xulWin(do_GetInterface(treeOwner)); michael@0: if (!xulWin) { michael@0: return 0; michael@0: } michael@0: uint32_t chromeFlags; michael@0: xulWin->GetChromeFlags(&chromeFlags); michael@0: return chromeFlags; michael@0: } michael@0: #endif michael@0: michael@0: uint64_t michael@0: RootAccessible::NativeState() michael@0: { michael@0: uint64_t state = DocAccessibleWrap::NativeState(); michael@0: if (state & states::DEFUNCT) michael@0: return state; michael@0: michael@0: #ifdef MOZ_XUL michael@0: uint32_t chromeFlags = GetChromeFlags(); michael@0: if (chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) michael@0: state |= states::SIZEABLE; michael@0: // If it has a titlebar it's movable michael@0: // XXX unless it's minimized or maximized, but not sure michael@0: // how to detect that michael@0: if (chromeFlags & nsIWebBrowserChrome::CHROME_TITLEBAR) michael@0: state |= states::MOVEABLE; michael@0: if (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) michael@0: state |= states::MODAL; michael@0: #endif michael@0: michael@0: nsFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: if (fm && fm->GetActiveWindow() == mDocumentNode->GetWindow()) michael@0: state |= states::ACTIVE; michael@0: michael@0: return state; michael@0: } michael@0: michael@0: const char* const kEventTypes[] = { michael@0: #ifdef DEBUG_DRAGDROPSTART michael@0: // Capture mouse over events and fire fake DRAGDROPSTART event to simplify michael@0: // debugging a11y objects with event viewers. michael@0: "mouseover", michael@0: #endif michael@0: // Fired when list or tree selection changes. michael@0: "select", michael@0: // Fired when value changes immediately, wether or not focused changed. michael@0: "ValueChange", michael@0: "AlertActive", michael@0: "TreeRowCountChanged", michael@0: "TreeInvalidated", michael@0: // add ourself as a OpenStateChange listener (custom event fired in tree.xml) michael@0: "OpenStateChange", michael@0: // add ourself as a CheckboxStateChange listener (custom event fired in HTMLInputElement.cpp) michael@0: "CheckboxStateChange", michael@0: // add ourself as a RadioStateChange Listener ( custom event fired in in HTMLInputElement.cpp & radio.xml) michael@0: "RadioStateChange", michael@0: "popupshown", michael@0: "popuphiding", michael@0: "DOMMenuInactive", michael@0: "DOMMenuItemActive", michael@0: "DOMMenuItemInactive", michael@0: "DOMMenuBarActive", michael@0: "DOMMenuBarInactive" michael@0: }; michael@0: michael@0: nsresult michael@0: RootAccessible::AddEventListeners() michael@0: { michael@0: // EventTarget interface allows to register event listeners to michael@0: // receive untrusted events (synthetic events generated by untrusted code). michael@0: // For example, XBL bindings implementations for elements that are hosted in michael@0: // non chrome document fire untrusted events. michael@0: nsCOMPtr nstarget = mDocumentNode; michael@0: michael@0: if (nstarget) { michael@0: for (const char* const* e = kEventTypes, michael@0: * const* e_end = ArrayEnd(kEventTypes); michael@0: e < e_end; ++e) { michael@0: nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e), michael@0: this, true, true, 2); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: return DocAccessible::AddEventListeners(); michael@0: } michael@0: michael@0: nsresult michael@0: RootAccessible::RemoveEventListeners() michael@0: { michael@0: nsCOMPtr target = mDocumentNode; michael@0: if (target) { michael@0: for (const char* const* e = kEventTypes, michael@0: * const* e_end = ArrayEnd(kEventTypes); michael@0: e < e_end; ++e) { michael@0: nsresult rv = target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: // Do this before removing clearing caret accessible, so that it can use michael@0: // shutdown the caret accessible's selection listener michael@0: DocAccessible::RemoveEventListeners(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // public michael@0: michael@0: void michael@0: RootAccessible::DocumentActivated(DocAccessible* aDocument) michael@0: { michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsIDOMEventListener michael@0: michael@0: NS_IMETHODIMP michael@0: RootAccessible::HandleEvent(nsIDOMEvent* aDOMEvent) michael@0: { michael@0: MOZ_ASSERT(aDOMEvent); michael@0: Event* event = aDOMEvent->InternalDOMEvent(); michael@0: nsCOMPtr origTargetNode = do_QueryInterface(event->GetOriginalTarget()); michael@0: if (!origTargetNode) michael@0: return NS_OK; michael@0: michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDOMEvents)) { michael@0: nsAutoString eventType; michael@0: aDOMEvent->GetType(eventType); michael@0: logging::DOMEvent("handled", origTargetNode, eventType); michael@0: } michael@0: #endif michael@0: michael@0: DocAccessible* document = michael@0: GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc()); michael@0: michael@0: if (document) { michael@0: // Root accessible exists longer than any of its descendant documents so michael@0: // that we are guaranteed notification is processed before root accessible michael@0: // is destroyed. michael@0: document->HandleNotification michael@0: (this, &RootAccessible::ProcessDOMEvent, aDOMEvent); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // RootAccessible protected michael@0: void michael@0: RootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent) michael@0: { michael@0: MOZ_ASSERT(aDOMEvent); michael@0: Event* event = aDOMEvent->InternalDOMEvent(); michael@0: nsCOMPtr origTargetNode = do_QueryInterface(event->GetOriginalTarget()); michael@0: michael@0: nsAutoString eventType; michael@0: aDOMEvent->GetType(eventType); michael@0: michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eDOMEvents)) michael@0: logging::DOMEvent("processed", origTargetNode, eventType); michael@0: #endif michael@0: michael@0: if (eventType.EqualsLiteral("popuphiding")) { michael@0: HandlePopupHidingEvent(origTargetNode); michael@0: return; michael@0: } michael@0: michael@0: DocAccessible* targetDocument = GetAccService()-> michael@0: GetDocAccessible(origTargetNode->OwnerDoc()); michael@0: NS_ASSERTION(targetDocument, "No document while accessible is in document?!"); michael@0: michael@0: Accessible* accessible = michael@0: targetDocument->GetAccessibleOrContainer(origTargetNode); michael@0: if (!accessible) michael@0: return; michael@0: michael@0: #ifdef MOZ_XUL michael@0: XULTreeAccessible* treeAcc = accessible->AsXULTree(); michael@0: if (treeAcc) { michael@0: if (eventType.EqualsLiteral("TreeRowCountChanged")) { michael@0: HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); michael@0: return; michael@0: } michael@0: michael@0: if (eventType.EqualsLiteral("TreeInvalidated")) { michael@0: HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); michael@0: return; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (eventType.EqualsLiteral("RadioStateChange")) { michael@0: uint64_t state = accessible->State(); michael@0: bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0; michael@0: michael@0: if (accessible->NeedsDOMUIEvent()) { michael@0: nsRefPtr accEvent = michael@0: new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); michael@0: nsEventShell::FireEvent(accEvent); michael@0: } michael@0: michael@0: if (isEnabled) { michael@0: FocusMgr()->ActiveItemChanged(accessible); michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eFocus)) michael@0: logging::ActiveItemChangeCausedBy("RadioStateChange", accessible); michael@0: #endif michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (eventType.EqualsLiteral("CheckboxStateChange")) { michael@0: if (accessible->NeedsDOMUIEvent()) { michael@0: uint64_t state = accessible->State(); michael@0: bool isEnabled = !!(state & states::CHECKED); michael@0: michael@0: nsRefPtr accEvent = michael@0: new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); michael@0: nsEventShell::FireEvent(accEvent); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: Accessible* treeItemAcc = nullptr; michael@0: #ifdef MOZ_XUL michael@0: // If it's a tree element, need the currently selected item. michael@0: if (treeAcc) { michael@0: treeItemAcc = accessible->CurrentItem(); michael@0: if (treeItemAcc) michael@0: accessible = treeItemAcc; michael@0: } michael@0: michael@0: if (treeItemAcc && eventType.EqualsLiteral("OpenStateChange")) { michael@0: uint64_t state = accessible->State(); michael@0: bool isEnabled = (state & states::EXPANDED) != 0; michael@0: michael@0: nsRefPtr accEvent = michael@0: new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled); michael@0: nsEventShell::FireEvent(accEvent); michael@0: return; michael@0: } michael@0: michael@0: nsINode* targetNode = accessible->GetNode(); michael@0: if (treeItemAcc && eventType.EqualsLiteral("select")) { michael@0: // XXX: We shouldn't be based on DOM select event which doesn't provide us michael@0: // any context info. We should integrate into nsTreeSelection instead. michael@0: // If multiselect tree, we should fire selectionadd or selection removed michael@0: if (FocusMgr()->HasDOMFocus(targetNode)) { michael@0: nsCOMPtr multiSel = michael@0: do_QueryInterface(targetNode); michael@0: nsAutoString selType; michael@0: multiSel->GetSelType(selType); michael@0: if (selType.IsEmpty() || !selType.EqualsLiteral("single")) { michael@0: // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE michael@0: // for each tree item. Perhaps each tree item will need to cache its michael@0: // selection state and fire an event after a DOM "select" event when michael@0: // that state changes. XULTreeAccessible::UpdateTreeSelection(); michael@0: nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, michael@0: accessible); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr selChangeEvent = michael@0: new AccSelChangeEvent(treeAcc, treeItemAcc, michael@0: AccSelChangeEvent::eSelectionAdd); michael@0: nsEventShell::FireEvent(selChangeEvent); michael@0: return; michael@0: } michael@0: } michael@0: else michael@0: #endif michael@0: if (eventType.EqualsLiteral("AlertActive")) { michael@0: nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible); michael@0: } michael@0: else if (eventType.EqualsLiteral("popupshown")) { michael@0: HandlePopupShownEvent(accessible); michael@0: } michael@0: else if (eventType.EqualsLiteral("DOMMenuInactive")) { michael@0: if (accessible->Role() == roles::MENUPOPUP) { michael@0: nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, michael@0: accessible); michael@0: } michael@0: } michael@0: else if (eventType.EqualsLiteral("DOMMenuItemActive")) { michael@0: FocusMgr()->ActiveItemChanged(accessible); michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eFocus)) michael@0: logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible); michael@0: #endif michael@0: } michael@0: else if (eventType.EqualsLiteral("DOMMenuItemInactive")) { michael@0: // Process DOMMenuItemInactive event for autocomplete only because this is michael@0: // unique widget that may acquire focus from autocomplete popup while popup michael@0: // stays open and has no active item. In case of XUL tree autocomplete michael@0: // popup this event is fired for tree accessible. michael@0: Accessible* widget = michael@0: accessible->IsWidget() ? accessible : accessible->ContainerWidget(); michael@0: if (widget && widget->IsAutoCompletePopup()) { michael@0: FocusMgr()->ActiveItemChanged(nullptr); michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eFocus)) michael@0: logging::ActiveItemChangeCausedBy("DOMMenuItemInactive", accessible); michael@0: #endif michael@0: } michael@0: } michael@0: else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always from user input michael@0: nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START, michael@0: accessible, eFromUserInput); michael@0: michael@0: // Notify of active item change when menubar gets active and if it has michael@0: // current item. This is a case of mouseover (set current menuitem) and michael@0: // mouse click (activate the menubar). If menubar doesn't have current item michael@0: // (can be a case of menubar activation from keyboard) then ignore this michael@0: // notification because later we'll receive DOMMenuItemActive event after michael@0: // current menuitem is set. michael@0: Accessible* activeItem = accessible->CurrentItem(); michael@0: if (activeItem) { michael@0: FocusMgr()->ActiveItemChanged(activeItem); michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eFocus)) michael@0: logging::ActiveItemChangeCausedBy("DOMMenuBarActive", accessible); michael@0: #endif michael@0: } michael@0: } michael@0: else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always from user input michael@0: nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END, michael@0: accessible, eFromUserInput); michael@0: michael@0: FocusMgr()->ActiveItemChanged(nullptr); michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eFocus)) michael@0: logging::ActiveItemChangeCausedBy("DOMMenuBarInactive", accessible); michael@0: #endif michael@0: } michael@0: else if (accessible->NeedsDOMUIEvent() && michael@0: eventType.EqualsLiteral("ValueChange")) { michael@0: targetDocument->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, michael@0: accessible); michael@0: } michael@0: #ifdef DEBUG_DRAGDROPSTART michael@0: else if (eventType.EqualsLiteral("mouseover")) { michael@0: nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START, michael@0: accessible); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Accessible michael@0: michael@0: void michael@0: RootAccessible::Shutdown() michael@0: { michael@0: // Called manually or by Accessible::LastRelease() michael@0: if (!PresShell()) michael@0: return; // Already shutdown michael@0: michael@0: DocAccessibleWrap::Shutdown(); michael@0: } michael@0: michael@0: // nsIAccessible method michael@0: Relation michael@0: RootAccessible::RelationByType(RelationType aType) michael@0: { michael@0: if (!mDocumentNode || aType != RelationType::EMBEDS) michael@0: return DocAccessibleWrap::RelationByType(aType); michael@0: michael@0: nsIDOMWindow* rootWindow = mDocumentNode->GetWindow(); michael@0: if (rootWindow) { michael@0: nsCOMPtr contentWindow; michael@0: rootWindow->GetContent(getter_AddRefs(contentWindow)); michael@0: if (contentWindow) { michael@0: nsCOMPtr contentDOMDocument; michael@0: contentWindow->GetDocument(getter_AddRefs(contentDOMDocument)); michael@0: nsCOMPtr contentDocumentNode = michael@0: do_QueryInterface(contentDOMDocument); michael@0: if (contentDocumentNode) { michael@0: DocAccessible* contentDocument = michael@0: GetAccService()->GetDocAccessible(contentDocumentNode); michael@0: if (contentDocument) michael@0: return Relation(contentDocument); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return Relation(); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Protected members michael@0: michael@0: void michael@0: RootAccessible::HandlePopupShownEvent(Accessible* aAccessible) michael@0: { michael@0: roles::Role role = aAccessible->Role(); michael@0: michael@0: if (role == roles::MENUPOPUP) { michael@0: // Don't fire menupopup events for combobox and autocomplete lists. michael@0: nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, michael@0: aAccessible); michael@0: return; michael@0: } michael@0: michael@0: if (role == roles::TOOLTIP) { michael@0: // There is a single node which Mozilla moves around. michael@0: // The accessible for it stays the same no matter where it moves. michael@0: // AT's expect to get an EVENT_SHOW for the tooltip. michael@0: // In event callback the tooltip's accessible will be ready. michael@0: nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SHOW, aAccessible); michael@0: return; michael@0: } michael@0: michael@0: if (role == roles::COMBOBOX_LIST) { michael@0: // Fire expanded state change event for comboboxes and autocompeletes. michael@0: Accessible* combobox = aAccessible->Parent(); michael@0: if (!combobox) michael@0: return; michael@0: michael@0: roles::Role comboboxRole = combobox->Role(); michael@0: if (comboboxRole == roles::COMBOBOX || michael@0: comboboxRole == roles::AUTOCOMPLETE) { michael@0: nsRefPtr event = michael@0: new AccStateChangeEvent(combobox, states::EXPANDED, true); michael@0: if (event) michael@0: nsEventShell::FireEvent(event); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode) michael@0: { michael@0: // Get popup accessible. There are cases when popup element isn't accessible michael@0: // but an underlying widget is and behaves like popup, an example is michael@0: // autocomplete popups. michael@0: DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode); michael@0: if (!document) michael@0: return; michael@0: michael@0: Accessible* popup = document->GetAccessible(aPopupNode); michael@0: if (!popup) { michael@0: Accessible* popupContainer = document->GetContainerAccessible(aPopupNode); michael@0: if (!popupContainer) michael@0: return; michael@0: michael@0: uint32_t childCount = popupContainer->ChildCount(); michael@0: for (uint32_t idx = 0; idx < childCount; idx++) { michael@0: Accessible* child = popupContainer->GetChildAt(idx); michael@0: if (child->IsAutoCompletePopup()) { michael@0: popup = child; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // No popup no events. Focus is managed by DOM. This is a case for michael@0: // menupopups of menus on Linux since there are no accessible for popups. michael@0: if (!popup) michael@0: return; michael@0: } michael@0: michael@0: // In case of autocompletes and comboboxes fire state change event for michael@0: // expanded state. Note, HTML form autocomplete isn't a subject of state michael@0: // change event because they aren't autocompletes strictly speaking. michael@0: // When popup closes (except nested popups and menus) then fire focus event to michael@0: // where it was. The focus event is expected even if popup didn't take a focus. michael@0: michael@0: static const uint32_t kNotifyOfFocus = 1; michael@0: static const uint32_t kNotifyOfState = 2; michael@0: uint32_t notifyOf = 0; michael@0: michael@0: // HTML select is target of popuphidding event. Otherwise get container michael@0: // widget. No container widget means this is either tooltip or menupopup. michael@0: // No events in the former case. michael@0: Accessible* widget = nullptr; michael@0: if (popup->IsCombobox()) { michael@0: widget = popup; michael@0: } else { michael@0: widget = popup->ContainerWidget(); michael@0: if (!widget) { michael@0: if (!popup->IsMenuPopup()) michael@0: return; michael@0: michael@0: widget = popup; michael@0: } michael@0: } michael@0: michael@0: if (popup->IsAutoCompletePopup()) { michael@0: // No focus event for autocomplete because it's managed by michael@0: // DOMMenuItemInactive events. michael@0: if (widget->IsAutoComplete()) michael@0: notifyOf = kNotifyOfState; michael@0: michael@0: } else if (widget->IsCombobox()) { michael@0: // Fire focus for active combobox, otherwise the focus is managed by DOM michael@0: // focus notifications. Always fire state change event. michael@0: if (widget->IsActiveWidget()) michael@0: notifyOf = kNotifyOfFocus; michael@0: notifyOf |= kNotifyOfState; michael@0: michael@0: } else if (widget->IsMenuButton()) { michael@0: // Can be a part of autocomplete. michael@0: Accessible* compositeWidget = widget->ContainerWidget(); michael@0: if (compositeWidget && compositeWidget->IsAutoComplete()) { michael@0: widget = compositeWidget; michael@0: notifyOf = kNotifyOfState; michael@0: } michael@0: michael@0: // Autocomplete (like searchbar) can be inactive when popup hiddens michael@0: notifyOf |= kNotifyOfFocus; michael@0: michael@0: } else if (widget == popup) { michael@0: // Top level context menus and alerts. michael@0: // Ignore submenus and menubar. When submenu is closed then sumbenu michael@0: // container menuitem takes a focus via DOMMenuItemActive notification. michael@0: // For menubars processing we listen DOMMenubarActive/Inactive michael@0: // notifications. michael@0: notifyOf = kNotifyOfFocus; michael@0: } michael@0: michael@0: // Restore focus to where it was. michael@0: if (notifyOf & kNotifyOfFocus) { michael@0: FocusMgr()->ActiveItemChanged(nullptr); michael@0: #ifdef A11Y_LOG michael@0: if (logging::IsEnabled(logging::eFocus)) michael@0: logging::ActiveItemChangeCausedBy("popuphiding", popup); michael@0: #endif michael@0: } michael@0: michael@0: // Fire expanded state change event. michael@0: if (notifyOf & kNotifyOfState) { michael@0: nsRefPtr event = michael@0: new AccStateChangeEvent(widget, states::EXPANDED, false); michael@0: document->FireDelayedEvent(event); michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_XUL michael@0: void michael@0: RootAccessible::HandleTreeRowCountChangedEvent(nsIDOMEvent* aEvent, michael@0: XULTreeAccessible* aAccessible) michael@0: { michael@0: nsCOMPtr customEvent(do_QueryInterface(aEvent)); michael@0: if (!customEvent) michael@0: return; michael@0: michael@0: nsCOMPtr detailVariant; michael@0: customEvent->GetDetail(getter_AddRefs(detailVariant)); michael@0: if (!detailVariant) michael@0: return; michael@0: michael@0: nsCOMPtr supports; michael@0: detailVariant->GetAsISupports(getter_AddRefs(supports)); michael@0: nsCOMPtr propBag(do_QueryInterface(supports)); michael@0: if (!propBag) michael@0: return; michael@0: michael@0: nsresult rv; michael@0: int32_t index, count; michael@0: rv = propBag->GetPropertyAsInt32(NS_LITERAL_STRING("index"), &index); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: rv = propBag->GetPropertyAsInt32(NS_LITERAL_STRING("count"), &count); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: aAccessible->InvalidateCache(index, count); michael@0: } michael@0: michael@0: void michael@0: RootAccessible::HandleTreeInvalidatedEvent(nsIDOMEvent* aEvent, michael@0: XULTreeAccessible* aAccessible) michael@0: { michael@0: nsCOMPtr customEvent(do_QueryInterface(aEvent)); michael@0: if (!customEvent) michael@0: return; michael@0: michael@0: nsCOMPtr detailVariant; michael@0: customEvent->GetDetail(getter_AddRefs(detailVariant)); michael@0: if (!detailVariant) michael@0: return; michael@0: michael@0: nsCOMPtr supports; michael@0: detailVariant->GetAsISupports(getter_AddRefs(supports)); michael@0: nsCOMPtr propBag(do_QueryInterface(supports)); michael@0: if (!propBag) michael@0: return; michael@0: michael@0: int32_t startRow = 0, endRow = -1, startCol = 0, endCol = -1; michael@0: propBag->GetPropertyAsInt32(NS_LITERAL_STRING("startrow"), michael@0: &startRow); michael@0: propBag->GetPropertyAsInt32(NS_LITERAL_STRING("endrow"), michael@0: &endRow); michael@0: propBag->GetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"), michael@0: &startCol); michael@0: propBag->GetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"), michael@0: &endCol); michael@0: michael@0: aAccessible->TreeViewInvalidated(startRow, endRow, startCol, endCol); michael@0: } michael@0: #endif