accessible/src/base/nsAccessiblePivot.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "nsAccessiblePivot.h"
michael@0 8
michael@0 9 #include "HyperTextAccessible.h"
michael@0 10 #include "nsAccUtils.h"
michael@0 11 #include "States.h"
michael@0 12
michael@0 13 using namespace mozilla::a11y;
michael@0 14
michael@0 15
michael@0 16 /**
michael@0 17 * An object that stores a given traversal rule during
michael@0 18 */
michael@0 19 class RuleCache
michael@0 20 {
michael@0 21 public:
michael@0 22 RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule),
michael@0 23 mAcceptRoles(nullptr) { }
michael@0 24 ~RuleCache () {
michael@0 25 if (mAcceptRoles)
michael@0 26 nsMemory::Free(mAcceptRoles);
michael@0 27 }
michael@0 28
michael@0 29 nsresult ApplyFilter(Accessible* aAccessible, uint16_t* aResult);
michael@0 30
michael@0 31 private:
michael@0 32 nsCOMPtr<nsIAccessibleTraversalRule> mRule;
michael@0 33 uint32_t* mAcceptRoles;
michael@0 34 uint32_t mAcceptRolesLength;
michael@0 35 uint32_t mPreFilter;
michael@0 36 };
michael@0 37
michael@0 38 ////////////////////////////////////////////////////////////////////////////////
michael@0 39 // nsAccessiblePivot
michael@0 40
michael@0 41 nsAccessiblePivot::nsAccessiblePivot(Accessible* aRoot) :
michael@0 42 mRoot(aRoot), mModalRoot(nullptr), mPosition(nullptr),
michael@0 43 mStartOffset(-1), mEndOffset(-1)
michael@0 44 {
michael@0 45 NS_ASSERTION(aRoot, "A root accessible is required");
michael@0 46 }
michael@0 47
michael@0 48 ////////////////////////////////////////////////////////////////////////////////
michael@0 49 // nsISupports
michael@0 50
michael@0 51 NS_IMPL_CYCLE_COLLECTION(nsAccessiblePivot, mRoot, mPosition, mObservers)
michael@0 52
michael@0 53 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot)
michael@0 54 NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot)
michael@0 55 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot)
michael@0 56 NS_INTERFACE_MAP_END
michael@0 57
michael@0 58 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot)
michael@0 59 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot)
michael@0 60
michael@0 61 ////////////////////////////////////////////////////////////////////////////////
michael@0 62 // nsIAccessiblePivot
michael@0 63
michael@0 64 NS_IMETHODIMP
michael@0 65 nsAccessiblePivot::GetRoot(nsIAccessible** aRoot)
michael@0 66 {
michael@0 67 NS_ENSURE_ARG_POINTER(aRoot);
michael@0 68
michael@0 69 NS_IF_ADDREF(*aRoot = mRoot);
michael@0 70
michael@0 71 return NS_OK;
michael@0 72 }
michael@0 73
michael@0 74 NS_IMETHODIMP
michael@0 75 nsAccessiblePivot::GetPosition(nsIAccessible** aPosition)
michael@0 76 {
michael@0 77 NS_ENSURE_ARG_POINTER(aPosition);
michael@0 78
michael@0 79 NS_IF_ADDREF(*aPosition = mPosition);
michael@0 80
michael@0 81 return NS_OK;
michael@0 82 }
michael@0 83
michael@0 84 NS_IMETHODIMP
michael@0 85 nsAccessiblePivot::SetPosition(nsIAccessible* aPosition)
michael@0 86 {
michael@0 87 nsRefPtr<Accessible> secondPosition;
michael@0 88
michael@0 89 if (aPosition) {
michael@0 90 secondPosition = do_QueryObject(aPosition);
michael@0 91 if (!secondPosition || !IsDescendantOf(secondPosition, GetActiveRoot()))
michael@0 92 return NS_ERROR_INVALID_ARG;
michael@0 93 }
michael@0 94
michael@0 95 // Swap old position with new position, saves us an AddRef/Release.
michael@0 96 mPosition.swap(secondPosition);
michael@0 97 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
michael@0 98 mStartOffset = mEndOffset = -1;
michael@0 99 NotifyOfPivotChange(secondPosition, oldStart, oldEnd,
michael@0 100 nsIAccessiblePivot::REASON_NONE);
michael@0 101
michael@0 102 return NS_OK;
michael@0 103 }
michael@0 104
michael@0 105 NS_IMETHODIMP
michael@0 106 nsAccessiblePivot::GetModalRoot(nsIAccessible** aModalRoot)
michael@0 107 {
michael@0 108 NS_ENSURE_ARG_POINTER(aModalRoot);
michael@0 109
michael@0 110 NS_IF_ADDREF(*aModalRoot = mModalRoot);
michael@0 111
michael@0 112 return NS_OK;
michael@0 113 }
michael@0 114
michael@0 115 NS_IMETHODIMP
michael@0 116 nsAccessiblePivot::SetModalRoot(nsIAccessible* aModalRoot)
michael@0 117 {
michael@0 118 nsRefPtr<Accessible> modalRoot;
michael@0 119
michael@0 120 if (aModalRoot) {
michael@0 121 modalRoot = do_QueryObject(aModalRoot);
michael@0 122 if (!modalRoot || !IsDescendantOf(modalRoot, mRoot))
michael@0 123 return NS_ERROR_INVALID_ARG;
michael@0 124 }
michael@0 125
michael@0 126 mModalRoot.swap(modalRoot);
michael@0 127
michael@0 128 return NS_OK;
michael@0 129 }
michael@0 130
michael@0 131 NS_IMETHODIMP
michael@0 132 nsAccessiblePivot::GetStartOffset(int32_t* aStartOffset)
michael@0 133 {
michael@0 134 NS_ENSURE_ARG_POINTER(aStartOffset);
michael@0 135
michael@0 136 *aStartOffset = mStartOffset;
michael@0 137
michael@0 138 return NS_OK;
michael@0 139 }
michael@0 140
michael@0 141 NS_IMETHODIMP
michael@0 142 nsAccessiblePivot::GetEndOffset(int32_t* aEndOffset)
michael@0 143 {
michael@0 144 NS_ENSURE_ARG_POINTER(aEndOffset);
michael@0 145
michael@0 146 *aEndOffset = mEndOffset;
michael@0 147
michael@0 148 return NS_OK;
michael@0 149 }
michael@0 150
michael@0 151 NS_IMETHODIMP
michael@0 152 nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible,
michael@0 153 int32_t aStartOffset, int32_t aEndOffset)
michael@0 154 {
michael@0 155 NS_ENSURE_ARG(aTextAccessible);
michael@0 156
michael@0 157 // Check that start offset is smaller than end offset, and that if a value is
michael@0 158 // smaller than 0, both should be -1.
michael@0 159 NS_ENSURE_TRUE(aStartOffset <= aEndOffset &&
michael@0 160 (aStartOffset >= 0 || (aStartOffset != -1 && aEndOffset != -1)),
michael@0 161 NS_ERROR_INVALID_ARG);
michael@0 162
michael@0 163 nsRefPtr<Accessible> acc(do_QueryObject(aTextAccessible));
michael@0 164 if (!acc)
michael@0 165 return NS_ERROR_INVALID_ARG;
michael@0 166
michael@0 167 HyperTextAccessible* newPosition = acc->AsHyperText();
michael@0 168 if (!newPosition || !IsDescendantOf(newPosition, GetActiveRoot()))
michael@0 169 return NS_ERROR_INVALID_ARG;
michael@0 170
michael@0 171 // Make sure the given offsets don't exceed the character count.
michael@0 172 int32_t charCount = newPosition->CharacterCount();
michael@0 173
michael@0 174 if (aEndOffset > charCount)
michael@0 175 return NS_ERROR_FAILURE;
michael@0 176
michael@0 177 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
michael@0 178 mStartOffset = aStartOffset;
michael@0 179 mEndOffset = aEndOffset;
michael@0 180
michael@0 181 nsRefPtr<Accessible> oldPosition = mPosition.forget();
michael@0 182 mPosition = newPosition;
michael@0 183
michael@0 184 NotifyOfPivotChange(oldPosition, oldStart, oldEnd,
michael@0 185 nsIAccessiblePivot::REASON_TEXT);
michael@0 186
michael@0 187 return NS_OK;
michael@0 188 }
michael@0 189
michael@0 190 // Traversal functions
michael@0 191
michael@0 192 NS_IMETHODIMP
michael@0 193 nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule,
michael@0 194 nsIAccessible* aAnchor, bool aIncludeStart,
michael@0 195 uint8_t aArgc, bool* aResult)
michael@0 196 {
michael@0 197 NS_ENSURE_ARG(aResult);
michael@0 198 NS_ENSURE_ARG(aRule);
michael@0 199
michael@0 200 *aResult = false;
michael@0 201
michael@0 202 Accessible* root = GetActiveRoot();
michael@0 203 nsRefPtr<Accessible> anchor =
michael@0 204 (aArgc > 0) ? do_QueryObject(aAnchor) : mPosition;
michael@0 205 if (anchor && (anchor->IsDefunct() || !IsDescendantOf(anchor, root)))
michael@0 206 return NS_ERROR_NOT_IN_TREE;
michael@0 207
michael@0 208 nsresult rv = NS_OK;
michael@0 209 Accessible* accessible =
michael@0 210 SearchForward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv);
michael@0 211 NS_ENSURE_SUCCESS(rv, rv);
michael@0 212
michael@0 213 if (accessible)
michael@0 214 *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_NEXT);
michael@0 215
michael@0 216 return NS_OK;
michael@0 217 }
michael@0 218
michael@0 219 NS_IMETHODIMP
michael@0 220 nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule,
michael@0 221 nsIAccessible* aAnchor,
michael@0 222 bool aIncludeStart,
michael@0 223 uint8_t aArgc, bool* aResult)
michael@0 224 {
michael@0 225 NS_ENSURE_ARG(aResult);
michael@0 226 NS_ENSURE_ARG(aRule);
michael@0 227
michael@0 228 *aResult = false;
michael@0 229
michael@0 230 Accessible* root = GetActiveRoot();
michael@0 231 nsRefPtr<Accessible> anchor =
michael@0 232 (aArgc > 0) ? do_QueryObject(aAnchor) : mPosition;
michael@0 233 if (anchor && (anchor->IsDefunct() || !IsDescendantOf(anchor, root)))
michael@0 234 return NS_ERROR_NOT_IN_TREE;
michael@0 235
michael@0 236 nsresult rv = NS_OK;
michael@0 237 Accessible* accessible =
michael@0 238 SearchBackward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv);
michael@0 239 NS_ENSURE_SUCCESS(rv, rv);
michael@0 240
michael@0 241 if (accessible)
michael@0 242 *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_PREV);
michael@0 243
michael@0 244 return NS_OK;
michael@0 245 }
michael@0 246
michael@0 247 NS_IMETHODIMP
michael@0 248 nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule, bool* aResult)
michael@0 249 {
michael@0 250 NS_ENSURE_ARG(aResult);
michael@0 251 NS_ENSURE_ARG(aRule);
michael@0 252
michael@0 253 Accessible* root = GetActiveRoot();
michael@0 254 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
michael@0 255
michael@0 256 nsresult rv = NS_OK;
michael@0 257 Accessible* accessible = SearchForward(root, aRule, true, &rv);
michael@0 258 NS_ENSURE_SUCCESS(rv, rv);
michael@0 259
michael@0 260 if (accessible)
michael@0 261 *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_FIRST);
michael@0 262
michael@0 263 return NS_OK;
michael@0 264 }
michael@0 265
michael@0 266 NS_IMETHODIMP
michael@0 267 nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule,
michael@0 268 bool* aResult)
michael@0 269 {
michael@0 270 NS_ENSURE_ARG(aResult);
michael@0 271 NS_ENSURE_ARG(aRule);
michael@0 272
michael@0 273 Accessible* root = GetActiveRoot();
michael@0 274 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
michael@0 275
michael@0 276 *aResult = false;
michael@0 277 nsresult rv = NS_OK;
michael@0 278 Accessible* lastAccessible = root;
michael@0 279 Accessible* accessible = nullptr;
michael@0 280
michael@0 281 // First go to the last accessible in pre-order
michael@0 282 while (lastAccessible->HasChildren())
michael@0 283 lastAccessible = lastAccessible->LastChild();
michael@0 284
michael@0 285 // Search backwards from last accessible and find the last occurrence in the doc
michael@0 286 accessible = SearchBackward(lastAccessible, aRule, true, &rv);
michael@0 287 NS_ENSURE_SUCCESS(rv, rv);
michael@0 288
michael@0 289 if (accessible)
michael@0 290 *aResult = MovePivotInternal(accessible, nsAccessiblePivot::REASON_LAST);
michael@0 291
michael@0 292 return NS_OK;
michael@0 293 }
michael@0 294
michael@0 295 NS_IMETHODIMP
michael@0 296 nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult)
michael@0 297 {
michael@0 298 NS_ENSURE_ARG(aResult);
michael@0 299
michael@0 300 *aResult = false;
michael@0 301
michael@0 302 int32_t tempStart = mStartOffset, tempEnd = mEndOffset;
michael@0 303 Accessible* tempPosition = mPosition;
michael@0 304 Accessible* root = GetActiveRoot();
michael@0 305 while (true) {
michael@0 306 Accessible* curPosition = tempPosition;
michael@0 307 HyperTextAccessible* text = nullptr;
michael@0 308 // Find the nearest text node using a preorder traversal starting from
michael@0 309 // the current node.
michael@0 310 if (!(text = tempPosition->AsHyperText())) {
michael@0 311 text = SearchForText(tempPosition, false);
michael@0 312 if (!text)
michael@0 313 return NS_OK;
michael@0 314 if (text != curPosition)
michael@0 315 tempStart = tempEnd = -1;
michael@0 316 tempPosition = text;
michael@0 317 }
michael@0 318
michael@0 319 // If the search led to the parent of the node we started on (e.g. when
michael@0 320 // starting on a text leaf), start the text movement from the end of that
michael@0 321 // node, otherwise we just default to 0.
michael@0 322 if (tempEnd == -1)
michael@0 323 tempEnd = text == curPosition->Parent() ?
michael@0 324 text->GetChildOffset(curPosition) : 0;
michael@0 325
michael@0 326 // If there's no more text on the current node, try to find the next text
michael@0 327 // node; if there isn't one, bail out.
michael@0 328 if (tempEnd == text->CharacterCount()) {
michael@0 329 if (tempPosition == root)
michael@0 330 return NS_OK;
michael@0 331
michael@0 332 // If we're currently sitting on a link, try move to either the next
michael@0 333 // sibling or the parent, whichever is closer to the current end
michael@0 334 // offset. Otherwise, do a forward search for the next node to land on
michael@0 335 // (we don't do this in the first case because we don't want to go to the
michael@0 336 // subtree).
michael@0 337 Accessible* sibling = tempPosition->NextSibling();
michael@0 338 if (tempPosition->IsLink()) {
michael@0 339 if (sibling && sibling->IsLink()) {
michael@0 340 tempStart = tempEnd = -1;
michael@0 341 tempPosition = sibling;
michael@0 342 } else {
michael@0 343 tempStart = tempPosition->StartOffset();
michael@0 344 tempEnd = tempPosition->EndOffset();
michael@0 345 tempPosition = tempPosition->Parent();
michael@0 346 }
michael@0 347 } else {
michael@0 348 tempPosition = SearchForText(tempPosition, false);
michael@0 349 if (!tempPosition)
michael@0 350 return NS_OK;
michael@0 351 tempStart = tempEnd = -1;
michael@0 352 }
michael@0 353 continue;
michael@0 354 }
michael@0 355
michael@0 356 AccessibleTextBoundary startBoundary, endBoundary;
michael@0 357 switch (aBoundary) {
michael@0 358 case CHAR_BOUNDARY:
michael@0 359 startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
michael@0 360 endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
michael@0 361 break;
michael@0 362 case WORD_BOUNDARY:
michael@0 363 startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
michael@0 364 endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
michael@0 365 break;
michael@0 366 default:
michael@0 367 return NS_ERROR_INVALID_ARG;
michael@0 368 }
michael@0 369
michael@0 370 nsAutoString unusedText;
michael@0 371 int32_t newStart = 0, newEnd = 0, currentEnd = tempEnd;
michael@0 372 text->TextAtOffset(tempEnd, endBoundary, &newStart, &tempEnd, unusedText);
michael@0 373 text->TextBeforeOffset(tempEnd, startBoundary, &newStart, &newEnd, unusedText);
michael@0 374 int32_t potentialStart = newEnd == tempEnd ? newStart : newEnd;
michael@0 375 tempStart = potentialStart > tempStart ? potentialStart : currentEnd;
michael@0 376
michael@0 377 // The offset range we've obtained might have embedded characters in it,
michael@0 378 // limit the range to the start of the first occurrence of an embedded
michael@0 379 // character.
michael@0 380 Accessible* childAtOffset = nullptr;
michael@0 381 for (int32_t i = tempStart; i < tempEnd; i++) {
michael@0 382 childAtOffset = text->GetChildAtOffset(i);
michael@0 383 if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset)) {
michael@0 384 tempEnd = i;
michael@0 385 break;
michael@0 386 }
michael@0 387 }
michael@0 388 // If there's an embedded character at the very start of the range, we
michael@0 389 // instead want to traverse into it. So restart the movement with
michael@0 390 // the child as the starting point.
michael@0 391 if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset) &&
michael@0 392 tempStart == childAtOffset->StartOffset()) {
michael@0 393 tempPosition = childAtOffset;
michael@0 394 tempStart = tempEnd = -1;
michael@0 395 continue;
michael@0 396 }
michael@0 397
michael@0 398 *aResult = true;
michael@0 399
michael@0 400 Accessible* startPosition = mPosition;
michael@0 401 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
michael@0 402 mPosition = tempPosition;
michael@0 403 mStartOffset = tempStart;
michael@0 404 mEndOffset = tempEnd;
michael@0 405 NotifyOfPivotChange(startPosition, oldStart, oldEnd,
michael@0 406 nsIAccessiblePivot::REASON_TEXT);
michael@0 407 return NS_OK;
michael@0 408 }
michael@0 409 }
michael@0 410
michael@0 411 NS_IMETHODIMP
michael@0 412 nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult)
michael@0 413 {
michael@0 414 NS_ENSURE_ARG(aResult);
michael@0 415
michael@0 416 *aResult = false;
michael@0 417
michael@0 418 int32_t tempStart = mStartOffset, tempEnd = mEndOffset;
michael@0 419 Accessible* tempPosition = mPosition;
michael@0 420 Accessible* root = GetActiveRoot();
michael@0 421 while (true) {
michael@0 422 Accessible* curPosition = tempPosition;
michael@0 423 HyperTextAccessible* text;
michael@0 424 // Find the nearest text node using a reverse preorder traversal starting
michael@0 425 // from the current node.
michael@0 426 if (!(text = tempPosition->AsHyperText())) {
michael@0 427 text = SearchForText(tempPosition, true);
michael@0 428 if (!text)
michael@0 429 return NS_OK;
michael@0 430 if (text != curPosition)
michael@0 431 tempStart = tempEnd = -1;
michael@0 432 tempPosition = text;
michael@0 433 }
michael@0 434
michael@0 435 // If the search led to the parent of the node we started on (e.g. when
michael@0 436 // starting on a text leaf), start the text movement from the end of that
michael@0 437 // node, otherwise we just default to 0.
michael@0 438 if (tempStart == -1) {
michael@0 439 if (tempPosition != curPosition)
michael@0 440 tempStart = text == curPosition->Parent() ?
michael@0 441 text->GetChildOffset(curPosition) : text->CharacterCount();
michael@0 442 else
michael@0 443 tempStart = 0;
michael@0 444 }
michael@0 445
michael@0 446 // If there's no more text on the current node, try to find the previous
michael@0 447 // text node; if there isn't one, bail out.
michael@0 448 if (tempStart == 0) {
michael@0 449 if (tempPosition == root)
michael@0 450 return NS_OK;
michael@0 451
michael@0 452 // If we're currently sitting on a link, try move to either the previous
michael@0 453 // sibling or the parent, whichever is closer to the current end
michael@0 454 // offset. Otherwise, do a forward search for the next node to land on
michael@0 455 // (we don't do this in the first case because we don't want to go to the
michael@0 456 // subtree).
michael@0 457 Accessible* sibling = tempPosition->PrevSibling();
michael@0 458 if (tempPosition->IsLink()) {
michael@0 459 if (sibling && sibling->IsLink()) {
michael@0 460 HyperTextAccessible* siblingText = sibling->AsHyperText();
michael@0 461 tempStart = tempEnd = siblingText ?
michael@0 462 siblingText->CharacterCount() : -1;
michael@0 463 tempPosition = sibling;
michael@0 464 } else {
michael@0 465 tempStart = tempPosition->StartOffset();
michael@0 466 tempEnd = tempPosition->EndOffset();
michael@0 467 tempPosition = tempPosition->Parent();
michael@0 468 }
michael@0 469 } else {
michael@0 470 HyperTextAccessible* tempText = SearchForText(tempPosition, true);
michael@0 471 if (!tempText)
michael@0 472 return NS_OK;
michael@0 473 tempPosition = tempText;
michael@0 474 tempStart = tempEnd = tempText->CharacterCount();
michael@0 475 }
michael@0 476 continue;
michael@0 477 }
michael@0 478
michael@0 479 AccessibleTextBoundary startBoundary, endBoundary;
michael@0 480 switch (aBoundary) {
michael@0 481 case CHAR_BOUNDARY:
michael@0 482 startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
michael@0 483 endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
michael@0 484 break;
michael@0 485 case WORD_BOUNDARY:
michael@0 486 startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
michael@0 487 endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
michael@0 488 break;
michael@0 489 default:
michael@0 490 return NS_ERROR_INVALID_ARG;
michael@0 491 }
michael@0 492
michael@0 493 nsAutoString unusedText;
michael@0 494 int32_t newStart = 0, newEnd = 0, currentStart = tempStart, potentialEnd = 0;
michael@0 495 text->TextBeforeOffset(tempStart, startBoundary, &newStart, &newEnd, unusedText);
michael@0 496 if (newStart < tempStart)
michael@0 497 tempStart = newEnd >= currentStart ? newStart : newEnd;
michael@0 498 else // XXX: In certain odd cases newStart is equal to tempStart
michael@0 499 text->TextBeforeOffset(tempStart - 1, startBoundary, &newStart,
michael@0 500 &tempStart, unusedText);
michael@0 501 text->TextAtOffset(tempStart, endBoundary, &newStart, &potentialEnd,
michael@0 502 unusedText);
michael@0 503 tempEnd = potentialEnd < tempEnd ? potentialEnd : currentStart;
michael@0 504
michael@0 505 // The offset range we've obtained might have embedded characters in it,
michael@0 506 // limit the range to the start of the last occurrence of an embedded
michael@0 507 // character.
michael@0 508 Accessible* childAtOffset = nullptr;
michael@0 509 for (int32_t i = tempEnd - 1; i >= tempStart; i--) {
michael@0 510 childAtOffset = text->GetChildAtOffset(i);
michael@0 511 if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset)) {
michael@0 512 tempStart = childAtOffset->EndOffset();
michael@0 513 break;
michael@0 514 }
michael@0 515 }
michael@0 516 // If there's an embedded character at the very end of the range, we
michael@0 517 // instead want to traverse into it. So restart the movement with
michael@0 518 // the child as the starting point.
michael@0 519 if (childAtOffset && nsAccUtils::IsEmbeddedObject(childAtOffset) &&
michael@0 520 tempEnd == childAtOffset->EndOffset()) {
michael@0 521 tempPosition = childAtOffset;
michael@0 522 tempStart = tempEnd = childAtOffset->AsHyperText()->CharacterCount();
michael@0 523 continue;
michael@0 524 }
michael@0 525
michael@0 526 *aResult = true;
michael@0 527
michael@0 528 Accessible* startPosition = mPosition;
michael@0 529 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
michael@0 530 mPosition = tempPosition;
michael@0 531 mStartOffset = tempStart;
michael@0 532 mEndOffset = tempEnd;
michael@0 533
michael@0 534 NotifyOfPivotChange(startPosition, oldStart, oldEnd,
michael@0 535 nsIAccessiblePivot::REASON_TEXT);
michael@0 536 return NS_OK;
michael@0 537 }
michael@0 538 }
michael@0 539
michael@0 540 NS_IMETHODIMP
michael@0 541 nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule,
michael@0 542 int32_t aX, int32_t aY, bool aIgnoreNoMatch,
michael@0 543 bool* aResult)
michael@0 544 {
michael@0 545 NS_ENSURE_ARG_POINTER(aResult);
michael@0 546 NS_ENSURE_ARG_POINTER(aRule);
michael@0 547
michael@0 548 *aResult = false;
michael@0 549
michael@0 550 Accessible* root = GetActiveRoot();
michael@0 551 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
michael@0 552
michael@0 553 RuleCache cache(aRule);
michael@0 554 Accessible* match = nullptr;
michael@0 555 Accessible* child = root->ChildAtPoint(aX, aY, Accessible::eDeepestChild);
michael@0 556 while (child && root != child) {
michael@0 557 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
michael@0 558 nsresult rv = cache.ApplyFilter(child, &filtered);
michael@0 559 NS_ENSURE_SUCCESS(rv, rv);
michael@0 560
michael@0 561 // Ignore any matching nodes that were below this one
michael@0 562 if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE)
michael@0 563 match = nullptr;
michael@0 564
michael@0 565 // Match if no node below this is a match
michael@0 566 if ((filtered & nsIAccessibleTraversalRule::FILTER_MATCH) && !match) {
michael@0 567 int32_t childX, childY, childWidth, childHeight;
michael@0 568 child->GetBounds(&childX, &childY, &childWidth, &childHeight);
michael@0 569 // Double-check child's bounds since the deepest child may have been out
michael@0 570 // of bounds. This assures we don't return a false positive.
michael@0 571 if (aX >= childX && aX < childX + childWidth &&
michael@0 572 aY >= childY && aY < childY + childHeight)
michael@0 573 match = child;
michael@0 574 }
michael@0 575
michael@0 576 child = child->Parent();
michael@0 577 }
michael@0 578
michael@0 579 if (match || !aIgnoreNoMatch)
michael@0 580 *aResult = MovePivotInternal(match, nsIAccessiblePivot::REASON_POINT);
michael@0 581
michael@0 582 return NS_OK;
michael@0 583 }
michael@0 584
michael@0 585 // Observer functions
michael@0 586
michael@0 587 NS_IMETHODIMP
michael@0 588 nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver* aObserver)
michael@0 589 {
michael@0 590 NS_ENSURE_ARG(aObserver);
michael@0 591
michael@0 592 mObservers.AppendElement(aObserver);
michael@0 593
michael@0 594 return NS_OK;
michael@0 595 }
michael@0 596
michael@0 597 NS_IMETHODIMP
michael@0 598 nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver* aObserver)
michael@0 599 {
michael@0 600 NS_ENSURE_ARG(aObserver);
michael@0 601
michael@0 602 return mObservers.RemoveElement(aObserver) ? NS_OK : NS_ERROR_FAILURE;
michael@0 603 }
michael@0 604
michael@0 605 // Private utility methods
michael@0 606
michael@0 607 bool
michael@0 608 nsAccessiblePivot::IsDescendantOf(Accessible* aAccessible, Accessible* aAncestor)
michael@0 609 {
michael@0 610 if (!aAncestor || aAncestor->IsDefunct())
michael@0 611 return false;
michael@0 612
michael@0 613 // XXX Optimize with IsInDocument() when appropriate. Blocked by bug 759875.
michael@0 614 Accessible* accessible = aAccessible;
michael@0 615 do {
michael@0 616 if (accessible == aAncestor)
michael@0 617 return true;
michael@0 618 } while ((accessible = accessible->Parent()));
michael@0 619
michael@0 620 return false;
michael@0 621 }
michael@0 622
michael@0 623 bool
michael@0 624 nsAccessiblePivot::MovePivotInternal(Accessible* aPosition,
michael@0 625 PivotMoveReason aReason)
michael@0 626 {
michael@0 627 nsRefPtr<Accessible> oldPosition = mPosition.forget();
michael@0 628 mPosition = aPosition;
michael@0 629 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
michael@0 630 mStartOffset = mEndOffset = -1;
michael@0 631
michael@0 632 return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason);
michael@0 633 }
michael@0 634
michael@0 635 Accessible*
michael@0 636 nsAccessiblePivot::AdjustStartPosition(Accessible* aAccessible,
michael@0 637 RuleCache& aCache,
michael@0 638 uint16_t* aFilterResult,
michael@0 639 nsresult* aResult)
michael@0 640 {
michael@0 641 Accessible* matched = aAccessible;
michael@0 642 *aResult = aCache.ApplyFilter(aAccessible, aFilterResult);
michael@0 643
michael@0 644 if (aAccessible != mRoot && aAccessible != mModalRoot) {
michael@0 645 for (Accessible* temp = aAccessible->Parent();
michael@0 646 temp && temp != mRoot && temp != mModalRoot; temp = temp->Parent()) {
michael@0 647 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
michael@0 648 *aResult = aCache.ApplyFilter(temp, &filtered);
michael@0 649 NS_ENSURE_SUCCESS(*aResult, nullptr);
michael@0 650 if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) {
michael@0 651 *aFilterResult = filtered;
michael@0 652 matched = temp;
michael@0 653 }
michael@0 654 }
michael@0 655 }
michael@0 656
michael@0 657 return matched;
michael@0 658 }
michael@0 659
michael@0 660 Accessible*
michael@0 661 nsAccessiblePivot::SearchBackward(Accessible* aAccessible,
michael@0 662 nsIAccessibleTraversalRule* aRule,
michael@0 663 bool aSearchCurrent,
michael@0 664 nsresult* aResult)
michael@0 665 {
michael@0 666 *aResult = NS_OK;
michael@0 667
michael@0 668 // Initial position could be unset, in that case return null.
michael@0 669 if (!aAccessible)
michael@0 670 return nullptr;
michael@0 671
michael@0 672 RuleCache cache(aRule);
michael@0 673 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
michael@0 674 Accessible* accessible = AdjustStartPosition(aAccessible, cache,
michael@0 675 &filtered, aResult);
michael@0 676 NS_ENSURE_SUCCESS(*aResult, nullptr);
michael@0 677
michael@0 678 if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) {
michael@0 679 return accessible;
michael@0 680 }
michael@0 681
michael@0 682 Accessible* root = GetActiveRoot();
michael@0 683 while (accessible != root) {
michael@0 684 Accessible* parent = accessible->Parent();
michael@0 685 int32_t idxInParent = accessible->IndexInParent();
michael@0 686 while (idxInParent > 0) {
michael@0 687 if (!(accessible = parent->GetChildAt(--idxInParent)))
michael@0 688 continue;
michael@0 689
michael@0 690 *aResult = cache.ApplyFilter(accessible, &filtered);
michael@0 691 NS_ENSURE_SUCCESS(*aResult, nullptr);
michael@0 692
michael@0 693 Accessible* lastChild = nullptr;
michael@0 694 while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
michael@0 695 (lastChild = accessible->LastChild())) {
michael@0 696 parent = accessible;
michael@0 697 accessible = lastChild;
michael@0 698 idxInParent = accessible->IndexInParent();
michael@0 699 *aResult = cache.ApplyFilter(accessible, &filtered);
michael@0 700 NS_ENSURE_SUCCESS(*aResult, nullptr);
michael@0 701 }
michael@0 702
michael@0 703 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
michael@0 704 return accessible;
michael@0 705 }
michael@0 706
michael@0 707 if (!(accessible = parent))
michael@0 708 break;
michael@0 709
michael@0 710 *aResult = cache.ApplyFilter(accessible, &filtered);
michael@0 711 NS_ENSURE_SUCCESS(*aResult, nullptr);
michael@0 712
michael@0 713 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
michael@0 714 return accessible;
michael@0 715 }
michael@0 716
michael@0 717 return nullptr;
michael@0 718 }
michael@0 719
michael@0 720 Accessible*
michael@0 721 nsAccessiblePivot::SearchForward(Accessible* aAccessible,
michael@0 722 nsIAccessibleTraversalRule* aRule,
michael@0 723 bool aSearchCurrent,
michael@0 724 nsresult* aResult)
michael@0 725 {
michael@0 726 *aResult = NS_OK;
michael@0 727
michael@0 728 // Initial position could be not set, in that case begin search from root.
michael@0 729 Accessible* root = GetActiveRoot();
michael@0 730 Accessible* accessible = (!aAccessible) ? root : aAccessible;
michael@0 731
michael@0 732 RuleCache cache(aRule);
michael@0 733
michael@0 734 uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
michael@0 735 accessible = AdjustStartPosition(accessible, cache, &filtered, aResult);
michael@0 736 NS_ENSURE_SUCCESS(*aResult, nullptr);
michael@0 737 if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH))
michael@0 738 return accessible;
michael@0 739
michael@0 740 while (true) {
michael@0 741 Accessible* firstChild = nullptr;
michael@0 742 while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
michael@0 743 (firstChild = accessible->FirstChild())) {
michael@0 744 accessible = firstChild;
michael@0 745 *aResult = cache.ApplyFilter(accessible, &filtered);
michael@0 746 NS_ENSURE_SUCCESS(*aResult, nullptr);
michael@0 747
michael@0 748 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
michael@0 749 return accessible;
michael@0 750 }
michael@0 751
michael@0 752 Accessible* sibling = nullptr;
michael@0 753 Accessible* temp = accessible;
michael@0 754 do {
michael@0 755 if (temp == root)
michael@0 756 break;
michael@0 757
michael@0 758 sibling = temp->NextSibling();
michael@0 759
michael@0 760 if (sibling)
michael@0 761 break;
michael@0 762 } while ((temp = temp->Parent()));
michael@0 763
michael@0 764 if (!sibling)
michael@0 765 break;
michael@0 766
michael@0 767 accessible = sibling;
michael@0 768 *aResult = cache.ApplyFilter(accessible, &filtered);
michael@0 769 NS_ENSURE_SUCCESS(*aResult, nullptr);
michael@0 770
michael@0 771 if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
michael@0 772 return accessible;
michael@0 773 }
michael@0 774
michael@0 775 return nullptr;
michael@0 776 }
michael@0 777
michael@0 778 HyperTextAccessible*
michael@0 779 nsAccessiblePivot::SearchForText(Accessible* aAccessible, bool aBackward)
michael@0 780 {
michael@0 781 Accessible* root = GetActiveRoot();
michael@0 782 Accessible* accessible = aAccessible;
michael@0 783 while (true) {
michael@0 784 Accessible* child = nullptr;
michael@0 785
michael@0 786 while ((child = (aBackward ? accessible->LastChild() :
michael@0 787 accessible->FirstChild()))) {
michael@0 788 accessible = child;
michael@0 789 if (child->IsHyperText())
michael@0 790 return child->AsHyperText();
michael@0 791 }
michael@0 792
michael@0 793 Accessible* sibling = nullptr;
michael@0 794 Accessible* temp = accessible;
michael@0 795 do {
michael@0 796 if (temp == root)
michael@0 797 break;
michael@0 798
michael@0 799 if (temp != aAccessible && temp->IsHyperText())
michael@0 800 return temp->AsHyperText();
michael@0 801
michael@0 802 sibling = aBackward ? temp->PrevSibling() : temp->NextSibling();
michael@0 803
michael@0 804 if (sibling)
michael@0 805 break;
michael@0 806 } while ((temp = temp->Parent()));
michael@0 807
michael@0 808 if (!sibling)
michael@0 809 break;
michael@0 810
michael@0 811 accessible = sibling;
michael@0 812 if (accessible->IsHyperText())
michael@0 813 return accessible->AsHyperText();
michael@0 814 }
michael@0 815
michael@0 816 return nullptr;
michael@0 817 }
michael@0 818
michael@0 819
michael@0 820 bool
michael@0 821 nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition,
michael@0 822 int32_t aOldStart, int32_t aOldEnd,
michael@0 823 int16_t aReason)
michael@0 824 {
michael@0 825 if (aOldPosition == mPosition &&
michael@0 826 aOldStart == mStartOffset && aOldEnd == mEndOffset)
michael@0 827 return false;
michael@0 828
michael@0 829 nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> >::ForwardIterator iter(mObservers);
michael@0 830 while (iter.HasMore()) {
michael@0 831 nsIAccessiblePivotObserver* obs = iter.GetNext();
michael@0 832 obs->OnPivotChanged(this, aOldPosition, aOldStart, aOldEnd, aReason);
michael@0 833 }
michael@0 834
michael@0 835 return true;
michael@0 836 }
michael@0 837
michael@0 838 nsresult
michael@0 839 RuleCache::ApplyFilter(Accessible* aAccessible, uint16_t* aResult)
michael@0 840 {
michael@0 841 *aResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
michael@0 842
michael@0 843 if (!mAcceptRoles) {
michael@0 844 nsresult rv = mRule->GetMatchRoles(&mAcceptRoles, &mAcceptRolesLength);
michael@0 845 NS_ENSURE_SUCCESS(rv, rv);
michael@0 846 rv = mRule->GetPreFilter(&mPreFilter);
michael@0 847 NS_ENSURE_SUCCESS(rv, rv);
michael@0 848 }
michael@0 849
michael@0 850 if (mPreFilter) {
michael@0 851 uint64_t state = aAccessible->State();
michael@0 852
michael@0 853 if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) &&
michael@0 854 (state & states::INVISIBLE))
michael@0 855 return NS_OK;
michael@0 856
michael@0 857 if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) &&
michael@0 858 (state & states::OFFSCREEN))
michael@0 859 return NS_OK;
michael@0 860
michael@0 861 if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) &&
michael@0 862 !(state & states::FOCUSABLE))
michael@0 863 return NS_OK;
michael@0 864
michael@0 865 if (nsIAccessibleTraversalRule::PREFILTER_ARIA_HIDDEN & mPreFilter) {
michael@0 866 nsIContent* content = aAccessible->GetContent();
michael@0 867 if (content &&
michael@0 868 nsAccUtils::HasDefinedARIAToken(content, nsGkAtoms::aria_hidden) &&
michael@0 869 !content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_hidden,
michael@0 870 nsGkAtoms::_false, eCaseMatters)) {
michael@0 871 *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
michael@0 872 return NS_OK;
michael@0 873 }
michael@0 874 }
michael@0 875
michael@0 876 if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) &&
michael@0 877 !(state & states::OPAQUE1)) {
michael@0 878 nsIFrame* frame = aAccessible->GetFrame();
michael@0 879 if (frame->StyleDisplay()->mOpacity == 0.0f) {
michael@0 880 *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
michael@0 881 return NS_OK;
michael@0 882 }
michael@0 883 }
michael@0 884 }
michael@0 885
michael@0 886 if (mAcceptRolesLength > 0) {
michael@0 887 uint32_t accessibleRole = aAccessible->Role();
michael@0 888 bool matchesRole = false;
michael@0 889 for (uint32_t idx = 0; idx < mAcceptRolesLength; idx++) {
michael@0 890 matchesRole = mAcceptRoles[idx] == accessibleRole;
michael@0 891 if (matchesRole)
michael@0 892 break;
michael@0 893 }
michael@0 894 if (!matchesRole)
michael@0 895 return NS_OK;
michael@0 896 }
michael@0 897
michael@0 898 return mRule->Match(aAccessible, aResult);
michael@0 899 }

mercurial