accessible/src/base/nsTextEquivUtils.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:expandtab:shiftwidth=2:tabstop=2:
michael@0 3 */
michael@0 4 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 5 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 7
michael@0 8 #include "nsTextEquivUtils.h"
michael@0 9
michael@0 10 #include "Accessible-inl.h"
michael@0 11 #include "AccIterator.h"
michael@0 12 #include "nsCoreUtils.h"
michael@0 13 #include "nsIDOMXULLabeledControlEl.h"
michael@0 14
michael@0 15 using namespace mozilla::a11y;
michael@0 16
michael@0 17 /**
michael@0 18 * The accessible for which we are computing a text equivalent. It is useful
michael@0 19 * for bailing out during recursive text computation, or for special cases
michael@0 20 * like step f. of the ARIA implementation guide.
michael@0 21 */
michael@0 22 static Accessible* sInitiatorAcc = nullptr;
michael@0 23
michael@0 24 ////////////////////////////////////////////////////////////////////////////////
michael@0 25 // nsTextEquivUtils. Public.
michael@0 26
michael@0 27 nsresult
michael@0 28 nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible,
michael@0 29 nsAString& aName)
michael@0 30 {
michael@0 31 aName.Truncate();
michael@0 32
michael@0 33 if (sInitiatorAcc)
michael@0 34 return NS_OK;
michael@0 35
michael@0 36 sInitiatorAcc = aAccessible;
michael@0 37 if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
michael@0 38 //XXX: is it necessary to care the accessible is not a document?
michael@0 39 if (aAccessible->IsContent()) {
michael@0 40 nsAutoString name;
michael@0 41 AppendFromAccessibleChildren(aAccessible, &name);
michael@0 42 name.CompressWhitespace();
michael@0 43 if (!nsCoreUtils::IsWhitespaceString(name))
michael@0 44 aName = name;
michael@0 45 }
michael@0 46 }
michael@0 47
michael@0 48 sInitiatorAcc = nullptr;
michael@0 49
michael@0 50 return NS_OK;
michael@0 51 }
michael@0 52
michael@0 53 nsresult
michael@0 54 nsTextEquivUtils::GetTextEquivFromIDRefs(Accessible* aAccessible,
michael@0 55 nsIAtom *aIDRefsAttr,
michael@0 56 nsAString& aTextEquiv)
michael@0 57 {
michael@0 58 aTextEquiv.Truncate();
michael@0 59
michael@0 60 nsIContent* content = aAccessible->GetContent();
michael@0 61 if (!content)
michael@0 62 return NS_OK;
michael@0 63
michael@0 64 nsIContent* refContent = nullptr;
michael@0 65 IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
michael@0 66 while ((refContent = iter.NextElem())) {
michael@0 67 if (!aTextEquiv.IsEmpty())
michael@0 68 aTextEquiv += ' ';
michael@0 69
michael@0 70 nsresult rv = AppendTextEquivFromContent(aAccessible, refContent,
michael@0 71 &aTextEquiv);
michael@0 72 NS_ENSURE_SUCCESS(rv, rv);
michael@0 73 }
michael@0 74
michael@0 75 return NS_OK;
michael@0 76 }
michael@0 77
michael@0 78 nsresult
michael@0 79 nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc,
michael@0 80 nsIContent *aContent,
michael@0 81 nsAString *aString)
michael@0 82 {
michael@0 83 // Prevent recursion which can cause infinite loops.
michael@0 84 if (sInitiatorAcc)
michael@0 85 return NS_OK;
michael@0 86
michael@0 87 sInitiatorAcc = aInitiatorAcc;
michael@0 88
michael@0 89 // If the given content is not visible or isn't accessible then go down
michael@0 90 // through the DOM subtree otherwise go down through accessible subtree and
michael@0 91 // calculate the flat string.
michael@0 92 nsIFrame *frame = aContent->GetPrimaryFrame();
michael@0 93 bool isVisible = frame && frame->StyleVisibility()->IsVisible();
michael@0 94
michael@0 95 nsresult rv = NS_ERROR_FAILURE;
michael@0 96 bool goThroughDOMSubtree = true;
michael@0 97
michael@0 98 if (isVisible) {
michael@0 99 Accessible* accessible =
michael@0 100 sInitiatorAcc->Document()->GetAccessible(aContent);
michael@0 101 if (accessible) {
michael@0 102 rv = AppendFromAccessible(accessible, aString);
michael@0 103 goThroughDOMSubtree = false;
michael@0 104 }
michael@0 105 }
michael@0 106
michael@0 107 if (goThroughDOMSubtree)
michael@0 108 rv = AppendFromDOMNode(aContent, aString);
michael@0 109
michael@0 110 sInitiatorAcc = nullptr;
michael@0 111 return rv;
michael@0 112 }
michael@0 113
michael@0 114 nsresult
michael@0 115 nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent,
michael@0 116 nsAString *aString)
michael@0 117 {
michael@0 118 if (aContent->IsNodeOfType(nsINode::eTEXT)) {
michael@0 119 bool isHTMLBlock = false;
michael@0 120
michael@0 121 nsIContent *parentContent = aContent->GetFlattenedTreeParent();
michael@0 122 if (parentContent) {
michael@0 123 nsIFrame *frame = parentContent->GetPrimaryFrame();
michael@0 124 if (frame) {
michael@0 125 // If this text is inside a block level frame (as opposed to span
michael@0 126 // level), we need to add spaces around that block's text, so we don't
michael@0 127 // get words jammed together in final name.
michael@0 128 const nsStyleDisplay* display = frame->StyleDisplay();
michael@0 129 if (display->IsBlockOutsideStyle() ||
michael@0 130 display->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL) {
michael@0 131 isHTMLBlock = true;
michael@0 132 if (!aString->IsEmpty()) {
michael@0 133 aString->Append(char16_t(' '));
michael@0 134 }
michael@0 135 }
michael@0 136 }
michael@0 137 }
michael@0 138
michael@0 139 if (aContent->TextLength() > 0) {
michael@0 140 nsIFrame *frame = aContent->GetPrimaryFrame();
michael@0 141 if (frame) {
michael@0 142 nsresult rv = frame->GetRenderedText(aString);
michael@0 143 NS_ENSURE_SUCCESS(rv, rv);
michael@0 144 } else {
michael@0 145 // If aContent is an object that is display: none, we have no a frame.
michael@0 146 aContent->AppendTextTo(*aString);
michael@0 147 }
michael@0 148 if (isHTMLBlock && !aString->IsEmpty()) {
michael@0 149 aString->Append(char16_t(' '));
michael@0 150 }
michael@0 151 }
michael@0 152
michael@0 153 return NS_OK;
michael@0 154 }
michael@0 155
michael@0 156 if (aContent->IsHTML() &&
michael@0 157 aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
michael@0 158 aString->AppendLiteral("\r\n");
michael@0 159 return NS_OK;
michael@0 160 }
michael@0 161
michael@0 162 return NS_OK_NO_NAME_CLAUSE_HANDLED;
michael@0 163 }
michael@0 164
michael@0 165 ////////////////////////////////////////////////////////////////////////////////
michael@0 166 // nsTextEquivUtils. Private.
michael@0 167
michael@0 168 nsresult
michael@0 169 nsTextEquivUtils::AppendFromAccessibleChildren(Accessible* aAccessible,
michael@0 170 nsAString *aString)
michael@0 171 {
michael@0 172 nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
michael@0 173
michael@0 174 uint32_t childCount = aAccessible->ChildCount();
michael@0 175 for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
michael@0 176 Accessible* child = aAccessible->GetChildAt(childIdx);
michael@0 177 rv = AppendFromAccessible(child, aString);
michael@0 178 NS_ENSURE_SUCCESS(rv, rv);
michael@0 179 }
michael@0 180
michael@0 181 return rv;
michael@0 182 }
michael@0 183
michael@0 184 nsresult
michael@0 185 nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
michael@0 186 nsAString *aString)
michael@0 187 {
michael@0 188 //XXX: is it necessary to care the accessible is not a document?
michael@0 189 if (aAccessible->IsContent()) {
michael@0 190 nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(),
michael@0 191 aString);
michael@0 192 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
michael@0 193 return rv;
michael@0 194 }
michael@0 195
michael@0 196 bool isEmptyTextEquiv = true;
michael@0 197
michael@0 198 // If the name is from tooltip then append it to result string in the end
michael@0 199 // (see h. step of name computation guide).
michael@0 200 nsAutoString text;
michael@0 201 if (aAccessible->Name(text) != eNameFromTooltip)
michael@0 202 isEmptyTextEquiv = !AppendString(aString, text);
michael@0 203
michael@0 204 // Implementation of f. step.
michael@0 205 nsresult rv = AppendFromValue(aAccessible, aString);
michael@0 206 NS_ENSURE_SUCCESS(rv, rv);
michael@0 207
michael@0 208 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
michael@0 209 isEmptyTextEquiv = false;
michael@0 210
michael@0 211 // Implementation of g) step of text equivalent computation guide. Go down
michael@0 212 // into subtree if accessible allows "text equivalent from subtree rule" or
michael@0 213 // it's not root and not control.
michael@0 214 if (isEmptyTextEquiv) {
michael@0 215 uint32_t nameRule = GetRoleRule(aAccessible->Role());
michael@0 216 if (nameRule & eNameFromSubtreeIfReqRule) {
michael@0 217 rv = AppendFromAccessibleChildren(aAccessible, aString);
michael@0 218 NS_ENSURE_SUCCESS(rv, rv);
michael@0 219
michael@0 220 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
michael@0 221 isEmptyTextEquiv = false;
michael@0 222 }
michael@0 223 }
michael@0 224
michael@0 225 // Implementation of h. step
michael@0 226 if (isEmptyTextEquiv && !text.IsEmpty()) {
michael@0 227 AppendString(aString, text);
michael@0 228 return NS_OK;
michael@0 229 }
michael@0 230
michael@0 231 return rv;
michael@0 232 }
michael@0 233
michael@0 234 nsresult
michael@0 235 nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
michael@0 236 nsAString *aString)
michael@0 237 {
michael@0 238 if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule)
michael@0 239 return NS_OK_NO_NAME_CLAUSE_HANDLED;
michael@0 240
michael@0 241 // Implementation of step f. of text equivalent computation. If the given
michael@0 242 // accessible is not root accessible (the accessible the text equivalent is
michael@0 243 // computed for in the end) then append accessible value. Otherwise append
michael@0 244 // value if and only if the given accessible is in the middle of its parent.
michael@0 245
michael@0 246 nsAutoString text;
michael@0 247 if (aAccessible != sInitiatorAcc) {
michael@0 248 aAccessible->Value(text);
michael@0 249
michael@0 250 return AppendString(aString, text) ?
michael@0 251 NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
michael@0 252 }
michael@0 253
michael@0 254 //XXX: is it necessary to care the accessible is not a document?
michael@0 255 if (aAccessible->IsDoc())
michael@0 256 return NS_ERROR_UNEXPECTED;
michael@0 257
michael@0 258 nsIContent *content = aAccessible->GetContent();
michael@0 259
michael@0 260 for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
michael@0 261 childContent = childContent->GetPreviousSibling()) {
michael@0 262 // check for preceding text...
michael@0 263 if (!childContent->TextIsOnlyWhitespace()) {
michael@0 264 for (nsIContent* siblingContent = content->GetNextSibling(); siblingContent;
michael@0 265 siblingContent = siblingContent->GetNextSibling()) {
michael@0 266 // .. and subsequent text
michael@0 267 if (!siblingContent->TextIsOnlyWhitespace()) {
michael@0 268 aAccessible->Value(text);
michael@0 269
michael@0 270 return AppendString(aString, text) ?
michael@0 271 NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
michael@0 272 break;
michael@0 273 }
michael@0 274 }
michael@0 275 break;
michael@0 276 }
michael@0 277 }
michael@0 278
michael@0 279 return NS_OK_NO_NAME_CLAUSE_HANDLED;
michael@0 280 }
michael@0 281
michael@0 282 nsresult
michael@0 283 nsTextEquivUtils::AppendFromDOMChildren(nsIContent *aContent,
michael@0 284 nsAString *aString)
michael@0 285 {
michael@0 286 for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
michael@0 287 childContent = childContent->GetNextSibling()) {
michael@0 288 nsresult rv = AppendFromDOMNode(childContent, aString);
michael@0 289 NS_ENSURE_SUCCESS(rv, rv);
michael@0 290 }
michael@0 291
michael@0 292 return NS_OK;
michael@0 293 }
michael@0 294
michael@0 295 nsresult
michael@0 296 nsTextEquivUtils::AppendFromDOMNode(nsIContent *aContent, nsAString *aString)
michael@0 297 {
michael@0 298 nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
michael@0 299 NS_ENSURE_SUCCESS(rv, rv);
michael@0 300
michael@0 301 if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
michael@0 302 return NS_OK;
michael@0 303
michael@0 304 if (aContent->IsXUL()) {
michael@0 305 nsAutoString textEquivalent;
michael@0 306 nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
michael@0 307 do_QueryInterface(aContent);
michael@0 308
michael@0 309 if (labeledEl) {
michael@0 310 labeledEl->GetLabel(textEquivalent);
michael@0 311 } else {
michael@0 312 if (aContent->NodeInfo()->Equals(nsGkAtoms::label,
michael@0 313 kNameSpaceID_XUL))
michael@0 314 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
michael@0 315 textEquivalent);
michael@0 316
michael@0 317 if (textEquivalent.IsEmpty())
michael@0 318 aContent->GetAttr(kNameSpaceID_None,
michael@0 319 nsGkAtoms::tooltiptext, textEquivalent);
michael@0 320 }
michael@0 321
michael@0 322 AppendString(aString, textEquivalent);
michael@0 323 }
michael@0 324
michael@0 325 return AppendFromDOMChildren(aContent, aString);
michael@0 326 }
michael@0 327
michael@0 328 bool
michael@0 329 nsTextEquivUtils::AppendString(nsAString *aString,
michael@0 330 const nsAString& aTextEquivalent)
michael@0 331 {
michael@0 332 if (aTextEquivalent.IsEmpty())
michael@0 333 return false;
michael@0 334
michael@0 335 // Insert spaces to insure that words from controls aren't jammed together.
michael@0 336 if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last()))
michael@0 337 aString->Append(char16_t(' '));
michael@0 338
michael@0 339 aString->Append(aTextEquivalent);
michael@0 340
michael@0 341 if (!nsCoreUtils::IsWhitespace(aString->Last()))
michael@0 342 aString->Append(char16_t(' '));
michael@0 343
michael@0 344 return true;
michael@0 345 }
michael@0 346
michael@0 347 uint32_t
michael@0 348 nsTextEquivUtils::GetRoleRule(role aRole)
michael@0 349 {
michael@0 350 #define ROLE(geckoRole, stringRole, atkRole, \
michael@0 351 macRole, msaaRole, ia2Role, nameRule) \
michael@0 352 case roles::geckoRole: \
michael@0 353 return nameRule;
michael@0 354
michael@0 355 switch (aRole) {
michael@0 356 #include "RoleMap.h"
michael@0 357 default:
michael@0 358 MOZ_CRASH("Unknown role.");
michael@0 359 }
michael@0 360
michael@0 361 #undef ROLE
michael@0 362 }

mercurial