forked from mirrors/gecko-dev
		
	 c68c3ca8b3
			
		
	
	
		c68c3ca8b3
		
	
	
	
	
		
			
			Role.h will soon be generated, but it is generated within the obj dir, so local includes won't work. Our C++ style guide says we should prefer exported includes wherever possible anyway. This was done with this shell command inside the accessible/ directory: ``` sed -i 's,#include "Role.h",#include "mozilla/a11y/Role.h",' `git grep -l '#include "Role.h"'` ``` Differential Revision: https://phabricator.services.mozilla.com/D183940
		
			
				
	
	
		
			621 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			621 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsAccUtils.h"
 | |
| 
 | |
| #include "AccAttributes.h"
 | |
| #include "ARIAMap.h"
 | |
| #include "nsCoreUtils.h"
 | |
| #include "nsGenericHTMLElement.h"
 | |
| #include "DocAccessible.h"
 | |
| #include "DocAccessibleParent.h"
 | |
| #include "HyperTextAccessible.h"
 | |
| #include "nsIAccessibleTypes.h"
 | |
| #include "mozilla/a11y/Role.h"
 | |
| #include "States.h"
 | |
| #include "TextLeafAccessible.h"
 | |
| 
 | |
| #include "nsIBaseWindow.h"
 | |
| #include "nsIDocShellTreeOwner.h"
 | |
| #include "nsIDOMXULContainerElement.h"
 | |
| #include "mozilla/a11y/RemoteAccessible.h"
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "mozilla/dom/Element.h"
 | |
| #include "mozilla/dom/ElementInternals.h"
 | |
| #include "nsAccessibilityService.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::a11y;
 | |
| 
 | |
| void nsAccUtils::SetAccGroupAttrs(AccAttributes* aAttributes, int32_t aLevel,
 | |
|                                   int32_t aSetSize, int32_t aPosInSet) {
 | |
|   nsAutoString value;
 | |
| 
 | |
|   if (aLevel) {
 | |
|     aAttributes->SetAttribute(nsGkAtoms::level, aLevel);
 | |
|   }
 | |
| 
 | |
|   if (aSetSize && aPosInSet) {
 | |
|     aAttributes->SetAttribute(nsGkAtoms::posinset, aPosInSet);
 | |
|     aAttributes->SetAttribute(nsGkAtoms::setsize, aSetSize);
 | |
|   }
 | |
| }
 | |
| 
 | |
| int32_t nsAccUtils::GetLevelForXULContainerItem(nsIContent* aContent) {
 | |
|   nsCOMPtr<nsIDOMXULContainerItemElement> item =
 | |
|       aContent->AsElement()->AsXULContainerItem();
 | |
|   if (!item) return 0;
 | |
| 
 | |
|   nsCOMPtr<dom::Element> containerElement;
 | |
|   item->GetParentContainer(getter_AddRefs(containerElement));
 | |
|   nsCOMPtr<nsIDOMXULContainerElement> container =
 | |
|       containerElement ? containerElement->AsXULContainer() : nullptr;
 | |
|   if (!container) return 0;
 | |
| 
 | |
|   // Get level of the item.
 | |
|   int32_t level = -1;
 | |
|   while (container) {
 | |
|     level++;
 | |
| 
 | |
|     container->GetParentContainer(getter_AddRefs(containerElement));
 | |
|     container = containerElement ? containerElement->AsXULContainer() : nullptr;
 | |
|   }
 | |
| 
 | |
|   return level;
 | |
| }
 | |
| 
 | |
| void nsAccUtils::SetLiveContainerAttributes(AccAttributes* aAttributes,
 | |
|                                             Accessible* aStartAcc) {
 | |
|   nsAutoString live, relevant, busy;
 | |
|   nsStaticAtom* role = nullptr;
 | |
|   Maybe<bool> atomic;
 | |
|   for (Accessible* acc = aStartAcc; acc; acc = acc->Parent()) {
 | |
|     // We only want the nearest value for each attribute. If we already got a
 | |
|     // value, don't bother fetching it from further ancestors.
 | |
|     const bool wasLiveEmpty = live.IsEmpty();
 | |
|     acc->LiveRegionAttributes(wasLiveEmpty ? &live : nullptr,
 | |
|                               relevant.IsEmpty() ? &relevant : nullptr,
 | |
|                               atomic ? nullptr : &atomic,
 | |
|                               busy.IsEmpty() ? &busy : nullptr);
 | |
|     if (wasLiveEmpty) {
 | |
|       const nsRoleMapEntry* roleMap = acc->ARIARoleMap();
 | |
|       if (live.IsEmpty()) {
 | |
|         // aria-live wasn't explicitly set. See if an aria-live value is implied
 | |
|         // by an ARIA role or markup element.
 | |
|         if (roleMap) {
 | |
|           GetLiveAttrValue(roleMap->liveAttRule, live);
 | |
|         } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
 | |
|                        acc, nsGkAtoms::aria_live)) {
 | |
|           value->ToString(live);
 | |
|         }
 | |
|       }
 | |
|       if (!live.IsEmpty() && roleMap &&
 | |
|           roleMap->roleAtom != nsGkAtoms::_empty) {
 | |
|         role = roleMap->roleAtom;
 | |
|       }
 | |
|     }
 | |
|     if (acc->IsDoc()) {
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   if (!live.IsEmpty()) {
 | |
|     aAttributes->SetAttribute(nsGkAtoms::containerLive, std::move(live));
 | |
|   }
 | |
|   if (role) {
 | |
|     aAttributes->SetAttribute(nsGkAtoms::containerLiveRole, std::move(role));
 | |
|   }
 | |
|   if (!relevant.IsEmpty()) {
 | |
|     aAttributes->SetAttribute(nsGkAtoms::containerRelevant,
 | |
|                               std::move(relevant));
 | |
|   }
 | |
|   if (atomic) {
 | |
|     aAttributes->SetAttribute(nsGkAtoms::containerAtomic, *atomic);
 | |
|   }
 | |
|   if (!busy.IsEmpty()) {
 | |
|     aAttributes->SetAttribute(nsGkAtoms::containerBusy, std::move(busy));
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::HasDefinedARIAToken(nsIContent* aContent, nsAtom* aAtom) {
 | |
|   NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!");
 | |
| 
 | |
|   if (!aContent->IsElement()) return false;
 | |
| 
 | |
|   dom::Element* element = aContent->AsElement();
 | |
|   if (auto* htmlElement = nsGenericHTMLElement::FromNode(element);
 | |
|       htmlElement && !element->HasAttr(aAtom)) {
 | |
|     const auto* defaults = GetARIADefaults(htmlElement);
 | |
|     if (!defaults) {
 | |
|       return false;
 | |
|     }
 | |
|     return HasDefinedARIAToken(defaults, aAtom);
 | |
|   }
 | |
|   return HasDefinedARIAToken(&element->GetAttrs(), aAtom);
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::HasDefinedARIAToken(const AttrArray* aAttrs, nsAtom* aAtom) {
 | |
|   return aAttrs->HasAttr(aAtom) &&
 | |
|          !aAttrs->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_empty,
 | |
|                               eCaseMatters) &&
 | |
|          !aAttrs->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_undefined,
 | |
|                               eCaseMatters);
 | |
| }
 | |
| 
 | |
| nsStaticAtom* nsAccUtils::NormalizeARIAToken(const AttrArray* aAttrs,
 | |
|                                              nsAtom* aAttr) {
 | |
|   if (!HasDefinedARIAToken(aAttrs, aAttr)) {
 | |
|     return nsGkAtoms::_empty;
 | |
|   }
 | |
| 
 | |
|   if (aAttr == nsGkAtoms::aria_current) {
 | |
|     static AttrArray::AttrValuesArray tokens[] = {
 | |
|         nsGkAtoms::page, nsGkAtoms::step, nsGkAtoms::location_,
 | |
|         nsGkAtoms::date, nsGkAtoms::time, nsGkAtoms::_true,
 | |
|         nullptr};
 | |
|     int32_t idx =
 | |
|         aAttrs->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters);
 | |
|     // If the token is present, return it, otherwise TRUE as per spec.
 | |
|     return (idx >= 0) ? tokens[idx] : nsGkAtoms::_true;
 | |
|   }
 | |
| 
 | |
|   static AttrArray::AttrValuesArray tokens[] = {
 | |
|       nsGkAtoms::_false, nsGkAtoms::_true, nsGkAtoms::mixed, nullptr};
 | |
|   int32_t idx =
 | |
|       aAttrs->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters);
 | |
|   if (idx >= 0) {
 | |
|     return tokens[idx];
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| nsStaticAtom* nsAccUtils::NormalizeARIAToken(dom::Element* aElement,
 | |
|                                              nsAtom* aAttr) {
 | |
|   if (auto* htmlElement = nsGenericHTMLElement::FromNode(aElement);
 | |
|       htmlElement && !aElement->HasAttr(aAttr)) {
 | |
|     const auto* defaults = GetARIADefaults(htmlElement);
 | |
|     if (!defaults) {
 | |
|       return nsGkAtoms::_empty;
 | |
|     }
 | |
|     return NormalizeARIAToken(defaults, aAttr);
 | |
|   }
 | |
|   return NormalizeARIAToken(&aElement->GetAttrs(), aAttr);
 | |
| }
 | |
| 
 | |
| Accessible* nsAccUtils::GetSelectableContainer(const Accessible* aAccessible,
 | |
|                                                uint64_t aState) {
 | |
|   if (!aAccessible) return nullptr;
 | |
| 
 | |
|   if (!(aState & states::SELECTABLE)) return nullptr;
 | |
|   MOZ_ASSERT(!aAccessible->IsDoc());
 | |
| 
 | |
|   const Accessible* parent = aAccessible;
 | |
|   while ((parent = parent->Parent()) && !parent->IsSelect()) {
 | |
|     if (parent->IsDoc() || parent->Role() == roles::PANE) {
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
|   return const_cast<Accessible*>(parent);
 | |
| }
 | |
| 
 | |
| LocalAccessible* nsAccUtils::GetSelectableContainer(
 | |
|     LocalAccessible* aAccessible, uint64_t aState) {
 | |
|   Accessible* selectable =
 | |
|       GetSelectableContainer(static_cast<Accessible*>(aAccessible), aState);
 | |
|   return selectable ? selectable->AsLocal() : nullptr;
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::IsDOMAttrTrue(const LocalAccessible* aAccessible,
 | |
|                                nsAtom* aAttr) {
 | |
|   dom::Element* el = aAccessible->Elm();
 | |
|   return el && ARIAAttrValueIs(el, aAttr, nsGkAtoms::_true, eCaseMatters);
 | |
| }
 | |
| 
 | |
| Accessible* nsAccUtils::TableFor(Accessible* aAcc) {
 | |
|   if (!aAcc ||
 | |
|       (!aAcc->IsTable() && !aAcc->IsTableRow() && !aAcc->IsTableCell())) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   Accessible* table = aAcc;
 | |
|   for (; table && !table->IsTable(); table = table->Parent()) {
 | |
|   }
 | |
|   // We don't assert (table && table->IsTable()) here because
 | |
|   // it's possible for this tree walk to yield no table at all
 | |
|   // ex. because a table part has been moved in the tree
 | |
|   // using aria-owns.
 | |
|   return table;
 | |
| }
 | |
| 
 | |
| LocalAccessible* nsAccUtils::TableFor(LocalAccessible* aRow) {
 | |
|   Accessible* table = TableFor(static_cast<Accessible*>(aRow));
 | |
|   return table ? table->AsLocal() : nullptr;
 | |
| }
 | |
| 
 | |
| HyperTextAccessible* nsAccUtils::GetTextContainer(nsINode* aNode) {
 | |
|   // Get text accessible containing the result node.
 | |
|   DocAccessible* doc = GetAccService()->GetDocAccessible(aNode->OwnerDoc());
 | |
|   LocalAccessible* accessible =
 | |
|       doc ? doc->GetAccessibleOrContainer(aNode) : nullptr;
 | |
|   if (!accessible) return nullptr;
 | |
| 
 | |
|   do {
 | |
|     HyperTextAccessible* textAcc = accessible->AsHyperText();
 | |
|     if (textAcc) return textAcc;
 | |
| 
 | |
|     accessible = accessible->LocalParent();
 | |
|   } while (accessible);
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntPoint nsAccUtils::ConvertToScreenCoords(
 | |
|     int32_t aX, int32_t aY, uint32_t aCoordinateType, Accessible* aAccessible) {
 | |
|   LayoutDeviceIntPoint coords(aX, aY);
 | |
| 
 | |
|   switch (aCoordinateType) {
 | |
|     // Regardless of coordinate type, the coords returned
 | |
|     // are in dev pixels.
 | |
|     case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
 | |
|       break;
 | |
| 
 | |
|     case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: {
 | |
|       coords += GetScreenCoordsForWindow(aAccessible);
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: {
 | |
|       coords += GetScreenCoordsForParent(aAccessible);
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE("invalid coord type!");
 | |
|   }
 | |
| 
 | |
|   return coords;
 | |
| }
 | |
| 
 | |
| void nsAccUtils::ConvertScreenCoordsTo(int32_t* aX, int32_t* aY,
 | |
|                                        uint32_t aCoordinateType,
 | |
|                                        Accessible* aAccessible) {
 | |
|   switch (aCoordinateType) {
 | |
|     // Regardless of coordinate type, the values returned for
 | |
|     // aX and aY are in dev pixels.
 | |
|     case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
 | |
|       break;
 | |
| 
 | |
|     case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: {
 | |
|       LayoutDeviceIntPoint coords = GetScreenCoordsForWindow(aAccessible);
 | |
|       *aX -= coords.x;
 | |
|       *aY -= coords.y;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: {
 | |
|       LayoutDeviceIntPoint coords = GetScreenCoordsForParent(aAccessible);
 | |
|       *aX -= coords.x;
 | |
|       *aY -= coords.y;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE("invalid coord type!");
 | |
|   }
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntPoint nsAccUtils::GetScreenCoordsForParent(
 | |
|     Accessible* aAccessible) {
 | |
|   if (!aAccessible) return LayoutDeviceIntPoint();
 | |
| 
 | |
|   if (Accessible* parent = aAccessible->Parent()) {
 | |
|     LayoutDeviceIntRect parentBounds = parent->Bounds();
 | |
|     // The rect returned from Bounds() is already in dev
 | |
|     // pixels, so we don't need to do any conversion here.
 | |
|     return parentBounds.TopLeft();
 | |
|   }
 | |
| 
 | |
|   return LayoutDeviceIntPoint();
 | |
| }
 | |
| 
 | |
| LayoutDeviceIntPoint nsAccUtils::GetScreenCoordsForWindow(
 | |
|     Accessible* aAccessible) {
 | |
|   a11y::LocalAccessible* localAcc = aAccessible->AsLocal();
 | |
|   if (!localAcc) {
 | |
|     localAcc = aAccessible->AsRemote()->OuterDocOfRemoteBrowser();
 | |
|   }
 | |
| 
 | |
|   LayoutDeviceIntPoint coords(0, 0);
 | |
|   nsCOMPtr<nsIDocShellTreeItem> treeItem(
 | |
|       nsCoreUtils::GetDocShellFor(localAcc->GetNode()));
 | |
|   if (!treeItem) return coords;
 | |
| 
 | |
|   nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
 | |
|   treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
 | |
|   if (!treeOwner) return coords;
 | |
| 
 | |
|   nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
 | |
|   if (baseWindow) {
 | |
|     baseWindow->GetPosition(&coords.x.value,
 | |
|                             &coords.y.value);  // in device pixels
 | |
|   }
 | |
| 
 | |
|   return coords;
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue) {
 | |
|   switch (aRule) {
 | |
|     case eOffLiveAttr:
 | |
|       aValue = u"off"_ns;
 | |
|       return true;
 | |
|     case ePoliteLiveAttr:
 | |
|       aValue = u"polite"_ns;
 | |
|       return true;
 | |
|     case eAssertiveLiveAttr:
 | |
|       aValue = u"assertive"_ns;
 | |
|       return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| 
 | |
| bool nsAccUtils::IsTextInterfaceSupportCorrect(LocalAccessible* aAccessible) {
 | |
|   // Don't test for accessible docs, it makes us create accessibles too
 | |
|   // early and fire mutation events before we need to
 | |
|   if (aAccessible->IsDoc()) return true;
 | |
| 
 | |
|   bool foundText = false;
 | |
|   uint32_t childCount = aAccessible->ChildCount();
 | |
|   for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
 | |
|     LocalAccessible* child = aAccessible->LocalChildAt(childIdx);
 | |
|     if (child && child->IsText()) {
 | |
|       foundText = true;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return !foundText || aAccessible->IsHyperText();
 | |
| }
 | |
| #endif
 | |
| 
 | |
| uint32_t nsAccUtils::TextLength(Accessible* aAccessible) {
 | |
|   if (!aAccessible->IsText()) {
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   if (LocalAccessible* localAcc = aAccessible->AsLocal()) {
 | |
|     TextLeafAccessible* textLeaf = localAcc->AsTextLeaf();
 | |
|     if (textLeaf) {
 | |
|       return textLeaf->Text().Length();
 | |
|     }
 | |
|   } else if (aAccessible->IsText()) {
 | |
|     RemoteAccessible* remoteAcc = aAccessible->AsRemote();
 | |
|     MOZ_ASSERT(remoteAcc);
 | |
|     return remoteAcc->GetCachedTextLength();
 | |
|   }
 | |
| 
 | |
|   // For list bullets (or anything other accessible which would compute its own
 | |
|   // text. They don't have their own frame.
 | |
|   // XXX In the future, list bullets may have frame and anon content, so
 | |
|   // we should be able to remove this at that point
 | |
|   nsAutoString text;
 | |
|   aAccessible->AppendTextTo(text);  // Get all the text
 | |
|   return text.Length();
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::MustPrune(Accessible* aAccessible) {
 | |
|   MOZ_ASSERT(aAccessible);
 | |
|   roles::Role role = aAccessible->Role();
 | |
| 
 | |
|   if (role == roles::SLIDER || role == roles::PROGRESSBAR) {
 | |
|     // Always prune the tree for sliders and progressbars, as it doesn't make
 | |
|     // sense for either to have descendants. Per the ARIA spec, children of
 | |
|     // these elements are presentational. They also confuse NVDA.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   if (role != roles::MENUITEM && role != roles::COMBOBOX_OPTION &&
 | |
|       role != roles::OPTION && role != roles::ENTRY &&
 | |
|       role != roles::FLAT_EQUATION && role != roles::PASSWORD_TEXT &&
 | |
|       role != roles::PUSHBUTTON && role != roles::TOGGLE_BUTTON &&
 | |
|       role != roles::GRAPHIC && role != roles::SEPARATOR) {
 | |
|     // If it doesn't match any of these roles, don't prune its children.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (aAccessible->ChildCount() != 1) {
 | |
|     // If the accessible has more than one child, don't prune it.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   roles::Role childRole = aAccessible->FirstChild()->Role();
 | |
|   // If the accessible's child is a text leaf, prune the accessible.
 | |
|   return childRole == roles::TEXT_LEAF || childRole == roles::STATICTEXT;
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::IsARIALive(const LocalAccessible* aAccessible) {
 | |
|   // Get computed aria-live property based on the closest container with the
 | |
|   // attribute. Inner nodes override outer nodes within the same
 | |
|   // document.
 | |
|   // This should be the same as the container-live attribute, but we don't need
 | |
|   // the other container-* attributes, so we can't use the same function.
 | |
|   nsIContent* ancestor = aAccessible->GetContent();
 | |
|   if (!ancestor) {
 | |
|     return false;
 | |
|   }
 | |
|   dom::Document* doc = ancestor->GetComposedDoc();
 | |
|   if (!doc) {
 | |
|     return false;
 | |
|   }
 | |
|   dom::Element* topEl = doc->GetRootElement();
 | |
|   while (ancestor) {
 | |
|     const nsRoleMapEntry* role = nullptr;
 | |
|     if (ancestor->IsElement()) {
 | |
|       role = aria::GetRoleMap(ancestor->AsElement());
 | |
|     }
 | |
|     nsAutoString live;
 | |
|     if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
 | |
|       GetARIAAttr(ancestor->AsElement(), nsGkAtoms::aria_live, live);
 | |
|     } else if (role) {
 | |
|       GetLiveAttrValue(role->liveAttRule, live);
 | |
|     } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
 | |
|                    ancestor, nsGkAtoms::aria_live)) {
 | |
|       value->ToString(live);
 | |
|     }
 | |
|     if (!live.IsEmpty() && !live.EqualsLiteral("off")) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (ancestor == topEl) {
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     ancestor = ancestor->GetParent();
 | |
|     if (!ancestor) {
 | |
|       ancestor = topEl;  // Use <body>/<frameset>
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| Accessible* nsAccUtils::DocumentFor(Accessible* aAcc) {
 | |
|   if (!aAcc) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (LocalAccessible* localAcc = aAcc->AsLocal()) {
 | |
|     return localAcc->Document();
 | |
|   }
 | |
|   return aAcc->AsRemote()->Document();
 | |
| }
 | |
| 
 | |
| Accessible* nsAccUtils::GetAccessibleByID(Accessible* aDoc, uint64_t aID) {
 | |
|   if (!aDoc) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   if (LocalAccessible* localAcc = aDoc->AsLocal()) {
 | |
|     if (DocAccessible* doc = localAcc->AsDoc()) {
 | |
|       if (!aID) {
 | |
|         // GetAccessibleByUniqueID doesn't treat 0 as the document.
 | |
|         return aDoc;
 | |
|       }
 | |
|       return doc->GetAccessibleByUniqueID(
 | |
|           reinterpret_cast<void*>(static_cast<uintptr_t>(aID)));
 | |
|     }
 | |
|   } else if (DocAccessibleParent* doc = aDoc->AsRemote()->AsDoc()) {
 | |
|     return doc->GetAccessible(aID);
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void nsAccUtils::DocumentURL(Accessible* aDoc, nsAString& aURL) {
 | |
|   MOZ_ASSERT(aDoc && aDoc->IsDoc());
 | |
|   if (LocalAccessible* localAcc = aDoc->AsLocal()) {
 | |
|     return localAcc->AsDoc()->URL(aURL);
 | |
|   }
 | |
|   return aDoc->AsRemote()->AsDoc()->URL(aURL);
 | |
| }
 | |
| 
 | |
| void nsAccUtils::DocumentMimeType(Accessible* aDoc, nsAString& aMimeType) {
 | |
|   MOZ_ASSERT(aDoc && aDoc->IsDoc());
 | |
|   if (LocalAccessible* localAcc = aDoc->AsLocal()) {
 | |
|     return localAcc->AsDoc()->MimeType(aMimeType);
 | |
|   }
 | |
|   return aDoc->AsRemote()->AsDoc()->MimeType(aMimeType);
 | |
| }
 | |
| 
 | |
| // ARIA Accessibility Default Accessors
 | |
| const AttrArray* nsAccUtils::GetARIADefaults(dom::Element* aElement) {
 | |
|   auto* element = nsGenericHTMLElement::FromNode(aElement);
 | |
|   if (!element) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   auto* internals = element->GetInternals();
 | |
|   if (!internals) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return &internals->GetAttrs();
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::HasARIAAttr(dom::Element* aElement, const nsAtom* aName) {
 | |
|   if (aElement->HasAttr(aName)) {
 | |
|     return true;
 | |
|   }
 | |
|   const auto* defaults = GetARIADefaults(aElement);
 | |
|   if (!defaults) {
 | |
|     return false;
 | |
|   }
 | |
|   return defaults->HasAttr(aName);
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::GetARIAAttr(dom::Element* aElement, const nsAtom* aName,
 | |
|                              nsAString& aResult) {
 | |
|   if (aElement->GetAttr(aName, aResult)) {
 | |
|     return true;
 | |
|   }
 | |
|   const auto* defaults = GetARIADefaults(aElement);
 | |
|   if (!defaults) {
 | |
|     return false;
 | |
|   }
 | |
|   return defaults->GetAttr(aName, aResult);
 | |
| }
 | |
| 
 | |
| const nsAttrValue* nsAccUtils::GetARIAAttr(dom::Element* aElement,
 | |
|                                            const nsAtom* aName) {
 | |
|   if (const auto* val = aElement->GetParsedAttr(aName, kNameSpaceID_None)) {
 | |
|     return val;
 | |
|   }
 | |
|   const auto* defaults = GetARIADefaults(aElement);
 | |
|   if (!defaults) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return defaults->GetAttr(aName, kNameSpaceID_None);
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::ARIAAttrValueIs(dom::Element* aElement, const nsAtom* aName,
 | |
|                                  const nsAString& aValue,
 | |
|                                  nsCaseTreatment aCaseSensitive) {
 | |
|   if (aElement->AttrValueIs(kNameSpaceID_None, aName, aValue, aCaseSensitive)) {
 | |
|     return true;
 | |
|   }
 | |
|   const auto* defaults = GetARIADefaults(aElement);
 | |
|   if (!defaults) {
 | |
|     return false;
 | |
|   }
 | |
|   return defaults->AttrValueIs(kNameSpaceID_None, aName, aValue,
 | |
|                                aCaseSensitive);
 | |
| }
 | |
| 
 | |
| bool nsAccUtils::ARIAAttrValueIs(dom::Element* aElement, const nsAtom* aName,
 | |
|                                  const nsAtom* aValue,
 | |
|                                  nsCaseTreatment aCaseSensitive) {
 | |
|   if (aElement->AttrValueIs(kNameSpaceID_None, aName, aValue, aCaseSensitive)) {
 | |
|     return true;
 | |
|   }
 | |
|   const auto* defaults = GetARIADefaults(aElement);
 | |
|   if (!defaults) {
 | |
|     return false;
 | |
|   }
 | |
|   return defaults->AttrValueIs(kNameSpaceID_None, aName, aValue,
 | |
|                                aCaseSensitive);
 | |
| }
 | |
| 
 | |
| int32_t nsAccUtils::FindARIAAttrValueIn(dom::Element* aElement,
 | |
|                                         const nsAtom* aName,
 | |
|                                         AttrArray::AttrValuesArray* aValues,
 | |
|                                         nsCaseTreatment aCaseSensitive) {
 | |
|   int32_t index = aElement->FindAttrValueIn(kNameSpaceID_None, aName, aValues,
 | |
|                                             aCaseSensitive);
 | |
|   if (index == AttrArray::ATTR_MISSING) {
 | |
|     const auto* defaults = GetARIADefaults(aElement);
 | |
|     if (!defaults) {
 | |
|       return index;
 | |
|     }
 | |
|     index = defaults->FindAttrValueIn(kNameSpaceID_None, aName, aValues,
 | |
|                                       aCaseSensitive);
 | |
|   }
 | |
|   return index;
 | |
| }
 |