Bug 455886 - AccessibleNameFromSubtree(): don't recurse into subtrees for roles that don't use name from subtree, r=davidb, marcoz

--HG--
rename : accessible/src/base/nsNameUtils.cpp => accessible/src/base/nsTextEquivUtils.cpp
rename : accessible/src/base/nsNameUtils.h => accessible/src/base/nsTextEquivUtils.h
This commit is contained in:
Alexander Surkov 2009-02-19 15:06:14 +08:00
parent a9f284053f
commit ed9e9ba487
25 changed files with 999 additions and 677 deletions

View File

@ -81,7 +81,6 @@ CPPSRCS = \
nsAccessibilityAtoms.cpp \
nsCoreUtils.cpp \
nsAccUtils.cpp \
nsNameUtils.cpp \
nsRelUtils.cpp \
nsAccessibilityService.cpp \
nsAccessible.cpp \
@ -93,6 +92,7 @@ CPPSRCS = \
nsApplicationAccessible.cpp \
nsCaretAccessible.cpp \
nsTextAccessible.cpp \
nsTextEquivUtils.cpp \
nsTextAttrs.cpp \
$(NULL)

View File

@ -40,7 +40,6 @@
#include "nsAccessible.h"
#include "nsAccessibleRelation.h"
#include "nsHyperTextAccessibleWrap.h"
#include "nsNameUtils.h"
#include "nsIAccessibleDocument.h"
#include "nsIAccessibleHyperText.h"
@ -329,7 +328,9 @@ NS_IMETHODIMP nsAccessible::GetDescription(nsAString& aDescription)
}
if (!content->IsNodeOfType(nsINode::eTEXT)) {
nsAutoString description;
nsresult rv = GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description);
nsresult rv = nsTextEquivUtils::
GetTextEquivFromIDRefs(this, nsAccessibilityAtoms::aria_describedby,
description);
if (NS_FAILED(rv)) {
PRBool isXUL = content->IsNodeOfType(nsINode::eXUL);
if (isXUL) {
@ -341,7 +342,8 @@ NS_IMETHODIMP nsAccessible::GetDescription(nsAString& aDescription)
if (descriptionContent) {
// We have a description content node
AppendFlatStringFromSubtree(descriptionContent, &description);
nsTextEquivUtils::
AppendTextEquivFromContent(this, descriptionContent, &description);
}
}
if (description.IsEmpty()) {
@ -1456,284 +1458,6 @@ nsAccessible::TakeFocus()
return NS_OK;
}
nsresult nsAccessible::AppendStringWithSpaces(nsAString *aFlatString, const nsAString& textEquivalent)
{
// Insert spaces to insure that words from controls aren't jammed together
if (!textEquivalent.IsEmpty()) {
if (!aFlatString->IsEmpty())
aFlatString->Append(PRUnichar(' '));
aFlatString->Append(textEquivalent);
aFlatString->Append(PRUnichar(' '));
}
return NS_OK;
}
nsresult nsAccessible::AppendNameFromAccessibleFor(nsIContent *aContent,
nsAString *aFlatString,
PRBool aFromValue)
{
nsAutoString textEquivalent, value;
nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(aContent));
nsCOMPtr<nsIAccessible> accessible;
if (domNode == mDOMNode) {
accessible = this;
if (!aFromValue) {
// prevent recursive call GetName()
return NS_OK;
}
}
else {
nsCOMPtr<nsIAccessibilityService> accService =
do_GetService("@mozilla.org/accessibilityService;1");
NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
accService->GetAccessibleInWeakShell(domNode, mWeakShell, getter_AddRefs(accessible));
}
if (accessible) {
if (aFromValue) {
accessible->GetValue(textEquivalent);
}
else {
accessible->GetName(textEquivalent);
}
}
textEquivalent.CompressWhitespace();
return AppendStringWithSpaces(aFlatString, textEquivalent);
}
/*
* AppendFlatStringFromContentNode and AppendFlatStringFromSubtree
*
* This method will glean useful text, in whatever form it exists, from any content node given to it.
* It is used by any decendant of nsAccessible that needs to get text from a single node, as
* well as by nsAccessible::AppendFlatStringFromSubtree, which gleans and concatenates text from any node and
* that node's decendants.
*/
nsresult nsAccessible::AppendFlatStringFromContentNode(nsIContent *aContent, nsAString *aFlatString)
{
if (aContent->IsNodeOfType(nsINode::eTEXT)) {
// If it's a text node, append the text
PRBool isHTMLBlock = PR_FALSE;
nsCOMPtr<nsIPresShell> shell = GetPresShell();
if (!shell) {
return NS_ERROR_FAILURE;
}
nsIContent *parentContent = aContent->GetParent();
nsCOMPtr<nsIContent> appendedSubtreeStart(do_QueryInterface(mDOMNode));
if (parentContent && parentContent != appendedSubtreeStart) {
nsIFrame *frame = shell->GetPrimaryFrameFor(parentContent);
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
// Extra spaces will be trimmed out later
const nsStyleDisplay* display = frame->GetStyleDisplay();
if (display->IsBlockOutside() ||
display->mDisplay == NS_STYLE_DISPLAY_TABLE_CELL) {
isHTMLBlock = PR_TRUE;
if (!aFlatString->IsEmpty()) {
aFlatString->Append(PRUnichar(' '));
}
}
}
}
if (aContent->TextLength() > 0) {
nsIFrame *frame = shell->GetPrimaryFrameFor(aContent);
if (frame) {
nsresult rv = frame->GetRenderedText(aFlatString);
NS_ENSURE_SUCCESS(rv, rv);
} else {
//if aContent is an object that is display: none, we have no a frame
aContent->AppendTextTo(*aFlatString);
}
if (isHTMLBlock && !aFlatString->IsEmpty()) {
aFlatString->Append(PRUnichar(' '));
}
}
return NS_OK;
}
nsAutoString textEquivalent;
if (!aContent->IsNodeOfType(nsINode::eHTML)) {
if (aContent->IsNodeOfType(nsINode::eXUL)) {
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);
}
}
AppendNameFromAccessibleFor(aContent, &textEquivalent, PR_TRUE /* use value */);
return AppendStringWithSpaces(aFlatString, textEquivalent);
}
return NS_OK; // Not HTML and not XUL -- we don't handle it yet
}
nsCOMPtr<nsIAtom> tag = aContent->Tag();
if (tag == nsAccessibilityAtoms::img) {
return AppendNameFromAccessibleFor(aContent, aFlatString);
}
if (tag == nsAccessibilityAtoms::input) {
static nsIContent::AttrValuesArray strings[] =
{&nsAccessibilityAtoms::button, &nsAccessibilityAtoms::submit,
&nsAccessibilityAtoms::reset, &nsAccessibilityAtoms::image, nsnull};
if (aContent->FindAttrValueIn(kNameSpaceID_None, nsAccessibilityAtoms::type,
strings, eIgnoreCase) >= 0) {
return AppendNameFromAccessibleFor(aContent, aFlatString);
}
}
if (tag == nsAccessibilityAtoms::object && !aContent->GetChildCount()) {
// If object has no alternative content children, try title
aContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::title, textEquivalent);
}
else if (tag == nsAccessibilityAtoms::br) {
// If it's a line break, insert a space so that words aren't jammed together
aFlatString->AppendLiteral("\r\n");
return NS_OK;
}
else if (tag != nsAccessibilityAtoms::a && tag != nsAccessibilityAtoms::area) {
AppendNameFromAccessibleFor(aContent, aFlatString, PR_TRUE /* use value */);
}
textEquivalent.CompressWhitespace();
return AppendStringWithSpaces(aFlatString, textEquivalent);
}
nsresult nsAccessible::AppendFlatStringFromSubtree(nsIContent *aContent, nsAString *aFlatString)
{
static PRBool isAlreadyHere; // Prevent recursion which can cause infinite loops
if (isAlreadyHere) {
return NS_OK;
}
isAlreadyHere = PR_TRUE;
nsCOMPtr<nsIPresShell> shell = GetPresShell();
NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
nsIFrame *frame = shell->GetPrimaryFrameFor(aContent);
PRBool isHidden = (!frame || !frame->GetStyleVisibility()->IsVisible());
nsresult rv = AppendFlatStringFromSubtreeRecurse(aContent, aFlatString,
isHidden);
isAlreadyHere = PR_FALSE;
if (NS_SUCCEEDED(rv) && !aFlatString->IsEmpty()) {
nsAString::const_iterator start, end;
aFlatString->BeginReading(start);
aFlatString->EndReading(end);
PRInt32 spacesToTruncate = 0;
while (-- end != start && *end == ' ')
++ spacesToTruncate;
if (spacesToTruncate > 0)
aFlatString->Truncate(aFlatString->Length() - spacesToTruncate);
}
return rv;
}
nsresult
nsAccessible::AppendFlatStringFromSubtreeRecurse(nsIContent *aContent,
nsAString *aFlatString,
PRBool aIsRootHidden)
{
// Depth first search for all text nodes that are decendants of content node.
// Append all the text into one flat string
PRUint32 numChildren = 0;
nsCOMPtr<nsIDOMXULSelectControlElement> selectControlEl(do_QueryInterface(aContent));
nsCOMPtr<nsIAtom> tag = aContent->Tag();
if (!selectControlEl &&
tag != nsAccessibilityAtoms::textarea &&
tag != nsAccessibilityAtoms::select) {
// Don't walk children of elements with options, just get label directly.
// Don't traverse the children of a textarea, we want the value, not the
// static text node.
// Don't traverse the children of a select element, we only want the
// current value.
numChildren = aContent->GetChildCount();
}
if (numChildren == 0) {
// There are no children or they are irrelvant: get the text from the current node
AppendFlatStringFromContentNode(aContent, aFlatString);
return NS_OK;
}
// There are relevant children: use them to get the text.
nsCOMPtr<nsIPresShell> shell = GetPresShell();
NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
PRUint32 index;
for (index = 0; index < numChildren; index++) {
nsCOMPtr<nsIContent> childContent = aContent->GetChildAt(index);
// Walk into hidden subtree if the the root parent is also hidden. This
// happens when the author explictly uses a hidden label or description.
if (!aIsRootHidden) {
nsIFrame *childFrame = shell->GetPrimaryFrameFor(childContent);
if (!childFrame || !childFrame->GetStyleVisibility()->IsVisible())
continue;
}
AppendFlatStringFromSubtreeRecurse(childContent, aFlatString,
aIsRootHidden);
}
return NS_OK;
}
nsresult nsAccessible::GetTextFromRelationID(nsIAtom *aIDProperty, nsString &aName)
{
// Get DHTML name from content subtree pointed to by ID attribute
aName.Truncate();
NS_ASSERTION(mDOMNode, "Called from shutdown accessible");
nsCOMPtr<nsIContent> content = nsCoreUtils::GetRoleContent(mDOMNode);
if (!content)
return NS_OK;
nsCOMPtr<nsIArray> refElms;
nsCoreUtils::GetElementsByIDRefsAttr(content, aIDProperty,
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 (!aName.IsEmpty())
aName += ' '; // Need whitespace between multiple labels or descriptions
rv = AppendFlatStringFromSubtree(refContent, &aName);
NS_ENSURE_SUCCESS(rv, rv);
}
aName.CompressWhitespace();
return NS_OK;
}
nsresult
nsAccessible::GetHTMLName(nsAString& aLabel)
{
@ -1746,7 +1470,8 @@ nsAccessible::GetHTMLName(nsAString& aLabel)
nsIContent *labelContent = nsCoreUtils::GetHTMLLabelContent(content);
if (labelContent) {
nsAutoString label;
nsresult rv = AppendFlatStringFromSubtree(labelContent, &label);
nsresult rv =
nsTextEquivUtils::AppendTextEquivFromContent(this, labelContent, &label);
NS_ENSURE_SUCCESS(rv, rv);
label.CompressWhitespace();
@ -1756,20 +1481,7 @@ nsAccessible::GetHTMLName(nsAString& aLabel)
}
}
PRUint32 role = nsAccUtils::Role(this);
PRUint32 canAggregateName =
nsNameUtils::gRoleToNameRulesMap[role] & eFromSubtree;
if (canAggregateName) {
// Don't use AppendFlatStringFromSubtree for container widgets like menulist
nsresult rv = AppendFlatStringFromSubtree(content, &aLabel);
NS_ENSURE_SUCCESS(rv, rv);
if (!aLabel.IsEmpty())
return NS_OK;
}
return NS_OK;
return nsTextEquivUtils::GetNameFromSubtree(this, aLabel);
}
/**
@ -1829,7 +1541,7 @@ nsAccessible::GetXULName(nsAString& aLabel)
if (xulLabel && NS_SUCCEEDED(xulLabel->GetValue(label)) && label.IsEmpty()) {
// If no value attribute, a non-empty label must contain
// children that define its text -- possibly using HTML
AppendFlatStringFromSubtree(labelContent, &label);
nsTextEquivUtils::AppendTextEquivFromContent(this, labelContent, &label);
}
}
@ -1854,12 +1566,7 @@ nsAccessible::GetXULName(nsAString& aLabel)
parent = parent->GetParent();
}
PRUint32 role = nsAccUtils::Role(this);
PRUint32 canAggregateName =
nsNameUtils::gRoleToNameRulesMap[role] & eFromSubtree;
return canAggregateName ?
AppendFlatStringFromSubtree(content, &aLabel) : NS_OK;
return nsTextEquivUtils::GetNameFromSubtree(this, aLabel);
}
NS_IMETHODIMP
@ -3390,14 +3097,18 @@ nsAccessible::GetARIAName(nsAString& aName)
// First check for label override via aria-label property
nsAutoString label;
if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_label, label)) {
label.CompressWhitespace();
aName = label;
return NS_OK;
}
// Second check for label override via aria-labelledby relationship
nsresult rv = GetTextFromRelationID(nsAccessibilityAtoms::aria_labelledby, label);
if (NS_SUCCEEDED(rv))
nsresult rv = nsTextEquivUtils::
GetTextEquivFromIDRefs(this, nsAccessibilityAtoms::aria_labelledby, label);
if (NS_SUCCEEDED(rv)) {
label.CompressWhitespace();
aName = label;
}
return rv;
}

View File

@ -41,6 +41,10 @@
#include "nsAccessNodeWrap.h"
#include "nsARIAMap.h"
#include "nsRelUtils.h"
#include "nsTextEquivUtils.h"
#include "nsIAccessible.h"
#include "nsPIAccessible.h"
#include "nsIAccessibleHyperLink.h"
@ -50,15 +54,12 @@
#include "nsIAccessibleStates.h"
#include "nsIAccessibleEvent.h"
#include "nsRelUtils.h"
#include "nsIDOMNodeList.h"
#include "nsINameSpaceManager.h"
#include "nsWeakReference.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsIDOMDOMStringList.h"
#include "nsARIAMap.h"
struct nsRect;
class nsIContent;
@ -181,18 +182,6 @@ protected:
virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame);
PRBool IsVisible(PRBool *aIsOffscreen);
// Relation helpers
/**
* For a given ARIA relation, such as labelledby or describedby, get the collated text
* for the subtree that's pointed to.
*
* @param aIDProperty The ARIA relationship property to get the text for
* @param aName Where to put the text
* @return error or success code
*/
nsresult GetTextFromRelationID(nsIAtom *aIDProperty, nsString &aName);
//////////////////////////////////////////////////////////////////////////////
// Name helpers.
@ -206,14 +195,6 @@ protected:
*/
nsresult GetXULName(nsAString& aName);
// For accessibles that are not lists of choices, the name of the subtree should be the
// sum of names in the subtree
nsresult AppendFlatStringFromSubtree(nsIContent *aContent, nsAString *aFlatString);
nsresult AppendNameFromAccessibleFor(nsIContent *aContent, nsAString *aFlatString,
PRBool aFromValue = PR_FALSE);
nsresult AppendFlatStringFromContentNode(nsIContent *aContent, nsAString *aFlatString);
nsresult AppendStringWithSpaces(nsAString *aFlatString, const nsAString& textEquivalent);
// helper method to verify frames
static nsresult GetFullKeyName(const nsAString& aModifierName, const nsAString& aKeyName, nsAString& aStringOut);
static nsresult GetTranslatedString(const nsAString& aKey, nsAString& aStringOut);

View File

@ -263,7 +263,9 @@ nsDocAccessible::GetDescription(nsAString& aDescription)
if (aDescription.IsEmpty()) {
nsAutoString description;
GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description);
nsTextEquivUtils::
GetTextEquivFromIDRefs(this, nsAccessibilityAtoms::aria_describedby,
description);
aDescription = description;
}

View File

@ -1,169 +0,0 @@
/* -*- 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 "nsNameUtils.h"
////////////////////////////////////////////////////////////////////////////////
// Name rules to role map.
PRUint32 nsNameUtils::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
eNoRule, // ROLE_CELL
eFromSubtree, // ROLE_LINK
eFromSubtree, // ROLE_HELPBALLOON
eNoRule, // ROLE_CHARACTER
eNoRule, // 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
eNoRule, // ROLE_COMBOBOX
eNoRule, // ROLE_DROPLIST
eNoRule, // 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
eNoRule, // ROLE_HTML_CONTAINER
eNoRule, // ROLE_ICON
eNoRule, // 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
eNoRule, // ROLE_TEXT_CONTAINER
eFromSubtree, // ROLE_TOGGLE_BUTTON
eNoRule, // ROLE_TREE_TABLE
eNoRule, // ROLE_VIEWPORT
eNoRule, // ROLE_HEADER
eNoRule, // ROLE_FOOTER
eNoRule, // ROLE_PARAGRAPH
eNoRule, // ROLE_RULER
eNoRule, // ROLE_AUTOCOMPLETE
eNoRule, // ROLE_EDITBAR
eNoRule, // ROLE_ENTRY
eNoRule, // ROLE_CAPTION
eNoRule, // ROLE_DOCUMENT_FRAME
eNoRule, // ROLE_HEADING
eNoRule, // ROLE_PAGE
eNoRule, // 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
};

View File

@ -1,71 +0,0 @@
/* -*- 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 ***** */
#ifndef _nsNameUtils_H_
#define _nsNameUtils_H_
#include "prtypes.h"
/**
* Name from subtree calculation rules.
*/
enum ENameFromSubtreeRule
{
// do not walk into subtree to compute the name
eNoRule = 0x00,
// compute the name from the subtree
eFromSubtree = 0x01
};
/**
* The class provides utils methods to compute the accessible name and
* description.
*/
class nsNameUtils
{
public:
/**
* Map array from roles to name rules (bit state of ENameFromSubtreeRule).
*/
static PRUint32 gRoleToNameRulesMap[];
};
#endif

View File

@ -0,0 +1,508 @@
/* -*- 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();
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 = shell->GetPrimaryFrameFor(aContent);
PRBool isVisible = frame && frame->GetStyleVisibility()->IsVisible();
nsresult rv;
PRBool goThroughDOMSubtree = PR_TRUE;
if (isVisible) {
nsCOMPtr<nsIAccessible> accessible;
rv = nsAccessNode::GetAccService()->
GetAccessibleInShell(DOMNode, shell, getter_AddRefs(accessible));
if (NS_SUCCEEDED(rv) && 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;
nsCOMPtr<nsIPresShell> shell = nsCoreUtils::GetPresShellFor(DOMNode);
NS_ENSURE_STATE(shell);
nsIContent *parentContent = aContent->GetParent();
if (parentContent) {
nsIFrame *frame = shell->GetPrimaryFrameFor(parentContent);
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 = shell->GetPrimaryFrameFor(aContent);
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->IsNodeOfType(nsINode::eHTML) &&
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));
while (accChild) {
nsresult rv = AppendFromAccessible(accChild, aString);
NS_ENSURE_SUCCESS(rv, rv);
accChild->GetNextSibling(getter_AddRefs(accNextChild));
accChild.swap(accNextChild);
}
return NS_OK;
}
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);
AppendString(aString, text);
PRUint32 role = nsAccUtils::Role(aAccessible);
PRUint32 nameRule = gRoleToNameRulesMap[role];
if (nameRule == eFromValue) {
// 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.
if (aAccessible != gInitiatorAcc) {
rv = aAccessible->GetValue(text);
NS_ENSURE_SUCCESS(rv, rv);
AppendString(aString, text);
} else {
nsCOMPtr<nsIAccessible> nextSibling;
aAccessible->GetNextSibling(getter_AddRefs(nextSibling));
if (nextSibling) {
nsCOMPtr<nsIAccessible> parent;
aAccessible->GetParent(getter_AddRefs(parent));
if (parent) {
nsCOMPtr<nsIAccessible> firstChild;
parent->GetFirstChild(getter_AddRefs(firstChild));
if (firstChild && firstChild != aAccessible) {
rv = aAccessible->GetValue(text);
NS_ENSURE_SUCCESS(rv, rv);
AppendString(aString, text);
}
}
}
}
}
// 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 (text.IsEmpty() && (nameRule & eFromSubtreeIfRec))
return AppendFromAccessibleChildren(aAccessible, aString);
return NS_OK;
}
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->IsNodeOfType(nsINode::eXUL)) {
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);
}
void
nsTextEquivUtils::AppendString(nsAString *aString,
const nsAString& aTextEquivalent)
{
// Insert spaces to insure that words from controls aren't jammed together.
if (aTextEquivalent.IsEmpty())
return;
if (!aString->IsEmpty())
aString->Append(PRUnichar(' '));
aString->Append(aTextEquivalent);
}
////////////////////////////////////////////////////////////////////////////////
// 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
eNoRule, // 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
};

View File

@ -0,0 +1,168 @@
/* -*- 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 ***** */
#ifndef _nsTextEquivUtils_H_
#define _nsTextEquivUtils_H_
#include "nsIAccessible.h"
#include "nsIContent.h"
#include "nsIStringBundle.h"
/**
* Text equivalent computation rules (see nsTextEquivUtils::gRoleToNameRulesMap)
*/
enum ETextEquivRule
{
// No rule.
eNoRule = 0x00,
// Walk into subtree only if the currently navigated accessible is not root
// accessible (i.e. if the accessible is part of text equivalent computation).
eFromSubtreeIfRec = 0x01,
// Text equivalent computation from subtree is allowed.
eFromSubtree = 0x03,
// The accessible allows to append its value to text equivalent.
// XXX: This is temporary solution. Once we move accessible value of links
// and linkable accessibles to MSAA part we can remove this.
eFromValue = 0x04
};
/**
* The class provides utils methods to compute the accessible name and
* description.
*/
class nsTextEquivUtils
{
public:
/**
* Calculates the name from accessible subtree if allowed.
*
* @param aAccessible [in] the given accessible
* @param aName [out] accessible name
*/
static nsresult GetNameFromSubtree(nsIAccessible *aAccessible,
nsAString& aName);
/**
* Calculates text equivalent for the given accessible from its IDRefs
* attribute (like aria-labelledby or aria-describedby).
*
* @param aAccessible [in] the accessible text equivalent is computed for
* @param aIDRefsAttr [in] IDRefs attribute on DOM node of the accessible
* @param aTextEquiv [out] result text equivalent
*/
static nsresult GetTextEquivFromIDRefs(nsIAccessible *aAccessible,
nsIAtom *aIDRefsAttr,
nsAString& aTextEquiv);
/**
* Calculates the text equivalent from the given content and its subtree if
* allowed and appends it to the given string.
*
* @param aInitiatorAcc [in] the accessible text equivalent is computed for
* in the end (root accessible of text equivalent
* calculation recursion)
* @param aContent [in] the given content the text equivalent is
* computed from
* @param aString [in, out] the string
*/
static nsresult AppendTextEquivFromContent(nsIAccessible *aInitiatorAcc,
nsIContent *aContent,
nsAString *aString);
/**
* Calculates the text equivalent from the given text content (may be text
* node or html:br) and appends it to the given string.
*
* @param aContent [in] the text content
* @param aString [in, out] the string
*/
static nsresult AppendTextEquivFromTextContent(nsIContent *aContent,
nsAString *aString);
private:
/**
* Iterates accessible children and calculates text equivalent from each
* child.
*/
static nsresult AppendFromAccessibleChildren(nsIAccessible *aAccessible,
nsAString *aString);
/**
* Calculates text equivalent from the given accessible and its subtree if
* allowed.
*/
static nsresult AppendFromAccessible(nsIAccessible *aAccessible,
nsAString *aString);
/**
* Iterates DOM children and calculates text equivalent from each child node.
*/
static nsresult AppendFromDOMChildren(nsIContent *aContent,
nsAString *aString);
/**
* Calculates text equivalent from the given DOM node and its subtree if
* allowed.
*/
static nsresult AppendFromDOMNode(nsIContent *aContent, nsAString *aString);
/**
* Concatenates strings and appends space between them.
*/
static void AppendString(nsAString *aString, const nsAString& aTextEquivalent);
/**
* Map array from roles to name rules (constants of ETextEquivRule).
*/
static PRUint32 gRoleToNameRulesMap[];
/**
* The accessible for which we are computing a text equivalent. It is useful
* for bailing out during recursive text computation, or for special cases
* like step f. of the ARIA implementation guide.
*/
static nsCOMPtr<nsIAccessible> gInitiatorAcc;
};
#endif

View File

@ -627,7 +627,8 @@ nsHTMLGroupboxAccessible::GetNameInternal(nsAString& aName)
nsIContent *legendContent = GetLegend();
if (legendContent) {
return AppendFlatStringFromSubtree(legendContent, &aName);
return nsTextEquivUtils::
AppendTextEquivFromContent(this, legendContent, &aName);
}
return NS_OK;

View File

@ -526,7 +526,8 @@ nsHTMLSelectOptionAccessible::GetNameInternal(nsAString& aName)
if (text->IsNodeOfType(nsINode::eTEXT)) {
nsAutoString txtValue;
nsresult rv = AppendFlatStringFromContentNode(text, &txtValue);
nsresult rv = nsTextEquivUtils::
AppendTextEquivFromTextContent(text, &txtValue);
NS_ENSURE_SUCCESS(rv, rv);
// Temp var (txtValue) needed until CompressWhitespace built for nsAString

View File

@ -967,7 +967,8 @@ NS_IMETHODIMP nsHTMLTableAccessible::GetDescription(nsAString& aDescription)
captionAccessNode->GetDOMNode(getter_AddRefs(captionNode));
nsCOMPtr<nsIContent> captionContent = do_QueryInterface(captionNode);
if (captionContent) {
AppendFlatStringFromSubtree(captionContent, &aDescription);
nsTextEquivUtils::
AppendTextEquivFromContent(this, captionContent, &aDescription);
}
}
#ifdef SHOW_LAYOUT_HEURISTIC

View File

@ -169,22 +169,9 @@ nsTextAccessible(aDomNode, aShell)
}
nsresult
nsHTMLLabelAccessible::GetNameInternal(nsAString& aReturn)
{
nsresult rv = NS_ERROR_FAILURE;
nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
nsAutoString name;
if (content)
rv = AppendFlatStringFromSubtree(content, &name);
if (NS_SUCCEEDED(rv)) {
// Temp var needed until CompressWhitespace built for nsAString
name.CompressWhitespace();
aReturn = name;
}
return rv;
nsHTMLLabelAccessible::GetNameInternal(nsAString& aName)
{
return nsTextEquivUtils::GetNameFromSubtree(this, aName);
}
NS_IMETHODIMP nsHTMLLabelAccessible::GetRole(PRUint32 *aRole)

View File

@ -234,7 +234,9 @@ NS_IMETHODIMP
nsXFormsAccessible::GetDescription(nsAString& aDescription)
{
nsAutoString description;
nsresult rv = GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description);
nsresult rv = nsTextEquivUtils::
GetTextEquivFromIDRefs(this, nsAccessibilityAtoms::aria_describedby,
description);
if (NS_SUCCEEDED(rv) && !description.IsEmpty()) {
aDescription = description;

View File

@ -66,7 +66,9 @@ NS_IMETHODIMP
nsXFormsLabelAccessible::GetDescription(nsAString& aDescription)
{
nsAutoString description;
nsresult rv = GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description);
nsresult rv = nsTextEquivUtils::
GetTextEquivFromIDRefs(this, nsAccessibilityAtoms::aria_describedby,
description);
aDescription = description;
return rv;
}

View File

@ -61,8 +61,6 @@ public:
protected:
nsresult ChangeSelection(PRInt32 aIndex, PRUint8 aMethod, PRBool *aSelState);
nsresult AppendFlatStringFromSubtree(nsIContent *aContent, nsAString *aFlatString)
{ return NS_OK; } // Overrides base impl in nsAccessible
// nsIDOMXULMultiSelectControlElement inherits from this, so we'll always have
// one of these if the widget is valid and not defunct

View File

@ -165,12 +165,15 @@ nsXULLinkAccessible::GetValue(nsAString& aValue)
nsresult
nsXULLinkAccessible::GetNameInternal(nsAString& aName)
{
if (IsDefunct())
return NS_ERROR_FAILURE;
nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::value, aName);
if (!aName.IsEmpty())
return NS_OK;
return AppendFlatStringFromSubtree(content, &aName);
return nsTextEquivUtils::GetNameFromSubtree(this, aName);
}
NS_IMETHODIMP

View File

@ -81,7 +81,7 @@ _TEST_FILES =\
test_nsIAccessible_name.html \
test_nsIAccessible_name_button.html \
test_nsIAccessible_name_link.html \
$(warning test_nsIAccessible_name.xul temporarily disabled) \
test_nsIAccessible_name.xul \
test_nsIAccessible_selects.html \
test_nsIAccessible_focus.html \
test_nsIAccessibleDocument.html \

View File

@ -178,6 +178,9 @@ function getNode(aNodeOrID)
*/
function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIfNoAcc)
{
if (!aAccOrElmOrID)
return;
var elm = null;
if (aAccOrElmOrID instanceof nsIAccessible) {
@ -246,6 +249,27 @@ function isAccessible(aAccOrElmOrID)
return getAccessible(aAccOrElmOrID, null, null, true) ? true : false;
}
/**
* Run through accessible tree of the given identifier so that we ensure
* accessible tree is created.
*/
function ensureAccessibleTree(aAccOrElmOrID)
{
var acc = getAccessible(aAccOrElmOrID);
if (!acc)
return;
var child = acc.firstChild;
while (child) {
ensureAccessibleTree(child);
try {
child = child.nextSibling;
} catch (e) {
child = null;
}
}
}
/**
* Convert role to human readable string.
*/

View File

@ -1,3 +1,9 @@
////////////////////////////////////////////////////////////////////////////////
// Constants
const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
const EVENT_DOM_DESTROY = nsIAccessibleEvent.EVENT_DOM_DESTROY;
////////////////////////////////////////////////////////////////////////////////
// General
@ -6,6 +12,38 @@
*/
var gA11yEventDumpID = "";
/**
* Executes the function when requested event is handled.
*
* @param aEventType [in] event type
* @param aTarget [in] event target
* @param aFunc [in] function to call when event is handled
* @param aContext [in, optional] object in which context the function is
* called
* @param aArg1 [in, optional] argument passed into the function
* @param aArg2 [in, optional] argument passed into the function
*/
function waitForEvent(aEventType, aTarget, aFunc, aContext, aArg1, aArg2)
{
var handler = {
handleEvent: function handleEvent(aEvent) {
if (!aTarget || aTarget == aEvent.DOMNode) {
unregisterA11yEventListener(aEventType, this);
window.setTimeout(
function ()
{
aFunc.call(aContext, aArg1, aArg2);
},
0
);
}
}
};
registerA11yEventListener(aEventType, handler);
}
/**
* Register accessibility event listener.
*
@ -386,6 +424,9 @@ function removeA11yEventListener(aEventType, aEventHandler)
function dumpInfoToDOM(aInfo)
{
if (!gA11yEventDumpID)
return;
var dumpElm = document.getElementById(gA11yEventDumpID);
var div = document.createElement("div");
div.textContent = aInfo;

View File

@ -119,6 +119,7 @@
<rule fromsubtree="true"/>
<rule attr="title" type="string"/>
</ruleset>
</ruledfn>
<rulesample>
@ -193,12 +194,13 @@
role="gridcell"
aria-label="test1"
aria-labelledby="l1 l2"
a11yname=" This is a paragraph This is a link This is a list"
a11yname="This is a paragraph This is a link • Listitem1 • Listitem2"
title="This is a paragraph This is a link This is a list">
<html:p>This is a paragraph</html:p>
<html:a href="#">This is a link</html:a>
<html:ul>
<html:li>This is a list</html:li>
<html:li>Listitem1</html:li>
<html:li>Listitem2</html:li>
</html:ul>
</html:td>
</html:tr>

View File

@ -3,9 +3,8 @@ function testName(aAccOrElmOrID, aName, aMsg)
var msg = aMsg ? aMsg : "";
var acc = getAccessible(aAccOrElmOrID);
if (!acc) {
ok(false, msg + "No accessible for " + aAccOrElmOrID + "!");
}
if (!acc)
return;
try {
is(acc.name, aName, msg + "Wrong name of the accessible for " + aAccOrElmOrID);
@ -36,13 +35,67 @@ function testNames()
gRuleDoc = request.responseXML;
var markupElms = evaluateXPath(gRuleDoc, "//rules/rulesample/markup");
for (var idx = 0; idx < markupElms.length; idx++)
testNamesForMarkup(markupElms[idx]);
gTestIterator.iterateMarkups(markupElms);
}
////////////////////////////////////////////////////////////////////////////////
// Private section.
/**
* Helper class to interate through name tests.
*/
var gTestIterator =
{
iterateMarkups: function gTestIterator_iterateMarkups(aMarkupElms)
{
this.markupElms = aMarkupElms;
this.iterateNext();
},
iterateRules: function gTestIterator_iterateRules(aElm, aContainer, aRuleElms)
{
this.ruleElms = aRuleElms;
this.elm = aElm;
this.container = aContainer;
this.iterateNext();
},
iterateNext: function gTestIterator_iterateNext()
{
if (this.markupIdx == -1) {
this.markupIdx++;
testNamesForMarkup(this.markupElms[this.markupIdx]);
return;
}
this.ruleIdx++;
if (this.ruleIdx == this.ruleElms.length) {
this.markupIdx++;
if (this.markupIdx == this.markupElms.length) {
SimpleTest.finish();
return;
}
document.body.removeChild(this.container);
this.ruleIdx = -1;
testNamesForMarkup(this.markupElms[this.markupIdx]);
return;
}
testNameForRule(this.elm, this.ruleElms[this.ruleIdx]);
},
markupElms: null,
markupIdx: -1,
ruleElms: null,
ruleIdx: -1,
elm: null,
container: null
};
/**
* Process every 'markup' element and test names for it. Used by testNames
* function.
@ -50,7 +103,7 @@ function testNames()
function testNamesForMarkup(aMarkupElm)
{
var div = document.createElement("div");
div.setAttribute("test", "test");
div.setAttribute("id", "test");
var child = aMarkupElm.firstChild;
while (child) {
@ -58,90 +111,114 @@ function testNamesForMarkup(aMarkupElm)
div.appendChild(newChild);
child = child.nextSibling;
}
waitForEvent(EVENT_REORDER, document, testNamesForMarkupRules,
null, aMarkupElm, div);
document.body.appendChild(div);
}
function testNamesForMarkupRules(aMarkupElm, aContainer)
{
ensureAccessibleTree(aContainer);
var serializer = new XMLSerializer();
var expr = "//html/body/div[@test='test']/" + aMarkupElm.getAttribute("ref");
var expr = "//html/body/div[@id='test']/" + aMarkupElm.getAttribute("ref");
var elms = evaluateXPath(document, expr, htmlDocResolver);
var ruleId = aMarkupElm.getAttribute("ruleset");
var ruleElms = getRuleElmsByRulesetId(ruleId);
for (var idx = 0; idx < ruleElms.length; idx++)
testNameForRule(elms[0], ruleElms[idx]);
document.body.removeChild(div);
gTestIterator.iterateRules(elms[0], aContainer, ruleElms);
}
/**
* Test name for current rule and current 'markup' element. Used by
* testNamesForMarkup function.
*/
function testNameForRule(aElm, aRule)
function testNameForRule(aElm, aRuleElm)
{
if (aRuleElm.hasAttribute("attr"))
testNameForAttrRule(aElm, aRuleElm);
else if (aRuleElm.hasAttribute("elm") && aRuleElm.hasAttribute("elmattr"))
testNameForElmRule(aElm, aRuleElm);
else if (aRuleElm.getAttribute("fromsubtree") == "true")
testNameForSubtreeRule(aElm, aRuleElm);
}
function testNameForAttrRule(aElm, aRule)
{
var name = "";
var attr = aRule.getAttribute("attr");
if (attr) {
var name = "";
var attrValue = aElm.getAttribute(attr);
var type = aRule.getAttribute("type");
if (type == "string") {
name = attrValue;
var attrValue = aElm.getAttribute(attr);
} else if (type == "ref") {
var ids = attrValue.split(/\s+/);///\,\s*/);
for (var idx = 0; idx < ids.length; idx++) {
var labelElm = getNode(ids[idx]);
if (name != "")
name += " ";
var type = aRule.getAttribute("type");
if (type == "string") {
name = attrValue;
name += labelElm.getAttribute("a11yname");
}
} else if (type == "ref") {
var ids = attrValue.split(/\s+/);
for (var idx = 0; idx < ids.length; idx++) {
var labelElm = getNode(ids[idx]);
if (name != "")
name += " ";
name += labelElm.getAttribute("a11yname");
}
var msg = "Attribute '" + attr + "' test. ";
testName(aElm, name, msg);
aElm.removeAttribute(attr);
return;
}
var msg = "Attribute '" + attr + "' test. ";
testName(aElm, name, msg);
aElm.removeAttribute(attr);
gTestIterator.iterateNext();
}
function testNameForElmRule(aElm, aRule)
{
var elm = aRule.getAttribute("elm");
var elmattr = aRule.getAttribute("elmattr");
if (elm && elmattr) {
var filter = {
acceptNode: function filter_acceptNode(aNode)
{
if (aNode.localName == this.mLocalName &&
aNode.getAttribute(this.mAttrName) == this.mAttrValue)
return NodeFilter.FILTER_ACCEPT;
return NodeFilter.FILTER_SKIP;
},
var filter = {
acceptNode: function filter_acceptNode(aNode)
{
if (aNode.localName == this.mLocalName &&
aNode.getAttribute(this.mAttrName) == this.mAttrValue)
return NodeFilter.FILTER_ACCEPT;
mLocalName: elm,
mAttrName: elmattr,
mAttrValue: aElm.getAttribute("id")
};
return NodeFilter.FILTER_SKIP;
},
var treeWalker = document.createTreeWalker(document.body,
NodeFilter.SHOW_ELEMENT,
filter, false);
var labelElm = treeWalker.nextNode();
var msg = "Element '" + elm + "' test.";
testName(aElm, labelElm.getAttribute("a11yname"), msg);
labelElm.parentNode.removeChild(labelElm);
return;
}
mLocalName: elm,
mAttrName: elmattr,
mAttrValue: aElm.getAttribute("id")
};
var fromSubtree = aRule.getAttribute("fromsubtree");
if (fromSubtree == "true") {
var msg = "From subtree test.";
testName(aElm, aElm.getAttribute("a11yname"), msg);
while (aElm.firstChild)
aElm.removeChild(aElm.firstChild);
return;
}
var treeWalker = document.createTreeWalker(document.body,
NodeFilter.SHOW_ELEMENT,
filter, false);
var labelElm = treeWalker.nextNode();
var msg = "Element '" + elm + "' test.";
testName(aElm, labelElm.getAttribute("a11yname"), msg);
var parentNode = labelElm.parentNode;
waitForEvent(EVENT_REORDER, parentNode,
gTestIterator.iterateNext, gTestIterator);
parentNode.removeChild(labelElm);
}
function testNameForSubtreeRule(aElm, aRule)
{
var msg = "From subtree test.";
testName(aElm, aElm.getAttribute("a11yname"), msg);
waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
while (aElm.firstChild)
aElm.removeChild(aElm.firstChild);
}
/**

View File

@ -1,7 +1,7 @@
<html>
<head>
<title>nsIAccessible::name calculation for HTML buttons</title>
<title>nsIAccessible::name calculation for elements</title>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
@ -10,19 +10,16 @@
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/events.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/nsIAccessible_name.js"></script>
<script type="application/javascript">
function doTest()
{
testNames();
SimpleTest.finish();
}
// gA11yEventDumpID = "eventdump";
SimpleTest.waitForExplicitFinish();
addLoadEvent(doTest);
addA11yLoadEvent(testNames);
</script>
</head>
@ -31,7 +28,7 @@
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=459635"
title="nsIAccessible::name calculation for HTML buttons">
title="nsIAccessible::name calculation for elements">
Mozilla Bug 459635
</a>
<p id="display"></p>
@ -39,6 +36,6 @@
<pre id="test">
</pre>
<div id="eventdump"></div>
</body>
</html>

View File

@ -35,6 +35,10 @@
// of elements. Gets the name from text nodes of those elements.
testName("btn_labelledby_texts", "text1 text2");
//////////////////////////////////////////////////////////////////////////
// Name from named accessible
testName("input_labelledby_namedacc", "Data");
//////////////////////////////////////////////////////////////////////////
// Name from subtree (single relation labelled_by).
@ -129,10 +133,21 @@
testName("textareawithchild", "Story: Bar");
/////////////////////////////////////////////////////////////////////////
// label with nested combobox
// label with nested combobox (test for 'f' item of name computation guide)
testName("comboinstart", "One day(s).");
testName("combo3", "day(s).");
testName("comboinmiddle", "Subscribe to ATOM feed.");
testName("combo4", "Subscribe to ATOM feed.");
testName("comboinmiddle2", "Play the Haliluya sound when new mail arrives");
testName("combo5", "Play the Haliluya sound when new mail arrives");
testName("checkbox", "Play the Haliluya sound when new mail arrives");
testName("comboinend", "This day was sunny");
testName("combo6", "This day was");
SimpleTest.finish();
}
@ -175,6 +190,12 @@
aria-labelledby="labelledby_text1 labelledby_text2">2</button>
<br/>
<!-- name from named accessible -->
<input id="labelledby_namedacc" type="checkbox"
aria-label="Data" />
<input id="input_labelledby_namedacc"
aria-labelledby="labelledby_namedacc" />
<!-- the name from subtree, mixed content -->
<span id="labelledby_mixed">no<span>more text</span></span>
<button id="btn_labelledby_mixed"
@ -286,16 +307,39 @@
</label>
</form>
<!-- A label with a nested control in the middle -->
<!-- a label with a nested control in the start, middle and end -->
<form>
<label id="comboinstart"><select id="combo3">
<option>One</option>
<option>Two</option>
</select>
day(s).
</label>
<label id="comboinmiddle">
Subscribe to
<select id="combo3" name="occupation">
<select id="combo4" name="occupation">
<option>ATOM</option>
<option>RSS</option>
</select>
feed.
</label>
<label id="comboinmiddle2" for="checkbox">Play the
<select id="combo5">
<option>Haliluya</option>
<option>Hurra</option>
</select>
sound when new mail arrives
</label>
<input id="checkbox" type="checkbox" />
<label id="comboinend">
This day was
<select id="combo6" name="occupation">
<option>sunny</option>
<option>rainy</option>
</select></label>
</form>
</body>

View File

@ -40,6 +40,10 @@
// of elements. Gets the name from text nodes of those elements.
testName("btn_labelledby_texts", "text1 text2");
// Trick cases. Self and recursive referencing.
testName("rememberHistoryDays", "Remember 3 days");
testName("historyDays", "Remember 3 days");
testName("rememberAfter", null); // XUL labels doesn't allow name from subtree
//////////////////////////////////////////////////////////////////////////
// Name from subtree (single relation labelled_by).
@ -101,7 +105,7 @@
// Get the name from anonymous label element for anonymous textbox
// (@anonid is used).
var ID = "box_label_anon1";
var box1Acc = testName(ID, "");
var box1Acc = testName(ID, null);
if (box1Acc) {
var textboxAcc = box1Acc.firstChild;
is(textboxAcc.name, "Label",
@ -111,7 +115,7 @@
// Get the name from anonymous label element for anonymous textbox
// (@anonid is used). Nested bindings.
ID = "box_label_anon2";
var box2Acc = testName(ID, "");
var box2Acc = testName(ID, null);
if (box2Acc) {
var textboxAcc = box2Acc.firstChild;
is(textboxAcc.name, "Label",
@ -195,6 +199,14 @@
<button id="btn_labelledby_texts"
aria-labelledby="labelledby_text1 labelledby_text2"/>
<!-- trick aria-labelledby -->
<checkbox id="rememberHistoryDays"
label="Remember "
aria-labelledby="rememberHistoryDays historyDays rememberAfter"/>
<textbox id="historyDays" type="number" size="3" value="3"
aria-labelledby="rememberHistoryDays historyDays rememberAfter"/>
<label id="rememberAfter">days</label>
<!-- the name from subtree, mixed content -->
<description id="labelledby_mixed">
no<description>more text</description>

View File

@ -47,8 +47,8 @@
names = [
"Search in: Newsticker", // label
"Search in:", // text leaf
"Search in: Newsticker", // combobox
"Search in:", // combobox_list
"Search in:", // combobox
"Search in: Newsticker", // combobox_list
"Newsticker", // option 1
"Entire site" // Option 2
];