mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 12:37:37 +00:00
20729605eb
--HG-- rename : accessible/tests/mochitest/test_bug368835.xul => accessible/tests/mochitest/test_events_tree.xul
514 lines
14 KiB
JavaScript
514 lines
14 KiB
JavaScript
////////////////////////////////////////////////////////////////////////////////
|
|
// Constants
|
|
|
|
const EVENT_DOM_DESTROY = nsIAccessibleEvent.EVENT_DOM_DESTROY;
|
|
const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
|
|
const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// General
|
|
|
|
/**
|
|
* Set up this variable to dump events into DOM.
|
|
*/
|
|
var gA11yEventDumpID = "";
|
|
|
|
/**
|
|
* Executes the function when requested event is handled.
|
|
*
|
|
* @param aEventType [in] event type
|
|
* @param aTarget [in] event target
|
|
* @param aFunc [in] function to call when event is handled
|
|
* @param aContext [in, optional] object in which context the function is
|
|
* called
|
|
* @param aArg1 [in, optional] argument passed into the function
|
|
* @param aArg2 [in, optional] argument passed into the function
|
|
*/
|
|
function waitForEvent(aEventType, aTarget, aFunc, aContext, aArg1, aArg2)
|
|
{
|
|
var handler = {
|
|
handleEvent: function handleEvent(aEvent) {
|
|
if (!aTarget || aTarget == aEvent.DOMNode) {
|
|
unregisterA11yEventListener(aEventType, this);
|
|
|
|
window.setTimeout(
|
|
function ()
|
|
{
|
|
aFunc.call(aContext, aArg1, aArg2);
|
|
},
|
|
0
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
registerA11yEventListener(aEventType, handler);
|
|
}
|
|
|
|
/**
|
|
* Register accessibility event listener.
|
|
*
|
|
* @param aEventType the accessible event type (see nsIAccessibleEvent for
|
|
* available constants).
|
|
* @param aEventHandler event listener object, when accessible event of the
|
|
* given type is handled then 'handleEvent' method of
|
|
* this object is invoked with nsIAccessibleEvent object
|
|
* as the first argument.
|
|
*/
|
|
function registerA11yEventListener(aEventType, aEventHandler)
|
|
{
|
|
listenA11yEvents(true);
|
|
|
|
gA11yEventApplicantsCount++;
|
|
addA11yEventListener(aEventType, aEventHandler);
|
|
}
|
|
|
|
/**
|
|
* Unregister accessibility event listener. Must be called for every registered
|
|
* event listener (see registerA11yEventListener() function) when the listener
|
|
* is not needed.
|
|
*/
|
|
function unregisterA11yEventListener(aEventType, aEventHandler)
|
|
{
|
|
removeA11yEventListener(aEventType, aEventHandler);
|
|
|
|
gA11yEventApplicantsCount--;
|
|
listenA11yEvents(false);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Event queue
|
|
|
|
/**
|
|
* 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
|
|
* started then every invoker object is asked to generate event after timeout.
|
|
* When event is caught then current invoker object is asked to check wether
|
|
* event was handled correctly.
|
|
*
|
|
* Invoker interface is:
|
|
*
|
|
* var invoker = {
|
|
* // Generates accessible event or event sequence.
|
|
* invoke: function(){},
|
|
*
|
|
* // [optional] Invoker's check of handled event for correctness.
|
|
* check: function(aEvent){},
|
|
*
|
|
* // [optional] Is called when event of registered type is handled.
|
|
* debugCheck: function(aEvent){},
|
|
*
|
|
* // [ignored if 'eventSeq' is defined] DOM node event is generated for
|
|
* // (used in the case when invoker expects single event).
|
|
* DOMNode getter: function() {},
|
|
*
|
|
* // Array of items defining events expected (or not expected, see
|
|
* // 'doNotExpectEvents' property) on invoker's action.
|
|
* //
|
|
* // Every array item should be either
|
|
* // 1) an array consisted from two elements, the first element is DOM or
|
|
* // a11y event type, second element is event target (DOM node or
|
|
* // accessible).
|
|
* //
|
|
* // 2) object (invoker's checker object) like
|
|
* // var checker = {
|
|
* // type getter: function() {}, // DOM or a11y event type
|
|
* // target getter: function() {}, // DOM node or accessible
|
|
* // check: function(aEvent) {},
|
|
* // getID: function() {}
|
|
* // };
|
|
* eventSeq getter() {},
|
|
*
|
|
* // [optional, used together with 'eventSeq'] Boolean indicates if events
|
|
* // specified by 'eventSeq' property shouldn't be triggerd by invoker.
|
|
* doNotExpectEvents getter() {},
|
|
*
|
|
* // The ID of invoker.
|
|
* getID: function(){} // returns invoker ID
|
|
* };
|
|
*
|
|
* @param aEventType [optional] the default event type (isn't used if
|
|
* invoker defines eventSeq property).
|
|
*/
|
|
function eventQueue(aEventType)
|
|
{
|
|
// public
|
|
|
|
/**
|
|
* Add invoker object into queue.
|
|
*/
|
|
this.push = function eventQueue_push(aEventInvoker)
|
|
{
|
|
this.mInvokers.push(aEventInvoker);
|
|
}
|
|
|
|
/**
|
|
* Start the queue processing.
|
|
*/
|
|
this.invoke = function eventQueue_invoke()
|
|
{
|
|
listenA11yEvents(true);
|
|
gA11yEventApplicantsCount++;
|
|
|
|
// XXX: Intermittent test_events_caretmove.html fails withouth timeout,
|
|
// see bug 474952.
|
|
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500,
|
|
this);
|
|
}
|
|
|
|
// private
|
|
|
|
/**
|
|
* Process next invoker.
|
|
*/
|
|
this.processNextInvoker = function eventQueue_processNextInvoker()
|
|
{
|
|
// Finish rocessing of the current invoker.
|
|
var testFailed = false;
|
|
|
|
var invoker = this.getInvoker();
|
|
if (invoker) {
|
|
if (invoker.wasCaught) {
|
|
for (var idx = 0; idx < invoker.wasCaught.length; idx++) {
|
|
var id = this.getEventID(idx);
|
|
var type = this.getEventType(idx);
|
|
var typeStr = (typeof type == "string") ?
|
|
type : gAccRetrieval.getStringEventType(type);
|
|
|
|
var msg = "test with ID = '" + id + "' failed. ";
|
|
if (invoker.doNotExpectEvents) {
|
|
var wasCaught = invoker.wasCaught[idx];
|
|
if (!testFailed)
|
|
testFailed = wasCaught;
|
|
|
|
ok(!wasCaught,
|
|
msg + "There is unexpected " + typeStr + " event.");
|
|
|
|
} else {
|
|
var wasCaught = invoker.wasCaught[idx];
|
|
if (!testFailed)
|
|
testFailed = !wasCaught;
|
|
|
|
ok(wasCaught,
|
|
msg + "No " + typeStr + " event.");
|
|
}
|
|
}
|
|
} else {
|
|
testFailed = true;
|
|
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
|
|
var id = this.getEventID(idx);
|
|
ok(false,
|
|
"test with ID = '" + id + "' failed. No events were registered.");
|
|
}
|
|
}
|
|
}
|
|
|
|
this.clearEventHandler();
|
|
|
|
// Check if need to stop the test.
|
|
if (testFailed || this.mIndex == this.mInvokers.length - 1) {
|
|
gA11yEventApplicantsCount--;
|
|
listenA11yEvents(false);
|
|
|
|
SimpleTest.finish();
|
|
return;
|
|
}
|
|
|
|
// Start processing of next invoker.
|
|
invoker = this.getNextInvoker();
|
|
|
|
this.setEventHandler(invoker);
|
|
|
|
invoker.invoke();
|
|
|
|
if (invoker.doNotExpectEvents) {
|
|
// Check in timeout invoker didn't fire registered events.
|
|
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500,
|
|
this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle events for the current invoker.
|
|
*/
|
|
this.handleEvent = function eventQueue_handleEvent(aEvent)
|
|
{
|
|
var invoker = this.getInvoker();
|
|
if (!invoker) // skip events before test was started
|
|
return;
|
|
|
|
if (!this.mEventSeq) {
|
|
// Bad invoker object, error will be reported before processing of next
|
|
// invoker in the queue.
|
|
this.processNextInvoker();
|
|
return;
|
|
}
|
|
|
|
if ("debugCheck" in invoker)
|
|
invoker.debugCheck(aEvent);
|
|
|
|
if (invoker.doNotExpectEvents) {
|
|
// Search through event sequence to ensure it doesn't contain handled
|
|
// event.
|
|
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
|
|
if (this.compareEvents(idx, aEvent))
|
|
invoker.wasCaught[idx] = true;
|
|
}
|
|
} else {
|
|
// We wait for events in order specified by eventSeq variable.
|
|
var idx = this.mEventSeqIdx + 1;
|
|
|
|
if (gA11yEventDumpID) { // debug stuff
|
|
|
|
var currType = this.getEventType(idx);
|
|
var currTarget = this.getEventTarget(idx);
|
|
|
|
var info = "Event queue processing. Expected event type: ";
|
|
info += (typeof currType == "string") ?
|
|
currType : eventTypeToString(currType);
|
|
info += ". Target: " + prettyName(currTarget);
|
|
|
|
dumpInfoToDOM(info);
|
|
}
|
|
|
|
if (this.compareEvents(idx, aEvent)) {
|
|
this.checkEvent(idx, aEvent);
|
|
invoker.wasCaught[idx] = true;
|
|
|
|
if (idx == this.mEventSeq.length - 1) {
|
|
// We need delay to avoid events coalesce from different invokers.
|
|
var queue = this;
|
|
SimpleTest.executeSoon(function() { queue.processNextInvoker(); });
|
|
return;
|
|
}
|
|
|
|
this.mEventSeqIdx = idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helpers
|
|
this.getInvoker = function eventQueue_getInvoker()
|
|
{
|
|
return this.mInvokers[this.mIndex];
|
|
}
|
|
|
|
this.getNextInvoker = function eventQueue_getNextInvoker()
|
|
{
|
|
return this.mInvokers[++this.mIndex];
|
|
}
|
|
|
|
this.setEventHandler = function eventQueue_setEventHandler(aInvoker)
|
|
{
|
|
this.mEventSeq = ("eventSeq" in aInvoker) ?
|
|
aInvoker.eventSeq : [[this.mDefEventType, aInvoker.DOMNode]];
|
|
this.mEventSeqIdx = -1;
|
|
|
|
if (this.mEventSeq) {
|
|
aInvoker.wasCaught = new Array(this.mEventSeq.length);
|
|
|
|
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
|
|
var eventType = this.getEventType(idx);
|
|
if (typeof eventType == "string") // DOM event
|
|
document.addEventListener(eventType, this, true);
|
|
else // A11y event
|
|
addA11yEventListener(eventType, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.clearEventHandler = function eventQueue_clearEventHandler()
|
|
{
|
|
if (this.mEventSeq) {
|
|
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
|
|
var eventType = this.getEventType(idx);
|
|
if (typeof eventType == "string") // DOM event
|
|
document.removeEventListener(eventType, this, true);
|
|
else // A11y event
|
|
removeA11yEventListener(eventType, this);
|
|
}
|
|
|
|
this.mEventSeq = null;
|
|
}
|
|
}
|
|
|
|
this.getEventType = function eventQueue_getEventType(aIdx)
|
|
{
|
|
var eventItem = this.mEventSeq[aIdx];
|
|
if ("type" in eventItem)
|
|
return eventItem.type;
|
|
|
|
return eventItem[0];
|
|
}
|
|
|
|
this.getEventTarget = function eventQueue_getEventTarget(aIdx)
|
|
{
|
|
var eventItem = this.mEventSeq[aIdx];
|
|
if ("target" in eventItem)
|
|
return eventItem.target;
|
|
|
|
return eventItem[1];
|
|
}
|
|
|
|
this.compareEvents = function eventQueue_compareEvents(aIdx, aEvent)
|
|
{
|
|
var eventType1 = this.getEventType(aIdx);
|
|
|
|
var eventType2 = (aEvent instanceof nsIDOMEvent) ?
|
|
aEvent.type : aEvent.eventType;
|
|
|
|
if (eventType1 != eventType2)
|
|
return false;
|
|
|
|
var target1 = this.getEventTarget(aIdx);
|
|
if (target1 instanceof nsIAccessible) {
|
|
var target2 = (aEvent instanceof nsIDOMEvent) ?
|
|
getAccessible(aEvent.target) : aEvent.accessible;
|
|
|
|
return target1 == target2;
|
|
}
|
|
|
|
var target2 = (aEvent instanceof nsIDOMEvent) ?
|
|
aEvent.target : aEvent.DOMNode;
|
|
return target1 == target2;
|
|
}
|
|
|
|
this.checkEvent = function eventQueue_checkEvent(aIdx, aEvent)
|
|
{
|
|
var eventItem = this.mEventSeq[aIdx];
|
|
if ("check" in eventItem)
|
|
eventItem.check(aEvent);
|
|
|
|
var invoker = this.getInvoker();
|
|
if ("check" in invoker)
|
|
invoker.check(aEvent);
|
|
}
|
|
|
|
this.getEventID = function eventQueue_getEventID(aIdx)
|
|
{
|
|
var eventItem = this.mEventSeq[aIdx];
|
|
if ("getID" in eventItem)
|
|
return eventItem.getID();
|
|
|
|
var invoker = this.getInvoker();
|
|
return invoker.getID();
|
|
}
|
|
|
|
this.mDefEventType = aEventType;
|
|
|
|
this.mInvokers = new Array();
|
|
this.mIndex = -1;
|
|
|
|
this.mEventSeq = null;
|
|
this.mEventSeqIdx = -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Private implementation details.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// General
|
|
|
|
var gObserverService = null;
|
|
|
|
var gA11yEventListeners = {};
|
|
var gA11yEventApplicantsCount = 0;
|
|
|
|
var gA11yEventObserver =
|
|
{
|
|
observe: function observe(aSubject, aTopic, aData)
|
|
{
|
|
if (aTopic != "accessible-event")
|
|
return;
|
|
|
|
var event = aSubject.QueryInterface(nsIAccessibleEvent);
|
|
var listenersArray = gA11yEventListeners[event.eventType];
|
|
|
|
if (gA11yEventDumpID) { // debug stuff
|
|
var target = event.DOMNode;
|
|
var dumpElm = document.getElementById(gA11yEventDumpID);
|
|
|
|
var parent = target;
|
|
while (parent && parent != dumpElm)
|
|
parent = parent.parentNode;
|
|
|
|
if (parent != dumpElm) {
|
|
var type = eventTypeToString(event.eventType);
|
|
var info = "Event type: " + type;
|
|
info += ". Target: " + prettyName(event.accessible);
|
|
|
|
if (listenersArray)
|
|
info += ". Listeners count: " + listenersArray.length;
|
|
|
|
dumpInfoToDOM(info);
|
|
}
|
|
}
|
|
|
|
if (!listenersArray)
|
|
return;
|
|
|
|
for (var index = 0; index < listenersArray.length; index++)
|
|
listenersArray[index].handleEvent(event);
|
|
}
|
|
};
|
|
|
|
function listenA11yEvents(aStartToListen)
|
|
{
|
|
if (aStartToListen && !gObserverService) {
|
|
gObserverService = Components.classes["@mozilla.org/observer-service;1"].
|
|
getService(nsIObserverService);
|
|
|
|
gObserverService.addObserver(gA11yEventObserver, "accessible-event",
|
|
false);
|
|
} else if (!gA11yEventApplicantsCount) {
|
|
gObserverService.removeObserver(gA11yEventObserver,
|
|
"accessible-event");
|
|
gObserverService = null;
|
|
}
|
|
}
|
|
|
|
function addA11yEventListener(aEventType, aEventHandler)
|
|
{
|
|
if (!(aEventType in gA11yEventListeners))
|
|
gA11yEventListeners[aEventType] = new Array();
|
|
|
|
gA11yEventListeners[aEventType].push(aEventHandler);
|
|
}
|
|
|
|
function removeA11yEventListener(aEventType, aEventHandler)
|
|
{
|
|
var listenersArray = gA11yEventListeners[aEventType];
|
|
if (!listenersArray)
|
|
return false;
|
|
|
|
var index = listenersArray.indexOf(aEventHandler);
|
|
if (index == -1)
|
|
return false;
|
|
|
|
listenersArray.splice(index, 1);
|
|
|
|
if (!listenersArray.length) {
|
|
gA11yEventListeners[aEventType] = null;
|
|
delete gA11yEventListeners[aEventType];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function dumpInfoToDOM(aInfo)
|
|
{
|
|
if (!gA11yEventDumpID)
|
|
return;
|
|
|
|
var dumpElm = document.getElementById(gA11yEventDumpID);
|
|
|
|
var containerTagName = document instanceof nsIDOMHTMLDocument ?
|
|
"div" : "description";
|
|
var container = document.createElement(containerTagName);
|
|
|
|
container.textContent = aInfo;
|
|
dumpElm.appendChild(container);
|
|
}
|