/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "RootAccessible.h" #include "mozilla/ArrayUtils.h" #include "mozilla/PresShell.h" // for nsAccUtils::GetDocAccessibleFor() #define CreateEvent CreateEventA #include "Accessible-inl.h" #include "DocAccessible-inl.h" #include "mozilla/a11y/DocAccessibleParent.h" #include "nsAccessibilityService.h" #include "nsAccUtils.h" #include "nsCoreUtils.h" #include "nsEventShell.h" #include "Relation.h" #include "Role.h" #include "States.h" #ifdef MOZ_XUL # include "XULTreeAccessible.h" #endif #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/CustomEvent.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/BrowserHost.h" #include "nsIDocShellTreeOwner.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/EventTarget.h" #include "nsIDOMXULMultSelectCntrlEl.h" #include "mozilla/dom/Document.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIPropertyBag2.h" #include "nsPIDOMWindow.h" #include "nsIWebBrowserChrome.h" #include "nsReadableUtils.h" #include "nsFocusManager.h" #include "nsGlobalWindow.h" #ifdef MOZ_XUL # include "nsIAppWindow.h" #endif using namespace mozilla; using namespace mozilla::a11y; using namespace mozilla::dom; //////////////////////////////////////////////////////////////////////////////// // nsISupports NS_IMPL_ISUPPORTS_INHERITED(RootAccessible, DocAccessible, nsIDOMEventListener) //////////////////////////////////////////////////////////////////////////////// // Constructor/destructor RootAccessible::RootAccessible(Document* aDocument, PresShell* aPresShell) : DocAccessibleWrap(aDocument, aPresShell) { mType = eRootType; } RootAccessible::~RootAccessible() {} //////////////////////////////////////////////////////////////////////////////// // Accessible ENameValueFlag RootAccessible::Name(nsString& aName) const { aName.Truncate(); if (ARIARoleMap()) { Accessible::Name(aName); if (!aName.IsEmpty()) return eNameOK; } mDocumentNode->GetTitle(aName); return eNameOK; } // RootAccessible protected member #ifdef MOZ_XUL uint32_t RootAccessible::GetChromeFlags() const { // Return the flag set for the top level window as defined // by nsIWebBrowserChrome::CHROME_WINDOW_[FLAGNAME] // Not simple: nsIAppWindow is not just a QI from nsIDOMWindow nsCOMPtr docShell = nsCoreUtils::GetDocShellFor(mDocumentNode); NS_ENSURE_TRUE(docShell, 0); nsCOMPtr treeOwner; docShell->GetTreeOwner(getter_AddRefs(treeOwner)); NS_ENSURE_TRUE(treeOwner, 0); nsCOMPtr appWin(do_GetInterface(treeOwner)); if (!appWin) { return 0; } uint32_t chromeFlags; appWin->GetChromeFlags(&chromeFlags); return chromeFlags; } #endif uint64_t RootAccessible::NativeState() const { uint64_t state = DocAccessibleWrap::NativeState(); if (state & states::DEFUNCT) return state; #ifdef MOZ_XUL uint32_t chromeFlags = GetChromeFlags(); if (chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) state |= states::SIZEABLE; // If it has a titlebar it's movable // XXX unless it's minimized or maximized, but not sure // how to detect that if (chromeFlags & nsIWebBrowserChrome::CHROME_TITLEBAR) state |= states::MOVEABLE; if (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) state |= states::MODAL; #endif nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (fm && fm->GetActiveWindow() == mDocumentNode->GetWindow()) state |= states::ACTIVE; return state; } const char* const kEventTypes[] = { #ifdef DEBUG_DRAGDROPSTART // Capture mouse over events and fire fake DRAGDROPSTART event to simplify // debugging a11y objects with event viewers. "mouseover", #endif // Fired when list or tree selection changes. "select", // Fired when value changes immediately, wether or not focused changed. "ValueChange", "AlertActive", "TreeRowCountChanged", "TreeInvalidated", // add ourself as a OpenStateChange listener (custom event fired in // tree.xml) "OpenStateChange", // add ourself as a CheckboxStateChange listener (custom event fired in // HTMLInputElement.cpp) "CheckboxStateChange", // add ourself as a RadioStateChange Listener (custom event fired in in // HTMLInputElement.cpp & radio.js) "RadioStateChange", "popupshown", "popuphiding", "DOMMenuInactive", "DOMMenuItemActive", "DOMMenuItemInactive", "DOMMenuBarActive", "DOMMenuBarInactive", "scroll"}; nsresult RootAccessible::AddEventListeners() { // EventTarget interface allows to register event listeners to // receive untrusted events (synthetic events generated by untrusted code). // For example, XBL bindings implementations for elements that are hosted in // non chrome document fire untrusted events. // We must use the window's parent target in order to receive events from // iframes and shadow DOM; e.g. ValueChange events from a dropdowns, which use a // menulist containing div elements instead of XUL menuitems. XUL menuitems // fire DOMMenuItemActive events from layout instead. MOZ_ASSERT(aAccessible->Elm()); if (aAccessible->Elm()->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) { Accessible* activeDescendant = aAccessible->CurrentItem(); if (activeDescendant) { FocusMgr()->ActiveItemChanged(activeDescendant, false); #ifdef A11Y_LOG if (logging::IsEnabled(logging::eFocus)) { logging::ActiveItemChangeCausedBy("ARIA activedescendant on popup", activeDescendant); } #endif } } } } void RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode) { DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode); if (!document) return; if (aPopupNode->IsXULElement(nsGkAtoms::tooltip)) { document->ContentRemoved(aPopupNode->AsContent()); return; } // 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. 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) { RefPtr event = new AccStateChangeEvent(widget, states::EXPANDED, false); document->FireDelayedEvent(event); } } #ifdef MOZ_XUL static void GetPropertyBagFromEvent(Event* aEvent, nsIPropertyBag2** aPropertyBag) { *aPropertyBag = nullptr; CustomEvent* customEvent = aEvent->AsCustomEvent(); if (!customEvent) return; AutoJSAPI jsapi; if (!jsapi.Init(customEvent->GetParentObject())) return; JSContext* cx = jsapi.cx(); JS::Rooted detail(cx); customEvent->GetDetail(cx, &detail); if (!detail.isObject()) return; JS::Rooted detailObj(cx, &detail.toObject()); nsresult rv; nsCOMPtr propBag; rv = UnwrapArg(cx, detailObj, getter_AddRefs(propBag)); if (NS_FAILED(rv)) return; propBag.forget(aPropertyBag); } void RootAccessible::HandleTreeRowCountChangedEvent( Event* aEvent, XULTreeAccessible* aAccessible) { nsCOMPtr propBag; GetPropertyBagFromEvent(aEvent, getter_AddRefs(propBag)); if (!propBag) return; nsresult rv; int32_t index, count; rv = propBag->GetPropertyAsInt32(u"index"_ns, &index); if (NS_FAILED(rv)) return; rv = propBag->GetPropertyAsInt32(u"count"_ns, &count); if (NS_FAILED(rv)) return; aAccessible->InvalidateCache(index, count); } void RootAccessible::HandleTreeInvalidatedEvent( Event* aEvent, XULTreeAccessible* aAccessible) { nsCOMPtr propBag; GetPropertyBagFromEvent(aEvent, getter_AddRefs(propBag)); if (!propBag) return; int32_t startRow = 0, endRow = -1, startCol = 0, endCol = -1; propBag->GetPropertyAsInt32(u"startrow"_ns, &startRow); propBag->GetPropertyAsInt32(u"endrow"_ns, &endRow); propBag->GetPropertyAsInt32(u"startcolumn"_ns, &startCol); propBag->GetPropertyAsInt32(u"endcolumn"_ns, &endCol); aAccessible->TreeViewInvalidated(startRow, endRow, startCol, endCol); } #endif ProxyAccessible* RootAccessible::GetPrimaryRemoteTopLevelContentDoc() const { nsCOMPtr owner; mDocumentNode->GetDocShell()->GetTreeOwner(getter_AddRefs(owner)); NS_ENSURE_TRUE(owner, nullptr); nsCOMPtr remoteTab; owner->GetPrimaryRemoteTab(getter_AddRefs(remoteTab)); if (!remoteTab) { return nullptr; } auto tab = static_cast(remoteTab.get()); return tab->GetTopLevelDocAccessible(); }