Bug 606125 - develop a way to handle visibility style, r=marcoz, davidb, sr=bz, a=blocking
authorAlexander Surkov <surkov.alexander@gmail.com>
Sat, 13 Nov 2010 12:49:26 -0500
changeset 57448 e030f7ac043bc2abd0349aba9002a6c200eeabeb
parent 57447 b8a0c34bb9f70e8e36e39581b17f0a1977baa07c
child 57449 d293acc7a273cee5155a7c0be6e8d0bed37ee860
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersmarcoz, davidb, bz, blocking
bugs606125
milestone2.0b8pre
Bug 606125 - develop a way to handle visibility style, r=marcoz, davidb, sr=bz, a=blocking
accessible/src/base/nsAccTreeWalker.cpp
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsAccessibilityService.h
accessible/src/base/nsTextEquivUtils.cpp
accessible/tests/mochitest/common.js
accessible/tests/mochitest/events/test_mutation.html
accessible/tests/mochitest/tree/test_tabbrowser.xul
accessible/tests/mochitest/treeupdate/Makefile.in
accessible/tests/mochitest/treeupdate/test_ariadialog.html
accessible/tests/mochitest/treeupdate/test_tableinsubtree.html
accessible/tests/mochitest/treeupdate/test_visibility.html
layout/base/nsFrameManager.cpp
layout/base/nsFrameManager.h
--- a/accessible/src/base/nsAccTreeWalker.cpp
+++ b/accessible/src/base/nsAccTreeWalker.cpp
@@ -108,26 +108,26 @@ nsAccTreeWalker::GetNextChildInternal(PR
   PRUint32 length = 0;
   if (mState->childList)
     mState->childList->GetLength(&length);
 
   while (mState->childIdx < length) {
     nsIContent* childNode = mState->childList->GetNodeAt(mState->childIdx);
     mState->childIdx++;
 
-    PRBool isHidden = PR_FALSE;
+    bool isSubtreeHidden = false;
     nsRefPtr<nsAccessible> accessible =
       GetAccService()->GetOrCreateAccessible(childNode, presShell, mWeakShell,
-                                             &isHidden);
+                                             &isSubtreeHidden);
 
     if (accessible)
       return accessible.forget();
 
     // Walk down into subtree to find accessibles.
-    if (!isHidden) {
+    if (!isSubtreeHidden) {
       if (!PushState(childNode))
         break;
 
       accessible = GetNextChildInternal(PR_TRUE);
       if (accessible)
         return accessible.forget();
     }
   }
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -864,23 +864,23 @@ static PRBool HasRelatedContent(nsIConte
 
   return PR_FALSE;
 }
 
 already_AddRefed<nsAccessible>
 nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
                                               nsIPresShell* aPresShell,
                                               nsIWeakReference* aWeakShell,
-                                              PRBool* aIsHidden)
+                                              bool* aIsSubtreeHidden)
 {
   if (!aPresShell || !aWeakShell || !aNode || gIsShutdown)
     return nsnull;
 
-  if (aIsHidden)
-    *aIsHidden = PR_FALSE;
+  if (aIsSubtreeHidden)
+    *aIsSubtreeHidden = false;
 
   // Check to see if we already have an accessible for this node in the cache.
   nsAccessible *cachedAccessible = GetCachedAccessible(aNode, aWeakShell);
   if (cachedAccessible) {
     NS_ADDREF(cachedAccessible);
     return cachedAccessible;
   }
 
@@ -911,20 +911,21 @@ nsAccessibilityService::GetOrCreateAcces
     return nsnull;
 
   // Frames can be deallocated when we flush layout, or when we call into code
   // that can flush layout, either directly, or via DOM manipulation, or some
   // CSS styles like :hover. We use the weak frame checks to avoid calling
   // methods on a dead frame pointer.
   nsWeakFrame weakFrame = content->GetPrimaryFrame();
 
-  // Check frame to see if it is hidden.
-  if (!weakFrame.GetFrame()) {
-    if (aIsHidden)
-      *aIsHidden = PR_TRUE;
+  // Check frame and its visibility. Note, hidden frame allows visible
+  // elements in subtree.
+  if (!weakFrame.GetFrame() || !weakFrame->GetStyleVisibility()->IsVisible()) {
+    if (aIsSubtreeHidden && !weakFrame.GetFrame())
+      *aIsSubtreeHidden = true;
 
     return nsnull;
   }
 
   if (weakFrame.GetFrame()->GetContent() != content) {
     // Not the main content for this frame. This happens because <area>
     // elements return the image frame as their primary frame. The main content
     // for the image frame is the image content. If the frame is not an image
@@ -948,18 +949,18 @@ nsAccessibilityService::GetOrCreateAcces
   if (content->IsNodeOfType(nsINode::eTEXT)) {
     // --- Create HTML for visible text frames ---
     nsIFrame* f = weakFrame.GetFrame();
     if (f && f->IsEmpty()) {
       nsAutoString renderedWhitespace;
       f->GetRenderedText(&renderedWhitespace, nsnull, nsnull, 0, 1);
       if (renderedWhitespace.IsEmpty()) {
         // Really empty -- nothing is rendered
-        if (aIsHidden)
-          *aIsHidden = PR_TRUE;
+        if (aIsSubtreeHidden)
+          *aIsSubtreeHidden = true;
 
         return nsnull;
       }
     }
     if (weakFrame.IsAlive()) {
       newAcc = weakFrame.GetFrame()->CreateAccessible();
       if (docAcc->BindToDocument(newAcc, nsnull))
         return newAcc.forget();
@@ -977,18 +978,18 @@ nsAccessibilityService::GetOrCreateAcces
     // suppose it is used for links grouping. Otherwise we think it is used in
     // conjuction with HTML image element and in this case we don't create any
     // accessible for it and don't walk into it. The accessibles for HTML area
     // (nsHTMLAreaAccessible) the map contains are attached as children of the
     // appropriate accessible for HTML image (nsHTMLImageAccessible).
     nsAutoString name;
     content->GetAttr(kNameSpaceID_None, nsAccessibilityAtoms::name, name);
     if (!name.IsEmpty()) {
-      if (aIsHidden)
-        *aIsHidden = PR_TRUE;
+      if (aIsSubtreeHidden)
+        *aIsSubtreeHidden = true;
 
       return nsnull;
     }
 
     newAcc = new nsHyperTextAccessibleWrap(content, aWeakShell);
     if (docAcc->BindToDocument(newAcc, nsAccUtils::GetRoleMapEntry(aNode)))
       return newAcc.forget();
     return nsnull;
@@ -1102,28 +1103,28 @@ nsAccessibilityService::GetOrCreateAcces
       newAcc = CreateHTMLAccessibleByMarkup(weakFrame.GetFrame(), content,
                                             aWeakShell);
 
       if (!newAcc) {
         // Do not create accessible object subtrees for non-rendered table
         // captions. This could not be done in
         // nsTableCaptionFrame::GetAccessible() because the descendants of
         // the table caption would still be created. By setting
-        // *aIsHidden = PR_TRUE we ensure that no descendant accessibles are
-        // created.
+        // *aIsSubtreeHidden = true we ensure that no descendant accessibles
+        // are created.
         nsIFrame* f = weakFrame.GetFrame();
         if (!f) {
           f = aPresShell->GetRealPrimaryFrameFor(content);
         }
         if (f->GetType() == nsAccessibilityAtoms::tableCaptionFrame &&
            f->GetRect().IsEmpty()) {
           // XXX This is not the ideal place for this code, but right now there
           // is no better place:
-          if (aIsHidden)
-            *aIsHidden = PR_TRUE;
+          if (aIsSubtreeHidden)
+            *aIsSubtreeHidden = true;
 
           return nsnull;
         }
 
         // Try using frame to do it.
         newAcc = f->CreateAccessible();
       }
     }
--- a/accessible/src/base/nsAccessibilityService.h
+++ b/accessible/src/base/nsAccessibilityService.h
@@ -106,19 +106,19 @@ public:
     CreateHyperTextAccessible(nsIContent* aContent, nsIPresShell* aPresShell);
   virtual already_AddRefed<nsAccessible>
     CreateOuterDocAccessible(nsIContent* aContent, nsIPresShell* aPresShell);
 
   virtual nsAccessible* AddNativeRootAccessible(void* aAtkAccessible);
   virtual void RemoveNativeRootAccessible(nsAccessible* aRootAccessible);
 
   virtual void ContentRangeInserted(nsIPresShell* aPresShell,
-                                      nsIContent* aContainer,
-                                      nsIContent* aStartChild,
-                                      nsIContent* aEndChild);
+                                    nsIContent* aContainer,
+                                    nsIContent* aStartChild,
+                                    nsIContent* aEndChild);
 
   virtual void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aContainer,
                               nsIContent* aChild);
 
   virtual void NotifyOfAnchorJumpTo(nsIContent *aTarget);
 
   virtual void PresShellDestroyed(nsIPresShell* aPresShell);
 
@@ -133,26 +133,26 @@ public:
    * Return true if accessibility service has been shutdown.
    */
   static PRBool IsShutdown() { return gIsShutdown; }
 
   /**
    * Return an accessible for the given DOM node from the cache or create new
    * one.
    *
-   * @param  aNode       [in] the given node
-   * @param  aPresShell  [in] the pres shell of the node
-   * @param  aWeakShell  [in] the weak shell for the pres shell
-   * @param  aIsHidden   [out, optional] indicates whether the node's frame is
-   *                       hidden
+   * @param  aNode             [in] the given node
+   * @param  aPresShell        [in] the pres shell of the node
+   * @param  aWeakShell        [in] the weak shell for the pres shell
+   * @param  aIsSubtreeHidden  [out, optional] indicates whether the node's
+   *                             frame and its subtree is hidden
    */
   already_AddRefed<nsAccessible>
     GetOrCreateAccessible(nsINode* aNode, nsIPresShell* aPresShell,
                           nsIWeakReference* aWeakShell,
-                          PRBool* aIsHidden = nsnull);
+                          bool* aIsSubtreeHidden = nsnull);
 
   /**
    * Return an accessible for the given DOM node.
    */
   nsAccessible* GetAccessible(nsINode* aNode);
 
   /**
    * Return an accessible for a DOM node in the given presshell.
--- a/accessible/src/base/nsTextEquivUtils.cpp
+++ b/accessible/src/base/nsTextEquivUtils.cpp
@@ -232,21 +232,16 @@ nsTextEquivUtils::AppendFromAccessibleCh
 
   return rv;
 }
 
 nsresult
 nsTextEquivUtils::AppendFromAccessible(nsAccessible *aAccessible,
                                        nsAString *aString)
 {
-  // Ignore hidden accessible for name computation.
-  nsIFrame* frame = aAccessible->GetFrame();
-  if (!frame || !frame->GetStyleVisibility()->IsVisible())
-    return NS_OK;
-
   //XXX: is it necessary to care the accessible is not a document?
   if (aAccessible->IsContent()) {
     nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(),
                                                  aString);
     if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
       return rv;
   }
 
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -315,37 +315,51 @@ function ensureAccessibleTree(aAccOrElmO
  *                                      fields
  */
 function testAccessibleTree(aAccOrElmOrID, aAccTree)
 {
   var acc = getAccessible(aAccOrElmOrID);
   if (!acc)
     return;
 
-  for (var prop in aAccTree) {
+  var accTree = aAccTree;
+
+  // Support of simplified accessible tree object.
+  var key = Object.keys(accTree)[0];
+  var roleName = "ROLE_" + key;
+  if (roleName in nsIAccessibleRole) {
+    accTree = {
+      role: nsIAccessibleRole[roleName],
+      children: accTree[key]
+    };
+  }
+
+  // Test accessible properties.
+  for (var prop in accTree) {
     var msg = "Wrong value of property '" + prop + "' for " + prettyName(acc) + ".";
     if (prop == "role") {
-      is(roleToString(acc[prop]), roleToString(aAccTree[prop]), msg);
+      is(roleToString(acc[prop]), roleToString(accTree[prop]), msg);
 
     } else if (prop == "states") {
-      var statesObj = aAccTree[prop];
+      var statesObj = accTree[prop];
       testStates(acc, statesObj.states, statesObj.extraStates,
                  statesObj.absentStates, statesObj.absentExtraStates);
 
     } else if (prop != "children") {
-      is(acc[prop], aAccTree[prop], msg);
+      is(acc[prop], accTree[prop], msg);
     }
   }
 
-  if ("children" in aAccTree && aAccTree["children"] instanceof Array) {
+  // Test children.
+  if ("children" in accTree && accTree["children"] instanceof Array) {
     var children = acc.children;
-    is(children.length, aAccTree.children.length,
+    is(children.length, accTree.children.length,
        "Different amount of expected children of " + prettyName(acc) + ".");
 
-    if (aAccTree.children.length == children.length) {
+    if (accTree.children.length == children.length) {
       var childCount = children.length;
 
       // nsIAccessible::firstChild
       var expectedFirstChild = childCount > 0 ?
         children.queryElementAt(0, nsIAccessible) : null;
       var firstChild = null;
       try { firstChild = acc.firstChild; } catch (e) {}
       is(firstChild, expectedFirstChild,
@@ -385,17 +399,17 @@ function testAccessibleTree(aAccOrElmOrI
         var expectedPrevSibling = (i > 0) ?
           children.queryElementAt(i - 1, nsIAccessible) : null;
         var prevSibling = null;
         try { prevSibling = child.previousSibling; } catch (e) {}
         is(prevSibling, expectedPrevSibling,
            "Wrong previous sibling of " + prettyName(child));
 
         // Go down through subtree
-        testAccessibleTree(child, aAccTree.children[i]);
+        testAccessibleTree(child, accTree.children[i]);
       }
     }
   }
 }
 
 /**
  * Return true if accessible for the given node is in cache.
  */
--- a/accessible/tests/mochitest/events/test_mutation.html
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -288,23 +288,36 @@
 
       // Show/hide events by changing of display style of accessible DOM node
       // from 'inline' to 'none', 'none' to 'inline'.
       var id = "link1";
       getAccessible(id); // ensure accessible is created
       gQueue.push(new changeStyle(id, "display", "none", kHideEvents));
       gQueue.push(new changeStyle(id, "display", "inline", kShowEvents));
 
+      // Show/hide events by changing of visibility style of accessible DOM node
+      // from 'visible' to 'hidden', 'hidden' to 'visible'.
+      var id = "link2";
+      getAccessible(id);
+      gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents));
+      gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents));
+
       // Show/hide events by changing of display style of accessible DOM node
       // from 'inline' to 'block', 'block' to 'inline'.
       var id = "link3";
       getAccessible(id); // ensure accessible is created
       gQueue.push(new changeStyle(id, "display", "block", kHideAndShowEvents));
       gQueue.push(new changeStyle(id, "display", "inline", kHideAndShowEvents));
 
+      // Show/hide events by changing of visibility style of accessible DOM node
+      // from 'collapse' to 'visible', 'visible' to 'collapse'.
+      var id = "link4";
+      gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents));
+      gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents));
+
       // Show/hide events by adding new accessible DOM node and removing old one.
       var id = "link5";
       gQueue.push(new cloneAndAppendToDOM(id));
       gQueue.push(new removeFromDOM(id));
 
       // No show/hide events by adding new not accessible DOM node and removing
       // old one, no reorder event for their parent.
       var id = "child1";
@@ -338,16 +351,20 @@
       getAccessible("link6"); // ensure accessible is created
       gQueue.push(new cloneAndReplaceInDOM("link6"));
 
       // Show/hide events by changing class name on the parent node.
       gQueue.push(new changeClass("container2", "link7", "", kShowEvents));
       gQueue.push(new changeClass("container2", "link7", "displayNone",
                                   kHideEvents));
 
+      gQueue.push(new changeClass("container3", "link8", "", kShowEvents));
+      gQueue.push(new changeClass("container3", "link8", "visibilityHidden",
+                                  kHideEvents));
+
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
@@ -362,16 +379,21 @@
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662"
      title="no reorder event when html:link display property is changed from 'none' to 'inline'">
     Mozilla Bug 472662
   </a><br>
   <a target="_blank"
      title="Rework accessible tree update code"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
     Mozilla Bug 570275
+  </a><br>
+  <a target="_blank"
+     title="Develop a way to handle visibility style"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125">
+    Mozilla Bug 606125
   </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
   <div id="eventdump"></div>
 
--- a/accessible/tests/mochitest/tree/test_tabbrowser.xul
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul
@@ -66,19 +66,16 @@
     }
 
     function testAccTree()
     {
       var tabsAccTree = {
         role: ROLE_PAGETABLIST,
         children: [
           {
-            role: ROLE_PUSHBUTTON // tab scroll up button
-          },
-          {
             role: ROLE_PAGETAB,
             children: [
               {
                 role: ROLE_PUSHBUTTON
               }
             ]
           },
           {
@@ -86,19 +83,16 @@
             children: [
               {
                 role: ROLE_PUSHBUTTON
               }
             ]
           },
           {
             role: ROLE_PUSHBUTTON
-          },
-          {
-            role: ROLE_PUSHBUTTON // tab scroll down button
           }
         ]
       };
       testAccessibleTree(getNode("tabbrowser").tabContainer, tabsAccTree);
 
       var tabboxAccTree = {
         role: ROLE_PANE,
         children: [
--- a/accessible/tests/mochitest/treeupdate/Makefile.in
+++ b/accessible/tests/mochitest/treeupdate/Makefile.in
@@ -41,18 +41,19 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = accessible/treeupdate
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =\
+		test_ariadialog.html \
 		test_doc.html \
 		test_list_editabledoc.html \
 		test_list.html \
 		test_recreation.html \
-		test_tableinsubtree.html \
 		test_textleaf.html \
+		test_visibility.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
rename from accessible/tests/mochitest/treeupdate/test_tableinsubtree.html
rename to accessible/tests/mochitest/treeupdate/test_ariadialog.html
--- a/accessible/tests/mochitest/treeupdate/test_tableinsubtree.html
+++ b/accessible/tests/mochitest/treeupdate/test_ariadialog.html
@@ -29,44 +29,34 @@
       this.node = getNode(aID);
 
       this.eventSeq = [
         new invokerChecker(EVENT_SHOW, this.node)
       ];
 
       this.invoke = function showARIADialog_invoke()
       {
-        this.node.style.display = "block";
+        getNode("dialog").style.display = "block";
+        getNode("table").style.visibility = "visible";
+        getNode("a").textContent = "link";
         getNode("input").value = "hello";
-        getNode("cell").textContent = "cell1";
         getNode("input").focus();
       }
 
       this.finalCheck = function showARIADialog_finalCheck()
       {
         var tree = {
           role: ROLE_DIALOG,
           children: [
             {
-              role: ROLE_TABLE,
-              children: [
-                {
-                  role: ROLE_ROW,
-                  children: [
-                    {
-                      role: ROLE_CELL,
-                      children: [ { role: ROLE_TEXT_LEAF } ]
-                    },
-                    {
-                      role: ROLE_CELL,
-                      children: [ { role: ROLE_ENTRY } ]
-                    }
-                  ]
-                }
-              ]
+              role: ROLE_PUSHBUTTON,
+              children: [ { role: ROLE_TEXT_LEAF } ]
+            },
+            {
+              role: ROLE_ENTRY
             }
           ]
         };
         testAccessibleTree(aID, tree);
       }
 
       this.getID = function showARIADialog_getID()
       {
@@ -105,16 +95,26 @@
   </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <div id="dialog" role="dialog" style="display: none;">
-    <table>
-      <tr><td id="cell"></td><td><input id="input"></td>
+    <table id="table" role="presentation"
+           style="display: block; position: fixed; top: 88px; left: 312.5px; z-index: 10010; visibility: hidden;">
+      <tbody>
+        <tr>
+          <td role="presentation">
+            <div role="presentation">
+              <a id="a" role="button">text</a>
+            </div>
+            <input id="input">
+          </td>
+        </tr>
+      </tbody>
     </table>
   </div>
 
   <div id="eventdump"></div>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_visibility.html
@@ -0,0 +1,439 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <title>Style visibility tree update test</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <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
+
+    /**
+     * Hide parent while child stays visible.
+     */
+    function test1(aContainerID, aParentID, aChildID)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode(aParentID)),
+        new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+        new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+      ];
+
+      this.invoke = function invoke()
+      {
+        var tree =
+          { SECTION: [
+            { SECTION: [
+              { SECTION: [
+                { TEXT_LEAF: [] }
+              ] }
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+
+        getNode(aParentID).style.visibility = "hidden";
+      }
+
+      this.finalCheck = function finalCheck()
+      {
+        var tree =
+          { SECTION: [
+            { SECTION: [
+              { TEXT_LEAF: [] }
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+      }
+
+      this.getID = function getID()
+      {
+        return "hide parent while child stays visible";
+      }
+    }
+
+    /**
+     * Hide grand parent while its children stay visible.
+     */
+    function test2(aContainerID, aGrandParentID, aChildID, aChild2ID)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode(aGrandParentID)),
+        new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+        new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+        new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+      ];
+
+      this.invoke = function invoke()
+      {
+        var tree =
+          { SECTION: [ // container
+            { SECTION: [ // grand parent
+              { SECTION: [
+                { SECTION: [ // child
+                  { TEXT_LEAF: [] }
+                ] },
+                { SECTION: [ // child2
+                  { TEXT_LEAF: [] }
+                ] }
+              ] }
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+
+        getNode(aGrandParentID).style.visibility = "hidden";
+      }
+
+      this.finalCheck = function finalCheck()
+      {
+        var tree =
+          { SECTION: [ // container
+            { SECTION: [ // child
+              { TEXT_LEAF: [] }
+            ] },
+            { SECTION: [ // child2
+              { TEXT_LEAF: [] }
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+      }
+
+      this.getID = function getID()
+      {
+        return "hide grand parent while its children stay visible";
+      }
+    }
+
+    /**
+     * Change container style, hide parents while their children stay visible.
+     */
+    function test3(aContainerID, aParentID, aParent2ID, aChildID, aChild2ID)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode(aParentID)),
+        new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+        new invokerChecker(EVENT_HIDE, getNode(aParent2ID)),
+        new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+        new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+      ];
+
+      this.invoke = function invoke()
+      {
+        var tree =
+          { SECTION: [ // container
+            { SECTION: [ // parent
+              { SECTION: [ // child
+                { TEXT_LEAF: [] }
+              ] }
+            ] },
+            { SECTION: [ // parent2
+              { SECTION: [ // child2
+                { TEXT_LEAF: [] }
+              ] },
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+
+        getNode(aContainerID).style.color = "red";
+        getNode(aParentID).style.visibility = "hidden";
+        getNode(aParent2ID).style.visibility = "hidden";
+      }
+
+      this.finalCheck = function finalCheck()
+      {
+        var tree =
+          { SECTION: [ // container
+            { SECTION: [ // child
+              { TEXT_LEAF: [] }
+            ] },
+            { SECTION: [ // child2
+              { TEXT_LEAF: [] }
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+      }
+
+      this.getID = function getID()
+      {
+        return "change container style, hide parents while their children stay visible";
+      }
+    }
+
+    /**
+     * Change container style and make visible child inside the table.
+     */
+    function test4(aContainerID, aChildID)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+        new invokerChecker(EVENT_REORDER, getNode(aChildID).parentNode)
+      ];
+
+      this.invoke = function invoke()
+      {
+        var tree =
+          { SECTION: [
+            { TABLE: [
+              { ROW: [
+                { CELL: [ ] }
+              ] }
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+
+        getNode(aContainerID).style.color = "red";
+        getNode(aChildID).style.visibility = "visible";
+      }
+
+      this.finalCheck = function finalCheck()
+      {
+        var tree =
+          { SECTION: [
+            { TABLE: [
+              { ROW: [
+                { CELL: [
+                  { SECTION: [
+                    { TEXT_LEAF: [] }
+                  ] }
+              ] }
+            ] }
+          ] }
+        ] };
+        testAccessibleTree(aContainerID, tree);
+      }
+
+      this.getID = function getID()
+      {
+        return "change container style, make visible child insdie the table";
+      }
+    }
+
+    /**
+     * Hide subcontainer while child inside the table stays visible.
+     */
+    function test5(aContainerID, aSubContainerID, aChildID)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)),
+        new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+        new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+      ];
+
+      this.invoke = function invoke()
+      {
+        var tree =
+          { SECTION: [ // container
+            { SECTION: [ // subcontainer
+              { TABLE: [
+                { ROW: [
+                  { CELL: [
+                    { SECTION: [ // child
+                      { TEXT_LEAF: [] }
+                    ] }
+                  ] }
+                ] }
+              ] }
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+
+        getNode(aSubContainerID).style.visibility = "hidden";
+      }
+
+      this.finalCheck = function finalCheck()
+      {
+        var tree =
+          { SECTION: [ // container
+            { SECTION: [ // child
+              { TEXT_LEAF: [] }
+            ] }
+          ] };
+        testAccessibleTree(aContainerID, tree);
+      }
+
+      this.getID = function getID()
+      {
+        return "hide subcontainer while child inside the table stays visible";
+      }
+    }
+
+    /**
+     * Hide subcontainer while its child and child inside the nested table stays visible.
+     */
+    function test6(aContainerID, aSubContainerID, aChildID, aChild2ID)
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)),
+        new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+        new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+        new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+      ];
+
+      this.invoke = function invoke()
+      {
+        var tree =
+          { SECTION: [ // container
+            { SECTION: [ // subcontainer
+              { TABLE: [
+                { ROW: [
+                  { CELL: [
+                    { TABLE: [ // nested table
+                      { ROW: [
+                        { CELL: [
+                          { SECTION: [ // child
+                            { TEXT_LEAF: [] } ]} ]} ]} ]} ]} ]} ]},
+              { SECTION: [ // child2
+                { TEXT_LEAF: [] } ]} ]} ]};
+
+        testAccessibleTree(aContainerID, tree);
+
+        // invoke
+        getNode(aSubContainerID).style.visibility = "hidden";
+      }
+
+      this.finalCheck = function finalCheck()
+      {
+        var tree =
+          { SECTION: [ // container
+            { SECTION: [ // child
+              { TEXT_LEAF: [] } ]},
+            { SECTION: [ // child2
+              { TEXT_LEAF: [] } ]} ]};
+
+        testAccessibleTree(aContainerID, tree);
+      }
+
+      this.getID = function getID()
+      {
+        return "hide subcontainer while its child and child inside the nested table stays visible";
+      }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Test
+
+    //gA11yEventDumpID = "eventdump"; // debug stuff
+    //gA11yEventDumpToConsole = true;
+
+    var gQueue = null;
+
+    function doTest()
+    {
+      gQueue = new eventQueue();
+
+      gQueue.push(new test1("t1_container", "t1_parent", "t1_child"));
+      gQueue.push(new test2("t2_container", "t2_grandparent", "t2_child", "t2_child2"));
+      gQueue.push(new test3("t3_container", "t3_parent", "t3_parent2", "t3_child", "t3_child2"));
+      gQueue.push(new test4("t4_container", "t4_child"));
+      gQueue.push(new test5("t5_container", "t5_subcontainer", "t5_child"));
+      gQueue.push(new test6("t6_container", "t6_subcontainer", "t6_child", "t6_child2"));
+
+      gQueue.invoke(); // SimpleTest.finish() will be called in the end
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+     title="Develop a way to handle visibility style"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125">
+    Mozilla Bug 606125
+  </a>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <!-- hide parent while child stays visible -->
+  <div id="t1_container">
+    <div id="t1_parent">
+      <div id="t1_child" style="visibility: visible">text</div>
+    </div>
+  </div>
+
+  <!-- hide grandparent while its children stay visible -->
+  <div id="t2_container">
+    <div id="t2_grandparent">
+      <div>
+        <div id="t2_child" style="visibility: visible">text</div>
+        <div id="t2_child2" style="visibility: visible">text</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- change container style, hide parents while their children stay visible -->
+  <div id="t3_container">
+    <div id="t3_parent">
+      <div id="t3_child" style="visibility: visible">text</div>
+    </div>
+    <div id="t3_parent2">
+      <div id="t3_child2" style="visibility: visible">text</div>
+    </div>
+  </div>
+
+  <!-- change container style, show child inside the table -->
+  <div id="t4_container">
+    <table>
+      <tr>
+        <td>
+          <div id="t4_child" style="visibility: hidden;">text</div>
+        </td>
+      </tr>
+    </table>
+  </div>
+
+  <!-- hide subcontainer while child inside the table stays visible -->
+  <div id="t5_container">
+    <div id="t5_subcontainer">
+      <table>
+        <tr>
+          <td>
+            <div id="t5_child" style="visibility: visible;">text</div>
+          </td>
+        </tr>
+      </table>
+    </div>
+  </div>
+
+  <!-- hide subcontainer while its child and child inside the nested table stays visible -->
+  <div id="t6_container">
+    <div id="t6_subcontainer">
+      <table>
+        <tr>
+          <td>
+            <table>
+              <tr>
+                <td>
+                  <div id="t6_child" style="visibility: visible;">text</div>
+                </td>
+              </tr>
+            </table>
+          </td>
+        </tr>
+      </table>
+      <div id="t6_child2" style="visibility: visible">text</div>
+    </div>
+  </div>
+
+  <div id="eventdump"></div>
+</body>
+</html>
--- a/layout/base/nsFrameManager.cpp
+++ b/layout/base/nsFrameManager.cpp
@@ -91,16 +91,20 @@
 #include "nsLayoutUtils.h"
 #include "nsAutoPtr.h"
 #include "imgIRequest.h"
 #include "nsTransitionManager.h"
 #include "RestyleTracker.h"
 
 #include "nsFrameManager.h"
 
+#ifdef ACCESSIBILITY
+#include "nsIAccessibilityService.h"
+#endif
+
   #ifdef DEBUG
     //#define NOISY_DEBUG
     //#define DEBUG_UNDISPLAYED_MAP
   #else
     #undef NOISY_DEBUG
     #undef DEBUG_UNDISPLAYED_MAP
   #endif
 
@@ -995,17 +999,19 @@ CaptureChange(nsStyleContext* aOldContex
  */
 nsChangeHint
 nsFrameManager::ReResolveStyleContext(nsPresContext     *aPresContext,
                                       nsIFrame          *aFrame,
                                       nsIContent        *aParentContent,
                                       nsStyleChangeList *aChangeList, 
                                       nsChangeHint       aMinChange,
                                       nsRestyleHint      aRestyleHint,
-                                      RestyleTracker&    aRestyleTracker)
+                                      RestyleTracker&    aRestyleTracker,
+                                      DesiredA11yNotifications aDesiredA11yNotifications,
+                                      nsTArray<nsIContent*>& aVisibleKidsOfHiddenElement)
 {
   if (!NS_IsHintSubset(nsChangeHint_NeedDirtyReflow, aMinChange)) {
     // If aMinChange doesn't include nsChangeHint_NeedDirtyReflow, clear out
     // all the reflow change bits from it, so that we'll make sure to append a
     // change to the list for ourselves if we need a reflow.  We need this
     // because the parent may or may not actually end up reflowing us
     // otherwise.
     aMinChange = NS_SubtractHint(aMinChange, nsChangeHint_ReflowFrame);
@@ -1042,16 +1048,22 @@ nsFrameManager::ReResolveStyleContext(ns
   // XXXbz oldContext should just be an nsRefPtr
   nsStyleContext* oldContext = aFrame->GetStyleContext();
   nsStyleSet* styleSet = aPresContext->StyleSet();
 
   // XXXbz the nsIFrame constructor takes an nsStyleContext, so how
   // could oldContext be null?
   if (oldContext) {
     oldContext->AddRef();
+
+#ifdef ACCESSIBILITY
+    PRBool wasFrameVisible = mPresShell->IsAccessibilityActive() ?
+      oldContext->GetStyleVisibility()->IsVisible() : PR_FALSE;
+#endif
+
     nsIAtom* const pseudoTag = oldContext->GetPseudo();
     const nsCSSPseudoElements::Type pseudoType = oldContext->GetPseudoType();
     nsIContent* localContent = aFrame->GetContent();
     // |content| is the node that we used for rule matching of
     // normal elements (not pseudo-elements) and for which we generate
     // framechange hints if we need them.
     // XXXldb Why does it make sense to use aParentContent?  (See
     // comment above assertion at start of function.)
@@ -1095,17 +1107,19 @@ nsFrameManager::ReResolveStyleContext(ns
       // nsStyleContext::CalcStyleDifference says. CalcStyleDifference
       // can't be trusted because it assumes any changes to the parent
       // style context provider will be automatically propagated to
       // the frame(s) with child style contexts.
 
       assumeDifferenceHint = ReResolveStyleContext(aPresContext, providerFrame,
                                                    aParentContent, aChangeList,
                                                    aMinChange, aRestyleHint,
-                                                   aRestyleTracker);
+                                                   aRestyleTracker,
+                                                   aDesiredA11yNotifications,
+                                                   aVisibleKidsOfHiddenElement);
 
       // The provider's new context becomes the parent context of
       // aFrame's context.
       parentContext = providerFrame->GetStyleContext();
       // Set |resolvedChild| so we don't bother resolving the
       // provider again.
       resolvedChild = providerFrame;
     }
@@ -1394,17 +1408,54 @@ nsFrameManager::ReResolveStyleContext(ns
             aChangeList->AppendChange(aFrame, content,
                                       nsChangeHint_ReconstructFrame);
           }
         }      
       }
     }
 
     if (!(aMinChange & nsChangeHint_ReconstructFrame)) {
-      
+      A11yNotificationType ourA11yNotification = eDontNotify;
+      DesiredA11yNotifications kidsDesiredA11yNotification =
+        aDesiredA11yNotifications;
+#ifdef ACCESSIBILITY
+      // Notify a11y for primary frame only if it's a root frame of visibility
+      // changes or its parent frame was hidden while it stays visible and
+      // it is not inside a {ib} split or is the first frame of {ib} split.
+      if (mPresShell->IsAccessibilityActive() && !aFrame->GetPrevContinuation() &&
+          !nsLayoutUtils::FrameIsNonFirstInIBSplit(aFrame)) {
+        if (aDesiredA11yNotifications == eSendAllNotifications) {
+          PRBool isFrameVisible = newContext->GetStyleVisibility()->IsVisible();
+          if (isFrameVisible != wasFrameVisible) {
+            if (isFrameVisible) {
+              // Notify a11y the element (perhaps with its children) was shown.
+              // We don't fall into this case if this element gets or stays shown
+              // while its parent becomes hidden.
+              kidsDesiredA11yNotification = eSkipNotifications;
+              ourA11yNotification = eNotifyShown;
+            } else {
+              // The element is being hidden; its children may stay visible, or
+              // become visible after being hidden previously. If we'll find
+              // visible children then we should notify a11y about that as if
+              // they were inserted into tree. Notify a11y this element was
+              // hidden.
+              kidsDesiredA11yNotification = eNotifyIfShown;
+              ourA11yNotification = eNotifyHidden;
+            }
+          }
+        } else if (aDesiredA11yNotifications == eNotifyIfShown &&
+                   newContext->GetStyleVisibility()->IsVisible()) {
+          // Notify a11y that element stayed visible while its parent was
+          // hidden.
+          aVisibleKidsOfHiddenElement.AppendElement(aFrame->GetContent());
+          kidsDesiredA11yNotification = eSkipNotifications;
+        }
+      }
+#endif
+
       // There is no need to waste time crawling into a frame's children on a frame change.
       // The act of reconstructing frames will force new style contexts to be resolved on all
       // of this frame's descendants anyway, so we want to avoid wasting time processing
       // style contexts that we're just going to throw away anyway. - dwh
 
       // now do children
       PRInt32 listIndex = 0;
       nsIAtom* childList = nsnull;
@@ -1437,45 +1488,83 @@ nsFrameManager::ReResolveStyleContext(ns
               // |nsFrame::GetParentStyleContextFrame| checks being out
               // of flow so that this works correctly.
               do {
                 ReResolveStyleContext(aPresContext, outOfFlowFrame,
                                       content, aChangeList,
                                       NS_SubtractHint(aMinChange,
                                                       nsChangeHint_ReflowFrame),
                                       childRestyleHint,
-                                      aRestyleTracker);
+                                      aRestyleTracker,
+                                      kidsDesiredA11yNotification,
+                                      aVisibleKidsOfHiddenElement);
               } while (outOfFlowFrame = outOfFlowFrame->GetNextContinuation());
 
               // reresolve placeholder's context under the same parent
               // as the out-of-flow frame
               ReResolveStyleContext(aPresContext, child, content,
                                     aChangeList, aMinChange,
                                     childRestyleHint,
-                                    aRestyleTracker);
+                                    aRestyleTracker,
+                                    kidsDesiredA11yNotification,
+                                    aVisibleKidsOfHiddenElement);
             }
             else {  // regular child frame
               if (child != resolvedChild) {
                 ReResolveStyleContext(aPresContext, child, content,
                                       aChangeList, aMinChange,
                                       childRestyleHint,
-                                      aRestyleTracker);
+                                      aRestyleTracker,
+                                      kidsDesiredA11yNotification,
+                                      aVisibleKidsOfHiddenElement);
               } else {
                 NOISY_TRACE_FRAME("child frame already resolved as descendant, skipping",aFrame);
               }
             }
           }
           child = child->GetNextSibling();
         }
 
         childList = aFrame->GetAdditionalChildListName(listIndex++);
       } while (childList);
       // XXX need to do overflow frames???
+
+#ifdef ACCESSIBILITY
+      // Send notifications about visibility changes.
+      if (ourA11yNotification == eNotifyShown) {
+        nsCOMPtr<nsIAccessibilityService> accService =
+          do_GetService("@mozilla.org/accessibilityService;1");
+        if (accService) {
+          nsIPresShell* presShell = aFrame->PresContext()->GetPresShell();
+          nsIContent* content = aFrame->GetContent();
+
+          accService->ContentRangeInserted(presShell, content->GetParent(),
+                                           content,
+                                           content->GetNextSibling());
+        }
+      } else if (ourA11yNotification == eNotifyHidden) {
+        nsCOMPtr<nsIAccessibilityService> accService =
+          do_GetService("@mozilla.org/accessibilityService;1");
+        if (accService) {
+          nsIPresShell* presShell = aFrame->PresContext()->GetPresShell();
+          nsIContent* content = aFrame->GetContent();
+          accService->ContentRemoved(presShell, content->GetParent(), content);
+
+          // Process children staying shown.
+          PRUint32 visibleContentCount = aVisibleKidsOfHiddenElement.Length();
+          for (PRUint32 idx = 0; idx < visibleContentCount; idx++) {
+            nsIContent* content = aVisibleKidsOfHiddenElement[idx];
+            accService->ContentRangeInserted(presShell, content->GetParent(),
+                                             content, content->GetNextSibling());
+          }
+          aVisibleKidsOfHiddenElement.Clear();
+        }
+      }
+#endif
     }
-
   }
 
   return aMinChange;
 }
 
 void
 nsFrameManager::ComputeStyleChangeFor(nsIFrame          *aFrame, 
                                       nsStyleChangeList *aChangeList,
@@ -1495,26 +1584,29 @@ nsFrameManager::ComputeStyleChangeFor(ns
   NS_ASSERTION(!frame->GetPrevContinuation(), "must start with the first in flow");
 
   // We want to start with this frame and walk all its next-in-flows,
   // as well as all its special siblings and their next-in-flows,
   // reresolving style on all the frames we encounter in this walk.
 
   FramePropertyTable *propTable = GetPresContext()->PropertyTable();
 
+  nsTArray<nsIContent*> visibleKidsOfHiddenElement;
   do {
     // Outer loop over special siblings
     do {
       // Inner loop over next-in-flows of the current frame
       nsChangeHint frameChange =
         ReResolveStyleContext(GetPresContext(), frame, nsnull,
                               aChangeList, topLevelChange,
                               aRestyleDescendants ?
                                 eRestyle_Subtree : eRestyle_Self,
-                              aRestyleTracker);
+                              aRestyleTracker,
+                              eSendAllNotifications,
+                              visibleKidsOfHiddenElement);
       NS_UpdateHint(topLevelChange, frameChange);
 
       if (topLevelChange & nsChangeHint_ReconstructFrame) {
         // If it's going to cause a framechange, then don't bother
         // with the continuations or special siblings since they'll be
         // clobbered by the frame reconstruct anyway.
         NS_ASSERTION(!frame->GetPrevContinuation(),
                      "continuing frame had more severe impact than first-in-flow");
--- a/layout/base/nsFrameManager.h
+++ b/layout/base/nsFrameManager.h
@@ -197,24 +197,38 @@ public:
 #endif
 
   NS_HIDDEN_(nsIPresShell*) GetPresShell() const { return mPresShell; }
   NS_HIDDEN_(nsPresContext*) GetPresContext() const {
     return mPresShell->GetPresContext();
   }
 
 private:
+  enum DesiredA11yNotifications {
+    eSkipNotifications,
+    eSendAllNotifications,
+    eNotifyIfShown
+  };
+
+  enum A11yNotificationType {
+    eDontNotify,
+    eNotifyShown,
+    eNotifyHidden
+  };
+
   // Use eRestyle_Self for the aRestyleHint argument to mean
   // "reresolve our style context but not kids", use eRestyle_Subtree
   // to mean "reresolve our style context and kids", and use
   // nsRestyleHint(0) to mean recompute a new style context for our
   // current parent and existing rulenode, and the same for kids.
   NS_HIDDEN_(nsChangeHint)
     ReResolveStyleContext(nsPresContext    *aPresContext,
                           nsIFrame          *aFrame,
                           nsIContent        *aParentContent,
                           nsStyleChangeList *aChangeList, 
                           nsChangeHint       aMinChange,
                           nsRestyleHint      aRestyleHint,
-                          RestyleTracker&    aRestyleTracker);
+                          RestyleTracker&    aRestyleTracker,
+                          DesiredA11yNotifications aDesiredA11yNotifications,
+                          nsTArray<nsIContent*>& aVisibleKidsOfHiddenElement);
 };
 
 #endif