gecko/accessible/src/base/nsTextEquivUtils.cpp

582 lines
18 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=2:
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Alexander Surkov <surkov.alexander@gmail.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsTextEquivUtils.h"
#include "nsAccessible.h"
#include "nsIDOMXULLabeledControlEl.h"
#include "nsArrayUtils.h"
#define NS_OK_NO_NAME_CLAUSE_HANDLED \
NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x24)
////////////////////////////////////////////////////////////////////////////////
// nsTextEquivUtils. Public.
nsresult
nsTextEquivUtils::GetNameFromSubtree(nsIAccessible *aAccessible,
nsAString& aName)
{
aName.Truncate();
if (gInitiatorAcc)
return NS_OK;
gInitiatorAcc = aAccessible;
PRUint32 role = nsAccUtils::Role(aAccessible);
PRUint32 nameRule = gRoleToNameRulesMap[role];
if (nameRule == eFromSubtree) {
nsCOMPtr<nsIAccessNode> accessNode(do_QueryInterface(aAccessible));
nsCOMPtr<nsIDOMNode> DOMNode;
accessNode->GetDOMNode(getter_AddRefs(DOMNode));
nsCOMPtr<nsIContent> content(do_QueryInterface(DOMNode));
if (content) {
nsAutoString name;
AppendFromAccessibleChildren(aAccessible, &name);
name.CompressWhitespace();
if (!IsWhitespaceString(name))
aName = name;
}
}
gInitiatorAcc = nsnull;
return NS_OK;
}
nsresult
nsTextEquivUtils::GetTextEquivFromIDRefs(nsIAccessible *aAccessible,
nsIAtom *aIDRefsAttr,
nsAString& aTextEquiv)
{
aTextEquiv.Truncate();
nsCOMPtr<nsIAccessNode> accessNode(do_QueryInterface(aAccessible));
nsCOMPtr<nsIDOMNode> DOMNode;
accessNode->GetDOMNode(getter_AddRefs(DOMNode));
nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(DOMNode);
if (!content)
return NS_OK;
nsCOMPtr<nsIArray> refElms;
nsCoreUtils::GetElementsByIDRefsAttr(content, aIDRefsAttr,
getter_AddRefs(refElms));
if (!refElms)
return NS_OK;
PRUint32 count = 0;
nsresult rv = refElms->GetLength(&count);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContent> refContent;
for (PRUint32 idx = 0; idx < count; idx++) {
refContent = do_QueryElementAt(refElms, idx, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (!aTextEquiv.IsEmpty())
aTextEquiv += ' ';
rv = AppendTextEquivFromContent(aAccessible, refContent, &aTextEquiv);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsTextEquivUtils::AppendTextEquivFromContent(nsIAccessible *aInitiatorAcc,
nsIContent *aContent,
nsAString *aString)
{
// Prevent recursion which can cause infinite loops.
if (gInitiatorAcc)
return NS_OK;
gInitiatorAcc = aInitiatorAcc;
nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(aContent));
nsCOMPtr<nsIPresShell> shell = nsCoreUtils::GetPresShellFor(DOMNode);
if (!shell) {
NS_ASSERTION(PR_TRUE, "There is no presshell!");
gInitiatorAcc = nsnull;
return NS_ERROR_UNEXPECTED;
}
// If the given content is not visible or isn't accessible then go down
// through the DOM subtree otherwise go down through accessible subtree and
// calculate the flat string.
nsIFrame *frame = aContent->GetPrimaryFrame();
PRBool isVisible = frame && frame->GetStyleVisibility()->IsVisible();
nsresult rv = NS_ERROR_FAILURE;
PRBool goThroughDOMSubtree = PR_TRUE;
if (isVisible) {
nsCOMPtr<nsIAccessible> accessible;
GetAccService()->GetAccessibleInShell(DOMNode, shell,
getter_AddRefs(accessible));
if (accessible) {
rv = AppendFromAccessible(accessible, aString);
goThroughDOMSubtree = PR_FALSE;
}
}
if (goThroughDOMSubtree)
rv = AppendFromDOMNode(aContent, aString);
gInitiatorAcc = nsnull;
return rv;
}
nsresult
nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent,
nsAString *aString)
{
if (aContent->IsNodeOfType(nsINode::eTEXT)) {
nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(aContent));
PRBool isHTMLBlock = PR_FALSE;
nsIContent *parentContent = aContent->GetParent();
if (parentContent) {
nsIFrame *frame = parentContent->GetPrimaryFrame();
if (frame) {
// If this text is inside a block level frame (as opposed to span
// level), we need to add spaces around that block's text, so we don't
// get words jammed together in final name.
const nsStyleDisplay* display = frame->GetStyleDisplay();
if (display->IsBlockOutside() ||
display->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL) {
isHTMLBlock = PR_TRUE;
if (!aString->IsEmpty()) {
aString->Append(PRUnichar(' '));
}
}
}
}
if (aContent->TextLength() > 0) {
nsIFrame *frame = aContent->GetPrimaryFrame();
if (frame) {
nsresult rv = frame->GetRenderedText(aString);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// If aContent is an object that is display: none, we have no a frame.
aContent->AppendTextTo(*aString);
}
if (isHTMLBlock && !aString->IsEmpty()) {
aString->Append(PRUnichar(' '));
}
}
return NS_OK;
}
if (aContent->IsHTML() &&
aContent->NodeInfo()->Equals(nsAccessibilityAtoms::br)) {
aString->AppendLiteral("\r\n");
return NS_OK;
}
return NS_OK_NO_NAME_CLAUSE_HANDLED;
}
////////////////////////////////////////////////////////////////////////////////
// nsTextEquivUtils. Private.
nsCOMPtr<nsIAccessible> nsTextEquivUtils::gInitiatorAcc;
nsresult
nsTextEquivUtils::AppendFromAccessibleChildren(nsIAccessible *aAccessible,
nsAString *aString)
{
nsCOMPtr<nsIAccessible> accChild, accNextChild;
aAccessible->GetFirstChild(getter_AddRefs(accChild));
nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
while (accChild) {
rv = AppendFromAccessible(accChild, aString);
NS_ENSURE_SUCCESS(rv, rv);
accChild->GetNextSibling(getter_AddRefs(accNextChild));
accChild.swap(accNextChild);
}
return rv;
}
nsresult
nsTextEquivUtils::AppendFromAccessible(nsIAccessible *aAccessible,
nsAString *aString)
{
nsCOMPtr<nsIAccessNode> accessNode(do_QueryInterface(aAccessible));
nsCOMPtr<nsIDOMNode> DOMNode;
accessNode->GetDOMNode(getter_AddRefs(DOMNode));
nsCOMPtr<nsIContent> content(do_QueryInterface(DOMNode));
NS_ASSERTION(content, "There is no content!");
if (content) {
nsresult rv = AppendTextEquivFromTextContent(content, aString);
if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
return rv;
}
nsAutoString text;
nsresult rv = aAccessible->GetName(text);
NS_ENSURE_SUCCESS(rv, rv);
PRBool isEmptyTextEquiv = PR_TRUE;
// If the name is from tooltip then append it to result string in the end
// (see h. step of name computation guide).
if (rv != NS_OK_NAME_FROM_TOOLTIP)
isEmptyTextEquiv = !AppendString(aString, text);
// Implementation of f. step.
rv = AppendFromValue(aAccessible, aString);
NS_ENSURE_SUCCESS(rv, rv);
if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
isEmptyTextEquiv = PR_FALSE;
// Implementation of g) step of text equivalent computation guide. Go down
// into subtree if accessible allows "text equivalent from subtree rule" or
// it's not root and not control.
if (isEmptyTextEquiv) {
PRUint32 role = nsAccUtils::Role(aAccessible);
PRUint32 nameRule = gRoleToNameRulesMap[role];
if (nameRule & eFromSubtreeIfRec) {
rv = AppendFromAccessibleChildren(aAccessible, aString);
NS_ENSURE_SUCCESS(rv, rv);
if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
isEmptyTextEquiv = PR_FALSE;
}
}
// Implementation of h. step
if (isEmptyTextEquiv && !text.IsEmpty()) {
AppendString(aString, text);
return NS_OK;
}
return rv;
}
nsresult
nsTextEquivUtils::AppendFromValue(nsIAccessible *aAccessible,
nsAString *aString)
{
PRUint32 role = nsAccUtils::Role(aAccessible);
PRUint32 nameRule = gRoleToNameRulesMap[role];
if (nameRule != eFromValue)
return NS_OK_NO_NAME_CLAUSE_HANDLED;
// Implementation of step f. of text equivalent computation. If the given
// accessible is not root accessible (the accessible the text equivalent is
// computed for in the end) then append accessible value. Otherwise append
// value if and only if the given accessible is in the middle of its parent.
nsAutoString text;
if (aAccessible != gInitiatorAcc) {
nsresult rv = aAccessible->GetValue(text);
NS_ENSURE_SUCCESS(rv, rv);
return AppendString(aString, text) ?
NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
}
nsRefPtr<nsAccessible> acc = do_QueryObject(aAccessible);
nsCOMPtr<nsIDOMNode> node;
acc->GetDOMNode(getter_AddRefs(node));
NS_ENSURE_STATE(node);
nsCOMPtr<nsIContent> content(do_QueryInterface(node));
NS_ENSURE_STATE(content);
nsCOMPtr<nsIContent> parent = content->GetParent();
PRInt32 indexOf = parent->IndexOf(content);
for (PRInt32 i = indexOf - 1; i >= 0; i--) {
// check for preceding text...
if (!parent->GetChildAt(i)->TextIsOnlyWhitespace()) {
PRUint32 childCount = parent->GetChildCount();
for (PRUint32 j = indexOf + 1; j < childCount; j++) {
// .. and subsequent text
if (!parent->GetChildAt(j)->TextIsOnlyWhitespace()) {
nsresult rv = aAccessible->GetValue(text);
NS_ENSURE_SUCCESS(rv, rv);
return AppendString(aString, text) ?
NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
break;
}
}
break;
}
}
return NS_OK_NO_NAME_CLAUSE_HANDLED;
}
nsresult
nsTextEquivUtils::AppendFromDOMChildren(nsIContent *aContent,
nsAString *aString)
{
PRUint32 childCount = aContent->GetChildCount();
for (PRUint32 childIdx = 0; childIdx < childCount; childIdx++) {
nsCOMPtr<nsIContent> childContent = aContent->GetChildAt(childIdx);
nsresult rv = AppendFromDOMNode(childContent, aString);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsTextEquivUtils::AppendFromDOMNode(nsIContent *aContent, nsAString *aString)
{
nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
NS_ENSURE_SUCCESS(rv, rv);
if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
return NS_OK;
if (aContent->IsXUL()) {
nsAutoString textEquivalent;
nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
do_QueryInterface(aContent);
if (labeledEl) {
labeledEl->GetLabel(textEquivalent);
} else {
if (aContent->NodeInfo()->Equals(nsAccessibilityAtoms::label,
kNameSpaceID_XUL))
aContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::value,
textEquivalent);
if (textEquivalent.IsEmpty())
aContent->GetAttr(kNameSpaceID_None,
nsAccessibilityAtoms::tooltiptext, textEquivalent);
}
AppendString(aString, textEquivalent);
}
return AppendFromDOMChildren(aContent, aString);
}
PRBool
nsTextEquivUtils::AppendString(nsAString *aString,
const nsAString& aTextEquivalent)
{
// Insert spaces to insure that words from controls aren't jammed together.
if (aTextEquivalent.IsEmpty())
return PR_FALSE;
if (!aString->IsEmpty())
aString->Append(PRUnichar(' '));
aString->Append(aTextEquivalent);
return PR_TRUE;
}
PRBool
nsTextEquivUtils::IsWhitespaceString(const nsSubstring& aString)
{
nsSubstring::const_char_iterator iterBegin, iterEnd;
aString.BeginReading(iterBegin);
aString.EndReading(iterEnd);
while (iterBegin != iterEnd && IsWhitespace(*iterBegin))
++iterBegin;
return iterBegin == iterEnd;
}
PRBool
nsTextEquivUtils::IsWhitespace(PRUnichar aChar)
{
return aChar == ' ' || aChar == '\n' ||
aChar == '\r' || aChar == '\t' || aChar == 0xa0;
}
////////////////////////////////////////////////////////////////////////////////
// Name rules to role map.
PRUint32 nsTextEquivUtils::gRoleToNameRulesMap[] =
{
eNoRule, // ROLE_NOTHING
eNoRule, // ROLE_TITLEBAR
eNoRule, // ROLE_MENUBAR
eNoRule, // ROLE_SCROLLBAR
eNoRule, // ROLE_GRIP
eNoRule, // ROLE_SOUND
eNoRule, // ROLE_CURSOR
eNoRule, // ROLE_CARET
eNoRule, // ROLE_ALERT
eNoRule, // ROLE_WINDOW
eNoRule, // ROLE_INTERNAL_FRAME
eNoRule, // ROLE_MENUPOPUP
eFromSubtree, // ROLE_MENUITEM
eFromSubtree, // ROLE_TOOLTIP
eNoRule, // ROLE_APPLICATION
eNoRule, // ROLE_DOCUMENT
eNoRule, // ROLE_PANE
eNoRule, // ROLE_CHART
eNoRule, // ROLE_DIALOG
eNoRule, // ROLE_BORDER
eNoRule, // ROLE_GROUPING
eNoRule, // ROLE_SEPARATOR
eNoRule, // ROLE_TOOLBAR
eNoRule, // ROLE_STATUSBAR
eNoRule, // ROLE_TABLE
eFromSubtree, // ROLE_COLUMNHEADER
eFromSubtree, // ROLE_ROWHEADER
eFromSubtree, // ROLE_COLUMN
eFromSubtree, // ROLE_ROW
eFromSubtreeIfRec, // ROLE_CELL
eFromSubtree, // ROLE_LINK
eFromSubtree, // ROLE_HELPBALLOON
eNoRule, // ROLE_CHARACTER
eFromSubtreeIfRec, // ROLE_LIST
eFromSubtree, // ROLE_LISTITEM
eNoRule, // ROLE_OUTLINE
eFromSubtree, // ROLE_OUTLINEITEM
eFromSubtree, // ROLE_PAGETAB
eNoRule, // ROLE_PROPERTYPAGE
eNoRule, // ROLE_INDICATOR
eNoRule, // ROLE_GRAPHIC
eNoRule, // ROLE_STATICTEXT
eNoRule, // ROLE_TEXT_LEAF
eFromSubtree, // ROLE_PUSHBUTTON
eFromSubtree, // ROLE_CHECKBUTTON
eFromSubtree, // ROLE_RADIOBUTTON
eFromValue, // ROLE_COMBOBOX
eNoRule, // ROLE_DROPLIST
eFromValue, // ROLE_PROGRESSBAR
eNoRule, // ROLE_DIAL
eNoRule, // ROLE_HOTKEYFIELD
eNoRule, // ROLE_SLIDER
eNoRule, // ROLE_SPINBUTTON
eNoRule, // ROLE_DIAGRAM
eNoRule, // ROLE_ANIMATION
eNoRule, // ROLE_EQUATION
eFromSubtree, // ROLE_BUTTONDROPDOWN
eFromSubtree, // ROLE_BUTTONMENU
eFromSubtree, // ROLE_BUTTONDROPDOWNGRID
eNoRule, // ROLE_WHITESPACE
eNoRule, // ROLE_PAGETABLIST
eNoRule, // ROLE_CLOCK
eNoRule, // ROLE_SPLITBUTTON
eNoRule, // ROLE_IPADDRESS
eNoRule, // ROLE_ACCEL_LABEL
eNoRule, // ROLE_ARROW
eNoRule, // ROLE_CANVAS
eFromSubtree, // ROLE_CHECK_MENU_ITEM
eNoRule, // ROLE_COLOR_CHOOSER
eNoRule, // ROLE_DATE_EDITOR
eNoRule, // ROLE_DESKTOP_ICON
eNoRule, // ROLE_DESKTOP_FRAME
eNoRule, // ROLE_DIRECTORY_PANE
eNoRule, // ROLE_FILE_CHOOSER
eNoRule, // ROLE_FONT_CHOOSER
eNoRule, // ROLE_CHROME_WINDOW
eNoRule, // ROLE_GLASS_PANE
eFromSubtreeIfRec, // ROLE_HTML_CONTAINER
eNoRule, // ROLE_ICON
eFromSubtree, // ROLE_LABEL
eNoRule, // ROLE_LAYERED_PANE
eNoRule, // ROLE_OPTION_PANE
eNoRule, // ROLE_PASSWORD_TEXT
eNoRule, // ROLE_POPUP_MENU
eFromSubtree, // ROLE_RADIO_MENU_ITEM
eNoRule, // ROLE_ROOT_PANE
eNoRule, // ROLE_SCROLL_PANE
eNoRule, // ROLE_SPLIT_PANE
eFromSubtree, // ROLE_TABLE_COLUMN_HEADER
eFromSubtree, // ROLE_TABLE_ROW_HEADER
eFromSubtree, // ROLE_TEAR_OFF_MENU_ITEM
eNoRule, // ROLE_TERMINAL
eFromSubtreeIfRec, // ROLE_TEXT_CONTAINER
eFromSubtree, // ROLE_TOGGLE_BUTTON
eNoRule, // ROLE_TREE_TABLE
eNoRule, // ROLE_VIEWPORT
eNoRule, // ROLE_HEADER
eNoRule, // ROLE_FOOTER
eFromSubtreeIfRec, // ROLE_PARAGRAPH
eNoRule, // ROLE_RULER
eNoRule, // ROLE_AUTOCOMPLETE
eNoRule, // ROLE_EDITBAR
eFromValue, // ROLE_ENTRY
eNoRule, // ROLE_CAPTION
eNoRule, // ROLE_DOCUMENT_FRAME
eFromSubtreeIfRec, // ROLE_HEADING
eNoRule, // ROLE_PAGE
eFromSubtreeIfRec, // ROLE_SECTION
eNoRule, // ROLE_REDUNDANT_OBJECT
eNoRule, // ROLE_FORM
eNoRule, // ROLE_IME
eNoRule, // ROLE_APP_ROOT
eFromSubtree, // ROLE_PARENT_MENUITEM
eNoRule, // ROLE_CALENDAR
eNoRule, // ROLE_COMBOBOX_LIST
eFromSubtree, // ROLE_COMBOBOX_OPTION
eNoRule, // ROLE_IMAGE_MAP
eFromSubtree, // ROLE_OPTION
eFromSubtree, // ROLE_RICH_OPTION
eNoRule, // ROLE_LISTBOX
eNoRule, // ROLE_FLAT_EQUATION
eFromSubtree // ROLE_GRID_CELL
};