Bug 573469 - part2, cache IDs pointed by relation attributes, r=marcoz, davidb, sr=neil, a=blockingFinal+

This commit is contained in:
Alexander Surkov 2010-11-17 21:55:44 -05:00
parent c5e8166d7a
commit 7871888c61
10 changed files with 467 additions and 42 deletions

View File

@ -37,10 +37,12 @@
#include "AccIterator.h"
#include "nsAccessibilityService.h"
#include "nsAccessible.h"
////////////////////////////////////////////////////////////////////////////////
// nsAccIterator
// AccIterator
////////////////////////////////////////////////////////////////////////////////
AccIterator::AccIterator(nsAccessible *aAccessible,
filters::FilterFuncPtr aFilterFunc,
@ -93,3 +95,45 @@ AccIterator::IteratorState::IteratorState(nsAccessible *aParent,
mParent(aParent), mIndex(0), mParentState(mParentState)
{
}
////////////////////////////////////////////////////////////////////////////////
// RelatedAccIterator
////////////////////////////////////////////////////////////////////////////////
RelatedAccIterator::
RelatedAccIterator(nsDocAccessible* aDocument, nsIContent* aDependentContent,
nsIAtom* aRelAttr) :
mRelAttr(aRelAttr), mProviders(nsnull), mBindingParent(nsnull), mIndex(0)
{
mBindingParent = aDependentContent->GetBindingParent();
nsIAtom* IDAttr = mBindingParent ?
nsAccessibilityAtoms::anonid : aDependentContent->GetIDAttributeName();
nsAutoString id;
if (aDependentContent->GetAttr(kNameSpaceID_None, IDAttr, id))
mProviders = aDocument->mDependentIDsHash.Get(id);
}
nsAccessible*
RelatedAccIterator::Next()
{
if (!mProviders)
return nsnull;
while (mIndex < mProviders->Length()) {
nsDocAccessible::AttrRelProvider* provider = (*mProviders)[mIndex++];
// Return related accessible for the given attribute and if the provider
// content is in the same binding in the case of XBL usage.
if (provider->mRelAttr == mRelAttr &&
(!mBindingParent ||
mBindingParent == provider->mContent->GetBindingParent())) {
nsAccessible* related = GetAccService()->GetAccessible(provider->mContent);
if (related)
return related;
}
}
return nsnull;
}

View File

@ -40,6 +40,7 @@
#include "filters.h"
#include "nscore.h"
#include "nsDocAccessible.h"
/**
* Allows to iterate through accessible children or subtree complying with
@ -93,4 +94,41 @@ private:
IteratorState *mState;
};
/**
* Allows to traverse through related accessibles that are pointing to the given
* dependent accessible by relation attribute.
*/
class RelatedAccIterator
{
public:
/**
* Constructor.
*
* @param aDocument [in] the document accessible the related
* & accessibles belong to.
* @param aDependentContent [in] the content of dependent accessible that
* relations were requested for
* @param aRelAttr [in] relation attribute that relations are
* pointed by
*/
RelatedAccIterator(nsDocAccessible* aDocument, nsIContent* aDependentContent,
nsIAtom* aRelAttr);
/**
* Return next related accessible for the given dependent accessible.
*/
nsAccessible* Next();
private:
RelatedAccIterator();
RelatedAccIterator(const RelatedAccIterator&);
RelatedAccIterator& operator = (const RelatedAccIterator&);
nsIAtom* mRelAttr;
nsDocAccessible::AttrRelProviderArray* mProviders;
nsIContent* mBindingParent;
PRUint32 mIndex;
};
#endif

View File

@ -2037,25 +2037,28 @@ nsAccessible::GetRelationByType(PRUint32 aRelationType,
// Relationships are defined on the same content node that the role would be
// defined on.
nsresult rv;
nsresult rv = NS_OK_NO_RELATION_TARGET;
switch (aRelationType)
{
case nsIAccessibleRelation::RELATION_LABEL_FOR:
{
RelatedAccIterator iter(GetDocAccessible(), mContent,
nsAccessibilityAtoms::aria_labelledby);
nsAccessible* related = nsnull;
while ((related = iter.Next())) {
rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mContent->Tag() == nsAccessibilityAtoms::label) {
nsIAtom *IDAttr = mContent->IsHTML() ?
nsAccessibilityAtoms::_for : nsAccessibilityAtoms::control;
rv = nsRelUtils::
AddTargetFromIDRefAttr(aRelationType, aRelation, mContent, IDAttr);
NS_ENSURE_SUCCESS(rv, rv);
if (rv != NS_OK_NO_RELATION_TARGET)
return NS_OK; // XXX bug 381599, avoid performance problems
}
return nsRelUtils::
AddTargetFromNeighbour(aRelationType, aRelation, mContent,
nsAccessibilityAtoms::aria_labelledby);
return rv;
}
case nsIAccessibleRelation::RELATION_LABELLED_BY:
@ -2091,13 +2094,14 @@ nsAccessible::GetRelationByType(PRUint32 aRelationType,
case nsIAccessibleRelation::RELATION_DESCRIPTION_FOR:
{
rv = nsRelUtils::
AddTargetFromNeighbour(aRelationType, aRelation, mContent,
nsAccessibilityAtoms::aria_describedby);
NS_ENSURE_SUCCESS(rv, rv);
RelatedAccIterator iter(GetDocAccessible(), mContent,
nsAccessibilityAtoms::aria_describedby);
if (rv != NS_OK_NO_RELATION_TARGET)
return NS_OK; // XXX bug 381599, avoid performance problems
nsAccessible* related = nsnull;
while ((related = iter.Next())) {
rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mContent->Tag() == nsAccessibilityAtoms::description &&
mContent->IsXUL()) {
@ -2109,18 +2113,23 @@ nsAccessible::GetRelationByType(PRUint32 aRelationType,
nsAccessibilityAtoms::control);
}
return NS_OK;
return rv;
}
case nsIAccessibleRelation::RELATION_NODE_CHILD_OF:
{
rv = nsRelUtils::
AddTargetFromNeighbour(aRelationType, aRelation, mContent,
nsAccessibilityAtoms::aria_owns);
NS_ENSURE_SUCCESS(rv, rv);
RelatedAccIterator iter(GetDocAccessible(), mContent,
nsAccessibilityAtoms::aria_owns);
nsAccessible* related = nsnull;
while ((related = iter.Next())) {
rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
NS_ENSURE_SUCCESS(rv, rv);
}
// Got relation from aria-owns, don't calculate it from native markup.
if (rv != NS_OK_NO_RELATION_TARGET)
return NS_OK; // XXX bug 381599, avoid performance problems
return NS_OK;
// This is an ARIA tree or treegrid that doesn't use owns, so we need to
// get the parent the hard way.
@ -2153,14 +2162,20 @@ nsAccessible::GetRelationByType(PRUint32 aRelationType,
}
}
return NS_OK;
return rv;
}
case nsIAccessibleRelation::RELATION_CONTROLLED_BY:
{
return nsRelUtils::
AddTargetFromNeighbour(aRelationType, aRelation, mContent,
nsAccessibilityAtoms::aria_controls);
RelatedAccIterator iter(GetDocAccessible(), mContent,
nsAccessibilityAtoms::aria_controls);
nsAccessible* related = nsnull;
while ((related = iter.Next())) {
rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
NS_ENSURE_SUCCESS(rv, rv);
}
return rv;
}
case nsIAccessibleRelation::RELATION_CONTROLLER_FOR:
@ -2188,9 +2203,15 @@ nsAccessible::GetRelationByType(PRUint32 aRelationType,
case nsIAccessibleRelation::RELATION_FLOWS_FROM:
{
return nsRelUtils::
AddTargetFromNeighbour(aRelationType, aRelation, mContent,
nsAccessibilityAtoms::aria_flowto);
RelatedAccIterator iter(GetDocAccessible(), mContent,
nsAccessibilityAtoms::aria_flowto);
nsAccessible* related = nsnull;
while ((related = iter.Next())) {
rv = nsRelUtils::AddTarget(aRelationType, aRelation, related);
NS_ENSURE_SUCCESS(rv, rv);
}
return rv;
}
case nsIAccessibleRelation::RELATION_DEFAULT_BUTTON:

View File

@ -51,7 +51,6 @@
#include "nsStringGlue.h"
#include "nsTArray.h"
#include "nsRefPtrHashtable.h"
#include "nsDataHashtable.h"
class AccGroupInfo;
class EmbeddedObjCollector;
@ -67,8 +66,6 @@ class nsIView;
typedef nsRefPtrHashtable<nsVoidPtrHashKey, nsAccessible>
nsAccessibleHashtable;
typedef nsDataHashtable<nsPtrHashKey<const nsINode>, nsAccessible*>
NodeToAccessibleMap;
// see nsAccessible::GetAttrValue
#define NS_OK_NO_ARIA_VALUE \

View File

@ -85,6 +85,16 @@ namespace dom = mozilla::dom;
PRUint32 nsDocAccessible::gLastFocusedAccessiblesState = 0;
static nsIAtom** kRelationAttrs[] =
{
&nsAccessibilityAtoms::aria_labelledby,
&nsAccessibilityAtoms::aria_describedby,
&nsAccessibilityAtoms::aria_owns,
&nsAccessibilityAtoms::aria_controls,
&nsAccessibilityAtoms::aria_flowto
};
static const PRUint32 kRelationAttrsLen = NS_ARRAY_LENGTH(kRelationAttrs);
////////////////////////////////////////////////////////////////////////////////
// Constructor/desctructor
@ -95,6 +105,7 @@ nsDocAccessible::
nsHyperTextAccessibleWrap(aRootContent, aShell),
mDocument(aDocument), mScrollPositionChangedTicks(0), mIsLoaded(PR_FALSE)
{
mDependentIDsHash.Init();
// XXX aaronl should we use an algorithm for the initial cache size?
mAccessibleCache.Init(kDefaultCacheSize);
mNodeToAccessibleMap.Init(kDefaultCacheSize);
@ -134,6 +145,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventQueue)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildDocuments)
tmp->mDependentIDsHash.Clear();
tmp->mNodeToAccessibleMap.Clear();
ClearCache(tmp->mAccessibleCache);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -664,6 +676,7 @@ nsDocAccessible::Shutdown()
mWeakShell = nsnull; // Avoid reentrancy
mDependentIDsHash.Clear();
mNodeToAccessibleMap.Clear();
ClearCache(mAccessibleCache);
@ -929,10 +942,19 @@ nsDocAccessible::AttributeWillChange(nsIDocument *aDocument,
PRInt32 aNameSpaceID,
nsIAtom* aAttribute, PRInt32 aModType)
{
// XXX TODO: bugs 381599 467143 472142 472143
// XXX TODO: bugs 381599 (partially fixed by 573469), 467143, 472142, 472143.
// Here we will want to cache whatever state we are potentially interested in,
// such as the existence of aria-pressed for button (so we know if we need to
// newly expose it as a toggle button) etc.
// Update dependent IDs cache.
if (aModType == nsIDOMMutationEvent::MODIFICATION ||
aModType == nsIDOMMutationEvent::REMOVAL) {
nsAccessible* accessible =
GetAccService()->GetAccessibleInWeakShell(aElement, mWeakShell);
if (accessible)
RemoveDependentIDsFor(accessible, aAttribute);
}
}
void
@ -943,6 +965,16 @@ nsDocAccessible::AttributeChanged(nsIDocument *aDocument,
{
AttributeChangedImpl(aElement, aNameSpaceID, aAttribute);
// Update dependent IDs cache.
if (aModType == nsIDOMMutationEvent::MODIFICATION ||
aModType == nsIDOMMutationEvent::ADDITION) {
nsAccessible* accessible =
GetAccService()->GetAccessibleInWeakShell(aElement, mWeakShell);
if (accessible)
AddDependentIDsFor(accessible, aAttribute);
}
// If it was the focused node, cache the new state
if (aElement == gLastFocusedNode) {
nsAccessible *focusedAccessible = GetAccService()->GetAccessible(aElement);
@ -1362,6 +1394,7 @@ nsDocAccessible::BindToDocument(nsAccessible* aAccessible,
}
aAccessible->SetRoleMapEntry(aRoleMapEntry);
AddDependentIDsFor(aAccessible);
return true;
}
@ -1373,6 +1406,8 @@ nsDocAccessible::UnbindFromDocument(nsAccessible* aAccessible)
mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
mNodeToAccessibleMap.Remove(aAccessible->GetNode());
RemoveDependentIDsFor(aAccessible);
#ifdef DEBUG
NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
"Unbinding the unbound accessible!");
@ -1557,6 +1592,84 @@ nsDocAccessible::RecreateAccessible(nsINode* aNode)
////////////////////////////////////////////////////////////////////////////////
// Protected members
void
nsDocAccessible::AddDependentIDsFor(nsAccessible* aRelProvider,
nsIAtom* aRelAttr)
{
for (PRUint32 idx = 0; idx < kRelationAttrsLen; idx++) {
nsIAtom* relAttr = *kRelationAttrs[idx];
if (aRelAttr && aRelAttr != relAttr)
continue;
IDRefsIterator iter(aRelProvider->GetContent(), relAttr);
while (true) {
const nsDependentSubstring id = iter.NextID();
if (id.IsEmpty())
break;
AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
if (!providers) {
providers = new AttrRelProviderArray();
if (providers) {
if (!mDependentIDsHash.Put(id, providers)) {
delete providers;
providers = nsnull;
}
}
}
if (providers) {
AttrRelProvider* provider =
new AttrRelProvider(relAttr, aRelProvider->GetContent());
if (provider)
providers->AppendElement(provider);
}
}
// If the relation attribute is given then we don't have anything else to
// check.
if (aRelAttr)
break;
}
}
void
nsDocAccessible::RemoveDependentIDsFor(nsAccessible* aRelProvider,
nsIAtom* aRelAttr)
{
for (PRUint32 idx = 0; idx < kRelationAttrsLen; idx++) {
nsIAtom* relAttr = *kRelationAttrs[idx];
if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
continue;
IDRefsIterator iter(aRelProvider->GetContent(), relAttr);
while (true) {
const nsDependentSubstring id = iter.NextID();
if (id.IsEmpty())
break;
AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
if (providers) {
for (PRUint32 jdx = 0; jdx < providers->Length(); ) {
AttrRelProvider* provider = (*providers)[jdx];
if (provider->mRelAttr == relAttr &&
provider->mContent == aRelProvider->GetContent())
providers->RemoveElement(provider);
else
jdx++;
}
if (providers->Length() == 0)
mDependentIDsHash.Remove(id);
}
}
// If the relation attribute is given then we don't have anything else to
// check.
if (aRelAttr)
break;
}
}
void
nsDocAccessible::FireValueChangeForTextFields(nsAccessible *aAccessible)
{

View File

@ -44,6 +44,8 @@
#include "nsHyperTextAccessibleWrap.h"
#include "nsEventShell.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
#include "nsIDocument.h"
#include "nsIDocumentObserver.h"
#include "nsIEditor.h"
@ -267,6 +269,28 @@ protected:
mChildDocuments.RemoveElement(aChildDocument);
}
/**
* Add dependent IDs pointed by accessible element by relation attribute to
* cache. If the relation attribute is missed then all relation attributes
* are checked.
*
* @param aRelProvider [in] accessible that element has relation attribute
* @param aRelAttr [in, optional] relation attribute
*/
void AddDependentIDsFor(nsAccessible* aRelProvider,
nsIAtom* aRelAttr = nsnull);
/**
* Remove dependent IDs pointed by accessible element by relation attribute
* from cache. If the relation attribute is absent then all relation
* attributes are checked.
*
* @param aRelProvider [in] accessible that element has relation attribute
* @param aRelAttr [in, optional] relation attribute
*/
void RemoveDependentIDsFor(nsAccessible* aRelProvider,
nsIAtom* aRelAttr = nsnull);
static void ScrollTimerCallback(nsITimer *aTimer, void *aClosure);
/**
@ -299,14 +323,6 @@ protected:
CharacterDataChangeInfo* aInfo,
PRBool aIsInserted);
/**
* Used to define should the event be fired on a delay.
*/
enum EEventFiringType {
eNormalEvent,
eDelayedEvent
};
/**
* Fire a value change event for the the given accessible if it is a text
* field (has a ROLE_ENTRY).
@ -347,7 +363,8 @@ protected:
* Cache of accessibles within this document accessible.
*/
nsAccessibleHashtable mAccessibleCache;
NodeToAccessibleMap mNodeToAccessibleMap;
nsDataHashtable<nsPtrHashKey<const nsINode>, nsAccessible*>
mNodeToAccessibleMap;
nsCOMPtr<nsIDocument> mDocument;
nsCOMPtr<nsITimer> mScrollWatchTimer;
@ -366,9 +383,35 @@ protected:
static nsIAtom *gLastFocusedFrameType;
nsTArray<nsRefPtr<nsDocAccessible> > mChildDocuments;
/**
* A storage class for pairing content with one of its relation attributes.
*/
class AttrRelProvider
{
public:
AttrRelProvider(nsIAtom* aRelAttr, nsIContent* aContent) :
mRelAttr(aRelAttr), mContent(aContent) { }
nsIAtom* mRelAttr;
nsIContent* mContent;
private:
AttrRelProvider();
AttrRelProvider(const AttrRelProvider&);
AttrRelProvider& operator =(const AttrRelProvider&);
};
/**
* The cache of IDs pointed by relation attributes.
*/
typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
nsClassHashtable<nsStringHashKey, AttrRelProviderArray> mDependentIDsHash;
friend class RelatedAccIterator;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsDocAccessible,
NS_DOCACCESSIBLE_IMPL_CID)
#endif
#endif

View File

@ -50,6 +50,7 @@ _TEST_FILES =\
test_general.xul \
test_tabbrowser.xul \
test_tree.xul \
test_update.html \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -65,6 +65,9 @@
testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
// aria-controls
getAccessible("tab");
todo(false,
"Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");

View File

@ -66,6 +66,9 @@
testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
// aria-controls
getAccessible("tab");
todo(false,
"Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");

View File

@ -0,0 +1,162 @@
<html>
<head>
<title>Test updating of accessible relations</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../relations.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript">
function testRelated(aRelAttr, aHostRelation, aDependentRelation)
{
// no attribute
testRelation("dependent1", aDependentRelation, null);
testRelation("dependent2", aDependentRelation, null);
if (aHostRelation)
testRelation("host", aHostRelation, null);
// set attribute
getNode("host").setAttribute(aRelAttr, "dependent1");
testRelation("dependent1", aDependentRelation, "host");
testRelation("dependent2", aDependentRelation, null);
if (aHostRelation)
testRelation("host", aHostRelation, "dependent1");
// change attribute
getNode("host").setAttribute(aRelAttr, "dependent2");
testRelation("dependent1", aDependentRelation, null);
testRelation("dependent2", aDependentRelation, "host");
if (aHostRelation)
testRelation("host", aHostRelation, "dependent2");
// remove attribute
getNode("host").removeAttribute(aRelAttr);
testRelation("dependent1", aDependentRelation, null);
testRelation("dependent2", aDependentRelation, null);
if (aHostRelation)
testRelation("host", aHostRelation, null);
}
function insertRelated(aHostRelAttr, aDependentID, aInsertHostFirst,
aHostRelation, aDependentRelation)
{
this.eventSeq = [
new invokerChecker(EVENT_REORDER, document)
];
this.invoke = function insertRelated_invoke()
{
this.hostNode = document.createElement("div");
this.hostNode.setAttribute(aHostRelAttr, aDependentID);
this.dependentNode = document.createElement("div");
this.dependentNode.setAttribute("id", aDependentID);
if (aInsertHostFirst) {
document.body.appendChild(this.hostNode);
document.body.appendChild(this.dependentNode);
} else {
document.body.appendChild(this.dependentNode);
document.body.appendChild(this.hostNode);
}
}
this.finalCheck = function insertRelated_finalCheck()
{
testRelation(this.dependentNode, aDependentRelation, this.hostNode);
if (aHostRelation)
testRelation(this.hostNode, aHostRelation, this.dependentNode);
}
this.getID = function insertRelated_getID()
{
return "Insert " + aHostRelAttr + "='" + aDependentID + "' node" +
(aInsertHostFirst ? " before" : "after") + " dependent node";
}
}
var gQueue = null;
function doTest()
{
// Relation updates on ARIA attribute changes.
testRelated("aria-labelledby", RELATION_LABELLED_BY, RELATION_LABEL_FOR);
testRelated("aria-describedby",
RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR);
testRelated("aria-owns", null, RELATION_NODE_CHILD_OF);
testRelated("aria-controls",
RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY);
testRelated("aria-flowto", RELATION_FLOWS_TO, RELATION_FLOWS_FROM);
// Insert related accessibles into tree.
gQueue = new eventQueue();
gQueue.push(new insertRelated("aria-labelledby", "dependent3", true,
RELATION_LABELLED_BY, RELATION_LABEL_FOR));
gQueue.push(new insertRelated("aria-labelledby", "dependent4", false,
RELATION_LABELLED_BY, RELATION_LABEL_FOR));
gQueue.push(new insertRelated("aria-describedby", "dependent5", true,
RELATION_DESCRIBED_BY,
RELATION_DESCRIPTION_FOR));
gQueue.push(new insertRelated("aria-describedby", "dependent6", false,
RELATION_DESCRIBED_BY,
RELATION_DESCRIPTION_FOR));
gQueue.push(new insertRelated("aria-owns", "dependent7", true,
null, RELATION_NODE_CHILD_OF));
gQueue.push(new insertRelated("aria-owns", "dependent8", false,
null, RELATION_NODE_CHILD_OF));
gQueue.push(new insertRelated("aria-controls", "dependent9", true,
RELATION_CONTROLLER_FOR,
RELATION_CONTROLLED_BY));
gQueue.push(new insertRelated("aria-controls", "dependent10", false,
RELATION_CONTROLLER_FOR,
RELATION_CONTROLLED_BY));
gQueue.push(new insertRelated("aria-flowto", "dependent11", true,
RELATION_FLOWS_TO, RELATION_FLOWS_FROM));
gQueue.push(new insertRelated("aria-flowto", "dependent12", false,
RELATION_FLOWS_TO, RELATION_FLOWS_FROM));
gQueue.invoke(); // will call SimpleTest.finish()
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=573469"
title="Cache relations defined by ARIA attributes">
Mozilla Bug 573469
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="dependent1">label</div>
<div id="dependent2">label2</div>
<div role="checkbox" id="host"></div>
</body>
</html>