Bug 1219299 - rework aria-owns implementation, r=yzen
This commit is contained in:
@@ -324,8 +324,14 @@ IDRefsIterator::GetElem(const nsDependentSubstring& aID)
|
||||
Accessible*
|
||||
IDRefsIterator::Next()
|
||||
{
|
||||
nsIContent* nextElm = NextElem();
|
||||
return nextElm ? mDoc->GetAccessible(nextElm) : nullptr;
|
||||
nsIContent* nextEl = nullptr;
|
||||
while ((nextEl = NextElem())) {
|
||||
Accessible* acc = mDoc->GetAccessible(nextEl);
|
||||
if (acc) {
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -295,12 +295,16 @@ EventQueue::CoalesceReorderEvents(AccEvent* aTailEvent)
|
||||
AccReorderEvent* thisReorder = downcast_accEvent(thisEvent);
|
||||
AccReorderEvent* tailReorder = downcast_accEvent(aTailEvent);
|
||||
uint32_t eventType = thisReorder->IsShowHideEventTarget(tailParent);
|
||||
if (eventType == nsIAccessibleEvent::EVENT_SHOW)
|
||||
if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
|
||||
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?");
|
||||
else
|
||||
}
|
||||
else {
|
||||
aTailEvent->mEventRule = AccEvent::eDoNotEmit;
|
||||
mEvents[index].swap(mEvents[count - 1]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentInsertions)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
|
||||
@@ -86,6 +87,7 @@ NotificationController::Shutdown()
|
||||
mContentInsertions.Clear();
|
||||
mNotifications.Clear();
|
||||
mEvents.Clear();
|
||||
mRelocations.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -351,6 +353,16 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
|
||||
// modification are done.
|
||||
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
|
||||
// process it synchronously. However we do not want to reenter if fireing
|
||||
// events causes script to run.
|
||||
|
||||
@@ -133,6 +133,16 @@ public:
|
||||
nsIContent* aStartChildNode,
|
||||
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
|
||||
* layout.
|
||||
@@ -303,6 +313,11 @@ private:
|
||||
* use SwapElements() on it.
|
||||
*/
|
||||
nsTArray<RefPtr<Notification> > mNotifications;
|
||||
|
||||
/**
|
||||
* Holds all scheduled relocations.
|
||||
*/
|
||||
nsTArray<RefPtr<Accessible> > mRelocations;
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
||||
@@ -116,7 +116,7 @@ TreeWalker::Next(ChildrenIterator* aIter, Accessible** aAccesible,
|
||||
// Ignore the accessible and its subtree if it was repositioned by means of
|
||||
// aria-owns.
|
||||
if (accessible) {
|
||||
if (accessible->IsRepositioned()) {
|
||||
if (accessible->IsRelocated()) {
|
||||
*aSkipSubtree = true;
|
||||
} else {
|
||||
*aAccesible = accessible;
|
||||
|
||||
@@ -910,13 +910,13 @@ public:
|
||||
* Get/set repositioned bit indicating that the accessible was moved in
|
||||
* the accessible tree, i.e. the accessible tree structure differs from DOM.
|
||||
*/
|
||||
bool IsRepositioned() const { return mStateFlags & eRepositioned; }
|
||||
void SetRepositioned(bool aRepositioned)
|
||||
bool IsRelocated() const { return mStateFlags & eRelocated; }
|
||||
void SetRelocated(bool aRelocated)
|
||||
{
|
||||
if (aRepositioned)
|
||||
mStateFlags |= eRepositioned;
|
||||
if (aRelocated)
|
||||
mStateFlags |= eRelocated;
|
||||
else
|
||||
mStateFlags &= ~eRepositioned;
|
||||
mStateFlags &= ~eRelocated;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1009,9 +1009,9 @@ protected:
|
||||
eSubtreeMutating = 1 << 6, // subtree is being mutated
|
||||
eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events
|
||||
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
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -130,9 +130,13 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
|
||||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
|
||||
for (uint32_t i = 0; i < tmp->mARIAOwnsInvalidationList.Length(); ++i) {
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mARIAOwnsInvalidationList[i].mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mARIAOwnsInvalidationList[i].mChild)
|
||||
for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
|
||||
nsTArray<RefPtr<Accessible> >* ar = it.UserData();
|
||||
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
|
||||
|
||||
@@ -144,10 +148,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
|
||||
tmp->mNodeToAccessibleMap.Clear();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
|
||||
for (uint32_t i = 0; i < tmp->mARIAOwnsInvalidationList.Length(); ++i) {
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mARIAOwnsInvalidationList[i].mOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mARIAOwnsInvalidationList[i].mChild)
|
||||
}
|
||||
tmp->mARIAOwnsHash.Clear();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
|
||||
@@ -731,6 +732,10 @@ DocAccessible::AttributeWillChange(nsIDocument* aDocument,
|
||||
if (aModType != nsIDOMMutationEvent::ADDITION)
|
||||
RemoveDependentIDsFor(accessible, aAttribute);
|
||||
|
||||
if (aAttribute == nsGkAtoms::id) {
|
||||
RelocateARIAOwnedIfNeeded(aElement);
|
||||
}
|
||||
|
||||
// Store the ARIA attribute old value so that it can be used after
|
||||
// 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
|
||||
@@ -906,6 +911,10 @@ DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
|
||||
return;
|
||||
}
|
||||
|
||||
if (aAttribute == nsGkAtoms::id) {
|
||||
RelocateARIAOwnedIfNeeded(elm);
|
||||
}
|
||||
|
||||
// ARIA or XUL selection
|
||||
if ((aAccessible->GetContent()->IsXULElement() &&
|
||||
aAttribute == nsGkAtoms::selected) ||
|
||||
@@ -1040,6 +1049,10 @@ DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute
|
||||
FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aAttribute == nsGkAtoms::aria_owns) {
|
||||
mNotificationController->ScheduleRelocation(aAccessible);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1271,6 +1284,15 @@ DocAccessible::BindToDocument(Accessible* aAccessible,
|
||||
aAccessible->SetRoleMapEntry(aRoleMapEntry);
|
||||
|
||||
AddDependentIDsFor(aAccessible);
|
||||
|
||||
if (aAccessible->HasOwnContent()) {
|
||||
nsIContent* el = aAccessible->GetContent();
|
||||
if (el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_owns)) {
|
||||
mNotificationController->ScheduleRelocation(aAccessible);
|
||||
}
|
||||
|
||||
RelocateARIAOwnedIfNeeded(el);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1365,67 +1387,6 @@ DocAccessible::ProcessInvalidationList()
|
||||
}
|
||||
|
||||
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*
|
||||
@@ -1635,31 +1596,6 @@ DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
|
||||
if (!HasAccessible(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
|
||||
// check.
|
||||
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
|
||||
DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
|
||||
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.
|
||||
if (updateFlags == eNoAccessible)
|
||||
return;
|
||||
@@ -2111,23 +1950,244 @@ DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
|
||||
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
|
||||
DocAccessible::ValidateARIAOwned()
|
||||
{
|
||||
for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
|
||||
nsTArray<nsIContent*>* childEls = it.UserData();
|
||||
for (uint32_t idx = 0; idx < childEls->Length(); idx++) {
|
||||
nsIContent* childEl = childEls->ElementAt(idx);
|
||||
Accessible* child = GetAccessible(childEl);
|
||||
if (child && child->IsInDocument() && !child->GetFrame()) {
|
||||
if (!child->Parent()) {
|
||||
NS_ERROR("An element in the document doesn't have a parent?");
|
||||
Accessible* owner = it.Key();
|
||||
nsTArray<RefPtr<Accessible> >* children = it.UserData();
|
||||
|
||||
// Owner is about to die, put children back if applicable.
|
||||
if (!owner->IsInDocument()) {
|
||||
PutChildrenBack(children, 0);
|
||||
it.Remove();
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -286,13 +286,9 @@ public:
|
||||
*/
|
||||
Accessible* ARIAOwnedAt(Accessible* aParent, uint32_t aIndex) const
|
||||
{
|
||||
nsTArray<nsIContent*>* childrenEl = mARIAOwnsHash.Get(aParent);
|
||||
if (childrenEl) {
|
||||
nsIContent* childEl = childrenEl->SafeElementAt(aIndex);
|
||||
Accessible* child = GetAccessible(childEl);
|
||||
if (child && child->IsRepositioned()) {
|
||||
return child;
|
||||
}
|
||||
nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
|
||||
if (children) {
|
||||
return children->SafeElementAt(aIndex);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -436,12 +432,6 @@ protected:
|
||||
void RemoveDependentIDsFor(Accessible* aRelProvider,
|
||||
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.
|
||||
*
|
||||
@@ -513,11 +503,38 @@ protected:
|
||||
uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
|
||||
AccReorderEvent* aReorderEvent);
|
||||
|
||||
/**
|
||||
* Schedule ARIA owned element relocation if needed.
|
||||
*/
|
||||
void RelocateARIAOwnedIfNeeded(nsIContent* aEl);
|
||||
|
||||
/**
|
||||
* Validates all aria-owns connections and updates the tree accordingly.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -649,14 +666,12 @@ protected:
|
||||
AttrRelProvider& operator =(const AttrRelProvider&);
|
||||
};
|
||||
|
||||
typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
|
||||
typedef nsClassHashtable<nsStringHashKey, AttrRelProviderArray>
|
||||
DependentIDsHashtable;
|
||||
|
||||
/**
|
||||
* The cache of IDs pointed by relation attributes.
|
||||
*/
|
||||
DependentIDsHashtable mDependentIDsHash;
|
||||
typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
|
||||
nsClassHashtable<nsStringHashKey, AttrRelProviderArray>
|
||||
mDependentIDsHash;
|
||||
|
||||
friend class RelatedAccIterator;
|
||||
|
||||
@@ -669,24 +684,11 @@ protected:
|
||||
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;
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -51,9 +51,11 @@
|
||||
{
|
||||
this.menuNode = getNode(aMenuID);
|
||||
|
||||
// Because of aria-owns processing we may have menupopup start fired before
|
||||
// related show event.
|
||||
this.eventSeq = [
|
||||
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))
|
||||
];
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
{
|
||||
var tree =
|
||||
{ SECTION: [ // t1_1
|
||||
{ SECTION: [ // t1_2
|
||||
{ HEADING: [ // t1_2
|
||||
// no kids, no loop
|
||||
] }
|
||||
] };
|
||||
@@ -36,8 +36,8 @@
|
||||
|
||||
tree =
|
||||
{ SECTION: [ // t2_1
|
||||
{ SECTION: [ // t2_2
|
||||
{ SECTION: [ // t2_3
|
||||
{ GROUPING: [ // t2_2
|
||||
{ HEADING: [ // t2_3
|
||||
// no kids, no loop
|
||||
] }
|
||||
] }
|
||||
@@ -46,9 +46,9 @@
|
||||
|
||||
tree =
|
||||
{ SECTION: [ // t3_3
|
||||
{ SECTION: [ // t3_1
|
||||
{ SECTION: [ // t3_2
|
||||
{ SECTION: [ // DOM child of t3_2
|
||||
{ GROUPING: [ // t3_1
|
||||
{ NOTE: [ // t3_2
|
||||
{ HEADING: [ // DOM child of t3_2
|
||||
// no kids, no loop
|
||||
] }
|
||||
] }
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
tree =
|
||||
{ SECTION: [ // t4_1
|
||||
{ SECTION: [ // DOM child of t4_1
|
||||
{ GROUPING: [ // DOM child of t4_1, aria-owns ignored
|
||||
// no kids, no loop
|
||||
] }
|
||||
] };
|
||||
@@ -66,11 +66,11 @@
|
||||
|
||||
tree =
|
||||
{ SECTION: [ // t5_1
|
||||
{ SECTION: [ // DOM child of t5_1
|
||||
{ SECTION: [ // t5_2
|
||||
{ SECTION: [ // DOM child of t5_2
|
||||
{ SECTION: [ // t5_3
|
||||
{ SECTION: [ // DOM child of t5_3
|
||||
{ GROUPING: [ // DOM child of t5_1
|
||||
{ NOTE: [ // t5_2
|
||||
{ HEADING: [ // DOM child of t5_2
|
||||
{ FORM: [ // t5_3
|
||||
{ TOOLTIP: [ // DOM child of t5_3
|
||||
// no kids, no loop
|
||||
]}
|
||||
]}
|
||||
@@ -80,6 +80,14 @@
|
||||
] };
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -96,24 +104,37 @@
|
||||
<pre id="test">
|
||||
</pre>
|
||||
|
||||
<!-- simple loop -->
|
||||
<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_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>
|
||||
<div id="t3_2">
|
||||
<div aria-owns="t3_3"></div>
|
||||
<!-- loop #2 -->
|
||||
<div id="t3_1" aria-owns="t3_2" role="group"></div>
|
||||
<div id="t3_2" role="note">
|
||||
<div aria-owns="t3_3" role="heading"></div>
|
||||
</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>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
function removeARIAOwns()
|
||||
{
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_HIDE, getNode("t2_checkbox")),
|
||||
new invokerChecker(EVENT_HIDE, getNode("t2_button")),
|
||||
new invokerChecker(EVENT_SHOW, getNode("t2_button")),
|
||||
new invokerChecker(EVENT_SHOW, getNode("t2_checkbox")),
|
||||
new invokerChecker(EVENT_REORDER, getNode("container2"))
|
||||
new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")),
|
||||
new invokerChecker(EVENT_HIDE, getNode("t1_button")),
|
||||
new invokerChecker(EVENT_SHOW, getNode("t1_button")),
|
||||
new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")),
|
||||
new invokerChecker(EVENT_REORDER, getNode("t1_container"))
|
||||
];
|
||||
|
||||
this.invoke = function removeARIAOwns_invoke()
|
||||
@@ -43,9 +43,9 @@
|
||||
] },
|
||||
{ PUSHBUTTON: [ ] }
|
||||
] };
|
||||
testAccessibleTree("container2", tree);
|
||||
testAccessibleTree("t1_container", tree);
|
||||
|
||||
getNode("container2").removeAttribute("aria-owns");
|
||||
getNode("t1_container").removeAttribute("aria-owns");
|
||||
}
|
||||
|
||||
this.finalCheck = function removeARIAOwns_finalCheck()
|
||||
@@ -58,7 +58,7 @@
|
||||
{ SECTION: [] }
|
||||
] }
|
||||
] };
|
||||
testAccessibleTree("container2", tree);
|
||||
testAccessibleTree("t1_container", tree);
|
||||
}
|
||||
|
||||
this.getID = function removeARIAOwns_getID()
|
||||
@@ -70,16 +70,17 @@
|
||||
function setARIAOwns()
|
||||
{
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_HIDE, getNode("t2_button")),
|
||||
new invokerChecker(EVENT_SHOW, getNode("t2_button")),
|
||||
new invokerChecker(EVENT_HIDE, getNode("t2_subdiv")),
|
||||
new invokerChecker(EVENT_SHOW, getNode("t2_subdiv")),
|
||||
new invokerChecker(EVENT_REORDER, getNode("container2"))
|
||||
new invokerChecker(EVENT_HIDE, getNode("t1_button")),
|
||||
new invokerChecker(EVENT_SHOW, getNode("t1_button")),
|
||||
new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
|
||||
new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
|
||||
new invokerChecker(EVENT_REORDER, getNode("t1_container"))
|
||||
];
|
||||
|
||||
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()
|
||||
@@ -88,11 +89,11 @@
|
||||
// the children.
|
||||
var tree =
|
||||
{ SECTION: [
|
||||
{ CHECKBUTTON: [ ] }, // div
|
||||
{ PUSHBUTTON: [ ] }, // button
|
||||
{ SECTION: [ ] } // subdiv
|
||||
{ CHECKBUTTON: [ ] }, // checkbox
|
||||
{ PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
|
||||
{ SECTION: [ ] } // subdiv from the subtree, ARIA owned
|
||||
] };
|
||||
testAccessibleTree("container2", tree);
|
||||
testAccessibleTree("t1_container", tree);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_SHOW, getNode, "child3"),
|
||||
new invokerChecker(EVENT_REORDER, getNode("container2"))
|
||||
new invokerChecker(EVENT_SHOW, getNode, "t1_child3"),
|
||||
new invokerChecker(EVENT_REORDER, getNode("t1_container"))
|
||||
];
|
||||
|
||||
this.invoke = function appendEl_invoke()
|
||||
{
|
||||
var div = document.createElement("div");
|
||||
div.setAttribute("id", "child3");
|
||||
div.setAttribute("id", "t1_child3");
|
||||
div.setAttribute("role", "radio")
|
||||
getNode("container2").appendChild(div);
|
||||
getNode("t1_container").appendChild(div);
|
||||
}
|
||||
|
||||
this.finalCheck = function appendEl_finalCheck()
|
||||
@@ -124,10 +159,11 @@
|
||||
{ SECTION: [
|
||||
{ CHECKBUTTON: [ ] },
|
||||
{ RADIOBUTTON: [ ] },
|
||||
{ PUSHBUTTON: [ ] }, // ARIA owned
|
||||
{ SECTION: [ ] } // ARIA owned
|
||||
{ PUSHBUTTON: [ ] }, // ARIA owned, t1_button
|
||||
{ SECTION: [ ] }, // ARIA owned, t1_subdiv
|
||||
{ GROUPING: [ ] } // ARIA owned, t1_group
|
||||
] };
|
||||
testAccessibleTree("container2", tree);
|
||||
testAccessibleTree("t1_container", tree);
|
||||
}
|
||||
|
||||
this.getID = function appendEl_getID()
|
||||
@@ -139,15 +175,15 @@
|
||||
function removeEl()
|
||||
{
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_HIDE, getNode, "t2_checkbox"),
|
||||
new invokerChecker(EVENT_SHOW, getNode, "t2_checkbox"),
|
||||
new invokerChecker(EVENT_REORDER, getNode("container2"))
|
||||
new invokerChecker(EVENT_HIDE, getNode, "t1_checkbox"),
|
||||
new invokerChecker(EVENT_SHOW, getNode, "t1_checkbox"),
|
||||
new invokerChecker(EVENT_REORDER, getNode("t1_container"))
|
||||
];
|
||||
|
||||
this.invoke = function removeEl_invoke()
|
||||
{
|
||||
// remove a container of t2_subdiv
|
||||
getNode("t2_span").parentNode.removeChild(getNode("t2_span"));
|
||||
// remove a container of t1_subdiv
|
||||
getNode("t1_span").parentNode.removeChild(getNode("t1_span"));
|
||||
}
|
||||
|
||||
this.finalCheck = function removeEl_finalCheck()
|
||||
@@ -157,14 +193,214 @@
|
||||
{ SECTION: [
|
||||
{ CHECKBUTTON: [ ] },
|
||||
{ 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()
|
||||
{
|
||||
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();
|
||||
|
||||
// test1
|
||||
gQueue.push(new removeARIAOwns());
|
||||
gQueue.push(new setARIAOwns());
|
||||
gQueue.push(new addIdToARIAOwns());
|
||||
gQueue.push(new appendEl());
|
||||
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
|
||||
}
|
||||
@@ -202,15 +452,31 @@
|
||||
<pre id="test">
|
||||
</pre>
|
||||
|
||||
<div id="container2" aria-owns="t2_checkbox t2_button">
|
||||
<div role="button" id="t2_button"></div>
|
||||
<div role="checkbox" id="t2_checkbox">
|
||||
<span id="t2_span">
|
||||
<div id="t2_subdiv"></div>
|
||||
<div id="t1_container" aria-owns="t1_checkbox t1_button">
|
||||
<div role="button" id="t1_button"></div>
|
||||
<div role="checkbox" id="t1_checkbox">
|
||||
<span id="t1_span">
|
||||
<div id="t1_subdiv"></div>
|
||||
</span>
|
||||
</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>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
|
||||
this.eventSeq = [
|
||||
new invokerChecker(EVENT_HIDE, getNode("child")),
|
||||
new invokerChecker(EVENT_REORDER, this.containerNode),
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user