RemoteAccessible.cpp (89127B)
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 "ARIAMap.h" 8 #include "CachedTableAccessible.h" 9 #include "RemoteAccessible.h" 10 #include "mozilla/a11y/CacheConstants.h" 11 #include "mozilla/a11y/DocAccessibleParent.h" 12 #include "mozilla/a11y/DocManager.h" 13 #include "mozilla/a11y/Platform.h" 14 #include "mozilla/a11y/TableAccessible.h" 15 #include "mozilla/a11y/TableCellAccessible.h" 16 #include "mozilla/dom/Element.h" 17 #include "mozilla/dom/BrowserParent.h" 18 #include "mozilla/dom/CanonicalBrowsingContext.h" 19 #include "mozilla/gfx/Matrix.h" 20 #include "nsAccessibilityService.h" 21 #include "nsAccUtils.h" 22 #include "nsFocusManager.h" 23 #include "nsTextEquivUtils.h" 24 #include "Pivot.h" 25 #include "Relation.h" 26 #include "mozilla/a11y/RelationType.h" 27 #include "xpcAccessibleDocument.h" 28 29 #ifdef A11Y_LOG 30 # include "Logging.h" 31 # define VERIFY_CACHE(domain) \ 32 if (logging::IsEnabled(logging::eCache)) { \ 33 (void)mDoc->SendVerifyCache(mID, domain, mCachedFields); \ 34 } 35 #else 36 # define VERIFY_CACHE(domain) \ 37 do { \ 38 } while (0) 39 40 #endif 41 42 namespace mozilla { 43 namespace a11y { 44 45 // Domain sets we need commonly for functions in this file. 46 static constexpr uint64_t kNecessaryBoundsDomains = 47 CacheDomain::Bounds | CacheDomain::TransformMatrix | CacheDomain::Style | 48 CacheDomain::ScrollPosition | CacheDomain::APZ; 49 static constexpr uint64_t kNecessaryStateDomains = 50 CacheDomain::State | CacheDomain::Viewport; 51 52 void RemoteAccessible::Shutdown() { 53 MOZ_DIAGNOSTIC_ASSERT(!IsDoc()); 54 xpcAccessibleDocument* xpcDoc = 55 GetAccService()->GetCachedXPCDocument(Document()); 56 if (xpcDoc) { 57 xpcDoc->NotifyOfShutdown(static_cast<RemoteAccessible*>(this)); 58 } 59 60 if (IsTable() || IsTableCell()) { 61 CachedTableAccessible::Invalidate(this); 62 } 63 64 // Remove this acc's relation map from the doc's map of 65 // reverse relations. Prune forward relations associated with this 66 // acc's reverse relations. This also removes the acc's map of reverse 67 // rels from the mDoc's mReverseRelations. 68 PruneRelationsOnShutdown(); 69 70 // XXX Ideally this wouldn't be necessary, but it seems OuterDoc 71 // accessibles can be destroyed before the doc they own. 72 uint32_t childCount = mChildren.Length(); 73 if (!IsOuterDoc()) { 74 for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown(); 75 } else { 76 if (childCount > 1) { 77 MOZ_CRASH("outer doc has too many documents!"); 78 } else if (childCount == 1) { 79 mChildren[0]->AsDoc()->Unbind(); 80 } 81 } 82 83 mChildren.Clear(); 84 ProxyDestroyed(static_cast<RemoteAccessible*>(this)); 85 // mDoc owns this RemoteAccessible, so RemoveAccessible deletes this. 86 mDoc->RemoveAccessible(static_cast<RemoteAccessible*>(this)); 87 } 88 89 void RemoteAccessible::SetChildDoc(DocAccessibleParent* aChildDoc) { 90 MOZ_ASSERT(aChildDoc); 91 MOZ_ASSERT(mChildren.Length() == 0); 92 mChildren.AppendElement(aChildDoc); 93 aChildDoc->mIndexInParent = 0; 94 } 95 96 void RemoteAccessible::ClearChildDoc(DocAccessibleParent* aChildDoc) { 97 MOZ_ASSERT(aChildDoc); 98 // This is possible if we're replacing one document with another: Doc 1 99 // has not had a chance to remove itself, but was already replaced by Doc 2 100 // in SetChildDoc(). This could result in two subsequent calls to 101 // ClearChildDoc() even though mChildren.Length() == 1. 102 MOZ_ASSERT(mChildren.Length() <= 1); 103 mChildren.RemoveElement(aChildDoc); 104 } 105 106 uint32_t RemoteAccessible::EmbeddedChildCount() { 107 size_t count = 0, kids = mChildren.Length(); 108 for (size_t i = 0; i < kids; i++) { 109 if (mChildren[i]->IsEmbeddedObject()) { 110 count++; 111 } 112 } 113 114 return count; 115 } 116 117 int32_t RemoteAccessible::IndexOfEmbeddedChild(Accessible* aChild) { 118 size_t index = 0, kids = mChildren.Length(); 119 for (size_t i = 0; i < kids; i++) { 120 if (mChildren[i]->IsEmbeddedObject()) { 121 if (mChildren[i] == aChild) { 122 return index; 123 } 124 125 index++; 126 } 127 } 128 129 return -1; 130 } 131 132 Accessible* RemoteAccessible::EmbeddedChildAt(uint32_t aChildIdx) { 133 size_t index = 0, kids = mChildren.Length(); 134 for (size_t i = 0; i < kids; i++) { 135 if (!mChildren[i]->IsEmbeddedObject()) { 136 continue; 137 } 138 139 if (index == aChildIdx) { 140 return mChildren[i]; 141 } 142 143 index++; 144 } 145 146 return nullptr; 147 } 148 149 LocalAccessible* RemoteAccessible::OuterDocOfRemoteBrowser() const { 150 auto tab = static_cast<dom::BrowserParent*>(mDoc->Manager()); 151 dom::Element* frame = tab->GetOwnerElement(); 152 NS_ASSERTION(frame, "why isn't the tab in a frame!"); 153 if (!frame) return nullptr; 154 155 DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc()); 156 157 return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr; 158 } 159 160 void RemoteAccessible::SetParent(RemoteAccessible* aParent) { 161 MOZ_ASSERT(!IsDoc() || !AsDoc()->IsTopLevel(), 162 "Top level doc should not have remote parent"); 163 MOZ_ASSERT(!IsDoc() || !aParent || !aParent->IsDoc(), 164 "Doc can't be direct parent of another doc"); 165 MOZ_ASSERT(!IsDoc() || !aParent || aParent->IsOuterDoc(), 166 "Doc's parent must be OuterDoc"); 167 mParent = aParent; 168 if (!aParent) { 169 mIndexInParent = -1; 170 } 171 } 172 173 RemoteAccessible* RemoteAccessible::RemoteParent() const { 174 MOZ_ASSERT(!IsDoc() || !AsDoc()->IsTopLevel() || !mParent, 175 "Top level doc should not have RemoteParent"); 176 MOZ_ASSERT(!IsDoc() || !mParent || mParent->mDoc != mDoc, 177 "Doc's parent should be in another doc"); 178 MOZ_ASSERT(!IsDoc() || !mParent || mParent->IsOuterDoc(), 179 "Doc's parent should be in another doc"); 180 return mParent; 181 } 182 183 void RemoteAccessible::ApplyCache(CacheUpdateType aUpdateType, 184 AccAttributes* aFields) { 185 if (!aFields) { 186 MOZ_ASSERT_UNREACHABLE("ApplyCache called with aFields == null"); 187 return; 188 } 189 190 const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields); 191 if (auto maybeViewportCache = 192 aFields->GetAttribute<nsTArray<uint64_t>>(CacheKey::Viewport)) { 193 // Updating the viewport cache means the offscreen state of this 194 // document's accessibles has changed. Update the HashSet we use for 195 // checking offscreen state here. 196 MOZ_ASSERT(IsDoc(), 197 "Fetched the viewport cache from a non-doc accessible?"); 198 AsDoc()->mOnScreenAccessibles.Clear(); 199 for (auto id : *maybeViewportCache) { 200 AsDoc()->mOnScreenAccessibles.Insert(id); 201 } 202 } 203 204 if (aUpdateType == CacheUpdateType::Initial) { 205 mCachedFields = aFields; 206 } else { 207 if (!mCachedFields) { 208 // The fields cache can be uninitialized if there were no cache-worthy 209 // fields in the initial cache push. 210 // We don't do a simple assign because we don't want to store the 211 // DeleteEntry entries. 212 mCachedFields = new AccAttributes(); 213 } 214 mCachedFields->Update(aFields); 215 } 216 217 if (IsTextLeaf()) { 218 RemoteAccessible* parent = RemoteParent(); 219 if (parent && parent->IsHyperText()) { 220 parent->InvalidateCachedHyperTextOffsets(); 221 } 222 } 223 224 PostProcessRelations(relUpdatesNeeded); 225 } 226 227 ENameValueFlag RemoteAccessible::Name(nsString& aName) const { 228 if (RequestDomainsIfInactive(CacheDomain::NameAndDescription | 229 CacheDomain::Text | CacheDomain::Relations) || 230 !mCachedFields) { 231 aName.SetIsVoid(true); 232 return eNameOK; 233 } 234 235 if (IsText()) { 236 mCachedFields->GetAttribute(CacheKey::Text, aName); 237 return eNameOK; 238 } 239 240 if (mCachedFields->GetAttribute(CacheKey::Name, aName)) { 241 VERIFY_CACHE(CacheDomain::NameAndDescription); 242 return eNameOK; 243 } 244 245 if (auto maybeAriaLabelIds = mCachedFields->GetAttribute<nsTArray<uint64_t>>( 246 nsGkAtoms::aria_labelledby)) { 247 RemoteAccIterator iter(*maybeAriaLabelIds, Document()); 248 nsTextEquivUtils::GetTextEquivFromAccIterable(this, &iter, aName); 249 aName.CompressWhitespace(); 250 } 251 252 if (!aName.IsEmpty()) { 253 return eNameFromRelations; 254 } 255 256 if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) { 257 nsTArray<uint64_t> relationCandidateIds; 258 for (const auto& data : kRelationTypeAtoms) { 259 if (data.mAtom != nsGkAtoms::_for || data.mValidTag != nsGkAtoms::label) { 260 continue; 261 } 262 263 if (auto labelIds = accRelMapEntry.Data().Lookup(&data)) { 264 RemoteAccIterator iter(*labelIds, Document()); 265 nsTextEquivUtils::GetTextEquivFromAccIterable(this, &iter, aName); 266 aName.CompressWhitespace(); 267 } 268 } 269 aName.CompressWhitespace(); 270 } 271 272 if (!aName.IsEmpty()) { 273 return eNameFromRelations; 274 } 275 276 ArrayAccIterator iter(LegendsOrCaptions()); 277 nsTextEquivUtils::GetTextEquivFromAccIterable(this, &iter, aName); 278 aName.CompressWhitespace(); 279 280 if (!aName.IsEmpty()) { 281 return eNameFromRelations; 282 } 283 284 nsTextEquivUtils::GetNameFromSubtree(this, aName); 285 if (!aName.IsEmpty()) { 286 return eNameFromSubtree; 287 } 288 289 if (mCachedFields->GetAttribute(CacheKey::Tooltip, aName)) { 290 VERIFY_CACHE(CacheDomain::NameAndDescription); 291 return eNameFromTooltip; 292 } 293 294 if (mCachedFields->GetAttribute(CacheKey::CssAltContent, aName)) { 295 VERIFY_CACHE(CacheDomain::NameAndDescription); 296 return eNameOK; 297 } 298 299 MOZ_ASSERT(aName.IsEmpty()); 300 aName.SetIsVoid(true); 301 return eNameOK; 302 } 303 304 EDescriptionValueFlag RemoteAccessible::Description( 305 nsString& aDescription) const { 306 if (RequestDomainsIfInactive(CacheDomain::NameAndDescription)) { 307 return eDescriptionOK; 308 } 309 310 EDescriptionValueFlag descFlag = eDescriptionOK; 311 312 if (mCachedFields) { 313 auto cachedDescriptionFlag = 314 mCachedFields->GetAttribute<int32_t>(CacheKey::DescriptionValueFlag); 315 if (cachedDescriptionFlag) { 316 descFlag = static_cast<EDescriptionValueFlag>(*cachedDescriptionFlag); 317 } 318 mCachedFields->GetAttribute(CacheKey::Description, aDescription); 319 VERIFY_CACHE(CacheDomain::NameAndDescription); 320 } 321 322 return descFlag; 323 } 324 325 void RemoteAccessible::Value(nsString& aValue) const { 326 if (RequestDomainsIfInactive( 327 CacheDomain::Value | // CurValue, etc. 328 CacheDomain::Actions | // ActionAncestor (HasPrimaryAction) 329 CacheDomain::State | // GetSelectedItem 330 CacheDomain::Viewport // GetSelectedItem 331 )) { 332 return; 333 } 334 335 if (mCachedFields) { 336 if (mCachedFields->HasAttribute(CacheKey::TextValue)) { 337 mCachedFields->GetAttribute(CacheKey::TextValue, aValue); 338 VERIFY_CACHE(CacheDomain::Value); 339 return; 340 } 341 342 if (HasNumericValue()) { 343 double checkValue = CurValue(); 344 if (!std::isnan(checkValue)) { 345 aValue.AppendFloat(checkValue); 346 } 347 return; 348 } 349 350 const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); 351 // Value of textbox is a textified subtree. 352 if ((roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) || 353 (IsGeneric() && IsEditableRoot())) { 354 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); 355 return; 356 } 357 358 if (IsCombobox()) { 359 // For combo boxes, rely on selection state to determine the value. 360 const Accessible* option = 361 const_cast<RemoteAccessible*>(this)->GetSelectedItem(0); 362 if (option) { 363 option->Name(aValue); 364 } else { 365 // If no selected item, determine the value from descendant elements. 366 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); 367 } 368 return; 369 } 370 371 if (IsTextLeaf() || IsImage()) { 372 if (const Accessible* actionAcc = ActionAncestor()) { 373 if (const_cast<Accessible*>(actionAcc)->State() & states::LINKED) { 374 // Text and image descendants of links expose the link URL as the 375 // value. 376 return actionAcc->Value(aValue); 377 } 378 } 379 } 380 } 381 } 382 383 double RemoteAccessible::CurValue() const { 384 if (RequestDomainsIfInactive(CacheDomain::Value)) { 385 return UnspecifiedNaN<double>(); 386 } 387 388 if (mCachedFields) { 389 if (auto value = 390 mCachedFields->GetAttribute<double>(CacheKey::NumericValue)) { 391 VERIFY_CACHE(CacheDomain::Value); 392 return *value; 393 } 394 } 395 396 return UnspecifiedNaN<double>(); 397 } 398 399 double RemoteAccessible::MinValue() const { 400 if (RequestDomainsIfInactive(CacheDomain::Value)) { 401 return UnspecifiedNaN<double>(); 402 } 403 404 if (mCachedFields) { 405 if (auto min = mCachedFields->GetAttribute<double>(CacheKey::MinValue)) { 406 VERIFY_CACHE(CacheDomain::Value); 407 return *min; 408 } 409 } 410 411 return UnspecifiedNaN<double>(); 412 } 413 414 double RemoteAccessible::MaxValue() const { 415 if (RequestDomainsIfInactive(CacheDomain::Value)) { 416 return UnspecifiedNaN<double>(); 417 } 418 419 if (mCachedFields) { 420 if (auto max = mCachedFields->GetAttribute<double>(CacheKey::MaxValue)) { 421 VERIFY_CACHE(CacheDomain::Value); 422 return *max; 423 } 424 } 425 426 return UnspecifiedNaN<double>(); 427 } 428 429 double RemoteAccessible::Step() const { 430 if (RequestDomainsIfInactive(CacheDomain::Value)) { 431 return UnspecifiedNaN<double>(); 432 } 433 434 if (mCachedFields) { 435 if (auto step = mCachedFields->GetAttribute<double>(CacheKey::Step)) { 436 VERIFY_CACHE(CacheDomain::Value); 437 return *step; 438 } 439 } 440 441 return UnspecifiedNaN<double>(); 442 } 443 444 bool RemoteAccessible::SetCurValue(double aValue) { 445 if (RequestDomainsIfInactive(CacheDomain::Value | // MinValue, MaxValue 446 CacheDomain::State | // State 447 CacheDomain::Viewport // State 448 )) { 449 return false; 450 } 451 452 if (!HasNumericValue() || IsProgress()) { 453 return false; 454 } 455 456 const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE; 457 if (State() & kValueCannotChange) { 458 return false; 459 } 460 461 double checkValue = MinValue(); 462 if (!std::isnan(checkValue) && aValue < checkValue) { 463 return false; 464 } 465 466 checkValue = MaxValue(); 467 if (!std::isnan(checkValue) && aValue > checkValue) { 468 return false; 469 } 470 471 (void)mDoc->SendSetCurValue(mID, aValue); 472 return true; 473 } 474 475 bool RemoteAccessible::ContainsPoint(int32_t aX, int32_t aY) { 476 ASSERT_DOMAINS_ACTIVE(CacheDomain::TextBounds | // GetCachedTextLines 477 kNecessaryBoundsDomains); 478 479 if (!BoundsWithOffset(Nothing(), true).Contains(aX, aY)) { 480 return false; 481 } 482 if (!IsTextLeaf()) { 483 if (IsImage() || IsImageMap() || !HasChildren() || 484 RefPtr{DisplayStyle()} != nsGkAtoms::inlinevalue) { 485 // This isn't an inline element that might contain text, so we don't need 486 // to walk lines. It's enough that our rect contains the point. 487 return true; 488 } 489 // Non-image inline elements with children can wrap across lines just like 490 // text leaves; see below. 491 // Walk the children, which will walk the lines of text in any text leaves. 492 uint32_t count = ChildCount(); 493 for (uint32_t c = 0; c < count; ++c) { 494 RemoteAccessible* child = RemoteChildAt(c); 495 if (child->Role() == roles::TEXT_CONTAINER && child->IsClipped()) { 496 // There is a clipped child. This is a candidate for fuzzy hit testing. 497 // See RemoteAccessible::DoFuzzyHittesting. 498 return true; 499 } 500 if (child->ContainsPoint(aX, aY)) { 501 return true; 502 } 503 } 504 // None of our descendants contain the point, so nor do we. 505 return false; 506 } 507 // This is a text leaf. The text might wrap across lines, which means our 508 // rect might cover a wider area than the actual text. For example, if the 509 // text begins in the middle of the first line and wraps on to the second, 510 // the rect will cover the start of the first line and the end of the second. 511 auto lines = GetCachedTextLines(); 512 if (!lines) { 513 // This means the text is empty or occupies a single line (but does not 514 // begin the line). In that case, the Bounds check above is sufficient, 515 // since there's only one rect. 516 return true; 517 } 518 uint32_t length = lines->Length(); 519 MOZ_ASSERT(length > 0, 520 "Line starts shouldn't be in cache if there aren't any"); 521 if (length == 0 || (length == 1 && (*lines)[0] == 0)) { 522 // This means the text begins and occupies a single line. Again, the Bounds 523 // check above is sufficient. 524 return true; 525 } 526 // Walk the lines of the text. Even if this text doesn't start at the 527 // beginning of a line (i.e. lines[0] > 0), we always want to consider its 528 // first line. 529 int32_t lineStart = 0; 530 for (uint32_t index = 0; index <= length; ++index) { 531 int32_t lineEnd; 532 if (index < length) { 533 int32_t nextLineStart = (*lines)[index]; 534 if (nextLineStart == 0) { 535 // This Accessible starts at the beginning of a line. Here, we always 536 // treat 0 as the first line start anyway. 537 MOZ_ASSERT(index == 0); 538 continue; 539 } 540 lineEnd = nextLineStart - 1; 541 } else { 542 // This is the last line. 543 lineEnd = static_cast<int32_t>(nsAccUtils::TextLength(this)) - 1; 544 } 545 MOZ_ASSERT(lineEnd >= lineStart); 546 nsRect lineRect = GetCachedCharRect(lineStart); 547 if (lineEnd > lineStart) { 548 nsRect lineEndRect = GetCachedCharRect(lineEnd); 549 if (lineEndRect.IsEmpty() && lineEnd - 1 > lineStart) { 550 // The line feed character at the end of a line in pre-formatted text 551 // doesn't have a useful rect. Use the previous character. Otherwise, 552 // lineRect won't span the line of text and we'll miss characters. 553 lineEndRect = GetCachedCharRect(lineEnd - 1); 554 } 555 lineRect.UnionRect(lineRect, lineEndRect); 556 } 557 if (BoundsWithOffset(Some(lineRect), true).Contains(aX, aY)) { 558 return true; 559 } 560 lineStart = lineEnd + 1; 561 } 562 return false; 563 } 564 565 RemoteAccessible* RemoteAccessible::DoFuzzyHittesting() { 566 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds); 567 568 uint32_t childCount = ChildCount(); 569 if (!childCount) { 570 return nullptr; 571 } 572 // Check if this match has a clipped child. 573 // This usually indicates invisible text, and we're 574 // interested in returning the inner text content 575 // even if it doesn't contain the point we're hittesting. 576 RemoteAccessible* clippedContainer = nullptr; 577 for (uint32_t i = 0; i < childCount; i++) { 578 RemoteAccessible* child = RemoteChildAt(i); 579 if (child->Role() == roles::TEXT_CONTAINER) { 580 if (child->IsClipped()) { 581 clippedContainer = child; 582 break; 583 } 584 } 585 } 586 // If we found a clipped container, descend it in search of 587 // meaningful text leaves. Ignore non-text-leaf/text-container 588 // siblings. 589 RemoteAccessible* container = clippedContainer; 590 while (container) { 591 RemoteAccessible* textLeaf = nullptr; 592 bool continueSearch = false; 593 childCount = container->ChildCount(); 594 for (uint32_t i = 0; i < childCount; i++) { 595 RemoteAccessible* child = container->RemoteChildAt(i); 596 if (child->Role() == roles::TEXT_CONTAINER) { 597 container = child; 598 continueSearch = true; 599 break; 600 } 601 if (child->IsTextLeaf()) { 602 textLeaf = child; 603 // Don't break here -- it's possible a text container 604 // exists as another sibling, and we should descend as 605 // deep as possible. 606 } 607 } 608 if (textLeaf) { 609 return textLeaf; 610 } 611 if (!continueSearch) { 612 // We didn't find anything useful in this set of siblings. 613 // Don't keep searching 614 break; 615 } 616 } 617 return nullptr; 618 } 619 620 Accessible* RemoteAccessible::ChildAtPoint( 621 int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) { 622 if (RequestDomainsIfInactive( 623 kNecessaryBoundsDomains | 624 CacheDomain::TextBounds // GetCachedTextLines (via ContainsPoint) 625 )) { 626 return nullptr; 627 } 628 629 // Elements that are partially on-screen should have their bounds masked by 630 // their containing scroll area so hittesting yields results that are 631 // consistent with the content's visual representation. Pass this value to 632 // bounds calculation functions to indicate that we're hittesting. 633 const bool hitTesting = true; 634 635 if (IsOuterDoc() && aWhichChild == EWhichChildAtPoint::DirectChild) { 636 // This is an iframe, which is as deep as the viewport cache goes. The 637 // caller wants a direct child, which can only be the embedded document. 638 if (BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) { 639 return RemoteFirstChild(); 640 } 641 return nullptr; 642 } 643 644 RemoteAccessible* lastMatch = nullptr; 645 // If `this` is a document, use its viewport cache instead of 646 // the cache of its parent document. 647 if (DocAccessibleParent* doc = IsDoc() ? AsDoc() : mDoc) { 648 if (!doc->mCachedFields) { 649 // A client call might arrive after we've constructed doc but before we 650 // get a cache push for it. 651 return nullptr; 652 } 653 if (auto maybeViewportCache = 654 doc->mCachedFields->GetAttribute<nsTArray<uint64_t>>( 655 CacheKey::Viewport)) { 656 // The retrieved viewport cache contains acc IDs in hittesting order. 657 // That is, items earlier in the list have z-indexes that are larger than 658 // those later in the list. If you were to build a tree by z-index, where 659 // chilren have larger z indices than their parents, iterating this list 660 // is essentially a postorder tree traversal. 661 const nsTArray<uint64_t>& viewportCache = *maybeViewportCache; 662 663 for (auto id : viewportCache) { 664 RemoteAccessible* acc = doc->GetAccessible(id); 665 if (!acc) { 666 // This can happen if the acc died in between 667 // pushing the viewport cache and doing this hittest 668 continue; 669 } 670 671 if (acc->IsOuterDoc() && 672 aWhichChild == EWhichChildAtPoint::DeepestChild && 673 acc->BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) { 674 // acc is an iframe, which is as deep as the viewport cache goes. This 675 // iframe contains the requested point. 676 RemoteAccessible* innerDoc = acc->RemoteFirstChild(); 677 if (innerDoc) { 678 MOZ_ASSERT(innerDoc->IsDoc()); 679 // Search the embedded document's viewport cache so we return the 680 // deepest descendant in that embedded document. 681 Accessible* deepestAcc = innerDoc->ChildAtPoint( 682 aX, aY, EWhichChildAtPoint::DeepestChild); 683 MOZ_ASSERT(!deepestAcc || deepestAcc->IsRemote()); 684 lastMatch = deepestAcc ? deepestAcc->AsRemote() : nullptr; 685 break; 686 } 687 // If there is no embedded document, the iframe itself is the deepest 688 // descendant. 689 lastMatch = acc; 690 break; 691 } 692 693 if (acc == this) { 694 MOZ_ASSERT(!acc->IsOuterDoc()); 695 // Even though we're searching from the doc's cache 696 // this call shouldn't pass the boundary defined by 697 // the acc this call originated on. If we hit `this`, 698 // return our most recent match. 699 if (!lastMatch && 700 BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) { 701 // If we haven't found a match, but `this` contains the point we're 702 // looking for, set it as our temp last match so we can 703 // (potentially) do fuzzy hittesting on it below. 704 lastMatch = acc; 705 } 706 break; 707 } 708 709 if (acc->ContainsPoint(aX, aY)) { 710 // Because our rects are in hittesting order, the 711 // first match we encounter is guaranteed to be the 712 // deepest match. 713 lastMatch = acc; 714 break; 715 } 716 } 717 if (lastMatch) { 718 RemoteAccessible* fuzzyMatch = lastMatch->DoFuzzyHittesting(); 719 lastMatch = fuzzyMatch ? fuzzyMatch : lastMatch; 720 } 721 } 722 } 723 724 if (aWhichChild == EWhichChildAtPoint::DirectChild && lastMatch) { 725 // lastMatch is the deepest match. Walk up to the direct child of this. 726 RemoteAccessible* parent = lastMatch->RemoteParent(); 727 for (;;) { 728 if (parent == this) { 729 break; 730 } 731 if (!parent || parent->IsDoc()) { 732 // `this` is not an ancestor of lastMatch. Ignore lastMatch. 733 lastMatch = nullptr; 734 break; 735 } 736 lastMatch = parent; 737 parent = parent->RemoteParent(); 738 } 739 } else if (aWhichChild == EWhichChildAtPoint::DeepestChild && lastMatch && 740 !IsDoc() && !IsAncestorOf(lastMatch)) { 741 // If we end up with a match that is not in the ancestor chain 742 // of the accessible this call originated on, we should ignore it. 743 // This can happen when the aX, aY given are outside `this`. 744 lastMatch = nullptr; 745 } 746 747 if (!lastMatch && BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) { 748 // Even though the hit target isn't inside `this`, the point is still 749 // within our bounds, so fall back to `this`. 750 return this; 751 } 752 753 return lastMatch; 754 } 755 756 Maybe<nsRect> RemoteAccessible::RetrieveCachedBounds() const { 757 if (!mCachedFields) { 758 return Nothing(); 759 } 760 761 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds); 762 Maybe<const UniquePtr<nsRect>&> maybeRect = 763 mCachedFields->GetAttribute<UniquePtr<nsRect>>( 764 CacheKey::ParentRelativeBounds); 765 if (maybeRect) { 766 return Some(*(*maybeRect)); 767 } 768 769 return Nothing(); 770 } 771 772 void RemoteAccessible::ApplyCrossDocOffset(nsRect& aBounds) const { 773 if (!IsDoc()) { 774 // We should only apply cross-doc offsets to documents. If we're anything 775 // else, return early here. 776 return; 777 } 778 779 RemoteAccessible* parentAcc = RemoteParent(); 780 if (!parentAcc || !parentAcc->IsOuterDoc()) { 781 return; 782 } 783 784 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds); 785 Maybe<const nsTArray<int32_t>&> maybeOffset = 786 parentAcc->mCachedFields->GetAttribute<nsTArray<int32_t>>( 787 CacheKey::CrossDocOffset); 788 if (!maybeOffset) { 789 return; 790 } 791 792 MOZ_ASSERT(maybeOffset->Length() == 2); 793 const nsTArray<int32_t>& offset = *maybeOffset; 794 // Our retrieved value is in app units, so we don't need to do any 795 // unit conversion here. 796 aBounds.MoveBy(offset[0], offset[1]); 797 } 798 799 bool RemoteAccessible::ApplyTransform(nsRect& aCumulativeBounds) const { 800 ASSERT_DOMAINS_ACTIVE(CacheDomain::TransformMatrix); 801 802 // First, attempt to retrieve the transform from the cache. 803 Maybe<const UniquePtr<gfx::Matrix4x4>&> maybeTransform = 804 mCachedFields->GetAttribute<UniquePtr<gfx::Matrix4x4>>( 805 CacheKey::TransformMatrix); 806 if (!maybeTransform) { 807 return false; 808 } 809 810 auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix( 811 *(*maybeTransform)); 812 813 // Our matrix is in CSS Pixels, so we need our rect to be in CSS 814 // Pixels too. Convert before applying. 815 auto boundsInPixels = CSSRect::FromAppUnits(aCumulativeBounds); 816 boundsInPixels = mtxInPixels.TransformBounds(boundsInPixels); 817 aCumulativeBounds = CSSRect::ToAppUnits(boundsInPixels); 818 819 return true; 820 } 821 822 bool RemoteAccessible::ApplyScrollOffset(nsRect& aBounds, 823 float aResolution) const { 824 ASSERT_DOMAINS_ACTIVE(CacheDomain::ScrollPosition); 825 Maybe<const nsTArray<int32_t>&> maybeScrollPosition = 826 mCachedFields->GetAttribute<nsTArray<int32_t>>(CacheKey::ScrollPosition); 827 828 if (!maybeScrollPosition || maybeScrollPosition->Length() != 2) { 829 return false; 830 } 831 // Our retrieved value is in app units, so we don't need to do any 832 // unit conversion here. 833 const nsTArray<int32_t>& scrollPosition = *maybeScrollPosition; 834 835 // Scroll position is an inverse representation of scroll offset (since the 836 // further the scroll bar moves down the page, the further the page content 837 // moves up/closer to the origin). 838 nsPoint scrollOffset(-scrollPosition[0], -scrollPosition[1]); 839 840 aBounds.MoveBy(scrollOffset.x * aResolution * aResolution, 841 scrollOffset.y * aResolution * aResolution); 842 843 // Return true here even if the scroll offset was 0,0 because the RV is used 844 // as a scroll container indicator. Non-scroll containers won't have cached 845 // scroll position. 846 return true; 847 } 848 849 void RemoteAccessible::ApplyVisualViewportOffset(nsRect& aBounds) const { 850 ASSERT_DOMAINS_ACTIVE(CacheDomain::APZ); 851 MOZ_ASSERT(IsDoc(), "Attempting to get visual viewport data from non-doc?"); 852 Maybe<const nsTArray<int32_t>&> maybeViewportOffset = 853 mCachedFields->GetAttribute<nsTArray<int32_t>>( 854 CacheKey::VisualViewportOffset); 855 856 if (!maybeViewportOffset || maybeViewportOffset->Length() != 2) { 857 return; 858 } 859 // Our retrieved value is in app units, so we don't need to do any 860 // unit conversion here. 861 const nsTArray<int32_t>& viewportOffset = *maybeViewportOffset; 862 863 // Like scroll position, this offset is an inverse representation: the 864 // further the visual viewport moves, the further the page content 865 // moves up/closer to the origin 866 aBounds.MoveBy(-viewportOffset[0], -viewportOffset[1]); 867 } 868 869 nsRect RemoteAccessible::BoundsInAppUnits() const { 870 if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) { 871 return {}; 872 } 873 if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()->Top()) { 874 if (dom::BrowserParent* bp = cbc->GetBrowserParent()) { 875 DocAccessibleParent* topDoc = bp->GetTopLevelDocAccessible(); 876 if (topDoc && topDoc->mCachedFields) { 877 auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>( 878 CacheKey::AppUnitsPerDevPixel); 879 MOZ_ASSERT(appUnitsPerDevPixel); 880 return LayoutDeviceIntRect::ToAppUnits(Bounds(), *appUnitsPerDevPixel); 881 } 882 } 883 } 884 return LayoutDeviceIntRect::ToAppUnits(Bounds(), AppUnitsPerCSSPixel()); 885 } 886 887 bool RemoteAccessible::IsFixedPos() const { 888 ASSERT_DOMAINS_ACTIVE(CacheDomain::Style); 889 MOZ_ASSERT(mCachedFields); 890 if (auto maybePosition = 891 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CssPosition)) { 892 return *maybePosition == nsGkAtoms::fixed; 893 } 894 895 return false; 896 } 897 898 bool RemoteAccessible::IsOverflowHidden() const { 899 ASSERT_DOMAINS_ACTIVE(CacheDomain::Style); 900 MOZ_ASSERT(mCachedFields); 901 if (auto maybeOverflow = 902 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSOverflow)) { 903 return *maybeOverflow == nsGkAtoms::hidden; 904 } 905 906 return false; 907 } 908 909 bool RemoteAccessible::IsClipped() const { 910 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds); 911 MOZ_ASSERT(mCachedFields); 912 if (mCachedFields->GetAttribute<bool>(CacheKey::IsClipped)) { 913 return true; 914 } 915 916 return false; 917 } 918 919 LayoutDeviceIntRect RemoteAccessible::BoundsWithOffset( 920 Maybe<nsRect> aOffset, bool aBoundsAreForHittesting) const { 921 if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) { 922 return LayoutDeviceIntRect{}; 923 } 924 925 Maybe<nsRect> maybeBounds = RetrieveCachedBounds(); 926 if (maybeBounds) { 927 nsRect bounds = *maybeBounds; 928 // maybeBounds is parent-relative. However, the transform matrix we cache 929 // (if any) is meant to operate on self-relative rects. Therefore, make 930 // bounds self-relative until after we transform. 931 bounds.MoveTo(0, 0); 932 const DocAccessibleParent* topDoc = IsDoc() ? AsDoc() : nullptr; 933 934 if (aOffset.isSome()) { 935 // The rect we've passed in is in app units, so no conversion needed. 936 nsRect internalRect = *aOffset; 937 bounds.SetRectX(bounds.x + internalRect.x, internalRect.width); 938 bounds.SetRectY(bounds.y + internalRect.y, internalRect.height); 939 } 940 941 (void)ApplyTransform(bounds); 942 // Now apply the parent-relative offset. 943 bounds.MoveBy(maybeBounds->TopLeft()); 944 945 ApplyCrossDocOffset(bounds); 946 947 Maybe<float> res = 948 mDoc->mCachedFields->GetAttribute<float>(CacheKey::Resolution); 949 MOZ_ASSERT(res, "No cached document resolution found."); 950 const float resolution = res.valueOr(1.0f); 951 952 LayoutDeviceIntRect devPxBounds; 953 const Accessible* acc = Parent(); 954 bool encounteredFixedContainer = IsFixedPos(); 955 while (acc && acc->IsRemote()) { 956 // Return early if we're hit testing and our cumulative bounds are empty, 957 // since walking the ancestor chain won't produce any hits. 958 if (aBoundsAreForHittesting && bounds.IsEmpty()) { 959 return LayoutDeviceIntRect{}; 960 } 961 962 RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote(); 963 964 if (Maybe<nsRect> maybeRemoteBounds = remoteAcc->RetrieveCachedBounds()) { 965 nsRect remoteBounds = *maybeRemoteBounds; 966 // We need to take into account a non-1 resolution set on the 967 // presshell. This happens with async pinch zooming, among other 968 // things. We can't reliably query this value in the parent process, 969 // so we retrieve it from the document's cache. 970 if (remoteAcc->IsDoc()) { 971 // Apply our visual viewport offset, which is non-zero when 972 // pinch zoom has been applied. Do this before we scale by 973 // resolution as this offset is unscaled. 974 remoteAcc->ApplyVisualViewportOffset(bounds); 975 // Apply the document's resolution to the bounds we've gathered 976 // thus far. We do this before applying the document's offset 977 // because document accs should not have their bounds scaled by 978 // their own resolution. They should be scaled by the resolution 979 // of their containing document (if any). 980 Maybe<float> res = 981 remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>( 982 CacheKey::Resolution); 983 MOZ_ASSERT(res, "No cached document resolution found."); 984 bounds.ScaleRoundOut(res.valueOr(1.0f)); 985 986 topDoc = remoteAcc->AsDoc(); 987 } 988 989 // We don't account for the document offset of iframes when 990 // computing parent-relative bounds. Instead, we store this value 991 // separately on all iframes and apply it here. See the comments in 992 // LocalAccessible::BundleFieldsForCache where we set the 993 // nsGkAtoms::crossorigin attribute. 994 remoteAcc->ApplyCrossDocOffset(remoteBounds); 995 if (!encounteredFixedContainer) { 996 // Apply scroll offset, if applicable. Only the contents of an 997 // element are affected by its scroll offset, which is why this call 998 // happens in this loop instead of both inside and outside of 999 // the loop (like ApplyTransform). 1000 // Never apply scroll offsets past a fixed container. 1001 const bool hasScrollArea = 1002 remoteAcc->ApplyScrollOffset(bounds, resolution); 1003 1004 // If we are hit testing and the Accessible has a scroll area, ensure 1005 // that the bounds we've calculated so far are constrained to the 1006 // bounds of the scroll area. Without this, we'll "hit" the off-screen 1007 // portions of accs that are are partially (but not fully) within the 1008 // scroll area. This is also a problem for accs with overflow:hidden; 1009 if (aBoundsAreForHittesting && 1010 (hasScrollArea || remoteAcc->IsOverflowHidden())) { 1011 nsRect selfRelativeVisibleBounds(0, 0, remoteBounds.width, 1012 remoteBounds.height); 1013 bounds = bounds.SafeIntersect(selfRelativeVisibleBounds); 1014 } 1015 } 1016 if (remoteAcc->IsDoc()) { 1017 // Fixed elements are document relative, so if we've hit a 1018 // document we're now subject to that document's styling 1019 // (including scroll offsets that operate on it). 1020 // This ordering is important, we don't want to apply scroll 1021 // offsets on this doc's content. 1022 encounteredFixedContainer = false; 1023 } 1024 if (!encounteredFixedContainer) { 1025 // The transform matrix we cache (if any) is meant to operate on 1026 // self-relative rects. Therefore, we must apply the transform before 1027 // we make bounds parent-relative. 1028 (void)remoteAcc->ApplyTransform(bounds); 1029 // Regardless of whether this is a doc, we should offset `bounds` 1030 // by the bounds retrieved here. This is how we build screen 1031 // coordinates from relative coordinates. 1032 bounds.MoveBy(remoteBounds.X(), remoteBounds.Y()); 1033 } 1034 1035 if (remoteAcc->IsFixedPos()) { 1036 encounteredFixedContainer = true; 1037 } 1038 // we can't just break here if we're scroll suppressed because we still 1039 // need to find the top doc. 1040 } 1041 acc = acc->Parent(); 1042 } 1043 1044 MOZ_ASSERT(topDoc); 1045 if (topDoc) { 1046 // We use the top documents app-units-per-dev-pixel even though 1047 // theoretically nested docs can have different values. Practically, 1048 // that isn't likely since we only offer zoom controls for the top 1049 // document and all subdocuments inherit from it. 1050 auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>( 1051 CacheKey::AppUnitsPerDevPixel); 1052 MOZ_ASSERT(appUnitsPerDevPixel); 1053 if (appUnitsPerDevPixel) { 1054 // Convert our existing `bounds` rect from app units to dev pixels 1055 devPxBounds = LayoutDeviceIntRect::FromAppUnitsToNearest( 1056 bounds, *appUnitsPerDevPixel); 1057 } 1058 } 1059 1060 #if !defined(ANDROID) 1061 // This block is not thread safe because it queries a LocalAccessible. 1062 // It is also not needed in Android since the only local accessible is 1063 // the outer doc browser that has an offset of 0. 1064 // acc could be null if the OuterDocAccessible died before the top level 1065 // DocAccessibleParent. 1066 if (LocalAccessible* localAcc = 1067 acc ? const_cast<Accessible*>(acc)->AsLocal() : nullptr) { 1068 // LocalAccessible::Bounds returns screen-relative bounds in 1069 // dev pixels. 1070 LayoutDeviceIntRect localBounds = localAcc->Bounds(); 1071 1072 // The root document will always have an APZ resolution of 1, 1073 // so we don't factor in its scale here. We also don't scale 1074 // by GetFullZoom because LocalAccessible::Bounds already does 1075 // that. 1076 devPxBounds.MoveBy(localBounds.X(), localBounds.Y()); 1077 } 1078 #endif 1079 1080 return devPxBounds; 1081 } 1082 1083 return LayoutDeviceIntRect(); 1084 } 1085 1086 LayoutDeviceIntRect RemoteAccessible::Bounds() const { 1087 if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) { 1088 return {}; 1089 } 1090 return BoundsWithOffset(Nothing()); 1091 } 1092 1093 Relation RemoteAccessible::RelationByType(RelationType aType) const { 1094 if (RequestDomainsIfInactive( 1095 CacheDomain::Relations | // relations info, DOMName attribute 1096 CacheDomain::Value | // Value 1097 CacheDomain::DOMNodeIDAndClass | // DOMNodeID 1098 CacheDomain::GroupInfo // GetOrCreateGroupInfo 1099 )) { 1100 return Relation(); 1101 } 1102 1103 // We are able to handle some relations completely in the 1104 // parent process, without the help of the cache. Those 1105 // relations are enumerated here. Other relations, whose 1106 // types are stored in kRelationTypeAtoms, are processed 1107 // below using the cache. 1108 if (aType == RelationType::CONTAINING_TAB_PANE) { 1109 if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()) { 1110 if (dom::CanonicalBrowsingContext* topCbc = cbc->Top()) { 1111 if (dom::BrowserParent* bp = topCbc->GetBrowserParent()) { 1112 return Relation(bp->GetTopLevelDocAccessible()); 1113 } 1114 } 1115 } 1116 return Relation(); 1117 } 1118 1119 if (aType == RelationType::LINKS_TO && Role() == roles::LINK) { 1120 Pivot p = Pivot(mDoc); 1121 nsString href; 1122 Value(href); 1123 int32_t i = href.FindChar('#'); 1124 int32_t len = static_cast<int32_t>(href.Length()); 1125 if (i != -1 && i < (len - 1)) { 1126 nsDependentSubstring anchorName = Substring(href, i + 1, len); 1127 MustPruneSameDocRule rule; 1128 Accessible* nameMatch = nullptr; 1129 for (Accessible* match = p.Next(mDoc, rule); match; 1130 match = p.Next(match, rule)) { 1131 nsString currID; 1132 match->DOMNodeID(currID); 1133 MOZ_ASSERT(match->IsRemote()); 1134 if (anchorName.Equals(currID)) { 1135 return Relation(match->AsRemote()); 1136 } 1137 if (!nameMatch) { 1138 nsString currName = match->AsRemote()->GetCachedHTMLNameAttribute(); 1139 if (match->TagName() == nsGkAtoms::a && anchorName.Equals(currName)) { 1140 // If we find an element with a matching ID, we should return 1141 // that, but if we don't we should return the first anchor with 1142 // a matching name. To avoid doing two traversals, store the first 1143 // name match here. 1144 nameMatch = match; 1145 } 1146 } 1147 } 1148 return nameMatch ? Relation(nameMatch->AsRemote()) : Relation(); 1149 } 1150 1151 return Relation(); 1152 } 1153 1154 // Handle ARIA tree, treegrid parent/child relations. Each of these cases 1155 // relies on cached group info. To find the parent of an accessible, use the 1156 // unified conceptual parent. 1157 if (aType == RelationType::NODE_CHILD_OF) { 1158 const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); 1159 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM || 1160 roleMapEntry->role == roles::LISTITEM || 1161 roleMapEntry->role == roles::ROW)) { 1162 if (const AccGroupInfo* groupInfo = 1163 const_cast<RemoteAccessible*>(this)->GetOrCreateGroupInfo()) { 1164 return Relation(groupInfo->ConceptualParent()); 1165 } 1166 } 1167 return Relation(); 1168 } 1169 1170 // To find the children of a parent, provide an iterator through its items. 1171 if (aType == RelationType::NODE_PARENT_OF) { 1172 const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); 1173 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM || 1174 roleMapEntry->role == roles::LISTITEM || 1175 roleMapEntry->role == roles::ROW || 1176 roleMapEntry->role == roles::OUTLINE || 1177 roleMapEntry->role == roles::LIST || 1178 roleMapEntry->role == roles::TREE_TABLE)) { 1179 return Relation(new ItemIterator(this)); 1180 } 1181 return Relation(); 1182 } 1183 1184 if (aType == RelationType::MEMBER_OF) { 1185 Relation rel = Relation(); 1186 // HTML radio buttons with cached names should be grouped. 1187 if (IsHTMLRadioButton()) { 1188 nsString name = GetCachedHTMLNameAttribute(); 1189 if (name.IsEmpty()) { 1190 return rel; 1191 } 1192 1193 RemoteAccessible* ancestor = RemoteParent(); 1194 while (ancestor && ancestor->Role() != roles::FORM && ancestor != mDoc) { 1195 ancestor = ancestor->RemoteParent(); 1196 } 1197 if (ancestor) { 1198 // Sometimes we end up with an unparented acc here, potentially 1199 // because the acc is being moved. See bug 1807639. 1200 // Pivot expects to be created with a non-null mRoot. 1201 Pivot p = Pivot(ancestor); 1202 PivotRadioNameRule rule(name); 1203 Accessible* match = p.Next(ancestor, rule); 1204 while (match) { 1205 rel.AppendTarget(match->AsRemote()); 1206 match = p.Next(match, rule); 1207 } 1208 } 1209 return rel; 1210 } 1211 1212 if (IsARIARole(nsGkAtoms::radio)) { 1213 // ARIA radio buttons should be grouped by their radio group 1214 // parent, if one exists. 1215 RemoteAccessible* currParent = RemoteParent(); 1216 while (currParent && currParent->Role() != roles::RADIO_GROUP) { 1217 currParent = currParent->RemoteParent(); 1218 } 1219 1220 if (currParent && currParent->Role() == roles::RADIO_GROUP) { 1221 // If we found a radiogroup parent, search for all 1222 // roles::RADIOBUTTON children and add them to our relation. 1223 // This search will include the radio button this method 1224 // was called from, which is expected. 1225 Pivot p = Pivot(currParent); 1226 PivotRoleRule rule(roles::RADIOBUTTON); 1227 Accessible* match = p.Next(currParent, rule); 1228 while (match) { 1229 MOZ_ASSERT(match->IsRemote(), 1230 "We should only be traversing the remote tree."); 1231 rel.AppendTarget(match->AsRemote()); 1232 match = p.Next(match, rule); 1233 } 1234 } 1235 } 1236 // By webkit's standard, aria radio buttons do not get grouped 1237 // if they lack a group parent, so we return an empty 1238 // relation here if the above check fails. 1239 return rel; 1240 } 1241 1242 Relation rel; 1243 if (!mCachedFields) { 1244 return rel; 1245 } 1246 1247 auto GetDirectRelationFromCache = 1248 [](const RemoteAccessible* aAcc, 1249 RelationType aRelType) -> Maybe<const nsTArray<uint64_t>&> { 1250 for (const auto& data : kRelationTypeAtoms) { 1251 if (data.mType != aRelType || 1252 (data.mValidTag && aAcc->TagName() != data.mValidTag)) { 1253 continue; 1254 } 1255 1256 if (auto maybeIds = aAcc->mCachedFields->GetAttribute<nsTArray<uint64_t>>( 1257 data.mAtom)) { 1258 if (data.mAtom == nsGkAtoms::target) { 1259 if (!maybeIds->IsEmpty() && 1260 !nsAccUtils::IsValidDetailsTargetForAnchor( 1261 aAcc->mDoc->GetAccessible(maybeIds->ElementAt(0)), aAcc)) { 1262 continue; 1263 } 1264 } 1265 1266 // Relations can have several cached attributes in order of precedence, 1267 // if one is found we use it. 1268 return maybeIds; 1269 } 1270 } 1271 1272 return Nothing(); 1273 }; 1274 1275 if (auto maybeIds = GetDirectRelationFromCache(this, aType)) { 1276 rel.AppendIter(new RemoteAccIterator(*maybeIds, Document())); 1277 } 1278 1279 if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) { 1280 // A list of candidate IDs that might have a relation of type aType 1281 // pointing to us. We keep a list so we don't evaluate or add the same 1282 // target more than once. 1283 nsTArray<uint64_t> relationCandidateIds; 1284 for (const auto& data : kRelationTypeAtoms) { 1285 if (data.mReverseType != aType) { 1286 continue; 1287 } 1288 1289 auto reverseIdsEntry = accRelMapEntry.Data().Lookup(&data); 1290 if (!reverseIdsEntry) { 1291 continue; 1292 } 1293 1294 for (auto id : *reverseIdsEntry) { 1295 if (relationCandidateIds.Contains(id)) { 1296 // If multiple attributes point to the same target, only 1297 // include it once. Our assumption is that there is a 1:1 1298 // relationship between relation and reverse relation *types*. 1299 continue; 1300 } 1301 relationCandidateIds.AppendElement(id); 1302 1303 RemoteAccessible* relatedAcc = mDoc->GetAccessible(id); 1304 if (!relatedAcc) { 1305 continue; 1306 } 1307 1308 if (auto maybeIds = 1309 GetDirectRelationFromCache(relatedAcc, data.mType)) { 1310 if (maybeIds->Contains(ID())) { 1311 // The candidate target has the forward relation type pointing 1312 // to us, so we can add it as a target. 1313 rel.AppendTarget(relatedAcc); 1314 } 1315 } 1316 } 1317 } 1318 } 1319 1320 // We handle these relations here rather than before cached relations because 1321 // the cached relations need to take precedence. For example, a <figure> with 1322 // both aria-labelledby and a <figcaption> must return two LABELLED_BY 1323 // targets: the aria-labelledby and then the <figcaption>. 1324 if (aType == RelationType::LABELLED_BY) { 1325 rel.AppendIter(new ArrayAccIterator(LegendsOrCaptions())); 1326 } else if (aType == RelationType::LABEL_FOR) { 1327 if (RemoteAccessible* labelTarget = LegendOrCaptionFor()) { 1328 rel.AppendTarget(labelTarget); 1329 } 1330 } 1331 1332 return rel; 1333 } 1334 1335 nsTArray<Accessible*> RemoteAccessible::LegendsOrCaptions() const { 1336 nsTArray<Accessible*> children; 1337 auto AddChildWithTag = [this, &children](nsAtom* aTarget) { 1338 uint32_t count = ChildCount(); 1339 for (uint32_t c = 0; c < count; ++c) { 1340 Accessible* child = ChildAt(c); 1341 MOZ_ASSERT(child); 1342 if (child->TagName() == aTarget) { 1343 children.AppendElement(child); 1344 } 1345 } 1346 }; 1347 1348 auto tag = TagName(); 1349 if (tag == nsGkAtoms::figure) { 1350 AddChildWithTag(nsGkAtoms::figcaption); 1351 } else if (tag == nsGkAtoms::fieldset) { 1352 AddChildWithTag(nsGkAtoms::legend); 1353 } else if (tag == nsGkAtoms::table) { 1354 AddChildWithTag(nsGkAtoms::caption); 1355 } 1356 1357 return children; 1358 } 1359 1360 RemoteAccessible* RemoteAccessible::LegendOrCaptionFor() const { 1361 auto tag = TagName(); 1362 if (tag == nsGkAtoms::figcaption) { 1363 if (RemoteAccessible* parent = RemoteParent()) { 1364 if (parent->TagName() == nsGkAtoms::figure) { 1365 return parent; 1366 } 1367 } 1368 } else if (tag == nsGkAtoms::legend) { 1369 if (RemoteAccessible* parent = RemoteParent()) { 1370 if (parent->TagName() == nsGkAtoms::fieldset) { 1371 return parent; 1372 } 1373 } 1374 } else if (tag == nsGkAtoms::caption) { 1375 if (RemoteAccessible* parent = RemoteParent()) { 1376 if (parent->TagName() == nsGkAtoms::table) { 1377 return parent; 1378 } 1379 } 1380 } 1381 1382 return nullptr; 1383 } 1384 1385 void RemoteAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset, 1386 uint32_t aLength) { 1387 if (IsText()) { 1388 if (RequestDomainsIfInactive(CacheDomain::Text)) { 1389 return; 1390 } 1391 if (mCachedFields) { 1392 if (auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text)) { 1393 aText.Append(Substring(*text, aStartOffset, aLength)); 1394 } 1395 VERIFY_CACHE(CacheDomain::Text); 1396 } 1397 return; 1398 } 1399 1400 if (aStartOffset != 0 || aLength == 0) { 1401 return; 1402 } 1403 1404 if (IsHTMLBr()) { 1405 aText += kForcedNewLineChar; 1406 } else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) { 1407 // Expose the embedded object accessible as imaginary embedded object 1408 // character if its parent hypertext accessible doesn't expose children to 1409 // AT. 1410 aText += kImaginaryEmbeddedObjectChar; 1411 } else { 1412 aText += kEmbeddedObjectChar; 1413 } 1414 } 1415 1416 nsTArray<bool> RemoteAccessible::PreProcessRelations(AccAttributes* aFields) { 1417 if (!DomainsAreActive(CacheDomain::Relations)) { 1418 return {}; 1419 } 1420 nsTArray<bool> updateTracker(std::size(kRelationTypeAtoms)); 1421 for (auto const& data : kRelationTypeAtoms) { 1422 if (data.mValidTag) { 1423 // The relation we're currently processing only applies to particular 1424 // elements. Check to see if we're one of them. 1425 nsAtom* tag = TagName(); 1426 if (!tag) { 1427 // TagName() returns null on an initial cache push -- check aFields 1428 // for a tag name instead. 1429 if (auto maybeTag = 1430 aFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) { 1431 tag = *maybeTag; 1432 } 1433 } 1434 MOZ_ASSERT( 1435 tag || IsTextLeaf() || IsDoc(), 1436 "Could not fetch tag via TagName() or from initial cache push!"); 1437 if (tag != data.mValidTag) { 1438 // If this rel doesn't apply to us, do no pre-processing. Also, 1439 // note in our updateTracker that we should do no post-processing. 1440 updateTracker.AppendElement(false); 1441 continue; 1442 } 1443 } 1444 1445 nsStaticAtom* const relAtom = data.mAtom; 1446 auto newRelationTargets = 1447 aFields->GetAttribute<nsTArray<uint64_t>>(relAtom); 1448 bool shouldAddNewImplicitRels = 1449 newRelationTargets && newRelationTargets->Length(); 1450 1451 // Remove existing implicit relations if we need to perform an update, or 1452 // if we've received a DeleteEntry(). Only do this if mCachedFields is 1453 // initialized. If mCachedFields is not initialized, we still need to 1454 // construct the update array so we correctly handle reverse rels in 1455 // PostProcessRelations. 1456 if ((shouldAddNewImplicitRels || 1457 aFields->GetAttribute<DeleteEntry>(relAtom)) && 1458 mCachedFields) { 1459 ASSERT_DOMAINS_ACTIVE(CacheDomain::Relations); 1460 if (auto maybeOldIDs = 1461 mCachedFields->GetAttribute<nsTArray<uint64_t>>(relAtom)) { 1462 for (uint64_t id : *maybeOldIDs) { 1463 // For each target, fetch its reverse relation map 1464 // We need to call `Lookup` here instead of `LookupOrInsert` because 1465 // it's possible the ID we're querying is from an acc that has since 1466 // been Shutdown(), and so has intentionally removed its reverse rels 1467 // from the doc's reverse rel cache. 1468 if (auto reverseRels = Document()->mReverseRelations.Lookup(id)) { 1469 // Then fetch its reverse relation's ID list. This should be safe 1470 // to do via LookupOrInsert because by the time we've gotten here, 1471 // we know the acc and `this` are still alive in the doc. If we hit 1472 // the following assert, we don't have parity on implicit/explicit 1473 // rels and something is wrong. 1474 nsTArray<uint64_t>& reverseRelIDs = 1475 reverseRels->LookupOrInsert(&data); 1476 // There might be other reverse relations stored for this acc, so 1477 // remove our ID instead of deleting the array entirely. 1478 DebugOnly<bool> removed = reverseRelIDs.RemoveElement(ID()); 1479 MOZ_ASSERT(removed, "Can't find old reverse relation"); 1480 } 1481 } 1482 } 1483 } 1484 1485 updateTracker.AppendElement(shouldAddNewImplicitRels); 1486 } 1487 1488 return updateTracker; 1489 } 1490 1491 void RemoteAccessible::PostProcessRelations(const nsTArray<bool>& aToUpdate) { 1492 if (!DomainsAreActive(CacheDomain::Relations)) { 1493 return; 1494 } 1495 size_t updateCount = aToUpdate.Length(); 1496 MOZ_ASSERT(updateCount == std::size(kRelationTypeAtoms), 1497 "Did not note update status for every relation type!"); 1498 for (size_t i = 0; i < updateCount; i++) { 1499 if (aToUpdate.ElementAt(i)) { 1500 // Since kRelationTypeAtoms was used to generate aToUpdate, we 1501 // know the ith entry of aToUpdate corresponds to the relation type in 1502 // the ith entry of kRelationTypeAtoms. Fetch the related data here. 1503 auto const& data = kRelationTypeAtoms[i]; 1504 1505 const nsTArray<uint64_t>& newIDs = 1506 *mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom); 1507 for (uint64_t id : newIDs) { 1508 auto& relations = Document()->mReverseRelations.LookupOrInsert(id); 1509 nsTArray<uint64_t>& ids = relations.LookupOrInsert(&data); 1510 ids.AppendElement(ID()); 1511 } 1512 } 1513 } 1514 } 1515 1516 void RemoteAccessible::PruneRelationsOnShutdown() { 1517 auto reverseRels = mDoc->mReverseRelations.Lookup(ID()); 1518 if (!reverseRels) { 1519 return; 1520 } 1521 for (auto const& data : kRelationTypeAtoms) { 1522 // Fetch the list of targets for this reverse relation 1523 auto reverseTargetList = reverseRels->Lookup(&data); 1524 if (!reverseTargetList) { 1525 continue; 1526 } 1527 for (uint64_t id : *reverseTargetList) { 1528 // For each target, retrieve its corresponding forward relation target 1529 // list 1530 RemoteAccessible* affectedAcc = mDoc->GetAccessible(id); 1531 if (!affectedAcc) { 1532 // It's possible the affect acc also shut down, in which case 1533 // we don't have anything to update. 1534 continue; 1535 } 1536 if (auto forwardTargetList = 1537 affectedAcc->mCachedFields 1538 ->GetMutableAttribute<nsTArray<uint64_t>>(data.mAtom)) { 1539 forwardTargetList->RemoveElement(ID()); 1540 if (!forwardTargetList->Length()) { 1541 // The ID we removed was the only thing in the list, so remove the 1542 // entry from the cache entirely -- don't leave an empty array. 1543 affectedAcc->mCachedFields->Remove(data.mAtom); 1544 } 1545 } 1546 } 1547 } 1548 // Remove this ID from the document's map of reverse relations. 1549 reverseRels.Remove(); 1550 } 1551 1552 uint32_t RemoteAccessible::GetCachedTextLength() { 1553 if (RequestDomainsIfInactive(CacheDomain::Text)) { 1554 return 0; 1555 } 1556 MOZ_ASSERT(!HasChildren()); 1557 if (!mCachedFields) { 1558 return 0; 1559 } 1560 VERIFY_CACHE(CacheDomain::Text); 1561 auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text); 1562 if (!text) { 1563 return 0; 1564 } 1565 return text->Length(); 1566 } 1567 1568 Maybe<const nsTArray<int32_t>&> RemoteAccessible::GetCachedTextLines() { 1569 if (RequestDomainsIfInactive(CacheDomain::TextBounds)) { 1570 return Nothing(); 1571 } 1572 1573 MOZ_ASSERT(!HasChildren()); 1574 if (!mCachedFields) { 1575 return Nothing(); 1576 } 1577 VERIFY_CACHE(CacheDomain::TextBounds); 1578 return mCachedFields->GetAttribute<nsTArray<int32_t>>( 1579 CacheKey::TextLineStarts); 1580 } 1581 1582 nsRect RemoteAccessible::GetCachedCharRect(int32_t aOffset) { 1583 ASSERT_DOMAINS_ACTIVE(CacheDomain::TextBounds); 1584 MOZ_ASSERT(IsText()); 1585 if (!mCachedFields) { 1586 return nsRect(); 1587 } 1588 1589 if (Maybe<const nsTArray<int32_t>&> maybeCharData = 1590 mCachedFields->GetAttribute<nsTArray<int32_t>>( 1591 CacheKey::TextBounds)) { 1592 const nsTArray<int32_t>& charData = *maybeCharData; 1593 const int32_t index = aOffset * kNumbersInRect; 1594 if (index < static_cast<int32_t>(charData.Length())) { 1595 return nsRect(charData[index], charData[index + 1], charData[index + 2], 1596 charData[index + 3]); 1597 } 1598 // It is valid for a client to call this with an offset 1 after the last 1599 // character because of the insertion point at the end of text boxes. 1600 MOZ_ASSERT(index == static_cast<int32_t>(charData.Length())); 1601 } 1602 1603 return nsRect(); 1604 } 1605 1606 void RemoteAccessible::DOMNodeID(nsString& aID) const { 1607 if (RequestDomainsIfInactive(CacheDomain::DOMNodeIDAndClass)) { 1608 return; 1609 } 1610 if (mCachedFields) { 1611 mCachedFields->GetAttribute(CacheKey::DOMNodeID, aID); 1612 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass); 1613 } 1614 } 1615 1616 void RemoteAccessible::DOMNodeClass(nsString& aClass) const { 1617 if (mCachedFields) { 1618 mCachedFields->GetAttribute(CacheKey::DOMNodeClass, aClass); 1619 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass); 1620 } 1621 } 1622 1623 void RemoteAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX, 1624 int32_t aY) { 1625 (void)mDoc->SendScrollToPoint(mID, aScrollType, aX, aY); 1626 } 1627 1628 bool RemoteAccessible::IsScrollable() const { 1629 if (RequestDomainsIfInactive(CacheDomain::ScrollPosition)) { 1630 return false; 1631 } 1632 return mCachedFields && mCachedFields->HasAttribute(CacheKey::ScrollPosition); 1633 } 1634 1635 bool RemoteAccessible::IsPopover() const { 1636 return mCachedFields && mCachedFields->HasAttribute(CacheKey::PopupType); 1637 } 1638 1639 bool RemoteAccessible::IsEditable() const { 1640 if (RequestDomainsIfInactive(CacheDomain::State)) { 1641 return false; 1642 } 1643 1644 if (mCachedFields) { 1645 if (auto rawState = 1646 mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) { 1647 VERIFY_CACHE(CacheDomain::State); 1648 return (*rawState & states::EDITABLE) != 0; 1649 } 1650 } 1651 1652 return false; 1653 } 1654 1655 #if !defined(XP_WIN) 1656 void RemoteAccessible::Announce(const nsString& aAnnouncement, 1657 uint16_t aPriority) { 1658 (void)mDoc->SendAnnounce(mID, aAnnouncement, aPriority); 1659 } 1660 #endif // !defined(XP_WIN) 1661 1662 int32_t RemoteAccessible::ValueRegion() const { 1663 MOZ_ASSERT(TagName() == nsGkAtoms::meter, 1664 "Accessing value region on non-meter element?"); 1665 if (mCachedFields) { 1666 if (auto region = 1667 mCachedFields->GetAttribute<int32_t>(CacheKey::ValueRegion)) { 1668 return *region; 1669 } 1670 } 1671 // Expose sub-optimal (but not critical) as the value region, as a fallback. 1672 return 0; 1673 } 1674 1675 void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset, 1676 int32_t aEndOffset, 1677 uint32_t aCoordinateType, 1678 int32_t aX, int32_t aY) { 1679 (void)mDoc->SendScrollSubstringToPoint(mID, aStartOffset, aEndOffset, 1680 aCoordinateType, aX, aY); 1681 } 1682 1683 RefPtr<const AccAttributes> RemoteAccessible::GetCachedTextAttributes() { 1684 if (RequestDomainsIfInactive(CacheDomain::Text)) { 1685 return nullptr; 1686 } 1687 MOZ_ASSERT(IsText() || IsHyperText()); 1688 if (mCachedFields) { 1689 auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>( 1690 CacheKey::TextAttributes); 1691 VERIFY_CACHE(CacheDomain::Text); 1692 return attrs; 1693 } 1694 return nullptr; 1695 } 1696 1697 std::pair<LayoutDeviceIntRect, nsIWidget*> RemoteAccessible::GetCaretRect() { 1698 nsIWidget* widget = nullptr; 1699 LocalAccessible* outerDoc = OuterDocOfRemoteBrowser(); 1700 if (outerDoc) { 1701 widget = nsContentUtils::WidgetForContent(outerDoc->GetContent()); 1702 } 1703 1704 return {mDoc->GetCachedCaretRect(), widget}; 1705 } 1706 1707 already_AddRefed<AccAttributes> RemoteAccessible::DefaultTextAttributes() { 1708 if (RequestDomainsIfInactive(CacheDomain::Text)) { 1709 return nullptr; 1710 } 1711 1712 RefPtr<AccAttributes> result = new AccAttributes(); 1713 for (RemoteAccessible* parent = this; parent; 1714 parent = parent->RemoteParent()) { 1715 if (!parent->IsHyperText()) { 1716 // We are only interested in hypertext nodes for defaults, not in text 1717 // leafs or non hypertext nodes. 1718 continue; 1719 } 1720 1721 if (RefPtr<const AccAttributes> parentAttrs = 1722 parent->GetCachedTextAttributes()) { 1723 // Update our text attributes with any parent entries we don't have. 1724 parentAttrs->CopyTo(result, true); 1725 } 1726 } 1727 1728 return result.forget(); 1729 } 1730 1731 const AccAttributes* RemoteAccessible::GetCachedARIAAttributes() const { 1732 ASSERT_DOMAINS_ACTIVE(CacheDomain::ARIA); 1733 if (mCachedFields) { 1734 auto attrs = mCachedFields->GetAttributeWeakPtr<AccAttributes>( 1735 CacheKey::ARIAAttributes); 1736 VERIFY_CACHE(CacheDomain::ARIA); 1737 return attrs; 1738 } 1739 return nullptr; 1740 } 1741 1742 nsString RemoteAccessible::GetCachedHTMLNameAttribute() const { 1743 ASSERT_DOMAINS_ACTIVE(CacheDomain::Relations); 1744 if (mCachedFields) { 1745 if (auto maybeName = 1746 mCachedFields->GetAttribute<nsString>(CacheKey::DOMName)) { 1747 return *maybeName; 1748 } 1749 } 1750 return nsString(); 1751 } 1752 1753 uint64_t RemoteAccessible::State() { 1754 if (RequestDomainsIfInactive( 1755 CacheDomain::State | // State attributes 1756 CacheDomain::Style | // for Opacity (via ApplyImplicitState) 1757 CacheDomain::Viewport // necessary to build mOnScreenAccessibles 1758 )) { 1759 return 0; 1760 } 1761 uint64_t state = 0; 1762 if (mCachedFields) { 1763 if (auto rawState = 1764 mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) { 1765 VERIFY_CACHE(CacheDomain::State); 1766 state = *rawState; 1767 } 1768 1769 ApplyImplicitState(state); 1770 1771 auto* cbc = mDoc->GetBrowsingContext(); 1772 if (cbc && !cbc->IsActive()) { 1773 // If our browsing context is _not_ active, we're in a background tab 1774 // and inherently offscreen. 1775 state |= states::OFFSCREEN; 1776 } else { 1777 // If we're in an active browsing context, there are a few scenarios we 1778 // need to address: 1779 // - We are an iframe document in the visual viewport 1780 // - We are an iframe document out of the visual viewport 1781 // - We are non-iframe content in the visual viewport 1782 // - We are non-iframe content out of the visual viewport 1783 // We assume top level tab docs are on screen if their BC is active, so 1784 // we don't need additional handling for them here. 1785 if (!mDoc->IsTopLevel()) { 1786 // Here we handle iframes and iframe content. 1787 // We use an iframe's outer doc's position in the embedding document's 1788 // viewport to determine if the iframe has been scrolled offscreen. 1789 Accessible* docParent = mDoc->Parent(); 1790 // In rare cases, we might not have an outer doc yet. Return if that's 1791 // the case. 1792 if (NS_WARN_IF(!docParent || !docParent->IsRemote())) { 1793 return state; 1794 } 1795 1796 RemoteAccessible* outerDoc = docParent->AsRemote(); 1797 DocAccessibleParent* embeddingDocument = outerDoc->Document(); 1798 if (embeddingDocument && 1799 !embeddingDocument->mOnScreenAccessibles.Contains(outerDoc->ID())) { 1800 // Our embedding document's viewport cache doesn't contain the ID of 1801 // our outer doc, so this iframe (and any of its content) is 1802 // offscreen. 1803 state |= states::OFFSCREEN; 1804 } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) { 1805 // Our embedding document's viewport cache contains the ID of our 1806 // outer doc, but the iframe's viewport cache doesn't contain our ID. 1807 // We are offscreen. 1808 state |= states::OFFSCREEN; 1809 } 1810 } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) { 1811 // We are top level tab content (but not a top level tab doc). 1812 // If our tab doc's viewport cache doesn't contain our ID, we're 1813 // offscreen. 1814 state |= states::OFFSCREEN; 1815 } 1816 } 1817 } 1818 1819 return state; 1820 } 1821 1822 already_AddRefed<AccAttributes> RemoteAccessible::Attributes() { 1823 RefPtr<AccAttributes> attributes = new AccAttributes(); 1824 if (RequestDomainsIfInactive(CacheDomain::ARIA | // GetCachedARIAAttributes 1825 CacheDomain::NameAndDescription | // Name 1826 CacheDomain::Text | // Name 1827 CacheDomain::Value | // Value 1828 CacheDomain::Actions | // Value 1829 CacheDomain::Style | // DisplayStyle 1830 CacheDomain::GroupInfo | // GroupPosition 1831 CacheDomain::State | // State 1832 CacheDomain::Viewport | // State 1833 CacheDomain::Table | // TableIsProbablyForLayout 1834 CacheDomain::DOMNodeIDAndClass | // DOMNodeID 1835 CacheDomain::Relations)) { 1836 return attributes.forget(); 1837 } 1838 1839 nsAccessibilityService* accService = GetAccService(); 1840 if (!accService) { 1841 // The service can be shut down before RemoteAccessibles. If it is shut 1842 // down, we can't calculate some attributes. We're about to die anyway. 1843 return attributes.forget(); 1844 } 1845 1846 if (mCachedFields) { 1847 // We use GetAttribute instead of GetAttributeRefPtr because we need 1848 // nsAtom, not const nsAtom. 1849 if (auto tag = 1850 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) { 1851 attributes->SetAttribute(nsGkAtoms::tag, *tag); 1852 } 1853 1854 bool hierarchical = false; 1855 uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical); 1856 if (itemCount) { 1857 attributes->SetAttribute(nsGkAtoms::child_item_count, 1858 static_cast<int32_t>(itemCount)); 1859 } 1860 1861 if (hierarchical) { 1862 attributes->SetAttribute(nsGkAtoms::tree, true); 1863 } 1864 1865 if (auto inputType = 1866 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) { 1867 attributes->SetAttribute(nsGkAtoms::textInputType, *inputType); 1868 } 1869 1870 if (RefPtr<nsAtom> display = DisplayStyle()) { 1871 attributes->SetAttribute(nsGkAtoms::display, display); 1872 } 1873 1874 if (TableCellAccessible* cell = AsTableCell()) { 1875 TableAccessible* table = cell->Table(); 1876 uint32_t row = cell->RowIdx(); 1877 uint32_t col = cell->ColIdx(); 1878 int32_t cellIdx = table->CellIndexAt(row, col); 1879 if (cellIdx != -1) { 1880 attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx); 1881 } 1882 } 1883 1884 if (bool layoutGuess = TableIsProbablyForLayout()) { 1885 attributes->SetAttribute(nsGkAtoms::layout_guess, layoutGuess); 1886 } 1887 1888 accService->MarkupAttributes(this, attributes); 1889 1890 const nsRoleMapEntry* roleMap = ARIARoleMap(); 1891 nsAutoString role; 1892 mCachedFields->GetAttribute(CacheKey::ARIARole, role); 1893 if (role.IsEmpty()) { 1894 if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty) { 1895 // Single, known role. 1896 attributes->SetAttribute(nsGkAtoms::xmlroles, roleMap->roleAtom); 1897 } else if (nsAtom* landmark = LandmarkRole()) { 1898 // Landmark role from markup; e.g. HTML <main>. 1899 attributes->SetAttribute(nsGkAtoms::xmlroles, landmark); 1900 } 1901 } else { 1902 // Unknown role or multiple roles. 1903 attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role)); 1904 } 1905 1906 if (roleMap) { 1907 nsAutoString live; 1908 if (nsAccUtils::GetLiveAttrValue(roleMap->liveAttRule, live)) { 1909 attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live)); 1910 } 1911 } 1912 1913 if (auto ariaAttrs = GetCachedARIAAttributes()) { 1914 ariaAttrs->CopyTo(attributes); 1915 } 1916 1917 nsAccUtils::SetLiveContainerAttributes(attributes, this); 1918 1919 nsString id; 1920 DOMNodeID(id); 1921 if (!id.IsEmpty()) { 1922 attributes->SetAttribute(nsGkAtoms::id, std::move(id)); 1923 } 1924 1925 nsString className; 1926 DOMNodeClass(className); 1927 if (!className.IsEmpty()) { 1928 attributes->SetAttribute(nsGkAtoms::_class, std::move(className)); 1929 } 1930 1931 if (IsImage()) { 1932 nsString src; 1933 mCachedFields->GetAttribute(CacheKey::SrcURL, src); 1934 if (!src.IsEmpty()) { 1935 attributes->SetAttribute(nsGkAtoms::src, std::move(src)); 1936 } 1937 } 1938 1939 if (IsTextField()) { 1940 nsString placeholder; 1941 mCachedFields->GetAttribute(CacheKey::HTMLPlaceholder, placeholder); 1942 if (!placeholder.IsEmpty()) { 1943 attributes->SetAttribute(nsGkAtoms::placeholder, 1944 std::move(placeholder)); 1945 attributes->Remove(nsGkAtoms::aria_placeholder); 1946 } 1947 } 1948 1949 nsString popupType; 1950 mCachedFields->GetAttribute(CacheKey::PopupType, popupType); 1951 if (!popupType.IsEmpty()) { 1952 attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popupType)); 1953 } 1954 1955 if (HasCustomActions()) { 1956 attributes->SetAttribute(nsGkAtoms::hasActions, true); 1957 } 1958 1959 nsString detailsFrom; 1960 if (mCachedFields->HasAttribute(nsGkAtoms::aria_details)) { 1961 detailsFrom.AssignLiteral("aria-details"); 1962 } else if (mCachedFields->HasAttribute(nsGkAtoms::commandfor)) { 1963 detailsFrom.AssignLiteral("command-for"); 1964 } else if (mCachedFields->HasAttribute(nsGkAtoms::popovertarget)) { 1965 detailsFrom.AssignLiteral("popover-target"); 1966 } else if (mCachedFields->HasAttribute(nsGkAtoms::target)) { 1967 detailsFrom.AssignLiteral("css-anchor"); 1968 } 1969 1970 if (!detailsFrom.IsEmpty()) { 1971 attributes->SetAttribute(nsGkAtoms::details_from, std::move(detailsFrom)); 1972 } 1973 } 1974 1975 nsAutoString name; 1976 if (Name(name) != eNameFromSubtree && !name.IsVoid()) { 1977 attributes->SetAttribute(nsGkAtoms::explicit_name, true); 1978 } 1979 1980 // Expose the string value via the valuetext attribute. We test for the value 1981 // interface because we don't want to expose traditional Value() information 1982 // such as URLs on links and documents, or text in an input. 1983 // XXX This is only needed for ATK, since other APIs have native ways to 1984 // retrieve value text. We should probably move this into ATK specific code. 1985 // For now, we do this because LocalAccessible does it. 1986 if (HasNumericValue()) { 1987 nsString valuetext; 1988 Value(valuetext); 1989 attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext)); 1990 } 1991 1992 return attributes.forget(); 1993 } 1994 1995 nsAtom* RemoteAccessible::TagName() const { 1996 if (mCachedFields) { 1997 if (auto tag = 1998 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) { 1999 return *tag; 2000 } 2001 } 2002 2003 return nullptr; 2004 } 2005 2006 already_AddRefed<nsAtom> RemoteAccessible::DisplayStyle() const { 2007 if (RequestDomainsIfInactive(CacheDomain::Style)) { 2008 return nullptr; 2009 } 2010 if (mCachedFields) { 2011 if (auto display = 2012 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSDisplay)) { 2013 RefPtr<nsAtom> result = *display; 2014 return result.forget(); 2015 } 2016 } 2017 return nullptr; 2018 } 2019 2020 float RemoteAccessible::Opacity() const { 2021 if (RequestDomainsIfInactive(CacheDomain::Style)) { 2022 return 1.0f; 2023 } 2024 2025 if (mCachedFields) { 2026 if (auto opacity = mCachedFields->GetAttribute<float>(CacheKey::Opacity)) { 2027 return *opacity; 2028 } 2029 } 2030 2031 return 1.0f; 2032 } 2033 2034 WritingMode RemoteAccessible::GetWritingMode() const { 2035 if (RequestDomainsIfInactive(CacheDomain::Style)) { 2036 return WritingMode(); 2037 } 2038 2039 if (mCachedFields) { 2040 if (auto wm = 2041 mCachedFields->GetAttribute<WritingMode>(CacheKey::WritingMode)) { 2042 return *wm; 2043 } 2044 } 2045 2046 return WritingMode(); 2047 } 2048 2049 void RemoteAccessible::LiveRegionAttributes(nsAString* aLive, 2050 nsAString* aRelevant, 2051 Maybe<bool>* aAtomic, 2052 nsAString* aBusy) const { 2053 if (RequestDomainsIfInactive(CacheDomain::ARIA)) { 2054 return; 2055 } 2056 if (!mCachedFields) { 2057 return; 2058 } 2059 auto attrs = GetCachedARIAAttributes(); 2060 if (!attrs) { 2061 return; 2062 } 2063 if (aLive) { 2064 attrs->GetAttribute(nsGkAtoms::aria_live, *aLive); 2065 } 2066 if (aRelevant) { 2067 attrs->GetAttribute(nsGkAtoms::aria_relevant, *aRelevant); 2068 } 2069 if (aAtomic) { 2070 if (auto value = 2071 attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::aria_atomic)) { 2072 *aAtomic = Some(*value == nsGkAtoms::_true); 2073 } 2074 } 2075 if (aBusy) { 2076 attrs->GetAttribute(nsGkAtoms::aria_busy, *aBusy); 2077 } 2078 } 2079 2080 Maybe<bool> RemoteAccessible::ARIASelected() const { 2081 if (RequestDomainsIfInactive(CacheDomain::State)) { 2082 return Nothing(); 2083 } 2084 2085 if (mCachedFields) { 2086 return mCachedFields->GetAttribute<bool>(CacheKey::ARIASelected); 2087 } 2088 return Nothing(); 2089 } 2090 2091 nsAtom* RemoteAccessible::GetPrimaryAction() const { 2092 if (mCachedFields) { 2093 ASSERT_DOMAINS_ACTIVE(CacheDomain::Actions); 2094 if (auto action = mCachedFields->GetAttribute<RefPtr<nsAtom>>( 2095 CacheKey::PrimaryAction)) { 2096 return *action; 2097 } 2098 } 2099 2100 return nullptr; 2101 } 2102 2103 uint8_t RemoteAccessible::ActionCount() const { 2104 uint8_t actionCount = 0; 2105 if (RequestDomainsIfInactive(CacheDomain::Actions)) { 2106 return actionCount; 2107 } 2108 if (mCachedFields) { 2109 if (HasPrimaryAction() || ActionAncestor()) { 2110 actionCount++; 2111 } 2112 2113 if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) { 2114 actionCount++; 2115 } 2116 VERIFY_CACHE(CacheDomain::Actions); 2117 } 2118 2119 return actionCount; 2120 } 2121 2122 void RemoteAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { 2123 if (RequestDomainsIfInactive(CacheDomain::Actions)) { 2124 return; 2125 } 2126 2127 if (mCachedFields) { 2128 aName.Truncate(); 2129 nsAtom* action = GetPrimaryAction(); 2130 bool hasActionAncestor = !action && ActionAncestor(); 2131 2132 switch (aIndex) { 2133 case 0: 2134 if (action) { 2135 action->ToString(aName); 2136 } else if (hasActionAncestor) { 2137 aName.AssignLiteral("clickAncestor"); 2138 } else if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) { 2139 aName.AssignLiteral("showlongdesc"); 2140 } 2141 break; 2142 case 1: 2143 if ((action || hasActionAncestor) && 2144 mCachedFields->HasAttribute(CacheKey::HasLongdesc)) { 2145 aName.AssignLiteral("showlongdesc"); 2146 } 2147 break; 2148 default: 2149 break; 2150 } 2151 } 2152 VERIFY_CACHE(CacheDomain::Actions); 2153 } 2154 2155 bool RemoteAccessible::DoAction(uint8_t aIndex) const { 2156 if (RequestDomainsIfInactive(CacheDomain::Actions)) { 2157 return false; 2158 } 2159 2160 if (ActionCount() < aIndex + 1) { 2161 return false; 2162 } 2163 2164 (void)mDoc->SendDoActionAsync(mID, aIndex); 2165 return true; 2166 } 2167 2168 KeyBinding RemoteAccessible::AccessKey() const { 2169 if (RequestDomainsIfInactive(CacheDomain::Actions)) { 2170 return {}; 2171 } 2172 2173 if (mCachedFields) { 2174 if (auto value = 2175 mCachedFields->GetAttribute<uint64_t>(CacheKey::AccessKey)) { 2176 return KeyBinding(*value); 2177 } 2178 } 2179 return KeyBinding(); 2180 } 2181 2182 void RemoteAccessible::SelectionRanges(nsTArray<TextRange>* aRanges) const { 2183 Document()->SelectionRanges(aRanges); 2184 } 2185 2186 bool RemoteAccessible::RemoveFromSelection(int32_t aSelectionNum) { 2187 MOZ_ASSERT(IsHyperText()); 2188 if (SelectionCount() <= aSelectionNum) { 2189 return false; 2190 } 2191 2192 (void)mDoc->SendRemoveTextSelection(mID, aSelectionNum); 2193 2194 return true; 2195 } 2196 2197 void RemoteAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize, 2198 int32_t* aPosInSet) const { 2199 if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) { 2200 return; 2201 } 2202 2203 if (!mCachedFields) { 2204 return; 2205 } 2206 2207 if (aLevel) { 2208 if (auto level = 2209 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_level)) { 2210 *aLevel = *level; 2211 } 2212 } 2213 if (aSetSize) { 2214 if (auto setsize = 2215 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_setsize)) { 2216 *aSetSize = *setsize; 2217 } 2218 } 2219 if (aPosInSet) { 2220 if (auto posinset = 2221 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_posinset)) { 2222 *aPosInSet = *posinset; 2223 } 2224 } 2225 } 2226 2227 AccGroupInfo* RemoteAccessible::GetGroupInfo() const { 2228 // Interpret a call to GetGroupInfo as a signal that the AT will want group 2229 // info information. CacheKey::GroupInfo is not in CacheDomain::GroupInfo, so 2230 // this isn't strictly necessary, but is likely helpful. 2231 if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) { 2232 return nullptr; 2233 } 2234 2235 if (!mCachedFields) { 2236 return nullptr; 2237 } 2238 2239 if (auto groupInfo = mCachedFields->GetAttribute<UniquePtr<AccGroupInfo>>( 2240 CacheKey::GroupInfo)) { 2241 return groupInfo->get(); 2242 } 2243 2244 return nullptr; 2245 } 2246 2247 AccGroupInfo* RemoteAccessible::GetOrCreateGroupInfo() { 2248 if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) { 2249 return nullptr; 2250 } 2251 2252 AccGroupInfo* groupInfo = GetGroupInfo(); 2253 if (groupInfo) { 2254 return groupInfo; 2255 } 2256 2257 groupInfo = AccGroupInfo::CreateGroupInfo(this); 2258 if (groupInfo) { 2259 if (!mCachedFields) { 2260 mCachedFields = new AccAttributes(); 2261 } 2262 2263 mCachedFields->SetAttribute(CacheKey::GroupInfo, groupInfo); 2264 } 2265 2266 return groupInfo; 2267 } 2268 2269 void RemoteAccessible::InvalidateGroupInfo() { 2270 if (mCachedFields) { 2271 mCachedFields->Remove(CacheKey::GroupInfo); 2272 } 2273 } 2274 2275 void RemoteAccessible::GetPositionAndSetSize(int32_t* aPosInSet, 2276 int32_t* aSetSize) { 2277 // Note: Required domains come from requirements of RelationByType. 2278 if (RequestDomainsIfInactive(CacheDomain::Relations | CacheDomain::Value | 2279 CacheDomain::DOMNodeIDAndClass | 2280 CacheDomain::GroupInfo)) { 2281 return; 2282 } 2283 2284 if (IsHTMLRadioButton()) { 2285 *aSetSize = 0; 2286 Relation rel = RelationByType(RelationType::MEMBER_OF); 2287 while (Accessible* radio = rel.Next()) { 2288 ++*aSetSize; 2289 if (radio == this) { 2290 *aPosInSet = *aSetSize; 2291 } 2292 } 2293 return; 2294 } 2295 2296 Accessible::GetPositionAndSetSize(aPosInSet, aSetSize); 2297 } 2298 2299 bool RemoteAccessible::HasPrimaryAction() const { 2300 if (RequestDomainsIfInactive(CacheDomain::Actions)) { 2301 return false; 2302 } 2303 return mCachedFields && mCachedFields->HasAttribute(CacheKey::PrimaryAction); 2304 } 2305 2306 void RemoteAccessible::TakeFocus() const { 2307 (void)mDoc->SendTakeFocus(mID); 2308 auto* bp = static_cast<dom::BrowserParent*>(mDoc->Manager()); 2309 MOZ_ASSERT(bp); 2310 if (nsFocusManager::GetFocusedElementStatic() == bp->GetOwnerElement()) { 2311 // This remote document tree is already focused. We don't need to do 2312 // anything else. 2313 return; 2314 } 2315 // Otherwise, we need to focus the <browser> or <iframe> element embedding the 2316 // remote document in the parent process. If `this` is in an OOP iframe, we 2317 // first need to focus the embedder iframe (and any ancestor OOP iframes). If 2318 // the parent process embedder element were already focused, that would happen 2319 // automatically, but it isn't. We can't simply focus the parent process 2320 // embedder element before calling mDoc->SendTakeFocus because that would 2321 // cause the remote document to restore focus to the last focused element, 2322 // which we don't want. 2323 DocAccessibleParent* embeddedDoc = mDoc; 2324 Accessible* embedder = mDoc->Parent(); 2325 while (embedder) { 2326 MOZ_ASSERT(embedder->IsOuterDoc()); 2327 RemoteAccessible* embedderRemote = embedder->AsRemote(); 2328 if (!embedderRemote) { 2329 // This is the element in the parent process which embeds the remote 2330 // document. 2331 embedder->TakeFocus(); 2332 break; 2333 } 2334 // This is a remote <iframe>. 2335 if (embeddedDoc->IsTopLevelInContentProcess()) { 2336 // We only need to focus OOP iframes because these are where we cross 2337 // process boundaries. 2338 (void)embedderRemote->mDoc->SendTakeFocus(embedderRemote->mID); 2339 } 2340 embeddedDoc = embedderRemote->mDoc; 2341 embedder = embeddedDoc->Parent(); 2342 } 2343 } 2344 2345 void RemoteAccessible::ScrollTo(uint32_t aHow) const { 2346 (void)mDoc->SendScrollTo(mID, aHow); 2347 } 2348 2349 //////////////////////////////////////////////////////////////////////////////// 2350 // SelectAccessible 2351 2352 void RemoteAccessible::SelectedItems(nsTArray<Accessible*>* aItems) { 2353 if (RequestDomainsIfInactive(kNecessaryStateDomains)) { 2354 return; 2355 } 2356 Pivot p = Pivot(this); 2357 PivotStateRule rule(states::SELECTED); 2358 for (Accessible* selected = p.First(rule); selected; 2359 selected = p.Next(selected, rule)) { 2360 aItems->AppendElement(selected); 2361 } 2362 } 2363 2364 uint32_t RemoteAccessible::SelectedItemCount() { 2365 if (RequestDomainsIfInactive(kNecessaryStateDomains)) { 2366 return 0; 2367 } 2368 uint32_t count = 0; 2369 Pivot p = Pivot(this); 2370 PivotStateRule rule(states::SELECTED); 2371 for (Accessible* selected = p.First(rule); selected; 2372 selected = p.Next(selected, rule)) { 2373 count++; 2374 } 2375 2376 return count; 2377 } 2378 2379 Accessible* RemoteAccessible::GetSelectedItem(uint32_t aIndex) { 2380 if (RequestDomainsIfInactive(kNecessaryStateDomains)) { 2381 return nullptr; 2382 } 2383 uint32_t index = 0; 2384 Accessible* selected = nullptr; 2385 Pivot p = Pivot(this); 2386 PivotStateRule rule(states::SELECTED); 2387 for (selected = p.First(rule); selected && index < aIndex; 2388 selected = p.Next(selected, rule)) { 2389 index++; 2390 } 2391 2392 return selected; 2393 } 2394 2395 bool RemoteAccessible::IsItemSelected(uint32_t aIndex) { 2396 if (RequestDomainsIfInactive(kNecessaryStateDomains)) { 2397 return false; 2398 } 2399 uint32_t index = 0; 2400 Accessible* selectable = nullptr; 2401 Pivot p = Pivot(this); 2402 PivotStateRule rule(states::SELECTABLE); 2403 for (selectable = p.First(rule); selectable && index < aIndex; 2404 selectable = p.Next(selectable, rule)) { 2405 index++; 2406 } 2407 2408 return selectable && selectable->State() & states::SELECTED; 2409 } 2410 2411 bool RemoteAccessible::AddItemToSelection(uint32_t aIndex) { 2412 if (RequestDomainsIfInactive(kNecessaryStateDomains)) { 2413 return false; 2414 } 2415 uint32_t index = 0; 2416 Accessible* selectable = nullptr; 2417 Pivot p = Pivot(this); 2418 PivotStateRule rule(states::SELECTABLE); 2419 for (selectable = p.First(rule); selectable && index < aIndex; 2420 selectable = p.Next(selectable, rule)) { 2421 index++; 2422 } 2423 2424 if (selectable) selectable->SetSelected(true); 2425 2426 return static_cast<bool>(selectable); 2427 } 2428 2429 bool RemoteAccessible::RemoveItemFromSelection(uint32_t aIndex) { 2430 if (RequestDomainsIfInactive(kNecessaryStateDomains)) { 2431 return false; 2432 } 2433 uint32_t index = 0; 2434 Accessible* selectable = nullptr; 2435 Pivot p = Pivot(this); 2436 PivotStateRule rule(states::SELECTABLE); 2437 for (selectable = p.First(rule); selectable && index < aIndex; 2438 selectable = p.Next(selectable, rule)) { 2439 index++; 2440 } 2441 2442 if (selectable) selectable->SetSelected(false); 2443 2444 return static_cast<bool>(selectable); 2445 } 2446 2447 bool RemoteAccessible::SelectAll() { 2448 if (RequestDomainsIfInactive(kNecessaryStateDomains)) { 2449 return false; 2450 } 2451 if ((State() & states::MULTISELECTABLE) == 0) { 2452 return false; 2453 } 2454 2455 bool success = false; 2456 Accessible* selectable = nullptr; 2457 Pivot p = Pivot(this); 2458 PivotStateRule rule(states::SELECTABLE); 2459 for (selectable = p.First(rule); selectable; 2460 selectable = p.Next(selectable, rule)) { 2461 success = true; 2462 selectable->SetSelected(true); 2463 } 2464 return success; 2465 } 2466 2467 bool RemoteAccessible::UnselectAll() { 2468 if (RequestDomainsIfInactive(kNecessaryStateDomains)) { 2469 return false; 2470 } 2471 if ((State() & states::MULTISELECTABLE) == 0) { 2472 return false; 2473 } 2474 2475 bool success = false; 2476 Accessible* selectable = nullptr; 2477 Pivot p = Pivot(this); 2478 PivotStateRule rule(states::SELECTABLE); 2479 for (selectable = p.First(rule); selectable; 2480 selectable = p.Next(selectable, rule)) { 2481 success = true; 2482 selectable->SetSelected(false); 2483 } 2484 return success; 2485 } 2486 2487 void RemoteAccessible::TakeSelection() { (void)mDoc->SendTakeSelection(mID); } 2488 2489 void RemoteAccessible::SetSelected(bool aSelect) { 2490 (void)mDoc->SendSetSelected(mID, aSelect); 2491 } 2492 2493 TableAccessible* RemoteAccessible::AsTable() { 2494 if (IsTable()) { 2495 return CachedTableAccessible::GetFrom(this); 2496 } 2497 return nullptr; 2498 } 2499 2500 TableCellAccessible* RemoteAccessible::AsTableCell() { 2501 if (IsTableCell()) { 2502 return CachedTableCellAccessible::GetFrom(this); 2503 } 2504 return nullptr; 2505 } 2506 2507 bool RemoteAccessible::TableIsProbablyForLayout() { 2508 if (RequestDomainsIfInactive(CacheDomain::Table)) { 2509 return false; 2510 } 2511 if (mCachedFields) { 2512 if (auto layoutGuess = 2513 mCachedFields->GetAttribute<bool>(CacheKey::TableLayoutGuess)) { 2514 return *layoutGuess; 2515 } 2516 } 2517 return false; 2518 } 2519 2520 nsTArray<int32_t>& RemoteAccessible::GetCachedHyperTextOffsets() { 2521 if (mCachedFields) { 2522 if (auto offsets = mCachedFields->GetMutableAttribute<nsTArray<int32_t>>( 2523 CacheKey::HyperTextOffsets)) { 2524 return *offsets; 2525 } 2526 } 2527 nsTArray<int32_t> newOffsets; 2528 if (!mCachedFields) { 2529 mCachedFields = new AccAttributes(); 2530 } 2531 mCachedFields->SetAttribute(CacheKey::HyperTextOffsets, 2532 std::move(newOffsets)); 2533 return *mCachedFields->GetMutableAttribute<nsTArray<int32_t>>( 2534 CacheKey::HyperTextOffsets); 2535 } 2536 2537 Maybe<int32_t> RemoteAccessible::GetIntARIAAttr(nsAtom* aAttrName) const { 2538 if (RequestDomainsIfInactive(CacheDomain::ARIA)) { 2539 return Nothing(); 2540 } 2541 if (auto attrs = GetCachedARIAAttributes()) { 2542 if (auto val = attrs->GetAttribute<int32_t>(aAttrName)) { 2543 return val; 2544 } 2545 } 2546 return Nothing(); 2547 } 2548 2549 bool RemoteAccessible::GetStringARIAAttr(nsAtom* aAttrName, 2550 nsAString& aAttrValue) const { 2551 if (RequestDomainsIfInactive(CacheDomain::ARIA)) { 2552 return false; 2553 } 2554 2555 if (aAttrName == nsGkAtoms::role) { 2556 if (mCachedFields->GetAttribute(CacheKey::ARIARole, aAttrValue)) { 2557 // Unknown, or multiple roles. 2558 return true; 2559 } 2560 2561 if (const nsRoleMapEntry* roleMap = ARIARoleMap()) { 2562 if (roleMap->roleAtom != nsGkAtoms::_empty) { 2563 // Non-empty rolemap, stringify it and return true. 2564 roleMap->roleAtom->ToString(aAttrValue); 2565 return true; 2566 } 2567 } 2568 } 2569 2570 if (auto attrs = GetCachedARIAAttributes()) { 2571 return attrs->GetAttribute(aAttrName, aAttrValue); 2572 } 2573 2574 return false; 2575 } 2576 2577 bool RemoteAccessible::ARIAAttrValueIs(nsAtom* aAttrName, 2578 nsAtom* aAttrValue) const { 2579 if (RequestDomainsIfInactive(CacheDomain::ARIA)) { 2580 return false; 2581 } 2582 2583 if (aAttrName == nsGkAtoms::role) { 2584 nsAutoString roleStr; 2585 if (mCachedFields->GetAttribute(CacheKey::ARIARole, roleStr)) { 2586 return aAttrValue->Equals(roleStr); 2587 } 2588 2589 if (const nsRoleMapEntry* roleMap = ARIARoleMap()) { 2590 return roleMap->roleAtom == aAttrValue; 2591 } 2592 } 2593 2594 if (auto attrs = GetCachedARIAAttributes()) { 2595 if (auto val = attrs->GetAttribute<RefPtr<nsAtom>>(aAttrName)) { 2596 return *val == aAttrValue; 2597 } 2598 } 2599 2600 return false; 2601 } 2602 2603 bool RemoteAccessible::HasARIAAttr(nsAtom* aAttrName) const { 2604 if (RequestDomainsIfInactive(CacheDomain::ARIA)) { 2605 return false; 2606 } 2607 2608 if (aAttrName == nsGkAtoms::role) { 2609 if (const nsRoleMapEntry* roleMap = ARIARoleMap()) { 2610 if (roleMap->roleAtom != nsGkAtoms::_empty) { 2611 return true; 2612 } 2613 } 2614 2615 return mCachedFields->HasAttribute(CacheKey::ARIARole); 2616 } 2617 2618 if (auto attrs = GetCachedARIAAttributes()) { 2619 return attrs->HasAttribute(aAttrName); 2620 } 2621 2622 return false; 2623 } 2624 2625 void RemoteAccessible::Language(nsAString& aLocale) { 2626 if (RequestDomainsIfInactive(CacheDomain::Text)) { 2627 return; 2628 } 2629 2630 if (IsHyperText() || IsText()) { 2631 for (RemoteAccessible* parent = this; parent; 2632 parent = parent->RemoteParent()) { 2633 // Climb up the tree to find where the nearest language attribute is. 2634 if (RefPtr<const AccAttributes> attrs = 2635 parent->GetCachedTextAttributes()) { 2636 if (attrs->GetAttribute(nsGkAtoms::language, aLocale)) { 2637 return; 2638 } 2639 } 2640 } 2641 } else if (mCachedFields) { 2642 mCachedFields->GetAttribute(CacheKey::Language, aLocale); 2643 } 2644 } 2645 2646 void RemoteAccessible::ReplaceText(const nsAString& aText) { 2647 (void)mDoc->SendReplaceText(mID, aText); 2648 } 2649 2650 void RemoteAccessible::InsertText(const nsAString& aText, int32_t aPosition) { 2651 (void)mDoc->SendInsertText(mID, aText, aPosition); 2652 } 2653 2654 void RemoteAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) { 2655 (void)mDoc->SendCopyText(mID, aStartPos, aEndPos); 2656 } 2657 2658 void RemoteAccessible::CutText(int32_t aStartPos, int32_t aEndPos) { 2659 (void)mDoc->SendCutText(mID, aStartPos, aEndPos); 2660 } 2661 2662 void RemoteAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) { 2663 (void)mDoc->SendDeleteText(mID, aStartPos, aEndPos); 2664 } 2665 2666 void RemoteAccessible::PasteText(int32_t aPosition) { 2667 (void)mDoc->SendPasteText(mID, aPosition); 2668 } 2669 2670 bool RemoteAccessible::HasCustomActions() const { 2671 if (RequestDomainsIfInactive(CacheDomain::ARIA) || !mCachedFields) { 2672 return false; 2673 } 2674 auto hasActions = mCachedFields->GetAttribute<bool>(CacheKey::HasActions); 2675 return hasActions && *hasActions; 2676 } 2677 2678 size_t RemoteAccessible::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { 2679 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 2680 } 2681 2682 size_t RemoteAccessible::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { 2683 size_t size = 0; 2684 2685 // Count attributes. 2686 if (mCachedFields) { 2687 size += mCachedFields->SizeOfIncludingThis(aMallocSizeOf); 2688 } 2689 2690 // We don't recurse into mChildren because they're already counted in their 2691 // document's mAccessibles. 2692 size += mChildren.ShallowSizeOfExcludingThis(aMallocSizeOf); 2693 2694 return size; 2695 } 2696 2697 } // namespace a11y 2698 } // namespace mozilla