nsAccUtils.cpp (22281B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "nsAccUtils.h" 7 8 #include "AccAttributes.h" 9 #include "ARIAMap.h" 10 #include "nsCoreUtils.h" 11 #include "nsGenericHTMLElement.h" 12 #include "DocAccessible.h" 13 #include "DocAccessibleParent.h" 14 #include "HyperTextAccessible.h" 15 #include "nsIAccessibleTypes.h" 16 #include "mozilla/a11y/Role.h" 17 #include "States.h" 18 #include "TextLeafAccessible.h" 19 20 #include "nsIBaseWindow.h" 21 #include "nsIDocShellTreeOwner.h" 22 #include "nsIDOMXULContainerElement.h" 23 #include "mozilla/a11y/RemoteAccessible.h" 24 #include "mozilla/dom/Document.h" 25 #include "mozilla/dom/Element.h" 26 #include "mozilla/dom/ElementInternals.h" 27 #include "nsAccessibilityService.h" 28 29 using namespace mozilla; 30 using namespace mozilla::a11y; 31 32 void nsAccUtils::SetAccGroupAttrs(AccAttributes* aAttributes, int32_t aLevel, 33 int32_t aSetSize, int32_t aPosInSet) { 34 nsAutoString value; 35 36 if (aLevel) { 37 aAttributes->SetAttribute(nsGkAtoms::level, aLevel); 38 } 39 40 if (aSetSize && aPosInSet) { 41 aAttributes->SetAttribute(nsGkAtoms::posinset, aPosInSet); 42 aAttributes->SetAttribute(nsGkAtoms::setsize, aSetSize); 43 } 44 } 45 46 void nsAccUtils::SetAccGroupAttrs(AccAttributes* aAttributes, 47 Accessible* aAcc) { 48 GroupPos groupPos = aAcc->GroupPosition(); 49 nsAccUtils::SetAccGroupAttrs(aAttributes, groupPos.level, groupPos.setSize, 50 groupPos.posInSet); 51 } 52 53 int32_t nsAccUtils::GetLevelForXULContainerItem(nsIContent* aContent) { 54 nsCOMPtr<nsIDOMXULContainerItemElement> item = 55 aContent->AsElement()->AsXULContainerItem(); 56 if (!item) return 0; 57 58 nsCOMPtr<dom::Element> containerElement; 59 item->GetParentContainer(getter_AddRefs(containerElement)); 60 nsCOMPtr<nsIDOMXULContainerElement> container = 61 containerElement ? containerElement->AsXULContainer() : nullptr; 62 if (!container) return 0; 63 64 // Get level of the item. 65 int32_t level = -1; 66 while (container) { 67 level++; 68 69 container->GetParentContainer(getter_AddRefs(containerElement)); 70 container = containerElement ? containerElement->AsXULContainer() : nullptr; 71 } 72 73 return level; 74 } 75 76 void nsAccUtils::SetLiveContainerAttributes(AccAttributes* aAttributes, 77 Accessible* aStartAcc) { 78 nsAutoString live, relevant, busy; 79 nsStaticAtom* role = nullptr; 80 Maybe<bool> atomic; 81 for (Accessible* acc = aStartAcc; acc; acc = acc->Parent()) { 82 // We only want the nearest value for each attribute. If we already got a 83 // value, don't bother fetching it from further ancestors. 84 const bool wasLiveEmpty = live.IsEmpty(); 85 acc->LiveRegionAttributes(wasLiveEmpty ? &live : nullptr, 86 relevant.IsEmpty() ? &relevant : nullptr, 87 atomic ? nullptr : &atomic, 88 busy.IsEmpty() ? &busy : nullptr); 89 if (wasLiveEmpty) { 90 const nsRoleMapEntry* roleMap = acc->ARIARoleMap(); 91 if (live.IsEmpty()) { 92 // aria-live wasn't explicitly set. See if an aria-live value is implied 93 // by an ARIA role or markup element. 94 MOZ_ASSERT(GetAccService()); 95 if (roleMap) { 96 GetLiveAttrValue(roleMap->liveAttRule, live); 97 } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute( 98 acc, nsGkAtoms::aria_live)) { 99 value->ToString(live); 100 } 101 } 102 if (!live.IsEmpty() && roleMap && 103 roleMap->roleAtom != nsGkAtoms::_empty) { 104 role = roleMap->roleAtom; 105 } 106 } 107 if (acc->IsDoc()) { 108 break; 109 } 110 } 111 if (!live.IsEmpty()) { 112 aAttributes->SetAttribute(nsGkAtoms::containerLive, std::move(live)); 113 } 114 if (role) { 115 aAttributes->SetAttribute(nsGkAtoms::containerLiveRole, std::move(role)); 116 } 117 if (!relevant.IsEmpty()) { 118 aAttributes->SetAttribute(nsGkAtoms::containerRelevant, 119 std::move(relevant)); 120 } 121 if (atomic) { 122 aAttributes->SetAttribute(nsGkAtoms::containerAtomic, *atomic); 123 } 124 if (!busy.IsEmpty()) { 125 aAttributes->SetAttribute(nsGkAtoms::containerBusy, std::move(busy)); 126 } 127 } 128 129 bool nsAccUtils::HasDefinedARIAToken(nsIContent* aContent, nsAtom* aAtom) { 130 NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!"); 131 132 if (!aContent->IsElement()) return false; 133 134 dom::Element* element = aContent->AsElement(); 135 if (auto* htmlElement = nsGenericHTMLElement::FromNode(element); 136 htmlElement && !element->HasAttr(aAtom)) { 137 const auto* defaults = GetARIADefaults(htmlElement); 138 if (!defaults) { 139 return false; 140 } 141 return HasDefinedARIAToken(defaults, aAtom); 142 } 143 return HasDefinedARIAToken(&element->GetAttrs(), aAtom); 144 } 145 146 bool nsAccUtils::HasDefinedARIAToken(const AttrArray* aAttrs, nsAtom* aAtom) { 147 return aAttrs->HasAttr(aAtom) && 148 !aAttrs->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_empty, 149 eCaseMatters) && 150 !aAttrs->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_undefined, 151 eCaseMatters); 152 } 153 154 nsStaticAtom* nsAccUtils::NormalizeARIAToken(const AttrArray* aAttrs, 155 nsAtom* aAttr) { 156 if (!HasDefinedARIAToken(aAttrs, aAttr)) { 157 return nsGkAtoms::_empty; 158 } 159 160 if (aAttr == nsGkAtoms::aria_current) { 161 static AttrArray::AttrValuesArray tokens[] = { 162 nsGkAtoms::page, nsGkAtoms::step, nsGkAtoms::location, 163 nsGkAtoms::date, nsGkAtoms::time, nsGkAtoms::_true, 164 nullptr}; 165 int32_t idx = 166 aAttrs->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters); 167 // If the token is present, return it, otherwise TRUE as per spec. 168 return (idx >= 0) ? tokens[idx] : nsGkAtoms::_true; 169 } 170 171 static AttrArray::AttrValuesArray tokens[] = { 172 nsGkAtoms::_false, nsGkAtoms::_true, nsGkAtoms::mixed, nullptr}; 173 int32_t idx = 174 aAttrs->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters); 175 if (idx >= 0) { 176 return tokens[idx]; 177 } 178 179 return nullptr; 180 } 181 182 nsStaticAtom* nsAccUtils::NormalizeARIAToken(dom::Element* aElement, 183 nsAtom* aAttr) { 184 if (auto* htmlElement = nsGenericHTMLElement::FromNode(aElement); 185 htmlElement && !aElement->HasAttr(aAttr)) { 186 const auto* defaults = GetARIADefaults(htmlElement); 187 if (!defaults) { 188 return nsGkAtoms::_empty; 189 } 190 return NormalizeARIAToken(defaults, aAttr); 191 } 192 return NormalizeARIAToken(&aElement->GetAttrs(), aAttr); 193 } 194 195 Accessible* nsAccUtils::GetSelectableContainer(const Accessible* aAccessible, 196 uint64_t aState) { 197 if (!aAccessible) return nullptr; 198 199 if (!(aState & states::SELECTABLE)) return nullptr; 200 MOZ_ASSERT(!aAccessible->IsDoc()); 201 202 const Accessible* parent = aAccessible; 203 while ((parent = parent->Parent()) && !parent->IsSelect()) { 204 if (parent->IsDoc() || parent->Role() == roles::PANE) { 205 return nullptr; 206 } 207 } 208 return const_cast<Accessible*>(parent); 209 } 210 211 LocalAccessible* nsAccUtils::GetSelectableContainer( 212 LocalAccessible* aAccessible, uint64_t aState) { 213 Accessible* selectable = 214 GetSelectableContainer(static_cast<Accessible*>(aAccessible), aState); 215 return selectable ? selectable->AsLocal() : nullptr; 216 } 217 218 bool nsAccUtils::IsDOMAttrTrue(const LocalAccessible* aAccessible, 219 nsAtom* aAttr) { 220 dom::Element* el = aAccessible->Elm(); 221 return el && ARIAAttrValueIs(el, aAttr, nsGkAtoms::_true, eCaseMatters); 222 } 223 224 Accessible* nsAccUtils::TableFor(const Accessible* aAcc) { 225 if (!aAcc || 226 (!aAcc->IsTable() && !aAcc->IsTableRow() && !aAcc->IsTableCell())) { 227 return nullptr; 228 } 229 Accessible* table = const_cast<Accessible*>(aAcc); 230 for (; table && !table->IsTable(); table = table->Parent()) { 231 } 232 // We don't assert (table && table->IsTable()) here because 233 // it's possible for this tree walk to yield no table at all 234 // ex. because a table part has been moved in the tree 235 // using aria-owns. 236 return table; 237 } 238 239 LocalAccessible* nsAccUtils::TableFor(LocalAccessible* aRow) { 240 Accessible* table = TableFor(static_cast<Accessible*>(aRow)); 241 return table ? table->AsLocal() : nullptr; 242 } 243 244 HyperTextAccessible* nsAccUtils::GetTextContainer(nsINode* aNode) { 245 // Get text accessible containing the result node. 246 DocAccessible* doc = GetAccService()->GetDocAccessible(aNode->OwnerDoc()); 247 LocalAccessible* accessible = 248 doc ? doc->GetAccessibleOrContainer(aNode) : nullptr; 249 if (!accessible) return nullptr; 250 251 do { 252 HyperTextAccessible* textAcc = accessible->AsHyperText(); 253 if (textAcc) return textAcc; 254 255 accessible = accessible->LocalParent(); 256 } while (accessible); 257 258 return nullptr; 259 } 260 261 LayoutDeviceIntPoint nsAccUtils::ConvertToScreenCoords( 262 int32_t aX, int32_t aY, uint32_t aCoordinateType, Accessible* aAccessible) { 263 LayoutDeviceIntPoint coords(aX, aY); 264 265 switch (aCoordinateType) { 266 // Regardless of coordinate type, the coords returned 267 // are in dev pixels. 268 case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE: 269 break; 270 271 case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: { 272 coords += GetScreenCoordsForWindow(aAccessible); 273 break; 274 } 275 276 case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: { 277 coords += GetScreenCoordsForParent(aAccessible); 278 break; 279 } 280 281 default: 282 MOZ_ASSERT_UNREACHABLE("invalid coord type!"); 283 } 284 285 return coords; 286 } 287 288 void nsAccUtils::ConvertScreenCoordsTo(int32_t* aX, int32_t* aY, 289 uint32_t aCoordinateType, 290 Accessible* aAccessible) { 291 switch (aCoordinateType) { 292 // Regardless of coordinate type, the values returned for 293 // aX and aY are in dev pixels. 294 case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE: 295 break; 296 297 case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: { 298 LayoutDeviceIntPoint coords = GetScreenCoordsForWindow(aAccessible); 299 *aX -= coords.x; 300 *aY -= coords.y; 301 break; 302 } 303 304 case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: { 305 LayoutDeviceIntPoint coords = GetScreenCoordsForParent(aAccessible); 306 *aX -= coords.x; 307 *aY -= coords.y; 308 break; 309 } 310 311 default: 312 MOZ_ASSERT_UNREACHABLE("invalid coord type!"); 313 } 314 } 315 316 LayoutDeviceIntPoint nsAccUtils::GetScreenCoordsForParent( 317 Accessible* aAccessible) { 318 if (!aAccessible) return LayoutDeviceIntPoint(); 319 320 if (Accessible* parent = aAccessible->Parent()) { 321 LayoutDeviceIntRect parentBounds = parent->Bounds(); 322 // The rect returned from Bounds() is already in dev 323 // pixels, so we don't need to do any conversion here. 324 return parentBounds.TopLeft(); 325 } 326 327 return LayoutDeviceIntPoint(); 328 } 329 330 LayoutDeviceIntPoint nsAccUtils::GetScreenCoordsForWindow( 331 Accessible* aAccessible) { 332 LayoutDeviceIntPoint coords(0, 0); 333 a11y::LocalAccessible* localAcc = aAccessible->AsLocal(); 334 if (!localAcc) { 335 localAcc = aAccessible->AsRemote()->OuterDocOfRemoteBrowser(); 336 if (!localAcc) { 337 // This could be null if the tab is closing but the document is still 338 // being shut down. 339 return coords; 340 } 341 } 342 343 nsCOMPtr<nsIDocShellTreeItem> treeItem( 344 nsCoreUtils::GetDocShellFor(localAcc->GetNode())); 345 if (!treeItem) return coords; 346 347 nsCOMPtr<nsIDocShellTreeOwner> treeOwner; 348 treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); 349 if (!treeOwner) return coords; 350 351 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner); 352 if (baseWindow) { 353 baseWindow->GetPosition(&coords.x.value, 354 &coords.y.value); // in device pixels 355 } 356 357 return coords; 358 } 359 360 bool nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue) { 361 switch (aRule) { 362 case eOffLiveAttr: 363 aValue = u"off"_ns; 364 return true; 365 case ePoliteLiveAttr: 366 aValue = u"polite"_ns; 367 return true; 368 case eAssertiveLiveAttr: 369 aValue = u"assertive"_ns; 370 return true; 371 } 372 373 return false; 374 } 375 376 #ifdef DEBUG 377 378 bool nsAccUtils::IsTextInterfaceSupportCorrect(LocalAccessible* aAccessible) { 379 // Don't test for accessible docs, it makes us create accessibles too 380 // early and fire mutation events before we need to 381 if (aAccessible->IsDoc()) return true; 382 383 bool foundText = false; 384 uint32_t childCount = aAccessible->ChildCount(); 385 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { 386 LocalAccessible* child = aAccessible->LocalChildAt(childIdx); 387 if (child && child->IsText()) { 388 foundText = true; 389 break; 390 } 391 } 392 393 return !foundText || aAccessible->IsHyperText(); 394 } 395 #endif 396 397 uint32_t nsAccUtils::TextLength(Accessible* aAccessible) { 398 if (!aAccessible->IsText()) { 399 return 1; 400 } 401 402 if (LocalAccessible* localAcc = aAccessible->AsLocal()) { 403 TextLeafAccessible* textLeaf = localAcc->AsTextLeaf(); 404 if (textLeaf) { 405 return textLeaf->Text().Length(); 406 } 407 } else if (aAccessible->IsText()) { 408 RemoteAccessible* remoteAcc = aAccessible->AsRemote(); 409 MOZ_ASSERT(remoteAcc); 410 return remoteAcc->GetCachedTextLength(); 411 } 412 413 // For list bullets (or anything other accessible which would compute its own 414 // text. They don't have their own frame. 415 // XXX In the future, list bullets may have frame and anon content, so 416 // we should be able to remove this at that point 417 nsAutoString text; 418 aAccessible->AppendTextTo(text); // Get all the text 419 return text.Length(); 420 } 421 422 bool nsAccUtils::MustPrune(Accessible* aAccessible) { 423 MOZ_ASSERT(aAccessible); 424 roles::Role role = aAccessible->Role(); 425 426 if (role == roles::SLIDER || role == roles::PROGRESSBAR || 427 role == roles::METER) { 428 // Always prune the tree for sliders and progressbars, as it doesn't make 429 // sense for either to have descendants. Per the ARIA spec, children of 430 // these elements are presentational. They also confuse NVDA. 431 return true; 432 } 433 434 if (role != roles::MENUITEM && role != roles::COMBOBOX_OPTION && 435 role != roles::OPTION && role != roles::ENTRY && 436 role != roles::FLAT_EQUATION && role != roles::PASSWORD_TEXT && 437 role != roles::PUSHBUTTON && role != roles::TOGGLE_BUTTON && 438 role != roles::GRAPHIC && role != roles::SEPARATOR) { 439 // If it doesn't match any of these roles, don't prune its children. 440 return false; 441 } 442 443 if (aAccessible->ChildCount() != 1) { 444 // If the accessible has more than one child, don't prune it. 445 return false; 446 } 447 448 roles::Role childRole = aAccessible->FirstChild()->Role(); 449 // If the accessible's child is a text leaf, prune the accessible. 450 return childRole == roles::TEXT_LEAF || childRole == roles::STATICTEXT; 451 } 452 453 void nsAccUtils::GetLiveRegionSetting(Accessible* aAcc, nsAString& aLive) { 454 MOZ_ASSERT(aAcc); 455 aAcc->LiveRegionAttributes(&aLive, nullptr, nullptr, nullptr); 456 // aria-live wasn't explicitly set. See if an aria-live value is implied 457 // by an ARIA role or markup element. 458 if (const nsRoleMapEntry* roleMap = aAcc->ARIARoleMap()) { 459 GetLiveAttrValue(roleMap->liveAttRule, aLive); 460 } else if (nsStaticAtom* value = GetAccService() 461 ? GetAccService()->MarkupAttribute( 462 aAcc, nsGkAtoms::aria_live) 463 : nullptr) { 464 value->ToString(aLive); 465 } 466 } 467 468 Accessible* nsAccUtils::GetLiveRegionRoot(Accessible* aAcc) { 469 MOZ_ASSERT(aAcc); 470 nsAutoString live; 471 Accessible* acc; 472 for (acc = aAcc; acc; acc = acc->Parent()) { 473 GetLiveRegionSetting(acc, live); 474 if (!live.IsEmpty()) { 475 break; 476 } 477 if (acc->IsDoc()) { 478 // A document can be the root of a live region, but a live region cannot 479 // cross document boundaries. 480 return nullptr; 481 } 482 } 483 if (live.IsEmpty() || live.EqualsLiteral("off")) { 484 return nullptr; 485 } 486 return acc; 487 } 488 489 Accessible* nsAccUtils::DocumentFor(Accessible* aAcc) { 490 if (!aAcc) { 491 return nullptr; 492 } 493 if (LocalAccessible* localAcc = aAcc->AsLocal()) { 494 return localAcc->Document(); 495 } 496 return aAcc->AsRemote()->Document(); 497 } 498 499 Accessible* nsAccUtils::GetAccessibleByID(Accessible* aDoc, uint64_t aID) { 500 if (!aDoc) { 501 return nullptr; 502 } 503 if (LocalAccessible* localAcc = aDoc->AsLocal()) { 504 if (DocAccessible* doc = localAcc->AsDoc()) { 505 if (!aID) { 506 // GetAccessibleByUniqueID doesn't treat 0 as the document. 507 return aDoc; 508 } 509 return doc->GetAccessibleByUniqueID( 510 reinterpret_cast<void*>(static_cast<uintptr_t>(aID))); 511 } 512 } else if (DocAccessibleParent* doc = aDoc->AsRemote()->AsDoc()) { 513 return doc->GetAccessible(aID); 514 } 515 return nullptr; 516 } 517 518 void nsAccUtils::DocumentURL(Accessible* aDoc, nsAString& aURL) { 519 MOZ_ASSERT(aDoc && aDoc->IsDoc()); 520 if (LocalAccessible* localAcc = aDoc->AsLocal()) { 521 return localAcc->AsDoc()->URL(aURL); 522 } 523 return aDoc->AsRemote()->AsDoc()->URL(aURL); 524 } 525 526 void nsAccUtils::DocumentMimeType(Accessible* aDoc, nsAString& aMimeType) { 527 MOZ_ASSERT(aDoc && aDoc->IsDoc()); 528 if (LocalAccessible* localAcc = aDoc->AsLocal()) { 529 return localAcc->AsDoc()->MimeType(aMimeType); 530 } 531 return aDoc->AsRemote()->AsDoc()->MimeType(aMimeType); 532 } 533 534 // ARIA Accessibility Default Accessors 535 const AttrArray* nsAccUtils::GetARIADefaults(dom::Element* aElement) { 536 auto* element = nsGenericHTMLElement::FromNode(aElement); 537 if (!element) { 538 return nullptr; 539 } 540 auto* internals = element->GetInternals(); 541 if (!internals) { 542 return nullptr; 543 } 544 return &internals->GetAttrs(); 545 } 546 547 bool nsAccUtils::HasARIAAttr(dom::Element* aElement, const nsAtom* aName) { 548 if (aElement->HasAttr(aName)) { 549 return true; 550 } 551 const auto* defaults = GetARIADefaults(aElement); 552 if (!defaults) { 553 return false; 554 } 555 return defaults->HasAttr(aName); 556 } 557 558 bool nsAccUtils::GetARIAAttr(dom::Element* aElement, const nsAtom* aName, 559 nsAString& aResult) { 560 if (aElement->GetAttr(aName, aResult)) { 561 return true; 562 } 563 const auto* defaults = GetARIADefaults(aElement); 564 if (!defaults) { 565 return false; 566 } 567 return defaults->GetAttr(aName, aResult); 568 } 569 570 const nsAttrValue* nsAccUtils::GetARIAAttr(dom::Element* aElement, 571 const nsAtom* aName) { 572 if (const auto* val = aElement->GetParsedAttr(aName, kNameSpaceID_None)) { 573 return val; 574 } 575 const auto* defaults = GetARIADefaults(aElement); 576 if (!defaults) { 577 return nullptr; 578 } 579 return defaults->GetAttr(aName, kNameSpaceID_None); 580 } 581 582 bool nsAccUtils::GetARIAElementsAttr(dom::Element* aElement, nsAtom* aName, 583 nsTArray<dom::Element*>& aElements) { 584 if (aElement->HasAttr(aName)) { 585 aElement->GetExplicitlySetAttrElements(aName, aElements); 586 return true; 587 } 588 589 if (auto* element = nsGenericHTMLElement::FromNode(aElement)) { 590 if (auto* internals = element->GetInternals()) { 591 return internals->GetAttrElements(aName, aElements); 592 } 593 } 594 595 return false; 596 } 597 598 bool nsAccUtils::ARIAAttrValueIs(dom::Element* aElement, const nsAtom* aName, 599 const nsAString& aValue, 600 nsCaseTreatment aCaseSensitive) { 601 if (aElement->HasAttr(kNameSpaceID_None, aName)) { 602 return aElement->AttrValueIs(kNameSpaceID_None, aName, aValue, 603 aCaseSensitive); 604 } 605 const auto* defaults = GetARIADefaults(aElement); 606 if (!defaults) { 607 return false; 608 } 609 return defaults->AttrValueIs(kNameSpaceID_None, aName, aValue, 610 aCaseSensitive); 611 } 612 613 bool nsAccUtils::ARIAAttrValueIs(dom::Element* aElement, const nsAtom* aName, 614 const nsAtom* aValue, 615 nsCaseTreatment aCaseSensitive) { 616 if (aElement->HasAttr(kNameSpaceID_None, aName)) { 617 return aElement->AttrValueIs(kNameSpaceID_None, aName, aValue, 618 aCaseSensitive); 619 } 620 const auto* defaults = GetARIADefaults(aElement); 621 if (!defaults) { 622 return false; 623 } 624 return defaults->AttrValueIs(kNameSpaceID_None, aName, aValue, 625 aCaseSensitive); 626 } 627 628 int32_t nsAccUtils::FindARIAAttrValueIn(dom::Element* aElement, 629 const nsAtom* aName, 630 AttrArray::AttrValuesArray* aValues, 631 nsCaseTreatment aCaseSensitive) { 632 int32_t index = aElement->FindAttrValueIn(kNameSpaceID_None, aName, aValues, 633 aCaseSensitive); 634 if (index == AttrArray::ATTR_MISSING) { 635 const auto* defaults = GetARIADefaults(aElement); 636 if (!defaults) { 637 return index; 638 } 639 index = defaults->FindAttrValueIn(kNameSpaceID_None, aName, aValues, 640 aCaseSensitive); 641 } 642 return index; 643 } 644 645 bool nsAccUtils::IsEditableARIACombobox(const LocalAccessible* aAccessible) { 646 const nsRoleMapEntry* roleMap = aAccessible->ARIARoleMap(); 647 if (!roleMap || roleMap->role != roles::EDITCOMBOBOX) { 648 return false; 649 } 650 651 return aAccessible->IsTextField() || 652 aAccessible->Elm()->State().HasState(dom::ElementState::READWRITE); 653 } 654 655 bool nsAccUtils::IsValidDetailsTargetForAnchor(const Accessible* aTarget, 656 const Accessible* aAnchor) { 657 if (aAnchor->IsAncestorOf(aTarget)) { 658 // If the anchor is a parent of the target, the target is not valid 659 // relation. 660 return false; 661 } 662 663 Accessible* nextSibling = aAnchor->NextSibling(); 664 if (nextSibling && nextSibling->IsTextLeaf()) { 665 nsAutoString text; 666 nextSibling->Name(text); 667 if (nsCoreUtils::IsWhitespaceString(text)) { 668 nextSibling = nextSibling->NextSibling(); 669 } 670 } 671 672 if (nextSibling == aTarget) { 673 // If the target is the next sibling of the anchor (ignoring whitespace 674 // text nodes), the target is not a valid relation. 675 return false; 676 } 677 678 return true; 679 }