Bug 1568151 - Replace `target.getInspector()` by `target.getFront("inspector")`....
[gecko.git] / accessible / base / nsTextEquivUtils.cpp
blob647ab66500a1cde5d5d0bf993376765db08af43b
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 "Accessible-inl.h"
11 #include "AccIterator.h"
12 #include "nsCoreUtils.h"
13 #include "mozilla/dom/Text.h"
15 using namespace mozilla;
16 using namespace mozilla::a11y;
18 /**
19 * The accessible for which we are computing a text equivalent. It is useful
20 * for bailing out during recursive text computation, or for special cases
21 * like step f. of the ARIA implementation guide.
23 static const Accessible* sInitiatorAcc = nullptr;
25 ////////////////////////////////////////////////////////////////////////////////
26 // nsTextEquivUtils. Public.
28 nsresult nsTextEquivUtils::GetNameFromSubtree(const Accessible* aAccessible,
29 nsAString& aName) {
30 aName.Truncate();
32 if (sInitiatorAcc) return NS_OK;
34 sInitiatorAcc = aAccessible;
35 if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
36 // XXX: is it necessary to care the accessible is not a document?
37 if (aAccessible->IsContent()) {
38 nsAutoString name;
39 AppendFromAccessibleChildren(aAccessible, &name);
40 name.CompressWhitespace();
41 if (!nsCoreUtils::IsWhitespaceString(name)) aName = name;
45 sInitiatorAcc = nullptr;
47 return NS_OK;
50 nsresult nsTextEquivUtils::GetTextEquivFromIDRefs(const Accessible* aAccessible,
51 nsAtom* aIDRefsAttr,
52 nsAString& aTextEquiv) {
53 aTextEquiv.Truncate();
55 nsIContent* content = aAccessible->GetContent();
56 if (!content) return NS_OK;
58 nsIContent* refContent = nullptr;
59 IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
60 while ((refContent = iter.NextElem())) {
61 if (!aTextEquiv.IsEmpty()) aTextEquiv += ' ';
63 nsresult rv =
64 AppendTextEquivFromContent(aAccessible, refContent, &aTextEquiv);
65 NS_ENSURE_SUCCESS(rv, rv);
68 return NS_OK;
71 nsresult nsTextEquivUtils::AppendTextEquivFromContent(
72 const Accessible* aInitiatorAcc, nsIContent* aContent, nsAString* aString) {
73 // Prevent recursion which can cause infinite loops.
74 if (sInitiatorAcc) return NS_OK;
76 sInitiatorAcc = aInitiatorAcc;
78 // If the given content is not visible or isn't accessible then go down
79 // through the DOM subtree otherwise go down through accessible subtree and
80 // calculate the flat string.
81 nsIFrame* frame = aContent->GetPrimaryFrame();
82 bool isVisible = frame && frame->StyleVisibility()->IsVisible();
84 nsresult rv = NS_ERROR_FAILURE;
85 bool goThroughDOMSubtree = true;
87 if (isVisible) {
88 Accessible* accessible = sInitiatorAcc->Document()->GetAccessible(aContent);
89 if (accessible) {
90 rv = AppendFromAccessible(accessible, aString);
91 goThroughDOMSubtree = false;
95 if (goThroughDOMSubtree) rv = AppendFromDOMNode(aContent, aString);
97 sInitiatorAcc = nullptr;
98 return rv;
101 nsresult nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent* aContent,
102 nsAString* aString) {
103 if (aContent->IsText()) {
104 bool isHTMLBlock = false;
106 nsIContent* parentContent = aContent->GetFlattenedTreeParent();
107 if (parentContent) {
108 nsIFrame* frame = parentContent->GetPrimaryFrame();
109 if (frame) {
110 // If this text is inside a block level frame (as opposed to span
111 // level), we need to add spaces around that block's text, so we don't
112 // get words jammed together in final name.
113 const nsStyleDisplay* display = frame->StyleDisplay();
114 if (display->IsBlockOutsideStyle() ||
115 display->mDisplay == StyleDisplay::TableCell) {
116 isHTMLBlock = true;
117 if (!aString->IsEmpty()) {
118 aString->Append(char16_t(' '));
124 if (aContent->TextLength() > 0) {
125 nsIFrame* frame = aContent->GetPrimaryFrame();
126 if (frame) {
127 nsIFrame::RenderedText text = frame->GetRenderedText(
128 0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
129 nsIFrame::TrailingWhitespace::DontTrim);
130 aString->Append(text.mString);
131 } else {
132 // If aContent is an object that is display: none, we have no a frame.
133 aContent->GetAsText()->AppendTextTo(*aString);
135 if (isHTMLBlock && !aString->IsEmpty()) {
136 aString->Append(char16_t(' '));
140 return NS_OK;
143 if (aContent->IsHTMLElement() &&
144 aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
145 aString->AppendLiteral("\r\n");
146 return NS_OK;
149 return NS_OK_NO_NAME_CLAUSE_HANDLED;
152 ////////////////////////////////////////////////////////////////////////////////
153 // nsTextEquivUtils. Private.
155 nsresult nsTextEquivUtils::AppendFromAccessibleChildren(
156 const Accessible* aAccessible, nsAString* aString) {
157 nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
159 uint32_t childCount = aAccessible->ChildCount();
160 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
161 Accessible* child = aAccessible->GetChildAt(childIdx);
162 rv = AppendFromAccessible(child, aString);
163 NS_ENSURE_SUCCESS(rv, rv);
166 return rv;
169 nsresult nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
170 nsAString* aString) {
171 // XXX: is it necessary to care the accessible is not a document?
172 if (aAccessible->IsContent()) {
173 nsresult rv =
174 AppendTextEquivFromTextContent(aAccessible->GetContent(), aString);
175 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return rv;
178 bool isEmptyTextEquiv = true;
180 // If the name is from tooltip then append it to result string in the end
181 // (see h. step of name computation guide).
182 nsAutoString text;
183 if (aAccessible->Name(text) != eNameFromTooltip)
184 isEmptyTextEquiv = !AppendString(aString, text);
186 // Implementation of f. step.
187 nsresult rv = AppendFromValue(aAccessible, aString);
188 NS_ENSURE_SUCCESS(rv, rv);
190 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
192 // Implementation of g) step of text equivalent computation guide. Go down
193 // into subtree if accessible allows "text equivalent from subtree rule" or
194 // it's not root and not control.
195 if (isEmptyTextEquiv) {
196 uint32_t nameRule = GetRoleRule(aAccessible->Role());
197 if (nameRule & eNameFromSubtreeIfReqRule) {
198 rv = AppendFromAccessibleChildren(aAccessible, aString);
199 NS_ENSURE_SUCCESS(rv, rv);
201 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) isEmptyTextEquiv = false;
205 // Implementation of h. step
206 if (isEmptyTextEquiv && !text.IsEmpty()) {
207 AppendString(aString, text);
208 return NS_OK;
211 return rv;
214 nsresult nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
215 nsAString* aString) {
216 if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule)
217 return NS_OK_NO_NAME_CLAUSE_HANDLED;
219 // Implementation of step f. of text equivalent computation. If the given
220 // accessible is not root accessible (the accessible the text equivalent is
221 // computed for in the end) then append accessible value. Otherwise append
222 // value if and only if the given accessible is in the middle of its parent.
224 nsAutoString text;
225 if (aAccessible != sInitiatorAcc) {
226 aAccessible->Value(text);
228 return AppendString(aString, text) ? NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
231 // XXX: is it necessary to care the accessible is not a document?
232 if (aAccessible->IsDoc()) return NS_ERROR_UNEXPECTED;
234 nsIContent* content = aAccessible->GetContent();
236 for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
237 childContent = childContent->GetPreviousSibling()) {
238 // check for preceding text...
239 if (!childContent->TextIsOnlyWhitespace()) {
240 for (nsIContent* siblingContent = content->GetNextSibling();
241 siblingContent; siblingContent = siblingContent->GetNextSibling()) {
242 // .. and subsequent text
243 if (!siblingContent->TextIsOnlyWhitespace()) {
244 aAccessible->Value(text);
246 return AppendString(aString, text) ? NS_OK
247 : NS_OK_NO_NAME_CLAUSE_HANDLED;
248 break;
251 break;
255 return NS_OK_NO_NAME_CLAUSE_HANDLED;
258 nsresult nsTextEquivUtils::AppendFromDOMChildren(nsIContent* aContent,
259 nsAString* aString) {
260 for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
261 childContent = childContent->GetNextSibling()) {
262 nsresult rv = AppendFromDOMNode(childContent, aString);
263 NS_ENSURE_SUCCESS(rv, rv);
266 return NS_OK;
269 nsresult nsTextEquivUtils::AppendFromDOMNode(nsIContent* aContent,
270 nsAString* aString) {
271 nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
272 NS_ENSURE_SUCCESS(rv, rv);
274 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return NS_OK;
276 if (aContent->IsXULElement()) {
277 nsAutoString textEquivalent;
278 if (aContent->NodeInfo()->Equals(nsGkAtoms::label, kNameSpaceID_XUL)) {
279 aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
280 textEquivalent);
281 } else {
282 aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label,
283 textEquivalent);
286 if (textEquivalent.IsEmpty()) {
287 aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext,
288 textEquivalent);
291 AppendString(aString, textEquivalent);
294 return AppendFromDOMChildren(aContent, aString);
297 bool nsTextEquivUtils::AppendString(nsAString* aString,
298 const nsAString& aTextEquivalent) {
299 if (aTextEquivalent.IsEmpty()) return false;
301 // Insert spaces to insure that words from controls aren't jammed together.
302 if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last()))
303 aString->Append(char16_t(' '));
305 aString->Append(aTextEquivalent);
307 if (!nsCoreUtils::IsWhitespace(aString->Last()))
308 aString->Append(char16_t(' '));
310 return true;
313 uint32_t nsTextEquivUtils::GetRoleRule(role aRole) {
314 #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, \
315 androidClass, nameRule) \
316 case roles::geckoRole: \
317 return nameRule;
319 switch (aRole) {
320 #include "RoleMap.h"
321 default:
322 MOZ_CRASH("Unknown role.");
325 #undef ROLE