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/. */
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"
27 #include "mozilla/a11y/RelationType.h"
28 #include "xpcAccessibleDocument.h"
32 # define VERIFY_CACHE(domain) \
33 if (logging::IsEnabled(logging::eCache)) { \
34 Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \
37 # define VERIFY_CACHE(domain) \
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());
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();
75 for (uint32_t idx
= 0; idx
< childCount
; idx
++) mChildren
[idx
]->Shutdown();
78 MOZ_CRASH("outer doc has too many documents!");
79 } else if (childCount
== 1) {
80 mChildren
[0]->AsDoc()->Unbind();
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()) {
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
) {
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()) {
140 if (index
== aChildIdx
) {
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");
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");
184 void RemoteAccessible::ApplyCache(CacheUpdateType aUpdateType
,
185 AccAttributes
* aFields
) {
187 MOZ_ASSERT_UNREACHABLE("ApplyCache called with aFields == null");
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.
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
;
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
);
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);
235 ENameValueFlag nameFlag
= eNameOK
;
238 mCachedFields
->GetAttribute(CacheKey::Text
, aName
);
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
);
252 MOZ_ASSERT(aName
.IsEmpty());
253 aName
.SetIsVoid(true);
257 void RemoteAccessible::Description(nsString
& aDescription
) const {
258 if (RequestDomainsIfInactive(CacheDomain::NameAndDescription
)) {
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
279 if (mCachedFields
->HasAttribute(CacheKey::TextValue
)) {
280 mCachedFields
->GetAttribute(CacheKey::TextValue
, aValue
);
281 VERIFY_CACHE(CacheDomain::Value
);
285 if (HasNumericValue()) {
286 double checkValue
= CurValue();
287 if (!std::isnan(checkValue
)) {
288 aValue
.AppendFloat(checkValue
);
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
);
301 // For combo boxes, rely on selection state to determine the value.
302 const Accessible
* option
=
303 const_cast<RemoteAccessible
*>(this)->GetSelectedItem(0);
305 option
->Name(aValue
);
307 // If no selected item, determine the value from descendant elements.
308 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue
);
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
318 return actionAcc
->Value(aValue
);
325 double RemoteAccessible::CurValue() const {
326 if (RequestDomainsIfInactive(CacheDomain::Value
)) {
327 return UnspecifiedNaN
<double>();
332 mCachedFields
->GetAttribute
<double>(CacheKey::NumericValue
)) {
333 VERIFY_CACHE(CacheDomain::Value
);
338 return UnspecifiedNaN
<double>();
341 double RemoteAccessible::MinValue() const {
342 if (RequestDomainsIfInactive(CacheDomain::Value
)) {
343 return UnspecifiedNaN
<double>();
347 if (auto min
= mCachedFields
->GetAttribute
<double>(CacheKey::MinValue
)) {
348 VERIFY_CACHE(CacheDomain::Value
);
353 return UnspecifiedNaN
<double>();
356 double RemoteAccessible::MaxValue() const {
357 if (RequestDomainsIfInactive(CacheDomain::Value
)) {
358 return UnspecifiedNaN
<double>();
362 if (auto max
= mCachedFields
->GetAttribute
<double>(CacheKey::MaxValue
)) {
363 VERIFY_CACHE(CacheDomain::Value
);
368 return UnspecifiedNaN
<double>();
371 double RemoteAccessible::Step() const {
372 if (RequestDomainsIfInactive(CacheDomain::Value
)) {
373 return UnspecifiedNaN
<double>();
377 if (auto step
= mCachedFields
->GetAttribute
<double>(CacheKey::Step
)) {
378 VERIFY_CACHE(CacheDomain::Value
);
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
394 if (!HasNumericValue() || IsProgress()) {
398 const uint32_t kValueCannotChange
= states::READONLY
| states::UNAVAILABLE
;
399 if (State() & kValueCannotChange
) {
403 double checkValue
= MinValue();
404 if (!std::isnan(checkValue
) && aValue
< checkValue
) {
408 checkValue
= MaxValue();
409 if (!std::isnan(checkValue
) && aValue
> checkValue
) {
413 Unused
<< mDoc
->SendSetCurValue(mID
, aValue
);
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
)) {
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.
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.
442 if (child
->ContainsPoint(aX
, aY
)) {
446 // None of our descendants contain the point, so nor do we.
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();
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.
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.
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
471 int32_t lineStart
= 0;
472 for (uint32_t index
= 0; index
<= length
; ++index
) {
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);
482 lineEnd
= nextLineStart
- 1;
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
)) {
495 lineStart
= lineEnd
+ 1;
500 RemoteAccessible
* RemoteAccessible::DoFuzzyHittesting() {
501 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds
);
503 uint32_t childCount
= ChildCount();
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
;
521 // If we found a clipped container, descend it in search of
522 // meaningful text leaves. Ignore non-text-leaf/text-container
524 RemoteAccessible
* container
= clippedContainer
;
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
) {
533 continueSearch
= true;
536 if (child
->IsTextLeaf()) {
538 // Don't break here -- it's possible a text container
539 // exists as another sibling, and we should descend as
546 if (!continueSearch
) {
547 // We didn't find anything useful in this set of siblings.
548 // Don't keep searching
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)
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();
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.
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
);
601 // This can happen if the acc died in between
602 // pushing the viewport cache and doing this hittest
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();
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;
622 // If there is no embedded document, the iframe itself is the deepest
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.
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.
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
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();
663 if (parent
== this) {
666 if (!parent
|| parent
->IsDoc()) {
667 // `this` is not an ancestor of lastMatch. Ignore lastMatch.
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`.
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`.
691 Maybe
<nsRect
> RemoteAccessible::RetrieveCachedBounds() const {
692 if (!mCachedFields
) {
696 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds
);
697 Maybe
<const nsTArray
<int32_t>&> maybeArray
=
698 mCachedFields
->GetAttribute
<nsTArray
<int32_t>>(
699 CacheKey::ParentRelativeBounds
);
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
);
712 void RemoteAccessible::ApplyCrossDocOffset(nsRect
& aBounds
) const {
714 // We should only apply cross-doc offsets to documents. If we're anything
715 // else, return early here.
719 RemoteAccessible
* parentAcc
= RemoteParent();
720 if (!parentAcc
|| !parentAcc
->IsOuterDoc()) {
724 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds
);
725 Maybe
<const nsTArray
<int32_t>&> maybeOffset
=
726 parentAcc
->mCachedFields
->GetAttribute
<nsTArray
<int32_t>>(
727 CacheKey::CrossDocOffset
);
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
) {
750 auto mtxInPixels
= gfx::Matrix4x4Typed
<CSSPixel
, CSSPixel
>::FromUnknownMatrix(
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
);
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) {
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
787 nsRect
RemoteAccessible::BoundsInAppUnits() const {
788 if (RequestDomainsIfInactive(kNecessaryBoundsDomains
)) {
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
;
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
;
827 bool RemoteAccessible::IsClipped() const {
828 ASSERT_DOMAINS_ACTIVE(CacheDomain::Bounds
);
829 MOZ_ASSERT(mCachedFields
);
830 if (mCachedFields
->GetAttribute
<bool>(CacheKey::IsClipped
)) {
837 LayoutDeviceIntRect
RemoteAccessible::BoundsWithOffset(
838 Maybe
<nsRect
> aOffset
, bool aBoundsAreForHittesting
) const {
839 if (RequestDomainsIfInactive(kNecessaryBoundsDomains
)) {
840 return LayoutDeviceIntRect
{};
843 Maybe
<nsRect
> maybeBounds
= RetrieveCachedBounds();
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.
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).
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.
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
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
984 devPxBounds
.MoveBy(localBounds
.X(), localBounds
.Y());
991 return LayoutDeviceIntRect();
994 LayoutDeviceIntRect
RemoteAccessible::Bounds() const {
995 if (RequestDomainsIfInactive(kNecessaryBoundsDomains
)) {
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
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());
1027 if (aType
== RelationType::LINKS_TO
&& Role() == roles::LINK
) {
1028 Pivot p
= Pivot(mDoc
);
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
)) {
1040 match
->DOMNodeID(currID
);
1041 MOZ_ASSERT(match
->IsRemote());
1042 if (anchorName
.Equals(currID
)) {
1043 return Relation(match
->AsRemote());
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
1056 return nameMatch
? Relation(nameMatch
->AsRemote()) : 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());
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));
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()) {
1101 RemoteAccessible
* ancestor
= RemoteParent();
1102 while (ancestor
&& ancestor
->Role() != roles::FORM
&& ancestor
!= mDoc
) {
1103 ancestor
= ancestor
->RemoteParent();
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
);
1113 rel
.AppendTarget(match
->AsRemote());
1114 match
= p
.Next(match
, rule
);
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
);
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.
1151 if (!mCachedFields
) {
1155 for (const auto& data
: kRelationTypeAtoms
) {
1156 if (data
.mType
!= aType
||
1157 (data
.mValidTag
&& TagName() != data
.mValidTag
)) {
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.
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
);
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
);
1202 void RemoteAccessible::AppendTextTo(nsAString
& aText
, uint32_t aStartOffset
,
1205 if (RequestDomainsIfInactive(CacheDomain::Text
)) {
1208 if (mCachedFields
) {
1209 if (auto text
= mCachedFields
->GetAttribute
<nsString
>(CacheKey::Text
)) {
1210 aText
.Append(Substring(*text
, aStartOffset
, aLength
));
1212 VERIFY_CACHE(CacheDomain::Text
);
1217 if (aStartOffset
!= 0 || aLength
== 0) {
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
1227 aText
+= kImaginaryEmbeddedObjectChar
;
1229 aText
+= kEmbeddedObjectChar
;
1233 nsTArray
<bool> RemoteAccessible::PreProcessRelations(AccAttributes
* aFields
) {
1234 if (!DomainsAreActive(CacheDomain::Relations
)) {
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();
1244 // TagName() returns null on an initial cache push -- check aFields
1245 // for a tag name instead.
1247 aFields
->GetAttribute
<RefPtr
<nsAtom
>>(CacheKey::TagName
)) {
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);
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
)) &&
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
)) {
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());
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
) {
1345 for (uint64_t id
: *reverseTargetList
) {
1346 // For each target, retrieve its corresponding forward relation target
1348 RemoteAccessible
* affectedAcc
= mDoc
->GetAccessible(id
);
1350 // It's possible the affect acc also shut down, in which case
1351 // we don't have anything to update.
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
)) {
1374 MOZ_ASSERT(!HasChildren());
1375 if (!mCachedFields
) {
1378 VERIFY_CACHE(CacheDomain::Text
);
1379 auto text
= mCachedFields
->GetAttribute
<nsString
>(CacheKey::Text
);
1383 return text
->Length();
1386 Maybe
<const nsTArray
<int32_t>&> RemoteAccessible::GetCachedTextLines() {
1387 if (RequestDomainsIfInactive(CacheDomain::TextBounds
)) {
1391 MOZ_ASSERT(!HasChildren());
1392 if (!mCachedFields
) {
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
) {
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()));
1424 void RemoteAccessible::DOMNodeID(nsString
& aID
) const {
1425 if (RequestDomainsIfInactive(CacheDomain::DOMNodeIDAndClass
)) {
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
,
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
) {
1458 mCachedFields
->GetAttribute
<int32_t>(CacheKey::ValueRegion
)) {
1462 // Expose sub-optimal (but not critical) as the value region, as a fallback.
1466 void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset
,
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
)) {
1478 MOZ_ASSERT(IsText() || IsHyperText());
1479 if (mCachedFields
) {
1480 auto attrs
= mCachedFields
->GetAttributeRefPtr
<AccAttributes
>(
1481 CacheKey::TextAttributes
);
1482 VERIFY_CACHE(CacheDomain::Text
);
1488 already_AddRefed
<AccAttributes
> RemoteAccessible::DefaultTextAttributes() {
1489 if (RequestDomainsIfInactive(CacheDomain::Text
)) {
1492 RefPtr
<const AccAttributes
> attrs
= GetCachedTextAttributes();
1493 RefPtr
<AccAttributes
> result
= new AccAttributes();
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
);
1511 nsString
RemoteAccessible::GetCachedHTMLNameAttribute() const {
1512 ASSERT_DOMAINS_ACTIVE(CacheDomain::Relations
);
1513 if (mCachedFields
) {
1514 if (auto maybeName
=
1515 mCachedFields
->GetAttribute
<nsString
>(CacheKey::DOMName
)) {
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
1531 if (mCachedFields
) {
1533 mCachedFields
->GetAttribute
<uint64_t>(CacheKey::State
)) {
1534 VERIFY_CACHE(CacheDomain::State
);
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
;
1553 // If we're in an active browsing context, there are a few scenarios we
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
1568 if (NS_WARN_IF(!docParent
|| !docParent
->IsRemote())) {
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
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
1590 state
|= states::OFFSCREEN
;
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
1612 return attributes
.forget();
1615 nsAccessibilityService
* accService
= GetAccService();
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.
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
,
1634 bool hierarchical
= false;
1635 uint32_t itemCount
= AccGroupInfo::TotalItemCount(this, &hierarchical
);
1637 attributes
->SetAttribute(nsGkAtoms::child_item_count
,
1638 static_cast<int32_t>(itemCount
));
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();
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
);
1682 // Unknown role or multiple roles.
1683 attributes
->SetAttribute(nsGkAtoms::xmlroles
, std::move(role
));
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);
1701 if (!id
.IsEmpty()) {
1702 attributes
->SetAttribute(nsGkAtoms::id
, std::move(id
));
1706 DOMNodeClass(className
);
1707 if (!className
.IsEmpty()) {
1708 attributes
->SetAttribute(nsGkAtoms::_class
, std::move(className
));
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
);
1730 mCachedFields
->GetAttribute(CacheKey::PopupType
, popupType
);
1731 if (!popupType
.IsEmpty()) {
1732 attributes
->SetAttribute(nsGkAtoms::ispopup
, std::move(popupType
));
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()) {
1750 attributes
->SetAttribute(nsGkAtoms::aria_valuetext
, std::move(valuetext
));
1753 return attributes
.forget();
1756 nsAtom
* RemoteAccessible::TagName() const {
1757 if (mCachedFields
) {
1759 mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(CacheKey::TagName
)) {
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();
1779 already_AddRefed
<nsAtom
> RemoteAccessible::DisplayStyle() const {
1780 if (RequestDomainsIfInactive(CacheDomain::Style
)) {
1783 if (mCachedFields
) {
1785 mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(CacheKey::CSSDisplay
)) {
1786 RefPtr
<nsAtom
> result
= *display
;
1787 return result
.forget();
1793 float RemoteAccessible::Opacity() const {
1794 if (RequestDomainsIfInactive(CacheDomain::Style
)) {
1798 if (mCachedFields
) {
1799 if (auto opacity
= mCachedFields
->GetAttribute
<float>(CacheKey::Opacity
)) {
1807 void RemoteAccessible::LiveRegionAttributes(nsAString
* aLive
,
1808 nsAString
* aRelevant
,
1809 Maybe
<bool>* aAtomic
,
1810 nsAString
* aBusy
) const {
1811 if (RequestDomainsIfInactive(CacheDomain::ARIA
)) {
1814 if (!mCachedFields
) {
1817 RefPtr
<const AccAttributes
> attrs
= GetCachedARIAAttributes();
1822 attrs
->GetAttribute(nsGkAtoms::aria_live
, *aLive
);
1825 attrs
->GetAttribute(nsGkAtoms::aria_relevant
, *aRelevant
);
1829 attrs
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::aria_atomic
)) {
1830 *aAtomic
= Some(*value
== nsGkAtoms::_true
);
1834 attrs
->GetAttribute(nsGkAtoms::aria_busy
, *aBusy
);
1838 Maybe
<bool> RemoteAccessible::ARIASelected() const {
1839 if (RequestDomainsIfInactive(CacheDomain::State
)) {
1843 if (mCachedFields
) {
1844 return mCachedFields
->GetAttribute
<bool>(CacheKey::ARIASelected
);
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
)) {
1861 uint8_t RemoteAccessible::ActionCount() const {
1862 uint8_t actionCount
= 0;
1863 if (RequestDomainsIfInactive(CacheDomain::Actions
)) {
1866 if (mCachedFields
) {
1867 if (HasPrimaryAction() || ActionAncestor()) {
1871 if (mCachedFields
->HasAttribute(CacheKey::HasLongdesc
)) {
1874 VERIFY_CACHE(CacheDomain::Actions
);
1880 void RemoteAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
1881 if (RequestDomainsIfInactive(CacheDomain::Actions
)) {
1885 if (mCachedFields
) {
1887 nsAtom
* action
= GetPrimaryAction();
1888 bool hasActionAncestor
= !action
&& ActionAncestor();
1893 action
->ToString(aName
);
1894 } else if (hasActionAncestor
) {
1895 aName
.AssignLiteral("clickAncestor");
1896 } else if (mCachedFields
->HasAttribute(CacheKey::HasLongdesc
)) {
1897 aName
.AssignLiteral("showlongdesc");
1901 if ((action
|| hasActionAncestor
) &&
1902 mCachedFields
->HasAttribute(CacheKey::HasLongdesc
)) {
1903 aName
.AssignLiteral("showlongdesc");
1910 VERIFY_CACHE(CacheDomain::Actions
);
1913 bool RemoteAccessible::DoAction(uint8_t aIndex
) const {
1914 if (RequestDomainsIfInactive(CacheDomain::Actions
)) {
1918 if (ActionCount() < aIndex
+ 1) {
1922 Unused
<< mDoc
->SendDoActionAsync(mID
, aIndex
);
1926 KeyBinding
RemoteAccessible::AccessKey() const {
1927 if (RequestDomainsIfInactive(CacheDomain::Actions
)) {
1931 if (mCachedFields
) {
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
) {
1950 Unused
<< mDoc
->SendRemoveTextSelection(mID
, aSelectionNum
);
1955 void RemoteAccessible::ARIAGroupPosition(int32_t* aLevel
, int32_t* aSetSize
,
1956 int32_t* aPosInSet
) const {
1957 if (RequestDomainsIfInactive(CacheDomain::GroupInfo
)) {
1961 if (!mCachedFields
) {
1967 mCachedFields
->GetAttribute
<int32_t>(nsGkAtoms::aria_level
)) {
1973 mCachedFields
->GetAttribute
<int32_t>(nsGkAtoms::aria_setsize
)) {
1974 *aSetSize
= *setsize
;
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
)) {
1993 if (!mCachedFields
) {
1997 if (auto groupInfo
= mCachedFields
->GetAttribute
<UniquePtr
<AccGroupInfo
>>(
1998 CacheKey::GroupInfo
)) {
1999 return groupInfo
->get();
2005 AccGroupInfo
* RemoteAccessible::GetOrCreateGroupInfo() {
2006 if (RequestDomainsIfInactive(CacheDomain::GroupInfo
)) {
2010 AccGroupInfo
* groupInfo
= GetGroupInfo();
2015 groupInfo
= AccGroupInfo::CreateGroupInfo(this);
2017 if (!mCachedFields
) {
2018 mCachedFields
= new AccAttributes();
2021 mCachedFields
->SetAttribute(CacheKey::GroupInfo
, 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
)) {
2042 if (IsHTMLRadioButton()) {
2044 Relation rel
= RelationByType(RelationType::MEMBER_OF
);
2045 while (Accessible
* radio
= rel
.Next()) {
2047 if (radio
== this) {
2048 *aPosInSet
= *aSetSize
;
2054 Accessible::GetPositionAndSetSize(aPosInSet
, aSetSize
);
2057 bool RemoteAccessible::HasPrimaryAction() const {
2058 if (RequestDomainsIfInactive(CacheDomain::Actions
)) {
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());
2068 if (nsFocusManager::GetFocusedElementStatic() == bp
->GetOwnerElement()) {
2069 // This remote document tree is already focused. We don't need to do
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();
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
2089 embedder
->TakeFocus();
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 ////////////////////////////////////////////////////////////////////////////////
2110 void RemoteAccessible::SelectedItems(nsTArray
<Accessible
*>* aItems
) {
2111 if (RequestDomainsIfInactive(kNecessaryStateDomains
)) {
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
)) {
2127 Pivot p
= Pivot(this);
2128 PivotStateRule
rule(states::SELECTED
);
2129 for (Accessible
* selected
= p
.First(rule
); selected
;
2130 selected
= p
.Next(selected
, rule
)) {
2137 Accessible
* RemoteAccessible::GetSelectedItem(uint32_t aIndex
) {
2138 if (RequestDomainsIfInactive(kNecessaryStateDomains
)) {
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
)) {
2153 bool RemoteAccessible::IsItemSelected(uint32_t aIndex
) {
2154 if (RequestDomainsIfInactive(kNecessaryStateDomains
)) {
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
)) {
2166 return selectable
&& selectable
->State() & states::SELECTED
;
2169 bool RemoteAccessible::AddItemToSelection(uint32_t aIndex
) {
2170 if (RequestDomainsIfInactive(kNecessaryStateDomains
)) {
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
)) {
2182 if (selectable
) selectable
->SetSelected(true);
2184 return static_cast<bool>(selectable
);
2187 bool RemoteAccessible::RemoveItemFromSelection(uint32_t aIndex
) {
2188 if (RequestDomainsIfInactive(kNecessaryStateDomains
)) {
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
)) {
2200 if (selectable
) selectable
->SetSelected(false);
2202 return static_cast<bool>(selectable
);
2205 bool RemoteAccessible::SelectAll() {
2206 if (RequestDomainsIfInactive(kNecessaryStateDomains
)) {
2209 if ((State() & states::MULTISELECTABLE
) == 0) {
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
)) {
2220 selectable
->SetSelected(true);
2225 bool RemoteAccessible::UnselectAll() {
2226 if (RequestDomainsIfInactive(kNecessaryStateDomains
)) {
2229 if ((State() & states::MULTISELECTABLE
) == 0) {
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
)) {
2240 selectable
->SetSelected(false);
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() {
2255 return CachedTableAccessible::GetFrom(this);
2260 TableCellAccessible
* RemoteAccessible::AsTableCell() {
2261 if (IsTableCell()) {
2262 return CachedTableCellAccessible::GetFrom(this);
2267 bool RemoteAccessible::TableIsProbablyForLayout() {
2268 if (RequestDomainsIfInactive(CacheDomain::Table
)) {
2271 if (mCachedFields
) {
2272 if (auto layoutGuess
=
2273 mCachedFields
->GetAttribute
<bool>(CacheKey::TableLayoutGuess
)) {
2274 return *layoutGuess
;
2280 nsTArray
<int32_t>& RemoteAccessible::GetCachedHyperTextOffsets() {
2281 if (mCachedFields
) {
2282 if (auto offsets
= mCachedFields
->GetMutableAttribute
<nsTArray
<int32_t>>(
2283 CacheKey::HyperTextOffsets
)) {
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
)) {
2305 if (RefPtr
<const AccAttributes
> attrs
= GetCachedARIAAttributes()) {
2306 if (auto val
= attrs
->GetAttribute
<int32_t>(aAttrName
)) {
2313 void RemoteAccessible::Language(nsAString
& aLocale
) {
2314 if (RequestDomainsIfInactive(CacheDomain::Text
)) {
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
) {
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
);
2380 } // namespace mozilla