Bug 1877642 - Disable browser_fullscreen-tab-close-race.js on apple_silicon !debug...
[gecko.git] / accessible / ipc / RemoteAccessible.cpp
blob0077750ed3c3067803b37c9ff9b0302bff30439d
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 "nsTextEquivUtils.h"
23 #include "Pivot.h"
24 #include "Relation.h"
25 #include "mozilla/a11y/RelationType.h"
26 #include "xpcAccessibleDocument.h"
28 #ifdef A11Y_LOG
29 # include "Logging.h"
30 # define VERIFY_CACHE(domain) \
31 if (logging::IsEnabled(logging::eCache)) { \
32 Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \
34 #else
35 # define VERIFY_CACHE(domain) \
36 do { \
37 } while (0)
39 #endif
41 namespace mozilla {
42 namespace a11y {
44 void RemoteAccessible::Shutdown() {
45 MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
46 xpcAccessibleDocument* xpcDoc =
47 GetAccService()->GetCachedXPCDocument(Document());
48 if (xpcDoc) {
49 xpcDoc->NotifyOfShutdown(static_cast<RemoteAccessible*>(this));
52 if (IsTable() || IsTableCell()) {
53 CachedTableAccessible::Invalidate(this);
56 // Remove this acc's relation map from the doc's map of
57 // reverse relations. Prune forward relations associated with this
58 // acc's reverse relations. This also removes the acc's map of reverse
59 // rels from the mDoc's mReverseRelations.
60 PruneRelationsOnShutdown();
62 // XXX Ideally this wouldn't be necessary, but it seems OuterDoc
63 // accessibles can be destroyed before the doc they own.
64 uint32_t childCount = mChildren.Length();
65 if (!IsOuterDoc()) {
66 for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown();
67 } else {
68 if (childCount > 1) {
69 MOZ_CRASH("outer doc has too many documents!");
70 } else if (childCount == 1) {
71 mChildren[0]->AsDoc()->Unbind();
75 mChildren.Clear();
76 ProxyDestroyed(static_cast<RemoteAccessible*>(this));
77 mDoc->RemoveAccessible(static_cast<RemoteAccessible*>(this));
80 void RemoteAccessible::SetChildDoc(DocAccessibleParent* aChildDoc) {
81 MOZ_ASSERT(aChildDoc);
82 MOZ_ASSERT(mChildren.Length() == 0);
83 mChildren.AppendElement(aChildDoc);
86 void RemoteAccessible::ClearChildDoc(DocAccessibleParent* aChildDoc) {
87 MOZ_ASSERT(aChildDoc);
88 // This is possible if we're replacing one document with another: Doc 1
89 // has not had a chance to remove itself, but was already replaced by Doc 2
90 // in SetChildDoc(). This could result in two subsequent calls to
91 // ClearChildDoc() even though mChildren.Length() == 1.
92 MOZ_ASSERT(mChildren.Length() <= 1);
93 mChildren.RemoveElement(aChildDoc);
96 uint32_t RemoteAccessible::EmbeddedChildCount() {
97 size_t count = 0, kids = mChildren.Length();
98 for (size_t i = 0; i < kids; i++) {
99 if (mChildren[i]->IsEmbeddedObject()) {
100 count++;
104 return count;
107 int32_t RemoteAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
108 size_t index = 0, kids = mChildren.Length();
109 for (size_t i = 0; i < kids; i++) {
110 if (mChildren[i]->IsEmbeddedObject()) {
111 if (mChildren[i] == aChild) {
112 return index;
115 index++;
119 return -1;
122 Accessible* RemoteAccessible::EmbeddedChildAt(uint32_t aChildIdx) {
123 size_t index = 0, kids = mChildren.Length();
124 for (size_t i = 0; i < kids; i++) {
125 if (!mChildren[i]->IsEmbeddedObject()) {
126 continue;
129 if (index == aChildIdx) {
130 return mChildren[i];
133 index++;
136 return nullptr;
139 LocalAccessible* RemoteAccessible::OuterDocOfRemoteBrowser() const {
140 auto tab = static_cast<dom::BrowserParent*>(mDoc->Manager());
141 dom::Element* frame = tab->GetOwnerElement();
142 NS_ASSERTION(frame, "why isn't the tab in a frame!");
143 if (!frame) return nullptr;
145 DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
147 return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr;
150 void RemoteAccessible::SetParent(RemoteAccessible* aParent) {
151 if (!aParent) {
152 mParent = kNoParent;
153 } else {
154 MOZ_ASSERT(!IsDoc() || !aParent->IsDoc());
155 mParent = aParent->ID();
159 RemoteAccessible* RemoteAccessible::RemoteParent() const {
160 if (mParent == kNoParent) {
161 return nullptr;
164 // if we are not a document then are parent is another proxy in the same
165 // document. That means we can just ask our document for the proxy with our
166 // parent id.
167 if (!IsDoc()) {
168 return Document()->GetAccessible(mParent);
171 // If we are a top level document then our parent is not a proxy.
172 if (AsDoc()->IsTopLevel()) {
173 return nullptr;
176 // Finally if we are a non top level document then our parent id is for a
177 // proxy in our parent document so get the proxy from there.
178 DocAccessibleParent* parentDoc = AsDoc()->ParentDoc();
179 MOZ_ASSERT(parentDoc);
180 MOZ_ASSERT(mParent);
181 return parentDoc->GetAccessible(mParent);
184 void RemoteAccessible::ApplyCache(CacheUpdateType aUpdateType,
185 AccAttributes* aFields) {
186 if (!aFields) {
187 MOZ_ASSERT_UNREACHABLE("ApplyCache called with aFields == null");
188 return;
191 const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields);
192 if (auto maybeViewportCache =
193 aFields->GetAttribute<nsTArray<uint64_t>>(CacheKey::Viewport)) {
194 // Updating the viewport cache means the offscreen state of this
195 // document's accessibles has changed. Update the HashSet we use for
196 // checking offscreen state here.
197 MOZ_ASSERT(IsDoc(),
198 "Fetched the viewport cache from a non-doc accessible?");
199 AsDoc()->mOnScreenAccessibles.Clear();
200 for (auto id : *maybeViewportCache) {
201 AsDoc()->mOnScreenAccessibles.Insert(id);
205 if (aUpdateType == CacheUpdateType::Initial) {
206 mCachedFields = aFields;
207 } else {
208 if (!mCachedFields) {
209 // The fields cache can be uninitialized if there were no cache-worthy
210 // fields in the initial cache push.
211 // We don't do a simple assign because we don't want to store the
212 // DeleteEntry entries.
213 mCachedFields = new AccAttributes();
215 mCachedFields->Update(aFields);
218 if (IsTextLeaf()) {
219 RemoteAccessible* parent = RemoteParent();
220 if (parent && parent->IsHyperText()) {
221 parent->InvalidateCachedHyperTextOffsets();
225 PostProcessRelations(relUpdatesNeeded);
228 ENameValueFlag RemoteAccessible::Name(nsString& aName) const {
229 ENameValueFlag nameFlag = eNameOK;
230 if (mCachedFields) {
231 if (IsText()) {
232 mCachedFields->GetAttribute(CacheKey::Text, aName);
233 return eNameOK;
235 auto cachedNameFlag =
236 mCachedFields->GetAttribute<int32_t>(CacheKey::NameValueFlag);
237 if (cachedNameFlag) {
238 nameFlag = static_cast<ENameValueFlag>(*cachedNameFlag);
240 if (mCachedFields->GetAttribute(CacheKey::Name, aName)) {
241 VERIFY_CACHE(CacheDomain::NameAndDescription);
242 return nameFlag;
246 MOZ_ASSERT(aName.IsEmpty());
247 aName.SetIsVoid(true);
248 return nameFlag;
251 void RemoteAccessible::Description(nsString& aDescription) const {
252 if (mCachedFields) {
253 mCachedFields->GetAttribute(CacheKey::Description, aDescription);
254 VERIFY_CACHE(CacheDomain::NameAndDescription);
258 void RemoteAccessible::Value(nsString& aValue) const {
259 if (mCachedFields) {
260 if (mCachedFields->HasAttribute(CacheKey::TextValue)) {
261 mCachedFields->GetAttribute(CacheKey::TextValue, aValue);
262 VERIFY_CACHE(CacheDomain::Value);
263 return;
266 if (HasNumericValue()) {
267 double checkValue = CurValue();
268 if (!std::isnan(checkValue)) {
269 aValue.AppendFloat(checkValue);
271 return;
274 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
275 // Value of textbox is a textified subtree.
276 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) {
277 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
278 return;
281 if (IsCombobox()) {
282 // For combo boxes, rely on selection state to determine the value.
283 const Accessible* option =
284 const_cast<RemoteAccessible*>(this)->GetSelectedItem(0);
285 if (option) {
286 option->Name(aValue);
287 } else {
288 // If no selected item, determine the value from descendant elements.
289 nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
291 return;
294 if (IsTextLeaf() || IsImage()) {
295 if (const Accessible* actionAcc = ActionAncestor()) {
296 if (const_cast<Accessible*>(actionAcc)->State() & states::LINKED) {
297 // Text and image descendants of links expose the link URL as the
298 // value.
299 return actionAcc->Value(aValue);
306 double RemoteAccessible::CurValue() const {
307 if (mCachedFields) {
308 if (auto value =
309 mCachedFields->GetAttribute<double>(CacheKey::NumericValue)) {
310 VERIFY_CACHE(CacheDomain::Value);
311 return *value;
315 return UnspecifiedNaN<double>();
318 double RemoteAccessible::MinValue() const {
319 if (mCachedFields) {
320 if (auto min = mCachedFields->GetAttribute<double>(CacheKey::MinValue)) {
321 VERIFY_CACHE(CacheDomain::Value);
322 return *min;
326 return UnspecifiedNaN<double>();
329 double RemoteAccessible::MaxValue() const {
330 if (mCachedFields) {
331 if (auto max = mCachedFields->GetAttribute<double>(CacheKey::MaxValue)) {
332 VERIFY_CACHE(CacheDomain::Value);
333 return *max;
337 return UnspecifiedNaN<double>();
340 double RemoteAccessible::Step() const {
341 if (mCachedFields) {
342 if (auto step = mCachedFields->GetAttribute<double>(CacheKey::Step)) {
343 VERIFY_CACHE(CacheDomain::Value);
344 return *step;
348 return UnspecifiedNaN<double>();
351 bool RemoteAccessible::SetCurValue(double aValue) {
352 if (!HasNumericValue() || IsProgress()) {
353 return false;
356 const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
357 if (State() & kValueCannotChange) {
358 return false;
361 double checkValue = MinValue();
362 if (!std::isnan(checkValue) && aValue < checkValue) {
363 return false;
366 checkValue = MaxValue();
367 if (!std::isnan(checkValue) && aValue > checkValue) {
368 return false;
371 Unused << mDoc->SendSetCurValue(mID, aValue);
372 return true;
375 bool RemoteAccessible::ContainsPoint(int32_t aX, int32_t aY) {
376 if (!BoundsWithOffset(Nothing(), true).Contains(aX, aY)) {
377 return false;
379 if (!IsTextLeaf()) {
380 if (IsImage() || IsImageMap() || !HasChildren() ||
381 RefPtr{DisplayStyle()} != nsGkAtoms::inlinevalue) {
382 // This isn't an inline element that might contain text, so we don't need
383 // to walk lines. It's enough that our rect contains the point.
384 return true;
386 // Non-image inline elements with children can wrap across lines just like
387 // text leaves; see below.
388 // Walk the children, which will walk the lines of text in any text leaves.
389 uint32_t count = ChildCount();
390 for (uint32_t c = 0; c < count; ++c) {
391 RemoteAccessible* child = RemoteChildAt(c);
392 if (child->Role() == roles::TEXT_CONTAINER && child->IsClipped()) {
393 // There is a clipped child. This is a candidate for fuzzy hit testing.
394 // See RemoteAccessible::DoFuzzyHittesting.
395 return true;
397 if (child->ContainsPoint(aX, aY)) {
398 return true;
401 // None of our descendants contain the point, so nor do we.
402 return false;
404 // This is a text leaf. The text might wrap across lines, which means our
405 // rect might cover a wider area than the actual text. For example, if the
406 // text begins in the middle of the first line and wraps on to the second,
407 // the rect will cover the start of the first line and the end of the second.
408 auto lines = GetCachedTextLines();
409 if (!lines) {
410 // This means the text is empty or occupies a single line (but does not
411 // begin the line). In that case, the Bounds check above is sufficient,
412 // since there's only one rect.
413 return true;
415 uint32_t length = lines->Length();
416 MOZ_ASSERT(length > 0,
417 "Line starts shouldn't be in cache if there aren't any");
418 if (length == 0 || (length == 1 && (*lines)[0] == 0)) {
419 // This means the text begins and occupies a single line. Again, the Bounds
420 // check above is sufficient.
421 return true;
423 // Walk the lines of the text. Even if this text doesn't start at the
424 // beginning of a line (i.e. lines[0] > 0), we always want to consider its
425 // first line.
426 int32_t lineStart = 0;
427 for (uint32_t index = 0; index <= length; ++index) {
428 int32_t lineEnd;
429 if (index < length) {
430 int32_t nextLineStart = (*lines)[index];
431 if (nextLineStart == 0) {
432 // This Accessible starts at the beginning of a line. Here, we always
433 // treat 0 as the first line start anyway.
434 MOZ_ASSERT(index == 0);
435 continue;
437 lineEnd = nextLineStart - 1;
438 } else {
439 // This is the last line.
440 lineEnd = static_cast<int32_t>(nsAccUtils::TextLength(this)) - 1;
442 MOZ_ASSERT(lineEnd >= lineStart);
443 nsRect lineRect = GetCachedCharRect(lineStart);
444 if (lineEnd > lineStart) {
445 lineRect.UnionRect(lineRect, GetCachedCharRect(lineEnd));
447 if (BoundsWithOffset(Some(lineRect), true).Contains(aX, aY)) {
448 return true;
450 lineStart = lineEnd + 1;
452 return false;
455 RemoteAccessible* RemoteAccessible::DoFuzzyHittesting() {
456 uint32_t childCount = ChildCount();
457 if (!childCount) {
458 return nullptr;
460 // Check if this match has a clipped child.
461 // This usually indicates invisible text, and we're
462 // interested in returning the inner text content
463 // even if it doesn't contain the point we're hittesting.
464 RemoteAccessible* clippedContainer = nullptr;
465 for (uint32_t i = 0; i < childCount; i++) {
466 RemoteAccessible* child = RemoteChildAt(i);
467 if (child->Role() == roles::TEXT_CONTAINER) {
468 if (child->IsClipped()) {
469 clippedContainer = child;
470 break;
474 // If we found a clipped container, descend it in search of
475 // meaningful text leaves. Ignore non-text-leaf/text-container
476 // siblings.
477 RemoteAccessible* container = clippedContainer;
478 while (container) {
479 RemoteAccessible* textLeaf = nullptr;
480 bool continueSearch = false;
481 childCount = container->ChildCount();
482 for (uint32_t i = 0; i < childCount; i++) {
483 RemoteAccessible* child = container->RemoteChildAt(i);
484 if (child->Role() == roles::TEXT_CONTAINER) {
485 container = child;
486 continueSearch = true;
487 break;
489 if (child->IsTextLeaf()) {
490 textLeaf = child;
491 // Don't break here -- it's possible a text container
492 // exists as another sibling, and we should descend as
493 // deep as possible.
496 if (textLeaf) {
497 return textLeaf;
499 if (!continueSearch) {
500 // We didn't find anything useful in this set of siblings.
501 // Don't keep searching
502 break;
505 return nullptr;
508 Accessible* RemoteAccessible::ChildAtPoint(
509 int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) {
510 // Elements that are partially on-screen should have their bounds masked by
511 // their containing scroll area so hittesting yields results that are
512 // consistent with the content's visual representation. Pass this value to
513 // bounds calculation functions to indicate that we're hittesting.
514 const bool hitTesting = true;
516 if (IsOuterDoc() && aWhichChild == EWhichChildAtPoint::DirectChild) {
517 // This is an iframe, which is as deep as the viewport cache goes. The
518 // caller wants a direct child, which can only be the embedded document.
519 if (BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
520 return RemoteFirstChild();
522 return nullptr;
525 RemoteAccessible* lastMatch = nullptr;
526 // If `this` is a document, use its viewport cache instead of
527 // the cache of its parent document.
528 if (DocAccessibleParent* doc = IsDoc() ? AsDoc() : mDoc) {
529 if (!doc->mCachedFields) {
530 // A client call might arrive after we've constructed doc but before we
531 // get a cache push for it.
532 return nullptr;
534 if (auto maybeViewportCache =
535 doc->mCachedFields->GetAttribute<nsTArray<uint64_t>>(
536 CacheKey::Viewport)) {
537 // The retrieved viewport cache contains acc IDs in hittesting order.
538 // That is, items earlier in the list have z-indexes that are larger than
539 // those later in the list. If you were to build a tree by z-index, where
540 // chilren have larger z indices than their parents, iterating this list
541 // is essentially a postorder tree traversal.
542 const nsTArray<uint64_t>& viewportCache = *maybeViewportCache;
544 for (auto id : viewportCache) {
545 RemoteAccessible* acc = doc->GetAccessible(id);
546 if (!acc) {
547 // This can happen if the acc died in between
548 // pushing the viewport cache and doing this hittest
549 continue;
552 if (acc->IsOuterDoc() &&
553 aWhichChild == EWhichChildAtPoint::DeepestChild &&
554 acc->BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
555 // acc is an iframe, which is as deep as the viewport cache goes. This
556 // iframe contains the requested point.
557 RemoteAccessible* innerDoc = acc->RemoteFirstChild();
558 if (innerDoc) {
559 MOZ_ASSERT(innerDoc->IsDoc());
560 // Search the embedded document's viewport cache so we return the
561 // deepest descendant in that embedded document.
562 Accessible* deepestAcc = innerDoc->ChildAtPoint(
563 aX, aY, EWhichChildAtPoint::DeepestChild);
564 MOZ_ASSERT(!deepestAcc || deepestAcc->IsRemote());
565 lastMatch = deepestAcc ? deepestAcc->AsRemote() : nullptr;
566 break;
568 // If there is no embedded document, the iframe itself is the deepest
569 // descendant.
570 lastMatch = acc;
571 break;
574 if (acc == this) {
575 MOZ_ASSERT(!acc->IsOuterDoc());
576 // Even though we're searching from the doc's cache
577 // this call shouldn't pass the boundary defined by
578 // the acc this call originated on. If we hit `this`,
579 // return our most recent match.
580 if (!lastMatch &&
581 BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
582 // If we haven't found a match, but `this` contains the point we're
583 // looking for, set it as our temp last match so we can
584 // (potentially) do fuzzy hittesting on it below.
585 lastMatch = acc;
587 break;
590 if (acc->ContainsPoint(aX, aY)) {
591 // Because our rects are in hittesting order, the
592 // first match we encounter is guaranteed to be the
593 // deepest match.
594 lastMatch = acc;
595 break;
598 if (lastMatch) {
599 RemoteAccessible* fuzzyMatch = lastMatch->DoFuzzyHittesting();
600 lastMatch = fuzzyMatch ? fuzzyMatch : lastMatch;
605 if (aWhichChild == EWhichChildAtPoint::DirectChild && lastMatch) {
606 // lastMatch is the deepest match. Walk up to the direct child of this.
607 RemoteAccessible* parent = lastMatch->RemoteParent();
608 for (;;) {
609 if (parent == this) {
610 break;
612 if (!parent || parent->IsDoc()) {
613 // `this` is not an ancestor of lastMatch. Ignore lastMatch.
614 lastMatch = nullptr;
615 break;
617 lastMatch = parent;
618 parent = parent->RemoteParent();
620 } else if (aWhichChild == EWhichChildAtPoint::DeepestChild && lastMatch &&
621 !IsDoc() && !IsAncestorOf(lastMatch)) {
622 // If we end up with a match that is not in the ancestor chain
623 // of the accessible this call originated on, we should ignore it.
624 // This can happen when the aX, aY given are outside `this`.
625 lastMatch = nullptr;
628 if (!lastMatch && BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
629 // Even though the hit target isn't inside `this`, the point is still
630 // within our bounds, so fall back to `this`.
631 return this;
634 return lastMatch;
637 Maybe<nsRect> RemoteAccessible::RetrieveCachedBounds() const {
638 if (!mCachedFields) {
639 return Nothing();
642 Maybe<const nsTArray<int32_t>&> maybeArray =
643 mCachedFields->GetAttribute<nsTArray<int32_t>>(
644 CacheKey::ParentRelativeBounds);
645 if (maybeArray) {
646 const nsTArray<int32_t>& relativeBoundsArr = *maybeArray;
647 MOZ_ASSERT(relativeBoundsArr.Length() == 4,
648 "Incorrectly sized bounds array");
649 nsRect relativeBoundsRect(relativeBoundsArr[0], relativeBoundsArr[1],
650 relativeBoundsArr[2], relativeBoundsArr[3]);
651 return Some(relativeBoundsRect);
654 return Nothing();
657 void RemoteAccessible::ApplyCrossDocOffset(nsRect& aBounds) const {
658 if (!IsDoc()) {
659 // We should only apply cross-doc offsets to documents. If we're anything
660 // else, return early here.
661 return;
664 RemoteAccessible* parentAcc = RemoteParent();
665 if (!parentAcc || !parentAcc->IsOuterDoc()) {
666 return;
669 Maybe<const nsTArray<int32_t>&> maybeOffset =
670 parentAcc->mCachedFields->GetAttribute<nsTArray<int32_t>>(
671 CacheKey::CrossDocOffset);
672 if (!maybeOffset) {
673 return;
676 MOZ_ASSERT(maybeOffset->Length() == 2);
677 const nsTArray<int32_t>& offset = *maybeOffset;
678 // Our retrieved value is in app units, so we don't need to do any
679 // unit conversion here.
680 aBounds.MoveBy(offset[0], offset[1]);
683 bool RemoteAccessible::ApplyTransform(nsRect& aCumulativeBounds) const {
684 // First, attempt to retrieve the transform from the cache.
685 Maybe<const UniquePtr<gfx::Matrix4x4>&> maybeTransform =
686 mCachedFields->GetAttribute<UniquePtr<gfx::Matrix4x4>>(
687 CacheKey::TransformMatrix);
688 if (!maybeTransform) {
689 return false;
692 auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix(
693 *(*maybeTransform));
695 // Our matrix is in CSS Pixels, so we need our rect to be in CSS
696 // Pixels too. Convert before applying.
697 auto boundsInPixels = CSSRect::FromAppUnits(aCumulativeBounds);
698 boundsInPixels = mtxInPixels.TransformBounds(boundsInPixels);
699 aCumulativeBounds = CSSRect::ToAppUnits(boundsInPixels);
701 return true;
704 bool RemoteAccessible::ApplyScrollOffset(nsRect& aBounds) const {
705 Maybe<const nsTArray<int32_t>&> maybeScrollPosition =
706 mCachedFields->GetAttribute<nsTArray<int32_t>>(CacheKey::ScrollPosition);
708 if (!maybeScrollPosition || maybeScrollPosition->Length() != 2) {
709 return false;
711 // Our retrieved value is in app units, so we don't need to do any
712 // unit conversion here.
713 const nsTArray<int32_t>& scrollPosition = *maybeScrollPosition;
715 // Scroll position is an inverse representation of scroll offset (since the
716 // further the scroll bar moves down the page, the further the page content
717 // moves up/closer to the origin).
718 nsPoint scrollOffset(-scrollPosition[0], -scrollPosition[1]);
720 aBounds.MoveBy(scrollOffset.x, scrollOffset.y);
722 // Return true here even if the scroll offset was 0,0 because the RV is used
723 // as a scroll container indicator. Non-scroll containers won't have cached
724 // scroll position.
725 return true;
728 nsRect RemoteAccessible::BoundsInAppUnits() const {
729 if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()->Top()) {
730 if (dom::BrowserParent* bp = cbc->GetBrowserParent()) {
731 DocAccessibleParent* topDoc = bp->GetTopLevelDocAccessible();
732 if (topDoc && topDoc->mCachedFields) {
733 auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
734 CacheKey::AppUnitsPerDevPixel);
735 MOZ_ASSERT(appUnitsPerDevPixel);
736 return LayoutDeviceIntRect::ToAppUnits(Bounds(), *appUnitsPerDevPixel);
740 return LayoutDeviceIntRect::ToAppUnits(Bounds(), AppUnitsPerCSSPixel());
743 bool RemoteAccessible::IsFixedPos() const {
744 MOZ_ASSERT(mCachedFields);
745 if (auto maybePosition =
746 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CssPosition)) {
747 return *maybePosition == nsGkAtoms::fixed;
750 return false;
753 bool RemoteAccessible::IsOverflowHidden() const {
754 MOZ_ASSERT(mCachedFields);
755 if (auto maybeOverflow =
756 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSOverflow)) {
757 return *maybeOverflow == nsGkAtoms::hidden;
760 return false;
763 bool RemoteAccessible::IsClipped() const {
764 MOZ_ASSERT(mCachedFields);
765 if (mCachedFields->GetAttribute<bool>(CacheKey::IsClipped)) {
766 return true;
769 return false;
772 LayoutDeviceIntRect RemoteAccessible::BoundsWithOffset(
773 Maybe<nsRect> aOffset, bool aBoundsAreForHittesting) const {
774 Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
775 if (maybeBounds) {
776 nsRect bounds = *maybeBounds;
777 // maybeBounds is parent-relative. However, the transform matrix we cache
778 // (if any) is meant to operate on self-relative rects. Therefore, make
779 // bounds self-relative until after we transform.
780 bounds.MoveTo(0, 0);
781 const DocAccessibleParent* topDoc = IsDoc() ? AsDoc() : nullptr;
783 if (aOffset.isSome()) {
784 // The rect we've passed in is in app units, so no conversion needed.
785 nsRect internalRect = *aOffset;
786 bounds.SetRectX(bounds.x + internalRect.x, internalRect.width);
787 bounds.SetRectY(bounds.y + internalRect.y, internalRect.height);
790 Unused << ApplyTransform(bounds);
791 // Now apply the parent-relative offset.
792 bounds.MoveBy(maybeBounds->TopLeft());
794 ApplyCrossDocOffset(bounds);
796 LayoutDeviceIntRect devPxBounds;
797 const Accessible* acc = Parent();
798 bool encounteredFixedContainer = IsFixedPos();
799 while (acc && acc->IsRemote()) {
800 // Return early if we're hit testing and our cumulative bounds are empty,
801 // since walking the ancestor chain won't produce any hits.
802 if (aBoundsAreForHittesting && bounds.IsEmpty()) {
803 return LayoutDeviceIntRect{};
806 RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote();
808 if (Maybe<nsRect> maybeRemoteBounds = remoteAcc->RetrieveCachedBounds()) {
809 nsRect remoteBounds = *maybeRemoteBounds;
810 // We need to take into account a non-1 resolution set on the
811 // presshell. This happens with async pinch zooming, among other
812 // things. We can't reliably query this value in the parent process,
813 // so we retrieve it from the document's cache.
814 if (remoteAcc->IsDoc()) {
815 // Apply the document's resolution to the bounds we've gathered
816 // thus far. We do this before applying the document's offset
817 // because document accs should not have their bounds scaled by
818 // their own resolution. They should be scaled by the resolution
819 // of their containing document (if any).
820 Maybe<float> res =
821 remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>(
822 CacheKey::Resolution);
823 MOZ_ASSERT(res, "No cached document resolution found.");
824 bounds.ScaleRoundOut(res.valueOr(1.0f));
826 topDoc = remoteAcc->AsDoc();
829 // We don't account for the document offset of iframes when
830 // computing parent-relative bounds. Instead, we store this value
831 // separately on all iframes and apply it here. See the comments in
832 // LocalAccessible::BundleFieldsForCache where we set the
833 // nsGkAtoms::crossorigin attribute.
834 remoteAcc->ApplyCrossDocOffset(remoteBounds);
835 if (!encounteredFixedContainer) {
836 // Apply scroll offset, if applicable. Only the contents of an
837 // element are affected by its scroll offset, which is why this call
838 // happens in this loop instead of both inside and outside of
839 // the loop (like ApplyTransform).
840 // Never apply scroll offsets past a fixed container.
841 const bool hasScrollArea = remoteAcc->ApplyScrollOffset(bounds);
843 // If we are hit testing and the Accessible has a scroll area, ensure
844 // that the bounds we've calculated so far are constrained to the
845 // bounds of the scroll area. Without this, we'll "hit" the off-screen
846 // portions of accs that are are partially (but not fully) within the
847 // scroll area. This is also a problem for accs with overflow:hidden;
848 if (aBoundsAreForHittesting &&
849 (hasScrollArea || remoteAcc->IsOverflowHidden())) {
850 nsRect selfRelativeVisibleBounds(0, 0, remoteBounds.width,
851 remoteBounds.height);
852 bounds = bounds.SafeIntersect(selfRelativeVisibleBounds);
855 if (remoteAcc->IsDoc()) {
856 // Fixed elements are document relative, so if we've hit a
857 // document we're now subject to that document's styling
858 // (including scroll offsets that operate on it).
859 // This ordering is important, we don't want to apply scroll
860 // offsets on this doc's content.
861 encounteredFixedContainer = false;
863 if (!encounteredFixedContainer) {
864 // The transform matrix we cache (if any) is meant to operate on
865 // self-relative rects. Therefore, we must apply the transform before
866 // we make bounds parent-relative.
867 Unused << remoteAcc->ApplyTransform(bounds);
868 // Regardless of whether this is a doc, we should offset `bounds`
869 // by the bounds retrieved here. This is how we build screen
870 // coordinates from relative coordinates.
871 bounds.MoveBy(remoteBounds.X(), remoteBounds.Y());
874 if (remoteAcc->IsFixedPos()) {
875 encounteredFixedContainer = true;
877 // we can't just break here if we're scroll suppressed because we still
878 // need to find the top doc.
880 acc = acc->Parent();
883 MOZ_ASSERT(topDoc);
884 if (topDoc) {
885 // We use the top documents app-units-per-dev-pixel even though
886 // theoretically nested docs can have different values. Practically,
887 // that isn't likely since we only offer zoom controls for the top
888 // document and all subdocuments inherit from it.
889 auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
890 CacheKey::AppUnitsPerDevPixel);
891 MOZ_ASSERT(appUnitsPerDevPixel);
892 if (appUnitsPerDevPixel) {
893 // Convert our existing `bounds` rect from app units to dev pixels
894 devPxBounds = LayoutDeviceIntRect::FromAppUnitsToNearest(
895 bounds, *appUnitsPerDevPixel);
899 #if !defined(ANDROID)
900 // This block is not thread safe because it queries a LocalAccessible.
901 // It is also not needed in Android since the only local accessible is
902 // the outer doc browser that has an offset of 0.
903 // acc could be null if the OuterDocAccessible died before the top level
904 // DocAccessibleParent.
905 if (LocalAccessible* localAcc =
906 acc ? const_cast<Accessible*>(acc)->AsLocal() : nullptr) {
907 // LocalAccessible::Bounds returns screen-relative bounds in
908 // dev pixels.
909 LayoutDeviceIntRect localBounds = localAcc->Bounds();
911 // The root document will always have an APZ resolution of 1,
912 // so we don't factor in its scale here. We also don't scale
913 // by GetFullZoom because LocalAccessible::Bounds already does
914 // that.
915 devPxBounds.MoveBy(localBounds.X(), localBounds.Y());
917 #endif
919 return devPxBounds;
922 return LayoutDeviceIntRect();
925 LayoutDeviceIntRect RemoteAccessible::Bounds() const {
926 return BoundsWithOffset(Nothing());
929 Relation RemoteAccessible::RelationByType(RelationType aType) const {
930 // We are able to handle some relations completely in the
931 // parent process, without the help of the cache. Those
932 // relations are enumerated here. Other relations, whose
933 // types are stored in kRelationTypeAtoms, are processed
934 // below using the cache.
935 if (aType == RelationType::CONTAINING_TAB_PANE) {
936 if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()) {
937 if (dom::CanonicalBrowsingContext* topCbc = cbc->Top()) {
938 if (dom::BrowserParent* bp = topCbc->GetBrowserParent()) {
939 return Relation(bp->GetTopLevelDocAccessible());
943 return Relation();
946 if (aType == RelationType::LINKS_TO && Role() == roles::LINK) {
947 Pivot p = Pivot(mDoc);
948 nsString href;
949 Value(href);
950 int32_t i = href.FindChar('#');
951 int32_t len = static_cast<int32_t>(href.Length());
952 if (i != -1 && i < (len - 1)) {
953 nsDependentSubstring anchorName = Substring(href, i + 1, len);
954 MustPruneSameDocRule rule;
955 Accessible* nameMatch = nullptr;
956 for (Accessible* match = p.Next(mDoc, rule); match;
957 match = p.Next(match, rule)) {
958 nsString currID;
959 match->DOMNodeID(currID);
960 MOZ_ASSERT(match->IsRemote());
961 if (anchorName.Equals(currID)) {
962 return Relation(match->AsRemote());
964 if (!nameMatch) {
965 nsString currName = match->AsRemote()->GetCachedHTMLNameAttribute();
966 if (match->TagName() == nsGkAtoms::a && anchorName.Equals(currName)) {
967 // If we find an element with a matching ID, we should return
968 // that, but if we don't we should return the first anchor with
969 // a matching name. To avoid doing two traversals, store the first
970 // name match here.
971 nameMatch = match;
975 return nameMatch ? Relation(nameMatch->AsRemote()) : Relation();
978 return Relation();
981 // Handle ARIA tree, treegrid parent/child relations. Each of these cases
982 // relies on cached group info. To find the parent of an accessible, use the
983 // unified conceptual parent.
984 if (aType == RelationType::NODE_CHILD_OF) {
985 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
986 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
987 roleMapEntry->role == roles::LISTITEM ||
988 roleMapEntry->role == roles::ROW)) {
989 if (const AccGroupInfo* groupInfo =
990 const_cast<RemoteAccessible*>(this)->GetOrCreateGroupInfo()) {
991 return Relation(groupInfo->ConceptualParent());
994 return Relation();
997 // To find the children of a parent, provide an iterator through its items.
998 if (aType == RelationType::NODE_PARENT_OF) {
999 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1000 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
1001 roleMapEntry->role == roles::LISTITEM ||
1002 roleMapEntry->role == roles::ROW ||
1003 roleMapEntry->role == roles::OUTLINE ||
1004 roleMapEntry->role == roles::LIST ||
1005 roleMapEntry->role == roles::TREE_TABLE)) {
1006 return Relation(new ItemIterator(this));
1008 return Relation();
1011 if (aType == RelationType::MEMBER_OF) {
1012 Relation rel = Relation();
1013 // HTML radio buttons with cached names should be grouped.
1014 if (IsHTMLRadioButton()) {
1015 nsString name = GetCachedHTMLNameAttribute();
1016 if (name.IsEmpty()) {
1017 return rel;
1020 RemoteAccessible* ancestor = RemoteParent();
1021 while (ancestor && ancestor->Role() != roles::FORM && ancestor != mDoc) {
1022 ancestor = ancestor->RemoteParent();
1024 if (ancestor) {
1025 // Sometimes we end up with an unparented acc here, potentially
1026 // because the acc is being moved. See bug 1807639.
1027 // Pivot expects to be created with a non-null mRoot.
1028 Pivot p = Pivot(ancestor);
1029 PivotRadioNameRule rule(name);
1030 Accessible* match = p.Next(ancestor, rule);
1031 while (match) {
1032 rel.AppendTarget(match->AsRemote());
1033 match = p.Next(match, rule);
1036 return rel;
1039 if (IsARIARole(nsGkAtoms::radio)) {
1040 // ARIA radio buttons should be grouped by their radio group
1041 // parent, if one exists.
1042 RemoteAccessible* currParent = RemoteParent();
1043 while (currParent && currParent->Role() != roles::RADIO_GROUP) {
1044 currParent = currParent->RemoteParent();
1047 if (currParent && currParent->Role() == roles::RADIO_GROUP) {
1048 // If we found a radiogroup parent, search for all
1049 // roles::RADIOBUTTON children and add them to our relation.
1050 // This search will include the radio button this method
1051 // was called from, which is expected.
1052 Pivot p = Pivot(currParent);
1053 PivotRoleRule rule(roles::RADIOBUTTON);
1054 Accessible* match = p.Next(currParent, rule);
1055 while (match) {
1056 MOZ_ASSERT(match->IsRemote(),
1057 "We should only be traversing the remote tree.");
1058 rel.AppendTarget(match->AsRemote());
1059 match = p.Next(match, rule);
1063 // By webkit's standard, aria radio buttons do not get grouped
1064 // if they lack a group parent, so we return an empty
1065 // relation here if the above check fails.
1066 return rel;
1069 Relation rel;
1070 if (!mCachedFields) {
1071 return rel;
1074 for (const auto& data : kRelationTypeAtoms) {
1075 if (data.mType != aType ||
1076 (data.mValidTag && TagName() != data.mValidTag)) {
1077 continue;
1080 if (auto maybeIds =
1081 mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom)) {
1082 rel.AppendIter(new RemoteAccIterator(*maybeIds, Document()));
1084 // Each relation type has only one relevant cached attribute,
1085 // so break after we've handled the attr for this type,
1086 // even if we didn't find any targets.
1087 break;
1090 if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) {
1091 if (auto reverseIdsEntry = accRelMapEntry.Data().Lookup(aType)) {
1092 rel.AppendIter(new RemoteAccIterator(reverseIdsEntry.Data(), Document()));
1096 // We handle these relations here rather than before cached relations because
1097 // the cached relations need to take precedence. For example, a <figure> with
1098 // both aria-labelledby and a <figcaption> must return two LABELLED_BY
1099 // targets: the aria-labelledby and then the <figcaption>.
1100 if (aType == RelationType::LABELLED_BY && TagName() == nsGkAtoms::figure) {
1101 uint32_t count = ChildCount();
1102 for (uint32_t c = 0; c < count; ++c) {
1103 RemoteAccessible* child = RemoteChildAt(c);
1104 MOZ_ASSERT(child);
1105 if (child->TagName() == nsGkAtoms::figcaption) {
1106 rel.AppendTarget(child);
1109 } else if (aType == RelationType::LABEL_FOR &&
1110 TagName() == nsGkAtoms::figcaption) {
1111 if (RemoteAccessible* parent = RemoteParent()) {
1112 if (parent->TagName() == nsGkAtoms::figure) {
1113 rel.AppendTarget(parent);
1118 return rel;
1121 void RemoteAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
1122 uint32_t aLength) {
1123 if (IsText()) {
1124 if (mCachedFields) {
1125 if (auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text)) {
1126 aText.Append(Substring(*text, aStartOffset, aLength));
1128 VERIFY_CACHE(CacheDomain::Text);
1130 return;
1133 if (aStartOffset != 0 || aLength == 0) {
1134 return;
1137 if (IsHTMLBr()) {
1138 aText += kForcedNewLineChar;
1139 } else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) {
1140 // Expose the embedded object accessible as imaginary embedded object
1141 // character if its parent hypertext accessible doesn't expose children to
1142 // AT.
1143 aText += kImaginaryEmbeddedObjectChar;
1144 } else {
1145 aText += kEmbeddedObjectChar;
1149 nsTArray<bool> RemoteAccessible::PreProcessRelations(AccAttributes* aFields) {
1150 nsTArray<bool> updateTracker(ArrayLength(kRelationTypeAtoms));
1151 for (auto const& data : kRelationTypeAtoms) {
1152 if (data.mValidTag) {
1153 // The relation we're currently processing only applies to particular
1154 // elements. Check to see if we're one of them.
1155 nsAtom* tag = TagName();
1156 if (!tag) {
1157 // TagName() returns null on an initial cache push -- check aFields
1158 // for a tag name instead.
1159 if (auto maybeTag =
1160 aFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1161 tag = *maybeTag;
1164 MOZ_ASSERT(
1165 tag || IsTextLeaf() || IsDoc(),
1166 "Could not fetch tag via TagName() or from initial cache push!");
1167 if (tag != data.mValidTag) {
1168 // If this rel doesn't apply to us, do no pre-processing. Also,
1169 // note in our updateTracker that we should do no post-processing.
1170 updateTracker.AppendElement(false);
1171 continue;
1175 nsStaticAtom* const relAtom = data.mAtom;
1176 auto newRelationTargets =
1177 aFields->GetAttribute<nsTArray<uint64_t>>(relAtom);
1178 bool shouldAddNewImplicitRels =
1179 newRelationTargets && newRelationTargets->Length();
1181 // Remove existing implicit relations if we need to perform an update, or
1182 // if we've received a DeleteEntry(). Only do this if mCachedFields is
1183 // initialized. If mCachedFields is not initialized, we still need to
1184 // construct the update array so we correctly handle reverse rels in
1185 // PostProcessRelations.
1186 if ((shouldAddNewImplicitRels ||
1187 aFields->GetAttribute<DeleteEntry>(relAtom)) &&
1188 mCachedFields) {
1189 if (auto maybeOldIDs =
1190 mCachedFields->GetAttribute<nsTArray<uint64_t>>(relAtom)) {
1191 for (uint64_t id : *maybeOldIDs) {
1192 // For each target, fetch its reverse relation map
1193 // We need to call `Lookup` here instead of `LookupOrInsert` because
1194 // it's possible the ID we're querying is from an acc that has since
1195 // been Shutdown(), and so has intentionally removed its reverse rels
1196 // from the doc's reverse rel cache.
1197 if (auto reverseRels = Document()->mReverseRelations.Lookup(id)) {
1198 // Then fetch its reverse relation's ID list. This should be safe
1199 // to do via LookupOrInsert because by the time we've gotten here,
1200 // we know the acc and `this` are still alive in the doc. If we hit
1201 // the following assert, we don't have parity on implicit/explicit
1202 // rels and something is wrong.
1203 nsTArray<uint64_t>& reverseRelIDs =
1204 reverseRels->LookupOrInsert(data.mReverseType);
1205 // There might be other reverse relations stored for this acc, so
1206 // remove our ID instead of deleting the array entirely.
1207 DebugOnly<bool> removed = reverseRelIDs.RemoveElement(ID());
1208 MOZ_ASSERT(removed, "Can't find old reverse relation");
1214 updateTracker.AppendElement(shouldAddNewImplicitRels);
1217 return updateTracker;
1220 void RemoteAccessible::PostProcessRelations(const nsTArray<bool>& aToUpdate) {
1221 size_t updateCount = aToUpdate.Length();
1222 MOZ_ASSERT(updateCount == ArrayLength(kRelationTypeAtoms),
1223 "Did not note update status for every relation type!");
1224 for (size_t i = 0; i < updateCount; i++) {
1225 if (aToUpdate.ElementAt(i)) {
1226 // Since kRelationTypeAtoms was used to generate aToUpdate, we
1227 // know the ith entry of aToUpdate corresponds to the relation type in
1228 // the ith entry of kRelationTypeAtoms. Fetch the related data here.
1229 auto const& data = kRelationTypeAtoms[i];
1231 const nsTArray<uint64_t>& newIDs =
1232 *mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom);
1233 for (uint64_t id : newIDs) {
1234 nsTHashMap<RelationType, nsTArray<uint64_t>>& relations =
1235 Document()->mReverseRelations.LookupOrInsert(id);
1236 nsTArray<uint64_t>& ids = relations.LookupOrInsert(data.mReverseType);
1237 ids.AppendElement(ID());
1243 void RemoteAccessible::PruneRelationsOnShutdown() {
1244 auto reverseRels = mDoc->mReverseRelations.Lookup(ID());
1245 if (!reverseRels) {
1246 return;
1248 for (auto const& data : kRelationTypeAtoms) {
1249 // Fetch the list of targets for this reverse relation
1250 auto reverseTargetList = reverseRels->Lookup(data.mReverseType);
1251 if (!reverseTargetList) {
1252 continue;
1254 for (uint64_t id : *reverseTargetList) {
1255 // For each target, retrieve its corresponding forward relation target
1256 // list
1257 RemoteAccessible* affectedAcc = mDoc->GetAccessible(id);
1258 if (!affectedAcc) {
1259 // It's possible the affect acc also shut down, in which case
1260 // we don't have anything to update.
1261 continue;
1263 if (auto forwardTargetList =
1264 affectedAcc->mCachedFields
1265 ->GetMutableAttribute<nsTArray<uint64_t>>(data.mAtom)) {
1266 forwardTargetList->RemoveElement(ID());
1267 if (!forwardTargetList->Length()) {
1268 // The ID we removed was the only thing in the list, so remove the
1269 // entry from the cache entirely -- don't leave an empty array.
1270 affectedAcc->mCachedFields->Remove(data.mAtom);
1275 // Remove this ID from the document's map of reverse relations.
1276 reverseRels.Remove();
1279 uint32_t RemoteAccessible::GetCachedTextLength() {
1280 MOZ_ASSERT(!HasChildren());
1281 if (!mCachedFields) {
1282 return 0;
1284 VERIFY_CACHE(CacheDomain::Text);
1285 auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text);
1286 if (!text) {
1287 return 0;
1289 return text->Length();
1292 Maybe<const nsTArray<int32_t>&> RemoteAccessible::GetCachedTextLines() {
1293 MOZ_ASSERT(!HasChildren());
1294 if (!mCachedFields) {
1295 return Nothing();
1297 VERIFY_CACHE(CacheDomain::Text);
1298 return mCachedFields->GetAttribute<nsTArray<int32_t>>(
1299 CacheKey::TextLineStarts);
1302 nsRect RemoteAccessible::GetCachedCharRect(int32_t aOffset) {
1303 MOZ_ASSERT(IsText());
1304 if (!mCachedFields) {
1305 return nsRect();
1308 if (Maybe<const nsTArray<int32_t>&> maybeCharData =
1309 mCachedFields->GetAttribute<nsTArray<int32_t>>(
1310 CacheKey::TextBounds)) {
1311 const nsTArray<int32_t>& charData = *maybeCharData;
1312 const int32_t index = aOffset * kNumbersInRect;
1313 if (index < static_cast<int32_t>(charData.Length())) {
1314 return nsRect(charData[index], charData[index + 1], charData[index + 2],
1315 charData[index + 3]);
1317 // It is valid for a client to call this with an offset 1 after the last
1318 // character because of the insertion point at the end of text boxes.
1319 MOZ_ASSERT(index == static_cast<int32_t>(charData.Length()));
1322 return nsRect();
1325 void RemoteAccessible::DOMNodeID(nsString& aID) const {
1326 if (mCachedFields) {
1327 mCachedFields->GetAttribute(CacheKey::DOMNodeID, aID);
1328 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
1332 void RemoteAccessible::DOMNodeClass(nsString& aClass) const {
1333 if (mCachedFields) {
1334 mCachedFields->GetAttribute(CacheKey::DOMNodeClass, aClass);
1335 VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
1339 void RemoteAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX,
1340 int32_t aY) {
1341 Unused << mDoc->SendScrollToPoint(mID, aScrollType, aX, aY);
1344 #if !defined(XP_WIN)
1345 void RemoteAccessible::Announce(const nsString& aAnnouncement,
1346 uint16_t aPriority) {
1347 Unused << mDoc->SendAnnounce(mID, aAnnouncement, aPriority);
1349 #endif // !defined(XP_WIN)
1351 void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
1352 int32_t aEndOffset,
1353 uint32_t aCoordinateType,
1354 int32_t aX, int32_t aY) {
1355 Unused << mDoc->SendScrollSubstringToPoint(mID, aStartOffset, aEndOffset,
1356 aCoordinateType, aX, aY);
1359 RefPtr<const AccAttributes> RemoteAccessible::GetCachedTextAttributes() {
1360 MOZ_ASSERT(IsText() || IsHyperText());
1361 if (mCachedFields) {
1362 auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
1363 CacheKey::TextAttributes);
1364 VERIFY_CACHE(CacheDomain::Text);
1365 return attrs;
1367 return nullptr;
1370 already_AddRefed<AccAttributes> RemoteAccessible::DefaultTextAttributes() {
1371 RefPtr<const AccAttributes> attrs = GetCachedTextAttributes();
1372 RefPtr<AccAttributes> result = new AccAttributes();
1373 if (attrs) {
1374 attrs->CopyTo(result);
1376 return result.forget();
1379 RefPtr<const AccAttributes> RemoteAccessible::GetCachedARIAAttributes() const {
1380 if (mCachedFields) {
1381 auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
1382 CacheKey::ARIAAttributes);
1383 VERIFY_CACHE(CacheDomain::ARIA);
1384 return attrs;
1386 return nullptr;
1389 nsString RemoteAccessible::GetCachedHTMLNameAttribute() const {
1390 if (mCachedFields) {
1391 if (auto maybeName =
1392 mCachedFields->GetAttribute<nsString>(CacheKey::DOMName)) {
1393 return *maybeName;
1396 return nsString();
1399 uint64_t RemoteAccessible::State() {
1400 uint64_t state = 0;
1401 if (mCachedFields) {
1402 if (auto rawState =
1403 mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) {
1404 VERIFY_CACHE(CacheDomain::State);
1405 state = *rawState;
1406 // Handle states that are derived from other states.
1407 if (!(state & states::UNAVAILABLE)) {
1408 state |= states::ENABLED | states::SENSITIVE;
1410 if (state & states::EXPANDABLE && !(state & states::EXPANDED)) {
1411 state |= states::COLLAPSED;
1415 ApplyImplicitState(state);
1417 auto* cbc = mDoc->GetBrowsingContext();
1418 if (cbc && !cbc->IsActive()) {
1419 // If our browsing context is _not_ active, we're in a background tab
1420 // and inherently offscreen.
1421 state |= states::OFFSCREEN;
1422 } else {
1423 // If we're in an active browsing context, there are a few scenarios we
1424 // need to address:
1425 // - We are an iframe document in the visual viewport
1426 // - We are an iframe document out of the visual viewport
1427 // - We are non-iframe content in the visual viewport
1428 // - We are non-iframe content out of the visual viewport
1429 // We assume top level tab docs are on screen if their BC is active, so
1430 // we don't need additional handling for them here.
1431 if (!mDoc->IsTopLevel()) {
1432 // Here we handle iframes and iframe content.
1433 // We use an iframe's outer doc's position in the embedding document's
1434 // viewport to determine if the iframe has been scrolled offscreen.
1435 Accessible* docParent = mDoc->Parent();
1436 // In rare cases, we might not have an outer doc yet. Return if that's
1437 // the case.
1438 if (NS_WARN_IF(!docParent || !docParent->IsRemote())) {
1439 return state;
1442 RemoteAccessible* outerDoc = docParent->AsRemote();
1443 DocAccessibleParent* embeddingDocument = outerDoc->Document();
1444 if (embeddingDocument &&
1445 !embeddingDocument->mOnScreenAccessibles.Contains(outerDoc->ID())) {
1446 // Our embedding document's viewport cache doesn't contain the ID of
1447 // our outer doc, so this iframe (and any of its content) is
1448 // offscreen.
1449 state |= states::OFFSCREEN;
1450 } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
1451 // Our embedding document's viewport cache contains the ID of our
1452 // outer doc, but the iframe's viewport cache doesn't contain our ID.
1453 // We are offscreen.
1454 state |= states::OFFSCREEN;
1456 } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
1457 // We are top level tab content (but not a top level tab doc).
1458 // If our tab doc's viewport cache doesn't contain our ID, we're
1459 // offscreen.
1460 state |= states::OFFSCREEN;
1465 return state;
1468 already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
1469 RefPtr<AccAttributes> attributes = new AccAttributes();
1470 nsAccessibilityService* accService = GetAccService();
1471 if (!accService) {
1472 // The service can be shut down before RemoteAccessibles. If it is shut
1473 // down, we can't calculate some attributes. We're about to die anyway.
1474 return attributes.forget();
1477 if (mCachedFields) {
1478 // We use GetAttribute instead of GetAttributeRefPtr because we need
1479 // nsAtom, not const nsAtom.
1480 if (auto tag =
1481 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1482 attributes->SetAttribute(nsGkAtoms::tag, *tag);
1485 GroupPos groupPos = GroupPosition();
1486 nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, groupPos.setSize,
1487 groupPos.posInSet);
1489 bool hierarchical = false;
1490 uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
1491 if (itemCount) {
1492 attributes->SetAttribute(nsGkAtoms::child_item_count,
1493 static_cast<int32_t>(itemCount));
1496 if (hierarchical) {
1497 attributes->SetAttribute(nsGkAtoms::tree, true);
1500 if (auto inputType =
1501 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
1502 attributes->SetAttribute(nsGkAtoms::textInputType, *inputType);
1505 if (RefPtr<nsAtom> display = DisplayStyle()) {
1506 attributes->SetAttribute(nsGkAtoms::display, display);
1509 if (TableCellAccessible* cell = AsTableCell()) {
1510 TableAccessible* table = cell->Table();
1511 uint32_t row = cell->RowIdx();
1512 uint32_t col = cell->ColIdx();
1513 int32_t cellIdx = table->CellIndexAt(row, col);
1514 if (cellIdx != -1) {
1515 attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx);
1519 if (bool layoutGuess = TableIsProbablyForLayout()) {
1520 attributes->SetAttribute(nsGkAtoms::layout_guess, layoutGuess);
1523 accService->MarkupAttributes(this, attributes);
1525 const nsRoleMapEntry* roleMap = ARIARoleMap();
1526 nsAutoString role;
1527 mCachedFields->GetAttribute(CacheKey::ARIARole, role);
1528 if (role.IsEmpty()) {
1529 if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty) {
1530 // Single, known role.
1531 attributes->SetAttribute(nsGkAtoms::xmlroles, roleMap->roleAtom);
1532 } else if (nsAtom* landmark = LandmarkRole()) {
1533 // Landmark role from markup; e.g. HTML <main>.
1534 attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
1536 } else {
1537 // Unknown role or multiple roles.
1538 attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role));
1541 if (roleMap) {
1542 nsAutoString live;
1543 if (nsAccUtils::GetLiveAttrValue(roleMap->liveAttRule, live)) {
1544 attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
1548 if (auto ariaAttrs = GetCachedARIAAttributes()) {
1549 ariaAttrs->CopyTo(attributes);
1552 nsAccUtils::SetLiveContainerAttributes(attributes, this);
1554 nsString id;
1555 DOMNodeID(id);
1556 if (!id.IsEmpty()) {
1557 attributes->SetAttribute(nsGkAtoms::id, std::move(id));
1560 nsString className;
1561 DOMNodeClass(className);
1562 if (!className.IsEmpty()) {
1563 attributes->SetAttribute(nsGkAtoms::_class, std::move(className));
1566 if (IsImage()) {
1567 nsString src;
1568 mCachedFields->GetAttribute(CacheKey::SrcURL, src);
1569 if (!src.IsEmpty()) {
1570 attributes->SetAttribute(nsGkAtoms::src, std::move(src));
1574 if (IsTextField()) {
1575 nsString placeholder;
1576 mCachedFields->GetAttribute(CacheKey::HTMLPlaceholder, placeholder);
1577 if (!placeholder.IsEmpty()) {
1578 attributes->SetAttribute(nsGkAtoms::placeholder,
1579 std::move(placeholder));
1580 attributes->Remove(nsGkAtoms::aria_placeholder);
1584 nsString popupType;
1585 mCachedFields->GetAttribute(CacheKey::PopupType, popupType);
1586 if (!popupType.IsEmpty()) {
1587 attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popupType));
1591 nsAutoString name;
1592 if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
1593 attributes->SetAttribute(nsGkAtoms::explicit_name, true);
1596 // Expose the string value via the valuetext attribute. We test for the value
1597 // interface because we don't want to expose traditional Value() information
1598 // such as URLs on links and documents, or text in an input.
1599 // XXX This is only needed for ATK, since other APIs have native ways to
1600 // retrieve value text. We should probably move this into ATK specific code.
1601 // For now, we do this because LocalAccessible does it.
1602 if (HasNumericValue()) {
1603 nsString valuetext;
1604 Value(valuetext);
1605 attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
1608 return attributes.forget();
1611 nsAtom* RemoteAccessible::TagName() const {
1612 if (mCachedFields) {
1613 if (auto tag =
1614 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
1615 return *tag;
1619 return nullptr;
1622 already_AddRefed<nsAtom> RemoteAccessible::InputType() const {
1623 if (mCachedFields) {
1624 if (auto inputType =
1625 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
1626 RefPtr<nsAtom> result = *inputType;
1627 return result.forget();
1631 return nullptr;
1634 already_AddRefed<nsAtom> RemoteAccessible::DisplayStyle() const {
1635 if (mCachedFields) {
1636 if (auto display =
1637 mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSDisplay)) {
1638 RefPtr<nsAtom> result = *display;
1639 return result.forget();
1642 return nullptr;
1645 float RemoteAccessible::Opacity() const {
1646 if (mCachedFields) {
1647 if (auto opacity = mCachedFields->GetAttribute<float>(CacheKey::Opacity)) {
1648 return *opacity;
1652 return 1.0f;
1655 void RemoteAccessible::LiveRegionAttributes(nsAString* aLive,
1656 nsAString* aRelevant,
1657 Maybe<bool>* aAtomic,
1658 nsAString* aBusy) const {
1659 if (!mCachedFields) {
1660 return;
1662 RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes();
1663 if (!attrs) {
1664 return;
1666 if (aLive) {
1667 attrs->GetAttribute(nsGkAtoms::aria_live, *aLive);
1669 if (aRelevant) {
1670 attrs->GetAttribute(nsGkAtoms::aria_relevant, *aRelevant);
1672 if (aAtomic) {
1673 if (auto value =
1674 attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::aria_atomic)) {
1675 *aAtomic = Some(*value == nsGkAtoms::_true);
1678 if (aBusy) {
1679 attrs->GetAttribute(nsGkAtoms::aria_busy, *aBusy);
1683 Maybe<bool> RemoteAccessible::ARIASelected() const {
1684 if (mCachedFields) {
1685 return mCachedFields->GetAttribute<bool>(CacheKey::ARIASelected);
1687 return Nothing();
1690 nsAtom* RemoteAccessible::GetPrimaryAction() const {
1691 if (mCachedFields) {
1692 if (auto action = mCachedFields->GetAttribute<RefPtr<nsAtom>>(
1693 CacheKey::PrimaryAction)) {
1694 return *action;
1698 return nullptr;
1701 uint8_t RemoteAccessible::ActionCount() const {
1702 uint8_t actionCount = 0;
1703 if (mCachedFields) {
1704 if (HasPrimaryAction() || ActionAncestor()) {
1705 actionCount++;
1708 if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1709 actionCount++;
1711 VERIFY_CACHE(CacheDomain::Actions);
1714 return actionCount;
1717 void RemoteAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
1718 if (mCachedFields) {
1719 aName.Truncate();
1720 nsAtom* action = GetPrimaryAction();
1721 bool hasActionAncestor = !action && ActionAncestor();
1723 switch (aIndex) {
1724 case 0:
1725 if (action) {
1726 action->ToString(aName);
1727 } else if (hasActionAncestor) {
1728 aName.AssignLiteral("click ancestor");
1729 } else if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1730 aName.AssignLiteral("showlongdesc");
1732 break;
1733 case 1:
1734 if ((action || hasActionAncestor) &&
1735 mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
1736 aName.AssignLiteral("showlongdesc");
1738 break;
1739 default:
1740 break;
1743 VERIFY_CACHE(CacheDomain::Actions);
1746 bool RemoteAccessible::DoAction(uint8_t aIndex) const {
1747 if (ActionCount() < aIndex + 1) {
1748 return false;
1751 Unused << mDoc->SendDoActionAsync(mID, aIndex);
1752 return true;
1755 KeyBinding RemoteAccessible::AccessKey() const {
1756 if (mCachedFields) {
1757 if (auto value =
1758 mCachedFields->GetAttribute<uint64_t>(CacheKey::AccessKey)) {
1759 return KeyBinding(*value);
1762 return KeyBinding();
1765 void RemoteAccessible::SelectionRanges(nsTArray<TextRange>* aRanges) const {
1766 Document()->SelectionRanges(aRanges);
1769 bool RemoteAccessible::RemoveFromSelection(int32_t aSelectionNum) {
1770 MOZ_ASSERT(IsHyperText());
1771 if (SelectionCount() <= aSelectionNum) {
1772 return false;
1775 Unused << mDoc->SendRemoveTextSelection(mID, aSelectionNum);
1777 return true;
1780 void RemoteAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
1781 int32_t* aPosInSet) const {
1782 if (!mCachedFields) {
1783 return;
1786 if (aLevel) {
1787 if (auto level =
1788 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_level)) {
1789 *aLevel = *level;
1792 if (aSetSize) {
1793 if (auto setsize =
1794 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_setsize)) {
1795 *aSetSize = *setsize;
1798 if (aPosInSet) {
1799 if (auto posinset =
1800 mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_posinset)) {
1801 *aPosInSet = *posinset;
1806 AccGroupInfo* RemoteAccessible::GetGroupInfo() const {
1807 if (!mCachedFields) {
1808 return nullptr;
1811 if (auto groupInfo = mCachedFields->GetAttribute<UniquePtr<AccGroupInfo>>(
1812 CacheKey::GroupInfo)) {
1813 return groupInfo->get();
1816 return nullptr;
1819 AccGroupInfo* RemoteAccessible::GetOrCreateGroupInfo() {
1820 AccGroupInfo* groupInfo = GetGroupInfo();
1821 if (groupInfo) {
1822 return groupInfo;
1825 groupInfo = AccGroupInfo::CreateGroupInfo(this);
1826 if (groupInfo) {
1827 if (!mCachedFields) {
1828 mCachedFields = new AccAttributes();
1831 mCachedFields->SetAttribute(CacheKey::GroupInfo, groupInfo);
1834 return groupInfo;
1837 void RemoteAccessible::InvalidateGroupInfo() {
1838 if (mCachedFields) {
1839 mCachedFields->Remove(CacheKey::GroupInfo);
1843 void RemoteAccessible::GetPositionAndSetSize(int32_t* aPosInSet,
1844 int32_t* aSetSize) {
1845 if (IsHTMLRadioButton()) {
1846 *aSetSize = 0;
1847 Relation rel = RelationByType(RelationType::MEMBER_OF);
1848 while (Accessible* radio = rel.Next()) {
1849 ++*aSetSize;
1850 if (radio == this) {
1851 *aPosInSet = *aSetSize;
1854 return;
1857 Accessible::GetPositionAndSetSize(aPosInSet, aSetSize);
1860 bool RemoteAccessible::HasPrimaryAction() const {
1861 return mCachedFields && mCachedFields->HasAttribute(CacheKey::PrimaryAction);
1864 void RemoteAccessible::TakeFocus() const { Unused << mDoc->SendTakeFocus(mID); }
1866 void RemoteAccessible::ScrollTo(uint32_t aHow) const {
1867 Unused << mDoc->SendScrollTo(mID, aHow);
1870 ////////////////////////////////////////////////////////////////////////////////
1871 // SelectAccessible
1873 void RemoteAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
1874 Pivot p = Pivot(this);
1875 PivotStateRule rule(states::SELECTED);
1876 for (Accessible* selected = p.First(rule); selected;
1877 selected = p.Next(selected, rule)) {
1878 aItems->AppendElement(selected);
1882 uint32_t RemoteAccessible::SelectedItemCount() {
1883 uint32_t count = 0;
1884 Pivot p = Pivot(this);
1885 PivotStateRule rule(states::SELECTED);
1886 for (Accessible* selected = p.First(rule); selected;
1887 selected = p.Next(selected, rule)) {
1888 count++;
1891 return count;
1894 Accessible* RemoteAccessible::GetSelectedItem(uint32_t aIndex) {
1895 uint32_t index = 0;
1896 Accessible* selected = nullptr;
1897 Pivot p = Pivot(this);
1898 PivotStateRule rule(states::SELECTED);
1899 for (selected = p.First(rule); selected && index < aIndex;
1900 selected = p.Next(selected, rule)) {
1901 index++;
1904 return selected;
1907 bool RemoteAccessible::IsItemSelected(uint32_t aIndex) {
1908 uint32_t index = 0;
1909 Accessible* selectable = nullptr;
1910 Pivot p = Pivot(this);
1911 PivotStateRule rule(states::SELECTABLE);
1912 for (selectable = p.First(rule); selectable && index < aIndex;
1913 selectable = p.Next(selectable, rule)) {
1914 index++;
1917 return selectable && selectable->State() & states::SELECTED;
1920 bool RemoteAccessible::AddItemToSelection(uint32_t aIndex) {
1921 uint32_t index = 0;
1922 Accessible* selectable = nullptr;
1923 Pivot p = Pivot(this);
1924 PivotStateRule rule(states::SELECTABLE);
1925 for (selectable = p.First(rule); selectable && index < aIndex;
1926 selectable = p.Next(selectable, rule)) {
1927 index++;
1930 if (selectable) selectable->SetSelected(true);
1932 return static_cast<bool>(selectable);
1935 bool RemoteAccessible::RemoveItemFromSelection(uint32_t aIndex) {
1936 uint32_t index = 0;
1937 Accessible* selectable = nullptr;
1938 Pivot p = Pivot(this);
1939 PivotStateRule rule(states::SELECTABLE);
1940 for (selectable = p.First(rule); selectable && index < aIndex;
1941 selectable = p.Next(selectable, rule)) {
1942 index++;
1945 if (selectable) selectable->SetSelected(false);
1947 return static_cast<bool>(selectable);
1950 bool RemoteAccessible::SelectAll() {
1951 if ((State() & states::MULTISELECTABLE) == 0) {
1952 return false;
1955 bool success = false;
1956 Accessible* selectable = nullptr;
1957 Pivot p = Pivot(this);
1958 PivotStateRule rule(states::SELECTABLE);
1959 for (selectable = p.First(rule); selectable;
1960 selectable = p.Next(selectable, rule)) {
1961 success = true;
1962 selectable->SetSelected(true);
1964 return success;
1967 bool RemoteAccessible::UnselectAll() {
1968 if ((State() & states::MULTISELECTABLE) == 0) {
1969 return false;
1972 bool success = false;
1973 Accessible* selectable = nullptr;
1974 Pivot p = Pivot(this);
1975 PivotStateRule rule(states::SELECTABLE);
1976 for (selectable = p.First(rule); selectable;
1977 selectable = p.Next(selectable, rule)) {
1978 success = true;
1979 selectable->SetSelected(false);
1981 return success;
1984 void RemoteAccessible::TakeSelection() {
1985 Unused << mDoc->SendTakeSelection(mID);
1988 void RemoteAccessible::SetSelected(bool aSelect) {
1989 Unused << mDoc->SendSetSelected(mID, aSelect);
1992 TableAccessible* RemoteAccessible::AsTable() {
1993 if (IsTable()) {
1994 return CachedTableAccessible::GetFrom(this);
1996 return nullptr;
1999 TableCellAccessible* RemoteAccessible::AsTableCell() {
2000 if (IsTableCell()) {
2001 return CachedTableCellAccessible::GetFrom(this);
2003 return nullptr;
2006 bool RemoteAccessible::TableIsProbablyForLayout() {
2007 if (mCachedFields) {
2008 if (auto layoutGuess =
2009 mCachedFields->GetAttribute<bool>(CacheKey::TableLayoutGuess)) {
2010 return *layoutGuess;
2013 return false;
2016 nsTArray<int32_t>& RemoteAccessible::GetCachedHyperTextOffsets() {
2017 if (mCachedFields) {
2018 if (auto offsets = mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
2019 CacheKey::HyperTextOffsets)) {
2020 return *offsets;
2023 nsTArray<int32_t> newOffsets;
2024 if (!mCachedFields) {
2025 mCachedFields = new AccAttributes();
2027 mCachedFields->SetAttribute(CacheKey::HyperTextOffsets,
2028 std::move(newOffsets));
2029 return *mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
2030 CacheKey::HyperTextOffsets);
2033 void RemoteAccessible::SetCaretOffset(int32_t aOffset) {
2034 Unused << mDoc->SendSetCaretOffset(mID, aOffset);
2037 Maybe<int32_t> RemoteAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
2038 if (RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes()) {
2039 if (auto val = attrs->GetAttribute<int32_t>(aAttrName)) {
2040 return val;
2043 return Nothing();
2046 void RemoteAccessible::Language(nsAString& aLocale) {
2047 if (!IsHyperText()) {
2048 return;
2050 if (auto attrs = GetCachedTextAttributes()) {
2051 attrs->GetAttribute(nsGkAtoms::language, aLocale);
2055 void RemoteAccessible::ReplaceText(const nsAString& aText) {
2056 Unused << mDoc->SendReplaceText(mID, aText);
2059 void RemoteAccessible::InsertText(const nsAString& aText, int32_t aPosition) {
2060 Unused << mDoc->SendInsertText(mID, aText, aPosition);
2063 void RemoteAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) {
2064 Unused << mDoc->SendCopyText(mID, aStartPos, aEndPos);
2067 void RemoteAccessible::CutText(int32_t aStartPos, int32_t aEndPos) {
2068 Unused << mDoc->SendCutText(mID, aStartPos, aEndPos);
2071 void RemoteAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) {
2072 Unused << mDoc->SendDeleteText(mID, aStartPos, aEndPos);
2075 void RemoteAccessible::PasteText(int32_t aPosition) {
2076 Unused << mDoc->SendPasteText(mID, aPosition);
2079 size_t RemoteAccessible::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
2080 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
2083 size_t RemoteAccessible::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
2084 size_t size = 0;
2086 // Count attributes.
2087 if (mCachedFields) {
2088 size += mCachedFields->SizeOfIncludingThis(aMallocSizeOf);
2091 // We don't recurse into mChildren because they're already counted in their
2092 // document's mAccessibles.
2093 size += mChildren.ShallowSizeOfExcludingThis(aMallocSizeOf);
2095 return size;
2098 } // namespace a11y
2099 } // namespace mozilla