Backed out 9 changesets (bug 1901851, bug 1728331) for causing remote worker crashes...
[gecko.git] / accessible / ipc / RemoteAccessible.cpp
blob346e556237c341448534e53ed01150009b4180af
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ARIAMap.h"
8 #include "CachedTableAccessible.h"
9 #include "RemoteAccessible.h"
10 #include "mozilla/a11y/DocAccessibleParent.h"
11 #include "mozilla/a11y/DocManager.h"
12 #include "mozilla/a11y/Platform.h"
13 #include "mozilla/a11y/TableAccessible.h"
14 #include "mozilla/a11y/TableCellAccessible.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/BrowserParent.h"
17 #include "mozilla/dom/CanonicalBrowsingContext.h"
18 #include "mozilla/gfx/Matrix.h"
19 #include "nsAccessibilityService.h"
20 #include "mozilla/Unused.h"
21 #include "nsAccUtils.h"
22 #include "nsFocusManager.h"
23 #include "nsTextEquivUtils.h"
24 #include "Pivot.h"
25 #include "Relation.h"
26 #include "mozilla/a11y/RelationType.h"
27 #include "xpcAccessibleDocument.h"
29 #ifdef A11Y_LOG
30 # include "Logging.h"
31 # define VERIFY_CACHE(domain) \
32 if (logging::IsEnabled(logging::eCache)) { \
33 Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \
35 #else
36 # define VERIFY_CACHE(domain) \
37 do { \
38 } while (0)
40 #endif
42 namespace mozilla {
43 namespace a11y {
45 void RemoteAccessible::Shutdown() {
46 MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
47 xpcAccessibleDocument* xpcDoc =
48 GetAccService()->GetCachedXPCDocument(Document());
49 if (xpcDoc) {
50 xpcDoc->NotifyOfShutdown(static_cast<RemoteAccessible*>(this));
53 if (IsTable() || IsTableCell()) {
54 CachedTableAccessible::Invalidate(this);
57 // Remove this acc's relation map from the doc's map of
58 // reverse relations. Prune forward relations associated with this
59 // acc's reverse relations. This also removes the acc's map of reverse
60 // rels from the mDoc's mReverseRelations.
61 PruneRelationsOnShutdown();
63 // XXX Ideally this wouldn't be necessary, but it seems OuterDoc
64 // accessibles can be destroyed before the doc they own.
65 uint32_t childCount = mChildren.Length();
66 if (!IsOuterDoc()) {
67 for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown();
68 } else {
69 if (childCount > 1) {
70 MOZ_CRASH("outer doc has too many documents!");
71 } else if (childCount == 1) {
72 mChildren[0]->AsDoc()->Unbind();
76 mChildren.Clear();
77 ProxyDestroyed(static_cast<RemoteAccessible*>(this));
78 mDoc->RemoveAccessible(static_cast<RemoteAccessible*>(this));
81 void RemoteAccessible::SetChildDoc(DocAccessibleParent* aChildDoc) {
82 MOZ_ASSERT(aChildDoc);
83 MOZ_ASSERT(mChildren.Length() == 0);
84 mChildren.AppendElement(aChildDoc);
87 void RemoteAccessible::ClearChildDoc(DocAccessibleParent* aChildDoc) {
88 MOZ_ASSERT(aChildDoc);
89 // This is possible if we're replacing one document with another: Doc 1
90 // has not had a chance to remove itself, but was already replaced by Doc 2
91 // in SetChildDoc(). This could result in two subsequent calls to
92 // ClearChildDoc() even though mChildren.Length() == 1.
93 MOZ_ASSERT(mChildren.Length() <= 1);
94 mChildren.RemoveElement(aChildDoc);
97 uint32_t RemoteAccessible::EmbeddedChildCount() {
98 size_t count = 0, kids = mChildren.Length();
99 for (size_t i = 0; i < kids; i++) {
100 if (mChildren[i]->IsEmbeddedObject()) {
101 count++;
105 return count;
108 int32_t RemoteAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
109 size_t index = 0, kids = mChildren.Length();
110 for (size_t i = 0; i < kids; i++) {
111 if (mChildren[i]->IsEmbeddedObject()) {
112 if (mChildren[i] == aChild) {
113 return index;
116 index++;
120 return -1;
123 Accessible* RemoteAccessible::EmbeddedChildAt(uint32_t aChildIdx) {
124 size_t index = 0, kids = mChildren.Length();
125 for (size_t i = 0; i < kids; i++) {
126 if (!mChildren[i]->IsEmbeddedObject()) {
127 continue;
130 if (index == aChildIdx) {
131 return mChildren[i];
134 index++;
137 return nullptr;
140 LocalAccessible* RemoteAccessible::OuterDocOfRemoteBrowser() const {
141 auto tab = static_cast<dom::BrowserParent*>(mDoc->Manager());
142 dom::Element* frame = tab->GetOwnerElement();
143 NS_ASSERTION(frame, "why isn't the tab in a frame!");
144 if (!frame) return nullptr;
146 DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
148 return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr;
151 void RemoteAccessible::SetParent(RemoteAccessible* aParent) {
152 if (!aParent) {
153 mParent = kNoParent;
154 } else {
155 MOZ_ASSERT(!IsDoc() || !aParent->IsDoc());
156 mParent = aParent->ID();
160 RemoteAccessible* RemoteAccessible::RemoteParent() const {
161 if (mParent == kNoParent) {
162 return nullptr;
165 // if we are not a document then are parent is another proxy in the same
166 // document. That means we can just ask our document for the proxy with our
167 // parent id.
168 if (!IsDoc()) {
169 return Document()->GetAccessible(mParent);
172 // If we are a top level document then our parent is not a proxy.
173 if (AsDoc()->IsTopLevel()) {
174 return nullptr;
177 // Finally if we are a non top level document then our parent id is for a
178 // proxy in our parent document so get the proxy from there.
179 DocAccessibleParent* parentDoc = AsDoc()->ParentDoc();
180 MOZ_ASSERT(parentDoc);
181 MOZ_ASSERT(mParent);
182 return parentDoc->GetAccessible(mParent);
185 void RemoteAccessible::ApplyCache(CacheUpdateType aUpdateType,
186 AccAttributes* aFields) {
187 if (!aFields) {
188 MOZ_ASSERT_UNREACHABLE("ApplyCache called with aFields == null");
189 return;
192 const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields);
193 if (auto maybeViewportCache =
194 aFields->GetAttribute<nsTArray<uint64_t>>(CacheKey::Viewport)) {
195 // Updating the viewport cache means the offscreen state of this
196 // document's accessibles has changed. Update the HashSet we use for
197 // checking offscreen state here.
198 MOZ_ASSERT(IsDoc(),
199 "Fetched the viewport cache from a non-doc accessible?");
200 AsDoc()->mOnScreenAccessibles.Clear();
201 for (auto id : *maybeViewportCache) {
202 AsDoc()->mOnScreenAccessibles.Insert(id);
206 if (aUpdateType == CacheUpdateType::Initial) {
207 mCachedFields = aFields;
208 } else {
209 if (!mCachedFields) {
210 // The fields cache can be uninitialized if there were no cache-worthy
211 // fields in the initial cache push.
212 // We don't do a simple assign because we don't want to store the
213 // DeleteEntry entries.
214 mCachedFields = new AccAttributes();
216 mCachedFields->Update(aFields);
219 if (IsTextLeaf()) {
220 RemoteAccessible* parent = RemoteParent();
221 if (parent && parent->IsHyperText()) {
222 parent->InvalidateCachedHyperTextOffsets();
226 PostProcessRelations(relUpdatesNeeded);
229 ENameValueFlag RemoteAccessible::Name(nsString& aName) const {
230 ENameValueFlag nameFlag = eNameOK;
231 if (mCachedFields) {
232 if (IsText()) {
233 mCachedFields->GetAttribute(CacheKey::Text, aName);
234 return eNameOK;
236 auto cachedNameFlag =
237 mCachedFields->GetAttribute<int32_t>(CacheKey::NameValueFlag);
238 if (cachedNameFlag) {
239 nameFlag = static_cast<ENameValueFlag>(*cachedNameFlag);
241 if (mCachedFields->GetAttribute(CacheKey::Name, aName)) {
242 VERIFY_CACHE(CacheDomain::NameAndDescription);
243 return nameFlag;
247 MOZ_ASSERT(aName.IsEmpty());
248 aName.SetIsVoid(true);
249 return nameFlag;
252 void RemoteAccessible::Description(nsString& aDescription) const {
253 if (mCachedFields) {
254 mCachedFields->GetAttribute(CacheKey::Description, aDescription);
255 VERIFY_CACHE(CacheDomain::NameAndDescription);
259 void RemoteAccessible::Value(nsString& aValue) const {
260 if (mCachedFields) {
261 if (mCachedFields->HasAttribute(CacheKey::TextValue)) {
262 mCachedFields->GetAttribute(CacheKey::TextValue, aValue);
263 VERIFY_CACHE(CacheDomain::Value);
264 return;
267 if (HasNumericValue()) {
268 double checkValue = CurValue();
269 if (!std::isnan(checkValue)) {
270 aValue.AppendFloat(checkValue);
272 return;
275 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
276 // Value of textbox is a textified subtree.
277 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) {
278 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
279 return;
282 if (IsCombobox()) {
283 // For combo boxes, rely on selection state to determine the value.
284 const Accessible* option =
285 const_cast<RemoteAccessible*>(this)->GetSelectedItem(0);
286 if (option) {
287 option->Name(aValue);
288 } else {
289 // If no selected item, determine the value from descendant elements.
290 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
292 return;
295 if (IsTextLeaf() || IsImage()) {
296 if (const Accessible* actionAcc = ActionAncestor()) {
297 if (const_cast<Accessible*>(actionAcc)->State() & states::LINKED) {
298 // Text and image descendants of links expose the link URL as the
299 // value.
300 return actionAcc->Value(aValue);
307 double RemoteAccessible::CurValue() const {
308 if (mCachedFields) {
309 if (auto value =
310 mCachedFields->GetAttribute<double>(CacheKey::NumericValue)) {
311 VERIFY_CACHE(CacheDomain::Value);
312 return *value;
316 return UnspecifiedNaN<double>();
319 double RemoteAccessible::MinValue() const {
320 if (mCachedFields) {
321 if (auto min = mCachedFields->GetAttribute<double>(CacheKey::MinValue)) {
322 VERIFY_CACHE(CacheDomain::Value);
323 return *min;
327 return UnspecifiedNaN<double>();
330 double RemoteAccessible::MaxValue() const {
331 if (mCachedFields) {
332 if (auto max = mCachedFields->GetAttribute<double>(CacheKey::MaxValue)) {
333 VERIFY_CACHE(CacheDomain::Value);
334 return *max;
338 return UnspecifiedNaN<double>();
341 double RemoteAccessible::Step() const {
342 if (mCachedFields) {
343 if (auto step = mCachedFields->GetAttribute<double>(CacheKey::Step)) {
344 VERIFY_CACHE(CacheDomain::Value);
345 return *step;
349 return UnspecifiedNaN<double>();
352 bool RemoteAccessible::SetCurValue(double aValue) {
353 if (!HasNumericValue() || IsProgress()) {
354 return false;
357 const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
358 if (State() & kValueCannotChange) {
359 return false;
362 double checkValue = MinValue();
363 if (!std::isnan(checkValue) && aValue < checkValue) {
364 return false;
367 checkValue = MaxValue();
368 if (!std::isnan(checkValue) && aValue > checkValue) {
369 return false;
372 Unused << mDoc->SendSetCurValue(mID, aValue);
373 return true;
376 bool RemoteAccessible::ContainsPoint(int32_t aX, int32_t aY) {
377 if (!BoundsWithOffset(Nothing(), true).Contains(aX, aY)) {
378 return false;
380 if (!IsTextLeaf()) {
381 if (IsImage() || IsImageMap() || !HasChildren() ||
382 RefPtr{DisplayStyle()} != nsGkAtoms::inlinevalue) {
383 // This isn't an inline element that might contain text, so we don't need
384 // to walk lines. It's enough that our rect contains the point.
385 return true;
387 // Non-image inline elements with children can wrap across lines just like
388 // text leaves; see below.
389 // Walk the children, which will walk the lines of text in any text leaves.
390 uint32_t count = ChildCount();
391 for (uint32_t c = 0; c < count; ++c) {
392 RemoteAccessible* child = RemoteChildAt(c);
393 if (child->Role() == roles::TEXT_CONTAINER && child->IsClipped()) {
394 // There is a clipped child. This is a candidate for fuzzy hit testing.
395 // See RemoteAccessible::DoFuzzyHittesting.
396 return true;
398 if (child->ContainsPoint(aX, aY)) {
399 return true;
402 // None of our descendants contain the point, so nor do we.
403 return false;
405 // This is a text leaf. The text might wrap across lines, which means our
406 // rect might cover a wider area than the actual text. For example, if the
407 // text begins in the middle of the first line and wraps on to the second,
408 // the rect will cover the start of the first line and the end of the second.
409 auto lines = GetCachedTextLines();
410 if (!lines) {
411 // This means the text is empty or occupies a single line (but does not
412 // begin the line). In that case, the Bounds check above is sufficient,
413 // since there's only one rect.
414 return true;
416 uint32_t length = lines->Length();
417 MOZ_ASSERT(length > 0,
418 "Line starts shouldn't be in cache if there aren't any");
419 if (length == 0 || (length == 1 && (*lines)[0] == 0)) {
420 // This means the text begins and occupies a single line. Again, the Bounds
421 // check above is sufficient.
422 return true;
424 // Walk the lines of the text. Even if this text doesn't start at the
425 // beginning of a line (i.e. lines[0] > 0), we always want to consider its
426 // first line.
427 int32_t lineStart = 0;
428 for (uint32_t index = 0; index <= length; ++index) {
429 int32_t lineEnd;
430 if (index < length) {
431 int32_t nextLineStart = (*lines)[index];
432 if (nextLineStart == 0) {
433 // This Accessible starts at the beginning of a line. Here, we always
434 // treat 0 as the first line start anyway.
435 MOZ_ASSERT(index == 0);
436 continue;
438 lineEnd = nextLineStart - 1;
439 } else {
440 // This is the last line.
441 lineEnd = static_cast<int32_t>(nsAccUtils::TextLength(this)) - 1;
443 MOZ_ASSERT(lineEnd >= lineStart);
444 nsRect lineRect = GetCachedCharRect(lineStart);
445 if (lineEnd > lineStart) {
446 lineRect.UnionRect(lineRect, GetCachedCharRect(lineEnd));
448 if (BoundsWithOffset(Some(lineRect), true).Contains(aX, aY)) {
449 return true;
451 lineStart = lineEnd + 1;
453 return false;
456 RemoteAccessible* RemoteAccessible::DoFuzzyHittesting() {
457 uint32_t childCount = ChildCount();
458 if (!childCount) {
459 return nullptr;
461 // Check if this match has a clipped child.
462 // This usually indicates invisible text, and we're
463 // interested in returning the inner text content
464 // even if it doesn't contain the point we're hittesting.
465 RemoteAccessible* clippedContainer = nullptr;
466 for (uint32_t i = 0; i < childCount; i++) {
467 RemoteAccessible* child = RemoteChildAt(i);
468 if (child->Role() == roles::TEXT_CONTAINER) {
469 if (child->IsClipped()) {
470 clippedContainer = child;
471 break;
475 // If we found a clipped container, descend it in search of
476 // meaningful text leaves. Ignore non-text-leaf/text-container
477 // siblings.
478 RemoteAccessible* container = clippedContainer;
479 while (container) {
480 RemoteAccessible* textLeaf = nullptr;
481 bool continueSearch = false;
482 childCount = container->ChildCount();
483 for (uint32_t i = 0; i < childCount; i++) {
484 RemoteAccessible* child = container->RemoteChildAt(i);
485 if (child->Role() == roles::TEXT_CONTAINER) {
486 container = child;
487 continueSearch = true;
488 break;
490 if (child->IsTextLeaf()) {
491 textLeaf = child;
492 // Don't break here -- it's possible a text container
493 // exists as another sibling, and we should descend as
494 // deep as possible.
497 if (textLeaf) {
498 return textLeaf;
500 if (!continueSearch) {
501 // We didn't find anything useful in this set of siblings.
502 // Don't keep searching
503 break;
506 return nullptr;
509 Accessible* RemoteAccessible::ChildAtPoint(
510 int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) {
511 // Elements that are partially on-screen should have their bounds masked by
512 // their containing scroll area so hittesting yields results that are
513 // consistent with the content's visual representation. Pass this value to
514 // bounds calculation functions to indicate that we're hittesting.
515 const bool hitTesting = true;
517 if (IsOuterDoc() && aWhichChild == EWhichChildAtPoint::DirectChild) {
518 // This is an iframe, which is as deep as the viewport cache goes. The
519 // caller wants a direct child, which can only be the embedded document.
520 if (BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
521 return RemoteFirstChild();
523 return nullptr;
526 RemoteAccessible* lastMatch = nullptr;
527 // If `this` is a document, use its viewport cache instead of
528 // the cache of its parent document.
529 if (DocAccessibleParent* doc = IsDoc() ? AsDoc() : mDoc) {
530 if (!doc->mCachedFields) {
531 // A client call might arrive after we've constructed doc but before we
532 // get a cache push for it.
533 return nullptr;
535 if (auto maybeViewportCache =
536 doc->mCachedFields->GetAttribute<nsTArray<uint64_t>>(
537 CacheKey::Viewport)) {
538 // The retrieved viewport cache contains acc IDs in hittesting order.
539 // That is, items earlier in the list have z-indexes that are larger than
540 // those later in the list. If you were to build a tree by z-index, where
541 // chilren have larger z indices than their parents, iterating this list
542 // is essentially a postorder tree traversal.
543 const nsTArray<uint64_t>& viewportCache = *maybeViewportCache;
545 for (auto id : viewportCache) {
546 RemoteAccessible* acc = doc->GetAccessible(id);
547 if (!acc) {
548 // This can happen if the acc died in between
549 // pushing the viewport cache and doing this hittest
550 continue;
553 if (acc->IsOuterDoc() &&
554 aWhichChild == EWhichChildAtPoint::DeepestChild &&
555 acc->BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
556 // acc is an iframe, which is as deep as the viewport cache goes. This
557 // iframe contains the requested point.
558 RemoteAccessible* innerDoc = acc->RemoteFirstChild();
559 if (innerDoc) {
560 MOZ_ASSERT(innerDoc->IsDoc());
561 // Search the embedded document's viewport cache so we return the
562 // deepest descendant in that embedded document.
563 Accessible* deepestAcc = innerDoc->ChildAtPoint(
564 aX, aY, EWhichChildAtPoint::DeepestChild);
565 MOZ_ASSERT(!deepestAcc || deepestAcc->IsRemote());
566 lastMatch = deepestAcc ? deepestAcc->AsRemote() : nullptr;
567 break;
569 // If there is no embedded document, the iframe itself is the deepest
570 // descendant.
571 lastMatch = acc;
572 break;
575 if (acc == this) {
576 MOZ_ASSERT(!acc->IsOuterDoc());
577 // Even though we're searching from the doc's cache
578 // this call shouldn't pass the boundary defined by
579 // the acc this call originated on. If we hit `this`,
580 // return our most recent match.
581 if (!lastMatch &&
582 BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
583 // If we haven't found a match, but `this` contains the point we're
584 // looking for, set it as our temp last match so we can
585 // (potentially) do fuzzy hittesting on it below.
586 lastMatch = acc;
588 break;
591 if (acc->ContainsPoint(aX, aY)) {
592 // Because our rects are in hittesting order, the
593 // first match we encounter is guaranteed to be the
594 // deepest match.
595 lastMatch = acc;
596 break;
599 if (lastMatch) {
600 RemoteAccessible* fuzzyMatch = lastMatch->DoFuzzyHittesting();
601 lastMatch = fuzzyMatch ? fuzzyMatch : lastMatch;
606 if (aWhichChild == EWhichChildAtPoint::DirectChild && lastMatch) {
607 // lastMatch is the deepest match. Walk up to the direct child of this.
608 RemoteAccessible* parent = lastMatch->RemoteParent();
609 for (;;) {
610 if (parent == this) {
611 break;
613 if (!parent || parent->IsDoc()) {
614 // `this` is not an ancestor of lastMatch. Ignore lastMatch.
615 lastMatch = nullptr;
616 break;
618 lastMatch = parent;
619 parent = parent->RemoteParent();
621 } else if (aWhichChild == EWhichChildAtPoint::DeepestChild && lastMatch &&
622 !IsDoc() && !IsAncestorOf(lastMatch)) {
623 // If we end up with a match that is not in the ancestor chain
624 // of the accessible this call originated on, we should ignore it.
625 // This can happen when the aX, aY given are outside `this`.
626 lastMatch = nullptr;
629 if (!lastMatch && BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
630 // Even though the hit target isn't inside `this`, the point is still
631 // within our bounds, so fall back to `this`.
632 return this;
635 return lastMatch;
638 Maybe<nsRect> RemoteAccessible::RetrieveCachedBounds() const {
639 if (!mCachedFields) {
640 return Nothing();
643 Maybe<const nsTArray<int32_t>&> maybeArray =
644 mCachedFields->GetAttribute<nsTArray<int32_t>>(
645 CacheKey::ParentRelativeBounds);
646 if (maybeArray) {
647 const nsTArray<int32_t>& relativeBoundsArr = *maybeArray;
648 MOZ_ASSERT(relativeBoundsArr.Length() == 4,
649 "Incorrectly sized bounds array");
650 nsRect relativeBoundsRect(relativeBoundsArr[0], relativeBoundsArr[1],
651 relativeBoundsArr[2], relativeBoundsArr[3]);
652 return Some(relativeBoundsRect);
655 return Nothing();
658 void RemoteAccessible::ApplyCrossDocOffset(nsRect& aBounds) const {
659 if (!IsDoc()) {
660 // We should only apply cross-doc offsets to documents. If we're anything
661 // else, return early here.
662 return;
665 RemoteAccessible* parentAcc = RemoteParent();
666 if (!parentAcc || !parentAcc->IsOuterDoc()) {
667 return;
670 Maybe<const nsTArray<int32_t>&> maybeOffset =
671 parentAcc->mCachedFields->GetAttribute<nsTArray<int32_t>>(
672 CacheKey::CrossDocOffset);
673 if (!maybeOffset) {
674 return;
677 MOZ_ASSERT(maybeOffset->Length() == 2);
678 const nsTArray<int32_t>& offset = *maybeOffset;
679 // Our retrieved value is in app units, so we don't need to do any
680 // unit conversion here.
681 aBounds.MoveBy(offset[0], offset[1]);
684 bool RemoteAccessible::ApplyTransform(nsRect& aCumulativeBounds) const {
685 // First, attempt to retrieve the transform from the cache.
686 Maybe<const UniquePtr<gfx::Matrix4x4>&> maybeTransform =
687 mCachedFields->GetAttribute<UniquePtr<gfx::Matrix4x4>>(
688 CacheKey::TransformMatrix);
689 if (!maybeTransform) {
690 return false;
693 auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix(
694 *(*maybeTransform));
696 // Our matrix is in CSS Pixels, so we need our rect to be in CSS
697 // Pixels too. Convert before applying.
698 auto boundsInPixels = CSSRect::FromAppUnits(aCumulativeBounds);
699 boundsInPixels = mtxInPixels.TransformBounds(boundsInPixels);
700 aCumulativeBounds = CSSRect::ToAppUnits(boundsInPixels);
702 return true;
705 bool RemoteAccessible::ApplyScrollOffset(nsRect& aBounds) const {
706 Maybe<const nsTArray<int32_t>&> maybeScrollPosition =
707 mCachedFields->GetAttribute<nsTArray<int32_t>>(CacheKey::ScrollPosition);
709 if (!maybeScrollPosition || maybeScrollPosition->Length() != 2) {
710 return false;
712 // Our retrieved value is in app units, so we don't need to do any
713 // unit conversion here.
714 const nsTArray<int32_t>& scrollPosition = *maybeScrollPosition;
716 // Scroll position is an inverse representation of scroll offset (since the
717 // further the scroll bar moves down the page, the further the page content
718 // moves up/closer to the origin).
719 nsPoint scrollOffset(-scrollPosition[0], -scrollPosition[1]);
721 aBounds.MoveBy(scrollOffset.x, scrollOffset.y);
723 // Return true here even if the scroll offset was 0,0 because the RV is used
724 // as a scroll container indicator. Non-scroll containers won't have cached
725 // scroll position.
726 return true;
729 nsRect RemoteAccessible::BoundsInAppUnits() const {
730 if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()->Top()) {
731 if (dom::BrowserParent* bp = cbc->GetBrowserParent()) {
732 DocAccessibleParent* topDoc = bp->GetTopLevelDocAccessible();
733 if (topDoc && topDoc->mCachedFields) {
734 auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
735 CacheKey::AppUnitsPerDevPixel);
736 MOZ_ASSERT(appUnitsPerDevPixel);
737 return LayoutDeviceIntRect::ToAppUnits(Bounds(), *appUnitsPerDevPixel);
741 return LayoutDeviceIntRect::ToAppUnits(Bounds(), AppUnitsPerCSSPixel());
744 bool RemoteAccessible::IsFixedPos() const {
745 MOZ_ASSERT(mCachedFields);
746 if (auto maybePosition =
747 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CssPosition)) {
748 return *maybePosition == nsGkAtoms::fixed;
751 return false;
754 bool RemoteAccessible::IsOverflowHidden() const {
755 MOZ_ASSERT(mCachedFields);
756 if (auto maybeOverflow =
757 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSOverflow)) {
758 return *maybeOverflow == nsGkAtoms::hidden;
761 return false;
764 bool RemoteAccessible::IsClipped() const {
765 MOZ_ASSERT(mCachedFields);
766 if (mCachedFields->GetAttribute<bool>(CacheKey::IsClipped)) {
767 return true;
770 return false;
773 LayoutDeviceIntRect RemoteAccessible::BoundsWithOffset(
774 Maybe<nsRect> aOffset, bool aBoundsAreForHittesting) const {
775 Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
776 if (maybeBounds) {
777 nsRect bounds = *maybeBounds;
778 // maybeBounds is parent-relative. However, the transform matrix we cache
779 // (if any) is meant to operate on self-relative rects. Therefore, make
780 // bounds self-relative until after we transform.
781 bounds.MoveTo(0, 0);
782 const DocAccessibleParent* topDoc = IsDoc() ? AsDoc() : nullptr;
784 if (aOffset.isSome()) {
785 // The rect we've passed in is in app units, so no conversion needed.
786 nsRect internalRect = *aOffset;
787 bounds.SetRectX(bounds.x + internalRect.x, internalRect.width);
788 bounds.SetRectY(bounds.y + internalRect.y, internalRect.height);
791 Unused << ApplyTransform(bounds);
792 // Now apply the parent-relative offset.
793 bounds.MoveBy(maybeBounds->TopLeft());
795 ApplyCrossDocOffset(bounds);
797 LayoutDeviceIntRect devPxBounds;
798 const Accessible* acc = Parent();
799 bool encounteredFixedContainer = IsFixedPos();
800 while (acc && acc->IsRemote()) {
801 // Return early if we're hit testing and our cumulative bounds are empty,
802 // since walking the ancestor chain won't produce any hits.
803 if (aBoundsAreForHittesting && bounds.IsEmpty()) {
804 return LayoutDeviceIntRect{};
807 RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote();
809 if (Maybe<nsRect> maybeRemoteBounds = remoteAcc->RetrieveCachedBounds()) {
810 nsRect remoteBounds = *maybeRemoteBounds;
811 // We need to take into account a non-1 resolution set on the
812 // presshell. This happens with async pinch zooming, among other
813 // things. We can't reliably query this value in the parent process,
814 // so we retrieve it from the document's cache.
815 if (remoteAcc->IsDoc()) {
816 // Apply the document's resolution to the bounds we've gathered
817 // thus far. We do this before applying the document's offset
818 // because document accs should not have their bounds scaled by
819 // their own resolution. They should be scaled by the resolution
820 // of their containing document (if any).
821 Maybe<float> res =
822 remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>(
823 CacheKey::Resolution);
824 MOZ_ASSERT(res, "No cached document resolution found.");
825 bounds.ScaleRoundOut(res.valueOr(1.0f));
827 topDoc = remoteAcc->AsDoc();
830 // We don't account for the document offset of iframes when
831 // computing parent-relative bounds. Instead, we store this value
832 // separately on all iframes and apply it here. See the comments in
833 // LocalAccessible::BundleFieldsForCache where we set the
834 // nsGkAtoms::crossorigin attribute.
835 remoteAcc->ApplyCrossDocOffset(remoteBounds);
836 if (!encounteredFixedContainer) {
837 // Apply scroll offset, if applicable. Only the contents of an
838 // element are affected by its scroll offset, which is why this call
839 // happens in this loop instead of both inside and outside of
840 // the loop (like ApplyTransform).
841 // Never apply scroll offsets past a fixed container.
842 const bool hasScrollArea = remoteAcc->ApplyScrollOffset(bounds);
844 // If we are hit testing and the Accessible has a scroll area, ensure
845 // that the bounds we've calculated so far are constrained to the
846 // bounds of the scroll area. Without this, we'll "hit" the off-screen
847 // portions of accs that are are partially (but not fully) within the
848 // scroll area. This is also a problem for accs with overflow:hidden;
849 if (aBoundsAreForHittesting &&
850 (hasScrollArea || remoteAcc->IsOverflowHidden())) {
851 nsRect selfRelativeVisibleBounds(0, 0, remoteBounds.width,
852 remoteBounds.height);
853 bounds = bounds.SafeIntersect(selfRelativeVisibleBounds);
856 if (remoteAcc->IsDoc()) {
857 // Fixed elements are document relative, so if we've hit a
858 // document we're now subject to that document's styling
859 // (including scroll offsets that operate on it).
860 // This ordering is important, we don't want to apply scroll
861 // offsets on this doc's content.
862 encounteredFixedContainer = false;
864 if (!encounteredFixedContainer) {
865 // The transform matrix we cache (if any) is meant to operate on
866 // self-relative rects. Therefore, we must apply the transform before
867 // we make bounds parent-relative.
868 Unused << remoteAcc->ApplyTransform(bounds);
869 // Regardless of whether this is a doc, we should offset `bounds`
870 // by the bounds retrieved here. This is how we build screen
871 // coordinates from relative coordinates.
872 bounds.MoveBy(remoteBounds.X(), remoteBounds.Y());
875 if (remoteAcc->IsFixedPos()) {
876 encounteredFixedContainer = true;
878 // we can't just break here if we're scroll suppressed because we still
879 // need to find the top doc.
881 acc = acc->Parent();
884 MOZ_ASSERT(topDoc);
885 if (topDoc) {
886 // We use the top documents app-units-per-dev-pixel even though
887 // theoretically nested docs can have different values. Practically,
888 // that isn't likely since we only offer zoom controls for the top
889 // document and all subdocuments inherit from it.
890 auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
891 CacheKey::AppUnitsPerDevPixel);
892 MOZ_ASSERT(appUnitsPerDevPixel);
893 if (appUnitsPerDevPixel) {
894 // Convert our existing `bounds` rect from app units to dev pixels
895 devPxBounds = LayoutDeviceIntRect::FromAppUnitsToNearest(
896 bounds, *appUnitsPerDevPixel);
900 #if !defined(ANDROID)
901 // This block is not thread safe because it queries a LocalAccessible.
902 // It is also not needed in Android since the only local accessible is
903 // the outer doc browser that has an offset of 0.
904 // acc could be null if the OuterDocAccessible died before the top level
905 // DocAccessibleParent.
906 if (LocalAccessible* localAcc =
907 acc ? const_cast<Accessible*>(acc)->AsLocal() : nullptr) {
908 // LocalAccessible::Bounds returns screen-relative bounds in
909 // dev pixels.
910 LayoutDeviceIntRect localBounds = localAcc->Bounds();
912 // The root document will always have an APZ resolution of 1,
913 // so we don't factor in its scale here. We also don't scale
914 // by GetFullZoom because LocalAccessible::Bounds already does
915 // that.
916 devPxBounds.MoveBy(localBounds.X(), localBounds.Y());
918 #endif
920 return devPxBounds;
923 return LayoutDeviceIntRect();
926 LayoutDeviceIntRect RemoteAccessible::Bounds() const {
927 return BoundsWithOffset(Nothing());
930 Relation RemoteAccessible::RelationByType(RelationType aType) const {
931 // We are able to handle some relations completely in the
932 // parent process, without the help of the cache. Those
933 // relations are enumerated here. Other relations, whose
934 // types are stored in kRelationTypeAtoms, are processed
935 // below using the cache.
936 if (aType == RelationType::CONTAINING_TAB_PANE) {
937 if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()) {
938 if (dom::CanonicalBrowsingContext* topCbc = cbc->Top()) {
939 if (dom::BrowserParent* bp = topCbc->GetBrowserParent()) {
940 return Relation(bp->GetTopLevelDocAccessible());
944 return Relation();
947 if (aType == RelationType::LINKS_TO && Role() == roles::LINK) {
948 Pivot p = Pivot(mDoc);
949 nsString href;
950 Value(href);
951 int32_t i = href.FindChar('#');
952 int32_t len = static_cast<int32_t>(href.Length());
953 if (i != -1 && i < (len - 1)) {
954 nsDependentSubstring anchorName = Substring(href, i + 1, len);
955 MustPruneSameDocRule rule;
956 Accessible* nameMatch = nullptr;
957 for (Accessible* match = p.Next(mDoc, rule); match;
958 match = p.Next(match, rule)) {
959 nsString currID;
960 match->DOMNodeID(currID);
961 MOZ_ASSERT(match->IsRemote());
962 if (anchorName.Equals(currID)) {
963 return Relation(match->AsRemote());
965 if (!nameMatch) {
966 nsString currName = match->AsRemote()->GetCachedHTMLNameAttribute();
967 if (match->TagName() == nsGkAtoms::a && anchorName.Equals(currName)) {
968 // If we find an element with a matching ID, we should return
969 // that, but if we don't we should return the first anchor with
970 // a matching name. To avoid doing two traversals, store the first
971 // name match here.
972 nameMatch = match;
976 return nameMatch ? Relation(nameMatch->AsRemote()) : Relation();
979 return Relation();
982 // Handle ARIA tree, treegrid parent/child relations. Each of these cases
983 // relies on cached group info. To find the parent of an accessible, use the
984 // unified conceptual parent.
985 if (aType == RelationType::NODE_CHILD_OF) {
986 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
987 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
988 roleMapEntry->role == roles::LISTITEM ||
989 roleMapEntry->role == roles::ROW)) {
990 if (const AccGroupInfo* groupInfo =
991 const_cast<RemoteAccessible*>(this)->GetOrCreateGroupInfo()) {
992 return Relation(groupInfo->ConceptualParent());
995 return Relation();
998 // To find the children of a parent, provide an iterator through its items.
999 if (aType == RelationType::NODE_PARENT_OF) {
1000 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1001 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
1002 roleMapEntry->role == roles::LISTITEM ||
1003 roleMapEntry->role == roles::ROW ||
1004 roleMapEntry->role == roles::OUTLINE ||
1005 roleMapEntry->role == roles::LIST ||
1006 roleMapEntry->role == roles::TREE_TABLE)) {
1007 return Relation(new ItemIterator(this));
1009 return Relation();
1012 if (aType == RelationType::MEMBER_OF) {
1013 Relation rel = Relation();
1014 // HTML radio buttons with cached names should be grouped.
1015 if (IsHTMLRadioButton()) {
1016 nsString name = GetCachedHTMLNameAttribute();
1017 if (name.IsEmpty()) {
1018 return rel;
1021 RemoteAccessible* ancestor = RemoteParent();
1022 while (ancestor && ancestor->Role() != roles::FORM && ancestor != mDoc) {
1023 ancestor = ancestor->RemoteParent();
1025 if (ancestor) {
1026 // Sometimes we end up with an unparented acc here, potentially
1027 // because the acc is being moved. See bug 1807639.
1028 // Pivot expects to be created with a non-null mRoot.
1029 Pivot p = Pivot(ancestor);
1030 PivotRadioNameRule rule(name);
1031 Accessible* match = p.Next(ancestor, rule);
1032 while (match) {
1033 rel.AppendTarget(match->AsRemote());
1034 match = p.Next(match, rule);
1037 return rel;
1040 if (IsARIARole(nsGkAtoms::radio)) {
1041 // ARIA radio buttons should be grouped by their radio group
1042 // parent, if one exists.
1043 RemoteAccessible* currParent = RemoteParent();
1044 while (currParent && currParent->Role() != roles::RADIO_GROUP) {
1045 currParent = currParent->RemoteParent();
1048 if (currParent && currParent->Role() == roles::RADIO_GROUP) {
1049 // If we found a radiogroup parent, search for all
1050 // roles::RADIOBUTTON children and add them to our relation.
1051 // This search will include the radio button this method
1052 // was called from, which is expected.
1053 Pivot p = Pivot(currParent);
1054 PivotRoleRule rule(roles::RADIOBUTTON);
1055 Accessible* match = p.Next(currParent, rule);
1056 while (match) {
1057 MOZ_ASSERT(match->IsRemote(),
1058 "We should only be traversing the remote tree.");
1059 rel.AppendTarget(match->AsRemote());
1060 match = p.Next(match, rule);
1064 // By webkit's standard, aria radio buttons do not get grouped
1065 // if they lack a group parent, so we return an empty
1066 // relation here if the above check fails.
1067 return rel;
1070 Relation rel;
1071 if (!mCachedFields) {
1072 return rel;
1075 for (const auto& data : kRelationTypeAtoms) {
1076 if (data.mType != aType ||
1077 (data.mValidTag && TagName() != data.mValidTag)) {
1078 continue;
1081 if (auto maybeIds =
1082 mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom)) {
1083 rel.AppendIter(new RemoteAccIterator(*maybeIds, Document()));
1085 // Each relation type has only one relevant cached attribute,
1086 // so break after we've handled the attr for this type,
1087 // even if we didn't find any targets.
1088 break;
1091 if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) {
1092 if (auto reverseIdsEntry = accRelMapEntry.Data().Lookup(aType)) {
1093 rel.AppendIter(new RemoteAccIterator(reverseIdsEntry.Data(), Document()));
1097 // We handle these relations here rather than before cached relations because
1098 // the cached relations need to take precedence. For example, a <figure> with
1099 // both aria-labelledby and a <figcaption> must return two LABELLED_BY
1100 // targets: the aria-labelledby and then the <figcaption>.
1101 if (aType == RelationType::LABELLED_BY && TagName() == nsGkAtoms::figure) {
1102 uint32_t count = ChildCount();
1103 for (uint32_t c = 0; c < count; ++c) {
1104 RemoteAccessible* child = RemoteChildAt(c);
1105 MOZ_ASSERT(child);
1106 if (child->TagName() == nsGkAtoms::figcaption) {
1107 rel.AppendTarget(child);
1110 } else if (aType == RelationType::LABEL_FOR &&
1111 TagName() == nsGkAtoms::figcaption) {
1112 if (RemoteAccessible* parent = RemoteParent()) {
1113 if (parent->TagName() == nsGkAtoms::figure) {
1114 rel.AppendTarget(parent);
1119 return rel;
1122 void RemoteAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
1123 uint32_t aLength) {
1124 if (IsText()) {
1125 if (mCachedFields) {
1126 if (auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text)) {
1127 aText.Append(Substring(*text, aStartOffset, aLength));
1129 VERIFY_CACHE(CacheDomain::Text);
1131 return;
1134 if (aStartOffset != 0 || aLength == 0) {
1135 return;
1138 if (IsHTMLBr()) {
1139 aText += kForcedNewLineChar;
1140 } else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) {
1141 // Expose the embedded object accessible as imaginary embedded object
1142 // character if its parent hypertext accessible doesn't expose children to
1143 // AT.
1144 aText += kImaginaryEmbeddedObjectChar;
1145 } else {
1146 aText += kEmbeddedObjectChar;
1150 nsTArray<bool> RemoteAccessible::PreProcessRelations(AccAttributes* aFields) {
1151 nsTArray<bool> updateTracker(ArrayLength(kRelationTypeAtoms));
1152 for (auto const& data : kRelationTypeAtoms) {
1153 if (data.mValidTag) {
1154 // The relation we're currently processing only applies to particular
1155 // elements. Check to see if we're one of them.
1156 nsAtom* tag = TagName();
1157 if (!tag) {
1158 // TagName() returns null on an initial cache push -- check aFields
1159 // for a tag name instead.
1160 if (auto maybeTag =
1161 aFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1162 tag = *maybeTag;
1165 MOZ_ASSERT(
1166 tag || IsTextLeaf() || IsDoc(),
1167 "Could not fetch tag via TagName() or from initial cache push!");
1168 if (tag != data.mValidTag) {
1169 // If this rel doesn't apply to us, do no pre-processing. Also,
1170 // note in our updateTracker that we should do no post-processing.
1171 updateTracker.AppendElement(false);
1172 continue;
1176 nsStaticAtom* const relAtom = data.mAtom;
1177 auto newRelationTargets =
1178 aFields->GetAttribute<nsTArray<uint64_t>>(relAtom);
1179 bool shouldAddNewImplicitRels =
1180 newRelationTargets && newRelationTargets->Length();
1182 // Remove existing implicit relations if we need to perform an update, or
1183 // if we've received a DeleteEntry(). Only do this if mCachedFields is
1184 // initialized. If mCachedFields is not initialized, we still need to
1185 // construct the update array so we correctly handle reverse rels in
1186 // PostProcessRelations.
1187 if ((shouldAddNewImplicitRels ||
1188 aFields->GetAttribute<DeleteEntry>(relAtom)) &&
1189 mCachedFields) {
1190 if (auto maybeOldIDs =
1191 mCachedFields->GetAttribute<nsTArray<uint64_t>>(relAtom)) {
1192 for (uint64_t id : *maybeOldIDs) {
1193 // For each target, fetch its reverse relation map
1194 // We need to call `Lookup` here instead of `LookupOrInsert` because
1195 // it's possible the ID we're querying is from an acc that has since
1196 // been Shutdown(), and so has intentionally removed its reverse rels
1197 // from the doc's reverse rel cache.
1198 if (auto reverseRels = Document()->mReverseRelations.Lookup(id)) {
1199 // Then fetch its reverse relation's ID list. This should be safe
1200 // to do via LookupOrInsert because by the time we've gotten here,
1201 // we know the acc and `this` are still alive in the doc. If we hit
1202 // the following assert, we don't have parity on implicit/explicit
1203 // rels and something is wrong.
1204 nsTArray<uint64_t>& reverseRelIDs =
1205 reverseRels->LookupOrInsert(data.mReverseType);
1206 // There might be other reverse relations stored for this acc, so
1207 // remove our ID instead of deleting the array entirely.
1208 DebugOnly<bool> removed = reverseRelIDs.RemoveElement(ID());
1209 MOZ_ASSERT(removed, "Can't find old reverse relation");
1215 updateTracker.AppendElement(shouldAddNewImplicitRels);
1218 return updateTracker;
1221 void RemoteAccessible::PostProcessRelations(const nsTArray<bool>& aToUpdate) {
1222 size_t updateCount = aToUpdate.Length();
1223 MOZ_ASSERT(updateCount == ArrayLength(kRelationTypeAtoms),
1224 "Did not note update status for every relation type!");
1225 for (size_t i = 0; i < updateCount; i++) {
1226 if (aToUpdate.ElementAt(i)) {
1227 // Since kRelationTypeAtoms was used to generate aToUpdate, we
1228 // know the ith entry of aToUpdate corresponds to the relation type in
1229 // the ith entry of kRelationTypeAtoms. Fetch the related data here.
1230 auto const& data = kRelationTypeAtoms[i];
1232 const nsTArray<uint64_t>& newIDs =
1233 *mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom);
1234 for (uint64_t id : newIDs) {
1235 nsTHashMap<RelationType, nsTArray<uint64_t>>& relations =
1236 Document()->mReverseRelations.LookupOrInsert(id);
1237 nsTArray<uint64_t>& ids = relations.LookupOrInsert(data.mReverseType);
1238 ids.AppendElement(ID());
1244 void RemoteAccessible::PruneRelationsOnShutdown() {
1245 auto reverseRels = mDoc->mReverseRelations.Lookup(ID());
1246 if (!reverseRels) {
1247 return;
1249 for (auto const& data : kRelationTypeAtoms) {
1250 // Fetch the list of targets for this reverse relation
1251 auto reverseTargetList = reverseRels->Lookup(data.mReverseType);
1252 if (!reverseTargetList) {
1253 continue;
1255 for (uint64_t id : *reverseTargetList) {
1256 // For each target, retrieve its corresponding forward relation target
1257 // list
1258 RemoteAccessible* affectedAcc = mDoc->GetAccessible(id);
1259 if (!affectedAcc) {
1260 // It's possible the affect acc also shut down, in which case
1261 // we don't have anything to update.
1262 continue;
1264 if (auto forwardTargetList =
1265 affectedAcc->mCachedFields
1266 ->GetMutableAttribute<nsTArray<uint64_t>>(data.mAtom)) {
1267 forwardTargetList->RemoveElement(ID());
1268 if (!forwardTargetList->Length()) {
1269 // The ID we removed was the only thing in the list, so remove the
1270 // entry from the cache entirely -- don't leave an empty array.
1271 affectedAcc->mCachedFields->Remove(data.mAtom);
1276 // Remove this ID from the document's map of reverse relations.
1277 reverseRels.Remove();
1280 uint32_t RemoteAccessible::GetCachedTextLength() {
1281 MOZ_ASSERT(!HasChildren());
1282 if (!mCachedFields) {
1283 return 0;
1285 VERIFY_CACHE(CacheDomain::Text);
1286 auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text);
1287 if (!text) {
1288 return 0;
1290 return text->Length();
1293 Maybe<const nsTArray<int32_t>&> RemoteAccessible::GetCachedTextLines() {
1294 MOZ_ASSERT(!HasChildren());
1295 if (!mCachedFields) {
1296 return Nothing();
1298 VERIFY_CACHE(CacheDomain::Text);
1299 return mCachedFields->GetAttribute<nsTArray<int32_t>>(
1300 CacheKey::TextLineStarts);
1303 nsRect RemoteAccessible::GetCachedCharRect(int32_t aOffset) {
1304 MOZ_ASSERT(IsText());
1305 if (!mCachedFields) {
1306 return nsRect();
1309 if (Maybe<const nsTArray<int32_t>&> maybeCharData =
1310 mCachedFields->GetAttribute<nsTArray<int32_t>>(
1311 CacheKey::TextBounds)) {
1312 const nsTArray<int32_t>& charData = *maybeCharData;
1313 const int32_t index = aOffset * kNumbersInRect;
1314 if (index < static_cast<int32_t>(charData.Length())) {
1315 return nsRect(charData[index], charData[index + 1], charData[index + 2],
1316 charData[index + 3]);
1318 // It is valid for a client to call this with an offset 1 after the last
1319 // character because of the insertion point at the end of text boxes.
1320 MOZ_ASSERT(index == static_cast<int32_t>(charData.Length()));
1323 return nsRect();
1326 void RemoteAccessible::DOMNodeID(nsString& aID) const {
1327 if (mCachedFields) {
1328 mCachedFields->GetAttribute(CacheKey::DOMNodeID, aID);
1329 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
1333 void RemoteAccessible::DOMNodeClass(nsString& aClass) const {
1334 if (mCachedFields) {
1335 mCachedFields->GetAttribute(CacheKey::DOMNodeClass, aClass);
1336 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
1340 void RemoteAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX,
1341 int32_t aY) {
1342 Unused << mDoc->SendScrollToPoint(mID, aScrollType, aX, aY);
1345 #if !defined(XP_WIN)
1346 void RemoteAccessible::Announce(const nsString& aAnnouncement,
1347 uint16_t aPriority) {
1348 Unused << mDoc->SendAnnounce(mID, aAnnouncement, aPriority);
1350 #endif // !defined(XP_WIN)
1352 int32_t RemoteAccessible::ValueRegion() const {
1353 MOZ_ASSERT(TagName() == nsGkAtoms::meter,
1354 "Accessing value region on non-meter element?");
1355 if (mCachedFields) {
1356 if (auto region =
1357 mCachedFields->GetAttribute<int32_t>(CacheKey::ValueRegion)) {
1358 return *region;
1361 // Expose sub-optimal (but not critical) as the value region, as a fallback.
1362 return 0;
1365 void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
1366 int32_t aEndOffset,
1367 uint32_t aCoordinateType,
1368 int32_t aX, int32_t aY) {
1369 Unused << mDoc->SendScrollSubstringToPoint(mID, aStartOffset, aEndOffset,
1370 aCoordinateType, aX, aY);
1373 RefPtr<const AccAttributes> RemoteAccessible::GetCachedTextAttributes() {
1374 MOZ_ASSERT(IsText() || IsHyperText());
1375 if (mCachedFields) {
1376 auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
1377 CacheKey::TextAttributes);
1378 VERIFY_CACHE(CacheDomain::Text);
1379 return attrs;
1381 return nullptr;
1384 already_AddRefed<AccAttributes> RemoteAccessible::DefaultTextAttributes() {
1385 RefPtr<const AccAttributes> attrs = GetCachedTextAttributes();
1386 RefPtr<AccAttributes> result = new AccAttributes();
1387 if (attrs) {
1388 attrs->CopyTo(result);
1390 return result.forget();
1393 RefPtr<const AccAttributes> RemoteAccessible::GetCachedARIAAttributes() const {
1394 if (mCachedFields) {
1395 auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
1396 CacheKey::ARIAAttributes);
1397 VERIFY_CACHE(CacheDomain::ARIA);
1398 return attrs;
1400 return nullptr;
1403 nsString RemoteAccessible::GetCachedHTMLNameAttribute() const {
1404 if (mCachedFields) {
1405 if (auto maybeName =
1406 mCachedFields->GetAttribute<nsString>(CacheKey::DOMName)) {
1407 return *maybeName;
1410 return nsString();
1413 uint64_t RemoteAccessible::State() {
1414 uint64_t state = 0;
1415 if (mCachedFields) {
1416 if (auto rawState =
1417 mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) {
1418 VERIFY_CACHE(CacheDomain::State);
1419 state = *rawState;
1420 // Handle states that are derived from other states.
1421 if (!(state & states::UNAVAILABLE)) {
1422 state |= states::ENABLED | states::SENSITIVE;
1424 if (state & states::EXPANDABLE && !(state & states::EXPANDED)) {
1425 state |= states::COLLAPSED;
1429 ApplyImplicitState(state);
1431 auto* cbc = mDoc->GetBrowsingContext();
1432 if (cbc && !cbc->IsActive()) {
1433 // If our browsing context is _not_ active, we're in a background tab
1434 // and inherently offscreen.
1435 state |= states::OFFSCREEN;
1436 } else {
1437 // If we're in an active browsing context, there are a few scenarios we
1438 // need to address:
1439 // - We are an iframe document in the visual viewport
1440 // - We are an iframe document out of the visual viewport
1441 // - We are non-iframe content in the visual viewport
1442 // - We are non-iframe content out of the visual viewport
1443 // We assume top level tab docs are on screen if their BC is active, so
1444 // we don't need additional handling for them here.
1445 if (!mDoc->IsTopLevel()) {
1446 // Here we handle iframes and iframe content.
1447 // We use an iframe's outer doc's position in the embedding document's
1448 // viewport to determine if the iframe has been scrolled offscreen.
1449 Accessible* docParent = mDoc->Parent();
1450 // In rare cases, we might not have an outer doc yet. Return if that's
1451 // the case.
1452 if (NS_WARN_IF(!docParent || !docParent->IsRemote())) {
1453 return state;
1456 RemoteAccessible* outerDoc = docParent->AsRemote();
1457 DocAccessibleParent* embeddingDocument = outerDoc->Document();
1458 if (embeddingDocument &&
1459 !embeddingDocument->mOnScreenAccessibles.Contains(outerDoc->ID())) {
1460 // Our embedding document's viewport cache doesn't contain the ID of
1461 // our outer doc, so this iframe (and any of its content) is
1462 // offscreen.
1463 state |= states::OFFSCREEN;
1464 } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
1465 // Our embedding document's viewport cache contains the ID of our
1466 // outer doc, but the iframe's viewport cache doesn't contain our ID.
1467 // We are offscreen.
1468 state |= states::OFFSCREEN;
1470 } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
1471 // We are top level tab content (but not a top level tab doc).
1472 // If our tab doc's viewport cache doesn't contain our ID, we're
1473 // offscreen.
1474 state |= states::OFFSCREEN;
1479 return state;
1482 already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
1483 RefPtr<AccAttributes> attributes = new AccAttributes();
1484 nsAccessibilityService* accService = GetAccService();
1485 if (!accService) {
1486 // The service can be shut down before RemoteAccessibles. If it is shut
1487 // down, we can't calculate some attributes. We're about to die anyway.
1488 return attributes.forget();
1491 if (mCachedFields) {
1492 // We use GetAttribute instead of GetAttributeRefPtr because we need
1493 // nsAtom, not const nsAtom.
1494 if (auto tag =
1495 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1496 attributes->SetAttribute(nsGkAtoms::tag, *tag);
1499 GroupPos groupPos = GroupPosition();
1500 nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, groupPos.setSize,
1501 groupPos.posInSet);
1503 bool hierarchical = false;
1504 uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
1505 if (itemCount) {
1506 attributes->SetAttribute(nsGkAtoms::child_item_count,
1507 static_cast<int32_t>(itemCount));
1510 if (hierarchical) {
1511 attributes->SetAttribute(nsGkAtoms::tree, true);
1514 if (auto inputType =
1515 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
1516 attributes->SetAttribute(nsGkAtoms::textInputType, *inputType);
1519 if (RefPtr<nsAtom> display = DisplayStyle()) {
1520 attributes->SetAttribute(nsGkAtoms::display, display);
1523 if (TableCellAccessible* cell = AsTableCell()) {
1524 TableAccessible* table = cell->Table();
1525 uint32_t row = cell->RowIdx();
1526 uint32_t col = cell->ColIdx();
1527 int32_t cellIdx = table->CellIndexAt(row, col);
1528 if (cellIdx != -1) {
1529 attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx);
1533 if (bool layoutGuess = TableIsProbablyForLayout()) {
1534 attributes->SetAttribute(nsGkAtoms::layout_guess, layoutGuess);
1537 accService->MarkupAttributes(this, attributes);
1539 const nsRoleMapEntry* roleMap = ARIARoleMap();
1540 nsAutoString role;
1541 mCachedFields->GetAttribute(CacheKey::ARIARole, role);
1542 if (role.IsEmpty()) {
1543 if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty) {
1544 // Single, known role.
1545 attributes->SetAttribute(nsGkAtoms::xmlroles, roleMap->roleAtom);
1546 } else if (nsAtom* landmark = LandmarkRole()) {
1547 // Landmark role from markup; e.g. HTML <main>.
1548 attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
1550 } else {
1551 // Unknown role or multiple roles.
1552 attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role));
1555 if (roleMap) {
1556 nsAutoString live;
1557 if (nsAccUtils::GetLiveAttrValue(roleMap->liveAttRule, live)) {
1558 attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
1562 if (auto ariaAttrs = GetCachedARIAAttributes()) {
1563 ariaAttrs->CopyTo(attributes);
1566 nsAccUtils::SetLiveContainerAttributes(attributes, this);
1568 nsString id;
1569 DOMNodeID(id);
1570 if (!id.IsEmpty()) {
1571 attributes->SetAttribute(nsGkAtoms::id, std::move(id));
1574 nsString className;
1575 DOMNodeClass(className);
1576 if (!className.IsEmpty()) {
1577 attributes->SetAttribute(nsGkAtoms::_class, std::move(className));
1580 if (IsImage()) {
1581 nsString src;
1582 mCachedFields->GetAttribute(CacheKey::SrcURL, src);
1583 if (!src.IsEmpty()) {
1584 attributes->SetAttribute(nsGkAtoms::src, std::move(src));
1588 if (IsTextField()) {
1589 nsString placeholder;
1590 mCachedFields->GetAttribute(CacheKey::HTMLPlaceholder, placeholder);
1591 if (!placeholder.IsEmpty()) {
1592 attributes->SetAttribute(nsGkAtoms::placeholder,
1593 std::move(placeholder));
1594 attributes->Remove(nsGkAtoms::aria_placeholder);
1598 nsString popupType;
1599 mCachedFields->GetAttribute(CacheKey::PopupType, popupType);
1600 if (!popupType.IsEmpty()) {
1601 attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popupType));
1605 nsAutoString name;
1606 if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
1607 attributes->SetAttribute(nsGkAtoms::explicit_name, true);
1610 // Expose the string value via the valuetext attribute. We test for the value
1611 // interface because we don't want to expose traditional Value() information
1612 // such as URLs on links and documents, or text in an input.
1613 // XXX This is only needed for ATK, since other APIs have native ways to
1614 // retrieve value text. We should probably move this into ATK specific code.
1615 // For now, we do this because LocalAccessible does it.
1616 if (HasNumericValue()) {
1617 nsString valuetext;
1618 Value(valuetext);
1619 attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
1622 return attributes.forget();
1625 nsAtom* RemoteAccessible::TagName() const {
1626 if (mCachedFields) {
1627 if (auto tag =
1628 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1629 return *tag;
1633 return nullptr;
1636 already_AddRefed<nsAtom> RemoteAccessible::InputType() const {
1637 if (mCachedFields) {
1638 if (auto inputType =
1639 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
1640 RefPtr<nsAtom> result = *inputType;
1641 return result.forget();
1645 return nullptr;
1648 already_AddRefed<nsAtom> RemoteAccessible::DisplayStyle() const {
1649 if (mCachedFields) {
1650 if (auto display =
1651 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSDisplay)) {
1652 RefPtr<nsAtom> result = *display;
1653 return result.forget();
1656 return nullptr;
1659 float RemoteAccessible::Opacity() const {
1660 if (mCachedFields) {
1661 if (auto opacity = mCachedFields->GetAttribute<float>(CacheKey::Opacity)) {
1662 return *opacity;
1666 return 1.0f;
1669 void RemoteAccessible::LiveRegionAttributes(nsAString* aLive,
1670 nsAString* aRelevant,
1671 Maybe<bool>* aAtomic,
1672 nsAString* aBusy) const {
1673 if (!mCachedFields) {
1674 return;
1676 RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes();
1677 if (!attrs) {
1678 return;
1680 if (aLive) {
1681 attrs->GetAttribute(nsGkAtoms::aria_live, *aLive);
1683 if (aRelevant) {
1684 attrs->GetAttribute(nsGkAtoms::aria_relevant, *aRelevant);
1686 if (aAtomic) {
1687 if (auto value =
1688 attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::aria_atomic)) {
1689 *aAtomic = Some(*value == nsGkAtoms::_true);
1692 if (aBusy) {
1693 attrs->GetAttribute(nsGkAtoms::aria_busy, *aBusy);
1697 Maybe<bool> RemoteAccessible::ARIASelected() const {
1698 if (mCachedFields) {
1699 return mCachedFields->GetAttribute<bool>(CacheKey::ARIASelected);
1701 return Nothing();
1704 nsAtom* RemoteAccessible::GetPrimaryAction() const {
1705 if (mCachedFields) {
1706 if (auto action = mCachedFields->GetAttribute<RefPtr<nsAtom>>(
1707 CacheKey::PrimaryAction)) {
1708 return *action;
1712 return nullptr;
1715 uint8_t RemoteAccessible::ActionCount() const {
1716 uint8_t actionCount = 0;
1717 if (mCachedFields) {
1718 if (HasPrimaryAction() || ActionAncestor()) {
1719 actionCount++;
1722 if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1723 actionCount++;
1725 VERIFY_CACHE(CacheDomain::Actions);
1728 return actionCount;
1731 void RemoteAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
1732 if (mCachedFields) {
1733 aName.Truncate();
1734 nsAtom* action = GetPrimaryAction();
1735 bool hasActionAncestor = !action && ActionAncestor();
1737 switch (aIndex) {
1738 case 0:
1739 if (action) {
1740 action->ToString(aName);
1741 } else if (hasActionAncestor) {
1742 aName.AssignLiteral("click ancestor");
1743 } else if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1744 aName.AssignLiteral("showlongdesc");
1746 break;
1747 case 1:
1748 if ((action || hasActionAncestor) &&
1749 mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1750 aName.AssignLiteral("showlongdesc");
1752 break;
1753 default:
1754 break;
1757 VERIFY_CACHE(CacheDomain::Actions);
1760 bool RemoteAccessible::DoAction(uint8_t aIndex) const {
1761 if (ActionCount() < aIndex + 1) {
1762 return false;
1765 Unused << mDoc->SendDoActionAsync(mID, aIndex);
1766 return true;
1769 KeyBinding RemoteAccessible::AccessKey() const {
1770 if (mCachedFields) {
1771 if (auto value =
1772 mCachedFields->GetAttribute<uint64_t>(CacheKey::AccessKey)) {
1773 return KeyBinding(*value);
1776 return KeyBinding();
1779 void RemoteAccessible::SelectionRanges(nsTArray<TextRange>* aRanges) const {
1780 Document()->SelectionRanges(aRanges);
1783 bool RemoteAccessible::RemoveFromSelection(int32_t aSelectionNum) {
1784 MOZ_ASSERT(IsHyperText());
1785 if (SelectionCount() <= aSelectionNum) {
1786 return false;
1789 Unused << mDoc->SendRemoveTextSelection(mID, aSelectionNum);
1791 return true;
1794 void RemoteAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
1795 int32_t* aPosInSet) const {
1796 if (!mCachedFields) {
1797 return;
1800 if (aLevel) {
1801 if (auto level =
1802 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_level)) {
1803 *aLevel = *level;
1806 if (aSetSize) {
1807 if (auto setsize =
1808 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_setsize)) {
1809 *aSetSize = *setsize;
1812 if (aPosInSet) {
1813 if (auto posinset =
1814 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_posinset)) {
1815 *aPosInSet = *posinset;
1820 AccGroupInfo* RemoteAccessible::GetGroupInfo() const {
1821 if (!mCachedFields) {
1822 return nullptr;
1825 if (auto groupInfo = mCachedFields->GetAttribute<UniquePtr<AccGroupInfo>>(
1826 CacheKey::GroupInfo)) {
1827 return groupInfo->get();
1830 return nullptr;
1833 AccGroupInfo* RemoteAccessible::GetOrCreateGroupInfo() {
1834 AccGroupInfo* groupInfo = GetGroupInfo();
1835 if (groupInfo) {
1836 return groupInfo;
1839 groupInfo = AccGroupInfo::CreateGroupInfo(this);
1840 if (groupInfo) {
1841 if (!mCachedFields) {
1842 mCachedFields = new AccAttributes();
1845 mCachedFields->SetAttribute(CacheKey::GroupInfo, groupInfo);
1848 return groupInfo;
1851 void RemoteAccessible::InvalidateGroupInfo() {
1852 if (mCachedFields) {
1853 mCachedFields->Remove(CacheKey::GroupInfo);
1857 void RemoteAccessible::GetPositionAndSetSize(int32_t* aPosInSet,
1858 int32_t* aSetSize) {
1859 if (IsHTMLRadioButton()) {
1860 *aSetSize = 0;
1861 Relation rel = RelationByType(RelationType::MEMBER_OF);
1862 while (Accessible* radio = rel.Next()) {
1863 ++*aSetSize;
1864 if (radio == this) {
1865 *aPosInSet = *aSetSize;
1868 return;
1871 Accessible::GetPositionAndSetSize(aPosInSet, aSetSize);
1874 bool RemoteAccessible::HasPrimaryAction() const {
1875 return mCachedFields && mCachedFields->HasAttribute(CacheKey::PrimaryAction);
1878 void RemoteAccessible::TakeFocus() const {
1879 Unused << mDoc->SendTakeFocus(mID);
1880 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
1881 auto* bp = static_cast<dom::BrowserParent*>(mDoc->Manager());
1882 MOZ_ASSERT(bp);
1883 dom::Element* owner = bp->GetOwnerElement();
1884 if (fm->GetFocusedElement() == owner) {
1885 // This remote document tree is already focused. We don't need to do
1886 // anything else.
1887 return;
1890 // Otherwise, we need to focus the <browser> or <iframe> element embedding the
1891 // remote document in the parent process. If `this` is in an OOP iframe, we
1892 // first need to focus the embedder iframe (and any ancestor OOP iframes). If
1893 // the parent process embedder element were already focused, that would happen
1894 // automatically, but it isn't. We can't simply focus the parent process
1895 // embedder element before calling mDoc->SendTakeFocus because that would
1896 // cause the remote document to restore focus to the last focused element,
1897 // which we don't want.
1898 DocAccessibleParent* embeddedDoc = mDoc;
1899 Accessible* embedder = mDoc->Parent();
1900 while (embedder) {
1901 MOZ_ASSERT(embedder->IsOuterDoc());
1902 RemoteAccessible* embedderRemote = embedder->AsRemote();
1903 if (!embedderRemote) {
1904 // This is the element in the parent process which embeds the remote
1905 // document.
1906 embedder->TakeFocus();
1907 break;
1909 // This is a remote <iframe>.
1910 if (embeddedDoc->IsTopLevelInContentProcess()) {
1911 // We only need to focus OOP iframes because these are where we cross
1912 // process boundaries.
1913 Unused << embedderRemote->mDoc->SendTakeFocus(embedderRemote->mID);
1915 embeddedDoc = embedderRemote->mDoc;
1916 embedder = embeddedDoc->Parent();
1920 void RemoteAccessible::ScrollTo(uint32_t aHow) const {
1921 Unused << mDoc->SendScrollTo(mID, aHow);
1924 ////////////////////////////////////////////////////////////////////////////////
1925 // SelectAccessible
1927 void RemoteAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
1928 Pivot p = Pivot(this);
1929 PivotStateRule rule(states::SELECTED);
1930 for (Accessible* selected = p.First(rule); selected;
1931 selected = p.Next(selected, rule)) {
1932 aItems->AppendElement(selected);
1936 uint32_t RemoteAccessible::SelectedItemCount() {
1937 uint32_t count = 0;
1938 Pivot p = Pivot(this);
1939 PivotStateRule rule(states::SELECTED);
1940 for (Accessible* selected = p.First(rule); selected;
1941 selected = p.Next(selected, rule)) {
1942 count++;
1945 return count;
1948 Accessible* RemoteAccessible::GetSelectedItem(uint32_t aIndex) {
1949 uint32_t index = 0;
1950 Accessible* selected = nullptr;
1951 Pivot p = Pivot(this);
1952 PivotStateRule rule(states::SELECTED);
1953 for (selected = p.First(rule); selected && index < aIndex;
1954 selected = p.Next(selected, rule)) {
1955 index++;
1958 return selected;
1961 bool RemoteAccessible::IsItemSelected(uint32_t aIndex) {
1962 uint32_t index = 0;
1963 Accessible* selectable = nullptr;
1964 Pivot p = Pivot(this);
1965 PivotStateRule rule(states::SELECTABLE);
1966 for (selectable = p.First(rule); selectable && index < aIndex;
1967 selectable = p.Next(selectable, rule)) {
1968 index++;
1971 return selectable && selectable->State() & states::SELECTED;
1974 bool RemoteAccessible::AddItemToSelection(uint32_t aIndex) {
1975 uint32_t index = 0;
1976 Accessible* selectable = nullptr;
1977 Pivot p = Pivot(this);
1978 PivotStateRule rule(states::SELECTABLE);
1979 for (selectable = p.First(rule); selectable && index < aIndex;
1980 selectable = p.Next(selectable, rule)) {
1981 index++;
1984 if (selectable) selectable->SetSelected(true);
1986 return static_cast<bool>(selectable);
1989 bool RemoteAccessible::RemoveItemFromSelection(uint32_t aIndex) {
1990 uint32_t index = 0;
1991 Accessible* selectable = nullptr;
1992 Pivot p = Pivot(this);
1993 PivotStateRule rule(states::SELECTABLE);
1994 for (selectable = p.First(rule); selectable && index < aIndex;
1995 selectable = p.Next(selectable, rule)) {
1996 index++;
1999 if (selectable) selectable->SetSelected(false);
2001 return static_cast<bool>(selectable);
2004 bool RemoteAccessible::SelectAll() {
2005 if ((State() & states::MULTISELECTABLE) == 0) {
2006 return false;
2009 bool success = false;
2010 Accessible* selectable = nullptr;
2011 Pivot p = Pivot(this);
2012 PivotStateRule rule(states::SELECTABLE);
2013 for (selectable = p.First(rule); selectable;
2014 selectable = p.Next(selectable, rule)) {
2015 success = true;
2016 selectable->SetSelected(true);
2018 return success;
2021 bool RemoteAccessible::UnselectAll() {
2022 if ((State() & states::MULTISELECTABLE) == 0) {
2023 return false;
2026 bool success = false;
2027 Accessible* selectable = nullptr;
2028 Pivot p = Pivot(this);
2029 PivotStateRule rule(states::SELECTABLE);
2030 for (selectable = p.First(rule); selectable;
2031 selectable = p.Next(selectable, rule)) {
2032 success = true;
2033 selectable->SetSelected(false);
2035 return success;
2038 void RemoteAccessible::TakeSelection() {
2039 Unused << mDoc->SendTakeSelection(mID);
2042 void RemoteAccessible::SetSelected(bool aSelect) {
2043 Unused << mDoc->SendSetSelected(mID, aSelect);
2046 TableAccessible* RemoteAccessible::AsTable() {
2047 if (IsTable()) {
2048 return CachedTableAccessible::GetFrom(this);
2050 return nullptr;
2053 TableCellAccessible* RemoteAccessible::AsTableCell() {
2054 if (IsTableCell()) {
2055 return CachedTableCellAccessible::GetFrom(this);
2057 return nullptr;
2060 bool RemoteAccessible::TableIsProbablyForLayout() {
2061 if (mCachedFields) {
2062 if (auto layoutGuess =
2063 mCachedFields->GetAttribute<bool>(CacheKey::TableLayoutGuess)) {
2064 return *layoutGuess;
2067 return false;
2070 nsTArray<int32_t>& RemoteAccessible::GetCachedHyperTextOffsets() {
2071 if (mCachedFields) {
2072 if (auto offsets = mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
2073 CacheKey::HyperTextOffsets)) {
2074 return *offsets;
2077 nsTArray<int32_t> newOffsets;
2078 if (!mCachedFields) {
2079 mCachedFields = new AccAttributes();
2081 mCachedFields->SetAttribute(CacheKey::HyperTextOffsets,
2082 std::move(newOffsets));
2083 return *mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
2084 CacheKey::HyperTextOffsets);
2087 void RemoteAccessible::SetCaretOffset(int32_t aOffset) {
2088 Unused << mDoc->SendSetCaretOffset(mID, aOffset);
2091 Maybe<int32_t> RemoteAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
2092 if (RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes()) {
2093 if (auto val = attrs->GetAttribute<int32_t>(aAttrName)) {
2094 return val;
2097 return Nothing();
2100 void RemoteAccessible::Language(nsAString& aLocale) {
2101 if (IsHyperText() || IsText()) {
2102 if (auto attrs = GetCachedTextAttributes()) {
2103 attrs->GetAttribute(nsGkAtoms::language, aLocale);
2105 if (IsText() && aLocale.IsEmpty()) {
2106 // If a leaf has the same language as its parent HyperTextAccessible, it
2107 // won't be cached in the leaf's text attributes. Check the parent.
2108 if (RemoteAccessible* parent = RemoteParent()) {
2109 if (auto attrs = parent->GetCachedTextAttributes()) {
2110 attrs->GetAttribute(nsGkAtoms::language, aLocale);
2114 } else if (mCachedFields) {
2115 mCachedFields->GetAttribute(CacheKey::Language, aLocale);
2119 void RemoteAccessible::ReplaceText(const nsAString& aText) {
2120 Unused << mDoc->SendReplaceText(mID, aText);
2123 void RemoteAccessible::InsertText(const nsAString& aText, int32_t aPosition) {
2124 Unused << mDoc->SendInsertText(mID, aText, aPosition);
2127 void RemoteAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) {
2128 Unused << mDoc->SendCopyText(mID, aStartPos, aEndPos);
2131 void RemoteAccessible::CutText(int32_t aStartPos, int32_t aEndPos) {
2132 Unused << mDoc->SendCutText(mID, aStartPos, aEndPos);
2135 void RemoteAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) {
2136 Unused << mDoc->SendDeleteText(mID, aStartPos, aEndPos);
2139 void RemoteAccessible::PasteText(int32_t aPosition) {
2140 Unused << mDoc->SendPasteText(mID, aPosition);
2143 size_t RemoteAccessible::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
2144 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
2147 size_t RemoteAccessible::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
2148 size_t size = 0;
2150 // Count attributes.
2151 if (mCachedFields) {
2152 size += mCachedFields->SizeOfIncludingThis(aMallocSizeOf);
2155 // We don't recurse into mChildren because they're already counted in their
2156 // document's mAccessibles.
2157 size += mChildren.ShallowSizeOfExcludingThis(aMallocSizeOf);
2159 return size;
2162 } // namespace a11y
2163 } // namespace mozilla