XULTabAccessible.cpp (8367B)
1 /* -*- Mode: C++; tab-width: 2; 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 "XULTabAccessible.h" 7 8 #include "ARIAMap.h" 9 #include "nsAccUtils.h" 10 #include "Relation.h" 11 #include "mozilla/a11y/Role.h" 12 #include "States.h" 13 14 // NOTE: alphabetically ordered 15 #include "mozilla/dom/Document.h" 16 #include "nsIDOMXULSelectCntrlItemEl.h" 17 #include "nsIDOMXULRelatedElement.h" 18 #include "nsXULElement.h" 19 20 #include "mozilla/dom/BindingDeclarations.h" 21 22 using namespace mozilla::a11y; 23 24 //////////////////////////////////////////////////////////////////////////////// 25 // XULTabAccessible 26 //////////////////////////////////////////////////////////////////////////////// 27 28 XULTabAccessible::XULTabAccessible(nsIContent* aContent, DocAccessible* aDoc) 29 : HyperTextAccessible(aContent, aDoc) {} 30 31 //////////////////////////////////////////////////////////////////////////////// 32 // XULTabAccessible: LocalAccessible 33 34 bool XULTabAccessible::HasPrimaryAction() const { return true; } 35 36 void XULTabAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 37 if (aIndex == eAction_Switch) aName.AssignLiteral("switch"); 38 } 39 40 bool XULTabAccessible::DoAction(uint8_t index) const { 41 if (index == eAction_Switch) { 42 // XXXbz Could this just FromContent? 43 RefPtr<nsXULElement> tab = nsXULElement::FromNodeOrNull(mContent); 44 if (tab) { 45 tab->Click(mozilla::dom::CallerType::System); 46 return true; 47 } 48 } 49 return false; 50 } 51 52 //////////////////////////////////////////////////////////////////////////////// 53 // XULTabAccessible: LocalAccessible 54 55 role XULTabAccessible::NativeRole() const { return roles::PAGETAB; } 56 57 uint64_t XULTabAccessible::NativeState() const { 58 // Possible states: focused, focusable, unavailable(disabled), offscreen. 59 60 // get focus and disable status from base class 61 uint64_t state = AccessibleWrap::NativeState(); 62 63 // Check whether the tab is selected and/or pinned 64 nsCOMPtr<nsIDOMXULSelectControlItemElement> tab = 65 Elm()->AsXULSelectControlItem(); 66 if (tab) { 67 bool selected = false; 68 if (NS_SUCCEEDED(tab->GetSelected(&selected)) && selected) { 69 state |= states::SELECTED; 70 } 71 72 if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::pinned)) { 73 state |= states::PINNED; 74 } 75 } 76 77 return state; 78 } 79 80 uint64_t XULTabAccessible::NativeInteractiveState() const { 81 uint64_t state = LocalAccessible::NativeInteractiveState(); 82 // Bug 1951776: XUL tabs are implemented such that only the selected tab is 83 // programmatically focusable. Unfortunately, Windows Voice Access requires 84 // tabs to be focusable in order for them to be discovered by the "show 85 // numbers" command. Therefore, always expose the focusable state for 86 // accessibility. While this seems strange on the surface, it can be argued 87 // that unselected tabs *can* receive focus at some point in the future (when 88 // they're selected), even if they can't receive focus *now*. There is some 89 // precedent for this: aria-activedescendant requires browsers to expose the 90 // focusable state on all possible descendants even if they aren't the current 91 // active descendant. 92 return (state & states::UNAVAILABLE) 93 ? state 94 : state | states::SELECTABLE | states::FOCUSABLE; 95 } 96 97 Relation XULTabAccessible::RelationByType(RelationType aType) const { 98 Relation rel = AccessibleWrap::RelationByType(aType); 99 if (aType != RelationType::LABEL_FOR) return rel; 100 101 // Expose 'LABEL_FOR' relation on tab accessible for tabpanel accessible. 102 ErrorResult rv; 103 nsIContent* parent = mContent->AsElement()->Closest("tabs"_ns, rv); 104 if (!parent) return rel; 105 106 nsCOMPtr<nsIDOMXULRelatedElement> tabsElm = 107 parent->AsElement()->AsXULRelated(); 108 if (!tabsElm) return rel; 109 110 RefPtr<mozilla::dom::Element> tabpanelElement; 111 tabsElm->GetRelatedElement(GetNode(), getter_AddRefs(tabpanelElement)); 112 if (!tabpanelElement) return rel; 113 114 rel.AppendTarget(mDoc, tabpanelElement); 115 return rel; 116 } 117 118 void XULTabAccessible::ApplyARIAState(uint64_t* aState) const { 119 HyperTextAccessible::ApplyARIAState(aState); 120 // XUL tab has an implicit ARIA role of tab, so support aria-selected. 121 // Don't use aria::MapToState because that will set the SELECTABLE state 122 // even if the tab is disabled. 123 if (nsAccUtils::IsARIASelected(this)) { 124 *aState |= states::SELECTED; 125 } 126 } 127 128 //////////////////////////////////////////////////////////////////////////////// 129 // XULTabsAccessible 130 //////////////////////////////////////////////////////////////////////////////// 131 132 XULTabsAccessible::XULTabsAccessible(nsIContent* aContent, DocAccessible* aDoc) 133 : XULSelectControlAccessible(aContent, aDoc) {} 134 135 role XULTabsAccessible::NativeRole() const { return roles::PAGETABLIST; } 136 137 bool XULTabsAccessible::HasPrimaryAction() const { return false; } 138 139 void XULTabsAccessible::Value(nsString& aValue) const { aValue.Truncate(); } 140 141 ENameValueFlag XULTabsAccessible::NativeName(nsString& aName) const { 142 // no name 143 return eNameOK; 144 } 145 146 void XULTabsAccessible::ApplyARIAState(uint64_t* aState) const { 147 XULSelectControlAccessible::ApplyARIAState(aState); 148 // XUL tabs has an implicit ARIA role of tablist, so support 149 // aria-multiselectable. 150 MOZ_ASSERT(Elm()); 151 aria::MapToState(aria::eARIAMultiSelectable, Elm(), aState); 152 } 153 154 // XUL tabs is a single selection control and doesn't allow ARIA selection. 155 // However, if aria-multiselectable is used, it becomes a multiselectable 156 // control, where both native and ARIA markup are used to set selection. 157 // Therefore, if aria-multiselectable is set, use the base implementation of 158 // the selection retrieval methods in order to support ARIA selection. 159 // We don't bother overriding the selection setting methods because 160 // current front-end code using XUL tabs doesn't support setting of 161 // aria-selected by the a11y engine and we still want to be able to set the 162 // primary selected item according to XUL. 163 164 void XULTabsAccessible::SelectedItems(nsTArray<Accessible*>* aItems) { 165 if (nsAccUtils::IsARIAMultiSelectable(this)) { 166 AccessibleWrap::SelectedItems(aItems); 167 } else { 168 XULSelectControlAccessible::SelectedItems(aItems); 169 } 170 } 171 172 Accessible* XULTabsAccessible::GetSelectedItem(uint32_t aIndex) { 173 if (nsAccUtils::IsARIAMultiSelectable(this)) { 174 return AccessibleWrap::GetSelectedItem(aIndex); 175 } 176 177 return XULSelectControlAccessible::GetSelectedItem(aIndex); 178 } 179 180 uint32_t XULTabsAccessible::SelectedItemCount() { 181 if (nsAccUtils::IsARIAMultiSelectable(this)) { 182 return AccessibleWrap::SelectedItemCount(); 183 } 184 185 return XULSelectControlAccessible::SelectedItemCount(); 186 } 187 188 bool XULTabsAccessible::IsItemSelected(uint32_t aIndex) { 189 if (nsAccUtils::IsARIAMultiSelectable(this)) { 190 return AccessibleWrap::IsItemSelected(aIndex); 191 } 192 193 return XULSelectControlAccessible::IsItemSelected(aIndex); 194 } 195 196 //////////////////////////////////////////////////////////////////////////////// 197 // XULTabpanelsAccessible 198 //////////////////////////////////////////////////////////////////////////////// 199 200 role XULTabpanelsAccessible::NativeRole() const { return roles::PANE; } 201 202 //////////////////////////////////////////////////////////////////////////////// 203 // XULTabpanelAccessible 204 //////////////////////////////////////////////////////////////////////////////// 205 206 XULTabpanelAccessible::XULTabpanelAccessible(nsIContent* aContent, 207 DocAccessible* aDoc) 208 : AccessibleWrap(aContent, aDoc) {} 209 210 role XULTabpanelAccessible::NativeRole() const { return roles::PROPERTYPAGE; } 211 212 Relation XULTabpanelAccessible::RelationByType(RelationType aType) const { 213 Relation rel = AccessibleWrap::RelationByType(aType); 214 if (aType != RelationType::LABELLED_BY) return rel; 215 216 // Expose 'LABELLED_BY' relation on tabpanel accessible for tab accessible. 217 if (!mContent->GetParent()) return rel; 218 219 nsCOMPtr<nsIDOMXULRelatedElement> tabpanelsElm = 220 mContent->GetParent()->AsElement()->AsXULRelated(); 221 if (!tabpanelsElm) return rel; 222 223 RefPtr<mozilla::dom::Element> tabElement; 224 tabpanelsElm->GetRelatedElement(GetNode(), getter_AddRefs(tabElement)); 225 if (!tabElement) return rel; 226 227 rel.AppendTarget(mDoc, tabElement); 228 return rel; 229 }