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 "DocAccessible.h"
10 #include "mozilla/a11y/DocAccessibleParent.h"
11 #include "mozilla/a11y/DocManager.h"
12 #include "mozilla/a11y/Platform.h"
13 #include "mozilla/a11y/RemoteAccessibleBase.h"
14 #include "mozilla/a11y/RemoteAccessible.h"
15 #include "mozilla/a11y/Role.h"
16 #include "mozilla/BinarySearch.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/dom/BrowserParent.h"
19 #include "mozilla/dom/CanonicalBrowsingContext.h"
20 #include "mozilla/dom/DocumentInlines.h"
21 #include "mozilla/gfx/Matrix.h"
22 #include "nsAccessibilityService.h"
23 #include "mozilla/Unused.h"
24 #include "nsAccUtils.h"
25 #include "nsTextEquivUtils.h"
28 #include "RelationType.h"
29 #include "TextLeafRange.h"
30 #include "xpcAccessibleDocument.h"
34 # define VERIFY_CACHE(domain) \
35 if (logging::IsEnabled(logging::eCache)) { \
36 Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \
39 # define VERIFY_CACHE(domain) \
48 template <class Derived
>
49 void RemoteAccessibleBase
<Derived
>::Shutdown() {
50 MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
51 xpcAccessibleDocument
* xpcDoc
=
52 GetAccService()->GetCachedXPCDocument(Document());
54 xpcDoc
->NotifyOfShutdown(static_cast<Derived
*>(this));
57 if (IsTable() || IsTableCell()) {
58 CachedTableAccessible::Invalidate(this);
61 if (a11y::IsCacheActive()) {
62 // Remove this acc's relation map from the doc's map of
63 // reverse relations. Prune forward relations associated with this
64 // acc's reverse relations. This also removes the acc's map of reverse
65 // rels from the mDoc's mReverseRelations.
66 PruneRelationsOnShutdown();
69 // XXX Ideally this wouldn't be necessary, but it seems OuterDoc
70 // accessibles can be destroyed before the doc they own.
71 uint32_t childCount
= mChildren
.Length();
73 for (uint32_t idx
= 0; idx
< childCount
; idx
++) mChildren
[idx
]->Shutdown();
76 MOZ_CRASH("outer doc has too many documents!");
77 } else if (childCount
== 1) {
78 mChildren
[0]->AsDoc()->Unbind();
83 ProxyDestroyed(static_cast<Derived
*>(this));
84 mDoc
->RemoveAccessible(static_cast<Derived
*>(this));
87 template <class Derived
>
88 void RemoteAccessibleBase
<Derived
>::SetChildDoc(
89 DocAccessibleParent
* aChildDoc
) {
90 MOZ_ASSERT(aChildDoc
);
91 MOZ_ASSERT(mChildren
.Length() == 0);
92 mChildren
.AppendElement(aChildDoc
);
95 template <class Derived
>
96 void RemoteAccessibleBase
<Derived
>::ClearChildDoc(
97 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 template <class Derived
>
108 uint32_t RemoteAccessibleBase
<Derived
>::EmbeddedChildCount() {
109 size_t count
= 0, kids
= mChildren
.Length();
110 for (size_t i
= 0; i
< kids
; i
++) {
111 if (mChildren
[i
]->IsEmbeddedObject()) {
119 template <class Derived
>
120 int32_t RemoteAccessibleBase
<Derived
>::IndexOfEmbeddedChild(
121 Accessible
* aChild
) {
122 size_t index
= 0, kids
= mChildren
.Length();
123 for (size_t i
= 0; i
< kids
; i
++) {
124 if (mChildren
[i
]->IsEmbeddedObject()) {
125 if (mChildren
[i
] == aChild
) {
136 template <class Derived
>
137 Accessible
* RemoteAccessibleBase
<Derived
>::EmbeddedChildAt(uint32_t aChildIdx
) {
138 size_t index
= 0, kids
= mChildren
.Length();
139 for (size_t i
= 0; i
< kids
; i
++) {
140 if (!mChildren
[i
]->IsEmbeddedObject()) {
144 if (index
== aChildIdx
) {
154 template <class Derived
>
155 LocalAccessible
* RemoteAccessibleBase
<Derived
>::OuterDocOfRemoteBrowser()
157 auto tab
= static_cast<dom::BrowserParent
*>(mDoc
->Manager());
158 dom::Element
* frame
= tab
->GetOwnerElement();
159 NS_ASSERTION(frame
, "why isn't the tab in a frame!");
160 if (!frame
) return nullptr;
162 DocAccessible
* chromeDoc
= GetExistingDocAccessible(frame
->OwnerDoc());
164 return chromeDoc
? chromeDoc
->GetAccessible(frame
) : nullptr;
167 template <class Derived
>
168 void RemoteAccessibleBase
<Derived
>::SetParent(Derived
* aParent
) {
172 MOZ_ASSERT(!IsDoc() || !aParent
->IsDoc());
173 mParent
= aParent
->ID();
177 template <class Derived
>
178 Derived
* RemoteAccessibleBase
<Derived
>::RemoteParent() const {
179 if (mParent
== kNoParent
) {
183 // if we are not a document then are parent is another proxy in the same
184 // document. That means we can just ask our document for the proxy with our
187 return Document()->GetAccessible(mParent
);
190 // If we are a top level document then our parent is not a proxy.
191 if (AsDoc()->IsTopLevel()) {
195 // Finally if we are a non top level document then our parent id is for a
196 // proxy in our parent document so get the proxy from there.
197 DocAccessibleParent
* parentDoc
= AsDoc()->ParentDoc();
198 MOZ_ASSERT(parentDoc
);
200 return parentDoc
->GetAccessible(mParent
);
203 template <class Derived
>
204 ENameValueFlag RemoteAccessibleBase
<Derived
>::Name(nsString
& aName
) const {
205 ENameValueFlag nameFlag
= eNameOK
;
208 mCachedFields
->GetAttribute(nsGkAtoms::text
, aName
);
211 auto cachedNameFlag
=
212 mCachedFields
->GetAttribute
<int32_t>(nsGkAtoms::explicit_name
);
213 if (cachedNameFlag
) {
214 nameFlag
= static_cast<ENameValueFlag
>(*cachedNameFlag
);
216 if (mCachedFields
->GetAttribute(nsGkAtoms::name
, aName
)) {
217 VERIFY_CACHE(CacheDomain::NameAndDescription
);
222 MOZ_ASSERT(aName
.IsEmpty());
223 aName
.SetIsVoid(true);
227 template <class Derived
>
228 void RemoteAccessibleBase
<Derived
>::Description(nsString
& aDescription
) const {
230 mCachedFields
->GetAttribute(nsGkAtoms::description
, aDescription
);
231 VERIFY_CACHE(CacheDomain::NameAndDescription
);
235 template <class Derived
>
236 void RemoteAccessibleBase
<Derived
>::Value(nsString
& aValue
) const {
238 if (mCachedFields
->HasAttribute(nsGkAtoms::aria_valuetext
)) {
239 mCachedFields
->GetAttribute(nsGkAtoms::aria_valuetext
, aValue
);
240 VERIFY_CACHE(CacheDomain::Value
);
244 if (HasNumericValue()) {
245 double checkValue
= CurValue();
246 if (!std::isnan(checkValue
)) {
247 aValue
.AppendFloat(checkValue
);
252 const nsRoleMapEntry
* roleMapEntry
= ARIARoleMap();
253 // Value of textbox is a textified subtree.
254 if (roleMapEntry
&& roleMapEntry
->Is(nsGkAtoms::textbox
)) {
255 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue
);
260 // For combo boxes, rely on selection state to determine the value.
261 const Accessible
* option
=
262 const_cast<RemoteAccessibleBase
<Derived
>*>(this)->GetSelectedItem(0);
264 option
->Name(aValue
);
266 // If no selected item, determine the value from descendant elements.
267 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue
);
272 if (IsTextLeaf() || IsImage()) {
273 if (const Accessible
* actionAcc
= ActionAncestor()) {
274 if (const_cast<Accessible
*>(actionAcc
)->State() & states::LINKED
) {
275 // Text and image descendants of links expose the link URL as the
277 return actionAcc
->Value(aValue
);
284 template <class Derived
>
285 double RemoteAccessibleBase
<Derived
>::CurValue() const {
287 if (auto value
= mCachedFields
->GetAttribute
<double>(nsGkAtoms::value
)) {
288 VERIFY_CACHE(CacheDomain::Value
);
293 return UnspecifiedNaN
<double>();
296 template <class Derived
>
297 double RemoteAccessibleBase
<Derived
>::MinValue() const {
299 if (auto min
= mCachedFields
->GetAttribute
<double>(nsGkAtoms::min
)) {
300 VERIFY_CACHE(CacheDomain::Value
);
305 return UnspecifiedNaN
<double>();
308 template <class Derived
>
309 double RemoteAccessibleBase
<Derived
>::MaxValue() const {
311 if (auto max
= mCachedFields
->GetAttribute
<double>(nsGkAtoms::max
)) {
312 VERIFY_CACHE(CacheDomain::Value
);
317 return UnspecifiedNaN
<double>();
320 template <class Derived
>
321 double RemoteAccessibleBase
<Derived
>::Step() const {
323 if (auto step
= mCachedFields
->GetAttribute
<double>(nsGkAtoms::step
)) {
324 VERIFY_CACHE(CacheDomain::Value
);
329 return UnspecifiedNaN
<double>();
332 template <class Derived
>
333 bool RemoteAccessibleBase
<Derived
>::SetCurValue(double aValue
) {
334 if (!HasNumericValue() || IsProgress()) {
338 if (a11y::IsCacheActive()) {
339 // XXX: If cache is disabled there is a slight regression
340 // where we don't check the readonly/unavailable state or the min/max
341 // values. This will go away once cache is enabled by default.
342 const uint32_t kValueCannotChange
= states::READONLY
| states::UNAVAILABLE
;
343 if (State() & kValueCannotChange
) {
347 double checkValue
= MinValue();
348 if (!std::isnan(checkValue
) && aValue
< checkValue
) {
352 checkValue
= MaxValue();
353 if (!std::isnan(checkValue
) && aValue
> checkValue
) {
358 Unused
<< mDoc
->SendSetCurValue(mID
, aValue
);
362 template <class Derived
>
363 bool RemoteAccessibleBase
<Derived
>::ContainsPoint(int32_t aX
, int32_t aY
) {
364 if (!BoundsWithOffset(Nothing(), true).Contains(aX
, aY
)) {
370 // This is a text leaf. The text might wrap across lines, which means our
371 // rect might cover a wider area than the actual text. For example, if the
372 // text begins in the middle of the first line and wraps on to the second,
373 // the rect will cover the start of the first line and the end of the second.
374 auto lines
= GetCachedTextLines();
376 // This means the text is empty or occupies a single line (but does not
377 // begin the line). In that case, the Bounds check above is sufficient,
378 // since there's only one rect.
381 uint32_t length
= lines
->Length();
382 MOZ_ASSERT(length
> 0,
383 "Line starts shouldn't be in cache if there aren't any");
384 if (length
== 0 || (length
== 1 && (*lines
)[0] == 0)) {
385 // This means the text begins and occupies a single line. Again, the Bounds
386 // check above is sufficient.
389 // Walk the lines of the text. Even if this text doesn't start at the
390 // beginning of a line (i.e. lines[0] > 0), we always want to consider its
392 int32_t lineStart
= 0;
393 for (uint32_t index
= 0; index
<= length
; ++index
) {
395 if (index
< length
) {
396 int32_t nextLineStart
= (*lines
)[index
];
397 if (nextLineStart
== 0) {
398 // This Accessible starts at the beginning of a line. Here, we always
399 // treat 0 as the first line start anyway.
400 MOZ_ASSERT(index
== 0);
403 lineEnd
= nextLineStart
- 1;
405 // This is the last line.
406 lineEnd
= static_cast<int32_t>(nsAccUtils::TextLength(this)) - 1;
408 MOZ_ASSERT(lineEnd
>= lineStart
);
409 nsRect lineRect
= GetCachedCharRect(lineStart
);
410 if (lineEnd
> lineStart
) {
411 lineRect
.UnionRect(lineRect
, GetCachedCharRect(lineEnd
));
413 if (BoundsWithOffset(Some(lineRect
), true).Contains(aX
, aY
)) {
416 lineStart
= lineEnd
+ 1;
421 template <class Derived
>
422 Accessible
* RemoteAccessibleBase
<Derived
>::ChildAtPoint(
423 int32_t aX
, int32_t aY
, LocalAccessible::EWhichChildAtPoint aWhichChild
) {
424 // Elements that are partially on-screen should have their bounds masked by
425 // their containing scroll area so hittesting yields results that are
426 // consistent with the content's visual representation. Pass this value to
427 // bounds calculation functions to indicate that we're hittesting.
428 const bool hitTesting
= true;
430 if (IsOuterDoc() && aWhichChild
== EWhichChildAtPoint::DirectChild
) {
431 // This is an iframe, which is as deep as the viewport cache goes. The
432 // caller wants a direct child, which can only be the embedded document.
433 if (BoundsWithOffset(Nothing(), hitTesting
).Contains(aX
, aY
)) {
434 return RemoteFirstChild();
439 RemoteAccessible
* lastMatch
= nullptr;
440 // If `this` is a document, use its viewport cache instead of
441 // the cache of its parent document.
442 if (DocAccessibleParent
* doc
= IsDoc() ? AsDoc() : mDoc
) {
443 if (!doc
->mCachedFields
) {
444 // A client call might arrive after we've constructed doc but before we
445 // get a cache push for it.
448 if (auto maybeViewportCache
=
449 doc
->mCachedFields
->GetAttribute
<nsTArray
<uint64_t>>(
450 nsGkAtoms::viewport
)) {
451 // The retrieved viewport cache contains acc IDs in hittesting order.
452 // That is, items earlier in the list have z-indexes that are larger than
453 // those later in the list. If you were to build a tree by z-index, where
454 // chilren have larger z indices than their parents, iterating this list
455 // is essentially a postorder tree traversal.
456 const nsTArray
<uint64_t>& viewportCache
= *maybeViewportCache
;
458 for (auto id
: viewportCache
) {
459 RemoteAccessible
* acc
= doc
->GetAccessible(id
);
461 // This can happen if the acc died in between
462 // pushing the viewport cache and doing this hittest
466 if (acc
->IsOuterDoc() &&
467 aWhichChild
== EWhichChildAtPoint::DeepestChild
&&
468 acc
->BoundsWithOffset(Nothing(), hitTesting
).Contains(aX
, aY
)) {
469 // acc is an iframe, which is as deep as the viewport cache goes. This
470 // iframe contains the requested point.
471 RemoteAccessible
* innerDoc
= acc
->RemoteFirstChild();
473 MOZ_ASSERT(innerDoc
->IsDoc());
474 // Search the embedded document's viewport cache so we return the
475 // deepest descendant in that embedded document.
476 Accessible
* deepestAcc
= innerDoc
->ChildAtPoint(
477 aX
, aY
, EWhichChildAtPoint::DeepestChild
);
478 MOZ_ASSERT(!deepestAcc
|| deepestAcc
->IsRemote());
479 lastMatch
= deepestAcc
? deepestAcc
->AsRemote() : nullptr;
482 // If there is no embedded document, the iframe itself is the deepest
489 MOZ_ASSERT(!acc
->IsOuterDoc());
490 // Even though we're searching from the doc's cache
491 // this call shouldn't pass the boundary defined by
492 // the acc this call originated on. If we hit `this`,
493 // return our most recent match.
497 if (acc
->ContainsPoint(aX
, aY
)) {
498 // Because our rects are in hittesting order, the
499 // first match we encounter is guaranteed to be the
508 if (aWhichChild
== EWhichChildAtPoint::DirectChild
&& lastMatch
) {
509 // lastMatch is the deepest match. Walk up to the direct child of this.
510 RemoteAccessible
* parent
= lastMatch
->RemoteParent();
512 if (parent
== this) {
515 if (!parent
|| parent
->IsDoc()) {
516 // `this` is not an ancestor of lastMatch. Ignore lastMatch.
521 parent
= parent
->RemoteParent();
523 } else if (aWhichChild
== EWhichChildAtPoint::DeepestChild
&& lastMatch
&&
524 !IsDoc() && !IsAncestorOf(lastMatch
)) {
525 // If we end up with a match that is not in the ancestor chain
526 // of the accessible this call originated on, we should ignore it.
527 // This can happen when the aX, aY given are outside `this`.
531 if (!lastMatch
&& BoundsWithOffset(Nothing(), hitTesting
).Contains(aX
, aY
)) {
532 // Even though the hit target isn't inside `this`, the point is still
533 // within our bounds, so fall back to `this`.
540 template <class Derived
>
541 Maybe
<nsRect
> RemoteAccessibleBase
<Derived
>::RetrieveCachedBounds() const {
542 if (!mCachedFields
) {
546 Maybe
<const nsTArray
<int32_t>&> maybeArray
=
547 mCachedFields
->GetAttribute
<nsTArray
<int32_t>>(nsGkAtoms::relativeBounds
);
549 const nsTArray
<int32_t>& relativeBoundsArr
= *maybeArray
;
550 MOZ_ASSERT(relativeBoundsArr
.Length() == 4,
551 "Incorrectly sized bounds array");
552 nsRect
relativeBoundsRect(relativeBoundsArr
[0], relativeBoundsArr
[1],
553 relativeBoundsArr
[2], relativeBoundsArr
[3]);
554 return Some(relativeBoundsRect
);
560 template <class Derived
>
561 void RemoteAccessibleBase
<Derived
>::ApplyCrossDocOffset(nsRect
& aBounds
) const {
563 // We should only apply cross-doc offsets to documents. If we're anything
564 // else, return early here.
568 RemoteAccessible
* parentAcc
= RemoteParent();
569 if (!parentAcc
|| !parentAcc
->IsOuterDoc()) {
573 Maybe
<const nsTArray
<int32_t>&> maybeOffset
=
574 parentAcc
->mCachedFields
->GetAttribute
<nsTArray
<int32_t>>(
575 nsGkAtoms::crossorigin
);
580 MOZ_ASSERT(maybeOffset
->Length() == 2);
581 const nsTArray
<int32_t>& offset
= *maybeOffset
;
582 // Our retrieved value is in app units, so we don't need to do any
583 // unit conversion here.
584 aBounds
.MoveBy(offset
[0], offset
[1]);
587 template <class Derived
>
588 bool RemoteAccessibleBase
<Derived
>::ApplyTransform(
589 nsRect
& aCumulativeBounds
) const {
590 // First, attempt to retrieve the transform from the cache.
591 Maybe
<const UniquePtr
<gfx::Matrix4x4
>&> maybeTransform
=
592 mCachedFields
->GetAttribute
<UniquePtr
<gfx::Matrix4x4
>>(
593 nsGkAtoms::transform
);
594 if (!maybeTransform
) {
598 auto mtxInPixels
= gfx::Matrix4x4Typed
<CSSPixel
, CSSPixel
>::FromUnknownMatrix(
601 // Our matrix is in CSS Pixels, so we need our rect to be in CSS
602 // Pixels too. Convert before applying.
603 auto boundsInPixels
= CSSRect::FromAppUnits(aCumulativeBounds
);
604 boundsInPixels
= mtxInPixels
.TransformBounds(boundsInPixels
);
605 aCumulativeBounds
= CSSRect::ToAppUnits(boundsInPixels
);
610 template <class Derived
>
611 bool RemoteAccessibleBase
<Derived
>::ApplyScrollOffset(nsRect
& aBounds
) const {
612 Maybe
<const nsTArray
<int32_t>&> maybeScrollPosition
=
613 mCachedFields
->GetAttribute
<nsTArray
<int32_t>>(nsGkAtoms::scrollPosition
);
615 if (!maybeScrollPosition
|| maybeScrollPosition
->Length() != 2) {
618 // Our retrieved value is in app units, so we don't need to do any
619 // unit conversion here.
620 const nsTArray
<int32_t>& scrollPosition
= *maybeScrollPosition
;
622 // Scroll position is an inverse representation of scroll offset (since the
623 // further the scroll bar moves down the page, the further the page content
624 // moves up/closer to the origin).
625 nsPoint
scrollOffset(-scrollPosition
[0], -scrollPosition
[1]);
627 aBounds
.MoveBy(scrollOffset
.x
, scrollOffset
.y
);
629 // Return true here even if the scroll offset was 0,0 because the RV is used
630 // as a scroll container indicator. Non-scroll containers won't have cached
635 template <class Derived
>
636 nsRect RemoteAccessibleBase
<Derived
>::BoundsInAppUnits() const {
637 if (dom::CanonicalBrowsingContext
* cbc
= mDoc
->GetBrowsingContext()->Top()) {
638 if (dom::BrowserParent
* bp
= cbc
->GetBrowserParent()) {
639 DocAccessibleParent
* topDoc
= bp
->GetTopLevelDocAccessible();
640 if (topDoc
&& topDoc
->mCachedFields
) {
641 auto appUnitsPerDevPixel
= topDoc
->mCachedFields
->GetAttribute
<int32_t>(
642 nsGkAtoms::_moz_device_pixel_ratio
);
643 MOZ_ASSERT(appUnitsPerDevPixel
);
644 return LayoutDeviceIntRect::ToAppUnits(Bounds(), *appUnitsPerDevPixel
);
648 return LayoutDeviceIntRect::ToAppUnits(Bounds(), AppUnitsPerCSSPixel());
651 template <class Derived
>
652 bool RemoteAccessibleBase
<Derived
>::IsFixedPos() const {
653 MOZ_ASSERT(mCachedFields
);
654 if (auto maybePosition
=
655 mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::position
)) {
656 return *maybePosition
== nsGkAtoms::fixed
;
662 template <class Derived
>
663 bool RemoteAccessibleBase
<Derived
>::IsOverflowHidden() const {
664 MOZ_ASSERT(mCachedFields
);
665 if (auto maybeOverflow
=
666 mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::overflow
)) {
667 return *maybeOverflow
== nsGkAtoms::hidden
;
673 template <class Derived
>
674 LayoutDeviceIntRect RemoteAccessibleBase
<Derived
>::BoundsWithOffset(
675 Maybe
<nsRect
> aOffset
, bool aBoundsAreForHittesting
) const {
676 Maybe
<nsRect
> maybeBounds
= RetrieveCachedBounds();
678 nsRect bounds
= *maybeBounds
;
679 // maybeBounds is parent-relative. However, the transform matrix we cache
680 // (if any) is meant to operate on self-relative rects. Therefore, make
681 // bounds self-relative until after we transform.
683 const DocAccessibleParent
* topDoc
= IsDoc() ? AsDoc() : nullptr;
685 if (aOffset
.isSome()) {
686 // The rect we've passed in is in app units, so no conversion needed.
687 nsRect internalRect
= *aOffset
;
688 bounds
.SetRectX(bounds
.x
+ internalRect
.x
, internalRect
.width
);
689 bounds
.SetRectY(bounds
.y
+ internalRect
.y
, internalRect
.height
);
692 Unused
<< ApplyTransform(bounds
);
693 // Now apply the parent-relative offset.
694 bounds
.MoveBy(maybeBounds
->TopLeft());
696 ApplyCrossDocOffset(bounds
);
698 LayoutDeviceIntRect devPxBounds
;
699 const Accessible
* acc
= Parent();
700 bool encounteredFixedContainer
= IsFixedPos();
701 while (acc
&& acc
->IsRemote()) {
702 // Return early if we're hit testing and our cumulative bounds are empty,
703 // since walking the ancestor chain won't produce any hits.
704 if (aBoundsAreForHittesting
&& bounds
.IsEmpty()) {
705 return LayoutDeviceIntRect
{};
708 RemoteAccessible
* remoteAcc
= const_cast<Accessible
*>(acc
)->AsRemote();
710 if (Maybe
<nsRect
> maybeRemoteBounds
= remoteAcc
->RetrieveCachedBounds()) {
711 nsRect remoteBounds
= *maybeRemoteBounds
;
712 // We need to take into account a non-1 resolution set on the
713 // presshell. This happens with async pinch zooming, among other
714 // things. We can't reliably query this value in the parent process,
715 // so we retrieve it from the document's cache.
716 if (remoteAcc
->IsDoc()) {
717 // Apply the document's resolution to the bounds we've gathered
718 // thus far. We do this before applying the document's offset
719 // because document accs should not have their bounds scaled by
720 // their own resolution. They should be scaled by the resolution
721 // of their containing document (if any).
723 remoteAcc
->AsDoc()->mCachedFields
->GetAttribute
<float>(
724 nsGkAtoms::resolution
);
725 MOZ_ASSERT(res
, "No cached document resolution found.");
726 bounds
.ScaleRoundOut(res
.valueOr(1.0f
));
728 topDoc
= remoteAcc
->AsDoc();
731 // We don't account for the document offset of iframes when
732 // computing parent-relative bounds. Instead, we store this value
733 // separately on all iframes and apply it here. See the comments in
734 // LocalAccessible::BundleFieldsForCache where we set the
735 // nsGkAtoms::crossorigin attribute.
736 remoteAcc
->ApplyCrossDocOffset(remoteBounds
);
737 if (!encounteredFixedContainer
) {
738 // Apply scroll offset, if applicable. Only the contents of an
739 // element are affected by its scroll offset, which is why this call
740 // happens in this loop instead of both inside and outside of
741 // the loop (like ApplyTransform).
742 // Never apply scroll offsets past a fixed container.
743 const bool hasScrollArea
= remoteAcc
->ApplyScrollOffset(bounds
);
745 // If we are hit testing and the Accessible has a scroll area, ensure
746 // that the bounds we've calculated so far are constrained to the
747 // bounds of the scroll area. Without this, we'll "hit" the off-screen
748 // portions of accs that are are partially (but not fully) within the
749 // scroll area. This is also a problem for accs with overflow:hidden;
750 if (aBoundsAreForHittesting
&&
751 (hasScrollArea
|| remoteAcc
->IsOverflowHidden())) {
752 nsRect
selfRelativeVisibleBounds(0, 0, remoteBounds
.width
,
753 remoteBounds
.height
);
754 bounds
= bounds
.SafeIntersect(selfRelativeVisibleBounds
);
757 if (remoteAcc
->IsDoc()) {
758 // Fixed elements are document relative, so if we've hit a
759 // document we're now subject to that document's styling
760 // (including scroll offsets that operate on it).
761 // This ordering is important, we don't want to apply scroll
762 // offsets on this doc's content.
763 encounteredFixedContainer
= false;
765 if (!encounteredFixedContainer
) {
766 // The transform matrix we cache (if any) is meant to operate on
767 // self-relative rects. Therefore, we must apply the transform before
768 // we make bounds parent-relative.
769 Unused
<< remoteAcc
->ApplyTransform(bounds
);
770 // Regardless of whether this is a doc, we should offset `bounds`
771 // by the bounds retrieved here. This is how we build screen
772 // coordinates from relative coordinates.
773 bounds
.MoveBy(remoteBounds
.X(), remoteBounds
.Y());
776 if (remoteAcc
->IsFixedPos()) {
777 encounteredFixedContainer
= true;
779 // we can't just break here if we're scroll suppressed because we still
780 // need to find the top doc.
787 // We use the top documents app-units-per-dev-pixel even though
788 // theoretically nested docs can have different values. Practically,
789 // that isn't likely since we only offer zoom controls for the top
790 // document and all subdocuments inherit from it.
791 auto appUnitsPerDevPixel
= topDoc
->mCachedFields
->GetAttribute
<int32_t>(
792 nsGkAtoms::_moz_device_pixel_ratio
);
793 MOZ_ASSERT(appUnitsPerDevPixel
);
794 if (appUnitsPerDevPixel
) {
795 // Convert our existing `bounds` rect from app units to dev pixels
796 devPxBounds
= LayoutDeviceIntRect::FromAppUnitsToNearest(
797 bounds
, *appUnitsPerDevPixel
);
801 #if !defined(ANDROID)
802 // This block is not thread safe because it queries a LocalAccessible.
803 // It is also not needed in Android since the only local accessible is
804 // the outer doc browser that has an offset of 0.
805 // acc could be null if the OuterDocAccessible died before the top level
806 // DocAccessibleParent.
807 if (LocalAccessible
* localAcc
=
808 acc
? const_cast<Accessible
*>(acc
)->AsLocal() : nullptr) {
809 // LocalAccessible::Bounds returns screen-relative bounds in
811 LayoutDeviceIntRect localBounds
= localAcc
->Bounds();
813 // The root document will always have an APZ resolution of 1,
814 // so we don't factor in its scale here. We also don't scale
815 // by GetFullZoom because LocalAccessible::Bounds already does
817 devPxBounds
.MoveBy(localBounds
.X(), localBounds
.Y());
824 return LayoutDeviceIntRect();
827 template <class Derived
>
828 LayoutDeviceIntRect RemoteAccessibleBase
<Derived
>::Bounds() const {
829 return BoundsWithOffset(Nothing());
832 template <class Derived
>
833 Relation RemoteAccessibleBase
<Derived
>::RelationByType(
834 RelationType aType
) const {
835 // We are able to handle some relations completely in the
836 // parent process, without the help of the cache. Those
837 // relations are enumerated here. Other relations, whose
838 // types are stored in kRelationTypeAtoms, are processed
839 // below using the cache.
840 if (aType
== RelationType::CONTAINING_TAB_PANE
) {
841 if (dom::CanonicalBrowsingContext
* cbc
= mDoc
->GetBrowsingContext()) {
842 if (dom::CanonicalBrowsingContext
* topCbc
= cbc
->Top()) {
843 if (dom::BrowserParent
* bp
= topCbc
->GetBrowserParent()) {
844 return Relation(bp
->GetTopLevelDocAccessible());
851 if (aType
== RelationType::LINKS_TO
&& Role() == roles::LINK
) {
852 Pivot p
= Pivot(mDoc
);
855 int32_t i
= href
.FindChar('#');
856 int32_t len
= static_cast<int32_t>(href
.Length());
857 if (i
!= -1 && i
< (len
- 1)) {
858 nsDependentSubstring anchorName
= Substring(href
, i
+ 1, len
);
859 MustPruneSameDocRule rule
;
860 Accessible
* nameMatch
= nullptr;
861 for (Accessible
* match
= p
.Next(mDoc
, rule
); match
;
862 match
= p
.Next(match
, rule
)) {
864 match
->DOMNodeID(currID
);
865 MOZ_ASSERT(match
->IsRemote());
866 if (anchorName
.Equals(currID
)) {
867 return Relation(match
->AsRemote());
870 nsString currName
= match
->AsRemote()->GetCachedHTMLNameAttribute();
871 if (match
->TagName() == nsGkAtoms::a
&& anchorName
.Equals(currName
)) {
872 // If we find an element with a matching ID, we should return
873 // that, but if we don't we should return the first anchor with
874 // a matching name. To avoid doing two traversals, store the first
880 return nameMatch
? Relation(nameMatch
->AsRemote()) : Relation();
886 // Handle ARIA tree, treegrid parent/child relations. Each of these cases
887 // relies on cached group info. To find the parent of an accessible, use the
888 // unified conceptual parent.
889 if (aType
== RelationType::NODE_CHILD_OF
) {
890 const nsRoleMapEntry
* roleMapEntry
= ARIARoleMap();
891 if (roleMapEntry
&& (roleMapEntry
->role
== roles::OUTLINEITEM
||
892 roleMapEntry
->role
== roles::LISTITEM
||
893 roleMapEntry
->role
== roles::ROW
)) {
894 if (const AccGroupInfo
* groupInfo
=
895 const_cast<RemoteAccessibleBase
<Derived
>*>(this)
896 ->GetOrCreateGroupInfo()) {
897 return Relation(groupInfo
->ConceptualParent());
903 // To find the children of a parent, provide an iterator through its items.
904 if (aType
== RelationType::NODE_PARENT_OF
) {
905 const nsRoleMapEntry
* roleMapEntry
= ARIARoleMap();
906 if (roleMapEntry
&& (roleMapEntry
->role
== roles::OUTLINEITEM
||
907 roleMapEntry
->role
== roles::LISTITEM
||
908 roleMapEntry
->role
== roles::ROW
||
909 roleMapEntry
->role
== roles::OUTLINE
||
910 roleMapEntry
->role
== roles::LIST
||
911 roleMapEntry
->role
== roles::TREE_TABLE
)) {
912 return Relation(new ItemIterator(this));
917 if (aType
== RelationType::MEMBER_OF
) {
918 Relation rel
= Relation();
919 // HTML radio buttons with cached names should be grouped.
920 if (IsHTMLRadioButton()) {
921 nsString name
= GetCachedHTMLNameAttribute();
922 if (name
.IsEmpty()) {
926 RemoteAccessible
* ancestor
= RemoteParent();
927 while (ancestor
&& ancestor
->Role() != roles::FORM
&& ancestor
!= mDoc
) {
928 ancestor
= ancestor
->RemoteParent();
931 // Sometimes we end up with an unparented acc here, potentially
932 // because the acc is being moved. See bug 1807639.
933 // Pivot expects to be created with a non-null mRoot.
934 Pivot p
= Pivot(ancestor
);
935 PivotRadioNameRule
rule(name
);
936 Accessible
* match
= p
.Next(ancestor
, rule
);
938 rel
.AppendTarget(match
->AsRemote());
939 match
= p
.Next(match
, rule
);
945 if (IsARIARole(nsGkAtoms::radio
)) {
946 // ARIA radio buttons should be grouped by their radio group
947 // parent, if one exists.
948 RemoteAccessible
* currParent
= RemoteParent();
949 while (currParent
&& currParent
->Role() != roles::RADIO_GROUP
) {
950 currParent
= currParent
->RemoteParent();
953 if (currParent
&& currParent
->Role() == roles::RADIO_GROUP
) {
954 // If we found a radiogroup parent, search for all
955 // roles::RADIOBUTTON children and add them to our relation.
956 // This search will include the radio button this method
957 // was called from, which is expected.
958 Pivot p
= Pivot(currParent
);
959 PivotRoleRule
rule(roles::RADIOBUTTON
);
960 Accessible
* match
= p
.Next(currParent
, rule
);
962 MOZ_ASSERT(match
->IsRemote(),
963 "We should only be traversing the remote tree.");
964 rel
.AppendTarget(match
->AsRemote());
965 match
= p
.Next(match
, rule
);
969 // By webkit's standard, aria radio buttons do not get grouped
970 // if they lack a group parent, so we return an empty
971 // relation here if the above check fails.
976 if (!mCachedFields
) {
980 for (const auto& data
: kRelationTypeAtoms
) {
981 if (data
.mType
!= aType
||
982 (data
.mValidTag
&& TagName() != data
.mValidTag
)) {
987 mCachedFields
->GetAttribute
<nsTArray
<uint64_t>>(data
.mAtom
)) {
988 rel
.AppendIter(new RemoteAccIterator(*maybeIds
, Document()));
990 // Each relation type has only one relevant cached attribute,
991 // so break after we've handled the attr for this type,
992 // even if we didn't find any targets.
996 if (auto accRelMapEntry
= mDoc
->mReverseRelations
.Lookup(ID())) {
997 if (auto reverseIdsEntry
= accRelMapEntry
.Data().Lookup(aType
)) {
998 rel
.AppendIter(new RemoteAccIterator(reverseIdsEntry
.Data(), Document()));
1005 template <class Derived
>
1006 void RemoteAccessibleBase
<Derived
>::AppendTextTo(nsAString
& aText
,
1007 uint32_t aStartOffset
,
1010 if (mCachedFields
) {
1011 if (auto text
= mCachedFields
->GetAttribute
<nsString
>(nsGkAtoms::text
)) {
1012 aText
.Append(Substring(*text
, aStartOffset
, aLength
));
1014 VERIFY_CACHE(CacheDomain::Text
);
1019 if (aStartOffset
!= 0 || aLength
== 0) {
1024 aText
+= kForcedNewLineChar
;
1025 } else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) {
1026 // Expose the embedded object accessible as imaginary embedded object
1027 // character if its parent hypertext accessible doesn't expose children to
1029 aText
+= kImaginaryEmbeddedObjectChar
;
1031 aText
+= kEmbeddedObjectChar
;
1035 template <class Derived
>
1036 nsTArray
<bool> RemoteAccessibleBase
<Derived
>::PreProcessRelations(
1037 AccAttributes
* aFields
) {
1038 nsTArray
<bool> updateTracker(ArrayLength(kRelationTypeAtoms
));
1039 for (auto const& data
: kRelationTypeAtoms
) {
1040 if (data
.mValidTag
) {
1041 // The relation we're currently processing only applies to particular
1042 // elements. Check to see if we're one of them.
1043 nsAtom
* tag
= TagName();
1045 // TagName() returns null on an initial cache push -- check aFields
1046 // for a tag name instead.
1048 aFields
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::tag
)) {
1053 tag
|| IsTextLeaf() || IsDoc(),
1054 "Could not fetch tag via TagName() or from initial cache push!");
1055 if (tag
!= data
.mValidTag
) {
1056 // If this rel doesn't apply to us, do no pre-processing. Also,
1057 // note in our updateTracker that we should do no post-processing.
1058 updateTracker
.AppendElement(false);
1063 nsStaticAtom
* const relAtom
= data
.mAtom
;
1064 auto newRelationTargets
=
1065 aFields
->GetAttribute
<nsTArray
<uint64_t>>(relAtom
);
1066 bool shouldAddNewImplicitRels
=
1067 newRelationTargets
&& newRelationTargets
->Length();
1069 // Remove existing implicit relations if we need to perform an update, or
1070 // if we've recieved a DeleteEntry(). Only do this if mCachedFields is
1071 // initialized. If mCachedFields is not initialized, we still need to
1072 // construct the update array so we correctly handle reverse rels in
1073 // PostProcessRelations.
1074 if ((shouldAddNewImplicitRels
||
1075 aFields
->GetAttribute
<DeleteEntry
>(relAtom
)) &&
1077 if (auto maybeOldIDs
=
1078 mCachedFields
->GetAttribute
<nsTArray
<uint64_t>>(relAtom
)) {
1079 for (uint64_t id
: *maybeOldIDs
) {
1080 // For each target, fetch its reverse relation map
1081 // We need to call `Lookup` here instead of `LookupOrInsert` because
1082 // it's possible the ID we're querying is from an acc that has since
1083 // been Shutdown(), and so has intentionally removed its reverse rels
1084 // from the doc's reverse rel cache.
1085 if (auto reverseRels
= Document()->mReverseRelations
.Lookup(id
)) {
1086 // Then fetch its reverse relation's ID list. This should be safe
1087 // to do via LookupOrInsert because by the time we've gotten here,
1088 // we know the acc and `this` are still alive in the doc. If we hit
1089 // the following assert, we don't have parity on implicit/explicit
1090 // rels and something is wrong.
1091 nsTArray
<uint64_t>& reverseRelIDs
=
1092 reverseRels
->LookupOrInsert(data
.mReverseType
);
1093 // There might be other reverse relations stored for this acc, so
1094 // remove our ID instead of deleting the array entirely.
1095 DebugOnly
<bool> removed
= reverseRelIDs
.RemoveElement(ID());
1096 MOZ_ASSERT(removed
, "Can't find old reverse relation");
1102 updateTracker
.AppendElement(shouldAddNewImplicitRels
);
1105 return updateTracker
;
1108 template <class Derived
>
1109 void RemoteAccessibleBase
<Derived
>::PostProcessRelations(
1110 const nsTArray
<bool>& aToUpdate
) {
1111 size_t updateCount
= aToUpdate
.Length();
1112 MOZ_ASSERT(updateCount
== ArrayLength(kRelationTypeAtoms
),
1113 "Did not note update status for every relation type!");
1114 for (size_t i
= 0; i
< updateCount
; i
++) {
1115 if (aToUpdate
.ElementAt(i
)) {
1116 // Since kRelationTypeAtoms was used to generate aToUpdate, we
1117 // know the ith entry of aToUpdate corresponds to the relation type in
1118 // the ith entry of kRelationTypeAtoms. Fetch the related data here.
1119 auto const& data
= kRelationTypeAtoms
[i
];
1121 const nsTArray
<uint64_t>& newIDs
=
1122 *mCachedFields
->GetAttribute
<nsTArray
<uint64_t>>(data
.mAtom
);
1123 for (uint64_t id
: newIDs
) {
1124 nsTHashMap
<RelationType
, nsTArray
<uint64_t>>& relations
=
1125 Document()->mReverseRelations
.LookupOrInsert(id
);
1126 nsTArray
<uint64_t>& ids
= relations
.LookupOrInsert(data
.mReverseType
);
1127 ids
.AppendElement(ID());
1133 template <class Derived
>
1134 void RemoteAccessibleBase
<Derived
>::PruneRelationsOnShutdown() {
1135 auto reverseRels
= mDoc
->mReverseRelations
.Lookup(ID());
1139 for (auto const& data
: kRelationTypeAtoms
) {
1140 // Fetch the list of targets for this reverse relation
1141 auto reverseTargetList
= reverseRels
->Lookup(data
.mReverseType
);
1142 if (!reverseTargetList
) {
1145 for (uint64_t id
: *reverseTargetList
) {
1146 // For each target, retrieve its corresponding forward relation target
1148 RemoteAccessible
* affectedAcc
= mDoc
->GetAccessible(id
);
1150 // It's possible the affect acc also shut down, in which case
1151 // we don't have anything to update.
1154 if (auto forwardTargetList
=
1155 affectedAcc
->mCachedFields
1156 ->GetMutableAttribute
<nsTArray
<uint64_t>>(data
.mAtom
)) {
1157 forwardTargetList
->RemoveElement(ID());
1158 if (!forwardTargetList
->Length()) {
1159 // The ID we removed was the only thing in the list, so remove the
1160 // entry from the cache entirely -- don't leave an empty array.
1161 affectedAcc
->mCachedFields
->Remove(data
.mAtom
);
1166 // Remove this ID from the document's map of reverse relations.
1167 reverseRels
.Remove();
1170 template <class Derived
>
1171 uint32_t RemoteAccessibleBase
<Derived
>::GetCachedTextLength() {
1172 MOZ_ASSERT(!HasChildren());
1173 if (!mCachedFields
) {
1176 VERIFY_CACHE(CacheDomain::Text
);
1177 auto text
= mCachedFields
->GetAttribute
<nsString
>(nsGkAtoms::text
);
1181 return text
->Length();
1184 template <class Derived
>
1185 Maybe
<const nsTArray
<int32_t>&>
1186 RemoteAccessibleBase
<Derived
>::GetCachedTextLines() {
1187 MOZ_ASSERT(!HasChildren());
1188 if (!mCachedFields
) {
1191 VERIFY_CACHE(CacheDomain::Text
);
1192 return mCachedFields
->GetAttribute
<nsTArray
<int32_t>>(nsGkAtoms::line
);
1195 template <class Derived
>
1196 nsRect RemoteAccessibleBase
<Derived
>::GetCachedCharRect(int32_t aOffset
) {
1197 MOZ_ASSERT(IsText());
1198 if (!mCachedFields
) {
1202 if (Maybe
<const nsTArray
<int32_t>&> maybeCharData
=
1203 mCachedFields
->GetAttribute
<nsTArray
<int32_t>>(
1204 nsGkAtoms::characterData
)) {
1205 const nsTArray
<int32_t>& charData
= *maybeCharData
;
1206 const int32_t index
= aOffset
* kNumbersInRect
;
1207 if (index
< static_cast<int32_t>(charData
.Length())) {
1208 return nsRect(charData
[index
], charData
[index
+ 1], charData
[index
+ 2],
1209 charData
[index
+ 3]);
1211 // It is valid for a client to call this with an offset 1 after the last
1212 // character because of the insertion point at the end of text boxes.
1213 MOZ_ASSERT(index
== static_cast<int32_t>(charData
.Length()));
1219 template <class Derived
>
1220 void RemoteAccessibleBase
<Derived
>::DOMNodeID(nsString
& aID
) const {
1221 if (mCachedFields
) {
1222 mCachedFields
->GetAttribute(nsGkAtoms::id
, aID
);
1223 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass
);
1227 template <class Derived
>
1228 RefPtr
<const AccAttributes
>
1229 RemoteAccessibleBase
<Derived
>::GetCachedTextAttributes() {
1230 MOZ_ASSERT(IsText() || IsHyperText());
1231 if (mCachedFields
) {
1233 mCachedFields
->GetAttributeRefPtr
<AccAttributes
>(nsGkAtoms::style
);
1234 VERIFY_CACHE(CacheDomain::Text
);
1240 template <class Derived
>
1241 already_AddRefed
<AccAttributes
>
1242 RemoteAccessibleBase
<Derived
>::DefaultTextAttributes() {
1243 RefPtr
<const AccAttributes
> attrs
= GetCachedTextAttributes();
1244 RefPtr
<AccAttributes
> result
= new AccAttributes();
1246 attrs
->CopyTo(result
);
1248 return result
.forget();
1251 template <class Derived
>
1252 RefPtr
<const AccAttributes
>
1253 RemoteAccessibleBase
<Derived
>::GetCachedARIAAttributes() const {
1254 if (mCachedFields
) {
1256 mCachedFields
->GetAttributeRefPtr
<AccAttributes
>(nsGkAtoms::aria
);
1257 VERIFY_CACHE(CacheDomain::ARIA
);
1263 template <class Derived
>
1264 nsString RemoteAccessibleBase
<Derived
>::GetCachedHTMLNameAttribute() const {
1265 if (mCachedFields
) {
1266 if (auto maybeName
=
1267 mCachedFields
->GetAttribute
<nsString
>(nsGkAtoms::attributeName
)) {
1274 template <class Derived
>
1275 uint64_t RemoteAccessibleBase
<Derived
>::State() {
1277 if (mCachedFields
) {
1279 mCachedFields
->GetAttribute
<uint64_t>(nsGkAtoms::state
)) {
1280 VERIFY_CACHE(CacheDomain::State
);
1282 // Handle states that are derived from other states.
1283 if (!(state
& states::UNAVAILABLE
)) {
1284 state
|= states::ENABLED
| states::SENSITIVE
;
1286 if (state
& states::EXPANDABLE
&& !(state
& states::EXPANDED
)) {
1287 state
|= states::COLLAPSED
;
1291 ApplyImplicitState(state
);
1293 auto* cbc
= mDoc
->GetBrowsingContext();
1294 if (cbc
&& !cbc
->IsActive()) {
1295 // If our browsing context is _not_ active, we're in a background tab
1296 // and inherently offscreen.
1297 state
|= states::OFFSCREEN
;
1299 // If we're in an active browsing context, there are a few scenarios we
1301 // - We are an iframe document in the visual viewport
1302 // - We are an iframe document out of the visual viewport
1303 // - We are non-iframe content in the visual viewport
1304 // - We are non-iframe content out of the visual viewport
1305 // We assume top level tab docs are on screen if their BC is active, so
1306 // we don't need additional handling for them here.
1307 if (!mDoc
->IsTopLevel()) {
1308 // Here we handle iframes and iframe content.
1309 // We use an iframe's outer doc's position in the embedding document's
1310 // viewport to determine if the iframe has been scrolled offscreen.
1311 Accessible
* docParent
= mDoc
->Parent();
1312 // In rare cases, we might not have an outer doc yet. Return if that's
1314 if (NS_WARN_IF(!docParent
|| !docParent
->IsRemote())) {
1318 RemoteAccessible
* outerDoc
= docParent
->AsRemote();
1319 DocAccessibleParent
* embeddingDocument
= outerDoc
->Document();
1320 if (embeddingDocument
&&
1321 !embeddingDocument
->mOnScreenAccessibles
.Contains(outerDoc
->ID())) {
1322 // Our embedding document's viewport cache doesn't contain the ID of
1323 // our outer doc, so this iframe (and any of its content) is
1325 state
|= states::OFFSCREEN
;
1326 } else if (this != mDoc
&& !mDoc
->mOnScreenAccessibles
.Contains(ID())) {
1327 // Our embedding document's viewport cache contains the ID of our
1328 // outer doc, but the iframe's viewport cache doesn't contain our ID.
1329 // We are offscreen.
1330 state
|= states::OFFSCREEN
;
1332 } else if (this != mDoc
&& !mDoc
->mOnScreenAccessibles
.Contains(ID())) {
1333 // We are top level tab content (but not a top level tab doc).
1334 // If our tab doc's viewport cache doesn't contain our ID, we're
1336 state
|= states::OFFSCREEN
;
1344 template <class Derived
>
1345 already_AddRefed
<AccAttributes
> RemoteAccessibleBase
<Derived
>::Attributes() {
1346 RefPtr
<AccAttributes
> attributes
= new AccAttributes();
1347 nsAccessibilityService
* accService
= GetAccService();
1349 // The service can be shut down before RemoteAccessibles. If it is shut
1350 // down, we can't calculate some attributes. We're about to die anyway.
1351 return attributes
.forget();
1354 if (mCachedFields
) {
1355 // We use GetAttribute instead of GetAttributeRefPtr because we need
1356 // nsAtom, not const nsAtom.
1358 mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::tag
)) {
1359 attributes
->SetAttribute(nsGkAtoms::tag
, *tag
);
1362 GroupPos groupPos
= GroupPosition();
1363 nsAccUtils::SetAccGroupAttrs(attributes
, groupPos
.level
, groupPos
.setSize
,
1366 bool hierarchical
= false;
1367 uint32_t itemCount
= AccGroupInfo::TotalItemCount(this, &hierarchical
);
1369 attributes
->SetAttribute(nsGkAtoms::child_item_count
,
1370 static_cast<int32_t>(itemCount
));
1374 attributes
->SetAttribute(nsGkAtoms::tree
, true);
1377 if (auto inputType
= mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(
1378 nsGkAtoms::textInputType
)) {
1379 attributes
->SetAttribute(nsGkAtoms::textInputType
, *inputType
);
1382 if (RefPtr
<nsAtom
> display
= DisplayStyle()) {
1383 attributes
->SetAttribute(nsGkAtoms::display
, display
);
1386 if (TableCellAccessibleBase
* cell
= AsTableCellBase()) {
1387 TableAccessibleBase
* table
= cell
->Table();
1388 uint32_t row
= cell
->RowIdx();
1389 uint32_t col
= cell
->ColIdx();
1390 int32_t cellIdx
= table
->CellIndexAt(row
, col
);
1391 if (cellIdx
!= -1) {
1392 attributes
->SetAttribute(nsGkAtoms::tableCellIndex
, cellIdx
);
1396 if (bool layoutGuess
= TableIsProbablyForLayout()) {
1397 attributes
->SetAttribute(nsGkAtoms::layout_guess
, layoutGuess
);
1400 accService
->MarkupAttributes(this, attributes
);
1402 const nsRoleMapEntry
* roleMap
= ARIARoleMap();
1404 mCachedFields
->GetAttribute(nsGkAtoms::role
, role
);
1405 if (role
.IsEmpty()) {
1406 if (roleMap
&& roleMap
->roleAtom
!= nsGkAtoms::_empty
) {
1407 // Single, known role.
1408 attributes
->SetAttribute(nsGkAtoms::xmlroles
, roleMap
->roleAtom
);
1409 } else if (nsAtom
* landmark
= LandmarkRole()) {
1410 // Landmark role from markup; e.g. HTML <main>.
1411 attributes
->SetAttribute(nsGkAtoms::xmlroles
, landmark
);
1414 // Unknown role or multiple roles.
1415 attributes
->SetAttribute(nsGkAtoms::xmlroles
, std::move(role
));
1420 if (nsAccUtils::GetLiveAttrValue(roleMap
->liveAttRule
, live
)) {
1421 attributes
->SetAttribute(nsGkAtoms::aria_live
, std::move(live
));
1425 if (auto ariaAttrs
= GetCachedARIAAttributes()) {
1426 ariaAttrs
->CopyTo(attributes
);
1429 nsAccUtils::SetLiveContainerAttributes(attributes
, this);
1433 if (!id
.IsEmpty()) {
1434 attributes
->SetAttribute(nsGkAtoms::id
, std::move(id
));
1438 mCachedFields
->GetAttribute(nsGkAtoms::_class
, className
);
1439 if (!className
.IsEmpty()) {
1440 attributes
->SetAttribute(nsGkAtoms::_class
, std::move(className
));
1445 mCachedFields
->GetAttribute(nsGkAtoms::src
, src
);
1446 if (!src
.IsEmpty()) {
1447 attributes
->SetAttribute(nsGkAtoms::src
, std::move(src
));
1453 if (Name(name
) != eNameFromSubtree
&& !name
.IsVoid()) {
1454 attributes
->SetAttribute(nsGkAtoms::explicit_name
, true);
1457 // Expose the string value via the valuetext attribute. We test for the value
1458 // interface because we don't want to expose traditional Value() information
1459 // such as URLs on links and documents, or text in an input.
1460 // XXX This is only needed for ATK, since other APIs have native ways to
1461 // retrieve value text. We should probably move this into ATK specific code.
1462 // For now, we do this because LocalAccessible does it.
1463 if (HasNumericValue()) {
1466 attributes
->SetAttribute(nsGkAtoms::aria_valuetext
, std::move(valuetext
));
1469 return attributes
.forget();
1472 template <class Derived
>
1473 nsAtom
* RemoteAccessibleBase
<Derived
>::TagName() const {
1474 if (mCachedFields
) {
1476 mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::tag
)) {
1484 template <class Derived
>
1485 already_AddRefed
<nsAtom
> RemoteAccessibleBase
<Derived
>::InputType() const {
1486 if (mCachedFields
) {
1487 if (auto inputType
= mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(
1488 nsGkAtoms::textInputType
)) {
1489 RefPtr
<nsAtom
> result
= *inputType
;
1490 return result
.forget();
1497 template <class Derived
>
1498 already_AddRefed
<nsAtom
> RemoteAccessibleBase
<Derived
>::DisplayStyle() const {
1499 if (mCachedFields
) {
1501 mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::display
)) {
1502 RefPtr
<nsAtom
> result
= *display
;
1503 return result
.forget();
1509 template <class Derived
>
1510 float RemoteAccessibleBase
<Derived
>::Opacity() const {
1511 if (mCachedFields
) {
1512 if (auto opacity
= mCachedFields
->GetAttribute
<float>(nsGkAtoms::opacity
)) {
1520 template <class Derived
>
1521 void RemoteAccessibleBase
<Derived
>::LiveRegionAttributes(
1522 nsAString
* aLive
, nsAString
* aRelevant
, Maybe
<bool>* aAtomic
,
1523 nsAString
* aBusy
) const {
1524 if (!mCachedFields
) {
1527 RefPtr
<const AccAttributes
> attrs
= GetCachedARIAAttributes();
1532 attrs
->GetAttribute(nsGkAtoms::aria_live
, *aLive
);
1535 attrs
->GetAttribute(nsGkAtoms::aria_relevant
, *aRelevant
);
1539 attrs
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::aria_atomic
)) {
1540 *aAtomic
= Some(*value
== nsGkAtoms::_true
);
1544 attrs
->GetAttribute(nsGkAtoms::aria_busy
, *aBusy
);
1548 template <class Derived
>
1549 Maybe
<bool> RemoteAccessibleBase
<Derived
>::ARIASelected() const {
1550 if (mCachedFields
) {
1551 return mCachedFields
->GetAttribute
<bool>(nsGkAtoms::aria_selected
);
1556 template <class Derived
>
1557 nsAtom
* RemoteAccessibleBase
<Derived
>::GetPrimaryAction() const {
1558 if (mCachedFields
) {
1560 mCachedFields
->GetAttribute
<RefPtr
<nsAtom
>>(nsGkAtoms::action
)) {
1568 template <class Derived
>
1569 uint8_t RemoteAccessibleBase
<Derived
>::ActionCount() const {
1570 uint8_t actionCount
= 0;
1571 if (mCachedFields
) {
1572 if (HasPrimaryAction() || ActionAncestor()) {
1576 if (mCachedFields
->HasAttribute(nsGkAtoms::longdesc
)) {
1579 VERIFY_CACHE(CacheDomain::Actions
);
1585 template <class Derived
>
1586 void RemoteAccessibleBase
<Derived
>::ActionNameAt(uint8_t aIndex
,
1588 if (mCachedFields
) {
1590 nsAtom
* action
= GetPrimaryAction();
1591 bool hasActionAncestor
= !action
&& ActionAncestor();
1596 action
->ToString(aName
);
1597 } else if (hasActionAncestor
) {
1598 aName
.AssignLiteral("click ancestor");
1599 } else if (mCachedFields
->HasAttribute(nsGkAtoms::longdesc
)) {
1600 aName
.AssignLiteral("showlongdesc");
1604 if ((action
|| hasActionAncestor
) &&
1605 mCachedFields
->HasAttribute(nsGkAtoms::longdesc
)) {
1606 aName
.AssignLiteral("showlongdesc");
1613 VERIFY_CACHE(CacheDomain::Actions
);
1616 template <class Derived
>
1617 bool RemoteAccessibleBase
<Derived
>::DoAction(uint8_t aIndex
) const {
1618 if (ActionCount() < aIndex
+ 1) {
1622 Unused
<< mDoc
->SendDoActionAsync(mID
, aIndex
);
1626 template <class Derived
>
1627 KeyBinding RemoteAccessibleBase
<Derived
>::AccessKey() const {
1628 if (mCachedFields
) {
1630 mCachedFields
->GetAttribute
<uint64_t>(nsGkAtoms::accesskey
)) {
1631 return KeyBinding(*value
);
1634 return KeyBinding();
1637 template <class Derived
>
1638 void RemoteAccessibleBase
<Derived
>::SelectionRanges(
1639 nsTArray
<TextRange
>* aRanges
) const {
1640 Document()->SelectionRanges(aRanges
);
1643 template <class Derived
>
1644 bool RemoteAccessibleBase
<Derived
>::RemoveFromSelection(int32_t aSelectionNum
) {
1645 MOZ_ASSERT(IsHyperText());
1646 if (SelectionCount() <= aSelectionNum
) {
1650 Unused
<< mDoc
->SendRemoveTextSelection(mID
, aSelectionNum
);
1655 template <class Derived
>
1656 void RemoteAccessibleBase
<Derived
>::ARIAGroupPosition(
1657 int32_t* aLevel
, int32_t* aSetSize
, int32_t* aPosInSet
) const {
1658 if (!mCachedFields
) {
1664 mCachedFields
->GetAttribute
<int32_t>(nsGkAtoms::aria_level
)) {
1670 mCachedFields
->GetAttribute
<int32_t>(nsGkAtoms::aria_setsize
)) {
1671 *aSetSize
= *setsize
;
1676 mCachedFields
->GetAttribute
<int32_t>(nsGkAtoms::aria_posinset
)) {
1677 *aPosInSet
= *posinset
;
1682 template <class Derived
>
1683 AccGroupInfo
* RemoteAccessibleBase
<Derived
>::GetGroupInfo() const {
1684 if (!mCachedFields
) {
1688 if (auto groupInfo
= mCachedFields
->GetAttribute
<UniquePtr
<AccGroupInfo
>>(
1689 nsGkAtoms::group
)) {
1690 return groupInfo
->get();
1696 template <class Derived
>
1697 AccGroupInfo
* RemoteAccessibleBase
<Derived
>::GetOrCreateGroupInfo() {
1698 AccGroupInfo
* groupInfo
= GetGroupInfo();
1703 groupInfo
= AccGroupInfo::CreateGroupInfo(this);
1705 if (!mCachedFields
) {
1706 mCachedFields
= new AccAttributes();
1709 mCachedFields
->SetAttribute(nsGkAtoms::group
, groupInfo
);
1715 template <class Derived
>
1716 void RemoteAccessibleBase
<Derived
>::InvalidateGroupInfo() {
1717 if (mCachedFields
) {
1718 mCachedFields
->Remove(nsGkAtoms::group
);
1722 template <class Derived
>
1723 void RemoteAccessibleBase
<Derived
>::GetPositionAndSetSize(int32_t* aPosInSet
,
1724 int32_t* aSetSize
) {
1725 if (IsHTMLRadioButton()) {
1727 Relation rel
= RelationByType(RelationType::MEMBER_OF
);
1728 while (Accessible
* radio
= rel
.Next()) {
1730 if (radio
== this) {
1731 *aPosInSet
= *aSetSize
;
1737 Accessible::GetPositionAndSetSize(aPosInSet
, aSetSize
);
1740 template <class Derived
>
1741 bool RemoteAccessibleBase
<Derived
>::HasPrimaryAction() const {
1742 return mCachedFields
&& mCachedFields
->HasAttribute(nsGkAtoms::action
);
1745 template <class Derived
>
1746 void RemoteAccessibleBase
<Derived
>::TakeFocus() const {
1747 Unused
<< mDoc
->SendTakeFocus(mID
);
1750 template <class Derived
>
1751 void RemoteAccessibleBase
<Derived
>::ScrollTo(uint32_t aHow
) const {
1752 Unused
<< mDoc
->SendScrollTo(mID
, aHow
);
1755 ////////////////////////////////////////////////////////////////////////////////
1758 template <class Derived
>
1759 void RemoteAccessibleBase
<Derived
>::SelectedItems(
1760 nsTArray
<Accessible
*>* aItems
) {
1761 Pivot p
= Pivot(this);
1762 PivotStateRule
rule(states::SELECTED
);
1763 for (Accessible
* selected
= p
.First(rule
); selected
;
1764 selected
= p
.Next(selected
, rule
)) {
1765 aItems
->AppendElement(selected
);
1769 template <class Derived
>
1770 uint32_t RemoteAccessibleBase
<Derived
>::SelectedItemCount() {
1772 Pivot p
= Pivot(this);
1773 PivotStateRule
rule(states::SELECTED
);
1774 for (Accessible
* selected
= p
.First(rule
); selected
;
1775 selected
= p
.Next(selected
, rule
)) {
1782 template <class Derived
>
1783 Accessible
* RemoteAccessibleBase
<Derived
>::GetSelectedItem(uint32_t aIndex
) {
1785 Accessible
* selected
= nullptr;
1786 Pivot p
= Pivot(this);
1787 PivotStateRule
rule(states::SELECTED
);
1788 for (selected
= p
.First(rule
); selected
&& index
< aIndex
;
1789 selected
= p
.Next(selected
, rule
)) {
1796 template <class Derived
>
1797 bool RemoteAccessibleBase
<Derived
>::IsItemSelected(uint32_t aIndex
) {
1799 Accessible
* selectable
= nullptr;
1800 Pivot p
= Pivot(this);
1801 PivotStateRule
rule(states::SELECTABLE
);
1802 for (selectable
= p
.First(rule
); selectable
&& index
< aIndex
;
1803 selectable
= p
.Next(selectable
, rule
)) {
1807 return selectable
&& selectable
->State() & states::SELECTED
;
1810 template <class Derived
>
1811 bool RemoteAccessibleBase
<Derived
>::AddItemToSelection(uint32_t aIndex
) {
1813 Accessible
* selectable
= nullptr;
1814 Pivot p
= Pivot(this);
1815 PivotStateRule
rule(states::SELECTABLE
);
1816 for (selectable
= p
.First(rule
); selectable
&& index
< aIndex
;
1817 selectable
= p
.Next(selectable
, rule
)) {
1821 if (selectable
) selectable
->SetSelected(true);
1823 return static_cast<bool>(selectable
);
1826 template <class Derived
>
1827 bool RemoteAccessibleBase
<Derived
>::RemoveItemFromSelection(uint32_t aIndex
) {
1829 Accessible
* selectable
= nullptr;
1830 Pivot p
= Pivot(this);
1831 PivotStateRule
rule(states::SELECTABLE
);
1832 for (selectable
= p
.First(rule
); selectable
&& index
< aIndex
;
1833 selectable
= p
.Next(selectable
, rule
)) {
1837 if (selectable
) selectable
->SetSelected(false);
1839 return static_cast<bool>(selectable
);
1842 template <class Derived
>
1843 bool RemoteAccessibleBase
<Derived
>::SelectAll() {
1844 if ((State() & states::MULTISELECTABLE
) == 0) {
1848 bool success
= false;
1849 Accessible
* selectable
= nullptr;
1850 Pivot p
= Pivot(this);
1851 PivotStateRule
rule(states::SELECTABLE
);
1852 for (selectable
= p
.First(rule
); selectable
;
1853 selectable
= p
.Next(selectable
, rule
)) {
1855 selectable
->SetSelected(true);
1860 template <class Derived
>
1861 bool RemoteAccessibleBase
<Derived
>::UnselectAll() {
1862 if ((State() & states::MULTISELECTABLE
) == 0) {
1866 bool success
= false;
1867 Accessible
* selectable
= nullptr;
1868 Pivot p
= Pivot(this);
1869 PivotStateRule
rule(states::SELECTABLE
);
1870 for (selectable
= p
.First(rule
); selectable
;
1871 selectable
= p
.Next(selectable
, rule
)) {
1873 selectable
->SetSelected(false);
1878 template <class Derived
>
1879 void RemoteAccessibleBase
<Derived
>::TakeSelection() {
1880 Unused
<< mDoc
->SendTakeSelection(mID
);
1883 template <class Derived
>
1884 void RemoteAccessibleBase
<Derived
>::SetSelected(bool aSelect
) {
1885 Unused
<< mDoc
->SendSetSelected(mID
, aSelect
);
1888 template <class Derived
>
1889 TableAccessibleBase
* RemoteAccessibleBase
<Derived
>::AsTableBase() {
1890 MOZ_ASSERT(a11y::IsCacheActive());
1892 return CachedTableAccessible::GetFrom(this);
1897 template <class Derived
>
1898 TableCellAccessibleBase
* RemoteAccessibleBase
<Derived
>::AsTableCellBase() {
1899 MOZ_ASSERT(a11y::IsCacheActive());
1900 if (IsTableCell()) {
1901 return CachedTableCellAccessible::GetFrom(this);
1906 template <class Derived
>
1907 bool RemoteAccessibleBase
<Derived
>::TableIsProbablyForLayout() {
1908 MOZ_ASSERT(a11y::IsCacheActive());
1909 if (mCachedFields
) {
1910 if (auto layoutGuess
=
1911 mCachedFields
->GetAttribute
<bool>(nsGkAtoms::layout_guess
)) {
1912 return *layoutGuess
;
1918 template <class Derived
>
1919 nsTArray
<int32_t>& RemoteAccessibleBase
<Derived
>::GetCachedHyperTextOffsets() {
1920 if (mCachedFields
) {
1921 if (auto offsets
= mCachedFields
->GetMutableAttribute
<nsTArray
<int32_t>>(
1922 nsGkAtoms::offset
)) {
1926 nsTArray
<int32_t> newOffsets
;
1927 if (!mCachedFields
) {
1928 mCachedFields
= new AccAttributes();
1930 mCachedFields
->SetAttribute(nsGkAtoms::offset
, std::move(newOffsets
));
1931 return *mCachedFields
->GetMutableAttribute
<nsTArray
<int32_t>>(
1935 template <class Derived
>
1936 void RemoteAccessibleBase
<Derived
>::SetCaretOffset(int32_t aOffset
) {
1937 Unused
<< mDoc
->SendSetCaretOffset(mID
, aOffset
);
1940 template <class Derived
>
1941 Maybe
<int32_t> RemoteAccessibleBase
<Derived
>::GetIntARIAAttr(
1942 nsAtom
* aAttrName
) const {
1943 if (RefPtr
<const AccAttributes
> attrs
= GetCachedARIAAttributes()) {
1944 if (auto val
= attrs
->GetAttribute
<int32_t>(aAttrName
)) {
1951 template <class Derived
>
1952 void RemoteAccessibleBase
<Derived
>::Language(nsAString
& aLocale
) {
1953 if (!IsHyperText()) {
1956 if (auto attrs
= GetCachedTextAttributes()) {
1957 attrs
->GetAttribute(nsGkAtoms::language
, aLocale
);
1961 template <class Derived
>
1962 size_t RemoteAccessibleBase
<Derived
>::SizeOfIncludingThis(
1963 MallocSizeOf aMallocSizeOf
) {
1964 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
1967 template <class Derived
>
1968 size_t RemoteAccessibleBase
<Derived
>::SizeOfExcludingThis(
1969 MallocSizeOf aMallocSizeOf
) {
1972 // Count attributes.
1973 if (mCachedFields
) {
1974 size
+= mCachedFields
->SizeOfIncludingThis(aMallocSizeOf
);
1977 // We don't recurse into mChildren because they're already counted in their
1978 // document's mAccessibles.
1979 size
+= mChildren
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1984 template class RemoteAccessibleBase
<RemoteAccessible
>;
1987 } // namespace mozilla