diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /accessible/generic/RootAccessible.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/generic/RootAccessible.cpp')
-rw-r--r-- | accessible/generic/RootAccessible.cpp | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/accessible/generic/RootAccessible.cpp b/accessible/generic/RootAccessible.cpp new file mode 100644 index 0000000000..b41e348912 --- /dev/null +++ b/accessible/generic/RootAccessible.cpp @@ -0,0 +1,709 @@ +/* -*- 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() +#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 "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 "nsReadableUtils.h" +#include "nsFocusManager.h" +#include "nsGlobalWindow.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<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode); + NS_ENSURE_TRUE(docShell, 0); + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShell->GetTreeOwner(getter_AddRefs(treeOwner)); + NS_ENSURE_TRUE(treeOwner, 0); + nsCOMPtr<nsIAppWindow> 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 <select> in an + // iframe or shadow DOM. The root document itself doesn't receive these. + nsPIDOMWindowOuter* window = mDocumentNode->GetWindow(); + nsCOMPtr<EventTarget> nstarget = window ? window->GetParentTarget() : nullptr; + + if (nstarget) { + for (const char *const *e = kEventTypes, *const *e_end = + ArrayEnd(kEventTypes); + e < e_end; ++e) { + nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e), this, + true, true); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return DocAccessible::AddEventListeners(); +} + +nsresult RootAccessible::RemoveEventListeners() { + nsPIDOMWindowOuter* window = mDocumentNode->GetWindow(); + nsCOMPtr<EventTarget> target = window ? window->GetParentTarget() : nullptr; + if (target) { + for (const char *const *e = kEventTypes, *const *e_end = + ArrayEnd(kEventTypes); + e < e_end; ++e) { + target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, true); + } + } + + // Do this before removing clearing caret accessible, so that it can use + // shutdown the caret accessible's selection listener + DocAccessible::RemoveEventListeners(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// public + +void RootAccessible::DocumentActivated(DocAccessible* aDocument) {} + +//////////////////////////////////////////////////////////////////////////////// +// nsIDOMEventListener + +NS_IMETHODIMP +RootAccessible::HandleEvent(Event* aDOMEvent) { + MOZ_ASSERT(aDOMEvent); + if (IsDefunct()) { + // Even though we've been shut down, RemoveEventListeners might not have + // removed the event handlers on the window's parent target if GetWindow + // returned null, so we might still get events here in this case. We should + // just ignore these events. + return NS_OK; + } + + nsCOMPtr<nsINode> origTargetNode = + do_QueryInterface(aDOMEvent->GetOriginalTarget()); + if (!origTargetNode) return NS_OK; + +#ifdef A11Y_LOG + if (logging::IsEnabled(logging::eDOMEvents)) { + nsAutoString eventType; + aDOMEvent->GetType(eventType); + logging::DOMEvent("handled", origTargetNode, eventType); + } +#endif + + DocAccessible* document = + GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc()); + + if (document) { + nsAutoString eventType; + aDOMEvent->GetType(eventType); + if (eventType.EqualsLiteral("scroll")) { + // We don't put this in the notification queue for 2 reasons: + // 1. We will flood the queue with repetitive events. + // 2. Since this doesn't necessarily touch layout, we are not + // guaranteed to have a WillRefresh tick any time soon. + document->HandleScroll(origTargetNode); + } else { + // Root accessible exists longer than any of its descendant documents so + // that we are guaranteed notification is processed before root accessible + // is destroyed. + // For shadow DOM, GetOriginalTarget on the Event returns null if we + // process the event async, so we must pass the target node as well. + document->HandleNotification<RootAccessible, Event, nsINode>( + this, &RootAccessible::ProcessDOMEvent, aDOMEvent, origTargetNode); + } + } + + return NS_OK; +} + +// RootAccessible protected +void RootAccessible::ProcessDOMEvent(Event* aDOMEvent, nsINode* aTarget) { + MOZ_ASSERT(aDOMEvent); + MOZ_ASSERT(aTarget); + + nsAutoString eventType; + aDOMEvent->GetType(eventType); + +#ifdef A11Y_LOG + if (logging::IsEnabled(logging::eDOMEvents)) { + logging::DOMEvent("processed", aTarget, eventType); + } +#endif + + if (eventType.EqualsLiteral("popuphiding")) { + HandlePopupHidingEvent(aTarget); + return; + } + + DocAccessible* targetDocument = + GetAccService()->GetDocAccessible(aTarget->OwnerDoc()); + if (!targetDocument) { + // Document has ceased to exist. + return; + } + + if (eventType.EqualsLiteral("popupshown") && + aTarget->IsAnyOfXULElements(nsGkAtoms::tooltip, nsGkAtoms::panel)) { + targetDocument->ContentInserted(aTarget->AsContent(), + aTarget->GetNextSibling()); + return; + } + + LocalAccessible* accessible = + targetDocument->GetAccessibleOrContainer(aTarget); + if (!accessible) return; + + if (accessible->IsDoc() && eventType.EqualsLiteral("DOMTitleChanged")) { + targetDocument->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, + accessible); + return; + } + + XULTreeAccessible* treeAcc = accessible->AsXULTree(); + if (treeAcc) { + if (eventType.EqualsLiteral("TreeRowCountChanged")) { + HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc); + return; + } + + if (eventType.EqualsLiteral("TreeInvalidated")) { + HandleTreeInvalidatedEvent(aDOMEvent, treeAcc); + return; + } + } + + if (eventType.EqualsLiteral("RadioStateChange")) { + uint64_t state = accessible->State(); + bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0; + + if (accessible->NeedsDOMUIEvent()) { + RefPtr<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")) { + if (accessible->NeedsDOMUIEvent()) { + uint64_t state = accessible->State(); + bool isEnabled = !!(state & states::CHECKED); + + RefPtr<AccEvent> accEvent = + new AccStateChangeEvent(accessible, states::CHECKED, isEnabled); + nsEventShell::FireEvent(accEvent); + } + return; + } + + LocalAccessible* treeItemAcc = nullptr; + // 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; + + RefPtr<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 = + targetNode->AsElement()->AsXULMultiSelectControl(); + if (!multiSel) { + // This shouldn't be possible. All XUL trees should have + // nsIDOMXULMultiSelectControlElement, and the tree is focused, so it + // shouldn't be dying. Nevertheless, this sometimes happens in the wild + // (bug 1597043). + MOZ_ASSERT_UNREACHABLE( + "XUL tree doesn't have nsIDOMXULMultiSelectControlElement"); + return; + } + 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; + } + + RefPtr<AccSelChangeEvent> selChangeEvent = new AccSelChangeEvent( + treeAcc, treeItemAcc, AccSelChangeEvent::eSelectionAdd); + nsEventShell::FireEvent(selChangeEvent); + return; + } + } else 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); + } + if (auto* focus = FocusMgr()->FocusedLocalAccessible()) { + // Intentionally use the content tree, because Linux strips menupopups + // from the a11y tree so accessible might be an arbitrary ancestor. + if (focus->GetContent() && + focus->GetContent()->IsShadowIncludingInclusiveDescendantOf( + aTarget)) { + // Move the focus to the topmost menu active content if any. The + // menu item in the parent menu will not fire a DOMMenuItemActive + // event if it's already active. + LocalAccessible* newActiveAccessible = nullptr; + if (auto* pm = nsXULPopupManager::GetInstance()) { + if (auto* content = pm->GetTopActiveMenuItemContent()) { + newActiveAccessible = + accessible->Document()->GetAccessible(content); + } + } + FocusMgr()->ActiveItemChanged(newActiveAccessible); +#ifdef A11Y_LOG + if (logging::IsEnabled(logging::eFocus)) { + logging::ActiveItemChangeCausedBy("DOMMenuInactive", + newActiveAccessible); + } +#endif + } + } + } else if (eventType.EqualsLiteral("DOMMenuItemActive")) { + RefPtr<AccEvent> event = + new AccStateChangeEvent(accessible, states::ACTIVE, true); + nsEventShell::FireEvent(event); + FocusMgr()->ActiveItemChanged(accessible); +#ifdef A11Y_LOG + if (logging::IsEnabled(logging::eFocus)) { + logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible); + } +#endif + } else if (eventType.EqualsLiteral("DOMMenuItemInactive")) { + RefPtr<AccEvent> event = + new AccStateChangeEvent(accessible, states::ACTIVE, false); + nsEventShell::FireEvent(event); + + // 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. + LocalAccessible* 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. + LocalAccessible* 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 (accessible->NeedsDOMUIEvent() && + eventType.EqualsLiteral("ValueChange")) { + uint32_t event = accessible->HasNumericValue() + ? nsIAccessibleEvent::EVENT_VALUE_CHANGE + : nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE; + targetDocument->FireDelayedEvent(event, accessible); + } +#ifdef DEBUG_DRAGDROPSTART + else if (eventType.EqualsLiteral("mouseover")) { + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START, + accessible); + } +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// LocalAccessible + +void RootAccessible::Shutdown() { + // Called manually or by LocalAccessible::LastRelease() + if (HasShutdown()) { + return; + } + DocAccessibleWrap::Shutdown(); +} + +Relation RootAccessible::RelationByType(RelationType aType) const { + if (!mDocumentNode || aType != RelationType::EMBEDS) { + return DocAccessibleWrap::RelationByType(aType); + } + + if (RemoteAccessible* remoteDoc = GetPrimaryRemoteTopLevelContentDoc()) { + return Relation(remoteDoc); + } + + if (nsIDocShell* docShell = mDocumentNode->GetDocShell()) { + nsCOMPtr<nsIDocShellTreeOwner> owner; + docShell->GetTreeOwner(getter_AddRefs(owner)); + if (owner) { + nsCOMPtr<nsIDocShellTreeItem> contentShell; + owner->GetPrimaryContentShell(getter_AddRefs(contentShell)); + if (contentShell) { + return Relation(nsAccUtils::GetDocAccessibleFor(contentShell)); + } + } + } + + return Relation(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Protected members + +void RootAccessible::HandlePopupShownEvent(LocalAccessible* 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::COMBOBOX_LIST) { + // Fire expanded state change event for comboboxes and autocompeletes. + LocalAccessible* combobox = aAccessible->LocalParent(); + if (!combobox) return; + + if (combobox->IsCombobox()) { + RefPtr<AccEvent> event = + new AccStateChangeEvent(combobox, states::EXPANDED, true); + nsEventShell::FireEvent(event); + } + + // If aria-activedescendant is present, redirect focus. + // This is needed for parent process <select> 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<AccEvent> 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<JS::Value> detail(cx); + customEvent->GetDetail(cx, &detail); + if (!detail.isObject()) return; + + JS::Rooted<JSObject*> detailObj(cx, &detail.toObject()); + + nsresult rv; + nsCOMPtr<nsIPropertyBag2> propBag; + rv = UnwrapArg<nsIPropertyBag2>(cx, detailObj, getter_AddRefs(propBag)); + if (NS_FAILED(rv)) return; + + propBag.forget(aPropertyBag); +} + +void RootAccessible::HandleTreeRowCountChangedEvent( + Event* aEvent, XULTreeAccessible* aAccessible) { + nsCOMPtr<nsIPropertyBag2> 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<nsIPropertyBag2> 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<nsIDocShellTreeOwner> owner; + mDocumentNode->GetDocShell()->GetTreeOwner(getter_AddRefs(owner)); + NS_ENSURE_TRUE(owner, nullptr); + + nsCOMPtr<nsIRemoteTab> remoteTab; + owner->GetPrimaryRemoteTab(getter_AddRefs(remoteTab)); + if (!remoteTab) { + return nullptr; + } + + auto tab = static_cast<dom::BrowserHost*>(remoteTab.get()); + return tab->GetTopLevelDocAccessible(); +} |