Bug 1219299 - rework aria-owns implementation, r=yzen

This commit is contained in:
Alexander Surkov
2015-10-29 18:08:48 -04:00
parent 3c486b1d0b
commit f19d67034f
12 changed files with 697 additions and 309 deletions

View File

@@ -324,8 +324,14 @@ IDRefsIterator::GetElem(const nsDependentSubstring& aID)
Accessible* Accessible*
IDRefsIterator::Next() IDRefsIterator::Next()
{ {
nsIContent* nextElm = NextElem(); nsIContent* nextEl = nullptr;
return nextElm ? mDoc->GetAccessible(nextElm) : nullptr; while ((nextEl = NextElem())) {
Accessible* acc = mDoc->GetAccessible(nextEl);
if (acc) {
return acc;
}
}
return nullptr;
} }

View File

@@ -295,12 +295,16 @@ EventQueue::CoalesceReorderEvents(AccEvent* aTailEvent)
AccReorderEvent* thisReorder = downcast_accEvent(thisEvent); AccReorderEvent* thisReorder = downcast_accEvent(thisEvent);
AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent); AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
uint32_t eventType = thisReorder->IsShowHideEventTarget(tailParent); uint32_t eventType = thisReorder->IsShowHideEventTarget(tailParent);
if (eventType == nsIAccessibleEvent::EVENT_SHOW) if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
tailReorder->DoNotEmitAll(); tailReorder->DoNotEmitAll();
else if (eventType == nsIAccessibleEvent::EVENT_HIDE) }
else if (eventType == nsIAccessibleEvent::EVENT_HIDE) {
NS_ERROR("Accessible tree was modified after it was removed! Huh?"); NS_ERROR("Accessible tree was modified after it was removed! Huh?");
else }
else {
aTailEvent->mEventRule = AccEvent::eDoNotEmit; aTailEvent->mEventRule = AccEvent::eDoNotEmit;
mEvents[index].swap(mEvents[count - 1]);
}
return; return;
} }

View File

@@ -54,6 +54,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentInsertions) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentInsertions)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
@@ -86,6 +87,7 @@ NotificationController::Shutdown()
mContentInsertions.Clear(); mContentInsertions.Clear();
mNotifications.Clear(); mNotifications.Clear();
mEvents.Clear(); mEvents.Clear();
mRelocations.Clear();
} }
void void
@@ -351,6 +353,16 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
// modification are done. // modification are done.
mDocument->ProcessInvalidationList(); mDocument->ProcessInvalidationList();
// We cannot rely on DOM tree to keep aria-owns relations updated. Make
// a validation to remove dead links.
mDocument->ValidateARIAOwned();
// Process relocation list.
for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
}
mRelocations.Clear();
// If a generic notification occurs after this point then we may be allowed to // If a generic notification occurs after this point then we may be allowed to
// process it synchronously. However we do not want to reenter if fireing // process it synchronously. However we do not want to reenter if fireing
// events causes script to run. // events causes script to run.

View File

@@ -133,6 +133,16 @@ public:
nsIContent* aStartChildNode, nsIContent* aStartChildNode,
nsIContent* aEndChildNode); nsIContent* aEndChildNode);
/**
* Pend an accessible subtree relocation.
*/
void ScheduleRelocation(Accessible* aOwner)
{
if (!mRelocations.Contains(aOwner) && mRelocations.AppendElement(aOwner)) {
ScheduleProcessing();
}
}
/** /**
* Start to observe refresh to make notifications and events processing after * Start to observe refresh to make notifications and events processing after
* layout. * layout.
@@ -303,6 +313,11 @@ private:
* use SwapElements() on it. * use SwapElements() on it.
*/ */
nsTArray<RefPtr<Notification> > mNotifications; nsTArray<RefPtr<Notification> > mNotifications;
/**
* Holds all scheduled relocations.
*/
nsTArray<RefPtr<Accessible> > mRelocations;
}; };
} // namespace a11y } // namespace a11y

View File

@@ -116,7 +116,7 @@ TreeWalker::Next(ChildrenIterator* aIter, Accessible** aAccesible,
// Ignore the accessible and its subtree if it was repositioned by means of // Ignore the accessible and its subtree if it was repositioned by means of
// aria-owns. // aria-owns.
if (accessible) { if (accessible) {
if (accessible->IsRepositioned()) { if (accessible->IsRelocated()) {
*aSkipSubtree = true; *aSkipSubtree = true;
} else { } else {
*aAccesible = accessible; *aAccesible = accessible;

View File

@@ -910,13 +910,13 @@ public:
* Get/set repositioned bit indicating that the accessible was moved in * Get/set repositioned bit indicating that the accessible was moved in
* the accessible tree, i.e. the accessible tree structure differs from DOM. * the accessible tree, i.e. the accessible tree structure differs from DOM.
*/ */
bool IsRepositioned() const { return mStateFlags & eRepositioned; } bool IsRelocated() const { return mStateFlags & eRelocated; }
void SetRepositioned(bool aRepositioned) void SetRelocated(bool aRelocated)
{ {
if (aRepositioned) if (aRelocated)
mStateFlags |= eRepositioned; mStateFlags |= eRelocated;
else else
mStateFlags &= ~eRepositioned; mStateFlags &= ~eRelocated;
} }
/** /**
@@ -1009,9 +1009,9 @@ protected:
eSubtreeMutating = 1 << 6, // subtree is being mutated eSubtreeMutating = 1 << 6, // subtree is being mutated
eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events
eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them
eRepositioned = 1 << 9, // accessible was moved in tree eRelocated = 1 << 9, // accessible was moved in tree
eLastStateFlag = eRepositioned eLastStateFlag = eRelocated
}; };
/** /**

View File

@@ -130,9 +130,13 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
} }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
for (uint32_t i = 0; i < tmp->mARIAOwnsInvalidationList.Length(); ++i) { for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mARIAOwnsInvalidationList[i].mOwner) nsTArray<RefPtr<Accessible> >* ar = it.UserData();
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mARIAOwnsInvalidationList[i].mChild) for (uint32_t i = 0; i < ar->Length(); i++) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mARIAOwnsHash entry item");
cb.NoteXPCOMChild(ar->ElementAt(i));
}
} }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@@ -144,10 +148,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
tmp->mNodeToAccessibleMap.Clear(); tmp->mNodeToAccessibleMap.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache) NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm) NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
for (uint32_t i = 0; i < tmp->mARIAOwnsInvalidationList.Length(); ++i) { tmp->mARIAOwnsHash.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mARIAOwnsInvalidationList[i].mOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mARIAOwnsInvalidationList[i].mChild)
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
@@ -731,6 +732,10 @@ DocAccessible::AttributeWillChange(nsIDocument* aDocument,
if (aModType != nsIDOMMutationEvent::ADDITION) if (aModType != nsIDOMMutationEvent::ADDITION)
RemoveDependentIDsFor(accessible, aAttribute); RemoveDependentIDsFor(accessible, aAttribute);
if (aAttribute == nsGkAtoms::id) {
RelocateARIAOwnedIfNeeded(aElement);
}
// Store the ARIA attribute old value so that it can be used after // Store the ARIA attribute old value so that it can be used after
// attribute change. Note, we assume there's no nested ARIA attribute // attribute change. Note, we assume there's no nested ARIA attribute
// changes. If this happens then we should end up with keeping a stack of // changes. If this happens then we should end up with keeping a stack of
@@ -906,6 +911,10 @@ DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
return; return;
} }
if (aAttribute == nsGkAtoms::id) {
RelocateARIAOwnedIfNeeded(elm);
}
// ARIA or XUL selection // ARIA or XUL selection
if ((aAccessible->GetContent()->IsXULElement() && if ((aAccessible->GetContent()->IsXULElement() &&
aAttribute == nsGkAtoms::selected) || aAttribute == nsGkAtoms::selected) ||
@@ -1040,6 +1049,10 @@ DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute
FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible); FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
return; return;
} }
if (aAttribute == nsGkAtoms::aria_owns) {
mNotificationController->ScheduleRelocation(aAccessible);
}
} }
void void
@@ -1271,6 +1284,15 @@ DocAccessible::BindToDocument(Accessible* aAccessible,
aAccessible->SetRoleMapEntry(aRoleMapEntry); aAccessible->SetRoleMapEntry(aRoleMapEntry);
AddDependentIDsFor(aAccessible); AddDependentIDsFor(aAccessible);
if (aAccessible->HasOwnContent()) {
nsIContent* el = aAccessible->GetContent();
if (el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_owns)) {
mNotificationController->ScheduleRelocation(aAccessible);
}
RelocateARIAOwnedIfNeeded(el);
}
} }
void void
@@ -1365,67 +1387,6 @@ DocAccessible::ProcessInvalidationList()
} }
mInvalidationList.Clear(); mInvalidationList.Clear();
// Alter the tree according to aria-owns (seize the trees).
for (uint32_t idx = 0; idx < mARIAOwnsInvalidationList.Length(); idx++) {
Accessible* owner = mARIAOwnsInvalidationList[idx].mOwner;
if (!owner->IsInDocument()) { // eventually died before we've got here
continue;
}
Accessible* child = GetAccessible(mARIAOwnsInvalidationList[idx].mChild);
if (!child || !child->IsInDocument()) {
continue;
}
Accessible* oldParent = child->Parent();
if (!oldParent) {
NS_ERROR("The accessible is in document but doesn't have a parent");
continue;
}
int32_t idxInParent = child->IndexInParent();
// XXX: update context flags
{
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(oldParent);
RefPtr<AccMutationEvent> hideEvent =
new AccHideEvent(child, child->GetContent(), false);
FireDelayedEvent(hideEvent);
reorderEvent->AddSubMutationEvent(hideEvent);
AutoTreeMutation mut(oldParent);
oldParent->RemoveChild(child);
MaybeNotifyOfValueChange(oldParent);
FireDelayedEvent(reorderEvent);
}
bool isReinserted = false;
{
AutoTreeMutation mut(owner);
isReinserted = owner->AppendChild(child);
}
Accessible* newParent = owner;
if (!isReinserted) {
AutoTreeMutation mut(oldParent);
oldParent->InsertChildAt(idxInParent, child);
newParent = oldParent;
}
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(newParent);
RefPtr<AccMutationEvent> showEvent =
new AccShowEvent(child, child->GetContent());
FireDelayedEvent(showEvent);
reorderEvent->AddSubMutationEvent(showEvent);
MaybeNotifyOfValueChange(newParent);
FireDelayedEvent(reorderEvent);
child->SetRepositioned(isReinserted);
}
mARIAOwnsInvalidationList.Clear();
} }
Accessible* Accessible*
@@ -1635,31 +1596,6 @@ DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
if (!HasAccessible(dependentContent)) { if (!HasAccessible(dependentContent)) {
mInvalidationList.AppendElement(dependentContent); mInvalidationList.AppendElement(dependentContent);
} }
// Update ARIA owns cache.
if (relAttr == nsGkAtoms::aria_owns) {
// ARIA owns cannot refer to itself or a parent. Ignore
// the element if so.
nsIContent* parentEl = relProviderEl;
while (parentEl && parentEl != dependentContent) {
parentEl = parentEl->GetParent();
}
if (!parentEl) {
// ARIA owns element cannot refer to an element in parents chain
// of other ARIA owns element (including that ARIA owns element)
// if it's inside of a dependent element subtree of that
// ARIA owns element. Applied recursively.
if (!IsInARIAOwnsLoop(relProviderEl, dependentContent)) {
nsTArray<nsIContent*>* list =
mARIAOwnsHash.LookupOrAdd(aRelProvider);
list->AppendElement(dependentContent);
mARIAOwnsInvalidationList.AppendElement(
ARIAOwnsPair(aRelProvider, dependentContent));
}
}
}
} }
} }
} }
@@ -1709,59 +1645,6 @@ DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
} }
} }
// aria-owns has gone, put the children back.
if (relAttr == nsGkAtoms::aria_owns) {
nsTArray<nsIContent*>* children = mARIAOwnsHash.Get(aRelProvider);
if (children) {
nsTArray<Accessible*> containers;
// Remove ARIA owned elements from where they belonged.
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aRelProvider);
{
AutoTreeMutation mut(aRelProvider);
for (uint32_t idx = 0; idx < children->Length(); idx++) {
nsIContent* childEl = children->ElementAt(idx);
Accessible* child = GetAccessible(childEl);
if (child && child->IsRepositioned()) {
{
RefPtr<AccMutationEvent> hideEvent =
new AccHideEvent(child, childEl, false);
FireDelayedEvent(hideEvent);
reorderEvent->AddSubMutationEvent(hideEvent);
aRelProvider->RemoveChild(child);
}
// Collect DOM-order containers to update their trees.
child->SetRepositioned(false);
Accessible* container = GetContainerAccessible(childEl);
if (!containers.Contains(container)) {
containers.AppendElement(container);
}
}
}
}
mARIAOwnsHash.Remove(aRelProvider);
for (uint32_t idx = 0; idx < mARIAOwnsInvalidationList.Length();) {
if (mARIAOwnsInvalidationList[idx].mOwner == aRelProvider) {
mARIAOwnsInvalidationList.RemoveElementAt(idx);
continue;
}
idx++;
}
MaybeNotifyOfValueChange(aRelProvider);
FireDelayedEvent(reorderEvent);
// Reinserted previously ARIA owned elements into the tree
// (restore a DOM-like order).
for (uint32_t idx = 0; idx < containers.Length(); idx++) {
UpdateTreeOnInsertion(containers[idx]);
}
}
}
// If the relation attribute is given then we don't have anything else to // If the relation attribute is given then we don't have anything else to
// check. // check.
if (aRelAttr) if (aRelAttr)
@@ -1769,45 +1652,6 @@ DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
} }
} }
bool
DocAccessible::IsInARIAOwnsLoop(nsIContent* aOwnerEl, nsIContent* aDependentEl)
{
// ARIA owns element cannot refer to an element in parents chain of other ARIA
// owns element (including that ARIA owns element) if it's inside of
// a dependent element subtree of that ARIA owns element.
for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
Accessible* otherOwner = it.Key();
nsIContent* parentEl = otherOwner->GetContent();
while (parentEl && parentEl != aDependentEl) {
parentEl = parentEl->GetParent();
}
// The dependent element of this ARIA owns element contains some other ARIA
// owns element, make sure this ARIA owns element is not in a subtree of
// a dependent element of that other ARIA owns element. If not then
// continue a check recursively.
if (parentEl) {
nsTArray<nsIContent*>* childEls = it.UserData();
for (uint32_t idx = 0; idx < childEls->Length(); idx++) {
nsIContent* childEl = childEls->ElementAt(idx);
nsIContent* parentEl = aOwnerEl;
while (parentEl && parentEl != childEl) {
parentEl = parentEl->GetParent();
}
if (parentEl) {
return true;
}
if (IsInARIAOwnsLoop(aOwnerEl, childEl)) {
return true;
}
}
}
}
return false;
}
bool bool
DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement, DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
nsIAtom* aAttribute) nsIAtom* aAttribute)
@@ -2023,11 +1867,6 @@ DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNod
} }
} }
// We may not have an integral DOM tree to remove all aria-owns relations
// from the tree. Validate all relations after timeout to workaround that.
mNotificationController->ScheduleNotification<DocAccessible>
(this, &DocAccessible::ValidateARIAOwned);
// Content insertion/removal is not cause of accessible tree change. // Content insertion/removal is not cause of accessible tree change.
if (updateFlags == eNoAccessible) if (updateFlags == eNoAccessible)
return; return;
@@ -2111,22 +1950,243 @@ DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
return updateFlags; return updateFlags;
} }
void
DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
{
if (!aElement->HasID())
return;
AttrRelProviderArray* list =
mDependentIDsHash.Get(nsDependentAtomString(aElement->GetID()));
if (list) {
for (uint32_t idx = 0; idx < list->Length(); idx++) {
if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
Accessible* owner = GetAccessible(list->ElementAt(idx)->mContent);
if (owner) {
mNotificationController->ScheduleRelocation(owner);
}
}
}
}
}
void void
DocAccessible::ValidateARIAOwned() DocAccessible::ValidateARIAOwned()
{ {
for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) { for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
nsTArray<nsIContent*>* childEls = it.UserData(); Accessible* owner = it.Key();
for (uint32_t idx = 0; idx < childEls->Length(); idx++) { nsTArray<RefPtr<Accessible> >* children = it.UserData();
nsIContent* childEl = childEls->ElementAt(idx);
Accessible* child = GetAccessible(childEl); // Owner is about to die, put children back if applicable.
if (child && child->IsInDocument() && !child->GetFrame()) { if (!owner->IsInDocument()) {
if (!child->Parent()) { PutChildrenBack(children, 0);
NS_ERROR("An element in the document doesn't have a parent?"); it.Remove();
continue; continue;
} }
UpdateTreeOnRemoval(child->Parent(), childEl);
for (uint32_t idx = 0; idx < children->Length(); idx++) {
Accessible* child = children->ElementAt(idx);
if (!child->IsInDocument()) {
children->RemoveElementAt(idx);
idx--;
continue;
}
NS_ASSERTION(child->Parent(), "No parent for ARIA owned?");
// If DOM node doesn't have a frame anymore then shutdown its accessible.
if (child->Parent() && !child->GetFrame()) {
UpdateTreeOnRemoval(child->Parent(), child->GetContent());
children->RemoveElementAt(idx);
idx--;
continue;
}
NS_ASSERTION(child->Parent() == owner,
"Illigally stolen ARIA owned child!");
}
if (children->Length() == 0) {
it.Remove();
}
}
}
void
DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
{
nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.LookupOrAdd(aOwner);
IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
Accessible* child = nullptr;
uint32_t arrayIdx = 0, insertIdx = aOwner->ChildCount() - children->Length();
while ((child = iter.Next())) {
// Same child on same position, no change.
if (child->Parent() == aOwner &&
child->IndexInParent() == static_cast<int32_t>(insertIdx)) {
NS_ASSERTION(child == children->ElementAt(arrayIdx), "Not in sync!");
insertIdx++; arrayIdx++;
continue;
}
NS_ASSERTION(children->SafeElementAt(arrayIdx) != child, "Already in place!");
nsTArray<RefPtr<Accessible> >::index_type idx = children->IndexOf(child);
if (idx < arrayIdx) {
continue; // ignore second entry of same ID
}
// A new child is found, check for loops.
if (child->Parent() != aOwner) {
Accessible* parent = aOwner;
while (parent && parent != child && !parent->IsDoc()) {
parent = parent->Parent();
}
// A referred child cannot be a parent of the owner.
if (parent == child) {
continue;
} }
} }
if (child->Parent() == aOwner) {
MoveChild(child, insertIdx - 1);
children->InsertElementAt(arrayIdx, child);
arrayIdx++;
} else if (SeizeChild(aOwner, child, insertIdx)) {
children->InsertElementAt(arrayIdx, child);
insertIdx++; arrayIdx++;
}
}
// Put back children that are not seized anymore.
PutChildrenBack(children, arrayIdx);
if (children->Length() == 0) {
mARIAOwnsHash.Remove(aOwner);
}
}
bool
DocAccessible::SeizeChild(Accessible* aNewParent, Accessible* aChild,
int32_t aIdxInParent)
{
Accessible* oldParent = aChild->Parent();
NS_PRECONDITION(oldParent, "No parent?");
int32_t oldIdxInParent = aChild->IndexInParent();
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(oldParent);
RefPtr<AccMutationEvent> hideEvent =
new AccHideEvent(aChild, aChild->GetContent(), false);
reorderEvent->AddSubMutationEvent(hideEvent);
{
AutoTreeMutation mut(oldParent);
oldParent->RemoveChild(aChild);
}
bool isReinserted = false;
{
AutoTreeMutation mut(aNewParent);
isReinserted = aNewParent->InsertChildAt(aIdxInParent, aChild);
}
if (!isReinserted) {
AutoTreeMutation mut(oldParent);
oldParent->InsertChildAt(oldIdxInParent, aChild);
return false;
}
// The child may be stolen from other ARIA owns element.
if (aChild->IsRelocated()) {
nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(oldParent);
children->RemoveElement(aChild);
}
FireDelayedEvent(hideEvent);
MaybeNotifyOfValueChange(oldParent);
FireDelayedEvent(reorderEvent);
reorderEvent = new AccReorderEvent(aNewParent);
RefPtr<AccMutationEvent> showEvent =
new AccShowEvent(aChild, aChild->GetContent());
reorderEvent->AddSubMutationEvent(showEvent);
FireDelayedEvent(showEvent);
MaybeNotifyOfValueChange(aNewParent);
FireDelayedEvent(reorderEvent);
aChild->SetRelocated(true);
return true;
}
void
DocAccessible::MoveChild(Accessible* aChild, int32_t aIdxInParent)
{
NS_PRECONDITION(aChild->Parent(), "No parent?");
Accessible* parent = aChild->Parent();
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(parent);
RefPtr<AccMutationEvent> hideEvent =
new AccHideEvent(aChild, aChild->GetContent(), false);
reorderEvent->AddSubMutationEvent(hideEvent);
AutoTreeMutation mut(parent);
parent->RemoveChild(aChild);
parent->InsertChildAt(aIdxInParent, aChild);
aChild->SetRelocated(true);
FireDelayedEvent(hideEvent);
RefPtr<AccMutationEvent> showEvent =
new AccShowEvent(aChild, aChild->GetContent());
reorderEvent->AddSubMutationEvent(showEvent);
FireDelayedEvent(showEvent);
MaybeNotifyOfValueChange(parent);
FireDelayedEvent(reorderEvent);
}
void
DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
uint32_t aStartIdx)
{
nsTArray<Accessible*> containers;
for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
Accessible* child = aChildren->ElementAt(idx);
// If the child is in the tree then remove it from the owner.
if (child->IsInDocument()) {
Accessible* owner = child->Parent();
RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(owner);
RefPtr<AccMutationEvent> hideEvent =
new AccHideEvent(child, child->GetContent(), false);
reorderEvent->AddSubMutationEvent(hideEvent);
{
AutoTreeMutation mut(owner);
owner->RemoveChild(child);
child->SetRelocated(false);
}
FireDelayedEvent(hideEvent);
MaybeNotifyOfValueChange(owner);
FireDelayedEvent(reorderEvent);
}
Accessible* container = GetContainerAccessible(child->GetContent());
if (container &&
containers.IndexOf(container) == nsTArray<Accessible*>::NoIndex) {
containers.AppendElement(container);
}
}
// And put it back where it belongs to.
aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
for (uint32_t idx = 0; idx < containers.Length(); idx++) {
UpdateTreeOnInsertion(containers[idx]);
} }
} }

View File

@@ -286,13 +286,9 @@ public:
*/ */
Accessible* ARIAOwnedAt(Accessible* aParent, uint32_t aIndex) const Accessible* ARIAOwnedAt(Accessible* aParent, uint32_t aIndex) const
{ {
nsTArray<nsIContent*>* childrenEl = mARIAOwnsHash.Get(aParent); nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
if (childrenEl) { if (children) {
nsIContent* childEl = childrenEl->SafeElementAt(aIndex); return children->SafeElementAt(aIndex);
Accessible* child = GetAccessible(childEl);
if (child && child->IsRepositioned()) {
return child;
}
} }
return nullptr; return nullptr;
} }
@@ -436,12 +432,6 @@ protected:
void RemoveDependentIDsFor(Accessible* aRelProvider, void RemoveDependentIDsFor(Accessible* aRelProvider,
nsIAtom* aRelAttr = nullptr); nsIAtom* aRelAttr = nullptr);
/**
* Return true if given ARIA owner element and its referred content make
* the loop closed.
*/
bool IsInARIAOwnsLoop(nsIContent* aOwnerEl, nsIContent* aDependentEl);
/** /**
* Update or recreate an accessible depending on a changed attribute. * Update or recreate an accessible depending on a changed attribute.
* *
@@ -513,11 +503,38 @@ protected:
uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert, uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
AccReorderEvent* aReorderEvent); AccReorderEvent* aReorderEvent);
/**
* Schedule ARIA owned element relocation if needed.
*/
void RelocateARIAOwnedIfNeeded(nsIContent* aEl);
/** /**
* Validates all aria-owns connections and updates the tree accordingly. * Validates all aria-owns connections and updates the tree accordingly.
*/ */
void ValidateARIAOwned(); void ValidateARIAOwned();
/**
* Steals or puts back accessible subtrees.
*/
void DoARIAOwnsRelocation(Accessible* aOwner);
/**
* Moves the child from old parent under new one.
*/
bool SeizeChild(Accessible* aNewParent, Accessible* aChild,
int32_t aIdxInParent);
/**
* Move the child under same parent.
*/
void MoveChild(Accessible* aChild, int32_t aIdxInParent);
/**
* Moves children back under their original parents.
*/
void PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
uint32_t aStartIdx);
/** /**
* Create accessible tree. * Create accessible tree.
* *
@@ -649,14 +666,12 @@ protected:
AttrRelProvider& operator =(const AttrRelProvider&); AttrRelProvider& operator =(const AttrRelProvider&);
}; };
typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
typedef nsClassHashtable<nsStringHashKey, AttrRelProviderArray>
DependentIDsHashtable;
/** /**
* The cache of IDs pointed by relation attributes. * The cache of IDs pointed by relation attributes.
*/ */
DependentIDsHashtable mDependentIDsHash; typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
nsClassHashtable<nsStringHashKey, AttrRelProviderArray>
mDependentIDsHash;
friend class RelatedAccIterator; friend class RelatedAccIterator;
@@ -669,24 +684,11 @@ protected:
nsTArray<nsIContent*> mInvalidationList; nsTArray<nsIContent*> mInvalidationList;
/** /**
* Holds a list of aria-owns relations. * Holds a list of aria-owns relocations.
*/ */
nsClassHashtable<nsPtrHashKey<Accessible>, nsTArray<nsIContent*> > nsClassHashtable<nsPtrHashKey<Accessible>, nsTArray<RefPtr<Accessible> > >
mARIAOwnsHash; mARIAOwnsHash;
struct ARIAOwnsPair {
ARIAOwnsPair(Accessible* aOwner, nsIContent* aChild) :
mOwner(aOwner), mChild(aChild) { }
ARIAOwnsPair(const ARIAOwnsPair& aPair) :
mOwner(aPair.mOwner), mChild(aPair.mChild) { }
ARIAOwnsPair& operator =(const ARIAOwnsPair& aPair)
{ mOwner = aPair.mOwner; mChild = aPair.mChild; return *this; }
RefPtr<Accessible> mOwner;
nsCOMPtr<nsIContent> mChild;
};
nsTArray<ARIAOwnsPair> mARIAOwnsInvalidationList;
/** /**
* Used to process notification from core and accessible events. * Used to process notification from core and accessible events.
*/ */

View File

@@ -51,9 +51,11 @@
{ {
this.menuNode = getNode(aMenuID); this.menuNode = getNode(aMenuID);
// Because of aria-owns processing we may have menupopup start fired before
// related show event.
this.eventSeq = [ this.eventSeq = [
new invokerChecker(EVENT_SHOW, this.menuNode), new invokerChecker(EVENT_SHOW, this.menuNode),
new invokerChecker(EVENT_MENUPOPUP_START, this.menuNode), new asyncInvokerChecker(EVENT_MENUPOPUP_START, this.menuNode),
new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)) new invokerChecker(EVENT_REORDER, getNode(aParentMenuID))
]; ];

View File

@@ -28,7 +28,7 @@
{ {
var tree = var tree =
{ SECTION: [ // t1_1 { SECTION: [ // t1_1
{ SECTION: [ // t1_2 { HEADING: [ // t1_2
// no kids, no loop // no kids, no loop
] } ] }
] }; ] };
@@ -36,8 +36,8 @@
tree = tree =
{ SECTION: [ // t2_1 { SECTION: [ // t2_1
{ SECTION: [ // t2_2 { GROUPING: [ // t2_2
{ SECTION: [ // t2_3 { HEADING: [ // t2_3
// no kids, no loop // no kids, no loop
] } ] }
] } ] }
@@ -46,9 +46,9 @@
tree = tree =
{ SECTION: [ // t3_3 { SECTION: [ // t3_3
{ SECTION: [ // t3_1 { GROUPING: [ // t3_1
{ SECTION: [ // t3_2 { NOTE: [ // t3_2
{ SECTION: [ // DOM child of t3_2 { HEADING: [ // DOM child of t3_2
// no kids, no loop // no kids, no loop
] } ] }
] } ] }
@@ -58,7 +58,7 @@
tree = tree =
{ SECTION: [ // t4_1 { SECTION: [ // t4_1
{ SECTION: [ // DOM child of t4_1 { GROUPING: [ // DOM child of t4_1, aria-owns ignored
// no kids, no loop // no kids, no loop
] } ] }
] }; ] };
@@ -66,11 +66,11 @@
tree = tree =
{ SECTION: [ // t5_1 { SECTION: [ // t5_1
{ SECTION: [ // DOM child of t5_1 { GROUPING: [ // DOM child of t5_1
{ SECTION: [ // t5_2 { NOTE: [ // t5_2
{ SECTION: [ // DOM child of t5_2 { HEADING: [ // DOM child of t5_2
{ SECTION: [ // t5_3 { FORM: [ // t5_3
{ SECTION: [ // DOM child of t5_3 { TOOLTIP: [ // DOM child of t5_3
// no kids, no loop // no kids, no loop
]} ]}
]} ]}
@@ -80,6 +80,14 @@
] }; ] };
testAccessibleTree("t5_1", tree); testAccessibleTree("t5_1", tree);
tree =
{ SECTION: [ // t6_1
{ RADIOBUTTON: [ ] },
{ CHECKBUTTON: [ ] }, // t6_3, rearranged by aria-owns
{ PUSHBUTTON: [ ] }, // t6_2, rearranged by aria-owns
] };
testAccessibleTree("t6_1", tree);
SimpleTest.finish(); SimpleTest.finish();
} }
@@ -96,24 +104,37 @@
<pre id="test"> <pre id="test">
</pre> </pre>
<!-- simple loop -->
<div id="t1_1" aria-owns="t1_2"></div> <div id="t1_1" aria-owns="t1_2"></div>
<div id="t1_2" aria-owns="t1_1"></div> <div id="t1_2" aria-owns="t1_1" role="heading"></div>
<div id="t2_2" aria-owns="t2_3"></div> <!-- loop -->
<div id="t2_2" aria-owns="t2_3" role="group"></div>
<div id="t2_1" aria-owns="t2_2"></div> <div id="t2_1" aria-owns="t2_2"></div>
<div id="t2_3" aria-owns="t2_1"></div> <div id="t2_3" aria-owns="t2_1" role="heading"></div>
<div id="t3_1" aria-owns="t3_2"></div> <!-- loop #2 -->
<div id="t3_2"> <div id="t3_1" aria-owns="t3_2" role="group"></div>
<div aria-owns="t3_3"></div> <div id="t3_2" role="note">
<div aria-owns="t3_3" role="heading"></div>
</div> </div>
<div id="t3_3" aria-owns="t3_1"></div> <div id="t3_3" aria-owns="t3_1"></div>
<div id="t4_1"><div aria-owns="t4_1"></div></div> <!-- self loop -->
<div id="t4_1"><div aria-owns="t4_1" role="group"></div></div>
<!-- natural and aria-owns hierarchy -->
<div id="t5_1"><div aria-owns="t5_2" role="group"></div></div>
<div id="t5_2" role="note"><div aria-owns="t5_3" role="heading"></div></div>
<div id="t5_3" role="form"><div aria-owns="t5_1" role="tooltip"></div></div>
<!-- rearrange children -->
<div id="t6_1" aria-owns="t6_3 t6_2">
<div id="t6_2" role="button"></div>
<div id="t6_3" role="checkbox"></div>
<div role="radio"></div>
</div>
<div id="t5_1"><div aria-owns="t5_2"></div>
<div id="t5_2"><div aria-owns="t5_3"></div></div>
<div id="t5_3"><div aria-owns="t5_1"></div></div>
</body> </body>
</html> </html>

View File

@@ -26,11 +26,11 @@
function removeARIAOwns() function removeARIAOwns()
{ {
this.eventSeq = [ this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t2_checkbox")), new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")),
new invokerChecker(EVENT_HIDE, getNode("t2_button")), new invokerChecker(EVENT_HIDE, getNode("t1_button")),
new invokerChecker(EVENT_SHOW, getNode("t2_button")), new invokerChecker(EVENT_SHOW, getNode("t1_button")),
new invokerChecker(EVENT_SHOW, getNode("t2_checkbox")), new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")),
new invokerChecker(EVENT_REORDER, getNode("container2")) new invokerChecker(EVENT_REORDER, getNode("t1_container"))
]; ];
this.invoke = function removeARIAOwns_invoke() this.invoke = function removeARIAOwns_invoke()
@@ -43,9 +43,9 @@
] }, ] },
{ PUSHBUTTON: [ ] } { PUSHBUTTON: [ ] }
] }; ] };
testAccessibleTree("container2", tree); testAccessibleTree("t1_container", tree);
getNode("container2").removeAttribute("aria-owns"); getNode("t1_container").removeAttribute("aria-owns");
} }
this.finalCheck = function removeARIAOwns_finalCheck() this.finalCheck = function removeARIAOwns_finalCheck()
@@ -58,7 +58,7 @@
{ SECTION: [] } { SECTION: [] }
] } ] }
] }; ] };
testAccessibleTree("container2", tree); testAccessibleTree("t1_container", tree);
} }
this.getID = function removeARIAOwns_getID() this.getID = function removeARIAOwns_getID()
@@ -70,16 +70,17 @@
function setARIAOwns() function setARIAOwns()
{ {
this.eventSeq = [ this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t2_button")), new invokerChecker(EVENT_HIDE, getNode("t1_button")),
new invokerChecker(EVENT_SHOW, getNode("t2_button")), new invokerChecker(EVENT_SHOW, getNode("t1_button")),
new invokerChecker(EVENT_HIDE, getNode("t2_subdiv")), new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
new invokerChecker(EVENT_SHOW, getNode("t2_subdiv")), new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
new invokerChecker(EVENT_REORDER, getNode("container2")) new invokerChecker(EVENT_REORDER, getNode("t1_container"))
]; ];
this.invoke = function setARIAOwns_invoke() this.invoke = function setARIAOwns_invoke()
{ {
getNode("container2").setAttribute("aria-owns", "t2_button t2_subdiv"); getNode("t1_container").
setAttribute("aria-owns", "t1_button t1_subdiv");
} }
this.finalCheck = function setARIAOwns_finalCheck() this.finalCheck = function setARIAOwns_finalCheck()
@@ -88,11 +89,11 @@
// the children. // the children.
var tree = var tree =
{ SECTION: [ { SECTION: [
{ CHECKBUTTON: [ ] }, // div { CHECKBUTTON: [ ] }, // checkbox
{ PUSHBUTTON: [ ] }, // button { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
{ SECTION: [ ] } // subdiv { SECTION: [ ] } // subdiv from the subtree, ARIA owned
] }; ] };
testAccessibleTree("container2", tree); testAccessibleTree("t1_container", tree);
} }
this.getID = function setARIAOwns_getID() this.getID = function setARIAOwns_getID()
@@ -101,19 +102,53 @@
} }
} }
function addIdToARIAOwns()
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t1_group")),
new invokerChecker(EVENT_SHOW, getNode("t1_group")),
new invokerChecker(EVENT_REORDER, document)
];
this.invoke = function addIdToARIAOwns_invoke()
{
getNode("t1_container").
setAttribute("aria-owns", "t1_button t1_subdiv t1_group");
}
this.finalCheck = function addIdToARIAOwns_finalCheck()
{
// children are swapped again, button and subdiv are appended to
// the children.
var tree =
{ SECTION: [
{ CHECKBUTTON: [ ] }, // t1_checkbox
{ PUSHBUTTON: [ ] }, // button, t1_button
{ SECTION: [ ] }, // subdiv from the subtree, t1_subdiv
{ GROUPING: [ ] } // group from outside, t1_group
] };
testAccessibleTree("t1_container", tree);
}
this.getID = function addIdToARIAOwns_getID()
{
return "Add id to @aria-owns attribute value";
}
}
function appendEl() function appendEl()
{ {
this.eventSeq = [ this.eventSeq = [
new invokerChecker(EVENT_SHOW, getNode, "child3"), new invokerChecker(EVENT_SHOW, getNode, "t1_child3"),
new invokerChecker(EVENT_REORDER, getNode("container2")) new invokerChecker(EVENT_REORDER, getNode("t1_container"))
]; ];
this.invoke = function appendEl_invoke() this.invoke = function appendEl_invoke()
{ {
var div = document.createElement("div"); var div = document.createElement("div");
div.setAttribute("id", "child3"); div.setAttribute("id", "t1_child3");
div.setAttribute("role", "radio") div.setAttribute("role", "radio")
getNode("container2").appendChild(div); getNode("t1_container").appendChild(div);
} }
this.finalCheck = function appendEl_finalCheck() this.finalCheck = function appendEl_finalCheck()
@@ -124,10 +159,11 @@
{ SECTION: [ { SECTION: [
{ CHECKBUTTON: [ ] }, { CHECKBUTTON: [ ] },
{ RADIOBUTTON: [ ] }, { RADIOBUTTON: [ ] },
{ PUSHBUTTON: [ ] }, // ARIA owned { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
{ SECTION: [ ] } // ARIA owned { SECTION: [ ] }, // ARIA owned, t1_subdiv
{ GROUPING: [ ] } // ARIA owned, t1_group
] }; ] };
testAccessibleTree("container2", tree); testAccessibleTree("t1_container", tree);
} }
this.getID = function appendEl_getID() this.getID = function appendEl_getID()
@@ -139,15 +175,15 @@
function removeEl() function removeEl()
{ {
this.eventSeq = [ this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode, "t2_checkbox"), new invokerChecker(EVENT_HIDE, getNode, "t1_checkbox"),
new invokerChecker(EVENT_SHOW, getNode, "t2_checkbox"), new invokerChecker(EVENT_SHOW, getNode, "t1_checkbox"),
new invokerChecker(EVENT_REORDER, getNode("container2")) new invokerChecker(EVENT_REORDER, getNode("t1_container"))
]; ];
this.invoke = function removeEl_invoke() this.invoke = function removeEl_invoke()
{ {
// remove a container of t2_subdiv // remove a container of t1_subdiv
getNode("t2_span").parentNode.removeChild(getNode("t2_span")); getNode("t1_span").parentNode.removeChild(getNode("t1_span"));
} }
this.finalCheck = function removeEl_finalCheck() this.finalCheck = function removeEl_finalCheck()
@@ -157,14 +193,214 @@
{ SECTION: [ { SECTION: [
{ CHECKBUTTON: [ ] }, { CHECKBUTTON: [ ] },
{ RADIOBUTTON: [ ] }, { RADIOBUTTON: [ ] },
{ PUSHBUTTON: [ ] } // ARIA owned { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
{ GROUPING: [ ] } // ARIA owned, t1_group
] }; ] };
testAccessibleTree("container2", tree); testAccessibleTree("t1_container", tree);
} }
this.getID = function removeEl_getID() this.getID = function removeEl_getID()
{ {
return "Remove a container of ARIA ownded element"; return "Remove a container of ARIA owned element";
}
}
function removeId()
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t1_group")),
new invokerChecker(EVENT_SHOW, getNode("t1_group")),
new invokerChecker(EVENT_REORDER, document)
];
this.invoke = function removeId_invoke()
{
getNode("t1_group").removeAttribute("id");
}
this.finalCheck = function removeId_finalCheck()
{
var tree =
{ SECTION: [
{ CHECKBUTTON: [ ] },
{ RADIOBUTTON: [ ] },
{ PUSHBUTTON: [ ] } // ARIA owned, t1_button
] };
testAccessibleTree("t1_container", tree);
}
this.getID = function removeId_getID()
{
return "Remove ID from ARIA owned element";
}
}
function setId()
{
this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")),
new invokerChecker(EVENT_REORDER, document)
];
this.invoke = function setId_invoke()
{
getNode("t1_grouptmp").setAttribute("id", "t1_group");
}
this.finalCheck = function setId_finalCheck()
{
var tree =
{ SECTION: [
{ CHECKBUTTON: [ ] },
{ RADIOBUTTON: [ ] },
{ PUSHBUTTON: [ ] }, // ARIA owned, t1_button
{ GROUPING: [ ] } // ARIA owned, t1_group, previously t1_grouptmp
] };
testAccessibleTree("t1_container", tree);
}
this.getID = function setId_getID()
{
return "Set ID that is referred by ARIA owns";
}
}
/**
* Remove an accessible DOM element containing an element referred by
* ARIA owns.
*/
function removeA11eteiner()
{
this.eventSeq = [
new invokerChecker(EVENT_REORDER, getNode("t2_container1"))
];
this.invoke = function removeA11eteiner_invoke()
{
var tree =
{ SECTION: [
{ CHECKBUTTON: [ ] } // ARIA owned, 't2_owned'
] };
testAccessibleTree("t2_container1", tree);
getNode("t2_container2").removeChild(getNode("t2_container3"));
}
this.finalCheck = function removeA11eteiner_finalCheck()
{
var tree =
{ SECTION: [
] };
testAccessibleTree("t2_container1", tree);
}
this.getID = function removeA11eteiner_getID()
{
return "Remove an accessible DOM element containing an element referred by ARIA owns";
}
}
/**
* Steal an element from other ARIA owns element. This use case guarantees
* that result of setAttribute/removeAttribute doesn't depend on their order.
*/
function stealFromOtherARIAOwns()
{
this.eventSeq = [
new invokerChecker(EVENT_REORDER, getNode("t3_container2"))
];
this.invoke = function stealFromOtherARIAOwns_invoke()
{
getNode("t3_container2").setAttribute("aria-owns", "t3_child");
}
this.finalCheck = function stealFromOtherARIAOwns_finalCheck()
{
var tree =
{ SECTION: [
] };
testAccessibleTree("t3_container1", tree);
tree =
{ SECTION: [
{ CHECKBUTTON: [
] }
] };
testAccessibleTree("t3_container2", tree);
}
this.getID = function stealFromOtherARIAOwns_getID()
{
return "Steal an element from other ARIA owns element";
}
}
function appendElToRecacheChildren()
{
this.eventSeq = [
new invokerChecker(EVENT_REORDER, getNode("t3_container2"))
];
this.invoke = function appendElToRecacheChildren_invoke()
{
var div = document.createElement("div");
div.setAttribute("role", "radio")
getNode("t3_container2").appendChild(div);
}
this.finalCheck = function appendElToRecacheChildren_finalCheck()
{
var tree =
{ SECTION: [
] };
testAccessibleTree("t3_container1", tree);
tree =
{ SECTION: [
{ RADIOBUTTON: [ ] },
{ CHECKBUTTON: [ ] } // ARIA owned
] };
testAccessibleTree("t3_container2", tree);
}
this.getID = function appendElToRecacheChildren_getID()
{
return "Append a child under @aria-owns element to trigger children recache";
}
}
function showHiddenElement()
{
this.eventSeq = [
new invokerChecker(EVENT_REORDER, getNode("t4_container1"))
];
this.invoke = function showHiddenElement_invoke()
{
var tree =
{ SECTION: [
{ RADIOBUTTON: [] }
] };
testAccessibleTree("t4_container1", tree);
getNode("t4_child1").style.display = "block";
}
this.finalCheck = function showHiddenElement_finalCheck()
{
var tree =
{ SECTION: [
{ CHECKBUTTON: [] },
{ RADIOBUTTON: [] }
] };
testAccessibleTree("t4_container1", tree);
}
this.getID = function showHiddenElement_getID()
{
return "Show hidden ARIA owns referred element";
} }
} }
@@ -181,10 +417,24 @@
{ {
gQueue = new eventQueue(); gQueue = new eventQueue();
// test1
gQueue.push(new removeARIAOwns()); gQueue.push(new removeARIAOwns());
gQueue.push(new setARIAOwns()); gQueue.push(new setARIAOwns());
gQueue.push(new addIdToARIAOwns());
gQueue.push(new appendEl()); gQueue.push(new appendEl());
gQueue.push(new removeEl()); gQueue.push(new removeEl());
gQueue.push(new removeId());
gQueue.push(new setId());
// test2
gQueue.push(new removeA11eteiner());
// test3
gQueue.push(new stealFromOtherARIAOwns());
gQueue.push(new appendElToRecacheChildren());
// test4
gQueue.push(new showHiddenElement());
gQueue.invoke(); // SimpleTest.finish() will be called in the end gQueue.invoke(); // SimpleTest.finish() will be called in the end
} }
@@ -202,15 +452,31 @@
<pre id="test"> <pre id="test">
</pre> </pre>
<div id="container2" aria-owns="t2_checkbox t2_button"> <div id="t1_container" aria-owns="t1_checkbox t1_button">
<div role="button" id="t2_button"></div> <div role="button" id="t1_button"></div>
<div role="checkbox" id="t2_checkbox"> <div role="checkbox" id="t1_checkbox">
<span id="t2_span"> <span id="t1_span">
<div id="t2_subdiv"></div> <div id="t1_subdiv"></div>
</span> </span>
</div> </div>
</div> </div>
<div id="t1_group" role="group"></div>
<div id="t1_grouptmp" role="group"></div>
<div id="t2_container1" aria-owns="t2_owned"></div>
<div id="t2_container2">
<div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
</div>
<div id="t3_container1" aria-owns="t3_child"></div>
<div id="t3_child" role="checkbox"></div>
<div id="t3_container2"></div>
<div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
<div id="t4_container2">
<div id="t4_child1" style="display:none" role="checkbox"></div>
<div id="t4_child2" role="radio"></div>
</div>
</body> </body>
</html> </html>

View File

@@ -22,9 +22,9 @@
this.eventSeq = [ this.eventSeq = [
new invokerChecker(EVENT_HIDE, getNode("child")), new invokerChecker(EVENT_HIDE, getNode("child")),
new invokerChecker(EVENT_REORDER, this.containerNode),
new invokerChecker(EVENT_HIDE, getNode("childDoc")), new invokerChecker(EVENT_HIDE, getNode("childDoc")),
new invokerChecker(EVENT_SHOW, "newChildDoc") new invokerChecker(EVENT_SHOW, "newChildDoc"),
new invokerChecker(EVENT_REORDER, this.containerNode)
]; ];
this.invoke = function runTest_invoke() this.invoke = function runTest_invoke()