Bug 419416. Follow useful rules for handling ARIA properties on a frame, iframe and body elements. r=ginn.chen, a=dsicore
authoraaronleventhal@moonset.net
Fri, 14 Mar 2008 13:49:38 -0700
changeset 13094 fdf70ae7859e642677f8ec83d516d01e52ca5c62
parent 13093 a3d024e408fe531388bee2670562c846d17bc87d
child 13095 6ba05c69eca68b159d20bbf3210040db46b7284b
push idunknown
push userunknown
push dateunknown
reviewersginn.chen, dsicore
bugs419416
milestone1.9b5pre
Bug 419416. Follow useful rules for handling ARIA properties on a frame, iframe and body elements. r=ginn.chen, a=dsicore
accessible/public/nsPIAccessible.idl
accessible/src/base/nsAccessibilityAtomList.h
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsAccessibilityUtils.cpp
accessible/src/base/nsAccessibilityUtils.h
accessible/src/base/nsAccessible.cpp
accessible/src/base/nsAccessible.h
accessible/src/base/nsDocAccessible.cpp
accessible/src/base/nsDocAccessible.h
accessible/src/base/nsOuterDocAccessible.cpp
accessible/src/base/nsOuterDocAccessible.h
accessible/src/base/nsRootAccessible.cpp
accessible/src/msaa/nsAccessibleWrap.cpp
accessible/src/msaa/nsDocAccessibleWrap.cpp
accessible/src/msaa/nsDocAccessibleWrap.h
--- a/accessible/public/nsPIAccessible.idl
+++ b/accessible/public/nsPIAccessible.idl
@@ -112,10 +112,18 @@ interface nsPIAccessible : nsISupports
                     
   /**
    * Set the ARIA role map entry for a new accessible.
    * For a newly created accessible, specify which role map entry should be used.
    * @param aRoleMapEntry The ARIA nsRoleMapEntry* for the accessible, or 
    *                      nsnull if none.
    */
    void setRoleMapEntry(in nsRoleMapEntryPtr aRoleMapEntry);
+
+  /**
+   * Maps ARIA state attributes to state of accessible. Note the given state
+   * argument should hold states for accessible before you pass it into this
+   * method.
+   * @param  in/out where to fill the states into.
+   */
+   void getARIAState(out unsigned long aState);
 };
 
--- a/accessible/src/base/nsAccessibilityAtomList.h
+++ b/accessible/src/base/nsAccessibilityAtomList.h
@@ -229,8 +229,13 @@ ACCESSIBILITY_ATOM(aria_valuetext, "aria
 // of an HTML button from the button frame
 ACCESSIBILITY_ATOM(defaultLabel, "defaultLabel")
 
 // Object attributes
 ACCESSIBILITY_ATOM(level, "level")
 ACCESSIBILITY_ATOM(posinset, "posinset") 
 ACCESSIBILITY_ATOM(setsize, "setsize")
 ACCESSIBILITY_ATOM(lineNumber, "line-number")
+ACCESSIBILITY_ATOM(containerRelevant, "container-relevant")
+ACCESSIBILITY_ATOM(containerLive, "container-live")
+ACCESSIBILITY_ATOM(containerChannel, "container-channel")
+ACCESSIBILITY_ATOM(containerAtomic, "container-atomic")
+ACCESSIBILITY_ATOM(containerBusy, "container-busy")
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -448,16 +448,20 @@ nsAccessibilityService::CreateRootAccess
   else {
     *aRootAcc = new nsRootAccessibleWrap(rootNode, weakShell);
   }
   if (!*aRootAcc)
     return NS_ERROR_OUT_OF_MEMORY;
 
   nsCOMPtr<nsPIAccessNode> privateAccessNode(do_QueryInterface(*aRootAcc));
   privateAccessNode->Init();
+  nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(rootNode);
+  nsCOMPtr<nsPIAccessible> privateAccessible =
+    do_QueryInterface(privateAccessNode);
+  privateAccessible->SetRoleMapEntry(roleMapEntry);
 
   NS_ADDREF(*aRootAcc);
 
   return NS_OK;
 }
 
  /**
    * HTML widget creation
--- a/accessible/src/base/nsAccessibilityUtils.cpp
+++ b/accessible/src/base/nsAccessibilityUtils.cpp
@@ -928,8 +928,38 @@ nsAccUtils::IsARIAPropForObjectAttr(nsIA
          aAtom != nsAccessibilityAtoms::aria_relevant &&
          aAtom != nsAccessibilityAtoms::aria_required &&
          aAtom != nsAccessibilityAtoms::aria_selected &&
          aAtom != nsAccessibilityAtoms::aria_valuemax &&
          aAtom != nsAccessibilityAtoms::aria_valuemin &&
          aAtom != nsAccessibilityAtoms::aria_valuenow &&
          aAtom != nsAccessibilityAtoms::aria_valuetext;
 }
+
+void nsAccUtils::GetLiveContainerAttributes(nsIPersistentProperties *aAttributes,
+                                                nsIContent *aStartContent, nsIContent *aTopContent)
+{
+  nsAutoString atomic, live, relevant, channel, busy;
+  nsIContent *ancestor = aStartContent;
+  while (ancestor) {
+    if (relevant.IsEmpty() &&
+        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_relevant, relevant))
+      SetAccAttr(aAttributes, nsAccessibilityAtoms::containerRelevant, relevant);
+    if (live.IsEmpty() &&
+        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_live, live))
+      SetAccAttr(aAttributes, nsAccessibilityAtoms::containerLive, live);
+    if (channel.IsEmpty() &&
+        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_channel, channel))
+      SetAccAttr(aAttributes, nsAccessibilityAtoms::containerChannel, channel);
+    if (atomic.IsEmpty() &&
+        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_atomic, atomic))
+      SetAccAttr(aAttributes, nsAccessibilityAtoms::containerAtomic, atomic);
+    if (busy.IsEmpty() &&
+        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_busy, busy))
+      SetAccAttr(aAttributes, nsAccessibilityAtoms::containerBusy, busy);
+    if (ancestor == aTopContent)
+      break;
+    ancestor = ancestor->GetParent();
+    if (!ancestor) {
+      ancestor = aTopContent; // Use <body>/<frameset>
+    }
+  }
+}
--- a/accessible/src/base/nsAccessibilityUtils.h
+++ b/accessible/src/base/nsAccessibilityUtils.h
@@ -361,12 +361,22 @@ public:
                                                     nsIContent *aLookContent,
                                                     nsIAtom **aRelationAttrs,
                                                     PRUint32 aAttrNum = 1,
                                                     nsIContent *aExcludeContent = nsnull,
                                                     nsIAtom *aTagType = nsAccessibilityAtoms::label);
   
   // Return PR_TRUE if the ARIA property should always be exposed as an object attribute
   static PRBool IsARIAPropForObjectAttr(nsIAtom *aAtom);
+
+
+  /**
+   * Get container-foo live region attributes for the given node
+   * @param aAttributes     Where to store the attributes
+   * @param aStartContent   Node to start from
+   * @param aTopContent     Node to end at
+   */
+  static void GetLiveContainerAttributes(nsIPersistentProperties *aAttributes,
+                                         nsIContent *aStartContent, nsIContent *aTopContent);
 };
 
 #endif
 
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -484,32 +484,31 @@ NS_IMETHODIMP nsAccessible::SetNextSibli
 {
   mNextSibling = aNextSibling? aNextSibling: DEAD_END_ACCESSIBLE;
   return NS_OK;
 }
 
 nsIContent *nsAccessible::GetRoleContent(nsIDOMNode *aDOMNode)
 {
   // Given the DOM node for an acessible, return content node that
-  // we should look at role string from
+  // we should look for ARIA properties on.
   // For non-document accessibles, this is the associated content node.
-  // For doc accessibles, first try the <body> if it's HTML and there is
-  // a role attribute used there.
+  // For doc accessibles, use the <body>/<frameset> if it's HTML.
   // For any other doc accessible , this is the document element.
   nsCOMPtr<nsIContent> content(do_QueryInterface(aDOMNode));
   if (!content) {
     nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(aDOMNode));
     if (domDoc) {
       nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(aDOMNode));
       if (htmlDoc) {
         nsCOMPtr<nsIDOMHTMLElement> bodyElement;
         htmlDoc->GetBody(getter_AddRefs(bodyElement));
         content = do_QueryInterface(bodyElement);
       }
-      if (!content) {
+      else {
         nsCOMPtr<nsIDOMElement> docElement;
         domDoc->GetDocumentElement(getter_AddRefs(docElement));
         content = do_QueryInterface(docElement);
       }
     }
   }
   return content;
 }
@@ -1998,82 +1997,58 @@ NS_IMETHODIMP nsAccessible::GetFinalRole
     }
   }
   return mDOMNode ? GetRole(aRole) : NS_ERROR_FAILURE;  // Node already shut down
 }
 
 NS_IMETHODIMP
 nsAccessible::GetAttributes(nsIPersistentProperties **aAttributes)
 {
-  NS_ENSURE_ARG_POINTER(aAttributes);
-  *aAttributes = nsnull;
-
+  NS_ENSURE_ARG_POINTER(aAttributes);  // In/out param. Created if necessary.
+  
   nsCOMPtr<nsIContent> content = GetRoleContent(mDOMNode);
   if (!content) {
     return NS_ERROR_FAILURE;
   }
 
-  nsCOMPtr<nsIPersistentProperties> attributes =
-     do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
-  NS_ENSURE_TRUE(attributes, NS_ERROR_OUT_OF_MEMORY);
-
-  nsAccEvent::GetLastEventAttributes(mDOMNode, attributes);
+  nsCOMPtr<nsIPersistentProperties> attributes = *aAttributes;
+  if (!attributes) {
+    // Create only if an array wasn't already passed in
+    attributes = do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+    NS_ENSURE_TRUE(attributes, NS_ERROR_OUT_OF_MEMORY);
+    NS_ADDREF(*aAttributes = attributes);
+  }
  
   nsresult rv = GetAttributesInternal(attributes);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoString id;
   nsAutoString oldValueUnused;
   if (nsAccUtils::GetID(content, id)) {
+    // Expose ID. If an <iframe id> exists override the one on the <body> of the source doc,
+    // because the specific instance is what makes the ID useful for scripts
     attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, oldValueUnused);
   }
   
-  nsAutoString _class;
-  if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::_class, _class))
-    nsAccUtils::SetAccAttr(attributes, nsAccessibilityAtoms::_class, _class);
-
   nsAutoString xmlRoles;
   if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::role, xmlRoles)) {
     attributes->SetStringProperty(NS_LITERAL_CSTRING("xml-roles"),  xmlRoles, oldValueUnused);          
   }
 
   nsCOMPtr<nsIAccessibleValue> supportsValue = do_QueryInterface(static_cast<nsIAccessible*>(this));
   if (supportsValue) {
     // We support values, so expose the string value as well, via the valuetext object attribute
     // We test for the value interface because we don't want to expose traditional get_accValue()
     // information such as URL's on links and documents, or text in an input
     nsAutoString valuetext;
     GetValue(valuetext);
     attributes->SetStringProperty(NS_LITERAL_CSTRING("valuetext"), valuetext, oldValueUnused);
   }
 
 
-  // Get container-foo computed live region properties based on the closest container with
-  // the live region attribute
-  nsAutoString atomic, live, relevant, channel, busy;
-  nsIContent *ancestor = content;
-  while (ancestor) {
-    if (relevant.IsEmpty() &&
-        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_relevant, relevant))
-      attributes->SetStringProperty(NS_LITERAL_CSTRING("container-relevant"), relevant, oldValueUnused);
-    if (live.IsEmpty() &&
-        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_live, live))
-      attributes->SetStringProperty(NS_LITERAL_CSTRING("container-live"), live, oldValueUnused);
-    if (channel.IsEmpty() &&
-        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_channel, channel))
-      attributes->SetStringProperty(NS_LITERAL_CSTRING("container-channel"), channel, oldValueUnused);
-    if (atomic.IsEmpty() &&
-        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_atomic, atomic))
-      attributes->SetStringProperty(NS_LITERAL_CSTRING("container-atomic"), atomic, oldValueUnused);
-    if (busy.IsEmpty() &&
-        ancestor->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_busy, busy))
-      attributes->SetStringProperty(NS_LITERAL_CSTRING("container-busy"), busy, oldValueUnused);
-    ancestor = ancestor->GetParent();
-  }
-
   PRUint32 role = Role(this);
   if (role == nsIAccessibleRole::ROLE_CHECKBUTTON ||
       role == nsIAccessibleRole::ROLE_PUSHBUTTON ||
       role == nsIAccessibleRole::ROLE_MENUITEM ||
       role == nsIAccessibleRole::ROLE_LISTITEM ||
       role == nsIAccessibleRole::ROLE_OUTLINEITEM ||
       content->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_checked)) {
     // Might be checkable -- checking role & ARIA attribute first is faster than getting state
@@ -2163,35 +2138,73 @@ nsAccessible::GetAttributes(nsIPersisten
         continue; // No need to expose obj attribute -- will be exposed some other way
       nsAutoString value;
       if (content->GetAttr(kNameSpaceID_None, attrAtom, value)) {
         attributes->SetStringProperty(nsDependentCString(attrStr + 5), value, oldValueUnused);
       }
     }
   }
 
-  attributes.swap(*aAttributes);
-
   return NS_OK;
 }
 
 nsresult
 nsAccessible::GetAttributesInternal(nsIPersistentProperties *aAttributes)
 {
-  nsCOMPtr<nsIDOMElement> element(do_QueryInterface(GetRoleContent(mDOMNode)));
+  // Attributes set by this method will not be used to override attributes on a sub-document accessible
+  // when there is a <frame>/<iframe> element that spawned the sub-document
+  nsIContent *content = GetRoleContent(mDOMNode);
+  nsCOMPtr<nsIDOMElement> element(do_QueryInterface(content));
   NS_ENSURE_TRUE(element, NS_ERROR_UNEXPECTED);
 
   nsAutoString tagName;
   element->GetTagName(tagName);
   if (!tagName.IsEmpty()) {
     nsAutoString oldValueUnused;
     aAttributes->SetStringProperty(NS_LITERAL_CSTRING("tag"), tagName,
                                    oldValueUnused);
   }
 
+  nsAccEvent::GetLastEventAttributes(mDOMNode, aAttributes);
+ 
+  // Expose class because it may have useful microformat information
+  // Let the class from an iframe's document be exposed, don't override from <iframe class>
+  nsAutoString _class;
+  if (content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::_class, _class))
+    nsAccUtils::SetAccAttr(aAttributes, nsAccessibilityAtoms::_class, _class);
+
+  // Get container-foo computed live region properties based on the closest container with
+  // the live region attribute. 
+  // Inner nodes override outer nodes within the same document --
+  //   The inner nodes can be used to override live region behavior on more general outer nodes
+  // However, nodes in outer documents override nodes in inner documents:
+  //   Outer doc author may want to override properties on a widget they used in an iframe
+  nsCOMPtr<nsIDOMNode> startNode = mDOMNode;
+  nsIContent *startContent = content;
+  while (PR_TRUE) {
+    NS_ENSURE_STATE(startContent);
+    nsIDocument *doc = startContent->GetDocument();
+    nsCOMPtr<nsIDOMNode> docNode = do_QueryInterface(doc);
+    NS_ENSURE_STATE(docNode);
+    nsIContent *topContent = GetRoleContent(docNode);
+    NS_ENSURE_STATE(topContent);
+    nsAccUtils::GetLiveContainerAttributes(aAttributes, startContent, topContent);
+    // Allow ARIA live region markup from outer documents to override
+    nsCOMPtr<nsISupports> container = doc->GetContainer();
+    nsIDocShellTreeItem *docShellTreeItem = nsnull;
+    if (container)
+      CallQueryInterface(container, &docShellTreeItem);
+    nsIDocShellTreeItem *sameTypeParent = nsnull;
+    docShellTreeItem->GetSameTypeParent(&sameTypeParent);
+    if (!sameTypeParent || sameTypeParent == docShellTreeItem)
+      break;
+    nsIDocument *parentDoc = doc->GetParentDocument();
+    startContent = parentDoc->FindContentForSubDocument(doc);      
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAccessible::GroupPosition(PRInt32 *aGroupLevel,
                             PRInt32 *aSimilarItemsInGroup,
                             PRInt32 *aPositionInGroup)
 {
@@ -2261,17 +2274,17 @@ NS_IMETHODIMP
 nsAccessible::GetFinalState(PRUint32 *aState, PRUint32 *aExtraState)
 {
   NS_ENSURE_ARG_POINTER(aState);
 
   nsresult rv = GetState(aState, aExtraState);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Apply ARIA states to be sure accessible states will be overriden.
-  *aState |= GetARIAState();
+  GetARIAState(aState);
 
   if (mRoleMapEntry && mRoleMapEntry->role == nsIAccessibleRole::ROLE_PAGETAB) {
     if (*aState & nsIAccessibleStates::STATE_FOCUSED) {
       *aState |= nsIAccessibleStates::STATE_SELECTED;
     } else {
       // Expose 'selected' state on ARIA tab if the focus is on internal element
       // of related tabpanel.
       nsCOMPtr<nsIAccessible> tabPanel;
@@ -2386,49 +2399,49 @@ nsAccessible::GetFinalState(PRUint32 *aS
     else {
       *aExtraState |= nsIAccessibleStates::EXT_STATE_HORIZONTAL;
     }
   }
 
   return NS_OK;
 }
 
-PRUint32
-nsAccessible::GetARIAState()
+nsresult
+nsAccessible::GetARIAState(PRUint32 *aState)
 {
   // Test for universal states first
   nsIContent *content = GetRoleContent(mDOMNode);
   if (!content) {
-    return 0;
+    return NS_OK;
   }
 
   PRUint32 ariaState = 0;
   PRUint32 index = 0;
-  while (MappedAttrState(content, &ariaState, &nsARIAMap::gWAIUnivStateMap[index])) {
+  while (MappedAttrState(content, aState, &nsARIAMap::gWAIUnivStateMap[index])) {
     ++ index;
   }
 
   if (!mRoleMapEntry)
-    return ariaState;
+    return NS_OK;
 
   // Once DHTML role is used, we're only readonly if DHTML readonly used
   ariaState &= ~nsIAccessibleStates::STATE_READONLY;
 
   ariaState |= mRoleMapEntry->state;
-  if (MappedAttrState(content, &ariaState, &mRoleMapEntry->attributeMap1) &&
-      MappedAttrState(content, &ariaState, &mRoleMapEntry->attributeMap2) &&
-      MappedAttrState(content, &ariaState, &mRoleMapEntry->attributeMap3) &&
-      MappedAttrState(content, &ariaState, &mRoleMapEntry->attributeMap4) &&
-      MappedAttrState(content, &ariaState, &mRoleMapEntry->attributeMap5) &&
-      MappedAttrState(content, &ariaState, &mRoleMapEntry->attributeMap6) &&
-      MappedAttrState(content, &ariaState, &mRoleMapEntry->attributeMap7)) {
-    MappedAttrState(content, &ariaState, &mRoleMapEntry->attributeMap8);
+  if (MappedAttrState(content, aState, &mRoleMapEntry->attributeMap1) &&
+      MappedAttrState(content, aState, &mRoleMapEntry->attributeMap2) &&
+      MappedAttrState(content, aState, &mRoleMapEntry->attributeMap3) &&
+      MappedAttrState(content, aState, &mRoleMapEntry->attributeMap4) &&
+      MappedAttrState(content, aState, &mRoleMapEntry->attributeMap5) &&
+      MappedAttrState(content, aState, &mRoleMapEntry->attributeMap6) &&
+      MappedAttrState(content, aState, &mRoleMapEntry->attributeMap7)) {
+    MappedAttrState(content, aState, &mRoleMapEntry->attributeMap8);
   }
 
-  return ariaState;
+  return NS_OK;
 }
 
 // Not implemented by this class
 
 /* DOMString getValue (); */
 NS_IMETHODIMP nsAccessible::GetValue(nsAString& aValue)
 {
   if (!mDOMNode) {
--- a/accessible/src/base/nsAccessible.h
+++ b/accessible/src/base/nsAccessible.h
@@ -125,23 +125,16 @@ public:
   NS_IMETHOD GetState(PRUint32 *aState, PRUint32 *aExtraState);
 
   /**
    * Returns attributes for accessible without explicitly setted ARIA
    * attributes.
    */
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
 
-  /**
-   * Maps ARIA state attributes to state of accessible. Note the given state
-   * argument should hold states for accessible before you pass it into this
-   * method.
-   */
-  PRUint32 GetARIAState();
-
 #ifdef DEBUG_A11Y
   static PRBool IsTextInterfaceSupportCorrect(nsIAccessible *aAccessible);
 #endif
 
   static PRBool IsCorrectFrameType(nsIFrame* aFrame, nsIAtom* aAtom);
   static PRUint32 State(nsIAccessible *aAcc) { PRUint32 state = 0; if (aAcc) aAcc->GetFinalState(&state, nsnull); return state; }
   static PRUint32 Role(nsIAccessible *aAcc) { PRUint32 role = nsIAccessibleRole::ROLE_NOTHING; if (aAcc) aAcc->GetFinalRole(&role); return role; }
   static PRBool IsText(nsIAccessible *aAcc) { PRUint32 role = Role(aAcc); return role == nsIAccessibleRole::ROLE_TEXT_LEAF || role == nsIAccessibleRole::ROLE_STATICTEXT; }
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -147,24 +147,24 @@ NS_INTERFACE_MAP_END_INHERITING(nsHyperT
 
 NS_IMPL_ADDREF_INHERITED(nsDocAccessible, nsHyperTextAccessible)
 NS_IMPL_RELEASE_INHERITED(nsDocAccessible, nsHyperTextAccessible)
 
 NS_IMETHODIMP nsDocAccessible::GetName(nsAString& aName)
 {
   nsresult rv = NS_OK;
   aName.Truncate();
-  if (mRoleMapEntry) {
-    rv = nsAccessible::GetName(aName);
+  if (mParent) {
+    rv = mParent->GetName(aName); // Allow owning iframe to override the name
   }
   if (aName.IsEmpty()) {
-    rv = GetTitle(aName);
+    rv = nsAccessible::GetName(aName); // Allow name via aria-labelledby or title attribute
   }
-  if (aName.IsEmpty() && mParent) {
-    rv = mParent->GetName(aName);
+  if (aName.IsEmpty()) {
+    rv = GetTitle(aName);   // Finally try title element
   }
 
   return rv;
 }
 
 NS_IMETHODIMP nsDocAccessible::GetRole(PRUint32 *aRole)
 {
   *aRole = nsIAccessibleRole::ROLE_PANE; // Fall back
@@ -197,27 +197,48 @@ NS_IMETHODIMP nsDocAccessible::GetRole(P
     else if (itemType == nsIDocShellTreeItem::typeContent) {
       *aRole = nsIAccessibleRole::ROLE_DOCUMENT;
     }
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP nsDocAccessible::GetValue(nsAString& aValue)
+NS_IMETHODIMP nsDocAccessible::SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry)
 {
-  return GetURL(aValue);
+  NS_ENSURE_STATE(mDocument);
+
+  mRoleMapEntry = aRoleMapEntry;
+
+  // Allow use of ARIA role from outer to override
+  nsIDocument *parentDoc = mDocument->GetParentDocument();
+  NS_ENSURE_TRUE(parentDoc, NS_ERROR_FAILURE);
+  nsIContent *ownerContent = parentDoc->FindContentForSubDocument(mDocument);
+  nsCOMPtr<nsIDOMNode> ownerNode(do_QueryInterface(ownerContent));
+  if (ownerNode) {
+    nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(ownerNode);
+    if (roleMapEntry)
+      mRoleMapEntry = roleMapEntry; // Override
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsDocAccessible::GetDescription(nsAString& aDescription)
 {
-  nsAutoString description;
-  GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description);
-  aDescription = description;
+  if (mParent)
+    mParent->GetDescription(aDescription);
+
+  if (aDescription.IsEmpty()) {
+    nsAutoString description;
+    GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description);
+    aDescription = description;
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState)
 {
   // nsAccessible::GetState() always fail for document accessible.
   nsAccessible::GetState(aState, aExtraState);
@@ -260,16 +281,41 @@ nsDocAccessible::GetState(PRUint32 *aSta
   }
   else if (aExtraState) {
     *aExtraState |= nsIAccessibleStates::EXT_STATE_EDITABLE;
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDocAccessible::GetARIAState(PRUint32 *aState)
+{
+  // Combine with states from outer doc
+  NS_ENSURE_ARG_POINTER(aState);
+  nsresult rv = nsAccessible::GetARIAState(aState);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsPIAccessible> privateParentAccessible = do_QueryInterface(mParent);
+  if (privateParentAccessible)  // Allow iframe/frame etc. to have final state override via ARIA
+    return privateParentAccessible->GetARIAState(aState);
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsDocAccessible::GetAttributes(nsIPersistentProperties **aAttributes)
+{
+  nsAccessible::GetAttributes(aAttributes);
+  if (mParent) {
+    mParent->GetAttributes(aAttributes); // Add parent attributes (override inner)
+  }
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsDocAccessible::GetFocusedChild(nsIAccessible **aFocusedChild)
 {
   if (!gLastFocusedNode) {
     *aFocusedChild = nsnull;
     return NS_OK;
   }
 
   // Return an accessible for the current global focus, which does not have to
@@ -497,28 +543,20 @@ NS_IMETHODIMP nsDocAccessible::GetParent
 }
 
 NS_IMETHODIMP nsDocAccessible::Init()
 {
   PutCacheEntry(gGlobalDocAccessibleCache, mDocument, this);
 
   AddEventListeners();
 
-  nsresult rv = nsHyperTextAccessibleWrap::Init();
+  nsCOMPtr<nsIAccessible> parentAccessible;  // Ensure outer doc mParent accessible
+  GetParent(getter_AddRefs(parentAccessible));
 
-  if (mRoleMapEntry && mRoleMapEntry->role != nsIAccessibleRole::ROLE_DIALOG &&
-      mRoleMapEntry->role != nsIAccessibleRole::ROLE_APPLICATION &&
-      mRoleMapEntry->role != nsIAccessibleRole::ROLE_ALERT &&
-      mRoleMapEntry->role != nsIAccessibleRole::ROLE_DOCUMENT) {
-    // Document accessible can only have certain roles
-    // This was set in nsAccessible::Init() based on dynamic role attribute
-    mRoleMapEntry = nsnull; // role attribute is not valid for a document
-  }
-
-  return rv;
+  return nsHyperTextAccessibleWrap::Init();
 }
 
 NS_IMETHODIMP nsDocAccessible::Shutdown()
 {
   if (!mWeakShell) {
     return NS_OK;  // Already shutdown
   }
 
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -69,20 +69,22 @@ class nsDocAccessible : public nsHyperTe
   NS_DECL_NSPIACCESSIBLEDOCUMENT
   NS_DECL_NSIOBSERVER
 
   public:
     nsDocAccessible(nsIDOMNode *aNode, nsIWeakReference* aShell);
     virtual ~nsDocAccessible();
 
     NS_IMETHOD GetRole(PRUint32 *aRole);
+    NS_IMETHOD SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry);
     NS_IMETHOD GetName(nsAString& aName);
-    NS_IMETHOD GetValue(nsAString& aValue);
     NS_IMETHOD GetDescription(nsAString& aDescription);
+    NS_IMETHOD GetARIAState(PRUint32 *aState);
     NS_IMETHOD GetState(PRUint32 *aState, PRUint32 *aExtraState);
+    NS_IMETHOD GetAttributes(nsIPersistentProperties **aAttributes);
     NS_IMETHOD GetFocusedChild(nsIAccessible **aFocusedChild);
     NS_IMETHOD GetParent(nsIAccessible **aParent);
     NS_IMETHOD TakeFocus(void);
 
     // ----- nsIScrollPositionListener ---------------------------
     NS_IMETHOD ScrollPositionWillChange(nsIScrollableView *aView, nscoord aX, nscoord aY);
     NS_IMETHOD ScrollPositionDidChange(nsIScrollableView *aView, nscoord aX, nscoord aY);
 
--- a/accessible/src/base/nsOuterDocAccessible.cpp
+++ b/accessible/src/base/nsOuterDocAccessible.cpp
@@ -150,8 +150,20 @@ void nsOuterDocAccessible::CacheChildren
 
   // Success getting inner document as first child -- now we cache it.
   mAccChildCount = 1;
   SetFirstChild(innerAccessible); // weak ref
   privateInnerAccessible->SetParent(this);
   privateInnerAccessible->SetNextSibling(nsnull);
 }
 
+nsresult
+nsOuterDocAccessible::GetAttributesInternal(nsIPersistentProperties *aAttributes)
+{
+  nsAutoString tag;
+  aAttributes->GetStringProperty(NS_LITERAL_CSTRING("tag"), tag);
+  if (!tag.IsEmpty()) {
+    // We're overriding the ARIA attributes on an sub document, but we don't want to
+    // override the other attributes
+    return NS_OK;
+  }
+  return nsAccessible::GetAttributesInternal(aAttributes);
+}
--- a/accessible/src/base/nsOuterDocAccessible.h
+++ b/accessible/src/base/nsOuterDocAccessible.h
@@ -53,11 +53,12 @@ class nsOuterDocAccessible : public nsAc
                          nsIWeakReference* aShell);
 
     NS_IMETHOD GetName(nsAString& aName);
     NS_IMETHOD GetRole(PRUint32 *aRole);
     NS_IMETHOD GetState(PRUint32 *aState, PRUint32 *aExtraState);
     NS_IMETHOD GetChildAtPoint(PRInt32 aX, PRInt32 aY,
                                nsIAccessible **aAccessible);
     void CacheChildren();
+    nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
 };
 
 #endif  
--- a/accessible/src/base/nsRootAccessible.cpp
+++ b/accessible/src/base/nsRootAccessible.cpp
@@ -443,22 +443,25 @@ PRBool nsRootAccessible::FireAccessibleF
         mCaretAccessible->SetControlSelectionListener(realFocusedNode);
       }
     }
   }
 
   // Check for aria-activedescendant, which changes which element has focus
   nsCOMPtr<nsIDOMNode> finalFocusNode = aNode;
   nsCOMPtr<nsIAccessible> finalFocusAccessible = aAccessible;
-  nsCOMPtr<nsIContent> finalFocusContent  = do_QueryInterface(aNode);
+  nsCOMPtr<nsIContent> finalFocusContent = GetRoleContent(finalFocusNode);
   if (finalFocusContent) {
     nsAutoString id;
     if (finalFocusContent->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::aria_activedescendant, id)) {
       nsCOMPtr<nsIDOMDocument> domDoc;
       aNode->GetOwnerDocument(getter_AddRefs(domDoc));
+      if (!domDoc) {  // Maybe the passed-in node actually is a doc
+        domDoc = do_QueryInterface(aNode);
+      }
       if (!domDoc) {
         return PR_FALSE;
       }
       nsCOMPtr<nsIDOMElement> relatedEl;
       domDoc->GetElementById(id, getter_AddRefs(relatedEl));
       finalFocusNode = do_QueryInterface(relatedEl);
       if (!finalFocusNode) {
         return PR_FALSE;
--- a/accessible/src/msaa/nsAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsAccessibleWrap.cpp
@@ -313,17 +313,17 @@ STDMETHODIMP nsAccessibleWrap::get_accVa
       /* [retval][out] */ BSTR __RPC_FAR *pszValue)
 {
 __try {
   *pszValue = NULL;
   nsCOMPtr<nsIAccessible> xpAccessible;
   GetXPAccessibleFor(varChild, getter_AddRefs(xpAccessible));
   if (xpAccessible) {
     nsAutoString value;
-    if (NS_FAILED(xpAccessible->GetValue(value)))
+    if (NS_FAILED(xpAccessible->GetValue(value)) || value.IsEmpty())
       return S_FALSE;
 
     *pszValue = ::SysAllocString(value.get());
   }
 } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
   return S_OK;
 }
 
--- a/accessible/src/msaa/nsDocAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsDocAccessibleWrap.cpp
@@ -267,8 +267,28 @@ STDMETHODIMP nsDocAccessibleWrap::get_na
   return E_FAIL;
 }
 
 STDMETHODIMP nsDocAccessibleWrap::put_alternateViewMediaTypes( /* [in] */ BSTR __RPC_FAR *commaSeparatedMediaTypes)
 {
   return E_NOTIMPL;
 }
 
+STDMETHODIMP nsDocAccessibleWrap::get_accValue(
+      /* [optional][in] */ VARIANT varChild,
+      /* [retval][out] */ BSTR __RPC_FAR *pszValue)
+{
+  // For backwards-compat, we still support old MSAA hack to provide URL in accValue
+  *pszValue = NULL;
+  // Check for real value first
+  HRESULT hr = nsAccessibleWrap::get_accValue(varChild, pszValue);
+  if (FAILED(hr) || *pszValue || varChild.lVal != CHILDID_SELF)
+    return hr;
+  // If document is being used to create a widget, don't use the URL hack
+  PRUint32 role = Role(this);
+  if (role != nsIAccessibleRole::ROLE_DOCUMENT &&
+      role != nsIAccessibleRole::ROLE_APPLICATION &&
+      role != nsIAccessibleRole::ROLE_DIALOG &&
+      role != nsIAccessibleRole::ROLE_ALERT)
+    return hr;
+
+  return get_URL(pszValue);
+}
--- a/accessible/src/msaa/nsDocAccessibleWrap.h
+++ b/accessible/src/msaa/nsDocAccessibleWrap.h
@@ -82,12 +82,17 @@ public:
         /* [in] */ BSTR __RPC_FAR *commaSeparatedMediaTypes);
 
     // IAccessible
     // Override get_accChild so that it can get any child via the unique ID
     virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChild( 
         /* [in] */ VARIANT varChild,
         /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild);
 
+    // Override get_accValue to provide URL when no other value is available
+    virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accValue( 
+        /* [optional][in] */ VARIANT varChild,
+        /* [retval][out] */ BSTR __RPC_FAR *pszValue);
+
     NS_IMETHOD FireAnchorJumpEvent();
 };
 
 #endif