Bug 761853 - ARIA grid with rowgroup breaks table row/col counting and indices, r=tbsaunde

--HG--
rename : accessible/src/base/filters.cpp => accessible/src/base/AccFilters.cpp
rename : accessible/src/base/filters.h => accessible/src/base/AccFilters.h
This commit is contained in:
Alexander Surkov 2012-08-28 22:13:59 +09:00
parent fa9b9c167a
commit e7d291b1a2
18 changed files with 306 additions and 190 deletions

View File

@ -6,6 +6,8 @@
#include "Accessible.h"
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// nsAccCollector
////////////////////////////////////////////////////////////////////////////////
@ -56,7 +58,7 @@ AccCollector::EnsureNGetObject(uint32_t aIndex)
uint32_t childCount = mRoot->ChildCount();
while (mRootChildIdx < childCount) {
Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
if (!mFilterFunc(child))
if (!(mFilterFunc(child) & filters::eMatch))
continue;
AppendObject(child);
@ -73,7 +75,7 @@ AccCollector::EnsureNGetIndex(Accessible* aAccessible)
uint32_t childCount = mRoot->ChildCount();
while (mRootChildIdx < childCount) {
Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
if (!mFilterFunc(child))
if (!(mFilterFunc(child) & filters::eMatch))
continue;
AppendObject(child);
@ -103,7 +105,8 @@ EmbeddedObjCollector::GetIndexAt(Accessible* aAccessible)
if (aAccessible->mIndexOfEmbeddedChild != -1)
return aAccessible->mIndexOfEmbeddedChild;
return mFilterFunc(aAccessible) ? EnsureNGetIndex(aAccessible) : -1;
return mFilterFunc(aAccessible) & filters::eMatch ?
EnsureNGetIndex(aAccessible) : -1;
}
void

View File

@ -2,14 +2,18 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef AccCollector_h_
#define AccCollector_h_
#ifndef mozilla_a11y_AccCollector_h__
#define mozilla_a11y_AccCollector_h__
#include "filters.h"
#include "AccFilters.h"
#include "nscore.h"
#include "nsTArray.h"
class Accessible;
namespace mozilla {
namespace a11y {
/**
* Collect accessible children complying with filter function. Provides quick
* access to accessible by index.
@ -82,7 +86,10 @@ protected:
virtual void AppendObject(Accessible* aAccessible);
friend class Accessible;
friend class ::Accessible;
};
} // namespace a11y
} // namespace mozilla
#endif

View File

@ -0,0 +1,60 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AccFilters.h"
#include "Accessible-inl.h"
#include "nsAccUtils.h"
#include "Role.h"
#include "States.h"
using namespace mozilla::a11y;
using namespace mozilla::a11y::filters;
uint32_t
filters::GetSelected(Accessible* aAccessible)
{
if (aAccessible->State() & states::SELECTED)
return eMatch | eSkipSubtree;
return eSkip;
}
uint32_t
filters::GetSelectable(Accessible* aAccessible)
{
if (aAccessible->InteractiveState() & states::SELECTABLE)
return eMatch | eSkipSubtree;
return eSkip;
}
uint32_t
filters::GetRow(Accessible* aAccessible)
{
a11y::role role = aAccessible->Role();
if (role == roles::ROW)
return eMatch | eSkipSubtree;
// Look for rows inside rowgroup.
if (role == roles::SECTION)
return eSkip;
return eSkipSubtree;
}
uint32_t
filters::GetCell(Accessible* aAccessible)
{
a11y::role role = aAccessible->Role();
return role == roles::GRID_CELL || role == roles::ROWHEADER ||
role == roles::COLUMNHEADER ? eMatch : eSkipSubtree;
}
uint32_t
filters::GetEmbeddedObject(Accessible* aAccessible)
{
return nsAccUtils::IsEmbeddedObject(aAccessible) ?
eMatch | eSkipSubtree : eSkipSubtree;
}

View File

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_a11y_Filters_h__
#define mozilla_a11y_Filters_h__
#include "mozilla/StandardInteger.h"
class Accessible;
/**
* Predefined filters used for nsAccIterator and nsAccCollector.
*/
namespace mozilla {
namespace a11y {
namespace filters {
enum EResult {
eSkip = 0,
eMatch = 1,
eSkipSubtree = 2
};
/**
* Return true if the traversed accessible complies with filter.
*/
typedef uint32_t (*FilterFuncPtr) (Accessible*);
/**
* Matches selected/selectable accessibles in subtree.
*/
uint32_t GetSelected(Accessible* aAccessible);
uint32_t GetSelectable(Accessible* aAccessible);
/**
* Matches row accessibles in subtree.
*/
uint32_t GetRow(Accessible* aAccessible);
/**
* Matches cell accessibles in children.
*/
uint32_t GetCell(Accessible* aAccessible);
/**
* Matches embedded objects in children.
*/
uint32_t GetEmbeddedObject(Accessible* aAccessible);
} // namespace filters
} // namespace a11y
} // namespace mozilla
#endif

View File

@ -18,9 +18,8 @@ using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
AccIterator::AccIterator(Accessible* aAccessible,
filters::FilterFuncPtr aFilterFunc,
IterationType aIterationType) :
mFilterFunc(aFilterFunc), mIsDeep(aIterationType != eFlatNav)
filters::FilterFuncPtr aFilterFunc) :
mFilterFunc(aFilterFunc)
{
mState = new IteratorState(aAccessible);
}
@ -40,19 +39,19 @@ AccIterator::Next()
while (mState) {
Accessible* child = mState->mParent->GetChildAt(mState->mIndex++);
if (!child) {
IteratorState *tmp = mState;
IteratorState* tmp = mState;
mState = mState->mParentState;
delete tmp;
continue;
}
bool isComplying = mFilterFunc(child);
if (isComplying)
uint32_t result = mFilterFunc(child);
if (result & filters::eMatch)
return child;
if (mIsDeep) {
IteratorState *childState = new IteratorState(child, mState);
if (!(result & filters::eSkipSubtree)) {
IteratorState* childState = new IteratorState(child, mState);
mState = childState;
}
}

View File

@ -4,13 +4,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsAccIterator_h_
#define nsAccIterator_h_
#ifndef mozilla_a11y_AccIterator_h__
#define mozilla_a11y_AccIterator_h__
#include "nsAccessibilityService.h"
#include "filters.h"
#include "nscore.h"
#include "DocAccessible.h"
#include "AccFilters.h"
#include "nsAccessibilityService.h"
namespace mozilla {
namespace a11y {
/**
* AccIterable is a basic interface for iterators over accessibles.
@ -33,24 +35,7 @@ private:
class AccIterator : public AccIterable
{
public:
/**
* Used to define iteration type.
*/
enum IterationType {
/**
* Navigation happens through direct children.
*/
eFlatNav,
/**
* Navigation through subtree excluding iterator root; if the accessible
* complies with filter, iterator ignores its children.
*/
eTreeNav
};
AccIterator(Accessible* aRoot, filters::FilterFuncPtr aFilterFunc,
IterationType aIterationType = eFlatNav);
AccIterator(Accessible* aRoot, filters::FilterFuncPtr aFilterFunc);
virtual ~AccIterator();
/**
@ -70,12 +55,11 @@ private:
Accessible* mParent;
int32_t mIndex;
IteratorState *mParentState;
IteratorState* mParentState;
};
filters::FilterFuncPtr mFilterFunc;
bool mIsDeep;
IteratorState *mState;
IteratorState* mState;
};
@ -282,4 +266,7 @@ private:
nsRefPtr<Accessible> mAcc;
};
} // namespace a11y
} // namespace mozilla
#endif

View File

@ -19,8 +19,8 @@ CPPSRCS = \
AccEvent.cpp \
AccGroupInfo.cpp \
AccIterator.cpp \
AccFilters.cpp \
ARIAStateMap.cpp \
filters.cpp \
FocusManager.cpp \
NotificationController.cpp \
nsAccDocManager.cpp \

View File

@ -19,11 +19,12 @@ namespace a11y {
*/
struct RelationCopyHelper
{
RelationCopyHelper(AccIterable* aFirstIter, AccIterable* aLastIter) :
RelationCopyHelper(mozilla::a11y::AccIterable* aFirstIter,
mozilla::a11y::AccIterable* aLastIter) :
mFirstIter(aFirstIter), mLastIter(aLastIter) { }
AccIterable* mFirstIter;
AccIterable* mLastIter;
mozilla::a11y::AccIterable* mFirstIter;
mozilla::a11y::AccIterable* mLastIter;
};
/**
@ -38,7 +39,8 @@ public:
Relation(const RelationCopyHelper aRelation) :
mFirstIter(aRelation.mFirstIter), mLastIter(aRelation.mLastIter) { }
Relation(AccIterable* aIter) : mFirstIter(aIter), mLastIter(aIter) { }
Relation(mozilla::a11y::AccIterable* aIter) :
mFirstIter(aIter), mLastIter(aIter) { }
Relation(Accessible* aAcc) :
mFirstIter(nullptr), mLastIter(nullptr)
@ -67,7 +69,7 @@ public:
return RelationCopyHelper(mFirstIter.forget(), mLastIter);
}
inline void AppendIter(AccIterable* aIter)
inline void AppendIter(mozilla::a11y::AccIterable* aIter)
{
if (mLastIter)
mLastIter->mNextIter = aIter;
@ -83,7 +85,7 @@ public:
inline void AppendTarget(Accessible* aAcc)
{
if (aAcc)
AppendIter(new SingleAccIterator(aAcc));
AppendIter(new mozilla::a11y::SingleAccIterator(aAcc));
}
/**
@ -116,8 +118,8 @@ public:
private:
Relation& operator = (const Relation&);
nsAutoPtr<AccIterable> mFirstIter;
AccIterable* mLastIter;
nsAutoPtr<mozilla::a11y::AccIterable> mFirstIter;
mozilla::a11y::AccIterable* mLastIter;
};
} // namespace a11y

View File

@ -1,44 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "filters.h"
#include "Accessible-inl.h"
#include "nsAccUtils.h"
#include "Role.h"
#include "States.h"
using namespace mozilla::a11y;
bool
filters::GetSelected(Accessible* aAccessible)
{
return aAccessible->State() & states::SELECTED;
}
bool
filters::GetSelectable(Accessible* aAccessible)
{
return aAccessible->InteractiveState() & states::SELECTABLE;
}
bool
filters::GetRow(Accessible* aAccessible)
{
return aAccessible->Role() == roles::ROW;
}
bool
filters::GetCell(Accessible* aAccessible)
{
roles::Role role = aAccessible->Role();
return role == roles::GRID_CELL || role == roles::ROWHEADER ||
role == roles::COLUMNHEADER;
}
bool
filters::GetEmbeddedObject(Accessible* aAccessible)
{
return nsAccUtils::IsEmbeddedObject(aAccessible);
}

View File

@ -1,27 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef a11yFilters_h_
#define a11yFilters_h_
class Accessible;
/**
* Predefined filters used for nsAccIterator and nsAccCollector.
*/
namespace filters {
/**
* Return true if the traversed accessible complies with filter.
*/
typedef bool (*FilterFuncPtr) (Accessible*);
bool GetSelected(Accessible* aAccessible);
bool GetSelectable(Accessible* aAccessible);
bool GetRow(Accessible* aAccessible);
bool GetCell(Accessible* aAccessible);
bool GetEmbeddedObject(Accessible* aAccessible);
}
#endif

View File

@ -103,7 +103,7 @@ public:
* these accessibles share the same DOM node. The primary accessible "owns"
* that DOM node in terms it gets stored in the accessible to node map.
*/
virtual bool IsPrimaryForNode() const;
virtual bool IsPrimaryForNode() const;//hello
/**
* Interface methods on nsIAccessible shared with ISimpleDOM.

View File

@ -0,0 +1,53 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_a11y_ARIAGridAccessible_inl_h__
#define mozilla_a11y_ARIAGridAccessible_inl_h__
#include "ARIAGridAccessible.h"
#include "AccIterator.h"
inline Accessible*
mozilla::a11y::ARIAGridCellAccessible::TableFor(Accessible* aRow) const
{
if (aRow) {
Accessible* table = aRow->Parent();
if (table) {
roles::Role tableRole = table->Role();
if (tableRole == roles::SECTION) { // if there's a rowgroup.
table = table->Parent();
if (table)
tableRole = table->Role();
}
return tableRole == roles::TABLE || tableRole == roles::TREE_TABLE ?
table : nullptr;
}
}
return nullptr;
}
inline int32_t
mozilla::a11y::ARIAGridCellAccessible::RowIndexFor(Accessible* aRow) const
{
Accessible* table = TableFor(aRow);
if (table) {
int32_t rowIdx = 0;
Accessible* row = nullptr;
AccIterator rowIter(table, filters::GetRow);
while ((row = rowIter.Next()) && row != aRow)
rowIdx++;
if (row)
return rowIdx;
}
return -1;
}
#endif

View File

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ARIAGridAccessible.h"
#include "ARIAGridAccessible-inl.h"
#include "Accessible-inl.h"
#include "AccIterator.h"
@ -551,19 +551,10 @@ ARIAGridCellAccessible::GetTable(nsIAccessibleTable** aTable)
NS_ENSURE_ARG_POINTER(aTable);
*aTable = nullptr;
Accessible* thisRow = Parent();
if (!thisRow || thisRow->Role() != roles::ROW)
return NS_OK;
Accessible* table = TableFor(Row());
if (table)
CallQueryInterface(table, aTable);
Accessible* table = thisRow->Parent();
if (!table)
return NS_OK;
roles::Role tableRole = table->Role();
if (tableRole != roles::TABLE && tableRole != roles::TREE_TABLE)
return NS_OK;
CallQueryInterface(table, aTable);
return NS_OK;
}
@ -603,23 +594,7 @@ ARIAGridCellAccessible::GetRowIndex(int32_t* aRowIndex)
if (IsDefunct())
return NS_ERROR_FAILURE;
Accessible* row = Parent();
if (!row)
return NS_OK;
Accessible* table = row->Parent();
if (!table)
return NS_OK;
*aRowIndex = 0;
int32_t indexInTable = row->IndexInParent();
for (int32_t idx = 0; idx < indexInTable; idx++) {
row = table->GetChildAt(idx);
if (row->Role() == roles::ROW)
(*aRowIndex)++;
}
*aRowIndex = RowIndexFor(Row());
return NS_OK;
}
@ -738,14 +713,13 @@ ARIAGridCellAccessible::GetAttributesInternal(nsIPersistentProperties* aAttribut
{
if (IsDefunct())
return NS_ERROR_FAILURE;
nsresult rv = HyperTextAccessibleWrap::GetAttributesInternal(aAttributes);
NS_ENSURE_SUCCESS(rv, rv);
// Expose "table-cell-index" attribute.
Accessible* thisRow = Parent();
if (!thisRow || thisRow->Role() != roles::ROW)
Accessible* thisRow = Row();
if (!thisRow)
return NS_OK;
int32_t colIdx = 0, colCount = 0;
@ -761,29 +735,10 @@ ARIAGridCellAccessible::GetAttributesInternal(nsIPersistentProperties* aAttribut
colCount++;
}
Accessible* table = thisRow->Parent();
if (!table)
return NS_OK;
roles::Role tableRole = table->Role();
if (tableRole != roles::TABLE && tableRole != roles::TREE_TABLE)
return NS_OK;
int32_t rowIdx = 0;
childCount = table->ChildCount();
for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
Accessible* child = table->GetChildAt(childIdx);
if (child == thisRow)
break;
if (child->Role() == roles::ROW)
rowIdx++;
}
int32_t idx = rowIdx * colCount + colIdx;
int32_t rowIdx = RowIndexFor(thisRow);
nsAutoString stringIdx;
stringIdx.AppendInt(idx);
stringIdx.AppendInt(rowIdx * colCount + colIdx);
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::tableCellIndex,
stringIdx);

View File

@ -60,6 +60,7 @@ public:
virtual void UnselectRow(uint32_t aRowIdx);
protected:
/**
* Return true if the given row index is valid.
*/
@ -114,6 +115,27 @@ public:
virtual void Shutdown();
virtual void ApplyARIAState(uint64_t* aState) const;
virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
protected:
/**
* Return a containing row.
*/
Accessible* Row() const
{
Accessible* row = Parent();
return row && row->Role() == roles::ROW ? row : nullptr;
}
/**
* Return a table for the given row.
*/
Accessible* TableFor(Accessible* aRow) const;
/**
* Return index of the given row.
*/
int32_t RowIndexFor(Accessible* aRow) const;
};
} // namespace a11y

View File

@ -2728,7 +2728,7 @@ Accessible::SelectedItems()
if (!selectedItems)
return nullptr;
AccIterator iter(this, filters::GetSelected, AccIterator::eTreeNav);
AccIterator iter(this, filters::GetSelected);
nsIAccessible* selected = nullptr;
while ((selected = iter.Next()))
selectedItems->AppendElement(selected, false);
@ -2742,7 +2742,7 @@ uint32_t
Accessible::SelectedItemCount()
{
uint32_t count = 0;
AccIterator iter(this, filters::GetSelected, AccIterator::eTreeNav);
AccIterator iter(this, filters::GetSelected);
Accessible* selected = nullptr;
while ((selected = iter.Next()))
++count;
@ -2753,7 +2753,7 @@ Accessible::SelectedItemCount()
Accessible*
Accessible::GetSelectedItem(uint32_t aIndex)
{
AccIterator iter(this, filters::GetSelected, AccIterator::eTreeNav);
AccIterator iter(this, filters::GetSelected);
Accessible* selected = nullptr;
uint32_t index = 0;
@ -2767,7 +2767,7 @@ bool
Accessible::IsItemSelected(uint32_t aIndex)
{
uint32_t index = 0;
AccIterator iter(this, filters::GetSelectable, AccIterator::eTreeNav);
AccIterator iter(this, filters::GetSelectable);
Accessible* selected = nullptr;
while ((selected = iter.Next()) && index < aIndex)
index++;
@ -2780,7 +2780,7 @@ bool
Accessible::AddItemToSelection(uint32_t aIndex)
{
uint32_t index = 0;
AccIterator iter(this, filters::GetSelectable, AccIterator::eTreeNav);
AccIterator iter(this, filters::GetSelectable);
Accessible* selected = nullptr;
while ((selected = iter.Next()) && index < aIndex)
index++;
@ -2795,7 +2795,7 @@ bool
Accessible::RemoveItemFromSelection(uint32_t aIndex)
{
uint32_t index = 0;
AccIterator iter(this, filters::GetSelectable, AccIterator::eTreeNav);
AccIterator iter(this, filters::GetSelectable);
Accessible* selected = nullptr;
while ((selected = iter.Next()) && index < aIndex)
index++;
@ -2812,7 +2812,7 @@ Accessible::SelectAll()
bool success = false;
Accessible* selectable = nullptr;
AccIterator iter(this, filters::GetSelectable, AccIterator::eTreeNav);
AccIterator iter(this, filters::GetSelectable);
while((selectable = iter.Next())) {
success = true;
selectable->SetSelected(true);
@ -2826,7 +2826,7 @@ Accessible::UnselectAll()
bool success = false;
Accessible* selected = nullptr;
AccIterator iter(this, filters::GetSelected, AccIterator::eTreeNav);
AccIterator iter(this, filters::GetSelected);
while ((selected = iter.Next())) {
success = true;
selected->SetSelected(false);

View File

@ -23,7 +23,6 @@
class AccEvent;
class AccGroupInfo;
class EmbeddedObjCollector;
class KeyBinding;
class Accessible;
class HyperTextAccessible;
@ -32,6 +31,7 @@ struct nsRoleMapEntry;
namespace mozilla {
namespace a11y {
class EmbeddedObjCollector;
class HTMLImageMapAccessible;
class HTMLLIAccessible;
class ImageAccessible;
@ -875,9 +875,9 @@ protected:
uint32_t mFlags;
friend class DocAccessible;
nsAutoPtr<EmbeddedObjCollector> mEmbeddedObjCollector;
nsAutoPtr<mozilla::a11y::EmbeddedObjCollector> mEmbeddedObjCollector;
int32_t mIndexOfEmbeddedChild;
friend class EmbeddedObjCollector;
friend class mozilla::a11y::EmbeddedObjCollector;
nsAutoPtr<AccGroupInfo> mGroupInfo;
friend class AccGroupInfo;

View File

@ -33,6 +33,14 @@ class nsAccessiblePivot;
const uint32_t kDefaultCacheSize = 256;
namespace mozilla {
namespace a11y {
class RelatedAccIterator;
} // namespace a11y
} // namespace mozilla
class DocAccessible : public HyperTextAccessibleWrap,
public nsIAccessibleDocument,
public nsIDocumentObserver,
@ -567,7 +575,7 @@ protected:
typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
nsClassHashtable<nsStringHashKey, AttrRelProviderArray> mDependentIDsHash;
friend class RelatedAccIterator;
friend class mozilla::a11y::RelatedAccIterator;
/**
* Used for our caching algorithm. We store the list of nodes that should be

View File

@ -28,6 +28,14 @@
];
testTableIndexes("grid", idxes);
idxes = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[9, 10, 11]
];
testTableIndexes("grid-rowgroups", idxes);
//////////////////////////////////////////////////////////////////////////
// a bit crazy ARIA grid
idxes = [
@ -51,6 +59,9 @@
<a target="_blank"
title="nsHTMLTableCellAccessible is used in dojo's crazy ARIA grid"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a>
<a target="_blank"
title="ARIA grid with rowgroup breaks table row/col counting and indices"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=761853">Mozilla Bug 761853</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
@ -80,6 +91,31 @@
</div>
</div>
<div role="grid" id="grid-rowgroups">
<div role="row">
<span role="columnheader">grid-rowgroups-col1</span>
<span role="columnheader">grid-rowgroups-col2</span>
<span role="columnheader">grid-rowgroups-col3</span>
</div>
<div role="rowgroup">
<div role="row">
<span role="rowheader">grid-rowgroups-row1</span>
<span role="gridcell">grid-rowgroups-cell1</span>
<span role="gridcell">grid-rowgroups-cell2</span>
</div>
<div role="row">
<span role="rowheader">grid-rowgroups-row2</span>
<span role="gridcell">grid-rowgroups-cell3</span>
<span role="gridcell">grid-rowgroups-cell4</span>
</div>
</div>
<div role="row">
<span role="rowheader">grid-rowgroups-row3</span>
<span role="gridcell">grid-rowgroups-cell5</span>
<span role="gridcell">grid-rowgroups-cell6</span>
</div>
</div>
<div role="grid" id="grid2">
<div role="row">
<table role="presentation">