Bug 1833114 - Simplify marking code now |stack| represents the mark stack for the...
[gecko.git] / accessible / ipc / RemoteAccessibleBase.cpp
blob88a55f726757a3b6af89799114a406922cbca7d1
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ARIAMap.h"
8 #include "CachedTableAccessible.h"
9 #include "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"
26 #include "Pivot.h"
27 #include "Relation.h"
28 #include "RelationType.h"
29 #include "TextLeafRange.h"
30 #include "xpcAccessibleDocument.h"
32 #ifdef A11Y_LOG
33 # include "Logging.h"
34 # define VERIFY_CACHE(domain) \
35 if (logging::IsEnabled(logging::eCache)) { \
36 Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \
38 #else
39 # define VERIFY_CACHE(domain) \
40 do { \
41 } while (0)
43 #endif
45 namespace mozilla {
46 namespace a11y {
48 template <class Derived>
49 void RemoteAccessibleBase<Derived>::Shutdown() {
50 MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
51 xpcAccessibleDocument* xpcDoc =
52 GetAccService()->GetCachedXPCDocument(Document());
53 if (xpcDoc) {
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();
72 if (!IsOuterDoc()) {
73 for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown();
74 } else {
75 if (childCount > 1) {
76 MOZ_CRASH("outer doc has too many documents!");
77 } else if (childCount == 1) {
78 mChildren[0]->AsDoc()->Unbind();
82 mChildren.Clear();
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()) {
112 count++;
116 return count;
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) {
126 return index;
129 index++;
133 return -1;
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()) {
141 continue;
144 if (index == aChildIdx) {
145 return mChildren[i];
148 index++;
151 return nullptr;
154 template <class Derived>
155 LocalAccessible* RemoteAccessibleBase<Derived>::OuterDocOfRemoteBrowser()
156 const {
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) {
169 if (!aParent) {
170 mParent = kNoParent;
171 } else {
172 MOZ_ASSERT(!IsDoc() || !aParent->IsDoc());
173 mParent = aParent->ID();
177 template <class Derived>
178 Derived* RemoteAccessibleBase<Derived>::RemoteParent() const {
179 if (mParent == kNoParent) {
180 return nullptr;
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
185 // parent id.
186 if (!IsDoc()) {
187 return Document()->GetAccessible(mParent);
190 // If we are a top level document then our parent is not a proxy.
191 if (AsDoc()->IsTopLevel()) {
192 return nullptr;
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);
199 MOZ_ASSERT(mParent);
200 return parentDoc->GetAccessible(mParent);
203 template <class Derived>
204 ENameValueFlag RemoteAccessibleBase<Derived>::Name(nsString& aName) const {
205 ENameValueFlag nameFlag = eNameOK;
206 if (mCachedFields) {
207 if (IsText()) {
208 mCachedFields->GetAttribute(nsGkAtoms::text, aName);
209 return eNameOK;
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);
218 return nameFlag;
222 MOZ_ASSERT(aName.IsEmpty());
223 aName.SetIsVoid(true);
224 return nameFlag;
227 template <class Derived>
228 void RemoteAccessibleBase<Derived>::Description(nsString& aDescription) const {
229 if (mCachedFields) {
230 mCachedFields->GetAttribute(nsGkAtoms::description, aDescription);
231 VERIFY_CACHE(CacheDomain::NameAndDescription);
235 template <class Derived>
236 void RemoteAccessibleBase<Derived>::Value(nsString& aValue) const {
237 if (mCachedFields) {
238 if (mCachedFields->HasAttribute(nsGkAtoms::aria_valuetext)) {
239 mCachedFields->GetAttribute(nsGkAtoms::aria_valuetext, aValue);
240 VERIFY_CACHE(CacheDomain::Value);
241 return;
244 if (HasNumericValue()) {
245 double checkValue = CurValue();
246 if (!std::isnan(checkValue)) {
247 aValue.AppendFloat(checkValue);
249 return;
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);
256 return;
259 if (IsCombobox()) {
260 // For combo boxes, rely on selection state to determine the value.
261 const Accessible* option =
262 const_cast<RemoteAccessibleBase<Derived>*>(this)->GetSelectedItem(0);
263 if (option) {
264 option->Name(aValue);
265 } else {
266 // If no selected item, determine the value from descendant elements.
267 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
269 return;
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
276 // value.
277 return actionAcc->Value(aValue);
284 template <class Derived>
285 double RemoteAccessibleBase<Derived>::CurValue() const {
286 if (mCachedFields) {
287 if (auto value = mCachedFields->GetAttribute<double>(nsGkAtoms::value)) {
288 VERIFY_CACHE(CacheDomain::Value);
289 return *value;
293 return UnspecifiedNaN<double>();
296 template <class Derived>
297 double RemoteAccessibleBase<Derived>::MinValue() const {
298 if (mCachedFields) {
299 if (auto min = mCachedFields->GetAttribute<double>(nsGkAtoms::min)) {
300 VERIFY_CACHE(CacheDomain::Value);
301 return *min;
305 return UnspecifiedNaN<double>();
308 template <class Derived>
309 double RemoteAccessibleBase<Derived>::MaxValue() const {
310 if (mCachedFields) {
311 if (auto max = mCachedFields->GetAttribute<double>(nsGkAtoms::max)) {
312 VERIFY_CACHE(CacheDomain::Value);
313 return *max;
317 return UnspecifiedNaN<double>();
320 template <class Derived>
321 double RemoteAccessibleBase<Derived>::Step() const {
322 if (mCachedFields) {
323 if (auto step = mCachedFields->GetAttribute<double>(nsGkAtoms::step)) {
324 VERIFY_CACHE(CacheDomain::Value);
325 return *step;
329 return UnspecifiedNaN<double>();
332 template <class Derived>
333 bool RemoteAccessibleBase<Derived>::SetCurValue(double aValue) {
334 if (!HasNumericValue() || IsProgress()) {
335 return false;
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) {
344 return false;
347 double checkValue = MinValue();
348 if (!std::isnan(checkValue) && aValue < checkValue) {
349 return false;
352 checkValue = MaxValue();
353 if (!std::isnan(checkValue) && aValue > checkValue) {
354 return false;
358 Unused << mDoc->SendSetCurValue(mID, aValue);
359 return true;
362 template <class Derived>
363 bool RemoteAccessibleBase<Derived>::ContainsPoint(int32_t aX, int32_t aY) {
364 if (!BoundsWithOffset(Nothing(), true).Contains(aX, aY)) {
365 return false;
367 if (!IsTextLeaf()) {
368 return true;
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();
375 if (!lines) {
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.
379 return true;
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.
387 return true;
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
391 // first line.
392 int32_t lineStart = 0;
393 for (uint32_t index = 0; index <= length; ++index) {
394 int32_t lineEnd;
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);
401 continue;
403 lineEnd = nextLineStart - 1;
404 } else {
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)) {
414 return true;
416 lineStart = lineEnd + 1;
418 return false;
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();
436 return nullptr;
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.
446 return nullptr;
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);
460 if (!acc) {
461 // This can happen if the acc died in between
462 // pushing the viewport cache and doing this hittest
463 continue;
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();
472 if (innerDoc) {
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;
480 break;
482 // If there is no embedded document, the iframe itself is the deepest
483 // descendant.
484 lastMatch = acc;
485 break;
488 if (acc == this) {
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.
494 break;
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
500 // deepest match.
501 lastMatch = acc;
502 break;
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();
511 for (;;) {
512 if (parent == this) {
513 break;
515 if (!parent || parent->IsDoc()) {
516 // `this` is not an ancestor of lastMatch. Ignore lastMatch.
517 lastMatch = nullptr;
518 break;
520 lastMatch = parent;
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`.
528 lastMatch = nullptr;
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`.
534 return this;
537 return lastMatch;
540 template <class Derived>
541 Maybe<nsRect> RemoteAccessibleBase<Derived>::RetrieveCachedBounds() const {
542 if (!mCachedFields) {
543 return Nothing();
546 Maybe<const nsTArray<int32_t>&> maybeArray =
547 mCachedFields->GetAttribute<nsTArray<int32_t>>(nsGkAtoms::relativeBounds);
548 if (maybeArray) {
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);
557 return Nothing();
560 template <class Derived>
561 void RemoteAccessibleBase<Derived>::ApplyCrossDocOffset(nsRect& aBounds) const {
562 if (!IsDoc()) {
563 // We should only apply cross-doc offsets to documents. If we're anything
564 // else, return early here.
565 return;
568 RemoteAccessible* parentAcc = RemoteParent();
569 if (!parentAcc || !parentAcc->IsOuterDoc()) {
570 return;
573 Maybe<const nsTArray<int32_t>&> maybeOffset =
574 parentAcc->mCachedFields->GetAttribute<nsTArray<int32_t>>(
575 nsGkAtoms::crossorigin);
576 if (!maybeOffset) {
577 return;
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) {
595 return false;
598 auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix(
599 *(*maybeTransform));
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);
607 return true;
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) {
616 return false;
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
631 // scroll position.
632 return true;
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;
659 return false;
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;
670 return false;
673 template <class Derived>
674 LayoutDeviceIntRect RemoteAccessibleBase<Derived>::BoundsWithOffset(
675 Maybe<nsRect> aOffset, bool aBoundsAreForHittesting) const {
676 Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
677 if (maybeBounds) {
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.
682 bounds.MoveTo(0, 0);
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).
722 Maybe<float> res =
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.
782 acc = acc->Parent();
785 MOZ_ASSERT(topDoc);
786 if (topDoc) {
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
810 // dev pixels.
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
816 // that.
817 devPxBounds.MoveBy(localBounds.X(), localBounds.Y());
819 #endif
821 return devPxBounds;
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());
848 return Relation();
851 if (aType == RelationType::LINKS_TO && Role() == roles::LINK) {
852 Pivot p = Pivot(mDoc);
853 nsString href;
854 Value(href);
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)) {
863 nsString currID;
864 match->DOMNodeID(currID);
865 MOZ_ASSERT(match->IsRemote());
866 if (anchorName.Equals(currID)) {
867 return Relation(match->AsRemote());
869 if (!nameMatch) {
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
875 // name match here.
876 nameMatch = match;
880 return nameMatch ? Relation(nameMatch->AsRemote()) : Relation();
883 return 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());
900 return Relation();
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));
914 return Relation();
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()) {
923 return rel;
926 RemoteAccessible* ancestor = RemoteParent();
927 while (ancestor && ancestor->Role() != roles::FORM && ancestor != mDoc) {
928 ancestor = ancestor->RemoteParent();
930 if (ancestor) {
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);
937 while (match) {
938 rel.AppendTarget(match->AsRemote());
939 match = p.Next(match, rule);
942 return rel;
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);
961 while (match) {
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.
972 return rel;
975 Relation rel;
976 if (!mCachedFields) {
977 return rel;
980 for (const auto& data : kRelationTypeAtoms) {
981 if (data.mType != aType ||
982 (data.mValidTag && TagName() != data.mValidTag)) {
983 continue;
986 if (auto maybeIds =
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.
993 break;
996 if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) {
997 if (auto reverseIdsEntry = accRelMapEntry.Data().Lookup(aType)) {
998 rel.AppendIter(new RemoteAccIterator(reverseIdsEntry.Data(), Document()));
1002 return rel;
1005 template <class Derived>
1006 void RemoteAccessibleBase<Derived>::AppendTextTo(nsAString& aText,
1007 uint32_t aStartOffset,
1008 uint32_t aLength) {
1009 if (IsText()) {
1010 if (mCachedFields) {
1011 if (auto text = mCachedFields->GetAttribute<nsString>(nsGkAtoms::text)) {
1012 aText.Append(Substring(*text, aStartOffset, aLength));
1014 VERIFY_CACHE(CacheDomain::Text);
1016 return;
1019 if (aStartOffset != 0 || aLength == 0) {
1020 return;
1023 if (IsHTMLBr()) {
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
1028 // AT.
1029 aText += kImaginaryEmbeddedObjectChar;
1030 } else {
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();
1044 if (!tag) {
1045 // TagName() returns null on an initial cache push -- check aFields
1046 // for a tag name instead.
1047 if (auto maybeTag =
1048 aFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::tag)) {
1049 tag = *maybeTag;
1052 MOZ_ASSERT(
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);
1059 continue;
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)) &&
1076 mCachedFields) {
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());
1136 if (!reverseRels) {
1137 return;
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) {
1143 continue;
1145 for (uint64_t id : *reverseTargetList) {
1146 // For each target, retrieve its corresponding forward relation target
1147 // list
1148 RemoteAccessible* affectedAcc = mDoc->GetAccessible(id);
1149 if (!affectedAcc) {
1150 // It's possible the affect acc also shut down, in which case
1151 // we don't have anything to update.
1152 continue;
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) {
1174 return 0;
1176 VERIFY_CACHE(CacheDomain::Text);
1177 auto text = mCachedFields->GetAttribute<nsString>(nsGkAtoms::text);
1178 if (!text) {
1179 return 0;
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) {
1189 return Nothing();
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) {
1199 return nsRect();
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()));
1216 return nsRect();
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) {
1232 auto attrs =
1233 mCachedFields->GetAttributeRefPtr<AccAttributes>(nsGkAtoms::style);
1234 VERIFY_CACHE(CacheDomain::Text);
1235 return attrs;
1237 return nullptr;
1240 template <class Derived>
1241 already_AddRefed<AccAttributes>
1242 RemoteAccessibleBase<Derived>::DefaultTextAttributes() {
1243 RefPtr<const AccAttributes> attrs = GetCachedTextAttributes();
1244 RefPtr<AccAttributes> result = new AccAttributes();
1245 if (attrs) {
1246 attrs->CopyTo(result);
1248 return result.forget();
1251 template <class Derived>
1252 RefPtr<const AccAttributes>
1253 RemoteAccessibleBase<Derived>::GetCachedARIAAttributes() const {
1254 if (mCachedFields) {
1255 auto attrs =
1256 mCachedFields->GetAttributeRefPtr<AccAttributes>(nsGkAtoms::aria);
1257 VERIFY_CACHE(CacheDomain::ARIA);
1258 return attrs;
1260 return nullptr;
1263 template <class Derived>
1264 nsString RemoteAccessibleBase<Derived>::GetCachedHTMLNameAttribute() const {
1265 if (mCachedFields) {
1266 if (auto maybeName =
1267 mCachedFields->GetAttribute<nsString>(nsGkAtoms::attributeName)) {
1268 return *maybeName;
1271 return nsString();
1274 template <class Derived>
1275 uint64_t RemoteAccessibleBase<Derived>::State() {
1276 uint64_t state = 0;
1277 if (mCachedFields) {
1278 if (auto rawState =
1279 mCachedFields->GetAttribute<uint64_t>(nsGkAtoms::state)) {
1280 VERIFY_CACHE(CacheDomain::State);
1281 state = *rawState;
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;
1298 } else {
1299 // If we're in an active browsing context, there are a few scenarios we
1300 // need to address:
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
1313 // the case.
1314 if (NS_WARN_IF(!docParent || !docParent->IsRemote())) {
1315 return state;
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
1324 // offscreen.
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
1335 // offscreen.
1336 state |= states::OFFSCREEN;
1341 return state;
1344 template <class Derived>
1345 already_AddRefed<AccAttributes> RemoteAccessibleBase<Derived>::Attributes() {
1346 RefPtr<AccAttributes> attributes = new AccAttributes();
1347 nsAccessibilityService* accService = GetAccService();
1348 if (!accService) {
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.
1357 if (auto tag =
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,
1364 groupPos.posInSet);
1366 bool hierarchical = false;
1367 uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
1368 if (itemCount) {
1369 attributes->SetAttribute(nsGkAtoms::child_item_count,
1370 static_cast<int32_t>(itemCount));
1373 if (hierarchical) {
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();
1403 nsAutoString role;
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);
1413 } else {
1414 // Unknown role or multiple roles.
1415 attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role));
1418 if (roleMap) {
1419 nsAutoString live;
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);
1431 nsString id;
1432 DOMNodeID(id);
1433 if (!id.IsEmpty()) {
1434 attributes->SetAttribute(nsGkAtoms::id, std::move(id));
1437 nsString className;
1438 mCachedFields->GetAttribute(nsGkAtoms::_class, className);
1439 if (!className.IsEmpty()) {
1440 attributes->SetAttribute(nsGkAtoms::_class, std::move(className));
1443 if (IsImage()) {
1444 nsString src;
1445 mCachedFields->GetAttribute(nsGkAtoms::src, src);
1446 if (!src.IsEmpty()) {
1447 attributes->SetAttribute(nsGkAtoms::src, std::move(src));
1452 nsAutoString name;
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()) {
1464 nsString valuetext;
1465 Value(valuetext);
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) {
1475 if (auto tag =
1476 mCachedFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::tag)) {
1477 return *tag;
1481 return nullptr;
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();
1494 return nullptr;
1497 template <class Derived>
1498 already_AddRefed<nsAtom> RemoteAccessibleBase<Derived>::DisplayStyle() const {
1499 if (mCachedFields) {
1500 if (auto display =
1501 mCachedFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::display)) {
1502 RefPtr<nsAtom> result = *display;
1503 return result.forget();
1506 return nullptr;
1509 template <class Derived>
1510 float RemoteAccessibleBase<Derived>::Opacity() const {
1511 if (mCachedFields) {
1512 if (auto opacity = mCachedFields->GetAttribute<float>(nsGkAtoms::opacity)) {
1513 return *opacity;
1517 return 1.0f;
1520 template <class Derived>
1521 void RemoteAccessibleBase<Derived>::LiveRegionAttributes(
1522 nsAString* aLive, nsAString* aRelevant, Maybe<bool>* aAtomic,
1523 nsAString* aBusy) const {
1524 if (!mCachedFields) {
1525 return;
1527 RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes();
1528 if (!attrs) {
1529 return;
1531 if (aLive) {
1532 attrs->GetAttribute(nsGkAtoms::aria_live, *aLive);
1534 if (aRelevant) {
1535 attrs->GetAttribute(nsGkAtoms::aria_relevant, *aRelevant);
1537 if (aAtomic) {
1538 if (auto value =
1539 attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::aria_atomic)) {
1540 *aAtomic = Some(*value == nsGkAtoms::_true);
1543 if (aBusy) {
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);
1553 return Nothing();
1556 template <class Derived>
1557 nsAtom* RemoteAccessibleBase<Derived>::GetPrimaryAction() const {
1558 if (mCachedFields) {
1559 if (auto action =
1560 mCachedFields->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::action)) {
1561 return *action;
1565 return nullptr;
1568 template <class Derived>
1569 uint8_t RemoteAccessibleBase<Derived>::ActionCount() const {
1570 uint8_t actionCount = 0;
1571 if (mCachedFields) {
1572 if (HasPrimaryAction() || ActionAncestor()) {
1573 actionCount++;
1576 if (mCachedFields->HasAttribute(nsGkAtoms::longdesc)) {
1577 actionCount++;
1579 VERIFY_CACHE(CacheDomain::Actions);
1582 return actionCount;
1585 template <class Derived>
1586 void RemoteAccessibleBase<Derived>::ActionNameAt(uint8_t aIndex,
1587 nsAString& aName) {
1588 if (mCachedFields) {
1589 aName.Truncate();
1590 nsAtom* action = GetPrimaryAction();
1591 bool hasActionAncestor = !action && ActionAncestor();
1593 switch (aIndex) {
1594 case 0:
1595 if (action) {
1596 action->ToString(aName);
1597 } else if (hasActionAncestor) {
1598 aName.AssignLiteral("click ancestor");
1599 } else if (mCachedFields->HasAttribute(nsGkAtoms::longdesc)) {
1600 aName.AssignLiteral("showlongdesc");
1602 break;
1603 case 1:
1604 if ((action || hasActionAncestor) &&
1605 mCachedFields->HasAttribute(nsGkAtoms::longdesc)) {
1606 aName.AssignLiteral("showlongdesc");
1608 break;
1609 default:
1610 break;
1613 VERIFY_CACHE(CacheDomain::Actions);
1616 template <class Derived>
1617 bool RemoteAccessibleBase<Derived>::DoAction(uint8_t aIndex) const {
1618 if (ActionCount() < aIndex + 1) {
1619 return false;
1622 Unused << mDoc->SendDoActionAsync(mID, aIndex);
1623 return true;
1626 template <class Derived>
1627 KeyBinding RemoteAccessibleBase<Derived>::AccessKey() const {
1628 if (mCachedFields) {
1629 if (auto value =
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) {
1647 return false;
1650 Unused << mDoc->SendRemoveTextSelection(mID, aSelectionNum);
1652 return true;
1655 template <class Derived>
1656 void RemoteAccessibleBase<Derived>::ARIAGroupPosition(
1657 int32_t* aLevel, int32_t* aSetSize, int32_t* aPosInSet) const {
1658 if (!mCachedFields) {
1659 return;
1662 if (aLevel) {
1663 if (auto level =
1664 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_level)) {
1665 *aLevel = *level;
1668 if (aSetSize) {
1669 if (auto setsize =
1670 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_setsize)) {
1671 *aSetSize = *setsize;
1674 if (aPosInSet) {
1675 if (auto posinset =
1676 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_posinset)) {
1677 *aPosInSet = *posinset;
1682 template <class Derived>
1683 AccGroupInfo* RemoteAccessibleBase<Derived>::GetGroupInfo() const {
1684 if (!mCachedFields) {
1685 return nullptr;
1688 if (auto groupInfo = mCachedFields->GetAttribute<UniquePtr<AccGroupInfo>>(
1689 nsGkAtoms::group)) {
1690 return groupInfo->get();
1693 return nullptr;
1696 template <class Derived>
1697 AccGroupInfo* RemoteAccessibleBase<Derived>::GetOrCreateGroupInfo() {
1698 AccGroupInfo* groupInfo = GetGroupInfo();
1699 if (groupInfo) {
1700 return groupInfo;
1703 groupInfo = AccGroupInfo::CreateGroupInfo(this);
1704 if (groupInfo) {
1705 if (!mCachedFields) {
1706 mCachedFields = new AccAttributes();
1709 mCachedFields->SetAttribute(nsGkAtoms::group, groupInfo);
1712 return 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()) {
1726 *aSetSize = 0;
1727 Relation rel = RelationByType(RelationType::MEMBER_OF);
1728 while (Accessible* radio = rel.Next()) {
1729 ++*aSetSize;
1730 if (radio == this) {
1731 *aPosInSet = *aSetSize;
1734 return;
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 ////////////////////////////////////////////////////////////////////////////////
1756 // SelectAccessible
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() {
1771 uint32_t count = 0;
1772 Pivot p = Pivot(this);
1773 PivotStateRule rule(states::SELECTED);
1774 for (Accessible* selected = p.First(rule); selected;
1775 selected = p.Next(selected, rule)) {
1776 count++;
1779 return count;
1782 template <class Derived>
1783 Accessible* RemoteAccessibleBase<Derived>::GetSelectedItem(uint32_t aIndex) {
1784 uint32_t index = 0;
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)) {
1790 index++;
1793 return selected;
1796 template <class Derived>
1797 bool RemoteAccessibleBase<Derived>::IsItemSelected(uint32_t aIndex) {
1798 uint32_t index = 0;
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)) {
1804 index++;
1807 return selectable && selectable->State() & states::SELECTED;
1810 template <class Derived>
1811 bool RemoteAccessibleBase<Derived>::AddItemToSelection(uint32_t aIndex) {
1812 uint32_t index = 0;
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)) {
1818 index++;
1821 if (selectable) selectable->SetSelected(true);
1823 return static_cast<bool>(selectable);
1826 template <class Derived>
1827 bool RemoteAccessibleBase<Derived>::RemoveItemFromSelection(uint32_t aIndex) {
1828 uint32_t index = 0;
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)) {
1834 index++;
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) {
1845 return false;
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)) {
1854 success = true;
1855 selectable->SetSelected(true);
1857 return success;
1860 template <class Derived>
1861 bool RemoteAccessibleBase<Derived>::UnselectAll() {
1862 if ((State() & states::MULTISELECTABLE) == 0) {
1863 return false;
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)) {
1872 success = true;
1873 selectable->SetSelected(false);
1875 return success;
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());
1891 if (IsTable()) {
1892 return CachedTableAccessible::GetFrom(this);
1894 return nullptr;
1897 template <class Derived>
1898 TableCellAccessibleBase* RemoteAccessibleBase<Derived>::AsTableCellBase() {
1899 MOZ_ASSERT(a11y::IsCacheActive());
1900 if (IsTableCell()) {
1901 return CachedTableCellAccessible::GetFrom(this);
1903 return nullptr;
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;
1915 return false;
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)) {
1923 return *offsets;
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>>(
1932 nsGkAtoms::offset);
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)) {
1945 return val;
1948 return Nothing();
1951 template <class Derived>
1952 void RemoteAccessibleBase<Derived>::Language(nsAString& aLocale) {
1953 if (!IsHyperText()) {
1954 return;
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) {
1970 size_t size = 0;
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);
1981 return size;
1984 template class RemoteAccessibleBase<RemoteAccessible>;
1986 } // namespace a11y
1987 } // namespace mozilla