Bug 489549 - Buttons of HTML5 audio and video element control set have no accessible names, r=marcoz, davidb, dolske, enndeaking, sr=neil

--HG--
rename : accessible/tests/mochitest/nsIAccessible_actions.js => accessible/tests/mochitest/actions.js
This commit is contained in:
Alexander Surkov 2009-05-11 09:32:09 +08:00
Родитель e019bae7fa
Коммит 906cbaed37
12 изменённых файлов: 324 добавлений и 201 удалений

Просмотреть файл

@ -50,13 +50,13 @@ _TEST_FILES =\
moz.png \
$(topsrcdir)/content/media/video/test/bug461281.ogg \
longdesc_src.html \
actions.js \
attributes.js \
common.js \
events.js \
grid.js \
layout.js \
namerules.xml \
nsIAccessible_actions.js \
nsIAccessible_name.css \
nsIAccessible_name.js \
nsIAccessible_name.xbl \

Просмотреть файл

@ -0,0 +1,127 @@
////////////////////////////////////////////////////////////////////////////////
// Event constants
const MOUSEDOWN_EVENT = 1;
const MOUSEUP_EVENT = 2;
const CLICK_EVENT = 4;
const COMMAND_EVENT = 8;
const CLICK_EVENTS = MOUSEDOWN_EVENT | MOUSEUP_EVENT | CLICK_EVENT;
const ALL_EVENTS = CLICK_EVENTS | COMMAND_EVENT;
////////////////////////////////////////////////////////////////////////////////
// Public functions
/**
* Test default accessible actions.
*
* Action tester interface is:
*
* var actionObj = {
* // identifier of accessible
* get ID() {},
*
* // name of default action
* get actionName() {},
*
* // event constant defined above
* get events() {}
*
* // [optional] an array of invoker's checker objects (see eventQueue
* // constructor events.js)
* get eventSeq() {}
* };
*
*
* @param aArray [in] an array of action cheker objects
*/
function testActions(aArray)
{
gActionsQueue = new eventQueue();
for (var idx = 0; idx < aArray.length; idx++) {
var actionObj = aArray[idx];
var accOrElmOrID = actionObj.ID;
var actionName = actionObj.actionName;
var events = actionObj.events;
var eventSeq = new Array();
if (events) {
var elm = getNode(accOrElmOrID);
//alert(elm.QueryInterface(Components.interfaces.nsIDOMNode));
if (events & MOUSEDOWN_EVENT)
eventSeq.push(new checkerOfActionInvoker("mousedown", elm));
if (events & MOUSEUP_EVENT)
eventSeq.push(new checkerOfActionInvoker("mouseup", elm));
if (events & CLICK_EVENT)
eventSeq.push(new checkerOfActionInvoker("click", elm, actionObj));
if (events & COMMAND_EVENT)
eventSeq.push(new checkerOfActionInvoker("command", elm));
}
if (actionObj.eventSeq)
eventSeq = eventSeq.concat(actionObj.eventSeq);
var invoker = new actionInvoker(accOrElmOrID, actionName, eventSeq);
gActionsQueue.push(invoker);
}
gActionsQueue.invoke();
}
////////////////////////////////////////////////////////////////////////////////
// Private
var gActionsQueue = null;
function actionInvoker(aAccOrElmOrId, aActionName, aEventSeq)
{
this.invoke = function actionInvoker_invoke()
{
var acc = getAccessible(aAccOrElmOrId);
if (!acc)
return INVOKER_ACTION_FAILED;
var isThereActions = acc.numActions > 0;
ok(isThereActions,
"No actions on the accessible for " + prettyName(aAccOrElmOrId));
if (!isThereActions)
return INVOKER_ACTION_FAILED;
is(acc.getActionName(0), aActionName,
"Wrong action name of the accessible for " + prettyName(aAccOrElmOrId));
try {
acc.doAction(0);
}
catch (e){
ok(false, "doAction(0) failed with: " + e.name);
return INVOKER_ACTION_FAILED;
}
}
this.eventSeq = aEventSeq;
}
function checkerOfActionInvoker(aType, aTarget, aActionObj)
{
this.type = aType;
this.target = aTarget;
this.getID = function getID()
{
return aType + " event handling";
}
this.check = function check(aEvent)
{
if (aActionObj && "check" in aActionObj)
aActionObj.check(aEvent);
}
}

Просмотреть файл

@ -121,22 +121,25 @@ function addA11yLoadEvent(aFunc)
// Get DOM node/accesible helpers
/**
* Return the DOM node.
* Return the DOM node by identifier (may be accessible, DOM node or ID).
*/
function getNode(aNodeOrID)
function getNode(aAccOrNodeOrID)
{
if (!aNodeOrID)
if (!aAccOrNodeOrID)
return null;
var node = aNodeOrID;
if (aAccOrNodeOrID instanceof nsIDOMNode)
return aAccOrNodeOrID;
if (!(aNodeOrID instanceof nsIDOMNode)) {
node = document.getElementById(aNodeOrID);
if (!node) {
ok(false, "Can't get DOM element for " + aNodeOrID);
return null;
if (aAccOrNodeOrID instanceof nsIAccessible) {
aAccOrNodeOrID.QueryInterface(nsIAccessNode);
return aAccOrNodeOrID.DOMNode;
}
node = document.getElementById(aAccOrNodeOrID);
if (!node) {
ok(false, "Can't get DOM element for " + aAccOrNodeOrID);
return null;
}
return node;
@ -343,7 +346,7 @@ function prettyName(aIdentifier)
if (aIdentifier instanceof nsIAccessible) {
var acc = getAccessible(aIdentifier, [nsIAccessNode]);
return getNodePrettyName(acc.DOMNode) + ", role: " +
roleToString(acc.finalRole);
roleToString(acc.role);
}
if (aIdentifier instanceof nsIDOMNode)
@ -369,8 +372,12 @@ addLoadEvent(initialize);
function getNodePrettyName(aNode)
{
try {
if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE && aNode.hasAttribute("id"))
return " '" + aNode.getAttribute("id") + "' ";
return " '" + aNode.localName + " node' ";
} catch (e) {
return "no node info";
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// Constants
const EVENT_DOM_DESTROY = nsIAccessibleEvent.EVENT_DOM_DESTROY;
const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
@ -79,6 +80,12 @@ function unregisterA11yEventListener(aEventType, aEventHandler)
////////////////////////////////////////////////////////////////////////////////
// Event queue
/**
* Return value of invoke method of invoker object. Indicates invoker was unable
* to prepare action.
*/
const INVOKER_ACTION_FAILED = 1;
/**
* Creates event queue for the given event type. The queue consists of invoker
* objects, each of them generates the event of the event type. When queue is
@ -89,7 +96,8 @@ function unregisterA11yEventListener(aEventType, aEventHandler)
* Invoker interface is:
*
* var invoker = {
* // Generates accessible event or event sequence.
* // Generates accessible event or event sequence. If returns
* // INVOKER_ACTION_FAILED constant then stop tests.
* invoke: function(){},
*
* // [optional] Invoker's check of handled event for correctness.
@ -219,7 +227,11 @@ function eventQueue(aEventType)
this.setEventHandler(invoker);
invoker.invoke();
if (invoker.invoke() == INVOKER_ACTION_FAILED) {
// Invoker failed to prepare action, fail and finish tests.
this.processNextInvoker();
return;
}
if (invoker.doNotExpectEvents) {
// Check in timeout invoker didn't fire registered events.
@ -260,6 +272,12 @@ function eventQueue(aEventType)
if (gA11yEventDumpID) { // debug stuff
if (aEvent instanceof nsIDOMEvent) {
var info = "Event type: " + aEvent.type;
info += ". Target: " + prettyName(aEvent.originalTarget);
dumpInfoToDOM(info);
}
var currType = this.getEventType(idx);
var currTarget = this.getEventTarget(idx);
@ -368,8 +386,10 @@ function eventQueue(aEventType)
return target1 == target2;
}
// If original target isn't suitable then extend interface to support target
// (original target is used in test_elm_media.html).
var target2 = (aEvent instanceof nsIDOMEvent) ?
aEvent.target : aEvent.DOMNode;
aEvent.originalTarget : aEvent.DOMNode;
return target1 == target2;
}
@ -497,12 +517,20 @@ function removeA11yEventListener(aEventType, aEventHandler)
return true;
}
function dumpInfoToDOM(aInfo)
/**
* Dumps message to DOM.
*
* @param aInfo [in] the message to dump
* @param aDumpNode [in, optional] host DOM node for dumped message, if ommited
* then global variable gA11yEventDumpID is used
*/
function dumpInfoToDOM(aInfo, aDumpNode)
{
if (!gA11yEventDumpID)
var dumpID = gA11yEventDumpID ? gA11yEventDumpID : aDumpNode;
if (!dumpID)
return;
var dumpElm = document.getElementById(gA11yEventDumpID);
var dumpElm = document.getElementById(dumpID);
var containerTagName = document instanceof nsIDOMHTMLDocument ?
"div" : "description";

Просмотреть файл

@ -1,135 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// Event constants
const MOUSEDOWN_EVENT = 1;
const MOUSEUP_EVENT = 2;
const CLICK_EVENT = 4;
const COMMAND_EVENT = 8;
const CLICK_EVENTS = MOUSEDOWN_EVENT | MOUSEUP_EVENT | CLICK_EVENT;
const ALL_EVENTS = CLICK_EVENTS | COMMAND_EVENT;
////////////////////////////////////////////////////////////////////////////////
// Public functions
function testActions(aArray, aIndex)
{
if (!aIndex)
aIndex = 0;
if (aIndex == aArray.length) {
SimpleTest.finish();
return;
}
var accOrElmOrID = aArray[aIndex].ID;
var actionName = aArray[aIndex].actionName;
var events = aArray[aIndex].events;
var elmObj = {};
var acc = getAccessible(accOrElmOrID, null, elmObj);
var elm = elmObj.value;
var isThereActions = acc.numActions > 0;
ok(isThereActions,
"No actions on the accessible for " + accOrElmOrID);
if (!isThereActions) {
SimpleTest.finish();
return; // Stop test.
}
is(acc.getActionName(0), actionName,
"Wrong action name of the accessible for " + accOrElmOrID);
gEventHandler.initialize(accOrElmOrID, elm, events);
try {
acc.doAction(0);
}
catch (e){
ok(false, "doAction(0) failed with: " + e.name);
SimpleTest.finish();
return;
}
window.setTimeout(
function()
{
gEventHandler.checkEvents();
testActions(aArray, aIndex + 1);
},
200
);
}
////////////////////////////////////////////////////////////////////////////////
// Private
var gEventHandler =
{
initialize: function(aID, aElm, aExpectedEvents)
{
this.ID = aID;
this.element = aElm;
this.mExpectedEvents = aExpectedEvents;
this.mFiredEvents = 0;
if (this.mExpectedEvents & MOUSEDOWN_EVENT)
aElm.addEventListener("mousedown", this, false);
if (this.mExpectedEvents & MOUSEUP_EVENT)
aElm.addEventListener("mouseup", this, false);
if (this.mExpectedEvents & CLICK_EVENT)
aElm.addEventListener("click", this, false);
if (this.mExpectedEvents & COMMAND_EVENT)
aElm.addEventListener("command", this, false);
},
checkEvents: function()
{
if (this.mExpectedEvents & MOUSEDOWN_EVENT) {
ok(this.mFiredEvents & MOUSEDOWN_EVENT,
"mousedown hasn't been fired for " + this.ID);
this.element.removeEventListener("mousedown", this, false);
}
if (this.mExpectedEvents & MOUSEUP_EVENT) {
ok(this.mFiredEvents & MOUSEUP_EVENT,
"mouseup hasn't been fired for " + this.ID);
this.element.removeEventListener("mouseup", this, false);
}
if (this.mExpectedEvents & CLICK_EVENT) {
ok(this.mFiredEvents & CLICK_EVENT,
"click hasn't been fired for " + this.ID);
this.element.removeEventListener("click", this, false);
}
if (this.mExpectedEvents & COMMAND_EVENT) {
ok(this.mFiredEvents & COMMAND_EVENT,
"command hasn't been fired for " + this.ID);
this.element.removeEventListener("command", this, false);
}
},
ID: "",
element: null,
handleEvent : function(aEvent)
{
if (aEvent.type == "mousedown")
this.mFiredEvents |= MOUSEDOWN_EVENT;
else if(aEvent.type == "mouseup")
this.mFiredEvents |= MOUSEUP_EVENT;
else if(aEvent.type == "click")
this.mFiredEvents |= CLICK_EVENT;
else if(aEvent.type == "command")
this.mFiredEvents |= COMMAND_EVENT;
},
mExpectedEvents: 0,
mFiredEvents: 0
};

Просмотреть файл

@ -17,7 +17,9 @@
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/common.js" />
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/nsIAccessible_actions.js" />
src="chrome://mochikit/content/a11y/accessible/events.js" />
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/actions.js" />
<script type="application/javascript">
<![CDATA[
@ -48,13 +50,14 @@
ID: "buttonmenu",
actionName: "press",
events: CLICK_EVENTS
},
}/*, // XXX: bug 490288
{
ID: "buttonmenu_item",
actionName: "click",
events: CLICK_EVENTS
}
}*/
];
testActions(actionsArray);
}
@ -63,6 +66,7 @@
]]>
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=410765"
@ -76,6 +80,7 @@
</pre>
</body>
<vbox flex="1">
<menubar>
<menu label="menu" id="menu">
<menupopup>
@ -97,5 +102,7 @@
<menuitem label="item1"/>
</menupopup>
</button>
</vbox>
</hbox>
</window>

Просмотреть файл

@ -14,7 +14,9 @@
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/nsIAccessible_actions.js"></script>
src="chrome://mochikit/content/a11y/accessible/events.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/actions.js"></script>
<script type="application/javascript">
function doTest()

Просмотреть файл

@ -14,7 +14,9 @@
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/nsIAccessible_actions.js"></script>
src="chrome://mochikit/content/a11y/accessible/events.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/actions.js"></script>
<script type="application/javascript">
function doTest()

Просмотреть файл

@ -14,17 +14,46 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=483573
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/events.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/actions.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/role.js"></script>
<script type="application/javascript">
function focusChecker(aAcc, aName)
{
this.type = EVENT_FOCUS;
this.target = aAcc;
this.getID = function focusChecker_getID()
{
return "focus handling";
},
this.check = function focusChecker_check(aEvent)
{
SimpleTest.executeSoon(
function()
{
is(aEvent.accessible.name, aName,
"Wrong name of " + prettyName(aEvent.accessible) + " on focus");
}
);
}
}
function doTest()
{
//////////////////////////////////////////////////////////////////////////
// test the accessible tree
var accTree = {
role: ROLE_GROUPING,
children: [
{ // start/stop button
role: ROLE_PUSHBUTTON
role: ROLE_PUSHBUTTON,
name: "Play"
},
{ // buffer bar
role: ROLE_PROGRESSBAR
@ -39,17 +68,40 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=483573
role: ROLE_LABEL
},
{ // mute button
role: ROLE_PUSHBUTTON
role: ROLE_PUSHBUTTON,
name: "Mute"
}
]
};
testAccessibleTree("audio", accTree);
SimpleTest.finish();
//////////////////////////////////////////////////////////////////////////
// test actions of audio controls
var audioElm = getAccessible("audio");
var playBtn = audioElm.firstChild;
var muteBtn = audioElm.lastChild;
var actions = [
{
ID: playBtn,
actionName: "press",
events: CLICK_EVENTS,
eventSeq: [ new focusChecker(playBtn, "Pause") ],
},
{
ID: muteBtn,
actionName: "press",
events: CLICK_EVENTS,
eventSeq: [ new focusChecker(muteBtn, "Unmute") ],
}
];
testActions(actions); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(doTest);
addA11yLoadEvent(doTest);
</script>
</head>
<body>
@ -63,6 +115,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=483573
</pre>
<audio id="audio" src="chrome://mochikit/content/a11y/accessible/bug461281.ogg"
autoplay="true" controls="true">
controls="true"></audio>
</body>
</html>

Просмотреть файл

@ -1,5 +1,10 @@
<?xml version="1.0"?>
<!DOCTYPE bindings [
<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
%videocontrolsDTD;
]>
<bindings id="videoContolBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
@ -144,7 +149,9 @@
<vbox>
<spacer flex="1"/>
<hbox class="controlBar" hidden="true">
<button class="playButton"/>
<button class="playButton"
playlabel="&playButton.playLabel;"
pauselabel="&playButton.pauseLabel;"/>
<stack class="scrubberStack" flex="1">
<box class="backgroundBar"/>
<progressmeter class="bufferBar"/>
@ -154,7 +161,9 @@
<vbox class="durationBox">
<label class="durationLabel"/>
</vbox>
<button class="muteButton"/>
<button class="muteButton"
mutelabel="&muteButton.muteLabel;"
unmutelabel="&muteButton.unmuteLabel;"/>
<stack class="volumeStack" hidden="true">
<box class="volumeBackgroundBar"/>
<scale class="volumeControl" orient="vertical" dir="reverse" movetoclick="true"/>
@ -295,8 +304,8 @@
this.randomID = Math.random();
this.videocontrols.randomID = this.randomID;
this.playButton.setAttribute("paused", this.video.paused);
this.muteButton.setAttribute("muted", this.video.muted);
this.setPlayButtonState(this.video.paused);
this.setMuteButtonState(this.video.muted);
var volume = this.video.muted ? 0 : Math.round(this.video.volume * 100);
this.volumeControl.value = volume;
@ -368,15 +377,15 @@
switch (aEvent.type) {
case "play":
this.playButton.setAttribute("paused", false);
this.setPlayButtonState(false);
break;
case "pause":
case "ended":
this.playButton.setAttribute("paused", true);
this.setPlayButtonState(true);
break;
case "volumechange":
var volume = this.video.muted ? 0 : Math.round(this.video.volume * 100);
this.muteButton.setAttribute("muted", this.video.muted);
this.setMuteButtonState(this.video.muted);
this.volumeControl.value = volume;
break;
case "loadedmetadata":
@ -686,6 +695,24 @@
// controlling volume.
},
setPlayButtonState: function(aPaused)
{
this.playButton.setAttribute("paused", aPaused);
var attrName = aPaused ? "playlabel" : "pauselabel";
var value = this.playButton.getAttribute(attrName);
this.playButton.setAttribute("aria-label", value);
},
setMuteButtonState: function(aMuted)
{
this.muteButton.setAttribute("muted", aMuted);
var attrName = aMuted ? "unmutelabel" : "mutelabel";
var value = this.muteButton.getAttribute(attrName);
this.muteButton.setAttribute("aria-label", value);
},
isEventWithin : function (event, parent1, parent2) {
function isDescendant (node) {
while (node) {

Просмотреть файл

@ -0,0 +1,5 @@
<!ENTITY playButton.playLabel "Play">
<!ENTITY playButton.pauseLabel "Pause">
<!ENTITY muteButton.muteLabel "Mute">
<!ENTITY muteButton.unmuteLabel "Unmute">

Просмотреть файл

@ -49,6 +49,7 @@
+ locale/@AB_CD@/global/dialog.properties (%chrome/global/dialog.properties)
+ locale/@AB_CD@/global/tree.dtd (%chrome/global/tree.dtd)
+ locale/@AB_CD@/global/textcontext.dtd (%chrome/global/textcontext.dtd)
locale/@AB_CD@/global/videocontrols.dtd (%chrome/global/videocontrols.dtd)
+ locale/@AB_CD@/global/viewSource.dtd (%chrome/global/viewSource.dtd)
+ locale/@AB_CD@/global/viewSource.properties (%chrome/global/viewSource.properties)
+ locale/@AB_CD@/global/wizard.dtd (%chrome/global/wizard.dtd)