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 "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
;
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
,
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()) {
39 AppendFromAccessibleChildren(aAccessible
, &name
);
40 name
.CompressWhitespace();
41 if (!nsCoreUtils::IsWhitespaceString(name
)) aName
= name
;
45 sInitiatorAcc
= nullptr;
50 nsresult
nsTextEquivUtils::GetTextEquivFromIDRefs(const Accessible
* aAccessible
,
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
+= ' ';
64 AppendTextEquivFromContent(aAccessible
, refContent
, &aTextEquiv
);
65 NS_ENSURE_SUCCESS(rv
, rv
);
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;
88 Accessible
* accessible
= sInitiatorAcc
->Document()->GetAccessible(aContent
);
90 rv
= AppendFromAccessible(accessible
, aString
);
91 goThroughDOMSubtree
= false;
95 if (goThroughDOMSubtree
) rv
= AppendFromDOMNode(aContent
, aString
);
97 sInitiatorAcc
= nullptr;
101 nsresult
nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent
* aContent
,
102 nsAString
* aString
) {
103 if (aContent
->IsText()) {
104 bool isHTMLBlock
= false;
106 nsIContent
* parentContent
= aContent
->GetFlattenedTreeParent();
108 nsIFrame
* frame
= parentContent
->GetPrimaryFrame();
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
) {
117 if (!aString
->IsEmpty()) {
118 aString
->Append(char16_t(' '));
124 if (aContent
->TextLength() > 0) {
125 nsIFrame
* frame
= aContent
->GetPrimaryFrame();
127 nsIFrame::RenderedText text
= frame
->GetRenderedText(
128 0, UINT32_MAX
, nsIFrame::TextOffsetType::OffsetsInContentText
,
129 nsIFrame::TrailingWhitespace::DontTrim
);
130 aString
->Append(text
.mString
);
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(' '));
143 if (aContent
->IsHTMLElement() &&
144 aContent
->NodeInfo()->Equals(nsGkAtoms::br
)) {
145 aString
->AppendLiteral("\r\n");
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
);
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()) {
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).
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
);
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.
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
;
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
);
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
,
282 aContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::label
,
286 if (textEquivalent
.IsEmpty()) {
287 aContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::tooltiptext
,
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(' '));
313 uint32_t nsTextEquivUtils::GetRoleRule(role aRole
) {
314 #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, \
315 androidClass, nameRule) \
316 case roles::geckoRole: \
322 MOZ_CRASH("Unknown role.");