Bug 1054454 - added support for aria-hidden attribute changes. Added output filter wh when presenting aria-hidden elements. r=eeejay
authorYura Zenevich <yzenevich@mozilla.com>
Thu, 28 Aug 2014 09:07:30 -0400
changeset 223833 d20f8a8d74992021f36d041ce5b00aff6f7e8586
parent 223832 a90376972bed8530e56e15d5ae1b426ef8f4da50
child 223834 e89cb1dbd13fb794e44376e73d8197f26f7372f4
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerseeejay
bugs1054454
milestone34.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 1054454 - added support for aria-hidden attribute changes. Added output filter wh when presenting aria-hidden elements. r=eeejay --- accessible/jsat/AccessFu.jsm | 4 + accessible/jsat/EventManager.jsm | 92 ++++++++++----- accessible/jsat/Utils.jsm | 14 ++- .../mochitest/jsat/doc_content_integration.html | 17 +++ .../mochitest/jsat/test_content_integration.html | 67 +++++++++++ .../tests/mochitest/jsat/test_live_regions.html | 129 ++++++++++++++++++++- 6 files changed, 287 insertions(+), 36 deletions(-)
accessible/jsat/AccessFu.jsm
accessible/jsat/EventManager.jsm
accessible/jsat/Utils.jsm
accessible/tests/mochitest/jsat/doc_content_integration.html
accessible/tests/mochitest/jsat/test_content_integration.html
accessible/tests/mochitest/jsat/test_live_regions.html
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -230,16 +230,20 @@ this.AccessFu = { // jshint ignore:line
         break;
       case 'AccessFu:DoScroll':
         this.Input.doScroll(aMessage.json);
         break;
     }
   },
 
   _output: function _output(aPresentationData, aBrowser) {
+    if (!Utils.isAliveAndVisible(
+      Utils.AccRetrieval.getAccessibleFor(aBrowser))) {
+      return;
+    }
     for (let presenter of aPresentationData) {
       if (!presenter) {
         continue;
       }
 
       try {
         Output[presenter.type](presenter.details, aBrowser);
       } catch (x) {
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -220,53 +220,40 @@ this.EventManager.prototype = {
           this.sendMsgFunc("AccessFu:Input", editState);
 
         this.present(Presentation.textSelectionChanged(acc.getText(0,-1),
                      caretOffset, caretOffset, 0, 0, aEvent.isFromUserInput));
 
         this.editState = editState;
         break;
       }
+      case Events.OBJECT_ATTRIBUTE_CHANGED:
+      {
+        let evt = aEvent.QueryInterface(
+          Ci.nsIAccessibleObjectAttributeChangedEvent);
+        if (evt.changedAttribute.toString() !== 'aria-hidden') {
+          // Only handle aria-hidden attribute change.
+          break;
+        }
+        if (Utils.isHidden(aEvent.accessible)) {
+          this._handleHide(evt);
+        } else {
+          this._handleShow(aEvent);
+        }
+        break;
+      }
       case Events.SHOW:
       {
-        let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
-          ['additions', 'all']);
-        // Only handle show if it is a relevant live region.
-        if (!liveRegion) {
-          break;
-        }
-        // Show for text is handled by the EVENT_TEXT_INSERTED handler.
-        if (aEvent.accessible.role === Roles.TEXT_LEAF) {
-          break;
-        }
-        this._dequeueLiveEvent(Events.HIDE, liveRegion);
-        this.present(Presentation.liveRegion(liveRegion, isPolite, false));
+        this._handleShow(aEvent);
         break;
       }
       case Events.HIDE:
       {
         let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent);
-        let {liveRegion, isPolite} = this._handleLiveRegion(
-          evt, ['removals', 'all']);
-        if (liveRegion) {
-          // Hide for text is handled by the EVENT_TEXT_REMOVED handler.
-          if (aEvent.accessible.role === Roles.TEXT_LEAF) {
-            break;
-          }
-          this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
-        } else {
-          let vc = Utils.getVirtualCursor(this.contentScope.content.document);
-          if (vc.position &&
-            (Utils.getState(vc.position).contains(States.DEFUNCT) ||
-              Utils.isInSubtree(vc.position, aEvent.accessible))) {
-            this.contentControl.autoMove(
-              evt.targetPrevSibling || evt.targetParent,
-              { moveToFocused: true, delay: 500 });
-          }
-        }
+        this._handleHide(evt);
         break;
       }
       case Events.TEXT_INSERTED:
       case Events.TEXT_REMOVED:
       {
         let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
           ['text', 'all']);
         if (aEvent.isFromUserInput || liveRegion) {
@@ -301,16 +288,61 @@ this.EventManager.prototype = {
         if (position === target ||
             Utils.getEmbeddedControl(position) === target) {
           this.present(Presentation.valueChanged(target));
         }
       }
     }
   },
 
+  _handleShow: function _handleShow(aEvent) {
+    let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+      ['additions', 'all']);
+    // Only handle show if it is a relevant live region.
+    if (!liveRegion) {
+      return;
+    }
+    // Show for text is handled by the EVENT_TEXT_INSERTED handler.
+    if (aEvent.accessible.role === Roles.TEXT_LEAF) {
+      return;
+    }
+    this._dequeueLiveEvent(Events.HIDE, liveRegion);
+    this.present(Presentation.liveRegion(liveRegion, isPolite, false));
+  },
+
+  _handleHide: function _handleHide(aEvent) {
+    let {liveRegion, isPolite} = this._handleLiveRegion(
+      aEvent, ['removals', 'all']);
+    let acc = aEvent.accessible;
+    if (liveRegion) {
+      // Hide for text is handled by the EVENT_TEXT_REMOVED handler.
+      if (acc.role === Roles.TEXT_LEAF) {
+        return;
+      }
+      this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
+    } else {
+      let vc = Utils.getVirtualCursor(this.contentScope.content.document);
+      if (vc.position &&
+        (Utils.getState(vc.position).contains(States.DEFUNCT) ||
+          Utils.isInSubtree(vc.position, acc))) {
+        let position = aEvent.targetPrevSibling || aEvent.targetParent;
+        if (!position) {
+          try {
+            position = acc.previousSibling;
+          } catch (x) {
+            // Accessible is unattached from the accessible tree.
+            position = acc.parent;
+          }
+        }
+        this.contentControl.autoMove(position,
+          { moveToFocused: true, delay: 500 });
+      }
+    }
+  },
+
   _handleText: function _handleText(aEvent, aLiveRegion, aIsPolite) {
     let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
     let isInserted = event.isInserted;
     let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
 
     let text = '';
     try {
       text = txtIface.getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
--- a/accessible/jsat/Utils.jsm
+++ b/accessible/jsat/Utils.jsm
@@ -348,20 +348,26 @@ this.Utils = { // jshint ignore:line
         Logger.debug('Failed to get parent:', x);
         acc = null;
       }
     }
 
     return false;
   },
 
+  isHidden: function isHidden(aAccessible) {
+    // Need to account for aria-hidden, so can't just check for INVISIBLE
+    // state.
+    let hidden = Utils.getAttributes(aAccessible).hidden;
+    return hidden && hidden === 'true';
+  },
+
   inHiddenSubtree: function inHiddenSubtree(aAccessible) {
     for (let acc=aAccessible; acc; acc=acc.parent) {
-      let hidden = Utils.getAttributes(acc).hidden;
-      if (hidden && JSON.parse(hidden)) {
+      if (this.isHidden(acc)) {
         return true;
       }
     }
     return false;
   },
 
   isAliveAndVisible: function isAliveAndVisible(aAccessible, aIsOnScreen) {
     if (!aAccessible) {
@@ -784,19 +790,17 @@ PivotContext.prototype = {
       return;
     }
     let child = aAccessible.firstChild;
     while (child) {
       let include;
       if (this._includeInvisible) {
         include = true;
       } else {
-        // Need to account for aria-hidden, so can't just check for INVISIBLE
-        // state.
-        include = Utils.getAttributes(child).hidden !== 'true';
+        include = !Utils.isHidden(child);
       }
       if (include) {
         if (aPreorder) {
           yield child;
           [yield node for (node of this._traverse(child, aPreorder, aStop))]; // jshint ignore:line
         } else {
           [yield node for (node of this._traverse(child, aPreorder, aStop))]; // jshint ignore:line
           yield child;
--- a/accessible/tests/mochitest/jsat/doc_content_integration.html
+++ b/accessible/tests/mochitest/jsat/doc_content_integration.html
@@ -19,16 +19,32 @@
     function showAlert() {
       document.getElementById('alert').hidden = false;
     }
 
     function hideAlert() {
       document.getElementById('alert').hidden = true;
     }
 
+    function ariaShowBack() {
+      document.getElementById('back').setAttribute('aria-hidden', false);
+    }
+
+    function ariaHideBack() {
+      document.getElementById('back').setAttribute('aria-hidden', true);
+    }
+
+    function ariaShowIframe() {
+      document.getElementById('iframe').setAttribute('aria-hidden', false);
+    }
+
+    function ariaHideIframe() {
+      document.getElementById('iframe').setAttribute('aria-hidden', true);
+    }
+
   </script>
   <style>
     #windows {
       position: relative;
       width: 320px;
       height: 480px;
     }
 
@@ -53,16 +69,17 @@
     }
 
   </style>
 
 </head>
 <body>
   <div>Phone status bar</div>
   <div id="windows">
+    <button id="back">Back</button>
     <div id="appframe"></div>
     <div role="dialog" id="alert" hidden>
       <h1>This is an alert!</h1>
       <p>Do you agree?</p>
       <button onclick="hideAlert()">Yes</button>
       <button onclick="hideAlert()">No</button>
     </div>
   </div>
--- a/accessible/tests/mochitest/jsat/test_content_integration.html
+++ b/accessible/tests/mochitest/jsat/test_content_integration.html
@@ -19,26 +19,30 @@
   <script type="application/javascript" src="../states.js"></script>
   <script type="application/javascript" src="../layout.js"></script>
   <script type="application/javascript" src="jsatcommon.js"></script>
 
   <script type="application/javascript">
     function doTest() {
       var doc = currentTabDocument();
       var iframe = doc.createElement('iframe');
+      iframe.id = 'iframe';
       iframe.mozbrowser = true;
       iframe.addEventListener('mozbrowserloadend', function () {
       var contentTest = new AccessFuContentTest(
         [
           // Simple traversal forward
           [ContentMessages.simpleMoveNext, {
             speak: ['Phone status bar', 'Traversal Rule test document'],
             focused: 'body'
           }],
           [ContentMessages.simpleMoveNext, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
+          [ContentMessages.simpleMoveNext, {
             speak: ['wow', {'string': 'headingLevel', 'args': [1]} ,'such app'],
             focused: 'iframe'
           }],
           [ContentMessages.simpleMoveNext, {
             speak: ['many option', {'string': 'stateNotChecked'},
               {'string': 'checkbutton'}, {'string': 'listStart'},
               {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}]
           }],
@@ -75,19 +79,25 @@
           // uncheck checkbox
           [ContentMessages.activateCurrent(), {
             speak: [{'string': 'uncheckAction'}]
           }],
           [ContentMessages.simpleMovePrevious, {
             speak: ['wow', {'string': 'headingLevel', 'args': [1]}]
           }],
           [ContentMessages.simpleMovePrevious, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
+          [ContentMessages.simpleMovePrevious, {
             speak: ['Phone status bar']
           }],
 
+          [ContentMessages.simpleMoveNext, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
           // Moving to the absolute last item from an embedded document
           // fails. Bug 972035.
           [ContentMessages.simpleMoveNext, {
             speak: ['wow', {'string': 'headingLevel', 'args': [1]}, 'such app']
           }],
           // Move from an inner frame to the last element in the parent doc
           [ContentMessages.simpleMoveLast, {
             speak: ['Home', {'string': 'pushbutton'}],
@@ -97,16 +107,19 @@
           [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
 
           // Moving to the absolute first item from an embedded document
           // fails. Bug 972035.
           [ContentMessages.simpleMoveNext, {
             speak: ['Phone status bar', 'Traversal Rule test document']
           }],
           [ContentMessages.simpleMoveNext, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
+          [ContentMessages.simpleMoveNext, {
             speak: ['wow', {'string': 'headingLevel', 'args': [1]}, 'such app']
           }],
           [ContentMessages.simpleMoveNext, {
             speak: ['many option', {'string': 'stateNotChecked'},
               {'string': 'checkbutton'}, {'string': 'listStart'},
               {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}]
           }],
           [ContentMessages.simpleMoveFirst, {
@@ -130,30 +143,81 @@
           [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
 
           // Set focus on element outside of embedded frame while
           // cursor is in frame
           [ContentMessages.simpleMoveNext, {
             speak: ['Phone status bar', 'Traversal Rule test document']
           }],
           [ContentMessages.simpleMoveNext, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
+          [ContentMessages.simpleMoveNext, {
             speak: ['wow', {'string': 'headingLevel', 'args': [1]}, 'such app']
           }],
           [ContentMessages.focusSelector('button#home', false), {
             speak: ['Home', {'string': 'pushbutton'}]
           }],
 
           // Blur button and reset cursor
           [ContentMessages.focusSelector('button#home', true), null],
           [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
 
           // XXX: Set focus on iframe itself.
           // XXX: Set focus on element in iframe when cursor is outside of it.
           // XXX: Set focus on element in iframe when cursor is in iframe.
 
+          // aria-hidden element that the virtual cursor is positioned on
+          [ContentMessages.simpleMoveNext, {
+            speak: ['Phone status bar', 'Traversal Rule test document']
+          }],
+          [ContentMessages.simpleMoveNext, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
+          [doc.defaultView.ariaHideBack, {
+            speak: ["wow", {"string": "headingLevel","args": [1]}, "such app"],
+          }],
+          // Changing aria-hidden attribute twice and making sure that the event
+          // is fired only once when the actual change happens.
+          [doc.defaultView.ariaHideBack],
+          [doc.defaultView.ariaShowBack],
+          [ContentMessages.simpleMovePrevious, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
+          [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+          // aria-hidden on the iframe that has the vc.
+          [ContentMessages.simpleMoveNext, {
+            speak: ['Phone status bar', 'Traversal Rule test document']
+          }],
+          [ContentMessages.simpleMoveNext, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
+          [ContentMessages.simpleMoveNext, {
+            speak: ['wow', {'string': 'headingLevel', 'args': [1]}, 'such app']
+          }],
+          [doc.defaultView.ariaHideIframe, {
+            speak: ['Home', {'string': 'pushbutton'}]
+          }],
+          [doc.defaultView.ariaShowIframe],
+          [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+          // aria-hidden element and auto Move
+          [ContentMessages.simpleMoveNext, {
+            speak: ['Phone status bar', 'Traversal Rule test document']
+          }],
+          [doc.defaultView.ariaHideBack],
+          [ContentMessages.focusSelector('button#back', false), {
+            // Must not speak Back button as it is aria-hidden
+            speak: ["wow", {"string": "headingLevel","args": [1]}, "such app"],
+          }],
+          [doc.defaultView.ariaShowBack],
+          [ContentMessages.focusSelector('button#back', true), null],
+          [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
           // Open dialog in outer doc, while cursor is also in outer doc
           [ContentMessages.simpleMoveNext, {
             speak: ['Phone status bar', 'Traversal Rule test document']
           }],
           [doc.defaultView.showAlert, {
             speak: ['This is an alert!',
                     {'string': 'headingLevel', 'args': [1]},
                     {'string': 'dialog'}]
@@ -165,16 +229,19 @@
 
           [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
 
           // Open dialog in outer doc, while cursor is in inner frame
           [ContentMessages.simpleMoveNext, {
             speak: ['Phone status bar', 'Traversal Rule test document']
           }],
           [ContentMessages.simpleMoveNext, {
+            speak: ["Back", {"string": "pushbutton"}]
+          }],
+          [ContentMessages.simpleMoveNext, {
             speak: ['wow', {'string': 'headingLevel', 'args': [1]}, 'such app']
           }],
           [doc.defaultView.showAlert, {
             speak: ['This is an alert!',
                     {'string': 'headingLevel', 'args': [1]},
                     {'string': 'dialog'}]
           }],
 
--- a/accessible/tests/mochitest/jsat/test_live_regions.html
+++ b/accessible/tests/mochitest/jsat/test_live_regions.html
@@ -28,16 +28,26 @@
       element.style.display = "none";
     }
 
     function show(id) {
       var element = document.getElementById(id);
       element.style.display = "block";
     }
 
+    function ariaHide(id) {
+      var element = document.getElementById(id);
+      element.setAttribute('aria-hidden', true);
+    }
+
+    function ariaShow(id) {
+      var element = document.getElementById(id);
+      element.setAttribute('aria-hidden', false);
+    }
+
     function udpate(id, text, property) {
       var element = document.getElementById(id);
       element[property] = text;
     }
 
     function updateText(id, text) {
       udpate(id, text, "textContent");
     }
@@ -55,17 +65,17 @@
         }
       },
       action: function action() {
         [hide(id) for (id of ["to_hide1", "to_hide2", "to_hide3", "to_hide4"])];
       }
     }, {
       expected: {
         "eventType": "liveregion-change",
-        "data": [{"string": "hidden"},"I will be hidden"],
+        "data": [{"string": "hidden"}, "I will be hidden"],
         "options": {
           "enqueue": true
         }
       },
       action: function action() {
         [hide(id) for (id of ["to_hide_descendant1", "to_hide_descendant2",
           "to_hide_descendant3", "to_hide_descendant4"])];
       }
@@ -92,36 +102,107 @@
         [show(id) for (id of ["to_show_descendant1", "to_show_descendant2",
           "to_show_descendant3", "to_show_descendant4"])];
       }
     }, {
       expected: {
         "eventType": "liveregion-change",
         "data": [{"string": "hidden"}, "I will be hidden"],
         "options": {
+          "enqueue": true
+        }
+      },
+      action: function action() {
+        [ariaHide(id) for (id of ["to_hide5", "to_hide6", "to_hide7",
+          "to_hide8", "to_hide9"])];
+      }
+    }, {
+      expected: {
+        "eventType": "liveregion-change",
+        "data": [{"string": "hidden"}, "I will be hidden"],
+        "options": {
+          "enqueue": true
+        }
+      },
+      action: function action() {
+        [ariaHide(id) for (id of ["to_hide_descendant5", "to_hide_descendant6",
+          "to_hide_descendant7", "to_hide_descendant8"])];
+      }
+    }, {
+      expected: {
+        "eventType": "liveregion-change",
+        "data": ["I will be shown"],
+        "options": {
+          "enqueue": true
+        }
+      },
+      action: function action() {
+        [ariaShow(id) for (id of ["to_show5", "to_show6", "to_show7",
+          "to_show8", "to_show9"])];
+      }
+    }, {
+      expected: {
+        "eventType": "liveregion-change",
+        "data": ["I will be shown"],
+        "options": {
+          "enqueue": true
+        }
+      },
+      action: function action() {
+        [ariaShow(id) for (id of ["to_show_descendant5", "to_show_descendant6",
+          "to_show_descendant7", "to_show_descendant8"])];
+      }
+    }, {
+      expected: {
+        "eventType": "liveregion-change",
+        "data": [{"string": "hidden"}, "I will be hidden"],
+        "options": {
           "enqueue": false
         }
       },
       action: function action() {
         hide("to_hide_live_assertive");
       }
     }, {
       expected: {
         "eventType": "liveregion-change",
+        "data": [{"string": "hidden"}, "I will be hidden"],
+        "options": {
+          "enqueue": false
+        }
+      },
+      action: function action() {
+        ariaHide("to_hide_live_assertive2");
+      }
+    }, {
+      expected: {
+        "eventType": "liveregion-change",
         "data": ["I will be shown"],
         "options": {
           "enqueue": false
         }
       },
       action: function action() {
         [show(id) for (id of ["to_show_live_off", "to_show_live_assertive"])];
       }
     }, {
       expected: {
         "eventType": "liveregion-change",
+        "data": ["I will be shown"],
+        "options": {
+          "enqueue": false
+        }
+      },
+      action: function action() {
+        [ariaShow(id) for (id of ["to_show_live_off2",
+          "to_show_live_assertive2"])];
+      }
+    }, {
+      expected: {
+        "eventType": "liveregion-change",
         "data": ["Text Added"],
         "options": {
           "enqueue": false
         }
       },
       action: function action() {
         updateText("text_add", "Text Added");
       }
@@ -279,54 +360,100 @@
     <div id="content" style="display: none"></div>
     <pre id="test"></pre>
 
     <p id="to_hide1">I should not be announced 1</p>
     <p id="to_hide2" aria-live="polite">I should not be announced 2</p>
     <p id="to_hide3" aria-live="assertive" aria-relevant="text">I should not be announced 3</p>
     <p id="to_hide4" aria-live="polite" aria-relevant="all">I will be hidden</p>
 
+    <p id="to_hide5" aria-hidden="true">I should not be announced 5</p>
+    <p id="to_hide6">I should not be announced 6</p>
+    <p id="to_hide7" aria-live="polite">I should not be announced 7</p>
+    <p id="to_hide8" aria-live="assertive" aria-relevant="text">I should not be announced 8</p>
+    <p id="to_hide9" aria-live="polite" aria-relevant="all">I will be hidden</p>
+
     <div>
       <p id="to_hide_descendant1">I should not be announced 1</p>
     </div>
     <div aria-live="polite">
       <p id="to_hide_descendant2">I should not be announced 2</p>
     </div>
     <div aria-live="assertive" aria-relevant="text">
       <p id="to_hide_descendant3">I should not be announced 3</p>
     </div>
     <div aria-live="polite" aria-relevant="all">
       <p id="to_hide_descendant4">I will be hidden</p>
     </div>
 
+    <div>
+      <p id="to_hide_descendant5">I should not be announced 4</p>
+    </div>
+    <div aria-live="polite">
+      <p id="to_hide_descendant6">I should not be announced 5</p>
+    </div>
+    <div aria-live="assertive" aria-relevant="text">
+      <p id="to_hide_descendant7">I should not be announced 6</p>
+    </div>
+    <div aria-live="polite" aria-relevant="all">
+      <p id="to_hide_descendant8">I will be hidden</p>
+    </div>
+
     <p id="to_show1" style="display: none">I should not be announced 1</p>
     <p id="to_show2" aria-live="assertive" aria-relevant="text" style="display: none">I should not be announced 2</p>
     <p id="to_show3" aria-live="polite" aria-relevant="removals" style="display: none">I should not be announced 3</p>
     <p id="to_show4" aria-live="polite" aria-relevant="all" style="display: none">I will be shown</p>
 
+    <p id="to_show5" aria-hidden="false">I should not be announced 5</p>
+    <p id="to_show6" aria-hidden="true">I should not be announced 6</p>
+    <p id="to_show7" aria-hidden="true" aria-live="assertive" aria-relevant="text">I should not be announced 7</p>
+    <p id="to_show8" aria-hidden="true" aria-live="polite" aria-relevant="removals">I should not be announced 8</p>
+    <p id="to_show9" aria-hidden="true" aria-live="polite" aria-relevant="all">I will be shown</p>
+
     <div>
       <p id="to_show_descendant1" style="display: none">I should not be announced 1</p>
     </div>
     <div aria-live="polite" aria-relevant="removals">
       <p id="to_show_descendant2" style="display: none">I should not be announced 2</p>
     </div>
     <div aria-live="assertive" aria-relevant="text">
       <p id="to_show_descendant3" style="display: none">I should not be announced 3</p>
     </div>
     <div aria-live="polite" aria-relevant="all">
       <p id="to_show_descendant4" style="display: none">I will be shown</p>
     </div>
 
+    <div>
+      <p id="to_show_descendant5" aria-hidden="true">I should not be announced 5</p>
+    </div>
+    <div aria-live="polite" aria-relevant="removals">
+      <p id="to_show_descendant6" aria-hidden="true">I should not be announced 6</p>
+    </div>
+    <div aria-live="assertive" aria-relevant="text">
+      <p id="to_show_descendant7" aria-hidden="true">I should not be announced 7</p>
+    </div>
+    <div aria-live="polite" aria-relevant="all">
+      <p id="to_show_descendant8" aria-hidden="true">I will be shown</p>
+    </div>
+
     <div aria-live="assertive" aria-relevant="all">
       <p id="to_hide_live_assertive">I will be hidden</p>
     </div>
+
+    <div aria-live="assertive" aria-relevant="all">
+      <p id="to_hide_live_assertive2">I will be hidden</p>
+    </div>
     <p id="to_show_live_assertive" aria-live="assertive" style="display: none">I will be shown</p>
 
     <p id="to_show_live_off" aria-live="off" style="display: none">I will not be shown</p>
 
+    <p id="to_show_live_assertive2" aria-live="assertive" aria-hidden="true">I will be shown</p>
+
+    <p id="to_show_live_off2" aria-live="off" aria-hidden="true">I will not be shown</p>
+
     <div id="to_replace_region" aria-live="polite" aria-relevant="all">
       <p id="to_replace">I am replaced</p>
     </div>
 
     <p id="to_replace_text" aria-live="assertive" aria-relevant="text">I am going to be replaced</p>
 
     <p id="text_add" aria-live="assertive" aria-relevant="text"></p>
     <p id="text_remove" aria-live="polite" aria-relevant="all">Text Removed</p>