1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
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
;
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
) {
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()) {
40 AppendFromAccessibleChildren(aAccessible
, &name
);
41 name
.CompressWhitespace();
42 if (!nsCoreUtils::IsWhitespaceString(name
)) aName
= name
;
46 sInitiatorAcc
= nullptr;
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");
66 AppendTextEquivFromContent(aAccessible
, refContent
, &aTextEquiv
);
67 NS_ENSURE_SUCCESS(rv
, rv
);
73 nsresult
nsTextEquivUtils::AppendTextEquivFromContent(
74 const LocalAccessible
* aInitiatorAcc
, nsIContent
* aContent
,
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
);
86 // The given content is invisible or otherwise inaccessible, so use the DOM
88 rv
= AppendFromDOMNode(aContent
, aString
);
91 sInitiatorAcc
= nullptr;
95 nsresult
nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent
* aContent
,
97 if (aContent
->IsText()) {
98 if (aContent
->TextLength() > 0) {
99 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
101 nsIFrame::RenderedText text
= frame
->GetRenderedText(
102 0, UINT32_MAX
, nsIFrame::TextOffsetType::OffsetsInContentText
,
103 nsIFrame::TrailingWhitespace::DontTrim
);
104 aString
->Append(text
.mString
);
106 // If aContent is an object that is display: none, we have no a frame.
107 aContent
->GetAsText()->AppendTextTo(*aString
);
114 if (aContent
->IsHTMLElement() &&
115 aContent
->NodeInfo()->Equals(nsGkAtoms::br
)) {
116 aString
->AppendLiteral("\r\n");
120 return NS_OK_NO_NAME_CLAUSE_HANDLED
;
123 nsresult
nsTextEquivUtils::AppendFromDOMChildren(nsIContent
* aContent
,
124 nsAString
* aString
) {
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
);
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
);
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();
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
) {
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.
184 nsresult rv
= AppendFromValue(aAccessible
, &val
);
185 NS_ENSURE_SUCCESS(rv
, rv
);
187 AppendString(aString
, val
);
191 // If the name is from tooltip then append it to result string in the end
192 // (see h. step of name computation guide).
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
);
214 aString
->Append(char16_t(' '));
219 if (!isEmptyTextEquiv
&& isHTMLBlock
) {
220 aString
->Append(char16_t(' '));
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.
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);
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.
289 if (aContent
->IsXULElement()) {
290 nsAutoString textEquivalent
;
291 if (aContent
->NodeInfo()->Equals(nsGkAtoms::label
, kNameSpaceID_XUL
)) {
292 aContent
->AsElement()->GetAttr(nsGkAtoms::value
, textEquivalent
);
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(' '));
325 uint32_t nsTextEquivUtils::GetRoleRule(role aRole
) {
326 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
327 msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
329 case roles::geckoRole: \
335 MOZ_CRASH("Unknown role.");
341 bool nsTextEquivUtils::ShouldIncludeInSubtreeCalculation(
342 Accessible
* aAccessible
) {
343 uint32_t nameRule
= GetRoleRule(aAccessible
->Role());
344 if (nameRule
== eNameFromSubtreeRule
) {
347 if (!(nameRule
& eNameFromSubtreeIfReqRule
)) {
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.
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.
371 bool nsTextEquivUtils::IsWhitespaceLeaf(Accessible
* aAccessible
) {
372 if (!aAccessible
|| !aAccessible
->IsTextLeaf()) {
377 aAccessible
->Name(name
);
378 return nsCoreUtils::IsWhitespaceString(name
);