XULComboboxAccessible.cpp (6722B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "XULComboboxAccessible.h" 7 8 #include "LocalAccessible-inl.h" 9 #include "nsAccessibilityService.h" 10 #include "DocAccessible.h" 11 #include "nsCoreUtils.h" 12 #include "nsFocusManager.h" 13 14 #include "mozilla/a11y/DocAccessibleParent.h" 15 #include "mozilla/a11y/Role.h" 16 #include "States.h" 17 18 #include "mozilla/dom/Element.h" 19 #include "nsIDOMXULMenuListElement.h" 20 21 using namespace mozilla::a11y; 22 23 //////////////////////////////////////////////////////////////////////////////// 24 // XULComboboxAccessible 25 //////////////////////////////////////////////////////////////////////////////// 26 27 XULComboboxAccessible::XULComboboxAccessible(nsIContent* aContent, 28 DocAccessible* aDoc) 29 : AccessibleWrap(aContent, aDoc) { 30 mGenericTypes |= eCombobox; 31 } 32 33 role XULComboboxAccessible::NativeRole() const { return roles::COMBOBOX; } 34 35 uint64_t XULComboboxAccessible::NativeState() const { 36 // As a nsComboboxAccessible we can have the following states: 37 // STATE_FOCUSED 38 // STATE_FOCUSABLE 39 // STATE_HASPOPUP 40 // STATE_EXPANDED 41 // STATE_COLLAPSED 42 43 // Get focus status from base class 44 uint64_t state = LocalAccessible::NativeState(); 45 46 nsCOMPtr<nsIDOMXULMenuListElement> menuList = Elm()->AsXULMenuList(); 47 if (menuList) { 48 bool isOpen = false; 49 menuList->GetOpen(&isOpen); 50 if (isOpen) { 51 state |= states::EXPANDED; 52 } 53 } 54 55 return state | states::HASPOPUP | states::EXPANDABLE; 56 } 57 58 bool XULComboboxAccessible::IsAcceptableChild(nsIContent* aContent) const { 59 return AccessibleWrap::IsAcceptableChild(aContent) && !aContent->IsText(); 60 } 61 62 EDescriptionValueFlag XULComboboxAccessible::Description( 63 nsString& aDescription) const { 64 aDescription.Truncate(); 65 // Use description of currently focused option 66 nsCOMPtr<nsIDOMXULMenuListElement> menuListElm = Elm()->AsXULMenuList(); 67 if (!menuListElm) return eDescriptionOK; 68 69 nsCOMPtr<dom::Element> focusedOptionItem; 70 menuListElm->GetSelectedItem(getter_AddRefs(focusedOptionItem)); 71 if (focusedOptionItem && mDoc) { 72 LocalAccessible* focusedOptionAcc = mDoc->GetAccessible(focusedOptionItem); 73 if (focusedOptionAcc) { 74 return focusedOptionAcc->Description(aDescription); 75 } 76 } 77 78 return eDescriptionOK; 79 } 80 81 void XULComboboxAccessible::Value(nsString& aValue) const { 82 aValue.Truncate(); 83 84 // The value is the option or text shown entered in the combobox. 85 nsCOMPtr<nsIDOMXULMenuListElement> menuList = Elm()->AsXULMenuList(); 86 if (menuList) menuList->GetLabel(aValue); 87 } 88 89 bool XULComboboxAccessible::HasPrimaryAction() const { return true; } 90 91 bool XULComboboxAccessible::DoAction(uint8_t aIndex) const { 92 if (aIndex != XULComboboxAccessible::eAction_Click) return false; 93 94 // Programmaticaly toggle the combo box. 95 nsCOMPtr<nsIDOMXULMenuListElement> menuList = Elm()->AsXULMenuList(); 96 if (!menuList) return false; 97 98 bool isDroppedDown = false; 99 menuList->GetOpen(&isDroppedDown); 100 menuList->SetOpen(!isDroppedDown); 101 return true; 102 } 103 104 void XULComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 105 aName.Truncate(); 106 if (aIndex != XULComboboxAccessible::eAction_Click) return; 107 108 nsCOMPtr<nsIDOMXULMenuListElement> menuList = Elm()->AsXULMenuList(); 109 if (!menuList) return; 110 111 bool isDroppedDown = false; 112 menuList->GetOpen(&isDroppedDown); 113 if (isDroppedDown) { 114 aName.AssignLiteral("close"); 115 } else { 116 aName.AssignLiteral("open"); 117 } 118 } 119 120 //////////////////////////////////////////////////////////////////////////////// 121 // Widgets 122 123 bool XULComboboxAccessible::IsActiveWidget() const { 124 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, 125 nsGkAtoms::_true, eIgnoreCase)) { 126 int32_t childCount = mChildren.Length(); 127 for (int32_t idx = 0; idx < childCount; idx++) { 128 LocalAccessible* child = mChildren[idx]; 129 if (child->Role() == roles::ENTRY) { 130 return FocusMgr()->HasDOMFocus(child->GetContent()); 131 } 132 } 133 return false; 134 } 135 136 return FocusMgr()->HasDOMFocus(mContent); 137 } 138 139 bool XULComboboxAccessible::AreItemsOperable() const { 140 nsCOMPtr<nsIDOMXULMenuListElement> menuListElm = Elm()->AsXULMenuList(); 141 if (menuListElm) { 142 bool isOpen = false; 143 menuListElm->GetOpen(&isOpen); 144 return isOpen; 145 } 146 147 return false; 148 } 149 150 //////////////////////////////////////////////////////////////////////////////// 151 // XULContentSelectDropdownAccessible 152 //////////////////////////////////////////////////////////////////////////////// 153 154 Accessible* XULContentSelectDropdownAccessible::Parent() const { 155 // We render the expanded dropdown for <select>s in the parent process 156 // as a child of the application accessible. This confuses some 157 // ATs which expect the select to _always_ parent the dropdown (in 158 // both expanded and collapsed states). 159 // To rectify this, we spoof the <select> as the parent of the 160 // expanded dropdown here. Note that we do not spoof the child relationship. 161 162 // First, try to find the select that spawned this dropdown. 163 // The select that was activated does not get states::EXPANDED, but 164 // it should still have focus. 165 Accessible* focusedAcc = nullptr; 166 if (auto* focusedNode = FocusMgr()->FocusedDOMNode()) { 167 // If we get a node here, we're in a non-remote browser. 168 DocAccessible* doc = 169 GetAccService()->GetDocAccessible(focusedNode->OwnerDoc()); 170 focusedAcc = doc->GetAccessible(focusedNode); 171 } else { 172 nsFocusManager* focusManagerDOM = nsFocusManager::GetFocusManager(); 173 dom::BrowsingContext* focusedContext = 174 focusManagerDOM->GetFocusedBrowsingContextInChrome(); 175 176 DocAccessibleParent* focusedDoc = 177 DocAccessibleParent::GetFrom(focusedContext); 178 if (NS_WARN_IF(!focusedDoc)) { 179 // We can fail to get a document here if a user is 180 // performing a drag-and-drop selection with mouse. See 181 // `browser/base/content/tests/browser_selectpopup_large.js` 182 return LocalParent(); 183 } 184 MOZ_ASSERT(focusedDoc->IsDoc(), "Got non-document?"); 185 focusedAcc = focusedDoc->AsDoc()->GetFocusedAcc(); 186 } 187 188 if (!NS_WARN_IF(focusedAcc && focusedAcc->IsHTMLCombobox())) { 189 // We can sometimes get a document here if the select that 190 // this dropdown should anchor to loses focus. This can happen when 191 // calling AXPressed on macOS. Call into the regular parent 192 // function instead. 193 return LocalParent(); 194 } 195 196 return focusedAcc; 197 }