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(-)
This commit is contained in:
Yura Zenevich 2014-08-28 09:07:30 -04:00
parent 7f2c3de468
commit 7638708930
6 changed files with 287 additions and 36 deletions

View File

@ -235,6 +235,10 @@ this.AccessFu = { // jshint ignore:line
},
_output: function _output(aPresentationData, aBrowser) {
if (!Utils.isAliveAndVisible(
Utils.AccRetrieval.getAccessibleFor(aBrowser))) {
return;
}
for (let presenter of aPresentationData) {
if (!presenter) {
continue;

View File

@ -225,43 +225,30 @@ this.EventManager.prototype = {
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:
@ -306,6 +293,51 @@ this.EventManager.prototype = {
}
},
_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;

View File

@ -353,10 +353,16 @@ this.Utils = { // jshint ignore:line
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;
}
}
@ -789,9 +795,7 @@ PivotContext.prototype = {
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) {

View File

@ -24,6 +24,22 @@
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 {
@ -58,6 +74,7 @@
<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>

View File

@ -24,6 +24,7 @@
function doTest() {
var doc = currentTabDocument();
var iframe = doc.createElement('iframe');
iframe.id = 'iframe';
iframe.mozbrowser = true;
iframe.addEventListener('mozbrowserloadend', function () {
var contentTest = new AccessFuContentTest(
@ -33,6 +34,9 @@
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'
@ -79,10 +83,16 @@
[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, {
@ -101,6 +111,9 @@
[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']
}],
@ -134,6 +147,9 @@
[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']
}],
@ -149,6 +165,54 @@
// 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']
@ -169,6 +233,9 @@
[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']
}],

View File

@ -33,6 +33,16 @@
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;
@ -60,7 +70,7 @@
}, {
expected: {
"eventType": "liveregion-change",
"data": [{"string": "hidden"},"I will be hidden"],
"data": [{"string": "hidden"}, "I will be hidden"],
"options": {
"enqueue": true
}
@ -92,6 +102,54 @@
[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",
@ -103,6 +161,17 @@
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",
@ -114,6 +183,18 @@
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",
@ -284,6 +365,12 @@
<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>
@ -297,11 +384,30 @@
<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>
@ -315,13 +421,34 @@
<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>