Bug 612830 - make HTML document accessible work even when there's no body, r=tbsaunde
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 31 Oct 2012 11:25:17 +0900
changeset 111852 e0271552b1b0ee69bb9a202380a897be29313a70
parent 111851 6fdc6565bae9817b8a8301dc5883aa281e69114f
child 111853 76ee4691901159397416d48924c8bdee73e1d44f
push id17293
push usersurkov.alexander@gmail.com
push dateWed, 31 Oct 2012 03:08:08 +0000
treeherdermozilla-inbound@e0271552b1b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde
bugs612830
milestone19.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 612830 - make HTML document accessible work even when there's no body, r=tbsaunde
accessible/src/base/nsAccDocManager.cpp
accessible/src/base/nsAccessNode.cpp
accessible/src/generic/Accessible.cpp
accessible/src/generic/BaseAccessibles.cpp
accessible/src/generic/BaseAccessibles.h
accessible/src/generic/DocAccessible.cpp
accessible/src/generic/HyperTextAccessible.cpp
accessible/tests/mochitest/actions/test_media.html
accessible/tests/mochitest/events/test_aria_objattr.html
accessible/tests/mochitest/treeupdate/test_doc.html
--- a/accessible/src/base/nsAccDocManager.cpp
+++ b/accessible/src/base/nsAccDocManager.cpp
@@ -354,37 +354,32 @@ nsAccDocManager::CreateDocOrRootAccessib
       aDocument->IsResourceDoc() || !aDocument->IsActive())
     return nullptr;
 
   // Ignore documents without presshell and not having root frame.
   nsIPresShell* presShell = aDocument->GetShell();
   if (!presShell || !presShell->GetRootFrame() || presShell->IsDestroying())
     return nullptr;
 
-  // Do not create document accessible until role content is loaded, otherwise
-  // we get accessible document with wrong role.
-  nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument);
-  if (!rootElm)
-    return nullptr;
-
   bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
 
   DocAccessible* parentDocAcc = nullptr;
   if (!isRootDoc) {
     // XXXaaronl: ideally we would traverse the presshell chain. Since there's
     // no easy way to do that, we cheat and use the document hierarchy.
     parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
     NS_ASSERTION(parentDocAcc,
                  "Can't create an accessible for the document!");
     if (!parentDocAcc)
       return nullptr;
   }
 
   // We only create root accessibles for the true root, otherwise create a
   // doc accessible.
+  nsIContent *rootElm = nsCoreUtils::GetRoleContent(aDocument);
   nsRefPtr<DocAccessible> docAcc = isRootDoc ?
     new RootAccessibleWrap(aDocument, rootElm, presShell) :
     new DocAccessibleWrap(aDocument, rootElm, presShell);
 
   // Cache the document accessible into document cache.
   mDocAccessibleCache.Put(aDocument, docAcc);
 
   // Initialize the document accessible.
--- a/accessible/src/base/nsAccessNode.cpp
+++ b/accessible/src/base/nsAccessNode.cpp
@@ -78,17 +78,17 @@ nsAccessNode::Shutdown()
   mContent = nullptr;
   mDoc = nullptr;
 }
 
 RootAccessible*
 nsAccessNode::RootAccessible() const
 {
   nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
-    nsCoreUtils::GetDocShellTreeItemFor(mContent);
+    nsCoreUtils::GetDocShellTreeItemFor(GetNode());
   NS_ASSERTION(docShellTreeItem, "No docshell tree item for mContent");
   if (!docShellTreeItem) {
     return nullptr;
   }
   nsCOMPtr<nsIDocShellTreeItem> root;
   docShellTreeItem->GetRootTreeItem(getter_AddRefs(root));
   NS_ASSERTION(root, "No root content tree item");
   if (!root) {
@@ -116,13 +116,13 @@ nsAccessNode::Language(nsAString& aLangu
 {
   aLanguage.Truncate();
 
   if (!mDoc)
     return;
 
   nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
   if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
-    mContent->OwnerDoc()->GetHeaderData(nsGkAtoms::headerContentLanguage,
+    mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
                                         aLanguage);
   }
 }
 
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -1373,16 +1373,18 @@ Accessible::NativeAttributes()
 
   return attributes.forget();
 }
 
 GroupPos
 Accessible::GroupPosition()
 {
   GroupPos groupPos;
+  if (!HasOwnContent())
+    return groupPos;
 
   // Get group position from ARIA attributes.
   nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, &groupPos.level);
   nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, &groupPos.setSize);
   nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, &groupPos.posInSet);
 
   // If ARIA is missed and the accessible is visible then calculate group
   // position from hierarchy.
@@ -1896,16 +1898,19 @@ Accessible::GetRelationByType(uint32_t a
   Relation rel = RelationByType(aType);
   NS_ADDREF(*aRelation = new nsAccessibleRelation(aType, &rel));
   return *aRelation ? NS_OK : NS_ERROR_FAILURE;
 }
 
 Relation
 Accessible::RelationByType(uint32_t aType)
 {
+  if (!HasOwnContent())
+    return Relation();
+
   // Relationships are defined on the same content node that the role would be
   // defined on.
   switch (aType) {
     case nsIAccessibleRelation::RELATION_LABEL_FOR: {
       Relation rel(new RelatedAccIterator(Document(), mContent,
                                           nsGkAtoms::aria_labelledby));
       if (mContent->Tag() == nsGkAtoms::label)
         rel.AppendIter(new IDRefsIterator(mDoc, mContent, mContent->IsHTML() ?
@@ -2873,28 +2878,30 @@ Accessible::IsActiveWidget() const
   }
 
   return false;
 }
 
 bool
 Accessible::AreItemsOperable() const
 {
-  return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant);
+  return HasOwnContent() &&
+    mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant);
 }
 
 Accessible*
 Accessible::CurrentItem()
 {
   // Check for aria-activedescendant, which changes which element has focus.
   // For activedescendant, the ARIA spec does not require that the user agent
   // checks whether pointed node is actually a DOM descendant of the element
   // with the aria-activedescendant attribute.
   nsAutoString id;
-  if (mContent->GetAttr(kNameSpaceID_None,
+  if (HasOwnContent() &&
+      mContent->GetAttr(kNameSpaceID_None,
                         nsGkAtoms::aria_activedescendant, id)) {
     nsIDocument* DOMDoc = mContent->OwnerDoc();
     dom::Element* activeDescendantElm = DOMDoc->GetElementById(id);
     if (activeDescendantElm) {
       DocAccessible* document = Document();
       if (document)
         return document->GetAccessible(activeDescendantElm);
     }
--- a/accessible/src/generic/BaseAccessibles.cpp
+++ b/accessible/src/generic/BaseAccessibles.cpp
@@ -259,8 +259,13 @@ DummyAccessible::NativeLinkState() const
   return 0;
 }
 
 bool
 DummyAccessible::NativelyUnavailable() const
 {
   return false;
 }
+
+void
+DummyAccessible::ApplyARIAState(uint64_t* aState) const
+{
+}
--- a/accessible/src/generic/BaseAccessibles.h
+++ b/accessible/src/generic/BaseAccessibles.h
@@ -117,14 +117,15 @@ class DummyAccessible : public Accessibl
 public:
   DummyAccessible() : AccessibleWrap(nullptr, nullptr) { }
   virtual ~DummyAccessible() { }
 
   virtual uint64_t NativeState() MOZ_OVERRIDE MOZ_FINAL;
   virtual uint64_t NativeInteractiveState() const MOZ_OVERRIDE MOZ_FINAL;
   virtual uint64_t NativeLinkState() const MOZ_OVERRIDE MOZ_FINAL;
   virtual bool NativelyUnavailable() const MOZ_OVERRIDE MOZ_FINAL;
+  virtual void ApplyARIAState(uint64_t* aState) const MOZ_OVERRIDE MOZ_FINAL;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/src/generic/DocAccessible.cpp
+++ b/accessible/src/generic/DocAccessible.cpp
@@ -255,23 +255,18 @@ DocAccessible::Description(nsString& aDe
                              aDescription);
   }
 }
 
 // Accessible public method
 uint64_t
 DocAccessible::NativeState()
 {
-  // The root content of the document might be removed so that mContent is
-  // out of date.
-  uint64_t state = (mContent->GetCurrentDoc() == mDocument) ?
-    0 : states::STALE;
-
   // Document is always focusable.
-  state |= states::FOCUSABLE; // keep in sync with NativeIteractiveState() impl
+  uint64_t state = states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl
   if (FocusMgr()->IsFocused(this))
     state |= states::FOCUSED;
 
   // Expose stale state until the document is ready (DOM is loaded and tree is
   // constructed).
   if (!HasLoadState(eReady))
     state |= states::STALE;
 
@@ -304,21 +299,21 @@ DocAccessible::NativelyUnavailable() con
 {
   return false;
 }
 
 // Accessible public method
 void
 DocAccessible::ApplyARIAState(uint64_t* aState) const
 {
-  // Combine with states from outer doc
-  // 
-  Accessible::ApplyARIAState(aState);
+  // Grab states from content element.
+  if (mContent)
+    Accessible::ApplyARIAState(aState);
 
-  // Allow iframe/frame etc. to have final state override via ARIA
+  // Allow iframe/frame etc. to have final state override via ARIA.
   if (mParent)
     mParent->ApplyARIAState(aState);
 }
 
 already_AddRefed<nsIPersistentProperties>
 DocAccessible::Attributes()
 {
   nsCOMPtr<nsIPersistentProperties> attributes =
@@ -535,17 +530,17 @@ DocAccessible::GetVirtualCursor(nsIAcces
 
 // HyperTextAccessible method
 already_AddRefed<nsIEditor>
 DocAccessible::GetEditor() const
 {
   // Check if document is editable (designMode="on" case). Otherwise check if
   // the html:body (for HTML document case) or document element is editable.
   if (!mDocument->HasFlag(NODE_IS_EDITABLE) &&
-      !mContent->HasFlag(NODE_IS_EDITABLE))
+      (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
     return nullptr;
 
   nsCOMPtr<nsISupports> container = mDocument->GetContainer();
   nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(container));
   if (!editingSession)
     return nullptr; // No editing session interface
 
   nsCOMPtr<nsIEditor> editor;
@@ -1555,17 +1550,17 @@ void
 DocAccessible::DoInitialUpdate()
 {
   mLoadState |= eTreeConstructed;
 
   // The content element may be changed before the initial update and then we
   // miss the notification (since content tree change notifications are ignored
   // prior to initial update). Make sure the content element is valid.
   nsIContent* contentElm = nsCoreUtils::GetRoleContent(mDocument);
-  if (contentElm && mContent != contentElm)
+  if (mContent != contentElm)
     mContent = contentElm;
 
   // Build initial tree.
   CacheChildrenInSubtree(this);
 
   // Fire reorder event after the document tree is constructed. Note, since
   // this reorder event is processed by parent document then events targeted to
   // this document may be fired prior to this reorder event. If this is
@@ -1820,17 +1815,17 @@ DocAccessible::ProcessContentInserted(Ac
 {
   // Process the notification if the container accessible is still in tree.
   if (!HasAccessible(aContainer->GetNode()))
     return;
 
   if (aContainer == this) {
     // If new root content has been inserted then update it.
     nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocument);
-    if (rootContent && rootContent != mContent)
+    if (rootContent != mContent)
       mContent = rootContent;
 
     // Continue to update the tree even if we don't have root content.
     // For example, elements may be inserted under the document element while
     // there is no HTML body element.
   }
 
   // XXX: Invalidate parent-child relations for container accessible and its
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -1154,16 +1154,19 @@ HyperTextAccessible::NativeAttributes()
     int32_t lineNumber = CaretLineNumber();
     if (lineNumber >= 1) {
       nsAutoString strLineNumber;
       strLineNumber.AppendInt(lineNumber);
       nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber);
     }
   }
 
+  if (!HasOwnContent())
+    return attributes.forget();
+
   // For the html landmark elements we expose them like we do aria landmarks to
   // make AT navigation schemes "just work". Note html:header is redundant as
   // a landmark since it usually contains headings. We're not yet sure how the
   // web will use html:footer but our best bet right now is as contentinfo.
   if (mContent->Tag() == nsGkAtoms::nav)
     nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
                            NS_LITERAL_STRING("navigation"));
   else if (mContent->Tag() == nsGkAtoms::section) 
--- a/accessible/tests/mochitest/actions/test_media.html
+++ b/accessible/tests/mochitest/actions/test_media.html
@@ -19,16 +19,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
 
   <script type="application/javascript">
 
     // gA11yEventDumpID = "eventDump";
+    //gA11yEventDumpToConsole = true; // debug stuff
 
     function focusChecker(aAcc)
     {
       this.type = EVENT_FOCUS;
       this.target = aAcc;
       this.getID = function focusChecker_getID()
       {
         return "focus handling";
--- a/accessible/tests/mochitest/events/test_aria_objattr.html
+++ b/accessible/tests/mochitest/events/test_aria_objattr.html
@@ -38,18 +38,18 @@
       this.getID = function updateAttribute_getID()
       {
         return aAttr + " for " + aID + " " + aValue;
       }
     }
 
     // Debug stuff.
     // gA11yEventDumpID = "eventdump";
-    // gA11yEventDumpToConsole = true;
-    
+    //gA11yEventDumpToConsole = true;
+
     function doTests()
     {
       gQueue = new eventQueue();
 
       gQueue.push(new updateAttribute("hideable", "aria-hidden", "true"));
 
       gQueue.push(new updateAttribute("sortable", "aria-sort", "ascending"));
 
--- a/accessible/tests/mochitest/treeupdate/test_doc.html
+++ b/accessible/tests/mochitest/treeupdate/test_doc.html
@@ -67,21 +67,16 @@
         var text = getAccessible(getAccessible(getDocNode(aID)).firstChild);
         this.eventSeq[0].target = text;
       }
 
       this.finalCheck = function rootContentRemoved_finalCheck()
       {
         var tree = {
           role: ROLE_DOCUMENT,
-          states: {
-            // Out of date root content involves stale state presence.
-            states: 0,
-            extraStates: EXT_STATE_STALE
-          },
           children: [ ]
         };
         testAccessibleTree(getDocNode(aID), tree);
       }
     }
 
     function rootContentInserted(aID, aTextName)
     {
@@ -89,22 +84,16 @@
         new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
         new invokerChecker(EVENT_REORDER, getDocNode, aID)
       ];
 
       this.finalCheck = function rootContentInserted_finalCheck()
       {
         var tree = {
           role: ROLE_DOCUMENT,
-          states: {
-            states: 0,
-            extraStates: 0,
-            absentStates: 0,
-            absentExtraStates: EXT_STATE_STALE
-          },
           children: [
             {
               role: ROLE_TEXT_LEAF,
               name: aTextName
             }
           ]
         };
         testAccessibleTree(getDocNode(aID), tree);