/* -*- 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 "nsXULPopupManager.h" #define CreateEvent CreateEventA #include "LocalAccessible-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 "mozilla/a11y/Role.h" #include "States.h" #include "XULTreeAccessible.h" #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 "nsFocusManager.h" #include "nsIAppWindow.h" 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() {} //////////////////////////////////////////////////////////////////////////////// // LocalAccessible ENameValueFlag RootAccessible::Name(nsString& aName) const { aName.Truncate(); if (ARIARoleMap()) { LocalAccessible::Name(aName); if (!aName.IsEmpty()) return eNameOK; } mDocumentNode->GetTitle(aName); return eNameOK; } // RootAccessible protected member 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; } uint64_t RootAccessible::NativeState() const { uint64_t state = DocAccessibleWrap::NativeState(); if (state & states::DEFUNCT) return state; 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; 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", "DOMTitleChanged"}; 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(nsGkAtoms::aria_activedescendant)) { LocalAccessible* 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->IsAnyOfXULElements(nsGkAtoms::tooltip, nsGkAtoms::panel)) { 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. LocalAccessible* popup = document->GetAccessible(aPopupNode); if (!popup) { LocalAccessible* popupContainer = document->GetContainerAccessible(aPopupNode); if (!popupContainer) { return; } uint32_t childCount = popupContainer->ChildCount(); for (uint32_t idx = 0; idx < childCount; idx++) { LocalAccessible* child = popupContainer->LocalChildAt(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. // 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. LocalAccessible* widget = nullptr; if (popup->IsCombobox()) { widget = popup; } else { widget = popup->ContainerWidget(); if (!widget) { if (!popup->IsMenuPopup()) { return; } widget = popup; } } // Fire expanded state change event. if (widget->IsCombobox()) { RefPtr event = new AccStateChangeEvent(widget, states::EXPANDED, false); document->FireDelayedEvent(event); } } 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); } RemoteAccessible* 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(); }