Bug 1921551 - React to sync sign in flow correctly r=android-reviewers,matt-tighe
[gecko.git] / accessible / ipc / RemoteAccessible.cpp
blob8c32baa75e1574abb93657d5e2af864a56cbb5b1
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/. */
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 "mozilla/Unused.h"
22 #include "nsAccUtils.h"
23 #include "nsFocusManager.h"
24 #include "nsTextEquivUtils.h"
25 #include "Pivot.h"
26 #include "Relation.h"
27 #include "mozilla/a11y/RelationType.h"
28 #include "xpcAccessibleDocument.h"
30 #ifdef A11Y_LOG
31 # include "Logging.h"
32 # define VERIFY_CACHE(domain) \
33 if (logging::IsEnabled(logging::eCache)) { \
34 Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \
36 #else
37 # define VERIFY_CACHE(domain) \
38 do { \
39 } while (0)
41 #endif
43 namespace mozilla {
44 namespace a11y {
46 // Domain sets we need commonly for functions in this file.
47 static constexpr uint64_t kNecessaryBoundsDomains =
48 CacheDomain::Bounds | CacheDomain::TransformMatrix | CacheDomain::Style |
49 CacheDomain::ScrollPosition;
50 static constexpr uint64_t kNecessaryStateDomains =
51 CacheDomain::State | CacheDomain::Viewport;
53 void RemoteAccessible::Shutdown() {
54 MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
55 xpcAccessibleDocument* xpcDoc =
56 GetAccService()->GetCachedXPCDocument(Document());
57 if (xpcDoc) {
58 xpcDoc->NotifyOfShutdown(static_cast<RemoteAccessible*>(this));
61 if (IsTable() || IsTableCell()) {
62 CachedTableAccessible::Invalidate(this);
65 // Remove this acc's relation map from the doc's map of
66 // reverse relations. Prune forward relations associated with this
67 // acc's reverse relations. This also removes the acc's map of reverse
68 // rels from the mDoc's mReverseRelations.
69 PruneRelationsOnShutdown();
71 // XXX Ideally this wouldn't be necessary, but it seems OuterDoc
72 // accessibles can be destroyed before the doc they own.
73 uint32_t childCount = mChildren.Length();
74 if (!IsOuterDoc()) {
75 for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown();
76 } else {
77 if (childCount > 1) {
78 MOZ_CRASH("outer doc has too many documents!");
79 } else if (childCount == 1) {
80 mChildren[0]->AsDoc()->Unbind();
84 mChildren.Clear();
85 ProxyDestroyed(static_cast<RemoteAccessible*>(this));
86 // mDoc owns this RemoteAccessible, so RemoveAccessible deletes this.
87 mDoc->RemoveAccessible(static_cast<RemoteAccessible*>(this));
90 void RemoteAccessible::SetChildDoc(DocAccessibleParent* aChildDoc) {
91 MOZ_ASSERT(aChildDoc);
92 MOZ_ASSERT(mChildren.Length() == 0);
93 mChildren.AppendElement(aChildDoc);
94 aChildDoc->mIndexInParent = 0;
97 void RemoteAccessible::ClearChildDoc(DocAccessibleParent* aChildDoc) {
98 MOZ_ASSERT(aChildDoc);
99 // This is possible if we're replacing one document with another: Doc 1
100 // has not had a chance to remove itself, but was already replaced by Doc 2
101 // in SetChildDoc(). This could result in two subsequent calls to
102 // ClearChildDoc() even though mChildren.Length() == 1.
103 MOZ_ASSERT(mChildren.Length() <= 1);
104 mChildren.RemoveElement(aChildDoc);
107 uint32_t RemoteAccessible::EmbeddedChildCount() {
108 size_t count = 0, kids = mChildren.Length();
109 for (size_t i = 0; i < kids; i++) {
110 if (mChildren[i]->IsEmbeddedObject()) {
111 count++;
115 return count;
118 int32_t RemoteAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
119 size_t index = 0, kids = mChildren.Length();
120 for (size_t i = 0; i < kids; i++) {
121 if (mChildren[i]->IsEmbeddedObject()) {
122 if (mChildren[i] == aChild) {
123 return index;
126 index++;
130 return -1;
133 Accessible* RemoteAccessible::EmbeddedChildAt(uint32_t aChildIdx) {
134 size_t index = 0, kids = mChildren.Length();
135 for (size_t i = 0; i < kids; i++) {
136 if (!mChildren[i]->IsEmbeddedObject()) {
137 continue;
140 if (index == aChildIdx) {
141 return mChildren[i];
144 index++;
147 return nullptr;
150 LocalAccessible* RemoteAccessible::OuterDocOfRemoteBrowser() const {
151 auto tab = static_cast<dom::BrowserParent*>(mDoc->Manager());
152 dom::Element* frame = tab->GetOwnerElement();
153 NS_ASSERTION(frame, "why isn't the tab in a frame!");
154 if (!frame) return nullptr;
156 DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
158 return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr;
161 void RemoteAccessible::SetParent(RemoteAccessible* aParent) {
162 MOZ_ASSERT(!IsDoc() || !AsDoc()->IsTopLevel(),
163 "Top level doc should not have remote parent");
164 MOZ_ASSERT(!IsDoc() || !aParent || !aParent->IsDoc(),
165 "Doc can't be direct parent of another doc");
166 MOZ_ASSERT(!IsDoc() || !aParent || aParent->IsOuterDoc(),
167 "Doc's parent must be OuterDoc");
168 mParent = aParent;
169 if (!aParent) {
170 mIndexInParent = -1;
174 RemoteAccessible* RemoteAccessible::RemoteParent() const {
175 MOZ_ASSERT(!IsDoc() || !AsDoc()->IsTopLevel() || !mParent,
176 "Top level doc should not have RemoteParent");
177 MOZ_ASSERT(!IsDoc() || !mParent || mParent->mDoc != mDoc,
178 "Doc's parent should be in another doc");
179 MOZ_ASSERT(!IsDoc() || !mParent || mParent->IsOuterDoc(),
180 "Doc's parent should be in another doc");
181 return mParent;
184 void RemoteAccessible::ApplyCache(CacheUpdateType aUpdateType,
185 AccAttributes* aFields) {
186 if (!aFields) {
187 MOZ_ASSERT_UNREACHABLE("ApplyCache called with aFields == null");
188 return;
191 const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields);
192 if (auto maybeViewportCache =
193 aFields->GetAttribute<nsTArray<uint64_t>>(CacheKey::Viewport)) {
194 // Updating the viewport cache means the offscreen state of this
195 // document's accessibles has changed. Update the HashSet we use for
196 // checking offscreen state here.
197 MOZ_ASSERT(IsDoc(),
198 "Fetched the viewport cache from a non-doc accessible?");
199 AsDoc()->mOnScreenAccessibles.Clear();
200 for (auto id : *maybeViewportCache) {
201 AsDoc()->mOnScreenAccessibles.Insert(id);
205 if (aUpdateType == CacheUpdateType::Initial) {
206 mCachedFields = aFields;
207 } else {
208 if (!mCachedFields) {
209 // The fields cache can be uninitialized if there were no cache-worthy
210 // fields in the initial cache push.
211 // We don't do a simple assign because we don't want to store the
212 // DeleteEntry entries.
213 mCachedFields = new AccAttributes();
215 mCachedFields->Update(aFields);
218 if (IsTextLeaf()) {
219 RemoteAccessible* parent = RemoteParent();
220 if (parent && parent->IsHyperText()) {
221 parent->InvalidateCachedHyperTextOffsets();
225 PostProcessRelations(relUpdatesNeeded);
228 ENameValueFlag RemoteAccessible::Name(nsString& aName) const {
229 if (RequestDomainsIfInactive(CacheDomain::NameAndDescription |
230 CacheDomain::Text)) {
231 aName.SetIsVoid(true);
232 return eNameOK;
235 ENameValueFlag nameFlag = eNameOK;
236 if (mCachedFields) {
237 if (IsText()) {
238 mCachedFields->GetAttribute(CacheKey::Text, aName);
239 return eNameOK;
241 auto cachedNameFlag =
242 mCachedFields->GetAttribute<int32_t>(CacheKey::NameValueFlag);
243 if (cachedNameFlag) {
244 nameFlag = static_cast<ENameValueFlag>(*cachedNameFlag);
246 if (mCachedFields->GetAttribute(CacheKey::Name, aName)) {
247 VERIFY_CACHE(CacheDomain::NameAndDescription);
248 return nameFlag;
252 MOZ_ASSERT(aName.IsEmpty());
253 aName.SetIsVoid(true);
254 return nameFlag;
257 void RemoteAccessible::Description(nsString& aDescription) const {
258 if (RequestDomainsIfInactive(CacheDomain::NameAndDescription)) {
259 return;
262 if (mCachedFields) {
263 mCachedFields->GetAttribute(CacheKey::Description, aDescription);
264 VERIFY_CACHE(CacheDomain::NameAndDescription);
268 void RemoteAccessible::Value(nsString& aValue) const {
269 if (RequestDomainsIfInactive(
270 CacheDomain::Value | // CurValue, etc.
271 CacheDomain::Actions | // ActionAncestor (HasPrimaryAction)
272 CacheDomain::State | // GetSelectedItem
273 CacheDomain::Viewport // GetSelectedItem
274 )) {
275 return;
278 if (mCachedFields) {
279 if (mCachedFields->HasAttribute(CacheKey::TextValue)) {
280 mCachedFields->GetAttribute(CacheKey::TextValue, aValue);
281 VERIFY_CACHE(CacheDomain::Value);
282 return;
285 if (HasNumericValue()) {
286 double checkValue = CurValue();
287 if (!std::isnan(checkValue)) {
288 aValue.AppendFloat(checkValue);
290 return;
293 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
294 // Value of textbox is a textified subtree.
295 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) {
296 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
297 return;
300 if (IsCombobox()) {
301 // For combo boxes, rely on selection state to determine the value.
302 const Accessible* option =
303 const_cast<RemoteAccessible*>(this)->GetSelectedItem(0);
304 if (option) {
305 option->Name(aValue);
306 } else {
307 // If no selected item, determine the value from descendant elements.
308 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
310 return;
313 if (IsTextLeaf() || IsImage()) {
314 if (const Accessible* actionAcc = ActionAncestor()) {
315 if (const_cast<Accessible*>(actionAcc)->State() & states::LINKED) {
316 // Text and image descendants of links expose the link URL as the
317 // value.
318 return actionAcc->Value(aValue);
325 double RemoteAccessible::CurValue() const {
326 if (RequestDomainsIfInactive(CacheDomain::Value)) {
327 return UnspecifiedNaN<double>();
330 if (mCachedFields) {
331 if (auto value =
332 mCachedFields->GetAttribute<double>(CacheKey::NumericValue)) {
333 VERIFY_CACHE(CacheDomain::Value);
334 return *value;
338 return UnspecifiedNaN<double>();
341 double RemoteAccessible::MinValue() const {
342 if (RequestDomainsIfInactive(CacheDomain::Value)) {
343 return UnspecifiedNaN<double>();
346 if (mCachedFields) {
347 if (auto min = mCachedFields->GetAttribute<double>(CacheKey::MinValue)) {
348 VERIFY_CACHE(CacheDomain::Value);
349 return *min;
353 return UnspecifiedNaN<double>();
356 double RemoteAccessible::MaxValue() const {
357 if (RequestDomainsIfInactive(CacheDomain::Value)) {
358 return UnspecifiedNaN<double>();
361 if (mCachedFields) {
362 if (auto max = mCachedFields->GetAttribute<double>(CacheKey::MaxValue)) {
363 VERIFY_CACHE(CacheDomain::Value);
364 return *max;
368 return UnspecifiedNaN<double>();
371 double RemoteAccessible::Step() const {
372 if (RequestDomainsIfInactive(CacheDomain::Value)) {
373 return UnspecifiedNaN<double>();
376 if (mCachedFields) {
377 if (auto step = mCachedFields->GetAttribute<double>(CacheKey::Step)) {
378 VERIFY_CACHE(CacheDomain::Value);
379 return *step;
383 return UnspecifiedNaN<double>();
386 bool RemoteAccessible::SetCurValue(double aValue) {
387 if (RequestDomainsIfInactive(CacheDomain::Value | // MinValue, MaxValue
388 CacheDomain::State | // State
389 CacheDomain::Viewport // State
390 )) {
391 return false;
394 if (!HasNumericValue() || IsProgress()) {
395 return false;
398 const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
399 if (State() & kValueCannotChange) {
400 return false;
403 double checkValue = MinValue();
404 if (!std::isnan(checkValue) && aValue < checkValue) {
405 return false;
408 checkValue = MaxValue();
409 if (!std::isnan(checkValue) && aValue > checkValue) {
410 return false;
413 Unused << mDoc->SendSetCurValue(mID, aValue);
414 return true;
417 bool RemoteAccessible::ContainsPoint(int32_t aX, int32_t aY) {
418 ASSERT_DOMAINS_ACTIVE(CacheDomain::TextBounds | // GetCachedTextLines
419 kNecessaryBoundsDomains);
421 if (!BoundsWithOffset(Nothing(), true).Contains(aX, aY)) {
422 return false;
424 if (!IsTextLeaf()) {
425 if (IsImage() || IsImageMap() || !HasChildren() ||
426 RefPtr{DisplayStyle()} != nsGkAtoms::inlinevalue) {
427 // This isn't an inline element that might contain text, so we don't need
428 // to walk lines. It's enough that our rect contains the point.
429 return true;
431 // Non-image inline elements with children can wrap across lines just like
432 // text leaves; see below.
433 // Walk the children, which will walk the lines of text in any text leaves.
434 uint32_t count = ChildCount();
435 for (uint32_t c = 0; c < count; ++c) {
436 RemoteAccessible* child = RemoteChildAt(c);
437 if (child->Role() == roles::TEXT_CONTAINER && child->IsClipped()) {
438 // There is a clipped child. This is a candidate for fuzzy hit testing.
439 // See RemoteAccessible::DoFuzzyHittesting.
440 return true;
442 if (child->ContainsPoint(aX, aY)) {
443 return true;
446 // None of our descendants contain the point, so nor do we.
447 return false;
449 // This is a text leaf. The text might wrap across lines, which means our
450 // rect might cover a wider area than the actual text. For example, if the
451 // text begins in the middle of the first line and wraps on to the second,
452 // the rect will cover the start of the first line and the end of the second.
453 auto lines = GetCachedTextLines();
454 if (!lines) {
455 // This means the text is empty or occupies a single line (but does not
456 // begin the line). In that case, the Bounds check above is sufficient,
457 // since there's only one rect.
458 return true;
460 uint32_t length = lines->Length();
461 MOZ_ASSERT(length > 0,
462 "Line starts shouldn't be in cache if there aren't any");
463 if (length == 0 || (length == 1 && (*lines)[0] == 0)) {
464 // This means the text begins and occupies a single line. Again, the Bounds
465 // check above is sufficient.
466 return true;
468 // Walk the lines of the text. Even if this text doesn't start at the
469 // beginning of a line (i.e. lines[0] > 0), we always want to consider its
470 // first line.
471 int32_t lineStart = 0;
472 for (uint32_t index = 0; index <= length; ++index) {
473 int32_t lineEnd;
474 if (index < length) {
475 int32_t nextLineStart = (*lines)[index];
476 if (nextLineStart == 0) {
477 // This Accessible starts at the beginning of a line. Here, we always
478 // treat 0 as the first line start anyway.
479 MOZ_ASSERT(index == 0);
480 continue;
482 lineEnd = nextLineStart - 1;
483 } else {
484 // This is the last line.
485 lineEnd = static_cast<int32_t>(nsAccUtils::TextLength(this)) - 1;
487 MOZ_ASSERT(lineEnd >= lineStart);
488 nsRect lineRect = GetCachedCharRect(lineStart);
489 if (lineEnd > lineStart) {
490 lineRect.UnionRect(lineRect, GetCachedCharRect(lineEnd));
492 if (BoundsWithOffset(Some(lineRect), true).Contains(aX, aY)) {
493 return true;
495 lineStart = lineEnd + 1;
497 return false;
500 RemoteAccessible* RemoteAccessible::DoFuzzyHittesting() {
501 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds);
503 uint32_t childCount = ChildCount();
504 if (!childCount) {
505 return nullptr;
507 // Check if this match has a clipped child.
508 // This usually indicates invisible text, and we're
509 // interested in returning the inner text content
510 // even if it doesn't contain the point we're hittesting.
511 RemoteAccessible* clippedContainer = nullptr;
512 for (uint32_t i = 0; i < childCount; i++) {
513 RemoteAccessible* child = RemoteChildAt(i);
514 if (child->Role() == roles::TEXT_CONTAINER) {
515 if (child->IsClipped()) {
516 clippedContainer = child;
517 break;
521 // If we found a clipped container, descend it in search of
522 // meaningful text leaves. Ignore non-text-leaf/text-container
523 // siblings.
524 RemoteAccessible* container = clippedContainer;
525 while (container) {
526 RemoteAccessible* textLeaf = nullptr;
527 bool continueSearch = false;
528 childCount = container->ChildCount();
529 for (uint32_t i = 0; i < childCount; i++) {
530 RemoteAccessible* child = container->RemoteChildAt(i);
531 if (child->Role() == roles::TEXT_CONTAINER) {
532 container = child;
533 continueSearch = true;
534 break;
536 if (child->IsTextLeaf()) {
537 textLeaf = child;
538 // Don't break here -- it's possible a text container
539 // exists as another sibling, and we should descend as
540 // deep as possible.
543 if (textLeaf) {
544 return textLeaf;
546 if (!continueSearch) {
547 // We didn't find anything useful in this set of siblings.
548 // Don't keep searching
549 break;
552 return nullptr;
555 Accessible* RemoteAccessible::ChildAtPoint(
556 int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) {
557 if (RequestDomainsIfInactive(
558 kNecessaryBoundsDomains |
559 CacheDomain::TextBounds // GetCachedTextLines (via ContainsPoint)
560 )) {
561 return nullptr;
564 // Elements that are partially on-screen should have their bounds masked by
565 // their containing scroll area so hittesting yields results that are
566 // consistent with the content's visual representation. Pass this value to
567 // bounds calculation functions to indicate that we're hittesting.
568 const bool hitTesting = true;
570 if (IsOuterDoc() && aWhichChild == EWhichChildAtPoint::DirectChild) {
571 // This is an iframe, which is as deep as the viewport cache goes. The
572 // caller wants a direct child, which can only be the embedded document.
573 if (BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
574 return RemoteFirstChild();
576 return nullptr;
579 RemoteAccessible* lastMatch = nullptr;
580 // If `this` is a document, use its viewport cache instead of
581 // the cache of its parent document.
582 if (DocAccessibleParent* doc = IsDoc() ? AsDoc() : mDoc) {
583 if (!doc->mCachedFields) {
584 // A client call might arrive after we've constructed doc but before we
585 // get a cache push for it.
586 return nullptr;
588 if (auto maybeViewportCache =
589 doc->mCachedFields->GetAttribute<nsTArray<uint64_t>>(
590 CacheKey::Viewport)) {
591 // The retrieved viewport cache contains acc IDs in hittesting order.
592 // That is, items earlier in the list have z-indexes that are larger than
593 // those later in the list. If you were to build a tree by z-index, where
594 // chilren have larger z indices than their parents, iterating this list
595 // is essentially a postorder tree traversal.
596 const nsTArray<uint64_t>& viewportCache = *maybeViewportCache;
598 for (auto id : viewportCache) {
599 RemoteAccessible* acc = doc->GetAccessible(id);
600 if (!acc) {
601 // This can happen if the acc died in between
602 // pushing the viewport cache and doing this hittest
603 continue;
606 if (acc->IsOuterDoc() &&
607 aWhichChild == EWhichChildAtPoint::DeepestChild &&
608 acc->BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
609 // acc is an iframe, which is as deep as the viewport cache goes. This
610 // iframe contains the requested point.
611 RemoteAccessible* innerDoc = acc->RemoteFirstChild();
612 if (innerDoc) {
613 MOZ_ASSERT(innerDoc->IsDoc());
614 // Search the embedded document's viewport cache so we return the
615 // deepest descendant in that embedded document.
616 Accessible* deepestAcc = innerDoc->ChildAtPoint(
617 aX, aY, EWhichChildAtPoint::DeepestChild);
618 MOZ_ASSERT(!deepestAcc || deepestAcc->IsRemote());
619 lastMatch = deepestAcc ? deepestAcc->AsRemote() : nullptr;
620 break;
622 // If there is no embedded document, the iframe itself is the deepest
623 // descendant.
624 lastMatch = acc;
625 break;
628 if (acc == this) {
629 MOZ_ASSERT(!acc->IsOuterDoc());
630 // Even though we're searching from the doc's cache
631 // this call shouldn't pass the boundary defined by
632 // the acc this call originated on. If we hit `this`,
633 // return our most recent match.
634 if (!lastMatch &&
635 BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
636 // If we haven't found a match, but `this` contains the point we're
637 // looking for, set it as our temp last match so we can
638 // (potentially) do fuzzy hittesting on it below.
639 lastMatch = acc;
641 break;
644 if (acc->ContainsPoint(aX, aY)) {
645 // Because our rects are in hittesting order, the
646 // first match we encounter is guaranteed to be the
647 // deepest match.
648 lastMatch = acc;
649 break;
652 if (lastMatch) {
653 RemoteAccessible* fuzzyMatch = lastMatch->DoFuzzyHittesting();
654 lastMatch = fuzzyMatch ? fuzzyMatch : lastMatch;
659 if (aWhichChild == EWhichChildAtPoint::DirectChild && lastMatch) {
660 // lastMatch is the deepest match. Walk up to the direct child of this.
661 RemoteAccessible* parent = lastMatch->RemoteParent();
662 for (;;) {
663 if (parent == this) {
664 break;
666 if (!parent || parent->IsDoc()) {
667 // `this` is not an ancestor of lastMatch. Ignore lastMatch.
668 lastMatch = nullptr;
669 break;
671 lastMatch = parent;
672 parent = parent->RemoteParent();
674 } else if (aWhichChild == EWhichChildAtPoint::DeepestChild && lastMatch &&
675 !IsDoc() && !IsAncestorOf(lastMatch)) {
676 // If we end up with a match that is not in the ancestor chain
677 // of the accessible this call originated on, we should ignore it.
678 // This can happen when the aX, aY given are outside `this`.
679 lastMatch = nullptr;
682 if (!lastMatch && BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
683 // Even though the hit target isn't inside `this`, the point is still
684 // within our bounds, so fall back to `this`.
685 return this;
688 return lastMatch;
691 Maybe<nsRect> RemoteAccessible::RetrieveCachedBounds() const {
692 if (!mCachedFields) {
693 return Nothing();
696 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds);
697 Maybe<const nsTArray<int32_t>&> maybeArray =
698 mCachedFields->GetAttribute<nsTArray<int32_t>>(
699 CacheKey::ParentRelativeBounds);
700 if (maybeArray) {
701 const nsTArray<int32_t>& relativeBoundsArr = *maybeArray;
702 MOZ_ASSERT(relativeBoundsArr.Length() == 4,
703 "Incorrectly sized bounds array");
704 nsRect relativeBoundsRect(relativeBoundsArr[0], relativeBoundsArr[1],
705 relativeBoundsArr[2], relativeBoundsArr[3]);
706 return Some(relativeBoundsRect);
709 return Nothing();
712 void RemoteAccessible::ApplyCrossDocOffset(nsRect& aBounds) const {
713 if (!IsDoc()) {
714 // We should only apply cross-doc offsets to documents. If we're anything
715 // else, return early here.
716 return;
719 RemoteAccessible* parentAcc = RemoteParent();
720 if (!parentAcc || !parentAcc->IsOuterDoc()) {
721 return;
724 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds);
725 Maybe<const nsTArray<int32_t>&> maybeOffset =
726 parentAcc->mCachedFields->GetAttribute<nsTArray<int32_t>>(
727 CacheKey::CrossDocOffset);
728 if (!maybeOffset) {
729 return;
732 MOZ_ASSERT(maybeOffset->Length() == 2);
733 const nsTArray<int32_t>& offset = *maybeOffset;
734 // Our retrieved value is in app units, so we don't need to do any
735 // unit conversion here.
736 aBounds.MoveBy(offset[0], offset[1]);
739 bool RemoteAccessible::ApplyTransform(nsRect& aCumulativeBounds) const {
740 ASSERT_DOMAINS_ACTIVE(CacheDomain::TransformMatrix);
742 // First, attempt to retrieve the transform from the cache.
743 Maybe<const UniquePtr<gfx::Matrix4x4>&> maybeTransform =
744 mCachedFields->GetAttribute<UniquePtr<gfx::Matrix4x4>>(
745 CacheKey::TransformMatrix);
746 if (!maybeTransform) {
747 return false;
750 auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix(
751 *(*maybeTransform));
753 // Our matrix is in CSS Pixels, so we need our rect to be in CSS
754 // Pixels too. Convert before applying.
755 auto boundsInPixels = CSSRect::FromAppUnits(aCumulativeBounds);
756 boundsInPixels = mtxInPixels.TransformBounds(boundsInPixels);
757 aCumulativeBounds = CSSRect::ToAppUnits(boundsInPixels);
759 return true;
762 bool RemoteAccessible::ApplyScrollOffset(nsRect& aBounds) const {
763 ASSERT_DOMAINS_ACTIVE(CacheDomain::ScrollPosition);
764 Maybe<const nsTArray<int32_t>&> maybeScrollPosition =
765 mCachedFields->GetAttribute<nsTArray<int32_t>>(CacheKey::ScrollPosition);
767 if (!maybeScrollPosition || maybeScrollPosition->Length() != 2) {
768 return false;
770 // Our retrieved value is in app units, so we don't need to do any
771 // unit conversion here.
772 const nsTArray<int32_t>& scrollPosition = *maybeScrollPosition;
774 // Scroll position is an inverse representation of scroll offset (since the
775 // further the scroll bar moves down the page, the further the page content
776 // moves up/closer to the origin).
777 nsPoint scrollOffset(-scrollPosition[0], -scrollPosition[1]);
779 aBounds.MoveBy(scrollOffset.x, scrollOffset.y);
781 // Return true here even if the scroll offset was 0,0 because the RV is used
782 // as a scroll container indicator. Non-scroll containers won't have cached
783 // scroll position.
784 return true;
787 nsRect RemoteAccessible::BoundsInAppUnits() const {
788 if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) {
789 return {};
791 if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()->Top()) {
792 if (dom::BrowserParent* bp = cbc->GetBrowserParent()) {
793 DocAccessibleParent* topDoc = bp->GetTopLevelDocAccessible();
794 if (topDoc && topDoc->mCachedFields) {
795 auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
796 CacheKey::AppUnitsPerDevPixel);
797 MOZ_ASSERT(appUnitsPerDevPixel);
798 return LayoutDeviceIntRect::ToAppUnits(Bounds(), *appUnitsPerDevPixel);
802 return LayoutDeviceIntRect::ToAppUnits(Bounds(), AppUnitsPerCSSPixel());
805 bool RemoteAccessible::IsFixedPos() const {
806 ASSERT_DOMAINS_ACTIVE(CacheDomain::Style);
807 MOZ_ASSERT(mCachedFields);
808 if (auto maybePosition =
809 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CssPosition)) {
810 return *maybePosition == nsGkAtoms::fixed;
813 return false;
816 bool RemoteAccessible::IsOverflowHidden() const {
817 ASSERT_DOMAINS_ACTIVE(CacheDomain::Style);
818 MOZ_ASSERT(mCachedFields);
819 if (auto maybeOverflow =
820 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSOverflow)) {
821 return *maybeOverflow == nsGkAtoms::hidden;
824 return false;
827 bool RemoteAccessible::IsClipped() const {
828 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds);
829 MOZ_ASSERT(mCachedFields);
830 if (mCachedFields->GetAttribute<bool>(CacheKey::IsClipped)) {
831 return true;
834 return false;
837 LayoutDeviceIntRect RemoteAccessible::BoundsWithOffset(
838 Maybe<nsRect> aOffset, bool aBoundsAreForHittesting) const {
839 if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) {
840 return LayoutDeviceIntRect{};
843 Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
844 if (maybeBounds) {
845 nsRect bounds = *maybeBounds;
846 // maybeBounds is parent-relative. However, the transform matrix we cache
847 // (if any) is meant to operate on self-relative rects. Therefore, make
848 // bounds self-relative until after we transform.
849 bounds.MoveTo(0, 0);
850 const DocAccessibleParent* topDoc = IsDoc() ? AsDoc() : nullptr;
852 if (aOffset.isSome()) {
853 // The rect we've passed in is in app units, so no conversion needed.
854 nsRect internalRect = *aOffset;
855 bounds.SetRectX(bounds.x + internalRect.x, internalRect.width);
856 bounds.SetRectY(bounds.y + internalRect.y, internalRect.height);
859 Unused << ApplyTransform(bounds);
860 // Now apply the parent-relative offset.
861 bounds.MoveBy(maybeBounds->TopLeft());
863 ApplyCrossDocOffset(bounds);
865 LayoutDeviceIntRect devPxBounds;
866 const Accessible* acc = Parent();
867 bool encounteredFixedContainer = IsFixedPos();
868 while (acc && acc->IsRemote()) {
869 // Return early if we're hit testing and our cumulative bounds are empty,
870 // since walking the ancestor chain won't produce any hits.
871 if (aBoundsAreForHittesting && bounds.IsEmpty()) {
872 return LayoutDeviceIntRect{};
875 RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote();
877 if (Maybe<nsRect> maybeRemoteBounds = remoteAcc->RetrieveCachedBounds()) {
878 nsRect remoteBounds = *maybeRemoteBounds;
879 // We need to take into account a non-1 resolution set on the
880 // presshell. This happens with async pinch zooming, among other
881 // things. We can't reliably query this value in the parent process,
882 // so we retrieve it from the document's cache.
883 if (remoteAcc->IsDoc()) {
884 // Apply the document's resolution to the bounds we've gathered
885 // thus far. We do this before applying the document's offset
886 // because document accs should not have their bounds scaled by
887 // their own resolution. They should be scaled by the resolution
888 // of their containing document (if any).
889 Maybe<float> res =
890 remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>(
891 CacheKey::Resolution);
892 MOZ_ASSERT(res, "No cached document resolution found.");
893 bounds.ScaleRoundOut(res.valueOr(1.0f));
895 topDoc = remoteAcc->AsDoc();
898 // We don't account for the document offset of iframes when
899 // computing parent-relative bounds. Instead, we store this value
900 // separately on all iframes and apply it here. See the comments in
901 // LocalAccessible::BundleFieldsForCache where we set the
902 // nsGkAtoms::crossorigin attribute.
903 remoteAcc->ApplyCrossDocOffset(remoteBounds);
904 if (!encounteredFixedContainer) {
905 // Apply scroll offset, if applicable. Only the contents of an
906 // element are affected by its scroll offset, which is why this call
907 // happens in this loop instead of both inside and outside of
908 // the loop (like ApplyTransform).
909 // Never apply scroll offsets past a fixed container.
910 const bool hasScrollArea = remoteAcc->ApplyScrollOffset(bounds);
912 // If we are hit testing and the Accessible has a scroll area, ensure
913 // that the bounds we've calculated so far are constrained to the
914 // bounds of the scroll area. Without this, we'll "hit" the off-screen
915 // portions of accs that are are partially (but not fully) within the
916 // scroll area. This is also a problem for accs with overflow:hidden;
917 if (aBoundsAreForHittesting &&
918 (hasScrollArea || remoteAcc->IsOverflowHidden())) {
919 nsRect selfRelativeVisibleBounds(0, 0, remoteBounds.width,
920 remoteBounds.height);
921 bounds = bounds.SafeIntersect(selfRelativeVisibleBounds);
924 if (remoteAcc->IsDoc()) {
925 // Fixed elements are document relative, so if we've hit a
926 // document we're now subject to that document's styling
927 // (including scroll offsets that operate on it).
928 // This ordering is important, we don't want to apply scroll
929 // offsets on this doc's content.
930 encounteredFixedContainer = false;
932 if (!encounteredFixedContainer) {
933 // The transform matrix we cache (if any) is meant to operate on
934 // self-relative rects. Therefore, we must apply the transform before
935 // we make bounds parent-relative.
936 Unused << remoteAcc->ApplyTransform(bounds);
937 // Regardless of whether this is a doc, we should offset `bounds`
938 // by the bounds retrieved here. This is how we build screen
939 // coordinates from relative coordinates.
940 bounds.MoveBy(remoteBounds.X(), remoteBounds.Y());
943 if (remoteAcc->IsFixedPos()) {
944 encounteredFixedContainer = true;
946 // we can't just break here if we're scroll suppressed because we still
947 // need to find the top doc.
949 acc = acc->Parent();
952 MOZ_ASSERT(topDoc);
953 if (topDoc) {
954 // We use the top documents app-units-per-dev-pixel even though
955 // theoretically nested docs can have different values. Practically,
956 // that isn't likely since we only offer zoom controls for the top
957 // document and all subdocuments inherit from it.
958 auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
959 CacheKey::AppUnitsPerDevPixel);
960 MOZ_ASSERT(appUnitsPerDevPixel);
961 if (appUnitsPerDevPixel) {
962 // Convert our existing `bounds` rect from app units to dev pixels
963 devPxBounds = LayoutDeviceIntRect::FromAppUnitsToNearest(
964 bounds, *appUnitsPerDevPixel);
968 #if !defined(ANDROID)
969 // This block is not thread safe because it queries a LocalAccessible.
970 // It is also not needed in Android since the only local accessible is
971 // the outer doc browser that has an offset of 0.
972 // acc could be null if the OuterDocAccessible died before the top level
973 // DocAccessibleParent.
974 if (LocalAccessible* localAcc =
975 acc ? const_cast<Accessible*>(acc)->AsLocal() : nullptr) {
976 // LocalAccessible::Bounds returns screen-relative bounds in
977 // dev pixels.
978 LayoutDeviceIntRect localBounds = localAcc->Bounds();
980 // The root document will always have an APZ resolution of 1,
981 // so we don't factor in its scale here. We also don't scale
982 // by GetFullZoom because LocalAccessible::Bounds already does
983 // that.
984 devPxBounds.MoveBy(localBounds.X(), localBounds.Y());
986 #endif
988 return devPxBounds;
991 return LayoutDeviceIntRect();
994 LayoutDeviceIntRect RemoteAccessible::Bounds() const {
995 if (RequestDomainsIfInactive(kNecessaryBoundsDomains)) {
996 return {};
998 return BoundsWithOffset(Nothing());
1001 Relation RemoteAccessible::RelationByType(RelationType aType) const {
1002 if (RequestDomainsIfInactive(
1003 CacheDomain::Relations | // relations info, DOMName attribute
1004 CacheDomain::Value | // Value
1005 CacheDomain::DOMNodeIDAndClass | // DOMNodeID
1006 CacheDomain::GroupInfo // GetOrCreateGroupInfo
1007 )) {
1008 return Relation();
1011 // We are able to handle some relations completely in the
1012 // parent process, without the help of the cache. Those
1013 // relations are enumerated here. Other relations, whose
1014 // types are stored in kRelationTypeAtoms, are processed
1015 // below using the cache.
1016 if (aType == RelationType::CONTAINING_TAB_PANE) {
1017 if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()) {
1018 if (dom::CanonicalBrowsingContext* topCbc = cbc->Top()) {
1019 if (dom::BrowserParent* bp = topCbc->GetBrowserParent()) {
1020 return Relation(bp->GetTopLevelDocAccessible());
1024 return Relation();
1027 if (aType == RelationType::LINKS_TO && Role() == roles::LINK) {
1028 Pivot p = Pivot(mDoc);
1029 nsString href;
1030 Value(href);
1031 int32_t i = href.FindChar('#');
1032 int32_t len = static_cast<int32_t>(href.Length());
1033 if (i != -1 && i < (len - 1)) {
1034 nsDependentSubstring anchorName = Substring(href, i + 1, len);
1035 MustPruneSameDocRule rule;
1036 Accessible* nameMatch = nullptr;
1037 for (Accessible* match = p.Next(mDoc, rule); match;
1038 match = p.Next(match, rule)) {
1039 nsString currID;
1040 match->DOMNodeID(currID);
1041 MOZ_ASSERT(match->IsRemote());
1042 if (anchorName.Equals(currID)) {
1043 return Relation(match->AsRemote());
1045 if (!nameMatch) {
1046 nsString currName = match->AsRemote()->GetCachedHTMLNameAttribute();
1047 if (match->TagName() == nsGkAtoms::a && anchorName.Equals(currName)) {
1048 // If we find an element with a matching ID, we should return
1049 // that, but if we don't we should return the first anchor with
1050 // a matching name. To avoid doing two traversals, store the first
1051 // name match here.
1052 nameMatch = match;
1056 return nameMatch ? Relation(nameMatch->AsRemote()) : Relation();
1059 return Relation();
1062 // Handle ARIA tree, treegrid parent/child relations. Each of these cases
1063 // relies on cached group info. To find the parent of an accessible, use the
1064 // unified conceptual parent.
1065 if (aType == RelationType::NODE_CHILD_OF) {
1066 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1067 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
1068 roleMapEntry->role == roles::LISTITEM ||
1069 roleMapEntry->role == roles::ROW)) {
1070 if (const AccGroupInfo* groupInfo =
1071 const_cast<RemoteAccessible*>(this)->GetOrCreateGroupInfo()) {
1072 return Relation(groupInfo->ConceptualParent());
1075 return Relation();
1078 // To find the children of a parent, provide an iterator through its items.
1079 if (aType == RelationType::NODE_PARENT_OF) {
1080 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1081 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
1082 roleMapEntry->role == roles::LISTITEM ||
1083 roleMapEntry->role == roles::ROW ||
1084 roleMapEntry->role == roles::OUTLINE ||
1085 roleMapEntry->role == roles::LIST ||
1086 roleMapEntry->role == roles::TREE_TABLE)) {
1087 return Relation(new ItemIterator(this));
1089 return Relation();
1092 if (aType == RelationType::MEMBER_OF) {
1093 Relation rel = Relation();
1094 // HTML radio buttons with cached names should be grouped.
1095 if (IsHTMLRadioButton()) {
1096 nsString name = GetCachedHTMLNameAttribute();
1097 if (name.IsEmpty()) {
1098 return rel;
1101 RemoteAccessible* ancestor = RemoteParent();
1102 while (ancestor && ancestor->Role() != roles::FORM && ancestor != mDoc) {
1103 ancestor = ancestor->RemoteParent();
1105 if (ancestor) {
1106 // Sometimes we end up with an unparented acc here, potentially
1107 // because the acc is being moved. See bug 1807639.
1108 // Pivot expects to be created with a non-null mRoot.
1109 Pivot p = Pivot(ancestor);
1110 PivotRadioNameRule rule(name);
1111 Accessible* match = p.Next(ancestor, rule);
1112 while (match) {
1113 rel.AppendTarget(match->AsRemote());
1114 match = p.Next(match, rule);
1117 return rel;
1120 if (IsARIARole(nsGkAtoms::radio)) {
1121 // ARIA radio buttons should be grouped by their radio group
1122 // parent, if one exists.
1123 RemoteAccessible* currParent = RemoteParent();
1124 while (currParent && currParent->Role() != roles::RADIO_GROUP) {
1125 currParent = currParent->RemoteParent();
1128 if (currParent && currParent->Role() == roles::RADIO_GROUP) {
1129 // If we found a radiogroup parent, search for all
1130 // roles::RADIOBUTTON children and add them to our relation.
1131 // This search will include the radio button this method
1132 // was called from, which is expected.
1133 Pivot p = Pivot(currParent);
1134 PivotRoleRule rule(roles::RADIOBUTTON);
1135 Accessible* match = p.Next(currParent, rule);
1136 while (match) {
1137 MOZ_ASSERT(match->IsRemote(),
1138 "We should only be traversing the remote tree.");
1139 rel.AppendTarget(match->AsRemote());
1140 match = p.Next(match, rule);
1144 // By webkit's standard, aria radio buttons do not get grouped
1145 // if they lack a group parent, so we return an empty
1146 // relation here if the above check fails.
1147 return rel;
1150 Relation rel;
1151 if (!mCachedFields) {
1152 return rel;
1155 for (const auto& data : kRelationTypeAtoms) {
1156 if (data.mType != aType ||
1157 (data.mValidTag && TagName() != data.mValidTag)) {
1158 continue;
1161 if (auto maybeIds =
1162 mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom)) {
1163 rel.AppendIter(new RemoteAccIterator(*maybeIds, Document()));
1165 // Each relation type has only one relevant cached attribute,
1166 // so break after we've handled the attr for this type,
1167 // even if we didn't find any targets.
1168 break;
1171 if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) {
1172 if (auto reverseIdsEntry = accRelMapEntry.Data().Lookup(aType)) {
1173 rel.AppendIter(new RemoteAccIterator(reverseIdsEntry.Data(), Document()));
1177 // We handle these relations here rather than before cached relations because
1178 // the cached relations need to take precedence. For example, a <figure> with
1179 // both aria-labelledby and a <figcaption> must return two LABELLED_BY
1180 // targets: the aria-labelledby and then the <figcaption>.
1181 if (aType == RelationType::LABELLED_BY && TagName() == nsGkAtoms::figure) {
1182 uint32_t count = ChildCount();
1183 for (uint32_t c = 0; c < count; ++c) {
1184 RemoteAccessible* child = RemoteChildAt(c);
1185 MOZ_ASSERT(child);
1186 if (child->TagName() == nsGkAtoms::figcaption) {
1187 rel.AppendTarget(child);
1190 } else if (aType == RelationType::LABEL_FOR &&
1191 TagName() == nsGkAtoms::figcaption) {
1192 if (RemoteAccessible* parent = RemoteParent()) {
1193 if (parent->TagName() == nsGkAtoms::figure) {
1194 rel.AppendTarget(parent);
1199 return rel;
1202 void RemoteAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
1203 uint32_t aLength) {
1204 if (IsText()) {
1205 if (RequestDomainsIfInactive(CacheDomain::Text)) {
1206 return;
1208 if (mCachedFields) {
1209 if (auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text)) {
1210 aText.Append(Substring(*text, aStartOffset, aLength));
1212 VERIFY_CACHE(CacheDomain::Text);
1214 return;
1217 if (aStartOffset != 0 || aLength == 0) {
1218 return;
1221 if (IsHTMLBr()) {
1222 aText += kForcedNewLineChar;
1223 } else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) {
1224 // Expose the embedded object accessible as imaginary embedded object
1225 // character if its parent hypertext accessible doesn't expose children to
1226 // AT.
1227 aText += kImaginaryEmbeddedObjectChar;
1228 } else {
1229 aText += kEmbeddedObjectChar;
1233 nsTArray<bool> RemoteAccessible::PreProcessRelations(AccAttributes* aFields) {
1234 if (!DomainsAreActive(CacheDomain::Relations)) {
1235 return {};
1237 nsTArray<bool> updateTracker(ArrayLength(kRelationTypeAtoms));
1238 for (auto const& data : kRelationTypeAtoms) {
1239 if (data.mValidTag) {
1240 // The relation we're currently processing only applies to particular
1241 // elements. Check to see if we're one of them.
1242 nsAtom* tag = TagName();
1243 if (!tag) {
1244 // TagName() returns null on an initial cache push -- check aFields
1245 // for a tag name instead.
1246 if (auto maybeTag =
1247 aFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1248 tag = *maybeTag;
1251 MOZ_ASSERT(
1252 tag || IsTextLeaf() || IsDoc(),
1253 "Could not fetch tag via TagName() or from initial cache push!");
1254 if (tag != data.mValidTag) {
1255 // If this rel doesn't apply to us, do no pre-processing. Also,
1256 // note in our updateTracker that we should do no post-processing.
1257 updateTracker.AppendElement(false);
1258 continue;
1262 nsStaticAtom* const relAtom = data.mAtom;
1263 auto newRelationTargets =
1264 aFields->GetAttribute<nsTArray<uint64_t>>(relAtom);
1265 bool shouldAddNewImplicitRels =
1266 newRelationTargets && newRelationTargets->Length();
1268 // Remove existing implicit relations if we need to perform an update, or
1269 // if we've received a DeleteEntry(). Only do this if mCachedFields is
1270 // initialized. If mCachedFields is not initialized, we still need to
1271 // construct the update array so we correctly handle reverse rels in
1272 // PostProcessRelations.
1273 if ((shouldAddNewImplicitRels ||
1274 aFields->GetAttribute<DeleteEntry>(relAtom)) &&
1275 mCachedFields) {
1276 ASSERT_DOMAINS_ACTIVE(CacheDomain::Relations);
1277 if (auto maybeOldIDs =
1278 mCachedFields->GetAttribute<nsTArray<uint64_t>>(relAtom)) {
1279 for (uint64_t id : *maybeOldIDs) {
1280 // For each target, fetch its reverse relation map
1281 // We need to call `Lookup` here instead of `LookupOrInsert` because
1282 // it's possible the ID we're querying is from an acc that has since
1283 // been Shutdown(), and so has intentionally removed its reverse rels
1284 // from the doc's reverse rel cache.
1285 if (auto reverseRels = Document()->mReverseRelations.Lookup(id)) {
1286 // Then fetch its reverse relation's ID list. This should be safe
1287 // to do via LookupOrInsert because by the time we've gotten here,
1288 // we know the acc and `this` are still alive in the doc. If we hit
1289 // the following assert, we don't have parity on implicit/explicit
1290 // rels and something is wrong.
1291 nsTArray<uint64_t>& reverseRelIDs =
1292 reverseRels->LookupOrInsert(data.mReverseType);
1293 // There might be other reverse relations stored for this acc, so
1294 // remove our ID instead of deleting the array entirely.
1295 DebugOnly<bool> removed = reverseRelIDs.RemoveElement(ID());
1296 MOZ_ASSERT(removed, "Can't find old reverse relation");
1302 updateTracker.AppendElement(shouldAddNewImplicitRels);
1305 return updateTracker;
1308 void RemoteAccessible::PostProcessRelations(const nsTArray<bool>& aToUpdate) {
1309 if (!DomainsAreActive(CacheDomain::Relations)) {
1310 return;
1312 size_t updateCount = aToUpdate.Length();
1313 MOZ_ASSERT(updateCount == ArrayLength(kRelationTypeAtoms),
1314 "Did not note update status for every relation type!");
1315 for (size_t i = 0; i < updateCount; i++) {
1316 if (aToUpdate.ElementAt(i)) {
1317 // Since kRelationTypeAtoms was used to generate aToUpdate, we
1318 // know the ith entry of aToUpdate corresponds to the relation type in
1319 // the ith entry of kRelationTypeAtoms. Fetch the related data here.
1320 auto const& data = kRelationTypeAtoms[i];
1322 const nsTArray<uint64_t>& newIDs =
1323 *mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom);
1324 for (uint64_t id : newIDs) {
1325 nsTHashMap<RelationType, nsTArray<uint64_t>>& relations =
1326 Document()->mReverseRelations.LookupOrInsert(id);
1327 nsTArray<uint64_t>& ids = relations.LookupOrInsert(data.mReverseType);
1328 ids.AppendElement(ID());
1334 void RemoteAccessible::PruneRelationsOnShutdown() {
1335 auto reverseRels = mDoc->mReverseRelations.Lookup(ID());
1336 if (!reverseRels) {
1337 return;
1339 for (auto const& data : kRelationTypeAtoms) {
1340 // Fetch the list of targets for this reverse relation
1341 auto reverseTargetList = reverseRels->Lookup(data.mReverseType);
1342 if (!reverseTargetList) {
1343 continue;
1345 for (uint64_t id : *reverseTargetList) {
1346 // For each target, retrieve its corresponding forward relation target
1347 // list
1348 RemoteAccessible* affectedAcc = mDoc->GetAccessible(id);
1349 if (!affectedAcc) {
1350 // It's possible the affect acc also shut down, in which case
1351 // we don't have anything to update.
1352 continue;
1354 if (auto forwardTargetList =
1355 affectedAcc->mCachedFields
1356 ->GetMutableAttribute<nsTArray<uint64_t>>(data.mAtom)) {
1357 forwardTargetList->RemoveElement(ID());
1358 if (!forwardTargetList->Length()) {
1359 // The ID we removed was the only thing in the list, so remove the
1360 // entry from the cache entirely -- don't leave an empty array.
1361 affectedAcc->mCachedFields->Remove(data.mAtom);
1366 // Remove this ID from the document's map of reverse relations.
1367 reverseRels.Remove();
1370 uint32_t RemoteAccessible::GetCachedTextLength() {
1371 if (RequestDomainsIfInactive(CacheDomain::Text)) {
1372 return 0;
1374 MOZ_ASSERT(!HasChildren());
1375 if (!mCachedFields) {
1376 return 0;
1378 VERIFY_CACHE(CacheDomain::Text);
1379 auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text);
1380 if (!text) {
1381 return 0;
1383 return text->Length();
1386 Maybe<const nsTArray<int32_t>&> RemoteAccessible::GetCachedTextLines() {
1387 if (RequestDomainsIfInactive(CacheDomain::TextBounds)) {
1388 return Nothing();
1391 MOZ_ASSERT(!HasChildren());
1392 if (!mCachedFields) {
1393 return Nothing();
1395 VERIFY_CACHE(CacheDomain::TextBounds);
1396 return mCachedFields->GetAttribute<nsTArray<int32_t>>(
1397 CacheKey::TextLineStarts);
1400 nsRect RemoteAccessible::GetCachedCharRect(int32_t aOffset) {
1401 ASSERT_DOMAINS_ACTIVE(CacheDomain::TextBounds);
1402 MOZ_ASSERT(IsText());
1403 if (!mCachedFields) {
1404 return nsRect();
1407 if (Maybe<const nsTArray<int32_t>&> maybeCharData =
1408 mCachedFields->GetAttribute<nsTArray<int32_t>>(
1409 CacheKey::TextBounds)) {
1410 const nsTArray<int32_t>& charData = *maybeCharData;
1411 const int32_t index = aOffset * kNumbersInRect;
1412 if (index < static_cast<int32_t>(charData.Length())) {
1413 return nsRect(charData[index], charData[index + 1], charData[index + 2],
1414 charData[index + 3]);
1416 // It is valid for a client to call this with an offset 1 after the last
1417 // character because of the insertion point at the end of text boxes.
1418 MOZ_ASSERT(index == static_cast<int32_t>(charData.Length()));
1421 return nsRect();
1424 void RemoteAccessible::DOMNodeID(nsString& aID) const {
1425 if (RequestDomainsIfInactive(CacheDomain::DOMNodeIDAndClass)) {
1426 return;
1428 if (mCachedFields) {
1429 mCachedFields->GetAttribute(CacheKey::DOMNodeID, aID);
1430 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
1434 void RemoteAccessible::DOMNodeClass(nsString& aClass) const {
1435 if (mCachedFields) {
1436 mCachedFields->GetAttribute(CacheKey::DOMNodeClass, aClass);
1437 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
1441 void RemoteAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX,
1442 int32_t aY) {
1443 Unused << mDoc->SendScrollToPoint(mID, aScrollType, aX, aY);
1446 #if !defined(XP_WIN)
1447 void RemoteAccessible::Announce(const nsString& aAnnouncement,
1448 uint16_t aPriority) {
1449 Unused << mDoc->SendAnnounce(mID, aAnnouncement, aPriority);
1451 #endif // !defined(XP_WIN)
1453 int32_t RemoteAccessible::ValueRegion() const {
1454 MOZ_ASSERT(TagName() == nsGkAtoms::meter,
1455 "Accessing value region on non-meter element?");
1456 if (mCachedFields) {
1457 if (auto region =
1458 mCachedFields->GetAttribute<int32_t>(CacheKey::ValueRegion)) {
1459 return *region;
1462 // Expose sub-optimal (but not critical) as the value region, as a fallback.
1463 return 0;
1466 void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
1467 int32_t aEndOffset,
1468 uint32_t aCoordinateType,
1469 int32_t aX, int32_t aY) {
1470 Unused << mDoc->SendScrollSubstringToPoint(mID, aStartOffset, aEndOffset,
1471 aCoordinateType, aX, aY);
1474 RefPtr<const AccAttributes> RemoteAccessible::GetCachedTextAttributes() {
1475 if (RequestDomainsIfInactive(CacheDomain::Text)) {
1476 return nullptr;
1478 MOZ_ASSERT(IsText() || IsHyperText());
1479 if (mCachedFields) {
1480 auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
1481 CacheKey::TextAttributes);
1482 VERIFY_CACHE(CacheDomain::Text);
1483 return attrs;
1485 return nullptr;
1488 already_AddRefed<AccAttributes> RemoteAccessible::DefaultTextAttributes() {
1489 if (RequestDomainsIfInactive(CacheDomain::Text)) {
1490 return nullptr;
1492 RefPtr<const AccAttributes> attrs = GetCachedTextAttributes();
1493 RefPtr<AccAttributes> result = new AccAttributes();
1494 if (attrs) {
1495 attrs->CopyTo(result);
1497 return result.forget();
1500 RefPtr<const AccAttributes> RemoteAccessible::GetCachedARIAAttributes() const {
1501 ASSERT_DOMAINS_ACTIVE(CacheDomain::ARIA);
1502 if (mCachedFields) {
1503 auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
1504 CacheKey::ARIAAttributes);
1505 VERIFY_CACHE(CacheDomain::ARIA);
1506 return attrs;
1508 return nullptr;
1511 nsString RemoteAccessible::GetCachedHTMLNameAttribute() const {
1512 ASSERT_DOMAINS_ACTIVE(CacheDomain::Relations);
1513 if (mCachedFields) {
1514 if (auto maybeName =
1515 mCachedFields->GetAttribute<nsString>(CacheKey::DOMName)) {
1516 return *maybeName;
1519 return nsString();
1522 uint64_t RemoteAccessible::State() {
1523 if (RequestDomainsIfInactive(
1524 CacheDomain::State | // State attributes
1525 CacheDomain::Style | // for Opacity (via ApplyImplicitState)
1526 CacheDomain::Viewport // necessary to build mOnScreenAccessibles
1527 )) {
1528 return 0;
1530 uint64_t state = 0;
1531 if (mCachedFields) {
1532 if (auto rawState =
1533 mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) {
1534 VERIFY_CACHE(CacheDomain::State);
1535 state = *rawState;
1536 // Handle states that are derived from other states.
1537 if (!(state & states::UNAVAILABLE)) {
1538 state |= states::ENABLED | states::SENSITIVE;
1540 if (state & states::EXPANDABLE && !(state & states::EXPANDED)) {
1541 state |= states::COLLAPSED;
1545 ApplyImplicitState(state);
1547 auto* cbc = mDoc->GetBrowsingContext();
1548 if (cbc && !cbc->IsActive()) {
1549 // If our browsing context is _not_ active, we're in a background tab
1550 // and inherently offscreen.
1551 state |= states::OFFSCREEN;
1552 } else {
1553 // If we're in an active browsing context, there are a few scenarios we
1554 // need to address:
1555 // - We are an iframe document in the visual viewport
1556 // - We are an iframe document out of the visual viewport
1557 // - We are non-iframe content in the visual viewport
1558 // - We are non-iframe content out of the visual viewport
1559 // We assume top level tab docs are on screen if their BC is active, so
1560 // we don't need additional handling for them here.
1561 if (!mDoc->IsTopLevel()) {
1562 // Here we handle iframes and iframe content.
1563 // We use an iframe's outer doc's position in the embedding document's
1564 // viewport to determine if the iframe has been scrolled offscreen.
1565 Accessible* docParent = mDoc->Parent();
1566 // In rare cases, we might not have an outer doc yet. Return if that's
1567 // the case.
1568 if (NS_WARN_IF(!docParent || !docParent->IsRemote())) {
1569 return state;
1572 RemoteAccessible* outerDoc = docParent->AsRemote();
1573 DocAccessibleParent* embeddingDocument = outerDoc->Document();
1574 if (embeddingDocument &&
1575 !embeddingDocument->mOnScreenAccessibles.Contains(outerDoc->ID())) {
1576 // Our embedding document's viewport cache doesn't contain the ID of
1577 // our outer doc, so this iframe (and any of its content) is
1578 // offscreen.
1579 state |= states::OFFSCREEN;
1580 } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
1581 // Our embedding document's viewport cache contains the ID of our
1582 // outer doc, but the iframe's viewport cache doesn't contain our ID.
1583 // We are offscreen.
1584 state |= states::OFFSCREEN;
1586 } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
1587 // We are top level tab content (but not a top level tab doc).
1588 // If our tab doc's viewport cache doesn't contain our ID, we're
1589 // offscreen.
1590 state |= states::OFFSCREEN;
1595 return state;
1598 already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
1599 RefPtr<AccAttributes> attributes = new AccAttributes();
1600 if (RequestDomainsIfInactive(CacheDomain::ARIA | // GetCachedARIAAttributes
1601 CacheDomain::NameAndDescription | // Name
1602 CacheDomain::Text | // Name
1603 CacheDomain::Value | // Value
1604 CacheDomain::Actions | // Value
1605 CacheDomain::Style | // DisplayStyle
1606 CacheDomain::GroupInfo | // GroupPosition
1607 CacheDomain::State | // State
1608 CacheDomain::Viewport | // State
1609 CacheDomain::Table | // TableIsProbablyForLayout
1610 CacheDomain::DOMNodeIDAndClass // DOMNodeID
1611 )) {
1612 return attributes.forget();
1615 nsAccessibilityService* accService = GetAccService();
1616 if (!accService) {
1617 // The service can be shut down before RemoteAccessibles. If it is shut
1618 // down, we can't calculate some attributes. We're about to die anyway.
1619 return attributes.forget();
1622 if (mCachedFields) {
1623 // We use GetAttribute instead of GetAttributeRefPtr because we need
1624 // nsAtom, not const nsAtom.
1625 if (auto tag =
1626 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1627 attributes->SetAttribute(nsGkAtoms::tag, *tag);
1630 GroupPos groupPos = GroupPosition();
1631 nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, groupPos.setSize,
1632 groupPos.posInSet);
1634 bool hierarchical = false;
1635 uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
1636 if (itemCount) {
1637 attributes->SetAttribute(nsGkAtoms::child_item_count,
1638 static_cast<int32_t>(itemCount));
1641 if (hierarchical) {
1642 attributes->SetAttribute(nsGkAtoms::tree, true);
1645 if (auto inputType =
1646 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
1647 attributes->SetAttribute(nsGkAtoms::textInputType, *inputType);
1650 if (RefPtr<nsAtom> display = DisplayStyle()) {
1651 attributes->SetAttribute(nsGkAtoms::display, display);
1654 if (TableCellAccessible* cell = AsTableCell()) {
1655 TableAccessible* table = cell->Table();
1656 uint32_t row = cell->RowIdx();
1657 uint32_t col = cell->ColIdx();
1658 int32_t cellIdx = table->CellIndexAt(row, col);
1659 if (cellIdx != -1) {
1660 attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx);
1664 if (bool layoutGuess = TableIsProbablyForLayout()) {
1665 attributes->SetAttribute(nsGkAtoms::layout_guess, layoutGuess);
1668 accService->MarkupAttributes(this, attributes);
1670 const nsRoleMapEntry* roleMap = ARIARoleMap();
1671 nsAutoString role;
1672 mCachedFields->GetAttribute(CacheKey::ARIARole, role);
1673 if (role.IsEmpty()) {
1674 if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty) {
1675 // Single, known role.
1676 attributes->SetAttribute(nsGkAtoms::xmlroles, roleMap->roleAtom);
1677 } else if (nsAtom* landmark = LandmarkRole()) {
1678 // Landmark role from markup; e.g. HTML <main>.
1679 attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
1681 } else {
1682 // Unknown role or multiple roles.
1683 attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role));
1686 if (roleMap) {
1687 nsAutoString live;
1688 if (nsAccUtils::GetLiveAttrValue(roleMap->liveAttRule, live)) {
1689 attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
1693 if (auto ariaAttrs = GetCachedARIAAttributes()) {
1694 ariaAttrs->CopyTo(attributes);
1697 nsAccUtils::SetLiveContainerAttributes(attributes, this);
1699 nsString id;
1700 DOMNodeID(id);
1701 if (!id.IsEmpty()) {
1702 attributes->SetAttribute(nsGkAtoms::id, std::move(id));
1705 nsString className;
1706 DOMNodeClass(className);
1707 if (!className.IsEmpty()) {
1708 attributes->SetAttribute(nsGkAtoms::_class, std::move(className));
1711 if (IsImage()) {
1712 nsString src;
1713 mCachedFields->GetAttribute(CacheKey::SrcURL, src);
1714 if (!src.IsEmpty()) {
1715 attributes->SetAttribute(nsGkAtoms::src, std::move(src));
1719 if (IsTextField()) {
1720 nsString placeholder;
1721 mCachedFields->GetAttribute(CacheKey::HTMLPlaceholder, placeholder);
1722 if (!placeholder.IsEmpty()) {
1723 attributes->SetAttribute(nsGkAtoms::placeholder,
1724 std::move(placeholder));
1725 attributes->Remove(nsGkAtoms::aria_placeholder);
1729 nsString popupType;
1730 mCachedFields->GetAttribute(CacheKey::PopupType, popupType);
1731 if (!popupType.IsEmpty()) {
1732 attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popupType));
1736 nsAutoString name;
1737 if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
1738 attributes->SetAttribute(nsGkAtoms::explicit_name, true);
1741 // Expose the string value via the valuetext attribute. We test for the value
1742 // interface because we don't want to expose traditional Value() information
1743 // such as URLs on links and documents, or text in an input.
1744 // XXX This is only needed for ATK, since other APIs have native ways to
1745 // retrieve value text. We should probably move this into ATK specific code.
1746 // For now, we do this because LocalAccessible does it.
1747 if (HasNumericValue()) {
1748 nsString valuetext;
1749 Value(valuetext);
1750 attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
1753 return attributes.forget();
1756 nsAtom* RemoteAccessible::TagName() const {
1757 if (mCachedFields) {
1758 if (auto tag =
1759 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1760 return *tag;
1764 return nullptr;
1767 already_AddRefed<nsAtom> RemoteAccessible::InputType() const {
1768 if (mCachedFields) {
1769 if (auto inputType =
1770 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
1771 RefPtr<nsAtom> result = *inputType;
1772 return result.forget();
1776 return nullptr;
1779 already_AddRefed<nsAtom> RemoteAccessible::DisplayStyle() const {
1780 if (RequestDomainsIfInactive(CacheDomain::Style)) {
1781 return nullptr;
1783 if (mCachedFields) {
1784 if (auto display =
1785 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSDisplay)) {
1786 RefPtr<nsAtom> result = *display;
1787 return result.forget();
1790 return nullptr;
1793 float RemoteAccessible::Opacity() const {
1794 if (RequestDomainsIfInactive(CacheDomain::Style)) {
1795 return 1.0f;
1798 if (mCachedFields) {
1799 if (auto opacity = mCachedFields->GetAttribute<float>(CacheKey::Opacity)) {
1800 return *opacity;
1804 return 1.0f;
1807 void RemoteAccessible::LiveRegionAttributes(nsAString* aLive,
1808 nsAString* aRelevant,
1809 Maybe<bool>* aAtomic,
1810 nsAString* aBusy) const {
1811 if (RequestDomainsIfInactive(CacheDomain::ARIA)) {
1812 return;
1814 if (!mCachedFields) {
1815 return;
1817 RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes();
1818 if (!attrs) {
1819 return;
1821 if (aLive) {
1822 attrs->GetAttribute(nsGkAtoms::aria_live, *aLive);
1824 if (aRelevant) {
1825 attrs->GetAttribute(nsGkAtoms::aria_relevant, *aRelevant);
1827 if (aAtomic) {
1828 if (auto value =
1829 attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::aria_atomic)) {
1830 *aAtomic = Some(*value == nsGkAtoms::_true);
1833 if (aBusy) {
1834 attrs->GetAttribute(nsGkAtoms::aria_busy, *aBusy);
1838 Maybe<bool> RemoteAccessible::ARIASelected() const {
1839 if (RequestDomainsIfInactive(CacheDomain::State)) {
1840 return Nothing();
1843 if (mCachedFields) {
1844 return mCachedFields->GetAttribute<bool>(CacheKey::ARIASelected);
1846 return Nothing();
1849 nsAtom* RemoteAccessible::GetPrimaryAction() const {
1850 if (mCachedFields) {
1851 ASSERT_DOMAINS_ACTIVE(CacheDomain::Actions);
1852 if (auto action = mCachedFields->GetAttribute<RefPtr<nsAtom>>(
1853 CacheKey::PrimaryAction)) {
1854 return *action;
1858 return nullptr;
1861 uint8_t RemoteAccessible::ActionCount() const {
1862 uint8_t actionCount = 0;
1863 if (RequestDomainsIfInactive(CacheDomain::Actions)) {
1864 return actionCount;
1866 if (mCachedFields) {
1867 if (HasPrimaryAction() || ActionAncestor()) {
1868 actionCount++;
1871 if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1872 actionCount++;
1874 VERIFY_CACHE(CacheDomain::Actions);
1877 return actionCount;
1880 void RemoteAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
1881 if (RequestDomainsIfInactive(CacheDomain::Actions)) {
1882 return;
1885 if (mCachedFields) {
1886 aName.Truncate();
1887 nsAtom* action = GetPrimaryAction();
1888 bool hasActionAncestor = !action && ActionAncestor();
1890 switch (aIndex) {
1891 case 0:
1892 if (action) {
1893 action->ToString(aName);
1894 } else if (hasActionAncestor) {
1895 aName.AssignLiteral("clickAncestor");
1896 } else if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1897 aName.AssignLiteral("showlongdesc");
1899 break;
1900 case 1:
1901 if ((action || hasActionAncestor) &&
1902 mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1903 aName.AssignLiteral("showlongdesc");
1905 break;
1906 default:
1907 break;
1910 VERIFY_CACHE(CacheDomain::Actions);
1913 bool RemoteAccessible::DoAction(uint8_t aIndex) const {
1914 if (RequestDomainsIfInactive(CacheDomain::Actions)) {
1915 return false;
1918 if (ActionCount() < aIndex + 1) {
1919 return false;
1922 Unused << mDoc->SendDoActionAsync(mID, aIndex);
1923 return true;
1926 KeyBinding RemoteAccessible::AccessKey() const {
1927 if (RequestDomainsIfInactive(CacheDomain::Actions)) {
1928 return {};
1931 if (mCachedFields) {
1932 if (auto value =
1933 mCachedFields->GetAttribute<uint64_t>(CacheKey::AccessKey)) {
1934 return KeyBinding(*value);
1937 return KeyBinding();
1940 void RemoteAccessible::SelectionRanges(nsTArray<TextRange>* aRanges) const {
1941 Document()->SelectionRanges(aRanges);
1944 bool RemoteAccessible::RemoveFromSelection(int32_t aSelectionNum) {
1945 MOZ_ASSERT(IsHyperText());
1946 if (SelectionCount() <= aSelectionNum) {
1947 return false;
1950 Unused << mDoc->SendRemoveTextSelection(mID, aSelectionNum);
1952 return true;
1955 void RemoteAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
1956 int32_t* aPosInSet) const {
1957 if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) {
1958 return;
1961 if (!mCachedFields) {
1962 return;
1965 if (aLevel) {
1966 if (auto level =
1967 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_level)) {
1968 *aLevel = *level;
1971 if (aSetSize) {
1972 if (auto setsize =
1973 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_setsize)) {
1974 *aSetSize = *setsize;
1977 if (aPosInSet) {
1978 if (auto posinset =
1979 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_posinset)) {
1980 *aPosInSet = *posinset;
1985 AccGroupInfo* RemoteAccessible::GetGroupInfo() const {
1986 // Interpret a call to GetGroupInfo as a signal that the AT will want group
1987 // info information. CacheKey::GroupInfo is not in CacheDomain::GroupInfo, so
1988 // this isn't strictly necessary, but is likely helpful.
1989 if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) {
1990 return nullptr;
1993 if (!mCachedFields) {
1994 return nullptr;
1997 if (auto groupInfo = mCachedFields->GetAttribute<UniquePtr<AccGroupInfo>>(
1998 CacheKey::GroupInfo)) {
1999 return groupInfo->get();
2002 return nullptr;
2005 AccGroupInfo* RemoteAccessible::GetOrCreateGroupInfo() {
2006 if (RequestDomainsIfInactive(CacheDomain::GroupInfo)) {
2007 return nullptr;
2010 AccGroupInfo* groupInfo = GetGroupInfo();
2011 if (groupInfo) {
2012 return groupInfo;
2015 groupInfo = AccGroupInfo::CreateGroupInfo(this);
2016 if (groupInfo) {
2017 if (!mCachedFields) {
2018 mCachedFields = new AccAttributes();
2021 mCachedFields->SetAttribute(CacheKey::GroupInfo, groupInfo);
2024 return groupInfo;
2027 void RemoteAccessible::InvalidateGroupInfo() {
2028 if (mCachedFields) {
2029 mCachedFields->Remove(CacheKey::GroupInfo);
2033 void RemoteAccessible::GetPositionAndSetSize(int32_t* aPosInSet,
2034 int32_t* aSetSize) {
2035 // Note: Required domains come from requirements of RelationByType.
2036 if (RequestDomainsIfInactive(CacheDomain::Relations | CacheDomain::Value |
2037 CacheDomain::DOMNodeIDAndClass |
2038 CacheDomain::GroupInfo)) {
2039 return;
2042 if (IsHTMLRadioButton()) {
2043 *aSetSize = 0;
2044 Relation rel = RelationByType(RelationType::MEMBER_OF);
2045 while (Accessible* radio = rel.Next()) {
2046 ++*aSetSize;
2047 if (radio == this) {
2048 *aPosInSet = *aSetSize;
2051 return;
2054 Accessible::GetPositionAndSetSize(aPosInSet, aSetSize);
2057 bool RemoteAccessible::HasPrimaryAction() const {
2058 if (RequestDomainsIfInactive(CacheDomain::Actions)) {
2059 return false;
2061 return mCachedFields && mCachedFields->HasAttribute(CacheKey::PrimaryAction);
2064 void RemoteAccessible::TakeFocus() const {
2065 Unused << mDoc->SendTakeFocus(mID);
2066 auto* bp = static_cast<dom::BrowserParent*>(mDoc->Manager());
2067 MOZ_ASSERT(bp);
2068 if (nsFocusManager::GetFocusedElementStatic() == bp->GetOwnerElement()) {
2069 // This remote document tree is already focused. We don't need to do
2070 // anything else.
2071 return;
2073 // Otherwise, we need to focus the <browser> or <iframe> element embedding the
2074 // remote document in the parent process. If `this` is in an OOP iframe, we
2075 // first need to focus the embedder iframe (and any ancestor OOP iframes). If
2076 // the parent process embedder element were already focused, that would happen
2077 // automatically, but it isn't. We can't simply focus the parent process
2078 // embedder element before calling mDoc->SendTakeFocus because that would
2079 // cause the remote document to restore focus to the last focused element,
2080 // which we don't want.
2081 DocAccessibleParent* embeddedDoc = mDoc;
2082 Accessible* embedder = mDoc->Parent();
2083 while (embedder) {
2084 MOZ_ASSERT(embedder->IsOuterDoc());
2085 RemoteAccessible* embedderRemote = embedder->AsRemote();
2086 if (!embedderRemote) {
2087 // This is the element in the parent process which embeds the remote
2088 // document.
2089 embedder->TakeFocus();
2090 break;
2092 // This is a remote <iframe>.
2093 if (embeddedDoc->IsTopLevelInContentProcess()) {
2094 // We only need to focus OOP iframes because these are where we cross
2095 // process boundaries.
2096 Unused << embedderRemote->mDoc->SendTakeFocus(embedderRemote->mID);
2098 embeddedDoc = embedderRemote->mDoc;
2099 embedder = embeddedDoc->Parent();
2103 void RemoteAccessible::ScrollTo(uint32_t aHow) const {
2104 Unused << mDoc->SendScrollTo(mID, aHow);
2107 ////////////////////////////////////////////////////////////////////////////////
2108 // SelectAccessible
2110 void RemoteAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
2111 if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
2112 return;
2114 Pivot p = Pivot(this);
2115 PivotStateRule rule(states::SELECTED);
2116 for (Accessible* selected = p.First(rule); selected;
2117 selected = p.Next(selected, rule)) {
2118 aItems->AppendElement(selected);
2122 uint32_t RemoteAccessible::SelectedItemCount() {
2123 if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
2124 return 0;
2126 uint32_t count = 0;
2127 Pivot p = Pivot(this);
2128 PivotStateRule rule(states::SELECTED);
2129 for (Accessible* selected = p.First(rule); selected;
2130 selected = p.Next(selected, rule)) {
2131 count++;
2134 return count;
2137 Accessible* RemoteAccessible::GetSelectedItem(uint32_t aIndex) {
2138 if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
2139 return nullptr;
2141 uint32_t index = 0;
2142 Accessible* selected = nullptr;
2143 Pivot p = Pivot(this);
2144 PivotStateRule rule(states::SELECTED);
2145 for (selected = p.First(rule); selected && index < aIndex;
2146 selected = p.Next(selected, rule)) {
2147 index++;
2150 return selected;
2153 bool RemoteAccessible::IsItemSelected(uint32_t aIndex) {
2154 if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
2155 return false;
2157 uint32_t index = 0;
2158 Accessible* selectable = nullptr;
2159 Pivot p = Pivot(this);
2160 PivotStateRule rule(states::SELECTABLE);
2161 for (selectable = p.First(rule); selectable && index < aIndex;
2162 selectable = p.Next(selectable, rule)) {
2163 index++;
2166 return selectable && selectable->State() & states::SELECTED;
2169 bool RemoteAccessible::AddItemToSelection(uint32_t aIndex) {
2170 if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
2171 return false;
2173 uint32_t index = 0;
2174 Accessible* selectable = nullptr;
2175 Pivot p = Pivot(this);
2176 PivotStateRule rule(states::SELECTABLE);
2177 for (selectable = p.First(rule); selectable && index < aIndex;
2178 selectable = p.Next(selectable, rule)) {
2179 index++;
2182 if (selectable) selectable->SetSelected(true);
2184 return static_cast<bool>(selectable);
2187 bool RemoteAccessible::RemoveItemFromSelection(uint32_t aIndex) {
2188 if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
2189 return false;
2191 uint32_t index = 0;
2192 Accessible* selectable = nullptr;
2193 Pivot p = Pivot(this);
2194 PivotStateRule rule(states::SELECTABLE);
2195 for (selectable = p.First(rule); selectable && index < aIndex;
2196 selectable = p.Next(selectable, rule)) {
2197 index++;
2200 if (selectable) selectable->SetSelected(false);
2202 return static_cast<bool>(selectable);
2205 bool RemoteAccessible::SelectAll() {
2206 if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
2207 return false;
2209 if ((State() & states::MULTISELECTABLE) == 0) {
2210 return false;
2213 bool success = false;
2214 Accessible* selectable = nullptr;
2215 Pivot p = Pivot(this);
2216 PivotStateRule rule(states::SELECTABLE);
2217 for (selectable = p.First(rule); selectable;
2218 selectable = p.Next(selectable, rule)) {
2219 success = true;
2220 selectable->SetSelected(true);
2222 return success;
2225 bool RemoteAccessible::UnselectAll() {
2226 if (RequestDomainsIfInactive(kNecessaryStateDomains)) {
2227 return false;
2229 if ((State() & states::MULTISELECTABLE) == 0) {
2230 return false;
2233 bool success = false;
2234 Accessible* selectable = nullptr;
2235 Pivot p = Pivot(this);
2236 PivotStateRule rule(states::SELECTABLE);
2237 for (selectable = p.First(rule); selectable;
2238 selectable = p.Next(selectable, rule)) {
2239 success = true;
2240 selectable->SetSelected(false);
2242 return success;
2245 void RemoteAccessible::TakeSelection() {
2246 Unused << mDoc->SendTakeSelection(mID);
2249 void RemoteAccessible::SetSelected(bool aSelect) {
2250 Unused << mDoc->SendSetSelected(mID, aSelect);
2253 TableAccessible* RemoteAccessible::AsTable() {
2254 if (IsTable()) {
2255 return CachedTableAccessible::GetFrom(this);
2257 return nullptr;
2260 TableCellAccessible* RemoteAccessible::AsTableCell() {
2261 if (IsTableCell()) {
2262 return CachedTableCellAccessible::GetFrom(this);
2264 return nullptr;
2267 bool RemoteAccessible::TableIsProbablyForLayout() {
2268 if (RequestDomainsIfInactive(CacheDomain::Table)) {
2269 return false;
2271 if (mCachedFields) {
2272 if (auto layoutGuess =
2273 mCachedFields->GetAttribute<bool>(CacheKey::TableLayoutGuess)) {
2274 return *layoutGuess;
2277 return false;
2280 nsTArray<int32_t>& RemoteAccessible::GetCachedHyperTextOffsets() {
2281 if (mCachedFields) {
2282 if (auto offsets = mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
2283 CacheKey::HyperTextOffsets)) {
2284 return *offsets;
2287 nsTArray<int32_t> newOffsets;
2288 if (!mCachedFields) {
2289 mCachedFields = new AccAttributes();
2291 mCachedFields->SetAttribute(CacheKey::HyperTextOffsets,
2292 std::move(newOffsets));
2293 return *mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
2294 CacheKey::HyperTextOffsets);
2297 void RemoteAccessible::SetCaretOffset(int32_t aOffset) {
2298 Unused << mDoc->SendSetCaretOffset(mID, aOffset);
2301 Maybe<int32_t> RemoteAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
2302 if (RequestDomainsIfInactive(CacheDomain::ARIA)) {
2303 return Nothing();
2305 if (RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes()) {
2306 if (auto val = attrs->GetAttribute<int32_t>(aAttrName)) {
2307 return val;
2310 return Nothing();
2313 void RemoteAccessible::Language(nsAString& aLocale) {
2314 if (RequestDomainsIfInactive(CacheDomain::Text)) {
2315 return;
2318 if (IsHyperText() || IsText()) {
2319 if (auto attrs = GetCachedTextAttributes()) {
2320 attrs->GetAttribute(nsGkAtoms::language, aLocale);
2322 if (IsText() && aLocale.IsEmpty()) {
2323 // If a leaf has the same language as its parent HyperTextAccessible, it
2324 // won't be cached in the leaf's text attributes. Check the parent.
2325 if (RemoteAccessible* parent = RemoteParent()) {
2326 if (auto attrs = parent->GetCachedTextAttributes()) {
2327 attrs->GetAttribute(nsGkAtoms::language, aLocale);
2331 } else if (mCachedFields) {
2332 mCachedFields->GetAttribute(CacheKey::Language, aLocale);
2336 void RemoteAccessible::ReplaceText(const nsAString& aText) {
2337 Unused << mDoc->SendReplaceText(mID, aText);
2340 void RemoteAccessible::InsertText(const nsAString& aText, int32_t aPosition) {
2341 Unused << mDoc->SendInsertText(mID, aText, aPosition);
2344 void RemoteAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) {
2345 Unused << mDoc->SendCopyText(mID, aStartPos, aEndPos);
2348 void RemoteAccessible::CutText(int32_t aStartPos, int32_t aEndPos) {
2349 Unused << mDoc->SendCutText(mID, aStartPos, aEndPos);
2352 void RemoteAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) {
2353 Unused << mDoc->SendDeleteText(mID, aStartPos, aEndPos);
2356 void RemoteAccessible::PasteText(int32_t aPosition) {
2357 Unused << mDoc->SendPasteText(mID, aPosition);
2360 size_t RemoteAccessible::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
2361 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
2364 size_t RemoteAccessible::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
2365 size_t size = 0;
2367 // Count attributes.
2368 if (mCachedFields) {
2369 size += mCachedFields->SizeOfIncludingThis(aMallocSizeOf);
2372 // We don't recurse into mChildren because they're already counted in their
2373 // document's mAccessibles.
2374 size += mChildren.ShallowSizeOfExcludingThis(aMallocSizeOf);
2376 return size;
2379 } // namespace a11y
2380 } // namespace mozilla