Pivot.cpp (10030B)
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 "Pivot.h" 7 8 #include "AccIterator.h" 9 #include "LocalAccessible.h" 10 #include "RemoteAccessible.h" 11 #include "nsAccUtils.h" 12 #include "nsIAccessiblePivot.h" 13 14 #include "mozilla/a11y/Accessible.h" 15 16 using namespace mozilla; 17 using namespace mozilla::a11y; 18 19 //////////////////////////////////////////////////////////////////////////////// 20 // Pivot 21 //////////////////////////////////////////////////////////////////////////////// 22 23 Pivot::Pivot(Accessible* aRoot) : mRoot(aRoot) { MOZ_COUNT_CTOR(Pivot); } 24 25 Pivot::~Pivot() { MOZ_COUNT_DTOR(Pivot); } 26 27 Accessible* Pivot::AdjustStartPosition(Accessible* aAnchor, PivotRule& aRule, 28 uint16_t* aFilterResult) { 29 Accessible* matched = aAnchor; 30 *aFilterResult = aRule.Match(aAnchor); 31 32 if (aAnchor && aAnchor != mRoot) { 33 for (Accessible* temp = aAnchor->Parent(); temp && temp != mRoot; 34 temp = temp->Parent()) { 35 uint16_t filtered = aRule.Match(temp); 36 if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) { 37 *aFilterResult = filtered; 38 matched = temp; 39 } 40 } 41 } 42 43 return matched; 44 } 45 46 Accessible* Pivot::SearchBackward(Accessible* aAnchor, PivotRule& aRule, 47 bool aSearchCurrent) { 48 // Initial position could be unset, in that case return null. 49 if (!aAnchor) { 50 return nullptr; 51 } 52 53 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; 54 55 Accessible* acc = AdjustStartPosition(aAnchor, aRule, &filtered); 56 57 if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) { 58 return acc; 59 } 60 61 while (acc && acc != mRoot) { 62 Accessible* parent = acc->Parent(); 63 #if defined(ANDROID) 64 MOZ_ASSERT( 65 acc->IsLocal() || (acc->IsRemote() && parent->IsRemote()), 66 "Pivot::SearchBackward climbed out of remote subtree in Android!"); 67 #endif 68 int32_t idxInParent = acc->IndexInParent(); 69 while (idxInParent > 0 && parent) { 70 acc = parent->ChildAt(--idxInParent); 71 if (!acc) { 72 continue; 73 } 74 75 filtered = aRule.Match(acc); 76 77 Accessible* lastChild = acc->LastChild(); 78 while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) && 79 lastChild) { 80 parent = acc; 81 acc = lastChild; 82 idxInParent = acc->IndexInParent(); 83 filtered = aRule.Match(acc); 84 lastChild = acc->LastChild(); 85 } 86 87 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) { 88 return acc; 89 } 90 } 91 92 acc = parent; 93 if (!acc) { 94 break; 95 } 96 97 filtered = aRule.Match(acc); 98 99 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) { 100 return acc; 101 } 102 } 103 104 return nullptr; 105 } 106 107 Accessible* Pivot::SearchForward(Accessible* aAnchor, PivotRule& aRule, 108 bool aSearchCurrent) { 109 // Initial position could be not set, in that case begin search from root. 110 Accessible* acc = aAnchor ? aAnchor : mRoot; 111 112 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE; 113 acc = AdjustStartPosition(acc, aRule, &filtered); 114 if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) { 115 return acc; 116 } 117 118 while (acc) { 119 Accessible* firstChild = acc->FirstChild(); 120 while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) && 121 firstChild) { 122 acc = firstChild; 123 filtered = aRule.Match(acc); 124 125 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) { 126 return acc; 127 } 128 firstChild = acc->FirstChild(); 129 } 130 131 Accessible* sibling = nullptr; 132 Accessible* temp = acc; 133 do { 134 if (temp == mRoot) { 135 break; 136 } 137 138 sibling = temp->NextSibling(); 139 140 if (sibling) { 141 break; 142 } 143 temp = temp->Parent(); 144 #if defined(ANDROID) 145 MOZ_ASSERT( 146 acc->IsLocal() || (acc->IsRemote() && temp->IsRemote()), 147 "Pivot::SearchForward climbed out of remote subtree in Android!"); 148 #endif 149 150 } while (temp); 151 152 if (!sibling) { 153 break; 154 } 155 156 acc = sibling; 157 filtered = aRule.Match(acc); 158 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH) { 159 return acc; 160 } 161 } 162 163 return nullptr; 164 } 165 166 Accessible* Pivot::Next(Accessible* aAnchor, PivotRule& aRule, 167 bool aIncludeStart) { 168 return SearchForward(aAnchor, aRule, aIncludeStart); 169 } 170 171 Accessible* Pivot::Prev(Accessible* aAnchor, PivotRule& aRule, 172 bool aIncludeStart) { 173 return SearchBackward(aAnchor, aRule, aIncludeStart); 174 } 175 176 Accessible* Pivot::First(PivotRule& aRule) { 177 return SearchForward(mRoot, aRule, true); 178 } 179 180 Accessible* Pivot::Last(PivotRule& aRule) { 181 Accessible* lastAcc = mRoot; 182 183 // First go to the last accessible in pre-order 184 while (lastAcc && lastAcc->HasChildren()) { 185 lastAcc = lastAcc->LastChild(); 186 } 187 188 // Search backwards from last accessible and find the last occurrence in the 189 // doc 190 return SearchBackward(lastAcc, aRule, true); 191 } 192 193 Accessible* Pivot::AtPoint(int32_t aX, int32_t aY, PivotRule& aRule) { 194 Accessible* match = nullptr; 195 Accessible* child = 196 mRoot ? mRoot->ChildAtPoint(aX, aY, 197 Accessible::EWhichChildAtPoint::DeepestChild) 198 : nullptr; 199 while (child && (mRoot != child)) { 200 uint16_t filtered = aRule.Match(child); 201 202 // Ignore any matching nodes that were below this one 203 if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) { 204 match = nullptr; 205 } 206 207 // Match if no node below this is a match 208 if ((filtered & nsIAccessibleTraversalRule::FILTER_MATCH) && !match) { 209 LayoutDeviceIntRect childRect = child->IsLocal() 210 ? child->AsLocal()->Bounds() 211 : child->AsRemote()->Bounds(); 212 // Double-check child's bounds since the deepest child may have been out 213 // of bounds. This assures we don't return a false positive. 214 if (childRect.Contains(aX, aY)) { 215 match = child; 216 } 217 } 218 219 child = child->Parent(); 220 } 221 222 return match; 223 } 224 225 // Role Rule 226 227 PivotRoleRule::PivotRoleRule(mozilla::a11y::role aRole) 228 : mRole(aRole), mDirectDescendantsFrom(nullptr) {} 229 230 PivotRoleRule::PivotRoleRule(mozilla::a11y::role aRole, 231 Accessible* aDirectDescendantsFrom) 232 : mRole(aRole), mDirectDescendantsFrom(aDirectDescendantsFrom) {} 233 234 uint16_t PivotRoleRule::Match(Accessible* aAcc) { 235 uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; 236 237 if (nsAccUtils::MustPrune(aAcc)) { 238 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 239 } 240 241 if (mDirectDescendantsFrom && (aAcc != mDirectDescendantsFrom)) { 242 // If we've specified mDirectDescendantsFrom, we should ignore 243 // non-direct descendants of from the specified AoP. Because 244 // pivot performs a preorder traversal, the first aAcc 245 // object(s) that don't equal mDirectDescendantsFrom will be 246 // mDirectDescendantsFrom's children. We'll process them, but ignore 247 // their subtrees thereby processing direct descendants of 248 // mDirectDescendantsFrom only. 249 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 250 } 251 252 if (aAcc && aAcc->Role() == mRole) { 253 result |= nsIAccessibleTraversalRule::FILTER_MATCH; 254 } 255 256 return result; 257 } 258 259 // State Rule 260 261 PivotStateRule::PivotStateRule(uint64_t aState) : mState(aState) {} 262 263 uint16_t PivotStateRule::Match(Accessible* aAcc) { 264 uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; 265 266 if (nsAccUtils::MustPrune(aAcc)) { 267 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 268 } 269 270 if (aAcc && (aAcc->State() & mState)) { 271 result = nsIAccessibleTraversalRule::FILTER_MATCH | 272 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 273 } 274 275 return result; 276 } 277 278 // LocalAccInSameDocRule 279 280 uint16_t LocalAccInSameDocRule::Match(Accessible* aAcc) { 281 LocalAccessible* acc = aAcc ? aAcc->AsLocal() : nullptr; 282 if (!acc) { 283 return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 284 } 285 if (acc->IsOuterDoc()) { 286 return nsIAccessibleTraversalRule::FILTER_MATCH | 287 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 288 } 289 return nsIAccessibleTraversalRule::FILTER_MATCH; 290 } 291 292 // Radio Button Name Rule 293 294 PivotRadioNameRule::PivotRadioNameRule(const nsString& aName) : mName(aName) {} 295 296 uint16_t PivotRadioNameRule::Match(Accessible* aAcc) { 297 uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; 298 RemoteAccessible* remote = aAcc->AsRemote(); 299 if (!remote) { 300 // We need the cache to be able to fetch the name attribute below. 301 return result; 302 } 303 304 if (nsAccUtils::MustPrune(aAcc) || aAcc->IsOuterDoc()) { 305 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 306 } 307 308 if (remote->IsHTMLRadioButton()) { 309 nsString currName = remote->GetCachedHTMLNameAttribute(); 310 if (!currName.IsEmpty() && mName.Equals(currName)) { 311 result |= nsIAccessibleTraversalRule::FILTER_MATCH; 312 } 313 } 314 315 return result; 316 } 317 318 // MustPruneSameDocRule 319 320 uint16_t MustPruneSameDocRule::Match(Accessible* aAcc) { 321 if (!aAcc) { 322 return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 323 } 324 325 if (nsAccUtils::MustPrune(aAcc) || aAcc->IsOuterDoc()) { 326 return nsIAccessibleTraversalRule::FILTER_MATCH | 327 nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 328 } 329 330 return nsIAccessibleTraversalRule::FILTER_MATCH; 331 } 332 333 // ARIA Selected Rule 334 335 uint16_t PivotARIASelectedRule::Match(Accessible* aAcc) { 336 uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; 337 338 if (nsAccUtils::MustPrune(aAcc)) { 339 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; 340 } 341 342 if (aAcc && aAcc->ARIASelected()) { 343 result = nsIAccessibleTraversalRule::FILTER_MATCH; 344 } 345 346 return result; 347 }