Bug 1133213 - make aria-owns to alter the accessible tree, fire show/hide mutation events as we do for the accessible tree alterations, r=yzen, f=davidb
authorAlexander Surkov <surkov.alexander@gmail.com>
Tue, 15 Sep 2015 12:01:51 -0400
changeset 262621 a252f3d0a8c9
parent 262620 d90bfbc8688a
child 262622 1023659e3413
push id29378
push userkwierso@gmail.com
push date2015-09-16 00:16 +0000
treeherdermozilla-central@df10a3f6060f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyzen
bugs1133213
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1133213 - make aria-owns to alter the accessible tree, fire show/hide mutation events as we do for the accessible tree alterations, r=yzen, f=davidb
accessible/base/AccEvent.cpp
accessible/base/AccEvent.h
accessible/base/EventQueue.cpp
accessible/base/NotificationController.cpp
accessible/base/NotificationController.h
accessible/base/TreeWalker.cpp
accessible/base/TreeWalker.h
accessible/generic/Accessible.cpp
accessible/generic/Accessible.h
accessible/generic/DocAccessible.cpp
accessible/generic/DocAccessible.h
accessible/tests/mochitest/common.js
accessible/tests/mochitest/tree/test_aria_presentation.html
accessible/tests/mochitest/treeupdate/a11y.ini
accessible/tests/mochitest/treeupdate/test_ariaowns.html
dom/base/ChildIterator.h
--- a/accessible/base/AccEvent.cpp
+++ b/accessible/base/AccEvent.cpp
@@ -99,18 +99,19 @@ AccReorderEvent::IsShowHideEventTarget(c
   return 0;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // AccHideEvent
 ////////////////////////////////////////////////////////////////////////////////
 
 AccHideEvent::
-  AccHideEvent(Accessible* aTarget, nsINode* aTargetNode) :
-  AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget, aTargetNode)
+  AccHideEvent(Accessible* aTarget, nsINode* aTargetNode, bool aNeedsShutdown) :
+  AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget, aTargetNode),
+  mNeedsShutdown(aNeedsShutdown)
 {
   mNextSibling = mAccessible->NextSibling();
   mPrevSibling = mAccessible->PrevSibling();
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // AccShowEvent
--- a/accessible/base/AccEvent.h
+++ b/accessible/base/AccEvent.h
@@ -245,31 +245,34 @@ protected:
 
 
 /**
  * Accessible hide event.
  */
 class AccHideEvent: public AccMutationEvent
 {
 public:
-  AccHideEvent(Accessible* aTarget, nsINode* aTargetNode);
+  AccHideEvent(Accessible* aTarget, nsINode* aTargetNode,
+               bool aNeedsShutdown = true);
 
   // Event
   static const EventGroup kEventGroup = eHideEvent;
   virtual unsigned int GetEventGroups() const override
   {
     return AccMutationEvent::GetEventGroups() | (1U << eHideEvent);
   }
 
   // AccHideEvent
   Accessible* TargetParent() const { return mParent; }
   Accessible* TargetNextSibling() const { return mNextSibling; }
   Accessible* TargetPrevSibling() const { return mPrevSibling; }
+  bool NeedsShutdown() const { return mNeedsShutdown; }
 
 protected:
+  bool mNeedsShutdown;
   nsRefPtr<Accessible> mNextSibling;
   nsRefPtr<Accessible> mPrevSibling;
 
   friend class EventQueue;
 };
 
 
 /**
--- a/accessible/base/EventQueue.cpp
+++ b/accessible/base/EventQueue.cpp
@@ -541,15 +541,17 @@ EventQueue::ProcessEventQueue()
       // Fire text change events.
       AccMutationEvent* mutationEvent = downcast_accEvent(event);
       if (mutationEvent) {
         if (mutationEvent->mTextChangeEvent)
           nsEventShell::FireEvent(mutationEvent->mTextChangeEvent);
       }
     }
 
-    if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE)
+    AccHideEvent* hideEvent = downcast_accEvent(event);
+    if (hideEvent && hideEvent->NeedsShutdown()) {
       mDocument->ShutdownChildrenInSubtree(event->mAccessible);
+    }
 
     if (!mDocument)
       return;
   }
 }
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -104,30 +104,30 @@ NotificationController::ScheduleContentI
   nsRefPtr<ContentInsertion> insertion = new ContentInsertion(mDocument,
                                                               aContainer);
   if (insertion && insertion->InitChildList(aStartChildNode, aEndChildNode) &&
       mContentInsertions.AppendElement(insertion)) {
     ScheduleProcessing();
   }
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// NotificationCollector: protected
-
 void
 NotificationController::ScheduleProcessing()
 {
   // If notification flush isn't planed yet start notification flush
   // asynchronously (after style and layout).
   if (mObservingState == eNotObservingRefresh) {
     if (mPresShell->AddRefreshObserver(this, Flush_Display))
       mObservingState = eRefreshObserving;
   }
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: protected
+
 bool
 NotificationController::IsUpdatePending()
 {
   return mPresShell->IsLayoutFlushObserver() ||
     mObservingState == eRefreshProcessingForUpdate ||
     mContentInsertions.Length() != 0 || mNotifications.Length() != 0 ||
     mTextHash.Count() != 0 ||
     !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
--- a/accessible/base/NotificationController.h
+++ b/accessible/base/NotificationController.h
@@ -3,16 +3,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_NotificationController_h_
 #define mozilla_a11y_NotificationController_h_
 
 #include "EventQueue.h"
 
+#include "mozilla/IndexSequence.h"
+#include "mozilla/Tuple.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsRefreshDriver.h"
 
 #ifdef A11Y_LOG
 #include "Logging.h"
 #endif
 
 namespace mozilla {
@@ -49,42 +51,42 @@ private:
 
 /**
  * Template class for generic notification.
  *
  * @note  Instance is kept as a weak ref, the caller must guarantee it exists
  *        longer than the document accessible owning the notification controller
  *        that this notification is processed by.
  */
-template<class Class, class Arg>
+template<class Class, class ... Args>
 class TNotification : public Notification
 {
 public:
-  typedef void (Class::*Callback)(Arg*);
+  typedef void (Class::*Callback)(Args* ...);
 
-  TNotification(Class* aInstance, Callback aCallback, Arg* aArg) :
-    mInstance(aInstance), mCallback(aCallback), mArg(aArg) { }
+  TNotification(Class* aInstance, Callback aCallback, Args* ... aArgs) :
+    mInstance(aInstance), mCallback(aCallback), mArgs(aArgs...) { }
   virtual ~TNotification() { mInstance = nullptr; }
 
   virtual void Process() override
-  {
-    (mInstance->*mCallback)(mArg);
-
-    mInstance = nullptr;
-    mCallback = nullptr;
-    mArg = nullptr;
-  }
+    { ProcessHelper(typename IndexSequenceFor<Args...>::Type()); }
 
 private:
   TNotification(const TNotification&);
   TNotification& operator = (const TNotification&);
 
+  template <size_t... Indices>
+    void ProcessHelper(IndexSequence<Indices...>)
+  {
+     (mInstance->*mCallback)(Get<Indices>(mArgs)...);
+  }
+
   Class* mInstance;
   Callback mCallback;
-  nsRefPtr<Arg> mArg;
+  Tuple<nsRefPtr<Args> ...> mArgs;
 };
 
 /**
  * Used to process notifications from core for the document accessible.
  */
 class NotificationController final : public EventQueue,
                                      public nsARefreshObserver
 {
@@ -127,16 +129,22 @@ public:
   /**
    * Pend accessible tree update for content insertion.
    */
   void ScheduleContentInsertion(Accessible* aContainer,
                                 nsIContent* aStartChildNode,
                                 nsIContent* aEndChildNode);
 
   /**
+   * Start to observe refresh to make notifications and events processing after
+   * layout.
+   */
+  void ScheduleProcessing();
+
+  /**
    * Process the generic notification synchronously if there are no pending
    * layout changes and no notifications are pending or being processed right
    * now. Otherwise, queue it up to process asynchronously.
    *
    * @note  The caller must guarantee that the given instance still exists when
    *        the notification is processed.
    */
   template<class Class, class Arg>
@@ -160,45 +168,38 @@ public:
   }
 
   /**
    * Schedule the generic notification to process asynchronously.
    *
    * @note  The caller must guarantee that the given instance still exists when
    *        the notification is processed.
    */
-  template<class Class, class Arg>
+  template<class Class>
   inline void ScheduleNotification(Class* aInstance,
-                                   typename TNotification<Class, Arg>::Callback aMethod,
-                                   Arg* aArg)
+                                   typename TNotification<Class>::Callback aMethod)
   {
     nsRefPtr<Notification> notification =
-      new TNotification<Class, Arg>(aInstance, aMethod, aArg);
+      new TNotification<Class>(aInstance, aMethod);
     if (notification && mNotifications.AppendElement(notification))
       ScheduleProcessing();
   }
 
 #ifdef DEBUG
   bool IsUpdating() const
     { return mObservingState == eRefreshProcessingForUpdate; }
 #endif
 
 protected:
   virtual ~NotificationController();
 
   nsCycleCollectingAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
   /**
-   * Start to observe refresh to make notifications and events processing after
-   * layout.
-   */
-  void ScheduleProcessing();
-
-  /**
    * Return true if the accessible tree state update is pending.
    */
   bool IsUpdatePending();
 
 private:
   NotificationController(const NotificationController&);
   NotificationController& operator = (const NotificationController&);
 
--- a/accessible/base/TreeWalker.cpp
+++ b/accessible/base/TreeWalker.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TreeWalker.h"
 
 #include "Accessible.h"
+#include "AccIterator.h"
 #include "nsAccessibilityService.h"
 #include "DocAccessible.h"
 
 #include "mozilla/dom/ChildIterator.h"
 #include "mozilla/dom/Element.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
@@ -45,30 +46,26 @@ TreeWalker::~TreeWalker()
 // TreeWalker: private
 
 Accessible*
 TreeWalker::NextChild()
 {
   if (mStateStack.IsEmpty())
     return nullptr;
 
-  dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
+  ChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
   while (top) {
-    while (nsIContent* childNode = top->GetNextChild()) {
-      bool isSubtreeHidden = false;
-      Accessible* accessible = mFlags & eWalkCache ?
-        mDoc->GetAccessible(childNode) :
-        GetAccService()->GetOrCreateAccessible(childNode, mContext,
-                                               &isSubtreeHidden);
-
-      if (accessible)
-        return accessible;
+    Accessible* child = nullptr;
+    bool skipSubtree = false;
+    while (nsIContent* childNode = Next(top, &child, &skipSubtree)) {
+      if (child)
+        return child;
 
       // Walk down into subtree to find accessibles.
-      if (!isSubtreeHidden && childNode->IsElement())
+      if (!skipSubtree && childNode->IsElement())
         top = PushState(childNode);
     }
 
     top = PopState();
   }
 
   // If we traversed the whole subtree of the anchor node. Move to next node
   // relative anchor node within the context subtree if possible.
@@ -77,34 +74,73 @@ TreeWalker::NextChild()
 
   nsINode* contextNode = mContext->GetNode();
   while (mAnchorNode != contextNode) {
     nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
     if (!parentNode || !parentNode->IsElement())
       return nullptr;
 
     nsIContent* parent = parentNode->AsElement();
-    top = mStateStack.AppendElement(dom::AllChildrenIterator(parent,
-                                                             mChildFilter));
-    while (nsIContent* childNode = top->GetNextChild()) {
+    top = PushState(parent);
+    while (nsIContent* childNode = Next(top)) {
       if (childNode == mAnchorNode) {
         mAnchorNode = parent;
         return NextChild();
       }
     }
 
     // XXX We really should never get here, it means we're trying to find an
     // accessible for a dom node where iterating over its parent's children
     // doesn't return it. However this sometimes happens when we're asked for
     // the nearest accessible to place holder content which we ignore.
     mAnchorNode = parent;
   }
 
   return nullptr;
 }
 
-dom::AllChildrenIterator*
+nsIContent*
+TreeWalker::Next(ChildrenIterator* aIter, Accessible** aAccesible,
+                 bool* aSkipSubtree)
+{
+  nsIContent* childEl = aIter->mDOMIter.GetNextChild();
+  if (!aAccesible)
+    return childEl;
+
+  *aAccesible = nullptr;
+  *aSkipSubtree = false;
+
+  if (childEl) {
+    Accessible* accessible = mFlags & eWalkCache ?
+      mDoc->GetAccessible(childEl) :
+      GetAccService()->GetOrCreateAccessible(childEl, mContext, aSkipSubtree);
+
+    // Ignore the accessible and its subtree if it was repositioned by means of
+    // aria-owns.
+    if (accessible) {
+      if (accessible->IsRepositioned()) {
+        *aSkipSubtree = true;
+      } else {
+        *aAccesible = accessible;
+      }
+    }
+    return childEl;
+  }
+
+  // At last iterate over ARIA owned children.
+  Accessible* parent = mDoc->GetAccessible(aIter->mDOMIter.Parent());
+  if (parent) {
+    Accessible* child = mDoc->ARIAOwnedAt(parent, aIter->mARIAOwnsIdx++);
+    if (child) {
+      *aAccesible = child;
+      return child->GetContent();
+    }
+  }
+  return nullptr;
+}
+
+TreeWalker::ChildrenIterator*
 TreeWalker::PopState()
 {
   size_t length = mStateStack.Length();
   mStateStack.RemoveElementAt(length - 1);
   return mStateStack.IsEmpty() ? nullptr : &mStateStack[mStateStack.Length() - 1];
 }
--- a/accessible/base/TreeWalker.h
+++ b/accessible/base/TreeWalker.h
@@ -52,37 +52,47 @@ public:
    */
   Accessible* NextChild();
 
 private:
   TreeWalker();
   TreeWalker(const TreeWalker&);
   TreeWalker& operator =(const TreeWalker&);
 
+  struct ChildrenIterator {
+    ChildrenIterator(nsIContent* aNode, uint32_t aFilter) :
+      mDOMIter(aNode, aFilter), mARIAOwnsIdx(0) { }
+
+    dom::AllChildrenIterator mDOMIter;
+    uint32_t mARIAOwnsIdx;
+  };
+
+  nsIContent* Next(ChildrenIterator* aIter, Accessible** aAccessible = nullptr,
+                   bool* aSkipSubtree = nullptr);
+
   /**
    * Create new state for the given node and push it on top of stack.
    *
    * @note State stack is used to navigate up/down the DOM subtree during
    *        accessible children search.
    */
-  dom::AllChildrenIterator* PushState(nsIContent* aContent)
+  ChildrenIterator* PushState(nsIContent* aContent)
   {
-    return mStateStack.AppendElement(dom::AllChildrenIterator(aContent,
-                                                              mChildFilter));
+    return mStateStack.AppendElement(ChildrenIterator(aContent, mChildFilter));
   }
 
   /**
    * Pop state from stack.
    */
-  dom::AllChildrenIterator* PopState();
+  ChildrenIterator* PopState();
 
   DocAccessible* mDoc;
   Accessible* mContext;
   nsIContent* mAnchorNode;
-  nsAutoTArray<dom::AllChildrenIterator, 20> mStateStack;
+  nsAutoTArray<ChildrenIterator, 20> mStateStack;
   int32_t mChildFilter;
   uint32_t mFlags;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_TreeWalker_h_
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -1969,18 +1969,18 @@ Accessible::NativeName(nsString& aName)
 void
 Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent)
 {
   NS_PRECONDITION(aParent, "This method isn't used to set null parent!");
 
   if (mParent) {
     if (mParent != aParent) {
       NS_ERROR("Adopting child!");
+      mParent->InvalidateChildrenGroupInfo();
       mParent->RemoveChild(this);
-      mParent->InvalidateChildrenGroupInfo();
     } else {
       NS_ERROR("Binding to the same parent!");
       return;
     }
   }
 
   mParent = aParent;
   mIndexInParent = aIndexInParent;
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -155,16 +155,18 @@ public:
    */
   virtual nsINode* GetNode() const;
   inline already_AddRefed<nsIDOMNode> DOMNode() const
   {
     nsCOMPtr<nsIDOMNode> DOMNode = do_QueryInterface(GetNode());
     return DOMNode.forget();
   }
   nsIContent* GetContent() const { return mContent; }
+  mozilla::dom::Element* Elm() const
+    { return mContent && mContent->IsElement() ? mContent->AsElement() : nullptr; }
 
   /**
    * Return node type information of DOM node associated with the accessible.
    */
   bool IsContent() const
     { return GetNode() && GetNode()->IsNodeOfType(nsINode::eCONTENT); }
 
   /**
@@ -895,31 +897,43 @@ public:
   {
     if (aIsSurviving)
       mStateFlags |= eSurvivingInUpdate;
     else
       mStateFlags &= ~eSurvivingInUpdate;
   }
 
   /**
+   * 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)
+  {
+    if (aRepositioned)
+      mStateFlags |= eRepositioned;
+    else
+      mStateFlags &= ~eRepositioned;
+  }
+
+  /**
    * Return true if this accessible has a parent whose name depends on this
    * accessible.
    */
   bool HasNameDependentParent() const
     { return mContextFlags & eHasNameDependentParent; }
 
   /**
    * Return true if aria-hidden="true" is applied to the accessible or inherited
    * from the parent.
    */
   bool IsARIAHidden() const { return mContextFlags & eARIAHidden; }
   void SetARIAHidden(bool aIsDefined);
 
 protected:
-
   virtual ~Accessible();
 
   /**
    * Return the accessible name provided by native markup. It doesn't take
    * into account ARIA markup used to specify the name.
    */
   virtual mozilla::a11y::ENameValueFlag NativeName(nsString& aName);
 
@@ -985,18 +999,19 @@ protected:
     eIsNotInDocument = 1 << 1, // accessible is not in document
     eSharedNode = 1 << 2, // accessible shares DOM node from another accessible
     eNotNodeMapEntry = 1 << 3, // accessible shouldn't be in document node map
     eHasNumericValue = 1 << 4, // accessible has a numeric value
     eGroupInfoDirty = 1 << 5, // accessible needs to update group info
     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
 
-    eLastStateFlag = eSurvivingInUpdate
+    eLastStateFlag = eRepositioned
   };
 
   /**
    * Flags used for contextual information about the accessible.
    */
   enum ContextFlags {
     eHasNameDependentParent = 1 << 0, // Parent's name depends on this accessible.
     eARIAHidden = 1 << 1,
@@ -1101,17 +1116,17 @@ protected:
   nsCOMPtr<nsIContent> mContent;
   DocAccessible* mDoc;
 
   nsRefPtr<Accessible> mParent;
   nsTArray<nsRefPtr<Accessible> > mChildren;
   int32_t mIndexInParent;
 
   static const uint8_t kChildrenFlagsBits = 2;
-  static const uint8_t kStateFlagsBits = 9;
+  static const uint8_t kStateFlagsBits = 10;
   static const uint8_t kContextFlagsBits = 2;
   static const uint8_t kTypeBits = 6;
   static const uint8_t kGenericTypesBits = 14;
 
   /**
    * Keep in sync with ChildrenFlags, StateFlags, ContextFlags, and AccTypes.
    */
   uint32_t mChildrenFlags : kChildrenFlagsBits;
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -702,17 +702,17 @@ DocAccessible::AttributeWillChange(nsIDo
 
     accessible = this;
   }
 
   // Update dependent IDs cache. Take care of elements that are accessible
   // because dependent IDs cache doesn't contain IDs from non accessible
   // elements.
   if (aModType != nsIDOMMutationEvent::ADDITION)
-    RemoveDependentIDsFor(aElement, aAttribute);
+    RemoveDependentIDsFor(accessible, aAttribute);
 
   // 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
   // old values.
 
   // XXX TODO: bugs 472142, 472143.
   // Here we will want to cache whatever attribute values we are interested
@@ -764,17 +764,17 @@ DocAccessible::AttributeChanged(nsIDocum
 
   // Update dependent IDs cache. Take care of accessible elements because no
   // accessible element means either the element is not accessible at all or
   // its accessible will be created later. It doesn't make sense to keep
   // dependent IDs for non accessible elements. For the second case we'll update
   // dependent IDs cache when its accessible is created.
   if (aModType == nsIDOMMutationEvent::MODIFICATION ||
       aModType == nsIDOMMutationEvent::ADDITION) {
-    AddDependentIDsFor(aElement, aAttribute);
+    AddDependentIDsFor(accessible, aAttribute);
   }
 }
 
 // DocAccessible protected member
 void
 DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
                                     int32_t aNameSpaceID, nsIAtom* aAttribute)
 {
@@ -1236,19 +1236,17 @@ DocAccessible::BindToDocument(Accessible
   if (aAccessible->IsNodeMapEntry())
     mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
 
   // Put into unique ID cache.
   mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible);
 
   aAccessible->SetRoleMapEntry(aRoleMapEntry);
 
-  nsIContent* content = aAccessible->GetContent();
-  if (content && content->IsElement())
-    AddDependentIDsFor(content->AsElement());
+  AddDependentIDsFor(aAccessible);
 }
 
 void
 DocAccessible::UnbindFromDocument(Accessible* aAccessible)
 {
   NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
                "Unbinding the unbound accessible!");
 
@@ -1333,16 +1331,59 @@ DocAccessible::ProcessInvalidationList()
     if (!HasAccessible(content)) {
       Accessible* container = GetContainerAccessible(content);
       if (container)
         UpdateTreeOnInsertion(container);
     }
   }
 
   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;
+    Accessible* child = GetAccessible(mARIAOwnsInvalidationList[idx].mChild);
+    if (!child) {
+      continue;
+    }
+
+    // XXX: update context flags
+    {
+      Accessible* oldParent = child->Parent();
+      nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(oldParent);
+      nsRefPtr<AccMutationEvent> hideEvent =
+        new AccHideEvent(child, child->GetContent(), false);
+      FireDelayedEvent(hideEvent);
+      reorderEvent->AddSubMutationEvent(hideEvent);
+
+      AutoTreeMutation mut(oldParent);
+      oldParent->RemoveChild(child);
+
+      MaybeNotifyOfValueChange(oldParent);
+      FireDelayedEvent(reorderEvent);
+    }
+
+    {
+      nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(owner);
+      nsRefPtr<AccMutationEvent> showEvent =
+        new AccShowEvent(child, child->GetContent());
+      FireDelayedEvent(showEvent);
+      reorderEvent->AddSubMutationEvent(showEvent);
+
+      AutoTreeMutation mut(owner);
+      owner->AppendChild(child);
+
+      MaybeNotifyOfValueChange(owner);
+      FireDelayedEvent(reorderEvent);
+    }
+
+    child->SetRepositioned(true);
+  }
+
+  mARIAOwnsInvalidationList.Clear();
 }
 
 Accessible*
 DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
 {
 if (!aNode->IsContent() || !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area))
     return GetAccessible(aNode);
 
@@ -1491,104 +1532,207 @@ DocAccessible::ProcessLoad()
 
   // Fire busy state change event.
   nsRefPtr<AccEvent> stateEvent =
     new AccStateChangeEvent(this, states::BUSY, false);
   FireDelayedEvent(stateEvent);
 }
 
 void
-DocAccessible::AddDependentIDsFor(dom::Element* aRelProviderElm,
-                                  nsIAtom* aRelAttr)
+DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
 {
+  dom::Element* relProviderEl = aRelProvider->Elm();
+  if (!relProviderEl)
+    return;
+
   for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
     nsIAtom* relAttr = *kRelationAttrs[idx];
     if (aRelAttr && aRelAttr != relAttr)
       continue;
 
     if (relAttr == nsGkAtoms::_for) {
-      if (!aRelProviderElm->IsAnyOfHTMLElements(nsGkAtoms::label,
-                                                nsGkAtoms::output))
+      if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
+                                               nsGkAtoms::output))
         continue;
 
     } else if (relAttr == nsGkAtoms::control) {
-      if (!aRelProviderElm->IsAnyOfXULElements(nsGkAtoms::label,
-                                               nsGkAtoms::description))
+      if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
+                                              nsGkAtoms::description))
         continue;
     }
 
-    IDRefsIterator iter(this, aRelProviderElm, relAttr);
+    IDRefsIterator iter(this, relProviderEl, relAttr);
     while (true) {
       const nsDependentSubstring id = iter.NextID();
       if (id.IsEmpty())
         break;
 
       AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
       if (!providers) {
         providers = new AttrRelProviderArray();
         if (providers) {
           mDependentIDsHash.Put(id, providers);
         }
       }
 
       if (providers) {
         AttrRelProvider* provider =
-          new AttrRelProvider(relAttr, aRelProviderElm);
+          new AttrRelProvider(relAttr, relProviderEl);
         if (provider) {
           providers->AppendElement(provider);
 
           // We've got here during the children caching. If the referenced
           // content is not accessible then store it to pend its container
           // children invalidation (this happens immediately after the caching
           // is finished).
           nsIContent* dependentContent = iter.GetElem(id);
-          if (dependentContent && !HasAccessible(dependentContent)) {
-            mInvalidationList.AppendElement(dependentContent);
+          if (dependentContent) {
+            if (!HasAccessible(dependentContent)) {
+              mInvalidationList.AppendElement(dependentContent);
+            }
+
+            if (relAttr == nsGkAtoms::aria_owns) {
+              // Dependent content cannot point to other aria-owns content or
+              // their parents. Ignore it if so.
+              // XXX: note, this alg may make invalid the scenario when X owns Y
+              // and Y owns Z, we should have something smarter to handle that.
+              bool isvalid = true;
+              for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
+                Accessible* owner = it.Key();
+                nsIContent* parentEl = owner->GetContent();
+                while (parentEl && parentEl != dependentContent) {
+                  parentEl = parentEl->GetParent();
+                }
+                if (parentEl) {
+                  isvalid = false;
+                  break;
+                }
+              }
+              if (isvalid) {
+                // ARIA owns also cannot refer to itself or a parent.
+                nsIContent* parentEl = relProviderEl;
+                while (parentEl && parentEl != dependentContent) {
+                  parentEl = parentEl->GetParent();
+                }
+                if (parentEl) {
+                  isvalid = false;
+                }
+
+                if (isvalid) {
+                  nsTArray<nsIContent*>* list =
+                    mARIAOwnsHash.LookupOrAdd(aRelProvider);
+                  list->AppendElement(dependentContent);
+
+                  mARIAOwnsInvalidationList.AppendElement(
+                    ARIAOwnsPair(aRelProvider, dependentContent));
+                }
+              }
+            }
           }
         }
       }
     }
 
     // If the relation attribute is given then we don't have anything else to
     // check.
     if (aRelAttr)
       break;
   }
+
+  // Make sure to schedule the tree update if needed.
+  mNotificationController->ScheduleProcessing();
 }
 
 void
-DocAccessible::RemoveDependentIDsFor(dom::Element* aRelProviderElm,
+DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
                                      nsIAtom* aRelAttr)
 {
+  dom::Element* relProviderElm = aRelProvider->Elm();
+  if (!relProviderElm)
+    return;
+
   for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
     nsIAtom* relAttr = *kRelationAttrs[idx];
     if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
       continue;
 
-    IDRefsIterator iter(this, aRelProviderElm, relAttr);
+    IDRefsIterator iter(this, relProviderElm, relAttr);
     while (true) {
       const nsDependentSubstring id = iter.NextID();
       if (id.IsEmpty())
         break;
 
       AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
       if (providers) {
         for (uint32_t jdx = 0; jdx < providers->Length(); ) {
           AttrRelProvider* provider = (*providers)[jdx];
           if (provider->mRelAttr == relAttr &&
-              provider->mContent == aRelProviderElm)
+              provider->mContent == relProviderElm)
             providers->RemoveElement(provider);
           else
             jdx++;
         }
         if (providers->Length() == 0)
           mDependentIDsHash.Remove(id);
       }
     }
 
+    // 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.
+        nsRefPtr<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()) {
+              {
+                nsRefPtr<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)
       break;
   }
 }
 
 bool
@@ -1801,16 +1945,21 @@ DocAccessible::UpdateTreeOnRemoval(Acces
       if (childNode != containerNode) {
         updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
       } else {
         idx++;
       }
     }
   }
 
+  // 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;
 
   MaybeNotifyOfValueChange(aContainer);
   FireDelayedEvent(reorderEvent);
 }
 
@@ -1885,16 +2034,31 @@ DocAccessible::UpdateTreeInternal(Access
     FocusMgr()->DispatchFocusEvent(this, focusedAcc);
     SelectionMgr()->SetControlSelectionListener(focusedAcc->GetNode()->AsElement());
   }
 
   return updateFlags;
 }
 
 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->GetFrame()) {
+        UpdateTreeOnRemoval(child->Parent(), childEl);
+      }
+    }
+  }
+}
+
+void
 DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
                                       Accessible** aFocusedAcc)
 {
   // If the accessible is focused then report a focus event after all related
   // mutation events.
   if (aFocusedAcc && !*aFocusedAcc &&
       FocusMgr()->HasDOMFocus(aRoot->GetContent()))
     *aFocusedAcc = aRoot;
@@ -1921,20 +2085,17 @@ DocAccessible::CacheChildrenInSubtree(Ac
       FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
   }
 }
 
 void
 DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
 {
   aRoot->mStateFlags |= eIsNotInDocument;
-
-  nsIContent* rootContent = aRoot->GetContent();
-  if (rootContent && rootContent->IsElement())
-    RemoveDependentIDsFor(rootContent->AsElement());
+  RemoveDependentIDsFor(aRoot);
 
   uint32_t count = aRoot->ContentChildCount();
   for (uint32_t idx = 0; idx < count; idx++)
     UncacheChildrenInSubtree(aRoot->ContentChildAt(idx));
 
   if (aRoot->IsNodeMapEntry() &&
       mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
     mNodeToAccessibleMap.Remove(aRoot->GetNode());
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -27,17 +27,17 @@ const uint32_t kDefaultCacheLength = 128
 
 namespace mozilla {
 namespace a11y {
 
 class DocManager;
 class NotificationController;
 class DocAccessibleChild;
 class RelatedAccIterator;
-template<class Class, class Arg>
+template<class Class, class ... Args>
 class TNotification;
 
 class DocAccessible : public HyperTextAccessibleWrap,
                       public nsIDocumentObserver,
                       public nsIObserver,
                       public nsIScrollPositionListener,
                       public nsSupportsWeakReference,
                       public nsIAccessiblePivotObserver
@@ -277,16 +277,32 @@ public:
   }
 
   /**
    * Return an accessible for the given node or its first accessible descendant.
    */
   Accessible* GetAccessibleOrDescendant(nsINode* aNode) const;
 
   /**
+   * Returns aria-owns seized child at the given index.
+   */
+  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;
+      }
+    }
+    return nullptr;
+  }
+
+  /**
    * Return true if the given ID is referred by relation attribute.
    *
    * @note Different elements may share the same ID if they are hosted inside
    *       XBL bindings. Be careful the result of this method may be  senseless
    *       while it's called for XUL elements (where XBL is used widely).
    */
   bool IsDependentID(const nsAString& aID) const
     { return mDependentIDsHash.Get(aID, nullptr); }
@@ -401,28 +417,28 @@ protected:
   /**
    * 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(dom::Element* aRelProviderElm,
+  void AddDependentIDsFor(Accessible* aRelProvider,
                           nsIAtom* aRelAttr = nullptr);
 
   /**
    * 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(dom::Element* aRelProviderElm,
+  void RemoveDependentIDsFor(Accessible* aRelProvider,
                              nsIAtom* aRelAttr = nullptr);
 
   /**
    * Update or recreate an accessible depending on a changed attribute.
    *
    * @param aElement   [in] the element the attribute was changed on
    * @param aAttribute [in] the changed attribute
    * @return            true if an action was taken on the attribute change
@@ -487,16 +503,21 @@ protected:
     eAccessible = 1,
     eAlertAccessible = 2
   };
 
   uint32_t UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
                               AccReorderEvent* aReorderEvent);
 
   /**
+   * Validates all aria-owns connections and updates the tree accordingly.
+   */
+  void ValidateARIAOwned();
+
+  /**
    * Create accessible tree.
    *
    * @param aRoot       [in] a root of subtree to create
    * @param aFocusedAcc [in, optional] a focused accessible under created
    *                      subtree if any
    */
   void CacheChildrenInSubtree(Accessible* aRoot,
                               Accessible** aFocusedAcc = nullptr);
@@ -642,16 +663,35 @@ protected:
    * Used for our caching algorithm. We store the list of nodes that should be
    * invalidated.
    *
    * @see ProcessInvalidationList
    */
   nsTArray<nsIContent*> mInvalidationList;
 
   /**
+   * Holds a list of aria-owns relations.
+   */
+  nsClassHashtable<nsPtrHashKey<Accessible>, nsTArray<nsIContent*> >
+    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; }
+
+    Accessible* mOwner;
+    nsIContent* mChild;
+  };
+  nsTArray<ARIAOwnsPair> mARIAOwnsInvalidationList;
+
+  /**
    * Used to process notification from core and accessible events.
    */
   nsRefPtr<NotificationController> mNotificationController;
   friend class EventQueue;
   friend class NotificationController;
 
 private:
 
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -447,36 +447,49 @@ function testAccessibleTree(aAccOrElmOrI
     }
   }
 
   // Test children.
   if ("children" in accTree && accTree["children"] instanceof Array) {
     var children = acc.children;
     var childCount = children.length;
 
-
     if (accTree.children.length != childCount) {
       for (var i = 0; i < Math.max(accTree.children.length, childCount); i++) {
         var accChild;
         try {
           accChild = children.queryElementAt(i, nsIAccessible);
-          if (!accTree.children[i]) {
+
+          testChild = accTree.children[i];
+          if (!testChild) {
             ok(false, prettyName(acc) + " has an extra child at index " + i +
               " : " + prettyName(accChild));
+            continue;
           }
-          if (accChild.role !== accTree.children[i].role) {
+
+          var key = Object.keys(testChild)[0];
+          var roleName = "ROLE_" + key;
+          if (roleName in nsIAccessibleRole) {
+            testChild = {
+              role: nsIAccessibleRole[roleName],
+              children: testChild[key]
+            };
+          }
+
+          if (accChild.role !== testChild.role) {
             ok(false, prettyName(accTree) + " and " + prettyName(acc) +
               " have different children at index " + i + " : " +
-              prettyName(accTree.children[i]) + ", " + prettyName(accChild));
+              prettyName(testChild) + ", " + prettyName(accChild));
           }
           info("Matching " + prettyName(accTree) + " and " + prettyName(acc) +
                " child at index " + i + " : " + prettyName(accChild));
         } catch (e) {
           ok(false, prettyName(accTree) + " has an extra child at index " + i +
-             " : " + prettyName(accTree.children[i]));
+             " : " + prettyName(testChild) + ", " + e);
+          throw e;
         }
       }
     } else {
       if (aFlags & kSkipTreeFullCheck) {
         for (var i = 0; i < childCount; i++) {
           var child = children.queryElementAt(i, nsIAccessible);
           testAccessibleTree(child, accTree.children[i], aFlags);
         }
--- a/accessible/tests/mochitest/tree/test_aria_presentation.html
+++ b/accessible/tests/mochitest/tree/test_aria_presentation.html
@@ -82,29 +82,26 @@
       ] };
     testAccessibleTree("list_cnt", tree);
 
     // Has ARIA globals or referred by ARIA relationship, role='presentation'
     // and role='none' are ignored.
     tree =
       { SECTION: [ // container
         { LABEL: [ // label, has aria-owns
-          { TEXT_LEAF: [ ] }
-        ] },
-        { TEXT_LEAF: [ ] },
-        { LABEL: [ // label, referenced by aria-owns
-          { TEXT_LEAF: [ ] }
+          { TEXT_LEAF: [ ] },
+          { LABEL: [ // label, referenced by aria-owns
+            { TEXT_LEAF: [ ] }
+          ] },
         ] },
-        { TEXT_LEAF: [ ] },
         { LABEL: [ // label, has aria-owns
-          { TEXT_LEAF: [ ] }
-        ] },
-        { TEXT_LEAF: [ ] },
-        { LABEL: [ // label, referenced by aria-owns
-          { TEXT_LEAF: [ ] }
+          { TEXT_LEAF: [ ] },
+          { LABEL: [ // label, referenced by aria-owns
+            { TEXT_LEAF: [ ] }
+          ] }
         ] }
       ] };
     testAccessibleTree("airaglobalprop_cnt", tree);
 
     SimpleTest.finish();
   }
 
   SimpleTest.waitForExplicitFinish();
@@ -167,17 +164,16 @@
     <ul role="presentation">
       <li>item</li>
     </ul>
     <ul role="none">
       <li>item</li>
     </ul>
   </div>
 
-  <div id="airaglobalprop_cnt">
-    <label role="presentation" aria-owns="ariaowned">has aria-owns</label>
-    <label role="presentation" id="ariaowned">referred by aria-owns</label>
-    <label role="none" aria-owns="ariaowned2">has aria-owns</label>
-    <label role="none" id="ariaowned2">referred by aria-owns</label>
-  </div>
+  <div id="airaglobalprop_cnt"><label
+    role="presentation" aria-owns="ariaowned">has aria-owns</label><label
+    role="presentation" id="ariaowned">referred by aria-owns</label><label
+    role="none" aria-owns="ariaowned2">has aria-owns</label><label
+    role="none" id="ariaowned2">referred by aria-owns</label></div>
 
 </body>
 </html>
--- a/accessible/tests/mochitest/treeupdate/a11y.ini
+++ b/accessible/tests/mochitest/treeupdate/a11y.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 
 [test_ariadialog.html]
+[test_ariaowns.html]
 [test_bug852150.xhtml]
 [test_bug883708.xhtml]
 [test_bug884251.xhtml]
 [test_bug895082.html]
 [test_bug1040735.html]
 [test_bug1100602.html]
 [test_bug1175913.html]
 [test_bug1189277.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <title>@aria-owns attribute testing</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <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="../role.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
+
+  <script type="application/javascript">
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Invokers
+    ////////////////////////////////////////////////////////////////////////////
+
+    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"))
+      ];
+
+      this.invoke = function removeARIAOwns_invoke()
+      {
+        // children are swapped
+        var tree =
+          { SECTION: [
+              { CHECKBUTTON: [
+                { SECTION: [] }
+              ] },
+              { PUSHBUTTON: [ ] }
+          ] };
+        testAccessibleTree("container2", tree);
+
+        getNode("container2").removeAttribute("aria-owns");
+      }
+
+      this.finalCheck = function removeARIAOwns_finalCheck()
+      {
+        // children follow the DOM order
+        var tree =
+          { SECTION: [
+              { PUSHBUTTON: [ ] },
+              { CHECKBUTTON: [
+                  { SECTION: [] }
+              ] }
+          ] };
+        testAccessibleTree("container2", tree);
+      }
+
+      this.getID = function removeARIAOwns_getID()
+      {
+        return "Remove @aria-owns attribute";
+      }
+    }
+
+    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"))
+      ];
+
+      this.invoke = function setARIAOwns_invoke()
+      {
+        getNode("container2").setAttribute("aria-owns", "t2_button t2_subdiv");
+      }
+
+      this.finalCheck = function setARIAOwns_finalCheck()
+      {
+        // children are swapped again, button and subdiv are appended to
+        // the children.
+        var tree =
+          { SECTION: [
+              { CHECKBUTTON: [ ] }, // div
+              { PUSHBUTTON: [ ] }, // button
+              { SECTION: [ ] } // subdiv
+          ] };
+        testAccessibleTree("container2", tree);
+      }
+
+      this.getID = function setARIAOwns_getID()
+      {
+        return "Set @aria-owns attribute";
+      }
+    }
+
+    function appendEl()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, getNode, "child3"),
+        new invokerChecker(EVENT_REORDER, getNode("container2"))
+      ];
+
+      this.invoke = function appendEl_invoke()
+      {
+        var div = document.createElement("div");
+        div.setAttribute("id", "child3");
+        div.setAttribute("role", "radio")
+        getNode("container2").appendChild(div);
+      }
+
+      this.finalCheck = function appendEl_finalCheck()
+      {
+        // children are invalidated, they includes aria-owns swapped kids and
+        // newly inserted child.
+        var tree =
+          { SECTION: [
+              { CHECKBUTTON: [ ] },
+              { RADIOBUTTON: [ ] },
+              { PUSHBUTTON: [ ] }, // ARIA owned
+              { SECTION: [ ] } // ARIA owned
+          ] };
+        testAccessibleTree("container2", tree);
+      }
+
+      this.getID = function appendEl_getID()
+      {
+        return "Append child under @aria-owns element";
+      }
+    }
+
+    function removeEl()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode, "t2_checkbox"),
+        new invokerChecker(EVENT_SHOW, getNode, "t2_checkbox"),
+        new invokerChecker(EVENT_REORDER, getNode("container2"))
+      ];
+
+      this.invoke = function removeEl_invoke()
+      {
+        // remove a container of t2_subdiv
+        getNode("t2_span").parentNode.removeChild(getNode("t2_span"));
+      }
+
+      this.finalCheck = function removeEl_finalCheck()
+      {
+        // subdiv should go away
+        var tree =
+          { SECTION: [
+              { CHECKBUTTON: [ ] },
+              { RADIOBUTTON: [ ] },
+              { PUSHBUTTON: [ ] } // ARIA owned
+          ] };
+        testAccessibleTree("container2", tree);
+      }
+
+      this.getID = function removeEl_getID()
+      {
+        return "Remove a container of ARIA ownded element";
+      }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Test
+    ////////////////////////////////////////////////////////////////////////////
+
+    gA11yEventDumpToConsole = true;
+    enableLogging("tree"); // debug stuff
+
+    var gQueue = null;
+
+    function doTest()
+    {
+      // nested and recursive aria-owns
+      var tree =
+        { SECTION: [ // container
+          { SECTION: [ // child
+            { SECTION: [ // mid div
+              { SECTION: [] } // grandchild
+            ] }
+          ] }
+        ] };
+      testAccessibleTree("container", tree);
+
+      // dynamic tests
+      gQueue = new eventQueue();
+
+      gQueue.push(new removeARIAOwns());
+      gQueue.push(new setARIAOwns());
+      gQueue.push(new appendEl());
+      gQueue.push(new removeEl());
+
+      gQueue.invoke(); // SimpleTest.finish() will be called in the end
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+
+  </script>
+</head>
+
+<body>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <div id="container" aria-owns="child" aria-label="container"></div>
+  <div id="child" aria-label="child">
+    <div aria-owns="grandchild" aria-label="midchild"></div>
+  </div>
+  <div id="grandchild" aria-owns="container" aria-label="grandchild"></div>
+
+  <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>
+      </span>
+    </div>
+  </div>
+
+</body>
+
+</html>
--- a/dom/base/ChildIterator.h
+++ b/dom/base/ChildIterator.h
@@ -195,16 +195,17 @@ public:
 #endif
       {}
 
 #ifdef DEBUG
   ~AllChildrenIterator() { MOZ_ASSERT(!mMutationGuard.Mutated(0)); }
 #endif
 
   nsIContent* GetNextChild();
+  nsIContent* Parent() const { return mOriginalContent; }
 
 private:
   enum IteratorPhase
   {
     eNeedBeforeKid,
     eNeedExplicitKids,
     eNeedAnonKids,
     eNeedAfterKid,