1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/base/nsTextEquivUtils.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,362 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:expandtab:shiftwidth=2:tabstop=2: 1.6 + */ 1.7 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.8 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.10 + 1.11 +#include "nsTextEquivUtils.h" 1.12 + 1.13 +#include "Accessible-inl.h" 1.14 +#include "AccIterator.h" 1.15 +#include "nsCoreUtils.h" 1.16 +#include "nsIDOMXULLabeledControlEl.h" 1.17 + 1.18 +using namespace mozilla::a11y; 1.19 + 1.20 +/** 1.21 + * The accessible for which we are computing a text equivalent. It is useful 1.22 + * for bailing out during recursive text computation, or for special cases 1.23 + * like step f. of the ARIA implementation guide. 1.24 + */ 1.25 +static Accessible* sInitiatorAcc = nullptr; 1.26 + 1.27 +//////////////////////////////////////////////////////////////////////////////// 1.28 +// nsTextEquivUtils. Public. 1.29 + 1.30 +nsresult 1.31 +nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible, 1.32 + nsAString& aName) 1.33 +{ 1.34 + aName.Truncate(); 1.35 + 1.36 + if (sInitiatorAcc) 1.37 + return NS_OK; 1.38 + 1.39 + sInitiatorAcc = aAccessible; 1.40 + if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) { 1.41 + //XXX: is it necessary to care the accessible is not a document? 1.42 + if (aAccessible->IsContent()) { 1.43 + nsAutoString name; 1.44 + AppendFromAccessibleChildren(aAccessible, &name); 1.45 + name.CompressWhitespace(); 1.46 + if (!nsCoreUtils::IsWhitespaceString(name)) 1.47 + aName = name; 1.48 + } 1.49 + } 1.50 + 1.51 + sInitiatorAcc = nullptr; 1.52 + 1.53 + return NS_OK; 1.54 +} 1.55 + 1.56 +nsresult 1.57 +nsTextEquivUtils::GetTextEquivFromIDRefs(Accessible* aAccessible, 1.58 + nsIAtom *aIDRefsAttr, 1.59 + nsAString& aTextEquiv) 1.60 +{ 1.61 + aTextEquiv.Truncate(); 1.62 + 1.63 + nsIContent* content = aAccessible->GetContent(); 1.64 + if (!content) 1.65 + return NS_OK; 1.66 + 1.67 + nsIContent* refContent = nullptr; 1.68 + IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr); 1.69 + while ((refContent = iter.NextElem())) { 1.70 + if (!aTextEquiv.IsEmpty()) 1.71 + aTextEquiv += ' '; 1.72 + 1.73 + nsresult rv = AppendTextEquivFromContent(aAccessible, refContent, 1.74 + &aTextEquiv); 1.75 + NS_ENSURE_SUCCESS(rv, rv); 1.76 + } 1.77 + 1.78 + return NS_OK; 1.79 +} 1.80 + 1.81 +nsresult 1.82 +nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc, 1.83 + nsIContent *aContent, 1.84 + nsAString *aString) 1.85 +{ 1.86 + // Prevent recursion which can cause infinite loops. 1.87 + if (sInitiatorAcc) 1.88 + return NS_OK; 1.89 + 1.90 + sInitiatorAcc = aInitiatorAcc; 1.91 + 1.92 + // If the given content is not visible or isn't accessible then go down 1.93 + // through the DOM subtree otherwise go down through accessible subtree and 1.94 + // calculate the flat string. 1.95 + nsIFrame *frame = aContent->GetPrimaryFrame(); 1.96 + bool isVisible = frame && frame->StyleVisibility()->IsVisible(); 1.97 + 1.98 + nsresult rv = NS_ERROR_FAILURE; 1.99 + bool goThroughDOMSubtree = true; 1.100 + 1.101 + if (isVisible) { 1.102 + Accessible* accessible = 1.103 + sInitiatorAcc->Document()->GetAccessible(aContent); 1.104 + if (accessible) { 1.105 + rv = AppendFromAccessible(accessible, aString); 1.106 + goThroughDOMSubtree = false; 1.107 + } 1.108 + } 1.109 + 1.110 + if (goThroughDOMSubtree) 1.111 + rv = AppendFromDOMNode(aContent, aString); 1.112 + 1.113 + sInitiatorAcc = nullptr; 1.114 + return rv; 1.115 +} 1.116 + 1.117 +nsresult 1.118 +nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent, 1.119 + nsAString *aString) 1.120 +{ 1.121 + if (aContent->IsNodeOfType(nsINode::eTEXT)) { 1.122 + bool isHTMLBlock = false; 1.123 + 1.124 + nsIContent *parentContent = aContent->GetFlattenedTreeParent(); 1.125 + if (parentContent) { 1.126 + nsIFrame *frame = parentContent->GetPrimaryFrame(); 1.127 + if (frame) { 1.128 + // If this text is inside a block level frame (as opposed to span 1.129 + // level), we need to add spaces around that block's text, so we don't 1.130 + // get words jammed together in final name. 1.131 + const nsStyleDisplay* display = frame->StyleDisplay(); 1.132 + if (display->IsBlockOutsideStyle() || 1.133 + display->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL) { 1.134 + isHTMLBlock = true; 1.135 + if (!aString->IsEmpty()) { 1.136 + aString->Append(char16_t(' ')); 1.137 + } 1.138 + } 1.139 + } 1.140 + } 1.141 + 1.142 + if (aContent->TextLength() > 0) { 1.143 + nsIFrame *frame = aContent->GetPrimaryFrame(); 1.144 + if (frame) { 1.145 + nsresult rv = frame->GetRenderedText(aString); 1.146 + NS_ENSURE_SUCCESS(rv, rv); 1.147 + } else { 1.148 + // If aContent is an object that is display: none, we have no a frame. 1.149 + aContent->AppendTextTo(*aString); 1.150 + } 1.151 + if (isHTMLBlock && !aString->IsEmpty()) { 1.152 + aString->Append(char16_t(' ')); 1.153 + } 1.154 + } 1.155 + 1.156 + return NS_OK; 1.157 + } 1.158 + 1.159 + if (aContent->IsHTML() && 1.160 + aContent->NodeInfo()->Equals(nsGkAtoms::br)) { 1.161 + aString->AppendLiteral("\r\n"); 1.162 + return NS_OK; 1.163 + } 1.164 + 1.165 + return NS_OK_NO_NAME_CLAUSE_HANDLED; 1.166 +} 1.167 + 1.168 +//////////////////////////////////////////////////////////////////////////////// 1.169 +// nsTextEquivUtils. Private. 1.170 + 1.171 +nsresult 1.172 +nsTextEquivUtils::AppendFromAccessibleChildren(Accessible* aAccessible, 1.173 + nsAString *aString) 1.174 +{ 1.175 + nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED; 1.176 + 1.177 + uint32_t childCount = aAccessible->ChildCount(); 1.178 + for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { 1.179 + Accessible* child = aAccessible->GetChildAt(childIdx); 1.180 + rv = AppendFromAccessible(child, aString); 1.181 + NS_ENSURE_SUCCESS(rv, rv); 1.182 + } 1.183 + 1.184 + return rv; 1.185 +} 1.186 + 1.187 +nsresult 1.188 +nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible, 1.189 + nsAString *aString) 1.190 +{ 1.191 + //XXX: is it necessary to care the accessible is not a document? 1.192 + if (aAccessible->IsContent()) { 1.193 + nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(), 1.194 + aString); 1.195 + if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) 1.196 + return rv; 1.197 + } 1.198 + 1.199 + bool isEmptyTextEquiv = true; 1.200 + 1.201 + // If the name is from tooltip then append it to result string in the end 1.202 + // (see h. step of name computation guide). 1.203 + nsAutoString text; 1.204 + if (aAccessible->Name(text) != eNameFromTooltip) 1.205 + isEmptyTextEquiv = !AppendString(aString, text); 1.206 + 1.207 + // Implementation of f. step. 1.208 + nsresult rv = AppendFromValue(aAccessible, aString); 1.209 + NS_ENSURE_SUCCESS(rv, rv); 1.210 + 1.211 + if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) 1.212 + isEmptyTextEquiv = false; 1.213 + 1.214 + // Implementation of g) step of text equivalent computation guide. Go down 1.215 + // into subtree if accessible allows "text equivalent from subtree rule" or 1.216 + // it's not root and not control. 1.217 + if (isEmptyTextEquiv) { 1.218 + uint32_t nameRule = GetRoleRule(aAccessible->Role()); 1.219 + if (nameRule & eNameFromSubtreeIfReqRule) { 1.220 + rv = AppendFromAccessibleChildren(aAccessible, aString); 1.221 + NS_ENSURE_SUCCESS(rv, rv); 1.222 + 1.223 + if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) 1.224 + isEmptyTextEquiv = false; 1.225 + } 1.226 + } 1.227 + 1.228 + // Implementation of h. step 1.229 + if (isEmptyTextEquiv && !text.IsEmpty()) { 1.230 + AppendString(aString, text); 1.231 + return NS_OK; 1.232 + } 1.233 + 1.234 + return rv; 1.235 +} 1.236 + 1.237 +nsresult 1.238 +nsTextEquivUtils::AppendFromValue(Accessible* aAccessible, 1.239 + nsAString *aString) 1.240 +{ 1.241 + if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule) 1.242 + return NS_OK_NO_NAME_CLAUSE_HANDLED; 1.243 + 1.244 + // Implementation of step f. of text equivalent computation. If the given 1.245 + // accessible is not root accessible (the accessible the text equivalent is 1.246 + // computed for in the end) then append accessible value. Otherwise append 1.247 + // value if and only if the given accessible is in the middle of its parent. 1.248 + 1.249 + nsAutoString text; 1.250 + if (aAccessible != sInitiatorAcc) { 1.251 + aAccessible->Value(text); 1.252 + 1.253 + return AppendString(aString, text) ? 1.254 + NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED; 1.255 + } 1.256 + 1.257 + //XXX: is it necessary to care the accessible is not a document? 1.258 + if (aAccessible->IsDoc()) 1.259 + return NS_ERROR_UNEXPECTED; 1.260 + 1.261 + nsIContent *content = aAccessible->GetContent(); 1.262 + 1.263 + for (nsIContent* childContent = content->GetPreviousSibling(); childContent; 1.264 + childContent = childContent->GetPreviousSibling()) { 1.265 + // check for preceding text... 1.266 + if (!childContent->TextIsOnlyWhitespace()) { 1.267 + for (nsIContent* siblingContent = content->GetNextSibling(); siblingContent; 1.268 + siblingContent = siblingContent->GetNextSibling()) { 1.269 + // .. and subsequent text 1.270 + if (!siblingContent->TextIsOnlyWhitespace()) { 1.271 + aAccessible->Value(text); 1.272 + 1.273 + return AppendString(aString, text) ? 1.274 + NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED; 1.275 + break; 1.276 + } 1.277 + } 1.278 + break; 1.279 + } 1.280 + } 1.281 + 1.282 + return NS_OK_NO_NAME_CLAUSE_HANDLED; 1.283 +} 1.284 + 1.285 +nsresult 1.286 +nsTextEquivUtils::AppendFromDOMChildren(nsIContent *aContent, 1.287 + nsAString *aString) 1.288 +{ 1.289 + for (nsIContent* childContent = aContent->GetFirstChild(); childContent; 1.290 + childContent = childContent->GetNextSibling()) { 1.291 + nsresult rv = AppendFromDOMNode(childContent, aString); 1.292 + NS_ENSURE_SUCCESS(rv, rv); 1.293 + } 1.294 + 1.295 + return NS_OK; 1.296 +} 1.297 + 1.298 +nsresult 1.299 +nsTextEquivUtils::AppendFromDOMNode(nsIContent *aContent, nsAString *aString) 1.300 +{ 1.301 + nsresult rv = AppendTextEquivFromTextContent(aContent, aString); 1.302 + NS_ENSURE_SUCCESS(rv, rv); 1.303 + 1.304 + if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) 1.305 + return NS_OK; 1.306 + 1.307 + if (aContent->IsXUL()) { 1.308 + nsAutoString textEquivalent; 1.309 + nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl = 1.310 + do_QueryInterface(aContent); 1.311 + 1.312 + if (labeledEl) { 1.313 + labeledEl->GetLabel(textEquivalent); 1.314 + } else { 1.315 + if (aContent->NodeInfo()->Equals(nsGkAtoms::label, 1.316 + kNameSpaceID_XUL)) 1.317 + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, 1.318 + textEquivalent); 1.319 + 1.320 + if (textEquivalent.IsEmpty()) 1.321 + aContent->GetAttr(kNameSpaceID_None, 1.322 + nsGkAtoms::tooltiptext, textEquivalent); 1.323 + } 1.324 + 1.325 + AppendString(aString, textEquivalent); 1.326 + } 1.327 + 1.328 + return AppendFromDOMChildren(aContent, aString); 1.329 +} 1.330 + 1.331 +bool 1.332 +nsTextEquivUtils::AppendString(nsAString *aString, 1.333 + const nsAString& aTextEquivalent) 1.334 +{ 1.335 + if (aTextEquivalent.IsEmpty()) 1.336 + return false; 1.337 + 1.338 + // Insert spaces to insure that words from controls aren't jammed together. 1.339 + if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last())) 1.340 + aString->Append(char16_t(' ')); 1.341 + 1.342 + aString->Append(aTextEquivalent); 1.343 + 1.344 + if (!nsCoreUtils::IsWhitespace(aString->Last())) 1.345 + aString->Append(char16_t(' ')); 1.346 + 1.347 + return true; 1.348 +} 1.349 + 1.350 +uint32_t 1.351 +nsTextEquivUtils::GetRoleRule(role aRole) 1.352 +{ 1.353 +#define ROLE(geckoRole, stringRole, atkRole, \ 1.354 + macRole, msaaRole, ia2Role, nameRule) \ 1.355 + case roles::geckoRole: \ 1.356 + return nameRule; 1.357 + 1.358 + switch (aRole) { 1.359 +#include "RoleMap.h" 1.360 + default: 1.361 + MOZ_CRASH("Unknown role."); 1.362 + } 1.363 + 1.364 +#undef ROLE 1.365 +}