Bug 1877642 - Disable browser_fullscreen-tab-close-race.js on apple_silicon !debug...
[gecko.git] / accessible / base / nsTextEquivUtils.cpp
blob769559a2c9a9f7d511503ff843e2d93bd3ae9413
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsTextEquivUtils.h"
10 #include "LocalAccessible-inl.h"
11 #include "AccIterator.h"
12 #include "nsCoreUtils.h"
13 #include "mozilla/dom/ChildIterator.h"
14 #include "mozilla/dom/Text.h"
16 using namespace mozilla;
17 using namespace mozilla::a11y;
19 /**
20 * The accessible for which we are computing a text equivalent. It is useful
21 * for bailing out during recursive text computation, or for special cases
22 * like step f. of the ARIA implementation guide.
24 static const Accessible* sInitiatorAcc = nullptr;
26 ////////////////////////////////////////////////////////////////////////////////
27 // nsTextEquivUtils. Public.
29 nsresult nsTextEquivUtils::GetNameFromSubtree(
30 const LocalAccessible* aAccessible, nsAString& aName) {
31 aName.Truncate();
33 if (sInitiatorAcc) return NS_OK;
35 sInitiatorAcc = aAccessible;
36 if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
37 // XXX: is it necessary to care the accessible is not a document?
38 if (aAccessible->IsContent()) {
39 nsAutoString name;
40 AppendFromAccessibleChildren(aAccessible, &name);
41 name.CompressWhitespace();
42 if (!nsCoreUtils::IsWhitespaceString(name)) aName = name;
46 sInitiatorAcc = nullptr;
48 return NS_OK;
51 nsresult nsTextEquivUtils::GetTextEquivFromIDRefs(
52 const LocalAccessible* aAccessible, nsAtom* aIDRefsAttr,
53 nsAString& aTextEquiv) {
54 aTextEquiv.Truncate();
56 nsIContent* content = aAccessible->GetContent();
57 if (!content) return NS_OK;
59 nsIContent* refContent = nullptr;
60 IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
61 while ((refContent = iter.NextElem())) {
62 if (!aTextEquiv.IsEmpty()) aTextEquiv += ' ';
64 if (refContent->IsHTMLElement(nsGkAtoms::slot)) printf("jtd idref slot\n");
65 nsresult rv =
66 AppendTextEquivFromContent(aAccessible, refContent, &aTextEquiv);
67 NS_ENSURE_SUCCESS(rv, rv);
70 return NS_OK;
73 nsresult nsTextEquivUtils::AppendTextEquivFromContent(
74 const LocalAccessible* aInitiatorAcc, nsIContent* aContent,
75 nsAString* aString) {
76 // Prevent recursion which can cause infinite loops.
77 if (sInitiatorAcc) return NS_OK;
79 sInitiatorAcc = aInitiatorAcc;
81 nsresult rv = NS_ERROR_FAILURE;
82 if (LocalAccessible* accessible =
83 aInitiatorAcc->Document()->GetAccessible(aContent)) {
84 rv = AppendFromAccessible(accessible, aString);
85 } else {
86 // The given content is invisible or otherwise inaccessible, so use the DOM
87 // subtree.
88 rv = AppendFromDOMNode(aContent, aString);
91 sInitiatorAcc = nullptr;
92 return rv;
95 nsresult nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent* aContent,
96 nsAString* aString) {
97 if (aContent->IsText()) {
98 if (aContent->TextLength() > 0) {
99 nsIFrame* frame = aContent->GetPrimaryFrame();
100 if (frame) {
101 nsIFrame::RenderedText text = frame->GetRenderedText(
102 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
103 nsIFrame::TrailingWhitespace::DontTrim);
104 aString->Append(text.mString);
105 } else {
106 // If aContent is an object that is display: none, we have no a frame.
107 aContent->GetAsText()->AppendTextTo(*aString);
111 return NS_OK;
114 if (aContent->IsHTMLElement() &&
115 aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
116 aString->AppendLiteral("\r\n");
117 return NS_OK;
120 return NS_OK_NO_NAME_CLAUSE_HANDLED;
123 nsresult nsTextEquivUtils::AppendFromDOMChildren(nsIContent* aContent,
124 nsAString* aString) {
125 auto iter =
126 dom::AllChildrenIterator(aContent, nsIContent::eAllChildren, true);
127 while (nsIContent* childContent = iter.GetNextChild()) {
128 nsresult rv = AppendFromDOMNode(childContent, aString);
129 NS_ENSURE_SUCCESS(rv, rv);
132 return NS_OK;
135 ////////////////////////////////////////////////////////////////////////////////
136 // nsTextEquivUtils. Private.
138 nsresult nsTextEquivUtils::AppendFromAccessibleChildren(
139 const Accessible* aAccessible, nsAString* aString) {
140 nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
142 uint32_t childCount = aAccessible->ChildCount();
143 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
144 Accessible* child = aAccessible->ChildAt(childIdx);
145 rv = AppendFromAccessible(child, aString);
146 NS_ENSURE_SUCCESS(rv, rv);
149 return rv;
152 nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
153 nsAString* aString) {
154 // XXX: is it necessary to care the accessible is not a document?
155 bool isHTMLBlock = false;
156 if (aAccessible->IsLocal() && aAccessible->AsLocal()->IsContent()) {
157 nsIContent* content = aAccessible->AsLocal()->GetContent();
158 nsresult rv = AppendTextEquivFromTextContent(content, aString);
159 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return rv;
160 if (!content->IsText()) {
161 nsIFrame* frame = content->GetPrimaryFrame();
162 if (frame) {
163 // If this is a block level frame (as opposed to span level), we need to
164 // add spaces around that block's text, so we don't get words jammed
165 // together in final name.
166 const nsStyleDisplay* display = frame->StyleDisplay();
167 if (display->IsBlockOutsideStyle() ||
168 display->mDisplay == StyleDisplay::InlineBlock ||
169 display->mDisplay == StyleDisplay::TableCell) {
170 isHTMLBlock = true;
171 if (!aString->IsEmpty()) {
172 aString->Append(char16_t(' '));
179 bool isEmptyTextEquiv = true;
181 // Attempt to find the value. If it's non-empty, append and return it. See the
182 // "embedded control" section of the name spec.
183 nsAutoString val;
184 nsresult rv = AppendFromValue(aAccessible, &val);
185 NS_ENSURE_SUCCESS(rv, rv);
186 if (rv == NS_OK) {
187 AppendString(aString, val);
188 return NS_OK;
191 // If the name is from tooltip then append it to result string in the end
192 // (see h. step of name computation guide).
193 nsAutoString text;
194 if (aAccessible->Name(text) != eNameFromTooltip) {
195 isEmptyTextEquiv = !AppendString(aString, text);
198 // Implementation of g) step of text equivalent computation guide. Go down
199 // into subtree if accessible allows "text equivalent from subtree rule" or
200 // it's not root and not control.
201 if (isEmptyTextEquiv) {
202 if (ShouldIncludeInSubtreeCalculation(aAccessible)) {
203 rv = AppendFromAccessibleChildren(aAccessible, aString);
204 NS_ENSURE_SUCCESS(rv, rv);
206 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
210 // Implementation of h. step
211 if (isEmptyTextEquiv && !text.IsEmpty()) {
212 AppendString(aString, text);
213 if (isHTMLBlock) {
214 aString->Append(char16_t(' '));
216 return NS_OK;
219 if (!isEmptyTextEquiv && isHTMLBlock) {
220 aString->Append(char16_t(' '));
222 return rv;
225 nsresult nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
226 nsAString* aString) {
227 if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule) {
228 return NS_OK_NO_NAME_CLAUSE_HANDLED;
231 // Implementation of step f. of text equivalent computation. If the given
232 // accessible is not root accessible (the accessible the text equivalent is
233 // computed for in the end) then append accessible value. Otherwise append
234 // value if and only if the given accessible is in the middle of its parent.
236 nsAutoString text;
237 if (aAccessible != sInitiatorAcc) {
238 // For listboxes in non-initiator computations, we need to get the selected
239 // item and append its text alternative.
240 if (aAccessible->IsListControl()) {
241 Accessible* selected = aAccessible->GetSelectedItem(0);
242 if (selected) {
243 nsresult rv = AppendFromAccessible(selected, &text);
244 NS_ENSURE_SUCCESS(rv, rv);
245 return AppendString(aString, text) ? NS_OK
246 : NS_OK_NO_NAME_CLAUSE_HANDLED;
248 return NS_ERROR_FAILURE;
251 aAccessible->Value(text);
253 return AppendString(aString, text) ? NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
256 // XXX: is it necessary to care the accessible is not a document?
257 if (aAccessible->IsDoc()) return NS_ERROR_UNEXPECTED;
259 for (Accessible* next = aAccessible->NextSibling(); next;
260 next = next->NextSibling()) {
261 if (!IsWhitespaceLeaf(next)) {
262 for (Accessible* prev = aAccessible->PrevSibling(); prev;
263 prev = prev->PrevSibling()) {
264 if (!IsWhitespaceLeaf(prev)) {
265 aAccessible->Value(text);
267 return AppendString(aString, text) ? NS_OK
268 : NS_OK_NO_NAME_CLAUSE_HANDLED;
274 return NS_OK_NO_NAME_CLAUSE_HANDLED;
277 nsresult nsTextEquivUtils::AppendFromDOMNode(nsIContent* aContent,
278 nsAString* aString) {
279 nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
280 NS_ENSURE_SUCCESS(rv, rv);
282 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return NS_OK;
284 if (aContent->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::style)) {
285 // The text within these elements is never meant for users.
286 return NS_OK;
289 if (aContent->IsXULElement()) {
290 nsAutoString textEquivalent;
291 if (aContent->NodeInfo()->Equals(nsGkAtoms::label, kNameSpaceID_XUL)) {
292 aContent->AsElement()->GetAttr(nsGkAtoms::value, textEquivalent);
293 } else {
294 aContent->AsElement()->GetAttr(nsGkAtoms::label, textEquivalent);
297 if (textEquivalent.IsEmpty()) {
298 aContent->AsElement()->GetAttr(nsGkAtoms::tooltiptext, textEquivalent);
301 AppendString(aString, textEquivalent);
304 return AppendFromDOMChildren(aContent, aString);
307 bool nsTextEquivUtils::AppendString(nsAString* aString,
308 const nsAString& aTextEquivalent) {
309 if (aTextEquivalent.IsEmpty()) return false;
311 // Insert spaces to insure that words from controls aren't jammed together.
312 if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last())) {
313 aString->Append(char16_t(' '));
316 aString->Append(aTextEquivalent);
318 if (!nsCoreUtils::IsWhitespace(aString->Last())) {
319 aString->Append(char16_t(' '));
322 return true;
325 uint32_t nsTextEquivUtils::GetRoleRule(role aRole) {
326 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
327 msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
328 nameRule) \
329 case roles::geckoRole: \
330 return nameRule;
332 switch (aRole) {
333 #include "RoleMap.h"
334 default:
335 MOZ_CRASH("Unknown role.");
338 #undef ROLE
341 bool nsTextEquivUtils::ShouldIncludeInSubtreeCalculation(
342 Accessible* aAccessible) {
343 uint32_t nameRule = GetRoleRule(aAccessible->Role());
344 if (nameRule == eNameFromSubtreeRule) {
345 return true;
347 if (!(nameRule & eNameFromSubtreeIfReqRule)) {
348 return false;
351 if (aAccessible == sInitiatorAcc) {
352 // We're calculating the text equivalent for this accessible, but this
353 // accessible should only be included when calculating the text equivalent
354 // for something else.
355 return false;
358 // sInitiatorAcc can be null when, for example, LocalAccessible::Value calls
359 // GetTextEquivFromSubtree.
360 role initiatorRole = sInitiatorAcc ? sInitiatorAcc->Role() : roles::NOTHING;
361 if (initiatorRole == roles::OUTLINEITEM &&
362 aAccessible->Role() == roles::GROUPING) {
363 // Child treeitems are contained in a group. We don't want to include those
364 // in the parent treeitem's text equivalent.
365 return false;
368 return true;
371 bool nsTextEquivUtils::IsWhitespaceLeaf(Accessible* aAccessible) {
372 if (!aAccessible || !aAccessible->IsTextLeaf()) {
373 return false;
376 nsAutoString name;
377 aAccessible->Name(name);
378 return nsCoreUtils::IsWhitespaceString(name);