TraversalRule.cpp (9032B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "TraversalRule.h" 8 9 #include "mozilla/a11y/Accessible.h" 10 11 #include "mozilla/a11y/Role.h" 12 #include "HTMLListAccessible.h" 13 #include "SessionAccessibility.h" 14 #include "nsAccUtils.h" 15 #include "nsIAccessiblePivot.h" 16 17 using namespace mozilla; 18 using namespace mozilla::a11y; 19 20 TraversalRule::TraversalRule() 21 : TraversalRule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT, 22 true) {} 23 24 TraversalRule::TraversalRule(int32_t aGranularity, bool aIsLocal) 25 : mGranularity(aGranularity), mIsLocal(aIsLocal) {} 26 27 uint16_t TraversalRule::Match(Accessible* aAcc) { 28 MOZ_ASSERT(aAcc); 29 30 if (mIsLocal && aAcc->IsRemote()) { 31 // If we encounter a remote accessible in a local rule, we should 32 // ignore the subtree because we won't encounter anymore local accessibles 33 // in it. 34 return nsIAccessibleTraversalRule::FILTER_IGNORE | 35 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 36 } else if (!mIsLocal && aAcc->IsLocal()) { 37 // If we encounter a local accessible in a remote rule we are likely 38 // traversing backwards/upwards, we don't ignore its subtree because it is 39 // likely the outer doc root of the remote tree. 40 return nsIAccessibleTraversalRule::FILTER_IGNORE; 41 } 42 43 uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; 44 45 if (nsAccUtils::MustPrune(aAcc)) { 46 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 47 } 48 49 uint64_t state = aAcc->State(); 50 51 if ((state & states::INVISIBLE) != 0) { 52 return result; 53 } 54 55 if (aAcc->Opacity() == 0.0f) { 56 return result | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 57 } 58 59 switch (mGranularity) { 60 case java::SessionAccessibility::HTML_GRANULARITY_LINK: 61 result |= LinkMatch(aAcc); 62 break; 63 case java::SessionAccessibility::HTML_GRANULARITY_CONTROL: 64 result |= ControlMatch(aAcc); 65 break; 66 case java::SessionAccessibility::HTML_GRANULARITY_SECTION: 67 result |= SectionMatch(aAcc); 68 break; 69 case java::SessionAccessibility::HTML_GRANULARITY_HEADING: 70 result |= HeadingMatch(aAcc); 71 break; 72 case java::SessionAccessibility::HTML_GRANULARITY_LANDMARK: 73 result |= LandmarkMatch(aAcc); 74 break; 75 default: 76 result |= DefaultMatch(aAcc); 77 break; 78 } 79 80 return result; 81 } 82 83 bool TraversalRule::IsSingleLineage(Accessible* aAccessible) { 84 Accessible* child = aAccessible; 85 while (child) { 86 switch (child->ChildCount()) { 87 case 0: 88 return true; 89 case 1: 90 child = child->FirstChild(); 91 break; 92 case 2: 93 if (IsListItemBullet(child->FirstChild())) { 94 child = child->LastChild(); 95 } else { 96 return false; 97 } 98 break; 99 default: 100 return false; 101 } 102 } 103 104 return true; 105 } 106 107 bool TraversalRule::IsListItemBullet(const Accessible* aAccessible) { 108 return aAccessible->Role() == roles::LISTITEM_MARKER; 109 } 110 111 bool TraversalRule::IsFlatSubtree(const Accessible* aAccessible) { 112 for (auto child = aAccessible->FirstChild(); child; 113 child = child->NextSibling()) { 114 roles::Role role = child->Role(); 115 if (role == roles::TEXT_LEAF || role == roles::STATICTEXT) { 116 continue; 117 } 118 119 if (child->ChildCount() > 0 || child->ActionCount() > 0) { 120 return false; 121 } 122 } 123 124 return true; 125 } 126 127 bool TraversalRule::HasName(const Accessible* aAccessible) { 128 nsAutoString name; 129 aAccessible->Name(name); 130 name.CompressWhitespace(); 131 return !name.IsEmpty(); 132 } 133 134 uint16_t TraversalRule::LinkMatch(Accessible* aAccessible) { 135 if (aAccessible->Role() == roles::LINK && 136 (aAccessible->State() & states::LINKED) != 0) { 137 return nsIAccessibleTraversalRule::FILTER_MATCH | 138 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 139 } 140 141 return nsIAccessibleTraversalRule::FILTER_IGNORE; 142 } 143 144 uint16_t TraversalRule::HeadingMatch(Accessible* aAccessible) { 145 if (aAccessible->Role() == roles::HEADING && aAccessible->ChildCount()) { 146 return nsIAccessibleTraversalRule::FILTER_MATCH; 147 } 148 149 return nsIAccessibleTraversalRule::FILTER_IGNORE; 150 } 151 152 uint16_t TraversalRule::SectionMatch(Accessible* aAccessible) { 153 roles::Role role = aAccessible->Role(); 154 if (role == roles::HEADING || role == roles::LANDMARK || 155 aAccessible->LandmarkRole()) { 156 return nsIAccessibleTraversalRule::FILTER_MATCH; 157 } 158 159 return nsIAccessibleTraversalRule::FILTER_IGNORE; 160 } 161 162 uint16_t TraversalRule::LandmarkMatch(Accessible* aAccessible) { 163 if (aAccessible->LandmarkRole()) { 164 return nsIAccessibleTraversalRule::FILTER_MATCH; 165 } 166 167 return nsIAccessibleTraversalRule::FILTER_IGNORE; 168 } 169 170 uint16_t TraversalRule::ControlMatch(Accessible* aAccessible) { 171 switch (aAccessible->Role()) { 172 case roles::PUSHBUTTON: 173 case roles::SPINBUTTON: 174 case roles::TOGGLE_BUTTON: 175 case roles::BUTTONDROPDOWN: 176 case roles::COMBOBOX: 177 case roles::LISTBOX: 178 case roles::ENTRY: 179 case roles::PASSWORD_TEXT: 180 case roles::PAGETAB: 181 case roles::RADIOBUTTON: 182 case roles::RADIO_MENU_ITEM: 183 case roles::SLIDER: 184 case roles::CHECKBUTTON: 185 case roles::CHECK_MENU_ITEM: 186 case roles::SWITCH: 187 case roles::MENUITEM: 188 return nsIAccessibleTraversalRule::FILTER_MATCH | 189 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 190 case roles::LINK: 191 return LinkMatch(aAccessible); 192 case roles::EDITCOMBOBOX: 193 if (aAccessible->State() & states::EDITABLE) { 194 // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is 195 // editable. If it's a 1.1 combobox, the combobox is just a container; 196 // we want to stop on the textbox inside it, not the container. 197 return nsIAccessibleTraversalRule::FILTER_MATCH | 198 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 199 } 200 break; 201 default: 202 break; 203 } 204 205 return nsIAccessibleTraversalRule::FILTER_IGNORE; 206 } 207 208 uint16_t TraversalRule::DefaultMatch(Accessible* aAccessible) { 209 switch (aAccessible->Role()) { 210 case roles::COMBOBOX: 211 // We don't want to ignore the subtree because this is often 212 // where the list box hangs out. 213 return nsIAccessibleTraversalRule::FILTER_MATCH; 214 case roles::EDITCOMBOBOX: 215 if (aAccessible->State() & states::EDITABLE) { 216 // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is 217 // editable. If it's a 1.1 combobox, the combobox is just a container; 218 // we want to stop on the textbox inside it. 219 return nsIAccessibleTraversalRule::FILTER_MATCH; 220 } 221 break; 222 case roles::TEXT_LEAF: 223 case roles::GRAPHIC: 224 // Nameless text leaves are boring, skip them. 225 if (HasName(aAccessible)) { 226 return nsIAccessibleTraversalRule::FILTER_MATCH; 227 } 228 break; 229 case roles::STATICTEXT: 230 // Ignore list bullets 231 if (!IsListItemBullet(aAccessible)) { 232 return nsIAccessibleTraversalRule::FILTER_MATCH; 233 } 234 break; 235 case roles::HEADING: 236 case roles::COLUMNHEADER: 237 case roles::ROWHEADER: 238 case roles::STATUSBAR: 239 if ((aAccessible->ChildCount() > 0 || HasName(aAccessible)) && 240 (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible))) { 241 return nsIAccessibleTraversalRule::FILTER_MATCH | 242 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 243 } 244 break; 245 case roles::GRID_CELL: 246 if (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible)) { 247 return nsIAccessibleTraversalRule::FILTER_MATCH | 248 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 249 } 250 break; 251 case roles::LABEL: 252 if (IsFlatSubtree(aAccessible)) { 253 // Match if this is a label with text but no nested controls. 254 return nsIAccessibleTraversalRule::FILTER_MATCH | 255 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 256 } 257 break; 258 case roles::MENUITEM: 259 case roles::LINK: 260 case roles::PAGETAB: 261 case roles::PUSHBUTTON: 262 case roles::CHECKBUTTON: 263 case roles::RADIOBUTTON: 264 case roles::PROGRESSBAR: 265 case roles::BUTTONDROPDOWN: 266 case roles::BUTTONMENU: 267 case roles::CHECK_MENU_ITEM: 268 case roles::PASSWORD_TEXT: 269 case roles::RADIO_MENU_ITEM: 270 case roles::TOGGLE_BUTTON: 271 case roles::ENTRY: 272 case roles::KEY: 273 case roles::SLIDER: 274 case roles::SPINBUTTON: 275 case roles::OPTION: 276 case roles::SWITCH: 277 case roles::MATHML_MATH: 278 // Ignore the subtree, if there is one. So that we don't land on 279 // the same content that was already presented by its parent. 280 return nsIAccessibleTraversalRule::FILTER_MATCH | 281 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 282 default: 283 break; 284 } 285 286 return nsIAccessibleTraversalRule::FILTER_IGNORE; 287 }