michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:expandtab:shiftwidth=2:tabstop=2: michael@0: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsTextEquivUtils.h" michael@0: michael@0: #include "Accessible-inl.h" michael@0: #include "AccIterator.h" michael@0: #include "nsCoreUtils.h" michael@0: #include "nsIDOMXULLabeledControlEl.h" michael@0: michael@0: using namespace mozilla::a11y; michael@0: michael@0: /** michael@0: * The accessible for which we are computing a text equivalent. It is useful michael@0: * for bailing out during recursive text computation, or for special cases michael@0: * like step f. of the ARIA implementation guide. michael@0: */ michael@0: static Accessible* sInitiatorAcc = nullptr; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsTextEquivUtils. Public. michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible, michael@0: nsAString& aName) michael@0: { michael@0: aName.Truncate(); michael@0: michael@0: if (sInitiatorAcc) michael@0: return NS_OK; michael@0: michael@0: sInitiatorAcc = aAccessible; michael@0: if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) { michael@0: //XXX: is it necessary to care the accessible is not a document? michael@0: if (aAccessible->IsContent()) { michael@0: nsAutoString name; michael@0: AppendFromAccessibleChildren(aAccessible, &name); michael@0: name.CompressWhitespace(); michael@0: if (!nsCoreUtils::IsWhitespaceString(name)) michael@0: aName = name; michael@0: } michael@0: } michael@0: michael@0: sInitiatorAcc = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::GetTextEquivFromIDRefs(Accessible* aAccessible, michael@0: nsIAtom *aIDRefsAttr, michael@0: nsAString& aTextEquiv) michael@0: { michael@0: aTextEquiv.Truncate(); michael@0: michael@0: nsIContent* content = aAccessible->GetContent(); michael@0: if (!content) michael@0: return NS_OK; michael@0: michael@0: nsIContent* refContent = nullptr; michael@0: IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr); michael@0: while ((refContent = iter.NextElem())) { michael@0: if (!aTextEquiv.IsEmpty()) michael@0: aTextEquiv += ' '; michael@0: michael@0: nsresult rv = AppendTextEquivFromContent(aAccessible, refContent, michael@0: &aTextEquiv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc, michael@0: nsIContent *aContent, michael@0: nsAString *aString) michael@0: { michael@0: // Prevent recursion which can cause infinite loops. michael@0: if (sInitiatorAcc) michael@0: return NS_OK; michael@0: michael@0: sInitiatorAcc = aInitiatorAcc; michael@0: michael@0: // If the given content is not visible or isn't accessible then go down michael@0: // through the DOM subtree otherwise go down through accessible subtree and michael@0: // calculate the flat string. michael@0: nsIFrame *frame = aContent->GetPrimaryFrame(); michael@0: bool isVisible = frame && frame->StyleVisibility()->IsVisible(); michael@0: michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: bool goThroughDOMSubtree = true; michael@0: michael@0: if (isVisible) { michael@0: Accessible* accessible = michael@0: sInitiatorAcc->Document()->GetAccessible(aContent); michael@0: if (accessible) { michael@0: rv = AppendFromAccessible(accessible, aString); michael@0: goThroughDOMSubtree = false; michael@0: } michael@0: } michael@0: michael@0: if (goThroughDOMSubtree) michael@0: rv = AppendFromDOMNode(aContent, aString); michael@0: michael@0: sInitiatorAcc = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent, michael@0: nsAString *aString) michael@0: { michael@0: if (aContent->IsNodeOfType(nsINode::eTEXT)) { michael@0: bool isHTMLBlock = false; michael@0: michael@0: nsIContent *parentContent = aContent->GetFlattenedTreeParent(); michael@0: if (parentContent) { michael@0: nsIFrame *frame = parentContent->GetPrimaryFrame(); michael@0: if (frame) { michael@0: // If this text is inside a block level frame (as opposed to span michael@0: // level), we need to add spaces around that block's text, so we don't michael@0: // get words jammed together in final name. michael@0: const nsStyleDisplay* display = frame->StyleDisplay(); michael@0: if (display->IsBlockOutsideStyle() || michael@0: display->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL) { michael@0: isHTMLBlock = true; michael@0: if (!aString->IsEmpty()) { michael@0: aString->Append(char16_t(' ')); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (aContent->TextLength() > 0) { michael@0: nsIFrame *frame = aContent->GetPrimaryFrame(); michael@0: if (frame) { michael@0: nsresult rv = frame->GetRenderedText(aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: // If aContent is an object that is display: none, we have no a frame. michael@0: aContent->AppendTextTo(*aString); michael@0: } michael@0: if (isHTMLBlock && !aString->IsEmpty()) { michael@0: aString->Append(char16_t(' ')); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aContent->IsHTML() && michael@0: aContent->NodeInfo()->Equals(nsGkAtoms::br)) { michael@0: aString->AppendLiteral("\r\n"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_OK_NO_NAME_CLAUSE_HANDLED; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // nsTextEquivUtils. Private. michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::AppendFromAccessibleChildren(Accessible* aAccessible, michael@0: nsAString *aString) michael@0: { michael@0: nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED; michael@0: michael@0: uint32_t childCount = aAccessible->ChildCount(); michael@0: for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { michael@0: Accessible* child = aAccessible->GetChildAt(childIdx); michael@0: rv = AppendFromAccessible(child, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible, michael@0: nsAString *aString) michael@0: { michael@0: //XXX: is it necessary to care the accessible is not a document? michael@0: if (aAccessible->IsContent()) { michael@0: nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(), michael@0: aString); michael@0: if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) michael@0: return rv; michael@0: } michael@0: michael@0: bool isEmptyTextEquiv = true; michael@0: michael@0: // If the name is from tooltip then append it to result string in the end michael@0: // (see h. step of name computation guide). michael@0: nsAutoString text; michael@0: if (aAccessible->Name(text) != eNameFromTooltip) michael@0: isEmptyTextEquiv = !AppendString(aString, text); michael@0: michael@0: // Implementation of f. step. michael@0: nsresult rv = AppendFromValue(aAccessible, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) michael@0: isEmptyTextEquiv = false; michael@0: michael@0: // Implementation of g) step of text equivalent computation guide. Go down michael@0: // into subtree if accessible allows "text equivalent from subtree rule" or michael@0: // it's not root and not control. michael@0: if (isEmptyTextEquiv) { michael@0: uint32_t nameRule = GetRoleRule(aAccessible->Role()); michael@0: if (nameRule & eNameFromSubtreeIfReqRule) { michael@0: rv = AppendFromAccessibleChildren(aAccessible, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) michael@0: isEmptyTextEquiv = false; michael@0: } michael@0: } michael@0: michael@0: // Implementation of h. step michael@0: if (isEmptyTextEquiv && !text.IsEmpty()) { michael@0: AppendString(aString, text); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::AppendFromValue(Accessible* aAccessible, michael@0: nsAString *aString) michael@0: { michael@0: if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule) michael@0: return NS_OK_NO_NAME_CLAUSE_HANDLED; michael@0: michael@0: // Implementation of step f. of text equivalent computation. If the given michael@0: // accessible is not root accessible (the accessible the text equivalent is michael@0: // computed for in the end) then append accessible value. Otherwise append michael@0: // value if and only if the given accessible is in the middle of its parent. michael@0: michael@0: nsAutoString text; michael@0: if (aAccessible != sInitiatorAcc) { michael@0: aAccessible->Value(text); michael@0: michael@0: return AppendString(aString, text) ? michael@0: NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED; michael@0: } michael@0: michael@0: //XXX: is it necessary to care the accessible is not a document? michael@0: if (aAccessible->IsDoc()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: nsIContent *content = aAccessible->GetContent(); michael@0: michael@0: for (nsIContent* childContent = content->GetPreviousSibling(); childContent; michael@0: childContent = childContent->GetPreviousSibling()) { michael@0: // check for preceding text... michael@0: if (!childContent->TextIsOnlyWhitespace()) { michael@0: for (nsIContent* siblingContent = content->GetNextSibling(); siblingContent; michael@0: siblingContent = siblingContent->GetNextSibling()) { michael@0: // .. and subsequent text michael@0: if (!siblingContent->TextIsOnlyWhitespace()) { michael@0: aAccessible->Value(text); michael@0: michael@0: return AppendString(aString, text) ? michael@0: NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED; michael@0: break; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return NS_OK_NO_NAME_CLAUSE_HANDLED; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::AppendFromDOMChildren(nsIContent *aContent, michael@0: nsAString *aString) michael@0: { michael@0: for (nsIContent* childContent = aContent->GetFirstChild(); childContent; michael@0: childContent = childContent->GetNextSibling()) { michael@0: nsresult rv = AppendFromDOMNode(childContent, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextEquivUtils::AppendFromDOMNode(nsIContent *aContent, nsAString *aString) michael@0: { michael@0: nsresult rv = AppendTextEquivFromTextContent(aContent, aString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) michael@0: return NS_OK; michael@0: michael@0: if (aContent->IsXUL()) { michael@0: nsAutoString textEquivalent; michael@0: nsCOMPtr labeledEl = michael@0: do_QueryInterface(aContent); michael@0: michael@0: if (labeledEl) { michael@0: labeledEl->GetLabel(textEquivalent); michael@0: } else { michael@0: if (aContent->NodeInfo()->Equals(nsGkAtoms::label, michael@0: kNameSpaceID_XUL)) michael@0: aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, michael@0: textEquivalent); michael@0: michael@0: if (textEquivalent.IsEmpty()) michael@0: aContent->GetAttr(kNameSpaceID_None, michael@0: nsGkAtoms::tooltiptext, textEquivalent); michael@0: } michael@0: michael@0: AppendString(aString, textEquivalent); michael@0: } michael@0: michael@0: return AppendFromDOMChildren(aContent, aString); michael@0: } michael@0: michael@0: bool michael@0: nsTextEquivUtils::AppendString(nsAString *aString, michael@0: const nsAString& aTextEquivalent) michael@0: { michael@0: if (aTextEquivalent.IsEmpty()) michael@0: return false; michael@0: michael@0: // Insert spaces to insure that words from controls aren't jammed together. michael@0: if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last())) michael@0: aString->Append(char16_t(' ')); michael@0: michael@0: aString->Append(aTextEquivalent); michael@0: michael@0: if (!nsCoreUtils::IsWhitespace(aString->Last())) michael@0: aString->Append(char16_t(' ')); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: uint32_t michael@0: nsTextEquivUtils::GetRoleRule(role aRole) michael@0: { michael@0: #define ROLE(geckoRole, stringRole, atkRole, \ michael@0: macRole, msaaRole, ia2Role, nameRule) \ michael@0: case roles::geckoRole: \ michael@0: return nameRule; michael@0: michael@0: switch (aRole) { michael@0: #include "RoleMap.h" michael@0: default: michael@0: MOZ_CRASH("Unknown role."); michael@0: } michael@0: michael@0: #undef ROLE michael@0: }