Bug 1874684 - Part 28: Return DateDuration from DifferenceISODateTime. r=mgaudet
[gecko.git] / accessible / base / nsAccUtils.cpp
blob61211068ca43843ba255ccb40e1aaf4176645862
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/. */
6 #include "nsAccUtils.h"
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"
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"
29 using namespace mozilla;
30 using namespace mozilla::a11y;
32 void nsAccUtils::SetAccGroupAttrs(AccAttributes* aAttributes, int32_t aLevel,
33 int32_t aSetSize, int32_t aPosInSet) {
34 nsAutoString value;
36 if (aLevel) {
37 aAttributes->SetAttribute(nsGkAtoms::level, aLevel);
40 if (aSetSize && aPosInSet) {
41 aAttributes->SetAttribute(nsGkAtoms::posinset, aPosInSet);
42 aAttributes->SetAttribute(nsGkAtoms::setsize, aSetSize);
46 int32_t nsAccUtils::GetLevelForXULContainerItem(nsIContent* aContent) {
47 nsCOMPtr<nsIDOMXULContainerItemElement> item =
48 aContent->AsElement()->AsXULContainerItem();
49 if (!item) return 0;
51 nsCOMPtr<dom::Element> containerElement;
52 item->GetParentContainer(getter_AddRefs(containerElement));
53 nsCOMPtr<nsIDOMXULContainerElement> container =
54 containerElement ? containerElement->AsXULContainer() : nullptr;
55 if (!container) return 0;
57 // Get level of the item.
58 int32_t level = -1;
59 while (container) {
60 level++;
62 container->GetParentContainer(getter_AddRefs(containerElement));
63 container = containerElement ? containerElement->AsXULContainer() : nullptr;
66 return level;
69 void nsAccUtils::SetLiveContainerAttributes(AccAttributes* aAttributes,
70 Accessible* aStartAcc) {
71 nsAutoString live, relevant, busy;
72 nsStaticAtom* role = nullptr;
73 Maybe<bool> atomic;
74 for (Accessible* acc = aStartAcc; acc; acc = acc->Parent()) {
75 // We only want the nearest value for each attribute. If we already got a
76 // value, don't bother fetching it from further ancestors.
77 const bool wasLiveEmpty = live.IsEmpty();
78 acc->LiveRegionAttributes(wasLiveEmpty ? &live : nullptr,
79 relevant.IsEmpty() ? &relevant : nullptr,
80 atomic ? nullptr : &atomic,
81 busy.IsEmpty() ? &busy : nullptr);
82 if (wasLiveEmpty) {
83 const nsRoleMapEntry* roleMap = acc->ARIARoleMap();
84 if (live.IsEmpty()) {
85 // aria-live wasn't explicitly set. See if an aria-live value is implied
86 // by an ARIA role or markup element.
87 if (roleMap) {
88 GetLiveAttrValue(roleMap->liveAttRule, live);
89 } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
90 acc, nsGkAtoms::aria_live)) {
91 value->ToString(live);
94 if (!live.IsEmpty() && roleMap &&
95 roleMap->roleAtom != nsGkAtoms::_empty) {
96 role = roleMap->roleAtom;
99 if (acc->IsDoc()) {
100 break;
103 if (!live.IsEmpty()) {
104 aAttributes->SetAttribute(nsGkAtoms::containerLive, std::move(live));
106 if (role) {
107 aAttributes->SetAttribute(nsGkAtoms::containerLiveRole, std::move(role));
109 if (!relevant.IsEmpty()) {
110 aAttributes->SetAttribute(nsGkAtoms::containerRelevant,
111 std::move(relevant));
113 if (atomic) {
114 aAttributes->SetAttribute(nsGkAtoms::containerAtomic, *atomic);
116 if (!busy.IsEmpty()) {
117 aAttributes->SetAttribute(nsGkAtoms::containerBusy, std::move(busy));
121 bool nsAccUtils::HasDefinedARIAToken(nsIContent* aContent, nsAtom* aAtom) {
122 NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!");
124 if (!aContent->IsElement()) return false;
126 dom::Element* element = aContent->AsElement();
127 if (auto* htmlElement = nsGenericHTMLElement::FromNode(element);
128 htmlElement && !element->HasAttr(aAtom)) {
129 const auto* defaults = GetARIADefaults(htmlElement);
130 if (!defaults) {
131 return false;
133 return HasDefinedARIAToken(defaults, aAtom);
135 return HasDefinedARIAToken(&element->GetAttrs(), aAtom);
138 bool nsAccUtils::HasDefinedARIAToken(const AttrArray* aAttrs, nsAtom* aAtom) {
139 return aAttrs->HasAttr(aAtom) &&
140 !aAttrs->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_empty,
141 eCaseMatters) &&
142 !aAttrs->AttrValueIs(kNameSpaceID_None, aAtom, nsGkAtoms::_undefined,
143 eCaseMatters);
146 nsStaticAtom* nsAccUtils::NormalizeARIAToken(const AttrArray* aAttrs,
147 nsAtom* aAttr) {
148 if (!HasDefinedARIAToken(aAttrs, aAttr)) {
149 return nsGkAtoms::_empty;
152 if (aAttr == nsGkAtoms::aria_current) {
153 static AttrArray::AttrValuesArray tokens[] = {
154 nsGkAtoms::page, nsGkAtoms::step, nsGkAtoms::location_,
155 nsGkAtoms::date, nsGkAtoms::time, nsGkAtoms::_true,
156 nullptr};
157 int32_t idx =
158 aAttrs->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters);
159 // If the token is present, return it, otherwise TRUE as per spec.
160 return (idx >= 0) ? tokens[idx] : nsGkAtoms::_true;
163 static AttrArray::AttrValuesArray tokens[] = {
164 nsGkAtoms::_false, nsGkAtoms::_true, nsGkAtoms::mixed, nullptr};
165 int32_t idx =
166 aAttrs->FindAttrValueIn(kNameSpaceID_None, aAttr, tokens, eCaseMatters);
167 if (idx >= 0) {
168 return tokens[idx];
171 return nullptr;
174 nsStaticAtom* nsAccUtils::NormalizeARIAToken(dom::Element* aElement,
175 nsAtom* aAttr) {
176 if (auto* htmlElement = nsGenericHTMLElement::FromNode(aElement);
177 htmlElement && !aElement->HasAttr(aAttr)) {
178 const auto* defaults = GetARIADefaults(htmlElement);
179 if (!defaults) {
180 return nsGkAtoms::_empty;
182 return NormalizeARIAToken(defaults, aAttr);
184 return NormalizeARIAToken(&aElement->GetAttrs(), aAttr);
187 Accessible* nsAccUtils::GetSelectableContainer(const Accessible* aAccessible,
188 uint64_t aState) {
189 if (!aAccessible) return nullptr;
191 if (!(aState & states::SELECTABLE)) return nullptr;
192 MOZ_ASSERT(!aAccessible->IsDoc());
194 const Accessible* parent = aAccessible;
195 while ((parent = parent->Parent()) && !parent->IsSelect()) {
196 if (parent->IsDoc() || parent->Role() == roles::PANE) {
197 return nullptr;
200 return const_cast<Accessible*>(parent);
203 LocalAccessible* nsAccUtils::GetSelectableContainer(
204 LocalAccessible* aAccessible, uint64_t aState) {
205 Accessible* selectable =
206 GetSelectableContainer(static_cast<Accessible*>(aAccessible), aState);
207 return selectable ? selectable->AsLocal() : nullptr;
210 bool nsAccUtils::IsDOMAttrTrue(const LocalAccessible* aAccessible,
211 nsAtom* aAttr) {
212 dom::Element* el = aAccessible->Elm();
213 return el && ARIAAttrValueIs(el, aAttr, nsGkAtoms::_true, eCaseMatters);
216 Accessible* nsAccUtils::TableFor(Accessible* aAcc) {
217 if (!aAcc ||
218 (!aAcc->IsTable() && !aAcc->IsTableRow() && !aAcc->IsTableCell())) {
219 return nullptr;
221 Accessible* table = aAcc;
222 for (; table && !table->IsTable(); table = table->Parent()) {
224 // We don't assert (table && table->IsTable()) here because
225 // it's possible for this tree walk to yield no table at all
226 // ex. because a table part has been moved in the tree
227 // using aria-owns.
228 return table;
231 LocalAccessible* nsAccUtils::TableFor(LocalAccessible* aRow) {
232 Accessible* table = TableFor(static_cast<Accessible*>(aRow));
233 return table ? table->AsLocal() : nullptr;
236 HyperTextAccessible* nsAccUtils::GetTextContainer(nsINode* aNode) {
237 // Get text accessible containing the result node.
238 DocAccessible* doc = GetAccService()->GetDocAccessible(aNode->OwnerDoc());
239 LocalAccessible* accessible =
240 doc ? doc->GetAccessibleOrContainer(aNode) : nullptr;
241 if (!accessible) return nullptr;
243 do {
244 HyperTextAccessible* textAcc = accessible->AsHyperText();
245 if (textAcc) return textAcc;
247 accessible = accessible->LocalParent();
248 } while (accessible);
250 return nullptr;
253 LayoutDeviceIntPoint nsAccUtils::ConvertToScreenCoords(
254 int32_t aX, int32_t aY, uint32_t aCoordinateType, Accessible* aAccessible) {
255 LayoutDeviceIntPoint coords(aX, aY);
257 switch (aCoordinateType) {
258 // Regardless of coordinate type, the coords returned
259 // are in dev pixels.
260 case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
261 break;
263 case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: {
264 coords += GetScreenCoordsForWindow(aAccessible);
265 break;
268 case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: {
269 coords += GetScreenCoordsForParent(aAccessible);
270 break;
273 default:
274 MOZ_ASSERT_UNREACHABLE("invalid coord type!");
277 return coords;
280 void nsAccUtils::ConvertScreenCoordsTo(int32_t* aX, int32_t* aY,
281 uint32_t aCoordinateType,
282 Accessible* aAccessible) {
283 switch (aCoordinateType) {
284 // Regardless of coordinate type, the values returned for
285 // aX and aY are in dev pixels.
286 case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
287 break;
289 case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE: {
290 LayoutDeviceIntPoint coords = GetScreenCoordsForWindow(aAccessible);
291 *aX -= coords.x;
292 *aY -= coords.y;
293 break;
296 case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE: {
297 LayoutDeviceIntPoint coords = GetScreenCoordsForParent(aAccessible);
298 *aX -= coords.x;
299 *aY -= coords.y;
300 break;
303 default:
304 MOZ_ASSERT_UNREACHABLE("invalid coord type!");
308 LayoutDeviceIntPoint nsAccUtils::GetScreenCoordsForParent(
309 Accessible* aAccessible) {
310 if (!aAccessible) return LayoutDeviceIntPoint();
312 if (Accessible* parent = aAccessible->Parent()) {
313 LayoutDeviceIntRect parentBounds = parent->Bounds();
314 // The rect returned from Bounds() is already in dev
315 // pixels, so we don't need to do any conversion here.
316 return parentBounds.TopLeft();
319 return LayoutDeviceIntPoint();
322 LayoutDeviceIntPoint nsAccUtils::GetScreenCoordsForWindow(
323 Accessible* aAccessible) {
324 LayoutDeviceIntPoint coords(0, 0);
325 a11y::LocalAccessible* localAcc = aAccessible->AsLocal();
326 if (!localAcc) {
327 localAcc = aAccessible->AsRemote()->OuterDocOfRemoteBrowser();
328 if (!localAcc) {
329 // This could be null if the tab is closing but the document is still
330 // being shut down.
331 return coords;
335 nsCOMPtr<nsIDocShellTreeItem> treeItem(
336 nsCoreUtils::GetDocShellFor(localAcc->GetNode()));
337 if (!treeItem) return coords;
339 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
340 treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
341 if (!treeOwner) return coords;
343 nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
344 if (baseWindow) {
345 baseWindow->GetPosition(&coords.x.value,
346 &coords.y.value); // in device pixels
349 return coords;
352 bool nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue) {
353 switch (aRule) {
354 case eOffLiveAttr:
355 aValue = u"off"_ns;
356 return true;
357 case ePoliteLiveAttr:
358 aValue = u"polite"_ns;
359 return true;
360 case eAssertiveLiveAttr:
361 aValue = u"assertive"_ns;
362 return true;
365 return false;
368 #ifdef DEBUG
370 bool nsAccUtils::IsTextInterfaceSupportCorrect(LocalAccessible* aAccessible) {
371 // Don't test for accessible docs, it makes us create accessibles too
372 // early and fire mutation events before we need to
373 if (aAccessible->IsDoc()) return true;
375 bool foundText = false;
376 uint32_t childCount = aAccessible->ChildCount();
377 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
378 LocalAccessible* child = aAccessible->LocalChildAt(childIdx);
379 if (child && child->IsText()) {
380 foundText = true;
381 break;
385 return !foundText || aAccessible->IsHyperText();
387 #endif
389 uint32_t nsAccUtils::TextLength(Accessible* aAccessible) {
390 if (!aAccessible->IsText()) {
391 return 1;
394 if (LocalAccessible* localAcc = aAccessible->AsLocal()) {
395 TextLeafAccessible* textLeaf = localAcc->AsTextLeaf();
396 if (textLeaf) {
397 return textLeaf->Text().Length();
399 } else if (aAccessible->IsText()) {
400 RemoteAccessible* remoteAcc = aAccessible->AsRemote();
401 MOZ_ASSERT(remoteAcc);
402 return remoteAcc->GetCachedTextLength();
405 // For list bullets (or anything other accessible which would compute its own
406 // text. They don't have their own frame.
407 // XXX In the future, list bullets may have frame and anon content, so
408 // we should be able to remove this at that point
409 nsAutoString text;
410 aAccessible->AppendTextTo(text); // Get all the text
411 return text.Length();
414 bool nsAccUtils::MustPrune(Accessible* aAccessible) {
415 MOZ_ASSERT(aAccessible);
416 roles::Role role = aAccessible->Role();
418 if (role == roles::SLIDER || role == roles::PROGRESSBAR) {
419 // Always prune the tree for sliders and progressbars, as it doesn't make
420 // sense for either to have descendants. Per the ARIA spec, children of
421 // these elements are presentational. They also confuse NVDA.
422 return true;
425 if (role != roles::MENUITEM && role != roles::COMBOBOX_OPTION &&
426 role != roles::OPTION && role != roles::ENTRY &&
427 role != roles::FLAT_EQUATION && role != roles::PASSWORD_TEXT &&
428 role != roles::PUSHBUTTON && role != roles::TOGGLE_BUTTON &&
429 role != roles::GRAPHIC && role != roles::SEPARATOR) {
430 // If it doesn't match any of these roles, don't prune its children.
431 return false;
434 if (aAccessible->ChildCount() != 1) {
435 // If the accessible has more than one child, don't prune it.
436 return false;
439 roles::Role childRole = aAccessible->FirstChild()->Role();
440 // If the accessible's child is a text leaf, prune the accessible.
441 return childRole == roles::TEXT_LEAF || childRole == roles::STATICTEXT;
444 bool nsAccUtils::IsARIALive(const LocalAccessible* aAccessible) {
445 // Get computed aria-live property based on the closest container with the
446 // attribute. Inner nodes override outer nodes within the same
447 // document.
448 // This should be the same as the container-live attribute, but we don't need
449 // the other container-* attributes, so we can't use the same function.
450 nsIContent* ancestor = aAccessible->GetContent();
451 if (!ancestor) {
452 return false;
454 dom::Document* doc = ancestor->GetComposedDoc();
455 if (!doc) {
456 return false;
458 dom::Element* topEl = doc->GetRootElement();
459 while (ancestor) {
460 const nsRoleMapEntry* role = nullptr;
461 if (ancestor->IsElement()) {
462 role = aria::GetRoleMap(ancestor->AsElement());
464 nsAutoString live;
465 if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
466 GetARIAAttr(ancestor->AsElement(), nsGkAtoms::aria_live, live);
467 } else if (role) {
468 GetLiveAttrValue(role->liveAttRule, live);
469 } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
470 ancestor, nsGkAtoms::aria_live)) {
471 value->ToString(live);
473 if (!live.IsEmpty() && !live.EqualsLiteral("off")) {
474 return true;
477 if (ancestor == topEl) {
478 break;
481 ancestor = ancestor->GetParent();
482 if (!ancestor) {
483 ancestor = topEl; // Use <body>/<frameset>
487 return false;
490 Accessible* nsAccUtils::DocumentFor(Accessible* aAcc) {
491 if (!aAcc) {
492 return nullptr;
494 if (LocalAccessible* localAcc = aAcc->AsLocal()) {
495 return localAcc->Document();
497 return aAcc->AsRemote()->Document();
500 Accessible* nsAccUtils::GetAccessibleByID(Accessible* aDoc, uint64_t aID) {
501 if (!aDoc) {
502 return nullptr;
504 if (LocalAccessible* localAcc = aDoc->AsLocal()) {
505 if (DocAccessible* doc = localAcc->AsDoc()) {
506 if (!aID) {
507 // GetAccessibleByUniqueID doesn't treat 0 as the document.
508 return aDoc;
510 return doc->GetAccessibleByUniqueID(
511 reinterpret_cast<void*>(static_cast<uintptr_t>(aID)));
513 } else if (DocAccessibleParent* doc = aDoc->AsRemote()->AsDoc()) {
514 return doc->GetAccessible(aID);
516 return nullptr;
519 void nsAccUtils::DocumentURL(Accessible* aDoc, nsAString& aURL) {
520 MOZ_ASSERT(aDoc && aDoc->IsDoc());
521 if (LocalAccessible* localAcc = aDoc->AsLocal()) {
522 return localAcc->AsDoc()->URL(aURL);
524 return aDoc->AsRemote()->AsDoc()->URL(aURL);
527 void nsAccUtils::DocumentMimeType(Accessible* aDoc, nsAString& aMimeType) {
528 MOZ_ASSERT(aDoc && aDoc->IsDoc());
529 if (LocalAccessible* localAcc = aDoc->AsLocal()) {
530 return localAcc->AsDoc()->MimeType(aMimeType);
532 return aDoc->AsRemote()->AsDoc()->MimeType(aMimeType);
535 // ARIA Accessibility Default Accessors
536 const AttrArray* nsAccUtils::GetARIADefaults(dom::Element* aElement) {
537 auto* element = nsGenericHTMLElement::FromNode(aElement);
538 if (!element) {
539 return nullptr;
541 auto* internals = element->GetInternals();
542 if (!internals) {
543 return nullptr;
545 return &internals->GetAttrs();
548 bool nsAccUtils::HasARIAAttr(dom::Element* aElement, const nsAtom* aName) {
549 if (aElement->HasAttr(aName)) {
550 return true;
552 const auto* defaults = GetARIADefaults(aElement);
553 if (!defaults) {
554 return false;
556 return defaults->HasAttr(aName);
559 bool nsAccUtils::GetARIAAttr(dom::Element* aElement, const nsAtom* aName,
560 nsAString& aResult) {
561 if (aElement->GetAttr(aName, aResult)) {
562 return true;
564 const auto* defaults = GetARIADefaults(aElement);
565 if (!defaults) {
566 return false;
568 return defaults->GetAttr(aName, aResult);
571 const nsAttrValue* nsAccUtils::GetARIAAttr(dom::Element* aElement,
572 const nsAtom* aName) {
573 if (const auto* val = aElement->GetParsedAttr(aName, kNameSpaceID_None)) {
574 return val;
576 const auto* defaults = GetARIADefaults(aElement);
577 if (!defaults) {
578 return nullptr;
580 return defaults->GetAttr(aName, kNameSpaceID_None);
583 bool nsAccUtils::ARIAAttrValueIs(dom::Element* aElement, const nsAtom* aName,
584 const nsAString& aValue,
585 nsCaseTreatment aCaseSensitive) {
586 if (aElement->HasAttr(kNameSpaceID_None, aName)) {
587 return aElement->AttrValueIs(kNameSpaceID_None, aName, aValue,
588 aCaseSensitive);
590 const auto* defaults = GetARIADefaults(aElement);
591 if (!defaults) {
592 return false;
594 return defaults->AttrValueIs(kNameSpaceID_None, aName, aValue,
595 aCaseSensitive);
598 bool nsAccUtils::ARIAAttrValueIs(dom::Element* aElement, const nsAtom* aName,
599 const nsAtom* aValue,
600 nsCaseTreatment aCaseSensitive) {
601 if (aElement->HasAttr(kNameSpaceID_None, aName)) {
602 return aElement->AttrValueIs(kNameSpaceID_None, aName, aValue,
603 aCaseSensitive);
605 const auto* defaults = GetARIADefaults(aElement);
606 if (!defaults) {
607 return false;
609 return defaults->AttrValueIs(kNameSpaceID_None, aName, aValue,
610 aCaseSensitive);
613 int32_t nsAccUtils::FindARIAAttrValueIn(dom::Element* aElement,
614 const nsAtom* aName,
615 AttrArray::AttrValuesArray* aValues,
616 nsCaseTreatment aCaseSensitive) {
617 int32_t index = aElement->FindAttrValueIn(kNameSpaceID_None, aName, aValues,
618 aCaseSensitive);
619 if (index == AttrArray::ATTR_MISSING) {
620 const auto* defaults = GetARIADefaults(aElement);
621 if (!defaults) {
622 return index;
624 index = defaults->FindAttrValueIn(kNameSpaceID_None, aName, aValues,
625 aCaseSensitive);
627 return index;